build: update to wlroots 0.17

This commit is contained in:
Isaac Freund 2023-12-01 14:57:18 +01:00
parent 50ccd4c5b3
commit 7ee6c79b6b
No known key found for this signature in database
GPG Key ID: 86DED400DDFD7A11
21 changed files with 245 additions and 281 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

2
deps/zig-wlroots vendored

@ -1 +1 @@
Subproject commit 0f07b2c666125d06529dfc688da4e71bff9a04f9
Subproject commit a4e100599b9f742215fa09afce8c56cffea2e796

View File

@ -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);
}
if (self.xcursor_name) |name| {
self.wlr_cursor.setXcursor(self.xcursor_manager, name);
}
}
/// 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);
}
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();
}

View File

@ -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,
);
}

View File

@ -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;
}

View File

@ -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;
},

View File

@ -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 = {} });
}
}

View File

@ -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();
}
}
}

View File

@ -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);

View File

@ -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);
// We can't assert the current state of normal_content/locked_content
// here as this output may be newly created.
if (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);
},
.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);
},
}
} 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);
// 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;
}
// 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);
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 handleMode(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void {
const output = @fieldParentPtr(Self, "mode", listener);
output.updateBackgroundRect();
output.arrangeLayers();
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 (output.wlr_output.enabled) {
switch (server.lock_manager.state) {
.unlocked => {
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(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.
output.lock_render_state = .blanked;
output.normal_content.node.setEnabled(false);
output.locked_content.node.setEnabled(true);
}
}
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)

View File

@ -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

View File

@ -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;
}
}

View File

@ -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))
{

View File

@ -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);
}

View File

@ -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

View File

@ -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;

View File

@ -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 {

View File

@ -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 {