Cursor: restore previous image on unhide
If client A has an xdg_popup open and the user moves the cursor over a surface of client B and waits for the cursor to be hidden after a timeout, the cursor will not be shown on movement until the (invisible) cursor is moved back into a surface of client A or somewhere the compositor is responsible for rendering the cursor. This is due to the (flawed) xdg popup grab interface of wlroots which prevents wlr_seat_pointer_notify_enter() from sending events to clients other than the one with the active xdg popup. Closes: https://codeberg.org/river/river/issues/1192
This commit is contained in:
		| @ -108,6 +108,19 @@ const LayoutPoint = struct { | ||||
|     ly: f64, | ||||
| }; | ||||
|  | ||||
| const Image = union(enum) { | ||||
|     /// No cursor image | ||||
|     none, | ||||
|     /// Name of the current Xcursor shape | ||||
|     xcursor: [*:0]const u8, | ||||
|     /// Cursor surface configured by a client | ||||
|     client: struct { | ||||
|         surface: *wlr.Surface, | ||||
|         hotspot_x: i32, | ||||
|         hotspot_y: i32, | ||||
|     }, | ||||
| }; | ||||
|  | ||||
| const log = std.log.scoped(.cursor); | ||||
|  | ||||
| /// Current cursor mode as well as any state needed to implement that mode | ||||
| @ -124,9 +137,8 @@ wlr_cursor: *wlr.Cursor, | ||||
|  | ||||
| /// Xcursor manager for the currently configured Xcursor theme. | ||||
| xcursor_manager: *wlr.XcursorManager, | ||||
| /// Name of the current Xcursor shape, or null if a client has configured a | ||||
| /// surface to be used as the cursor shape instead. | ||||
| xcursor_name: ?[*:0]const u8 = null, | ||||
| image: Image = .none, | ||||
| image_surface_destroy: wl.Listener(*wlr.Surface) = .init(handleImageSurfaceDestroy), | ||||
|  | ||||
| /// Number of distinct buttons currently pressed | ||||
| pressed_count: u32 = 0, | ||||
| @ -286,18 +298,40 @@ pub fn setTheme(cursor: *Cursor, theme: ?[*:0]const u8, _size: ?u32) !void { | ||||
|     cursor.xcursor_manager.destroy(); | ||||
|     cursor.xcursor_manager = xcursor_manager; | ||||
|  | ||||
|     if (cursor.xcursor_name) |name| { | ||||
|         cursor.setXcursor(name); | ||||
|     switch (cursor.image) { | ||||
|         .none, .client => {}, | ||||
|         .xcursor => |name| cursor.wlr_cursor.setXcursor(xcursor_manager, name), | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub fn setXcursor(cursor: *Cursor, name: [*:0]const u8) void { | ||||
|     cursor.wlr_cursor.setXcursor(cursor.xcursor_manager, name); | ||||
|     cursor.xcursor_name = name; | ||||
| pub fn setImage(cursor: *Cursor, image: Image) void { | ||||
|     switch (cursor.image) { | ||||
|         .none, .xcursor => {}, | ||||
|         .client => { | ||||
|             cursor.image_surface_destroy.link.remove(); | ||||
|         }, | ||||
|     } | ||||
|     cursor.image = image; | ||||
|     switch (cursor.image) { | ||||
|         .none => cursor.wlr_cursor.unsetImage(), | ||||
|         .xcursor => |name| cursor.wlr_cursor.setXcursor(cursor.xcursor_manager, name), | ||||
|         .client => |client| { | ||||
|             cursor.wlr_cursor.setSurface(client.surface, client.hotspot_x, client.hotspot_y); | ||||
|             client.surface.events.destroy.add(&cursor.image_surface_destroy); | ||||
|         }, | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn handleImageSurfaceDestroy(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void { | ||||
|     const cursor: *Cursor = @fieldParentPtr("image_surface_destroy", listener); | ||||
|     // wlroots calls wlr_cursor_unset_image() automatically | ||||
|     // when the cursor surface is destroyed. | ||||
|     cursor.image = .none; | ||||
|     cursor.image_surface_destroy.link.remove(); | ||||
| } | ||||
|  | ||||
| fn clearFocus(cursor: *Cursor) void { | ||||
|     cursor.setXcursor("default"); | ||||
|     cursor.setImage(.{ .xcursor = "default" }); | ||||
|     cursor.seat.wlr_seat.pointerNotifyClearFocus(); | ||||
| } | ||||
|  | ||||
| @ -740,8 +774,15 @@ fn handleRequestSetCursor( | ||||
|         // on the output that it's currently on and continue to do so as the | ||||
|         // cursor moves between outputs. | ||||
|         log.debug("focused client set cursor", .{}); | ||||
|         cursor.wlr_cursor.setSurface(event.surface, event.hotspot_x, event.hotspot_y); | ||||
|         cursor.xcursor_name = null; | ||||
|         if (event.surface) |surface| { | ||||
|             cursor.setImage(.{ .client = .{ | ||||
|                 .surface = surface, | ||||
|                 .hotspot_x = event.hotspot_x, | ||||
|                 .hotspot_y = event.hotspot_y, | ||||
|             } }); | ||||
|         } else { | ||||
|             cursor.setImage(.none); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -757,8 +798,6 @@ pub fn hide(cursor: *Cursor) void { | ||||
|  | ||||
|     cursor.hidden = true; | ||||
|     cursor.wlr_cursor.unsetImage(); | ||||
|     cursor.xcursor_name = null; | ||||
|     cursor.seat.wlr_seat.pointerNotifyClearFocus(); | ||||
|     cursor.hide_cursor_timer.timerUpdate(0) catch { | ||||
|         log.err("failed to update cursor hide timeout", .{}); | ||||
|     }; | ||||
| @ -770,6 +809,7 @@ pub fn unhide(cursor: *Cursor) void { | ||||
|     }; | ||||
|     if (!cursor.hidden) return; | ||||
|     cursor.hidden = false; | ||||
|     cursor.setImage(cursor.image); | ||||
|     cursor.updateState(); | ||||
| } | ||||
|  | ||||
| @ -868,7 +908,7 @@ fn computeEdges(cursor: *const Cursor, view: *const View) wlr.Edges { | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn enterMode(cursor: *Cursor, mode: Mode, view: *View, xcursor_name: [*:0]const u8) void { | ||||
| fn enterMode(cursor: *Cursor, mode: Mode, view: *View, xcursor: [*:0]const u8) void { | ||||
|     assert(cursor.mode == .passthrough or cursor.mode == .down); | ||||
|     assert(mode == .move or mode == .resize); | ||||
|  | ||||
| @ -884,7 +924,7 @@ fn enterMode(cursor: *Cursor, mode: Mode, view: *View, xcursor_name: [*:0]const | ||||
|     } | ||||
|  | ||||
|     cursor.seat.wlr_seat.pointerNotifyClearFocus(); | ||||
|     cursor.setXcursor(xcursor_name); | ||||
|     cursor.setImage(.{ .xcursor = xcursor }); | ||||
|  | ||||
|     server.root.applyPending(); | ||||
| } | ||||
|  | ||||
| @ -519,7 +519,7 @@ fn handleRequestSetCursorShape( | ||||
|         // actually has pointer focus first. | ||||
|         if (focused_client == event.seat_client) { | ||||
|             const name = wlr.CursorShapeManagerV1.shapeName(event.shape); | ||||
|             seat.cursor.setXcursor(name); | ||||
|             seat.cursor.setImage(.{ .xcursor = name }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user