Cursor: apply x/y change during resize on commit
This fixes issues with resizing clients that stick to a fixed aspect ratio during resize such as mpv.
This commit is contained in:
		
							
								
								
									
										108
									
								
								river/Cursor.zig
									
									
									
									
									
								
							
							
						
						
									
										108
									
								
								river/Cursor.zig
									
									
									
									
									
								
							| @ -131,6 +131,12 @@ const log = std.log.scoped(.cursor); | ||||
| /// Current cursor mode as well as any state needed to implement that mode | ||||
| mode: Mode = .passthrough, | ||||
|  | ||||
| /// Set to whatever the current mode is when a transaction is started. | ||||
| /// This is necessary to handle termination of move/resize modes properly | ||||
| /// since the termination is not complete until a transaction completes and | ||||
| /// View.resizeUpdatePosition() is called. | ||||
| inflight_mode: Mode = .passthrough, | ||||
|  | ||||
| seat: *Seat, | ||||
| wlr_cursor: *wlr.Cursor, | ||||
| pointer_gestures: *wlr.PointerGesturesV1, | ||||
| @ -334,7 +340,22 @@ fn handleButton(listener: *wl.Listener(*wlr.Pointer.event.Button), event: *wlr.P | ||||
|         assert(self.pressed_count > 0); | ||||
|         self.pressed_count -= 1; | ||||
|         if (self.pressed_count == 0 and self.mode != .passthrough) { | ||||
|             self.leaveMode(event); | ||||
|             log.debug("leaving {s} mode", .{@tagName(self.mode)}); | ||||
|  | ||||
|             switch (self.mode) { | ||||
|                 .passthrough => unreachable, | ||||
|                 .down => { | ||||
|                     // If we were in down mode, we need pass along the release event | ||||
|                     _ = self.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state); | ||||
|                 }, | ||||
|                 .move => {}, | ||||
|                 .resize => |data| data.view.pending.resizing = false, | ||||
|             } | ||||
|  | ||||
|             self.mode = .passthrough; | ||||
|             self.passthrough(event.time_msec); | ||||
|  | ||||
|             server.root.applyPending(); | ||||
|         } else { | ||||
|             _ = self.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state); | ||||
|         } | ||||
| @ -785,24 +806,6 @@ fn enterMode(cursor: *Self, mode: Mode, view: *View, image: Image) void { | ||||
|     server.root.applyPending(); | ||||
| } | ||||
|  | ||||
| /// Return from down/move/resize to passthrough | ||||
| fn leaveMode(self: *Self, event: *wlr.Pointer.event.Button) void { | ||||
|     log.debug("leave {s} mode", .{@tagName(self.mode)}); | ||||
|  | ||||
|     switch (self.mode) { | ||||
|         .passthrough => unreachable, | ||||
|         .down => { | ||||
|             // If we were in down mode, we need pass along the release event | ||||
|             _ = self.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state); | ||||
|         }, | ||||
|         .move => {}, | ||||
|         .resize => |resize| resize.view.pending.resizing = false, | ||||
|     } | ||||
|  | ||||
|     self.mode = .passthrough; | ||||
|     self.passthrough(event.time_msec); | ||||
| } | ||||
|  | ||||
| fn processMotion(self: *Self, device: *wlr.InputDevice, time: u32, delta_x: f64, delta_y: f64, unaccel_dx: f64, unaccel_dy: f64) void { | ||||
|     self.unhide(); | ||||
|  | ||||
| @ -852,25 +855,18 @@ fn processMotion(self: *Self, device: *wlr.InputDevice, time: u32, delta_x: f64, | ||||
|                 constraint.maybeActivate(); | ||||
|             } | ||||
|         }, | ||||
|         .move => |*data| { | ||||
|         inline .move, .resize => |*data, tag| { | ||||
|             dx += data.delta_x; | ||||
|             dy += data.delta_y; | ||||
|             data.delta_x = dx - @trunc(dx); | ||||
|             data.delta_y = dy - @trunc(dy); | ||||
|  | ||||
|             const view = data.view; | ||||
|             view.pending.move(@floatToInt(i32, dx), @floatToInt(i32, dy)); | ||||
|  | ||||
|             server.root.applyPending(); | ||||
|         }, | ||||
|         .resize => |*data| { | ||||
|             dx += data.delta_x; | ||||
|             dy += data.delta_y; | ||||
|             data.delta_x = dx - @trunc(dx); | ||||
|             data.delta_y = dy - @trunc(dy); | ||||
|  | ||||
|             { | ||||
|                 // Modify the pending box, taking constraints into account | ||||
|             if (tag == .move) { | ||||
|                 data.view.pending.move(@floatToInt(i32, dx), @floatToInt(i32, dy)); | ||||
|             } else { | ||||
|                 // Modify width/height of the pending box, taking constraints into account | ||||
|                 // The x/y coordinates of the view will be adjusted as needed in View.resizeCommit() | ||||
|                 // based on the dimensions actually committed by the client. | ||||
|                 const border_width = if (data.view.pending.ssd) server.config.border_width else 0; | ||||
|  | ||||
|                 var output_width: i32 = undefined; | ||||
| @ -881,12 +877,13 @@ fn processMotion(self: *Self, device: *wlr.InputDevice, time: u32, delta_x: f64, | ||||
|                 const box = &data.view.pending.box; | ||||
|  | ||||
|                 if (data.edges.left) { | ||||
|                     var x1 = box.x; | ||||
|                     const x2 = box.x + box.width; | ||||
|                     box.x += @floatToInt(i32, dx); | ||||
|                     box.x = math.max(box.x, border_width); | ||||
|                     box.x = math.max(box.x, x2 - constraints.max_width); | ||||
|                     box.x = math.min(box.x, x2 - constraints.min_width); | ||||
|                     box.width = x2 - box.x; | ||||
|                     x1 += @floatToInt(i32, dx); | ||||
|                     x1 = math.max(x1, border_width); | ||||
|                     x1 = math.max(x1, x2 - constraints.max_width); | ||||
|                     x1 = math.min(x1, x2 - constraints.min_width); | ||||
|                     box.width = x2 - x1; | ||||
|                 } else if (data.edges.right) { | ||||
|                     box.width += @floatToInt(i32, dx); | ||||
|                     box.width = math.max(box.width, constraints.min_width); | ||||
| @ -895,12 +892,13 @@ fn processMotion(self: *Self, device: *wlr.InputDevice, time: u32, delta_x: f64, | ||||
|                 } | ||||
|  | ||||
|                 if (data.edges.top) { | ||||
|                     var y1 = box.y; | ||||
|                     const y2 = box.y + box.height; | ||||
|                     box.y += @floatToInt(i32, dy); | ||||
|                     box.y = math.max(box.y, border_width); | ||||
|                     box.y = math.max(box.y, y2 - constraints.max_height); | ||||
|                     box.y = math.min(box.y, y2 - constraints.min_height); | ||||
|                     box.height = y2 - box.y; | ||||
|                     y1 += @floatToInt(i32, dy); | ||||
|                     y1 = math.max(y1, border_width); | ||||
|                     y1 = math.max(y1, y2 - constraints.max_height); | ||||
|                     y1 = math.min(y1, y2 - constraints.min_height); | ||||
|                     box.height = y2 - y1; | ||||
|                 } else if (data.edges.bottom) { | ||||
|                     box.height += @floatToInt(i32, dy); | ||||
|                     box.height = math.max(box.height, constraints.min_height); | ||||
| @ -984,12 +982,26 @@ pub fn updateState(self: *Self) void { | ||||
|  | ||||
|             // Keep the cursor locked to the original offset from the edges of the view. | ||||
|             const box = &data.view.current.box; | ||||
|             const dx = data.offset_x; | ||||
|             const dy = data.offset_y; | ||||
|             const lx = if (mode == .move or data.edges.left) dx + box.x else box.x + box.width - dx; | ||||
|             const ly = if (mode == .move or data.edges.top) dy + box.y else box.y + box.height - dy; | ||||
|             const new_x = blk: { | ||||
|                 if (mode == .move or data.edges.left) { | ||||
|                     break :blk @intToFloat(f64, data.offset_x + box.x); | ||||
|                 } else if (data.edges.right) { | ||||
|                     break :blk @intToFloat(f64, box.x + box.width - data.offset_x); | ||||
|                 } else { | ||||
|                     break :blk self.wlr_cursor.x; | ||||
|                 } | ||||
|             }; | ||||
|             const new_y = blk: { | ||||
|                 if (mode == .move or data.edges.top) { | ||||
|                     break :blk @intToFloat(f64, data.offset_y + box.y); | ||||
|                 } else if (data.edges.bottom) { | ||||
|                     break :blk @intToFloat(f64, box.y + box.height - data.offset_y); | ||||
|                 } else { | ||||
|                     break :blk self.wlr_cursor.y; | ||||
|                 } | ||||
|             }; | ||||
|  | ||||
|             self.wlr_cursor.warpClosest(null, @intToFloat(f64, lx), @intToFloat(f64, ly)); | ||||
|             self.wlr_cursor.warpClosest(null, new_x, new_y); | ||||
|         }, | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -512,6 +512,8 @@ pub fn applyPending(root: *Self) void { | ||||
|                     } | ||||
|                 }, | ||||
|             } | ||||
|  | ||||
|             cursor.inflight_mode = cursor.mode; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
| @ -244,22 +244,72 @@ pub fn destroy(view: *Self) void { | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// The change in x/y position of the view during resize cannot be determined | ||||
| /// until the size of the buffer actually committed is known. Clients are permitted | ||||
| /// by the protocol to take a size smaller than that requested by the compositor in | ||||
| /// order to maintain an aspect ratio or similar (mpv does this for example). | ||||
| pub fn resizeUpdatePosition(view: *Self, width: i32, height: i32) void { | ||||
|     assert(view.inflight.resizing); | ||||
|  | ||||
|     const data = blk: { | ||||
|         var it = server.input_manager.seats.first; | ||||
|         while (it) |node| : (it = node.next) { | ||||
|             const cursor = &node.data.cursor; | ||||
|             if (cursor.inflight_mode == .resize and cursor.inflight_mode.resize.view == view) { | ||||
|                 break :blk cursor.inflight_mode.resize; | ||||
|             } | ||||
|         } else { | ||||
|             // The view resizing state should never be set when the view is | ||||
|             // not the target of an interactive resize. | ||||
|             unreachable; | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     if (data.edges.left) { | ||||
|         view.inflight.box.x += view.current.box.width - width; | ||||
|         view.pending.box.x = view.inflight.box.x; | ||||
|     } | ||||
|  | ||||
|     if (data.edges.top) { | ||||
|         view.inflight.box.y += view.current.box.height - height; | ||||
|         view.pending.box.y = view.inflight.box.y; | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub fn updateCurrent(view: *Self) void { | ||||
|     const config = &server.config; | ||||
|  | ||||
|     if (view.impl == .xdg_toplevel) { | ||||
|         switch (view.impl.xdg_toplevel.configure_state) { | ||||
|             // If the configure timed out, don't update current to dimensions | ||||
|             // that have not been committed by the client. | ||||
|             .inflight, .acked => { | ||||
|                 view.inflight.box.width = view.current.box.width; | ||||
|                 view.inflight.box.height = view.current.box.height; | ||||
|                 view.pending.box.width = view.current.box.width; | ||||
|                 view.pending.box.height = view.current.box.height; | ||||
|             }, | ||||
|             .idle, .committed => {}, | ||||
|         } | ||||
|         view.impl.xdg_toplevel.configure_state = .idle; | ||||
|     switch (view.impl) { | ||||
|         .xdg_toplevel => |*xdg_toplevel| { | ||||
|             switch (xdg_toplevel.configure_state) { | ||||
|                 // If the configure timed out, don't update current to dimensions | ||||
|                 // that have not been committed by the client. | ||||
|                 .inflight, .acked => { | ||||
|                     if (view.inflight.resizing) { | ||||
|                         view.resizeUpdatePosition(view.current.box.width, view.current.box.height); | ||||
|                     } | ||||
|                     view.inflight.box.width = view.current.box.width; | ||||
|                     view.inflight.box.height = view.current.box.height; | ||||
|                     view.pending.box.width = view.current.box.width; | ||||
|                     view.pending.box.height = view.current.box.height; | ||||
|                 }, | ||||
|                 .idle, .committed => {}, | ||||
|             } | ||||
|             xdg_toplevel.configure_state = .idle; | ||||
|         }, | ||||
|         .xwayland_view => |xwayland_view| { | ||||
|             if (view.inflight.resizing) { | ||||
|                 view.resizeUpdatePosition( | ||||
|                     xwayland_view.xwayland_surface.width, | ||||
|                     xwayland_view.xwayland_surface.height, | ||||
|                 ); | ||||
|             } | ||||
|             view.inflight.box.width = xwayland_view.xwayland_surface.width; | ||||
|             view.inflight.box.height = xwayland_view.xwayland_surface.height; | ||||
|             view.pending.box.width = xwayland_view.xwayland_surface.width; | ||||
|             view.pending.box.height = xwayland_view.xwayland_surface.height; | ||||
|         }, | ||||
|         .none => {}, | ||||
|     } | ||||
|  | ||||
|     view.foreign_toplevel_handle.update(); | ||||
|  | ||||
| @ -317,17 +317,20 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void { | ||||
|                 self.geometry.height != old_geometry.height; | ||||
|             const no_layout = view.current.output != null and view.current.output.?.layout == null; | ||||
|  | ||||
|             if (size_changed and (view.current.float or no_layout) and !view.current.fullscreen) { | ||||
|             if (size_changed) { | ||||
|                 log.info( | ||||
|                     "client initiated size change: {}x{} -> {}x{}", | ||||
|                     .{ old_geometry.width, old_geometry.height, self.geometry.width, self.geometry.height }, | ||||
|                 ); | ||||
|  | ||||
|                 view.current.box.width = self.geometry.width; | ||||
|                 view.current.box.height = self.geometry.height; | ||||
|                 view.pending.box.width = self.geometry.width; | ||||
|                 view.pending.box.height = self.geometry.height; | ||||
|                 server.root.applyPending(); | ||||
|                 if ((view.current.float or no_layout) and !view.current.fullscreen) { | ||||
|                     view.current.box.width = self.geometry.width; | ||||
|                     view.current.box.height = self.geometry.height; | ||||
|                     view.pending.box.width = self.geometry.width; | ||||
|                     view.pending.box.height = self.geometry.height; | ||||
|                     server.root.applyPending(); | ||||
|                 } else { | ||||
|                     log.err("client is buggy and initiated size change while tiled or fullscreen", .{}); | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         // If the client has not yet acked our configure, we need to send a | ||||
| @ -338,6 +341,10 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void { | ||||
|         .acked => { | ||||
|             self.configure_state = .committed; | ||||
|  | ||||
|             if (view.inflight.resizing) { | ||||
|                 view.resizeUpdatePosition(self.geometry.width, self.geometry.height); | ||||
|             } | ||||
|  | ||||
|             view.inflight.box.width = self.geometry.width; | ||||
|             view.inflight.box.height = self.geometry.height; | ||||
|             view.pending.box.width = self.geometry.width; | ||||
|  | ||||
		Reference in New Issue
	
	Block a user