diff options
Diffstat (limited to 'platform/linuxbsd/display_server_x11.cpp')
-rw-r--r-- | platform/linuxbsd/display_server_x11.cpp | 344 |
1 files changed, 250 insertions, 94 deletions
diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp index a0aced26ab..fe9e253cc9 100644 --- a/platform/linuxbsd/display_server_x11.cpp +++ b/platform/linuxbsd/display_server_x11.cpp @@ -83,6 +83,13 @@ #define VALUATOR_TILTX 3 #define VALUATOR_TILTY 4 +//#define DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED +#ifdef DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED +#define DEBUG_LOG_X11(...) printf(__VA_ARGS__) +#else +#define DEBUG_LOG_X11(...) +#endif + static const double abs_resolution_mult = 10000.0; static const double abs_resolution_range_mult = 10.0; @@ -681,6 +688,14 @@ DisplayServer::WindowID DisplayServerX11::create_sub_window(WindowMode p_mode, u return id; } +void DisplayServerX11::show_window(WindowID p_id) { + _THREAD_SAFE_METHOD_ + + WindowData &wd = windows[p_id]; + + XMapWindow(x11_display, wd.x11_window); +} + void DisplayServerX11::delete_sub_window(WindowID p_id) { _THREAD_SAFE_METHOD_ @@ -689,6 +704,8 @@ void DisplayServerX11::delete_sub_window(WindowID p_id) { WindowData &wd = windows[p_id]; + DEBUG_LOG_X11("delete_sub_window: %lu (%u) \n", wd.x11_window, p_id); + while (wd.transient_children.size()) { window_set_transient(wd.transient_children.front()->get(), INVALID_WINDOW_ID); } @@ -725,15 +742,31 @@ ObjectID DisplayServerX11::window_get_attached_instance_id(WindowID p_window) co } DisplayServerX11::WindowID DisplayServerX11::get_window_at_screen_position(const Point2i &p_position) const { -#warning This is an incorrect implementation, if windows overlap, it should return the topmost visible one or none if occluded by a foreign window - + WindowID found_window = INVALID_WINDOW_ID; + WindowID parent_window = INVALID_WINDOW_ID; + unsigned int focus_order = 0; for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - Rect2i win_rect = Rect2i(window_get_position(E->key()), window_get_size(E->key())); + const WindowData &wd = E->get(); + + // Discard windows with no focus. + if (wd.focus_order == 0) { + continue; + } + + // Find topmost window which contains the given position. + WindowID window_id = E->key(); + Rect2i win_rect = Rect2i(window_get_position(window_id), window_get_size(window_id)); if (win_rect.has_point(p_position)) { - return E->key(); + // For siblings, pick the window which was focused last. + if ((parent_window != wd.transient_parent) || (wd.focus_order > focus_order)) { + found_window = window_id; + parent_window = wd.transient_parent; + focus_order = wd.focus_order; + } } } - return INVALID_WINDOW_ID; + + return found_window; } void DisplayServerX11::window_set_title(const String &p_title, WindowID p_window) { @@ -842,24 +875,34 @@ void DisplayServerX11::window_set_transient(WindowID p_window, WindowID p_parent ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd_window = windows[p_window]; - ERR_FAIL_COND(wd_window.transient_parent == p_parent); + WindowID prev_parent = wd_window.transient_parent; + ERR_FAIL_COND(prev_parent == p_parent); ERR_FAIL_COND_MSG(wd_window.on_top, "Windows with the 'on top' can't become transient."); if (p_parent == INVALID_WINDOW_ID) { //remove transient - ERR_FAIL_COND(wd_window.transient_parent == INVALID_WINDOW_ID); - ERR_FAIL_COND(!windows.has(wd_window.transient_parent)); + ERR_FAIL_COND(prev_parent == INVALID_WINDOW_ID); + ERR_FAIL_COND(!windows.has(prev_parent)); - WindowData &wd_parent = windows[wd_window.transient_parent]; + WindowData &wd_parent = windows[prev_parent]; wd_window.transient_parent = INVALID_WINDOW_ID; wd_parent.transient_children.erase(p_window); XSetTransientForHint(x11_display, wd_window.x11_window, None); + + // Set focus to parent sub window to avoid losing all focus with nested menus. + // RevertToPointerRoot is used to make sure we don't lose all focus in case + // a subwindow and its parent are both destroyed. + if (wd_window.menu_type && !wd_window.no_focus) { + if (!wd_parent.no_focus) { + XSetInputFocus(x11_display, wd_parent.x11_window, RevertToPointerRoot, CurrentTime); + } + } } else { ERR_FAIL_COND(!windows.has(p_parent)); - ERR_FAIL_COND_MSG(wd_window.transient_parent != INVALID_WINDOW_ID, "Window already has a transient parent"); + ERR_FAIL_COND_MSG(prev_parent != INVALID_WINDOW_ID, "Window already has a transient parent"); WindowData &wd_parent = windows[p_parent]; wd_window.transient_parent = p_parent; @@ -2285,6 +2328,11 @@ void DisplayServerX11::_send_window_event(const WindowData &wd, WindowEvent p_ev void DisplayServerX11::process_events() { _THREAD_SAFE_METHOD_ +#ifdef DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED + static int frame = 0; + ++frame; +#endif + if (app_focused) { //verify that one of the windows has focus, else send focus out notification bool focus_found = false; @@ -2301,6 +2349,7 @@ void DisplayServerX11::process_events() { if (delta > 250) { //X11 can go between windows and have no focus for a while, when creating them or something else. Use this as safety to avoid unnecessary focus in/outs. if (OS::get_singleton()->get_main_loop()) { + DEBUG_LOG_X11("All focus lost, triggering NOTIFICATION_APPLICATION_FOCUS_OUT\n"); OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT); } app_focused = false; @@ -2323,6 +2372,10 @@ void DisplayServerX11::process_events() { XEvent event; XNextEvent(x11_display, &event); + if (XFilterEvent(&event, None)) { + continue; + } + WindowID window_id = MAIN_WINDOW_ID; // Assign the event to the relevant window @@ -2333,10 +2386,6 @@ void DisplayServerX11::process_events() { } } - if (XFilterEvent(&event, None)) { - continue; - } - if (XGetEventData(x11_display, &event.xcookie)) { if (event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) { XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data; @@ -2499,32 +2548,67 @@ void DisplayServerX11::process_events() { XFreeEventData(x11_display, &event.xcookie); switch (event.type) { - case Expose: + case MapNotify: { + DEBUG_LOG_X11("[%u] MapNotify window=%lu (%u) \n", frame, event.xmap.window, window_id); + + const WindowData &wd = windows[window_id]; + + // Set focus when menu window is started. + // RevertToPointerRoot is used to make sure we don't lose all focus in case + // a subwindow and its parent are both destroyed. + if (wd.menu_type && !wd.no_focus) { + XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime); + } + } break; + + case Expose: { + DEBUG_LOG_X11("[%u] Expose window=%lu (%u), count='%u' \n", frame, event.xexpose.window, window_id, event.xexpose.count); + Main::force_redraw(); - break; + } break; + + case NoExpose: { + DEBUG_LOG_X11("[%u] NoExpose drawable=%lu (%u) \n", frame, event.xnoexpose.drawable, window_id); - case NoExpose: windows[window_id].minimized = true; - break; + } break; case VisibilityNotify: { + DEBUG_LOG_X11("[%u] VisibilityNotify window=%lu (%u), state=%u \n", frame, event.xvisibility.window, window_id, event.xvisibility.state); + XVisibilityEvent *visibility = (XVisibilityEvent *)&event; windows[window_id].minimized = (visibility->state == VisibilityFullyObscured); } break; + case LeaveNotify: { + DEBUG_LOG_X11("[%u] LeaveNotify window=%lu (%u), mode='%u' \n", frame, event.xcrossing.window, window_id, event.xcrossing.mode); + if (!mouse_mode_grab) { _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_EXIT); } } break; + case EnterNotify: { + DEBUG_LOG_X11("[%u] EnterNotify window=%lu (%u), mode='%u' \n", frame, event.xcrossing.window, window_id, event.xcrossing.mode); + if (!mouse_mode_grab) { _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_ENTER); } } break; - case FocusIn: - windows[window_id].focused = true; - _send_window_event(windows[window_id], WINDOW_EVENT_FOCUS_IN); + + case FocusIn: { + DEBUG_LOG_X11("[%u] FocusIn window=%lu (%u), mode='%u' \n", frame, event.xfocus.window, window_id, event.xfocus.mode); + + WindowData &wd = windows[window_id]; + + wd.focused = true; + + // Keep track of focus order for overlapping windows. + static unsigned int focus_order = 0; + wd.focus_order = ++focus_order; + + _send_window_event(wd, WINDOW_EVENT_FOCUS_IN); if (mouse_mode_grab) { // Show and update the cursor if confined and the window regained focus. @@ -2548,8 +2632,8 @@ void DisplayServerX11::process_events() { XIGrabDevice(x11_display, xi.touch_devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &xi.touch_event_mask); }*/ #endif - if (windows[window_id].xic) { - XSetICFocus(windows[window_id].xic); + if (wd.xic) { + XSetICFocus(wd.xic); } if (!app_focused) { @@ -2558,12 +2642,17 @@ void DisplayServerX11::process_events() { } app_focused = true; } - break; + } break; + + 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; - case FocusOut: - windows[window_id].focused = false; Input::get_singleton()->release_pressed_events(); - _send_window_event(windows[window_id], WINDOW_EVENT_FOCUS_OUT); + _send_window_event(wd, WINDOW_EVENT_FOCUS_OUT); if (mouse_mode_grab) { for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { @@ -2592,14 +2681,26 @@ void DisplayServerX11::process_events() { } xi.state.clear(); #endif - if (windows[window_id].xic) { - XSetICFocus(windows[window_id].xic); + if (wd.xic) { + XSetICFocus(wd.xic); + } + } break; + + 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); + + const WindowData &wd = windows[window_id]; + + // Set focus when menu window is re-used. + // RevertToPointerRoot is used to make sure we don't lose all focus in case + // a subwindow and its parent are both destroyed. + if (wd.menu_type && !wd.no_focus) { + XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime); } - break; - case ConfigureNotify: _window_changed(&event); - break; + } break; + case ButtonPress: case ButtonRelease: { /* exit in case of a mouse button press */ @@ -2626,7 +2727,18 @@ void DisplayServerX11::process_events() { mb->set_pressed((event.type == ButtonPress)); + const WindowData &wd = windows[window_id]; + if (event.type == ButtonPress) { + DEBUG_LOG_X11("[%u] ButtonPress window=%lu (%u), button_index=%u \n", frame, event.xbutton.window, window_id, mb->get_button_index()); + + // Ensure window focus on click. + // RevertToPointerRoot is used to make sure we don't lose all focus in case + // a subwindow and its parent are both destroyed. + if (!wd.no_focus) { + XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime); + } + uint64_t diff = OS::get_singleton()->get_ticks_usec() / 1000 - last_click_ms; if (mb->get_button_index() == last_click_button_index) { @@ -2645,6 +2757,33 @@ void DisplayServerX11::process_events() { last_click_ms += diff; last_click_pos = Point2i(event.xbutton.x, event.xbutton.y); } + } else { + DEBUG_LOG_X11("[%u] ButtonRelease window=%lu (%u), button_index=%u \n", frame, event.xbutton.window, window_id, mb->get_button_index()); + + if (!wd.focused) { + // Propagate the event to the focused window, + // because it's received only on the topmost window. + // Note: This is needed for drag & drop to work between windows, + // because the engine expects events to keep being processed + // on the same window dragging started. + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + const WindowData &wd_other = E->get(); + WindowID window_id_other = E->key(); + if (wd_other.focused) { + if (window_id_other != window_id) { + int x, y; + Window child; + XTranslateCoordinates(x11_display, wd.x11_window, wd_other.x11_window, event.xbutton.x, event.xbutton.y, &x, &y, &child); + + mb->set_window_id(window_id_other); + mb->set_position(Vector2(x, y)); + mb->set_global_position(mb->get_position()); + Input::get_singleton()->accumulate_input_event(mb); + } + break; + } + } + } } Input::get_singleton()->accumulate_input_event(mb); @@ -2694,6 +2833,9 @@ void DisplayServerX11::process_events() { break; } + const WindowData &wd = windows[window_id]; + bool focused = wd.focused; + if (mouse_mode == MOUSE_MODE_CAPTURED) { if (xi.relative_motion.x == 0 && xi.relative_motion.y == 0) { break; @@ -2702,7 +2844,7 @@ void DisplayServerX11::process_events() { Point2i new_center = pos; pos = last_mouse_pos + xi.relative_motion; center = new_center; - do_mouse_warp = windows[window_id].focused; // warp the cursor if we're focused in + do_mouse_warp = focused; // warp the cursor if we're focused in } if (!last_mouse_pos_valid) { @@ -2744,14 +2886,11 @@ void DisplayServerX11::process_events() { } mm->set_tilt(xi.tilt); - // Make the absolute position integral so it doesn't look _too_ weird :) - Point2i posi(pos); - _get_key_modifier_state(event.xmotion.state, mm); mm->set_button_mask(mouse_get_button_state()); - mm->set_position(posi); - mm->set_global_position(posi); - Input::get_singleton()->set_mouse_position(posi); + mm->set_position(pos); + mm->set_global_position(pos); + Input::get_singleton()->set_mouse_position(pos); mm->set_speed(Input::get_singleton()->get_last_mouse_speed()); mm->set_relative(rel); @@ -2762,8 +2901,32 @@ void DisplayServerX11::process_events() { // Don't propagate the motion event unless we have focus // this is so that the relative motion doesn't get messed up // after we regain focus. - if (windows[window_id].focused || !mouse_mode_grab) { + if (focused) { Input::get_singleton()->accumulate_input_event(mm); + } else { + // Propagate the event to the focused window, + // because it's received only on the topmost window. + // Note: This is needed for drag & drop to work between windows, + // because the engine expects events to keep being processed + // on the same window dragging started. + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + const WindowData &wd_other = E->get(); + if (wd_other.focused) { + int x, y; + Window child; + XTranslateCoordinates(x11_display, wd.x11_window, wd_other.x11_window, event.xmotion.x, event.xmotion.y, &x, &y, &child); + + Point2i pos_focused(x, y); + + mm->set_window_id(E->key()); + mm->set_position(pos_focused); + mm->set_global_position(pos_focused); + mm->set_speed(Input::get_singleton()->get_last_mouse_speed()); + Input::get_singleton()->accumulate_input_event(mm); + + break; + } + } } } break; @@ -3140,12 +3303,37 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u unsigned long valuemask = CWBorderPixel | CWColormap | CWEventMask; - WindowID id; + WindowID id = window_id_counter++; + WindowData &wd = windows[id]; + + if ((id != MAIN_WINDOW_ID) && (p_flags & WINDOW_FLAG_BORDERLESS_BIT)) { + wd.menu_type = true; + } + + if (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) { + wd.menu_type = true; + wd.no_focus = true; + } + + // Setup for menu subwindows: + // - override_redirect forces the WM not to interfere with the window, to avoid delays due to + // 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.menu_type) { + windowAttributes.override_redirect = True; + windowAttributes.save_under = True; + valuemask |= CWOverrideRedirect | CWSaveUnder; + } + { - WindowData wd; wd.x11_window = XCreateWindow(x11_display, RootWindow(x11_display, visualInfo->screen), p_rect.position.x, p_rect.position.y, p_rect.size.width > 0 ? p_rect.size.width : 1, p_rect.size.height > 0 ? p_rect.size.height : 1, 0, visualInfo->depth, InputOutput, visualInfo->visual, valuemask, &windowAttributes); - XMapWindow(x11_display, wd.x11_window); + // Enable receiving notification when the window is initialized (MapNotify) + // so the focus can be set at the right time. + if (wd.menu_type && !wd.no_focus) { + XSelectInput(x11_display, wd.x11_window, StructureNotifyMask); + } //associate PID // make PID known to X11 @@ -3221,58 +3409,26 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u _update_context(wd); - id = window_id_counter++; - - windows[id] = wd; - - { - bool make_utility = false; - - if (p_flags & WINDOW_FLAG_BORDERLESS_BIT) { - Hints hints; - Atom property; - hints.flags = 2; - hints.decorations = 0; - property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True); - XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); - - make_utility = true; - } - if (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) { - make_utility = true; - } - - if (make_utility) { - //this one seems to disable the fade animations for regular windows - //but has the drawback that will not get focus by default, so - //we need to force it, unless no focus requested - - Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_UTILITY", False); - Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False); - - XChangeProperty(x11_display, wd.x11_window, wt_atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&type_atom, 1); - - if (!(p_flags & WINDOW_FLAG_NO_FOCUS_BIT)) { - //but as utility appears unfocused, it needs to be forcefuly focused, unless no focus requested - XEvent xev; - Atom net_active_window = XInternAtom(x11_display, "_NET_ACTIVE_WINDOW", False); + if (p_flags & WINDOW_FLAG_BORDERLESS_BIT) { + Hints hints; + Atom property; + hints.flags = 2; + hints.decorations = 0; + property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True); + XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); + } - memset(&xev, 0, sizeof(xev)); - xev.type = ClientMessage; - xev.xclient.window = wd.x11_window; - xev.xclient.message_type = net_active_window; - xev.xclient.format = 32; - xev.xclient.data.l[0] = 1; - xev.xclient.data.l[1] = CurrentTime; + if (wd.menu_type) { + // Set Utility type to disable fade animations. + Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_UTILITY", False); + Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False); - XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); - } - } else { - Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_NORMAL", False); - Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False); + XChangeProperty(x11_display, wd.x11_window, wt_atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&type_atom, 1); + } else { + Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_NORMAL", False); + Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False); - XChangeProperty(x11_display, wd.x11_window, wt_atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&type_atom, 1); - } + XChangeProperty(x11_display, wd.x11_window, wt_atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&type_atom, 1); } _update_size_hints(id); @@ -3293,8 +3449,6 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u XFree(visualInfo); } - WindowData &wd = windows[id]; - window_set_mode(p_mode, id); //sync size @@ -3316,6 +3470,7 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u if (cursors[current_cursor] != None) { XDefineCursor(x11_display, wd.x11_window, cursors[current_cursor]); } + return id; } @@ -3555,6 +3710,7 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode window_set_flag(WindowFlags(i), true, main_window); } } + show_window(main_window); //create RenderingDevice if used #if defined(VULKAN_ENABLED) |