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!
This commit is contained in:
Isaac Freund 2020-11-04 00:23:21 +01:00
parent 0c5e5a7b4a
commit 20d804cdb5
42 changed files with 1457 additions and 1874 deletions

9
.gitmodules vendored
View file

@ -1,3 +1,12 @@
[submodule "deps/zig-wayland"] [submodule "deps/zig-wayland"]
path = deps/zig-wayland path = deps/zig-wayland
url = https://github.com/ifreund/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

View file

@ -29,7 +29,7 @@ git submodule update --init
To compile river first ensure that you have the following dependencies To compile river first ensure that you have the following dependencies
installed: installed:
- [zig](https://ziglang.org/download/) 0.7.0 - [zig](https://ziglang.org/download/) 0.7.1
- wayland - wayland
- wayland-protocols - wayland-protocols
- [wlroots](https://github.com/swaywm/wlroots) 0.12.0 - [wlroots](https://github.com/swaywm/wlroots) 0.12.0

179
build.zig
View file

@ -1,16 +1,10 @@
const std = @import("std"); const std = @import("std");
const zbs = std.build;
const ScanProtocolsStep = @import("deps/zig-wayland/build.zig").ScanProtocolsStep; const ScanProtocolsStep = @import("deps/zig-wayland/build.zig").ScanProtocolsStep;
pub fn build(b: *std.build.Builder) !void { pub fn build(b: *zbs.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.
const target = b.standardTargetOptions(.{}); 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 mode = b.standardReleaseOptions();
const xwayland = b.option( const xwayland = b.option(
@ -31,17 +25,14 @@ pub fn build(b: *std.build.Builder) !void {
break :scdoc_found true; break :scdoc_found true;
}; };
const examples = b.option( const examples = b.option(bool, "examples", "Set to true to build examples") orelse false;
bool,
"examples",
"Set to true to build examples",
) orelse false;
// TODO: port all parts of river to zig-wayland and delete this const scanner = ScanProtocolsStep.create(b);
const scan_protocols = OldScanProtocolsStep.create(b); scanner.addSystemProtocol("stable/xdg-shell/xdg-shell.xml");
const scanner = ScanProtocolsStep.create(b, "deps/zig-wayland/");
scanner.addProtocolPath("protocol/river-control-unstable-v1.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"); const river = b.addExecutable("river", "river/main.zig");
@ -49,16 +40,9 @@ pub fn build(b: *std.build.Builder) !void {
river.setBuildMode(mode); river.setBuildMode(mode);
river.addBuildOption(bool, "xwayland", xwayland); river.addBuildOption(bool, "xwayland", xwayland);
addProtocolDeps(river, &scan_protocols.step); addServerDeps(river, scanner);
addServerDeps(river);
river.install(); 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.setTarget(target);
status.setBuildMode(mode); status.setBuildMode(mode);
addProtocolDeps(status, &scan_protocols.step); status.step.dependOn(&scanner.step);
status.addPackage(scanner.getPkg());
status.linkLibC(); status.linkLibC();
status.linkSystemLibrary("wayland-client"); status.linkSystemLibrary("wayland-client");
scanner.addCSource(status);
status.install(); status.install();
} }
@ -107,123 +93,44 @@ pub fn build(b: *std.build.Builder) !void {
river_test.setBuildMode(mode); river_test.setBuildMode(mode);
river_test.addBuildOption(bool, "xwayland", xwayland); river_test.addBuildOption(bool, "xwayland", xwayland);
addProtocolDeps(river_test, &scan_protocols.step); addServerDeps(river_test, scanner);
addServerDeps(river_test);
const test_step = b.step("test", "Run the tests"); const test_step = b.step("test", "Run the tests");
test_step.dependOn(&river_test.step); test_step.dependOn(&river_test.step);
} }
} }
fn addServerDeps(exe: *std.build.LibExeObjStep) void { fn addServerDeps(exe: *zbs.LibExeObjStep, scanner: *ScanProtocolsStep) void {
exe.addCSourceFile("include/bindings.c", &[_][]const u8{"-std=c99"}); const wayland = scanner.getPkg();
exe.addIncludeDir("."); 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.linkLibC();
exe.linkSystemLibrary("libevdev"); exe.linkSystemLibrary("libevdev");
exe.addPackage(wayland);
exe.linkSystemLibrary("wayland-server"); exe.linkSystemLibrary("wayland-server");
exe.linkSystemLibrary("wlroots");
exe.addPackage(xkbcommon);
exe.linkSystemLibrary("xkbcommon"); exe.linkSystemLibrary("xkbcommon");
exe.addPackage(pixman);
exe.linkSystemLibrary("pixman-1"); 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 ScdocStep = struct {
const scd_paths = [_][]const u8{ const scd_paths = [_][]const u8{
"doc/river.1.scd", "doc/river.1.scd",
@ -232,23 +139,23 @@ const ScdocStep = struct {
"doc/river-layouts.7.scd", "doc/river-layouts.7.scd",
}; };
builder: *std.build.Builder, builder: *zbs.Builder,
step: std.build.Step, 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"); const self = builder.allocator.create(ScdocStep) catch @panic("out of memory");
self.* = init(builder); self.* = init(builder);
return self; return self;
} }
fn init(builder: *std.build.Builder) ScdocStep { fn init(builder: *zbs.Builder) ScdocStep {
return ScdocStep{ return ScdocStep{
.builder = builder, .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); const self = @fieldParentPtr(ScdocStep, "step", step);
for (scd_paths) |path| { for (scd_paths) |path| {
const command = try std.fmt.allocPrint( const command = try std.fmt.allocPrint(

1
deps/zig-pixman vendored Submodule

@ -0,0 +1 @@
Subproject commit 7847fd1bae7021cdb572e77eac93676c551fd1eb

2
deps/zig-wayland vendored

@ -1 +1 @@
Subproject commit ba49b2b6f984b788aea5e752bfeb64e3381472e7 Subproject commit 52326e7ee09d7acb6b55855f7a697af083ae973a

1
deps/zig-wlroots vendored Submodule

@ -0,0 +1 @@
Subproject commit 16d9039b5c345b2cc26118032261df9782e24946

1
deps/zig-xkbcommon vendored Submodule

@ -0,0 +1 @@
Subproject commit 9e4d41fe9414094db31c873c2ad9cadcd8999cf6

View file

@ -17,145 +17,77 @@
const std = @import("std"); const std = @import("std");
const c = @cImport({ const wayland = @import("wayland");
@cInclude("wayland-client.h"); const wl = wayland.client.wl;
@cInclude("river-status-unstable-v1-client-protocol.h"); const zriver = wayland.client.zriver;
});
const wl_registry_listener = c.wl_registry_listener{ const SetupContext = struct {
.global = handleGlobal, status_manager: ?*zriver.StatusManagerV1 = null,
.global_remove = handleGlobalRemove, 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 { pub fn main() !void {
const wl_display = c.wl_display_connect(null) orelse return error.CantConnectToDisplay; const display = try wl.Display.connect(null);
const wl_registry = c.wl_display_get_registry(wl_display); const registry = try display.getRegistry();
if (c.wl_registry_add_listener(wl_registry, &wl_registry_listener, null) < 0) var context = SetupContext{};
return error.FailedToAddListener;
if (c.wl_display_roundtrip(wl_display) < 0) return error.RoundtripFailed;
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); const status_manager = context.status_manager orelse return error.RiverStatusManagerNotAdvertised;
for (seats.items) |wl_seat| createSeatStatus(wl_seat);
outputs.deinit(); for (context.outputs.items) |output| {
seats.deinit(); 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. // 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( fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *SetupContext) void {
data: ?*c_void, switch (event) {
wl_registry: ?*c.wl_registry, .global => |global| {
name: u32, if (std.cstr.cmp(global.interface, zriver.StatusManagerV1.getInterface().name) == 0) {
interface: ?[*:0]const u8, context.status_manager = registry.bind(global.name, zriver.StatusManagerV1, 1) catch return;
version: u32, } else if (std.cstr.cmp(global.interface, wl.Seat.getInterface().name) == 0) {
) callconv(.C) void { const seat = registry.bind(global.name, wl.Seat, 1) catch return;
// Global advertisement order is not defined, so save any outputs or seats context.seats.append(seat) catch @panic("out of memory");
// advertised before the river_status_manager. } else if (std.cstr.cmp(global.interface, wl.Output.getInterface().name) == 0) {
if (std.cstr.cmp(interface.?, @ptrCast([*:0]const u8, c.zriver_status_manager_v1_interface.name.?)) == 0) { const output = registry.bind(global.name, wl.Output, 1) catch return;
river_status_manager = @ptrCast( context.outputs.append(output) catch @panic("out of memory");
*c.zriver_status_manager_v1, }
c.wl_registry_bind(wl_registry, name, &c.zriver_status_manager_v1_interface, version), },
); .global_remove => {},
} 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 createOutputStatus(wl_output: *c.wl_output) void { fn outputStatusListener(output_status: *zriver.OutputStatusV1, event: zriver.OutputStatusV1.Event, data: ?*c_void) void {
const river_output_status = c.zriver_status_manager_v1_get_river_output_status( switch (event) {
river_status_manager.?, .focused_tags => |focused_tags| std.debug.warn("Focused tags: {b:0>10}\n", .{focused_tags.tags}),
wl_output, .view_tags => |view_tags| {
);
_ = 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", .{}); std.debug.warn("View tags:\n", .{});
var offset: usize = 0; for (view_tags.tags.slice(u32)) |t| std.debug.warn("{b:0>10}\n", .{t});
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 handleFocusedOutput( fn seatStatusListener(seat_status: *zriver.SeatStatusV1, event: zriver.SeatStatusV1.Event, data: ?*c_void) void {
data: ?*c_void, switch (event) {
seat_status: ?*c.zriver_seat_status_v1, .focused_output => |focused_output| std.debug.warn("Output id {} focused\n", .{
wl_output: ?*c.wl_output, @ptrCast(*wl.Proxy, focused_output.output orelse return).getId(),
) callconv(.C) void { }),
std.debug.warn("Output id {} focused\n", .{c.wl_proxy_get_id(@ptrCast(*c.wl_proxy, wl_output))}); .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}),
} }
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.?});
} }

View file

@ -1,11 +0,0 @@
#define WLR_USE_UNSTABLE
#include <wlr/backend.h>
#include <wlr/render/wlr_renderer.h>
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);
}

View file

@ -1,14 +0,0 @@
#ifndef RIVER_BINDINGS_H
#define RIVER_BINDINGS_H
#include <wlr/backend/session.h>
/*
* 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

View file

@ -17,14 +17,14 @@
const Self = @This(); const Self = @This();
const c = @import("c.zig"); const wlr = @import("wlroots");
x: i32, x: i32,
y: i32, y: i32,
width: u32, width: u32,
height: u32, height: u32,
pub fn fromWlrBox(wlr_box: c.wlr_box) Self { pub fn fromWlrBox(wlr_box: wlr.Box) Self {
return Self{ return Self{
.x = @intCast(i32, wlr_box.x), .x = @intCast(i32, wlr_box.x),
.y = @intCast(i32, wlr_box.y), .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 { pub fn toWlrBox(self: Self) wlr.Box {
return c.wlr_box{ return wlr.Box{
.x = @intCast(c_int, self.x), .x = @intCast(c_int, self.x),
.y = @intCast(c_int, self.y), .y = @intCast(c_int, self.y),
.width = @intCast(c_int, self.width), .width = @intCast(c_int, self.width),

View file

@ -18,6 +18,13 @@
const Self = @This(); const Self = @This();
const std = @import("std"); 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 c = @import("c.zig");
const command = @import("command.zig"); const command = @import("command.zig");
@ -26,141 +33,106 @@ const util = @import("util.zig");
const Seat = @import("Seat.zig"); const Seat = @import("Seat.zig");
const Server = @import("Server.zig"); const Server = @import("Server.zig");
const protocol_version = 1; global: *wl.Global,
const implementation = c.struct_zriver_control_v1_interface{
.destroy = destroy,
.add_argument = addArgument,
.run_command = runCommand,
};
wl_global: *c.wl_global,
args_map: std.AutoHashMap(u32, std.ArrayList([]const u8)), 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 { pub fn init(self: *Self, server: *Server) !void {
self.* = .{ self.* = .{
.wl_global = c.wl_global_create( .global = try wl.Global.create(server.wl_server, zriver.ControlV1, 1, *Self, self, bind),
server.wl_display,
&c.zriver_control_v1_interface,
protocol_version,
self,
bind,
) orelse return error.OutOfMemory,
.args_map = std.AutoHashMap(u32, std.ArrayList([]const u8)).init(util.gpa), .args_map = std.AutoHashMap(u32, std.ArrayList([]const u8)).init(util.gpa),
}; };
self.listen_display_destroy.notify = handleDisplayDestroy; self.server_destroy.setNotify(handleServerDestroy);
c.wl_display_add_destroy_listener(server.wl_display, &self.listen_display_destroy); server.wl_server.addDestroyListener(&self.server_destroy);
} }
fn handleDisplayDestroy(wl_listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleServerDestroy(listener: *wl.Listener(*wl.Server), wl_server: *wl.Server) void {
const self = @fieldParentPtr(Self, "listen_display_destroy", wl_listener.?); const self = @fieldParentPtr(Self, "server_destroy", listener);
c.wl_global_destroy(self.wl_global); self.global.destroy();
self.args_map.deinit(); self.args_map.deinit();
} }
/// Called when a client binds our global /// Called when a client binds our global
fn bind(wl_client: ?*c.wl_client, data: ?*c_void, version: u32, id: u32) callconv(.C) void { fn bind(client: *wl.Client, self: *Self, version: u32, id: u32) callconv(.C) void {
const self = util.voidCast(Self, data.?); const control = zriver.ControlV1.create(client, version, id) catch {
const wl_resource = c.wl_resource_create( client.postNoMemory();
wl_client,
&c.zriver_control_v1_interface,
@intCast(c_int, version),
id,
) orelse {
c.wl_client_post_no_memory(wl_client);
return; return;
}; };
self.args_map.putNoClobber(id, std.ArrayList([]const u8).init(util.gpa)) catch { self.args_map.putNoClobber(id, std.ArrayList([]const u8).init(util.gpa)) catch {
c.wl_resource_destroy(wl_resource); control.destroy();
c.wl_client_post_no_memory(wl_client); client.postNoMemory();
return; return;
}; };
c.wl_resource_set_implementation(wl_resource, &implementation, self, handleResourceDestroy); control.setHandler(*Self, handleRequest, handleDestroy, self);
} }
/// Remove the resource from the hash map and free all stored args fn handleRequest(control: *zriver.ControlV1, request: zriver.ControlV1.Request, self: *Self) void {
fn handleResourceDestroy(wl_resource: ?*c.wl_resource) callconv(.C) void { switch (request) {
const self = util.voidCast(Self, c.wl_resource_get_user_data(wl_resource).?); .destroy => control.destroy(),
const id = c.wl_resource_get_id(wl_resource); .add_argument => |add_argument| {
const list = self.args_map.remove(id).?.value; const owned_slice = mem.dupe(util.gpa, u8, mem.span(add_argument.argument)) catch {
for (list.items) |arg| list.allocator.free(arg); control.getClient().postNoMemory();
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; return;
}; };
self.args_map.getEntry(id).?.value.append(owned_slice) catch { self.args_map.getEntry(control.getId()).?.value.append(owned_slice) catch {
c.wl_client_post_no_memory(wl_client); control.getClient().postNoMemory();
util.gpa.free(owned_slice); util.gpa.free(owned_slice);
return; return;
}; };
} },
.run_command => |run_command| {
const seat = @intToPtr(*Seat, wlr.Seat.Client.fromWlSeat(run_command.seat).?.seat.data);
fn runCommand( const callback = zriver.CommandCallbackV1.create(
wl_client: ?*c.wl_client, control.getClient(),
wl_resource: ?*c.wl_resource, control.getVersion(),
seat_wl_resource: ?*c.wl_resource, run_command.callback,
callback_id: u32, ) catch {
) callconv(.C) void { control.getClient().postNoMemory();
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; 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; const args = self.args_map.get(control.getId()).?.items;
var out: ?[]const u8 = null; var out: ?[]const u8 = null;
defer if (out) |s| util.gpa.free(s); defer if (out) |s| util.gpa.free(s);
command.run(util.gpa, seat, args, &out) catch |err| { command.run(util.gpa, seat, args, &out) catch |err| {
const failure_message = switch (err) { const failure_message = switch (err) {
command.Error.OutOfMemory => { command.Error.OutOfMemory => {
c.wl_client_post_no_memory(wl_client); callback.getClient().postNoMemory();
return; return;
}, },
command.Error.Other => std.cstr.addNullByte(util.gpa, out.?) catch { command.Error.Other => std.cstr.addNullByte(util.gpa, out.?) catch {
c.wl_client_post_no_memory(wl_client); callback.getClient().postNoMemory();
return; return;
}, },
else => command.errToMsg(err), else => command.errToMsg(err),
}; };
defer if (err == command.Error.Other) util.gpa.free(failure_message); defer if (err == command.Error.Other) util.gpa.free(failure_message);
c.zriver_command_callback_v1_send_failure(callback_resource, failure_message); callback.sendFailure(failure_message);
return; return;
}; };
const success_message = if (out) |s| const success_message = if (out) |s|
std.cstr.addNullByte(util.gpa, s) catch { std.cstr.addNullByte(util.gpa, s) catch {
c.wl_client_post_no_memory(wl_client); callback.getClient().postNoMemory();
return; return;
} }
else else
""; "";
defer if (out != null) util.gpa.free(success_message); defer if (out != null) util.gpa.free(success_message);
c.zriver_command_callback_v1_send_success(callback_resource, success_message); callback.sendSuccess(success_message);
},
}
}
/// Remove the resource from the hash map and free all stored args
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();
} }

View file

@ -19,6 +19,10 @@ const Self = @This();
const build_options = @import("build_options"); const build_options = @import("build_options");
const std = @import("std"); 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 c = @import("c.zig");
const log = @import("log.zig"); const log = @import("log.zig");
@ -50,34 +54,34 @@ const default_size = 24;
mode: Mode = .passthrough, mode: Mode = .passthrough,
seat: *Seat, seat: *Seat,
wlr_cursor: *c.wlr_cursor, wlr_cursor: *wlr.Cursor,
wlr_xcursor_manager: *c.wlr_xcursor_manager, xcursor_manager: *wlr.XcursorManager,
/// Number of distinct buttons currently pressed /// Number of distinct buttons currently pressed
pressed_count: u32 = 0, pressed_count: u32 = 0,
listen_axis: c.wl_listener = undefined, axis: wl.Listener(*wlr.Pointer.event.Axis) = undefined,
listen_button: c.wl_listener = undefined, button: wl.Listener(*wlr.Pointer.event.Button) = undefined,
listen_frame: c.wl_listener = undefined, frame: wl.Listener(*wlr.Cursor) = undefined,
listen_motion_absolute: c.wl_listener = undefined, motion_absolute: wl.Listener(*wlr.Pointer.event.MotionAbsolute) = undefined,
listen_motion: c.wl_listener = undefined, motion: wl.Listener(*wlr.Pointer.event.Motion) = undefined,
listen_request_set_cursor: c.wl_listener = undefined, request_set_cursor: wl.Listener(*wlr.Seat.event.RequestSetCursor) = undefined,
pub fn init(self: *Self, seat: *Seat) !void { pub fn init(self: *Self, seat: *Seat) !void {
const wlr_cursor = c.wlr_cursor_create() orelse return error.OutOfMemory; const wlr_cursor = try wlr.Cursor.create();
errdefer c.wlr_cursor_destroy(wlr_cursor); errdefer wlr_cursor.destroy();
c.wlr_cursor_attach_output_layout(wlr_cursor, seat.input_manager.server.root.wlr_output_layout); 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, // optional pointer. This isn't optimal as it does a needless allocation,
// but this is not a hot path. // but this is not a hot path.
const wlr_xcursor_manager = c.wlr_xcursor_manager_create(null, default_size) orelse return error.OutOfMemory; const xcursor_manager = try wlr.XcursorManager.create(null, default_size);
errdefer c.wlr_xcursor_manager_destroy(wlr_xcursor_manager); errdefer xcursor_manager.destroy();
self.* = .{ self.* = .{
.seat = seat, .seat = seat,
.wlr_cursor = wlr_cursor, .wlr_cursor = wlr_cursor,
.wlr_xcursor_manager = wlr_xcursor_manager, .xcursor_manager = xcursor_manager,
}; };
try self.setTheme(null, null); 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 // can choose how we want to process them, forwarding them to clients and
// moving the cursor around. See following post for more detail: // moving the cursor around. See following post for more detail:
// https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html // https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html
self.listen_axis.notify = handleAxis; self.axis.setNotify(handleAxis);
c.wl_signal_add(&self.wlr_cursor.events.axis, &self.listen_axis); self.wlr_cursor.events.axis.add(&self.axis);
self.listen_button.notify = handleButton; self.button.setNotify(handleButton);
c.wl_signal_add(&self.wlr_cursor.events.button, &self.listen_button); self.wlr_cursor.events.button.add(&self.button);
self.listen_frame.notify = handleFrame; self.frame.setNotify(handleFrame);
c.wl_signal_add(&self.wlr_cursor.events.frame, &self.listen_frame); self.wlr_cursor.events.frame.add(&self.frame);
self.listen_motion_absolute.notify = handleMotionAbsolute; self.motion_absolute.setNotify(handleMotionAbsolute);
c.wl_signal_add(&self.wlr_cursor.events.motion_absolute, &self.listen_motion_absolute); self.wlr_cursor.events.motion_absolute.add(&self.motion_absolute);
self.listen_motion.notify = handleMotion; self.motion.setNotify(handleMotion);
c.wl_signal_add(&self.wlr_cursor.events.motion, &self.listen_motion); self.wlr_cursor.events.motion.add(&self.motion);
self.listen_request_set_cursor.notify = handleRequestSetCursor; self.request_set_cursor.setNotify(handleRequestSetCursor);
c.wl_signal_add(&self.seat.wlr_seat.events.request_set_cursor, &self.listen_request_set_cursor); self.seat.wlr_seat.events.request_set_cursor.add(&self.request_set_cursor);
} }
pub fn deinit(self: *Self) void { pub fn deinit(self: *Self) void {
c.wlr_xcursor_manager_destroy(self.wlr_xcursor_manager); self.xcursor_manager.destroy();
c.wlr_cursor_destroy(self.wlr_cursor); self.wlr_cursor.destroy();
} }
/// Set the cursor theme for the given seat, as well as the xwayland theme if /// 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 server = self.seat.input_manager.server;
const size = _size orelse default_size; const size = _size orelse default_size;
c.wlr_xcursor_manager_destroy(self.wlr_xcursor_manager); self.xcursor_manager.destroy();
self.wlr_xcursor_manager = c.wlr_xcursor_manager_create(theme, size) orelse self.xcursor_manager = try wlr.XcursorManager.create(theme, size);
return error.OutOfMemory;
// For each output, ensure a theme of the proper scale is loaded // For each output, ensure a theme of the proper scale is loaded
var it = server.root.outputs.first; var it = server.root.outputs.first;
while (it) |node| : (it = node.next) { while (it) |node| : (it = node.next) {
const wlr_output = node.data.wlr_output; 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 }); log.err(.cursor, "failed to load xcursor theme '{}' at scale {}", .{ theme, wlr_output.scale });
} }
@ -139,11 +142,13 @@ 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 (theme) |t| if (c.setenv("XCURSOR_THEME", t, 1) < 0) return error.OutOfMemory;
if (build_options.xwayland) { if (build_options.xwayland) {
if (c.wlr_xcursor_manager_load(self.wlr_xcursor_manager, 1)) { self.xcursor_manager.load(1) catch {
const wlr_xcursor = c.wlr_xcursor_manager_get_xcursor(self.wlr_xcursor_manager, "left_ptr", 1).?; log.err(.cursor, "failed to load xcursor theme '{}' at scale 1", .{theme});
const image: *c.wlr_xcursor_image = wlr_xcursor.*.images[0]; return;
c.wlr_xwayland_set_cursor( };
server.wlr_xwayland, const wlr_xcursor = self.xcursor_manager.getXcursor("left_ptr", 1).?;
const image = wlr_xcursor.images[0];
server.xwayland.setCursor(
image.buffer, image.buffer,
image.width * 4, image.width * 4,
image.width, image.width,
@ -151,7 +156,6 @@ pub fn setTheme(self: *Self, theme: ?[*:0]const u8, _size: ?u32) !void {
@intCast(i32, image.hotspot_x), @intCast(i32, image.hotspot_x),
@intCast(i32, image.hotspot_y), @intCast(i32, image.hotspot_y),
); );
} else log.err(.cursor, "failed to load xcursor theme '{}' at scale 1", .{theme});
} }
} }
} }
@ -173,25 +177,18 @@ pub fn handleViewUnmap(self: *Self, view: *View) void {
} }
fn clearFocus(self: Self) void { fn clearFocus(self: Self) void {
c.wlr_xcursor_manager_set_cursor_image( self.xcursor_manager.setCursorImage("left_ptr", self.wlr_cursor);
self.wlr_xcursor_manager, self.seat.wlr_seat.pointerNotifyClearFocus();
"left_ptr",
self.wlr_cursor,
);
c.wlr_seat_pointer_clear_focus(self.seat.wlr_seat);
} }
fn handleAxis(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { /// Axis event is a scroll wheel or similiar
// This event is forwarded by the cursor when a pointer emits an axis event, fn handleAxis(listener: *wl.Listener(*wlr.Pointer.event.Axis), event: *wlr.Pointer.event.Axis) void {
// for example when you move the scroll wheel. const self = @fieldParentPtr(Self, "axis", listener);
const self = @fieldParentPtr(Self, "listen_axis", listener.?);
const event = util.voidCast(c.wlr_event_pointer_axis, data.?);
self.seat.handleActivity(); self.seat.handleActivity();
// Notify the client with pointer focus of the axis event. // Notify the client with pointer focus of the axis event.
c.wlr_seat_pointer_notify_axis( self.seat.wlr_seat.pointerNotifyAxis(
self.seat.wlr_seat,
event.time_msec, event.time_msec,
event.orientation, event.orientation,
event.delta, 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 { fn handleButton(listener: *wl.Listener(*wlr.Pointer.event.Button), event: *wlr.Pointer.event.Button) void {
// This event is forwarded by the cursor when a pointer emits a button const self = @fieldParentPtr(Self, "button", listener);
// event.
const self = @fieldParentPtr(Self, "listen_button", listener.?);
const event = util.voidCast(c.wlr_event_pointer_button, data.?);
self.seat.handleActivity(); self.seat.handleActivity();
if (event.state == .WLR_BUTTON_PRESSED) { if (event.state == .pressed) {
self.pressed_count += 1; self.pressed_count += 1;
} else { } else {
std.debug.assert(self.pressed_count > 0); 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 sx: f64 = undefined;
var sy: 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, // If the found surface is a keyboard inteactive layer surface,
// give it keyboard focus. // give it keyboard focus.
if (c.wlr_surface_is_layer_surface(wlr_surface)) { if (surface.isLayerSurface()) {
const wlr_layer_surface = c.wlr_layer_surface_v1_from_wlr_surface(wlr_surface); const wlr_layer_surface = wlr.LayerSurfaceV1.fromWlrSurface(surface);
if (wlr_layer_surface.*.current.keyboard_interactive) { if (wlr_layer_surface.current.keyboard_interactive) {
const layer_surface = util.voidCast(LayerSurface, wlr_layer_surface.*.data.?); const layer_surface = @intToPtr(*LayerSurface, wlr_layer_surface.data);
self.seat.setFocusRaw(.{ .layer = layer_surface }); self.seat.setFocusRaw(.{ .layer = layer_surface });
} }
} }
// If the target surface has a view, give that view keyboard focus and // If the target surface has a view, give that view keyboard focus and
// perhaps enter move/resize mode. // perhaps enter move/resize mode.
if (View.fromWlrSurface(wlr_surface)) |view| { if (View.fromWlrSurface(surface)) |view| {
if (event.state == .WLR_BUTTON_PRESSED and self.pressed_count == 1) { if (event.state == .pressed and self.pressed_count == 1) {
// If there is an active mapping for this button which is // If there is an active mapping for this button which is
// handled we are done here // handled we are done here
if (self.handlePointerMapping(event, view)) return; 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.pointerNotifyButton(event.time_msec, event.button, event.state);
self.seat.wlr_seat,
event.time_msec,
event.button,
event.state,
);
} }
} }
/// Handle the mapping for the passed button if any. Returns true if there /// Handle the mapping for the passed button if any. Returns true if there
/// was a mapping and the button was handled. /// was a mapping and the button was handled.
fn handlePointerMapping(self: *Self, event: *c.wlr_event_pointer_button, view: *View) bool { fn handlePointerMapping(self: *Self, event: *wlr.Pointer.event.Button, view: *View) bool {
const wlr_keyboard = c.wlr_seat_get_keyboard(self.seat.wlr_seat); const wlr_keyboard = self.seat.wlr_seat.getKeyboard() orelse return false;
const modifiers = c.wlr_keyboard_get_modifiers(wlr_keyboard); const modifiers = wlr_keyboard.getModifiers();
const fullscreen = view.current.fullscreen or view.pending.fullscreen; const fullscreen = view.current.fullscreen or view.pending.fullscreen;
const config = self.seat.input_manager.server.config; const config = self.seat.input_manager.server.config;
return for (config.modes.items[self.seat.mode_id].pointer_mappings.items) |mapping| { 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) { switch (mapping.action) {
.move => if (!fullscreen) self.enterMode(.move, view), .move => if (!fullscreen) self.enterMode(.move, view),
.resize => if (!fullscreen) self.enterMode(.resize, 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; } else false;
} }
fn handleFrame(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { /// Frame events are sent after regular pointer events to group multiple
// This event is forwarded by the cursor when a pointer emits an frame /// events together. For instance, two axis events may happen at the same
// event. Frame events are sent after regular pointer events to group /// time, in which case a frame event won't be sent in between.
// multiple events together. For instance, two axis events may happen at the fn handleFrame(listener: *wl.Listener(*wlr.Cursor), wlr_cursor: *wlr.Cursor) void {
// same time, in which case a frame event won't be sent in between. const self = @fieldParentPtr(Self, "frame", listener);
const self = @fieldParentPtr(Self, "listen_frame", listener.?); self.seat.wlr_seat.pointerNotifyFrame();
// Notify the client with pointer focus of the frame event.
c.wlr_seat_pointer_notify_frame(self.seat.wlr_seat);
} }
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_
// 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
// 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
// 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,
// 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
// so we have to warp the mouse there. There is also some hardware which /// emits these events.
// emits these events. fn handleMotionAbsolute(
const self = @fieldParentPtr(Self, "listen_motion_absolute", listener.?); listener: *wl.Listener(*wlr.Pointer.event.MotionAbsolute),
const event = util.voidCast(c.wlr_event_pointer_motion_absolute, data.?); event: *wlr.Pointer.event.MotionAbsolute,
) void {
const self = @fieldParentPtr(Self, "motion_absolute", listener);
self.seat.handleActivity(); self.seat.handleActivity();
var lx: f64 = undefined; var lx: f64 = undefined;
var ly: 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); 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_
// This event is forwarded by the cursor when a pointer emits a _relative_ /// pointer motion event (i.e. a delta)
// pointer motion event (i.e. a delta) fn handleMotion(
const self = @fieldParentPtr(Self, "listen_motion", listener.?); listener: *wl.Listener(*wlr.Pointer.event.Motion),
const event = util.voidCast(c.wlr_event_pointer_motion, data.?); event: *wlr.Pointer.event.Motion,
) void {
const self = @fieldParentPtr(Self, "motion", listener);
self.seat.handleActivity(); self.seat.handleActivity();
self.processMotion(event.device, event.time_msec, event.delta_x, event.delta_y); 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 // This event is rasied by the seat when a client provides a cursor image
const self = @fieldParentPtr(Self, "listen_request_set_cursor", listener.?); const self = @fieldParentPtr(Self, "request_set_cursor", listener);
const event = util.voidCast(c.wlr_seat_pointer_request_set_cursor_event, data.?);
const focused_client = self.seat.wlr_seat.pointer_state.focused_client; 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 // 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 // on the output that it's currently on and continue to do so as the
// cursor moves between outputs. // cursor moves between outputs.
log.debug(.cursor, "focused client set cursor", .{}); log.debug(.cursor, "focused client set cursor", .{});
c.wlr_cursor_set_surface( self.wlr_cursor.setSurface(event.surface, event.hotspot_x, event.hotspot_y);
self.wlr_cursor,
event.surface,
event.hotspot_x,
event.hotspot_y,
);
} }
} }
/// Find the topmost surface under the output layout coordinates lx/ly /// Find the topmost surface under the output layout coordinates lx/ly
/// returns the surface if found and sets the sx/sy parametes to the /// returns the surface if found and sets the sx/sy parametes to the
/// surface coordinates. /// 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 // Find the output to check
const root = self.seat.input_manager.server.root; 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 wlr_output = root.output_layout.outputAt(lx, ly) orelse return null;
const output = util.voidCast(Output, wlr_output.*.data orelse return null); const output = @intToPtr(*Output, wlr_output.data);
// Get output-local coords from the layout coords // Get output-local coords from the layout coords
var ox = lx; var ox = lx;
var oy = ly; var oy = ly;
c.wlr_output_layout_output_coords(root.wlr_output_layout, wlr_output, &ox, &oy); root.output_layout.outputCoords(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,
};
// Check overlay layer incl. popups // 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 // Check top-background popups only
for (layer_idxs[1..4]) |idx| for ([_]zwlr.LayerShellV1.Layer{ .top, .bottom, .background }) |layer|
if (layerSurfaceAt(output.*, output.layers[idx], ox, oy, sx, sy, true)) |s| return s; if (layerSurfaceAt(output.*, output.getLayer(layer).*, ox, oy, sx, sy, true)) |s| return s;
// Check top layer // 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 // Check views
if (viewSurfaceAt(output.*, ox, oy, sx, sy)) |s| return s; if (viewSurfaceAt(output.*, ox, oy, sx, sy)) |s| return s;
// Check the bottom-background layers // Check the bottom-background layers
for (layer_idxs[2..4]) |idx| for ([_]zwlr.LayerShellV1.Layer{ .bottom, .background }) |layer|
if (layerSurfaceAt(output.*, output.layers[idx], ox, oy, sx, sy, false)) |s| return s; if (layerSurfaceAt(output.*, output.getLayer(layer).*, ox, oy, sx, sy, false)) |s| return s;
return null; return null;
} }
@ -388,33 +368,28 @@ fn layerSurfaceAt(
sx: *f64, sx: *f64,
sy: *f64, sy: *f64,
popups_only: bool, popups_only: bool,
) ?*c.wlr_surface { ) ?*wlr.Surface {
var it = layer.first; var it = layer.first;
while (it) |node| : (it = node.next) { while (it) |node| : (it = node.next) {
const layer_surface = &node.data; const layer_surface = &node.data;
const surface = c.wlr_layer_surface_v1_surface_at( if (layer_surface.wlr_layer_surface.surfaceAt(
layer_surface.wlr_layer_surface,
ox - @intToFloat(f64, layer_surface.box.x), ox - @intToFloat(f64, layer_surface.box.x),
oy - @intToFloat(f64, layer_surface.box.y), oy - @intToFloat(f64, layer_surface.box.y),
sx, sx,
sy, sy,
); )) |found| {
if (surface) |found| {
if (!popups_only) { if (!popups_only) {
return found; return found;
} else if (c.wlr_surface_is_xdg_surface(found)) { } else if (found.isXdgSurface() and wlr.XdgSurface.fromWlrSurface(found).role == .popup) {
const wlr_xdg_surface = c.wlr_xdg_surface_from_wlr_surface(found);
if (wlr_xdg_surface.*.role == .WLR_XDG_SURFACE_ROLE_POPUP) {
return found; return found;
} }
} }
} }
}
return null; return null;
} }
/// Find the topmost visible view surface (incl. popups) at ox,oy. /// 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. // Focused views are rendered on top, so look for them first.
var it = ViewStack(View).iter(output.views.first, .forward, output.current.tags, surfaceAtFilter); var it = ViewStack(View).iter(output.views.first, .forward, output.current.tags, surfaceAtFilter);
while (it.next()) |view| { 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 // 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.xcursor_manager.setCursorImage(
self.wlr_xcursor_manager,
if (mode == .move) "move" else "se-resize", if (mode == .move) "move" else "se-resize",
self.wlr_cursor, 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 /// 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); std.debug.assert(self.mode != .passthrough);
log.debug(.cursor, "leave {} mode", .{@tagName(self.mode)}); log.debug(.cursor, "leave {} mode", .{@tagName(self.mode)});
// If we were in down mode, we need pass along the release event // If we were in down mode, we need pass along the release event
if (self.mode == .down) if (self.mode == .down)
_ = c.wlr_seat_pointer_notify_button( _ = self.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state);
self.seat.wlr_seat,
event.time_msec,
event.button,
event.state,
);
self.mode = .passthrough; self.mode = .passthrough;
self.passthrough(event.time_msec); 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; const config = self.seat.input_manager.server.config;
switch (self.mode) { switch (self.mode) {
.passthrough => { .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); self.passthrough(time);
}, },
.down => |view| { .down => |view| {
c.wlr_cursor_move(self.wlr_cursor, device, delta_x, delta_y); self.wlr_cursor.move(device, delta_x, delta_y);
c.wlr_seat_pointer_notify_motion( self.seat.wlr_seat.pointerNotifyMotion(
self.seat.wlr_seat,
time, time,
self.wlr_cursor.x - @intToFloat(f64, view.current.box.x), self.wlr_cursor.x - @intToFloat(f64, view.current.box.x),
self.wlr_cursor.y - @intToFloat(f64, view.current.box.y), 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), @intCast(i32, output_resolution.height - view.pending.box.height - border_width),
); );
c.wlr_cursor_move( self.wlr_cursor.move(
self.wlr_cursor,
device, device,
@intToFloat(f64, view.pending.box.x - view.current.box.x), @intToFloat(f64, view.pending.box.x - view.current.box.x),
@intToFloat(f64, view.pending.box.y - view.current.box.y), @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(); data.view.applyPending();
// Keep cursor locked to the original offset from the bottom right corner // Keep cursor locked to the original offset from the bottom right corner
c.wlr_cursor_warp_closest( self.wlr_cursor.warpClosest(
self.wlr_cursor,
device, device,
@intToFloat(f64, box.x + @intCast(i32, box.width) - data.offset_x), @intToFloat(f64, box.x + @intCast(i32, box.width) - data.offset_x),
@intToFloat(f64, box.y + @intCast(i32, box.height) - data.offset_y), @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 sx: f64 = undefined;
var sy: 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 // If input is allowed on the surface, send pointer enter and motion
// events. Note that wlroots won't actually send an enter event if // events. Note that wlroots won't actually send an enter event if
// the surface has already been entered. // 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 // 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); self.seat.wlr_seat.pointerNotifyEnter(surface, sx, sy);
c.wlr_seat_pointer_notify_motion(self.seat.wlr_seat, time, sx, sy); self.seat.wlr_seat.pointerNotifyMotion(time, sx, sy);
const follow_mode = config.focus_follows_cursor; const follow_mode = config.focus_follows_cursor;
if (follow_mode == .strict or (follow_mode == .normal and focus_change)) { 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.focus(view);
self.seat.focusOutput(view.output); self.seat.focusOutput(view.output);
root.startTransaction(); root.startTransaction();

View file

@ -18,53 +18,56 @@
const Self = @This(); const Self = @This();
const std = @import("std"); 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 util = @import("util.zig");
const Server = @import("Server.zig"); const Server = @import("Server.zig");
server: *Server, server: *Server,
wlr_xdg_toplevel_decoration: *c.wlr_xdg_toplevel_decoration_v1, xdg_toplevel_decoration: *wlr.XdgToplevelDecorationV1,
listen_destroy: c.wl_listener = undefined, destroy: wl.Listener(*wlr.XdgToplevelDecorationV1) = undefined,
listen_request_mode: c.wl_listener = undefined, request_mode: wl.Listener(*wlr.XdgToplevelDecorationV1) = undefined,
pub fn init( pub fn init(
self: *Self, self: *Self,
server: *Server, server: *Server,
wlr_xdg_toplevel_decoration: *c.wlr_xdg_toplevel_decoration_v1, xdg_toplevel_decoration: *wlr.XdgToplevelDecorationV1,
) void { ) 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; self.destroy.setNotify(handleDestroy);
c.wl_signal_add(&self.wlr_xdg_toplevel_decoration.events.destroy, &self.listen_destroy); self.xdg_toplevel_decoration.events.destroy.add(&self.destroy);
self.listen_request_mode.notify = handleRequestMode; self.request_mode.setNotify(handleRequestMode);
c.wl_signal_add(&self.wlr_xdg_toplevel_decoration.events.request_mode, &self.listen_request_mode); 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 { fn handleDestroy(
const self = @fieldParentPtr(Self, "listen_destroy", listener.?); listener: *wl.Listener(*wlr.XdgToplevelDecorationV1),
xdg_toplevel_decoration: *wlr.XdgToplevelDecorationV1,
) void {
const self = @fieldParentPtr(Self, "destroy", listener);
util.gpa.destroy(self); util.gpa.destroy(self);
} }
fn handleRequestMode(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleRequestMode(
const self = @fieldParentPtr(Self, "listen_request_mode", listener.?); 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 toplevel = self.xdg_toplevel_decoration.surface.role_data.toplevel;
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 (toplevel.app_id) |id| id else "NULL";
const app_id: [*:0]const u8 = if (wlr_xdg_toplevel.app_id) |id| id else "NULL";
_ = c.wlr_xdg_toplevel_decoration_v1_set_mode( _ = self.xdg_toplevel_decoration.setMode(
self.wlr_xdg_toplevel_decoration,
for (self.server.config.csd_filter.items) |filter_app_id| { for (self.server.config.csd_filter.items) |filter_app_id| {
if (std.mem.eql(u8, std.mem.span(app_id), filter_app_id)) { if (std.mem.eql(u8, std.mem.span(app_id), filter_app_id)) break .client_side;
break .WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; } else .server_side,
}
} else .WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE,
); );
} }

View file

@ -18,8 +18,9 @@
const Self = @This(); const Self = @This();
const std = @import("std"); 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 util = @import("util.zig");
const Decoration = @import("Decoration.zig"); const Decoration = @import("Decoration.zig");
@ -27,31 +28,28 @@ const Server = @import("Server.zig");
server: *Server, 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 { pub fn init(self: *Self, server: *Server) !void {
self.* = .{ self.* = .{
.server = server, .server = server,
.wlr_xdg_decoration_manager = c.wlr_xdg_decoration_manager_v1_create(server.wl_display) orelse .xdg_decoration_manager = try wlr.XdgDecorationManagerV1.create(server.wl_server),
return error.OutOfMemory,
}; };
self.listen_new_toplevel_decoration.notify = handleNewToplevelDecoration; self.new_toplevel_decoration.setNotify(handleNewToplevelDecoration);
c.wl_signal_add( self.xdg_decoration_manager.events.new_toplevel_decoration.add(&self.new_toplevel_decoration);
&self.wlr_xdg_decoration_manager.events.new_toplevel_decoration,
&self.listen_new_toplevel_decoration,
);
} }
fn handleNewToplevelDecoration(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleNewToplevelDecoration(
const self = @fieldParentPtr(Self, "listen_new_toplevel_decoration", listener.?); listener: *wl.Listener(*wlr.XdgToplevelDecorationV1),
const wlr_xdg_toplevel_decoration = util.voidCast(c.wlr_xdg_toplevel_decoration_v1, data.?); xdg_toplevel_decoration: *wlr.XdgToplevelDecorationV1,
) void {
const self = @fieldParentPtr(Self, "new_toplevel_decoration", listener);
const decoration = util.gpa.create(Decoration) catch { const decoration = util.gpa.create(Decoration) catch {
c.wl_resource_post_no_memory(wlr_xdg_toplevel_decoration.resource); xdg_toplevel_decoration.resource.postNoMemory();
return; return;
}; };
decoration.init(self.server, wlr_xdg_toplevel_decoration); decoration.init(self.server, xdg_toplevel_decoration);
} }

View file

@ -18,29 +18,27 @@
const Self = @This(); const Self = @This();
const std = @import("std"); 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 util = @import("util.zig");
const Seat = @import("Seat.zig"); const Seat = @import("Seat.zig");
seat: *Seat, 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 { pub fn init(self: *Self, seat: *Seat, wlr_drag_icon: *wlr.Drag.Icon) void {
self.* = .{ self.* = .{ .seat = seat, .wlr_drag_icon = wlr_drag_icon };
.seat = seat,
.wlr_drag_icon = wlr_drag_icon,
};
self.listen_destroy.notify = handleDestroy; self.destroy.setNotify(handleDestroy);
c.wl_signal_add(&wlr_drag_icon.events.destroy, &self.listen_destroy); wlr_drag_icon.events.destroy.add(&self.destroy);
} }
fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleDestroy(listener: *wl.Listener(*wlr.Drag.Icon), wlr_drag_icon: *wlr.Drag.Icon) void {
const self = @fieldParentPtr(Self, "listen_destroy", listener.?); const self = @fieldParentPtr(Self, "destroy", listener);
const root = &self.seat.input_manager.server.root; const root = &self.seat.input_manager.server.root;
const node = @fieldParentPtr(std.SinglyLinkedList(Self).Node, "data", self); const node = @fieldParentPtr(std.SinglyLinkedList(Self).Node, "data", self);
root.drag_icons.remove(node); root.drag_icons.remove(node);

View file

@ -19,8 +19,9 @@ const Self = @This();
const build_options = @import("build_options"); const build_options = @import("build_options");
const std = @import("std"); 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 log = @import("log.zig");
const util = @import("util.zig"); const util = @import("util.zig");
@ -32,56 +33,53 @@ const default_seat_name = "default";
server: *Server, server: *Server,
wlr_idle: *c.wlr_idle, idle: *wlr.Idle,
wlr_input_inhibit_manager: *c.wlr_input_inhibit_manager, input_inhibit_manager: *wlr.InputInhibitManager,
wlr_virtual_pointer_manager: *c.wlr_virtual_pointer_manager_v1, virtual_pointer_manager: *wlr.VirtualPointerManagerV1,
wlr_virtual_keyboard_manager: *c.wlr_virtual_keyboard_manager_v1, virtual_keyboard_manager: *wlr.VirtualKeyboardManagerV1,
seats: std.TailQueue(Seat) = .{}, seats: std.TailQueue(Seat) = .{},
exclusive_client: ?*c.wl_client = null, exclusive_client: ?*wl.Client = null,
listen_inhibit_activate: c.wl_listener = undefined, inhibit_activate: wl.Listener(*wlr.InputInhibitManager) = undefined,
listen_inhibit_deactivate: c.wl_listener = undefined, inhibit_deactivate: wl.Listener(*wlr.InputInhibitManager) = undefined,
listen_new_input: c.wl_listener = undefined, new_input: wl.Listener(*wlr.InputDevice) = undefined,
listen_new_virtual_pointer: c.wl_listener = undefined, new_virtual_pointer: wl.Listener(*wlr.VirtualPointerManagerV1.event.NewPointer) = undefined,
listen_new_virtual_keyboard: c.wl_listener = undefined, new_virtual_keyboard: wl.Listener(*wlr.VirtualKeyboardV1) = undefined,
pub fn init(self: *Self, server: *Server) !void { pub fn init(self: *Self, server: *Server) !void {
const seat_node = try util.gpa.create(std.TailQueue(Seat).Node); const seat_node = try util.gpa.create(std.TailQueue(Seat).Node);
errdefer util.gpa.destroy(seat_node);
self.* = .{ self.* = .{
.server = server, .server = server,
// These are automatically freed when the display is destroyed // These are automatically freed when the display is destroyed
.wlr_idle = c.wlr_idle_create(server.wl_display) orelse return error.OutOfMemory, .idle = try wlr.Idle.create(server.wl_server),
.wlr_input_inhibit_manager = c.wlr_input_inhibit_manager_create(server.wl_display) orelse .input_inhibit_manager = try wlr.InputInhibitManager.create(server.wl_server),
return error.OutOfMemory, .virtual_pointer_manager = try wlr.VirtualPointerManagerV1.create(server.wl_server),
.wlr_virtual_pointer_manager = c.wlr_virtual_pointer_manager_v1_create(server.wl_display) orelse .virtual_keyboard_manager = try wlr.VirtualKeyboardManagerV1.create(server.wl_server),
return error.OutOfMemory,
.wlr_virtual_keyboard_manager = c.wlr_virtual_keyboard_manager_v1_create(server.wl_display) orelse
return error.OutOfMemory,
}; };
self.seats.prepend(seat_node); self.seats.prepend(seat_node);
try seat_node.data.init(self, default_seat_name); 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.inhibit_activate.setNotify(handleInhibitActivate);
self.listen_inhibit_activate.notify = handleInhibitActivate; self.input_inhibit_manager.events.activate.add(&self.inhibit_activate);
c.wl_signal_add(&self.wlr_input_inhibit_manager.events.activate, &self.listen_inhibit_activate);
self.listen_inhibit_deactivate.notify = handleInhibitDeactivate; self.inhibit_deactivate.setNotify(handleInhibitDeactivate);
c.wl_signal_add(&self.wlr_input_inhibit_manager.events.deactivate, &self.listen_inhibit_deactivate); self.input_inhibit_manager.events.deactivate.add(&self.inhibit_deactivate);
self.listen_new_input.notify = handleNewInput; self.new_input.setNotify(handleNewInput);
c.wl_signal_add(&self.server.wlr_backend.events.new_input, &self.listen_new_input); self.server.backend.events.new_input.add(&self.new_input);
self.listen_new_virtual_pointer.notify = handleNewVirtualPointer; self.new_virtual_pointer.setNotify(handleNewVirtualPointer);
c.wl_signal_add(&self.wlr_virtual_pointer_manager.events.new_virtual_pointer, &self.listen_new_virtual_pointer); self.virtual_pointer_manager.events.new_virtual_pointer.add(&self.new_virtual_pointer);
self.listen_new_virtual_keyboard.notify = handleNewVirtualKeyboard; self.new_virtual_keyboard.setNotify(handleNewVirtualKeyboard);
c.wl_signal_add(&self.wlr_virtual_keyboard_manager.events.new_virtual_keyboard, &self.listen_new_virtual_keyboard); self.virtual_keyboard_manager.events.new_virtual_keyboard.add(&self.new_virtual_keyboard);
} }
pub fn deinit(self: *Self) void { 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. /// 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| return if (self.exclusive_client) |exclusive_client|
exclusive_client == c.wl_resource_get_client(wlr_surface.resource) exclusive_client == wlr_surface.resource.getClient()
else else
true; true;
} }
@ -119,8 +117,11 @@ pub fn isCursorActionTarget(self: Self, view: *View) bool {
} else false; } else false;
} }
fn handleInhibitActivate(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleInhibitActivate(
const self = @fieldParentPtr(Self, "listen_inhibit_activate", listener.?); 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", .{}); 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; 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 { fn handleInhibitDeactivate(
const self = @fieldParentPtr(Self, "listen_inhibit_deactivate", listener.?); 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", .{}); 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. /// 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 { fn handleNewInput(listener: *wl.Listener(*wlr.InputDevice), device: *wlr.InputDevice) void {
const self = @fieldParentPtr(Self, "listen_new_input", listener.?); const self = @fieldParentPtr(Self, "new_input", listener);
const device = util.voidCast(c.wlr_input_device, data.?);
// TODO: suport multiple seats // TODO: suport multiple seats
self.defaultSeat().addDevice(device); self.defaultSeat().addDevice(device);
} }
fn handleNewVirtualPointer(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleNewVirtualPointer(
const self = @fieldParentPtr(Self, "listen_new_virtual_pointer", listener.?); listener: *wl.Listener(*wlr.VirtualPointerManagerV1.event.NewPointer),
const event = util.voidCast(c.wlr_virtual_pointer_v1_new_pointer_event, data.?); event: *wlr.VirtualPointerManagerV1.event.NewPointer,
) void {
const self = @fieldParentPtr(Self, "new_virtual_pointer", listener);
// TODO Support multiple seats and don't ignore // TODO Support multiple seats and don't ignore
if (event.suggested_seat != null) { 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", .{}); log.debug(.input_manager, "Ignoring output suggestion from virtual pointer", .{});
} }
const new_pointer: *c.wlr_virtual_pointer_v1 = event.new_pointer; self.defaultSeat().addDevice(&event.new_pointer.input_device);
self.defaultSeat().addDevice(&new_pointer.input_device);
} }
fn handleNewVirtualKeyboard(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleNewVirtualKeyboard(
const self = @fieldParentPtr(Self, "listen_new_virtual_keyboard", listener.?); listener: *wl.Listener(*wlr.VirtualKeyboardV1),
const virtual_keyboard = util.voidCast(c.wlr_virtual_keyboard_v1, data.?); virtual_keyboard: *wlr.VirtualKeyboardV1,
const seat = util.voidCast(Seat, @as(*c.wlr_seat, virtual_keyboard.seat).data.?); ) void {
const self = @fieldParentPtr(Self, "new_virtual_keyboard", listener);
const seat = @intToPtr(*Seat, virtual_keyboard.seat.data);
seat.addDevice(&virtual_keyboard.input_device); seat.addDevice(&virtual_keyboard.input_device);
} }

View file

@ -18,122 +18,102 @@
const Self = @This(); const Self = @This();
const std = @import("std"); 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 log = @import("log.zig");
const util = @import("util.zig"); const util = @import("util.zig");
const Seat = @import("Seat.zig"); const Seat = @import("Seat.zig");
seat: *Seat, seat: *Seat,
wlr_input_device: *c.wlr_input_device, input_device: *wlr.InputDevice,
wlr_keyboard: *c.wlr_keyboard,
listen_key: c.wl_listener = undefined, key: wl.Listener(*wlr.Keyboard.event.Key) = undefined,
listen_modifiers: c.wl_listener = undefined, modifiers: wl.Listener(*wlr.Keyboard) = undefined,
listen_destroy: c.wl_listener = 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.* = .{ self.* = .{
.seat = seat, .seat = seat,
.wlr_input_device = wlr_input_device, .input_device = input_device,
.wlr_keyboard = @field(wlr_input_device, c.wlr_input_device_union).keyboard,
}; };
// We need to prepare an XKB keymap and assign it to the keyboard. This // We need to prepare an XKB keymap and assign it to the keyboard. This
// assumes the defaults (e.g. layout = "us"). // assumes the defaults (e.g. layout = "us").
const rules = c.xkb_rule_names{ const rules = xkb.RuleNames{
.rules = null, .rules = null,
.model = null, .model = null,
.layout = null, .layout = null,
.variant = null, .variant = null,
.options = null, .options = null,
}; };
const context = c.xkb_context_new(.XKB_CONTEXT_NO_FLAGS) orelse return error.XkbContextFailed; const context = xkb.Context.new(.no_flags) orelse return error.XkbContextFailed;
defer c.xkb_context_unref(context); defer context.unref();
const keymap = c.xkb_keymap_new_from_names( const keymap = xkb.Keymap.newFromNames(context, &rules, .no_flags) orelse return error.XkbKeymapFailed;
context, defer keymap.unref();
&rules,
.XKB_KEYMAP_COMPILE_NO_FLAGS,
) orelse return error.XkbKeymapFailed;
defer c.xkb_keymap_unref(keymap);
if (!c.wlr_keyboard_set_keymap(self.wlr_keyboard, keymap)) return error.SetKeymapFailed; const wlr_keyboard = self.input_device.device.keyboard;
c.wlr_keyboard_set_repeat_info(self.wlr_keyboard, 25, 600);
// Setup listeners for keyboard events if (!wlr_keyboard.setKeymap(keymap)) return error.SetKeymapFailed;
self.listen_key.notify = handleKey; wlr_keyboard.setRepeatInfo(25, 600);
c.wl_signal_add(&self.wlr_keyboard.events.key, &self.listen_key);
self.listen_modifiers.notify = handleModifiers; self.key.setNotify(handleKey);
c.wl_signal_add(&self.wlr_keyboard.events.modifiers, &self.listen_modifiers); wlr_keyboard.events.key.add(&self.key);
self.listen_destroy.notify = handleDestroy; self.modifiers.setNotify(handleModifiers);
c.wl_signal_add(&self.wlr_keyboard.events.destroy, &self.listen_destroy); wlr_keyboard.events.modifiers.add(&self.modifiers);
self.destroy.setNotify(handleDestroy);
wlr_keyboard.events.destroy.add(&self.destroy);
} }
pub fn deinit(self: *Self) void { pub fn deinit(self: *Self) void {
c.wl_list_remove(&self.listen_key.link); self.key.link.remove();
c.wl_list_remove(&self.listen_modifiers.link); self.modifiers.link.remove();
c.wl_list_remove(&self.listen_destroy.link); 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. // This event is raised when a key is pressed or released.
const self = @fieldParentPtr(Self, "listen_key", listener.?); const self = @fieldParentPtr(Self, "key", listener);
const event = util.voidCast(c.wlr_event_keyboard_key, data.?); const wlr_keyboard = self.input_device.device.keyboard;
const wlr_keyboard = self.wlr_keyboard;
self.seat.handleActivity(); self.seat.handleActivity();
// Translate libinput keycode -> xkbcommon // Translate libinput keycode -> xkbcommon
const keycode = event.keycode + 8; const keycode = event.keycode + 8;
// Get a list of keysyms as xkb reports them // TODO: These modifiers aren't properly handled, see sway's code
var translated_keysyms: ?[*]c.xkb_keysym_t = undefined; const modifiers = wlr_keyboard.getModifiers();
const translated_keysyms_len = c.xkb_state_key_get_syms( const released = event.state == .released;
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,
);
var handled = false; 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; // First check translated keysyms as xkb reports them
while (i < translated_keysyms_len) : (i += 1) { for (wlr_keyboard.xkb_state.?.keyGetSyms(keycode)) |sym| {
// Handle builtin mapping only when keys are pressed // Handle builtin mapping only when keys are pressed
if (!released and self.handleBuiltinMapping(translated_keysyms.?[i])) { if (!released and self.handleBuiltinMapping(sym)) {
handled = true; handled = true;
break; break;
} else if (self.seat.handleMapping(translated_keysyms.?[i], modifiers, released)) { } else if (self.seat.handleMapping(sym, modifiers, released)) {
handled = true; handled = true;
break; break;
} }
} }
// If not yet handled, check keysyms ignoring modifiers (e.g. 1 instead of !)
// Important for mappings like Mod+Shift+1
if (!handled) { if (!handled) {
i = 0; const layout_index = wlr_keyboard.xkb_state.?.keyGetLayout(keycode);
while (i < raw_keysyms_len) : (i += 1) { for (wlr_keyboard.keymap.?.keyGetSymsByLevel(keycode, layout_index, 0)) |sym| {
// Handle builtin mapping only when keys are pressed // Handle builtin mapping only when keys are pressed
if (!released and self.handleBuiltinMapping(raw_keysyms.?[i])) { if (!released and self.handleBuiltinMapping(sym)) {
handled = true; handled = true;
break; break;
} else if (self.seat.handleMapping(raw_keysyms.?[i], modifiers, released)) { } else if (self.seat.handleMapping(sym, modifiers, released)) {
handled = true; handled = true;
break; break;
} }
@ -143,52 +123,44 @@ fn handleKey(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
if (!handled) { if (!handled) {
// Otherwise, we pass it along to the client. // Otherwise, we pass it along to the client.
const wlr_seat = self.seat.wlr_seat; const wlr_seat = self.seat.wlr_seat;
c.wlr_seat_set_keyboard(wlr_seat, self.wlr_input_device); wlr_seat.setKeyboard(self.input_device);
c.wlr_seat_keyboard_notify_key( wlr_seat.keyboardNotifyKey(event.time_msec, event.keycode, event.state);
wlr_seat,
event.time_msec,
event.keycode,
@intCast(u32, @enumToInt(event.state)),
);
} }
} }
fn handleModifiers(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { /// Simply pass modifiers along to the client
// This event is raised when a modifier key, such as shift or alt, is fn handleModifiers(listener: *wl.Listener(*wlr.Keyboard), wlr_keyboard: *wlr.Keyboard) void {
// pressed. We simply communicate this to the client. */ const self = @fieldParentPtr(Self, "modifiers", listener);
const self = @fieldParentPtr(Self, "listen_modifiers", listener.?);
// A seat can only have one keyboard, but this is a limitation of the self.seat.wlr_seat.setKeyboard(self.input_device);
// Wayland protocol - not wlroots. We assign all connected keyboards to the self.seat.wlr_seat.keyboardNotifyModifiers(&self.input_device.device.keyboard.modifiers);
// 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);
} }
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.Keyboard), wlr_keyboard: *wlr.Keyboard) void {
self.deinit(); const self = @fieldParentPtr(Self, "destroy", listener);
const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self);
self.seat.keyboards.remove(node); self.seat.keyboards.remove(node);
self.deinit();
util.gpa.destroy(node); util.gpa.destroy(node);
} }
/// Handle any builtin, harcoded compsitor mappings such as VT switching. /// Handle any builtin, harcoded compsitor mappings such as VT switching.
/// Returns true if the keysym was handled. /// Returns true if the keysym was handled.
fn handleBuiltinMapping(self: Self, keysym: c.xkb_keysym_t) bool { fn handleBuiltinMapping(self: Self, keysym: xkb.Keysym) bool {
if (keysym >= c.XKB_KEY_XF86Switch_VT_1 and keysym <= c.XKB_KEY_XF86Switch_VT_12) { switch (@enumToInt(keysym)) {
@enumToInt(xkb.Keysym.XF86Switch_VT_1)...@enumToInt(xkb.Keysym.XF86Switch_VT_12) => {
log.debug(.keyboard, "switch VT keysym received", .{}); log.debug(.keyboard, "switch VT keysym received", .{});
const wlr_backend = self.seat.input_manager.server.wlr_backend; const backend = self.seat.input_manager.server.backend;
if (c.wlr_backend_is_multi(wlr_backend)) { if (backend.isMulti()) {
if (c.wlr_backend_get_session(wlr_backend)) |session| { if (backend.getSession()) |session| {
const vt = keysym - c.XKB_KEY_XF86Switch_VT_1 + 1; const vt = @enumToInt(keysym) - @enumToInt(xkb.Keysym.XF86Switch_VT_1) + 1;
log.notice(.server, "switching to VT {}", .{vt}); log.notice(.server, "switching to VT {}", .{vt});
_ = c.wlr_session_change_vt(session, vt); session.changeVt(vt) catch log.err(.server, "changing VT failed", .{});
} }
} }
return true; return true;
},
else => return false,
} }
return false;
} }

View file

@ -18,8 +18,9 @@
const Self = @This(); const Self = @This();
const std = @import("std"); 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 log = @import("log.zig");
const util = @import("util.zig"); const util = @import("util.zig");
@ -28,27 +29,27 @@ const Output = @import("Output.zig");
const XdgPopup = @import("XdgPopup.zig"); const XdgPopup = @import("XdgPopup.zig");
output: *Output, output: *Output,
wlr_layer_surface: *c.wlr_layer_surface_v1, wlr_layer_surface: *wlr.LayerSurfaceV1,
box: Box = undefined, box: Box = undefined,
state: c.wlr_layer_surface_v1_state, state: wlr.LayerSurfaceV1.State,
// Listeners active the entire lifetime of the layser surface // Listeners active the entire lifetime of the layser surface
listen_destroy: c.wl_listener = undefined, destroy: wl.Listener(*wlr.LayerSurfaceV1) = undefined,
listen_map: c.wl_listener = undefined, map: wl.Listener(*wlr.LayerSurfaceV1) = undefined,
listen_unmap: c.wl_listener = undefined, unmap: wl.Listener(*wlr.LayerSurfaceV1) = undefined,
// Listeners only active while the layer surface is mapped // Listeners only active while the layer surface is mapped
listen_commit: c.wl_listener = undefined, commit: wl.Listener(*wlr.Surface) = undefined,
listen_new_popup: c.wl_listener = 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.* = .{ self.* = .{
.output = output, .output = output,
.wlr_layer_surface = wlr_layer_surface, .wlr_layer_surface = wlr_layer_surface,
.state = wlr_layer_surface.current, .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 // Temporarily add to the output's list to allow for inital arrangement
// which sends the first configure. // 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); list.remove(node);
// Set up listeners that are active for the entire lifetime of the layer surface // Set up listeners that are active for the entire lifetime of the layer surface
self.listen_destroy.notify = handleDestroy; self.destroy.setNotify(handleDestroy);
c.wl_signal_add(&self.wlr_layer_surface.events.destroy, &self.listen_destroy); self.wlr_layer_surface.events.destroy.add(&self.destroy);
self.listen_map.notify = handleMap; self.map.setNotify(handleMap);
c.wl_signal_add(&self.wlr_layer_surface.events.map, &self.listen_map); self.wlr_layer_surface.events.map.add(&self.map);
self.listen_unmap.notify = handleUnmap; self.unmap.setNotify(handleUnmap);
c.wl_signal_add(&self.wlr_layer_surface.events.unmap, &self.listen_unmap); self.wlr_layer_surface.events.unmap.add(&self.unmap);
} }
fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleDestroy(listener: *wl.Listener(*wlr.LayerSurfaceV1), wlr_layer_surface: *wlr.LayerSurfaceV1) void {
const self = @fieldParentPtr(Self, "listen_destroy", listener.?); const self = @fieldParentPtr(Self, "destroy", listener);
const output = self.output;
log.debug(.layer_shell, "layer surface '{}' destroyed", .{self.wlr_layer_surface.namespace}); log.debug(.layer_shell, "layer surface '{}' destroyed", .{self.wlr_layer_surface.namespace});
// Remove listeners active the entire lifetime of the layer surface // Remove listeners active the entire lifetime of the layer surface
c.wl_list_remove(&self.listen_destroy.link); self.destroy.link.remove();
c.wl_list_remove(&self.listen_map.link); self.map.link.remove();
c.wl_list_remove(&self.listen_unmap.link); self.unmap.link.remove();
const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self);
util.gpa.destroy(node); util.gpa.destroy(node);
} }
fn handleMap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleMap(listener: *wl.Listener(*wlr.LayerSurfaceV1), wlr_layer_surface: *wlr.LayerSurfaceV1) void {
const self = @fieldParentPtr(Self, "listen_map", listener.?); const self = @fieldParentPtr(Self, "map", listener);
const wlr_layer_surface = self.wlr_layer_surface;
log.debug(.layer_shell, "layer surface '{}' mapped", .{wlr_layer_surface.namespace}); log.debug(.layer_shell, "layer surface '{}' mapped", .{wlr_layer_surface.namespace});
// Add listeners that are only active while mapped // Add listeners that are only active while mapped
self.listen_commit.notify = handleCommit; self.commit.setNotify(handleCommit);
c.wl_signal_add(&wlr_layer_surface.surface.*.events.commit, &self.listen_commit); wlr_layer_surface.surface.events.commit.add(&self.commit);
self.listen_new_popup.notify = handleNewPopup; self.new_popup.setNotify(handleNewPopup);
c.wl_signal_add(&wlr_layer_surface.events.new_popup, &self.listen_new_popup); wlr_layer_surface.events.new_popup.add(&self.new_popup);
c.wlr_surface_send_enter( wlr_layer_surface.surface.sendEnter(wlr_layer_surface.output.?);
wlr_layer_surface.surface,
wlr_layer_surface.output,
);
const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self);
self.output.layers[@intCast(usize, @enumToInt(self.state.layer))].append(node); self.output.layers[@intCast(usize, @enumToInt(self.state.layer))].append(node);
} }
fn handleUnmap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleUnmap(listener: *wl.Listener(*wlr.LayerSurfaceV1), wlr_layer_surface: *wlr.LayerSurfaceV1) void {
const self = @fieldParentPtr(Self, "listen_unmap", listener.?); const self = @fieldParentPtr(Self, "unmap", listener);
log.debug(.layer_shell, "layer surface '{}' unmapped", .{self.wlr_layer_surface.namespace}); log.debug(.layer_shell, "layer surface '{}' unmapped", .{self.wlr_layer_surface.namespace});
// remove listeners only active while the layer surface is mapped // remove listeners only active while the layer surface is mapped
c.wl_list_remove(&self.listen_commit.link); self.commit.link.remove();
c.wl_list_remove(&self.listen_new_popup.link); self.new_popup.link.remove();
// Remove from the output's list of layer surfaces // Remove from the output's list of layer surfaces
const self_node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); 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(); self.output.root.startTransaction();
} }
fn handleCommit(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleCommit(listener: *wl.Listener(*wlr.Surface), wlr_surface: *wlr.Surface) void {
const self = @fieldParentPtr(Self, "listen_commit", listener.?); const self = @fieldParentPtr(Self, "commit", listener);
if (self.wlr_layer_surface.output == null) { if (self.wlr_layer_surface.output == null) {
log.err(.layer_shell, "layer surface committed with null output", .{}); 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 { fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), wlr_xdg_popup: *wlr.XdgPopup) void {
const self = @fieldParentPtr(Self, "listen_new_popup", listener.?); const self = @fieldParentPtr(Self, "new_popup", listener);
const wlr_xdg_popup = util.voidCast(c.wlr_xdg_popup, data.?);
// This will free itself on destroy // This will free itself on destroy
var xdg_popup = util.gpa.create(XdgPopup) catch { const xdg_popup = util.gpa.create(XdgPopup) catch {
c.wl_resource_post_no_memory(wlr_xdg_popup.resource); wlr_xdg_popup.resource.postNoMemory();
return; return;
}; };
xdg_popup.init(self.output, &self.box, wlr_xdg_popup); xdg_popup.init(self.output, &self.box, wlr_xdg_popup);

View file

@ -18,20 +18,21 @@
const Self = @This(); const Self = @This();
const std = @import("std"); const std = @import("std");
const wlr = @import("wlroots");
const xkb = @import("xkbcommon");
const c = @import("c.zig");
const util = @import("util.zig"); const util = @import("util.zig");
keysym: c.xkb_keysym_t, keysym: xkb.Keysym,
modifiers: u32, modifiers: wlr.Keyboard.ModifierMask,
command_args: []const []const u8, command_args: []const []const u8,
/// When set to true the mapping will be executed on key release rather than on press /// When set to true the mapping will be executed on key release rather than on press
release: bool, release: bool,
pub fn init( pub fn init(
keysym: c.xkb_keysym_t, keysym: xkb.Keysym,
modifiers: u32, modifiers: wlr.Keyboard.ModifierMask,
release: bool, release: bool,
command_args: []const []const u8, command_args: []const []const u8,
) !Self { ) !Self {

View file

@ -18,6 +18,10 @@
const Self = @This(); const Self = @This();
const std = @import("std"); 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 c = @import("c.zig");
const log = @import("log.zig"); const log = @import("log.zig");
@ -38,7 +42,7 @@ const State = struct {
}; };
root: *Root, root: *Root,
wlr_output: *c.wlr_output, wlr_output: *wlr.Output,
/// All layer surfaces on the output, indexed by the layer enum. /// All layer surfaces on the output, indexed by the layer enum.
layers: [4]std.TailQueue(LayerSurface) = [1]std.TailQueue(LayerSurface){.{}} ** 4, 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) /// An active output can have focus (e.g. an output turned off by dpms is active)
active: bool = false, active: bool = false,
// All listeners for this output, in alphabetical order destroy: wl.Listener(*wlr.Output) = undefined,
listen_destroy: c.wl_listener = undefined, enable: wl.Listener(*wlr.Output) = undefined,
listen_enable: c.wl_listener = undefined, frame: wl.Listener(*wlr.Output) = undefined,
listen_frame: c.wl_listener = undefined, mode: wl.Listener(*wlr.Output) = undefined,
listen_mode: c.wl_listener = 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 // 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, // 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 // refresh rate), and each monitor supports only a specific set of modes. We
// just pick the monitor's preferred mode, a more sophisticated compositor // just pick the monitor's preferred mode, a more sophisticated compositor
// would let the user configure it. // would let the user configure it.
if (c.wlr_output_preferred_mode(wlr_output)) |mode| { if (wlr_output.preferredMode()) |mode| {
c.wlr_output_set_mode(wlr_output, mode); wlr_output.setMode(mode);
c.wlr_output_enable(wlr_output, true); wlr_output.enable(true);
if (!c.wlr_output_commit(wlr_output)) return error.OutputCommitFailed; try wlr_output.commit();
} }
const layout = try std.mem.dupe(util.gpa, u8, "full"); 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, .layout = layout,
.usable_box = undefined, .usable_box = undefined,
}; };
wlr_output.data = self; wlr_output.data = @ptrToInt(self);
// Set up listeners self.destroy.setNotify(handleDestroy);
self.listen_destroy.notify = handleDestroy; wlr_output.events.destroy.add(&self.destroy);
c.wl_signal_add(&wlr_output.events.destroy, &self.listen_destroy);
self.listen_enable.notify = handleEnable; self.enable.setNotify(handleEnable);
c.wl_signal_add(&wlr_output.events.enable, &self.listen_enable); wlr_output.events.enable.add(&self.enable);
self.listen_frame.notify = handleFrame; self.frame.setNotify(handleFrame);
c.wl_signal_add(&wlr_output.events.frame, &self.listen_frame); wlr_output.events.frame.add(&self.frame);
self.listen_mode.notify = handleMode; self.mode.setNotify(handleMode);
c.wl_signal_add(&wlr_output.events.mode, &self.listen_mode); 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 // A noop output is always 0 x 0
self.usable_box = .{ self.usable_box = .{
.x = 0, .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; var it = root.server.input_manager.seats.first;
while (it) |node| : (it = node.next) { while (it) |node| : (it = node.next) {
const seat = &node.data; 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}); 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 { pub fn getLayer(self: *Self, layer: zwlr.LayerShellV1.Layer) *std.TailQueue(LayerSurface) {
return c.wlr_backend_get_renderer(self.wlr_output.backend); return &self.layers[@intCast(usize, @enumToInt(layer))];
} }
pub fn sendViewTags(self: Self) void { 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 // This box is modified as exclusive zones are applied
var usable_box = full_box; var usable_box = full_box;
const layer_idxs = [_]usize{ const layers = [_]zwlr.LayerShellV1.Layer{ .overlay, .top, .bottom, .background };
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,
};
// Arrange all layer surfaces with exclusive zones, applying them to the // Arrange all layer surfaces with exclusive zones, applying them to the
// usable box along the way. // 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 the the usable_box has changed, we need to rearrange the output
if (!std.meta.eql(self.usable_box, usable_box)) { 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 // 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 // Find the topmost layer surface in the top or overlay layers which
// requests keyboard interactivity if any. // 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 // 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) { while (it) |node| : (it = node.prev) {
const layer_surface = &node.data; const layer_surface = &node.data;
if (layer_surface.wlr_layer_surface.current.keyboard_interactive) { if (layer_surface.wlr_layer_surface.current.keyboard_interactive) {
@ -400,19 +397,14 @@ fn arrangeLayer(
var new_box: Box = undefined; var new_box: Box = undefined;
// Horizontal alignment // 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) { if (current_state.desired_width == 0) {
const anchor_left_right = anchor_left | anchor_right; std.debug.assert(current_state.anchor.right and current_state.anchor.left);
if (current_state.anchor & anchor_left_right == anchor_left_right) {
new_box.x = bounds.x + @intCast(i32, current_state.margin.left); new_box.x = bounds.x + @intCast(i32, current_state.margin.left);
new_box.width = bounds.width - new_box.width = bounds.width - (current_state.margin.left + current_state.margin.right);
(current_state.margin.left + current_state.margin.right); } else if (current_state.anchor.left) {
}
} else if (current_state.anchor & anchor_left != 0) {
new_box.x = bounds.x + @intCast(i32, current_state.margin.left); new_box.x = bounds.x + @intCast(i32, current_state.margin.left);
new_box.width = current_state.desired_width; 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 - new_box.x = bounds.x + @intCast(i32, bounds.width - current_state.desired_width -
current_state.margin.right); current_state.margin.right);
new_box.width = current_state.desired_width; new_box.width = current_state.desired_width;
@ -422,19 +414,14 @@ fn arrangeLayer(
} }
// Vertical alignment // 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) { if (current_state.desired_height == 0) {
const anchor_top_bottom = anchor_top | anchor_bottom; std.debug.assert(current_state.anchor.top and current_state.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.y = bounds.y + @intCast(i32, current_state.margin.top);
new_box.height = bounds.height - new_box.height = bounds.height - (current_state.margin.top + current_state.margin.bottom);
(current_state.margin.top + current_state.margin.bottom); } else if (current_state.anchor.top) {
}
} else if (current_state.anchor & anchor_top != 0) {
new_box.y = bounds.y + @intCast(i32, current_state.margin.top); new_box.y = bounds.y + @intCast(i32, current_state.margin.top);
new_box.height = current_state.desired_height; 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 - new_box.y = bounds.y + @intCast(i32, bounds.height - current_state.desired_height -
current_state.margin.bottom); current_state.margin.bottom);
new_box.height = current_state.desired_height; new_box.height = current_state.desired_height;
@ -447,36 +434,36 @@ fn arrangeLayer(
// Apply the exclusive zone to the current bounds // Apply the exclusive zone to the current bounds
const edges = [4]struct { const edges = [4]struct {
single: u32, single: zwlr.LayerSurfaceV1.Anchor,
triple: u32, triple: zwlr.LayerSurfaceV1.Anchor,
to_increase: ?*i32, to_increase: ?*i32,
to_decrease: *u32, to_decrease: *u32,
margin: u32, margin: u32,
}{ }{
.{ .{
.single = anchor_top, .single = .{ .top = true },
.triple = anchor_top | anchor_left | anchor_right, .triple = .{ .top = true, .left = true, .right = true },
.to_increase = &usable_box.y, .to_increase = &usable_box.y,
.to_decrease = &usable_box.height, .to_decrease = &usable_box.height,
.margin = current_state.margin.top, .margin = current_state.margin.top,
}, },
.{ .{
.single = anchor_bottom, .single = .{ .bottom = true },
.triple = anchor_bottom | anchor_left | anchor_right, .triple = .{ .bottom = true, .left = true, .right = true },
.to_increase = null, .to_increase = null,
.to_decrease = &usable_box.height, .to_decrease = &usable_box.height,
.margin = current_state.margin.bottom, .margin = current_state.margin.bottom,
}, },
.{ .{
.single = anchor_left, .single = .{ .left = true },
.triple = anchor_left | anchor_top | anchor_bottom, .triple = .{ .left = true, .top = true, .bottom = true },
.to_increase = &usable_box.x, .to_increase = &usable_box.x,
.to_decrease = &usable_box.width, .to_decrease = &usable_box.width,
.margin = current_state.margin.left, .margin = current_state.margin.left,
}, },
.{ .{
.single = anchor_right, .single = .{ .right = true },
.triple = anchor_right | anchor_top | anchor_bottom, .triple = .{ .right = true, .top = true, .bottom = true },
.to_increase = null, .to_increase = null,
.to_decrease = &usable_box.width, .to_decrease = &usable_box.width,
.margin = current_state.margin.right, .margin = current_state.margin.right,
@ -484,7 +471,7 @@ fn arrangeLayer(
}; };
for (edges) |edge| { 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) current_state.exclusive_zone + @intCast(i32, edge.margin) > 0)
{ {
const delta = current_state.exclusive_zone + @intCast(i32, edge.margin); const delta = current_state.exclusive_zone + @intCast(i32, edge.margin);
@ -496,21 +483,18 @@ fn arrangeLayer(
// Tell the client to assume the new size // Tell the client to assume the new size
log.debug(.layer_shell, "send configure, {} x {}", .{ layer_surface.box.width, layer_surface.box.height }); 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.configure(layer_surface.box.width, layer_surface.box.height);
layer_surface.wlr_layer_surface,
layer_surface.box.width,
layer_surface.box.height,
);
} }
} }
/// Called when the output is destroyed. Evacuate all views from the output /// Called when the output is destroyed. Evacuate all views from the output
/// and then remove it from the list of outputs. /// and then remove it from the list of outputs.
fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleDestroy(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void {
const self = @fieldParentPtr(Self, "listen_destroy", listener.?); const self = @fieldParentPtr(Self, "destroy", listener);
const root = self.root; const root = self.root;
log.debug(.server, "output '{}' destroyed", .{self.wlr_output.name}); log.debug(.server, "output '{}' destroyed", .{self.wlr_output.name});
// Remove the destroyed output from root if it wasn't already removed // Remove the destroyed output from root if it wasn't already removed
const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self);
if (self.active) { if (self.active) {
@ -526,37 +510,35 @@ fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
} }
// Remove all listeners // Remove all listeners
c.wl_list_remove(&self.listen_destroy.link); self.destroy.link.remove();
c.wl_list_remove(&self.listen_enable.link); self.enable.link.remove();
c.wl_list_remove(&self.listen_frame.link); self.frame.link.remove();
c.wl_list_remove(&self.listen_mode.link); 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); util.gpa.free(self.layout);
// Clean up the wlr_output
self.wlr_output.data = null;
util.gpa.destroy(node); util.gpa.destroy(node);
} }
fn handleEnable(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleEnable(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void {
const self = @fieldParentPtr(Self, "listen_enable", listener.?); 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); const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self);
self.root.addOutput(node); 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, // This function is called every time an output is ready to display a frame,
// generally at the output's refresh rate (e.g. 60Hz). // 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); render.renderOutput(self);
} }
fn handleMode(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleMode(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void {
const self = @fieldParentPtr(Self, "listen_mode", listener.?); const self = @fieldParentPtr(Self, "mode", listener);
self.arrangeLayers(); self.arrangeLayers();
self.arrangeViews(); self.arrangeViews();
self.root.startTransaction(); 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 } { pub fn getEffectiveResolution(self: *Self) struct { width: u32, height: u32 } {
var width: c_int = undefined; var width: c_int = undefined;
var height: 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 .{ return .{
.width = @intCast(u32, width), .width = @intCast(u32, width),
.height = @intCast(u32, height), .height = @intCast(u32, height),

View file

@ -19,8 +19,9 @@ const Self = @This();
const build_options = @import("build_options"); const build_options = @import("build_options");
const std = @import("std"); 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 log = @import("log.zig");
const util = @import("util.zig"); const util = @import("util.zig");
@ -38,65 +39,60 @@ const min_size = 50;
root: *Root, root: *Root,
listen_new_output: c.wl_listener = undefined, new_output: wl.Listener(*wlr.Output) = undefined,
listen_output_layout_change: c.wl_listener = undefined,
wlr_output_manager: *c.wlr_output_manager_v1, wlr_output_manager: *wlr.OutputManagerV1,
listen_output_manager_apply: c.wl_listener = undefined, manager_apply: wl.Listener(*wlr.OutputConfigurationV1) = undefined,
listen_output_manager_test: c.wl_listener = 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, power_manager: *wlr.OutputPowerManagerV1,
listen_output_power_manager_set_mode: c.wl_listener = undefined, power_manager_set_mode: wl.Listener(*wlr.OutputPowerManagerV1.event.SetMode) = undefined,
/// True if and only if we are currently applying an output config /// True if and only if we are currently applying an output config
output_config_pending: bool = false, output_config_pending: bool = false,
pub fn init(self: *Self, server: *Server) !void { pub fn init(self: *Self, server: *Server) !void {
self.* = .{ 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, .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; self.new_output.setNotify(handleNewOutput);
c.wl_signal_add(&server.wlr_backend.events.new_output, &self.listen_new_output); server.backend.events.new_output.add(&self.new_output);
// Set up wlr_output_management self.manager_apply.setNotify(handleOutputManagerApply);
self.listen_output_manager_apply.notify = handleOutputManagerApply; self.wlr_output_manager.events.apply.add(&self.manager_apply);
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);
// Listen for changes in the output layout to send them to the clients of wlr_output_manager self.manager_test.setNotify(handleOutputManagerTest);
self.listen_output_layout_change.notify = handleOutputLayoutChange; self.wlr_output_manager.events.@"test".add(&self.manager_test);
c.wl_signal_add(&self.root.wlr_output_layout.events.change, &self.listen_output_layout_change);
// Set up output power manager self.layout_change.setNotify(handleOutputLayoutChange);
self.listen_output_power_manager_set_mode.notify = handleOutputPowerManagementSetMode; self.root.output_layout.events.change.add(&self.layout_change);
c.wl_signal_add(&self.wlr_output_power_manager.events.set_mode, &self.listen_output_power_manager_set_mode);
_ = c.wlr_xdg_output_manager_v1_create(server.wl_display, server.root.wlr_output_layout) orelse self.power_manager_set_mode.setNotify(handleOutputPowerManagementSetMode);
return error.OutOfMemory; 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 { fn handleNewOutput(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void {
const self = @fieldParentPtr(Self, "listen_new_output", listener.?); const self = @fieldParentPtr(Self, "new_output", listener);
const wlr_output = util.voidCast(c.wlr_output, data.?);
log.debug(.output_manager, "new output {}", .{wlr_output.name}); log.debug(.output_manager, "new output {}", .{wlr_output.name});
const node = util.gpa.create(std.TailQueue(Output).Node) catch { const node = util.gpa.create(std.TailQueue(Output).Node) catch {
c.wlr_output_destroy(wlr_output); wlr_output.destroy();
return; return;
}; };
node.data.init(self.root, wlr_output) catch { node.data.init(self.root, wlr_output) catch {
c.wlr_output_destroy(wlr_output); wlr_output.destroy();
util.gpa.destroy(node);
return; return;
}; };
const ptr_node = util.gpa.create(std.TailQueue(*Output).Node) catch { const ptr_node = util.gpa.create(std.TailQueue(*Output).Node) catch {
wlr_output.destroy();
util.gpa.destroy(node); util.gpa.destroy(node);
c.wlr_output_destroy(wlr_output);
return; return;
}; };
ptr_node.data = &node.data; 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); self.root.addOutput(node);
} }
/// Sends the new output configuration to all clients of wlr_output_manager /// Send the new output configuration to all wlr-output-manager clients
fn handleOutputLayoutChange(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleOutputLayoutChange(
const self = @fieldParentPtr(Self, "listen_output_layout_change", listener.?); listener: *wl.Listener(*wlr.OutputLayout),
// Dont do anything if the layout change is coming from applying a config 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; if (self.output_config_pending) return;
const config = self.createOutputConfigurationFromCurrent() catch { const config = self.ouputConfigFromCurrent() catch {
log.err(.output_manager, "Could not create output configuration", .{}); log.crit(.output_manager, "out of memory", .{});
return; 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 { fn handleOutputManagerApply(
const self = @fieldParentPtr(Self, "listen_output_manager_apply", listener.?); listener: *wl.Listener(*wlr.OutputConfigurationV1),
const config = util.voidCast(c.wlr_output_configuration_v1, data.?); config: *wlr.OutputConfigurationV1,
defer c.wlr_output_configuration_v1_destroy(config); ) void {
const self = @fieldParentPtr(Self, "manager_apply", listener);
defer config.destroy();
if (self.applyOutputConfig(config)) { if (self.applyOutputConfig(config)) {
c.wlr_output_configuration_v1_send_succeeded(config); config.sendSucceeded();
} else { } else {
c.wlr_output_configuration_v1_send_failed(config); config.sendFailed();
} }
// Now send the config that actually was applied // Send the config that was actually applied
const actualConfig = self.createOutputConfigurationFromCurrent() catch { const applied_config = self.ouputConfigFromCurrent() catch {
log.err(.output_manager, "Could not create output configuration", .{}); log.crit(.output_manager, "out of memory", .{});
return; 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 { fn handleOutputManagerTest(
const self = @fieldParentPtr(Self, "listen_output_manager_test", listener.?); listener: *wl.Listener(*wlr.OutputConfigurationV1),
const config = util.voidCast(c.wlr_output_configuration_v1, data.?); config: *wlr.OutputConfigurationV1,
defer c.wlr_output_configuration_v1_destroy(config); ) void {
const self = @fieldParentPtr(Self, "manager_test", listener);
defer config.destroy();
if (testOutputConfig(config, true)) { if (testOutputConfig(config, true)) {
c.wlr_output_configuration_v1_send_succeeded(config); config.sendSucceeded();
} else { } else {
c.wlr_output_configuration_v1_send_failed(config); config.sendFailed();
} }
} }
fn handleOutputPowerManagementSetMode(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleOutputPowerManagementSetMode(
const self = @fieldParentPtr(Self, "listen_output_power_manager_set_mode", listener.?); listener: *wl.Listener(*wlr.OutputPowerManagerV1.event.SetMode),
const mode_event = util.voidCast(c.wlr_output_power_v1_set_mode_event, data.?); event: *wlr.OutputPowerManagerV1.event.SetMode,
const wlr_output: *c.wlr_output = mode_event.output; ) 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"; const log_text = if (enable) "Enabling" else "Disabling";
log.debug( log.debug(
.output_manager, .output_manager,
"{} dpms for output {}", "{} dpms for output {}",
.{ log_text, wlr_output.name }, .{ log_text, event.output.name },
); );
c.wlr_output_enable(wlr_output, enable); event.output.enable(enable);
if (!c.wlr_output_commit(wlr_output)) { event.output.commit() catch
log.err( log.err(.server, "output commit failed for {}", .{event.output.name});
.output_manager,
"wlr_output_commit failed for {}",
.{wlr_output.name},
);
}
} }
/// Applies an output config /// Apply the given config, return false on faliure
fn applyOutputConfig(self: *Self, config: *c.wlr_output_configuration_v1) bool { fn applyOutputConfig(self: *Self, config: *wlr.OutputConfigurationV1) bool {
// We need to store whether a config is pending because we listen to wlr_output_layout.change // Store whether a config is pending so we can tell if the
// and this event can be triggered by applying the config // wlr_output_layout.change event was triggered by applying the config
self.output_config_pending = true; self.output_config_pending = true;
defer self.output_config_pending = false; defer self.output_config_pending = false;
// Test if the config should apply cleanly // Test if the config should apply cleanly
if (!testOutputConfig(config, false)) return false; if (!testOutputConfig(config, false)) return false;
const list_head: *c.wl_list = &config.heads; var it = config.heads.iterator(.forward);
var it: *c.wl_list = list_head.next; while (it.next()) |head| {
while (it != list_head) : (it = it.next) { const output = @intToPtr(*Output, head.state.output.data);
const head = @fieldParentPtr(c.wlr_output_configuration_head_v1, "link", it);
const output = util.voidCast(Output, @as(*c.wlr_output, head.state.output).data.?);
const disable = output.wlr_output.enabled and !head.state.enabled; const disable = output.wlr_output.enabled and !head.state.enabled;
// This commit will only fail due to runtime errors. // Since we have done a successful test commit, this will only fail
// We choose to ignore this error // due to error in the output's backend implementation.
if (!c.wlr_output_commit(output.wlr_output)) { output.wlr_output.commit() catch
log.err(.output_manager, "wlr_output_commit failed for {}", .{output.wlr_output.name}); log.err(.output_manager, "output commit failed for {}", .{output.wlr_output.name});
}
if (output.wlr_output.enabled) { if (output.wlr_output.enabled) {
// Moves the output if it is already in the layout // 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) { if (disable) {
const node = @fieldParentPtr(std.TailQueue(Output).Node, "data", output); const node = @fieldParentPtr(std.TailQueue(Output).Node, "data", output);
self.root.removeOutput(node); 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 // Arrange layers to adjust the usable_box
// We dont need to call arrangeViews() since arrangeLayers() will call // We dont need to call arrangeViews() since arrangeLayers() will call
// it for us because the usable_box changed // 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. /// Tests the output configuration.
/// If rollback is false all changes are applied to the pending state of the affected outputs. /// 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; var ok = true;
const list_head: *c.wl_list = &config.heads; var it = config.heads.iterator(.forward);
var it: *c.wl_list = list_head.next; while (it.next()) |head| {
while (it != list_head) : (it = it.next) { const wlr_output = head.state.output;
const head = @fieldParentPtr(c.wlr_output_configuration_head_v1, "link", it);
const wlr_output = @as(*c.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 width = if (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 height = if (head.state.mode) |m| m.height else head.state.custom_mode.height;
const scale = head.state.scale; const scale = head.state.scale;
const too_small = (@intToFloat(f32, width) / scale < min_size) or 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); 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) { if (rollback or !ok) {
// Rollback all changes // Rollback all changes
it = list_head.next; it = config.heads.iterator(.forward);
while (it != list_head) : (it = it.next) { while (it.next()) |head| head.state.output.rollback();
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);
}
} }
return ok; return ok;
} }
fn applyHeadToOutput(head: *c.wlr_output_configuration_head_v1, wlr_output: *c.wlr_output) void { fn applyHeadToOutput(head: *wlr.OutputConfigurationV1.Head, wlr_output: *wlr.Output) void {
c.wlr_output_enable(wlr_output, head.state.enabled); wlr_output.enable(head.state.enabled);
// The output must be enabled for the following properties to apply // The output must be enabled for the following properties to apply
if (head.state.enabled) { if (head.state.enabled) {
// TODO(wlroots) Somehow on the drm backend setting the mode causes // 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 // We can just ignore this because nothing bad happens but it
// should be fixed in the future // should be fixed in the future
// See https://github.com/swaywm/wlroots/issues/2492 // See https://github.com/swaywm/wlroots/issues/2492
if (head.state.mode != null) { if (head.state.mode) |mode| {
c.wlr_output_set_mode(wlr_output, head.state.mode); wlr_output.setMode(mode);
} else { } else {
log.info(.output_manager, "custom modes are not supported until the next wlroots release: ignoring", .{}); 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 // TODO(wlroots) uncomment the following lines when wlroots 0.13.0 is released
// See https://github.com/swaywm/wlroots/pull/2517 // See https://github.com/swaywm/wlroots/pull/2517
//const custom_mode = &head.state.custom_mode; //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 // 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)); wlr_output.setScale(@floatCast(f32, head.state.scale));
c.wlr_output_set_transform(wlr_output, head.state.transform); wlr_output.setTransform(head.state.transform);
} }
} }
/// Creates an wlr_output_configuration from the current configuration /// Create the config describing the current configuration
fn createOutputConfigurationFromCurrent(self: *Self) !*c.wlr_output_configuration_v1 { fn ouputConfigFromCurrent(self: *Self) !*wlr.OutputConfigurationV1 {
var config = c.wlr_output_configuration_v1_create() orelse return error.OutOfMemory; const config = try wlr.OutputConfigurationV1.create();
errdefer c.wlr_output_configuration_v1_destroy(config); // this destroys all associated config heads as well
errdefer config.destroy();
var it = self.root.all_outputs.first; var it = self.root.all_outputs.first;
while (it) |node| : (it = node.next) { while (it) |node| : (it = node.next) try self.createHead(node.data, config);
try self.createHead(node.data, config);
}
return 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 wlr_output = output.wlr_output;
const head: *c.wlr_output_configuration_head_v1 = c.wlr_output_configuration_head_v1_create(config, wlr_output) orelse const head = try wlr.OutputConfigurationV1.Head.create(config, wlr_output);
return error.OutOfMemory;
// If the output is not part of the layout (and thus disabled) we dont care about the position // If the output is not part of the layout (and thus disabled) we dont care
const box = @as(?*c.wlr_box, c.wlr_output_layout_get_box(self.root.wlr_output_layout, wlr_output)); // about the position
if (box) |b| { if (output.root.output_layout.getBox(wlr_output)) |box| {
head.state.x = b.x; head.state.x = box.x;
head.state.y = b.y; head.state.y = box.y;
} }
} }

View file

@ -18,8 +18,10 @@
const Self = @This(); const Self = @This();
const std = @import("std"); 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 log = @import("log.zig");
const util = @import("util.zig"); const util = @import("util.zig");
@ -27,29 +29,28 @@ const Output = @import("Output.zig");
const View = @import("View.zig"); const View = @import("View.zig");
const ViewStack = @import("view_stack.zig").ViewStack; const ViewStack = @import("view_stack.zig").ViewStack;
const implementation = c.struct_zriver_output_status_v1_interface{ .destroy = destroy };
output: *Output, output: *Output,
wl_resource: *c.wl_resource, output_status: *zriver.OutputStatusV1,
pub fn init(self: *Self, output: *Output, wl_resource: *c.wl_resource) void { pub fn init(self: *Self, output: *Output, output_status: *zriver.OutputStatusV1) void {
self.* = .{ .output = output, .wl_resource = wl_resource }; 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. // Send view/focused tags once on bind.
self.sendViewTags(); self.sendViewTags();
self.sendFocusedTags(); self.sendFocusedTags();
} }
fn handleResourceDestroy(wl_resource: ?*c.wl_resource) callconv(.C) void { fn handleRequest(output_status: *zriver.OutputStatusV1, request: zriver.OutputStatusV1.Request, self: *Self) void {
const self = util.voidCast(Self, @ptrCast(*c_void, c.wl_resource_get_user_data(wl_resource))); switch (request) {
const node = @fieldParentPtr(std.SinglyLinkedList(Self).Node, "data", self); .destroy => output_status.destroy(),
self.output.status_trackers.remove(node); }
} }
fn destroy(wl_client: ?*c.wl_client, wl_resource: ?*c.wl_resource) callconv(.C) void { fn handleDestroy(output_status: *zriver.OutputStatusV1, self: *Self) void {
c.wl_resource_destroy(wl_resource); 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. /// 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) { while (it) |node| : (it = node.next) {
if (node.view.destroying) continue; if (node.view.destroying) continue;
view_tags.append(node.view.current.tags) catch { 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", .{}); log.crit(.river_status, "out of memory", .{});
return; return;
}; };
} }
var wl_array = c.wl_array{ var wl_array = wl.Array.fromArrayList(u32, view_tags);
.size = view_tags.items.len * @sizeOf(u32), self.output_status.sendViewTags(&wl_array);
.alloc = view_tags.capacity * @sizeOf(u32),
.data = view_tags.items.ptr,
};
c.zriver_output_status_v1_send_view_tags(self.wl_resource, &wl_array);
} }
/// Send the currently focused tags of the output to the client. /// Send the currently focused tags of the output to the client.
pub fn sendFocusedTags(self: Self) void { 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);
} }

View file

@ -15,11 +15,13 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const wlr = @import("wlroots");
pub const Action = enum { pub const Action = enum {
move, move,
resize, resize,
}; };
event_code: u32, event_code: u32,
modifiers: u32, modifiers: wlr.Keyboard.ModifierMask,
action: Action, action: Action,

View file

@ -17,10 +17,11 @@
const Self = @This(); const Self = @This();
const std = @import("std");
const build_options = @import("build_options"); 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 log = @import("log.zig");
const util = @import("util.zig"); const util = @import("util.zig");
@ -31,10 +32,9 @@ const ViewStack = @import("view_stack.zig").ViewStack;
const XwaylandUnmanaged = @import("XwaylandUnmanaged.zig"); const XwaylandUnmanaged = @import("XwaylandUnmanaged.zig");
const DragIcon = @import("DragIcon.zig"); const DragIcon = @import("DragIcon.zig");
/// Responsible for all windowing operations
server: *Server, server: *Server,
wlr_output_layout: *c.wlr_output_layout, output_layout: *wlr.OutputLayout,
/// A list of all outputs /// A list of all outputs
all_outputs: std.TailQueue(*Output) = .{}, all_outputs: std.TailQueue(*Output) = .{},
@ -60,24 +60,20 @@ else
pending_configures: u32 = 0, pending_configures: u32 = 0,
/// Handles timeout of transactions /// Handles timeout of transactions
transaction_timer: *c.wl_event_source, transaction_timer: *wl.EventSource,
pub fn init(self: *Self, server: *Server) !void { pub fn init(self: *Self, server: *Server) !void {
// Create an output layout, which a wlroots utility for working with an const output_layout = try wlr.OutputLayout.create();
// arrangement of screens in a physical layout. errdefer output_layout.destroy();
errdefer c.wlr_output_layout_destroy(self.wlr_output_layout);
self.* = .{ self.* = .{
.server = server, .server = server,
.wlr_output_layout = c.wlr_output_layout_create() orelse return error.OutOfMemory, .output_layout = output_layout,
.transaction_timer = c.wl_event_loop_add_timer( .transaction_timer = try self.server.wl_server.getEventLoop().addTimer(*Self, handleTimeout, self),
c.wl_display_get_event_loop(self.server.wl_display),
handleTimeout,
self,
) orelse return error.AddTimerError,
.noop_output = undefined, .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); 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, // the noop backend triggering the destroy event. However,
// Output.handleDestroy is not intended to handle the noop output being // Output.handleDestroy is not intended to handle the noop output being
// destroyed. // destroyed.
c.wl_list_remove(&self.noop_output.listen_destroy.link); self.noop_output.destroy.link.remove();
c.wl_list_remove(&self.noop_output.listen_frame.link); self.noop_output.frame.link.remove();
c.wl_list_remove(&self.noop_output.listen_mode.link); self.noop_output.mode.link.remove();
c.wlr_output_layout_destroy(self.wlr_output_layout); self.output_layout.destroy();
self.transaction_timer.remove();
// This literally cannot fail, but for some reason returns 0
if (c.wl_event_source_remove(self.transaction_timer) < 0) unreachable;
} }
/// Removes the output in node.data from self.outputs /// 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. // handle them.
self.noop_output.layers[layer_idx].prepend(layer_node); self.noop_output.layers[layer_idx].prepend(layer_node);
layer_surface.output = &self.noop_output; 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 // from left-to-right in the order they appear. A more sophisticated
// compositor would let the user configure the arrangement of outputs in the // compositor would let the user configure the arrangement of outputs in the
// layout. This automatically creates an output global on the wl_display. // 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 // if we previously had no real outputs, move focus from the noop output
// to the new one. // to the new one.
@ -222,26 +216,22 @@ pub fn startTransaction(self: *Self) void {
); );
// Set timeout to 200ms // 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", .{}); log.err(.transaction, "failed to update timer", .{});
self.commitTransaction(); self.commitTransaction();
} };
} else { } else {
// No views need configures, clear the current timer in case we are // No views need configures, clear the current timer in case we are
// interrupting another transaction and commit. // interrupting another transaction and commit.
if (c.wl_event_source_timer_update(self.transaction_timer, 0) < 0) self.transaction_timer.timerUpdate(0) catch log.err(.transaction, "error disarming timer", .{});
log.err(.transaction, "error disarming timer", .{});
self.commitTransaction(); self.commitTransaction();
} }
} }
fn handleTimeout(data: ?*c_void) callconv(.C) c_int { fn handleTimeout(self: *Self) callconv(.C) c_int {
const self = util.voidCast(Self, data.?);
log.err(.transaction, "timeout occurred, some imperfect frames may be shown", .{}); log.err(.transaction, "timeout occurred, some imperfect frames may be shown", .{});
self.pending_configures = 0; self.pending_configures = 0;
self.commitTransaction(); self.commitTransaction();
return 0; return 0;
@ -251,8 +241,7 @@ pub fn notifyConfigured(self: *Self) void {
self.pending_configures -= 1; self.pending_configures -= 1;
if (self.pending_configures == 0) { if (self.pending_configures == 0) {
// Disarm the timer, as we didn't timeout // Disarm the timer, as we didn't timeout
if (c.wl_event_source_timer_update(self.transaction_timer, 0) == -1) self.transaction_timer.timerUpdate(0) catch log.err(.transaction, "error disarming timer", .{});
log.err(.transaction, "error disarming timer", .{});
self.commitTransaction(); self.commitTransaction();
} }
} }

View file

@ -19,8 +19,10 @@ const Self = @This();
const build_options = @import("build_options"); const build_options = @import("build_options");
const std = @import("std"); 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 command = @import("command.zig");
const log = @import("log.zig"); const log = @import("log.zig");
const util = @import("util.zig"); const util = @import("util.zig");
@ -42,7 +44,7 @@ const FocusTarget = union(enum) {
}; };
input_manager: *InputManager, input_manager: *InputManager,
wlr_seat: *c.wlr_seat, wlr_seat: *wlr.Seat,
/// Multiple mice are handled by the same Cursor /// Multiple mice are handled by the same Cursor
cursor: Cursor = undefined, cursor: Cursor = undefined,
@ -56,7 +58,8 @@ mode_id: usize = 0,
/// ID of previous keymap mode, used when returning from "locked" mode /// ID of previous keymap mode, used when returning from "locked" mode
prev_mode_id: usize = 0, 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, focused_output: *Output,
/// Currently focused view/layer surface if any /// 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. /// List of status tracking objects relaying changes to this seat to clients.
status_trackers: std.SinglyLinkedList(SeatStatus) = .{}, status_trackers: std.SinglyLinkedList(SeatStatus) = .{},
listen_request_set_selection: c.wl_listener = undefined, request_set_selection: wl.Listener(*wlr.Seat.event.RequestSetSelection) = undefined,
listen_request_start_drag: c.wl_listener = undefined, request_start_drag: wl.Listener(*wlr.Seat.event.RequestStartDrag) = undefined,
listen_start_drag: c.wl_listener = undefined, start_drag: wl.Listener(*wlr.Drag) = undefined,
listen_request_set_primary_selection: c.wl_listener = 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 { pub fn init(self: *Self, input_manager: *InputManager, name: [*:0]const u8) !void {
self.* = .{ self.* = .{
.input_manager = input_manager, .input_manager = input_manager,
// This will be automatically destroyed when the display is destroyed // 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, .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); try self.cursor.init(self);
self.listen_request_set_selection.notify = handleRequestSetSelection; self.request_set_selection.setNotify(handleRequestSetSelection);
c.wl_signal_add(&self.wlr_seat.events.request_set_selection, &self.listen_request_set_selection); self.wlr_seat.events.request_set_selection.add(&self.request_set_selection);
self.listen_request_start_drag.notify = handleRequestStartDrag; self.request_start_drag.setNotify(handleRequestStartDrag);
c.wl_signal_add(&self.wlr_seat.events.request_start_drag, &self.listen_request_start_drag); self.wlr_seat.events.request_start_drag.add(&self.request_start_drag);
self.listen_start_drag.notify = handleStartDrag; self.start_drag.setNotify(handleStartDrag);
c.wl_signal_add(&self.wlr_seat.events.start_drag, &self.listen_start_drag); self.wlr_seat.events.start_drag.add(&self.start_drag);
self.listen_request_set_primary_selection.notify = handleRequestPrimarySelection; self.request_set_primary_selection.setNotify(handleRequestPrimarySelection);
c.wl_signal_add(&self.wlr_seat.events.request_set_primary_selection, &self.listen_request_set_primary_selection); self.wlr_seat.events.request_set_primary_selection.add(&self.request_set_primary_selection);
} }
pub fn deinit(self: *Self) void { 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 the target is already focused, do nothing
if (std.meta.eql(new_focus, self.focused)) return; if (std.meta.eql(new_focus, self.focused)) return;
// Obtain the target wlr_surface // Obtain the target surface
const target_wlr_surface = switch (new_focus) { const target_surface = switch (new_focus) {
.view => |target_view| target_view.wlr_surface.?, .view => |target_view| target_view.surface.?,
.layer => |target_layer| target_layer.wlr_layer_surface.surface.?, .layer => |target_layer| target_layer.wlr_layer_surface.surface,
.none => null, .none => null,
}; };
// If input is not allowed on the target surface (e.g. due to an active // 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 // input inhibitor) do not set focus. If there is no target surface we
// still clear the focus. // 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 // First clear the current focus
if (self.focused == .view) { if (self.focused == .view) {
self.focused.view.pending.focus -= 1; self.focused.view.pending.focus -= 1;
// This is needed because xwayland views don't double buffer // This is needed because xwayland views don't double buffer
// activated state. // activated state.
if (build_options.xwayland and self.focused.view.impl == .xwayland_view) 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) { 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; 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 // Set the new focus
switch (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 // This is needed because xwayland views don't double buffer
// activated state. // activated state.
if (build_options.xwayland and target_view.impl == .xwayland_view) 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) { if (!target_view.pending.fullscreen) {
target_view.pending.target_opacity = self.input_manager.server.config.view_opacity_focused; 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; self.focused = new_focus;
// Tell wlroots to send the new keyboard focus if we have a target // Send surface enter/leave events
if (target_wlr_surface) |wlr_surface| { if (target_surface) |wlr_surface| {
const keyboard: *c.wlr_keyboard = c.wlr_seat_get_keyboard(self.wlr_seat); if (self.wlr_seat.getKeyboard()) |keyboard| {
c.wlr_seat_keyboard_notify_enter( self.wlr_seat.keyboardNotifyEnter(
self.wlr_seat,
wlr_surface, wlr_surface,
&keyboard.keycodes, &keyboard.keycodes,
keyboard.num_keycodes, keyboard.num_keycodes,
&keyboard.modifiers, &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 { 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 /// 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 /// Handle any user-defined mapping for the passed keysym and modifiers
/// Returns true if the key was handled /// 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; const modes = &self.input_manager.server.config.modes;
for (modes.items[self.mode_id].mappings.items) |mapping| { 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 // Execute the bound command
const args = mapping.command_args; const args = mapping.command_args;
var out: ?[]const u8 = null; 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 /// Add a newly created input device to the seat and update the reported
/// capabilities. /// capabilities.
pub fn addDevice(self: *Self, device: *c.wlr_input_device) void { pub fn addDevice(self: *Self, device: *wlr.InputDevice) void {
switch (device.type) { switch (device.type) {
.WLR_INPUT_DEVICE_KEYBOARD => self.addKeyboard(device) catch return, .keyboard => self.addKeyboard(device) catch return,
.WLR_INPUT_DEVICE_POINTER => self.addPointer(device), .pointer => self.addPointer(device),
else => return, else => return,
} }
// We need to let the wlr_seat know what our capabilities are, which is // 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 // communiciated to the client. We always have a cursor, even if
// there are no pointer devices, so we always include that capability. // there are no pointer devices, so we always include that capability.
var caps = @intCast(u32, c.WL_SEAT_CAPABILITY_POINTER); self.wlr_seat.setCapabilities(.{
if (self.keyboards.len > 0) caps |= @intCast(u32, c.WL_SEAT_CAPABILITY_KEYBOARD); .pointer = true,
c.wlr_seat_set_capabilities(self.wlr_seat, caps); .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); const node = try util.gpa.create(std.TailQueue(Keyboard).Node);
node.data.init(self, device) catch |err| { node.data.init(self, device) catch |err| {
switch (err) { switch (err) {
@ -335,40 +347,46 @@ fn addKeyboard(self: *Self, device: *c.wlr_input_device) !void {
return; return;
}; };
self.keyboards.append(node); 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 // 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 // is proxied through wlr_cursor. On another compositor, you might take this
// opportunity to do libinput configuration on the device to set // opportunity to do libinput configuration on the device to set
// acceleration, etc. // 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 { fn handleRequestSetSelection(
const self = @fieldParentPtr(Self, "listen_request_set_selection", listener.?); listener: *wl.Listener(*wlr.Seat.event.RequestSetSelection),
const event = util.voidCast(c.wlr_seat_request_set_selection_event, data.?); event: *wlr.Seat.event.RequestSetSelection,
c.wlr_seat_set_selection(self.wlr_seat, event.source, event.serial); ) 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 { fn handleRequestStartDrag(
const self = @fieldParentPtr(Self, "listen_request_start_drag", listener.?); listener: *wl.Listener(*wlr.Seat.event.RequestStartDrag),
const event = util.voidCast(c.wlr_seat_request_start_drag_event, data.?); 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)) { if (!self.wlr_seat.validatePointerGrabSerial(event.origin, event.serial)) {
log.debug(.seat, "starting pointer drag", .{}); log.debug(.seat, "ignoring request to start drag, failed to validate serial {}", .{event.serial});
c.wlr_seat_start_pointer_drag(self.wlr_seat, event.drag, event.serial); if (event.drag.source) |source| source.destroy();
return; return;
} }
log.debug(.seat, "ignoring request to start drag, failed to validate serial {}", .{event.serial}); log.debug(.seat, "starting pointer drag", .{});
c.wlr_data_source_destroy(event.drag.*.source); self.wlr_seat.startPointerDrag(event.drag, event.serial);
} }
fn handleStartDrag(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleStartDrag(
const self = @fieldParentPtr(Self, "listen_start_drag", listener.?); listener: *wl.Listener(*wlr.Drag),
const wlr_drag = util.voidCast(c.wlr_drag, data.?); wlr_drag: *wlr.Drag,
) void {
const self = @fieldParentPtr(Self, "start_drag", listener);
if (wlr_drag.icon) |wlr_drag_icon| { if (wlr_drag.icon) |wlr_drag_icon| {
const node = util.gpa.create(std.SinglyLinkedList(DragIcon).Node) catch { 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; self.cursor.mode = .passthrough;
} }
fn handleRequestPrimarySelection(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleRequestPrimarySelection(
const self = @fieldParentPtr(Self, "listen_request_set_primary_selection", listener.?); listener: *wl.Listener(*wlr.Seat.event.RequestSetPrimarySelection),
const event = util.voidCast(c.wlr_seat_request_set_primary_selection_event, data.?); event: *wlr.Seat.event.RequestSetPrimarySelection,
c.wlr_seat_set_primary_selection(self.wlr_seat, event.source, event.serial); ) void {
const self = @fieldParentPtr(Self, "request_set_primary_selection", listener);
self.wlr_seat.setPrimarySelection(event.source, event.serial);
} }

View file

@ -18,55 +18,53 @@
const Self = @This(); const Self = @This();
const std = @import("std"); 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 util = @import("util.zig");
const Seat = @import("Seat.zig"); const Seat = @import("Seat.zig");
const Output = @import("Output.zig"); const Output = @import("Output.zig");
const View = @import("View.zig"); const View = @import("View.zig");
const implementation = c.struct_zriver_seat_status_v1_interface{ .destroy = destroy };
seat: *Seat, seat: *Seat,
wl_resource: *c.wl_resource, seat_status: *zriver.SeatStatusV1,
pub fn init(self: *Self, seat: *Seat, wl_resource: *c.wl_resource) void { pub fn init(self: *Self, seat: *Seat, seat_status: *zriver.SeatStatusV1) void {
self.* = .{ .seat = seat, .wl_resource = wl_resource }; 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 // Send focused output/view once on bind
self.sendOutput(.focused); self.sendOutput(.focused);
self.sendFocusedView(); self.sendFocusedView();
} }
fn handleResourceDestroy(wl_resource: ?*c.wl_resource) callconv(.C) void { fn handleRequest(seat_status: *zriver.SeatStatusV1, request: zriver.SeatStatusV1.Request, self: *Self) void {
const self = util.voidCast(Self, c.wl_resource_get_user_data(wl_resource).?); switch (request) {
.destroy => seat_status.destroy(),
}
}
fn handleDestroy(seat_status: *zriver.SeatStatusV1, self: *Self) void {
const node = @fieldParentPtr(std.SinglyLinkedList(Self).Node, "data", self); const node = @fieldParentPtr(std.SinglyLinkedList(Self).Node, "data", self);
self.seat.status_trackers.remove(node); self.seat.status_trackers.remove(node);
util.gpa.destroy(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 { pub fn sendOutput(self: Self, state: enum { focused, unfocused }) void {
const wl_client = c.wl_resource_get_client(self.wl_resource); const client = self.seat_status.getClient();
const output_resources = &self.seat.focused_output.wlr_output.resources; var it = self.seat.focused_output.wlr_output.resources.iterator(.forward);
var output_resource = c.wl_resource_from_link(output_resources.next); while (it.next()) |wl_output| {
while (c.wl_resource_get_link(output_resource) != output_resources) : (output_resource = if (wl_output.getClient() == client) switch (state) {
c.wl_resource_from_link(c.wl_resource_get_link(output_resource).*.next)) .focused => self.seat_status.sendFocusedOutput(wl_output),
{ .unfocused => self.seat_status.sendUnfocusedOutput(wl_output),
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),
}; };
} }
} }
pub fn sendFocusedView(self: Self) void { pub fn sendFocusedView(self: Self) void {
const title: [*:0]const u8 = if (self.seat.focused == .view) self.seat.focused.view.getTitle() else ""; 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);
} }

View file

@ -19,6 +19,8 @@ const Self = @This();
const build_options = @import("build_options"); const build_options = @import("build_options");
const std = @import("std"); const std = @import("std");
const wlr = @import("wlroots");
const wl = @import("wayland").server.wl;
const c = @import("c.zig"); const c = @import("c.zig");
const log = @import("log.zig"); const log = @import("log.zig");
@ -37,22 +39,22 @@ const View = @import("View.zig");
const ViewStack = @import("view_stack.zig").ViewStack; const ViewStack = @import("view_stack.zig").ViewStack;
const XwaylandUnmanaged = @import("XwaylandUnmanaged.zig"); const XwaylandUnmanaged = @import("XwaylandUnmanaged.zig");
wl_display: *c.wl_display, wl_server: *wl.Server,
sigint_source: *c.wl_event_source, sigint_source: *wl.EventSource,
sigterm_source: *c.wl_event_source, sigterm_source: *wl.EventSource,
wlr_backend: *c.wlr_backend, backend: *wlr.Backend,
noop_backend: *c.wlr_backend, noop_backend: *wlr.Backend,
wlr_xdg_shell: *c.wlr_xdg_shell, xdg_shell: *wlr.XdgShell,
listen_new_xdg_surface: c.wl_listener, new_xdg_surface: wl.Listener(*wlr.XdgSurface),
wlr_layer_shell: *c.wlr_layer_shell_v1, layer_shell: *wlr.LayerShellV1,
listen_new_layer_surface: c.wl_listener, new_layer_surface: wl.Listener(*wlr.LayerSurfaceV1),
wlr_xwayland: if (build_options.xwayland) *c.wlr_xwayland else void, xwayland: if (build_options.xwayland) *wlr.Xwayland else void,
listen_new_xwayland_surface: if (build_options.xwayland) c.wl_listener else void, new_xwayland_surface: if (build_options.xwayland) wl.Listener(*wlr.XwaylandSurface) else void,
decoration_manager: DecorationManager, decoration_manager: DecorationManager,
input_manager: InputManager, input_manager: InputManager,
@ -63,61 +65,46 @@ control: Control,
status_manager: StatusManager, status_manager: StatusManager,
pub fn init(self: *Self) !void { pub fn init(self: *Self) !void {
// The Wayland display is managed by libwayland. It handles accepting self.wl_server = try wl.Server.create();
// clients from the Unix socket, managing Wayland globals, and so on. errdefer self.wl_server.destroy();
self.wl_display = c.wl_display_create() orelse return error.CreateDisplayError;
errdefer c.wl_display_destroy(self.wl_display);
// Never returns null if the display was created successfully const loop = self.wl_server.getEventLoop();
const wl_event_loop = c.wl_display_get_event_loop(self.wl_display); self.sigint_source = try loop.addSignal(*wl.Server, std.os.SIGINT, terminate, self.wl_server);
self.sigint_source = c.wl_event_loop_add_signal(wl_event_loop, std.os.SIGINT, terminate, self.wl_display) orelse errdefer self.sigint_source.remove();
return error.AddEventSourceFailed; self.sigterm_source = try loop.addSignal(*wl.Server, std.os.SIGTERM, terminate, self.wl_server);
errdefer _ = c.wl_event_source_remove(self.sigint_source); errdefer self.sigterm_source.remove();
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);
// The wlr_backend abstracts the input/output hardware. Autocreate chooses // This frees itself when the wl.Server is destroyed
// the best option based on the environment, for example DRM when run from self.backend = try wlr.Backend.autocreate(self.wl_server, null);
// 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 backend is used to create a noop output for use when no actual // 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. // outputs are available. This frees itself when the wl.Server is destroyed.
self.noop_backend = c.wlr_noop_backend_create(self.wl_display) orelse self.noop_backend = try wlr.Backend.createNoop(self.wl_server);
return error.OutOfMemory;
// If we don't provide a renderer, autocreate makes a GLES2 renderer for us. // This will never be null for the non-custom backends in wlroots
// The renderer is responsible for defining the various pixel formats it const renderer = self.backend.getRenderer().?;
// supports for shared memory, this configures that for clients. try renderer.initServer(self.wl_server);
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;
const wlr_compositor = c.wlr_compositor_create(self.wl_display, wlr_renderer) orelse const compositor = try wlr.Compositor.create(self.wl_server, renderer);
return error.OutOfMemory;
// Set up xdg shell // Set up xdg shell
self.wlr_xdg_shell = c.wlr_xdg_shell_create(self.wl_display) orelse return error.OutOfMemory; self.xdg_shell = try wlr.XdgShell.create(self.wl_server);
self.listen_new_xdg_surface.notify = handleNewXdgSurface; self.new_xdg_surface.setNotify(handleNewXdgSurface);
c.wl_signal_add(&self.wlr_xdg_shell.events.new_surface, &self.listen_new_xdg_surface); self.xdg_shell.events.new_surface.add(&self.new_xdg_surface);
// Set up layer shell // Set up layer shell
self.wlr_layer_shell = c.wlr_layer_shell_v1_create(self.wl_display) orelse return error.OutOfMemory; self.layer_shell = try wlr.LayerShellV1.create(self.wl_server);
self.listen_new_layer_surface.notify = handleNewLayerSurface; self.new_layer_surface.setNotify(handleNewLayerSurface);
c.wl_signal_add(&self.wlr_layer_shell.events.new_surface, &self.listen_new_layer_surface); self.layer_shell.events.new_surface.add(&self.new_layer_surface);
// Set up xwayland if built with support // Set up xwayland if built with support
if (build_options.xwayland) { if (build_options.xwayland) {
self.wlr_xwayland = c.wlr_xwayland_create(self.wl_display, wlr_compositor, false) orelse self.xwayland = try wlr.Xwayland.create(self.wl_server, compositor, false);
return error.CreateXwaylandError; self.new_xwayland_surface.setNotify(handleNewXwaylandSurface);
self.listen_new_xwayland_surface.notify = handleNewXwaylandSurface; self.xwayland.events.new_surface.add(&self.new_xwayland_surface);
c.wl_signal_add(&self.wlr_xwayland.events.new_surface, &self.listen_new_xwayland_surface);
} }
// Set up primary selection _ = try wlr.PrimarySelectionDeviceManagerV1.create(self.wl_server);
_ = c.wlr_primary_selection_v1_device_manager_create(self.wl_display);
self.config = try Config.init(); self.config = try Config.init();
try self.decoration_manager.init(self); try self.decoration_manager.init(self);
@ -128,31 +115,28 @@ pub fn init(self: *Self) !void {
try self.status_manager.init(self); try self.status_manager.init(self);
try self.output_manager.init(self); try self.output_manager.init(self);
// These all free themselves when the wl_display is destroyed // These all free themselves when the wl_server is destroyed
_ = c.wlr_data_device_manager_create(self.wl_display) orelse return error.OutOfMemory; _ = try wlr.DataDeviceManager.create(self.wl_server);
_ = c.wlr_data_control_manager_v1_create(self.wl_display) orelse return error.OutOfMemory; _ = try wlr.DataControlManagerV1.create(self.wl_server);
_ = c.wlr_export_dmabuf_manager_v1_create(self.wl_display) orelse return error.OutOfMemory; _ = try wlr.ExportDmabufManagerV1.create(self.wl_server);
_ = c.wlr_gamma_control_manager_v1_create(self.wl_display) orelse return error.OutOfMemory; _ = try wlr.GammaControlManagerV1.create(self.wl_server);
_ = c.wlr_screencopy_manager_v1_create(self.wl_display) orelse return error.OutOfMemory; _ = try wlr.ScreencopyManagerV1.create(self.wl_server);
_ = c.wlr_viewporter_create(self.wl_display) orelse return error.OutOfMemory; _ = try wlr.Viewporter.create(self.wl_server);
_ = c.wlr_xdg_output_manager_v1_create(self.wl_display, self.root.wlr_output_layout) orelse
return error.OutOfMemory;
} }
/// Free allocated memory and clean up /// Free allocated memory and clean up. Note: order is important here
pub fn deinit(self: *Self) void { pub fn deinit(self: *Self) void {
// Note: order is important here self.sigint_source.remove();
_ = c.wl_event_source_remove(self.sigint_source); self.sigterm_source.remove();
_ = c.wl_event_source_remove(self.sigterm_source);
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(); self.root.deinit();
c.wl_display_destroy(self.wl_display); self.wl_server.destroy();
c.wlr_backend_destroy(self.noop_backend); self.noop_backend.destroy();
self.input_manager.deinit(); self.input_manager.deinit();
self.config.deinit(); self.config.deinit();
@ -160,33 +144,26 @@ pub fn deinit(self: *Self) void {
/// Create the socket, start the backend, and setup the environment /// Create the socket, start the backend, and setup the environment
pub fn start(self: Self) !void { pub fn start(self: Self) !void {
const socket = c.wl_display_add_socket_auto(self.wl_display) orelse return error.AddSocketError; var buf: [11]u8 = undefined;
if (!c.wlr_backend_start(self.wlr_backend)) return error.StartBackendError; 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 (c.setenv("WAYLAND_DISPLAY", socket, 1) < 0) return error.SetenvError;
if (build_options.xwayland) { 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 /// Handle SIGINT and SIGTERM by gracefully stopping the server
fn terminate(signal: c_int, data: ?*c_void) callconv(.C) c_int { fn terminate(signal: c_int, wl_server: *wl.Server) callconv(.C) c_int {
const wl_display = util.voidCast(c.wl_display, data.?); wl_server.terminate();
c.wl_display_terminate(wl_display);
return 0; return 0;
} }
fn handleNewXdgSurface(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleNewXdgSurface(listener: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSurface) void {
// This event is raised when wlr_xdg_shell receives a new xdg surface from a const self = @fieldParentPtr(Self, "new_xdg_surface", listener);
// 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.?);
if (wlr_xdg_surface.role == .WLR_XDG_SURFACE_ROLE_POPUP) { if (xdg_surface.role == .popup) {
log.debug(.server, "new xdg_popup", .{}); log.debug(.server, "new xdg_popup", .{});
return; 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 // The View will add itself to the output's view stack on map
const output = self.input_manager.defaultSeat().focused_output; const output = self.input_manager.defaultSeat().focused_output;
const node = util.gpa.create(ViewStack(View).Node) catch { const node = util.gpa.create(ViewStack(View).Node) catch {
c.wl_resource_post_no_memory(wlr_xdg_surface.resource); xdg_surface.resource.postNoMemory();
return; 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. /// 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 { fn handleNewLayerSurface(listener: *wl.Listener(*wlr.LayerSurfaceV1), wlr_layer_surface: *wlr.LayerSurfaceV1) void {
const self = @fieldParentPtr(Self, "listen_new_layer_surface", listener.?); const self = @fieldParentPtr(Self, "new_layer_surface", listener);
const wlr_layer_surface = util.voidCast(c.wlr_layer_surface_v1, data.?);
log.debug( log.debug(
.server, .server,
@ -229,31 +205,28 @@ fn handleNewLayerSurface(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C)
if (wlr_layer_surface.output == null) { if (wlr_layer_surface.output == null) {
const output = self.input_manager.defaultSeat().focused_output; const output = self.input_manager.defaultSeat().focused_output;
if (output == &self.root.noop_output) { if (output == &self.root.noop_output) {
log.err(.server, "no output available for layer surface '{s}'", .{wlr_layer_surface.namespace}); log.err(.server, "no output available for layer surface '{}'", .{wlr_layer_surface.namespace});
c.wlr_layer_surface_v1_close(wlr_layer_surface); wlr_layer_surface.close();
return; return;
} }
log.debug( log.debug(.server, "new layer surface had null output, assigning it to output '{}'", .{
.server, output.wlr_output.name,
"new layer surface had null output, assigning it to output '{s}'", });
.{output.wlr_output.name},
);
wlr_layer_surface.output = output.wlr_output; wlr_layer_surface.output = output.wlr_output;
} }
// The layer surface will add itself to the proper list of the output on map // 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 { 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; return;
}; };
node.data.init(output, wlr_layer_surface); node.data.init(output, wlr_layer_surface);
} }
fn handleNewXwaylandSurface(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleNewXwaylandSurface(listener: *wl.Listener(*wlr.XwaylandSurface), wlr_xwayland_surface: *wlr.XwaylandSurface) void {
const self = @fieldParentPtr(Self, "listen_new_xwayland_surface", listener.?); const self = @fieldParentPtr(Self, "new_xwayland_surface", listener);
const wlr_xwayland_surface = util.voidCast(c.wlr_xwayland_surface, data.?);
if (wlr_xwayland_surface.override_redirect) { if (wlr_xwayland_surface.override_redirect) {
log.debug(.server, "new unmanaged xwayland surface", .{}); log.debug(.server, "new unmanaged xwayland surface", .{});

View file

@ -18,8 +18,11 @@
const Self = @This(); const Self = @This();
const std = @import("std"); 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 log = @import("log.zig");
const util = @import("util.zig"); const util = @import("util.zig");
@ -29,120 +32,89 @@ const Seat = @import("Seat.zig");
const SeatStatus = @import("SeatStatus.zig"); const SeatStatus = @import("SeatStatus.zig");
const Server = @import("Server.zig"); const Server = @import("Server.zig");
const protocol_version = 1; global: *wl.Global,
const implementation = c.struct_zriver_status_manager_v1_interface{ server_destroy: wl.Listener(*wl.Server) = undefined,
.destroy = destroy,
.get_river_output_status = getRiverOutputStatus,
.get_river_seat_status = getRiverSeatStatus,
};
wl_global: *c.wl_global,
listen_display_destroy: c.wl_listener = undefined,
pub fn init(self: *Self, server: *Server) !void { pub fn init(self: *Self, server: *Server) !void {
self.* = .{ self.* = .{
.wl_global = c.wl_global_create( .global = try wl.Global.create(server.wl_server, zriver.StatusManagerV1, 1, *Self, self, bind),
server.wl_display,
&c.zriver_status_manager_v1_interface,
protocol_version,
self,
bind,
) orelse return error.OutOfMemory,
}; };
self.listen_display_destroy.notify = handleDisplayDestroy; self.server_destroy.setNotify(handleServerDestroy);
c.wl_display_add_destroy_listener(server.wl_display, &self.listen_display_destroy); server.wl_server.addDestroyListener(&self.server_destroy);
} }
fn handleDisplayDestroy(wl_listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleServerDestroy(listener: *wl.Listener(*wl.Server), wl_server: *wl.Server) void {
const self = @fieldParentPtr(Self, "listen_display_destroy", wl_listener.?); const self = @fieldParentPtr(Self, "server_destroy", listener);
c.wl_global_destroy(self.wl_global); self.global.destroy();
} }
/// Called when a client binds our global fn bind(client: *wl.Client, self: *Self, version: u32, id: u32) callconv(.C) void {
fn bind(wl_client: ?*c.wl_client, data: ?*c_void, version: u32, id: u32) callconv(.C) void { const status_manager = zriver.StatusManagerV1.create(client, version, id) catch {
const self = util.voidCast(Self, data.?); client.postNoMemory();
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);
log.crit(.river_status, "out of memory", .{}); log.crit(.river_status, "out of memory", .{});
return; 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 { fn handleRequest(
c.wl_resource_destroy(wl_resource); status_manager: *zriver.StatusManagerV1,
} request: zriver.StatusManagerV1.Request,
self: *Self,
fn getRiverOutputStatus( ) void {
wl_client: ?*c.wl_client, switch (request) {
wl_resource: ?*c.wl_resource, .destroy => status_manager.destroy(),
new_id: u32, .get_river_output_status => |req| {
output_wl_resource: ?*c.wl_resource, // ignore if the output is inert
) callconv(.C) void { const wlr_output = wlr.Output.fromWlOutput(req.output) orelse return;
const self = util.voidCast(Self, c.wl_resource_get_user_data(wl_resource).?); const output = @intToPtr(*Output, wlr_output.data);
// 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 { const node = util.gpa.create(std.SinglyLinkedList(OutputStatus).Node) catch {
c.wl_client_post_no_memory(wl_client); status_manager.getClient().postNoMemory();
log.crit(.river_status, "out of memory", .{}); log.crit(.river_status, "out of memory", .{});
return; return;
}; };
const output_status_resource = c.wl_resource_create( const output_status = zriver.OutputStatusV1.create(
wl_client, status_manager.getClient(),
&c.zriver_output_status_v1_interface, status_manager.getVersion(),
protocol_version, req.id,
new_id, ) catch {
) orelse { status_manager.getClient().postNoMemory();
c.wl_client_post_no_memory(wl_client);
util.gpa.destroy(node); util.gpa.destroy(node);
log.crit(.river_status, "out of memory", .{}); log.crit(.river_status, "out of memory", .{});
return; return;
}; };
node.data.init(output, output_status_resource); node.data.init(output, output_status);
output.status_trackers.prepend(node); output.status_trackers.prepend(node);
} },
.get_river_seat_status => |req| {
fn getRiverSeatStatus( // ignore if the seat is inert
wl_client: ?*c.wl_client, const wlr_seat = wlr.Seat.Client.fromWlSeat(req.seat) orelse return;
wl_resource: ?*c.wl_resource, const seat = @intToPtr(*Seat, wlr_seat.seat.data);
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 { const node = util.gpa.create(std.SinglyLinkedList(SeatStatus).Node) catch {
c.wl_client_post_no_memory(wl_client); status_manager.getClient().postNoMemory();
log.crit(.river_status, "out of memory", .{}); log.crit(.river_status, "out of memory", .{});
return; return;
}; };
const seat_status_resource = c.wl_resource_create( const seat_status = zriver.SeatStatusV1.create(
wl_client, status_manager.getClient(),
&c.zriver_seat_status_v1_interface, status_manager.getVersion(),
protocol_version, req.id,
new_id, ) catch {
) orelse { status_manager.getClient().postNoMemory();
c.wl_client_post_no_memory(wl_client);
util.gpa.destroy(node); util.gpa.destroy(node);
log.crit(.river_status, "out of memory", .{}); log.crit(.river_status, "out of memory", .{});
return; return;
}; };
node.data.init(seat, seat_status_resource); node.data.init(seat, seat_status);
seat.status_trackers.prepend(node); seat.status_trackers.prepend(node);
},
}
} }

View file

@ -19,8 +19,10 @@ const Self = @This();
const build_options = @import("build_options"); const build_options = @import("build_options");
const std = @import("std"); 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 log = @import("log.zig");
const util = @import("util.zig"); const util = @import("util.zig");
@ -71,9 +73,9 @@ const State = struct {
}; };
const SavedBuffer = struct { const SavedBuffer = struct {
wlr_client_buffer: *c.wlr_client_buffer, client_buffer: *wlr.ClientBuffer,
box: Box, box: Box,
transform: c.wl_output_transform, transform: wl.Output.Transform,
}; };
/// The implementation of this view /// The implementation of this view
@ -84,10 +86,10 @@ output: *Output,
/// This is from the point where the view is mapped until the surface /// This is from the point where the view is mapped until the surface
/// is destroyed by wlroots. /// 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 /// 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, destroying: bool = false,
/// The double-buffered state of the view /// The double-buffered state of the view
@ -116,7 +118,7 @@ float_box: Box = undefined,
opacity: f32, opacity: f32,
/// Opacity change timer event source /// Opacity change timer event source
opacity_timer: ?*c.wl_event_source = null, opacity_timer: ?*wl.EventSource = null,
draw_borders: bool = true, 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, .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 = undefined };
self.impl.xdg_toplevel.init(self, surface); 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 = undefined };
self.impl.xwayland_view.init(self, surface); self.impl.xwayland_view.init(self, surface);
} else unreachable; } 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. /// Deinit the view, remove it from the view stack and free the memory.
pub fn destroy(self: *Self) void { 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(); self.saved_buffers.deinit();
switch (self.impl) { switch (self.impl) {
.xdg_toplevel => |*xdg_toplevel| xdg_toplevel.deinit(), .xdg_toplevel => |*xdg_toplevel| xdg_toplevel.deinit(),
@ -183,12 +185,12 @@ pub fn applyPending(self: *Self) void {
// and turn the view fully opaque // and turn the view fully opaque
if (!self.current.fullscreen and self.pending.fullscreen) { if (!self.current.fullscreen and self.pending.fullscreen) {
self.pending.target_opacity = 1.0; 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 = .{ self.pending.box = .{
.x = 0, .x = 0,
.y = 0, .y = 0,
.width = @intCast(u32, layout_box.*.width), .width = @intCast(u32, layout_box.width),
.height = @intCast(u32, layout_box.*.height), .height = @intCast(u32, layout_box.height),
}; };
} }
@ -225,20 +227,20 @@ pub fn configure(self: Self) void {
} }
pub fn sendFrameDone(self: Self) void { pub fn sendFrameDone(self: Self) void {
var now: c.timespec = undefined; var now: os.timespec = undefined;
_ = c.clock_gettime(c.CLOCK_MONOTONIC, &now); os.clock_gettime(os.CLOCK_MONOTONIC, &now) catch @panic("CLOCK_MONOTONIC not supported");
c.wlr_surface_send_frame_done(self.wlr_surface.?, &now); self.surface.?.sendFrameDone(&now);
} }
pub fn dropSavedBuffers(self: *Self) void { 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; self.saved_buffers.items.len = 0;
} }
pub fn saveBuffers(self: *Self) void { pub fn saveBuffers(self: *Self) void {
std.debug.assert(self.saved_buffers.items.len == 0); std.debug.assert(self.saved_buffers.items.len == 0);
self.saved_surface_box = self.surface_box; 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 /// If this commit is in response to our configure and the
@ -257,16 +259,14 @@ pub fn notifyConfiguredOrApplyPending(self: *Self) void {
} }
fn saveBuffersIterator( fn saveBuffersIterator(
wlr_surface: ?*c.wlr_surface, surface: *wlr.Surface,
surface_x: c_int, surface_x: c_int,
surface_y: c_int, surface_y: c_int,
data: ?*c_void, saved_buffers: *std.ArrayList(SavedBuffer),
) callconv(.C) void { ) callconv(.C) void {
const saved_buffers = util.voidCast(std.ArrayList(SavedBuffer), data.?); if (surface.buffer) |buffer| {
if (wlr_surface) |surface| {
if (c.wlr_surface_has_buffer(surface)) {
saved_buffers.append(.{ saved_buffers.append(.{
.wlr_client_buffer = surface.buffer, .client_buffer = buffer,
.box = Box{ .box = Box{
.x = surface_x, .x = surface_x,
.y = surface_y, .y = surface_y,
@ -275,8 +275,7 @@ fn saveBuffersIterator(
}, },
.transform = surface.current.transform, .transform = surface.current.transform,
}) catch return; }) catch return;
_ = c.wlr_buffer_lock(&surface.buffer.*.base); _ = buffer.base.lock();
}
} }
} }
@ -291,8 +290,8 @@ pub fn sendToOutput(self: *Self, destination_output: *Output) void {
self.output.sendViewTags(); self.output.sendViewTags();
destination_output.sendViewTags(); destination_output.sendViewTags();
c.wlr_surface_send_leave(self.wlr_surface, self.output.wlr_output); self.surface.?.sendLeave(self.output.wlr_output);
c.wlr_surface_send_enter(self.wlr_surface, destination_output.wlr_output); self.surface.?.sendEnter(destination_output.wlr_output);
self.output = destination_output; self.output = destination_output;
} }
@ -304,20 +303,21 @@ pub fn close(self: Self) void {
} }
} }
pub fn forEachSurface( pub inline fn forEachSurface(
self: Self, self: Self,
iterator: c.wlr_surface_iterator_func_t, comptime T: type,
user_data: ?*c_void, iterator: fn (surface: *wlr.Surface, sx: c_int, sy: c_int, data: T) callconv(.C) void,
user_data: T,
) void { ) void {
switch (self.impl) { switch (self.impl) {
.xdg_toplevel => |xdg_toplevel| xdg_toplevel.forEachSurface(iterator, user_data), .xdg_toplevel => |xdg_toplevel| xdg_toplevel.forEachSurface(T, iterator, user_data),
.xwayland_view => |xwayland_view| xwayland_view.forEachSurface(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 /// Return the surface at output coordinates ox, oy and set sx, sy to the
/// corresponding surface-relative coordinates, if there is a surface. /// 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) { return switch (self.impl) {
.xdg_toplevel => |xdg_toplevel| xdg_toplevel.surfaceAt(ox, oy, sx, sy), .xdg_toplevel => |xdg_toplevel| xdg_toplevel.surfaceAt(ox, oy, sx, sy),
.xwayland_view => |xwayland_view| xwayland_view.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 /// Find and return the view corresponding to a given surface, if any
pub fn fromWlrSurface(wlr_surface: *c.wlr_surface) ?*Self { pub fn fromWlrSurface(surface: *wlr.Surface) ?*Self {
if (c.wlr_surface_is_xdg_surface(wlr_surface)) { if (surface.isXdgSurface()) {
const wlr_xdg_surface = c.wlr_xdg_surface_from_wlr_surface(wlr_surface); const xdg_surface = wlr.XdgSurface.fromWlrSurface(surface);
if (wlr_xdg_surface.*.role == .WLR_XDG_SURFACE_ROLE_TOPLEVEL) { if (xdg_surface.role == .toplevel) {
return util.voidCast(Self, wlr_xdg_surface.*.data.?); return @intToPtr(*Self, xdg_surface.data);
} }
} }
if (build_options.xwayland) { if (build_options.xwayland) {
if (c.wlr_surface_is_xwayland_surface(wlr_surface)) { if (surface.isXWaylandSurface()) {
const wlr_xwayland_surface = c.wlr_xwayland_surface_from_wlr_surface(wlr_surface); const xwayland_surface = wlr.XwaylandSurface.fromWlrSurface(surface);
return util.voidCast(Self, wlr_xwayland_surface.*.data.?); return @intToPtr(*Self, xwayland_surface.data);
} }
} }
return null; return null;
@ -392,7 +392,7 @@ pub fn map(self: *Self) void {
var it = root.server.input_manager.seats.first; var it = root.server.input_manager.seats.first;
while (it) |seat_node| : (it = seat_node.next) seat_node.data.focus(self); 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(); self.output.sendViewTags();
@ -446,22 +446,21 @@ fn incrementOpacity(self: *Self) bool {
/// Destroy a views opacity timer /// Destroy a views opacity timer
fn killOpacityTimer(self: *Self) void { fn killOpacityTimer(self: *Self) void {
if (c.wl_event_source_remove(self.opacity_timer) < 0) unreachable; self.opacity_timer.?.remove();
self.opacity_timer = null; self.opacity_timer = null;
} }
/// Set the timeout on a views opacity timer /// Set the timeout on a views opacity timer
fn armOpacityTimer(self: *Self) void { fn armOpacityTimer(self: *Self) void {
const delta_t = self.output.root.server.config.view_opacity_delta_t; 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) { self.opacity_timer.?.timerUpdate(delta_t) catch |err| {
log.err(.view, "failed to update opacity timer", .{}); log.err(.view, "failed to update opacity timer: {}", .{err});
self.killOpacityTimer(); self.killOpacityTimer();
} };
} }
/// Called by the opacity timer /// Called by the opacity timer
fn handleOpacityTimer(data: ?*c_void) callconv(.C) c_int { fn handleOpacityTimer(self: *Self) callconv(.C) c_int {
const self = util.voidCast(Self, data.?);
if (self.incrementOpacity()) { if (self.incrementOpacity()) {
self.killOpacityTimer(); self.killOpacityTimer();
} else { } else {
@ -472,12 +471,8 @@ fn handleOpacityTimer(data: ?*c_void) callconv(.C) c_int {
/// Create an opacity timer for a view and arm it /// Create an opacity timer for a view and arm it
fn attachOpacityTimer(self: *Self) void { fn attachOpacityTimer(self: *Self) void {
const server = self.output.root.server; const event_loop = self.output.root.server.wl_server.getEventLoop();
self.opacity_timer = c.wl_event_loop_add_timer( self.opacity_timer = event_loop.addTimer(*Self, handleOpacityTimer, self) catch {
c.wl_display_get_event_loop(server.wl_display),
handleOpacityTimer,
self,
) orelse {
log.err(.view, "failed to create opacity timer for view '{}'", .{self.getTitle()}); log.err(.view, "failed to create opacity timer for view '{}'", .{self.getTitle()});
return; return;
}; };

View file

@ -18,8 +18,7 @@
const Self = @This(); const Self = @This();
const std = @import("std"); const std = @import("std");
const wlr = @import("wlroots");
const c = @import("c.zig");
const Box = @import("Box.zig"); const Box = @import("Box.zig");
const View = @import("View.zig"); const View = @import("View.zig");
@ -42,13 +41,14 @@ pub fn close(self: Self) void {
pub fn forEachSurface( pub fn forEachSurface(
self: Self, self: Self,
iterator: c.wlr_surface_iterator_func_t, comptime T: type,
user_data: ?*c_void, iterator: fn (surface: *wlr.Surface, sx: c_int, sy: c_int, data: T) callconv(.C) void,
user_data: T,
) void { ) void {
unreachable; 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; unreachable;
} }

View file

@ -18,8 +18,10 @@
const Self = @This(); const Self = @This();
const std = @import("std"); 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 util = @import("util.zig");
const Box = @import("Box.zig"); const Box = @import("Box.zig");
@ -32,12 +34,12 @@ output: *Output,
parent_box: *const Box, parent_box: *const Box,
/// The corresponding wlroots object /// The corresponding wlroots object
wlr_xdg_popup: *c.wlr_xdg_popup, wlr_xdg_popup: *wlr.XdgPopup,
listen_destroy: c.wl_listener = undefined, destroy: wl.Listener(*wlr.XdgSurface) = undefined,
listen_new_popup: c.wl_listener = 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.* = .{ self.* = .{
.output = output, .output = output,
.parent_box = parent_box, .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 // 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.x -= parent_box.x;
box.y -= parent_box.y; box.y -= parent_box.y;
c.wlr_xdg_popup_unconstrain_from_box(wlr_xdg_popup, &box); wlr_xdg_popup.unconstrainFromBox(&box);
// Setup listeners self.destroy.setNotify(handleDestroy);
self.listen_destroy.notify = handleDestroy; wlr_xdg_popup.base.events.destroy.add(&self.destroy);
c.wl_signal_add(&wlr_xdg_popup.base.*.events.destroy, &self.listen_destroy);
self.listen_new_popup.notify = handleNewPopup; self.new_popup.setNotify(handleNewPopup);
c.wl_signal_add(&wlr_xdg_popup.base.*.events.new_popup, &self.listen_new_popup); wlr_xdg_popup.base.events.new_popup.add(&self.new_popup);
} }
fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleDestroy(listener: *wl.Listener(*wlr.XdgSurface), wlr_xdg_surface: *wlr.XdgSurface) void {
const self = @fieldParentPtr(Self, "listen_destroy", listener.?); const self = @fieldParentPtr(Self, "destroy", listener);
c.wl_list_remove(&self.listen_destroy.link); self.destroy.link.remove();
c.wl_list_remove(&self.listen_new_popup.link); self.new_popup.link.remove();
util.gpa.destroy(self); util.gpa.destroy(self);
} }
/// Called when a new xdg popup is requested by the client /// Called when a new xdg popup is requested by the client
fn handleNewPopup(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), wlr_xdg_popup: *wlr.XdgPopup) void {
const self = @fieldParentPtr(Self, "listen_new_popup", listener.?); const self = @fieldParentPtr(Self, "new_popup", listener);
const wlr_xdg_popup = util.voidCast(c.wlr_xdg_popup, data.?);
// This will free itself on destroy // This will free itself on destroy
var xdg_popup = util.gpa.create(Self) catch { const xdg_popup = util.gpa.create(Self) catch {
c.wl_resource_post_no_memory(wlr_xdg_popup.resource); wlr_xdg_popup.resource.postNoMemory();
log.crit(.server, "out of memory", .{});
return; return;
}; };
xdg_popup.init(self.output, self.parent_box, wlr_xdg_popup); xdg_popup.init(self.output, self.parent_box, wlr_xdg_popup);

View file

@ -18,8 +18,9 @@
const Self = @This(); const Self = @This();
const std = @import("std"); 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 log = @import("log.zig");
const util = @import("util.zig"); const util = @import("util.zig");
@ -33,52 +34,49 @@ const XdgPopup = @import("XdgPopup.zig");
view: *View, view: *View,
/// The corresponding wlroots object /// 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 // Listeners that are always active over the view's lifetime
listen_destroy: c.wl_listener = undefined, destroy: wl.Listener(*wlr.XdgSurface) = undefined,
listen_map: c.wl_listener = undefined, map: wl.Listener(*wlr.XdgSurface) = undefined,
listen_unmap: c.wl_listener = undefined, unmap: wl.Listener(*wlr.XdgSurface) = undefined,
// Listeners that are only active while the view is mapped // Listeners that are only active while the view is mapped
listen_commit: c.wl_listener = undefined, commit: wl.Listener(*wlr.Surface) = undefined,
listen_new_popup: c.wl_listener = undefined, new_popup: wl.Listener(*wlr.XdgPopup) = undefined,
listen_request_fullscreen: c.wl_listener = undefined, request_fullscreen: wl.Listener(*wlr.XdgToplevel.event.SetFullscreen) = undefined,
listen_request_move: c.wl_listener = undefined, request_move: wl.Listener(*wlr.XdgToplevel.event.Move) = undefined,
listen_request_resize: c.wl_listener = undefined, request_resize: wl.Listener(*wlr.XdgToplevel.event.Resize) = undefined,
listen_set_title: c.wl_listener = undefined, set_title: wl.Listener(*wlr.XdgSurface) = undefined,
pub fn init(self: *Self, view: *View, wlr_xdg_surface: *c.wlr_xdg_surface) void { pub fn init(self: *Self, view: *View, xdg_surface: *wlr.XdgSurface) void {
self.* = .{ .view = view, .wlr_xdg_surface = wlr_xdg_surface }; self.* = .{ .view = view, .xdg_surface = xdg_surface };
wlr_xdg_surface.data = self; xdg_surface.data = @ptrToInt(self);
// Add listeners that are active over the view's entire lifetime // Add listeners that are active over the view's entire lifetime
self.listen_destroy.notify = handleDestroy; self.destroy.setNotify(handleDestroy);
c.wl_signal_add(&self.wlr_xdg_surface.events.destroy, &self.listen_destroy); self.xdg_surface.events.destroy.add(&self.destroy);
self.listen_map.notify = handleMap; self.map.setNotify(handleMap);
c.wl_signal_add(&self.wlr_xdg_surface.events.map, &self.listen_map); self.xdg_surface.events.map.add(&self.map);
self.listen_unmap.notify = handleUnmap; self.unmap.setNotify(handleUnmap);
c.wl_signal_add(&self.wlr_xdg_surface.events.unmap, &self.listen_unmap); self.xdg_surface.events.unmap.add(&self.unmap);
} }
pub fn deinit(self: *Self) void { 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 // Remove listeners that are active for the entire lifetime of the view
c.wl_list_remove(&self.listen_destroy.link); self.destroy.link.remove();
c.wl_list_remove(&self.listen_map.link); self.map.link.remove();
c.wl_list_remove(&self.listen_unmap.link); self.unmap.link.remove();
} }
} }
/// Returns true if a configure must be sent to ensure the dimensions of the /// Returns true if a configure must be sent to ensure the dimensions of the
/// pending_box are applied. /// pending_box are applied.
pub fn needsConfigure(self: Self) bool { pub fn needsConfigure(self: Self) bool {
const server_pending = &@field( const server_pending = &self.xdg_surface.role_data.toplevel.server_pending;
self.wlr_xdg_surface,
c.wlr_xdg_surface_union,
).toplevel.*.server_pending;
const state = &self.view.pending; const state = &self.view.pending;
// Checking server_pending is sufficient here since it will be either in // 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. /// Send a configure event, applying the pending state of the view.
pub fn configure(self: Self) void { pub fn configure(self: Self) void {
const toplevel = self.xdg_surface.role_data.toplevel;
const state = &self.view.pending; const state = &self.view.pending;
_ = c.wlr_xdg_toplevel_set_activated(self.wlr_xdg_surface, state.focus != 0); _ = toplevel.setActivated(state.focus != 0);
_ = c.wlr_xdg_toplevel_set_fullscreen(self.wlr_xdg_surface, state.fullscreen); _ = toplevel.setFullscreen(state.fullscreen);
self.view.pending_serial = c.wlr_xdg_toplevel_set_size( self.view.pending_serial = toplevel.setSize(state.box.width, state.box.height);
self.wlr_xdg_surface,
state.box.width,
state.box.height,
);
} }
/// Close the view. This will lead to the unmap and destroy events being sent /// Close the view. This will lead to the unmap and destroy events being sent
pub fn close(self: Self) void { 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, self: Self,
iterator: c.wlr_surface_iterator_func_t, comptime T: type,
user_data: ?*c_void, iterator: fn (surface: *wlr.Surface, sx: c_int, sy: c_int, data: T) callconv(.C) void,
user_data: T,
) void { ) 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 /// Return the surface at output coordinates ox, oy and set sx, sy to the
/// corresponding surface-relative coordinates, if there is a surface. /// 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; const view = self.view;
return c.wlr_xdg_surface_surface_at( return self.xdg_surface.surfaceAt(
self.wlr_xdg_surface,
ox - @intToFloat(f64, view.current.box.x - view.surface_box.x), ox - @intToFloat(f64, view.current.box.x - view.surface_box.x),
oy - @intToFloat(f64, view.current.box.y - view.surface_box.y), oy - @intToFloat(f64, view.current.box.y - view.surface_box.y),
sx, 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. /// Return the current title of the toplevel. May be an empty string.
pub fn getTitle(self: Self) [*:0]const u8 { pub fn getTitle(self: Self) [*:0]const u8 {
const wlr_xdg_toplevel: *c.wlr_xdg_toplevel = @field( return self.xdg_surface.role_data.toplevel.title orelse "NULL";
self.wlr_xdg_surface,
c.wlr_xdg_surface_union,
).toplevel;
return wlr_xdg_toplevel.title orelse "NULL";
} }
/// Return bounds on the dimensions of the toplevel. /// Return bounds on the dimensions of the toplevel.
pub fn getConstraints(self: Self) View.Constraints { 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 .{ return .{
.min_width = std.math.max(state.min_width, View.min_size), .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), .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 /// Called when the xdg surface is destroyed
fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleDestroy(listener: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSurface) void {
const self = @fieldParentPtr(Self, "listen_destroy", listener.?); const self = @fieldParentPtr(Self, "destroy", listener);
self.deinit(); self.deinit();
self.view.wlr_surface = null; self.view.surface = null;
} }
/// Called when the xdg surface is mapped, or ready to display on-screen. /// 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 { fn handleMap(listener: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSurface) void {
const self = @fieldParentPtr(Self, "listen_map", listener.?); const self = @fieldParentPtr(Self, "map", listener);
const view = self.view; const view = self.view;
const root = view.output.root; 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 // Add listeners that are only active while mapped
self.listen_commit.notify = handleCommit; self.commit.setNotify(handleCommit);
c.wl_signal_add(&self.wlr_xdg_surface.surface.*.events.commit, &self.listen_commit); self.xdg_surface.surface.events.commit.add(&self.commit);
self.listen_new_popup.notify = handleNewPopup; self.new_popup.setNotify(handleNewPopup);
c.wl_signal_add(&self.wlr_xdg_surface.events.new_popup, &self.listen_new_popup); self.xdg_surface.events.new_popup.add(&self.new_popup);
self.listen_request_fullscreen.notify = handleRequestFullscreen; self.request_fullscreen.setNotify(handleRequestFullscreen);
c.wl_signal_add(&wlr_xdg_toplevel.events.request_fullscreen, &self.listen_request_fullscreen); toplevel.events.request_fullscreen.add(&self.request_fullscreen);
self.listen_request_move.notify = handleRequestMove; self.request_move.setNotify(handleRequestMove);
c.wl_signal_add(&wlr_xdg_toplevel.events.request_move, &self.listen_request_move); toplevel.events.request_move.add(&self.request_move);
self.listen_request_resize.notify = handleRequestResize; self.request_resize.setNotify(handleRequestResize);
c.wl_signal_add(&wlr_xdg_toplevel.events.request_resize, &self.listen_request_resize); toplevel.events.request_resize.add(&self.request_resize);
self.listen_set_title.notify = handleSetTitle; self.set_title.setNotify(handleSetTitle);
c.wl_signal_add(&wlr_xdg_toplevel.events.set_title, &self.listen_set_title); 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 // Use the view's "natural" size centered on the output as the default
// floating dimensions // floating dimensions
view.float_box.width = @intCast(u32, self.wlr_xdg_surface.geometry.width); view.float_box.width = @intCast(u32, self.xdg_surface.geometry.width);
view.float_box.height = @intCast(u32, self.wlr_xdg_surface.geometry.height); 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) - view.float_box.x = std.math.max(0, @divTrunc(@intCast(i32, view.output.usable_box.width) -
@intCast(i32, view.float_box.width), 2)); @intCast(i32, view.float_box.width), 2));
view.float_box.y = std.math.max(0, @divTrunc(@intCast(i32, view.output.usable_box.height) - view.float_box.y = std.math.max(0, @divTrunc(@intCast(i32, view.output.usable_box.height) -
@intCast(i32, view.float_box.height), 2)); @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 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); (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 // If the toplevel has a parent or has a fixed size make it float
view.current.float = true; view.current.float = true;
view.pending.float = true; view.pending.float = true;
@ -222,38 +213,35 @@ fn handleMap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
break; break;
} }
} else { } else {
_ = c.wlr_xdg_toplevel_set_tiled( _ = toplevel.setTiled(.{ .top = true, .bottom = true, .left = true, .right = true });
self.wlr_xdg_surface,
c.WLR_EDGE_LEFT | c.WLR_EDGE_RIGHT | c.WLR_EDGE_TOP | c.WLR_EDGE_BOTTOM,
);
} }
view.map(); view.map();
} }
/// Called when the surface is unmapped and will no longer be displayed. /// Called when the surface is unmapped and will no longer be displayed.
fn handleUnmap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleUnmap(listener: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSurface) void {
const self = @fieldParentPtr(Self, "listen_unmap", listener.?); const self = @fieldParentPtr(Self, "unmap", listener);
const root = self.view.output.root; const root = self.view.output.root;
self.view.unmap(); self.view.unmap();
// Remove listeners that are only active while mapped // Remove listeners that are only active while mapped
c.wl_list_remove(&self.listen_commit.link); self.commit.link.remove();
c.wl_list_remove(&self.listen_new_popup.link); self.new_popup.link.remove();
c.wl_list_remove(&self.listen_request_fullscreen.link); self.request_fullscreen.link.remove();
c.wl_list_remove(&self.listen_request_move.link); self.request_move.link.remove();
c.wl_list_remove(&self.listen_request_resize.link); self.request_resize.link.remove();
c.wl_list_remove(&self.listen_set_title.link); self.set_title.link.remove();
} }
/// Called when the surface is comitted /// Called when the surface is comitted
fn handleCommit(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleCommit(listener: *wl.Listener(*wlr.Surface), surface: *wlr.Surface) void {
const self = @fieldParentPtr(Self, "listen_commit", listener.?); const self = @fieldParentPtr(Self, "commit", listener);
const view = self.view; const view = self.view;
var wlr_box: c.wlr_box = undefined; var wlr_box: wlr.Box = undefined;
c.wlr_xdg_surface_get_geometry(self.wlr_xdg_surface, &wlr_box); self.xdg_surface.getGeometry(&wlr_box);
const new_box = Box.fromWlrBox(wlr_box); const new_box = Box.fromWlrBox(wlr_box);
// If we have sent a configure changing the size // 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 // Update the stored dimensions of the surface
view.surface_box = new_box; view.surface_box = new_box;
if (s == self.wlr_xdg_surface.configure_serial) { if (s == self.xdg_surface.configure_serial) {
view.notifyConfiguredOrApplyPending(); view.notifyConfiguredOrApplyPending();
} else { } else {
// If the client has not yet acked our configure, we need to send a // 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 /// Called when a new xdg popup is requested by the client
fn handleNewPopup(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), wlr_xdg_popup: *wlr.XdgPopup) void {
const self = @fieldParentPtr(Self, "listen_new_popup", listener.?); const self = @fieldParentPtr(Self, "new_popup", listener);
const wlr_xdg_popup = util.voidCast(c.wlr_xdg_popup, data.?);
// This will free itself on destroy // This will free itself on destroy
var xdg_popup = util.gpa.create(XdgPopup) catch { const xdg_popup = util.gpa.create(XdgPopup) catch {
c.wl_resource_post_no_memory(wlr_xdg_popup.resource); wlr_xdg_popup.resource.postNoMemory();
return; return;
}; };
xdg_popup.init(self.view.output, &self.view.current.box, wlr_xdg_popup); 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 /// 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. /// 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 { fn handleRequestFullscreen(
const self = @fieldParentPtr(Self, "listen_request_fullscreen", listener.?); listener: *wl.Listener(*wlr.XdgToplevel.event.SetFullscreen),
const event = util.voidCast(c.wlr_xdg_toplevel_set_fullscreen_event, data.?); event: *wlr.XdgToplevel.event.SetFullscreen,
) void {
const self = @fieldParentPtr(Self, "request_fullscreen", listener);
self.view.pending.fullscreen = event.fullscreen; self.view.pending.fullscreen = event.fullscreen;
self.view.applyPending(); self.view.applyPending();
} }
/// Called when the client asks to be moved via the cursor, for example when the /// Called when the client asks to be moved via the cursor, for example when the
/// user drags CSD titlebars. /// user drags CSD titlebars.
fn handleRequestMove(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleRequestMove(
const self = @fieldParentPtr(Self, "listen_request_move", listener.?); listener: *wl.Listener(*wlr.XdgToplevel.event.Move),
const event = util.voidCast(c.wlr_xdg_toplevel_move_event, data.?); event: *wlr.XdgToplevel.event.Move,
const seat = util.voidCast(Seat, event.seat.*.seat.*.data.?); ) void {
const self = @fieldParentPtr(Self, "request_move", listener);
const seat = @intToPtr(*Seat, event.seat.seat.data);
seat.cursor.enterMode(.move, self.view); seat.cursor.enterMode(.move, self.view);
} }
/// Called when the client asks to be resized via the cursor. /// Called when the client asks to be resized via the cursor.
fn handleRequestResize(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleRequestResize(listener: *wl.Listener(*wlr.XdgToplevel.event.Resize), event: *wlr.XdgToplevel.event.Resize) void {
const self = @fieldParentPtr(Self, "listen_request_resize", listener.?); const self = @fieldParentPtr(Self, "request_resize", listener);
const event = util.voidCast(c.wlr_xdg_toplevel_resize_event, data.?); const seat = @intToPtr(*Seat, event.seat.seat.data);
const seat = util.voidCast(Seat, event.seat.*.seat.*.data.?);
seat.cursor.enterMode(.resize, self.view); seat.cursor.enterMode(.resize, self.view);
} }
/// Called when the client sets / updates its title /// Called when the client sets / updates its title
fn handleSetTitle(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleSetTitle(listener: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSurface) void {
const self = @fieldParentPtr(Self, "listen_set_title", listener.?); const self = @fieldParentPtr(Self, "set_title", listener);
// Send title to all status listeners attached to a seat which focuses this view // 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; var seat_it = self.view.output.root.server.input_manager.seats.first;

View file

@ -18,8 +18,9 @@
const Self = @This(); const Self = @This();
const std = @import("std"); 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 util = @import("util.zig");
const Box = @import("Box.zig"); const Box = @import("Box.zig");
@ -28,39 +29,38 @@ const Root = @import("Root.zig");
root: *Root, root: *Root,
/// The corresponding wlroots object /// 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 // Listeners that are always active over the view's lifetime
liseten_request_configure: c.wl_listener = undefined, request_configure: wl.Listener(*wlr.XwaylandSurface.event.Configure) = undefined,
listen_destroy: c.wl_listener = undefined, destroy: wl.Listener(*wlr.XwaylandSurface) = undefined,
listen_map: c.wl_listener = undefined, map: wl.Listener(*wlr.XwaylandSurface) = undefined,
listen_unmap: c.wl_listener = undefined, unmap: wl.Listener(*wlr.XwaylandSurface) = undefined,
// Listeners that are only active while the view is mapped // 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 { pub fn init(self: *Self, root: *Root, xwayland_surface: *wlr.XwaylandSurface) void {
self.* = .{ .root = root, .wlr_xwayland_surface = wlr_xwayland_surface }; self.* = .{ .root = root, .xwayland_surface = xwayland_surface };
// Add listeners that are active over the view's entire lifetime // Add listeners that are active over the view's entire lifetime
self.liseten_request_configure.notify = handleRequestConfigure; self.request_configure.setNotify(handleRequestConfigure);
c.wl_signal_add(&wlr_xwayland_surface.events.request_configure, &self.liseten_request_configure); xwayland_surface.events.request_configure.add(&self.request_configure);
self.listen_destroy.notify = handleDestroy; self.destroy.setNotify(handleDestroy);
c.wl_signal_add(&wlr_xwayland_surface.events.destroy, &self.listen_destroy); xwayland_surface.events.destroy.add(&self.destroy);
self.listen_map.notify = handleMap; self.map.setNotify(handleMap);
c.wl_signal_add(&wlr_xwayland_surface.events.map, &self.listen_map); xwayland_surface.events.map.add(&self.map);
self.listen_unmap.notify = handleUnmap; self.unmap.setNotify(handleUnmap);
c.wl_signal_add(&wlr_xwayland_surface.events.unmap, &self.listen_unmap); xwayland_surface.events.unmap.add(&self.unmap);
} }
/// Return the surface at output coordinates ox, oy and set sx, sy to the /// Return the surface at output coordinates ox, oy and set sx, sy to the
/// corresponding surface-relative coordinates, if there is a surface. /// 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 c.wlr_surface_surface_at( return self.xwayland_surface.surface.?.surfaceAt(
self.wlr_xwayland_surface.surface,
ox - @intToFloat(f64, self.view.current_box.x), ox - @intToFloat(f64, self.view.current_box.x),
oy - @intToFloat(f64, self.view.current_box.y), oy - @intToFloat(f64, self.view.current_box.y),
sx, 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 { fn handleRequestConfigure(
const self = @fieldParentPtr(Self, "liseten_request_configure", listener.?); listener: *wl.Listener(*wlr.XwaylandSurface.event.Configure),
const wlr_xwayland_surface_configure_event = util.voidCast(c.wlr_xwayland_surface_configure_event, data.?); event: *wlr.XwaylandSurface.event.Configure,
c.wlr_xwayland_surface_configure( ) void {
self.wlr_xwayland_surface, const self = @fieldParentPtr(Self, "request_configure", listener);
wlr_xwayland_surface_configure_event.x, self.xwayland_surface.configure(event.x, event.y, event.width, event.height);
wlr_xwayland_surface_configure_event.y,
wlr_xwayland_surface_configure_event.width,
wlr_xwayland_surface_configure_event.height,
);
} }
/// Called when the xwayland surface is destroyed /// Called when the xwayland surface is destroyed
fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleDestroy(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *wlr.XwaylandSurface) void {
const self = @fieldParentPtr(Self, "listen_destroy", listener.?); const self = @fieldParentPtr(Self, "destroy", listener);
// Remove listeners that are active for the entire lifetime of the view // Remove listeners that are active for the entire lifetime of the view
c.wl_list_remove(&self.listen_destroy.link); self.destroy.link.remove();
c.wl_list_remove(&self.listen_map.link); self.map.link.remove();
c.wl_list_remove(&self.listen_unmap.link); self.unmap.link.remove();
// Deallocate the node // Deallocate the node
const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); 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. /// 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 { fn handleMap(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *wlr.XwaylandSurface) void {
const self = @fieldParentPtr(Self, "listen_map", listener.?); const self = @fieldParentPtr(Self, "map", listener);
const root = self.root; const root = self.root;
// Add self to the list of unmanaged views in the 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); root.xwayland_unmanaged_views.prepend(node);
// Add listeners that are only active while mapped // Add listeners that are only active while mapped
self.listen_commit.notify = handleCommit; self.commit.setNotify(handleCommit);
c.wl_signal_add(&self.wlr_xwayland_surface.surface.*.events.commit, &self.listen_commit); xwayland_surface.surface.?.events.commit.add(&self.commit);
// TODO: handle keyboard focus // 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. /// Called when the surface is unmapped and will no longer be displayed.
fn handleUnmap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleUnmap(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *wlr.XwaylandSurface) void {
const self = @fieldParentPtr(Self, "listen_unmap", listener.?); const self = @fieldParentPtr(Self, "unmap", listener);
// Remove self from the list of unmanged views in the root // Remove self from the list of unmanged views in the root
const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self);
self.root.xwayland_unmanaged_views.remove(node); self.root.xwayland_unmanaged_views.remove(node);
// Remove listeners that are only active while mapped // Remove listeners that are only active while mapped
c.wl_list_remove(&self.listen_commit.link); self.commit.link.remove();
// TODO: return focus // TODO: return focus
} }
/// Called when the surface is comitted /// Called when the surface is comitted
fn handleCommit(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleCommit(listener: *wl.Listener(*wlr.Surface), surface: *wlr.Surface) void {
const self = @fieldParentPtr(Self, "listen_commit", listener.?); const self = @fieldParentPtr(Self, "commit", listener);
// TODO: check if the surface has moved for damage tracking // TODO: check if the surface has moved for damage tracking
} }

View file

@ -18,8 +18,8 @@
const Self = @This(); const Self = @This();
const std = @import("std"); const std = @import("std");
const wlr = @import("wlroots");
const c = @import("c.zig"); const wl = @import("wayland").server.wl;
const Box = @import("Box.zig"); const Box = @import("Box.zig");
const View = @import("View.zig"); const View = @import("View.zig");
@ -30,58 +30,57 @@ const XdgPopup = @import("XdgPopup.zig");
view: *View, view: *View,
/// The corresponding wlroots object /// 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 // Listeners that are always active over the view's lifetime
listen_destroy: c.wl_listener = undefined, destroy: wl.Listener(*wlr.XwaylandSurface) = undefined,
listen_map: c.wl_listener = undefined, map: wl.Listener(*wlr.XwaylandSurface) = undefined,
listen_unmap: c.wl_listener = undefined, unmap: wl.Listener(*wlr.XwaylandSurface) = undefined,
listen_title: c.wl_listener = undefined, title: wl.Listener(*wlr.XwaylandSurface) = undefined,
// Listeners that are only active while the view is mapped // 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 { pub fn init(self: *Self, view: *View, xwayland_surface: *wlr.XwaylandSurface) void {
self.* = .{ .view = view, .wlr_xwayland_surface = wlr_xwayland_surface }; self.* = .{ .view = view, .xwayland_surface = xwayland_surface };
wlr_xwayland_surface.data = self; xwayland_surface.data = @ptrToInt(self);
// Add listeners that are active over the view's entire lifetime // Add listeners that are active over the view's entire lifetime
self.listen_destroy.notify = handleDestroy; self.destroy.setNotify(handleDestroy);
c.wl_signal_add(&self.wlr_xwayland_surface.events.destroy, &self.listen_destroy); self.xwayland_surface.events.destroy.add(&self.destroy);
self.listen_map.notify = handleMap; self.map.setNotify(handleMap);
c.wl_signal_add(&self.wlr_xwayland_surface.events.map, &self.listen_map); self.xwayland_surface.events.map.add(&self.map);
self.listen_unmap.notify = handleUnmap; self.unmap.setNotify(handleUnmap);
c.wl_signal_add(&self.wlr_xwayland_surface.events.unmap, &self.listen_unmap); self.xwayland_surface.events.unmap.add(&self.unmap);
self.listen_title.notify = handleTitle; self.title.setNotify(handleTitle);
c.wl_signal_add(&self.wlr_xwayland_surface.events.set_title, &self.listen_title); self.xwayland_surface.events.set_title.add(&self.title);
} }
pub fn deinit(self: *Self) void { 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 // Remove listeners that are active for the entire lifetime of the view
c.wl_list_remove(&self.listen_destroy.link); self.destroy.link.remove();
c.wl_list_remove(&self.listen_map.link); self.map.link.remove();
c.wl_list_remove(&self.listen_unmap.link); self.unmap.link.remove();
c.wl_list_remove(&self.listen_title.link); self.title.link.remove();
} }
} }
pub fn needsConfigure(self: Self) bool { pub fn needsConfigure(self: Self) bool {
return self.wlr_xwayland_surface.x != self.view.pending.box.x or return self.xwayland_surface.x != self.view.pending.box.x or
self.wlr_xwayland_surface.y != self.view.pending.box.y or self.xwayland_surface.y != self.view.pending.box.y or
self.wlr_xwayland_surface.width != self.view.pending.box.width or self.xwayland_surface.width != self.view.pending.box.width or
self.wlr_xwayland_surface.height != self.view.pending.box.height; self.xwayland_surface.height != self.view.pending.box.height;
} }
/// Apply pending state /// Apply pending state
pub fn configure(self: Self) void { pub fn configure(self: Self) void {
const state = &self.view.pending; const state = &self.view.pending;
c.wlr_xwayland_surface_set_fullscreen(self.wlr_xwayland_surface, state.fullscreen); self.xwayland_surface.setFullscreen(state.fullscreen);
c.wlr_xwayland_surface_configure( self.xwayland_surface.configure(
self.wlr_xwayland_surface,
@intCast(i16, state.box.x), @intCast(i16, state.box.x),
@intCast(i16, state.box.y), @intCast(i16, state.box.y),
@intCast(u16, state.box.width), @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 /// Close the view. This will lead to the unmap and destroy events being sent
pub fn close(self: Self) void { 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. /// Iterate over all surfaces of the xwayland view.
pub fn forEachSurface( pub fn forEachSurface(
self: Self, self: Self,
iterator: c.wlr_surface_iterator_func_t, comptime T: type,
user_data: ?*c_void, iterator: fn (surface: *wlr.Surface, sx: c_int, sy: c_int, data: T) callconv(.C) void,
data: T,
) void { ) 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 /// Return the surface at output coordinates ox, oy and set sx, sy to the
/// corresponding surface-relative coordinates, if there is a surface. /// 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 c.wlr_surface_surface_at( return self.xwayland_surface.surface.?.surfaceAt(
self.wlr_xwayland_surface.surface,
ox - @intToFloat(f64, self.view.current.box.x), ox - @intToFloat(f64, self.view.current.box.x),
oy - @intToFloat(f64, self.view.current.box.y), oy - @intToFloat(f64, self.view.current.box.y),
sx, 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 /// Get the current title of the xwayland surface. May be an empty string
pub fn getTitle(self: Self) [*:0]const u8 { 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 /// Return bounds on the dimensions of the view
pub fn getConstraints(self: Self) View.Constraints { 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, .min_width = View.min_size,
.max_width = std.math.maxInt(u32), .max_width = std.math.maxInt(u32),
.min_height = View.min_size, .min_height = View.min_size,
@ -143,39 +142,42 @@ pub fn getConstraints(self: Self) View.Constraints {
} }
/// Called when the xwayland surface is destroyed /// Called when the xwayland surface is destroyed
fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleDestroy(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *wlr.XwaylandSurface) void {
const self = @fieldParentPtr(Self, "listen_destroy", listener.?); const self = @fieldParentPtr(Self, "destroy", listener);
self.deinit(); self.deinit();
self.view.wlr_surface = null; self.view.surface = null;
} }
/// Called when the xwayland surface is mapped, or ready to display on-screen. /// 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 { fn handleMap(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *wlr.XwaylandSurface) void {
const self = @fieldParentPtr(Self, "listen_map", listener.?); const self = @fieldParentPtr(Self, "map", listener);
const view = self.view; const view = self.view;
const root = view.output.root; const root = view.output.root;
// Add listeners that are only active while mapped // Add listeners that are only active while mapped
self.listen_commit.notify = handleCommit; self.commit.setNotify(handleCommit);
c.wl_signal_add(&self.wlr_xwayland_surface.surface.*.events.commit, &self.listen_commit); 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 // Use the view's "natural" size centered on the output as the default
// floating dimensions // floating dimensions
view.float_box.width = self.wlr_xwayland_surface.width; view.float_box.width = self.xwayland_surface.width;
view.float_box.height = self.wlr_xwayland_surface.height; view.float_box.height = self.xwayland_surface.height;
view.float_box.x = std.math.max(0, @divTrunc(@intCast(i32, view.output.usable_box.width) - view.float_box.x = std.math.max(0, @divTrunc(@intCast(i32, view.output.usable_box.width) -
@intCast(i32, view.float_box.width), 2)); @intCast(i32, view.float_box.width), 2));
view.float_box.y = std.math.max(0, @divTrunc(@intCast(i32, view.output.usable_box.height) - view.float_box.y = std.math.max(0, @divTrunc(@intCast(i32, view.output.usable_box.height) -
@intCast(i32, view.float_box.height), 2)); @intCast(i32, view.float_box.height), 2));
const size_hints = self.wlr_xwayland_surface.size_hints; const has_fixed_size = if (self.xwayland_surface.size_hints) |size_hints|
const has_fixed_size = size_hints.*.min_width != 0 and size_hints.*.min_height != 0 and 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); (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"; 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 // If the toplevel has a parent or has a fixed size make it float
view.current.float = true; view.current.float = true;
view.pending.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. /// Called when the surface is unmapped and will no longer be displayed.
fn handleUnmap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleUnmap(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *wlr.XwaylandSurface) void {
const self = @fieldParentPtr(Self, "listen_unmap", listener.?); const self = @fieldParentPtr(Self, "unmap", listener);
self.view.unmap(); self.view.unmap();
// Remove listeners that are only active while mapped // 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 /// Called when the surface is comitted
/// TODO: check for unexpected change in size and react as needed /// TODO: check for unexpected change in size and react as needed
fn handleCommit(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleCommit(listener: *wl.Listener(*wlr.Surface), surface: *wlr.Surface) void {
const self = @fieldParentPtr(Self, "listen_commit", listener.?); const self = @fieldParentPtr(Self, "commit", listener);
const view = self.view; const view = self.view;
view.surface_box = Box{ view.surface_box = Box{
.x = 0, .x = 0,
.y = 0, .y = 0,
.width = @intCast(u32, self.wlr_xwayland_surface.surface.*.current.width), .width = @intCast(u32, surface.current.width),
.height = @intCast(u32, self.wlr_xwayland_surface.surface.*.current.height), .height = @intCast(u32, surface.current.height),
}; };
// See comment in XwaylandView.configure() // 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 /// Called then the window updates its title
fn handleTitle(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleTitle(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *wlr.XwaylandSurface) void {
const self = @fieldParentPtr(Self, "listen_title", listener.?); const self = @fieldParentPtr(Self, "title", listener);
// Send title to all status listeners attached to a seat which focuses this view // 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; var seat_it = self.view.output.root.server.input_manager.seats.first;

View file

@ -17,63 +17,10 @@
pub usingnamespace @cImport({ pub usingnamespace @cImport({
@cDefine("_POSIX_C_SOURCE", "200809L"); @cDefine("_POSIX_C_SOURCE", "200809L");
@cDefine("WLR_USE_UNSTABLE", {});
@cInclude("stdlib.h"); @cInclude("stdlib.h");
@cInclude("time.h");
@cInclude("unistd.h"); @cInclude("unistd.h");
@cInclude("linux/input-event-codes.h"); @cInclude("linux/input-event-codes.h");
@cInclude("libevdev/libevdev.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;

View file

@ -17,8 +17,6 @@
const std = @import("std"); const std = @import("std");
const c = @import("../c.zig");
const Error = @import("../command.zig").Error; const Error = @import("../command.zig").Error;
const Seat = @import("../Seat.zig"); const Seat = @import("../Seat.zig");
@ -30,5 +28,5 @@ pub fn exit(
out: *?[]const u8, out: *?[]const u8,
) Error!void { ) Error!void {
if (args.len > 1) return Error.TooManyArguments; if (args.len > 1) return Error.TooManyArguments;
c.wl_display_terminate(seat.input_manager.server.wl_display); seat.input_manager.server.wl_server.terminate();
} }

View file

@ -16,6 +16,9 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const mem = std.mem;
const wlr = @import("wlroots");
const xkb = @import("xkbcommon");
const c = @import("../c.zig"); const c = @import("../c.zig");
const util = @import("../util.zig"); const util = @import("../util.zig");
@ -25,21 +28,6 @@ const Mapping = @import("../Mapping.zig");
const PointerMapping = @import("../PointerMapping.zig"); const PointerMapping = @import("../PointerMapping.zig");
const Seat = @import("../Seat.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 /// Create a new mapping for a given mode
/// ///
/// Example: /// 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. /// 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| { 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; 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. /// 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| { 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; return i;
} }
} }
@ -166,32 +163,41 @@ fn parseEventCode(allocator: *std.mem.Allocator, event_code_str: []const u8, out
return @intCast(u32, ret); 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); const keysym_name = try std.cstr.addNullByte(allocator, keysym_str);
defer allocator.free(keysym_name); defer allocator.free(keysym_name);
const keysym = c.xkb_keysym_from_name(keysym_name, .XKB_KEYSYM_CASE_INSENSITIVE); const keysym = xkb.Keysym.fromName(keysym_name, .case_insensitive);
if (keysym == c.XKB_KEY_NoSymbol) { if (keysym == .NoSymbol) {
out.* = try std.fmt.allocPrint( out.* = try std.fmt.allocPrint(allocator, "invalid keysym '{}'", .{keysym_str});
allocator,
"invalid keysym '{}'",
.{keysym_str},
);
return Error.Other; return Error.Other;
} }
return keysym; 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 it = std.mem.split(modifiers_str, "+");
var modifiers: u32 = 0; var modifiers = wlr.Keyboard.ModifierMask{};
while (it.next()) |mod_name| { outer: while (it.next()) |mod_name| {
for (modifier_names) |def| { 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)) { if (std.mem.eql(u8, def.name, mod_name)) {
modifiers |= def.modifier; @field(modifiers, def.field_name) = true;
break; continue :outer;
}
} }
} else {
out.* = try std.fmt.allocPrint( out.* = try std.fmt.allocPrint(
allocator, allocator,
"invalid modifier '{}'", "invalid modifier '{}'",
@ -199,7 +205,6 @@ fn parseModifiers(allocator: *std.mem.Allocator, modifiers_str: []const u8, out:
); );
return Error.Other; return Error.Other;
} }
}
return modifiers; return modifiers;
} }

View file

@ -17,8 +17,6 @@
const std = @import("std"); const std = @import("std");
const c = @import("../c.zig");
const Error = @import("../command.zig").Error; const Error = @import("../command.zig").Error;
const Seat = @import("../Seat.zig"); const Seat = @import("../Seat.zig");
@ -37,6 +35,6 @@ pub fn setRepeat(
var it = seat.keyboards.first; var it = seat.keyboards.first;
while (it) |node| : (it = node.next) { 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);
} }
} }

View file

@ -16,6 +16,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const wlr = @import("wlroots");
const c = @import("c.zig"); const c = @import("c.zig");
const log = @import("log.zig"); const log = @import("log.zig");
@ -93,14 +94,11 @@ pub fn main() anyerror!void {
} }
} }
c.wlr_log_init( wlr.log.init(switch (log.level) {
switch (log.level) { .debug => .debug,
.debug => .WLR_DEBUG, .notice, .info => .info,
.notice, .info => .WLR_INFO, .warn, .err, .crit, .alert, .emerg => .err,
.warn, .err, .crit, .alert, .emerg => .WLR_ERROR, });
},
null,
);
log.info(.server, "initializing", .{}); log.info(.server, "initializing", .{});
@ -136,7 +134,7 @@ pub fn main() anyerror!void {
log.info(.server, "running...", .{}); log.info(.server, "running...", .{});
server.run(); server.wl_server.run();
log.info(.server, "shutting down", .{}); log.info(.server, "shutting down", .{});
} }

View file

@ -17,8 +17,11 @@
const build_options = @import("build_options"); const build_options = @import("build_options");
const std = @import("std"); 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 log = @import("log.zig");
const util = @import("util.zig"); const util = @import("util.zig");
@ -36,24 +39,21 @@ const SurfaceRenderData = struct {
output_x: i32, output_x: i32,
output_y: i32, output_y: i32,
when: *c.timespec, when: *os.timespec,
opacity: f32, opacity: f32,
}; };
pub fn renderOutput(output: *Output) void { pub fn renderOutput(output: *Output) void {
const config = &output.root.server.config; const config = &output.root.server.config;
const wlr_renderer = output.getRenderer(); const renderer = output.wlr_output.backend.getRenderer().?;
var now: c.timespec = undefined; var now: os.timespec = undefined;
_ = c.clock_gettime(c.CLOCK_MONOTONIC, &now); os.clock_gettime(os.CLOCK_MONOTONIC, &now) catch unreachable;
// wlr_output_attach_render makes the OpenGL context current. output.wlr_output.attachRender(null) catch return;
if (!c.wlr_output_attach_render(output.wlr_output, null)) return;
// Begin the renderer (calls glViewport and some other GL sanity checks) renderer.begin(output.wlr_output.width, output.wlr_output.height);
// 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);
// Find the first visible fullscreen view in the stack if there is one // 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); 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 we have a fullscreen view to render, render it.
if (fullscreen_view) |view| { if (fullscreen_view) |view| {
// Always clear with solid black for fullscreen // 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); renderView(output.*, view, &now);
if (build_options.xwayland) renderXwaylandUnmanaged(output.*, &now); if (build_options.xwayland) renderXwaylandUnmanaged(output.*, &now);
} else { } else {
// No fullscreen view, so render normal layers/views // 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.getLayer(.background).*, &now, .toplevels);
renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], &now, .toplevels); renderLayer(output.*, output.getLayer(.bottom).*, &now, .toplevels);
// The first view in the list is "on top" so iterate in reverse. // 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); 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); 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.getLayer(.background).*, &now, .popups);
renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], &now, .popups); renderLayer(output.*, output.getLayer(.bottom).*, &now, .popups);
renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_TOP], &now, .popups); renderLayer(output.*, output.getLayer(.top).*, &now, .popups);
} }
// The overlay layer is rendered in both fullscreen and normal cases // 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.getLayer(.overlay).*, &now, .toplevels);
renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], &now, .popups); renderLayer(output.*, output.getLayer(.overlay).*, &now, .popups);
renderDragIcons(output.*, &now); 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 // reason, wlroots provides a software fallback, which we ask it to render
// here. wlr_cursor handles configuring hardware vs software cursors for you, // here. wlr_cursor handles configuring hardware vs software cursors for you,
// and this function is a no-op when hardware cursors are in use. // 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 // Conclude rendering and swap the buffers, showing the final frame
// on-screen. // on-screen.
c.wlr_renderer_end(wlr_renderer); renderer.end();
// TODO(wlroots): remove this with the next release. It is here due to // TODO(wlroots): remove this with the next release. It is here due to
// a wlroots bug in the screencopy damage implementation // a wlroots bug in the screencopy damage implementation
{ {
var w: c_int = undefined; var w: c_int = undefined;
var h: c_int = undefined; var h: c_int = undefined;
c.wlr_output_transformed_resolution(output.wlr_output, &w, &h); output.wlr_output.transformedResolution(&w, &h);
var damage: c.pixman_region32_t = undefined; var damage: pixman.Region32 = undefined;
c.pixman_region32_init(&damage); damage.init();
_ = c.pixman_region32_union_rect(&damage, &damage, 0, 0, @intCast(c_uint, w), @intCast(c_uint, h)); _ = damage.unionRect(&damage, 0, 0, @intCast(c_uint, w), @intCast(c_uint, h));
c.wlr_output_set_damage(output.wlr_output, &damage); output.wlr_output.setDamage(&damage);
} }
// TODO: handle failure // TODO: handle failure
if (!c.wlr_output_commit(output.wlr_output)) { output.wlr_output.commit() catch
log.err(.render, "wlr_output_commit failed for {}", .{output.wlr_output.name}); log.err(.render, "output commit failed for {}", .{output.wlr_output.name});
}
} }
fn renderFilter(view: *View, filter_tags: u32) bool { fn renderFilter(view: *View, filter_tags: u32) bool {
@ -151,7 +150,7 @@ fn renderFilter(view: *View, filter_tags: u32) bool {
fn renderLayer( fn renderLayer(
output: Output, output: Output,
layer: std.TailQueue(LayerSurface), layer: std.TailQueue(LayerSurface),
now: *c.timespec, now: *os.timespec,
role: enum { toplevels, popups }, role: enum { toplevels, popups },
) void { ) void {
var it = layer.first; var it = layer.first;
@ -165,13 +164,13 @@ fn renderLayer(
.opacity = 1.0, .opacity = 1.0,
}; };
switch (role) { switch (role) {
.toplevels => c.wlr_surface_for_each_surface( .toplevels => layer_surface.wlr_layer_surface.surface.forEachSurface(
layer_surface.wlr_layer_surface.surface, *SurfaceRenderData,
renderSurfaceIterator, renderSurfaceIterator,
&rdata, &rdata,
), ),
.popups => c.wlr_layer_surface_v1_for_each_popup( .popups => layer_surface.wlr_layer_surface.forEachPopup(
layer_surface.wlr_layer_surface, *SurfaceRenderData,
renderSurfaceIterator, renderSurfaceIterator,
&rdata, &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 // If we have saved buffers, we are in the middle of a transaction
// and need to render those buffers until the transaction is complete. // and need to render those buffers until the transaction is complete.
if (view.saved_buffers.items.len != 0) { if (view.saved_buffers.items.len != 0) {
for (view.saved_buffers.items) |saved_buffer| for (view.saved_buffers.items) |saved_buffer|
renderTexture( renderTexture(
output, 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, .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, .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, .opacity = view.opacity,
}; };
view.forEachSurface(renderSurfaceIterator, &rdata); view.forEachSurface(*SurfaceRenderData, renderSurfaceIterator, &rdata);
} }
} }
fn renderDragIcons(output: Output, now: *c.timespec) void { fn renderDragIcons(output: Output, now: *os.timespec) void {
const output_box = c.wlr_output_layout_get_box(output.root.wlr_output_layout, output.wlr_output); const output_box = output.root.output_layout.getBox(output.wlr_output).?;
var it = output.root.drag_icons.first; var it = output.root.drag_icons.first;
while (it) |node| : (it = node.next) { while (it) |node| : (it = node.next) {
@ -221,70 +220,67 @@ fn renderDragIcons(output: Output, now: *c.timespec) void {
var rdata = SurfaceRenderData{ var rdata = SurfaceRenderData{
.output = &output, .output = &output,
.output_x = @floatToInt(i32, drag_icon.seat.cursor.wlr_cursor.x) + .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) + .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, .when = now,
.opacity = 1.0, .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 /// Render all xwayland unmanaged windows that appear on the output
fn renderXwaylandUnmanaged(output: Output, now: *c.timespec) void { fn renderXwaylandUnmanaged(output: Output, now: *os.timespec) void {
const output_box = c.wlr_output_layout_get_box(output.root.wlr_output_layout, output.wlr_output); const output_box = output.root.output_layout.getBox(output.wlr_output).?;
var it = output.root.xwayland_unmanaged_views.first; var it = output.root.xwayland_unmanaged_views.first;
while (it) |node| : (it = node.next) { 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{ var rdata = SurfaceRenderData{
.output = &output, .output = &output,
.output_x = wlr_xwayland_surface.x - output_box.*.x, .output_x = xwayland_surface.x - output_box.x,
.output_y = wlr_xwayland_surface.y - output_box.*.y, .output_y = xwayland_surface.y - output_box.y,
.when = now, .when = now,
.opacity = 1.0, .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 /// This function is passed to wlroots to render each surface during iteration
fn renderSurfaceIterator( fn renderSurfaceIterator(
surface: ?*c.wlr_surface, surface: *wlr.Surface,
surface_x: c_int, surface_x: c_int,
surface_y: c_int, surface_y: c_int,
data: ?*c_void, rdata: *SurfaceRenderData,
) callconv(.C) void { ) callconv(.C) void {
const rdata = util.voidCast(SurfaceRenderData, data.?);
renderTexture( renderTexture(
rdata.output.*, rdata.output.*,
c.wlr_surface_get_texture(surface), surface.getTexture() orelse return,
.{ .{
.x = rdata.output_x + surface_x, .x = rdata.output_x + surface_x,
.y = rdata.output_y + surface_y, .y = rdata.output_y + surface_y,
.width = surface.?.current.width, .width = surface.current.width,
.height = surface.?.current.height, .height = surface.current.height,
}, },
surface.?.current.transform, surface.current.transform,
rdata.opacity, 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 /// Render the given texture at the given box, taking the scale and transform
/// of the output into account. /// of the output into account.
fn renderTexture( fn renderTexture(
output: Output, output: Output,
wlr_texture: ?*c.wlr_texture, texture: *wlr.Texture,
wlr_box: c.wlr_box, wlr_box: wlr.Box,
transform: c.wl_output_transform, transform: wl.Output.Transform,
opacity: f32, opacity: f32,
) void { ) void {
const texture = wlr_texture orelse return;
var box = wlr_box; var box = wlr_box;
// Scale the box to the output's current scaling factor // Scale the box to the output's current scaling factor
@ -295,15 +291,16 @@ fn renderTexture(
// prepares an orthographic projection and multiplies the necessary // prepares an orthographic projection and multiplies the necessary
// transforms to produce a model-view-projection matrix. // transforms to produce a model-view-projection matrix.
var matrix: [9]f32 = undefined; var matrix: [9]f32 = undefined;
const inverted = c.wlr_output_transform_invert(transform); const inverted = wlr.Output.transformInvert(transform);
c.wlr_matrix_project_box(&matrix, &box, inverted, 0.0, &output.wlr_output.transform_matrix); 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 // This takes our matrix, the texture, and an alpha, and performs the actual
// rendering on the GPU. // 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 config = &output.root.server.config;
const color = if (view.current.focus != 0) &config.border_color_focused else &config.border_color_unfocused; const color = if (view.current.focus != 0) &config.border_color_focused else &config.border_color_unfocused;
const border_width = config.border_width; 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 { fn renderRect(output: Output, box: Box, color: *const [4]f32) void {
var wlr_box = box.toWlrBox(); var wlr_box = box.toWlrBox();
scaleBox(&wlr_box, output.wlr_output.scale); scaleBox(&wlr_box, output.wlr_output.scale);
c.wlr_render_rect( output.wlr_output.backend.getRenderer().?.renderRect(
output.getRenderer(),
&wlr_box, &wlr_box,
color, color,
&output.wlr_output.transform_matrix, &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. /// 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.x = @floatToInt(c_int, @round(@intToFloat(f64, box.x) * scale));
box.y = @floatToInt(c_int, @round(@intToFloat(f64, box.y) * scale)); box.y = @floatToInt(c_int, @round(@intToFloat(f64, box.y) * scale));
box.width = scaleLength(box.width, box.x, scale); box.width = scaleLength(box.width, box.x, scale);

View file

@ -19,10 +19,10 @@ const std = @import("std");
const wayland = @import("wayland"); const wayland = @import("wayland");
const wl = wayland.client.wl; const wl = wayland.client.wl;
const river = wayland.client.river; const zriver = wayland.client.zriver;
const SetupContext = struct { const SetupContext = struct {
river_control: ?*river.ControlV1 = null, river_control: ?*zriver.ControlV1 = null,
seat: ?*wl.Seat = 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 { fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *SetupContext) void {
switch (event) { switch (event) {
.global => |global| { .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; context.seat = registry.bind(global.name, wl.Seat, 1) catch return;
} else if (std.cstr.cmp(global.interface, river.ControlV1.interface().name) == 0) { } else if (std.cstr.cmp(global.interface, zriver.ControlV1.getInterface().name) == 0) {
context.river_control = registry.bind(global.name, river.ControlV1, 1) catch return; context.river_control = registry.bind(global.name, zriver.ControlV1, 1) catch return;
} }
}, },
.global_remove => {}, .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) { switch (event) {
.success => |success| { .success => |success| {
if (std.mem.len(success.output) > 0) { if (std.mem.len(success.output) > 0) {