diff --git a/include/wlr/xwayland/xwayland.h b/include/wlr/xwayland/xwayland.h index bf778c4cc..c02f45e3e 100644 --- a/include/wlr/xwayland/xwayland.h +++ b/include/wlr/xwayland/xwayland.h @@ -278,6 +278,18 @@ void wlr_xwayland_set_seat(struct wlr_xwayland *xwayland, struct wlr_xwayland_surface *wlr_xwayland_surface_try_from_wlr_surface( struct wlr_surface *surface); +/** + * Offer focus by sending WM_TAKE_FOCUS to a client window supporting it. + * The client may accept or ignore the offer. If it accepts, the surface will + * emit the focus_in signal notifying the compositor that it has received focus. + * + * This is a more compatible method of giving focus to windows using the + * Globally Active input model (see wlr_xwayland_icccm_input_model()) than + * calling wlr_xwayland_surface_activate() unconditionally, since there is no + * reliable way to know in advance whether these windows want to be focused. + */ +void wlr_xwayland_surface_offer_focus(struct wlr_xwayland_surface *xsurface); + void wlr_xwayland_surface_ping(struct wlr_xwayland_surface *surface); /** Metric to guess if an OR window should "receive" focus diff --git a/include/xwayland/xwm.h b/include/xwayland/xwm.h index 6259dd9bb..762fd4428 100644 --- a/include/xwayland/xwm.h +++ b/include/xwayland/xwm.h @@ -113,6 +113,7 @@ struct wlr_xwm { struct wlr_xwm_selection dnd_selection; struct wlr_xwayland_surface *focus_surface; + struct wlr_xwayland_surface *offered_focus; // Surfaces in creation order struct wl_list surfaces; // wlr_xwayland_surface.link diff --git a/xwayland/xwm.c b/xwayland/xwm.c index 4480aeb36..2ec053e4f 100644 --- a/xwayland/xwm.c +++ b/xwayland/xwm.c @@ -395,12 +395,17 @@ static void xwm_set_focused_window(struct wlr_xwm *xwm, struct wlr_xwayland_surface *xsurface) { struct wlr_xwayland_surface *unfocus_surface = xwm->focus_surface; - if (xwm->focus_surface == xsurface || - (xsurface && xsurface->override_redirect)) { + if (xsurface && xsurface->override_redirect) { return; } xwm->focus_surface = xsurface; + // cancel any pending focus offer + xwm->offered_focus = xsurface; + + if (xsurface == unfocus_surface) { + return; + } if (unfocus_surface) { xsurface_set_net_wm_state(unfocus_surface); @@ -414,13 +419,34 @@ static void xwm_set_focused_window(struct wlr_xwm *xwm, } } +void wlr_xwayland_surface_offer_focus(struct wlr_xwayland_surface *xsurface) { + if (!xsurface || xsurface->override_redirect) { + return; + } + + struct wlr_xwm *xwm = xsurface->xwm; + if (!xwm_atoms_contains(xwm, xsurface->protocols, + xsurface->protocols_len, WM_TAKE_FOCUS)) { + return; + } + + xwm->offered_focus = xsurface; + + xcb_client_message_data_t message_data = { 0 }; + message_data.data32[0] = xwm->atoms[WM_TAKE_FOCUS]; + message_data.data32[1] = XCB_TIME_CURRENT_TIME; + xwm_send_wm_message(xsurface, &message_data, XCB_EVENT_MASK_NO_EVENT); + + xcb_flush(xwm->xcb_conn); +} + static void xwm_surface_activate(struct wlr_xwm *xwm, struct wlr_xwayland_surface *xsurface) { if (xsurface && xsurface->override_redirect) { return; } - if (xsurface != xwm->focus_surface) { + if (xsurface != xwm->focus_surface && xsurface != xwm->offered_focus) { xwm_focus_window(xwm, xsurface); } @@ -503,6 +529,9 @@ static void xwayland_surface_destroy(struct wlr_xwayland_surface *xsurface) { if (xsurface == xsurface->xwm->focus_surface) { xwm_surface_activate(xsurface->xwm, NULL); } + if (xsurface == xsurface->xwm->offered_focus) { + xsurface->xwm->offered_focus = NULL; + } wl_list_remove(&xsurface->link); wl_list_remove(&xsurface->parent_link); @@ -1623,7 +1652,8 @@ static void xwm_handle_focus_in(struct wlr_xwm *xwm, // Allow focus changes between surfaces belonging to the same // application. Steam for example relies on this: // https://github.com/swaywm/sway/issues/1865 - if (xsurface && xwm->focus_surface && xsurface->pid == xwm->focus_surface->pid) { + if (xsurface && ((xwm->focus_surface && xsurface->pid == xwm->focus_surface->pid) || + (xwm->offered_focus && xsurface->pid == xwm->offered_focus->pid))) { xwm_set_focused_window(xwm, xsurface); wl_signal_emit_mutable(&xsurface->events.focus_in, NULL); } else {