aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.builds/alpine.yml4
-rw-r--r--.builds/archlinux.yml4
-rw-r--r--.builds/freebsd.yml5
-rw-r--r--README.md2
m---------deps/zig-wlroots0
-rw-r--r--river/Cursor.zig71
-rw-r--r--river/DragIcon.zig50
-rw-r--r--river/IdleInhibitorManager.zig2
-rw-r--r--river/Keyboard.zig12
-rw-r--r--river/LayerSurface.zig27
-rw-r--r--river/LockManager.zig19
-rw-r--r--river/LockSurface.zig13
-rw-r--r--river/Output.zig96
-rw-r--r--river/Root.zig24
-rw-r--r--river/SceneNodeData.zig6
-rw-r--r--river/Seat.zig2
-rw-r--r--river/Server.zig20
-rw-r--r--river/XdgDecoration.zig16
-rw-r--r--river/XdgToplevel.zig27
-rw-r--r--river/XwaylandOverrideRedirect.zig54
-rw-r--r--river/XwaylandView.zig56
21 files changed, 237 insertions, 273 deletions
diff --git a/.builds/alpine.yml b/.builds/alpine.yml
index 2cd5417..4b8dcf0 100644
--- a/.builds/alpine.yml
+++ b/.builds/alpine.yml
@@ -27,13 +27,13 @@ sources:
tasks:
- install_deps: |
cd wayland
- git checkout 1.21.0
+ git checkout 1.22.0
meson setup build -Ddocumentation=false -Dtests=false --prefix /usr
sudo ninja -C build install
cd ..
cd wlroots
- git checkout 0.16.0
+ git checkout 0.17.0
meson setup build --auto-features=enabled -Drenderers=gles2 -Dexamples=false \
-Dwerror=false -Db_ndebug=false -Dxcb-errors=disabled --prefix /usr
sudo ninja -C build/ install
diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml
index 8b7db18..9ea597a 100644
--- a/.builds/archlinux.yml
+++ b/.builds/archlinux.yml
@@ -25,13 +25,13 @@ sources:
tasks:
- install_deps: |
cd wayland
- git checkout 1.21.0
+ git checkout 1.22.0
meson setup build -Ddocumentation=false -Dtests=false --prefix /usr
sudo ninja -C build install
cd ..
cd wlroots
- git checkout 0.16.0
+ git checkout 0.17.0
meson setup build --auto-features=enabled -Drenderers=gles2 -Dexamples=false \
-Dwerror=false -Db_ndebug=false --prefix /usr
sudo ninja -C build/ install
diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml
index 5207f68..367f5c0 100644
--- a/.builds/freebsd.yml
+++ b/.builds/freebsd.yml
@@ -19,6 +19,7 @@ packages:
- x11/xcb-util-wm
- x11-servers/xwayland
- sysutils/seatd
+ - sysutils/libdisplay-info
- gmake
- scdoc
- wget
@@ -29,13 +30,13 @@ sources:
tasks:
- install_deps: |
cd wayland
- git checkout 1.21.0
+ git checkout 1.22.0
meson setup build -Ddocumentation=false -Dtests=false --prefix /usr
sudo ninja -C build install
cd ..
cd wlroots
- git checkout 0.16.0
+ git checkout 0.17.0
meson setup build --auto-features=enabled -Drenderers=gles2 -Dexamples=false \
-Dwerror=false -Db_ndebug=false --prefix /usr
sudo ninja -C build/ install
diff --git a/README.md b/README.md
index 618cf7d..e36f8ad 100644
--- a/README.md
+++ b/README.md
@@ -38,7 +38,7 @@ distribution.
- [zig](https://ziglang.org/download/) 0.11
- wayland
- wayland-protocols
-- [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) 0.16
+- [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) 0.17
- xkbcommon
- libevdev
- pixman
diff --git a/deps/zig-wlroots b/deps/zig-wlroots
-Subproject 0f07b2c666125d06529dfc688da4e71bff9a04f
+Subproject a4e100599b9f742215fa09afce8c56cffea2e79
diff --git a/river/Cursor.zig b/river/Cursor.zig
index e4ab7b3..b288ee2 100644
--- a/river/Cursor.zig
+++ b/river/Cursor.zig
@@ -98,37 +98,6 @@ const Mode = union(enum) {
},
};
-const Image = enum {
- /// The current image of the cursor is unknown, perhaps because it was set by a client.
- unknown,
- left_ptr,
- move,
- @"n-resize",
- @"s-resize",
- @"w-resize",
- @"e-resize",
- @"nw-resize",
- @"ne-resize",
- @"sw-resize",
- @"se-resize",
-
- fn resize(edges: wlr.Edges) Image {
- assert(!(edges.top and edges.bottom));
- assert(!(edges.left and edges.right));
-
- if (edges.top and edges.left) return .@"nw-resize";
- if (edges.top and edges.right) return .@"ne-resize";
- if (edges.bottom and edges.left) return .@"sw-resize";
- if (edges.bottom and edges.right) return .@"se-resize";
- if (edges.top) return .@"n-resize";
- if (edges.bottom) return .@"s-resize";
- if (edges.left) return .@"w-resize";
- if (edges.right) return .@"e-resize";
-
- return .@"se-resize";
- }
-};
-
const default_size = 24;
const LayoutPoint = struct {
@@ -150,9 +119,12 @@ inflight_mode: Mode = .passthrough,
seat: *Seat,
wlr_cursor: *wlr.Cursor,
pointer_gestures: *wlr.PointerGesturesV1,
-xcursor_manager: *wlr.XcursorManager,
-image: Image = .unknown,
+/// Xcursor manager for the currently configured Xcursor theme.
+xcursor_manager: *wlr.XcursorManager,
+/// Name of the current Xcursor shape, or null if a client has configured a
+/// surface to be used as the cursor shape instead.
+xcursor_name: ?[*:0]const u8 = null,
/// Number of distinct buttons currently pressed
pressed_count: u32 = 0,
@@ -301,26 +273,15 @@ pub fn setTheme(self: *Self, theme: ?[*:0]const u8, _size: ?u32) !void {
@intCast(image.hotspot_y),
);
}
-
- if (self.image != .unknown) {
- self.xcursor_manager.setCursorImage(@tagName(self.image), self.wlr_cursor);
- }
}
-}
-/// It seems that setCursorImage is actually fairly expensive to call repeatedly
-/// as it does no checks to see if the the given image is already set. Therefore,
-/// do that check here.
-fn setImage(self: *Self, image: Image) void {
- assert(image != .unknown);
-
- if (image == self.image) return;
- self.image = image;
- self.xcursor_manager.setCursorImage(@tagName(image), self.wlr_cursor);
+ if (self.xcursor_name) |name| {
+ self.wlr_cursor.setXcursor(self.xcursor_manager, name);
+ }
}
fn clearFocus(self: *Self) void {
- self.setImage(.left_ptr);
+ self.wlr_cursor.setXcursor(self.xcursor_manager, "left_ptr");
self.seat.wlr_seat.pointerNotifyClearFocus();
}
@@ -685,15 +646,15 @@ fn handleRequestSetCursor(
// cursor moves between outputs.
log.debug("focused client set cursor", .{});
self.wlr_cursor.setSurface(event.surface, event.hotspot_x, event.hotspot_y);
- self.image = .unknown;
+ self.xcursor_name = null;
}
}
pub fn hide(self: *Self) void {
if (self.pressed_count > 0) return;
self.hidden = true;
- self.wlr_cursor.setImage(null, 0, 0, 0, 0, 0, 0);
- self.image = .unknown;
+ self.wlr_cursor.unsetImage();
+ self.xcursor_name = null;
self.seat.wlr_seat.pointerNotifyClearFocus();
self.hide_cursor_timer.timerUpdate(0) catch {
log.err("failed to update cursor hide timeout", .{});
@@ -725,7 +686,7 @@ pub fn startMove(cursor: *Self, view: *View) void {
.offset_x = @as(i32, @intFromFloat(cursor.wlr_cursor.x)) - view.current.box.x,
.offset_y = @as(i32, @intFromFloat(cursor.wlr_cursor.y)) - view.current.box.y,
} };
- cursor.enterMode(new_mode, view, .move);
+ cursor.enterMode(new_mode, view, "move");
}
pub fn startResize(cursor: *Self, view: *View, proposed_edges: ?wlr.Edges) void {
@@ -758,7 +719,7 @@ pub fn startResize(cursor: *Self, view: *View, proposed_edges: ?wlr.Edges) void
.initial_width = @intCast(box.width),
.initial_height = @intCast(box.height),
} };
- cursor.enterMode(new_mode, view, Image.resize(edges));
+ cursor.enterMode(new_mode, view, wlr.Xcursor.getResizeName(edges));
}
fn computeEdges(cursor: *const Self, view: *const View) wlr.Edges {
@@ -798,7 +759,7 @@ fn computeEdges(cursor: *const Self, view: *const View) wlr.Edges {
}
}
-fn enterMode(cursor: *Self, mode: Mode, view: *View, image: Image) void {
+fn enterMode(cursor: *Self, mode: Mode, view: *View, xcursor_name: [*:0]const u8) void {
assert(cursor.mode == .passthrough or cursor.mode == .down);
assert(mode == .move or mode == .resize);
@@ -814,7 +775,7 @@ fn enterMode(cursor: *Self, mode: Mode, view: *View, image: Image) void {
}
cursor.seat.wlr_seat.pointerNotifyClearFocus();
- cursor.setImage(image);
+ cursor.wlr_cursor.setXcursor(cursor.xcursor_manager, xcursor_name);
server.root.applyPending();
}
diff --git a/river/DragIcon.zig b/river/DragIcon.zig
index a7f15ba..2505e4b 100644
--- a/river/DragIcon.zig
+++ b/river/DragIcon.zig
@@ -27,43 +27,33 @@ const Cursor = @import("Cursor.zig");
const SceneNodeData = @import("SceneNodeData.zig");
wlr_drag_icon: *wlr.Drag.Icon,
-
-tree: *wlr.SceneTree,
-surface: *wlr.SceneTree,
+scene_drag_icon: *wlr.SceneTree,
destroy: wl.Listener(*wlr.Drag.Icon) = wl.Listener(*wlr.Drag.Icon).init(handleDestroy),
-map: wl.Listener(*wlr.Drag.Icon) = wl.Listener(*wlr.Drag.Icon).init(handleMap),
-unmap: wl.Listener(*wlr.Drag.Icon) = wl.Listener(*wlr.Drag.Icon).init(handleUnmap),
-commit: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleCommit),
pub fn create(wlr_drag_icon: *wlr.Drag.Icon, cursor: *Cursor) error{OutOfMemory}!void {
- const tree = try server.root.drag_icons.createSceneTree();
- errdefer tree.node.destroy();
+ const scene_drag_icon = try server.root.drag_icons.createSceneDragIcon(wlr_drag_icon);
+ errdefer scene_drag_icon.node.destroy();
const drag_icon = try util.gpa.create(DragIcon);
errdefer util.gpa.destroy(drag_icon);
drag_icon.* = .{
.wlr_drag_icon = wlr_drag_icon,
- .tree = tree,
- .surface = try tree.createSceneSubsurfaceTree(wlr_drag_icon.surface),
+ .scene_drag_icon = scene_drag_icon,
};
- tree.node.data = @intFromPtr(drag_icon);
+ scene_drag_icon.node.data = @intFromPtr(drag_icon);
drag_icon.updatePosition(cursor);
- tree.node.setEnabled(wlr_drag_icon.mapped);
wlr_drag_icon.events.destroy.add(&drag_icon.destroy);
- wlr_drag_icon.events.map.add(&drag_icon.map);
- wlr_drag_icon.events.unmap.add(&drag_icon.unmap);
- wlr_drag_icon.surface.events.commit.add(&drag_icon.commit);
}
pub fn updatePosition(drag_icon: *DragIcon, cursor: *Cursor) void {
switch (drag_icon.wlr_drag_icon.drag.grab_type) {
.keyboard => unreachable,
.keyboard_pointer => {
- drag_icon.tree.node.setPosition(
+ drag_icon.scene_drag_icon.node.setPosition(
@intFromFloat(cursor.wlr_cursor.x),
@intFromFloat(cursor.wlr_cursor.y),
);
@@ -71,7 +61,7 @@ pub fn updatePosition(drag_icon: *DragIcon, cursor: *Cursor) void {
.keyboard_touch => {
const touch_id = drag_icon.wlr_drag_icon.drag.touch_id;
if (cursor.touch_points.get(touch_id)) |point| {
- drag_icon.tree.node.setPosition(
+ drag_icon.scene_drag_icon.node.setPosition(
@intFromFloat(point.lx),
@intFromFloat(point.ly),
);
@@ -83,33 +73,7 @@ pub fn updatePosition(drag_icon: *DragIcon, cursor: *Cursor) void {
fn handleDestroy(listener: *wl.Listener(*wlr.Drag.Icon), _: *wlr.Drag.Icon) void {
const drag_icon = @fieldParentPtr(DragIcon, "destroy", listener);
- drag_icon.tree.node.destroy();
-
drag_icon.destroy.link.remove();
- drag_icon.map.link.remove();
- drag_icon.unmap.link.remove();
- drag_icon.commit.link.remove();
util.gpa.destroy(drag_icon);
}
-
-fn handleMap(listener: *wl.Listener(*wlr.Drag.Icon), _: *wlr.Drag.Icon) void {
- const drag_icon = @fieldParentPtr(DragIcon, "map", listener);
-
- drag_icon.tree.node.setEnabled(true);
-}
-
-fn handleUnmap(listener: *wl.Listener(*wlr.Drag.Icon), _: *wlr.Drag.Icon) void {
- const drag_icon = @fieldParentPtr(DragIcon, "unmap", listener);
-
- drag_icon.tree.node.setEnabled(false);
-}
-
-fn handleCommit(listener: *wl.Listener(*wlr.Surface), surface: *wlr.Surface) void {
- const drag_icon = @fieldParentPtr(DragIcon, "commit", listener);
-
- drag_icon.surface.node.setPosition(
- drag_icon.surface.node.x + surface.current.dx,
- drag_icon.surface.node.y + surface.current.dy,
- );
-}
diff --git a/river/IdleInhibitorManager.zig b/river/IdleInhibitorManager.zig
index 945f3f8..b64f9aa 100644
--- a/river/IdleInhibitorManager.zig
+++ b/river/IdleInhibitorManager.zig
@@ -44,7 +44,7 @@ pub fn idleInhibitCheckActive(self: *Self) void {
}
},
.layer_surface => |layer_surface| {
- if (layer_surface.wlr_layer_surface.mapped) {
+ if (layer_surface.wlr_layer_surface.surface.mapped) {
inhibited = true;
break;
}
diff --git a/river/Keyboard.zig b/river/Keyboard.zig
index b98d9ec..568117d 100644
--- a/river/Keyboard.zig
+++ b/river/Keyboard.zig
@@ -147,13 +147,11 @@ fn handleBuiltinMapping(keysym: xkb.Keysym) bool {
switch (@intFromEnum(keysym)) {
xkb.Keysym.XF86Switch_VT_1...xkb.Keysym.XF86Switch_VT_12 => {
log.debug("switch VT keysym received", .{});
- if (server.backend.isMulti()) {
- if (server.backend.getSession()) |session| {
- const vt = @intFromEnum(keysym) - xkb.Keysym.XF86Switch_VT_1 + 1;
- const log_server = std.log.scoped(.server);
- log_server.info("switching to VT {}", .{vt});
- session.changeVt(vt) catch log_server.err("changing VT failed", .{});
- }
+ if (server.session) |session| {
+ const vt = @intFromEnum(keysym) - xkb.Keysym.XF86Switch_VT_1 + 1;
+ const log_server = std.log.scoped(.server);
+ log_server.info("switching to VT {}", .{vt});
+ session.changeVt(vt) catch log_server.err("changing VT failed", .{});
}
return true;
},
diff --git a/river/LayerSurface.zig b/river/LayerSurface.zig
index 5fea847..3ab20dc 100644
--- a/river/LayerSurface.zig
+++ b/river/LayerSurface.zig
@@ -37,8 +37,8 @@ scene_layer_surface: *wlr.SceneLayerSurfaceV1,
popup_tree: *wlr.SceneTree,
destroy: wl.Listener(*wlr.LayerSurfaceV1) = wl.Listener(*wlr.LayerSurfaceV1).init(handleDestroy),
-map: wl.Listener(*wlr.LayerSurfaceV1) = wl.Listener(*wlr.LayerSurfaceV1).init(handleMap),
-unmap: wl.Listener(*wlr.LayerSurfaceV1) = wl.Listener(*wlr.LayerSurfaceV1).init(handleUnmap),
+map: wl.Listener(void) = wl.Listener(void).init(handleMap),
+unmap: wl.Listener(void) = wl.Listener(void).init(handleUnmap),
commit: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleCommit),
new_popup: wl.Listener(*wlr.XdgPopup) = wl.Listener(*wlr.XdgPopup).init(handleNewPopup),
@@ -63,8 +63,8 @@ pub fn create(wlr_layer_surface: *wlr.LayerSurfaceV1) error{OutOfMemory}!void {
wlr_layer_surface.surface.data = @intFromPtr(&layer_surface.scene_layer_surface.tree.node);
wlr_layer_surface.events.destroy.add(&layer_surface.destroy);
- wlr_layer_surface.events.map.add(&layer_surface.map);
- wlr_layer_surface.events.unmap.add(&layer_surface.unmap);
+ wlr_layer_surface.surface.events.map.add(&layer_surface.map);
+ wlr_layer_surface.surface.events.unmap.add(&layer_surface.unmap);
wlr_layer_surface.surface.events.commit.add(&layer_surface.commit);
wlr_layer_surface.events.new_popup.add(&layer_surface.new_popup);
@@ -96,20 +96,20 @@ fn handleDestroy(listener: *wl.Listener(*wlr.LayerSurfaceV1), _: *wlr.LayerSurfa
util.gpa.destroy(layer_surface);
}
-fn handleMap(listener: *wl.Listener(*wlr.LayerSurfaceV1), wlr_layer_surface: *wlr.LayerSurfaceV1) void {
+fn handleMap(listener: *wl.Listener(void)) void {
const layer_surface = @fieldParentPtr(LayerSurface, "map", listener);
- log.debug("layer surface '{s}' mapped", .{wlr_layer_surface.namespace});
+ log.debug("layer surface '{s}' mapped", .{layer_surface.wlr_layer_surface.namespace});
layer_surface.output.arrangeLayers();
handleKeyboardInteractiveExclusive(layer_surface.output);
server.root.applyPending();
}
-fn handleUnmap(listener: *wl.Listener(*wlr.LayerSurfaceV1), wlr_layer_surface: *wlr.LayerSurfaceV1) void {
+fn handleUnmap(listener: *wl.Listener(void)) void {
const layer_surface = @fieldParentPtr(LayerSurface, "unmap", listener);
- log.debug("layer surface '{s}' unmapped", .{wlr_layer_surface.namespace});
+ log.debug("layer surface '{s}' unmapped", .{layer_surface.wlr_layer_surface.namespace});
layer_surface.output.arrangeLayers();
handleKeyboardInteractiveExclusive(layer_surface.output);
@@ -128,10 +128,9 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
layer_surface.scene_layer_surface.tree.node.reparent(tree);
}
- // If a surface is committed while it is not mapped, we must send a configure.
- // TODO: this mapped check is not correct as it will be true in the commit
- // that triggers the unmap as well.
- if (!wlr_layer_surface.mapped or @as(u32, @bitCast(wlr_layer_surface.current.committed)) != 0) {
+ if (wlr_layer_surface.initial_commit or
+ @as(u32, @bitCast(wlr_layer_surface.current.committed)) != 0)
+ {
layer_surface.output.arrangeLayers();
handleKeyboardInteractiveExclusive(layer_surface.output);
server.root.applyPending();
@@ -153,7 +152,7 @@ fn handleKeyboardInteractiveExclusive(output: *Output) void {
if (@as(?*SceneNodeData, @ptrFromInt(node.data))) |node_data| {
const layer_surface = node_data.data.layer_surface;
const wlr_layer_surface = layer_surface.wlr_layer_surface;
- if (wlr_layer_surface.mapped and
+ if (wlr_layer_surface.surface.mapped and
wlr_layer_surface.current.keyboard_interactive == .exclusive)
{
break :outer layer_surface;
@@ -179,7 +178,7 @@ fn handleKeyboardInteractiveExclusive(output: *Output) void {
const current_focus = seat.focused.layer.wlr_layer_surface;
// If the seat is currently focusing an unmapped layer surface or one
// without keyboard interactivity, stop focusing that layer surface.
- if (!current_focus.mapped or current_focus.current.keyboard_interactive == .none) {
+ if (!current_focus.surface.mapped or current_focus.current.keyboard_interactive == .none) {
seat.setFocusRaw(.{ .none = {} });
}
}
diff --git a/river/LockManager.zig b/river/LockManager.zig
index a500260..ae5db2c 100644
--- a/river/LockManager.zig
+++ b/river/LockManager.zig
@@ -28,6 +28,7 @@ const server = &@import("main.zig").server;
const util = @import("util.zig");
const LockSurface = @import("LockSurface.zig");
+const Output = @import("Output.zig");
const log = std.log.scoped(.session_lock);
@@ -190,12 +191,6 @@ pub fn maybeLock(manager: *LockManager) void {
fn handleUnlock(listener: *wl.Listener(void)) void {
const manager = @fieldParentPtr(LockManager, "unlock", listener);
- // TODO(wlroots): this will soon be handled by the wlroots session lock implementation
- if (manager.state != .locked) {
- manager.lock.?.resource.postError(.invalid_unlock, "the locked event was never sent");
- return;
- }
-
manager.state = .unlocked;
log.info("session unlocked", .{});
@@ -263,3 +258,15 @@ fn handleSurface(
wlr_lock_surface.resource.postNoMemory();
};
}
+
+pub fn updateLockSurfaceSize(manager: *LockManager, output: *Output) void {
+ const lock = manager.lock orelse return;
+
+ var it = lock.surfaces.iterator(.forward);
+ while (it.next()) |wlr_lock_surface| {
+ const lock_surface: *LockSurface = @ptrFromInt(wlr_lock_surface.data);
+ if (output == lock_surface.getOutput()) {
+ lock_surface.configure();
+ }
+ }
+}
diff --git a/river/LockSurface.zig b/river/LockSurface.zig
index 2de43ee..9c8ec94 100644
--- a/river/LockSurface.zig
+++ b/river/LockSurface.zig
@@ -33,7 +33,6 @@ lock: *wlr.SessionLockV1,
idle_update_focus: ?*wl.EventSource = null,
-output_mode: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleOutputMode),
map: wl.Listener(void) = wl.Listener(void).init(handleMap),
surface_destroy: wl.Listener(void) = wl.Listener(void).init(handleDestroy),
@@ -55,11 +54,10 @@ pub fn create(wlr_lock_surface: *wlr.SessionLockSurfaceV1, lock: *wlr.SessionLoc
wlr_lock_surface.surface.data = @intFromPtr(&tree.node);
- wlr_lock_surface.output.events.mode.add(&lock_surface.output_mode);
- wlr_lock_surface.events.map.add(&lock_surface.map);
+ wlr_lock_surface.surface.events.map.add(&lock_surface.map);
wlr_lock_surface.events.destroy.add(&lock_surface.surface_destroy);
- handleOutputMode(&lock_surface.output_mode, wlr_lock_surface.output);
+ lock_surface.configure();
}
pub fn destroy(lock_surface: *LockSurface) void {
@@ -84,20 +82,17 @@ pub fn destroy(lock_surface: *LockSurface) void {
event_source.remove();
}
- lock_surface.output_mode.link.remove();
lock_surface.map.link.remove();
lock_surface.surface_destroy.link.remove();
util.gpa.destroy(lock_surface);
}
-fn getOutput(lock_surface: *LockSurface) *Output {
+pub fn getOutput(lock_surface: *LockSurface) *Output {
return @ptrFromInt(lock_surface.wlr_lock_surface.output.data);
}
-fn handleOutputMode(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void {
- const lock_surface = @fieldParentPtr(LockSurface, "output_mode", listener);
-
+pub fn configure(lock_surface: *LockSurface) void {
var output_width: i32 = undefined;
var output_height: i32 = undefined;
lock_surface.getOutput().wlr_output.effectiveResolution(&output_width, &output_height);
diff --git a/river/Output.zig b/river/Output.zig
index 09c8d49..35b98ea 100644
--- a/river/Output.zig
+++ b/river/Output.zig
@@ -184,8 +184,7 @@ layout: ?*Layout = null,
status: OutputStatus,
destroy: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleDestroy),
-enable: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleEnable),
-mode: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleMode),
+request_state: wl.Listener(*wlr.Output.event.RequestState) = wl.Listener(*wlr.Output.event.RequestState).init(handleRequestState),
frame: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleFrame),
present: wl.Listener(*wlr.Output.event.Present) = wl.Listener(*wlr.Output.event.Present).init(handlePresent),
@@ -195,23 +194,19 @@ pub fn create(wlr_output: *wlr.Output) !void {
if (!wlr_output.initRender(server.allocator, server.renderer)) return error.InitRenderFailed;
+ var state = wlr.Output.State.init();
+ defer state.finish();
+
+ state.setEnabled(true);
+
if (wlr_output.preferredMode()) |preferred_mode| {
- wlr_output.setMode(preferred_mode);
- wlr_output.enable(true);
- wlr_output.commit() catch {
- var it = wlr_output.modes.iterator(.forward);
- while (it.next()) |mode| {
- if (mode == preferred_mode) continue;
- wlr_output.setMode(mode);
- wlr_output.commit() catch continue;
- // This mode works, use it
- break;
- }
- // If no mode works, then we will just leave the output disabled.
- // Perhaps the user will want to set a custom mode using wlr-output-management.
- };
+ state.setMode(preferred_mode);
}
+ // Ignore failure here and create the Output anyways.
+ // It will stay disabled unless the user configures a custom mode which may work.
+ _ = wlr_output.commitState(&state);
+
var width: c_int = undefined;
var height: c_int = undefined;
wlr_output.effectiveResolution(&width, &height);
@@ -270,8 +265,7 @@ pub fn create(wlr_output: *wlr.Output) !void {
output.layers.fullscreen.node.setEnabled(false);
wlr_output.events.destroy.add(&output.destroy);
- wlr_output.events.enable.add(&output.enable);
- wlr_output.events.mode.add(&output.mode);
+ wlr_output.events.request_state.add(&output.request_state);
wlr_output.events.frame.add(&output.frame);
wlr_output.events.present.add(&output.present);
@@ -289,7 +283,7 @@ pub fn create(wlr_output: *wlr.Output) !void {
output.active_link.init();
server.root.all_outputs.append(output);
- handleEnable(&output.enable, wlr_output);
+ output.handleEnable();
}
pub fn layerSurfaceTree(self: Self, layer: zwlr.LayerShellV1.Layer) *wlr.SceneTree {
@@ -370,9 +364,8 @@ fn handleDestroy(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void {
output.all_link.remove();
output.destroy.link.remove();
- output.enable.link.remove();
+ output.request_state.link.remove();
output.frame.link.remove();
- output.mode.link.remove();
output.present.link.remove();
output.tree.node.destroy();
@@ -386,42 +379,53 @@ fn handleDestroy(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void {
server.root.applyPending();
}
-fn handleEnable(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void {
- const self = @fieldParentPtr(Self, "enable", listener);
+fn handleRequestState(listener: *wl.Listener(*wlr.Output.event.RequestState), event: *wlr.Output.event.RequestState) void {
+ const output = @fieldParentPtr(Self, "request_state", listener);
+
+ // TODO double buffer output state changes for frame perfection and cleaner code.
+ // Schedule a frame and commit in the frame handler.
+ if (!output.wlr_output.commitState(event.state)) {
+ log.err("failed to commit requested state", .{});
+ return;
+ }
+
+ if (event.state.committed.enabled) {
+ output.handleEnable();
+ }
+ if (event.state.committed.mode) {
+ output.updateBackgroundRect();
+ output.arrangeLayers();
+ server.lock_manager.updateLockSurfaceSize(output);
+ server.root.applyPending();
+ }
+}
+
+fn handleEnable(output: *Self) void {
// We can't assert the current state of normal_content/locked_content
// here as this output may be newly created.
- if (wlr_output.enabled) {
+ if (output.wlr_output.enabled) {
switch (server.lock_manager.state) {
.unlocked => {
- assert(self.lock_render_state == .blanked);
- self.normal_content.node.setEnabled(true);
- self.locked_content.node.setEnabled(false);
+ assert(output.lock_render_state == .blanked);
+ output.normal_content.node.setEnabled(true);
+ output.locked_content.node.setEnabled(false);
},
.waiting_for_lock_surfaces, .waiting_for_blank, .locked => {
- assert(self.lock_render_state == .blanked);
- self.normal_content.node.setEnabled(false);
- self.locked_content.node.setEnabled(true);
+ assert(output.lock_render_state == .blanked);
+ output.normal_content.node.setEnabled(false);
+ output.locked_content.node.setEnabled(true);
},
}
+ // Add the output to root.active_outputs and the output layout if it has not
+ // already been added.
+ server.root.activateOutput(output);
} else {
// Disabling and re-enabling an output always blanks it.
- self.lock_render_state = .blanked;
- self.normal_content.node.setEnabled(false);
- self.locked_content.node.setEnabled(true);
+ output.lock_render_state = .blanked;
+ output.normal_content.node.setEnabled(false);
+ output.locked_content.node.setEnabled(true);
}
-
- // Add the output to root.active_outputs and the output layout if it has not
- // already been added.
- if (wlr_output.enabled) server.root.activateOutput(self);
-}
-
-fn handleMode(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void {
- const output = @fieldParentPtr(Self, "mode", listener);
-
- output.updateBackgroundRect();
- output.arrangeLayers();
- server.root.applyPending();
}
pub fn updateBackgroundRect(output: *Self) void {
@@ -439,7 +443,7 @@ fn handleFrame(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void {
const output = @fieldParentPtr(Self, "frame", listener);
const scene_output = server.root.scene.getSceneOutput(output.wlr_output).?;
- if (scene_output.commit()) {
+ if (scene_output.commit(null)) {
if (server.lock_manager.state == .locked or
(server.lock_manager.state == .waiting_for_lock_surfaces and output.locked_content.node.enabled) or
server.lock_manager.state == .waiting_for_blank)
diff --git a/river/Root.zig b/river/Root.zig
index cc3a630..d17f6ed 100644
--- a/river/Root.zig
+++ b/river/Root.zig
@@ -90,6 +90,7 @@ views: wl.list.Head(View, .link),
new_output: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleNewOutput),
output_layout: *wlr.OutputLayout,
+scene_output_layout: *wlr.SceneOutputLayout,
layout_change: wl.Listener(*wlr.OutputLayout) = wl.Listener(*wlr.OutputLayout).init(handleLayoutChange),
output_manager: *wlr.OutputManagerV1,
@@ -133,7 +134,7 @@ pub fn init(self: *Self) !void {
const outputs = try interactive_content.createSceneTree();
const xwayland_override_redirect = if (build_options.xwayland) try interactive_content.createSceneTree();
- try scene.attachOutputLayout(output_layout);
+ const scene_output_layout = try scene.attachOutputLayout(output_layout);
_ = try wlr.XdgOutputManagerV1.create(server.wl_server, output_layout);
@@ -175,6 +176,7 @@ pub fn init(self: *Self) !void {
},
.views = undefined,
.output_layout = output_layout,
+ .scene_output_layout = scene_output_layout,
.all_outputs = undefined,
.active_outputs = undefined,
.output_manager = try wlr.OutputManagerV1.create(server.wl_server),
@@ -226,7 +228,7 @@ pub fn at(self: Self, lx: f64, ly: f64) ?AtResult {
const surface: ?*wlr.Surface = blk: {
if (node.type == .buffer) {
const scene_buffer = wlr.SceneBuffer.fromNode(node);
- if (wlr.SceneSurface.fromBuffer(scene_buffer)) |scene_surface| {
+ if (wlr.SceneSurface.tryFromBuffer(scene_buffer)) |scene_surface| {
break :blk scene_surface.surface;
}
}
@@ -354,9 +356,20 @@ pub fn activateOutput(root: *Self, output: *Output) void {
// This arranges outputs from left-to-right in the order they appear. The
// wlr-output-management protocol may be used to modify this arrangement.
// This also creates a wl_output global which is advertised to clients.
- root.output_layout.addAuto(output.wlr_output);
+ const layout_output = root.output_layout.addAuto(output.wlr_output) catch {
+ // This would currently be very awkward to handle well and this output
+ // handling code needs to be heavily refactored soon anyways for double
+ // buffered state application as part of the transaction system.
+ // In any case, wlroots 0.16 would have crashed here, the error is only
+ // possible to handle after updating to 0.17.
+ @panic("TODO handle allocation failure here");
+ };
+ const scene_output = root.scene.createSceneOutput(output.wlr_output) catch {
+ // See above
+ @panic("TODO handle allocation failure here");
+ };
+ root.scene_output_layout.addOutput(layout_output, scene_output);
- const layout_output = root.output_layout.get(output.wlr_output).?;
output.tree.node.setEnabled(true);
output.tree.node.setPosition(layout_output.x, layout_output.y);
@@ -767,7 +780,8 @@ fn processOutputConfig(
if (wlr_output.commitState(&proposed_state)) {
if (head.state.enabled) {
// Just updates the output's position if it is already in the layout
- self.output_layout.add(output.wlr_output, head.state.x, head.state.y);
+ // This can't fail if the output is already in the layout, which we know to be the case here.
+ _ = self.output_layout.add(output.wlr_output, head.state.x, head.state.y) catch unreachable;
output.tree.node.setEnabled(true);
output.tree.node.setPosition(head.state.x, head.state.y);
// Even though we call this in the output's handler for the mode event
diff --git a/river/SceneNodeData.zig b/river/SceneNodeData.zig
index 6533b01..536a714 100644
--- a/river/SceneNodeData.zig
+++ b/river/SceneNodeData.zig
@@ -51,9 +51,9 @@ pub fn attach(node: *wlr.SceneNode, data: Data) error{OutOfMemory}!void {
}
pub fn fromNode(node: *wlr.SceneNode) ?*SceneNodeData {
- var it: ?*wlr.SceneNode = node;
- while (it) |n| : (it = n.parent) {
- if (@as(?*SceneNodeData, @ptrFromInt(n.data))) |scene_node_data| {
+ var it: ?*wlr.SceneTree = node.parent;
+ while (it) |tree| : (it = tree.node.parent) {
+ if (@as(?*SceneNodeData, @ptrFromInt(tree.node.data))) |scene_node_data| {
return scene_node_data;
}
}
diff --git a/river/Seat.zig b/river/Seat.zig
index 4984363..3fe8693 100644
--- a/river/Seat.zig
+++ b/river/Seat.zig
@@ -152,7 +152,7 @@ pub fn focus(self: *Self, _target: ?*View) void {
// While a layer surface is exclusively focused, views may not receive focus
if (self.focused == .layer) {
const wlr_layer_surface = self.focused.layer.wlr_layer_surface;
- assert(wlr_layer_surface.mapped);
+ assert(wlr_layer_surface.surface.mapped);
if (wlr_layer_surface.current.keyboard_interactive == .exclusive and
(wlr_layer_surface.current.layer == .top or wlr_layer_surface.current.layer == .overlay))
{
diff --git a/river/Server.zig b/river/Server.zig
index a05a539..f17f2c1 100644
--- a/river/Server.zig
+++ b/river/Server.zig
@@ -48,6 +48,7 @@ sigint_source: *wl.EventSource,
sigterm_source: *wl.EventSource,
backend: *wlr.Backend,
+session: ?*wlr.Session,
renderer: *wlr.Renderer,
allocator: *wlr.Allocator,
@@ -89,7 +90,7 @@ pub fn init(self: *Self) !void {
errdefer self.sigterm_source.remove();
// This frees itself when the wl.Server is destroyed
- self.backend = try wlr.Backend.autocreate(self.wl_server);
+ self.backend = try wlr.Backend.autocreate(self.wl_server, &self.session);
self.renderer = try wlr.Renderer.autocreate(self.backend);
errdefer self.renderer.destroy();
@@ -98,7 +99,7 @@ pub fn init(self: *Self) !void {
self.allocator = try wlr.Allocator.autocreate(self.backend, self.renderer);
errdefer self.allocator.destroy();
- const compositor = try wlr.Compositor.create(self.wl_server, self.renderer);
+ const compositor = try wlr.Compositor.create(self.wl_server, 6, self.renderer);
_ = try wlr.Subcompositor.create(self.wl_server);
self.xdg_shell = try wlr.XdgShell.create(self.wl_server, 5);
@@ -109,7 +110,7 @@ pub fn init(self: *Self) !void {
self.new_toplevel_decoration.setNotify(handleNewToplevelDecoration);
self.xdg_decoration_manager.events.new_toplevel_decoration.add(&self.new_toplevel_decoration);
- self.layer_shell = try wlr.LayerShellV1.create(self.wl_server);
+ self.layer_shell = try wlr.LayerShellV1.create(self.wl_server, 4);
self.new_layer_surface.setNotify(handleNewLayerSurface);
self.layer_shell.events.new_surface.add(&self.new_layer_surface);
@@ -203,7 +204,7 @@ fn handleNewXdgSurface(_: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSu
log.debug("new xdg_toplevel", .{});
- XdgToplevel.create(xdg_surface.role_data.toplevel) catch {
+ XdgToplevel.create(xdg_surface.role_data.toplevel.?) catch {
log.err("out of memory", .{});
xdg_surface.resource.postNoMemory();
return;
@@ -214,17 +215,6 @@ fn handleNewToplevelDecoration(
_: *wl.Listener(*wlr.XdgToplevelDecorationV1),
wlr_decoration: *wlr.XdgToplevelDecorationV1,
) void {
- const xdg_toplevel: *XdgToplevel = @ptrFromInt(wlr_decoration.surface.data);
-
- // TODO(wlroots): The next wlroots version will handle this for us
- if (xdg_toplevel.decoration != null) {
- wlr_decoration.resource.postError(
- .already_constructed,
- "xdg_toplevel already has a decoration object",
- );
- return;
- }
-
XdgDecoration.init(wlr_decoration);
}
diff --git a/river/XdgDecoration.zig b/river/XdgDecoration.zig
index f43f5e9..91ab481 100644
--- a/river/XdgDecoration.zig
+++ b/river/XdgDecoration.zig
@@ -34,7 +34,7 @@ request_mode: wl.Listener(*wlr.XdgToplevelDecorationV1) =
wl.Listener(*wlr.XdgToplevelDecorationV1).init(handleRequestMode),
pub fn init(wlr_decoration: *wlr.XdgToplevelDecorationV1) void {
- const xdg_toplevel: *XdgToplevel = @ptrFromInt(wlr_decoration.surface.data);
+ const xdg_toplevel: *XdgToplevel = @ptrFromInt(wlr_decoration.toplevel.base.data);
xdg_toplevel.decoration = .{ .wlr_decoration = wlr_decoration };
const decoration = &xdg_toplevel.decoration.?;
@@ -52,21 +52,15 @@ pub fn init(wlr_decoration: *wlr.XdgToplevelDecorationV1) void {
xdg_toplevel.view.pending.ssd = ssd;
}
-// TODO(wlroots): remove this function when updating to 0.17.0
-// https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4051
-pub fn deinit(decoration: *XdgDecoration) void {
- decoration.destroy.link.remove();
- decoration.request_mode.link.remove();
-}
-
fn handleDestroy(
listener: *wl.Listener(*wlr.XdgToplevelDecorationV1),
_: *wlr.XdgToplevelDecorationV1,
) void {
const decoration = @fieldParentPtr(XdgDecoration, "destroy", listener);
- const xdg_toplevel: *XdgToplevel = @ptrFromInt(decoration.wlr_decoration.surface.data);
+ const xdg_toplevel: *XdgToplevel = @ptrFromInt(decoration.wlr_decoration.toplevel.base.data);
- decoration.deinit();
+ decoration.destroy.link.remove();
+ decoration.request_mode.link.remove();
assert(xdg_toplevel.decoration != null);
xdg_toplevel.decoration = null;
@@ -78,7 +72,7 @@ fn handleRequestMode(
) void {
const decoration = @fieldParentPtr(XdgDecoration, "request_mode", listener);
- const xdg_toplevel: *XdgToplevel = @ptrFromInt(decoration.wlr_decoration.surface.data);
+ const xdg_toplevel: *XdgToplevel = @ptrFromInt(decoration.wlr_decoration.toplevel.base.data);
const view = xdg_toplevel.view;
const ssd = server.config.rules.ssd.match(xdg_toplevel.view) orelse
diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig
index dd42a99..4d28ffe 100644
--- a/river/XdgToplevel.zig
+++ b/river/XdgToplevel.zig
@@ -90,8 +90,8 @@ pub fn create(xdg_toplevel: *wlr.XdgToplevel) error{OutOfMemory}!void {
// Add listeners that are active over the toplevel's entire lifetime
xdg_toplevel.base.events.destroy.add(&self.destroy);
- xdg_toplevel.base.events.map.add(&self.map);
- xdg_toplevel.base.events.unmap.add(&self.unmap);
+ xdg_toplevel.base.surface.events.map.add(&self.map);
+ xdg_toplevel.base.surface.events.unmap.add(&self.unmap);
xdg_toplevel.base.events.new_popup.add(&self.new_popup);
_ = xdg_toplevel.setWmCapabilities(.{ .fullscreen = true });
@@ -183,16 +183,7 @@ pub fn destroyPopups(self: Self) void {
fn handleDestroy(listener: *wl.Listener(void)) void {
const self = @fieldParentPtr(Self, "destroy", listener);
- // TODO(wlroots): Replace this with an assertion when updating to wlroots 0.17.0
- // https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4051
- if (self.decoration) |*decoration| {
- decoration.wlr_decoration.resource.postError(
- .orphaned,
- "xdg_toplevel destroyed before xdg_toplevel_decoration",
- );
- decoration.deinit();
- self.decoration = null;
- }
+ assert(self.decoration == null);
// Remove listeners that are active for the entire lifetime of the view
self.destroy.link.remove();
@@ -392,18 +383,6 @@ fn handleRequestResize(listener: *wl.Listener(*wlr.XdgToplevel.event.Resize), ev
const seat: *Seat = @ptrFromInt(event.seat.seat.data);
const view = self.view;
- {
- // TODO(wlroots) remove this after updating to the next wlroots version
- // https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4041
- if ((event.edges.top and event.edges.bottom) or (event.edges.left and event.edges.right)) {
- self.xdg_toplevel.resource.postError(
- .invalid_resize_edge,
- "provided value is not a valid variant of the resize_edge enum",
- );
- return;
- }
- }
-
if (view.current.output == null or view.pending.output == null) return;
if (view.current.tags & view.current.output.?.current.tags == 0) return;
if (view.pending.fullscreen) return;
diff --git a/river/XwaylandOverrideRedirect.zig b/river/XwaylandOverrideRedirect.zig
index 2fdc978..609161a 100644
--- a/river/XwaylandOverrideRedirect.zig
+++ b/river/XwaylandOverrideRedirect.zig
@@ -34,14 +34,21 @@ const log = std.log.scoped(.xwayland);
xwayland_surface: *wlr.XwaylandSurface,
surface_tree: ?*wlr.SceneTree = null,
+// Active over entire lifetime
request_configure: wl.Listener(*wlr.XwaylandSurface.event.Configure) =
wl.Listener(*wlr.XwaylandSurface.event.Configure).init(handleRequestConfigure),
destroy: wl.Listener(*wlr.XwaylandSurface) = wl.Listener(*wlr.XwaylandSurface).init(handleDestroy),
-map: wl.Listener(*wlr.XwaylandSurface) = wl.Listener(*wlr.XwaylandSurface).init(handleMap),
-unmap: wl.Listener(*wlr.XwaylandSurface) = wl.Listener(*wlr.XwaylandSurface).init(handleUnmap),
-set_geometry: wl.Listener(void) = wl.Listener(void).init(handleSetGeometry),
set_override_redirect: wl.Listener(*wlr.XwaylandSurface) =
wl.Listener(*wlr.XwaylandSurface).init(handleSetOverrideRedirect),
+associate: wl.Listener(void) = wl.Listener(void).init(handleAssociate),
+dissociate: wl.Listener(void) = wl.Listener(void).init(handleDissociate),
+
+// Active while the xwayland_surface is associated with a wlr_surface
+map: wl.Listener(void) = wl.Listener(void).init(handleMap),
+unmap: wl.Listener(void) = wl.Listener(void).init(handleUnmap),
+
+// Active while mapped
+set_geometry: wl.Listener(void) = wl.Listener(void).init(handleSetGeometry),
pub fn create(xwayland_surface: *wlr.XwaylandSurface) error{OutOfMemory}!void {
const self = try util.gpa.create(Self);
@@ -51,12 +58,16 @@ pub fn create(xwayland_surface: *wlr.XwaylandSurface) error{OutOfMemory}!void {
xwayland_surface.events.request_configure.add(&self.request_configure);
xwayland_surface.events.destroy.add(&self.destroy);
- xwayland_surface.events.map.add(&self.map);
- xwayland_surface.events.unmap.add(&self.unmap);
xwayland_surface.events.set_override_redirect.add(&self.set_override_redirect);
- if (xwayland_surface.mapped) {
- handleMap(&self.map, xwayland_surface);
+ xwayland_surface.events.associate.add(&self.associate);
+ xwayland_surface.events.dissociate.add(&self.dissociate);
+
+ if (xwayland_surface.surface) |surface| {
+ handleAssociate(&self.associate);
+ if (surface.mapped) {
+ handleMap(&self.map);
+ }
}
}
@@ -72,14 +83,28 @@ fn handleDestroy(listener: *wl.Listener(*wlr.XwaylandSurface), _: *wlr.XwaylandS
self.request_configure.link.remove();
self.destroy.link.remove();
- self.map.link.remove();
- self.unmap.link.remove();
+ self.associate.link.remove();
+ self.dissociate.link.remove();
self.set_override_redirect.link.remove();
util.gpa.destroy(self);
}
-pub fn handleMap(listener: *wl.Listener(*wlr.XwaylandSurface), _: *wlr.XwaylandSurface) void {
+fn handleAssociate(listener: *wl.Listener(void)) void {
+ const self = @fieldParentPtr(Self, "associate", listener);
+
+ self.xwayland_surface.surface.?.events.map.add(&self.map);
+ self.xwayland_surface.surface.?.events.unmap.add(&self.unmap);
+}
+
+fn handleDissociate(listener: *wl.Listener(void)) void {
+ const self = @fieldParentPtr(Self, "dissociate", listener);
+
+ self.map.link.remove();
+ self.unmap.link.remove();
+}
+
+pub fn handleMap(listener: *wl.Listener(void)) void {
const self = @fieldParentPtr(Self, "map", listener);
self.mapImpl() catch {
@@ -124,7 +149,7 @@ pub fn focusIfDesired(self: *Self) void {
}
}
-fn handleUnmap(listener: *wl.Listener(*wlr.XwaylandSurface), _: *wlr.XwaylandSurface) void {
+fn handleUnmap(listener: *wl.Listener(void)) void {
const self = @fieldParentPtr(Self, "unmap", listener);
self.set_geometry.link.remove();
@@ -165,7 +190,12 @@ fn handleSetOverrideRedirect(
assert(!xwayland_surface.override_redirect);
- if (xwayland_surface.mapped) handleUnmap(&self.unmap, xwayland_surface);
+ if (xwayland_surface.surface) |surface| {
+ if (surface.mapped) {
+ handleUnmap(&self.unmap);
+ }
+ handleDissociate(&self.dissociate);
+ }
handleDestroy(&self.destroy, xwayland_surface);
XwaylandView.create(xwayland_surface) catch {
diff --git a/river/XwaylandView.zig b/river/XwaylandView.zig
index 8fd3be1..95ca92b 100644
--- a/river/XwaylandView.zig
+++ b/river/XwaylandView.zig
@@ -39,16 +39,20 @@ xwayland_surface: *wlr.XwaylandSurface,
/// Created on map and destroyed on unmap
surface_tree: ?*wlr.SceneTree = null,
-// Listeners that are always active over the view's lifetime
+// Active over entire lifetime
destroy: wl.Listener(*wlr.XwaylandSurface) = wl.Listener(*wlr.XwaylandSurface).init(handleDestroy),
-map: wl.Listener(*wlr.XwaylandSurface) = wl.Listener(*wlr.XwaylandSurface).init(handleMap),
-unmap: wl.Listener(*wlr.XwaylandSurface) = wl.Listener(*wlr.XwaylandSurface).init(handleUnmap),
request_configure: wl.Listener(*wlr.XwaylandSurface.event.Configure) =
wl.Listener(*wlr.XwaylandSurface.event.Configure).init(handleRequestConfigure),
set_override_redirect: wl.Listener(*wlr.XwaylandSurface) =
wl.Listener(*wlr.XwaylandSurface).init(handleSetOverrideRedirect),
+associate: wl.Listener(void) = wl.Listener(void).init(handleAssociate),
+dissociate: wl.Listener(void) = wl.Listener(void).init(handleDissociate),
-// Listeners that are only active while the view is mapped
+// Active while the xwayland_surface is associated with a wlr_surface
+map: wl.Listener(void) = wl.Listener(void).init(handleMap),
+unmap: wl.Listener(void) = wl.Listener(void).init(handleUnmap),
+
+// Active while mapped
set_title: wl.Listener(*wlr.XwaylandSurface) = wl.Listener(*wlr.XwaylandSurface).init(handleSetTitle),
set_class: wl.Listener(*wlr.XwaylandSurface) = wl.Listener(*wlr.XwaylandSurface).init(handleSetClass),
set_decorations: wl.Listener(*wlr.XwaylandSurface) =
@@ -70,13 +74,16 @@ pub fn create(xwayland_surface: *wlr.XwaylandSurface) error{OutOfMemory}!void {
// Add listeners that are active over the view's entire lifetime
xwayland_surface.events.destroy.add(&self.destroy);
- xwayland_surface.events.map.add(&self.map);
- xwayland_surface.events.unmap.add(&self.unmap);
+ xwayland_surface.events.associate.add(&self.associate);
+ xwayland_surface.events.dissociate.add(&self.dissociate);
xwayland_surface.events.request_configure.add(&self.request_configure);
xwayland_surface.events.set_override_redirect.add(&self.set_override_redirect);
- if (xwayland_surface.mapped) {
- handleMap(&self.map, xwayland_surface);
+ if (xwayland_surface.surface) |surface| {
+ handleAssociate(&self.associate);
+ if (surface.mapped) {
+ handleMap(&self.map);
+ }
}
}
@@ -150,8 +157,8 @@ fn handleDestroy(listener: *wl.Listener(*wlr.XwaylandSurface), _: *wlr.XwaylandS
// Remove listeners that are active for the entire lifetime of the view
self.destroy.link.remove();
- self.map.link.remove();
- self.unmap.link.remove();
+ self.associate.link.remove();
+ self.dissociate.link.remove();
self.request_configure.link.remove();
self.set_override_redirect.link.remove();
@@ -160,10 +167,24 @@ fn handleDestroy(listener: *wl.Listener(*wlr.XwaylandSurface), _: *wlr.XwaylandS
view.destroy();
}
-pub fn handleMap(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *wlr.XwaylandSurface) void {
+fn handleAssociate(listener: *wl.Listener(void)) void {
+ const self = @fieldParentPtr(Self, "associate", listener);
+
+ self.xwayland_surface.surface.?.events.map.add(&self.map);
+ self.xwayland_surface.surface.?.events.unmap.add(&self.unmap);
+}
+
+fn handleDissociate(listener: *wl.Listener(void)) void {
+ const self = @fieldParentPtr(Self, "dissociate", listener);
+ self.map.link.remove();
+ self.unmap.link.remove();
+}
+
+pub fn handleMap(listener: *wl.Listener(void)) void {
const self = @fieldParentPtr(Self, "map", listener);
const view = self.view;
+ const xwayland_surface = self.xwayland_surface;
const surface = xwayland_surface.surface.?;
surface.data = @intFromPtr(&view.tree.node);
@@ -213,7 +234,7 @@ pub fn handleMap(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface:
};
}
-fn handleUnmap(listener: *wl.Listener(*wlr.XwaylandSurface), _: *wlr.XwaylandSurface) void {
+fn handleUnmap(listener: *wl.Listener(void)) void {
const self = @fieldParentPtr(Self, "unmap", listener);
self.xwayland_surface.surface.?.data = 0;
@@ -239,7 +260,9 @@ fn handleRequestConfigure(
const self = @fieldParentPtr(Self, "request_configure", listener);
// If unmapped, let the client do whatever it wants
- if (!self.xwayland_surface.mapped) {
+ if (self.xwayland_surface.surface == null or
+ !self.xwayland_surface.surface.?.mapped)
+ {
self.xwayland_surface.configure(event.x, event.y, event.width, event.height);
return;
}
@@ -262,7 +285,12 @@ fn handleSetOverrideRedirect(
assert(xwayland_surface.override_redirect);
- if (xwayland_surface.mapped) handleUnmap(&self.unmap, xwayland_surface);
+ if (xwayland_surface.surface) |surface| {
+ if (surface.mapped) {
+ handleUnmap(&self.unmap);
+ }
+ handleDissociate(&self.dissociate);
+ }
handleDestroy(&self.destroy, xwayland_surface);
XwaylandOverrideRedirect.create(xwayland_surface) catch {