diff --git a/river/Cursor.zig b/river/Cursor.zig index c584556..e9671c6 100644 --- a/river/Cursor.zig +++ b/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); }, } } diff --git a/river/Root.zig b/river/Root.zig index b90c52a..b087730 100644 --- a/river/Root.zig +++ b/river/Root.zig @@ -512,6 +512,8 @@ pub fn applyPending(root: *Self) void { } }, } + + cursor.inflight_mode = cursor.mode; } } diff --git a/river/View.zig b/river/View.zig index 9eec85e..9a860b7 100644 --- a/river/View.zig +++ b/river/View.zig @@ -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(); diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig index 734e116..bcb7bb6 100644 --- a/river/XdgToplevel.zig +++ b/river/XdgToplevel.zig @@ -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;