diff options
Diffstat (limited to 'platform/linuxbsd/x11/display_server_x11.cpp')
-rw-r--r-- | platform/linuxbsd/x11/display_server_x11.cpp | 817 |
1 files changed, 681 insertions, 136 deletions
diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index d4f82cc81e..ac4fbe2068 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -128,6 +128,7 @@ bool DisplayServerX11::has_feature(Feature p_feature) const { #endif case FEATURE_CLIPBOARD_PRIMARY: case FEATURE_TEXT_TO_SPEECH: + case FEATURE_SCREEN_CAPTURE: return true; default: { } @@ -604,7 +605,7 @@ String DisplayServerX11::_clipboard_get_impl(Atom p_source, Window x11_window, A success = true; } } else { - printf("Failed to get selection data chunk.\n"); + print_verbose("Failed to get selection data chunk."); done = true; } @@ -631,7 +632,7 @@ String DisplayServerX11::_clipboard_get_impl(Atom p_source, Window x11_window, A if (result == Success) { ret.parse_utf8((const char *)data); } else { - printf("Failed to get selection data.\n"); + print_verbose("Failed to get selection data."); } if (data) { @@ -752,23 +753,56 @@ int DisplayServerX11::get_screen_count() const { } int DisplayServerX11::get_primary_screen() const { - return XDefaultScreen(x11_display); + int event_base, error_base; + if (XineramaQueryExtension(x11_display, &event_base, &error_base)) { + return 0; + } else { + return XDefaultScreen(x11_display); + } } -Rect2i DisplayServerX11::_screen_get_rect(int p_screen) const { - Rect2i rect(0, 0, 0, 0); +int DisplayServerX11::get_keyboard_focus_screen() const { + int count = get_screen_count(); + if (count < 2) { + // Early exit with single monitor. + return 0; + } - switch (p_screen) { - case SCREEN_PRIMARY: { - p_screen = get_primary_screen(); - } break; - case SCREEN_OF_MAIN_WINDOW: { - p_screen = window_get_current_screen(MAIN_WINDOW_ID); - } break; - default: - break; + Window focus = 0; + int revert_to = 0; + + XGetInputFocus(x11_display, &focus, &revert_to); + if (focus) { + Window focus_child = 0; + int x = 0, y = 0; + XTranslateCoordinates(x11_display, focus, DefaultRootWindow(x11_display), 0, 0, &x, &y, &focus_child); + + XWindowAttributes xwa; + XGetWindowAttributes(x11_display, focus, &xwa); + Rect2i window_rect = Rect2i(x, y, xwa.width, xwa.height); + + // Find which monitor has the largest overlap with the given window. + int screen_index = 0; + int max_area = 0; + for (int i = 0; i < count; i++) { + Rect2i screen_rect = _screen_get_rect(i); + Rect2i intersection = screen_rect.intersection(window_rect); + int area = intersection.get_area(); + if (area > max_area) { + max_area = area; + screen_index = i; + } + } + return screen_index; } + return get_primary_screen(); +} + +Rect2i DisplayServerX11::_screen_get_rect(int p_screen) const { + Rect2i rect(0, 0, 0, 0); + + p_screen = _get_screen_index(p_screen); ERR_FAIL_COND_V(p_screen < 0, rect); // Using Xinerama Extension. @@ -833,17 +867,7 @@ int bad_window_error_handler(Display *display, XErrorEvent *error) { Rect2i DisplayServerX11::screen_get_usable_rect(int p_screen) const { _THREAD_SAFE_METHOD_ - switch (p_screen) { - case SCREEN_PRIMARY: { - p_screen = get_primary_screen(); - } break; - case SCREEN_OF_MAIN_WINDOW: { - p_screen = window_get_current_screen(MAIN_WINDOW_ID); - } break; - default: - break; - } - + p_screen = _get_screen_index(p_screen); int screen_count = get_screen_count(); // Check if screen is valid. @@ -1120,18 +1144,7 @@ Rect2i DisplayServerX11::screen_get_usable_rect(int p_screen) const { int DisplayServerX11::screen_get_dpi(int p_screen) const { _THREAD_SAFE_METHOD_ - switch (p_screen) { - case SCREEN_PRIMARY: { - p_screen = get_primary_screen(); - } break; - case SCREEN_OF_MAIN_WINDOW: { - p_screen = window_get_current_screen(MAIN_WINDOW_ID); - } break; - default: - break; - } - - //invalid screen? + p_screen = _get_screen_index(p_screen); ERR_FAIL_INDEX_V(p_screen, get_screen_count(), 0); //Get physical monitor Dimensions through XRandR and calculate dpi @@ -1169,21 +1182,33 @@ int DisplayServerX11::screen_get_dpi(int p_screen) const { return 96; } -float DisplayServerX11::screen_get_refresh_rate(int p_screen) const { - _THREAD_SAFE_METHOD_ +Color DisplayServerX11::screen_get_pixel(const Point2i &p_position) const { + Point2i pos = p_position; - switch (p_screen) { - case SCREEN_PRIMARY: { - p_screen = get_primary_screen(); - } break; - case SCREEN_OF_MAIN_WINDOW: { - p_screen = window_get_current_screen(MAIN_WINDOW_ID); - } break; - default: - break; + int number_of_screens = XScreenCount(x11_display); + for (int i = 0; i < number_of_screens; i++) { + Window root = XRootWindow(x11_display, i); + XWindowAttributes root_attrs; + XGetWindowAttributes(x11_display, root, &root_attrs); + if ((pos.x >= root_attrs.x) && (pos.x <= root_attrs.x + root_attrs.width) && (pos.y >= root_attrs.y) && (pos.y <= root_attrs.y + root_attrs.height)) { + XImage *image = XGetImage(x11_display, root, pos.x, pos.y, 1, 1, AllPlanes, XYPixmap); + if (image) { + XColor c; + c.pixel = XGetPixel(image, 0, 0); + XFree(image); + XQueryColor(x11_display, XDefaultColormap(x11_display, i), &c); + return Color(float(c.red) / 65535.0, float(c.green) / 65535.0, float(c.blue) / 65535.0, 1.0); + } + } } - //invalid screen? + return Color(); +} + +float DisplayServerX11::screen_get_refresh_rate(int p_screen) const { + _THREAD_SAFE_METHOD_ + + p_screen = _get_screen_index(p_screen); ERR_FAIL_INDEX_V(p_screen, get_screen_count(), SCREEN_REFRESH_RATE_FALLBACK); //Use xrandr to get screen refresh rate. @@ -1324,12 +1349,22 @@ void DisplayServerX11::delete_sub_window(WindowID p_id) { } #endif - XUnmapWindow(x11_display, wd.x11_window); - XDestroyWindow(x11_display, wd.x11_window); if (wd.xic) { XDestroyIC(wd.xic); wd.xic = nullptr; } + XDestroyWindow(x11_display, wd.x11_xim_window); +#ifdef XKB_ENABLED + if (xkb_loaded_v05p) { + if (wd.xkb_state) { + xkb_compose_state_unref(wd.xkb_state); + wd.xkb_state = nullptr; + } + } +#endif + + XUnmapWindow(x11_display, wd.x11_window); + XDestroyWindow(x11_display, wd.x11_window); windows.erase(p_id); } @@ -1536,18 +1571,7 @@ void DisplayServerX11::window_set_current_screen(int p_screen, WindowID p_window ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; - switch (p_screen) { - case SCREEN_PRIMARY: { - p_screen = get_primary_screen(); - } break; - case SCREEN_OF_MAIN_WINDOW: { - p_screen = window_get_current_screen(MAIN_WINDOW_ID); - } break; - default: - break; - } - - // Check if screen is valid + p_screen = _get_screen_index(p_screen); ERR_FAIL_INDEX(p_screen, get_screen_count()); if (window_get_current_screen(p_window) == p_screen) { @@ -2047,6 +2071,22 @@ void DisplayServerX11::_validate_mode_on_map(WindowID p_window) { } else if (wd.minimized && !_window_minimize_check(p_window)) { _set_wm_minimized(p_window, true); } + + if (wd.on_top) { + Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False); + Atom wm_above = XInternAtom(x11_display, "_NET_WM_STATE_ABOVE", False); + + XClientMessageEvent xev; + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.window = wd.x11_window; + xev.message_type = wm_state; + xev.format = 32; + xev.data.l[0] = _NET_WM_STATE_ADD; + xev.data.l[1] = wm_above; + xev.data.l[3] = 1; + XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&xev); + } } bool DisplayServerX11::window_is_maximize_allowed(WindowID p_window) const { @@ -2490,23 +2530,40 @@ void DisplayServerX11::window_set_ime_active(const bool p_active, WindowID p_win ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; - wd.im_active = p_active; - if (!wd.xic) { return; } + if (!wd.focused) { + wd.ime_active = false; + im_text = String(); + im_selection = Vector2i(); + return; + } // Block events polling while changing input focus // because it triggers some event polling internally. if (p_active) { - { - MutexLock mutex_lock(events_mutex); - XSetICFocus(wd.xic); + MutexLock mutex_lock(events_mutex); + + wd.ime_active = true; + + XMapWindow(x11_display, wd.x11_xim_window); + + XWindowAttributes xwa; + XSync(x11_display, False); + XGetWindowAttributes(x11_display, wd.x11_xim_window, &xwa); + if (xwa.map_state == IsViewable) { + XSetInputFocus(x11_display, wd.x11_xim_window, RevertToParent, CurrentTime); } - window_set_ime_position(wd.im_position, p_window); + XSetICFocus(wd.xic); } else { MutexLock mutex_lock(events_mutex); XUnsetICFocus(wd.xic); + XUnmapWindow(x11_display, wd.x11_xim_window); + wd.ime_active = false; + + im_text = String(); + im_selection = Vector2i(); } } @@ -2516,25 +2573,29 @@ void DisplayServerX11::window_set_ime_position(const Point2i &p_pos, WindowID p_ ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; - wd.im_position = p_pos; - if (!wd.xic) { return; } + if (!wd.focused) { + return; + } - ::XPoint spot; - spot.x = short(p_pos.x); - spot.y = short(p_pos.y); - XVaNestedList preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &spot, nullptr); - - { - // Block events polling during this call - // because it triggers some event polling internally. - MutexLock mutex_lock(events_mutex); - XSetICValues(wd.xic, XNPreeditAttributes, preedit_attr, nullptr); + if (wd.ime_active) { + XWindowAttributes xwa; + XSync(x11_display, False); + XGetWindowAttributes(x11_display, wd.x11_xim_window, &xwa); + if (xwa.map_state == IsViewable) { + XMoveWindow(x11_display, wd.x11_xim_window, p_pos.x, p_pos.y); + } } +} - XFree(preedit_attr); +Point2i DisplayServerX11::ime_get_selection() const { + return im_selection; +} + +String DisplayServerX11::ime_get_text() const { + return im_text; } void DisplayServerX11::cursor_set_shape(CursorShape p_shape) { @@ -2568,6 +2629,8 @@ DisplayServerX11::CursorShape DisplayServerX11::cursor_get_shape() const { void DisplayServerX11::cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { _THREAD_SAFE_METHOD_ + ERR_FAIL_INDEX(p_shape, CURSOR_MAX); + if (p_cursor.is_valid()) { HashMap<CursorShape, Vector<Variant>>::Iterator cursor_c = cursors_cache.find(p_shape); @@ -2581,16 +2644,12 @@ void DisplayServerX11::cursor_set_custom_image(const Ref<Resource> &p_cursor, Cu } Ref<Texture2D> texture = p_cursor; + ERR_FAIL_COND(!texture.is_valid()); Ref<AtlasTexture> atlas_texture = p_cursor; - Ref<Image> image; Size2i texture_size; Rect2i atlas_rect; - if (texture.is_valid()) { - image = texture->get_image(); - } - - if (!image.is_valid() && atlas_texture.is_valid()) { + if (atlas_texture.is_valid()) { texture = atlas_texture->get_atlas(); atlas_rect.size.width = texture->get_width(); @@ -2600,19 +2659,23 @@ void DisplayServerX11::cursor_set_custom_image(const Ref<Resource> &p_cursor, Cu texture_size.width = atlas_texture->get_region().size.x; texture_size.height = atlas_texture->get_region().size.y; - } else if (image.is_valid()) { + } else { texture_size.width = texture->get_width(); texture_size.height = texture->get_height(); } - ERR_FAIL_COND(!texture.is_valid()); ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0); ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256); ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height); - image = texture->get_image(); + Ref<Image> image = texture->get_image(); ERR_FAIL_COND(!image.is_valid()); + if (image->is_compressed()) { + image = image->duplicate(true); + Error err = image->decompress(); + ERR_FAIL_COND_MSG(err != OK, "Couldn't decompress VRAM-compressed custom mouse cursor image. Switch to a lossless compression mode in the Import dock."); + } // Create the cursor structure XcursorImage *cursor_image = XcursorImageCreate(texture_size.width, texture_size.height); @@ -2775,7 +2838,7 @@ Key DisplayServerX11::keyboard_get_keycode_from_physical(Key p_keycode) const { Key modifiers = p_keycode & KeyModifierMask::MODIFIER_MASK; Key keycode_no_mod = p_keycode & KeyModifierMask::CODE_MASK; unsigned int xkeycode = KeyMappingX11::get_xlibcode(keycode_no_mod); - KeySym xkeysym = XkbKeycodeToKeysym(x11_display, xkeycode, 0, 0); + KeySym xkeysym = XkbKeycodeToKeysym(x11_display, xkeycode, keyboard_get_current_layout(), 0); if (is_ascii_lower_case(xkeysym)) { xkeysym -= ('a' - 'A'); } @@ -2868,10 +2931,20 @@ BitField<MouseButtonMask> DisplayServerX11::_get_mouse_button_state(MouseButton } void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, LocalVector<XEvent> &p_events, uint32_t &p_event_index, bool p_echo) { - WindowData wd = windows[p_window]; + WindowData &wd = windows[p_window]; // X11 functions don't know what const is XKeyEvent *xkeyevent = p_event; + if (wd.ime_in_progress) { + return; + } + if (wd.ime_suppress_next_keyup) { + wd.ime_suppress_next_keyup = false; + if (xkeyevent->type != KeyPress) { + return; + } + } + // This code was pretty difficult to write. // The docs stink and every toolkit seems to // do it in a different way. @@ -2895,13 +2968,22 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, KeySym keysym_unicode = 0; // keysym used to find unicode // XLookupString returns keysyms usable as nice keycodes. - char str[256 + 1]; + char str[256] = {}; XKeyEvent xkeyevent_no_mod = *xkeyevent; xkeyevent_no_mod.state &= ~ShiftMask; xkeyevent_no_mod.state &= ~ControlMask; - XLookupString(xkeyevent, str, 256, &keysym_unicode, nullptr); + XLookupString(xkeyevent, str, 255, &keysym_unicode, nullptr); XLookupString(&xkeyevent_no_mod, nullptr, 0, &keysym_keycode, nullptr); + String keysym; +#ifdef XKB_ENABLED + if (xkb_loaded_v08p) { + KeySym keysym_unicode_nm = 0; // keysym used to find unicode + XLookupString(&xkeyevent_no_mod, nullptr, 0, &keysym_unicode_nm, nullptr); + keysym = String::chr(xkb_keysym_to_utf32(xkb_keysym_to_upper(keysym_unicode_nm))); + } +#endif + // Meanwhile, XLookupString returns keysyms useful for unicode. if (!xmbstring) { @@ -2950,13 +3032,18 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, _get_key_modifier_state(xkeyevent->state, k); k->set_window_id(p_window); - k->set_unicode(tmp[i]); - k->set_pressed(keypress); k->set_keycode(keycode); - - k->set_physical_keycode((Key)physical_keycode); + k->set_physical_keycode(physical_keycode); + if (!keysym.is_empty()) { + k->set_key_label(fix_key_label(keysym[0], keycode)); + } else { + k->set_key_label(keycode); + } + if (keypress) { + k->set_unicode(fix_unicode(tmp[i])); + } k->set_echo(false); @@ -2984,6 +3071,66 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, } } while (status == XBufferOverflow); #endif +#ifdef XKB_ENABLED + } else if (xkeyevent->type == KeyPress && wd.xkb_state && xkb_loaded_v05p) { + xkb_compose_feed_result res = xkb_compose_state_feed(wd.xkb_state, keysym_unicode); + if (res == XKB_COMPOSE_FEED_ACCEPTED) { + if (xkb_compose_state_get_status(wd.xkb_state) == XKB_COMPOSE_COMPOSED) { + bool keypress = xkeyevent->type == KeyPress; + Key keycode = KeyMappingX11::get_keycode(keysym_keycode); + Key physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode); + + if (keycode >= Key::A + 32 && keycode <= Key::Z + 32) { + keycode -= 'a' - 'A'; + } + + char str_xkb[256] = {}; + int str_xkb_size = xkb_compose_state_get_utf8(wd.xkb_state, str_xkb, 255); + + String tmp; + tmp.parse_utf8(str_xkb, str_xkb_size); + for (int i = 0; i < tmp.length(); i++) { + Ref<InputEventKey> k; + k.instantiate(); + if (physical_keycode == Key::NONE && keycode == Key::NONE && tmp[i] == 0) { + continue; + } + + if (keycode == Key::NONE) { + keycode = (Key)physical_keycode; + } + + _get_key_modifier_state(xkeyevent->state, k); + + k->set_window_id(p_window); + k->set_pressed(keypress); + + k->set_keycode(keycode); + k->set_physical_keycode(physical_keycode); + if (!keysym.is_empty()) { + k->set_key_label(fix_key_label(keysym[0], keycode)); + } else { + k->set_key_label(keycode); + } + if (keypress) { + k->set_unicode(fix_unicode(tmp[i])); + } + + k->set_echo(false); + + if (k->get_keycode() == Key::BACKTAB) { + //make it consistent across platforms. + k->set_keycode(Key::TAB); + k->set_physical_keycode(Key::TAB); + k->set_shift_pressed(true); + } + + Input::get_singleton()->parse_input_event(k); + } + return; + } + } +#endif } /* Phase 2, obtain a Godot keycode from the keysym */ @@ -2999,7 +3146,7 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, // KeyMappingX11 also translates keysym to unicode. // It does a binary search on a table to translate // most properly. - unsigned int unicode = keysym_unicode > 0 ? KeyMappingX11::get_unicode_from_keysym(keysym_unicode) : 0; + char32_t unicode = keysym_unicode > 0 ? KeyMappingX11::get_unicode_from_keysym(keysym_unicode) : 0; /* Phase 4, determine if event must be filtered */ @@ -3085,7 +3232,14 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, k->set_keycode(keycode); k->set_physical_keycode((Key)physical_keycode); - k->set_unicode(unicode); + if (!keysym.is_empty()) { + k->set_key_label(fix_key_label(keysym[0], keycode)); + } else { + k->set_key_label(keycode); + } + if (keypress) { + k->set_unicode(fix_unicode(unicode)); + } k->set_echo(p_echo); if (k->get_keycode() == Key::BACKTAB) { @@ -3179,7 +3333,7 @@ Atom DisplayServerX11::_process_selection_request_target(Atom p_target, Window p return p_property; } else { char *target_name = XGetAtomName(x11_display, p_target); - printf("Target '%s' not supported.\n", target_name); + print_verbose(vformat("Target '%s' not supported.", target_name)); if (target_name) { XFree(target_name); } @@ -3237,6 +3391,81 @@ void DisplayServerX11::_handle_selection_request_event(XSelectionRequestEvent *p XFlush(x11_display); } +int DisplayServerX11::_xim_preedit_start_callback(::XIM xim, ::XPointer client_data, + ::XPointer call_data) { + DisplayServerX11 *ds = reinterpret_cast<DisplayServerX11 *>(client_data); + WindowID window_id = ds->_get_focused_window_or_popup(); + WindowData &wd = ds->windows[window_id]; + if (wd.ime_active) { + wd.ime_in_progress = true; + } + + return -1; // Allow preedit strings of any length (no limit). +} + +void DisplayServerX11::_xim_preedit_done_callback(::XIM xim, ::XPointer client_data, + ::XPointer call_data) { + DisplayServerX11 *ds = reinterpret_cast<DisplayServerX11 *>(client_data); + WindowID window_id = ds->_get_focused_window_or_popup(); + WindowData &wd = ds->windows[window_id]; + if (wd.ime_active) { + wd.ime_in_progress = false; + wd.ime_suppress_next_keyup = true; + } +} + +void DisplayServerX11::_xim_preedit_draw_callback(::XIM xim, ::XPointer client_data, + ::XIMPreeditDrawCallbackStruct *call_data) { + DisplayServerX11 *ds = reinterpret_cast<DisplayServerX11 *>(client_data); + WindowID window_id = ds->_get_focused_window_or_popup(); + WindowData &wd = ds->windows[window_id]; + + XIMText *xim_text = call_data->text; + if (wd.ime_active) { + if (xim_text != nullptr) { + String changed_text; + if (xim_text->encoding_is_wchar) { + changed_text = String(xim_text->string.wide_char); + } else { + changed_text.parse_utf8(xim_text->string.multi_byte); + } + + if (call_data->chg_length < 0) { + ds->im_text = ds->im_text.substr(0, call_data->chg_first) + changed_text; + } else { + ds->im_text = ds->im_text.substr(0, call_data->chg_first) + changed_text + ds->im_text.substr(call_data->chg_length); + } + + // Find the start and end of the selection. + int start = 0, count = 0; + for (int i = 0; i < xim_text->length; i++) { + if (xim_text->feedback[i] & XIMReverse) { + if (count == 0) { + start = i; + count = 1; + } else { + count++; + } + } + } + if (count > 0) { + ds->im_selection = Point2i(start + call_data->chg_first, count); + } else { + ds->im_selection = Point2i(call_data->caret, 0); + } + } else { + ds->im_text = String(); + ds->im_selection = Point2i(); + } + + OS_Unix::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE); + } +} + +void DisplayServerX11::_xim_preedit_caret_callback(::XIM xim, ::XPointer client_data, + ::XIMPreeditCaretCallbackStruct *call_data) { +} + void DisplayServerX11::_xim_destroy_callback(::XIM im, ::XPointer client_data, ::XPointer call_data) { WARN_PRINT("Input method stopped"); @@ -3271,6 +3500,15 @@ void DisplayServerX11::_window_changed(XEvent *event) { wd.minimized = _window_minimize_check(window_id); wd.maximized = _window_maximize_check(window_id, "_NET_WM_STATE"); + // Readjusting the window position if the window is being reparented by the window manager for decoration + Window root, parent, *children; + unsigned int nchildren; + if (XQueryTree(x11_display, wd.x11_window, &root, &parent, &children, &nchildren) && wd.parent != parent) { + wd.parent = parent; + window_set_position(wd.position, window_id); + } + XFree(children); + { //the position in xconfigure is not useful here, obtain it manually int x = 0, y = 0; @@ -3286,10 +3524,6 @@ void DisplayServerX11::_window_changed(XEvent *event) { if (new_rect == Rect2i(wd.position, wd.size)) { return; } - if (wd.xic) { - // Not portable. - window_set_ime_position(Point2(0, 1)); - } wd.position = new_rect.position; wd.size = new_rect.size; @@ -3485,8 +3719,23 @@ Rect2i DisplayServerX11::window_get_popup_safe_rect(WindowID p_window) const { void DisplayServerX11::popup_open(WindowID p_window) { _THREAD_SAFE_METHOD_ + bool has_popup_ancestor = false; + WindowID transient_root = p_window; + while (true) { + WindowID parent = windows[transient_root].transient_parent; + if (parent == INVALID_WINDOW_ID) { + break; + } else { + transient_root = parent; + if (windows[parent].is_popup) { + has_popup_ancestor = true; + break; + } + } + } + WindowData &wd = windows[p_window]; - if (wd.is_popup) { + if (wd.is_popup || has_popup_ancestor) { // Find current popup parent, or root popup if new window is not transient. List<WindowID>::Element *C = nullptr; List<WindowID>::Element *E = popup_list.back(); @@ -3632,11 +3881,8 @@ void DisplayServerX11::process_events() { for (uint32_t event_index = 0; event_index < events.size(); ++event_index) { XEvent &event = events[event_index]; - if (ignore_events) { - XFreeEventData(x11_display, &event.xcookie); - continue; - } + bool ime_window_event = false; WindowID window_id = MAIN_WINDOW_ID; // Assign the event to the relevant window @@ -3645,6 +3891,11 @@ void DisplayServerX11::process_events() { window_id = E.key; break; } + if (event.xany.window == E.value.x11_xim_window) { + window_id = E.key; + ime_window_event = true; + break; + } } if (XGetEventData(x11_display, &event.xcookie)) { @@ -3659,6 +3910,9 @@ void DisplayServerX11::process_events() { _refresh_device_info(); } break; case XI_RawMotion: { + if (ime_window_event || ignore_events) { + break; + } XIRawEvent *raw_event = (XIRawEvent *)event_data; int device_id = raw_event->sourceid; @@ -3761,6 +4015,9 @@ void DisplayServerX11::process_events() { #ifdef TOUCH_ENABLED case XI_TouchBegin: case XI_TouchEnd: { + if (ime_window_event || ignore_events) { + break; + } bool is_begin = event_data->evtype == XI_TouchBegin; Ref<InputEventScreenTouch> st; @@ -3791,6 +4048,9 @@ void DisplayServerX11::process_events() { } break; case XI_TouchUpdate: { + if (ime_window_event || ignore_events) { + break; + } HashMap<int, Vector2>::Iterator curr_pos_elem = xi.state.find(index); if (!curr_pos_elem) { // Defensive break; @@ -3817,6 +4077,9 @@ void DisplayServerX11::process_events() { switch (event.type) { case MapNotify: { DEBUG_LOG_X11("[%u] MapNotify window=%lu (%u) \n", frame, event.xmap.window, window_id); + if (ime_window_event) { + break; + } const WindowData &wd = windows[window_id]; @@ -3837,6 +4100,9 @@ void DisplayServerX11::process_events() { case Expose: { DEBUG_LOG_X11("[%u] Expose window=%lu (%u), count='%u' \n", frame, event.xexpose.window, window_id, event.xexpose.count); + if (ime_window_event) { + break; + } windows[window_id].fullscreen = _window_fullscreen_check(window_id); @@ -3845,18 +4111,27 @@ void DisplayServerX11::process_events() { case NoExpose: { DEBUG_LOG_X11("[%u] NoExpose drawable=%lu (%u) \n", frame, event.xnoexpose.drawable, window_id); + if (ime_window_event) { + break; + } windows[window_id].minimized = true; } break; case VisibilityNotify: { DEBUG_LOG_X11("[%u] VisibilityNotify window=%lu (%u), state=%u \n", frame, event.xvisibility.window, window_id, event.xvisibility.state); + if (ime_window_event) { + break; + } windows[window_id].minimized = _window_minimize_check(window_id); } break; case LeaveNotify: { DEBUG_LOG_X11("[%u] LeaveNotify window=%lu (%u), mode='%u' \n", frame, event.xcrossing.window, window_id, event.xcrossing.mode); + if (ime_window_event) { + break; + } if (!mouse_mode_grab) { _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_EXIT); @@ -3866,6 +4141,9 @@ void DisplayServerX11::process_events() { case EnterNotify: { DEBUG_LOG_X11("[%u] EnterNotify window=%lu (%u), mode='%u' \n", frame, event.xcrossing.window, window_id, event.xcrossing.mode); + if (ime_window_event) { + break; + } if (!mouse_mode_grab) { _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_ENTER); @@ -3874,18 +4152,14 @@ void DisplayServerX11::process_events() { case FocusIn: { DEBUG_LOG_X11("[%u] FocusIn window=%lu (%u), mode='%u' \n", frame, event.xfocus.window, window_id, event.xfocus.mode); + if (ime_window_event || (event.xfocus.detail == NotifyInferior)) { + break; + } WindowData &wd = windows[window_id]; last_focused_window = window_id; wd.focused = true; - if (wd.xic) { - // Block events polling while changing input focus - // because it triggers some event polling internally. - MutexLock mutex_lock(events_mutex); - XSetICFocus(wd.xic); - } - // Keep track of focus order for overlapping windows. static unsigned int focus_order = 0; wd.focus_order = ++focus_order; @@ -3925,17 +4199,20 @@ void DisplayServerX11::process_events() { case FocusOut: { DEBUG_LOG_X11("[%u] FocusOut window=%lu (%u), mode='%u' \n", frame, event.xfocus.window, window_id, event.xfocus.mode); - WindowData &wd = windows[window_id]; - - wd.focused = false; - - if (wd.xic) { - // Block events polling while changing input focus - // because it triggers some event polling internally. + if (ime_window_event || (event.xfocus.detail == NotifyInferior)) { + break; + } + if (wd.ime_active) { MutexLock mutex_lock(events_mutex); XUnsetICFocus(wd.xic); + XUnmapWindow(x11_display, wd.x11_xim_window); + wd.ime_active = false; + im_text = String(); + im_selection = Vector2i(); + OS_Unix::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE); } + wd.focused = false; Input::get_singleton()->release_pressed_events(); _send_window_event(wd, WINDOW_EVENT_FOCUS_OUT); @@ -3971,6 +4248,9 @@ void DisplayServerX11::process_events() { case ConfigureNotify: { DEBUG_LOG_X11("[%u] ConfigureNotify window=%lu (%u), event=%lu, above=%lu, override_redirect=%u \n", frame, event.xconfigure.window, window_id, event.xconfigure.event, event.xconfigure.above, event.xconfigure.override_redirect); + if (event.xconfigure.window == windows[window_id].x11_xim_window) { + break; + } const WindowData &wd = windows[window_id]; @@ -3990,6 +4270,9 @@ void DisplayServerX11::process_events() { case ButtonPress: case ButtonRelease: { + if (ime_window_event || ignore_events) { + break; + } /* exit in case of a mouse button press */ last_timestamp = event.xbutton.time; if (mouse_mode == MOUSE_MODE_CAPTURED) { @@ -4088,6 +4371,9 @@ void DisplayServerX11::process_events() { } break; case MotionNotify: { + if (ime_window_event || ignore_events) { + break; + } // The X11 API requires filtering one-by-one through the motion // notify events, in order to figure out which event is the one // generated by warping the mouse pointer. @@ -4233,6 +4519,9 @@ void DisplayServerX11::process_events() { } break; case KeyPress: case KeyRelease: { + if (ignore_events) { + break; + } #ifdef DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED if (event.type == KeyPress) { DEBUG_LOG_X11("[%u] KeyPress window=%lu (%u), keycode=%u, time=%lu \n", frame, event.xkey.window, window_id, event.xkey.keycode, event.xkey.time); @@ -4248,7 +4537,9 @@ void DisplayServerX11::process_events() { } break; case SelectionNotify: - + if (ime_window_event) { + break; + } if (event.xselection.target == requested) { Property p = _read_property(x11_display, windows[window_id].x11_window, XInternAtom(x11_display, "PRIMARY", 0)); @@ -4283,7 +4574,9 @@ void DisplayServerX11::process_events() { break; case ClientMessage: - + if (ime_window_event) { + break; + } if ((unsigned int)event.xclient.data.l[0] == (unsigned int)wm_delete) { _send_window_event(windows[window_id], WINDOW_EVENT_CLOSE_REQUEST); } @@ -4580,7 +4873,7 @@ DisplayServer *DisplayServerX11::create_func(const String &p_rendering_driver, W vformat("Your video card drivers seem not to support the required Vulkan version.\n\n" "If possible, consider updating your video card drivers or using the OpenGL 3 driver.\n\n" "You can enable the OpenGL 3 driver by starting the engine from the\n" - "command line with the command:\n'%s --rendering-driver opengl3'\n\n" + "command line with the command:\n\n \"%s\" --rendering-driver opengl3\n\n" "If you recently updated your video card drivers, try rebooting.", executable_name), "Unable to initialize Vulkan video driver"); @@ -4603,7 +4896,9 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V #ifdef GLES3_ENABLED if (gl_manager) { - visualInfo = gl_manager->get_vi(x11_display); + Error err; + visualInfo = gl_manager->get_vi(x11_display, err); + ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't acquire visual info from display."); vi_selected = true; } #endif @@ -4665,7 +4960,7 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V // handling decorations and placement. // On the other hand, focus changes need to be handled manually when this is set. // - save_under is a hint for the WM to keep the content of windows behind to avoid repaint. - if (wd.is_popup || wd.no_focus) { + if (wd.no_focus) { windowAttributes.override_redirect = True; windowAttributes.save_under = True; valuemask |= CWOverrideRedirect | CWSaveUnder; @@ -4690,9 +4985,24 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V win_rect.position = wpos; } + // Position and size hints are set from these values before they are updated to the actual + // window size, so we need to initialize them here. + wd.position = win_rect.position; + wd.size = win_rect.size; + { wd.x11_window = XCreateWindow(x11_display, RootWindow(x11_display, visualInfo.screen), win_rect.position.x, win_rect.position.y, win_rect.size.width > 0 ? win_rect.size.width : 1, win_rect.size.height > 0 ? win_rect.size.height : 1, 0, visualInfo.depth, InputOutput, visualInfo.visual, valuemask, &windowAttributes); + wd.parent = RootWindow(x11_display, visualInfo.screen); + XSetWindowAttributes window_attributes_ime = {}; + window_attributes_ime.event_mask = KeyPressMask | KeyReleaseMask | StructureNotifyMask | ExposureMask; + + wd.x11_xim_window = XCreateWindow(x11_display, wd.x11_window, 0, 0, 1, 1, 0, CopyFromParent, InputOnly, CopyFromParent, CWEventMask, &window_attributes_ime); +#ifdef XKB_ENABLED + if (dead_tbl && xkb_loaded_v05p) { + wd.xkb_state = xkb_compose_state_new(dead_tbl, XKB_COMPOSE_STATE_NO_FLAGS); + } +#endif // Enable receiving notification when the window is initialized (MapNotify) // so the focus can be set at the right time. if (!wd.no_focus && !wd.is_popup) { @@ -4763,7 +5073,50 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V // because it triggers some event polling internally. MutexLock mutex_lock(events_mutex); - wd.xic = XCreateIC(xim, XNInputStyle, xim_style, XNClientWindow, wd.x11_window, XNFocusWindow, wd.x11_window, (char *)nullptr); + // Force on-the-spot for the over-the-spot style. + if ((xim_style & XIMPreeditPosition) != 0) { + xim_style &= ~XIMPreeditPosition; + xim_style |= XIMPreeditCallbacks; + } + if ((xim_style & XIMPreeditCallbacks) != 0) { + ::XIMCallback preedit_start_callback; + preedit_start_callback.client_data = (::XPointer)(this); + preedit_start_callback.callback = (::XIMProc)(void *)(_xim_preedit_start_callback); + + ::XIMCallback preedit_done_callback; + preedit_done_callback.client_data = (::XPointer)(this); + preedit_done_callback.callback = (::XIMProc)(_xim_preedit_done_callback); + + ::XIMCallback preedit_draw_callback; + preedit_draw_callback.client_data = (::XPointer)(this); + preedit_draw_callback.callback = (::XIMProc)(_xim_preedit_draw_callback); + + ::XIMCallback preedit_caret_callback; + preedit_caret_callback.client_data = (::XPointer)(this); + preedit_caret_callback.callback = (::XIMProc)(_xim_preedit_caret_callback); + + ::XVaNestedList preedit_attributes = XVaCreateNestedList(0, + XNPreeditStartCallback, &preedit_start_callback, + XNPreeditDoneCallback, &preedit_done_callback, + XNPreeditDrawCallback, &preedit_draw_callback, + XNPreeditCaretCallback, &preedit_caret_callback, + (char *)nullptr); + + wd.xic = XCreateIC(xim, + XNInputStyle, xim_style, + XNClientWindow, wd.x11_xim_window, + XNFocusWindow, wd.x11_xim_window, + XNPreeditAttributes, preedit_attributes, + (char *)nullptr); + XFree(preedit_attributes); + } else { + wd.xic = XCreateIC(xim, + XNInputStyle, xim_style, + XNClientWindow, wd.x11_xim_window, + XNFocusWindow, wd.x11_xim_window, + (char *)nullptr); + } + if (XGetICValues(wd.xic, XNFilterEvents, &im_event_mask, nullptr) != nullptr) { WARN_PRINT("XGetICValues couldn't obtain XNFilterEvents value"); XDestroyIC(wd.xic); @@ -4854,7 +5207,67 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V return id; } +static bool _is_xim_style_supported(const ::XIMStyle &p_style) { + const ::XIMStyle supported_preedit = XIMPreeditCallbacks | XIMPreeditPosition | XIMPreeditNothing | XIMPreeditNone; + const ::XIMStyle supported_status = XIMStatusNothing | XIMStatusNone; + + // Check preedit style is supported + if ((p_style & supported_preedit) == 0) { + return false; + } + + // Check status style is supported + if ((p_style & supported_status) == 0) { + return false; + } + + return true; +} + +static ::XIMStyle _get_best_xim_style(const ::XIMStyle &p_style_a, const ::XIMStyle &p_style_b) { + if (p_style_a == 0) { + return p_style_b; + } + if (p_style_b == 0) { + return p_style_a; + } + + const ::XIMStyle preedit = XIMPreeditArea | XIMPreeditCallbacks | XIMPreeditPosition | XIMPreeditNothing | XIMPreeditNone; + const ::XIMStyle status = XIMStatusArea | XIMStatusCallbacks | XIMStatusNothing | XIMStatusNone; + + ::XIMStyle a = p_style_a & preedit; + ::XIMStyle b = p_style_b & preedit; + if (a != b) { + // Compare preedit styles. + if ((a | b) & XIMPreeditCallbacks) { + return a == XIMPreeditCallbacks ? p_style_a : p_style_b; + } else if ((a | b) & XIMPreeditPosition) { + return a == XIMPreeditPosition ? p_style_a : p_style_b; + } else if ((a | b) & XIMPreeditArea) { + return a == XIMPreeditArea ? p_style_a : p_style_b; + } else if ((a | b) & XIMPreeditNothing) { + return a == XIMPreeditNothing ? p_style_a : p_style_b; + } + } else { + // Preedit styles are the same, compare status styles. + a = p_style_a & status; + b = p_style_b & status; + + if ((a | b) & XIMStatusCallbacks) { + return a == XIMStatusCallbacks ? p_style_a : p_style_b; + } else if ((a | b) & XIMStatusArea) { + return a == XIMStatusArea ? p_style_a : p_style_b; + } else if ((a | b) & XIMStatusNothing) { + return a == XIMStatusNothing ? p_style_a : p_style_b; + } + } + return p_style_a; +} + DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Error &r_error) { + KeyMappingX11::initialize(); + +#ifdef SOWRAP_ENABLED #ifdef DEBUG_ENABLED int dylibloader_verbose = 1; #else @@ -4869,7 +5282,19 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode r_error = ERR_UNAVAILABLE; ERR_FAIL_MSG("Can't load XCursor dynamically."); } - +#ifdef XKB_ENABLED + bool xkb_loaded = (initialize_xkbcommon(dylibloader_verbose) == 0); + xkb_loaded_v05p = xkb_loaded; + if (!xkb_context_new || !xkb_compose_table_new_from_locale || !xkb_compose_table_unref || !xkb_context_unref || !xkb_compose_state_feed || !xkb_compose_state_unref || !xkb_compose_state_new || !xkb_compose_state_get_status || !xkb_compose_state_get_utf8) { + xkb_loaded_v05p = false; + print_verbose("Detected XKBcommon library version older than 0.5, dead key composition and Unicode key labels disabled."); + } + xkb_loaded_v08p = xkb_loaded; + if (!xkb_keysym_to_utf32 || !xkb_keysym_to_upper) { + xkb_loaded_v08p = false; + print_verbose("Detected XKBcommon library version older than 0.8, Unicode key labels disabled."); + } +#endif if (initialize_xext(dylibloader_verbose) != 0) { r_error = ERR_UNAVAILABLE; ERR_FAIL_MSG("Can't load Xext dynamically."); @@ -4894,11 +5319,44 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode r_error = ERR_UNAVAILABLE; ERR_FAIL_MSG("Can't load Xinput2 dynamically."); } +#else +#ifdef XKB_ENABLED + xkb_loaded = true; +#endif +#endif + +#ifdef XKB_ENABLED + if (xkb_loaded) { + xkb_ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (xkb_ctx) { + const char *locale = getenv("LC_ALL"); + if (!locale || !*locale) { + locale = getenv("LC_CTYPE"); + } + if (!locale || !*locale) { + locale = getenv("LANG"); + } + if (!locale || !*locale) { + locale = "C"; + } + dead_tbl = xkb_compose_table_new_from_locale(xkb_ctx, locale, XKB_COMPOSE_COMPILE_NO_FLAGS); + } + } +#endif Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events); r_error = OK; + { + if (!XcursorImageCreate || !XcursorImageLoadCursor || !XcursorImageDestroy || !XcursorGetDefaultSize || !XcursorGetTheme || !XcursorLibraryLoadImage) { + // There's no API to check version, check if functions are available instead. + ERR_PRINT("Unsupported Xcursor library version."); + r_error = ERR_UNAVAILABLE; + return; + } + } + for (int i = 0; i < CURSOR_MAX; i++) { cursors[i] = None; img[i] = nullptr; @@ -4915,6 +5373,71 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode return; } + { + int version_major = 0; + int version_minor = 0; + int rc = XShapeQueryVersion(x11_display, &version_major, &version_minor); + print_verbose(vformat("Xshape %d.%d detected.", version_major, version_minor)); + if (rc != 1 || version_major < 1) { + ERR_PRINT("Unsupported Xshape library version."); + r_error = ERR_UNAVAILABLE; + XCloseDisplay(x11_display); + return; + } + } + + { + int version_major = 0; + int version_minor = 0; + int rc = XineramaQueryVersion(x11_display, &version_major, &version_minor); + print_verbose(vformat("Xinerama %d.%d detected.", version_major, version_minor)); + if (rc != 1 || version_major < 1) { + ERR_PRINT("Unsupported Xinerama library version."); + r_error = ERR_UNAVAILABLE; + XCloseDisplay(x11_display); + return; + } + } + + { + int version_major = 0; + int version_minor = 0; + int rc = XRRQueryVersion(x11_display, &version_major, &version_minor); + print_verbose(vformat("Xrandr %d.%d detected.", version_major, version_minor)); + if (rc != 1 || (version_major == 1 && version_minor < 3) || (version_major < 1)) { + ERR_PRINT("Unsupported Xrandr library version."); + r_error = ERR_UNAVAILABLE; + XCloseDisplay(x11_display); + return; + } + } + + { + int version_major = 0; + int version_minor = 0; + int rc = XRenderQueryVersion(x11_display, &version_major, &version_minor); + print_verbose(vformat("Xrender %d.%d detected.", version_major, version_minor)); + if (rc != 1 || (version_major == 0 && version_minor < 11)) { + ERR_PRINT("Unsupported Xrender library version."); + r_error = ERR_UNAVAILABLE; + XCloseDisplay(x11_display); + return; + } + } + + { + int version_major = 2; // Report 2.2 as supported by engine, but should work with 2.1 or 2.0 library as well. + int version_minor = 2; + int rc = XIQueryVersion(x11_display, &version_major, &version_minor); + print_verbose(vformat("Xinput %d.%d detected.", version_major, version_minor)); + if (rc != Success || (version_major < 2)) { + ERR_PRINT("Unsupported Xinput2 library version."); + r_error = ERR_UNAVAILABLE; + XCloseDisplay(x11_display); + return; + } + } + char *modifiers = nullptr; Bool xkb_dar = False; XAutoRepeatOn(x11_display); @@ -5001,11 +5524,13 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode if (xim_styles) { xim_style = 0L; for (int i = 0; i < xim_styles->count_styles; i++) { - if (xim_styles->supported_styles[i] == - (XIMPreeditNothing | XIMStatusNothing)) { - xim_style = xim_styles->supported_styles[i]; - break; + const ::XIMStyle &style = xim_styles->supported_styles[i]; + + if (!_is_xim_style_supported(style)) { + continue; } + + xim_style = _get_best_xim_style(xim_style, style); } XFree(xim_styles); @@ -5333,10 +5858,30 @@ DisplayServerX11::~DisplayServerX11() { XDestroyIC(wd.xic); wd.xic = nullptr; } + XDestroyWindow(x11_display, wd.x11_xim_window); +#ifdef XKB_ENABLED + if (xkb_loaded_v05p) { + if (wd.xkb_state) { + xkb_compose_state_unref(wd.xkb_state); + wd.xkb_state = nullptr; + } + } +#endif XUnmapWindow(x11_display, wd.x11_window); XDestroyWindow(x11_display, wd.x11_window); } +#ifdef XKB_ENABLED + if (xkb_loaded_v05p) { + if (dead_tbl) { + xkb_compose_table_unref(dead_tbl); + } + if (xkb_ctx) { + xkb_context_unref(xkb_ctx); + } + } +#endif + //destroy drivers #if defined(VULKAN_ENABLED) if (rendering_device_vulkan) { |