Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
Seat: implement pointer move operations
Resize and starting the operation with a serial are TODO though there is
now some shared infrastructure in place.
river/Cursor.zig | 219 +++++++--------------------------------
river/PointerConstraint.zig | 2 +-
river/Seat.zig | 150 +++++++++++++++++++++++++--
river/Window.zig | 229 ++++++++++++++++++++++++++---------------
river/WindowManager.zig | 2 +
river/WmNode.zig | 6 +-
river/XdgToplevel.zig | 6 +-
rivercompat/PointerBinding.zig | 13 ++-
rivercompat/Seat.zig | 13 ++-
9 files changed, 355 insertions(+), 285 deletions(-)
diff --git a/river/Cursor.zig b/river/Cursor.zig
index 057f4dd..08e2d4d 100644
--- a/river/Cursor.zig
+++ b/river/Cursor.zig
@@ -64,9 +64,7 @@ const Mode = union(enum) {
sx: f64,
sy: f64,
},
- move: struct {
- window: *Window,
-
+ op: struct {
/// Window coordinates are stored as i32s as they are in logical pixels.
/// However, it is possible to move the cursor by a fraction of a
/// logical pixel and this happens in practice with low dpi, high
@@ -75,34 +73,6 @@ const Mode = union(enum) {
/// motions to 0.
delta_x: f64 = 0,
delta_y: f64 = 0,
-
- /// Offset from the left edge
- offset_x: i32,
- /// Offset from the top edge
- offset_y: i32,
- },
- resize: struct {
- window: *Window,
-
- delta_x: f64 = 0,
- delta_y: f64 = 0,
-
- /// Total x/y movement of the pointer device since the start of the resize,
- /// clamped to the bounds of the resize as defined by the window min/max
- /// dimensions and output dimensions.
- /// This is not directly tied to the rendered cursor position.
- x: i32 = 0,
- y: i32 = 0,
-
- /// Resize edges, maximum of 2 are set and they may not be opposing edges.
- edges: wlr.Edges,
- /// Offset from the left or right edge
- offset_x: i32,
- /// Offset from the top or bottom edge
- offset_y: i32,
-
- initial_width: u31,
- initial_height: u31,
},
};
@@ -116,12 +86,6 @@ const LayoutPoint = struct {
/// 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
-/// Window.resizeUpdatePosition() is called.
-inflight_mode: Mode = .passthrough,
-
seat: *Seat,
wlr_cursor: *wlr.Cursor,
@@ -315,6 +279,28 @@ fn clearFocus(cursor: *Cursor) void {
cursor.seat.wlr_seat.pointerNotifyClearFocus();
}
+pub fn startOpPointer(cursor: *Cursor) void {
+ if (cursor.constraint) |constraint| {
+ if (constraint.state == .active) constraint.deactivate();
+ }
+
+ log.debug("entering cursor mode op", .{});
+ cursor.mode = .{ .op = .{} };
+
+ cursor.clearFocus();
+}
+
+pub fn endOpPointer(cursor: *Cursor) void {
+ if (cursor.pressed.count() == 0) {
+ log.debug("entering cursor mode passthrough", .{});
+ cursor.mode = .passthrough;
+ cursor.updateState();
+ } else {
+ log.debug("entering cursor mode ignore", .{});
+ cursor.mode = .ignore;
+ }
+}
+
pub fn processMotionRelative(cursor: *Cursor, event: *const wlr.Pointer.event.Motion) void {
server.input_manager.relative_pointer_manager.sendRelativeMotion(
cursor.seat.wlr_seat,
@@ -362,67 +348,14 @@ pub fn processMotionRelative(cursor: *Cursor, event: *const wlr.Pointer.event.Mo
constraint.maybeActivate();
}
},
- .move => |*data| {
- dx += data.delta_x;
- dy += data.delta_y;
- data.delta_x = dx - @trunc(dx);
- data.delta_y = dy - @trunc(dy);
-
- // XXX move window
-
- },
- .resize => |*data| {
+ .op => |*data| {
dx += data.delta_x;
dy += data.delta_y;
data.delta_x = dx - @trunc(dx);
data.delta_y = dy - @trunc(dy);
- data.x += @intFromFloat(dx);
- data.y += @intFromFloat(dy);
-
- if (true) return; // XXX resize window
-
- // Modify width/height of the pending box, taking constraints into account
- // The x/y coordinates of the window will be adjusted as needed in Window.resizeCommit()
- // based on the dimensions actually committed by the client.
- const border_width = if (data.window.pending.ssd) server.config.border_width else 0;
-
- // TODO
- const output_width: i32 = 1920;
- const output_height: i32 = 1080;
-
- const constraints = &data.window.constraints;
- const box = &data.window.pending.box;
-
- if (data.edges.left) {
- const x2 = box.x + box.width;
- box.width = data.initial_width - data.x;
- box.width = @max(box.width, constraints.min_width);
- box.width = @min(box.width, constraints.max_width);
- box.width = @min(box.width, x2 - border_width);
- data.x = data.initial_width - box.width;
- } else if (data.edges.right) {
- box.width = data.initial_width + data.x;
- box.width = @max(box.width, constraints.min_width);
- box.width = @min(box.width, constraints.max_width);
- box.width = @min(box.width, output_width - border_width - box.x);
- data.x = box.width - data.initial_width;
- }
-
- if (data.edges.top) {
- const y2 = box.y + box.height;
- box.height = data.initial_height - data.y;
- box.height = @max(box.height, constraints.min_height);
- box.height = @min(box.height, constraints.max_height);
- box.height = @min(box.height, y2 - border_width);
- data.y = data.initial_height - box.height;
- } else if (data.edges.bottom) {
- box.height = data.initial_height + data.y;
- box.height = @max(box.height, constraints.min_height);
- box.height = @min(box.height, constraints.max_height);
- box.height = @min(box.height, output_height - border_width - box.y);
- data.y = box.height - data.initial_height;
- }
+ cursor.wlr_cursor.move(event.device, dx, dy);
+ cursor.seat.updateOp(@intFromFloat(dx), @intFromFloat(dy));
},
}
}
@@ -495,8 +428,8 @@ pub fn processButton(cursor: *Cursor, event: *const wlr.Pointer.event.Button) vo
.down => {
_ = cursor.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state);
},
- // No client has pointer focus while in ignore/move/resize mode.
- .ignore, .move, .resize => {},
+ // No client has pointer focus while in ignore/op mode.
+ .ignore, .op => {},
}
} else {
assert(event.state == .released);
@@ -508,16 +441,17 @@ pub fn processButton(cursor: *Cursor, event: *const wlr.Pointer.event.Button) vo
switch (cursor.mode) {
.passthrough => unreachable,
- .down => {
- _ = cursor.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state);
+ .down, .ignore => {
+ if (cursor.mode == .down) {
+ _ = cursor.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state);
+ }
+ if (cursor.pressed.count() == 0) {
+ log.debug("exiting cursor mode {s}", .{@tagName(cursor.mode)});
+ cursor.mode = .passthrough;
+ cursor.passthrough(event.time_msec);
+ }
},
- // No client has pointer focus while in ignore/move/resize mode.
- .ignore, .move, .resize => {},
- }
- if (cursor.pressed.count() == 0) {
- log.debug("exiting cursor mode {s}", .{@tagName(cursor.mode)});
- cursor.mode = .passthrough;
- cursor.passthrough(event.time_msec);
+ .op => {},
}
} else {
log.err("ignoring duplicate pointer button {d} release", .{event.button});
@@ -707,19 +641,6 @@ fn handleTabletToolButton(
tool.button(tablet, event);
}
-pub fn startMove(cursor: *Cursor, window: *Window) void {
- if (cursor.constraint) |constraint| {
- if (constraint.state == .active) constraint.deactivate();
- }
-
- const new_mode: Mode = .{ .move = .{
- .window = window,
- .offset_x = @as(i32, @intFromFloat(cursor.wlr_cursor.x)) - window.current.box.x,
- .offset_y = @as(i32, @intFromFloat(cursor.wlr_cursor.y)) - window.current.box.y,
- } };
- cursor.enterMode(new_mode, window, "move");
-}
-
pub fn startResize(cursor: *Cursor, window: *Window, proposed_edges: ?wlr.Edges) void {
if (cursor.constraint) |constraint| {
if (constraint.state == .active) constraint.deactivate();
@@ -787,21 +708,6 @@ fn computeEdges(cursor: *const Cursor, window: *const Window) wlr.Edges {
}
}
-fn enterMode(cursor: *Cursor, mode: Mode, _: *Window, xcursor_name: [*:0]const u8) void {
- assert(cursor.mode == .passthrough or cursor.mode == .down);
- assert(mode == .move or mode == .resize);
-
- log.debug("enter {s} cursor mode", .{@tagName(mode)});
-
- cursor.mode = mode;
-
- cursor.seat.wlr_seat.pointerNotifyClearFocus();
- cursor.setXcursor(xcursor_name);
-}
-
-/// Handle potential change in location of windows on the output, as well as
-/// the target window of a cursor operation potentially being moved to a non-visible tag,
-/// becoming fullscreen, etc.
pub fn updateState(cursor: *Cursor) void {
if (cursor.constraint) |constraint| {
constraint.updateState();
@@ -816,54 +722,7 @@ pub fn updateState(cursor: *Cursor) void {
cursor.passthrough(msec);
},
// TODO: Leave down mode if the target surface is no longer visible.
- .ignore, .down => {},
- .move, .resize => {
- // Moving and resizing of windows is handled through the transaction system. Therefore,
- // we must inspect the inflight_mode instead if a move or a resize is in progress.
- //
- // The cases when a move/resize is being started or ended and e.g. mode is resize
- // while inflight_mode is passthrough or mode is passthrough while inflight_mode
- // is resize shouldn't need any special handling.
- //
- // In the first case, a move/resize has been started along with a transaction but the
- // transaction hasn't been committed yet so there is nothing to do.
- //
- // In the second case, a move/resize has been terminated by the user but the
- // transaction carrying out the final size/position change is still inflight.
- // Therefore, the user already expects the cursor to be free from the window and
- // we should not warp it back to the fixed offset of the move/resize.
- switch (cursor.inflight_mode) {
- .passthrough, .ignore, .down => {},
- inline .move, .resize => |data, mode| {
-
- // These conditions are checked in WindowManager.dirtyPending()
- assert(!data.window.current.fullscreen);
-
- // Keep the cursor locked to the original offset from the edges of the window.
- const box = &data.window.current.box;
- const new_x: f64 = blk: {
- if (mode == .move or data.edges.left) {
- break :blk @floatFromInt(data.offset_x + box.x);
- } else if (data.edges.right) {
- break :blk @floatFromInt(box.x + box.width - data.offset_x);
- } else {
- break :blk cursor.wlr_cursor.x;
- }
- };
- const new_y: f64 = blk: {
- if (mode == .move or data.edges.top) {
- break :blk @floatFromInt(data.offset_y + box.y);
- } else if (data.edges.bottom) {
- break :blk @floatFromInt(box.y + box.height - data.offset_y);
- } else {
- break :blk cursor.wlr_cursor.y;
- }
- };
-
- cursor.wlr_cursor.warpClosest(null, new_x, new_y);
- },
- }
- },
+ .ignore, .down, .op => {},
}
}
diff --git a/river/PointerConstraint.zig b/river/PointerConstraint.zig
index e193091..f460afd 100644
--- a/river/PointerConstraint.zig
+++ b/river/PointerConstraint.zig
@@ -76,7 +76,7 @@ pub fn maybeActivate(constraint: *PointerConstraint) void {
if (constraint.state == .active) return;
- if (seat.cursor.mode == .move or seat.cursor.mode == .resize) return;
+ if (seat.cursor.mode == .op) return;
const result = server.scene.at(seat.cursor.wlr_cursor.x, seat.cursor.wlr_cursor.y) orelse return;
if (result.surface != constraint.wlr_constraint.surface) return;
diff --git a/river/Seat.zig b/river/Seat.zig
index fe39cd2..679253c 100644
--- a/river/Seat.zig
+++ b/river/Seat.zig
@@ -74,10 +74,14 @@ pub const Event = union(enum) {
pub const WmState = struct {
focus: WmFocus = .none,
- // TODO pointer move/resize state
+ op: union(enum) {
+ none,
+ //TODO start_serial: ?u32,
+ start_pointer,
+ end,
+ } = .none,
// TODO confine region
// TODO pointer warp
- // TODO xkb/pointer bindings
};
pub const WmFocus = union(enum) {
@@ -142,6 +146,20 @@ pointer_bindings: wl.list.Head(PointerBinding, .link),
/// Multiple physical mice are handled by the same Cursor
cursor: Cursor,
+op: ?struct {
+ // We always want to process as many input events as possible before sending configures
+ // and starting a transaction. Therefore, we set this flag if a seat operation modifies
+ // pending state and check it at the end of Seat.processEvents() rather than calling
+ // WindowManager.dirtyPending() directly in Seat.updateOp().
+ dirty: bool = false,
+ input: enum {
+ pointer,
+ },
+ /// Coordinates of the cursor/touch point/etc. at the start of the operation.
+ start_x: i32,
+ start_y: i32,
+} = null,
+
relay: InputRelay,
keyboard_groups: std.TailQueue(KeyboardGroup) = .{},
@@ -202,6 +220,26 @@ pub fn destroy(seat: *Seat) void {
while (it.next()) |device| assert(device.seat != seat);
}
+ {
+ // Remove pointers to the seat before they become dangling
+ var it = server.wm.windows.iterator(.forward);
+ while (it.next()) |window| {
+ inline for (.{
+ &window.uncommitted,
+ &window.committed,
+ &window.inflight,
+ &window.current,
+ }) |state| {
+ switch (state.op) {
+ .none => {},
+ inline .move, .resize => |data| {
+ if (data.seat == seat) state.op = .none;
+ },
+ }
+ }
+ }
+ }
+
seat.link.remove();
seat.link_sent.remove();
@@ -260,6 +298,13 @@ pub fn processEvents(seat: *Seat) void {
.pointer_pinch_end => |ev| pg.sendPinchEnd(seat.wlr_seat, ev.time_msec, ev.cancelled),
}
}
+
+ if (seat.op) |*op| {
+ if (op.dirty) {
+ server.wm.dirtyPending();
+ op.dirty = false;
+ }
+ }
}
pub fn sendDirty(seat: *Seat) void {
@@ -395,10 +440,16 @@ fn handleRequest(
.clear_focus => seat.uncommitted.focus = .none,
.op_start_serial => {},
- .op_start_pointer => {},
- .op_add_move_window => {},
+ .op_start_pointer => seat.uncommitted.op = .start_pointer,
+ .op_add_move_window => |args| {
+ const data = args.window.getUserData() orelse return;
+ const window: *Window = @ptrCast(@alignCast(data));
+ window.uncommitted.op = .{ .move = .{
+ .seat = seat,
+ } };
+ },
.op_add_resize_window => {},
- .op_end => {},
+ .op_end => seat.uncommitted.op = .end,
.pointer_confine_to_region => {},
.pointer_warp => {},
@@ -449,14 +500,71 @@ pub fn commitWmState(seat: *Seat) void {
}
seat.committed = seat.uncommitted;
+ seat.uncommitted.op = .none;
}
pub fn applyCommitted(seat: *Seat) void {
- if (server.lock_manager.state == .unlocked) {
- switch (seat.committed.focus) {
- .none => seat.focus(.none),
- .window => |window| seat.focus(.{ .window = window }),
- }
+ if (server.lock_manager.state != .unlocked) return;
+
+ switch (seat.committed.focus) {
+ .none => seat.focus(.none),
+ .window => |window| seat.focus(.{ .window = window }),
+ }
+
+ switch (seat.committed.op) {
+ .none => {},
+ .start_pointer => if (seat.op == null) {
+ log.debug("start seat op pointer", .{});
+ seat.op = .{
+ .input = .pointer,
+ .start_x = @intFromFloat(seat.cursor.wlr_cursor.x),
+ .start_y = @intFromFloat(seat.cursor.wlr_cursor.y),
+ };
+ seat.cursor.startOpPointer();
+
+ {
+ var it = server.wm.windows.iterator(.forward);
+ while (it.next()) |window| {
+ switch (window.committed.op) {
+ .none => {},
+ .move => |data| {
+ if (data.seat == seat) {
+ assert(window.inflight.op == .none);
+ window.inflight.op = .{
+ .move = .{
+ .seat = seat,
+ .start_x = window.pending.box.x,
+ .start_y = window.pending.box.y,
+ },
+ };
+ }
+ },
+ .resize => {}, // TODO
+ }
+ }
+ }
+ },
+ .end => if (seat.op) |op| {
+ log.debug("end seat op", .{});
+ seat.op = null;
+ switch (op.input) {
+ .pointer => seat.cursor.endOpPointer(),
+ }
+
+ {
+ var it = server.wm.windows.iterator(.forward);
+ while (it.next()) |window| {
+ switch (window.inflight.op) {
+ .none => {},
+ inline .move, .resize => |data| {
+ if (data.seat == seat) {
+ window.inflight.op = .none;
+ }
+ },
+ }
+ }
+ }
+ },
}
}
@@ -633,6 +741,28 @@ pub fn handleSwitchMapping(
}
}
+pub fn updateOp(seat: *Seat, dx: i32, dy: i32) void {
+ assert(seat.op != null);
+
+ {
+ var it = server.wm.windows.iterator(.forward);
+ while (it.next()) |window| {
+ switch (window.inflight.op) {
+ .none => {},
+ .move => |data| {
+ if (data.seat != seat) continue;
+
+ window.pending.box.x += dx;
+ window.pending.box.y += dy;
+
+ seat.op.?.dirty = true;
+ },
+ .resize => {}, // TODO
+ }
+ }
+ }
+}
+
pub fn addDevice(seat: *Seat, wlr_device: *wlr.InputDevice) void {
seat.tryAddDevice(wlr_device) catch |err| switch (err) {
error.OutOfMemory => log.err("out of memory", .{}),
diff --git a/river/Window.zig b/river/Window.zig
index 46bcae4..5ad0f3e 100644
--- a/river/Window.zig
+++ b/river/Window.zig
@@ -78,13 +78,28 @@ pub const State = struct {
capabilities: river.WindowV1.Capabilities = .{},
maximized: bool = false,
fullscreen: bool = false,
- resizing: bool = false,
+
+ op: union(enum) {
+ none,
+ move: struct {
+ seat: *Seat,
+ start_x: i32,
+ start_y: i32,
+ },
+ resize: struct {
+ seat: *Seat,
+ edges: river.WindowV1.Edges = .{},
+ start_box: wlr.Box,
+ },
+ } = .none,
};
pub const WmState = struct {
- x: i32 = 0,
- y: i32 = 0,
- proposed: ?struct {
+ position: ?struct {
+ x: i32,
+ y: i32,
+ } = null,
+ dimensions: ?struct {
width: u31,
height: u31,
} = null,
@@ -101,6 +116,16 @@ pub const WmState = struct {
maximized: bool = false,
fullscreen: bool = false, // XXX output
close: bool = false,
+ op: union(enum) {
+ none,
+ move: struct {
+ seat: *Seat,
+ },
+ resize: struct {
+ seat: *Seat,
+ edges: river.WindowV1.Edges = .{},
+ },
+ } = .none,
};
/// The window management protocol object for this window
@@ -142,8 +167,8 @@ pending: struct {
/// Indicates that the closed event will be sent in the next update sequence.
closing,
} = .init,
- dimensions_hint: DimensionsHint = .{},
box: wlr.Box = .{ .x = 0, .y = 0, .width = 0, .height = 0 },
+ dimensions_hint: DimensionsHint = .{},
decoration_hint: river.WindowV1.DecorationHint = .only_supports_csd,
/// Set back to no_request at the end of each update sequence
fullscreen_requested: enum {
@@ -158,8 +183,9 @@ pending: struct {
/// This state is only kept around in order to avoid sending redundant events
/// to the window manager client.
sent: struct {
+ position: ?struct { x: i32, y: i32 } = null,
+ dimensions: ?struct { width: i32, height: i32 } = null,
dimensions_hint: DimensionsHint = .{},
- box: wlr.Box = .{ .x = 0, .y = 0, .width = 0, .height = 0 },
decoration_hint: river.WindowV1.DecorationHint = .only_supports_csd,
} = .{},
@@ -258,6 +284,15 @@ pub fn setDimensionsHint(window: *Window, hint: DimensionsHint) void {
}
}
+pub fn setPosition(window: *Window, x: i32, y: i32) void {
+ window.pending.box.x = x;
+ window.pending.box.y = y;
+
+ if (x != window.sent.box.x or y != window.sent.box.y) {
+ server.wm.dirtyPending();
+ }
+}
+
pub fn setDimensions(window: *Window, width: i32, height: i32) void {
window.pending.box.width = width;
window.pending.box.height = height;
@@ -265,7 +300,9 @@ pub fn setDimensions(window: *Window, width: i32, height: i32) void {
window.inflight.box.width = width;
window.inflight.box.height = height;
- if (width != window.sent.box.width or height != window.sent.box.height) {
+ if (window.sent.dimensions == null or
+ width != window.sent.dimensions.?.width or height != window.sent.dimensions.?.height)
+ {
server.wm.dirtyPending();
}
}
@@ -334,6 +371,27 @@ pub fn sendDirty(window: *Window) void {
const sent = &window.sent;
// XXX send all dirty pending state
+ if (new or sent.position == null or
+ pending.box.x != sent.position.?.x or pending.box.y != sent.position.?.y)
+ {
+ if (window.node.object) |node_v1| {
+ node_v1.sendPosition(pending.box.x, pending.box.y);
+ sent.position = .{
+ .x = pending.box.x,
+ .y = pending.box.y,
+ };
+ }
+ }
+ if (!pending.box.empty() and (new or sent.dimensions == null or
+ pending.box.width != sent.dimensions.?.width or
+ pending.box.height != sent.dimensions.?.height))
+ {
+ window_v1.sendDimensions(window.pending.box.width, window.pending.box.height);
+ sent.dimensions = .{
+ .width = pending.box.width,
+ .height = pending.box.height,
+ };
+ }
if (new or !meta.eql(pending.dimensions_hint, sent.dimensions_hint)) {
window_v1.sendDimensionsHint(
pending.dimensions_hint.min_width,
@@ -343,13 +401,6 @@ pub fn sendDirty(window: *Window) void {
);
sent.dimensions_hint = pending.dimensions_hint;
}
- if ((new or pending.box.width != sent.box.width or
- pending.box.height != sent.box.height) and !pending.box.empty())
- {
- window_v1.sendDimensions(window.pending.box.width, window.pending.box.height);
- sent.box.width = pending.box.width;
- sent.box.height = pending.box.height;
- }
if (new or pending.decoration_hint != sent.decoration_hint) {
window_v1.sendDecorationHint(window.pending.decoration_hint);
sent.decoration_hint = pending.decoration_hint;
@@ -400,7 +451,7 @@ fn handleRequest(
if (args.width < 0 or args.height < 0) {
// XXX send protocol error
}
- uncommitted.proposed = .{
+ uncommitted.dimensions = .{
.width = @intCast(args.width),
.height = @intCast(args.height),
};
@@ -433,14 +484,13 @@ fn handleRequest(
}
pub fn commitWmState(window: *Window) void {
- if (!window.initialized and window.uncommitted.proposed != null) {
+ if (!window.initialized and window.uncommitted.dimensions != null) {
window.initialized = true;
}
window.committed = .{
- .x = window.uncommitted.x,
- .y = window.uncommitted.y,
- .proposed = window.uncommitted.proposed orelse window.committed.proposed,
+ .position = window.uncommitted.position orelse window.committed.position,
+ .dimensions = window.uncommitted.dimensions orelse window.committed.dimensions,
.hidden = window.uncommitted.hidden,
.ssd = window.uncommitted.ssd,
.border = window.uncommitted.border,
@@ -449,8 +499,83 @@ pub fn commitWmState(window: *Window) void {
.maximized = window.uncommitted.maximized,
.fullscreen = window.uncommitted.fullscreen,
.close = window.uncommitted.close,
+ .op = window.uncommitted.op,
+ };
+ window.uncommitted.position = null;
+ window.uncommitted.dimensions = null;
+ window.uncommitted.op = .none;
+}
+
+/// Applies committed state from the window manager client to the inflight state.
+/// Returns true if the configure should be waited for by the transaction system.
+pub fn configure(window: *Window) bool {
+ if (!window.initialized) return false;
+
+ assert(!window.destroying);
+
+ if (window.committed.close) {
+ window.close();
+ }
+
+ const activated = blk: {
+ var it = server.wm.sent.seats.iterator(.forward);
+ while (it.next()) |seat| {
+ if (seat.committed.focus == .window and seat.committed.focus.window == window) {
+ break :blk true;
+ }
+ }
+ break :blk false;
+ };
+
+ const committed = &window.committed;
+
+ if (window.inflight.op == .none) {
+ if (committed.position) |position| {
+ window.pending.box.x = position.x;
+ window.pending.box.y = position.y;
+ }
+ if (committed.dimensions) |dimensions| {
+ window.pending.box.width = dimensions.width;
+ window.pending.box.height = dimensions.height;
+ }
+ }
+ window.inflight = .{
+ .box = window.pending.box,
+ .hidden = committed.hidden,
+ .activated = activated,
+ .ssd = committed.ssd,
+ .border = committed.border,
+ .tiled = committed.tiled,
+ .capabilities = committed.capabilities,
+ .maximized = committed.maximized,
+ .fullscreen = committed.fullscreen,
+ .op = window.inflight.op,
+ };
+
+ const track_configure = switch (window.impl) {
+ .toplevel => |*toplevel| toplevel.configure(committed.dimensions != null),
+ .xwayland => |*xwindow| xwindow.configure(),
+ .none => unreachable,
};
- window.uncommitted.proposed = null;
+
+ // Ensure a position/dimension event is sent if the window manager has
+ // modified them even if the actual position/dimensions do not change.
+ if (committed.position != null) {
+ window.sent.position = null;
+ committed.position = null;
+ }
+ if (committed.dimensions != null) {
+ window.sent.dimensions = null;
+ committed.dimensions = null;
+ }
+ committed.op = .none;
+
+ if (track_configure and window.mapped) {
+ window.saveSurfaceTree();
+ window.sendFrameDone();
+ }
+
+ return track_configure;
}
/// The change in x/y position of the window during resize cannot be determined
@@ -517,7 +642,7 @@ pub fn commitTransaction(window: *Window) void {
// If we did not use the current geometry of the toplevel at this point
// we would be rendering the SSD border at initial size X but the surface
// would be rendered at size Y.
- if (window.inflight.resizing) {
+ if (false and window.inflight.resizing) {
window.resizeUpdatePosition(toplevel.geometry.width, toplevel.geometry.height);
}
@@ -602,68 +727,6 @@ pub fn updateSceneState(window: *Window) void {
}
}
-/// Applies committed state from the window manager client to the inflight state.
-/// Returns true if the configure should be waited for by the transaction system.
-pub fn configure(window: *Window) bool {
- if (!window.initialized) return false;
-
- assert(!window.destroying);
-
- if (window.committed.close) {
- window.close();
- }
-
- const activated = blk: {
- var it = server.wm.sent.seats.iterator(.forward);
- while (it.next()) |seat| {
- if (seat.committed.focus == .window and seat.committed.focus.window == window) {
- break :blk true;
- }
- }
- break :blk false;
- };
-
- const committed = &window.committed;
- window.inflight = .{
- .box = .{
- .x = committed.x,
- .y = committed.y,
- .width = if (committed.proposed) |p| p.width else window.pending.box.width,
- .height = if (committed.proposed) |p| p.height else window.pending.box.height,
- },
- .hidden = committed.hidden,
- .activated = activated,
- .ssd = committed.ssd,
- .border = committed.border,
- .tiled = committed.tiled,
- .capabilities = committed.capabilities,
- .maximized = committed.maximized,
- .fullscreen = committed.fullscreen,
- .resizing = false, // XXX
- };
-
- const track_configure = switch (window.impl) {
- .toplevel => |*toplevel| toplevel.configure(committed.proposed != null),
- .xwayland => |*xwindow| xwindow.configure(),
- .none => unreachable,
- };
-
- // Ensure a dimensions event is sent if the window manager has proposed dimensions
- // even if the actual dimensions commited by the window do not change.
- if (committed.proposed != null) {
- window.sent.box.width = 0;
- window.sent.box.height = 0;
- committed.proposed = null;
- }
-
- if (track_configure and window.mapped) {
- window.saveSurfaceTree();
- window.sendFrameDone();
- }
-
- return track_configure;
-}
-
/// Returns null if the window is currently being destroyed and no longer has
/// an associated surface.
/// May also return null for Xwayland windows that are not currently mapped.
diff --git a/river/WindowManager.zig b/river/WindowManager.zig
index 6502851..27db7d5 100644
--- a/river/WindowManager.zig
+++ b/river/WindowManager.zig
@@ -293,6 +293,8 @@ fn sendConfigures(wm: *WindowManager) void {
wm.committed.dirty = false;
{
+ // Order is important here, Seat.applyCommitted() must be called
+ // before configures are sent.
var it = wm.sent.seats.iterator(.forward);
while (it.next()) |seat| seat.applyCommitted();
}
diff --git a/river/WmNode.zig b/river/WmNode.zig
index a2170bd..58dc7f1 100644
--- a/river/WmNode.zig
+++ b/river/WmNode.zig
@@ -106,8 +106,10 @@ fn handleRequest(
},
.set_position => |args| switch (node.get()) {
.window => |window| {
- window.uncommitted.x = args.x;
- window.uncommitted.y = args.y;
+ window.uncommitted.position = .{
+ .x = args.x,
+ .y = args.y,
+ };
},
},
.place_top => {
diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig
index 2f58450..769993a 100644
--- a/river/XdgToplevel.zig
+++ b/river/XdgToplevel.zig
@@ -165,7 +165,7 @@ pub fn configure(toplevel: *XdgToplevel, force: bool) bool {
});
_ = wlr_toplevel.setMaximized(inflight.maximized);
_ = wlr_toplevel.setFullscreen(inflight.fullscreen);
- _ = wlr_toplevel.setResizing(inflight.resizing);
+ _ = wlr_toplevel.setResizing(inflight.op == .resize);
if (toplevel.decoration) |decoration| {
_ = decoration.wlr_decoration.setMode(if (inflight.ssd) .server_side else .client_side);
@@ -214,7 +214,7 @@ fn needsConfigure(toplevel: *XdgToplevel) bool {
if (!std.meta.eql(inflight.capabilities, current.capabilities)) return true;
if (inflight.maximized != current.maximized) return true;
if (inflight.fullscreen != current.fullscreen) return true;
- if (inflight.resizing != current.resizing) return true;
+ if ((inflight.op == .resize) != (current.op == .resize)) return true;
return false;
}
@@ -362,7 +362,7 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
.acked, .timed_out_acked => {
toplevel.wlr_toplevel.base.getGeometry(&toplevel.geometry);
- if (window.inflight.resizing) {
+ if (false and window.inflight.resizing) {
window.resizeUpdatePosition(toplevel.geometry.width, toplevel.geometry.height);
}
diff --git a/rivercompat/PointerBinding.zig b/rivercompat/PointerBinding.zig
index 2d51a3b..6779753 100644
--- a/rivercompat/PointerBinding.zig
+++ b/rivercompat/PointerBinding.zig
@@ -29,20 +29,23 @@ const gpa = std.heap.c_allocator;
seat: *Seat,
pointer_binding_v1: *river.PointerBindingV1,
-action: Seat.Action,
+press_action: Seat.Action,
+release_action: ?Seat.Action,
pub fn create(
seat: *Seat,
button: u32,
modifiers: river.SeatV1.Modifiers,
- action: Seat.Action,
+ press_action: Seat.Action,
+ release_action: ?Seat.Action,
) void {
const pointer_binding_v1 = seat.seat_v1.getPointerBinding(button, modifiers) catch @panic("OOM");
const binding = gpa.create(PointerBinding) catch @panic("OOM");
binding.* = .{
.seat = seat,
.pointer_binding_v1 = pointer_binding_v1,
- .action = action,
+ .press_action = press_action,
+ .release_action = release_action,
};
pointer_binding_v1.setListener(*PointerBinding, handleEvent, binding);
pointer_binding_v1.enable();
@@ -51,7 +54,7 @@ pub fn create(
fn handleEvent(pointer_binding_v1: *river.PointerBindingV1, event: river.PointerBindingV1.Event, binding: *PointerBinding) void {
assert(binding.pointer_binding_v1 == pointer_binding_v1);
switch (event) {
- .pressed => binding.seat.execute(binding.action),
- .released => {},
+ .pressed => binding.seat.execute(binding.press_action),
+ .released => if (binding.release_action) |a| binding.seat.execute(a),
}
}
diff --git a/rivercompat/Seat.zig b/rivercompat/Seat.zig
index 938414a..8439975 100644
--- a/rivercompat/Seat.zig
+++ b/rivercompat/Seat.zig
@@ -51,7 +51,8 @@ pub fn create(wm: *WindowManager, seat_v1: *river.SeatV1) void {
XkbBinding.create(seat, xkb.Keysym.n, .{ .mod4 = true }, .focus_next);
XkbBinding.create(seat, xkb.Keysym.h, .{ .mod4 = true }, .hide_focused);
XkbBinding.create(seat, xkb.Keysym.s, .{ .mod4 = true }, .show_all);
- PointerBinding.create(seat, c.BTN_RIGHT, .{ .mod4 = true }, .close_focused);
+ PointerBinding.create(seat, c.BTN_LEFT, .{ .mod4 = true }, .move_start, .move_end);
+ PointerBinding.create(seat, c.BTN_MIDDLE, .{ .mod4 = true }, .close_focused, null);
}
pub fn focus(seat: *Seat, target: ?*Window) void {
@@ -101,6 +102,8 @@ pub const Action = enum {
close_focused,
hide_focused,
show_all,
+ move_start,
+ move_end,
};
pub fn execute(seat: *Seat, action: Action) void {
@@ -114,5 +117,13 @@ pub fn execute(seat: *Seat, action: Action) void {
window.window_v1.show();
}
},
+ .move_start => {
+ seat.seat_v1.opStartPointer();
+ var it = seat.wm.windows.iterator(.forward);
+ while (it.next()) |window| {
+ seat.seat_v1.opAddMoveWindow(window.window_v1);
+ }
+ },
+ .move_end => seat.seat_v1.opEnd(),
}
}