Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
rwm: rework window-initiated interactive move/resize
And implement the now sensible requests/events in river.
protocol/river-window-management-v1.xml | 75 +++++++++++++++------------------
river/Cursor.zig | 13 ++++--
river/Seat.zig | 41 +++++++++++-------
river/Window.zig | 52 ++++++++++++++++-------
river/XdgToplevel.zig | 11 +++--
rivercompat/Seat.zig | 1 +
rivercompat/Window.zig | 4 +-
7 files changed, 113 insertions(+), 84 deletions(-)
diff --git a/protocol/river-window-management-v1.xml b/protocol/river-window-management-v1.xml
index 345f570..112c3b3 100644
--- a/protocol/river-window-management-v1.xml
+++ b/protocol/river-window-management-v1.xml
@@ -659,33 +659,30 @@
<arg name="surface" type="object" interface="wl_surface"/>
</request>
- <event name="move_requested">
- <description summary="the window requested interactive move">
+ <event name="pointer_move_requested">
+ <description summary="window requested interactive pointer move">
This event informs the window manager that the window has requested to
- be interactively moved using the pointer. The seat argument indicates
- the seat for the move and the serial argument identifies the input event
- (e.g. pointer button press or touch) that started the move.
+ be interactively moved using the pointer. The seat argument indicates the
+ seat for the move.
The xdg-shell protocol for example allows windows to request that an
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 request to
+ The window manager may use the river_seat_v1.op_start_pointer request to
interactively move the window or ignore this event entirely.
This event will be followed by a manage_start event after all other new
state has been sent by the server.
</description>
<arg name="seat" type="object" interface="river_seat_v1"/>
- <arg name="serial" type="uint"/>
</event>
- <event name="resize_requested">
- <description summary="the window requested interactive resize">
+ <event name="pointer_resize_requested">
+ <description summary="window requested interactive pointer resize">
This event informs the window manager that the window has requested to
be interactively resized using the pointer. The seat argument indicates
- the seat for the resize and the serial argument identifies the input
- event (e.g. pointer button press or touch) that started the resize.
+ the seat for the resize.
The edges argument indicates which edges the window has requested to be
resized from. The edges argument will never be none and will never have
@@ -695,14 +692,13 @@
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 request to
+ The window manager may use the river_seat_v1.op_start_pointer request to
interactively resize the window or ignore this event entirely.
This event will be followed by a manage_start event after all other new
state has been sent by the server.
</description>
<arg name="seat" type="object" interface="river_seat_v1"/>
- <arg name="serial" type="uint"/>
<arg name="edges" type="uint" enum="edges"/>
</event>
@@ -1417,44 +1413,21 @@
<arg name="shell_surface" type="object" interface="river_shell_surface_v1"/>
</event>
- <request name="op_start_serial">
- <description summary="start an interactive operation with a serial">
- Start an interactive seat operation with a serial from either the
- river_window_v1.move_requested or river_window_v1.resize_requested
- event.
-
- During the operation, op_delta events will be sent based on input
- corresponding to the provided serial (e.g. pointer or touch input).
-
- 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 manage sequence.
-
- This request is ignored if an operation is already in progress for a
- given river_seat_v1.
-
- This request modifies window management state and may only be made as
- part of a manage sequence, see the river_window_manager_v1 description.
- </description>
- <arg name="serial" type="uint"/>
- </request>
-
<request name="op_start_pointer">
<description summary="start an interactive pointer operation">
Start an interactive pointer operation. During the operation, op_delta
events will be sent based on pointer input.
+ When all pointer buttons are released, the op_release event is sent.
+
+ The pointer operation continues until the op_end request is made during
+ a manage sequence and that manage sequence is finished.
+
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 manage sequence and that manage sequence is finished. This request is
- ignored if an operation is already in progress.
+ This request is ignored if an operation is already in progress.
This request modifies window management state and may only be made as
part of a manage sequence, see the river_window_manager_v1 description.
@@ -1465,11 +1438,29 @@
<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.
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
</description>
<arg name="dx" type="int" summary="total change in x"/>
<arg name="dy" type="int" summary="total change in y"/>
</event>
+ <event name="op_release">
+ <description summary="operation input has been released">
+ The input driving the current interactive operation has been released.
+ For a pointer op for example, all pointer buttons have been released.
+
+ Depending on the op type, op_delta events may continue to be sent until
+ the op is ended with the op_end request.
+
+ This event is sent at most once during an interactive operation.
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
+ </description>
+ </event>
+
<request name="op_end">
<description summary="end an interactive operation">
End an interactive operation.
diff --git a/river/Cursor.zig b/river/Cursor.zig
index 446e31b..e4446df 100644
--- a/river/Cursor.zig
+++ b/river/Cursor.zig
@@ -285,7 +285,7 @@ fn clearFocus(cursor: *Cursor) void {
cursor.seat.wlr_seat.pointerNotifyClearFocus();
}
-pub fn startOpPointer(cursor: *Cursor) void {
+pub fn opStartPointer(cursor: *Cursor) void {
if (cursor.constraint) |constraint| {
if (constraint.state == .active) constraint.deactivate();
}
@@ -296,7 +296,7 @@ pub fn startOpPointer(cursor: *Cursor) void {
cursor.clearFocus();
}
-pub fn endOpPointer(cursor: *Cursor) void {
+pub fn opEndPointer(cursor: *Cursor) void {
if (cursor.pressed.count() == 0) {
log.debug("entering cursor mode passthrough", .{});
cursor.mode = .passthrough;
@@ -362,7 +362,7 @@ pub fn processMotionRelative(cursor: *Cursor, event: *const wlr.Pointer.event.Mo
data.delta_y = dy - @trunc(dy);
cursor.wlr_cursor.move(event.device, dx, dy);
- cursor.seat.updateOp(@intFromFloat(cursor.wlr_cursor.x), @intFromFloat(cursor.wlr_cursor.y));
+ cursor.seat.opUpdate(@intFromFloat(cursor.wlr_cursor.x), @intFromFloat(cursor.wlr_cursor.y));
},
}
}
@@ -494,7 +494,12 @@ pub fn processButton(cursor: *Cursor, event: *const wlr.Pointer.event.Button) vo
cursor.passthrough(event.time_msec);
}
},
- .op => {},
+ .op => {
+ if (cursor.pressed.count() == 0) {
+ cursor.seat.wm_scheduled.op_release = true;
+ server.wm.dirtyWindowing();
+ }
+ },
}
} else {
log.err("ignoring duplicate pointer button {d} release", .{event.button});
diff --git a/river/Seat.zig b/river/Seat.zig
index b17bce1..5e9bd33 100644
--- a/river/Seat.zig
+++ b/river/Seat.zig
@@ -121,6 +121,7 @@ wm_scheduled: struct {
window: ?*Window = null,
/// The window clicked on, touched, etc.
interaction: WmFocus = .none,
+ op_release: bool = false,
} = .{},
/// State sent to the wm in the latest manage sequence.
@@ -135,7 +136,6 @@ wm_requested: struct {
focus: WmFocus = .none,
op: union(enum) {
none,
- //TODO start_serial: ?u32,
start_pointer,
end,
} = .none,
@@ -151,6 +151,7 @@ cursor: Cursor,
op: ?struct {
dirty: bool = false,
+ sent_release: bool = false,
input: enum {
pointer,
},
@@ -356,8 +357,14 @@ pub fn manageStart(seat: *Seat) void {
},
}
- if (seat.op) |op| {
+ if (seat.op) |*op| {
seat_v1.sendOpDelta(op.x - op.start_x, op.y - op.start_y);
+
+ if (seat.wm_scheduled.op_release and !op.sent_release) {
+ seat_v1.sendOpRelease();
+ seat.wm_scheduled.op_release = false;
+ op.sent_release = true;
+ }
}
{
@@ -411,6 +418,7 @@ fn handleRequestInert(
fn handleDestroy(_: *river.SeatV1, seat: *Seat) void {
seat.object = null;
+ seat.opEnd();
}
fn handleRequest(
@@ -438,10 +446,6 @@ fn handleRequest(
},
.clear_focus => seat.wm_requested.focus = .none,
- .op_start_serial => {
- if (!server.wm.ensureWindowing()) return;
- // TODO
- },
.op_start_pointer => {
if (!server.wm.ensureWindowing()) return;
seat.wm_requested.op = .start_pointer;
@@ -496,7 +500,8 @@ pub fn manageFinish(seat: *Seat) void {
switch (seat.wm_requested.op) {
.none => {},
- .start_pointer => if (seat.op == null) {
+ .start_pointer,
+ => if (seat.op == null) {
log.debug("start seat op pointer", .{});
seat.op = .{
.input = .pointer,
@@ -505,15 +510,9 @@ pub fn manageFinish(seat: *Seat) void {
.x = @intFromFloat(seat.cursor.wlr_cursor.x),
.y = @intFromFloat(seat.cursor.wlr_cursor.y),
};
- seat.cursor.startOpPointer();
- },
- .end => if (seat.op) |op| {
- log.debug("end seat op", .{});
- seat.op = null;
- switch (op.input) {
- .pointer => seat.cursor.endOpPointer(),
- }
+ seat.cursor.opStartPointer();
},
+ .end => seat.opEnd(),
}
seat.wm_requested.op = .none;
}
@@ -689,13 +688,23 @@ pub fn handleSwitchMapping(
}
}
-pub fn updateOp(seat: *Seat, x: i32, y: i32) void {
+pub fn opUpdate(seat: *Seat, x: i32, y: i32) void {
const op = &seat.op.?;
op.x = x;
op.y = y;
op.dirty = true;
}
+pub fn opEnd(seat: *Seat) void {
+ if (seat.op) |op| {
+ log.debug("end seat op", .{});
+ seat.op = null;
+ switch (op.input) {
+ .pointer => seat.cursor.opEndPointer(),
+ }
+ }
+}
+
pub fn addDevice(seat: *Seat, wlr_device: *wlr.InputDevice, virtual: bool) void {
seat.tryAddDevice(wlr_device, virtual) catch |err| switch (err) {
error.OutOfMemory => log.err("out of memory", .{}),
diff --git a/river/Window.zig b/river/Window.zig
index 6ce8e6a..03b293f 100644
--- a/river/Window.zig
+++ b/river/Window.zig
@@ -145,6 +145,11 @@ wm_scheduled: struct {
fullscreen_requested: FullscreenRequest = .no_request,
dirty_app_id: bool = false,
dirty_title: bool = false,
+ pointer_move_requested: ?*Seat = null,
+ pointer_resize_requested: ?struct {
+ seat: *Seat,
+ edges: river.WindowV1.Edges,
+ } = null,
} = .{},
/// State sent to the wm in the latest manage sequence.
@@ -364,24 +369,24 @@ pub fn manageStart(window: *Window) void {
};
errdefer comptime unreachable;
- const pending = &window.wm_scheduled;
+ const scheduled = &window.wm_scheduled;
const sent = &window.wm_sent;
- // XXX send all dirty pending state
- if (new or !meta.eql(pending.dimensions_hint, sent.dimensions_hint)) {
+ // XXX send all dirty scheduled state
+ if (new or !meta.eql(scheduled.dimensions_hint, sent.dimensions_hint)) {
window_v1.sendDimensionsHint(
- pending.dimensions_hint.min_width,
- pending.dimensions_hint.min_height,
- pending.dimensions_hint.max_width,
- pending.dimensions_hint.max_height,
+ scheduled.dimensions_hint.min_width,
+ scheduled.dimensions_hint.min_height,
+ scheduled.dimensions_hint.max_width,
+ scheduled.dimensions_hint.max_height,
);
- sent.dimensions_hint = pending.dimensions_hint;
+ sent.dimensions_hint = scheduled.dimensions_hint;
}
- if (new or pending.decoration_hint != sent.decoration_hint) {
+ if (new or scheduled.decoration_hint != sent.decoration_hint) {
window_v1.sendDecorationHint(window.wm_scheduled.decoration_hint);
- sent.decoration_hint = pending.decoration_hint;
+ sent.decoration_hint = scheduled.decoration_hint;
}
- switch (pending.fullscreen_requested) {
+ switch (scheduled.fullscreen_requested) {
.no_request => {},
.fullscreen => |output_hint| {
if (output_hint) |output| {
@@ -392,16 +397,31 @@ pub fn manageStart(window: *Window) void {
},
.exit => window_v1.sendExitFullscreenRequested(),
}
- pending.fullscreen_requested = .no_request;
+ scheduled.fullscreen_requested = .no_request;
- if (new or pending.dirty_app_id) {
+ if (new or scheduled.dirty_app_id) {
window_v1.sendAppId(window.getAppId());
- pending.dirty_app_id = false;
+ scheduled.dirty_app_id = false;
}
- if (new or pending.dirty_title) {
+ if (new or scheduled.dirty_title) {
window_v1.sendTitle(window.getTitle());
- pending.dirty_title = false;
+ scheduled.dirty_title = false;
+ }
+
+ if (scheduled.pointer_move_requested) |seat| {
+ if (seat.object) |seat_v1| {
+ log.debug("send pointer move requested", .{});
+ window_v1.sendPointerMoveRequested(seat_v1);
+ }
+ }
+ scheduled.pointer_move_requested = null;
+ if (scheduled.pointer_resize_requested) |data| {
+ if (data.seat.object) |seat_v1| {
+ log.debug("send pointer resize requested", .{});
+ window_v1.sendPointerResizeRequested(seat_v1, data.edges);
+ }
}
+ scheduled.pointer_resize_requested = null;
},
}
}
diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig
index a9259c8..a93aa87 100644
--- a/river/XdgToplevel.zig
+++ b/river/XdgToplevel.zig
@@ -383,23 +383,26 @@ fn handleRequestMove(
event: *wlr.XdgToplevel.event.Move,
) void {
const toplevel: *XdgToplevel = @fieldParentPtr("request_move", listener);
- _ = toplevel;
const seat: *Seat = @alignCast(@ptrCast(event.seat.seat.data));
// Moving windows with touch or tablet tool is not yet supported.
if (seat.wlr_seat.validatePointerGrabSerial(null, event.serial)) {
- // XXX queue pointer_move_requested, dirtyWindowing()
+ toplevel.window.wm_scheduled.pointer_move_requested = seat;
+ server.wm.dirtyWindowing();
}
}
fn handleRequestResize(listener: *wl.Listener(*wlr.XdgToplevel.event.Resize), event: *wlr.XdgToplevel.event.Resize) void {
const toplevel: *XdgToplevel = @fieldParentPtr("request_resize", listener);
- _ = toplevel;
const seat: *Seat = @alignCast(@ptrCast(event.seat.seat.data));
// Resizing windows with touch or tablet tool is not yet supported.
if (seat.wlr_seat.validatePointerGrabSerial(null, event.serial)) {
- // XXX queue pointer_resize_requested, dirtyWindowing()
+ toplevel.window.wm_scheduled.pointer_resize_requested = .{
+ .seat = seat,
+ .edges = @bitCast(event.edges),
+ };
+ server.wm.dirtyWindowing();
}
}
diff --git a/rivercompat/Seat.zig b/rivercompat/Seat.zig
index b9493c0..9571acb 100644
--- a/rivercompat/Seat.zig
+++ b/rivercompat/Seat.zig
@@ -83,6 +83,7 @@ fn handleEvent(seat_v1: *river.SeatV1, event: river.SeatV1.Event, seat: *Seat) v
seat.op.?.dx = args.dx;
seat.op.?.dy = args.dy;
},
+ .op_release => {},
.pointer_activity => {},
.window_interaction => |args| {
const window_v1 = args.window orelse return;
diff --git a/rivercompat/Window.zig b/rivercompat/Window.zig
index 94274c8..2fd8c66 100644
--- a/rivercompat/Window.zig
+++ b/rivercompat/Window.zig
@@ -111,8 +111,8 @@ fn handleEvent(window_v1: *river.WindowV1, event: river.WindowV1.Event, window:
.title => {},
.parent => {},
.decoration_hint => {},
- .move_requested => {},
- .resize_requested => {},
+ .pointer_move_requested => {},
+ .pointer_resize_requested => {},
.show_window_menu_requested => {},
.maximize_requested => {},
.unmaximize_requested => {},