From b515414946453bbe14ac2bd4fb92764589281849 Mon Sep 17 00:00:00 2001 From: hujianwei Date: Thu, 20 Oct 2022 06:56:03 +0000 Subject: [PATCH] Improve behavior under older window managers * 12to11.man: Update documentation. * compositor.h (enum _FocusMode): New enum. (struct _RoleFuncs, struct _XdgRoleImplementationFuncs): Add `note_focus'. * seat.c (CancelResizeOperation, InterceptButtonEventForResize): Re-enter surface underneath pointer upon completion. (InterceptResizeEvent): Accept subcompositor argument. (NoticeDeviceDisabled): Unfocus surface upon the seat becoming inert. (SetFocusSurface): Call surface note_focus function. (DispatchMotion, DispatchButton): Adjust calls to InterceptResizeEvent. (HandleResizeUnmapped): Adjust calls to CancelResizeOperation. (DispatchBarrierHit): Use correct surface. (FakePointerEdge): Leave any entered surface, as button events will no longer be reported. * surface.c (XLSurfaceNoteFocus): New function. * xdg_surface.c (NoteFocus): New function. (XLGetXdgSurface): Attach new function. * xdg_toplevel.c (struct _XdgToplevel): New field focus_seat_count. (SendStates): Don't compute width and height when just sending state. (NoteFocus): Increment focus seat count. (XLGetXdgToplevel): Attach new function if the window manager does not support _NET_WM_STATE_FOCUSED. --- 12to11.man | 5 +-- compositor.h | 10 ++++++ seat.c | 87 +++++++++++++++++++++++++++++++++++++++----------- surface.c | 15 +++++++++ xdg_surface.c | 13 ++++++++ xdg_toplevel.c | 73 +++++++++++++++++++++++++++++++----------- 6 files changed, 161 insertions(+), 42 deletions(-) diff --git a/12to11.man b/12to11.man index 8bd7c44..cd35cb7 100644 --- a/12to11.man +++ b/12to11.man @@ -245,10 +245,7 @@ environment variable may help. Using this protocol translator under a window manager that does not at least support the .B _NET_WM_SYNC_REQUEST -and -.B _NET_WM_STATE -window manager hints will result in Wayland programs running -incorrectly. +window manager protocol will result in Wayland programs running badly. .PP In addition, surfaces transforms are not supported nor reported. The vast majority of clients seem not to make use of this feature, and diff --git a/compositor.h b/compositor.h index 50ebf71..0b45370 100644 --- a/compositor.h +++ b/compositor.h @@ -816,6 +816,13 @@ typedef struct _SyncRelease SyncRelease; typedef struct _State State; typedef struct _FrameCallback FrameCallback; typedef enum _RoleType RoleType; +typedef enum _FocusMode FocusMode; + +enum _FocusMode + { + SurfaceFocusIn, + SurfaceFocusOut, + }; enum _RoleType { @@ -1067,6 +1074,7 @@ struct _RoleFuncs void (*note_desync_child) (Surface *, Role *); void (*note_child_synced) (Surface *, Role *); void (*select_extra_events) (Surface *, Role *, unsigned long); + void (*note_focus) (Surface *, Role *, FocusMode); }; struct _Role @@ -1116,6 +1124,7 @@ extern void XLSurfaceMoveBy (Surface *, int, int); extern Window XLWindowFromSurface (Surface *); extern void XLUpdateSurfaceOutputs (Surface *, int, int, int, int); extern void XLSurfaceSelectExtraEvents (Surface *, unsigned long); +extern void XLSurfaceNoteFocus (Surface *, FocusMode); extern void SurfaceToWindow (Surface *, double, double, double *, double *); extern void ScaleToWindow (Surface *, double, double, double *, double *); @@ -1257,6 +1266,7 @@ struct _XdgRoleImplementationFuncs void (*post_resize) (Role *, XdgRoleImplementation *, int, int, int, int); void (*commit_inside_frame) (Role *, XdgRoleImplementation *); Bool (*is_window_mapped) (Role *, XdgRoleImplementation *); + void (*note_focus) (Role *, XdgRoleImplementation *, FocusMode); }; struct _XdgRoleImplementation diff --git a/seat.c b/seat.c index aac45bf..90923dc 100644 --- a/seat.c +++ b/seat.c @@ -1996,9 +1996,18 @@ RunResizeDoneCallbacks (Seat *seat) seat->resize_callbacks.last = &seat->resize_callbacks; } +/* Forward declarations. */ +static void TransformToSurface (Surface *, double, double, double *, double *); +static Surface *FindSurfaceUnder (Subcompositor *, double, double); +static void EnteredSurface (Seat *, Surface *, Time, double, double, Bool); + static void -CancelResizeOperation (Seat *seat, Time time) +CancelResizeOperation (Seat *seat, Time time, Subcompositor *subcompositor, + XIDeviceEvent *xev) { + Surface *dispatch; + double x, y; + /* Stop the resize operation. */ XLSurfaceCancelUnmapCallback (seat->resize_surface_callback); seat->resize_surface = NULL; @@ -2008,11 +2017,30 @@ CancelResizeOperation (Seat *seat, Time time) /* Ungrab the pointer. */ XIUngrabDevice (compositor.display, seat->master_pointer, - time); + xev->time); + + if (!subcompositor) + return; + + /* If there's no focus surface, look up the surface and enter + it. The grab should not be held at this point. */ + dispatch = FindSurfaceUnder (subcompositor, xev->event_x, xev->event_y); + + if (dispatch) + { + TransformToSurface (dispatch, xev->event_x, xev->event_y, &x, &y); + + /* Enter the surface. */ + EnteredSurface (seat, dispatch, xev->time, x, y, False); + } + + /* Otherwise, there should be no need to leave any entered surface, + since the entered surface should be NULL already. */ } static Bool -InterceptButtonEventForResize (Seat *seat, XIDeviceEvent *xev) +InterceptButtonEventForResize (Seat *seat, Subcompositor *subcompositor, + XIDeviceEvent *xev) { if (xev->type == XI_ButtonPress) return True; @@ -2020,7 +2048,7 @@ InterceptButtonEventForResize (Seat *seat, XIDeviceEvent *xev) /* If the button starting the resize has been released, cancel the resize operation. */ if (xev->detail == seat->resize_button) - CancelResizeOperation (seat, xev->time); + CancelResizeOperation (seat, xev->time, subcompositor, xev); return True; } @@ -2114,7 +2142,8 @@ InterceptMotionEventForResize (Seat *seat, XIDeviceEvent *xev) } static Bool -InterceptResizeEvent (Seat *seat, XIDeviceEvent *xev) +InterceptResizeEvent (Seat *seat, Subcompositor *subcompositor, + XIDeviceEvent *xev) { if (!seat->resize_surface) return False; @@ -2122,7 +2151,7 @@ InterceptResizeEvent (Seat *seat, XIDeviceEvent *xev) switch (xev->evtype) { case XI_ButtonRelease: - return InterceptButtonEventForResize (seat, xev); + return InterceptButtonEventForResize (seat, subcompositor, xev); case XI_Motion: return InterceptMotionEventForResize (seat, xev); @@ -2145,6 +2174,9 @@ RunDestroyListeners (Seat *seat) } } +/* Forward declaration. */ +static void SetFocusSurface (Seat *, Surface *); + static void NoticeDeviceDisabled (int deviceid) { @@ -2174,6 +2206,11 @@ NoticeDeviceDisabled (int deviceid) seat->flags |= IsInert; + /* Set the focus surface to NULL, so surfaces don't mistakenly + treat themselves as still focused. */ + + SetFocusSurface (seat, NULL); + /* Run destroy handlers. */ RunDestroyListeners (seat); @@ -2455,7 +2492,6 @@ HandleResizeComplete (Seat *seat) /* Forward declarations. */ static int GetXButton (int); -static void TransformToSurface (Surface *, double, double, double *, double *); static void SendButton (Seat *, Surface *, Time, uint32_t, uint32_t, double, double); @@ -2871,6 +2907,9 @@ SetFocusSurface (Seat *seat, Surface *focus) { SendKeyboardLeave (seat, seat->focus_surface); + /* Tell the surface it's no longer focused. */ + XLSurfaceNoteFocus (seat->focus_surface, SurfaceFocusOut); + /* Cancel any grab that may be associated with shortcut inhibition. */ XLReleaseShortcutInhibition (seat, seat->focus_surface); @@ -2905,6 +2944,9 @@ SetFocusSurface (Seat *seat, Surface *focus) SendKeyboardEnter (seat, focus); + /* Tell the surface it's now focused. */ + XLSurfaceNoteFocus (seat->focus_surface, SurfaceFocusIn); + XLPrimarySelectionHandleFocusChange (seat); if (seat->data_device) @@ -3807,7 +3849,7 @@ DispatchMotion (Subcompositor *subcompositor, XIDeviceEvent *xev) if (!seat) return; - if (InterceptResizeEvent (seat, xev)) + if (InterceptResizeEvent (seat, subcompositor, xev)) return; /* Move the drag-and-drop icon window. */ @@ -3834,8 +3876,7 @@ DispatchMotion (Subcompositor *subcompositor, XIDeviceEvent *xev) dispatch = seat->last_seen_surface; } else - dispatch = FindSurfaceUnder (subcompositor, xev->event_x, - xev->event_y); + dispatch = actual_dispatch; event_x = xev->event_x; event_y = xev->event_y; @@ -4080,7 +4121,7 @@ DispatchButton (Subcompositor *subcompositor, XIDeviceEvent *xev) if (!seat) return; - if (InterceptResizeEvent (seat, xev)) + if (InterceptResizeEvent (seat, subcompositor, xev)) return; if (seat->flags & IsDragging) @@ -4265,8 +4306,8 @@ DispatchBarrierHit (XIBarrierEvent *barrier) /* Report a barrier hit event as relative motion. */ - if (seat->focus_surface) - SendRelativeMotion (seat, seat->focus_surface, + if (seat->last_seen_surface) + SendRelativeMotion (seat, seat->last_seen_surface, barrier->dx, barrier->dy, barrier->time); @@ -4538,9 +4579,14 @@ HandleKeyboardEdge (Seat *seat, Surface *target, uint32_t serial, seat->its_press_time); /* Clear the grab immediately since it is no longer used. */ + if (seat->grab_held) CancelGrabEarly (seat); + /* Send leave to any surface that the pointer is currently within. + The position of the pointer is then restored upon entry. */ + EnteredSurface (seat, NULL, seat->its_press_time, 0, 0, False); + /* Send the message to the window manager. */ XSendEvent (compositor.display, DefaultRootWindow (compositor.display), @@ -4559,7 +4605,8 @@ HandleResizeUnmapped (void *data) Seat *seat; seat = data; - CancelResizeOperation (seat, seat->resize_time); + CancelResizeOperation (seat, seat->resize_time, + NULL, NULL); } static Bool @@ -4626,13 +4673,15 @@ FakePointerEdge (Seat *seat, Surface *target, uint32_t serial, if (state != Success) return False; - /* On the other hand, cancel focus locking, since we will not be - reporting motion events until the resize operation completes. + /* On the other hand, cancel focus locking and leave the surface, + since we will not be reporting motion events until the resize + operation completes. */ - Send leave events to any surface, since the pointer is - (logically) no longer inside. */ if (seat->grab_held) - CancelGrabEarly (seat); + { + SwapUnlockSurface (seat, NULL); + CancelGrabEarly (seat); + } /* Set the surface as the surface undergoing resize. */ seat->resize_surface = target; diff --git a/surface.c b/surface.c index a793be6..88a0676 100644 --- a/surface.c +++ b/surface.c @@ -1741,6 +1741,21 @@ XLSurfaceSelectExtraEvents (Surface *surface, unsigned long event_mask) event_mask); } +/* This function doesn't provide the seat that has now been focused + in. It is assumed that the role will perform some kind of + reference counting in order to determine how many seats currently + have it focused. */ + +void +XLSurfaceNoteFocus (Surface *surface, FocusMode focus) +{ + if (!surface->role || !surface->role->funcs.note_focus) + return; + + surface->role->funcs.note_focus (surface, surface->role, + focus); +} + /* The following functions convert from window to surface diff --git a/xdg_surface.c b/xdg_surface.c index b0be897..ec890fa 100644 --- a/xdg_surface.c +++ b/xdg_surface.c @@ -1424,6 +1424,18 @@ SelectExtraEvents (Surface *surface, Role *role, DefaultEventMask | event_mask); } +static void +NoteFocus (Surface *surface, Role *role, FocusMode focus) +{ + XdgRole *xdg_role; + + xdg_role = XdgRoleFromRole (role); + + if (xdg_role->impl && xdg_role->impl->funcs.note_focus) + xdg_role->impl->funcs.note_focus (role, xdg_role->impl, + focus); +} + void XLGetXdgSurface (struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *surface_resource) @@ -1509,6 +1521,7 @@ XLGetXdgSurface (struct wl_client *client, struct wl_resource *resource, role->role.funcs.note_desync_child = NoteDesyncChild; role->role.funcs.note_child_synced = NoteChildSynced; role->role.funcs.select_extra_events = SelectExtraEvents; + role->role.funcs.note_focus = NoteFocus; attrs.colormap = compositor.colormap; attrs.border_pixel = border_pixel; diff --git a/xdg_toplevel.c b/xdg_toplevel.c index 25f40ed..92c48d2 100644 --- a/xdg_toplevel.c +++ b/xdg_toplevel.c @@ -232,6 +232,9 @@ struct _XdgToplevel StatePendingConfigureSize is set. */ int configure_width, configure_height; + /* The number of seats that currently have this surface focused. */ + int focus_seat_count; + /* Array of states. */ struct wl_array states; @@ -571,27 +574,14 @@ WriteStates (XdgToplevel *toplevel) static void SendStates (XdgToplevel *toplevel) { - int width, height; - WriteStates (toplevel); - /* Adjust the width and height we're sending by the window - geometry. */ - if (toplevel->state & StateMissingState) - XLXdgRoleGetCurrentGeometry (toplevel->role, NULL, NULL, - &width, &height); - else - { - /* toplevel->role->surface should not be NULL here. */ - TruncateScaleToSurface (toplevel->role->surface, - toplevel->width, toplevel->height, - &width, &height); - - XLXdgRoleCalcNewWindowSize (toplevel->role, width, - height, &width, &height); - } - - SendConfigure (toplevel, width, height); + /* When SendStates is called, it means the width and height of the + surface did not change. weston-terminal and some other clients + that implement resize increments themselves will keep growing + their toplevels if width and height are specified here, so simply + send 0, 0 to make those clients decide their own size. */ + SendConfigure (toplevel, 0, 0); /* Mark the state has having been calculated if some state transition has occured. */ @@ -2148,6 +2138,46 @@ ReplyToPing (XEvent *event) | SubstructureNotifyMask), ©); } +static void +NoteFocus (Role *role, XdgRoleImplementation *impl, FocusMode mode) +{ + XdgToplevel *toplevel; + int old_focus; + + toplevel = ToplevelFromRoleImpl (impl); + old_focus = toplevel->focus_seat_count; + + /* Increase or decrease the number of seats that currently have this + surface under input focus. */ + switch (mode) + { + case SurfaceFocusIn: + toplevel->focus_seat_count++; + break; + + case SurfaceFocusOut: + toplevel->focus_seat_count + = MAX (toplevel->focus_seat_count - 1, 0); + break; + } + + /* Now, change the toplevel state accordingly. */ + if (old_focus && !toplevel->focus_seat_count) + { + /* The surface should no longer be activated. */ + toplevel->toplevel_state.activated = False; + WriteStates (toplevel); + SendStates (toplevel); + } + else + { + /* The surface should now be activated. */ + toplevel->toplevel_state.activated = True; + WriteStates (toplevel); + SendStates (toplevel); + } +} + static const struct xdg_toplevel_interface xdg_toplevel_impl = { .destroy = Destroy, @@ -2210,6 +2240,11 @@ XLGetXdgToplevel (struct wl_client *client, struct wl_resource *resource, toplevel->impl.funcs.commit_inside_frame = CommitInsideFrame; toplevel->impl.funcs.is_window_mapped = IsWindowMapped; + if (!XLWmSupportsHint (_NET_WM_STATE_FOCUSED)) + /* If _NET_WM_STATE_FOCUSED is unsupported, fall back to utilizing + focus in and focus out events to determine the focus state. */ + toplevel->impl.funcs.note_focus = NoteFocus; + /* Set up the sentinel node for the list of unmap callbacks. */ toplevel->unmap_callbacks.next = &toplevel->unmap_callbacks; toplevel->unmap_callbacks.last = &toplevel->unmap_callbacks;