From 20d804cdb5b4874a0ee168cd8db67caf0c841772 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Wed, 4 Nov 2020 00:23:21 +0100 Subject: [PATCH] code: switch to custom wlroots/libwayland bindings This is a big step up over @cImport() for ergonomics and type safety. Nearly all void pointer casts have been eliminated! --- .gitmodules | 9 ++ README.md | 2 +- build.zig | 179 +++++---------------- deps/zig-pixman | 1 + deps/zig-wayland | 2 +- deps/zig-wlroots | 1 + deps/zig-xkbcommon | 1 + example/status.zig | 180 +++++++-------------- include/bindings.c | 11 -- include/bindings.h | 14 -- river/Box.zig | 8 +- river/Control.zig | 196 ++++++++++------------- river/Cursor.zig | 302 ++++++++++++++++------------------- river/Decoration.zig | 51 +++--- river/DecorationManager.zig | 30 ++-- river/DragIcon.zig | 22 ++- river/InputManager.zig | 106 ++++++------ river/Keyboard.zig | 168 ++++++++----------- river/LayerSurface.zig | 83 +++++----- river/Mapping.zig | 11 +- river/Output.zig | 160 ++++++++----------- river/OutputManager.zig | 235 +++++++++++++-------------- river/OutputStatus.zig | 39 +++-- river/PointerMapping.zig | 4 +- river/Root.zig | 57 +++---- river/Seat.zig | 154 ++++++++++-------- river/SeatStatus.zig | 44 +++-- river/Server.zig | 177 +++++++++----------- river/StatusManager.zig | 176 +++++++++----------- river/View.zig | 123 +++++++------- river/VoidView.zig | 10 +- river/XdgPopup.zig | 43 ++--- river/XdgToplevel.zig | 206 ++++++++++++------------ river/XwaylandUnmanaged.zig | 86 +++++----- river/XwaylandView.zig | 130 +++++++-------- river/c.zig | 53 ------ river/command/exit.zig | 4 +- river/command/map.zig | 87 +++++----- river/command/set_repeat.zig | 4 +- river/main.zig | 16 +- river/render.zig | 134 ++++++++-------- riverctl/main.zig | 12 +- 42 files changed, 1457 insertions(+), 1874 deletions(-) create mode 160000 deps/zig-pixman create mode 160000 deps/zig-wlroots create mode 160000 deps/zig-xkbcommon delete mode 100644 include/bindings.c delete mode 100644 include/bindings.h diff --git a/.gitmodules b/.gitmodules index 2d523f0..bb6bc6e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,12 @@ [submodule "deps/zig-wayland"] path = deps/zig-wayland url = https://github.com/ifreund/zig-wayland +[submodule "deps/zig-pixman"] + path = deps/zig-pixman + url = https://github.com/ifreund/zig-pixman +[submodule "deps/zig-xkbcommon"] + path = deps/zig-xkbcommon + url = https://github.com/ifreund/zig-xkbcommon +[submodule "deps/zig-wlroots"] + path = deps/zig-wlroots + url = https://github.com/swaywm/zig-wlroots diff --git a/README.md b/README.md index 32a096f..b64a611 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ git submodule update --init To compile river first ensure that you have the following dependencies installed: -- [zig](https://ziglang.org/download/) 0.7.0 +- [zig](https://ziglang.org/download/) 0.7.1 - wayland - wayland-protocols - [wlroots](https://github.com/swaywm/wlroots) 0.12.0 diff --git a/build.zig b/build.zig index 5105227..0f46824 100644 --- a/build.zig +++ b/build.zig @@ -1,16 +1,10 @@ const std = @import("std"); +const zbs = std.build; const ScanProtocolsStep = @import("deps/zig-wayland/build.zig").ScanProtocolsStep; -pub fn build(b: *std.build.Builder) !void { - // Standard target options allows the person running `zig build` to choose - // what target to build for. Here we do not override the defaults, which - // means any target is allowed, and the default is native. Other options - // for restricting supported target set are available. +pub fn build(b: *zbs.Builder) !void { const target = b.standardTargetOptions(.{}); - - // Standard release options allow the person running `zig build` to select - // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. const mode = b.standardReleaseOptions(); const xwayland = b.option( @@ -31,17 +25,14 @@ pub fn build(b: *std.build.Builder) !void { break :scdoc_found true; }; - const examples = b.option( - bool, - "examples", - "Set to true to build examples", - ) orelse false; + const examples = b.option(bool, "examples", "Set to true to build examples") orelse false; - // TODO: port all parts of river to zig-wayland and delete this - const scan_protocols = OldScanProtocolsStep.create(b); - - const scanner = ScanProtocolsStep.create(b, "deps/zig-wayland/"); + const scanner = ScanProtocolsStep.create(b); + scanner.addSystemProtocol("stable/xdg-shell/xdg-shell.xml"); scanner.addProtocolPath("protocol/river-control-unstable-v1.xml"); + scanner.addProtocolPath("protocol/river-status-unstable-v1.xml"); + scanner.addProtocolPath("protocol/wlr-layer-shell-unstable-v1.xml"); + scanner.addProtocolPath("protocol/wlr-output-power-management-unstable-v1.xml"); { const river = b.addExecutable("river", "river/main.zig"); @@ -49,16 +40,9 @@ pub fn build(b: *std.build.Builder) !void { river.setBuildMode(mode); river.addBuildOption(bool, "xwayland", xwayland); - addProtocolDeps(river, &scan_protocols.step); - addServerDeps(river); + addServerDeps(river, scanner); river.install(); - - const run_cmd = river.run(); - run_cmd.step.dependOn(b.getInstallStep()); - - const run_step = b.step("run", "Run the compositor"); - run_step.dependOn(&run_cmd.step); } { @@ -93,11 +77,13 @@ pub fn build(b: *std.build.Builder) !void { status.setTarget(target); status.setBuildMode(mode); - addProtocolDeps(status, &scan_protocols.step); - + status.step.dependOn(&scanner.step); + status.addPackage(scanner.getPkg()); status.linkLibC(); status.linkSystemLibrary("wayland-client"); + scanner.addCSource(status); + status.install(); } @@ -107,123 +93,44 @@ pub fn build(b: *std.build.Builder) !void { river_test.setBuildMode(mode); river_test.addBuildOption(bool, "xwayland", xwayland); - addProtocolDeps(river_test, &scan_protocols.step); - addServerDeps(river_test); + addServerDeps(river_test, scanner); const test_step = b.step("test", "Run the tests"); test_step.dependOn(&river_test.step); } } -fn addServerDeps(exe: *std.build.LibExeObjStep) void { - exe.addCSourceFile("include/bindings.c", &[_][]const u8{"-std=c99"}); - exe.addIncludeDir("."); +fn addServerDeps(exe: *zbs.LibExeObjStep, scanner: *ScanProtocolsStep) void { + const wayland = scanner.getPkg(); + const xkbcommon = zbs.Pkg{ .name = "xkbcommon", .path = "deps/zig-xkbcommon/src/xkbcommon.zig" }; + const pixman = zbs.Pkg{ .name = "pixman", .path = "deps/zig-pixman/pixman.zig" }; + const wlroots = zbs.Pkg{ + .name = "wlroots", + .path = "deps/zig-wlroots/src/wlroots.zig", + .dependencies = &[_]zbs.Pkg{ wayland, xkbcommon, pixman }, + }; + + exe.step.dependOn(&scanner.step); exe.linkLibC(); exe.linkSystemLibrary("libevdev"); + + exe.addPackage(wayland); exe.linkSystemLibrary("wayland-server"); - exe.linkSystemLibrary("wlroots"); + + exe.addPackage(xkbcommon); exe.linkSystemLibrary("xkbcommon"); + + exe.addPackage(pixman); exe.linkSystemLibrary("pixman-1"); + + exe.addPackage(wlroots); + exe.linkSystemLibrary("wlroots"); + + // TODO: remove when zig issue #131 is implemented + scanner.addCSource(exe); } -fn addProtocolDeps(exe: *std.build.LibExeObjStep, protocol_step: *std.build.Step) void { - exe.step.dependOn(protocol_step); - exe.addIncludeDir("protocol"); - exe.addCSourceFile("protocol/river-control-unstable-v1-protocol.c", &[_][]const u8{"-std=c99"}); - exe.addCSourceFile("protocol/river-status-unstable-v1-protocol.c", &[_][]const u8{"-std=c99"}); -} - -const OldScanProtocolsStep = struct { - builder: *std.build.Builder, - step: std.build.Step, - - fn create(builder: *std.build.Builder) *OldScanProtocolsStep { - const self = builder.allocator.create(OldScanProtocolsStep) catch @panic("out of memory"); - self.* = init(builder); - return self; - } - - fn init(builder: *std.build.Builder) OldScanProtocolsStep { - return OldScanProtocolsStep{ - .builder = builder, - .step = std.build.Step.init(.Custom, "Scan Protocols", builder.allocator, make), - }; - } - - fn make(step: *std.build.Step) !void { - const self = @fieldParentPtr(OldScanProtocolsStep, "step", step); - - const protocol_dir = std.mem.trim(u8, try self.builder.exec( - &[_][]const u8{ "pkg-config", "--variable=pkgdatadir", "wayland-protocols" }, - ), &std.ascii.spaces); - - const protocol_dir_paths = [_][]const []const u8{ - &[_][]const u8{ protocol_dir, "stable/xdg-shell/xdg-shell.xml" }, - &[_][]const u8{ "protocol", "wlr-layer-shell-unstable-v1.xml" }, - &[_][]const u8{ "protocol", "wlr-output-power-management-unstable-v1.xml" }, - &[_][]const u8{ "protocol", "river-control-unstable-v1.xml" }, - &[_][]const u8{ "protocol", "river-status-unstable-v1.xml" }, - }; - - const server_protocols = [_][]const u8{ - "xdg-shell", - "wlr-layer-shell-unstable-v1", - "wlr-output-power-management-unstable-v1", - "river-control-unstable-v1", - "river-status-unstable-v1", - }; - - const client_protocols = [_][]const u8{ - "river-control-unstable-v1", - "river-status-unstable-v1", - }; - - for (protocol_dir_paths) |dir_path| { - const xml_in_path = try std.fs.path.join(self.builder.allocator, dir_path); - - // Extension is .xml, so slice off the last 4 characters - const basename = std.fs.path.basename(xml_in_path); - const basename_no_ext = basename[0..(basename.len - 4)]; - - const code_out_path = try std.mem.concat( - self.builder.allocator, - u8, - &[_][]const u8{ "protocol/", basename_no_ext, "-protocol.c" }, - ); - _ = try self.builder.exec( - &[_][]const u8{ "wayland-scanner", "private-code", xml_in_path, code_out_path }, - ); - - for (server_protocols) |server_protocol| { - if (std.mem.eql(u8, basename_no_ext, server_protocol)) { - const header_out_path = try std.mem.concat( - self.builder.allocator, - u8, - &[_][]const u8{ "protocol/", basename_no_ext, "-protocol.h" }, - ); - _ = try self.builder.exec( - &[_][]const u8{ "wayland-scanner", "server-header", xml_in_path, header_out_path }, - ); - } - } - - for (client_protocols) |client_protocol| { - if (std.mem.eql(u8, basename_no_ext, client_protocol)) { - const header_out_path = try std.mem.concat( - self.builder.allocator, - u8, - &[_][]const u8{ "protocol/", basename_no_ext, "-client-protocol.h" }, - ); - _ = try self.builder.exec( - &[_][]const u8{ "wayland-scanner", "client-header", xml_in_path, header_out_path }, - ); - } - } - } - } -}; - const ScdocStep = struct { const scd_paths = [_][]const u8{ "doc/river.1.scd", @@ -232,23 +139,23 @@ const ScdocStep = struct { "doc/river-layouts.7.scd", }; - builder: *std.build.Builder, - step: std.build.Step, + builder: *zbs.Builder, + step: zbs.Step, - fn create(builder: *std.build.Builder) *ScdocStep { + fn create(builder: *zbs.Builder) *ScdocStep { const self = builder.allocator.create(ScdocStep) catch @panic("out of memory"); self.* = init(builder); return self; } - fn init(builder: *std.build.Builder) ScdocStep { + fn init(builder: *zbs.Builder) ScdocStep { return ScdocStep{ .builder = builder, - .step = std.build.Step.init(.Custom, "Generate man pages", builder.allocator, make), + .step = zbs.Step.init(.Custom, "Generate man pages", builder.allocator, make), }; } - fn make(step: *std.build.Step) !void { + fn make(step: *zbs.Step) !void { const self = @fieldParentPtr(ScdocStep, "step", step); for (scd_paths) |path| { const command = try std.fmt.allocPrint( diff --git a/deps/zig-pixman b/deps/zig-pixman new file mode 160000 index 0000000..7847fd1 --- /dev/null +++ b/deps/zig-pixman @@ -0,0 +1 @@ +Subproject commit 7847fd1bae7021cdb572e77eac93676c551fd1eb diff --git a/deps/zig-wayland b/deps/zig-wayland index ba49b2b..52326e7 160000 --- a/deps/zig-wayland +++ b/deps/zig-wayland @@ -1 +1 @@ -Subproject commit ba49b2b6f984b788aea5e752bfeb64e3381472e7 +Subproject commit 52326e7ee09d7acb6b55855f7a697af083ae973a diff --git a/deps/zig-wlroots b/deps/zig-wlroots new file mode 160000 index 0000000..16d9039 --- /dev/null +++ b/deps/zig-wlroots @@ -0,0 +1 @@ +Subproject commit 16d9039b5c345b2cc26118032261df9782e24946 diff --git a/deps/zig-xkbcommon b/deps/zig-xkbcommon new file mode 160000 index 0000000..9e4d41f --- /dev/null +++ b/deps/zig-xkbcommon @@ -0,0 +1 @@ +Subproject commit 9e4d41fe9414094db31c873c2ad9cadcd8999cf6 diff --git a/example/status.zig b/example/status.zig index 76cf3d8..07f255d 100644 --- a/example/status.zig +++ b/example/status.zig @@ -17,145 +17,77 @@ const std = @import("std"); -const c = @cImport({ - @cInclude("wayland-client.h"); - @cInclude("river-status-unstable-v1-client-protocol.h"); -}); +const wayland = @import("wayland"); +const wl = wayland.client.wl; +const zriver = wayland.client.zriver; -const wl_registry_listener = c.wl_registry_listener{ - .global = handleGlobal, - .global_remove = handleGlobalRemove, +const SetupContext = struct { + status_manager: ?*zriver.StatusManagerV1 = null, + outputs: std.ArrayList(*wl.Output) = std.ArrayList(*wl.Output).init(std.heap.c_allocator), + seats: std.ArrayList(*wl.Seat) = std.ArrayList(*wl.Seat).init(std.heap.c_allocator), }; -const river_output_status_listener = c.zriver_output_status_v1_listener{ - .focused_tags = handleFocusedTags, - .view_tags = handleViewTags, -}; - -const river_seat_status_listener = c.zriver_seat_status_v1_listener{ - .focused_output = handleFocusedOutput, - .unfocused_output = handleUnfocusedOutput, - .focused_view = handleFocusedView, -}; - -var river_status_manager: ?*c.zriver_status_manager_v1 = null; - -var outputs = std.ArrayList(*c.wl_output).init(std.heap.c_allocator); -var seats = std.ArrayList(*c.wl_seat).init(std.heap.c_allocator); - pub fn main() !void { - const wl_display = c.wl_display_connect(null) orelse return error.CantConnectToDisplay; - const wl_registry = c.wl_display_get_registry(wl_display); + const display = try wl.Display.connect(null); + const registry = try display.getRegistry(); - if (c.wl_registry_add_listener(wl_registry, &wl_registry_listener, null) < 0) - return error.FailedToAddListener; - if (c.wl_display_roundtrip(wl_display) < 0) return error.RoundtripFailed; + var context = SetupContext{}; - if (river_status_manager == null) return error.RiverStatusManagerNotAdvertised; + registry.setListener(*SetupContext, registryListener, &context) catch unreachable; + _ = try display.roundtrip(); - for (outputs.items) |wl_output| createOutputStatus(wl_output); - for (seats.items) |wl_seat| createSeatStatus(wl_seat); - outputs.deinit(); - seats.deinit(); + const status_manager = context.status_manager orelse return error.RiverStatusManagerNotAdvertised; + + for (context.outputs.items) |output| { + const output_status = try status_manager.getRiverOutputStatus(output); + output_status.setListener(?*c_void, outputStatusListener, null) catch unreachable; + } + for (context.seats.items) |seat| { + const seat_status = try status_manager.getRiverSeatStatus(seat); + seat_status.setListener(?*c_void, seatStatusListener, null) catch unreachable; + } + context.outputs.deinit(); + context.seats.deinit(); // Loop forever, listening for new events. - while (true) if (c.wl_display_dispatch(wl_display) < 0) return error.DispatchFailed; + while (true) _ = try display.dispatch(); } -fn handleGlobal( - data: ?*c_void, - wl_registry: ?*c.wl_registry, - name: u32, - interface: ?[*:0]const u8, - version: u32, -) callconv(.C) void { - // Global advertisement order is not defined, so save any outputs or seats - // advertised before the river_status_manager. - if (std.cstr.cmp(interface.?, @ptrCast([*:0]const u8, c.zriver_status_manager_v1_interface.name.?)) == 0) { - river_status_manager = @ptrCast( - *c.zriver_status_manager_v1, - c.wl_registry_bind(wl_registry, name, &c.zriver_status_manager_v1_interface, version), - ); - } else if (std.cstr.cmp(interface.?, @ptrCast([*:0]const u8, c.wl_output_interface.name.?)) == 0) { - const wl_output = @ptrCast( - *c.wl_output, - c.wl_registry_bind(wl_registry, name, &c.wl_output_interface, version), - ); - outputs.append(wl_output) catch @panic("out of memory"); - } else if (std.cstr.cmp(interface.?, @ptrCast([*:0]const u8, c.wl_seat_interface.name.?)) == 0) { - const wl_seat = @ptrCast( - *c.wl_seat, - c.wl_registry_bind(wl_registry, name, &c.wl_seat_interface, version), - ); - seats.append(wl_seat) catch @panic("out of memory"); +fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *SetupContext) void { + switch (event) { + .global => |global| { + if (std.cstr.cmp(global.interface, zriver.StatusManagerV1.getInterface().name) == 0) { + context.status_manager = registry.bind(global.name, zriver.StatusManagerV1, 1) catch return; + } else if (std.cstr.cmp(global.interface, wl.Seat.getInterface().name) == 0) { + const seat = registry.bind(global.name, wl.Seat, 1) catch return; + context.seats.append(seat) catch @panic("out of memory"); + } else if (std.cstr.cmp(global.interface, wl.Output.getInterface().name) == 0) { + const output = registry.bind(global.name, wl.Output, 1) catch return; + context.outputs.append(output) catch @panic("out of memory"); + } + }, + .global_remove => {}, } } -fn createOutputStatus(wl_output: *c.wl_output) void { - const river_output_status = c.zriver_status_manager_v1_get_river_output_status( - river_status_manager.?, - wl_output, - ); - _ = c.zriver_output_status_v1_add_listener( - river_output_status, - &river_output_status_listener, - null, - ); -} - -fn createSeatStatus(wl_seat: *c.wl_seat) void { - const river_seat_status = c.zriver_status_manager_v1_get_river_seat_status( - river_status_manager.?, - wl_seat, - ); - _ = c.zriver_seat_status_v1_add_listener(river_seat_status, &river_seat_status_listener, null); -} - -fn handleGlobalRemove(data: ?*c_void, wl_registry: ?*c.wl_registry, name: u32) callconv(.C) void { - // Ignore the event -} - -fn handleFocusedTags( - data: ?*c_void, - output_status: ?*c.zriver_output_status_v1, - tags: u32, -) callconv(.C) void { - std.debug.warn("Focused tags: {b:0>10}\n", .{tags}); -} - -fn handleViewTags( - data: ?*c_void, - output_status: ?*c.zriver_output_status_v1, - tags: ?*c.wl_array, -) callconv(.C) void { - std.debug.warn("View tags:\n", .{}); - var offset: usize = 0; - while (offset < tags.?.size) : (offset += @sizeOf(u32)) { - const ptr = @ptrCast([*]u8, tags.?.data) + offset; - std.debug.warn("{b:0>10}\n", .{std.mem.bytesToValue(u32, ptr[0..4])}); +fn outputStatusListener(output_status: *zriver.OutputStatusV1, event: zriver.OutputStatusV1.Event, data: ?*c_void) void { + switch (event) { + .focused_tags => |focused_tags| std.debug.warn("Focused tags: {b:0>10}\n", .{focused_tags.tags}), + .view_tags => |view_tags| { + std.debug.warn("View tags:\n", .{}); + for (view_tags.tags.slice(u32)) |t| std.debug.warn("{b:0>10}\n", .{t}); + }, } } -fn handleFocusedOutput( - data: ?*c_void, - seat_status: ?*c.zriver_seat_status_v1, - wl_output: ?*c.wl_output, -) callconv(.C) void { - std.debug.warn("Output id {} focused\n", .{c.wl_proxy_get_id(@ptrCast(*c.wl_proxy, wl_output))}); -} - -fn handleUnfocusedOutput( - data: ?*c_void, - seat_status: ?*c.zriver_seat_status_v1, - wl_output: ?*c.wl_output, -) callconv(.C) void { - std.debug.warn("Output id {} unfocused\n", .{c.wl_proxy_get_id(@ptrCast(*c.wl_proxy, wl_output))}); -} - -fn handleFocusedView( - data: ?*c_void, - seat_status: ?*c.zriver_seat_status_v1, - title: ?[*:0]const u8, -) callconv(.C) void { - std.debug.warn("Focused view title: {}\n", .{title.?}); +fn seatStatusListener(seat_status: *zriver.SeatStatusV1, event: zriver.SeatStatusV1.Event, data: ?*c_void) void { + switch (event) { + .focused_output => |focused_output| std.debug.warn("Output id {} focused\n", .{ + @ptrCast(*wl.Proxy, focused_output.output orelse return).getId(), + }), + .unfocused_output => |unfocused_output| std.debug.warn("Output id {} focused\n", .{ + @ptrCast(*wl.Proxy, unfocused_output.output orelse return).getId(), + }), + .focused_view => |focused_view| std.debug.warn("Focused view title: {}\n", .{focused_view.title}), + } } diff --git a/include/bindings.c b/include/bindings.c deleted file mode 100644 index 8cf8651..0000000 --- a/include/bindings.c +++ /dev/null @@ -1,11 +0,0 @@ -#define WLR_USE_UNSTABLE -#include -#include - -struct wlr_backend *river_wlr_backend_autocreate(struct wl_display *display) { - return wlr_backend_autocreate(display, NULL); -} - -struct wlr_renderer *river_wlr_backend_get_renderer(struct wlr_backend *backend) { - return wlr_backend_get_renderer(backend); -} diff --git a/include/bindings.h b/include/bindings.h deleted file mode 100644 index ce8da0b..0000000 --- a/include/bindings.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef RIVER_BINDINGS_H -#define RIVER_BINDINGS_H - -#include - -/* - * This header is needed since zig cannot yet translate flexible arrays. - * See https://github.com/ziglang/zig/issues/4775 - */ - -struct wlr_backend *river_wlr_backend_autocreate(struct wl_display *display); -struct wlr_renderer *river_wlr_backend_get_renderer(struct wlr_backend *backend); - -#endif // RIVER_BINDINGS_H diff --git a/river/Box.zig b/river/Box.zig index 9efe073..a7094bd 100644 --- a/river/Box.zig +++ b/river/Box.zig @@ -17,14 +17,14 @@ const Self = @This(); -const c = @import("c.zig"); +const wlr = @import("wlroots"); x: i32, y: i32, width: u32, height: u32, -pub fn fromWlrBox(wlr_box: c.wlr_box) Self { +pub fn fromWlrBox(wlr_box: wlr.Box) Self { return Self{ .x = @intCast(i32, wlr_box.x), .y = @intCast(i32, wlr_box.y), @@ -33,8 +33,8 @@ pub fn fromWlrBox(wlr_box: c.wlr_box) Self { }; } -pub fn toWlrBox(self: Self) c.wlr_box { - return c.wlr_box{ +pub fn toWlrBox(self: Self) wlr.Box { + return wlr.Box{ .x = @intCast(c_int, self.x), .y = @intCast(c_int, self.y), .width = @intCast(c_int, self.width), diff --git a/river/Control.zig b/river/Control.zig index eb78f33..eca712a 100644 --- a/river/Control.zig +++ b/river/Control.zig @@ -18,6 +18,13 @@ const Self = @This(); const std = @import("std"); +const mem = std.mem; + +const wayland = @import("wayland"); +const wl = wayland.server.wl; +const zriver = wayland.server.zriver; + +const wlr = @import("wlroots"); const c = @import("c.zig"); const command = @import("command.zig"); @@ -26,141 +33,106 @@ const util = @import("util.zig"); const Seat = @import("Seat.zig"); const Server = @import("Server.zig"); -const protocol_version = 1; - -const implementation = c.struct_zriver_control_v1_interface{ - .destroy = destroy, - .add_argument = addArgument, - .run_command = runCommand, -}; - -wl_global: *c.wl_global, +global: *wl.Global, args_map: std.AutoHashMap(u32, std.ArrayList([]const u8)), -listen_display_destroy: c.wl_listener = undefined, +server_destroy: wl.Listener(*wl.Server) = undefined, pub fn init(self: *Self, server: *Server) !void { self.* = .{ - .wl_global = c.wl_global_create( - server.wl_display, - &c.zriver_control_v1_interface, - protocol_version, - self, - bind, - ) orelse return error.OutOfMemory, + .global = try wl.Global.create(server.wl_server, zriver.ControlV1, 1, *Self, self, bind), .args_map = std.AutoHashMap(u32, std.ArrayList([]const u8)).init(util.gpa), }; - self.listen_display_destroy.notify = handleDisplayDestroy; - c.wl_display_add_destroy_listener(server.wl_display, &self.listen_display_destroy); + self.server_destroy.setNotify(handleServerDestroy); + server.wl_server.addDestroyListener(&self.server_destroy); } -fn handleDisplayDestroy(wl_listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_display_destroy", wl_listener.?); - c.wl_global_destroy(self.wl_global); +fn handleServerDestroy(listener: *wl.Listener(*wl.Server), wl_server: *wl.Server) void { + const self = @fieldParentPtr(Self, "server_destroy", listener); + self.global.destroy(); self.args_map.deinit(); } /// Called when a client binds our global -fn bind(wl_client: ?*c.wl_client, data: ?*c_void, version: u32, id: u32) callconv(.C) void { - const self = util.voidCast(Self, data.?); - const wl_resource = c.wl_resource_create( - wl_client, - &c.zriver_control_v1_interface, - @intCast(c_int, version), - id, - ) orelse { - c.wl_client_post_no_memory(wl_client); +fn bind(client: *wl.Client, self: *Self, version: u32, id: u32) callconv(.C) void { + const control = zriver.ControlV1.create(client, version, id) catch { + client.postNoMemory(); return; }; self.args_map.putNoClobber(id, std.ArrayList([]const u8).init(util.gpa)) catch { - c.wl_resource_destroy(wl_resource); - c.wl_client_post_no_memory(wl_client); + control.destroy(); + client.postNoMemory(); return; }; - c.wl_resource_set_implementation(wl_resource, &implementation, self, handleResourceDestroy); + control.setHandler(*Self, handleRequest, handleDestroy, self); +} + +fn handleRequest(control: *zriver.ControlV1, request: zriver.ControlV1.Request, self: *Self) void { + switch (request) { + .destroy => control.destroy(), + .add_argument => |add_argument| { + const owned_slice = mem.dupe(util.gpa, u8, mem.span(add_argument.argument)) catch { + control.getClient().postNoMemory(); + return; + }; + + self.args_map.getEntry(control.getId()).?.value.append(owned_slice) catch { + control.getClient().postNoMemory(); + util.gpa.free(owned_slice); + return; + }; + }, + .run_command => |run_command| { + const seat = @intToPtr(*Seat, wlr.Seat.Client.fromWlSeat(run_command.seat).?.seat.data); + + const callback = zriver.CommandCallbackV1.create( + control.getClient(), + control.getVersion(), + run_command.callback, + ) catch { + control.getClient().postNoMemory(); + return; + }; + + const args = self.args_map.get(control.getId()).?.items; + + var out: ?[]const u8 = null; + defer if (out) |s| util.gpa.free(s); + command.run(util.gpa, seat, args, &out) catch |err| { + const failure_message = switch (err) { + command.Error.OutOfMemory => { + callback.getClient().postNoMemory(); + return; + }, + command.Error.Other => std.cstr.addNullByte(util.gpa, out.?) catch { + callback.getClient().postNoMemory(); + return; + }, + else => command.errToMsg(err), + }; + defer if (err == command.Error.Other) util.gpa.free(failure_message); + callback.sendFailure(failure_message); + return; + }; + + const success_message = if (out) |s| + std.cstr.addNullByte(util.gpa, s) catch { + callback.getClient().postNoMemory(); + return; + } + else + ""; + defer if (out != null) util.gpa.free(success_message); + callback.sendSuccess(success_message); + }, + } } /// Remove the resource from the hash map and free all stored args -fn handleResourceDestroy(wl_resource: ?*c.wl_resource) callconv(.C) void { - const self = util.voidCast(Self, c.wl_resource_get_user_data(wl_resource).?); - const id = c.wl_resource_get_id(wl_resource); - const list = self.args_map.remove(id).?.value; +fn handleDestroy(control: *zriver.ControlV1, self: *Self) void { + const list = self.args_map.remove(control.getId()).?.value; for (list.items) |arg| list.allocator.free(arg); list.deinit(); } - -fn destroy(wl_client: ?*c.wl_client, wl_resource: ?*c.wl_resource) callconv(.C) void { - c.wl_resource_destroy(wl_resource); -} - -fn addArgument(wl_client: ?*c.wl_client, wl_resource: ?*c.wl_resource, arg: ?[*:0]const u8) callconv(.C) void { - const self = util.voidCast(Self, c.wl_resource_get_user_data(wl_resource).?); - const id = c.wl_resource_get_id(wl_resource); - - const owned_slice = std.mem.dupe(util.gpa, u8, std.mem.span(arg.?)) catch { - c.wl_client_post_no_memory(wl_client); - return; - }; - - self.args_map.getEntry(id).?.value.append(owned_slice) catch { - c.wl_client_post_no_memory(wl_client); - util.gpa.free(owned_slice); - return; - }; -} - -fn runCommand( - wl_client: ?*c.wl_client, - wl_resource: ?*c.wl_resource, - seat_wl_resource: ?*c.wl_resource, - callback_id: u32, -) callconv(.C) void { - const self = util.voidCast(Self, c.wl_resource_get_user_data(wl_resource).?); - // This can be null if the seat is inert, in which case we ignore the request - const wlr_seat_client = c.wlr_seat_client_from_resource(seat_wl_resource) orelse return; - const seat = util.voidCast(Seat, wlr_seat_client.*.seat.*.data.?); - - const callback_resource = c.wl_resource_create( - wl_client, - &c.zriver_command_callback_v1_interface, - protocol_version, - callback_id, - ) orelse { - c.wl_client_post_no_memory(wl_client); - return; - }; - c.wl_resource_set_implementation(callback_resource, null, null, null); - - const args = self.args_map.get(c.wl_resource_get_id(wl_resource)).?.items; - - var out: ?[]const u8 = null; - defer if (out) |s| util.gpa.free(s); - command.run(util.gpa, seat, args, &out) catch |err| { - const failure_message = switch (err) { - command.Error.OutOfMemory => { - c.wl_client_post_no_memory(wl_client); - return; - }, - command.Error.Other => std.cstr.addNullByte(util.gpa, out.?) catch { - c.wl_client_post_no_memory(wl_client); - return; - }, - else => command.errToMsg(err), - }; - defer if (err == command.Error.Other) util.gpa.free(failure_message); - c.zriver_command_callback_v1_send_failure(callback_resource, failure_message); - return; - }; - - const success_message = if (out) |s| - std.cstr.addNullByte(util.gpa, s) catch { - c.wl_client_post_no_memory(wl_client); - return; - } - else - ""; - defer if (out != null) util.gpa.free(success_message); - c.zriver_command_callback_v1_send_success(callback_resource, success_message); -} diff --git a/river/Cursor.zig b/river/Cursor.zig index ae31e51..b10695b 100644 --- a/river/Cursor.zig +++ b/river/Cursor.zig @@ -19,6 +19,10 @@ const Self = @This(); const build_options = @import("build_options"); const std = @import("std"); +const wlr = @import("wlroots"); +const wayland = @import("wayland"); +const wl = wayland.server.wl; +const zwlr = wayland.server.zwlr; const c = @import("c.zig"); const log = @import("log.zig"); @@ -50,34 +54,34 @@ const default_size = 24; mode: Mode = .passthrough, seat: *Seat, -wlr_cursor: *c.wlr_cursor, -wlr_xcursor_manager: *c.wlr_xcursor_manager, +wlr_cursor: *wlr.Cursor, +xcursor_manager: *wlr.XcursorManager, /// Number of distinct buttons currently pressed pressed_count: u32 = 0, -listen_axis: c.wl_listener = undefined, -listen_button: c.wl_listener = undefined, -listen_frame: c.wl_listener = undefined, -listen_motion_absolute: c.wl_listener = undefined, -listen_motion: c.wl_listener = undefined, -listen_request_set_cursor: c.wl_listener = undefined, +axis: wl.Listener(*wlr.Pointer.event.Axis) = undefined, +button: wl.Listener(*wlr.Pointer.event.Button) = undefined, +frame: wl.Listener(*wlr.Cursor) = undefined, +motion_absolute: wl.Listener(*wlr.Pointer.event.MotionAbsolute) = undefined, +motion: wl.Listener(*wlr.Pointer.event.Motion) = undefined, +request_set_cursor: wl.Listener(*wlr.Seat.event.RequestSetCursor) = undefined, pub fn init(self: *Self, seat: *Seat) !void { - const wlr_cursor = c.wlr_cursor_create() orelse return error.OutOfMemory; - errdefer c.wlr_cursor_destroy(wlr_cursor); - c.wlr_cursor_attach_output_layout(wlr_cursor, seat.input_manager.server.root.wlr_output_layout); + const wlr_cursor = try wlr.Cursor.create(); + errdefer wlr_cursor.destroy(); + wlr_cursor.attachOutputLayout(seat.input_manager.server.root.output_layout); - // This is here so that self.wlr_xcursor_manager doesn't need to be an + // This is here so that self.xcursor_manager doesn't need to be an // optional pointer. This isn't optimal as it does a needless allocation, // but this is not a hot path. - const wlr_xcursor_manager = c.wlr_xcursor_manager_create(null, default_size) orelse return error.OutOfMemory; - errdefer c.wlr_xcursor_manager_destroy(wlr_xcursor_manager); + const xcursor_manager = try wlr.XcursorManager.create(null, default_size); + errdefer xcursor_manager.destroy(); self.* = .{ .seat = seat, .wlr_cursor = wlr_cursor, - .wlr_xcursor_manager = wlr_xcursor_manager, + .xcursor_manager = xcursor_manager, }; try self.setTheme(null, null); @@ -87,28 +91,28 @@ pub fn init(self: *Self, seat: *Seat) !void { // can choose how we want to process them, forwarding them to clients and // moving the cursor around. See following post for more detail: // https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html - self.listen_axis.notify = handleAxis; - c.wl_signal_add(&self.wlr_cursor.events.axis, &self.listen_axis); + self.axis.setNotify(handleAxis); + self.wlr_cursor.events.axis.add(&self.axis); - self.listen_button.notify = handleButton; - c.wl_signal_add(&self.wlr_cursor.events.button, &self.listen_button); + self.button.setNotify(handleButton); + self.wlr_cursor.events.button.add(&self.button); - self.listen_frame.notify = handleFrame; - c.wl_signal_add(&self.wlr_cursor.events.frame, &self.listen_frame); + self.frame.setNotify(handleFrame); + self.wlr_cursor.events.frame.add(&self.frame); - self.listen_motion_absolute.notify = handleMotionAbsolute; - c.wl_signal_add(&self.wlr_cursor.events.motion_absolute, &self.listen_motion_absolute); + self.motion_absolute.setNotify(handleMotionAbsolute); + self.wlr_cursor.events.motion_absolute.add(&self.motion_absolute); - self.listen_motion.notify = handleMotion; - c.wl_signal_add(&self.wlr_cursor.events.motion, &self.listen_motion); + self.motion.setNotify(handleMotion); + self.wlr_cursor.events.motion.add(&self.motion); - self.listen_request_set_cursor.notify = handleRequestSetCursor; - c.wl_signal_add(&self.seat.wlr_seat.events.request_set_cursor, &self.listen_request_set_cursor); + self.request_set_cursor.setNotify(handleRequestSetCursor); + self.seat.wlr_seat.events.request_set_cursor.add(&self.request_set_cursor); } pub fn deinit(self: *Self) void { - c.wlr_xcursor_manager_destroy(self.wlr_xcursor_manager); - c.wlr_cursor_destroy(self.wlr_cursor); + self.xcursor_manager.destroy(); + self.wlr_cursor.destroy(); } /// Set the cursor theme for the given seat, as well as the xwayland theme if @@ -118,15 +122,14 @@ pub fn setTheme(self: *Self, theme: ?[*:0]const u8, _size: ?u32) !void { const server = self.seat.input_manager.server; const size = _size orelse default_size; - c.wlr_xcursor_manager_destroy(self.wlr_xcursor_manager); - self.wlr_xcursor_manager = c.wlr_xcursor_manager_create(theme, size) orelse - return error.OutOfMemory; + self.xcursor_manager.destroy(); + self.xcursor_manager = try wlr.XcursorManager.create(theme, size); // For each output, ensure a theme of the proper scale is loaded var it = server.root.outputs.first; while (it) |node| : (it = node.next) { const wlr_output = node.data.wlr_output; - if (!c.wlr_xcursor_manager_load(self.wlr_xcursor_manager, wlr_output.scale)) + self.xcursor_manager.load(wlr_output.scale) catch log.err(.cursor, "failed to load xcursor theme '{}' at scale {}", .{ theme, wlr_output.scale }); } @@ -139,19 +142,20 @@ pub fn setTheme(self: *Self, theme: ?[*:0]const u8, _size: ?u32) !void { if (theme) |t| if (c.setenv("XCURSOR_THEME", t, 1) < 0) return error.OutOfMemory; if (build_options.xwayland) { - if (c.wlr_xcursor_manager_load(self.wlr_xcursor_manager, 1)) { - const wlr_xcursor = c.wlr_xcursor_manager_get_xcursor(self.wlr_xcursor_manager, "left_ptr", 1).?; - const image: *c.wlr_xcursor_image = wlr_xcursor.*.images[0]; - c.wlr_xwayland_set_cursor( - server.wlr_xwayland, - image.buffer, - image.width * 4, - image.width, - image.height, - @intCast(i32, image.hotspot_x), - @intCast(i32, image.hotspot_y), - ); - } else log.err(.cursor, "failed to load xcursor theme '{}' at scale 1", .{theme}); + self.xcursor_manager.load(1) catch { + log.err(.cursor, "failed to load xcursor theme '{}' at scale 1", .{theme}); + return; + }; + const wlr_xcursor = self.xcursor_manager.getXcursor("left_ptr", 1).?; + const image = wlr_xcursor.images[0]; + server.xwayland.setCursor( + image.buffer, + image.width * 4, + image.width, + image.height, + @intCast(i32, image.hotspot_x), + @intCast(i32, image.hotspot_y), + ); } } } @@ -173,25 +177,18 @@ pub fn handleViewUnmap(self: *Self, view: *View) void { } fn clearFocus(self: Self) void { - c.wlr_xcursor_manager_set_cursor_image( - self.wlr_xcursor_manager, - "left_ptr", - self.wlr_cursor, - ); - c.wlr_seat_pointer_clear_focus(self.seat.wlr_seat); + self.xcursor_manager.setCursorImage("left_ptr", self.wlr_cursor); + self.seat.wlr_seat.pointerNotifyClearFocus(); } -fn handleAxis(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - // This event is forwarded by the cursor when a pointer emits an axis event, - // for example when you move the scroll wheel. - const self = @fieldParentPtr(Self, "listen_axis", listener.?); - const event = util.voidCast(c.wlr_event_pointer_axis, data.?); +/// Axis event is a scroll wheel or similiar +fn handleAxis(listener: *wl.Listener(*wlr.Pointer.event.Axis), event: *wlr.Pointer.event.Axis) void { + const self = @fieldParentPtr(Self, "axis", listener); self.seat.handleActivity(); // Notify the client with pointer focus of the axis event. - c.wlr_seat_pointer_notify_axis( - self.seat.wlr_seat, + self.seat.wlr_seat.pointerNotifyAxis( event.time_msec, event.orientation, event.delta, @@ -200,15 +197,12 @@ fn handleAxis(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { ); } -fn handleButton(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - // This event is forwarded by the cursor when a pointer emits a button - // event. - const self = @fieldParentPtr(Self, "listen_button", listener.?); - const event = util.voidCast(c.wlr_event_pointer_button, data.?); +fn handleButton(listener: *wl.Listener(*wlr.Pointer.event.Button), event: *wlr.Pointer.event.Button) void { + const self = @fieldParentPtr(Self, "button", listener); self.seat.handleActivity(); - if (event.state == .WLR_BUTTON_PRESSED) { + if (event.state == .pressed) { self.pressed_count += 1; } else { std.debug.assert(self.pressed_count > 0); @@ -221,21 +215,21 @@ fn handleButton(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { var sx: f64 = undefined; var sy: f64 = undefined; - if (self.surfaceAt(self.wlr_cursor.x, self.wlr_cursor.y, &sx, &sy)) |wlr_surface| { + if (self.surfaceAt(self.wlr_cursor.x, self.wlr_cursor.y, &sx, &sy)) |surface| { // If the found surface is a keyboard inteactive layer surface, // give it keyboard focus. - if (c.wlr_surface_is_layer_surface(wlr_surface)) { - const wlr_layer_surface = c.wlr_layer_surface_v1_from_wlr_surface(wlr_surface); - if (wlr_layer_surface.*.current.keyboard_interactive) { - const layer_surface = util.voidCast(LayerSurface, wlr_layer_surface.*.data.?); + if (surface.isLayerSurface()) { + const wlr_layer_surface = wlr.LayerSurfaceV1.fromWlrSurface(surface); + if (wlr_layer_surface.current.keyboard_interactive) { + const layer_surface = @intToPtr(*LayerSurface, wlr_layer_surface.data); self.seat.setFocusRaw(.{ .layer = layer_surface }); } } // If the target surface has a view, give that view keyboard focus and // perhaps enter move/resize mode. - if (View.fromWlrSurface(wlr_surface)) |view| { - if (event.state == .WLR_BUTTON_PRESSED and self.pressed_count == 1) { + if (View.fromWlrSurface(surface)) |view| { + if (event.state == .pressed and self.pressed_count == 1) { // If there is an active mapping for this button which is // handled we are done here if (self.handlePointerMapping(event, view)) return; @@ -244,26 +238,21 @@ fn handleButton(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { } } - _ = c.wlr_seat_pointer_notify_button( - self.seat.wlr_seat, - event.time_msec, - event.button, - event.state, - ); + _ = self.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state); } } /// Handle the mapping for the passed button if any. Returns true if there /// was a mapping and the button was handled. -fn handlePointerMapping(self: *Self, event: *c.wlr_event_pointer_button, view: *View) bool { - const wlr_keyboard = c.wlr_seat_get_keyboard(self.seat.wlr_seat); - const modifiers = c.wlr_keyboard_get_modifiers(wlr_keyboard); +fn handlePointerMapping(self: *Self, event: *wlr.Pointer.event.Button, view: *View) bool { + const wlr_keyboard = self.seat.wlr_seat.getKeyboard() orelse return false; + const modifiers = wlr_keyboard.getModifiers(); const fullscreen = view.current.fullscreen or view.pending.fullscreen; const config = self.seat.input_manager.server.config; return for (config.modes.items[self.seat.mode_id].pointer_mappings.items) |mapping| { - if (event.button == mapping.event_code and modifiers == mapping.modifiers) { + if (event.button == mapping.event_code and std.meta.eql(modifiers, mapping.modifiers)) { switch (mapping.action) { .move => if (!fullscreen) self.enterMode(.move, view), .resize => if (!fullscreen) self.enterMode(.resize, view), @@ -273,50 +262,54 @@ fn handlePointerMapping(self: *Self, event: *c.wlr_event_pointer_button, view: * } else false; } -fn handleFrame(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - // This event is forwarded by the cursor when a pointer emits an frame - // event. Frame events are sent after regular pointer events to group - // multiple events together. For instance, two axis events may happen at the - // same time, in which case a frame event won't be sent in between. - const self = @fieldParentPtr(Self, "listen_frame", listener.?); - // Notify the client with pointer focus of the frame event. - c.wlr_seat_pointer_notify_frame(self.seat.wlr_seat); +/// Frame events are sent after regular pointer events to group multiple +/// events together. For instance, two axis events may happen at the same +/// time, in which case a frame event won't be sent in between. +fn handleFrame(listener: *wl.Listener(*wlr.Cursor), wlr_cursor: *wlr.Cursor) void { + const self = @fieldParentPtr(Self, "frame", listener); + self.seat.wlr_seat.pointerNotifyFrame(); } -fn handleMotionAbsolute(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - // This event is forwarded by the cursor when a pointer emits an _absolute_ - // motion event, from 0..1 on each axis. This happens, for example, when - // wlroots is running under a Wayland window rather than KMS+DRM, and you - // move the mouse over the window. You could enter the window from any edge, - // so we have to warp the mouse there. There is also some hardware which - // emits these events. - const self = @fieldParentPtr(Self, "listen_motion_absolute", listener.?); - const event = util.voidCast(c.wlr_event_pointer_motion_absolute, data.?); +/// This event is forwarded by the cursor when a pointer emits an _absolute_ +/// motion event, from 0..1 on each axis. This happens, for example, when +/// wlroots is running under a Wayland window rather than KMS+DRM, and you +/// move the mouse over the window. You could enter the window from any edge, +/// so we have to warp the mouse there. There is also some hardware which +/// emits these events. +fn handleMotionAbsolute( + listener: *wl.Listener(*wlr.Pointer.event.MotionAbsolute), + event: *wlr.Pointer.event.MotionAbsolute, +) void { + const self = @fieldParentPtr(Self, "motion_absolute", listener); self.seat.handleActivity(); var lx: f64 = undefined; var ly: f64 = undefined; - c.wlr_cursor_absolute_to_layout_coords(self.wlr_cursor, event.device, event.x, event.y, &lx, &ly); + self.wlr_cursor.absoluteToLayoutCoords(event.device, event.x, event.y, &lx, &ly); self.processMotion(event.device, event.time_msec, lx - self.wlr_cursor.x, ly - self.wlr_cursor.y); } -fn handleMotion(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - // This event is forwarded by the cursor when a pointer emits a _relative_ - // pointer motion event (i.e. a delta) - const self = @fieldParentPtr(Self, "listen_motion", listener.?); - const event = util.voidCast(c.wlr_event_pointer_motion, data.?); +/// This event is forwarded by the cursor when a pointer emits a _relative_ +/// pointer motion event (i.e. a delta) +fn handleMotion( + listener: *wl.Listener(*wlr.Pointer.event.Motion), + event: *wlr.Pointer.event.Motion, +) void { + const self = @fieldParentPtr(Self, "motion", listener); self.seat.handleActivity(); self.processMotion(event.device, event.time_msec, event.delta_x, event.delta_y); } -fn handleRequestSetCursor(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { +fn handleRequestSetCursor( + listener: *wl.Listener(*wlr.Seat.event.RequestSetCursor), + event: *wlr.Seat.event.RequestSetCursor, +) void { // This event is rasied by the seat when a client provides a cursor image - const self = @fieldParentPtr(Self, "listen_request_set_cursor", listener.?); - const event = util.voidCast(c.wlr_seat_pointer_request_set_cursor_event, data.?); + const self = @fieldParentPtr(Self, "request_set_cursor", listener); const focused_client = self.seat.wlr_seat.pointer_state.focused_client; // This can be sent by any client, so we check to make sure this one is @@ -327,53 +320,40 @@ fn handleRequestSetCursor(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C // on the output that it's currently on and continue to do so as the // cursor moves between outputs. log.debug(.cursor, "focused client set cursor", .{}); - c.wlr_cursor_set_surface( - self.wlr_cursor, - event.surface, - event.hotspot_x, - event.hotspot_y, - ); + self.wlr_cursor.setSurface(event.surface, event.hotspot_x, event.hotspot_y); } } /// Find the topmost surface under the output layout coordinates lx/ly /// returns the surface if found and sets the sx/sy parametes to the /// surface coordinates. -fn surfaceAt(self: Self, lx: f64, ly: f64, sx: *f64, sy: *f64) ?*c.wlr_surface { +fn surfaceAt(self: Self, lx: f64, ly: f64, sx: *f64, sy: *f64) ?*wlr.Surface { // Find the output to check const root = self.seat.input_manager.server.root; - const wlr_output = c.wlr_output_layout_output_at(root.wlr_output_layout, lx, ly) orelse return null; - const output = util.voidCast(Output, wlr_output.*.data orelse return null); + const wlr_output = root.output_layout.outputAt(lx, ly) orelse return null; + const output = @intToPtr(*Output, wlr_output.data); // Get output-local coords from the layout coords var ox = lx; var oy = ly; - c.wlr_output_layout_output_coords(root.wlr_output_layout, wlr_output, &ox, &oy); - - // Check layers and views from top to bottom - const layer_idxs = [_]usize{ - c.ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, - c.ZWLR_LAYER_SHELL_V1_LAYER_TOP, - c.ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, - c.ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, - }; + root.output_layout.outputCoords(wlr_output, &ox, &oy); // Check overlay layer incl. popups - if (layerSurfaceAt(output.*, output.layers[layer_idxs[0]], ox, oy, sx, sy, false)) |s| return s; + if (layerSurfaceAt(output.*, output.getLayer(.overlay).*, ox, oy, sx, sy, false)) |s| return s; // Check top-background popups only - for (layer_idxs[1..4]) |idx| - if (layerSurfaceAt(output.*, output.layers[idx], ox, oy, sx, sy, true)) |s| return s; + for ([_]zwlr.LayerShellV1.Layer{ .top, .bottom, .background }) |layer| + if (layerSurfaceAt(output.*, output.getLayer(layer).*, ox, oy, sx, sy, true)) |s| return s; // Check top layer - if (layerSurfaceAt(output.*, output.layers[layer_idxs[1]], ox, oy, sx, sy, false)) |s| return s; + if (layerSurfaceAt(output.*, output.getLayer(.top).*, ox, oy, sx, sy, false)) |s| return s; // Check views if (viewSurfaceAt(output.*, ox, oy, sx, sy)) |s| return s; // Check the bottom-background layers - for (layer_idxs[2..4]) |idx| - if (layerSurfaceAt(output.*, output.layers[idx], ox, oy, sx, sy, false)) |s| return s; + for ([_]zwlr.LayerShellV1.Layer{ .bottom, .background }) |layer| + if (layerSurfaceAt(output.*, output.getLayer(layer).*, ox, oy, sx, sy, false)) |s| return s; return null; } @@ -388,25 +368,20 @@ fn layerSurfaceAt( sx: *f64, sy: *f64, popups_only: bool, -) ?*c.wlr_surface { +) ?*wlr.Surface { var it = layer.first; while (it) |node| : (it = node.next) { const layer_surface = &node.data; - const surface = c.wlr_layer_surface_v1_surface_at( - layer_surface.wlr_layer_surface, + if (layer_surface.wlr_layer_surface.surfaceAt( ox - @intToFloat(f64, layer_surface.box.x), oy - @intToFloat(f64, layer_surface.box.y), sx, sy, - ); - if (surface) |found| { + )) |found| { if (!popups_only) { return found; - } else if (c.wlr_surface_is_xdg_surface(found)) { - const wlr_xdg_surface = c.wlr_xdg_surface_from_wlr_surface(found); - if (wlr_xdg_surface.*.role == .WLR_XDG_SURFACE_ROLE_POPUP) { - return found; - } + } else if (found.isXdgSurface() and wlr.XdgSurface.fromWlrSurface(found).role == .popup) { + return found; } } } @@ -414,7 +389,7 @@ fn layerSurfaceAt( } /// Find the topmost visible view surface (incl. popups) at ox,oy. -fn viewSurfaceAt(output: Output, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_surface { +fn viewSurfaceAt(output: Output, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*wlr.Surface { // Focused views are rendered on top, so look for them first. var it = ViewStack(View).iter(output.views.first, .forward, output.current.tags, surfaceAtFilter); while (it.next()) |view| { @@ -468,10 +443,9 @@ pub fn enterMode(self: *Self, mode: @TagType(Mode), view: *View) void { } // Clear cursor focus, so that the surface does not receive events - c.wlr_seat_pointer_clear_focus(self.seat.wlr_seat); + self.seat.wlr_seat.pointerNotifyClearFocus(); - c.wlr_xcursor_manager_set_cursor_image( - self.wlr_xcursor_manager, + self.xcursor_manager.setCursorImage( if (mode == .move) "move" else "se-resize", self.wlr_cursor, ); @@ -480,36 +454,30 @@ pub fn enterMode(self: *Self, mode: @TagType(Mode), view: *View) void { } /// Return from down/move/resize to passthrough -fn leaveMode(self: *Self, event: *c.wlr_event_pointer_button) void { +fn leaveMode(self: *Self, event: *wlr.Pointer.event.Button) void { std.debug.assert(self.mode != .passthrough); log.debug(.cursor, "leave {} mode", .{@tagName(self.mode)}); // If we were in down mode, we need pass along the release event if (self.mode == .down) - _ = c.wlr_seat_pointer_notify_button( - self.seat.wlr_seat, - event.time_msec, - event.button, - event.state, - ); + _ = self.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state); self.mode = .passthrough; self.passthrough(event.time_msec); } -fn processMotion(self: *Self, device: *c.wlr_input_device, time: u32, delta_x: f64, delta_y: f64) void { +fn processMotion(self: *Self, device: *wlr.InputDevice, time: u32, delta_x: f64, delta_y: f64) void { const config = self.seat.input_manager.server.config; switch (self.mode) { .passthrough => { - c.wlr_cursor_move(self.wlr_cursor, device, delta_x, delta_y); + self.wlr_cursor.move(device, delta_x, delta_y); self.passthrough(time); }, .down => |view| { - c.wlr_cursor_move(self.wlr_cursor, device, delta_x, delta_y); - c.wlr_seat_pointer_notify_motion( - self.seat.wlr_seat, + self.wlr_cursor.move(device, delta_x, delta_y); + self.seat.wlr_seat.pointerNotifyMotion( time, self.wlr_cursor.x - @intToFloat(f64, view.current.box.x), self.wlr_cursor.y - @intToFloat(f64, view.current.box.y), @@ -531,8 +499,7 @@ fn processMotion(self: *Self, device: *c.wlr_input_device, time: u32, delta_x: f @intCast(i32, output_resolution.height - view.pending.box.height - border_width), ); - c.wlr_cursor_move( - self.wlr_cursor, + self.wlr_cursor.move( device, @intToFloat(f64, view.pending.box.x - view.current.box.x), @intToFloat(f64, view.pending.box.y - view.current.box.y), @@ -557,8 +524,7 @@ fn processMotion(self: *Self, device: *c.wlr_input_device, time: u32, delta_x: f data.view.applyPending(); // Keep cursor locked to the original offset from the bottom right corner - c.wlr_cursor_warp_closest( - self.wlr_cursor, + self.wlr_cursor.warpClosest( device, @intToFloat(f64, box.x + @intCast(i32, box.width) - data.offset_x), @intToFloat(f64, box.y + @intCast(i32, box.height) - data.offset_y), @@ -574,20 +540,20 @@ fn passthrough(self: *Self, time: u32) void { var sx: f64 = undefined; var sy: f64 = undefined; - if (self.surfaceAt(self.wlr_cursor.x, self.wlr_cursor.y, &sx, &sy)) |wlr_surface| { + if (self.surfaceAt(self.wlr_cursor.x, self.wlr_cursor.y, &sx, &sy)) |surface| { // If input is allowed on the surface, send pointer enter and motion // events. Note that wlroots won't actually send an enter event if // the surface has already been entered. - if (self.seat.input_manager.inputAllowed(wlr_surface)) { + if (self.seat.input_manager.inputAllowed(surface)) { // The focus change must be checked before sending enter events - const focus_change = self.seat.wlr_seat.pointer_state.focused_surface != wlr_surface; + const focus_change = self.seat.wlr_seat.pointer_state.focused_surface != surface; - c.wlr_seat_pointer_notify_enter(self.seat.wlr_seat, wlr_surface, sx, sy); - c.wlr_seat_pointer_notify_motion(self.seat.wlr_seat, time, sx, sy); + self.seat.wlr_seat.pointerNotifyEnter(surface, sx, sy); + self.seat.wlr_seat.pointerNotifyMotion(time, sx, sy); const follow_mode = config.focus_follows_cursor; if (follow_mode == .strict or (follow_mode == .normal and focus_change)) { - if (View.fromWlrSurface(wlr_surface)) |view| { + if (View.fromWlrSurface(surface)) |view| { self.seat.focus(view); self.seat.focusOutput(view.output); root.startTransaction(); diff --git a/river/Decoration.zig b/river/Decoration.zig index f1d06ab..70cd138 100644 --- a/river/Decoration.zig +++ b/river/Decoration.zig @@ -18,53 +18,56 @@ const Self = @This(); const std = @import("std"); +const wlr = @import("wlroots"); +const wl = @import("wayland").server.wl; -const c = @import("c.zig"); const util = @import("util.zig"); const Server = @import("Server.zig"); server: *Server, -wlr_xdg_toplevel_decoration: *c.wlr_xdg_toplevel_decoration_v1, +xdg_toplevel_decoration: *wlr.XdgToplevelDecorationV1, -listen_destroy: c.wl_listener = undefined, -listen_request_mode: c.wl_listener = undefined, +destroy: wl.Listener(*wlr.XdgToplevelDecorationV1) = undefined, +request_mode: wl.Listener(*wlr.XdgToplevelDecorationV1) = undefined, pub fn init( self: *Self, server: *Server, - wlr_xdg_toplevel_decoration: *c.wlr_xdg_toplevel_decoration_v1, + xdg_toplevel_decoration: *wlr.XdgToplevelDecorationV1, ) void { - self.* = .{ .server = server, .wlr_xdg_toplevel_decoration = wlr_xdg_toplevel_decoration }; + self.* = .{ .server = server, .xdg_toplevel_decoration = xdg_toplevel_decoration }; - self.listen_destroy.notify = handleDestroy; - c.wl_signal_add(&self.wlr_xdg_toplevel_decoration.events.destroy, &self.listen_destroy); + self.destroy.setNotify(handleDestroy); + self.xdg_toplevel_decoration.events.destroy.add(&self.destroy); - self.listen_request_mode.notify = handleRequestMode; - c.wl_signal_add(&self.wlr_xdg_toplevel_decoration.events.request_mode, &self.listen_request_mode); + self.request_mode.setNotify(handleRequestMode); + self.xdg_toplevel_decoration.events.request_mode.add(&self.request_mode); - handleRequestMode(&self.listen_request_mode, self.wlr_xdg_toplevel_decoration); + handleRequestMode(&self.request_mode, self.xdg_toplevel_decoration); } -fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_destroy", listener.?); +fn handleDestroy( + listener: *wl.Listener(*wlr.XdgToplevelDecorationV1), + xdg_toplevel_decoration: *wlr.XdgToplevelDecorationV1, +) void { + const self = @fieldParentPtr(Self, "destroy", listener); util.gpa.destroy(self); } -fn handleRequestMode(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_request_mode", listener.?); +fn handleRequestMode( + listener: *wl.Listener(*wlr.XdgToplevelDecorationV1), + xdg_toplevel_decoration: *wlr.XdgToplevelDecorationV1, +) void { + const self = @fieldParentPtr(Self, "request_mode", listener); - const wlr_xdg_surface: *c.wlr_xdg_surface = self.wlr_xdg_toplevel_decoration.surface; - const wlr_xdg_toplevel: *c.wlr_xdg_toplevel = @field(wlr_xdg_surface, c.wlr_xdg_surface_union).toplevel; - const app_id: [*:0]const u8 = if (wlr_xdg_toplevel.app_id) |id| id else "NULL"; + const toplevel = self.xdg_toplevel_decoration.surface.role_data.toplevel; + const app_id: [*:0]const u8 = if (toplevel.app_id) |id| id else "NULL"; - _ = c.wlr_xdg_toplevel_decoration_v1_set_mode( - self.wlr_xdg_toplevel_decoration, + _ = self.xdg_toplevel_decoration.setMode( for (self.server.config.csd_filter.items) |filter_app_id| { - if (std.mem.eql(u8, std.mem.span(app_id), filter_app_id)) { - break .WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; - } - } else .WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE, + if (std.mem.eql(u8, std.mem.span(app_id), filter_app_id)) break .client_side; + } else .server_side, ); } diff --git a/river/DecorationManager.zig b/river/DecorationManager.zig index 22998b5..b4d6383 100644 --- a/river/DecorationManager.zig +++ b/river/DecorationManager.zig @@ -18,8 +18,9 @@ const Self = @This(); const std = @import("std"); +const wlr = @import("wlroots"); +const wl = @import("wayland").server.wl; -const c = @import("c.zig"); const util = @import("util.zig"); const Decoration = @import("Decoration.zig"); @@ -27,31 +28,28 @@ const Server = @import("Server.zig"); server: *Server, -wlr_xdg_decoration_manager: *c.wlr_xdg_decoration_manager_v1, +xdg_decoration_manager: *wlr.XdgDecorationManagerV1, -listen_new_toplevel_decoration: c.wl_listener = undefined, +new_toplevel_decoration: wl.Listener(*wlr.XdgToplevelDecorationV1) = undefined, pub fn init(self: *Self, server: *Server) !void { self.* = .{ .server = server, - .wlr_xdg_decoration_manager = c.wlr_xdg_decoration_manager_v1_create(server.wl_display) orelse - return error.OutOfMemory, + .xdg_decoration_manager = try wlr.XdgDecorationManagerV1.create(server.wl_server), }; - self.listen_new_toplevel_decoration.notify = handleNewToplevelDecoration; - c.wl_signal_add( - &self.wlr_xdg_decoration_manager.events.new_toplevel_decoration, - &self.listen_new_toplevel_decoration, - ); + self.new_toplevel_decoration.setNotify(handleNewToplevelDecoration); + self.xdg_decoration_manager.events.new_toplevel_decoration.add(&self.new_toplevel_decoration); } -fn handleNewToplevelDecoration(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_new_toplevel_decoration", listener.?); - const wlr_xdg_toplevel_decoration = util.voidCast(c.wlr_xdg_toplevel_decoration_v1, data.?); - +fn handleNewToplevelDecoration( + listener: *wl.Listener(*wlr.XdgToplevelDecorationV1), + xdg_toplevel_decoration: *wlr.XdgToplevelDecorationV1, +) void { + const self = @fieldParentPtr(Self, "new_toplevel_decoration", listener); const decoration = util.gpa.create(Decoration) catch { - c.wl_resource_post_no_memory(wlr_xdg_toplevel_decoration.resource); + xdg_toplevel_decoration.resource.postNoMemory(); return; }; - decoration.init(self.server, wlr_xdg_toplevel_decoration); + decoration.init(self.server, xdg_toplevel_decoration); } diff --git a/river/DragIcon.zig b/river/DragIcon.zig index 22b9d19..7c77ff2 100644 --- a/river/DragIcon.zig +++ b/river/DragIcon.zig @@ -18,29 +18,27 @@ const Self = @This(); const std = @import("std"); +const wlr = @import("wlroots"); +const wl = @import("wayland").server.wl; -const c = @import("c.zig"); const util = @import("util.zig"); const Seat = @import("Seat.zig"); seat: *Seat, -wlr_drag_icon: *c.wlr_drag_icon, +wlr_drag_icon: *wlr.Drag.Icon, -listen_destroy: c.wl_listener = undefined, +destroy: wl.Listener(*wlr.Drag.Icon) = undefined, -pub fn init(self: *Self, seat: *Seat, wlr_drag_icon: *c.wlr_drag_icon) void { - self.* = .{ - .seat = seat, - .wlr_drag_icon = wlr_drag_icon, - }; +pub fn init(self: *Self, seat: *Seat, wlr_drag_icon: *wlr.Drag.Icon) void { + self.* = .{ .seat = seat, .wlr_drag_icon = wlr_drag_icon }; - self.listen_destroy.notify = handleDestroy; - c.wl_signal_add(&wlr_drag_icon.events.destroy, &self.listen_destroy); + self.destroy.setNotify(handleDestroy); + wlr_drag_icon.events.destroy.add(&self.destroy); } -fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_destroy", listener.?); +fn handleDestroy(listener: *wl.Listener(*wlr.Drag.Icon), wlr_drag_icon: *wlr.Drag.Icon) void { + const self = @fieldParentPtr(Self, "destroy", listener); const root = &self.seat.input_manager.server.root; const node = @fieldParentPtr(std.SinglyLinkedList(Self).Node, "data", self); root.drag_icons.remove(node); diff --git a/river/InputManager.zig b/river/InputManager.zig index 026732c..a67f07d 100644 --- a/river/InputManager.zig +++ b/river/InputManager.zig @@ -19,8 +19,9 @@ const Self = @This(); const build_options = @import("build_options"); const std = @import("std"); +const wlr = @import("wlroots"); +const wl = @import("wayland").server.wl; -const c = @import("c.zig"); const log = @import("log.zig"); const util = @import("util.zig"); @@ -32,56 +33,53 @@ const default_seat_name = "default"; server: *Server, -wlr_idle: *c.wlr_idle, -wlr_input_inhibit_manager: *c.wlr_input_inhibit_manager, -wlr_virtual_pointer_manager: *c.wlr_virtual_pointer_manager_v1, -wlr_virtual_keyboard_manager: *c.wlr_virtual_keyboard_manager_v1, +idle: *wlr.Idle, +input_inhibit_manager: *wlr.InputInhibitManager, +virtual_pointer_manager: *wlr.VirtualPointerManagerV1, +virtual_keyboard_manager: *wlr.VirtualKeyboardManagerV1, seats: std.TailQueue(Seat) = .{}, -exclusive_client: ?*c.wl_client = null, +exclusive_client: ?*wl.Client = null, -listen_inhibit_activate: c.wl_listener = undefined, -listen_inhibit_deactivate: c.wl_listener = undefined, -listen_new_input: c.wl_listener = undefined, -listen_new_virtual_pointer: c.wl_listener = undefined, -listen_new_virtual_keyboard: c.wl_listener = undefined, +inhibit_activate: wl.Listener(*wlr.InputInhibitManager) = undefined, +inhibit_deactivate: wl.Listener(*wlr.InputInhibitManager) = undefined, +new_input: wl.Listener(*wlr.InputDevice) = undefined, +new_virtual_pointer: wl.Listener(*wlr.VirtualPointerManagerV1.event.NewPointer) = undefined, +new_virtual_keyboard: wl.Listener(*wlr.VirtualKeyboardV1) = undefined, pub fn init(self: *Self, server: *Server) !void { const seat_node = try util.gpa.create(std.TailQueue(Seat).Node); + errdefer util.gpa.destroy(seat_node); self.* = .{ .server = server, // These are automatically freed when the display is destroyed - .wlr_idle = c.wlr_idle_create(server.wl_display) orelse return error.OutOfMemory, - .wlr_input_inhibit_manager = c.wlr_input_inhibit_manager_create(server.wl_display) orelse - return error.OutOfMemory, - .wlr_virtual_pointer_manager = c.wlr_virtual_pointer_manager_v1_create(server.wl_display) orelse - return error.OutOfMemory, - .wlr_virtual_keyboard_manager = c.wlr_virtual_keyboard_manager_v1_create(server.wl_display) orelse - return error.OutOfMemory, + .idle = try wlr.Idle.create(server.wl_server), + .input_inhibit_manager = try wlr.InputInhibitManager.create(server.wl_server), + .virtual_pointer_manager = try wlr.VirtualPointerManagerV1.create(server.wl_server), + .virtual_keyboard_manager = try wlr.VirtualKeyboardManagerV1.create(server.wl_server), }; self.seats.prepend(seat_node); try seat_node.data.init(self, default_seat_name); - if (build_options.xwayland) c.wlr_xwayland_set_seat(server.wlr_xwayland, self.defaultSeat().wlr_seat); + if (build_options.xwayland) server.xwayland.setSeat(self.defaultSeat().wlr_seat); - // Set up all listeners - self.listen_inhibit_activate.notify = handleInhibitActivate; - c.wl_signal_add(&self.wlr_input_inhibit_manager.events.activate, &self.listen_inhibit_activate); + self.inhibit_activate.setNotify(handleInhibitActivate); + self.input_inhibit_manager.events.activate.add(&self.inhibit_activate); - self.listen_inhibit_deactivate.notify = handleInhibitDeactivate; - c.wl_signal_add(&self.wlr_input_inhibit_manager.events.deactivate, &self.listen_inhibit_deactivate); + self.inhibit_deactivate.setNotify(handleInhibitDeactivate); + self.input_inhibit_manager.events.deactivate.add(&self.inhibit_deactivate); - self.listen_new_input.notify = handleNewInput; - c.wl_signal_add(&self.server.wlr_backend.events.new_input, &self.listen_new_input); + self.new_input.setNotify(handleNewInput); + self.server.backend.events.new_input.add(&self.new_input); - self.listen_new_virtual_pointer.notify = handleNewVirtualPointer; - c.wl_signal_add(&self.wlr_virtual_pointer_manager.events.new_virtual_pointer, &self.listen_new_virtual_pointer); + self.new_virtual_pointer.setNotify(handleNewVirtualPointer); + self.virtual_pointer_manager.events.new_virtual_pointer.add(&self.new_virtual_pointer); - self.listen_new_virtual_keyboard.notify = handleNewVirtualKeyboard; - c.wl_signal_add(&self.wlr_virtual_keyboard_manager.events.new_virtual_keyboard, &self.listen_new_virtual_keyboard); + self.new_virtual_keyboard.setNotify(handleNewVirtualKeyboard); + self.virtual_keyboard_manager.events.new_virtual_keyboard.add(&self.new_virtual_keyboard); } pub fn deinit(self: *Self) void { @@ -105,9 +103,9 @@ pub fn handleViewUnmap(self: Self, view: *View) void { } /// Returns true if input is currently allowed on the passed surface. -pub fn inputAllowed(self: Self, wlr_surface: *c.wlr_surface) bool { +pub fn inputAllowed(self: Self, wlr_surface: *wlr.Surface) bool { return if (self.exclusive_client) |exclusive_client| - exclusive_client == c.wl_resource_get_client(wlr_surface.resource) + exclusive_client == wlr_surface.resource.getClient() else true; } @@ -119,8 +117,11 @@ pub fn isCursorActionTarget(self: Self, view: *View) bool { } else false; } -fn handleInhibitActivate(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_inhibit_activate", listener.?); +fn handleInhibitActivate( + listener: *wl.Listener(*wlr.InputInhibitManager), + input_inhibit_manager: *wlr.InputInhibitManager, +) void { + const self = @fieldParentPtr(Self, "inhibit_activate", listener); log.debug(.input_manager, "input inhibitor activated", .{}); @@ -134,11 +135,14 @@ fn handleInhibitActivate(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) seat_node.data.mode_id = 1; } - self.exclusive_client = self.wlr_input_inhibit_manager.active_client; + self.exclusive_client = self.input_inhibit_manager.active_client; } -fn handleInhibitDeactivate(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_inhibit_deactivate", listener.?); +fn handleInhibitDeactivate( + listener: *wl.Listener(*wlr.InputInhibitManager), + input_inhibit_manager: *wlr.InputInhibitManager, +) void { + const self = @fieldParentPtr(Self, "inhibit_deactivate", listener); log.debug(.input_manager, "input inhibitor deactivated", .{}); @@ -163,17 +167,17 @@ fn handleInhibitDeactivate(listener: ?*c.wl_listener, data: ?*c_void) callconv(. } /// This event is raised by the backend when a new input device becomes available. -fn handleNewInput(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_new_input", listener.?); - const device = util.voidCast(c.wlr_input_device, data.?); - +fn handleNewInput(listener: *wl.Listener(*wlr.InputDevice), device: *wlr.InputDevice) void { + const self = @fieldParentPtr(Self, "new_input", listener); // TODO: suport multiple seats self.defaultSeat().addDevice(device); } -fn handleNewVirtualPointer(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_new_virtual_pointer", listener.?); - const event = util.voidCast(c.wlr_virtual_pointer_v1_new_pointer_event, data.?); +fn handleNewVirtualPointer( + listener: *wl.Listener(*wlr.VirtualPointerManagerV1.event.NewPointer), + event: *wlr.VirtualPointerManagerV1.event.NewPointer, +) void { + const self = @fieldParentPtr(Self, "new_virtual_pointer", listener); // TODO Support multiple seats and don't ignore if (event.suggested_seat != null) { @@ -184,14 +188,14 @@ fn handleNewVirtualPointer(listener: ?*c.wl_listener, data: ?*c_void) callconv(. log.debug(.input_manager, "Ignoring output suggestion from virtual pointer", .{}); } - const new_pointer: *c.wlr_virtual_pointer_v1 = event.new_pointer; - self.defaultSeat().addDevice(&new_pointer.input_device); + self.defaultSeat().addDevice(&event.new_pointer.input_device); } -fn handleNewVirtualKeyboard(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_new_virtual_keyboard", listener.?); - const virtual_keyboard = util.voidCast(c.wlr_virtual_keyboard_v1, data.?); - const seat = util.voidCast(Seat, @as(*c.wlr_seat, virtual_keyboard.seat).data.?); - +fn handleNewVirtualKeyboard( + listener: *wl.Listener(*wlr.VirtualKeyboardV1), + virtual_keyboard: *wlr.VirtualKeyboardV1, +) void { + const self = @fieldParentPtr(Self, "new_virtual_keyboard", listener); + const seat = @intToPtr(*Seat, virtual_keyboard.seat.data); seat.addDevice(&virtual_keyboard.input_device); } diff --git a/river/Keyboard.zig b/river/Keyboard.zig index 9fe876d..2c3c4d5 100644 --- a/river/Keyboard.zig +++ b/river/Keyboard.zig @@ -18,122 +18,102 @@ const Self = @This(); const std = @import("std"); +const wlr = @import("wlroots"); +const wl = @import("wayland").server.wl; +const xkb = @import("xkbcommon"); -const c = @import("c.zig"); const log = @import("log.zig"); const util = @import("util.zig"); const Seat = @import("Seat.zig"); seat: *Seat, -wlr_input_device: *c.wlr_input_device, -wlr_keyboard: *c.wlr_keyboard, +input_device: *wlr.InputDevice, -listen_key: c.wl_listener = undefined, -listen_modifiers: c.wl_listener = undefined, -listen_destroy: c.wl_listener = undefined, +key: wl.Listener(*wlr.Keyboard.event.Key) = undefined, +modifiers: wl.Listener(*wlr.Keyboard) = undefined, +destroy: wl.Listener(*wlr.Keyboard) = undefined, -pub fn init(self: *Self, seat: *Seat, wlr_input_device: *c.wlr_input_device) !void { +pub fn init(self: *Self, seat: *Seat, input_device: *wlr.InputDevice) !void { self.* = .{ .seat = seat, - .wlr_input_device = wlr_input_device, - .wlr_keyboard = @field(wlr_input_device, c.wlr_input_device_union).keyboard, + .input_device = input_device, }; // We need to prepare an XKB keymap and assign it to the keyboard. This // assumes the defaults (e.g. layout = "us"). - const rules = c.xkb_rule_names{ + const rules = xkb.RuleNames{ .rules = null, .model = null, .layout = null, .variant = null, .options = null, }; - const context = c.xkb_context_new(.XKB_CONTEXT_NO_FLAGS) orelse return error.XkbContextFailed; - defer c.xkb_context_unref(context); + const context = xkb.Context.new(.no_flags) orelse return error.XkbContextFailed; + defer context.unref(); - const keymap = c.xkb_keymap_new_from_names( - context, - &rules, - .XKB_KEYMAP_COMPILE_NO_FLAGS, - ) orelse return error.XkbKeymapFailed; - defer c.xkb_keymap_unref(keymap); + const keymap = xkb.Keymap.newFromNames(context, &rules, .no_flags) orelse return error.XkbKeymapFailed; + defer keymap.unref(); - if (!c.wlr_keyboard_set_keymap(self.wlr_keyboard, keymap)) return error.SetKeymapFailed; - c.wlr_keyboard_set_repeat_info(self.wlr_keyboard, 25, 600); + const wlr_keyboard = self.input_device.device.keyboard; - // Setup listeners for keyboard events - self.listen_key.notify = handleKey; - c.wl_signal_add(&self.wlr_keyboard.events.key, &self.listen_key); + if (!wlr_keyboard.setKeymap(keymap)) return error.SetKeymapFailed; + wlr_keyboard.setRepeatInfo(25, 600); - self.listen_modifiers.notify = handleModifiers; - c.wl_signal_add(&self.wlr_keyboard.events.modifiers, &self.listen_modifiers); + self.key.setNotify(handleKey); + wlr_keyboard.events.key.add(&self.key); - self.listen_destroy.notify = handleDestroy; - c.wl_signal_add(&self.wlr_keyboard.events.destroy, &self.listen_destroy); + self.modifiers.setNotify(handleModifiers); + wlr_keyboard.events.modifiers.add(&self.modifiers); + + self.destroy.setNotify(handleDestroy); + wlr_keyboard.events.destroy.add(&self.destroy); } pub fn deinit(self: *Self) void { - c.wl_list_remove(&self.listen_key.link); - c.wl_list_remove(&self.listen_modifiers.link); - c.wl_list_remove(&self.listen_destroy.link); + self.key.link.remove(); + self.modifiers.link.remove(); + self.destroy.link.remove(); } -fn handleKey(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { +fn handleKey(listener: *wl.Listener(*wlr.Keyboard.event.Key), event: *wlr.Keyboard.event.Key) void { // This event is raised when a key is pressed or released. - const self = @fieldParentPtr(Self, "listen_key", listener.?); - const event = util.voidCast(c.wlr_event_keyboard_key, data.?); - const wlr_keyboard = self.wlr_keyboard; + const self = @fieldParentPtr(Self, "key", listener); + const wlr_keyboard = self.input_device.device.keyboard; self.seat.handleActivity(); // Translate libinput keycode -> xkbcommon const keycode = event.keycode + 8; - // Get a list of keysyms as xkb reports them - var translated_keysyms: ?[*]c.xkb_keysym_t = undefined; - const translated_keysyms_len = c.xkb_state_key_get_syms( - wlr_keyboard.xkb_state, - keycode, - &translated_keysyms, - ); - - // Get a list of keysyms ignoring modifiers (e.g. 1 instead of !) - // Important for mappings like Mod+Shift+1 - var raw_keysyms: ?[*]c.xkb_keysym_t = undefined; - const layout_index = c.xkb_state_key_get_layout(wlr_keyboard.xkb_state, keycode); - const raw_keysyms_len = c.xkb_keymap_key_get_syms_by_level( - wlr_keyboard.keymap, - keycode, - layout_index, - 0, - &raw_keysyms, - ); + // TODO: These modifiers aren't properly handled, see sway's code + const modifiers = wlr_keyboard.getModifiers(); + const released = event.state == .released; var handled = false; - // TODO: These modifiers aren't properly handled, see sway's code - const modifiers = c.wlr_keyboard_get_modifiers(wlr_keyboard); - const released = event.state == .WLR_KEY_RELEASED; - var i: usize = 0; - while (i < translated_keysyms_len) : (i += 1) { + // First check translated keysyms as xkb reports them + for (wlr_keyboard.xkb_state.?.keyGetSyms(keycode)) |sym| { // Handle builtin mapping only when keys are pressed - if (!released and self.handleBuiltinMapping(translated_keysyms.?[i])) { + if (!released and self.handleBuiltinMapping(sym)) { handled = true; break; - } else if (self.seat.handleMapping(translated_keysyms.?[i], modifiers, released)) { + } else if (self.seat.handleMapping(sym, modifiers, released)) { handled = true; break; } } + + // If not yet handled, check keysyms ignoring modifiers (e.g. 1 instead of !) + // Important for mappings like Mod+Shift+1 if (!handled) { - i = 0; - while (i < raw_keysyms_len) : (i += 1) { + const layout_index = wlr_keyboard.xkb_state.?.keyGetLayout(keycode); + for (wlr_keyboard.keymap.?.keyGetSymsByLevel(keycode, layout_index, 0)) |sym| { // Handle builtin mapping only when keys are pressed - if (!released and self.handleBuiltinMapping(raw_keysyms.?[i])) { + if (!released and self.handleBuiltinMapping(sym)) { handled = true; break; - } else if (self.seat.handleMapping(raw_keysyms.?[i], modifiers, released)) { + } else if (self.seat.handleMapping(sym, modifiers, released)) { handled = true; break; } @@ -143,52 +123,44 @@ fn handleKey(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { if (!handled) { // Otherwise, we pass it along to the client. const wlr_seat = self.seat.wlr_seat; - c.wlr_seat_set_keyboard(wlr_seat, self.wlr_input_device); - c.wlr_seat_keyboard_notify_key( - wlr_seat, - event.time_msec, - event.keycode, - @intCast(u32, @enumToInt(event.state)), - ); + wlr_seat.setKeyboard(self.input_device); + wlr_seat.keyboardNotifyKey(event.time_msec, event.keycode, event.state); } } -fn handleModifiers(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - // This event is raised when a modifier key, such as shift or alt, is - // pressed. We simply communicate this to the client. */ - const self = @fieldParentPtr(Self, "listen_modifiers", listener.?); +/// Simply pass modifiers along to the client +fn handleModifiers(listener: *wl.Listener(*wlr.Keyboard), wlr_keyboard: *wlr.Keyboard) void { + const self = @fieldParentPtr(Self, "modifiers", listener); - // A seat can only have one keyboard, but this is a limitation of the - // Wayland protocol - not wlroots. We assign all connected keyboards to the - // same seat. You can swap out the underlying wlr_keyboard like this and - // wlr_seat handles this transparently. - c.wlr_seat_set_keyboard(self.seat.wlr_seat, self.wlr_input_device); - - // Send modifiers to the client. - c.wlr_seat_keyboard_notify_modifiers(self.seat.wlr_seat, &self.wlr_keyboard.modifiers); + self.seat.wlr_seat.setKeyboard(self.input_device); + self.seat.wlr_seat.keyboardNotifyModifiers(&self.input_device.device.keyboard.modifiers); } -fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_destroy", listener.?); - self.deinit(); + +fn handleDestroy(listener: *wl.Listener(*wlr.Keyboard), wlr_keyboard: *wlr.Keyboard) void { + const self = @fieldParentPtr(Self, "destroy", listener); const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); + self.seat.keyboards.remove(node); + self.deinit(); util.gpa.destroy(node); } /// Handle any builtin, harcoded compsitor mappings such as VT switching. /// Returns true if the keysym was handled. -fn handleBuiltinMapping(self: Self, keysym: c.xkb_keysym_t) bool { - if (keysym >= c.XKB_KEY_XF86Switch_VT_1 and keysym <= c.XKB_KEY_XF86Switch_VT_12) { - log.debug(.keyboard, "switch VT keysym received", .{}); - const wlr_backend = self.seat.input_manager.server.wlr_backend; - if (c.wlr_backend_is_multi(wlr_backend)) { - if (c.wlr_backend_get_session(wlr_backend)) |session| { - const vt = keysym - c.XKB_KEY_XF86Switch_VT_1 + 1; - log.notice(.server, "switching to VT {}", .{vt}); - _ = c.wlr_session_change_vt(session, vt); +fn handleBuiltinMapping(self: Self, keysym: xkb.Keysym) bool { + switch (@enumToInt(keysym)) { + @enumToInt(xkb.Keysym.XF86Switch_VT_1)...@enumToInt(xkb.Keysym.XF86Switch_VT_12) => { + log.debug(.keyboard, "switch VT keysym received", .{}); + const backend = self.seat.input_manager.server.backend; + if (backend.isMulti()) { + if (backend.getSession()) |session| { + const vt = @enumToInt(keysym) - @enumToInt(xkb.Keysym.XF86Switch_VT_1) + 1; + log.notice(.server, "switching to VT {}", .{vt}); + session.changeVt(vt) catch log.err(.server, "changing VT failed", .{}); + } } - } - return true; + return true; + }, + else => return false, } - return false; } diff --git a/river/LayerSurface.zig b/river/LayerSurface.zig index d2af692..341a060 100644 --- a/river/LayerSurface.zig +++ b/river/LayerSurface.zig @@ -18,8 +18,9 @@ const Self = @This(); const std = @import("std"); +const wlr = @import("wlroots"); +const wl = @import("wayland").server.wl; -const c = @import("c.zig"); const log = @import("log.zig"); const util = @import("util.zig"); @@ -28,27 +29,27 @@ const Output = @import("Output.zig"); const XdgPopup = @import("XdgPopup.zig"); output: *Output, -wlr_layer_surface: *c.wlr_layer_surface_v1, +wlr_layer_surface: *wlr.LayerSurfaceV1, box: Box = undefined, -state: c.wlr_layer_surface_v1_state, +state: wlr.LayerSurfaceV1.State, // Listeners active the entire lifetime of the layser surface -listen_destroy: c.wl_listener = undefined, -listen_map: c.wl_listener = undefined, -listen_unmap: c.wl_listener = undefined, +destroy: wl.Listener(*wlr.LayerSurfaceV1) = undefined, +map: wl.Listener(*wlr.LayerSurfaceV1) = undefined, +unmap: wl.Listener(*wlr.LayerSurfaceV1) = undefined, // Listeners only active while the layer surface is mapped -listen_commit: c.wl_listener = undefined, -listen_new_popup: c.wl_listener = undefined, +commit: wl.Listener(*wlr.Surface) = undefined, +new_popup: wl.Listener(*wlr.XdgPopup) = undefined, -pub fn init(self: *Self, output: *Output, wlr_layer_surface: *c.wlr_layer_surface_v1) void { +pub fn init(self: *Self, output: *Output, wlr_layer_surface: *wlr.LayerSurfaceV1) void { self.* = .{ .output = output, .wlr_layer_surface = wlr_layer_surface, .state = wlr_layer_surface.current, }; - wlr_layer_surface.data = self; + wlr_layer_surface.data = @ptrToInt(self); // Temporarily add to the output's list to allow for inital arrangement // which sends the first configure. @@ -59,61 +60,56 @@ pub fn init(self: *Self, output: *Output, wlr_layer_surface: *c.wlr_layer_surfac list.remove(node); // Set up listeners that are active for the entire lifetime of the layer surface - self.listen_destroy.notify = handleDestroy; - c.wl_signal_add(&self.wlr_layer_surface.events.destroy, &self.listen_destroy); + self.destroy.setNotify(handleDestroy); + self.wlr_layer_surface.events.destroy.add(&self.destroy); - self.listen_map.notify = handleMap; - c.wl_signal_add(&self.wlr_layer_surface.events.map, &self.listen_map); + self.map.setNotify(handleMap); + self.wlr_layer_surface.events.map.add(&self.map); - self.listen_unmap.notify = handleUnmap; - c.wl_signal_add(&self.wlr_layer_surface.events.unmap, &self.listen_unmap); + self.unmap.setNotify(handleUnmap); + self.wlr_layer_surface.events.unmap.add(&self.unmap); } -fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_destroy", listener.?); - const output = self.output; +fn handleDestroy(listener: *wl.Listener(*wlr.LayerSurfaceV1), wlr_layer_surface: *wlr.LayerSurfaceV1) void { + const self = @fieldParentPtr(Self, "destroy", listener); log.debug(.layer_shell, "layer surface '{}' destroyed", .{self.wlr_layer_surface.namespace}); // Remove listeners active the entire lifetime of the layer surface - c.wl_list_remove(&self.listen_destroy.link); - c.wl_list_remove(&self.listen_map.link); - c.wl_list_remove(&self.listen_unmap.link); + self.destroy.link.remove(); + self.map.link.remove(); + self.unmap.link.remove(); const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); util.gpa.destroy(node); } -fn handleMap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_map", listener.?); - const wlr_layer_surface = self.wlr_layer_surface; +fn handleMap(listener: *wl.Listener(*wlr.LayerSurfaceV1), wlr_layer_surface: *wlr.LayerSurfaceV1) void { + const self = @fieldParentPtr(Self, "map", listener); log.debug(.layer_shell, "layer surface '{}' mapped", .{wlr_layer_surface.namespace}); // Add listeners that are only active while mapped - self.listen_commit.notify = handleCommit; - c.wl_signal_add(&wlr_layer_surface.surface.*.events.commit, &self.listen_commit); + self.commit.setNotify(handleCommit); + wlr_layer_surface.surface.events.commit.add(&self.commit); - self.listen_new_popup.notify = handleNewPopup; - c.wl_signal_add(&wlr_layer_surface.events.new_popup, &self.listen_new_popup); + self.new_popup.setNotify(handleNewPopup); + wlr_layer_surface.events.new_popup.add(&self.new_popup); - c.wlr_surface_send_enter( - wlr_layer_surface.surface, - wlr_layer_surface.output, - ); + wlr_layer_surface.surface.sendEnter(wlr_layer_surface.output.?); const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); self.output.layers[@intCast(usize, @enumToInt(self.state.layer))].append(node); } -fn handleUnmap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_unmap", listener.?); +fn handleUnmap(listener: *wl.Listener(*wlr.LayerSurfaceV1), wlr_layer_surface: *wlr.LayerSurfaceV1) void { + const self = @fieldParentPtr(Self, "unmap", listener); log.debug(.layer_shell, "layer surface '{}' unmapped", .{self.wlr_layer_surface.namespace}); // remove listeners only active while the layer surface is mapped - c.wl_list_remove(&self.listen_commit.link); - c.wl_list_remove(&self.listen_new_popup.link); + self.commit.link.remove(); + self.new_popup.link.remove(); // Remove from the output's list of layer surfaces const self_node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); @@ -142,8 +138,8 @@ fn handleUnmap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { self.output.root.startTransaction(); } -fn handleCommit(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_commit", listener.?); +fn handleCommit(listener: *wl.Listener(*wlr.Surface), wlr_surface: *wlr.Surface) void { + const self = @fieldParentPtr(Self, "commit", listener); if (self.wlr_layer_surface.output == null) { log.err(.layer_shell, "layer surface committed with null output", .{}); @@ -166,13 +162,12 @@ fn handleCommit(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { } } -fn handleNewPopup(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_new_popup", listener.?); - const wlr_xdg_popup = util.voidCast(c.wlr_xdg_popup, data.?); +fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), wlr_xdg_popup: *wlr.XdgPopup) void { + const self = @fieldParentPtr(Self, "new_popup", listener); // This will free itself on destroy - var xdg_popup = util.gpa.create(XdgPopup) catch { - c.wl_resource_post_no_memory(wlr_xdg_popup.resource); + const xdg_popup = util.gpa.create(XdgPopup) catch { + wlr_xdg_popup.resource.postNoMemory(); return; }; xdg_popup.init(self.output, &self.box, wlr_xdg_popup); diff --git a/river/Mapping.zig b/river/Mapping.zig index 1518f85..03a9b45 100644 --- a/river/Mapping.zig +++ b/river/Mapping.zig @@ -18,20 +18,21 @@ const Self = @This(); const std = @import("std"); +const wlr = @import("wlroots"); +const xkb = @import("xkbcommon"); -const c = @import("c.zig"); const util = @import("util.zig"); -keysym: c.xkb_keysym_t, -modifiers: u32, +keysym: xkb.Keysym, +modifiers: wlr.Keyboard.ModifierMask, command_args: []const []const u8, /// When set to true the mapping will be executed on key release rather than on press release: bool, pub fn init( - keysym: c.xkb_keysym_t, - modifiers: u32, + keysym: xkb.Keysym, + modifiers: wlr.Keyboard.ModifierMask, release: bool, command_args: []const []const u8, ) !Self { diff --git a/river/Output.zig b/river/Output.zig index 244c1a5..94501db 100644 --- a/river/Output.zig +++ b/river/Output.zig @@ -18,6 +18,10 @@ const Self = @This(); const std = @import("std"); +const wlr = @import("wlroots"); +const wayland = @import("wayland"); +const wl = wayland.server.wl; +const zwlr = wayland.server.zwlr; const c = @import("c.zig"); const log = @import("log.zig"); @@ -38,7 +42,7 @@ const State = struct { }; root: *Root, -wlr_output: *c.wlr_output, +wlr_output: *wlr.Output, /// All layer surfaces on the output, indexed by the layer enum. layers: [4]std.TailQueue(LayerSurface) = [1]std.TailQueue(LayerSurface){.{}} ** 4, @@ -76,22 +80,21 @@ status_trackers: std.SinglyLinkedList(OutputStatus) = .{}, /// An active output can have focus (e.g. an output turned off by dpms is active) active: bool = false, -// All listeners for this output, in alphabetical order -listen_destroy: c.wl_listener = undefined, -listen_enable: c.wl_listener = undefined, -listen_frame: c.wl_listener = undefined, -listen_mode: c.wl_listener = undefined, +destroy: wl.Listener(*wlr.Output) = undefined, +enable: wl.Listener(*wlr.Output) = undefined, +frame: wl.Listener(*wlr.Output) = undefined, +mode: wl.Listener(*wlr.Output) = undefined, -pub fn init(self: *Self, root: *Root, wlr_output: *c.wlr_output) !void { +pub fn init(self: *Self, root: *Root, wlr_output: *wlr.Output) !void { // Some backends don't have modes. DRM+KMS does, and we need to set a mode // before we can use the output. The mode is a tuple of (width, height, // refresh rate), and each monitor supports only a specific set of modes. We // just pick the monitor's preferred mode, a more sophisticated compositor // would let the user configure it. - if (c.wlr_output_preferred_mode(wlr_output)) |mode| { - c.wlr_output_set_mode(wlr_output, mode); - c.wlr_output_enable(wlr_output, true); - if (!c.wlr_output_commit(wlr_output)) return error.OutputCommitFailed; + if (wlr_output.preferredMode()) |mode| { + wlr_output.setMode(mode); + wlr_output.enable(true); + try wlr_output.commit(); } const layout = try std.mem.dupe(util.gpa, u8, "full"); @@ -103,22 +106,21 @@ pub fn init(self: *Self, root: *Root, wlr_output: *c.wlr_output) !void { .layout = layout, .usable_box = undefined, }; - wlr_output.data = self; + wlr_output.data = @ptrToInt(self); - // Set up listeners - self.listen_destroy.notify = handleDestroy; - c.wl_signal_add(&wlr_output.events.destroy, &self.listen_destroy); + self.destroy.setNotify(handleDestroy); + wlr_output.events.destroy.add(&self.destroy); - self.listen_enable.notify = handleEnable; - c.wl_signal_add(&wlr_output.events.enable, &self.listen_enable); + self.enable.setNotify(handleEnable); + wlr_output.events.enable.add(&self.enable); - self.listen_frame.notify = handleFrame; - c.wl_signal_add(&wlr_output.events.frame, &self.listen_frame); + self.frame.setNotify(handleFrame); + wlr_output.events.frame.add(&self.frame); - self.listen_mode.notify = handleMode; - c.wl_signal_add(&wlr_output.events.mode, &self.listen_mode); + self.mode.setNotify(handleMode); + wlr_output.events.mode.add(&self.mode); - if (c.wlr_output_is_noop(wlr_output)) { + if (wlr_output.isNoop()) { // A noop output is always 0 x 0 self.usable_box = .{ .x = 0, @@ -132,7 +134,7 @@ pub fn init(self: *Self, root: *Root, wlr_output: *c.wlr_output) !void { var it = root.server.input_manager.seats.first; while (it) |node| : (it = node.next) { const seat = &node.data; - if (!c.wlr_xcursor_manager_load(seat.cursor.wlr_xcursor_manager, wlr_output.scale)) + seat.cursor.xcursor_manager.load(wlr_output.scale) catch log.err(.cursor, "failed to load xcursor theme at scale {}", .{wlr_output.scale}); } @@ -146,8 +148,8 @@ pub fn init(self: *Self, root: *Root, wlr_output: *c.wlr_output) !void { } } -pub fn getRenderer(self: Self) *c.wlr_renderer { - return c.wlr_backend_get_renderer(self.wlr_output.backend); +pub fn getLayer(self: *Self, layer: zwlr.LayerShellV1.Layer) *std.TailQueue(LayerSurface) { + return &self.layers[@intCast(usize, @enumToInt(layer))]; } pub fn sendViewTags(self: Self) void { @@ -321,16 +323,11 @@ pub fn arrangeLayers(self: *Self) void { // This box is modified as exclusive zones are applied var usable_box = full_box; - const layer_idxs = [_]usize{ - c.ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, - c.ZWLR_LAYER_SHELL_V1_LAYER_TOP, - c.ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, - c.ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, - }; + const layers = [_]zwlr.LayerShellV1.Layer{ .overlay, .top, .bottom, .background }; // Arrange all layer surfaces with exclusive zones, applying them to the // usable box along the way. - for (layer_idxs) |layer| self.arrangeLayer(self.layers[layer], full_box, &usable_box, true); + for (layers) |layer| self.arrangeLayer(self.getLayer(layer).*, full_box, &usable_box, true); // If the the usable_box has changed, we need to rearrange the output if (!std.meta.eql(self.usable_box, usable_box)) { @@ -339,13 +336,13 @@ pub fn arrangeLayers(self: *Self) void { } // Arrange the layers without exclusive zones - for (layer_idxs) |layer| self.arrangeLayer(self.layers[layer], full_box, &usable_box, false); + for (layers) |layer| self.arrangeLayer(self.getLayer(layer).*, full_box, &usable_box, false); // Find the topmost layer surface in the top or overlay layers which // requests keyboard interactivity if any. - const topmost_surface = outer: for (layer_idxs[0..2]) |layer| { + const topmost_surface = outer: for (layers[0..2]) |layer| { // Iterate in reverse order since the last layer is rendered on top - var it = self.layers[layer].last; + var it = self.getLayer(layer).last; while (it) |node| : (it = node.prev) { const layer_surface = &node.data; if (layer_surface.wlr_layer_surface.current.keyboard_interactive) { @@ -400,19 +397,14 @@ fn arrangeLayer( var new_box: Box = undefined; // Horizontal alignment - const anchor_left = @as(u32, c.ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT); - const anchor_right = @as(u32, c.ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT); if (current_state.desired_width == 0) { - const anchor_left_right = anchor_left | anchor_right; - if (current_state.anchor & anchor_left_right == anchor_left_right) { - new_box.x = bounds.x + @intCast(i32, current_state.margin.left); - new_box.width = bounds.width - - (current_state.margin.left + current_state.margin.right); - } - } else if (current_state.anchor & anchor_left != 0) { + std.debug.assert(current_state.anchor.right and current_state.anchor.left); + new_box.x = bounds.x + @intCast(i32, current_state.margin.left); + new_box.width = bounds.width - (current_state.margin.left + current_state.margin.right); + } else if (current_state.anchor.left) { new_box.x = bounds.x + @intCast(i32, current_state.margin.left); new_box.width = current_state.desired_width; - } else if (current_state.anchor & anchor_right != 0) { + } else if (current_state.anchor.right) { new_box.x = bounds.x + @intCast(i32, bounds.width - current_state.desired_width - current_state.margin.right); new_box.width = current_state.desired_width; @@ -422,19 +414,14 @@ fn arrangeLayer( } // Vertical alignment - const anchor_top = @as(u32, c.ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP); - const anchor_bottom = @as(u32, c.ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM); if (current_state.desired_height == 0) { - const anchor_top_bottom = anchor_top | anchor_bottom; - if (current_state.anchor & anchor_top_bottom == anchor_top_bottom) { - new_box.y = bounds.y + @intCast(i32, current_state.margin.top); - new_box.height = bounds.height - - (current_state.margin.top + current_state.margin.bottom); - } - } else if (current_state.anchor & anchor_top != 0) { + std.debug.assert(current_state.anchor.top and current_state.anchor.bottom); + new_box.y = bounds.y + @intCast(i32, current_state.margin.top); + new_box.height = bounds.height - (current_state.margin.top + current_state.margin.bottom); + } else if (current_state.anchor.top) { new_box.y = bounds.y + @intCast(i32, current_state.margin.top); new_box.height = current_state.desired_height; - } else if (current_state.anchor & anchor_bottom != 0) { + } else if (current_state.anchor.bottom) { new_box.y = bounds.y + @intCast(i32, bounds.height - current_state.desired_height - current_state.margin.bottom); new_box.height = current_state.desired_height; @@ -447,36 +434,36 @@ fn arrangeLayer( // Apply the exclusive zone to the current bounds const edges = [4]struct { - single: u32, - triple: u32, + single: zwlr.LayerSurfaceV1.Anchor, + triple: zwlr.LayerSurfaceV1.Anchor, to_increase: ?*i32, to_decrease: *u32, margin: u32, }{ .{ - .single = anchor_top, - .triple = anchor_top | anchor_left | anchor_right, + .single = .{ .top = true }, + .triple = .{ .top = true, .left = true, .right = true }, .to_increase = &usable_box.y, .to_decrease = &usable_box.height, .margin = current_state.margin.top, }, .{ - .single = anchor_bottom, - .triple = anchor_bottom | anchor_left | anchor_right, + .single = .{ .bottom = true }, + .triple = .{ .bottom = true, .left = true, .right = true }, .to_increase = null, .to_decrease = &usable_box.height, .margin = current_state.margin.bottom, }, .{ - .single = anchor_left, - .triple = anchor_left | anchor_top | anchor_bottom, + .single = .{ .left = true }, + .triple = .{ .left = true, .top = true, .bottom = true }, .to_increase = &usable_box.x, .to_decrease = &usable_box.width, .margin = current_state.margin.left, }, .{ - .single = anchor_right, - .triple = anchor_right | anchor_top | anchor_bottom, + .single = .{ .right = true }, + .triple = .{ .right = true, .top = true, .bottom = true }, .to_increase = null, .to_decrease = &usable_box.width, .margin = current_state.margin.right, @@ -484,7 +471,7 @@ fn arrangeLayer( }; for (edges) |edge| { - if ((current_state.anchor == edge.single or current_state.anchor == edge.triple) and + if ((std.meta.eql(current_state.anchor, edge.single) or std.meta.eql(current_state.anchor, edge.triple)) and current_state.exclusive_zone + @intCast(i32, edge.margin) > 0) { const delta = current_state.exclusive_zone + @intCast(i32, edge.margin); @@ -496,21 +483,18 @@ fn arrangeLayer( // Tell the client to assume the new size log.debug(.layer_shell, "send configure, {} x {}", .{ layer_surface.box.width, layer_surface.box.height }); - c.wlr_layer_surface_v1_configure( - layer_surface.wlr_layer_surface, - layer_surface.box.width, - layer_surface.box.height, - ); + layer_surface.wlr_layer_surface.configure(layer_surface.box.width, layer_surface.box.height); } } /// Called when the output is destroyed. Evacuate all views from the output /// and then remove it from the list of outputs. -fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_destroy", listener.?); +fn handleDestroy(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void { + const self = @fieldParentPtr(Self, "destroy", listener); const root = self.root; log.debug(.server, "output '{}' destroyed", .{self.wlr_output.name}); + // Remove the destroyed output from root if it wasn't already removed const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); if (self.active) { @@ -526,37 +510,35 @@ fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { } // Remove all listeners - c.wl_list_remove(&self.listen_destroy.link); - c.wl_list_remove(&self.listen_enable.link); - c.wl_list_remove(&self.listen_frame.link); - c.wl_list_remove(&self.listen_mode.link); + self.destroy.link.remove(); + self.enable.link.remove(); + self.frame.link.remove(); + self.mode.link.remove(); - // Free the layout command + // Free all memory and clean up the wlr.Output + self.wlr_output.data = undefined; util.gpa.free(self.layout); - - // Clean up the wlr_output - self.wlr_output.data = null; util.gpa.destroy(node); } -fn handleEnable(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_enable", listener.?); +fn handleEnable(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void { + const self = @fieldParentPtr(Self, "enable", listener); - if (self.wlr_output.enabled and !self.active) { + if (wlr_output.enabled and !self.active) { const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); self.root.addOutput(node); } } -fn handleFrame(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { +fn handleFrame(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void { // This function is called every time an output is ready to display a frame, // generally at the output's refresh rate (e.g. 60Hz). - const self = @fieldParentPtr(Self, "listen_frame", listener.?); + const self = @fieldParentPtr(Self, "frame", listener); render.renderOutput(self); } -fn handleMode(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_mode", listener.?); +fn handleMode(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void { + const self = @fieldParentPtr(Self, "mode", listener); self.arrangeLayers(); self.arrangeViews(); self.root.startTransaction(); @@ -565,7 +547,7 @@ fn handleMode(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { pub fn getEffectiveResolution(self: *Self) struct { width: u32, height: u32 } { var width: c_int = undefined; var height: c_int = undefined; - c.wlr_output_effective_resolution(self.wlr_output, &width, &height); + self.wlr_output.effectiveResolution(&width, &height); return .{ .width = @intCast(u32, width), .height = @intCast(u32, height), diff --git a/river/OutputManager.zig b/river/OutputManager.zig index 3500547..352b631 100644 --- a/river/OutputManager.zig +++ b/river/OutputManager.zig @@ -19,8 +19,9 @@ const Self = @This(); const build_options = @import("build_options"); const std = @import("std"); +const wlr = @import("wlroots"); +const wl = @import("wayland").server.wl; -const c = @import("c.zig"); const log = @import("log.zig"); const util = @import("util.zig"); @@ -38,65 +39,60 @@ const min_size = 50; root: *Root, -listen_new_output: c.wl_listener = undefined, -listen_output_layout_change: c.wl_listener = undefined, +new_output: wl.Listener(*wlr.Output) = undefined, -wlr_output_manager: *c.wlr_output_manager_v1, -listen_output_manager_apply: c.wl_listener = undefined, -listen_output_manager_test: c.wl_listener = undefined, +wlr_output_manager: *wlr.OutputManagerV1, +manager_apply: wl.Listener(*wlr.OutputConfigurationV1) = undefined, +manager_test: wl.Listener(*wlr.OutputConfigurationV1) = undefined, +layout_change: wl.Listener(*wlr.OutputLayout) = undefined, -wlr_output_power_manager: *c.wlr_output_power_manager_v1, -listen_output_power_manager_set_mode: c.wl_listener = undefined, +power_manager: *wlr.OutputPowerManagerV1, +power_manager_set_mode: wl.Listener(*wlr.OutputPowerManagerV1.event.SetMode) = undefined, /// True if and only if we are currently applying an output config output_config_pending: bool = false, pub fn init(self: *Self, server: *Server) !void { self.* = .{ - .wlr_output_manager = c.wlr_output_manager_v1_create(server.wl_display) orelse - return error.OutOfMemory, - .wlr_output_power_manager = c.wlr_output_power_manager_v1_create(server.wl_display) orelse - return error.OutOfMemory, .root = &server.root, + .wlr_output_manager = try wlr.OutputManagerV1.create(server.wl_server), + .power_manager = try wlr.OutputPowerManagerV1.create(server.wl_server), }; - self.listen_new_output.notify = handleNewOutput; - c.wl_signal_add(&server.wlr_backend.events.new_output, &self.listen_new_output); + self.new_output.setNotify(handleNewOutput); + server.backend.events.new_output.add(&self.new_output); - // Set up wlr_output_management - self.listen_output_manager_apply.notify = handleOutputManagerApply; - c.wl_signal_add(&self.wlr_output_manager.events.apply, &self.listen_output_manager_apply); - self.listen_output_manager_test.notify = handleOutputManagerTest; - c.wl_signal_add(&self.wlr_output_manager.events.@"test", &self.listen_output_manager_test); + self.manager_apply.setNotify(handleOutputManagerApply); + self.wlr_output_manager.events.apply.add(&self.manager_apply); - // Listen for changes in the output layout to send them to the clients of wlr_output_manager - self.listen_output_layout_change.notify = handleOutputLayoutChange; - c.wl_signal_add(&self.root.wlr_output_layout.events.change, &self.listen_output_layout_change); + self.manager_test.setNotify(handleOutputManagerTest); + self.wlr_output_manager.events.@"test".add(&self.manager_test); - // Set up output power manager - self.listen_output_power_manager_set_mode.notify = handleOutputPowerManagementSetMode; - c.wl_signal_add(&self.wlr_output_power_manager.events.set_mode, &self.listen_output_power_manager_set_mode); + self.layout_change.setNotify(handleOutputLayoutChange); + self.root.output_layout.events.change.add(&self.layout_change); - _ = c.wlr_xdg_output_manager_v1_create(server.wl_display, server.root.wlr_output_layout) orelse - return error.OutOfMemory; + self.power_manager_set_mode.setNotify(handleOutputPowerManagementSetMode); + self.power_manager.events.set_mode.add(&self.power_manager_set_mode); + + _ = try wlr.XdgOutputManagerV1.create(server.wl_server, self.root.output_layout); } -fn handleNewOutput(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_new_output", listener.?); - const wlr_output = util.voidCast(c.wlr_output, data.?); +fn handleNewOutput(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void { + const self = @fieldParentPtr(Self, "new_output", listener); log.debug(.output_manager, "new output {}", .{wlr_output.name}); const node = util.gpa.create(std.TailQueue(Output).Node) catch { - c.wlr_output_destroy(wlr_output); + wlr_output.destroy(); return; }; node.data.init(self.root, wlr_output) catch { - c.wlr_output_destroy(wlr_output); + wlr_output.destroy(); + util.gpa.destroy(node); return; }; const ptr_node = util.gpa.create(std.TailQueue(*Output).Node) catch { + wlr_output.destroy(); util.gpa.destroy(node); - c.wlr_output_destroy(wlr_output); return; }; ptr_node.data = &node.data; @@ -105,107 +101,108 @@ fn handleNewOutput(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void self.root.addOutput(node); } -/// Sends the new output configuration to all clients of wlr_output_manager -fn handleOutputLayoutChange(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_output_layout_change", listener.?); - // Dont do anything if the layout change is coming from applying a config +/// Send the new output configuration to all wlr-output-manager clients +fn handleOutputLayoutChange( + listener: *wl.Listener(*wlr.OutputLayout), + output_layout: *wlr.OutputLayout, +) void { + const self = @fieldParentPtr(Self, "layout_change", listener); + // Ignore if the layout change is from applying a config if (self.output_config_pending) return; - const config = self.createOutputConfigurationFromCurrent() catch { - log.err(.output_manager, "Could not create output configuration", .{}); + const config = self.ouputConfigFromCurrent() catch { + log.crit(.output_manager, "out of memory", .{}); return; }; - c.wlr_output_manager_v1_set_configuration(self.wlr_output_manager, config); + self.wlr_output_manager.setConfiguration(config); } -fn handleOutputManagerApply(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_output_manager_apply", listener.?); - const config = util.voidCast(c.wlr_output_configuration_v1, data.?); - defer c.wlr_output_configuration_v1_destroy(config); +fn handleOutputManagerApply( + listener: *wl.Listener(*wlr.OutputConfigurationV1), + config: *wlr.OutputConfigurationV1, +) void { + const self = @fieldParentPtr(Self, "manager_apply", listener); + defer config.destroy(); if (self.applyOutputConfig(config)) { - c.wlr_output_configuration_v1_send_succeeded(config); + config.sendSucceeded(); } else { - c.wlr_output_configuration_v1_send_failed(config); + config.sendFailed(); } - // Now send the config that actually was applied - const actualConfig = self.createOutputConfigurationFromCurrent() catch { - log.err(.output_manager, "Could not create output configuration", .{}); + // Send the config that was actually applied + const applied_config = self.ouputConfigFromCurrent() catch { + log.crit(.output_manager, "out of memory", .{}); return; }; - c.wlr_output_manager_v1_set_configuration(self.wlr_output_manager, actualConfig); + self.wlr_output_manager.setConfiguration(applied_config); } -fn handleOutputManagerTest(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_output_manager_test", listener.?); - const config = util.voidCast(c.wlr_output_configuration_v1, data.?); - defer c.wlr_output_configuration_v1_destroy(config); +fn handleOutputManagerTest( + listener: *wl.Listener(*wlr.OutputConfigurationV1), + config: *wlr.OutputConfigurationV1, +) void { + const self = @fieldParentPtr(Self, "manager_test", listener); + defer config.destroy(); if (testOutputConfig(config, true)) { - c.wlr_output_configuration_v1_send_succeeded(config); + config.sendSucceeded(); } else { - c.wlr_output_configuration_v1_send_failed(config); + config.sendFailed(); } } -fn handleOutputPowerManagementSetMode(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_output_power_manager_set_mode", listener.?); - const mode_event = util.voidCast(c.wlr_output_power_v1_set_mode_event, data.?); - const wlr_output: *c.wlr_output = mode_event.output; +fn handleOutputPowerManagementSetMode( + listener: *wl.Listener(*wlr.OutputPowerManagerV1.event.SetMode), + event: *wlr.OutputPowerManagerV1.event.SetMode, +) void { + const self = @fieldParentPtr(Self, "power_manager_set_mode", listener); - const enable = mode_event.mode == .ZWLR_OUTPUT_POWER_V1_MODE_ON; + const enable = event.mode == .on; const log_text = if (enable) "Enabling" else "Disabling"; log.debug( .output_manager, "{} dpms for output {}", - .{ log_text, wlr_output.name }, + .{ log_text, event.output.name }, ); - c.wlr_output_enable(wlr_output, enable); - if (!c.wlr_output_commit(wlr_output)) { - log.err( - .output_manager, - "wlr_output_commit failed for {}", - .{wlr_output.name}, - ); - } + event.output.enable(enable); + event.output.commit() catch + log.err(.server, "output commit failed for {}", .{event.output.name}); } -/// Applies an output config -fn applyOutputConfig(self: *Self, config: *c.wlr_output_configuration_v1) bool { - // We need to store whether a config is pending because we listen to wlr_output_layout.change - // and this event can be triggered by applying the config +/// Apply the given config, return false on faliure +fn applyOutputConfig(self: *Self, config: *wlr.OutputConfigurationV1) bool { + // Store whether a config is pending so we can tell if the + // wlr_output_layout.change event was triggered by applying the config self.output_config_pending = true; defer self.output_config_pending = false; // Test if the config should apply cleanly if (!testOutputConfig(config, false)) return false; - const list_head: *c.wl_list = &config.heads; - var it: *c.wl_list = list_head.next; - while (it != list_head) : (it = it.next) { - const head = @fieldParentPtr(c.wlr_output_configuration_head_v1, "link", it); - const output = util.voidCast(Output, @as(*c.wlr_output, head.state.output).data.?); + var it = config.heads.iterator(.forward); + while (it.next()) |head| { + const output = @intToPtr(*Output, head.state.output.data); const disable = output.wlr_output.enabled and !head.state.enabled; - // This commit will only fail due to runtime errors. - // We choose to ignore this error - if (!c.wlr_output_commit(output.wlr_output)) { - log.err(.output_manager, "wlr_output_commit failed for {}", .{output.wlr_output.name}); - } + // Since we have done a successful test commit, this will only fail + // due to error in the output's backend implementation. + output.wlr_output.commit() catch + log.err(.output_manager, "output commit failed for {}", .{output.wlr_output.name}); if (output.wlr_output.enabled) { // Moves the output if it is already in the layout - c.wlr_output_layout_add(self.root.wlr_output_layout, output.wlr_output, head.state.x, head.state.y); + self.root.output_layout.add(output.wlr_output, head.state.x, head.state.y); } if (disable) { const node = @fieldParentPtr(std.TailQueue(Output).Node, "data", output); self.root.removeOutput(node); - c.wlr_output_layout_remove(self.root.wlr_output_layout, output.wlr_output); + self.root.output_layout.remove(output.wlr_output); } + // Arrange layers to adjust the usable_box // We dont need to call arrangeViews() since arrangeLayers() will call // it for us because the usable_box changed @@ -218,16 +215,14 @@ fn applyOutputConfig(self: *Self, config: *c.wlr_output_configuration_v1) bool { /// Tests the output configuration. /// If rollback is false all changes are applied to the pending state of the affected outputs. -fn testOutputConfig(config: *c.wlr_output_configuration_v1, rollback: bool) bool { +fn testOutputConfig(config: *wlr.OutputConfigurationV1, rollback: bool) bool { var ok = true; - const list_head: *c.wl_list = &config.heads; - var it: *c.wl_list = list_head.next; - while (it != list_head) : (it = it.next) { - const head = @fieldParentPtr(c.wlr_output_configuration_head_v1, "link", it); - const wlr_output = @as(*c.wlr_output, head.state.output); + var it = config.heads.iterator(.forward); + while (it.next()) |head| { + const wlr_output = head.state.output; - const width = if (@as(?*c.wlr_output_mode, head.state.mode)) |m| m.width else head.state.custom_mode.width; - const height = if (@as(?*c.wlr_output_mode, head.state.mode)) |m| m.height else head.state.custom_mode.height; + const width = if (head.state.mode) |m| m.width else head.state.custom_mode.width; + const height = if (head.state.mode) |m| m.height else head.state.custom_mode.height; const scale = head.state.scale; const too_small = (@intToFloat(f32, width) / scale < min_size) or @@ -242,24 +237,20 @@ fn testOutputConfig(config: *c.wlr_output_configuration_v1, rollback: bool) bool } applyHeadToOutput(head, wlr_output); - ok = ok and !too_small and c.wlr_output_test(wlr_output); + ok = ok and !too_small and wlr_output.testCommit(); } if (rollback or !ok) { // Rollback all changes - it = list_head.next; - while (it != list_head) : (it = it.next) { - const head = @fieldParentPtr(c.wlr_output_configuration_head_v1, "link", it); - const wlr_output = @as(*c.wlr_output, head.state.output); - c.wlr_output_rollback(wlr_output); - } + it = config.heads.iterator(.forward); + while (it.next()) |head| head.state.output.rollback(); } return ok; } -fn applyHeadToOutput(head: *c.wlr_output_configuration_head_v1, wlr_output: *c.wlr_output) void { - c.wlr_output_enable(wlr_output, head.state.enabled); +fn applyHeadToOutput(head: *wlr.OutputConfigurationV1.Head, wlr_output: *wlr.Output) void { + wlr_output.enable(head.state.enabled); // The output must be enabled for the following properties to apply if (head.state.enabled) { // TODO(wlroots) Somehow on the drm backend setting the mode causes @@ -268,43 +259,41 @@ fn applyHeadToOutput(head: *c.wlr_output_configuration_head_v1, wlr_output: *c.w // We can just ignore this because nothing bad happens but it // should be fixed in the future // See https://github.com/swaywm/wlroots/issues/2492 - if (head.state.mode != null) { - c.wlr_output_set_mode(wlr_output, head.state.mode); + if (head.state.mode) |mode| { + wlr_output.setMode(mode); } else { log.info(.output_manager, "custom modes are not supported until the next wlroots release: ignoring", .{}); // TODO(wlroots) uncomment the following lines when wlroots 0.13.0 is released // See https://github.com/swaywm/wlroots/pull/2517 //const custom_mode = &head.state.custom_mode; - //c.wlr_output_set_custom_mode(wlr_output, custom_mode.width, custom_mode.height, custom_mode.refresh); + //wlr_output.setCustomMode(custom_mode.width, custom_mode.height, custom_mode.refresh); } // TODO(wlroots) Figure out if this conversion is needed or if that is a bug in wlroots - c.wlr_output_set_scale(wlr_output, @floatCast(f32, head.state.scale)); - c.wlr_output_set_transform(wlr_output, head.state.transform); + wlr_output.setScale(@floatCast(f32, head.state.scale)); + wlr_output.setTransform(head.state.transform); } } -/// Creates an wlr_output_configuration from the current configuration -fn createOutputConfigurationFromCurrent(self: *Self) !*c.wlr_output_configuration_v1 { - var config = c.wlr_output_configuration_v1_create() orelse return error.OutOfMemory; - errdefer c.wlr_output_configuration_v1_destroy(config); +/// Create the config describing the current configuration +fn ouputConfigFromCurrent(self: *Self) !*wlr.OutputConfigurationV1 { + const config = try wlr.OutputConfigurationV1.create(); + // this destroys all associated config heads as well + errdefer config.destroy(); var it = self.root.all_outputs.first; - while (it) |node| : (it = node.next) { - try self.createHead(node.data, config); - } + while (it) |node| : (it = node.next) try self.createHead(node.data, config); return config; } -fn createHead(self: *Self, output: *Output, config: *c.wlr_output_configuration_v1) !void { +fn createHead(self: *Self, output: *Output, config: *wlr.OutputConfigurationV1) !void { const wlr_output = output.wlr_output; - const head: *c.wlr_output_configuration_head_v1 = c.wlr_output_configuration_head_v1_create(config, wlr_output) orelse - return error.OutOfMemory; + const head = try wlr.OutputConfigurationV1.Head.create(config, wlr_output); - // If the output is not part of the layout (and thus disabled) we dont care about the position - const box = @as(?*c.wlr_box, c.wlr_output_layout_get_box(self.root.wlr_output_layout, wlr_output)); - if (box) |b| { - head.state.x = b.x; - head.state.y = b.y; + // If the output is not part of the layout (and thus disabled) we dont care + // about the position + if (output.root.output_layout.getBox(wlr_output)) |box| { + head.state.x = box.x; + head.state.y = box.y; } } diff --git a/river/OutputStatus.zig b/river/OutputStatus.zig index 298a9d1..4b8e22c 100644 --- a/river/OutputStatus.zig +++ b/river/OutputStatus.zig @@ -18,8 +18,10 @@ const Self = @This(); const std = @import("std"); +const wayland = @import("wayland"); +const wl = wayland.server.wl; +const zriver = wayland.server.zriver; -const c = @import("c.zig"); const log = @import("log.zig"); const util = @import("util.zig"); @@ -27,29 +29,28 @@ const Output = @import("Output.zig"); const View = @import("View.zig"); const ViewStack = @import("view_stack.zig").ViewStack; -const implementation = c.struct_zriver_output_status_v1_interface{ .destroy = destroy }; - output: *Output, -wl_resource: *c.wl_resource, +output_status: *zriver.OutputStatusV1, -pub fn init(self: *Self, output: *Output, wl_resource: *c.wl_resource) void { - self.* = .{ .output = output, .wl_resource = wl_resource }; +pub fn init(self: *Self, output: *Output, output_status: *zriver.OutputStatusV1) void { + self.* = .{ .output = output, .output_status = output_status }; - c.wl_resource_set_implementation(wl_resource, &implementation, self, handleResourceDestroy); + output_status.setHandler(*Self, handleRequest, handleDestroy, self); // Send view/focused tags once on bind. self.sendViewTags(); self.sendFocusedTags(); } -fn handleResourceDestroy(wl_resource: ?*c.wl_resource) callconv(.C) void { - const self = util.voidCast(Self, @ptrCast(*c_void, c.wl_resource_get_user_data(wl_resource))); - const node = @fieldParentPtr(std.SinglyLinkedList(Self).Node, "data", self); - self.output.status_trackers.remove(node); +fn handleRequest(output_status: *zriver.OutputStatusV1, request: zriver.OutputStatusV1.Request, self: *Self) void { + switch (request) { + .destroy => output_status.destroy(), + } } -fn destroy(wl_client: ?*c.wl_client, wl_resource: ?*c.wl_resource) callconv(.C) void { - c.wl_resource_destroy(wl_resource); +fn handleDestroy(output_status: *zriver.OutputStatusV1, self: *Self) void { + const node = @fieldParentPtr(std.SinglyLinkedList(Self).Node, "data", self); + self.output.status_trackers.remove(node); } /// Send the current tags of each view on the output to the client. @@ -61,21 +62,17 @@ pub fn sendViewTags(self: Self) void { while (it) |node| : (it = node.next) { if (node.view.destroying) continue; view_tags.append(node.view.current.tags) catch { - c.wl_resource_post_no_memory(self.wl_resource); + self.output_status.postNoMemory(); log.crit(.river_status, "out of memory", .{}); return; }; } - var wl_array = c.wl_array{ - .size = view_tags.items.len * @sizeOf(u32), - .alloc = view_tags.capacity * @sizeOf(u32), - .data = view_tags.items.ptr, - }; - c.zriver_output_status_v1_send_view_tags(self.wl_resource, &wl_array); + var wl_array = wl.Array.fromArrayList(u32, view_tags); + self.output_status.sendViewTags(&wl_array); } /// Send the currently focused tags of the output to the client. pub fn sendFocusedTags(self: Self) void { - c.zriver_output_status_v1_send_focused_tags(self.wl_resource, self.output.current.tags); + self.output_status.sendFocusedTags(self.output.current.tags); } diff --git a/river/PointerMapping.zig b/river/PointerMapping.zig index cc41cc9..270e3a8 100644 --- a/river/PointerMapping.zig +++ b/river/PointerMapping.zig @@ -15,11 +15,13 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +const wlr = @import("wlroots"); + pub const Action = enum { move, resize, }; event_code: u32, -modifiers: u32, +modifiers: wlr.Keyboard.ModifierMask, action: Action, diff --git a/river/Root.zig b/river/Root.zig index b707143..4218276 100644 --- a/river/Root.zig +++ b/river/Root.zig @@ -17,10 +17,11 @@ const Self = @This(); -const std = @import("std"); const build_options = @import("build_options"); +const std = @import("std"); +const wlr = @import("wlroots"); +const wl = @import("wayland").server.wl; -const c = @import("c.zig"); const log = @import("log.zig"); const util = @import("util.zig"); @@ -31,10 +32,9 @@ const ViewStack = @import("view_stack.zig").ViewStack; const XwaylandUnmanaged = @import("XwaylandUnmanaged.zig"); const DragIcon = @import("DragIcon.zig"); -/// Responsible for all windowing operations server: *Server, -wlr_output_layout: *c.wlr_output_layout, +output_layout: *wlr.OutputLayout, /// A list of all outputs all_outputs: std.TailQueue(*Output) = .{}, @@ -60,24 +60,20 @@ else pending_configures: u32 = 0, /// Handles timeout of transactions -transaction_timer: *c.wl_event_source, +transaction_timer: *wl.EventSource, pub fn init(self: *Self, server: *Server) !void { - // Create an output layout, which a wlroots utility for working with an - // arrangement of screens in a physical layout. - errdefer c.wlr_output_layout_destroy(self.wlr_output_layout); + const output_layout = try wlr.OutputLayout.create(); + errdefer output_layout.destroy(); + self.* = .{ .server = server, - .wlr_output_layout = c.wlr_output_layout_create() orelse return error.OutOfMemory, - .transaction_timer = c.wl_event_loop_add_timer( - c.wl_display_get_event_loop(self.server.wl_display), - handleTimeout, - self, - ) orelse return error.AddTimerError, + .output_layout = output_layout, + .transaction_timer = try self.server.wl_server.getEventLoop().addTimer(*Self, handleTimeout, self), .noop_output = undefined, }; - const noop_wlr_output = c.wlr_noop_add_output(server.noop_backend) orelse return error.OutOfMemory; + const noop_wlr_output = try server.noop_backend.noopAddOutput(); try self.noop_output.init(self, noop_wlr_output); } @@ -86,14 +82,12 @@ pub fn deinit(self: *Self) void { // the noop backend triggering the destroy event. However, // Output.handleDestroy is not intended to handle the noop output being // destroyed. - c.wl_list_remove(&self.noop_output.listen_destroy.link); - c.wl_list_remove(&self.noop_output.listen_frame.link); - c.wl_list_remove(&self.noop_output.listen_mode.link); + self.noop_output.destroy.link.remove(); + self.noop_output.frame.link.remove(); + self.noop_output.mode.link.remove(); - c.wlr_output_layout_destroy(self.wlr_output_layout); - - // This literally cannot fail, but for some reason returns 0 - if (c.wl_event_source_remove(self.transaction_timer) < 0) unreachable; + self.output_layout.destroy(); + self.transaction_timer.remove(); } /// Removes the output in node.data from self.outputs @@ -124,7 +118,7 @@ pub fn removeOutput(self: *Self, node: *std.TailQueue(Output).Node) void { // handle them. self.noop_output.layers[layer_idx].prepend(layer_node); layer_surface.output = &self.noop_output; - c.wlr_layer_surface_v1_close(layer_surface.wlr_layer_surface); + layer_surface.wlr_layer_surface.close(); } } @@ -153,7 +147,7 @@ pub fn addOutput(self: *Self, node: *std.TailQueue(Output).Node) void { // from left-to-right in the order they appear. A more sophisticated // compositor would let the user configure the arrangement of outputs in the // layout. This automatically creates an output global on the wl_display. - c.wlr_output_layout_add_auto(self.wlr_output_layout, node.data.wlr_output); + self.output_layout.addAuto(node.data.wlr_output); // if we previously had no real outputs, move focus from the noop output // to the new one. @@ -222,26 +216,22 @@ pub fn startTransaction(self: *Self) void { ); // Set timeout to 200ms - if (c.wl_event_source_timer_update(self.transaction_timer, 200) < 0) { + self.transaction_timer.timerUpdate(200) catch { log.err(.transaction, "failed to update timer", .{}); self.commitTransaction(); - } + }; } else { // No views need configures, clear the current timer in case we are // interrupting another transaction and commit. - if (c.wl_event_source_timer_update(self.transaction_timer, 0) < 0) - log.err(.transaction, "error disarming timer", .{}); + self.transaction_timer.timerUpdate(0) catch log.err(.transaction, "error disarming timer", .{}); self.commitTransaction(); } } -fn handleTimeout(data: ?*c_void) callconv(.C) c_int { - const self = util.voidCast(Self, data.?); - +fn handleTimeout(self: *Self) callconv(.C) c_int { log.err(.transaction, "timeout occurred, some imperfect frames may be shown", .{}); self.pending_configures = 0; - self.commitTransaction(); return 0; @@ -251,8 +241,7 @@ pub fn notifyConfigured(self: *Self) void { self.pending_configures -= 1; if (self.pending_configures == 0) { // Disarm the timer, as we didn't timeout - if (c.wl_event_source_timer_update(self.transaction_timer, 0) == -1) - log.err(.transaction, "error disarming timer", .{}); + self.transaction_timer.timerUpdate(0) catch log.err(.transaction, "error disarming timer", .{}); self.commitTransaction(); } } diff --git a/river/Seat.zig b/river/Seat.zig index d2184d9..4a00d1a 100644 --- a/river/Seat.zig +++ b/river/Seat.zig @@ -19,8 +19,10 @@ const Self = @This(); const build_options = @import("build_options"); const std = @import("std"); +const wlr = @import("wlroots"); +const wl = @import("wayland").server.wl; +const xkb = @import("xkbcommon"); -const c = @import("c.zig"); const command = @import("command.zig"); const log = @import("log.zig"); const util = @import("util.zig"); @@ -42,7 +44,7 @@ const FocusTarget = union(enum) { }; input_manager: *InputManager, -wlr_seat: *c.wlr_seat, +wlr_seat: *wlr.Seat, /// Multiple mice are handled by the same Cursor cursor: Cursor = undefined, @@ -56,7 +58,8 @@ mode_id: usize = 0, /// ID of previous keymap mode, used when returning from "locked" mode prev_mode_id: usize = 0, -/// Currently focused output, may be the noop output if no +/// Currently focused output, may be the noop output if no real output +/// is currently available for focus. focused_output: *Output, /// Currently focused view/layer surface if any @@ -69,33 +72,33 @@ focus_stack: ViewStack(*View) = .{}, /// List of status tracking objects relaying changes to this seat to clients. status_trackers: std.SinglyLinkedList(SeatStatus) = .{}, -listen_request_set_selection: c.wl_listener = undefined, -listen_request_start_drag: c.wl_listener = undefined, -listen_start_drag: c.wl_listener = undefined, -listen_request_set_primary_selection: c.wl_listener = undefined, +request_set_selection: wl.Listener(*wlr.Seat.event.RequestSetSelection) = undefined, +request_start_drag: wl.Listener(*wlr.Seat.event.RequestStartDrag) = undefined, +start_drag: wl.Listener(*wlr.Drag) = undefined, +request_set_primary_selection: wl.Listener(*wlr.Seat.event.RequestSetPrimarySelection) = undefined, pub fn init(self: *Self, input_manager: *InputManager, name: [*:0]const u8) !void { self.* = .{ .input_manager = input_manager, // This will be automatically destroyed when the display is destroyed - .wlr_seat = c.wlr_seat_create(input_manager.server.wl_display, name) orelse return error.OutOfMemory, + .wlr_seat = try wlr.Seat.create(input_manager.server.wl_server, name), .focused_output = &self.input_manager.server.root.noop_output, }; - self.wlr_seat.data = self; + self.wlr_seat.data = @ptrToInt(self); try self.cursor.init(self); - self.listen_request_set_selection.notify = handleRequestSetSelection; - c.wl_signal_add(&self.wlr_seat.events.request_set_selection, &self.listen_request_set_selection); + self.request_set_selection.setNotify(handleRequestSetSelection); + self.wlr_seat.events.request_set_selection.add(&self.request_set_selection); - self.listen_request_start_drag.notify = handleRequestStartDrag; - c.wl_signal_add(&self.wlr_seat.events.request_start_drag, &self.listen_request_start_drag); + self.request_start_drag.setNotify(handleRequestStartDrag); + self.wlr_seat.events.request_start_drag.add(&self.request_start_drag); - self.listen_start_drag.notify = handleStartDrag; - c.wl_signal_add(&self.wlr_seat.events.start_drag, &self.listen_start_drag); + self.start_drag.setNotify(handleStartDrag); + self.wlr_seat.events.start_drag.add(&self.start_drag); - self.listen_request_set_primary_selection.notify = handleRequestPrimarySelection; - c.wl_signal_add(&self.wlr_seat.events.request_set_primary_selection, &self.listen_request_set_primary_selection); + self.request_set_primary_selection.setNotify(handleRequestPrimarySelection); + self.wlr_seat.events.request_set_primary_selection.add(&self.request_set_primary_selection); } pub fn deinit(self: *Self) void { @@ -180,29 +183,28 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void { // If the target is already focused, do nothing if (std.meta.eql(new_focus, self.focused)) return; - // Obtain the target wlr_surface - const target_wlr_surface = switch (new_focus) { - .view => |target_view| target_view.wlr_surface.?, - .layer => |target_layer| target_layer.wlr_layer_surface.surface.?, + // Obtain the target surface + const target_surface = switch (new_focus) { + .view => |target_view| target_view.surface.?, + .layer => |target_layer| target_layer.wlr_layer_surface.surface, .none => null, }; // If input is not allowed on the target surface (e.g. due to an active // input inhibitor) do not set focus. If there is no target surface we // still clear the focus. - if (if (target_wlr_surface) |wlr_surface| self.input_manager.inputAllowed(wlr_surface) else true) { + if (if (target_surface) |wlr_surface| self.input_manager.inputAllowed(wlr_surface) else true) { // First clear the current focus if (self.focused == .view) { self.focused.view.pending.focus -= 1; // This is needed because xwayland views don't double buffer // activated state. if (build_options.xwayland and self.focused.view.impl == .xwayland_view) - c.wlr_xwayland_surface_activate(self.focused.view.impl.xwayland_view.wlr_xwayland_surface, false); + self.focused.view.impl.xwayland_view.xwayland_surface.activate(false); if (self.focused.view.pending.focus == 0 and !self.focused.view.pending.fullscreen) { self.focused.view.pending.target_opacity = self.input_manager.server.config.view_opacity_unfocused; } } - c.wlr_seat_keyboard_clear_focus(self.wlr_seat); // Set the new focus switch (new_focus) { @@ -212,7 +214,7 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void { // This is needed because xwayland views don't double buffer // activated state. if (build_options.xwayland and target_view.impl == .xwayland_view) - c.wlr_xwayland_surface_activate(target_view.impl.xwayland_view.wlr_xwayland_surface, true); + target_view.impl.xwayland_view.xwayland_surface.activate(true); if (!target_view.pending.fullscreen) { target_view.pending.target_opacity = self.input_manager.server.config.view_opacity_focused; } @@ -222,16 +224,20 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void { } self.focused = new_focus; - // Tell wlroots to send the new keyboard focus if we have a target - if (target_wlr_surface) |wlr_surface| { - const keyboard: *c.wlr_keyboard = c.wlr_seat_get_keyboard(self.wlr_seat); - c.wlr_seat_keyboard_notify_enter( - self.wlr_seat, - wlr_surface, - &keyboard.keycodes, - keyboard.num_keycodes, - &keyboard.modifiers, - ); + // Send surface enter/leave events + if (target_surface) |wlr_surface| { + if (self.wlr_seat.getKeyboard()) |keyboard| { + self.wlr_seat.keyboardNotifyEnter( + wlr_surface, + &keyboard.keycodes, + keyboard.num_keycodes, + &keyboard.modifiers, + ); + } else { + self.wlr_seat.keyboardNotifyEnter(wlr_surface, null, 0, null); + } + } else { + self.wlr_seat.keyboardClearFocus(); } } @@ -256,7 +262,7 @@ pub fn focusOutput(self: *Self, output: *Output) void { } pub fn handleActivity(self: Self) void { - c.wlr_idle_notify_activity(self.input_manager.wlr_idle, self.wlr_seat); + self.input_manager.idle.notifyActivity(self.wlr_seat); } /// Handle the unmapping of a view, removing it from the focus stack and @@ -280,10 +286,15 @@ pub fn handleViewUnmap(self: *Self, view: *View) void { /// Handle any user-defined mapping for the passed keysym and modifiers /// Returns true if the key was handled -pub fn handleMapping(self: *Self, keysym: c.xkb_keysym_t, modifiers: u32, released: bool) bool { +pub fn handleMapping( + self: *Self, + keysym: xkb.Keysym, + modifiers: wlr.Keyboard.ModifierMask, + released: bool, +) bool { const modes = &self.input_manager.server.config.modes; for (modes.items[self.mode_id].mappings.items) |mapping| { - if (modifiers == mapping.modifiers and keysym == mapping.keysym and released == mapping.release) { + if (std.meta.eql(modifiers, mapping.modifiers) and keysym == mapping.keysym and released == mapping.release) { // Execute the bound command const args = mapping.command_args; var out: ?[]const u8 = null; @@ -309,22 +320,23 @@ pub fn handleMapping(self: *Self, keysym: c.xkb_keysym_t, modifiers: u32, releas /// Add a newly created input device to the seat and update the reported /// capabilities. -pub fn addDevice(self: *Self, device: *c.wlr_input_device) void { +pub fn addDevice(self: *Self, device: *wlr.InputDevice) void { switch (device.type) { - .WLR_INPUT_DEVICE_KEYBOARD => self.addKeyboard(device) catch return, - .WLR_INPUT_DEVICE_POINTER => self.addPointer(device), + .keyboard => self.addKeyboard(device) catch return, + .pointer => self.addPointer(device), else => return, } // We need to let the wlr_seat know what our capabilities are, which is // communiciated to the client. We always have a cursor, even if // there are no pointer devices, so we always include that capability. - var caps = @intCast(u32, c.WL_SEAT_CAPABILITY_POINTER); - if (self.keyboards.len > 0) caps |= @intCast(u32, c.WL_SEAT_CAPABILITY_KEYBOARD); - c.wlr_seat_set_capabilities(self.wlr_seat, caps); + self.wlr_seat.setCapabilities(.{ + .pointer = true, + .keyboard = self.keyboards.len > 0, + }); } -fn addKeyboard(self: *Self, device: *c.wlr_input_device) !void { +fn addKeyboard(self: *Self, device: *wlr.InputDevice) !void { const node = try util.gpa.create(std.TailQueue(Keyboard).Node); node.data.init(self, device) catch |err| { switch (err) { @@ -335,40 +347,46 @@ fn addKeyboard(self: *Self, device: *c.wlr_input_device) !void { return; }; self.keyboards.append(node); - c.wlr_seat_set_keyboard(self.wlr_seat, device); + self.wlr_seat.setKeyboard(device); } -fn addPointer(self: Self, device: *c.struct_wlr_input_device) void { +fn addPointer(self: Self, device: *wlr.InputDevice) void { // We don't do anything special with pointers. All of our pointer handling // is proxied through wlr_cursor. On another compositor, you might take this // opportunity to do libinput configuration on the device to set // acceleration, etc. - c.wlr_cursor_attach_input_device(self.cursor.wlr_cursor, device); + self.cursor.wlr_cursor.attachInputDevice(device); } -fn handleRequestSetSelection(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_request_set_selection", listener.?); - const event = util.voidCast(c.wlr_seat_request_set_selection_event, data.?); - c.wlr_seat_set_selection(self.wlr_seat, event.source, event.serial); +fn handleRequestSetSelection( + listener: *wl.Listener(*wlr.Seat.event.RequestSetSelection), + event: *wlr.Seat.event.RequestSetSelection, +) void { + const self = @fieldParentPtr(Self, "request_set_selection", listener); + self.wlr_seat.setSelection(event.source, event.serial); } -fn handleRequestStartDrag(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_request_start_drag", listener.?); - const event = util.voidCast(c.wlr_seat_request_start_drag_event, data.?); +fn handleRequestStartDrag( + listener: *wl.Listener(*wlr.Seat.event.RequestStartDrag), + event: *wlr.Seat.event.RequestStartDrag, +) void { + const self = @fieldParentPtr(Self, "request_start_drag", listener); - if (c.wlr_seat_validate_pointer_grab_serial(self.wlr_seat, event.origin, event.serial)) { - log.debug(.seat, "starting pointer drag", .{}); - c.wlr_seat_start_pointer_drag(self.wlr_seat, event.drag, event.serial); + if (!self.wlr_seat.validatePointerGrabSerial(event.origin, event.serial)) { + log.debug(.seat, "ignoring request to start drag, failed to validate serial {}", .{event.serial}); + if (event.drag.source) |source| source.destroy(); return; } - log.debug(.seat, "ignoring request to start drag, failed to validate serial {}", .{event.serial}); - c.wlr_data_source_destroy(event.drag.*.source); + log.debug(.seat, "starting pointer drag", .{}); + self.wlr_seat.startPointerDrag(event.drag, event.serial); } -fn handleStartDrag(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_start_drag", listener.?); - const wlr_drag = util.voidCast(c.wlr_drag, data.?); +fn handleStartDrag( + listener: *wl.Listener(*wlr.Drag), + wlr_drag: *wlr.Drag, +) void { + const self = @fieldParentPtr(Self, "start_drag", listener); if (wlr_drag.icon) |wlr_drag_icon| { const node = util.gpa.create(std.SinglyLinkedList(DragIcon).Node) catch { @@ -381,8 +399,10 @@ fn handleStartDrag(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void self.cursor.mode = .passthrough; } -fn handleRequestPrimarySelection(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_request_set_primary_selection", listener.?); - const event = util.voidCast(c.wlr_seat_request_set_primary_selection_event, data.?); - c.wlr_seat_set_primary_selection(self.wlr_seat, event.source, event.serial); +fn handleRequestPrimarySelection( + listener: *wl.Listener(*wlr.Seat.event.RequestSetPrimarySelection), + event: *wlr.Seat.event.RequestSetPrimarySelection, +) void { + const self = @fieldParentPtr(Self, "request_set_primary_selection", listener); + self.wlr_seat.setPrimarySelection(event.source, event.serial); } diff --git a/river/SeatStatus.zig b/river/SeatStatus.zig index 44f3ae3..a6c0d46 100644 --- a/river/SeatStatus.zig +++ b/river/SeatStatus.zig @@ -18,55 +18,53 @@ const Self = @This(); const std = @import("std"); +const wayland = @import("wayland"); +const wl = wayland.server.wl; +const zriver = wayland.server.zriver; -const c = @import("c.zig"); const util = @import("util.zig"); const Seat = @import("Seat.zig"); const Output = @import("Output.zig"); const View = @import("View.zig"); -const implementation = c.struct_zriver_seat_status_v1_interface{ .destroy = destroy }; - seat: *Seat, -wl_resource: *c.wl_resource, +seat_status: *zriver.SeatStatusV1, -pub fn init(self: *Self, seat: *Seat, wl_resource: *c.wl_resource) void { - self.* = .{ .seat = seat, .wl_resource = wl_resource }; +pub fn init(self: *Self, seat: *Seat, seat_status: *zriver.SeatStatusV1) void { + self.* = .{ .seat = seat, .seat_status = seat_status }; - c.wl_resource_set_implementation(wl_resource, &implementation, self, handleResourceDestroy); + seat_status.setHandler(*Self, handleRequest, handleDestroy, self); // Send focused output/view once on bind self.sendOutput(.focused); self.sendFocusedView(); } -fn handleResourceDestroy(wl_resource: ?*c.wl_resource) callconv(.C) void { - const self = util.voidCast(Self, c.wl_resource_get_user_data(wl_resource).?); +fn handleRequest(seat_status: *zriver.SeatStatusV1, request: zriver.SeatStatusV1.Request, self: *Self) void { + switch (request) { + .destroy => seat_status.destroy(), + } +} + +fn handleDestroy(seat_status: *zriver.SeatStatusV1, self: *Self) void { const node = @fieldParentPtr(std.SinglyLinkedList(Self).Node, "data", self); self.seat.status_trackers.remove(node); util.gpa.destroy(node); } -fn destroy(wl_client: ?*c.wl_client, wl_resource: ?*c.wl_resource) callconv(.C) void { - c.wl_resource_destroy(wl_resource); -} - pub fn sendOutput(self: Self, state: enum { focused, unfocused }) void { - const wl_client = c.wl_resource_get_client(self.wl_resource); - const output_resources = &self.seat.focused_output.wlr_output.resources; - var output_resource = c.wl_resource_from_link(output_resources.next); - while (c.wl_resource_get_link(output_resource) != output_resources) : (output_resource = - c.wl_resource_from_link(c.wl_resource_get_link(output_resource).*.next)) - { - if (c.wl_resource_get_client(output_resource) == wl_client) switch (state) { - .focused => c.zriver_seat_status_v1_send_focused_output(self.wl_resource, output_resource), - .unfocused => c.zriver_seat_status_v1_send_unfocused_output(self.wl_resource, output_resource), + const client = self.seat_status.getClient(); + var it = self.seat.focused_output.wlr_output.resources.iterator(.forward); + while (it.next()) |wl_output| { + if (wl_output.getClient() == client) switch (state) { + .focused => self.seat_status.sendFocusedOutput(wl_output), + .unfocused => self.seat_status.sendUnfocusedOutput(wl_output), }; } } pub fn sendFocusedView(self: Self) void { const title: [*:0]const u8 = if (self.seat.focused == .view) self.seat.focused.view.getTitle() else ""; - c.zriver_seat_status_v1_send_focused_view(self.wl_resource, title); + self.seat_status.sendFocusedView(title); } diff --git a/river/Server.zig b/river/Server.zig index b9b88b8..1d91061 100644 --- a/river/Server.zig +++ b/river/Server.zig @@ -19,6 +19,8 @@ const Self = @This(); const build_options = @import("build_options"); const std = @import("std"); +const wlr = @import("wlroots"); +const wl = @import("wayland").server.wl; const c = @import("c.zig"); const log = @import("log.zig"); @@ -37,22 +39,22 @@ const View = @import("View.zig"); const ViewStack = @import("view_stack.zig").ViewStack; const XwaylandUnmanaged = @import("XwaylandUnmanaged.zig"); -wl_display: *c.wl_display, +wl_server: *wl.Server, -sigint_source: *c.wl_event_source, -sigterm_source: *c.wl_event_source, +sigint_source: *wl.EventSource, +sigterm_source: *wl.EventSource, -wlr_backend: *c.wlr_backend, -noop_backend: *c.wlr_backend, +backend: *wlr.Backend, +noop_backend: *wlr.Backend, -wlr_xdg_shell: *c.wlr_xdg_shell, -listen_new_xdg_surface: c.wl_listener, +xdg_shell: *wlr.XdgShell, +new_xdg_surface: wl.Listener(*wlr.XdgSurface), -wlr_layer_shell: *c.wlr_layer_shell_v1, -listen_new_layer_surface: c.wl_listener, +layer_shell: *wlr.LayerShellV1, +new_layer_surface: wl.Listener(*wlr.LayerSurfaceV1), -wlr_xwayland: if (build_options.xwayland) *c.wlr_xwayland else void, -listen_new_xwayland_surface: if (build_options.xwayland) c.wl_listener else void, +xwayland: if (build_options.xwayland) *wlr.Xwayland else void, +new_xwayland_surface: if (build_options.xwayland) wl.Listener(*wlr.XwaylandSurface) else void, decoration_manager: DecorationManager, input_manager: InputManager, @@ -63,61 +65,46 @@ control: Control, status_manager: StatusManager, pub fn init(self: *Self) !void { - // The Wayland display is managed by libwayland. It handles accepting - // clients from the Unix socket, managing Wayland globals, and so on. - self.wl_display = c.wl_display_create() orelse return error.CreateDisplayError; - errdefer c.wl_display_destroy(self.wl_display); + self.wl_server = try wl.Server.create(); + errdefer self.wl_server.destroy(); - // Never returns null if the display was created successfully - const wl_event_loop = c.wl_display_get_event_loop(self.wl_display); - self.sigint_source = c.wl_event_loop_add_signal(wl_event_loop, std.os.SIGINT, terminate, self.wl_display) orelse - return error.AddEventSourceFailed; - errdefer _ = c.wl_event_source_remove(self.sigint_source); - self.sigterm_source = c.wl_event_loop_add_signal(wl_event_loop, std.os.SIGTERM, terminate, self.wl_display) orelse - return error.AddEventSourceFailed; - errdefer _ = c.wl_event_source_remove(self.sigterm_source); + const loop = self.wl_server.getEventLoop(); + self.sigint_source = try loop.addSignal(*wl.Server, std.os.SIGINT, terminate, self.wl_server); + errdefer self.sigint_source.remove(); + self.sigterm_source = try loop.addSignal(*wl.Server, std.os.SIGTERM, terminate, self.wl_server); + errdefer self.sigterm_source.remove(); - // The wlr_backend abstracts the input/output hardware. Autocreate chooses - // the best option based on the environment, for example DRM when run from - // a tty or wayland if WAYLAND_DISPLAY is set. This frees itself when the - // wl_display is destroyed. - self.wlr_backend = c.river_wlr_backend_autocreate(self.wl_display) orelse - return error.CreateBackendError; + // This frees itself when the wl.Server is destroyed + self.backend = try wlr.Backend.autocreate(self.wl_server, null); // This backend is used to create a noop output for use when no actual - // outputs are available. This frees itself when the wl_display is destroyed. - self.noop_backend = c.wlr_noop_backend_create(self.wl_display) orelse - return error.OutOfMemory; + // outputs are available. This frees itself when the wl.Server is destroyed. + self.noop_backend = try wlr.Backend.createNoop(self.wl_server); - // If we don't provide a renderer, autocreate makes a GLES2 renderer for us. - // The renderer is responsible for defining the various pixel formats it - // supports for shared memory, this configures that for clients. - const wlr_renderer = c.wlr_backend_get_renderer(self.wlr_backend).?; - if (!c.wlr_renderer_init_wl_display(wlr_renderer, self.wl_display)) return error.DisplayInitFailed; + // This will never be null for the non-custom backends in wlroots + const renderer = self.backend.getRenderer().?; + try renderer.initServer(self.wl_server); - const wlr_compositor = c.wlr_compositor_create(self.wl_display, wlr_renderer) orelse - return error.OutOfMemory; + const compositor = try wlr.Compositor.create(self.wl_server, renderer); // Set up xdg shell - self.wlr_xdg_shell = c.wlr_xdg_shell_create(self.wl_display) orelse return error.OutOfMemory; - self.listen_new_xdg_surface.notify = handleNewXdgSurface; - c.wl_signal_add(&self.wlr_xdg_shell.events.new_surface, &self.listen_new_xdg_surface); + self.xdg_shell = try wlr.XdgShell.create(self.wl_server); + self.new_xdg_surface.setNotify(handleNewXdgSurface); + self.xdg_shell.events.new_surface.add(&self.new_xdg_surface); // Set up layer shell - self.wlr_layer_shell = c.wlr_layer_shell_v1_create(self.wl_display) orelse return error.OutOfMemory; - self.listen_new_layer_surface.notify = handleNewLayerSurface; - c.wl_signal_add(&self.wlr_layer_shell.events.new_surface, &self.listen_new_layer_surface); + self.layer_shell = try wlr.LayerShellV1.create(self.wl_server); + self.new_layer_surface.setNotify(handleNewLayerSurface); + self.layer_shell.events.new_surface.add(&self.new_layer_surface); // Set up xwayland if built with support if (build_options.xwayland) { - self.wlr_xwayland = c.wlr_xwayland_create(self.wl_display, wlr_compositor, false) orelse - return error.CreateXwaylandError; - self.listen_new_xwayland_surface.notify = handleNewXwaylandSurface; - c.wl_signal_add(&self.wlr_xwayland.events.new_surface, &self.listen_new_xwayland_surface); + self.xwayland = try wlr.Xwayland.create(self.wl_server, compositor, false); + self.new_xwayland_surface.setNotify(handleNewXwaylandSurface); + self.xwayland.events.new_surface.add(&self.new_xwayland_surface); } - // Set up primary selection - _ = c.wlr_primary_selection_v1_device_manager_create(self.wl_display); + _ = try wlr.PrimarySelectionDeviceManagerV1.create(self.wl_server); self.config = try Config.init(); try self.decoration_manager.init(self); @@ -128,31 +115,28 @@ pub fn init(self: *Self) !void { try self.status_manager.init(self); try self.output_manager.init(self); - // These all free themselves when the wl_display is destroyed - _ = c.wlr_data_device_manager_create(self.wl_display) orelse return error.OutOfMemory; - _ = c.wlr_data_control_manager_v1_create(self.wl_display) orelse return error.OutOfMemory; - _ = c.wlr_export_dmabuf_manager_v1_create(self.wl_display) orelse return error.OutOfMemory; - _ = c.wlr_gamma_control_manager_v1_create(self.wl_display) orelse return error.OutOfMemory; - _ = c.wlr_screencopy_manager_v1_create(self.wl_display) orelse return error.OutOfMemory; - _ = c.wlr_viewporter_create(self.wl_display) orelse return error.OutOfMemory; - _ = c.wlr_xdg_output_manager_v1_create(self.wl_display, self.root.wlr_output_layout) orelse - return error.OutOfMemory; + // These all free themselves when the wl_server is destroyed + _ = try wlr.DataDeviceManager.create(self.wl_server); + _ = try wlr.DataControlManagerV1.create(self.wl_server); + _ = try wlr.ExportDmabufManagerV1.create(self.wl_server); + _ = try wlr.GammaControlManagerV1.create(self.wl_server); + _ = try wlr.ScreencopyManagerV1.create(self.wl_server); + _ = try wlr.Viewporter.create(self.wl_server); } -/// Free allocated memory and clean up +/// Free allocated memory and clean up. Note: order is important here pub fn deinit(self: *Self) void { - // Note: order is important here - _ = c.wl_event_source_remove(self.sigint_source); - _ = c.wl_event_source_remove(self.sigterm_source); + self.sigint_source.remove(); + self.sigterm_source.remove(); - if (build_options.xwayland) c.wlr_xwayland_destroy(self.wlr_xwayland); + if (build_options.xwayland) self.xwayland.destroy(); - c.wl_display_destroy_clients(self.wl_display); + self.wl_server.destroyClients(); self.root.deinit(); - c.wl_display_destroy(self.wl_display); - c.wlr_backend_destroy(self.noop_backend); + self.wl_server.destroy(); + self.noop_backend.destroy(); self.input_manager.deinit(); self.config.deinit(); @@ -160,33 +144,26 @@ pub fn deinit(self: *Self) void { /// Create the socket, start the backend, and setup the environment pub fn start(self: Self) !void { - const socket = c.wl_display_add_socket_auto(self.wl_display) orelse return error.AddSocketError; - if (!c.wlr_backend_start(self.wlr_backend)) return error.StartBackendError; + var buf: [11]u8 = undefined; + const socket = try self.wl_server.addSocketAuto(&buf); + try self.backend.start(); + // TODO: don't use libc's setenv if (c.setenv("WAYLAND_DISPLAY", socket, 1) < 0) return error.SetenvError; if (build_options.xwayland) { - if (c.setenv("DISPLAY", self.wlr_xwayland.display_name, 1) < 0) return error.SetenvError; + if (c.setenv("DISPLAY", self.xwayland.display_name, 1) < 0) return error.SetenvError; } } -/// Enter the wayland event loop and block until the compositor is exited -pub fn run(self: Self) void { - c.wl_display_run(self.wl_display); -} - /// Handle SIGINT and SIGTERM by gracefully stopping the server -fn terminate(signal: c_int, data: ?*c_void) callconv(.C) c_int { - const wl_display = util.voidCast(c.wl_display, data.?); - c.wl_display_terminate(wl_display); +fn terminate(signal: c_int, wl_server: *wl.Server) callconv(.C) c_int { + wl_server.terminate(); return 0; } -fn handleNewXdgSurface(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - // This event is raised when wlr_xdg_shell receives a new xdg surface from a - // client, either a toplevel (application window) or popup. - const self = @fieldParentPtr(Self, "listen_new_xdg_surface", listener.?); - const wlr_xdg_surface = util.voidCast(c.wlr_xdg_surface, data.?); +fn handleNewXdgSurface(listener: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSurface) void { + const self = @fieldParentPtr(Self, "new_xdg_surface", listener); - if (wlr_xdg_surface.role == .WLR_XDG_SURFACE_ROLE_POPUP) { + if (xdg_surface.role == .popup) { log.debug(.server, "new xdg_popup", .{}); return; } @@ -196,16 +173,15 @@ fn handleNewXdgSurface(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) v // The View will add itself to the output's view stack on map const output = self.input_manager.defaultSeat().focused_output; const node = util.gpa.create(ViewStack(View).Node) catch { - c.wl_resource_post_no_memory(wlr_xdg_surface.resource); + xdg_surface.resource.postNoMemory(); return; }; - node.view.init(output, output.current.tags, wlr_xdg_surface); + node.view.init(output, output.current.tags, xdg_surface); } /// This event is raised when the layer_shell recieves a new surface from a client. -fn handleNewLayerSurface(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_new_layer_surface", listener.?); - const wlr_layer_surface = util.voidCast(c.wlr_layer_surface_v1, data.?); +fn handleNewLayerSurface(listener: *wl.Listener(*wlr.LayerSurfaceV1), wlr_layer_surface: *wlr.LayerSurfaceV1) void { + const self = @fieldParentPtr(Self, "new_layer_surface", listener); log.debug( .server, @@ -229,31 +205,28 @@ fn handleNewLayerSurface(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) if (wlr_layer_surface.output == null) { const output = self.input_manager.defaultSeat().focused_output; if (output == &self.root.noop_output) { - log.err(.server, "no output available for layer surface '{s}'", .{wlr_layer_surface.namespace}); - c.wlr_layer_surface_v1_close(wlr_layer_surface); + log.err(.server, "no output available for layer surface '{}'", .{wlr_layer_surface.namespace}); + wlr_layer_surface.close(); return; } - log.debug( - .server, - "new layer surface had null output, assigning it to output '{s}'", - .{output.wlr_output.name}, - ); + log.debug(.server, "new layer surface had null output, assigning it to output '{}'", .{ + output.wlr_output.name, + }); wlr_layer_surface.output = output.wlr_output; } // The layer surface will add itself to the proper list of the output on map - const output = util.voidCast(Output, wlr_layer_surface.output.*.data.?); + const output = @intToPtr(*Output, wlr_layer_surface.output.?.data); const node = util.gpa.create(std.TailQueue(LayerSurface).Node) catch { - c.wl_resource_post_no_memory(wlr_layer_surface.resource); + wlr_layer_surface.resource.postNoMemory(); return; }; node.data.init(output, wlr_layer_surface); } -fn handleNewXwaylandSurface(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_new_xwayland_surface", listener.?); - const wlr_xwayland_surface = util.voidCast(c.wlr_xwayland_surface, data.?); +fn handleNewXwaylandSurface(listener: *wl.Listener(*wlr.XwaylandSurface), wlr_xwayland_surface: *wlr.XwaylandSurface) void { + const self = @fieldParentPtr(Self, "new_xwayland_surface", listener); if (wlr_xwayland_surface.override_redirect) { log.debug(.server, "new unmanaged xwayland surface", .{}); diff --git a/river/StatusManager.zig b/river/StatusManager.zig index bd4d48b..f630dde 100644 --- a/river/StatusManager.zig +++ b/river/StatusManager.zig @@ -18,8 +18,11 @@ const Self = @This(); const std = @import("std"); +const wlr = @import("wlroots"); +const wayland = @import("wayland"); +const wl = wayland.server.wl; +const zriver = wayland.server.zriver; -const c = @import("c.zig"); const log = @import("log.zig"); const util = @import("util.zig"); @@ -29,120 +32,89 @@ const Seat = @import("Seat.zig"); const SeatStatus = @import("SeatStatus.zig"); const Server = @import("Server.zig"); -const protocol_version = 1; +global: *wl.Global, -const implementation = c.struct_zriver_status_manager_v1_interface{ - .destroy = destroy, - .get_river_output_status = getRiverOutputStatus, - .get_river_seat_status = getRiverSeatStatus, -}; - -wl_global: *c.wl_global, - -listen_display_destroy: c.wl_listener = undefined, +server_destroy: wl.Listener(*wl.Server) = undefined, pub fn init(self: *Self, server: *Server) !void { self.* = .{ - .wl_global = c.wl_global_create( - server.wl_display, - &c.zriver_status_manager_v1_interface, - protocol_version, - self, - bind, - ) orelse return error.OutOfMemory, + .global = try wl.Global.create(server.wl_server, zriver.StatusManagerV1, 1, *Self, self, bind), }; - self.listen_display_destroy.notify = handleDisplayDestroy; - c.wl_display_add_destroy_listener(server.wl_display, &self.listen_display_destroy); + self.server_destroy.setNotify(handleServerDestroy); + server.wl_server.addDestroyListener(&self.server_destroy); } -fn handleDisplayDestroy(wl_listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_display_destroy", wl_listener.?); - c.wl_global_destroy(self.wl_global); +fn handleServerDestroy(listener: *wl.Listener(*wl.Server), wl_server: *wl.Server) void { + const self = @fieldParentPtr(Self, "server_destroy", listener); + self.global.destroy(); } -/// Called when a client binds our global -fn bind(wl_client: ?*c.wl_client, data: ?*c_void, version: u32, id: u32) callconv(.C) void { - const self = util.voidCast(Self, data.?); - const wl_resource = c.wl_resource_create( - wl_client, - &c.zriver_status_manager_v1_interface, - @intCast(c_int, version), - id, - ) orelse { - c.wl_client_post_no_memory(wl_client); +fn bind(client: *wl.Client, self: *Self, version: u32, id: u32) callconv(.C) void { + const status_manager = zriver.StatusManagerV1.create(client, version, id) catch { + client.postNoMemory(); log.crit(.river_status, "out of memory", .{}); return; }; - c.wl_resource_set_implementation(wl_resource, &implementation, self, null); + status_manager.setHandler(*Self, handleRequest, null, self); } -fn destroy(wl_client: ?*c.wl_client, wl_resource: ?*c.wl_resource) callconv(.C) void { - c.wl_resource_destroy(wl_resource); -} - -fn getRiverOutputStatus( - wl_client: ?*c.wl_client, - wl_resource: ?*c.wl_resource, - new_id: u32, - output_wl_resource: ?*c.wl_resource, -) callconv(.C) void { - const self = util.voidCast(Self, c.wl_resource_get_user_data(wl_resource).?); - // This can be null if the output is inert, in which case we ignore the request - const wlr_output = c.wlr_output_from_resource(output_wl_resource) orelse return; - const output = util.voidCast(Output, wlr_output.*.data.?); - - const node = util.gpa.create(std.SinglyLinkedList(OutputStatus).Node) catch { - c.wl_client_post_no_memory(wl_client); - log.crit(.river_status, "out of memory", .{}); - return; - }; - - const output_status_resource = c.wl_resource_create( - wl_client, - &c.zriver_output_status_v1_interface, - protocol_version, - new_id, - ) orelse { - c.wl_client_post_no_memory(wl_client); - util.gpa.destroy(node); - log.crit(.river_status, "out of memory", .{}); - return; - }; - - node.data.init(output, output_status_resource); - output.status_trackers.prepend(node); -} - -fn getRiverSeatStatus( - wl_client: ?*c.wl_client, - wl_resource: ?*c.wl_resource, - new_id: u32, - seat_wl_resource: ?*c.wl_resource, -) callconv(.C) void { - const self = util.voidCast(Self, c.wl_resource_get_user_data(wl_resource).?); - // This can be null if the seat is inert, in which case we ignore the request - const wlr_seat_client = c.wlr_seat_client_from_resource(seat_wl_resource) orelse return; - const seat = util.voidCast(Seat, wlr_seat_client.*.seat.*.data.?); - - const node = util.gpa.create(std.SinglyLinkedList(SeatStatus).Node) catch { - c.wl_client_post_no_memory(wl_client); - log.crit(.river_status, "out of memory", .{}); - return; - }; - - const seat_status_resource = c.wl_resource_create( - wl_client, - &c.zriver_seat_status_v1_interface, - protocol_version, - new_id, - ) orelse { - c.wl_client_post_no_memory(wl_client); - util.gpa.destroy(node); - log.crit(.river_status, "out of memory", .{}); - return; - }; - - node.data.init(seat, seat_status_resource); - seat.status_trackers.prepend(node); +fn handleRequest( + status_manager: *zriver.StatusManagerV1, + request: zriver.StatusManagerV1.Request, + self: *Self, +) void { + switch (request) { + .destroy => status_manager.destroy(), + .get_river_output_status => |req| { + // ignore if the output is inert + const wlr_output = wlr.Output.fromWlOutput(req.output) orelse return; + const output = @intToPtr(*Output, wlr_output.data); + + const node = util.gpa.create(std.SinglyLinkedList(OutputStatus).Node) catch { + status_manager.getClient().postNoMemory(); + log.crit(.river_status, "out of memory", .{}); + return; + }; + + const output_status = zriver.OutputStatusV1.create( + status_manager.getClient(), + status_manager.getVersion(), + req.id, + ) catch { + status_manager.getClient().postNoMemory(); + util.gpa.destroy(node); + log.crit(.river_status, "out of memory", .{}); + return; + }; + + node.data.init(output, output_status); + output.status_trackers.prepend(node); + }, + .get_river_seat_status => |req| { + // ignore if the seat is inert + const wlr_seat = wlr.Seat.Client.fromWlSeat(req.seat) orelse return; + const seat = @intToPtr(*Seat, wlr_seat.seat.data); + + const node = util.gpa.create(std.SinglyLinkedList(SeatStatus).Node) catch { + status_manager.getClient().postNoMemory(); + log.crit(.river_status, "out of memory", .{}); + return; + }; + + const seat_status = zriver.SeatStatusV1.create( + status_manager.getClient(), + status_manager.getVersion(), + req.id, + ) catch { + status_manager.getClient().postNoMemory(); + util.gpa.destroy(node); + log.crit(.river_status, "out of memory", .{}); + return; + }; + + node.data.init(seat, seat_status); + seat.status_trackers.prepend(node); + }, + } } diff --git a/river/View.zig b/river/View.zig index f049895..ddde6a4 100644 --- a/river/View.zig +++ b/river/View.zig @@ -19,8 +19,10 @@ const Self = @This(); const build_options = @import("build_options"); const std = @import("std"); +const os = std.os; +const wlr = @import("wlroots"); +const wl = @import("wayland").server.wl; -const c = @import("c.zig"); const log = @import("log.zig"); const util = @import("util.zig"); @@ -71,9 +73,9 @@ const State = struct { }; const SavedBuffer = struct { - wlr_client_buffer: *c.wlr_client_buffer, + client_buffer: *wlr.ClientBuffer, box: Box, - transform: c.wl_output_transform, + transform: wl.Output.Transform, }; /// The implementation of this view @@ -84,10 +86,10 @@ output: *Output, /// This is from the point where the view is mapped until the surface /// is destroyed by wlroots. -wlr_surface: ?*c.wlr_surface = null, +surface: ?*wlr.Surface = null, /// This View struct outlasts the wlroots object it wraps. This bool is set to -/// true when the backing wlr_xdg_toplevel or equivalent has been destroyed. +/// true when the backing wlr.XdgToplevel or equivalent has been destroyed. destroying: bool = false, /// The double-buffered state of the view @@ -116,7 +118,7 @@ float_box: Box = undefined, opacity: f32, /// Opacity change timer event source -opacity_timer: ?*c.wl_event_source = null, +opacity_timer: ?*wl.EventSource = null, draw_borders: bool = true, @@ -135,10 +137,10 @@ pub fn init(self: *Self, output: *Output, tags: u32, surface: anytype) void { .opacity = output.root.server.config.view_opacity_initial, }; - if (@TypeOf(surface) == *c.wlr_xdg_surface) { + if (@TypeOf(surface) == *wlr.XdgSurface) { self.impl = .{ .xdg_toplevel = undefined }; self.impl.xdg_toplevel.init(self, surface); - } else if (build_options.xwayland and @TypeOf(surface) == *c.wlr_xwayland_surface) { + } else if (build_options.xwayland and @TypeOf(surface) == *wlr.XwaylandSurface) { self.impl = .{ .xwayland_view = undefined }; self.impl.xwayland_view.init(self, surface); } else unreachable; @@ -146,7 +148,7 @@ pub fn init(self: *Self, output: *Output, tags: u32, surface: anytype) void { /// Deinit the view, remove it from the view stack and free the memory. pub fn destroy(self: *Self) void { - for (self.saved_buffers.items) |buffer| c.wlr_buffer_unlock(&buffer.wlr_client_buffer.*.base); + self.dropSavedBuffers(); self.saved_buffers.deinit(); switch (self.impl) { .xdg_toplevel => |*xdg_toplevel| xdg_toplevel.deinit(), @@ -183,12 +185,12 @@ pub fn applyPending(self: *Self) void { // and turn the view fully opaque if (!self.current.fullscreen and self.pending.fullscreen) { self.pending.target_opacity = 1.0; - const layout_box = c.wlr_output_layout_get_box(self.output.root.wlr_output_layout, self.output.wlr_output); + const layout_box = self.output.root.output_layout.getBox(self.output.wlr_output).?; self.pending.box = .{ .x = 0, .y = 0, - .width = @intCast(u32, layout_box.*.width), - .height = @intCast(u32, layout_box.*.height), + .width = @intCast(u32, layout_box.width), + .height = @intCast(u32, layout_box.height), }; } @@ -225,20 +227,20 @@ pub fn configure(self: Self) void { } pub fn sendFrameDone(self: Self) void { - var now: c.timespec = undefined; - _ = c.clock_gettime(c.CLOCK_MONOTONIC, &now); - c.wlr_surface_send_frame_done(self.wlr_surface.?, &now); + var now: os.timespec = undefined; + os.clock_gettime(os.CLOCK_MONOTONIC, &now) catch @panic("CLOCK_MONOTONIC not supported"); + self.surface.?.sendFrameDone(&now); } pub fn dropSavedBuffers(self: *Self) void { - for (self.saved_buffers.items) |buffer| c.wlr_buffer_unlock(&buffer.wlr_client_buffer.*.base); + for (self.saved_buffers.items) |buffer| buffer.client_buffer.base.unlock(); self.saved_buffers.items.len = 0; } pub fn saveBuffers(self: *Self) void { std.debug.assert(self.saved_buffers.items.len == 0); self.saved_surface_box = self.surface_box; - self.forEachSurface(saveBuffersIterator, &self.saved_buffers); + self.forEachSurface(*std.ArrayList(SavedBuffer), saveBuffersIterator, &self.saved_buffers); } /// If this commit is in response to our configure and the @@ -257,26 +259,23 @@ pub fn notifyConfiguredOrApplyPending(self: *Self) void { } fn saveBuffersIterator( - wlr_surface: ?*c.wlr_surface, + surface: *wlr.Surface, surface_x: c_int, surface_y: c_int, - data: ?*c_void, + saved_buffers: *std.ArrayList(SavedBuffer), ) callconv(.C) void { - const saved_buffers = util.voidCast(std.ArrayList(SavedBuffer), data.?); - if (wlr_surface) |surface| { - if (c.wlr_surface_has_buffer(surface)) { - saved_buffers.append(.{ - .wlr_client_buffer = surface.buffer, - .box = Box{ - .x = surface_x, - .y = surface_y, - .width = @intCast(u32, surface.current.width), - .height = @intCast(u32, surface.current.height), - }, - .transform = surface.current.transform, - }) catch return; - _ = c.wlr_buffer_lock(&surface.buffer.*.base); - } + if (surface.buffer) |buffer| { + saved_buffers.append(.{ + .client_buffer = buffer, + .box = Box{ + .x = surface_x, + .y = surface_y, + .width = @intCast(u32, surface.current.width), + .height = @intCast(u32, surface.current.height), + }, + .transform = surface.current.transform, + }) catch return; + _ = buffer.base.lock(); } } @@ -291,8 +290,8 @@ pub fn sendToOutput(self: *Self, destination_output: *Output) void { self.output.sendViewTags(); destination_output.sendViewTags(); - c.wlr_surface_send_leave(self.wlr_surface, self.output.wlr_output); - c.wlr_surface_send_enter(self.wlr_surface, destination_output.wlr_output); + self.surface.?.sendLeave(self.output.wlr_output); + self.surface.?.sendEnter(destination_output.wlr_output); self.output = destination_output; } @@ -304,20 +303,21 @@ pub fn close(self: Self) void { } } -pub fn forEachSurface( +pub inline fn forEachSurface( self: Self, - iterator: c.wlr_surface_iterator_func_t, - user_data: ?*c_void, + comptime T: type, + iterator: fn (surface: *wlr.Surface, sx: c_int, sy: c_int, data: T) callconv(.C) void, + user_data: T, ) void { switch (self.impl) { - .xdg_toplevel => |xdg_toplevel| xdg_toplevel.forEachSurface(iterator, user_data), - .xwayland_view => |xwayland_view| xwayland_view.forEachSurface(iterator, user_data), + .xdg_toplevel => |xdg_toplevel| xdg_toplevel.forEachSurface(T, iterator, user_data), + .xwayland_view => |xwayland_view| xwayland_view.forEachSurface(T, iterator, user_data), } } /// Return the surface at output coordinates ox, oy and set sx, sy to the /// corresponding surface-relative coordinates, if there is a surface. -pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_surface { +pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*wlr.Surface { return switch (self.impl) { .xdg_toplevel => |xdg_toplevel| xdg_toplevel.surfaceAt(ox, oy, sx, sy), .xwayland_view => |xwayland_view| xwayland_view.surfaceAt(ox, oy, sx, sy), @@ -348,18 +348,18 @@ pub fn getConstraints(self: Self) Constraints { }; } -/// Find and return the view corresponding to a given wlr_surface, if any -pub fn fromWlrSurface(wlr_surface: *c.wlr_surface) ?*Self { - if (c.wlr_surface_is_xdg_surface(wlr_surface)) { - const wlr_xdg_surface = c.wlr_xdg_surface_from_wlr_surface(wlr_surface); - if (wlr_xdg_surface.*.role == .WLR_XDG_SURFACE_ROLE_TOPLEVEL) { - return util.voidCast(Self, wlr_xdg_surface.*.data.?); +/// Find and return the view corresponding to a given surface, if any +pub fn fromWlrSurface(surface: *wlr.Surface) ?*Self { + if (surface.isXdgSurface()) { + const xdg_surface = wlr.XdgSurface.fromWlrSurface(surface); + if (xdg_surface.role == .toplevel) { + return @intToPtr(*Self, xdg_surface.data); } } if (build_options.xwayland) { - if (c.wlr_surface_is_xwayland_surface(wlr_surface)) { - const wlr_xwayland_surface = c.wlr_xwayland_surface_from_wlr_surface(wlr_surface); - return util.voidCast(Self, wlr_xwayland_surface.*.data.?); + if (surface.isXWaylandSurface()) { + const xwayland_surface = wlr.XwaylandSurface.fromWlrSurface(surface); + return @intToPtr(*Self, xwayland_surface.data); } } return null; @@ -392,7 +392,7 @@ pub fn map(self: *Self) void { var it = root.server.input_manager.seats.first; while (it) |seat_node| : (it = seat_node.next) seat_node.data.focus(self); - c.wlr_surface_send_enter(self.wlr_surface.?, self.output.wlr_output); + self.surface.?.sendEnter(self.output.wlr_output); self.output.sendViewTags(); @@ -446,22 +446,21 @@ fn incrementOpacity(self: *Self) bool { /// Destroy a views opacity timer fn killOpacityTimer(self: *Self) void { - if (c.wl_event_source_remove(self.opacity_timer) < 0) unreachable; + self.opacity_timer.?.remove(); self.opacity_timer = null; } /// Set the timeout on a views opacity timer fn armOpacityTimer(self: *Self) void { const delta_t = self.output.root.server.config.view_opacity_delta_t; - if (c.wl_event_source_timer_update(self.opacity_timer, delta_t) < 0) { - log.err(.view, "failed to update opacity timer", .{}); + self.opacity_timer.?.timerUpdate(delta_t) catch |err| { + log.err(.view, "failed to update opacity timer: {}", .{err}); self.killOpacityTimer(); - } + }; } /// Called by the opacity timer -fn handleOpacityTimer(data: ?*c_void) callconv(.C) c_int { - const self = util.voidCast(Self, data.?); +fn handleOpacityTimer(self: *Self) callconv(.C) c_int { if (self.incrementOpacity()) { self.killOpacityTimer(); } else { @@ -472,12 +471,8 @@ fn handleOpacityTimer(data: ?*c_void) callconv(.C) c_int { /// Create an opacity timer for a view and arm it fn attachOpacityTimer(self: *Self) void { - const server = self.output.root.server; - self.opacity_timer = c.wl_event_loop_add_timer( - c.wl_display_get_event_loop(server.wl_display), - handleOpacityTimer, - self, - ) orelse { + const event_loop = self.output.root.server.wl_server.getEventLoop(); + self.opacity_timer = event_loop.addTimer(*Self, handleOpacityTimer, self) catch { log.err(.view, "failed to create opacity timer for view '{}'", .{self.getTitle()}); return; }; diff --git a/river/VoidView.zig b/river/VoidView.zig index 033286d..ff29d99 100644 --- a/river/VoidView.zig +++ b/river/VoidView.zig @@ -18,8 +18,7 @@ const Self = @This(); const std = @import("std"); - -const c = @import("c.zig"); +const wlr = @import("wlroots"); const Box = @import("Box.zig"); const View = @import("View.zig"); @@ -42,13 +41,14 @@ pub fn close(self: Self) void { pub fn forEachSurface( self: Self, - iterator: c.wlr_surface_iterator_func_t, - user_data: ?*c_void, + comptime T: type, + iterator: fn (surface: *wlr.Surface, sx: c_int, sy: c_int, data: T) callconv(.C) void, + user_data: T, ) void { unreachable; } -pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_surface { +pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*wlr.Surface { unreachable; } diff --git a/river/XdgPopup.zig b/river/XdgPopup.zig index 7f73582..abdced9 100644 --- a/river/XdgPopup.zig +++ b/river/XdgPopup.zig @@ -18,8 +18,10 @@ const Self = @This(); const std = @import("std"); +const wlr = @import("wlroots"); +const wl = @import("wayland").server.wl; -const c = @import("c.zig"); +const log = @import("log.zig"); const util = @import("util.zig"); const Box = @import("Box.zig"); @@ -32,12 +34,12 @@ output: *Output, parent_box: *const Box, /// The corresponding wlroots object -wlr_xdg_popup: *c.wlr_xdg_popup, +wlr_xdg_popup: *wlr.XdgPopup, -listen_destroy: c.wl_listener = undefined, -listen_new_popup: c.wl_listener = undefined, +destroy: wl.Listener(*wlr.XdgSurface) = undefined, +new_popup: wl.Listener(*wlr.XdgPopup) = undefined, -pub fn init(self: *Self, output: *Output, parent_box: *const Box, wlr_xdg_popup: *c.wlr_xdg_popup) void { +pub fn init(self: *Self, output: *Output, parent_box: *const Box, wlr_xdg_popup: *wlr.XdgPopup) void { self.* = .{ .output = output, .parent_box = parent_box, @@ -45,36 +47,35 @@ pub fn init(self: *Self, output: *Output, parent_box: *const Box, wlr_xdg_popup: }; // The output box relative to the parent of the popup - var box = c.wlr_output_layout_get_box(output.root.wlr_output_layout, output.wlr_output).*; + var box = output.root.output_layout.getBox(output.wlr_output).?.*; box.x -= parent_box.x; box.y -= parent_box.y; - c.wlr_xdg_popup_unconstrain_from_box(wlr_xdg_popup, &box); + wlr_xdg_popup.unconstrainFromBox(&box); - // Setup listeners - self.listen_destroy.notify = handleDestroy; - c.wl_signal_add(&wlr_xdg_popup.base.*.events.destroy, &self.listen_destroy); + self.destroy.setNotify(handleDestroy); + wlr_xdg_popup.base.events.destroy.add(&self.destroy); - self.listen_new_popup.notify = handleNewPopup; - c.wl_signal_add(&wlr_xdg_popup.base.*.events.new_popup, &self.listen_new_popup); + self.new_popup.setNotify(handleNewPopup); + wlr_xdg_popup.base.events.new_popup.add(&self.new_popup); } -fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_destroy", listener.?); +fn handleDestroy(listener: *wl.Listener(*wlr.XdgSurface), wlr_xdg_surface: *wlr.XdgSurface) void { + const self = @fieldParentPtr(Self, "destroy", listener); - c.wl_list_remove(&self.listen_destroy.link); - c.wl_list_remove(&self.listen_new_popup.link); + self.destroy.link.remove(); + self.new_popup.link.remove(); util.gpa.destroy(self); } /// Called when a new xdg popup is requested by the client -fn handleNewPopup(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_new_popup", listener.?); - const wlr_xdg_popup = util.voidCast(c.wlr_xdg_popup, data.?); +fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), wlr_xdg_popup: *wlr.XdgPopup) void { + const self = @fieldParentPtr(Self, "new_popup", listener); // This will free itself on destroy - var xdg_popup = util.gpa.create(Self) catch { - c.wl_resource_post_no_memory(wlr_xdg_popup.resource); + const xdg_popup = util.gpa.create(Self) catch { + wlr_xdg_popup.resource.postNoMemory(); + log.crit(.server, "out of memory", .{}); return; }; xdg_popup.init(self.output, self.parent_box, wlr_xdg_popup); diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig index 6f28874..8017be9 100644 --- a/river/XdgToplevel.zig +++ b/river/XdgToplevel.zig @@ -18,8 +18,9 @@ const Self = @This(); const std = @import("std"); +const wlr = @import("wlroots"); +const wl = @import("wayland").server.wl; -const c = @import("c.zig"); const log = @import("log.zig"); const util = @import("util.zig"); @@ -33,52 +34,49 @@ const XdgPopup = @import("XdgPopup.zig"); view: *View, /// The corresponding wlroots object -wlr_xdg_surface: *c.wlr_xdg_surface, +xdg_surface: *wlr.XdgSurface, // Listeners that are always active over the view's lifetime -listen_destroy: c.wl_listener = undefined, -listen_map: c.wl_listener = undefined, -listen_unmap: c.wl_listener = undefined, +destroy: wl.Listener(*wlr.XdgSurface) = undefined, +map: wl.Listener(*wlr.XdgSurface) = undefined, +unmap: wl.Listener(*wlr.XdgSurface) = undefined, // Listeners that are only active while the view is mapped -listen_commit: c.wl_listener = undefined, -listen_new_popup: c.wl_listener = undefined, -listen_request_fullscreen: c.wl_listener = undefined, -listen_request_move: c.wl_listener = undefined, -listen_request_resize: c.wl_listener = undefined, -listen_set_title: c.wl_listener = undefined, +commit: wl.Listener(*wlr.Surface) = undefined, +new_popup: wl.Listener(*wlr.XdgPopup) = undefined, +request_fullscreen: wl.Listener(*wlr.XdgToplevel.event.SetFullscreen) = undefined, +request_move: wl.Listener(*wlr.XdgToplevel.event.Move) = undefined, +request_resize: wl.Listener(*wlr.XdgToplevel.event.Resize) = undefined, +set_title: wl.Listener(*wlr.XdgSurface) = undefined, -pub fn init(self: *Self, view: *View, wlr_xdg_surface: *c.wlr_xdg_surface) void { - self.* = .{ .view = view, .wlr_xdg_surface = wlr_xdg_surface }; - wlr_xdg_surface.data = self; +pub fn init(self: *Self, view: *View, xdg_surface: *wlr.XdgSurface) void { + self.* = .{ .view = view, .xdg_surface = xdg_surface }; + xdg_surface.data = @ptrToInt(self); // Add listeners that are active over the view's entire lifetime - self.listen_destroy.notify = handleDestroy; - c.wl_signal_add(&self.wlr_xdg_surface.events.destroy, &self.listen_destroy); + self.destroy.setNotify(handleDestroy); + self.xdg_surface.events.destroy.add(&self.destroy); - self.listen_map.notify = handleMap; - c.wl_signal_add(&self.wlr_xdg_surface.events.map, &self.listen_map); + self.map.setNotify(handleMap); + self.xdg_surface.events.map.add(&self.map); - self.listen_unmap.notify = handleUnmap; - c.wl_signal_add(&self.wlr_xdg_surface.events.unmap, &self.listen_unmap); + self.unmap.setNotify(handleUnmap); + self.xdg_surface.events.unmap.add(&self.unmap); } pub fn deinit(self: *Self) void { - if (self.view.wlr_surface != null) { + if (self.view.surface != null) { // Remove listeners that are active for the entire lifetime of the view - c.wl_list_remove(&self.listen_destroy.link); - c.wl_list_remove(&self.listen_map.link); - c.wl_list_remove(&self.listen_unmap.link); + self.destroy.link.remove(); + self.map.link.remove(); + self.unmap.link.remove(); } } /// Returns true if a configure must be sent to ensure the dimensions of the /// pending_box are applied. pub fn needsConfigure(self: Self) bool { - const server_pending = &@field( - self.wlr_xdg_surface, - c.wlr_xdg_surface_union, - ).toplevel.*.server_pending; + const server_pending = &self.xdg_surface.role_data.toplevel.server_pending; const state = &self.view.pending; // Checking server_pending is sufficient here since it will be either in @@ -92,35 +90,32 @@ pub fn needsConfigure(self: Self) bool { /// Send a configure event, applying the pending state of the view. pub fn configure(self: Self) void { + const toplevel = self.xdg_surface.role_data.toplevel; const state = &self.view.pending; - _ = c.wlr_xdg_toplevel_set_activated(self.wlr_xdg_surface, state.focus != 0); - _ = c.wlr_xdg_toplevel_set_fullscreen(self.wlr_xdg_surface, state.fullscreen); - self.view.pending_serial = c.wlr_xdg_toplevel_set_size( - self.wlr_xdg_surface, - state.box.width, - state.box.height, - ); + _ = toplevel.setActivated(state.focus != 0); + _ = toplevel.setFullscreen(state.fullscreen); + self.view.pending_serial = toplevel.setSize(state.box.width, state.box.height); } /// Close the view. This will lead to the unmap and destroy events being sent pub fn close(self: Self) void { - c.wlr_xdg_toplevel_send_close(self.wlr_xdg_surface); + self.xdg_surface.role_data.toplevel.sendClose(); } -pub fn forEachSurface( +pub inline fn forEachSurface( self: Self, - iterator: c.wlr_surface_iterator_func_t, - user_data: ?*c_void, + comptime T: type, + iterator: fn (surface: *wlr.Surface, sx: c_int, sy: c_int, data: T) callconv(.C) void, + user_data: T, ) void { - c.wlr_xdg_surface_for_each_surface(self.wlr_xdg_surface, iterator, user_data); + self.xdg_surface.forEachSurface(T, iterator, user_data); } /// Return the surface at output coordinates ox, oy and set sx, sy to the /// corresponding surface-relative coordinates, if there is a surface. -pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_surface { +pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*wlr.Surface { const view = self.view; - return c.wlr_xdg_surface_surface_at( - self.wlr_xdg_surface, + return self.xdg_surface.surfaceAt( ox - @intToFloat(f64, view.current.box.x - view.surface_box.x), oy - @intToFloat(f64, view.current.box.y - view.surface_box.y), sx, @@ -130,16 +125,12 @@ pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_surfa /// Return the current title of the toplevel. May be an empty string. pub fn getTitle(self: Self) [*:0]const u8 { - const wlr_xdg_toplevel: *c.wlr_xdg_toplevel = @field( - self.wlr_xdg_surface, - c.wlr_xdg_surface_union, - ).toplevel; - return wlr_xdg_toplevel.title orelse "NULL"; + return self.xdg_surface.role_data.toplevel.title orelse "NULL"; } /// Return bounds on the dimensions of the toplevel. pub fn getConstraints(self: Self) View.Constraints { - const state = @field(self.wlr_xdg_surface, c.wlr_xdg_surface_union).toplevel.*.current; + const state = &self.xdg_surface.role_data.toplevel.current; return .{ .min_width = std.math.max(state.min_width, View.min_size), .max_width = if (state.max_width > 0) state.max_width else std.math.maxInt(u32), @@ -149,55 +140,55 @@ pub fn getConstraints(self: Self) View.Constraints { } /// Called when the xdg surface is destroyed -fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_destroy", listener.?); +fn handleDestroy(listener: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSurface) void { + const self = @fieldParentPtr(Self, "destroy", listener); self.deinit(); - self.view.wlr_surface = null; + self.view.surface = null; } /// Called when the xdg surface is mapped, or ready to display on-screen. -fn handleMap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_map", listener.?); +fn handleMap(listener: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSurface) void { + const self = @fieldParentPtr(Self, "map", listener); const view = self.view; const root = view.output.root; - const wlr_xdg_toplevel: *c.wlr_xdg_toplevel = @field(self.wlr_xdg_surface, c.wlr_xdg_surface_union).toplevel; + const toplevel = self.xdg_surface.role_data.toplevel; // Add listeners that are only active while mapped - self.listen_commit.notify = handleCommit; - c.wl_signal_add(&self.wlr_xdg_surface.surface.*.events.commit, &self.listen_commit); + self.commit.setNotify(handleCommit); + self.xdg_surface.surface.events.commit.add(&self.commit); - self.listen_new_popup.notify = handleNewPopup; - c.wl_signal_add(&self.wlr_xdg_surface.events.new_popup, &self.listen_new_popup); + self.new_popup.setNotify(handleNewPopup); + self.xdg_surface.events.new_popup.add(&self.new_popup); - self.listen_request_fullscreen.notify = handleRequestFullscreen; - c.wl_signal_add(&wlr_xdg_toplevel.events.request_fullscreen, &self.listen_request_fullscreen); + self.request_fullscreen.setNotify(handleRequestFullscreen); + toplevel.events.request_fullscreen.add(&self.request_fullscreen); - self.listen_request_move.notify = handleRequestMove; - c.wl_signal_add(&wlr_xdg_toplevel.events.request_move, &self.listen_request_move); + self.request_move.setNotify(handleRequestMove); + toplevel.events.request_move.add(&self.request_move); - self.listen_request_resize.notify = handleRequestResize; - c.wl_signal_add(&wlr_xdg_toplevel.events.request_resize, &self.listen_request_resize); + self.request_resize.setNotify(handleRequestResize); + toplevel.events.request_resize.add(&self.request_resize); - self.listen_set_title.notify = handleSetTitle; - c.wl_signal_add(&wlr_xdg_toplevel.events.set_title, &self.listen_set_title); + self.set_title.setNotify(handleSetTitle); + toplevel.events.set_title.add(&self.set_title); - view.wlr_surface = self.wlr_xdg_surface.surface; + view.surface = self.xdg_surface.surface; // Use the view's "natural" size centered on the output as the default // floating dimensions - view.float_box.width = @intCast(u32, self.wlr_xdg_surface.geometry.width); - view.float_box.height = @intCast(u32, self.wlr_xdg_surface.geometry.height); + view.float_box.width = @intCast(u32, self.xdg_surface.geometry.width); + view.float_box.height = @intCast(u32, self.xdg_surface.geometry.height); view.float_box.x = std.math.max(0, @divTrunc(@intCast(i32, view.output.usable_box.width) - @intCast(i32, view.float_box.width), 2)); view.float_box.y = std.math.max(0, @divTrunc(@intCast(i32, view.output.usable_box.height) - @intCast(i32, view.float_box.height), 2)); - const state = &wlr_xdg_toplevel.current; + const state = &toplevel.current; const has_fixed_size = state.min_width != 0 and state.min_height != 0 and (state.min_width == state.max_width or state.min_height == state.max_height); - const app_id: [*:0]const u8 = if (wlr_xdg_toplevel.app_id) |id| id else "NULL"; + const app_id: [*:0]const u8 = if (toplevel.app_id) |id| id else "NULL"; - if (wlr_xdg_toplevel.parent != null or has_fixed_size) { + if (toplevel.parent != null or has_fixed_size) { // If the toplevel has a parent or has a fixed size make it float view.current.float = true; view.pending.float = true; @@ -222,38 +213,35 @@ fn handleMap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { break; } } else { - _ = c.wlr_xdg_toplevel_set_tiled( - self.wlr_xdg_surface, - c.WLR_EDGE_LEFT | c.WLR_EDGE_RIGHT | c.WLR_EDGE_TOP | c.WLR_EDGE_BOTTOM, - ); + _ = toplevel.setTiled(.{ .top = true, .bottom = true, .left = true, .right = true }); } view.map(); } /// Called when the surface is unmapped and will no longer be displayed. -fn handleUnmap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_unmap", listener.?); +fn handleUnmap(listener: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSurface) void { + const self = @fieldParentPtr(Self, "unmap", listener); const root = self.view.output.root; self.view.unmap(); // Remove listeners that are only active while mapped - c.wl_list_remove(&self.listen_commit.link); - c.wl_list_remove(&self.listen_new_popup.link); - c.wl_list_remove(&self.listen_request_fullscreen.link); - c.wl_list_remove(&self.listen_request_move.link); - c.wl_list_remove(&self.listen_request_resize.link); - c.wl_list_remove(&self.listen_set_title.link); + self.commit.link.remove(); + self.new_popup.link.remove(); + self.request_fullscreen.link.remove(); + self.request_move.link.remove(); + self.request_resize.link.remove(); + self.set_title.link.remove(); } /// Called when the surface is comitted -fn handleCommit(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_commit", listener.?); +fn handleCommit(listener: *wl.Listener(*wlr.Surface), surface: *wlr.Surface) void { + const self = @fieldParentPtr(Self, "commit", listener); const view = self.view; - var wlr_box: c.wlr_box = undefined; - c.wlr_xdg_surface_get_geometry(self.wlr_xdg_surface, &wlr_box); + var wlr_box: wlr.Box = undefined; + self.xdg_surface.getGeometry(&wlr_box); const new_box = Box.fromWlrBox(wlr_box); // If we have sent a configure changing the size @@ -261,7 +249,7 @@ fn handleCommit(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { // Update the stored dimensions of the surface view.surface_box = new_box; - if (s == self.wlr_xdg_surface.configure_serial) { + if (s == self.xdg_surface.configure_serial) { view.notifyConfiguredOrApplyPending(); } else { // If the client has not yet acked our configure, we need to send a @@ -279,13 +267,12 @@ fn handleCommit(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { } /// Called when a new xdg popup is requested by the client -fn handleNewPopup(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_new_popup", listener.?); - const wlr_xdg_popup = util.voidCast(c.wlr_xdg_popup, data.?); +fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), wlr_xdg_popup: *wlr.XdgPopup) void { + const self = @fieldParentPtr(Self, "new_popup", listener); // This will free itself on destroy - var xdg_popup = util.gpa.create(XdgPopup) catch { - c.wl_resource_post_no_memory(wlr_xdg_popup.resource); + const xdg_popup = util.gpa.create(XdgPopup) catch { + wlr_xdg_popup.resource.postNoMemory(); return; }; xdg_popup.init(self.view.output, &self.view.current.box, wlr_xdg_popup); @@ -293,33 +280,36 @@ fn handleNewPopup(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { /// Called when the client asks to be fullscreened. We always honor the request /// for now, perhaps it should be denied in some cases in the future. -fn handleRequestFullscreen(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_request_fullscreen", listener.?); - const event = util.voidCast(c.wlr_xdg_toplevel_set_fullscreen_event, data.?); +fn handleRequestFullscreen( + listener: *wl.Listener(*wlr.XdgToplevel.event.SetFullscreen), + event: *wlr.XdgToplevel.event.SetFullscreen, +) void { + const self = @fieldParentPtr(Self, "request_fullscreen", listener); self.view.pending.fullscreen = event.fullscreen; self.view.applyPending(); } /// Called when the client asks to be moved via the cursor, for example when the /// user drags CSD titlebars. -fn handleRequestMove(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_request_move", listener.?); - const event = util.voidCast(c.wlr_xdg_toplevel_move_event, data.?); - const seat = util.voidCast(Seat, event.seat.*.seat.*.data.?); +fn handleRequestMove( + listener: *wl.Listener(*wlr.XdgToplevel.event.Move), + event: *wlr.XdgToplevel.event.Move, +) void { + const self = @fieldParentPtr(Self, "request_move", listener); + const seat = @intToPtr(*Seat, event.seat.seat.data); seat.cursor.enterMode(.move, self.view); } /// Called when the client asks to be resized via the cursor. -fn handleRequestResize(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_request_resize", listener.?); - const event = util.voidCast(c.wlr_xdg_toplevel_resize_event, data.?); - const seat = util.voidCast(Seat, event.seat.*.seat.*.data.?); +fn handleRequestResize(listener: *wl.Listener(*wlr.XdgToplevel.event.Resize), event: *wlr.XdgToplevel.event.Resize) void { + const self = @fieldParentPtr(Self, "request_resize", listener); + const seat = @intToPtr(*Seat, event.seat.seat.data); seat.cursor.enterMode(.resize, self.view); } /// Called when the client sets / updates its title -fn handleSetTitle(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_set_title", listener.?); +fn handleSetTitle(listener: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSurface) void { + const self = @fieldParentPtr(Self, "set_title", listener); // Send title to all status listeners attached to a seat which focuses this view var seat_it = self.view.output.root.server.input_manager.seats.first; diff --git a/river/XwaylandUnmanaged.zig b/river/XwaylandUnmanaged.zig index 746e962..003693d 100644 --- a/river/XwaylandUnmanaged.zig +++ b/river/XwaylandUnmanaged.zig @@ -18,8 +18,9 @@ const Self = @This(); const std = @import("std"); +const wlr = @import("wlroots"); +const wl = @import("wayland").server.wl; -const c = @import("c.zig"); const util = @import("util.zig"); const Box = @import("Box.zig"); @@ -28,39 +29,38 @@ const Root = @import("Root.zig"); root: *Root, /// The corresponding wlroots object -wlr_xwayland_surface: *c.wlr_xwayland_surface, +xwayland_surface: *wlr.XwaylandSurface, // Listeners that are always active over the view's lifetime -liseten_request_configure: c.wl_listener = undefined, -listen_destroy: c.wl_listener = undefined, -listen_map: c.wl_listener = undefined, -listen_unmap: c.wl_listener = undefined, +request_configure: wl.Listener(*wlr.XwaylandSurface.event.Configure) = undefined, +destroy: wl.Listener(*wlr.XwaylandSurface) = undefined, +map: wl.Listener(*wlr.XwaylandSurface) = undefined, +unmap: wl.Listener(*wlr.XwaylandSurface) = undefined, // Listeners that are only active while the view is mapped -listen_commit: c.wl_listener = undefined, +commit: wl.Listener(*wlr.Surface) = undefined, -pub fn init(self: *Self, root: *Root, wlr_xwayland_surface: *c.wlr_xwayland_surface) void { - self.* = .{ .root = root, .wlr_xwayland_surface = wlr_xwayland_surface }; +pub fn init(self: *Self, root: *Root, xwayland_surface: *wlr.XwaylandSurface) void { + self.* = .{ .root = root, .xwayland_surface = xwayland_surface }; // Add listeners that are active over the view's entire lifetime - self.liseten_request_configure.notify = handleRequestConfigure; - c.wl_signal_add(&wlr_xwayland_surface.events.request_configure, &self.liseten_request_configure); + self.request_configure.setNotify(handleRequestConfigure); + xwayland_surface.events.request_configure.add(&self.request_configure); - self.listen_destroy.notify = handleDestroy; - c.wl_signal_add(&wlr_xwayland_surface.events.destroy, &self.listen_destroy); + self.destroy.setNotify(handleDestroy); + xwayland_surface.events.destroy.add(&self.destroy); - self.listen_map.notify = handleMap; - c.wl_signal_add(&wlr_xwayland_surface.events.map, &self.listen_map); + self.map.setNotify(handleMap); + xwayland_surface.events.map.add(&self.map); - self.listen_unmap.notify = handleUnmap; - c.wl_signal_add(&wlr_xwayland_surface.events.unmap, &self.listen_unmap); + self.unmap.setNotify(handleUnmap); + xwayland_surface.events.unmap.add(&self.unmap); } /// Return the surface at output coordinates ox, oy and set sx, sy to the /// corresponding surface-relative coordinates, if there is a surface. -pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_surface { - return c.wlr_surface_surface_at( - self.wlr_xwayland_surface.surface, +pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*wlr.Surface { + return self.xwayland_surface.surface.?.surfaceAt( ox - @intToFloat(f64, self.view.current_box.x), oy - @intToFloat(f64, self.view.current_box.y), sx, @@ -68,26 +68,22 @@ pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_surfa ); } -fn handleRequestConfigure(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "liseten_request_configure", listener.?); - const wlr_xwayland_surface_configure_event = util.voidCast(c.wlr_xwayland_surface_configure_event, data.?); - c.wlr_xwayland_surface_configure( - self.wlr_xwayland_surface, - wlr_xwayland_surface_configure_event.x, - wlr_xwayland_surface_configure_event.y, - wlr_xwayland_surface_configure_event.width, - wlr_xwayland_surface_configure_event.height, - ); +fn handleRequestConfigure( + listener: *wl.Listener(*wlr.XwaylandSurface.event.Configure), + event: *wlr.XwaylandSurface.event.Configure, +) void { + const self = @fieldParentPtr(Self, "request_configure", listener); + self.xwayland_surface.configure(event.x, event.y, event.width, event.height); } /// Called when the xwayland surface is destroyed -fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_destroy", listener.?); +fn handleDestroy(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *wlr.XwaylandSurface) void { + const self = @fieldParentPtr(Self, "destroy", listener); // Remove listeners that are active for the entire lifetime of the view - c.wl_list_remove(&self.listen_destroy.link); - c.wl_list_remove(&self.listen_map.link); - c.wl_list_remove(&self.listen_unmap.link); + self.destroy.link.remove(); + self.map.link.remove(); + self.unmap.link.remove(); // Deallocate the node const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); @@ -95,8 +91,8 @@ fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { } /// Called when the xwayland surface is mapped, or ready to display on-screen. -fn handleMap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_map", listener.?); +fn handleMap(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *wlr.XwaylandSurface) void { + const self = @fieldParentPtr(Self, "map", listener); const root = self.root; // Add self to the list of unmanaged views in the root @@ -104,29 +100,29 @@ fn handleMap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { root.xwayland_unmanaged_views.prepend(node); // Add listeners that are only active while mapped - self.listen_commit.notify = handleCommit; - c.wl_signal_add(&self.wlr_xwayland_surface.surface.*.events.commit, &self.listen_commit); + self.commit.setNotify(handleCommit); + xwayland_surface.surface.?.events.commit.add(&self.commit); // TODO: handle keyboard focus - // if (wlr_xwayland_or_surface_wants_focus(self.wlr_xwayland_surface)) { ... + // if (wlr_xwayland_or_surface_wants_focus(self.xwayland_surface)) { ... } /// Called when the surface is unmapped and will no longer be displayed. -fn handleUnmap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_unmap", listener.?); +fn handleUnmap(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *wlr.XwaylandSurface) void { + const self = @fieldParentPtr(Self, "unmap", listener); // Remove self from the list of unmanged views in the root const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); self.root.xwayland_unmanaged_views.remove(node); // Remove listeners that are only active while mapped - c.wl_list_remove(&self.listen_commit.link); + self.commit.link.remove(); // TODO: return focus } /// Called when the surface is comitted -fn handleCommit(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_commit", listener.?); +fn handleCommit(listener: *wl.Listener(*wlr.Surface), surface: *wlr.Surface) void { + const self = @fieldParentPtr(Self, "commit", listener); // TODO: check if the surface has moved for damage tracking } diff --git a/river/XwaylandView.zig b/river/XwaylandView.zig index 04a6b60..ab665a8 100644 --- a/river/XwaylandView.zig +++ b/river/XwaylandView.zig @@ -18,8 +18,8 @@ const Self = @This(); const std = @import("std"); - -const c = @import("c.zig"); +const wlr = @import("wlroots"); +const wl = @import("wayland").server.wl; const Box = @import("Box.zig"); const View = @import("View.zig"); @@ -30,58 +30,57 @@ const XdgPopup = @import("XdgPopup.zig"); view: *View, /// The corresponding wlroots object -wlr_xwayland_surface: *c.wlr_xwayland_surface, +xwayland_surface: *wlr.XwaylandSurface, // Listeners that are always active over the view's lifetime -listen_destroy: c.wl_listener = undefined, -listen_map: c.wl_listener = undefined, -listen_unmap: c.wl_listener = undefined, -listen_title: c.wl_listener = undefined, +destroy: wl.Listener(*wlr.XwaylandSurface) = undefined, +map: wl.Listener(*wlr.XwaylandSurface) = undefined, +unmap: wl.Listener(*wlr.XwaylandSurface) = undefined, +title: wl.Listener(*wlr.XwaylandSurface) = undefined, // Listeners that are only active while the view is mapped -listen_commit: c.wl_listener = undefined, +commit: wl.Listener(*wlr.Surface) = undefined, -pub fn init(self: *Self, view: *View, wlr_xwayland_surface: *c.wlr_xwayland_surface) void { - self.* = .{ .view = view, .wlr_xwayland_surface = wlr_xwayland_surface }; - wlr_xwayland_surface.data = self; +pub fn init(self: *Self, view: *View, xwayland_surface: *wlr.XwaylandSurface) void { + self.* = .{ .view = view, .xwayland_surface = xwayland_surface }; + xwayland_surface.data = @ptrToInt(self); // Add listeners that are active over the view's entire lifetime - self.listen_destroy.notify = handleDestroy; - c.wl_signal_add(&self.wlr_xwayland_surface.events.destroy, &self.listen_destroy); + self.destroy.setNotify(handleDestroy); + self.xwayland_surface.events.destroy.add(&self.destroy); - self.listen_map.notify = handleMap; - c.wl_signal_add(&self.wlr_xwayland_surface.events.map, &self.listen_map); + self.map.setNotify(handleMap); + self.xwayland_surface.events.map.add(&self.map); - self.listen_unmap.notify = handleUnmap; - c.wl_signal_add(&self.wlr_xwayland_surface.events.unmap, &self.listen_unmap); + self.unmap.setNotify(handleUnmap); + self.xwayland_surface.events.unmap.add(&self.unmap); - self.listen_title.notify = handleTitle; - c.wl_signal_add(&self.wlr_xwayland_surface.events.set_title, &self.listen_title); + self.title.setNotify(handleTitle); + self.xwayland_surface.events.set_title.add(&self.title); } pub fn deinit(self: *Self) void { - if (self.view.wlr_surface != null) { + if (self.view.surface != null) { // Remove listeners that are active for the entire lifetime of the view - c.wl_list_remove(&self.listen_destroy.link); - c.wl_list_remove(&self.listen_map.link); - c.wl_list_remove(&self.listen_unmap.link); - c.wl_list_remove(&self.listen_title.link); + self.destroy.link.remove(); + self.map.link.remove(); + self.unmap.link.remove(); + self.title.link.remove(); } } pub fn needsConfigure(self: Self) bool { - return self.wlr_xwayland_surface.x != self.view.pending.box.x or - self.wlr_xwayland_surface.y != self.view.pending.box.y or - self.wlr_xwayland_surface.width != self.view.pending.box.width or - self.wlr_xwayland_surface.height != self.view.pending.box.height; + return self.xwayland_surface.x != self.view.pending.box.x or + self.xwayland_surface.y != self.view.pending.box.y or + self.xwayland_surface.width != self.view.pending.box.width or + self.xwayland_surface.height != self.view.pending.box.height; } /// Apply pending state pub fn configure(self: Self) void { const state = &self.view.pending; - c.wlr_xwayland_surface_set_fullscreen(self.wlr_xwayland_surface, state.fullscreen); - c.wlr_xwayland_surface_configure( - self.wlr_xwayland_surface, + self.xwayland_surface.setFullscreen(state.fullscreen); + self.xwayland_surface.configure( @intCast(i16, state.box.x), @intCast(i16, state.box.y), @intCast(u16, state.box.width), @@ -97,23 +96,23 @@ pub fn configure(self: Self) void { /// Close the view. This will lead to the unmap and destroy events being sent pub fn close(self: Self) void { - c.wlr_xwayland_surface_close(self.wlr_xwayland_surface); + self.xwayland_surface.close(); } /// Iterate over all surfaces of the xwayland view. pub fn forEachSurface( self: Self, - iterator: c.wlr_surface_iterator_func_t, - user_data: ?*c_void, + comptime T: type, + iterator: fn (surface: *wlr.Surface, sx: c_int, sy: c_int, data: T) callconv(.C) void, + data: T, ) void { - c.wlr_surface_for_each_surface(self.wlr_xwayland_surface.surface, iterator, user_data); + self.xwayland_surface.surface.?.forEachSurface(T, iterator, data); } /// Return the surface at output coordinates ox, oy and set sx, sy to the /// corresponding surface-relative coordinates, if there is a surface. -pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_surface { - return c.wlr_surface_surface_at( - self.wlr_xwayland_surface.surface, +pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*wlr.Surface { + return self.xwayland_surface.surface.?.surfaceAt( ox - @intToFloat(f64, self.view.current.box.x), oy - @intToFloat(f64, self.view.current.box.y), sx, @@ -123,12 +122,12 @@ pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_surfa /// Get the current title of the xwayland surface. May be an empty string pub fn getTitle(self: Self) [*:0]const u8 { - return self.wlr_xwayland_surface.title orelse ""; + return self.xwayland_surface.title orelse ""; } /// Return bounds on the dimensions of the view pub fn getConstraints(self: Self) View.Constraints { - const hints: *c.wlr_xwayland_surface_size_hints = self.wlr_xwayland_surface.size_hints orelse return .{ + const hints = self.xwayland_surface.size_hints orelse return .{ .min_width = View.min_size, .max_width = std.math.maxInt(u32), .min_height = View.min_size, @@ -143,39 +142,42 @@ pub fn getConstraints(self: Self) View.Constraints { } /// Called when the xwayland surface is destroyed -fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_destroy", listener.?); +fn handleDestroy(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *wlr.XwaylandSurface) void { + const self = @fieldParentPtr(Self, "destroy", listener); self.deinit(); - self.view.wlr_surface = null; + self.view.surface = null; } /// Called when the xwayland surface is mapped, or ready to display on-screen. -fn handleMap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_map", listener.?); +fn handleMap(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *wlr.XwaylandSurface) void { + const self = @fieldParentPtr(Self, "map", listener); const view = self.view; const root = view.output.root; // Add listeners that are only active while mapped - self.listen_commit.notify = handleCommit; - c.wl_signal_add(&self.wlr_xwayland_surface.surface.*.events.commit, &self.listen_commit); + self.commit.setNotify(handleCommit); + self.xwayland_surface.surface.?.events.commit.add(&self.commit); - view.wlr_surface = self.wlr_xwayland_surface.surface; + view.surface = self.xwayland_surface.surface; // Use the view's "natural" size centered on the output as the default // floating dimensions - view.float_box.width = self.wlr_xwayland_surface.width; - view.float_box.height = self.wlr_xwayland_surface.height; + view.float_box.width = self.xwayland_surface.width; + view.float_box.height = self.xwayland_surface.height; view.float_box.x = std.math.max(0, @divTrunc(@intCast(i32, view.output.usable_box.width) - @intCast(i32, view.float_box.width), 2)); view.float_box.y = std.math.max(0, @divTrunc(@intCast(i32, view.output.usable_box.height) - @intCast(i32, view.float_box.height), 2)); - const size_hints = self.wlr_xwayland_surface.size_hints; - const has_fixed_size = size_hints.*.min_width != 0 and size_hints.*.min_height != 0 and - (size_hints.*.min_width == size_hints.*.max_width or size_hints.*.min_height == size_hints.*.max_height); - const app_id: [*:0]const u8 = if (self.wlr_xwayland_surface.class) |id| id else "NULL"; + const has_fixed_size = if (self.xwayland_surface.size_hints) |size_hints| + size_hints.min_width != 0 and size_hints.min_height != 0 and + (size_hints.min_width == size_hints.max_width or size_hints.min_height == size_hints.max_height) + else + false; - if (self.wlr_xwayland_surface.parent != null or has_fixed_size) { + const app_id: [*:0]const u8 = if (self.xwayland_surface.class) |id| id else "NULL"; + + if (self.xwayland_surface.parent != null or has_fixed_size) { // If the toplevel has a parent or has a fixed size make it float view.current.float = true; view.pending.float = true; @@ -196,26 +198,26 @@ fn handleMap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { } /// Called when the surface is unmapped and will no longer be displayed. -fn handleUnmap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_unmap", listener.?); +fn handleUnmap(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *wlr.XwaylandSurface) void { + const self = @fieldParentPtr(Self, "unmap", listener); self.view.unmap(); // Remove listeners that are only active while mapped - c.wl_list_remove(&self.listen_commit.link); + self.commit.link.remove(); } /// Called when the surface is comitted /// TODO: check for unexpected change in size and react as needed -fn handleCommit(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_commit", listener.?); +fn handleCommit(listener: *wl.Listener(*wlr.Surface), surface: *wlr.Surface) void { + const self = @fieldParentPtr(Self, "commit", listener); const view = self.view; view.surface_box = Box{ .x = 0, .y = 0, - .width = @intCast(u32, self.wlr_xwayland_surface.surface.*.current.width), - .height = @intCast(u32, self.wlr_xwayland_surface.surface.*.current.height), + .width = @intCast(u32, surface.current.width), + .height = @intCast(u32, surface.current.height), }; // See comment in XwaylandView.configure() @@ -225,8 +227,8 @@ fn handleCommit(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { } /// Called then the window updates its title -fn handleTitle(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { - const self = @fieldParentPtr(Self, "listen_title", listener.?); +fn handleTitle(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *wlr.XwaylandSurface) void { + const self = @fieldParentPtr(Self, "title", listener); // Send title to all status listeners attached to a seat which focuses this view var seat_it = self.view.output.root.server.input_manager.seats.first; diff --git a/river/c.zig b/river/c.zig index 306ae25..76b97e2 100644 --- a/river/c.zig +++ b/river/c.zig @@ -17,63 +17,10 @@ pub usingnamespace @cImport({ @cDefine("_POSIX_C_SOURCE", "200809L"); - @cDefine("WLR_USE_UNSTABLE", {}); @cInclude("stdlib.h"); - @cInclude("time.h"); @cInclude("unistd.h"); @cInclude("linux/input-event-codes.h"); @cInclude("libevdev/libevdev.h"); - - @cInclude("wayland-server-core.h"); - @cInclude("wlr/backend.h"); - @cInclude("wlr/backend/multi.h"); - @cInclude("wlr/backend/noop.h"); - //@cInclude("wlr/render/wlr_renderer.h"); - @cInclude("wlr/types/wlr_buffer.h"); - @cInclude("wlr/types/wlr_compositor.h"); - @cInclude("wlr/types/wlr_cursor.h"); - @cInclude("wlr/types/wlr_data_control_v1.h"); - @cInclude("wlr/types/wlr_data_device.h"); - @cInclude("wlr/types/wlr_export_dmabuf_v1.h"); - @cInclude("wlr/types/wlr_gamma_control_v1.h"); - @cInclude("wlr/types/wlr_idle.h"); - @cInclude("wlr/types/wlr_input_device.h"); - @cInclude("wlr/types/wlr_input_inhibitor.h"); - @cInclude("wlr/types/wlr_keyboard.h"); - @cInclude("wlr/types/wlr_layer_shell_v1.h"); - @cInclude("wlr/types/wlr_matrix.h"); - @cInclude("wlr/types/wlr_output.h"); - @cInclude("wlr/types/wlr_output_layout.h"); - @cInclude("wlr/types/wlr_output_management_v1.h"); - @cInclude("wlr/types/wlr_output_power_management_v1.h"); - @cInclude("wlr/types/wlr_pointer.h"); - @cInclude("wlr/types/wlr_primary_selection.h"); - @cInclude("wlr/types/wlr_primary_selection_v1.h"); - @cInclude("wlr/types/wlr_screencopy_v1.h"); - @cInclude("wlr/types/wlr_seat.h"); - @cInclude("wlr/types/wlr_viewporter.h"); - @cInclude("wlr/types/wlr_virtual_pointer_v1.h"); - @cInclude("wlr/types/wlr_virtual_keyboard_v1.h"); - @cInclude("wlr/types/wlr_xcursor_manager.h"); - @cInclude("wlr/types/wlr_xdg_decoration_v1.h"); - @cInclude("wlr/types/wlr_xdg_output_v1.h"); - @cInclude("wlr/types/wlr_xdg_shell.h"); - if (@import("build_options").xwayland) @cInclude("wlr/xwayland.h"); - @cInclude("wlr/util/log.h"); - @cInclude("xkbcommon/xkbcommon.h"); - - // Contains a subset of functions from wlr/backend.h and wlr/render/wlr_renderer.h - // that can be automatically imported - @cInclude("include/bindings.h"); - - @cInclude("river-control-unstable-v1-protocol.h"); - @cInclude("river-status-unstable-v1-protocol.h"); }); - -// These are needed because zig currently names translated anonymous unions -// with a global counter, which makes code unportable. -// See https://github.com/ifreund/river/issues/17 -pub const wlr_xdg_surface_union = @typeInfo(wlr_xdg_surface).Struct.fields[5].name; -pub const wlr_input_device_union = @typeInfo(wlr_input_device).Struct.fields[8].name; diff --git a/river/command/exit.zig b/river/command/exit.zig index db378fc..129a247 100644 --- a/river/command/exit.zig +++ b/river/command/exit.zig @@ -17,8 +17,6 @@ const std = @import("std"); -const c = @import("../c.zig"); - const Error = @import("../command.zig").Error; const Seat = @import("../Seat.zig"); @@ -30,5 +28,5 @@ pub fn exit( out: *?[]const u8, ) Error!void { if (args.len > 1) return Error.TooManyArguments; - c.wl_display_terminate(seat.input_manager.server.wl_display); + seat.input_manager.server.wl_server.terminate(); } diff --git a/river/command/map.zig b/river/command/map.zig index a7b95f9..b2ee3c2 100644 --- a/river/command/map.zig +++ b/river/command/map.zig @@ -16,6 +16,9 @@ // along with this program. If not, see . const std = @import("std"); +const mem = std.mem; +const wlr = @import("wlroots"); +const xkb = @import("xkbcommon"); const c = @import("../c.zig"); const util = @import("../util.zig"); @@ -25,21 +28,6 @@ const Mapping = @import("../Mapping.zig"); const PointerMapping = @import("../PointerMapping.zig"); const Seat = @import("../Seat.zig"); -const modifier_names = [_]struct { - name: []const u8, - modifier: u32, -}{ - .{ .name = "None", .modifier = 0 }, - .{ .name = "Shift", .modifier = c.WLR_MODIFIER_SHIFT }, - .{ .name = "Lock", .modifier = c.WLR_MODIFIER_CAPS }, - .{ .name = "Control", .modifier = c.WLR_MODIFIER_CTRL }, - .{ .name = "Mod1", .modifier = c.WLR_MODIFIER_ALT }, - .{ .name = "Mod2", .modifier = c.WLR_MODIFIER_MOD2 }, - .{ .name = "Mod3", .modifier = c.WLR_MODIFIER_MOD3 }, - .{ .name = "Mod4", .modifier = c.WLR_MODIFIER_LOGO }, - .{ .name = "Mod5", .modifier = c.WLR_MODIFIER_MOD5 }, -}; - /// Create a new mapping for a given mode /// /// Example: @@ -133,9 +121,14 @@ fn modeNameToId(allocator: *std.mem.Allocator, seat: *Seat, mode_name: []const u } /// Returns the index of the Mapping with matching modifiers, keysym and release, if any. -fn mappingExists(mappings: *std.ArrayList(Mapping), modifiers: u32, keysym: u32, release: bool) ?usize { +fn mappingExists( + mappings: *std.ArrayList(Mapping), + modifiers: wlr.Keyboard.ModifierMask, + keysym: xkb.Keysym, + release: bool, +) ?usize { for (mappings.items) |mapping, i| { - if (mapping.modifiers == modifiers and mapping.keysym == keysym and mapping.release == release) { + if (std.meta.eql(mapping.modifiers, modifiers) and mapping.keysym == keysym and mapping.release == release) { return i; } } @@ -144,9 +137,13 @@ fn mappingExists(mappings: *std.ArrayList(Mapping), modifiers: u32, keysym: u32, } /// Returns the index of the PointerMapping with matching modifiers and event code, if any. -fn pointerMappingExists(pointer_mappings: *std.ArrayList(PointerMapping), modifiers: u32, event_code: u32) ?usize { +fn pointerMappingExists( + pointer_mappings: *std.ArrayList(PointerMapping), + modifiers: wlr.Keyboard.ModifierMask, + event_code: u32, +) ?usize { for (pointer_mappings.items) |mapping, i| { - if (mapping.modifiers == modifiers and mapping.event_code == event_code) { + if (std.meta.eql(mapping.modifiers, modifiers) and mapping.event_code == event_code) { return i; } } @@ -166,39 +163,47 @@ fn parseEventCode(allocator: *std.mem.Allocator, event_code_str: []const u8, out return @intCast(u32, ret); } -fn parseKeysym(allocator: *std.mem.Allocator, keysym_str: []const u8, out: *?[]const u8) !u32 { +fn parseKeysym(allocator: *std.mem.Allocator, keysym_str: []const u8, out: *?[]const u8) !xkb.Keysym { const keysym_name = try std.cstr.addNullByte(allocator, keysym_str); defer allocator.free(keysym_name); - const keysym = c.xkb_keysym_from_name(keysym_name, .XKB_KEYSYM_CASE_INSENSITIVE); - if (keysym == c.XKB_KEY_NoSymbol) { - out.* = try std.fmt.allocPrint( - allocator, - "invalid keysym '{}'", - .{keysym_str}, - ); + const keysym = xkb.Keysym.fromName(keysym_name, .case_insensitive); + if (keysym == .NoSymbol) { + out.* = try std.fmt.allocPrint(allocator, "invalid keysym '{}'", .{keysym_str}); return Error.Other; } - return keysym; } -fn parseModifiers(allocator: *std.mem.Allocator, modifiers_str: []const u8, out: *?[]const u8) !u32 { +fn parseModifiers( + allocator: *std.mem.Allocator, + modifiers_str: []const u8, + out: *?[]const u8, +) !wlr.Keyboard.ModifierMask { var it = std.mem.split(modifiers_str, "+"); - var modifiers: u32 = 0; - while (it.next()) |mod_name| { - for (modifier_names) |def| { + var modifiers = wlr.Keyboard.ModifierMask{}; + outer: while (it.next()) |mod_name| { + if (mem.eql(u8, mod_name, "None")) continue; + inline for ([_]struct { name: []const u8, field_name: []const u8 }{ + .{ .name = "Shift", .field_name = "shift" }, + .{ .name = "Lock", .field_name = "caps" }, + .{ .name = "Control", .field_name = "ctrl" }, + .{ .name = "Mod1", .field_name = "alt" }, + .{ .name = "Mod2", .field_name = "mod2" }, + .{ .name = "Mod3", .field_name = "mod3" }, + .{ .name = "Mod4", .field_name = "logo" }, + .{ .name = "Mod5", .field_name = "mod5" }, + }) |def| { if (std.mem.eql(u8, def.name, mod_name)) { - modifiers |= def.modifier; - break; + @field(modifiers, def.field_name) = true; + continue :outer; } - } else { - out.* = try std.fmt.allocPrint( - allocator, - "invalid modifier '{}'", - .{mod_name}, - ); - return Error.Other; } + out.* = try std.fmt.allocPrint( + allocator, + "invalid modifier '{}'", + .{mod_name}, + ); + return Error.Other; } return modifiers; } diff --git a/river/command/set_repeat.zig b/river/command/set_repeat.zig index 32b9f78..8dec594 100644 --- a/river/command/set_repeat.zig +++ b/river/command/set_repeat.zig @@ -17,8 +17,6 @@ const std = @import("std"); -const c = @import("../c.zig"); - const Error = @import("../command.zig").Error; const Seat = @import("../Seat.zig"); @@ -37,6 +35,6 @@ pub fn setRepeat( var it = seat.keyboards.first; while (it) |node| : (it = node.next) { - c.wlr_keyboard_set_repeat_info(node.data.wlr_keyboard, rate, delay); + node.data.input_device.device.keyboard.setRepeatInfo(rate, delay); } } diff --git a/river/main.zig b/river/main.zig index df03b54..d576bb2 100644 --- a/river/main.zig +++ b/river/main.zig @@ -16,6 +16,7 @@ // along with this program. If not, see . const std = @import("std"); +const wlr = @import("wlroots"); const c = @import("c.zig"); const log = @import("log.zig"); @@ -93,14 +94,11 @@ pub fn main() anyerror!void { } } - c.wlr_log_init( - switch (log.level) { - .debug => .WLR_DEBUG, - .notice, .info => .WLR_INFO, - .warn, .err, .crit, .alert, .emerg => .WLR_ERROR, - }, - null, - ); + wlr.log.init(switch (log.level) { + .debug => .debug, + .notice, .info => .info, + .warn, .err, .crit, .alert, .emerg => .err, + }); log.info(.server, "initializing", .{}); @@ -136,7 +134,7 @@ pub fn main() anyerror!void { log.info(.server, "running...", .{}); - server.run(); + server.wl_server.run(); log.info(.server, "shutting down", .{}); } diff --git a/river/render.zig b/river/render.zig index 0bb65ab..15cc005 100644 --- a/river/render.zig +++ b/river/render.zig @@ -17,8 +17,11 @@ const build_options = @import("build_options"); const std = @import("std"); +const os = std.os; +const wlr = @import("wlroots"); +const wl = @import("wayland").server.wl; +const pixman = @import("pixman"); -const c = @import("c.zig"); const log = @import("log.zig"); const util = @import("util.zig"); @@ -36,24 +39,21 @@ const SurfaceRenderData = struct { output_x: i32, output_y: i32, - when: *c.timespec, + when: *os.timespec, opacity: f32, }; pub fn renderOutput(output: *Output) void { const config = &output.root.server.config; - const wlr_renderer = output.getRenderer(); + const renderer = output.wlr_output.backend.getRenderer().?; - var now: c.timespec = undefined; - _ = c.clock_gettime(c.CLOCK_MONOTONIC, &now); + var now: os.timespec = undefined; + os.clock_gettime(os.CLOCK_MONOTONIC, &now) catch unreachable; - // wlr_output_attach_render makes the OpenGL context current. - if (!c.wlr_output_attach_render(output.wlr_output, null)) return; + output.wlr_output.attachRender(null) catch return; - // Begin the renderer (calls glViewport and some other GL sanity checks) - // Here we don't want the output_effective_resolution since we want to render the whole output - c.wlr_renderer_begin(wlr_renderer, output.wlr_output.width, output.wlr_output.height); + renderer.begin(output.wlr_output.width, output.wlr_output.height); // Find the first visible fullscreen view in the stack if there is one var it = ViewStack(View).iter(output.views.first, .forward, output.current.tags, renderFilter); @@ -64,15 +64,15 @@ pub fn renderOutput(output: *Output) void { // If we have a fullscreen view to render, render it. if (fullscreen_view) |view| { // Always clear with solid black for fullscreen - c.wlr_renderer_clear(wlr_renderer, &[_]f32{ 0, 0, 0, 1 }); + renderer.clear(&[_]f32{ 0, 0, 0, 1 }); renderView(output.*, view, &now); if (build_options.xwayland) renderXwaylandUnmanaged(output.*, &now); } else { // No fullscreen view, so render normal layers/views - c.wlr_renderer_clear(wlr_renderer, &config.background_color); + renderer.clear(&config.background_color); - renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], &now, .toplevels); - renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], &now, .toplevels); + renderLayer(output.*, output.getLayer(.background).*, &now, .toplevels); + renderLayer(output.*, output.getLayer(.bottom).*, &now, .toplevels); // The first view in the list is "on top" so iterate in reverse. it = ViewStack(View).iter(output.views.last, .reverse, output.current.tags, renderFilter); @@ -96,16 +96,16 @@ pub fn renderOutput(output: *Output) void { if (build_options.xwayland) renderXwaylandUnmanaged(output.*, &now); - renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_TOP], &now, .toplevels); + renderLayer(output.*, output.getLayer(.top).*, &now, .toplevels); - renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], &now, .popups); - renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], &now, .popups); - renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_TOP], &now, .popups); + renderLayer(output.*, output.getLayer(.background).*, &now, .popups); + renderLayer(output.*, output.getLayer(.bottom).*, &now, .popups); + renderLayer(output.*, output.getLayer(.top).*, &now, .popups); } // The overlay layer is rendered in both fullscreen and normal cases - renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], &now, .toplevels); - renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], &now, .popups); + renderLayer(output.*, output.getLayer(.overlay).*, &now, .toplevels); + renderLayer(output.*, output.getLayer(.overlay).*, &now, .popups); renderDragIcons(output.*, &now); @@ -115,28 +115,27 @@ pub fn renderOutput(output: *Output) void { // reason, wlroots provides a software fallback, which we ask it to render // here. wlr_cursor handles configuring hardware vs software cursors for you, // and this function is a no-op when hardware cursors are in use. - c.wlr_output_render_software_cursors(output.wlr_output, null); + output.wlr_output.renderSoftwareCursors(null); // Conclude rendering and swap the buffers, showing the final frame // on-screen. - c.wlr_renderer_end(wlr_renderer); + renderer.end(); // TODO(wlroots): remove this with the next release. It is here due to // a wlroots bug in the screencopy damage implementation { var w: c_int = undefined; var h: c_int = undefined; - c.wlr_output_transformed_resolution(output.wlr_output, &w, &h); - var damage: c.pixman_region32_t = undefined; - c.pixman_region32_init(&damage); - _ = c.pixman_region32_union_rect(&damage, &damage, 0, 0, @intCast(c_uint, w), @intCast(c_uint, h)); - c.wlr_output_set_damage(output.wlr_output, &damage); + output.wlr_output.transformedResolution(&w, &h); + var damage: pixman.Region32 = undefined; + damage.init(); + _ = damage.unionRect(&damage, 0, 0, @intCast(c_uint, w), @intCast(c_uint, h)); + output.wlr_output.setDamage(&damage); } // TODO: handle failure - if (!c.wlr_output_commit(output.wlr_output)) { - log.err(.render, "wlr_output_commit failed for {}", .{output.wlr_output.name}); - } + output.wlr_output.commit() catch + log.err(.render, "output commit failed for {}", .{output.wlr_output.name}); } fn renderFilter(view: *View, filter_tags: u32) bool { @@ -151,7 +150,7 @@ fn renderFilter(view: *View, filter_tags: u32) bool { fn renderLayer( output: Output, layer: std.TailQueue(LayerSurface), - now: *c.timespec, + now: *os.timespec, role: enum { toplevels, popups }, ) void { var it = layer.first; @@ -165,13 +164,13 @@ fn renderLayer( .opacity = 1.0, }; switch (role) { - .toplevels => c.wlr_surface_for_each_surface( - layer_surface.wlr_layer_surface.surface, + .toplevels => layer_surface.wlr_layer_surface.surface.forEachSurface( + *SurfaceRenderData, renderSurfaceIterator, &rdata, ), - .popups => c.wlr_layer_surface_v1_for_each_popup( - layer_surface.wlr_layer_surface, + .popups => layer_surface.wlr_layer_surface.forEachPopup( + *SurfaceRenderData, renderSurfaceIterator, &rdata, ), @@ -179,14 +178,14 @@ fn renderLayer( } } -fn renderView(output: Output, view: *View, now: *c.timespec) void { +fn renderView(output: Output, view: *View, now: *os.timespec) void { // If we have saved buffers, we are in the middle of a transaction // and need to render those buffers until the transaction is complete. if (view.saved_buffers.items.len != 0) { for (view.saved_buffers.items) |saved_buffer| renderTexture( output, - saved_buffer.wlr_client_buffer.texture, + saved_buffer.client_buffer.texture orelse continue, .{ .x = saved_buffer.box.x + view.current.box.x - view.saved_surface_box.x, .y = saved_buffer.box.y + view.current.box.y - view.saved_surface_box.y, @@ -207,12 +206,12 @@ fn renderView(output: Output, view: *View, now: *c.timespec) void { .opacity = view.opacity, }; - view.forEachSurface(renderSurfaceIterator, &rdata); + view.forEachSurface(*SurfaceRenderData, renderSurfaceIterator, &rdata); } } -fn renderDragIcons(output: Output, now: *c.timespec) void { - const output_box = c.wlr_output_layout_get_box(output.root.wlr_output_layout, output.wlr_output); +fn renderDragIcons(output: Output, now: *os.timespec) void { + const output_box = output.root.output_layout.getBox(output.wlr_output).?; var it = output.root.drag_icons.first; while (it) |node| : (it = node.next) { @@ -221,70 +220,67 @@ fn renderDragIcons(output: Output, now: *c.timespec) void { var rdata = SurfaceRenderData{ .output = &output, .output_x = @floatToInt(i32, drag_icon.seat.cursor.wlr_cursor.x) + - drag_icon.wlr_drag_icon.surface.*.sx - output_box.*.x, + drag_icon.wlr_drag_icon.surface.sx - output_box.x, .output_y = @floatToInt(i32, drag_icon.seat.cursor.wlr_cursor.y) + - drag_icon.wlr_drag_icon.surface.*.sy - output_box.*.y, + drag_icon.wlr_drag_icon.surface.sy - output_box.y, .when = now, .opacity = 1.0, }; - c.wlr_surface_for_each_surface(drag_icon.wlr_drag_icon.surface, renderSurfaceIterator, &rdata); + drag_icon.wlr_drag_icon.surface.forEachSurface(*SurfaceRenderData, renderSurfaceIterator, &rdata); } } /// Render all xwayland unmanaged windows that appear on the output -fn renderXwaylandUnmanaged(output: Output, now: *c.timespec) void { - const output_box = c.wlr_output_layout_get_box(output.root.wlr_output_layout, output.wlr_output); +fn renderXwaylandUnmanaged(output: Output, now: *os.timespec) void { + const output_box = output.root.output_layout.getBox(output.wlr_output).?; var it = output.root.xwayland_unmanaged_views.first; while (it) |node| : (it = node.next) { - const wlr_xwayland_surface = node.data.wlr_xwayland_surface; + const xwayland_surface = node.data.xwayland_surface; var rdata = SurfaceRenderData{ .output = &output, - .output_x = wlr_xwayland_surface.x - output_box.*.x, - .output_y = wlr_xwayland_surface.y - output_box.*.y, + .output_x = xwayland_surface.x - output_box.x, + .output_y = xwayland_surface.y - output_box.y, .when = now, .opacity = 1.0, }; - c.wlr_surface_for_each_surface(wlr_xwayland_surface.surface, renderSurfaceIterator, &rdata); + xwayland_surface.surface.?.forEachSurface(*SurfaceRenderData, renderSurfaceIterator, &rdata); } } /// This function is passed to wlroots to render each surface during iteration fn renderSurfaceIterator( - surface: ?*c.wlr_surface, + surface: *wlr.Surface, surface_x: c_int, surface_y: c_int, - data: ?*c_void, + rdata: *SurfaceRenderData, ) callconv(.C) void { - const rdata = util.voidCast(SurfaceRenderData, data.?); - renderTexture( rdata.output.*, - c.wlr_surface_get_texture(surface), + surface.getTexture() orelse return, .{ .x = rdata.output_x + surface_x, .y = rdata.output_y + surface_y, - .width = surface.?.current.width, - .height = surface.?.current.height, + .width = surface.current.width, + .height = surface.current.height, }, - surface.?.current.transform, + surface.current.transform, rdata.opacity, ); - c.wlr_surface_send_frame_done(surface, rdata.when); + surface.sendFrameDone(rdata.when); } /// Render the given texture at the given box, taking the scale and transform /// of the output into account. fn renderTexture( output: Output, - wlr_texture: ?*c.wlr_texture, - wlr_box: c.wlr_box, - transform: c.wl_output_transform, + texture: *wlr.Texture, + wlr_box: wlr.Box, + transform: wl.Output.Transform, opacity: f32, ) void { - const texture = wlr_texture orelse return; var box = wlr_box; // Scale the box to the output's current scaling factor @@ -295,15 +291,16 @@ fn renderTexture( // prepares an orthographic projection and multiplies the necessary // transforms to produce a model-view-projection matrix. var matrix: [9]f32 = undefined; - const inverted = c.wlr_output_transform_invert(transform); - c.wlr_matrix_project_box(&matrix, &box, inverted, 0.0, &output.wlr_output.transform_matrix); + const inverted = wlr.Output.transformInvert(transform); + wlr.matrix.projectBox(&matrix, &box, inverted, 0.0, &output.wlr_output.transform_matrix); // This takes our matrix, the texture, and an alpha, and performs the actual // rendering on the GPU. - _ = c.wlr_render_texture_with_matrix(output.getRenderer(), texture, &matrix, opacity); + const renderer = output.wlr_output.backend.getRenderer().?; + renderer.renderTextureWithMatrix(texture, &matrix, opacity) catch return; } -fn renderBorders(output: Output, view: *View, now: *c.timespec) void { +fn renderBorders(output: Output, view: *View, now: *os.timespec) void { const config = &output.root.server.config; const color = if (view.current.focus != 0) &config.border_color_focused else &config.border_color_unfocused; const border_width = config.border_width; @@ -341,8 +338,7 @@ fn renderBorders(output: Output, view: *View, now: *c.timespec) void { fn renderRect(output: Output, box: Box, color: *const [4]f32) void { var wlr_box = box.toWlrBox(); scaleBox(&wlr_box, output.wlr_output.scale); - c.wlr_render_rect( - output.getRenderer(), + output.wlr_output.backend.getRenderer().?.renderRect( &wlr_box, color, &output.wlr_output.transform_matrix, @@ -350,7 +346,7 @@ fn renderRect(output: Output, box: Box, color: *const [4]f32) void { } /// Scale a wlr_box, taking the possibility of fractional scaling into account. -fn scaleBox(box: *c.wlr_box, scale: f64) void { +fn scaleBox(box: *wlr.Box, scale: f64) void { box.x = @floatToInt(c_int, @round(@intToFloat(f64, box.x) * scale)); box.y = @floatToInt(c_int, @round(@intToFloat(f64, box.y) * scale)); box.width = scaleLength(box.width, box.x, scale); diff --git a/riverctl/main.zig b/riverctl/main.zig index e3453eb..dad41ea 100644 --- a/riverctl/main.zig +++ b/riverctl/main.zig @@ -19,10 +19,10 @@ const std = @import("std"); const wayland = @import("wayland"); const wl = wayland.client.wl; -const river = wayland.client.river; +const zriver = wayland.client.zriver; const SetupContext = struct { - river_control: ?*river.ControlV1 = null, + river_control: ?*zriver.ControlV1 = null, seat: ?*wl.Seat = null, }; @@ -54,17 +54,17 @@ pub fn main() !void { fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *SetupContext) void { switch (event) { .global => |global| { - if (context.seat == null and std.cstr.cmp(global.interface, wl.Seat.interface().name) == 0) { + if (context.seat == null and std.cstr.cmp(global.interface, wl.Seat.getInterface().name) == 0) { context.seat = registry.bind(global.name, wl.Seat, 1) catch return; - } else if (std.cstr.cmp(global.interface, river.ControlV1.interface().name) == 0) { - context.river_control = registry.bind(global.name, river.ControlV1, 1) catch return; + } else if (std.cstr.cmp(global.interface, zriver.ControlV1.getInterface().name) == 0) { + context.river_control = registry.bind(global.name, zriver.ControlV1, 1) catch return; } }, .global_remove => {}, } } -fn callbackListener(callback: *river.CommandCallbackV1, event: river.CommandCallbackV1.Event, _: ?*c_void) void { +fn callbackListener(callback: *zriver.CommandCallbackV1, event: zriver.CommandCallbackV1.Event, _: ?*c_void) void { switch (event) { .success => |success| { if (std.mem.len(success.output) > 0) {