Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
rwm: shift move/resize application to client
Making the window management client fully responsible for setting
window positions and proposing dimensions during interactive move/resize
makes things much more flexible.
For example, it is now possible for a window manager performing an
interactive resize of a tiled layout to react to a new window opening
during the resize by including that window in the ongoing interactive
resize.
This also reduces global complexity as it is no longer necessary for
the compositor to ever set the position of a window and the
river_node_v1.position event and associated code can now be removed.
protocol/river-window-management-v1.xml | 142 ++++++++++--------------------
river/Cursor.zig | 2 +-
river/Seat.zig | 140 ++---------------------------
river/Window.zig | 151 +++++++++++---------------------
river/WmNode.zig | 6 +-
rivercompat/Output.zig | 3 +-
rivercompat/Seat.zig | 57 ++++++++++--
rivercompat/Window.zig | 79 ++++++++++++++---
8 files changed, 227 insertions(+), 353 deletions(-)
diff --git a/protocol/river-window-management-v1.xml b/protocol/river-window-management-v1.xml
index 00ee1e9..d921b49 100644
--- a/protocol/river-window-management-v1.xml
+++ b/protocol/river-window-management-v1.xml
@@ -676,9 +676,8 @@
interactive move be started, perhaps when a client-side rendered
titlebar is dragged.
- The window manager may use the river_seat_v1.op_start_serial and
- river_seat_v1.op_add_move_window requests to interactively move the
- window or ignore this event entirely.
+ The window manager may use the river_seat_v1.op_start_serial request to
+ interactively move the window or ignore this event entirely.
This event will be followed by an update_windowing_start event after all
other new state has been sent by the server.
@@ -702,9 +701,8 @@
interactive resize be started, perhaps when the corner of client-side
rendered decorations is dragged.
- The window manager may use the river_seat_v1.op_start_serial and
- river_seat_v1.op_add_resize_window requests to interactively resize the
- window or ignore this event entirely.
+ The window manager may use the river_seat_v1.op_start_serial request to
+ interactively resize the window or ignore this event entirely.
This event will be followed by an update_windowing_start event after all
other new state has been sent by the server.
@@ -714,6 +712,31 @@
<arg name="edges" type="uint" enum="edges"/>
</event>
+ <request name="inform_resize_start">
+ <description summary="inform the window it is being resized">
+ Inform the window that it is being resized. The window manager should
+ use this request to inform windows that are the target of an interactive
+ resize for example.
+
+ The window manager remains responsible for handling the position and
+ dimensions of the window while it is resizing.
+
+ This request modifies windowing state and may only be made as part of a
+ windowing update sequence, see the river_window_manager_v1 description.
+ </description>
+ </request>
+
+ <request name="inform_resize_end">
+ <description summary="inform the window it no longer being resized">
+ Inform the window that it is no longer being resized. The window manager
+ should use this request to inform windows that are the target of an
+ interactive resize that the interactive resize has ended for example.
+
+ This request modifies windowing state and may only be made as part of a
+ windowing update sequence, see the river_window_manager_v1 description.
+ </description>
+ </request>
+
<enum name="capabilities" bitfield="true">
<entry name="window_menu" value="1"/>
<entry name="maximize" value="2"/>
@@ -1026,29 +1049,6 @@
<arg name="y" type="int"/>
</request>
- <event name="position">
- <description summary="the absolute position of the node has changed">
- This event indicates the position of the node in the compositor's
- logical coordinate space. The x and y coordinates may be positive or
- negative.
-
- Note that the position of a river_window_v1 refers to the position of
- the window content and is unaffected by the presence of borders or
- decoration surfaces.
-
- While the window manager client usually sets the position of the node,
- there are some cases in which the position must be modified by the
- server in which case this event is sent to inform the client of the new
- position. For example, this event is necessary to communicate the
- results of interactive resize to the window manager client.
-
- This event is sent as part of a rendering update sequence before the
- update_rendering_start event.
- </description>
- <arg name="x" type="int"/>
- <arg name="y" type="int"/>
- </event>
-
<request name="place_top">
<description summary="place node above all other nodes">
This request places the node above all other nodes in the compositor's
@@ -1334,22 +1334,19 @@
river_window_v1.move_requested or river_window_v1.resize_requested
event.
- A set of windows added to the operation with the op_add_move_window and
- op_add_resize_window requests will be moved/resized based on input
+ During the operation, op_delta events will be sent based on input
corresponding to the provided serial (e.g. pointer or touch input).
- All windows must be added to the operation in the same windowing update
- sequence as the op_start_serial request.
-
- No windows are implicitly included in the operation, even the window
- that requested the move/resize must be explicitly added to the
- operation.
+ The window manager may use this operation to implement interactive
+ move/resize of windows by setting the position of windows and proposing
+ dimensions based off of the op_delta events.
The operation continues until the pointer button, touch point or similar
corresponding to the given serial is released or the op_end request is
made and applied during a windowing update sequence.
- This request is ignored if an operation is already in progress.
+ This request is ignored if an operation is already in progress for a
+ given river_seat_v1.
This request modifies windowing state and may only be made as part of a
windowing update sequence, see the river_window_manager_v1 description.
@@ -1359,12 +1356,12 @@
<request name="op_start_pointer">
<description summary="start an interactive pointer operation">
- Start an interactive pointer operation. A set of windows added to the
- operation with the op_add_move_window and op_add_resize_window requests
- will be moved/resized based on the movement of the pointer.
+ Start an interactive pointer operation. During the operation, op_delta
+ events will be sent based on pointer input.
- All windows must be added to the operation in the same windowing update
- sequence as the op_start_serial request.
+ The window manager may use this operation to implement interactive
+ move/resize of windows by setting the position of windows and proposing
+ dimensions based off of the op_delta events.
The pointer operation continues until the op_end request is made during
a windowing update sequence and that update sequence is finished.
@@ -1375,59 +1372,14 @@
</description>
</request>
- <request name="op_add_move_window">
- <description summary="add movement of a window to an operation">
- This request causes the position of a window to be updated by an
- operation started in the same windowing update sequence as this request.
-
- Multiple windows may be added to and moved or resized by a single
- operation.
-
- If a window is added more than once to a single operation, only the most
- recent op_add_move_window or op_add_resize_window request will have an
- effect.
-
- This request is ignored if not made in the same windowing update
- sequence as the op_start_serial or op_start_pointer request that starts
- an operation.
-
- This request modifies windowing state and may only be made as part of a
- windowing update sequence, see the river_window_manager_v1 description.
+ <event name="op_delta">
+ <description summary="total cumulative motion since op start">
+ This event indicates the total change in position since the start of the
+ operation of the pointer/touch point/etc.
</description>
- <arg name="window" type="object" interface="river_window_v1"/>
- </request>
-
- <request name="op_add_resize_window">
- <description summary="add resize of a window to an operation">
- This request causes the position/dimensions of a window to be updated by
- an operation started in the same windowing update sequence as this
- request.
-
- Multiple windows may be added to and moved or resized by a single
- operation.
-
- If a window is added more than once to a single operation, only the most
- recent op_add_move_window or op_add_resize_window request will have an
- effect.
-
- This request is ignored if not made in the same windowing update
- sequence as the op_start_serial or op_start_pointer request that starts
- an operation.
-
- The edges argument determines which edges of the window are resized from
- during the operation.
-
- It is a protocol error to set the edges argument to none. It is also an
- error to set both the top and bottom edges or both the left and right
- edges. Resizing from, for example, both the top and right edges is
- allowed.
-
- This request modifies windowing state and may only be made as part of a
- windowing update sequence, see the river_window_manager_v1 description.
- </description>
- <arg name="window" type="object" interface="river_window_v1"/>
- <arg name="edges" type="uint" enum="river_window_v1.edges"/>
- </request>
+ <arg name="dx" type="int" summary="total change in x"/>
+ <arg name="dy" type="int" summary="total change in y"/>
+ </event>
<request name="op_end">
<description summary="end an interactive operation">
diff --git a/river/Cursor.zig b/river/Cursor.zig
index cfdf21f..4f6de6d 100644
--- a/river/Cursor.zig
+++ b/river/Cursor.zig
@@ -370,7 +370,7 @@ fn updateHovered(cursor: *Cursor) void {
.toplevel => |toplevel| {
// Exclude input regions of the toplevel that extend beyond the window
if (result.surface != null and result.surface.?.getRootSurface() == toplevel.wlr_toplevel.base.surface) {
- if (window.rendering_sent.box.containsPoint(cursor.wlr_cursor.x, cursor.wlr_cursor.y)) {
+ if (window.box.containsPoint(cursor.wlr_cursor.x, cursor.wlr_cursor.y)) {
cursor.seat.windowing_scheduled.window = window;
}
} else {
diff --git a/river/Seat.zig b/river/Seat.zig
index a9541a3..f6505e8 100644
--- a/river/Seat.zig
+++ b/river/Seat.zig
@@ -146,11 +146,7 @@ pointer_bindings: wl.list.Head(PointerBinding, .link),
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
- // Window.pending state and check it at the end of Seat.processEvents() rather than sending
- // configures directly in Seat.updateOp().
- need_configures: bool = false,
+ dirty: bool = false,
input: enum {
pointer,
},
@@ -221,24 +217,6 @@ 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.windowing_requested,
- window,
- }) |state| {
- switch (state.op) {
- .none => {},
- inline .move, .resize => |data| {
- if (data.seat == seat) state.op = .none;
- },
- }
- }
- }
- }
-
seat.link.remove();
seat.link_sent.remove();
@@ -302,10 +280,9 @@ pub fn processEvents(seat: *Seat) void {
assert(server.wm.state == .idle);
if (seat.op) |*op| {
- if (op.need_configures) {
- op.need_configures = false;
- server.wm.state = .update_windowing;
- server.wm.updateWindowingFinish();
+ if (op.dirty) {
+ op.dirty = false;
+ server.wm.dirtyWindowing();
}
}
}
@@ -376,6 +353,10 @@ pub fn updateWindowingStart(seat: *Seat) void {
},
}
+ if (seat.op) |op| {
+ seat_v1.sendOpDelta(op.x - op.start_x, op.y - op.start_y);
+ }
+
{
var it = seat.xkb_bindings.iterator(.forward);
while (it.next()) |binding| {
@@ -463,23 +444,6 @@ fn handleRequest(
if (!server.wm.ensureWindowing()) return;
seat.windowing_requested.op = .start_pointer;
},
- .op_add_move_window => |args| {
- if (!server.wm.ensureWindowing()) return;
- const data = args.window.getUserData() orelse return;
- const window: *Window = @ptrCast(@alignCast(data));
- window.windowing_requested.op = .{ .move = .{
- .seat = seat,
- } };
- },
- .op_add_resize_window => |args| {
- if (!server.wm.ensureWindowing()) return;
- const data = args.window.getUserData() orelse return;
- const window: *Window = @ptrCast(@alignCast(data));
- window.windowing_requested.op = .{ .resize = .{
- .seat = seat,
- .edges = args.edges,
- } };
- },
.op_end => {
if (!server.wm.ensureWindowing()) return;
seat.windowing_requested.op = .end;
@@ -540,39 +504,6 @@ pub fn updateWindowingFinish(seat: *Seat) void {
.y = @intFromFloat(seat.cursor.wlr_cursor.y),
};
seat.cursor.startOpPointer();
-
- {
- var it = server.wm.windows.iterator(.forward);
- while (it.next()) |window| {
- switch (window.windowing_requested.op) {
- .none => {},
- .move => |data| {
- if (data.seat == seat) {
- assert(window.op == .none);
- window.op = .{
- .move = .{
- .seat = seat,
- .start_x = window.rendering_sent.box.x,
- .start_y = window.rendering_sent.box.y,
- },
- };
- }
- },
- .resize => |data| {
- if (data.seat == seat) {
- assert(window.op == .none);
- window.op = .{
- .resize = .{
- .seat = seat,
- .edges = data.edges,
- .start_box = window.rendering_sent.box,
- },
- };
- }
- },
- }
- }
- }
},
.end => if (seat.op) |op| {
log.debug("end seat op", .{});
@@ -580,20 +511,6 @@ pub fn updateWindowingFinish(seat: *Seat) void {
switch (op.input) {
.pointer => seat.cursor.endOpPointer(),
}
-
- {
- var it = server.wm.windows.iterator(.forward);
- while (it.next()) |window| {
- switch (window.op) {
- .none => {},
- inline .move, .resize => |data| {
- if (data.seat == seat) {
- window.op = .none;
- }
- },
- }
- }
- }
},
}
seat.windowing_requested.op = .none;
@@ -774,48 +691,9 @@ pub fn handleSwitchMapping(
pub fn updateOp(seat: *Seat, x: i32, y: i32) void {
const op = &seat.op.?;
-
op.x = x;
op.y = y;
-
- // Position is not updated until the window has committed its new dimensions.
- // The client may not commit exactly the dimensions we request and we need
- // to know the actual committed dimensions to correctly place the top left
- // corner in the case of a resize from the top or left edge.
- {
- var it = server.wm.windows.iterator(.forward);
- while (it.next()) |window| {
- switch (window.op) {
- .none => {},
- .move => |data| {
- if (data.seat != seat) continue;
-
- op.need_configures = true;
- },
- .resize => |data| {
- if (data.seat != seat) continue;
-
- // Total dx/dy since operation start
- const dx = x - op.start_x;
- const dy = y - op.start_y;
-
- if (data.edges.left) {
- window.configure_scheduled.width = @max(1, data.start_box.width - dx);
- } else if (data.edges.right) {
- window.configure_scheduled.width = @max(1, data.start_box.width + dx);
- }
-
- if (data.edges.top) {
- window.configure_scheduled.height = @max(1, data.start_box.height - dy);
- } else if (data.edges.bottom) {
- window.configure_scheduled.height = @max(1, data.start_box.height + dy);
- }
-
- op.need_configures = true;
- },
- }
- }
- }
+ op.dirty = true;
}
pub fn addDevice(seat: *Seat, wlr_device: *wlr.InputDevice) void {
diff --git a/river/Window.zig b/river/Window.zig
index 76b9a6d..b0d4d90 100644
--- a/river/Window.zig
+++ b/river/Window.zig
@@ -169,19 +169,10 @@ windowing_requested: struct {
.fullscreen = true,
.minimize = true,
},
+ resizing: bool = false,
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,
} = .{},
/// State to be sent to the window in the next configure.
@@ -200,32 +191,20 @@ rendering_scheduled: struct {
/// State sent to the wm in the latest rendering update sequence.
rendering_sent: struct {
- box: wlr.Box = .{ .x = 0, .y = 0, .width = 0, .height = 0 },
+ width: u31 = 0,
+ height: u31 = 0,
} = .{},
/// Rendering state requested by the wm.
rendering_requested: struct {
- position: ?struct {
- x: i32,
- y: i32,
- } = null,
+ x: i32 = 0,
+ y: i32 = 0,
hidden: bool = false,
border: Border = .{},
} = .{},
-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,
+/// The currently rendered position/dimensions of the window in the scene graph
+box: wlr.Box = .{ .x = 0, .y = 0, .width = 0, .height = 0 },
pub fn create(impl: Impl) error{OutOfMemory}!*Window {
assert(impl != .none);
@@ -337,8 +316,8 @@ pub fn setDimensions(window: *Window, width: u31, height: u31) void {
window.rendering_scheduled.height = height;
if (window.rendering_scheduled.resend_dimensions or
- window.rendering_scheduled.width != window.rendering_sent.box.width or
- window.rendering_scheduled.height != window.rendering_sent.box.height)
+ window.rendering_scheduled.width != window.rendering_sent.width or
+ window.rendering_scheduled.height != window.rendering_sent.height)
{
server.wm.dirtyRendering();
}
@@ -550,6 +529,14 @@ fn handleRequest(
window.decorations_below.append(decoration);
}
},
+ .inform_resize_start => {
+ if (!server.wm.ensureWindowing()) return;
+ windowing_requested.resizing = true;
+ },
+ .inform_resize_end => {
+ if (!server.wm.ensureWindowing()) return;
+ windowing_requested.resizing = false;
+ },
.set_capabilities => |args| {
if (!server.wm.ensureWindowing()) return;
windowing_requested.capabilities = args.caps;
@@ -592,6 +579,7 @@ pub fn updateWindowingFinish(window: *Window) bool {
window.configure_scheduled.ssd = windowing_requested.ssd;
window.configure_scheduled.tiled = windowing_requested.tiled;
window.configure_scheduled.capabilities = windowing_requested.capabilities;
+ window.configure_scheduled.resizing = windowing_requested.resizing;
window.configure_scheduled.maximized = windowing_requested.maximized;
window.configure_scheduled.fullscreen = windowing_requested.fullscreen;
@@ -613,16 +601,12 @@ pub fn updateWindowingFinish(window: *Window) bool {
}
if (windowing_requested.dimensions) |dimensions| {
- if (window.op == .none) {
- window.configure_scheduled.width = dimensions.width;
- window.configure_scheduled.height = dimensions.height;
- }
+ window.configure_scheduled.width = dimensions.width;
+ window.configure_scheduled.height = dimensions.height;
windowing_requested.dimensions = null;
window.rendering_scheduled.resend_dimensions = true;
}
- windowing_requested.op = .none;
-
const track_configure = switch (window.impl) {
.toplevel => |*toplevel| toplevel.configure(),
.xwayland => |*xwindow| xwindow.configure(),
@@ -642,12 +626,6 @@ pub fn updateRenderingStart(window: *Window) void {
.toplevel => |*toplevel| {
switch (toplevel.configure_state) {
.inflight, .acked => {
- switch (toplevel.configure_state) {
- .inflight => |serial| toplevel.configure_state = .{ .timed_out = serial },
- .acked => toplevel.configure_state = .timed_out_acked,
- else => unreachable,
- }
-
// The transaction has timed out for the xdg toplevel, which means a commit
// in response to the configure with the inflight width/height has not yet
// been made. It may seem that we should therefore leave the current.box
@@ -666,11 +644,18 @@ pub fn updateRenderingStart(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.
+ switch (toplevel.configure_state) {
+ .inflight => |serial| toplevel.configure_state = .{ .timed_out = serial },
+ .acked => toplevel.configure_state = .timed_out_acked,
+ else => unreachable,
+ }
},
- .idle, .committed => {
+ .committed => {
toplevel.configure_state = .idle;
},
- .timed_out, .timed_out_acked => unreachable,
+ // A timed_out or timed_out_acked value is possible in the case of a
+ // windowing update followed by two rendering updates for example.
+ .idle, .timed_out, .timed_out_acked => {},
}
window.rendering_scheduled.width = @intCast(toplevel.geometry.width);
window.rendering_scheduled.height = @intCast(toplevel.geometry.height);
@@ -683,71 +668,35 @@ pub fn updateRenderingStart(window: *Window) void {
}
const sent = &window.rendering_sent;
- var scheduled_box: wlr.Box = .{
- .x = sent.box.x,
- .y = sent.box.y,
- .width = window.rendering_scheduled.width,
- .height = window.rendering_scheduled.height,
- };
-
- switch (window.op) {
- .none => {},
- .move => |data| {
- const seat_op = &data.seat.op.?;
- const dx = seat_op.x - seat_op.start_x;
- const dy = seat_op.y - seat_op.start_y;
- scheduled_box.x = data.start_x + dx;
- scheduled_box.y = data.start_y + dy;
- },
- .resize => |data| {
- assert(data.seat.op != null);
- if (data.edges.left) {
- scheduled_box.x = data.start_box.x + data.start_box.width - scheduled_box.width;
- } else if (data.edges.right) {
- scheduled_box.x = data.start_box.x;
- }
- if (data.edges.top) {
- scheduled_box.y = data.start_box.y + data.start_box.height - scheduled_box.height;
- } else if (data.edges.bottom) {
- scheduled_box.y = data.start_box.y;
- }
- },
- }
+ const scheduled = &window.rendering_scheduled;
- if (scheduled_box.x != sent.box.x or scheduled_box.y != sent.box.y) {
- if (window.node.object) |node_v1| {
- node_v1.sendPosition(scheduled_box.x, scheduled_box.y);
- }
- }
// The check for 0 width/height is necessary to handle timeout of the first configure sent.
- if (!scheduled_box.empty() and
- (window.rendering_scheduled.resend_dimensions or
- scheduled_box.width != sent.box.width or scheduled_box.height != sent.box.height))
+ if (scheduled.width != 0 and scheduled.height != 0 and
+ (scheduled.resend_dimensions or
+ scheduled.width != sent.width or scheduled.height != sent.height))
{
if (window.object) |window_v1| {
- window_v1.sendDimensions(scheduled_box.width, scheduled_box.height);
+ window_v1.sendDimensions(scheduled.width, scheduled.height);
window.rendering_scheduled.resend_dimensions = false;
}
}
- sent.box = scheduled_box;
+ sent.width = scheduled.width;
+ sent.height = scheduled.height;
}
pub fn updateRenderingFinish(window: *Window) void {
window.tree.node.setEnabled(!window.rendering_requested.hidden);
window.popup_tree.node.setEnabled(!window.rendering_requested.hidden);
- const box = &window.rendering_sent.box;
-
- if (window.rendering_requested.position) |position| {
- if (window.op == .none) {
- box.x = position.x;
- box.y = position.y;
- }
- window.rendering_requested.position = null;
- }
+ window.box = .{
+ .x = window.rendering_requested.x,
+ .y = window.rendering_requested.y,
+ .width = window.rendering_sent.width,
+ .height = window.rendering_sent.height,
+ };
- window.tree.node.setPosition(box.x, box.y);
- window.popup_tree.node.setPosition(box.x, box.y);
+ window.tree.node.setPosition(window.box.x, window.box.y);
+ window.popup_tree.node.setPosition(window.box.x, window.box.y);
// f32 cannot represent all u32 values exactly, therefore we must initially use f64
// (which can) and then cast to f32, potentially losing precision.
@@ -763,24 +712,24 @@ pub fn updateRenderingFinish(window: *Window) void {
.x = -@as(i32, border.width),
.y = 0,
.width = border.width,
- .height = box.height,
+ .height = window.box.height,
};
var right: wlr.Box = .{
- .x = box.width,
+ .x = window.box.width,
.y = 0,
.width = border.width,
- .height = box.height,
+ .height = window.box.height,
};
const top: wlr.Box = .{
.x = 0,
.y = -@as(i32, border.width),
- .width = box.width,
+ .width = window.box.width,
.height = border.width,
};
const bottom: wlr.Box = .{
.x = 0,
- .y = box.height,
- .width = box.width,
+ .y = window.box.height,
+ .width = window.box.width,
.height = border.width,
};
diff --git a/river/WmNode.zig b/river/WmNode.zig
index 1120f6a..2315453 100644
--- a/river/WmNode.zig
+++ b/river/WmNode.zig
@@ -104,10 +104,8 @@ fn handleRequest(
if (!server.wm.ensureRendering()) return;
switch (node.get()) {
.window => |window| {
- window.rendering_requested.position = .{
- .x = args.x,
- .y = args.y,
- };
+ window.rendering_requested.x = args.x;
+ window.rendering_requested.y = args.y;
},
.shell_surface => |shell_surface| {
shell_surface.rendering_requested.x = args.x;
diff --git a/rivercompat/Output.zig b/rivercompat/Output.zig
index 636e9a4..7480e32 100644
--- a/rivercompat/Output.zig
+++ b/rivercompat/Output.zig
@@ -112,7 +112,7 @@ pub fn layout(output: *Output) void {
{
var it = output.stack_wm.iterator(.forward);
while (it.next()) |window| {
- if (window.tags & output.tags != 0) {
+ if (window.tags & output.tags != 0 and window.op == .none) {
count += 1;
}
}
@@ -160,6 +160,7 @@ pub fn layout(output: *Output) void {
var it = output.stack_wm.iterator(.forward);
while (it.next()) |window| {
if (window.tags & output.tags == 0) continue;
+ if (window.op != .none) continue;
defer i += 1;
var x: i32 = undefined;
diff --git a/rivercompat/Seat.zig b/rivercompat/Seat.zig
index 9263f2e..95ef662 100644
--- a/rivercompat/Seat.zig
+++ b/rivercompat/Seat.zig
@@ -38,10 +38,16 @@ const State = struct {
action: ?Action = null,
window_interaction: ?*Window = null,
shell_surface_interaction: ?*river.ShellSurfaceV1 = null,
+ op_dx: i32 = 0,
+ op_dy: i32 = 0,
};
seat_v1: *river.SeatV1,
pending: State = .{},
+op: ?struct {
+ dx: i32 = 0,
+ dy: i32 = 0,
+} = null,
focused: ?*Window = null,
focused_output: ?*Output = null,
hovered: ?*Window = null,
@@ -78,6 +84,10 @@ fn handleEvent(seat_v1: *river.SeatV1, event: river.SeatV1.Event, seat: *Seat) v
seat.hovered = null;
}
},
+ .op_delta => |args| {
+ seat.op.?.dx = args.dx;
+ seat.op.?.dy = args.dy;
+ },
.pointer_activity => {},
.window_interaction => |args| {
const window_v1 = args.window orelse return;
@@ -146,22 +156,53 @@ pub fn execute(seat: *Seat, action: Action) void {
}
},
.move_start => {
+ if (seat.op != null) return;
+ seat.op = .{};
seat.seat_v1.opStartPointer();
var it = wm.windows.iterator(.forward);
while (it.next()) |window| {
- seat.seat_v1.opAddMoveWindow(window.window_v1);
+ if (window.op == .none) {
+ window.op = .{ .move = .{
+ .seat = seat,
+ .start_x = window.box.x,
+ .start_y = window.box.y,
+ } };
+ }
}
},
.resize_start => {
- if (seat.hovered) |window| {
- seat.seat_v1.opStartPointer();
- seat.seat_v1.opAddResizeWindow(window.window_v1, .{
- .top = true,
- .left = true,
- });
+ if (seat.op != null) return;
+ seat.op = .{};
+ seat.seat_v1.opStartPointer();
+ var it = wm.windows.iterator(.forward);
+ while (it.next()) |window| {
+ if (window.op == .none) {
+ window.op = .{ .resize = .{
+ .seat = seat,
+ .start_box = window.box,
+ } };
+ window.window_v1.informResizeStart();
+ }
+ }
+ },
+ .op_end => {
+ seat.op = null;
+ seat.seat_v1.opEnd();
+ var it = wm.windows.iterator(.forward);
+ while (it.next()) |window| {
+ switch (window.op) {
+ .none => {},
+ inline .move, .resize => |op| {
+ if (op.seat == seat) {
+ window.op = .none;
+ }
+ if (window.op == .resize) {
+ window.window_v1.informResizeEnd();
+ }
+ },
+ }
}
},
- .op_end => seat.seat_v1.opEnd(),
.spawn => |command| {
if (std.posix.fork()) |pid| {
if (pid == 0) {
diff --git a/rivercompat/Window.zig b/rivercompat/Window.zig
index fabf599..26e7e24 100644
--- a/rivercompat/Window.zig
+++ b/rivercompat/Window.zig
@@ -25,6 +25,7 @@ const wp = wayland.client.wp;
const river = wayland.client.river;
const Output = @import("Output.zig");
+const Seat = @import("Seat.zig");
const wm = &@import("root").wm;
const gpa = std.heap.c_allocator;
@@ -36,14 +37,24 @@ windowing: struct {
closed: bool = false,
},
-x: i32 = 0,
-y: i32 = 0,
-width: i32 = 0,
-height: i32 = 0,
+box: Box = .{ .x = 0, .y = 0, .width = 0, .height = 0 },
output: ?*Output = null,
tags: u32 = 0,
+op: union(enum) {
+ none,
+ move: struct {
+ seat: *Seat,
+ start_x: i32,
+ start_y: i32,
+ },
+ resize: struct {
+ seat: *Seat,
+ start_box: Box,
+ },
+} = .none,
+
link: wl.list.Link,
link_focus: wl.list.Link,
link_wm: wl.list.Link,
@@ -91,8 +102,10 @@ fn handleEvent(window_v1: *river.WindowV1, event: river.WindowV1.Event, window:
.closed => window.windowing.closed = true,
.dimensions_hint => {},
.dimensions => |args| {
- window.width = args.width;
- window.height = args.height;
+ window.box.width = @intCast(args.width);
+ window.box.height = @intCast(args.height);
+ window.box.width += 2 * wm.config.border_width;
+ window.box.height += 2 * wm.config.border_width;
},
.app_id => {},
.title => {},
@@ -156,19 +169,48 @@ pub fn updateWindowing(window: *Window) void {
}
}
+ switch (window.op) {
+ .none, .move => {},
+ .resize => |op| {
+ window.window_v1.setTiled(.{ .top = false, .bottom = false, .left = false, .right = false });
+ // resize from top left corner
+ window.proposeDimensions(
+ @max(1, op.start_box.width - op.seat.op.?.dx),
+ @max(1, op.start_box.height - op.seat.op.?.dy),
+ );
+ },
+ }
+
window.windowing = .{};
}
pub fn updateRendering(window: *Window) void {
- if (window.width != 0 and window.height != 0) {
+ if (window.box.width != 0 and window.box.height != 0) {
window.shadow_surface.attach(window.shadow_buffer, 0, 0);
window.shadow_surface.damageBuffer(0, 0, math.maxInt(i32), math.maxInt(i32));
- window.shadow_viewport.setDestination(window.width + 2 * wm.config.border_width, window.height + 2 * wm.config.border_width);
+ window.shadow_viewport.setDestination(window.box.width + 2 * wm.config.border_width, window.box.height + 2 * wm.config.border_width);
window.shadow_decoration.setOffset(10 - wm.config.border_width, 10 - wm.config.border_width);
window.shadow_decoration.syncNextCommit();
window.shadow_surface.commit();
}
+ switch (window.op) {
+ .none => {},
+ .move => |op| {
+ window.setPosition(
+ op.seat.op.?.dx + op.start_x,
+ op.seat.op.?.dy + op.start_y,
+ );
+ },
+ .resize => |op| {
+ // resize from top left corner
+ window.setPosition(
+ op.start_box.x + (@as(i32, op.start_box.width) - window.box.width),
+ op.start_box.y + (@as(i32, op.start_box.height) - window.box.height),
+ );
+ },
+ }
+
{
var it = wm.seats.iterator(.forward);
while (it.next()) |seat| {
@@ -204,8 +246,21 @@ pub const Box = struct {
};
pub fn layout(window: *Window, box: Box) void {
- window.x = box.x + wm.config.border_width;
- window.y = box.y + wm.config.border_width;
- window.node_v1.setPosition(window.x, window.y);
- window.window_v1.proposeDimensions(box.width - 2 * wm.config.border_width, box.height - 2 * wm.config.border_width);
+ window.setPosition(box.x, box.y);
+ window.proposeDimensions(box.width, box.height);
+}
+
+pub fn setPosition(window: *Window, x: i32, y: i32) void {
+ window.box.x = x;
+ window.box.y = y;
+ window.node_v1.setPosition(x + wm.config.border_width, y + wm.config.border_width);
+}
+
+pub fn proposeDimensions(window: *Window, width: u31, height: u31) void {
+ window.box.width = width;
+ window.box.height = height;
+ window.window_v1.proposeDimensions(
+ @max(1, @as(i32, width) - 2 * wm.config.border_width),
+ @max(1, @as(i32, height) - 2 * wm.config.border_width),
+ );
}