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"]
path = deps/zig-wayland
url = https://github.com/ifreund/zig-wayland
[submodule "deps/zig-pixman"]
path = deps/zig-pixman
url = https://github.com/ifreund/zig-pixman
[submodule "deps/zig-xkbcommon"]
path = deps/zig-xkbcommon
url = https://github.com/ifreund/zig-xkbcommon
[submodule "deps/zig-wlroots"]
path = deps/zig-wlroots
url = https://github.com/swaywm/zig-wlroots

View file

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

179
build.zig
View file

@ -1,16 +1,10 @@
const std = @import("std");
const zbs = std.build;
const ScanProtocolsStep = @import("deps/zig-wayland/build.zig").ScanProtocolsStep;
pub fn build(b: *std.build.Builder) !void {
// Standard target options allows the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which
// means any target is allowed, and the default is native. Other options
// for restricting supported target set are available.
pub fn build(b: *zbs.Builder) !void {
const target = b.standardTargetOptions(.{});
// Standard release options allow the person running `zig build` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
const mode = b.standardReleaseOptions();
const xwayland = b.option(
@ -31,17 +25,14 @@ pub fn build(b: *std.build.Builder) !void {
break :scdoc_found true;
};
const examples = b.option(
bool,
"examples",
"Set to true to build examples",
) orelse false;
const examples = b.option(bool, "examples", "Set to true to build examples") orelse false;
// TODO: port all parts of river to zig-wayland and delete this
const scan_protocols = OldScanProtocolsStep.create(b);
const scanner = ScanProtocolsStep.create(b, "deps/zig-wayland/");
const scanner = ScanProtocolsStep.create(b);
scanner.addSystemProtocol("stable/xdg-shell/xdg-shell.xml");
scanner.addProtocolPath("protocol/river-control-unstable-v1.xml");
scanner.addProtocolPath("protocol/river-status-unstable-v1.xml");
scanner.addProtocolPath("protocol/wlr-layer-shell-unstable-v1.xml");
scanner.addProtocolPath("protocol/wlr-output-power-management-unstable-v1.xml");
{
const river = b.addExecutable("river", "river/main.zig");
@ -49,16 +40,9 @@ pub fn build(b: *std.build.Builder) !void {
river.setBuildMode(mode);
river.addBuildOption(bool, "xwayland", xwayland);
addProtocolDeps(river, &scan_protocols.step);
addServerDeps(river);
addServerDeps(river, scanner);
river.install();
const run_cmd = river.run();
run_cmd.step.dependOn(b.getInstallStep());
const run_step = b.step("run", "Run the compositor");
run_step.dependOn(&run_cmd.step);
}
{
@ -93,11 +77,13 @@ pub fn build(b: *std.build.Builder) !void {
status.setTarget(target);
status.setBuildMode(mode);
addProtocolDeps(status, &scan_protocols.step);
status.step.dependOn(&scanner.step);
status.addPackage(scanner.getPkg());
status.linkLibC();
status.linkSystemLibrary("wayland-client");
scanner.addCSource(status);
status.install();
}
@ -107,123 +93,44 @@ pub fn build(b: *std.build.Builder) !void {
river_test.setBuildMode(mode);
river_test.addBuildOption(bool, "xwayland", xwayland);
addProtocolDeps(river_test, &scan_protocols.step);
addServerDeps(river_test);
addServerDeps(river_test, scanner);
const test_step = b.step("test", "Run the tests");
test_step.dependOn(&river_test.step);
}
}
fn addServerDeps(exe: *std.build.LibExeObjStep) void {
exe.addCSourceFile("include/bindings.c", &[_][]const u8{"-std=c99"});
exe.addIncludeDir(".");
fn addServerDeps(exe: *zbs.LibExeObjStep, scanner: *ScanProtocolsStep) void {
const wayland = scanner.getPkg();
const xkbcommon = zbs.Pkg{ .name = "xkbcommon", .path = "deps/zig-xkbcommon/src/xkbcommon.zig" };
const pixman = zbs.Pkg{ .name = "pixman", .path = "deps/zig-pixman/pixman.zig" };
const wlroots = zbs.Pkg{
.name = "wlroots",
.path = "deps/zig-wlroots/src/wlroots.zig",
.dependencies = &[_]zbs.Pkg{ wayland, xkbcommon, pixman },
};
exe.step.dependOn(&scanner.step);
exe.linkLibC();
exe.linkSystemLibrary("libevdev");
exe.addPackage(wayland);
exe.linkSystemLibrary("wayland-server");
exe.linkSystemLibrary("wlroots");
exe.addPackage(xkbcommon);
exe.linkSystemLibrary("xkbcommon");
exe.addPackage(pixman);
exe.linkSystemLibrary("pixman-1");
exe.addPackage(wlroots);
exe.linkSystemLibrary("wlroots");
// TODO: remove when zig issue #131 is implemented
scanner.addCSource(exe);
}
fn addProtocolDeps(exe: *std.build.LibExeObjStep, protocol_step: *std.build.Step) void {
exe.step.dependOn(protocol_step);
exe.addIncludeDir("protocol");
exe.addCSourceFile("protocol/river-control-unstable-v1-protocol.c", &[_][]const u8{"-std=c99"});
exe.addCSourceFile("protocol/river-status-unstable-v1-protocol.c", &[_][]const u8{"-std=c99"});
}
const OldScanProtocolsStep = struct {
builder: *std.build.Builder,
step: std.build.Step,
fn create(builder: *std.build.Builder) *OldScanProtocolsStep {
const self = builder.allocator.create(OldScanProtocolsStep) catch @panic("out of memory");
self.* = init(builder);
return self;
}
fn init(builder: *std.build.Builder) OldScanProtocolsStep {
return OldScanProtocolsStep{
.builder = builder,
.step = std.build.Step.init(.Custom, "Scan Protocols", builder.allocator, make),
};
}
fn make(step: *std.build.Step) !void {
const self = @fieldParentPtr(OldScanProtocolsStep, "step", step);
const protocol_dir = std.mem.trim(u8, try self.builder.exec(
&[_][]const u8{ "pkg-config", "--variable=pkgdatadir", "wayland-protocols" },
), &std.ascii.spaces);
const protocol_dir_paths = [_][]const []const u8{
&[_][]const u8{ protocol_dir, "stable/xdg-shell/xdg-shell.xml" },
&[_][]const u8{ "protocol", "wlr-layer-shell-unstable-v1.xml" },
&[_][]const u8{ "protocol", "wlr-output-power-management-unstable-v1.xml" },
&[_][]const u8{ "protocol", "river-control-unstable-v1.xml" },
&[_][]const u8{ "protocol", "river-status-unstable-v1.xml" },
};
const server_protocols = [_][]const u8{
"xdg-shell",
"wlr-layer-shell-unstable-v1",
"wlr-output-power-management-unstable-v1",
"river-control-unstable-v1",
"river-status-unstable-v1",
};
const client_protocols = [_][]const u8{
"river-control-unstable-v1",
"river-status-unstable-v1",
};
for (protocol_dir_paths) |dir_path| {
const xml_in_path = try std.fs.path.join(self.builder.allocator, dir_path);
// Extension is .xml, so slice off the last 4 characters
const basename = std.fs.path.basename(xml_in_path);
const basename_no_ext = basename[0..(basename.len - 4)];
const code_out_path = try std.mem.concat(
self.builder.allocator,
u8,
&[_][]const u8{ "protocol/", basename_no_ext, "-protocol.c" },
);
_ = try self.builder.exec(
&[_][]const u8{ "wayland-scanner", "private-code", xml_in_path, code_out_path },
);
for (server_protocols) |server_protocol| {
if (std.mem.eql(u8, basename_no_ext, server_protocol)) {
const header_out_path = try std.mem.concat(
self.builder.allocator,
u8,
&[_][]const u8{ "protocol/", basename_no_ext, "-protocol.h" },
);
_ = try self.builder.exec(
&[_][]const u8{ "wayland-scanner", "server-header", xml_in_path, header_out_path },
);
}
}
for (client_protocols) |client_protocol| {
if (std.mem.eql(u8, basename_no_ext, client_protocol)) {
const header_out_path = try std.mem.concat(
self.builder.allocator,
u8,
&[_][]const u8{ "protocol/", basename_no_ext, "-client-protocol.h" },
);
_ = try self.builder.exec(
&[_][]const u8{ "wayland-scanner", "client-header", xml_in_path, header_out_path },
);
}
}
}
}
};
const ScdocStep = struct {
const scd_paths = [_][]const u8{
"doc/river.1.scd",
@ -232,23 +139,23 @@ const ScdocStep = struct {
"doc/river-layouts.7.scd",
};
builder: *std.build.Builder,
step: std.build.Step,
builder: *zbs.Builder,
step: zbs.Step,
fn create(builder: *std.build.Builder) *ScdocStep {
fn create(builder: *zbs.Builder) *ScdocStep {
const self = builder.allocator.create(ScdocStep) catch @panic("out of memory");
self.* = init(builder);
return self;
}
fn init(builder: *std.build.Builder) ScdocStep {
fn init(builder: *zbs.Builder) ScdocStep {
return ScdocStep{
.builder = builder,
.step = std.build.Step.init(.Custom, "Generate man pages", builder.allocator, make),
.step = zbs.Step.init(.Custom, "Generate man pages", builder.allocator, make),
};
}
fn make(step: *std.build.Step) !void {
fn make(step: *zbs.Step) !void {
const self = @fieldParentPtr(ScdocStep, "step", step);
for (scd_paths) |path| {
const command = try std.fmt.allocPrint(

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 c = @cImport({
@cInclude("wayland-client.h");
@cInclude("river-status-unstable-v1-client-protocol.h");
});
const wayland = @import("wayland");
const wl = wayland.client.wl;
const zriver = wayland.client.zriver;
const wl_registry_listener = c.wl_registry_listener{
.global = handleGlobal,
.global_remove = handleGlobalRemove,
const SetupContext = struct {
status_manager: ?*zriver.StatusManagerV1 = null,
outputs: std.ArrayList(*wl.Output) = std.ArrayList(*wl.Output).init(std.heap.c_allocator),
seats: std.ArrayList(*wl.Seat) = std.ArrayList(*wl.Seat).init(std.heap.c_allocator),
};
const river_output_status_listener = c.zriver_output_status_v1_listener{
.focused_tags = handleFocusedTags,
.view_tags = handleViewTags,
};
const river_seat_status_listener = c.zriver_seat_status_v1_listener{
.focused_output = handleFocusedOutput,
.unfocused_output = handleUnfocusedOutput,
.focused_view = handleFocusedView,
};
var river_status_manager: ?*c.zriver_status_manager_v1 = null;
var outputs = std.ArrayList(*c.wl_output).init(std.heap.c_allocator);
var seats = std.ArrayList(*c.wl_seat).init(std.heap.c_allocator);
pub fn main() !void {
const wl_display = c.wl_display_connect(null) orelse return error.CantConnectToDisplay;
const wl_registry = c.wl_display_get_registry(wl_display);
const display = try wl.Display.connect(null);
const registry = try display.getRegistry();
if (c.wl_registry_add_listener(wl_registry, &wl_registry_listener, null) < 0)
return error.FailedToAddListener;
if (c.wl_display_roundtrip(wl_display) < 0) return error.RoundtripFailed;
var context = SetupContext{};
if (river_status_manager == null) return error.RiverStatusManagerNotAdvertised;
registry.setListener(*SetupContext, registryListener, &context) catch unreachable;
_ = try display.roundtrip();
for (outputs.items) |wl_output| createOutputStatus(wl_output);
for (seats.items) |wl_seat| createSeatStatus(wl_seat);
outputs.deinit();
seats.deinit();
const status_manager = context.status_manager orelse return error.RiverStatusManagerNotAdvertised;
for (context.outputs.items) |output| {
const output_status = try status_manager.getRiverOutputStatus(output);
output_status.setListener(?*c_void, outputStatusListener, null) catch unreachable;
}
for (context.seats.items) |seat| {
const seat_status = try status_manager.getRiverSeatStatus(seat);
seat_status.setListener(?*c_void, seatStatusListener, null) catch unreachable;
}
context.outputs.deinit();
context.seats.deinit();
// Loop forever, listening for new events.
while (true) if (c.wl_display_dispatch(wl_display) < 0) return error.DispatchFailed;
while (true) _ = try display.dispatch();
}
fn handleGlobal(
data: ?*c_void,
wl_registry: ?*c.wl_registry,
name: u32,
interface: ?[*:0]const u8,
version: u32,
) callconv(.C) void {
// Global advertisement order is not defined, so save any outputs or seats
// advertised before the river_status_manager.
if (std.cstr.cmp(interface.?, @ptrCast([*:0]const u8, c.zriver_status_manager_v1_interface.name.?)) == 0) {
river_status_manager = @ptrCast(
*c.zriver_status_manager_v1,
c.wl_registry_bind(wl_registry, name, &c.zriver_status_manager_v1_interface, version),
);
} else if (std.cstr.cmp(interface.?, @ptrCast([*:0]const u8, c.wl_output_interface.name.?)) == 0) {
const wl_output = @ptrCast(
*c.wl_output,
c.wl_registry_bind(wl_registry, name, &c.wl_output_interface, version),
);
outputs.append(wl_output) catch @panic("out of memory");
} else if (std.cstr.cmp(interface.?, @ptrCast([*:0]const u8, c.wl_seat_interface.name.?)) == 0) {
const wl_seat = @ptrCast(
*c.wl_seat,
c.wl_registry_bind(wl_registry, name, &c.wl_seat_interface, version),
);
seats.append(wl_seat) catch @panic("out of memory");
fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *SetupContext) void {
switch (event) {
.global => |global| {
if (std.cstr.cmp(global.interface, zriver.StatusManagerV1.getInterface().name) == 0) {
context.status_manager = registry.bind(global.name, zriver.StatusManagerV1, 1) catch return;
} else if (std.cstr.cmp(global.interface, wl.Seat.getInterface().name) == 0) {
const seat = registry.bind(global.name, wl.Seat, 1) catch return;
context.seats.append(seat) catch @panic("out of memory");
} else if (std.cstr.cmp(global.interface, wl.Output.getInterface().name) == 0) {
const output = registry.bind(global.name, wl.Output, 1) catch return;
context.outputs.append(output) catch @panic("out of memory");
}
},
.global_remove => {},
}
}
fn createOutputStatus(wl_output: *c.wl_output) void {
const river_output_status = c.zriver_status_manager_v1_get_river_output_status(
river_status_manager.?,
wl_output,
);
_ = c.zriver_output_status_v1_add_listener(
river_output_status,
&river_output_status_listener,
null,
);
}
fn createSeatStatus(wl_seat: *c.wl_seat) void {
const river_seat_status = c.zriver_status_manager_v1_get_river_seat_status(
river_status_manager.?,
wl_seat,
);
_ = c.zriver_seat_status_v1_add_listener(river_seat_status, &river_seat_status_listener, null);
}
fn handleGlobalRemove(data: ?*c_void, wl_registry: ?*c.wl_registry, name: u32) callconv(.C) void {
// Ignore the event
}
fn handleFocusedTags(
data: ?*c_void,
output_status: ?*c.zriver_output_status_v1,
tags: u32,
) callconv(.C) void {
std.debug.warn("Focused tags: {b:0>10}\n", .{tags});
}
fn handleViewTags(
data: ?*c_void,
output_status: ?*c.zriver_output_status_v1,
tags: ?*c.wl_array,
) callconv(.C) void {
fn outputStatusListener(output_status: *zriver.OutputStatusV1, event: zriver.OutputStatusV1.Event, data: ?*c_void) void {
switch (event) {
.focused_tags => |focused_tags| std.debug.warn("Focused tags: {b:0>10}\n", .{focused_tags.tags}),
.view_tags => |view_tags| {
std.debug.warn("View tags:\n", .{});
var offset: usize = 0;
while (offset < tags.?.size) : (offset += @sizeOf(u32)) {
const ptr = @ptrCast([*]u8, tags.?.data) + offset;
std.debug.warn("{b:0>10}\n", .{std.mem.bytesToValue(u32, ptr[0..4])});
for (view_tags.tags.slice(u32)) |t| std.debug.warn("{b:0>10}\n", .{t});
},
}
}
fn handleFocusedOutput(
data: ?*c_void,
seat_status: ?*c.zriver_seat_status_v1,
wl_output: ?*c.wl_output,
) callconv(.C) void {
std.debug.warn("Output id {} focused\n", .{c.wl_proxy_get_id(@ptrCast(*c.wl_proxy, wl_output))});
fn seatStatusListener(seat_status: *zriver.SeatStatusV1, event: zriver.SeatStatusV1.Event, data: ?*c_void) void {
switch (event) {
.focused_output => |focused_output| std.debug.warn("Output id {} focused\n", .{
@ptrCast(*wl.Proxy, focused_output.output orelse return).getId(),
}),
.unfocused_output => |unfocused_output| std.debug.warn("Output id {} focused\n", .{
@ptrCast(*wl.Proxy, unfocused_output.output orelse return).getId(),
}),
.focused_view => |focused_view| std.debug.warn("Focused view title: {}\n", .{focused_view.title}),
}
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 c = @import("c.zig");
const wlr = @import("wlroots");
x: i32,
y: i32,
width: u32,
height: u32,
pub fn fromWlrBox(wlr_box: c.wlr_box) Self {
pub fn fromWlrBox(wlr_box: wlr.Box) Self {
return Self{
.x = @intCast(i32, wlr_box.x),
.y = @intCast(i32, wlr_box.y),
@ -33,8 +33,8 @@ pub fn fromWlrBox(wlr_box: c.wlr_box) Self {
};
}
pub fn toWlrBox(self: Self) c.wlr_box {
return c.wlr_box{
pub fn toWlrBox(self: Self) wlr.Box {
return wlr.Box{
.x = @intCast(c_int, self.x),
.y = @intCast(c_int, self.y),
.width = @intCast(c_int, self.width),

View file

@ -18,6 +18,13 @@
const Self = @This();
const std = @import("std");
const mem = std.mem;
const wayland = @import("wayland");
const wl = wayland.server.wl;
const zriver = wayland.server.zriver;
const wlr = @import("wlroots");
const c = @import("c.zig");
const command = @import("command.zig");
@ -26,141 +33,106 @@ const util = @import("util.zig");
const Seat = @import("Seat.zig");
const Server = @import("Server.zig");
const protocol_version = 1;
const implementation = c.struct_zriver_control_v1_interface{
.destroy = destroy,
.add_argument = addArgument,
.run_command = runCommand,
};
wl_global: *c.wl_global,
global: *wl.Global,
args_map: std.AutoHashMap(u32, std.ArrayList([]const u8)),
listen_display_destroy: c.wl_listener = undefined,
server_destroy: wl.Listener(*wl.Server) = undefined,
pub fn init(self: *Self, server: *Server) !void {
self.* = .{
.wl_global = c.wl_global_create(
server.wl_display,
&c.zriver_control_v1_interface,
protocol_version,
self,
bind,
) orelse return error.OutOfMemory,
.global = try wl.Global.create(server.wl_server, zriver.ControlV1, 1, *Self, self, bind),
.args_map = std.AutoHashMap(u32, std.ArrayList([]const u8)).init(util.gpa),
};
self.listen_display_destroy.notify = handleDisplayDestroy;
c.wl_display_add_destroy_listener(server.wl_display, &self.listen_display_destroy);
self.server_destroy.setNotify(handleServerDestroy);
server.wl_server.addDestroyListener(&self.server_destroy);
}
fn handleDisplayDestroy(wl_listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
const self = @fieldParentPtr(Self, "listen_display_destroy", wl_listener.?);
c.wl_global_destroy(self.wl_global);
fn handleServerDestroy(listener: *wl.Listener(*wl.Server), wl_server: *wl.Server) void {
const self = @fieldParentPtr(Self, "server_destroy", listener);
self.global.destroy();
self.args_map.deinit();
}
/// Called when a client binds our global
fn bind(wl_client: ?*c.wl_client, data: ?*c_void, version: u32, id: u32) callconv(.C) void {
const self = util.voidCast(Self, data.?);
const wl_resource = c.wl_resource_create(
wl_client,
&c.zriver_control_v1_interface,
@intCast(c_int, version),
id,
) orelse {
c.wl_client_post_no_memory(wl_client);
fn bind(client: *wl.Client, self: *Self, version: u32, id: u32) callconv(.C) void {
const control = zriver.ControlV1.create(client, version, id) catch {
client.postNoMemory();
return;
};
self.args_map.putNoClobber(id, std.ArrayList([]const u8).init(util.gpa)) catch {
c.wl_resource_destroy(wl_resource);
c.wl_client_post_no_memory(wl_client);
control.destroy();
client.postNoMemory();
return;
};
c.wl_resource_set_implementation(wl_resource, &implementation, self, handleResourceDestroy);
control.setHandler(*Self, handleRequest, handleDestroy, self);
}
/// Remove the resource from the hash map and free all stored args
fn handleResourceDestroy(wl_resource: ?*c.wl_resource) callconv(.C) void {
const self = util.voidCast(Self, c.wl_resource_get_user_data(wl_resource).?);
const id = c.wl_resource_get_id(wl_resource);
const list = self.args_map.remove(id).?.value;
for (list.items) |arg| list.allocator.free(arg);
list.deinit();
}
fn destroy(wl_client: ?*c.wl_client, wl_resource: ?*c.wl_resource) callconv(.C) void {
c.wl_resource_destroy(wl_resource);
}
fn addArgument(wl_client: ?*c.wl_client, wl_resource: ?*c.wl_resource, arg: ?[*:0]const u8) callconv(.C) void {
const self = util.voidCast(Self, c.wl_resource_get_user_data(wl_resource).?);
const id = c.wl_resource_get_id(wl_resource);
const owned_slice = std.mem.dupe(util.gpa, u8, std.mem.span(arg.?)) catch {
c.wl_client_post_no_memory(wl_client);
fn handleRequest(control: *zriver.ControlV1, request: zriver.ControlV1.Request, self: *Self) void {
switch (request) {
.destroy => control.destroy(),
.add_argument => |add_argument| {
const owned_slice = mem.dupe(util.gpa, u8, mem.span(add_argument.argument)) catch {
control.getClient().postNoMemory();
return;
};
self.args_map.getEntry(id).?.value.append(owned_slice) catch {
c.wl_client_post_no_memory(wl_client);
self.args_map.getEntry(control.getId()).?.value.append(owned_slice) catch {
control.getClient().postNoMemory();
util.gpa.free(owned_slice);
return;
};
}
},
.run_command => |run_command| {
const seat = @intToPtr(*Seat, wlr.Seat.Client.fromWlSeat(run_command.seat).?.seat.data);
fn runCommand(
wl_client: ?*c.wl_client,
wl_resource: ?*c.wl_resource,
seat_wl_resource: ?*c.wl_resource,
callback_id: u32,
) callconv(.C) void {
const self = util.voidCast(Self, c.wl_resource_get_user_data(wl_resource).?);
// This can be null if the seat is inert, in which case we ignore the request
const wlr_seat_client = c.wlr_seat_client_from_resource(seat_wl_resource) orelse return;
const seat = util.voidCast(Seat, wlr_seat_client.*.seat.*.data.?);
const callback_resource = c.wl_resource_create(
wl_client,
&c.zriver_command_callback_v1_interface,
protocol_version,
callback_id,
) orelse {
c.wl_client_post_no_memory(wl_client);
const callback = zriver.CommandCallbackV1.create(
control.getClient(),
control.getVersion(),
run_command.callback,
) catch {
control.getClient().postNoMemory();
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;
defer if (out) |s| util.gpa.free(s);
command.run(util.gpa, seat, args, &out) catch |err| {
const failure_message = switch (err) {
command.Error.OutOfMemory => {
c.wl_client_post_no_memory(wl_client);
callback.getClient().postNoMemory();
return;
},
command.Error.Other => std.cstr.addNullByte(util.gpa, out.?) catch {
c.wl_client_post_no_memory(wl_client);
callback.getClient().postNoMemory();
return;
},
else => command.errToMsg(err),
};
defer if (err == command.Error.Other) util.gpa.free(failure_message);
c.zriver_command_callback_v1_send_failure(callback_resource, failure_message);
callback.sendFailure(failure_message);
return;
};
const success_message = if (out) |s|
std.cstr.addNullByte(util.gpa, s) catch {
c.wl_client_post_no_memory(wl_client);
callback.getClient().postNoMemory();
return;
}
else
"";
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 std = @import("std");
const wlr = @import("wlroots");
const wayland = @import("wayland");
const wl = wayland.server.wl;
const zwlr = wayland.server.zwlr;
const c = @import("c.zig");
const log = @import("log.zig");
@ -50,34 +54,34 @@ const default_size = 24;
mode: Mode = .passthrough,
seat: *Seat,
wlr_cursor: *c.wlr_cursor,
wlr_xcursor_manager: *c.wlr_xcursor_manager,
wlr_cursor: *wlr.Cursor,
xcursor_manager: *wlr.XcursorManager,
/// Number of distinct buttons currently pressed
pressed_count: u32 = 0,
listen_axis: c.wl_listener = undefined,
listen_button: c.wl_listener = undefined,
listen_frame: c.wl_listener = undefined,
listen_motion_absolute: c.wl_listener = undefined,
listen_motion: c.wl_listener = undefined,
listen_request_set_cursor: c.wl_listener = undefined,
axis: wl.Listener(*wlr.Pointer.event.Axis) = undefined,
button: wl.Listener(*wlr.Pointer.event.Button) = undefined,
frame: wl.Listener(*wlr.Cursor) = undefined,
motion_absolute: wl.Listener(*wlr.Pointer.event.MotionAbsolute) = undefined,
motion: wl.Listener(*wlr.Pointer.event.Motion) = undefined,
request_set_cursor: wl.Listener(*wlr.Seat.event.RequestSetCursor) = undefined,
pub fn init(self: *Self, seat: *Seat) !void {
const wlr_cursor = c.wlr_cursor_create() orelse return error.OutOfMemory;
errdefer c.wlr_cursor_destroy(wlr_cursor);
c.wlr_cursor_attach_output_layout(wlr_cursor, seat.input_manager.server.root.wlr_output_layout);
const wlr_cursor = try wlr.Cursor.create();
errdefer wlr_cursor.destroy();
wlr_cursor.attachOutputLayout(seat.input_manager.server.root.output_layout);
// This is here so that self.wlr_xcursor_manager doesn't need to be an
// This is here so that self.xcursor_manager doesn't need to be an
// optional pointer. This isn't optimal as it does a needless allocation,
// but this is not a hot path.
const wlr_xcursor_manager = c.wlr_xcursor_manager_create(null, default_size) orelse return error.OutOfMemory;
errdefer c.wlr_xcursor_manager_destroy(wlr_xcursor_manager);
const xcursor_manager = try wlr.XcursorManager.create(null, default_size);
errdefer xcursor_manager.destroy();
self.* = .{
.seat = seat,
.wlr_cursor = wlr_cursor,
.wlr_xcursor_manager = wlr_xcursor_manager,
.xcursor_manager = xcursor_manager,
};
try self.setTheme(null, null);
@ -87,28 +91,28 @@ pub fn init(self: *Self, seat: *Seat) !void {
// can choose how we want to process them, forwarding them to clients and
// moving the cursor around. See following post for more detail:
// https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html
self.listen_axis.notify = handleAxis;
c.wl_signal_add(&self.wlr_cursor.events.axis, &self.listen_axis);
self.axis.setNotify(handleAxis);
self.wlr_cursor.events.axis.add(&self.axis);
self.listen_button.notify = handleButton;
c.wl_signal_add(&self.wlr_cursor.events.button, &self.listen_button);
self.button.setNotify(handleButton);
self.wlr_cursor.events.button.add(&self.button);
self.listen_frame.notify = handleFrame;
c.wl_signal_add(&self.wlr_cursor.events.frame, &self.listen_frame);
self.frame.setNotify(handleFrame);
self.wlr_cursor.events.frame.add(&self.frame);
self.listen_motion_absolute.notify = handleMotionAbsolute;
c.wl_signal_add(&self.wlr_cursor.events.motion_absolute, &self.listen_motion_absolute);
self.motion_absolute.setNotify(handleMotionAbsolute);
self.wlr_cursor.events.motion_absolute.add(&self.motion_absolute);
self.listen_motion.notify = handleMotion;
c.wl_signal_add(&self.wlr_cursor.events.motion, &self.listen_motion);
self.motion.setNotify(handleMotion);
self.wlr_cursor.events.motion.add(&self.motion);
self.listen_request_set_cursor.notify = handleRequestSetCursor;
c.wl_signal_add(&self.seat.wlr_seat.events.request_set_cursor, &self.listen_request_set_cursor);
self.request_set_cursor.setNotify(handleRequestSetCursor);
self.seat.wlr_seat.events.request_set_cursor.add(&self.request_set_cursor);
}
pub fn deinit(self: *Self) void {
c.wlr_xcursor_manager_destroy(self.wlr_xcursor_manager);
c.wlr_cursor_destroy(self.wlr_cursor);
self.xcursor_manager.destroy();
self.wlr_cursor.destroy();
}
/// Set the cursor theme for the given seat, as well as the xwayland theme if
@ -118,15 +122,14 @@ pub fn setTheme(self: *Self, theme: ?[*:0]const u8, _size: ?u32) !void {
const server = self.seat.input_manager.server;
const size = _size orelse default_size;
c.wlr_xcursor_manager_destroy(self.wlr_xcursor_manager);
self.wlr_xcursor_manager = c.wlr_xcursor_manager_create(theme, size) orelse
return error.OutOfMemory;
self.xcursor_manager.destroy();
self.xcursor_manager = try wlr.XcursorManager.create(theme, size);
// For each output, ensure a theme of the proper scale is loaded
var it = server.root.outputs.first;
while (it) |node| : (it = node.next) {
const wlr_output = node.data.wlr_output;
if (!c.wlr_xcursor_manager_load(self.wlr_xcursor_manager, wlr_output.scale))
self.xcursor_manager.load(wlr_output.scale) catch
log.err(.cursor, "failed to load xcursor theme '{}' at scale {}", .{ theme, wlr_output.scale });
}
@ -139,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 (build_options.xwayland) {
if (c.wlr_xcursor_manager_load(self.wlr_xcursor_manager, 1)) {
const wlr_xcursor = c.wlr_xcursor_manager_get_xcursor(self.wlr_xcursor_manager, "left_ptr", 1).?;
const image: *c.wlr_xcursor_image = wlr_xcursor.*.images[0];
c.wlr_xwayland_set_cursor(
server.wlr_xwayland,
self.xcursor_manager.load(1) catch {
log.err(.cursor, "failed to load xcursor theme '{}' at scale 1", .{theme});
return;
};
const wlr_xcursor = self.xcursor_manager.getXcursor("left_ptr", 1).?;
const image = wlr_xcursor.images[0];
server.xwayland.setCursor(
image.buffer,
image.width * 4,
image.width,
@ -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_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 {
c.wlr_xcursor_manager_set_cursor_image(
self.wlr_xcursor_manager,
"left_ptr",
self.wlr_cursor,
);
c.wlr_seat_pointer_clear_focus(self.seat.wlr_seat);
self.xcursor_manager.setCursorImage("left_ptr", self.wlr_cursor);
self.seat.wlr_seat.pointerNotifyClearFocus();
}
fn handleAxis(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
// This event is forwarded by the cursor when a pointer emits an axis event,
// for example when you move the scroll wheel.
const self = @fieldParentPtr(Self, "listen_axis", listener.?);
const event = util.voidCast(c.wlr_event_pointer_axis, data.?);
/// Axis event is a scroll wheel or similiar
fn handleAxis(listener: *wl.Listener(*wlr.Pointer.event.Axis), event: *wlr.Pointer.event.Axis) void {
const self = @fieldParentPtr(Self, "axis", listener);
self.seat.handleActivity();
// Notify the client with pointer focus of the axis event.
c.wlr_seat_pointer_notify_axis(
self.seat.wlr_seat,
self.seat.wlr_seat.pointerNotifyAxis(
event.time_msec,
event.orientation,
event.delta,
@ -200,15 +197,12 @@ fn handleAxis(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
);
}
fn handleButton(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
// This event is forwarded by the cursor when a pointer emits a button
// event.
const self = @fieldParentPtr(Self, "listen_button", listener.?);
const event = util.voidCast(c.wlr_event_pointer_button, data.?);
fn handleButton(listener: *wl.Listener(*wlr.Pointer.event.Button), event: *wlr.Pointer.event.Button) void {
const self = @fieldParentPtr(Self, "button", listener);
self.seat.handleActivity();
if (event.state == .WLR_BUTTON_PRESSED) {
if (event.state == .pressed) {
self.pressed_count += 1;
} else {
std.debug.assert(self.pressed_count > 0);
@ -221,21 +215,21 @@ fn handleButton(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
var sx: f64 = undefined;
var sy: f64 = undefined;
if (self.surfaceAt(self.wlr_cursor.x, self.wlr_cursor.y, &sx, &sy)) |wlr_surface| {
if (self.surfaceAt(self.wlr_cursor.x, self.wlr_cursor.y, &sx, &sy)) |surface| {
// If the found surface is a keyboard inteactive layer surface,
// give it keyboard focus.
if (c.wlr_surface_is_layer_surface(wlr_surface)) {
const wlr_layer_surface = c.wlr_layer_surface_v1_from_wlr_surface(wlr_surface);
if (wlr_layer_surface.*.current.keyboard_interactive) {
const layer_surface = util.voidCast(LayerSurface, wlr_layer_surface.*.data.?);
if (surface.isLayerSurface()) {
const wlr_layer_surface = wlr.LayerSurfaceV1.fromWlrSurface(surface);
if (wlr_layer_surface.current.keyboard_interactive) {
const layer_surface = @intToPtr(*LayerSurface, wlr_layer_surface.data);
self.seat.setFocusRaw(.{ .layer = layer_surface });
}
}
// If the target surface has a view, give that view keyboard focus and
// perhaps enter move/resize mode.
if (View.fromWlrSurface(wlr_surface)) |view| {
if (event.state == .WLR_BUTTON_PRESSED and self.pressed_count == 1) {
if (View.fromWlrSurface(surface)) |view| {
if (event.state == .pressed and self.pressed_count == 1) {
// If there is an active mapping for this button which is
// handled we are done here
if (self.handlePointerMapping(event, view)) return;
@ -244,26 +238,21 @@ fn handleButton(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
}
}
_ = c.wlr_seat_pointer_notify_button(
self.seat.wlr_seat,
event.time_msec,
event.button,
event.state,
);
_ = self.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state);
}
}
/// Handle the mapping for the passed button if any. Returns true if there
/// was a mapping and the button was handled.
fn handlePointerMapping(self: *Self, event: *c.wlr_event_pointer_button, view: *View) bool {
const wlr_keyboard = c.wlr_seat_get_keyboard(self.seat.wlr_seat);
const modifiers = c.wlr_keyboard_get_modifiers(wlr_keyboard);
fn handlePointerMapping(self: *Self, event: *wlr.Pointer.event.Button, view: *View) bool {
const wlr_keyboard = self.seat.wlr_seat.getKeyboard() orelse return false;
const modifiers = wlr_keyboard.getModifiers();
const fullscreen = view.current.fullscreen or view.pending.fullscreen;
const config = self.seat.input_manager.server.config;
return for (config.modes.items[self.seat.mode_id].pointer_mappings.items) |mapping| {
if (event.button == mapping.event_code and modifiers == mapping.modifiers) {
if (event.button == mapping.event_code and std.meta.eql(modifiers, mapping.modifiers)) {
switch (mapping.action) {
.move => if (!fullscreen) self.enterMode(.move, view),
.resize => if (!fullscreen) self.enterMode(.resize, view),
@ -273,50 +262,54 @@ fn handlePointerMapping(self: *Self, event: *c.wlr_event_pointer_button, view: *
} else false;
}
fn handleFrame(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
// This event is forwarded by the cursor when a pointer emits an frame
// event. Frame events are sent after regular pointer events to group
// multiple events together. For instance, two axis events may happen at the
// same time, in which case a frame event won't be sent in between.
const self = @fieldParentPtr(Self, "listen_frame", listener.?);
// Notify the client with pointer focus of the frame event.
c.wlr_seat_pointer_notify_frame(self.seat.wlr_seat);
/// Frame events are sent after regular pointer events to group multiple
/// events together. For instance, two axis events may happen at the same
/// time, in which case a frame event won't be sent in between.
fn handleFrame(listener: *wl.Listener(*wlr.Cursor), wlr_cursor: *wlr.Cursor) void {
const self = @fieldParentPtr(Self, "frame", listener);
self.seat.wlr_seat.pointerNotifyFrame();
}
fn handleMotionAbsolute(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
// This event is forwarded by the cursor when a pointer emits an _absolute_
// motion event, from 0..1 on each axis. This happens, for example, when
// wlroots is running under a Wayland window rather than KMS+DRM, and you
// move the mouse over the window. You could enter the window from any edge,
// so we have to warp the mouse there. There is also some hardware which
// emits these events.
const self = @fieldParentPtr(Self, "listen_motion_absolute", listener.?);
const event = util.voidCast(c.wlr_event_pointer_motion_absolute, data.?);
/// This event is forwarded by the cursor when a pointer emits an _absolute_
/// motion event, from 0..1 on each axis. This happens, for example, when
/// wlroots is running under a Wayland window rather than KMS+DRM, and you
/// move the mouse over the window. You could enter the window from any edge,
/// so we have to warp the mouse there. There is also some hardware which
/// emits these events.
fn handleMotionAbsolute(
listener: *wl.Listener(*wlr.Pointer.event.MotionAbsolute),
event: *wlr.Pointer.event.MotionAbsolute,
) void {
const self = @fieldParentPtr(Self, "motion_absolute", listener);
self.seat.handleActivity();
var lx: f64 = undefined;
var ly: f64 = undefined;
c.wlr_cursor_absolute_to_layout_coords(self.wlr_cursor, event.device, event.x, event.y, &lx, &ly);
self.wlr_cursor.absoluteToLayoutCoords(event.device, event.x, event.y, &lx, &ly);
self.processMotion(event.device, event.time_msec, lx - self.wlr_cursor.x, ly - self.wlr_cursor.y);
}
fn handleMotion(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
// This event is forwarded by the cursor when a pointer emits a _relative_
// pointer motion event (i.e. a delta)
const self = @fieldParentPtr(Self, "listen_motion", listener.?);
const event = util.voidCast(c.wlr_event_pointer_motion, data.?);
/// This event is forwarded by the cursor when a pointer emits a _relative_
/// pointer motion event (i.e. a delta)
fn handleMotion(
listener: *wl.Listener(*wlr.Pointer.event.Motion),
event: *wlr.Pointer.event.Motion,
) void {
const self = @fieldParentPtr(Self, "motion", listener);
self.seat.handleActivity();
self.processMotion(event.device, event.time_msec, event.delta_x, event.delta_y);
}
fn handleRequestSetCursor(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
fn handleRequestSetCursor(
listener: *wl.Listener(*wlr.Seat.event.RequestSetCursor),
event: *wlr.Seat.event.RequestSetCursor,
) void {
// This event is rasied by the seat when a client provides a cursor image
const self = @fieldParentPtr(Self, "listen_request_set_cursor", listener.?);
const event = util.voidCast(c.wlr_seat_pointer_request_set_cursor_event, data.?);
const self = @fieldParentPtr(Self, "request_set_cursor", listener);
const focused_client = self.seat.wlr_seat.pointer_state.focused_client;
// This can be sent by any client, so we check to make sure this one is
@ -327,53 +320,40 @@ fn handleRequestSetCursor(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C
// on the output that it's currently on and continue to do so as the
// cursor moves between outputs.
log.debug(.cursor, "focused client set cursor", .{});
c.wlr_cursor_set_surface(
self.wlr_cursor,
event.surface,
event.hotspot_x,
event.hotspot_y,
);
self.wlr_cursor.setSurface(event.surface, event.hotspot_x, event.hotspot_y);
}
}
/// Find the topmost surface under the output layout coordinates lx/ly
/// returns the surface if found and sets the sx/sy parametes to the
/// surface coordinates.
fn surfaceAt(self: Self, lx: f64, ly: f64, sx: *f64, sy: *f64) ?*c.wlr_surface {
fn surfaceAt(self: Self, lx: f64, ly: f64, sx: *f64, sy: *f64) ?*wlr.Surface {
// Find the output to check
const root = self.seat.input_manager.server.root;
const wlr_output = c.wlr_output_layout_output_at(root.wlr_output_layout, lx, ly) orelse return null;
const output = util.voidCast(Output, wlr_output.*.data orelse return null);
const wlr_output = root.output_layout.outputAt(lx, ly) orelse return null;
const output = @intToPtr(*Output, wlr_output.data);
// Get output-local coords from the layout coords
var ox = lx;
var oy = ly;
c.wlr_output_layout_output_coords(root.wlr_output_layout, wlr_output, &ox, &oy);
// Check layers and views from top to bottom
const layer_idxs = [_]usize{
c.ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY,
c.ZWLR_LAYER_SHELL_V1_LAYER_TOP,
c.ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM,
c.ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND,
};
root.output_layout.outputCoords(wlr_output, &ox, &oy);
// Check overlay layer incl. popups
if (layerSurfaceAt(output.*, output.layers[layer_idxs[0]], ox, oy, sx, sy, false)) |s| return s;
if (layerSurfaceAt(output.*, output.getLayer(.overlay).*, ox, oy, sx, sy, false)) |s| return s;
// Check top-background popups only
for (layer_idxs[1..4]) |idx|
if (layerSurfaceAt(output.*, output.layers[idx], ox, oy, sx, sy, true)) |s| return s;
for ([_]zwlr.LayerShellV1.Layer{ .top, .bottom, .background }) |layer|
if (layerSurfaceAt(output.*, output.getLayer(layer).*, ox, oy, sx, sy, true)) |s| return s;
// Check top layer
if (layerSurfaceAt(output.*, output.layers[layer_idxs[1]], ox, oy, sx, sy, false)) |s| return s;
if (layerSurfaceAt(output.*, output.getLayer(.top).*, ox, oy, sx, sy, false)) |s| return s;
// Check views
if (viewSurfaceAt(output.*, ox, oy, sx, sy)) |s| return s;
// Check the bottom-background layers
for (layer_idxs[2..4]) |idx|
if (layerSurfaceAt(output.*, output.layers[idx], ox, oy, sx, sy, false)) |s| return s;
for ([_]zwlr.LayerShellV1.Layer{ .bottom, .background }) |layer|
if (layerSurfaceAt(output.*, output.getLayer(layer).*, ox, oy, sx, sy, false)) |s| return s;
return null;
}
@ -388,33 +368,28 @@ fn layerSurfaceAt(
sx: *f64,
sy: *f64,
popups_only: bool,
) ?*c.wlr_surface {
) ?*wlr.Surface {
var it = layer.first;
while (it) |node| : (it = node.next) {
const layer_surface = &node.data;
const surface = c.wlr_layer_surface_v1_surface_at(
layer_surface.wlr_layer_surface,
if (layer_surface.wlr_layer_surface.surfaceAt(
ox - @intToFloat(f64, layer_surface.box.x),
oy - @intToFloat(f64, layer_surface.box.y),
sx,
sy,
);
if (surface) |found| {
)) |found| {
if (!popups_only) {
return found;
} else if (c.wlr_surface_is_xdg_surface(found)) {
const wlr_xdg_surface = c.wlr_xdg_surface_from_wlr_surface(found);
if (wlr_xdg_surface.*.role == .WLR_XDG_SURFACE_ROLE_POPUP) {
} else if (found.isXdgSurface() and wlr.XdgSurface.fromWlrSurface(found).role == .popup) {
return found;
}
}
}
}
return null;
}
/// Find the topmost visible view surface (incl. popups) at ox,oy.
fn viewSurfaceAt(output: Output, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_surface {
fn viewSurfaceAt(output: Output, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*wlr.Surface {
// Focused views are rendered on top, so look for them first.
var it = ViewStack(View).iter(output.views.first, .forward, output.current.tags, surfaceAtFilter);
while (it.next()) |view| {
@ -468,10 +443,9 @@ pub fn enterMode(self: *Self, mode: @TagType(Mode), view: *View) void {
}
// Clear cursor focus, so that the surface does not receive events
c.wlr_seat_pointer_clear_focus(self.seat.wlr_seat);
self.seat.wlr_seat.pointerNotifyClearFocus();
c.wlr_xcursor_manager_set_cursor_image(
self.wlr_xcursor_manager,
self.xcursor_manager.setCursorImage(
if (mode == .move) "move" else "se-resize",
self.wlr_cursor,
);
@ -480,36 +454,30 @@ pub fn enterMode(self: *Self, mode: @TagType(Mode), view: *View) void {
}
/// Return from down/move/resize to passthrough
fn leaveMode(self: *Self, event: *c.wlr_event_pointer_button) void {
fn leaveMode(self: *Self, event: *wlr.Pointer.event.Button) void {
std.debug.assert(self.mode != .passthrough);
log.debug(.cursor, "leave {} mode", .{@tagName(self.mode)});
// If we were in down mode, we need pass along the release event
if (self.mode == .down)
_ = c.wlr_seat_pointer_notify_button(
self.seat.wlr_seat,
event.time_msec,
event.button,
event.state,
);
_ = self.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state);
self.mode = .passthrough;
self.passthrough(event.time_msec);
}
fn processMotion(self: *Self, device: *c.wlr_input_device, time: u32, delta_x: f64, delta_y: f64) void {
fn processMotion(self: *Self, device: *wlr.InputDevice, time: u32, delta_x: f64, delta_y: f64) void {
const config = self.seat.input_manager.server.config;
switch (self.mode) {
.passthrough => {
c.wlr_cursor_move(self.wlr_cursor, device, delta_x, delta_y);
self.wlr_cursor.move(device, delta_x, delta_y);
self.passthrough(time);
},
.down => |view| {
c.wlr_cursor_move(self.wlr_cursor, device, delta_x, delta_y);
c.wlr_seat_pointer_notify_motion(
self.seat.wlr_seat,
self.wlr_cursor.move(device, delta_x, delta_y);
self.seat.wlr_seat.pointerNotifyMotion(
time,
self.wlr_cursor.x - @intToFloat(f64, view.current.box.x),
self.wlr_cursor.y - @intToFloat(f64, view.current.box.y),
@ -531,8 +499,7 @@ fn processMotion(self: *Self, device: *c.wlr_input_device, time: u32, delta_x: f
@intCast(i32, output_resolution.height - view.pending.box.height - border_width),
);
c.wlr_cursor_move(
self.wlr_cursor,
self.wlr_cursor.move(
device,
@intToFloat(f64, view.pending.box.x - view.current.box.x),
@intToFloat(f64, view.pending.box.y - view.current.box.y),
@ -557,8 +524,7 @@ fn processMotion(self: *Self, device: *c.wlr_input_device, time: u32, delta_x: f
data.view.applyPending();
// Keep cursor locked to the original offset from the bottom right corner
c.wlr_cursor_warp_closest(
self.wlr_cursor,
self.wlr_cursor.warpClosest(
device,
@intToFloat(f64, box.x + @intCast(i32, box.width) - data.offset_x),
@intToFloat(f64, box.y + @intCast(i32, box.height) - data.offset_y),
@ -574,20 +540,20 @@ fn passthrough(self: *Self, time: u32) void {
var sx: f64 = undefined;
var sy: f64 = undefined;
if (self.surfaceAt(self.wlr_cursor.x, self.wlr_cursor.y, &sx, &sy)) |wlr_surface| {
if (self.surfaceAt(self.wlr_cursor.x, self.wlr_cursor.y, &sx, &sy)) |surface| {
// If input is allowed on the surface, send pointer enter and motion
// events. Note that wlroots won't actually send an enter event if
// the surface has already been entered.
if (self.seat.input_manager.inputAllowed(wlr_surface)) {
if (self.seat.input_manager.inputAllowed(surface)) {
// The focus change must be checked before sending enter events
const focus_change = self.seat.wlr_seat.pointer_state.focused_surface != wlr_surface;
const focus_change = self.seat.wlr_seat.pointer_state.focused_surface != surface;
c.wlr_seat_pointer_notify_enter(self.seat.wlr_seat, wlr_surface, sx, sy);
c.wlr_seat_pointer_notify_motion(self.seat.wlr_seat, time, sx, sy);
self.seat.wlr_seat.pointerNotifyEnter(surface, sx, sy);
self.seat.wlr_seat.pointerNotifyMotion(time, sx, sy);
const follow_mode = config.focus_follows_cursor;
if (follow_mode == .strict or (follow_mode == .normal and focus_change)) {
if (View.fromWlrSurface(wlr_surface)) |view| {
if (View.fromWlrSurface(surface)) |view| {
self.seat.focus(view);
self.seat.focusOutput(view.output);
root.startTransaction();

View file

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

View file

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

View file

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

View file

@ -19,8 +19,9 @@ const Self = @This();
const build_options = @import("build_options");
const std = @import("std");
const wlr = @import("wlroots");
const wl = @import("wayland").server.wl;
const c = @import("c.zig");
const log = @import("log.zig");
const util = @import("util.zig");
@ -32,56 +33,53 @@ const default_seat_name = "default";
server: *Server,
wlr_idle: *c.wlr_idle,
wlr_input_inhibit_manager: *c.wlr_input_inhibit_manager,
wlr_virtual_pointer_manager: *c.wlr_virtual_pointer_manager_v1,
wlr_virtual_keyboard_manager: *c.wlr_virtual_keyboard_manager_v1,
idle: *wlr.Idle,
input_inhibit_manager: *wlr.InputInhibitManager,
virtual_pointer_manager: *wlr.VirtualPointerManagerV1,
virtual_keyboard_manager: *wlr.VirtualKeyboardManagerV1,
seats: std.TailQueue(Seat) = .{},
exclusive_client: ?*c.wl_client = null,
exclusive_client: ?*wl.Client = null,
listen_inhibit_activate: c.wl_listener = undefined,
listen_inhibit_deactivate: c.wl_listener = undefined,
listen_new_input: c.wl_listener = undefined,
listen_new_virtual_pointer: c.wl_listener = undefined,
listen_new_virtual_keyboard: c.wl_listener = undefined,
inhibit_activate: wl.Listener(*wlr.InputInhibitManager) = undefined,
inhibit_deactivate: wl.Listener(*wlr.InputInhibitManager) = undefined,
new_input: wl.Listener(*wlr.InputDevice) = undefined,
new_virtual_pointer: wl.Listener(*wlr.VirtualPointerManagerV1.event.NewPointer) = undefined,
new_virtual_keyboard: wl.Listener(*wlr.VirtualKeyboardV1) = undefined,
pub fn init(self: *Self, server: *Server) !void {
const seat_node = try util.gpa.create(std.TailQueue(Seat).Node);
errdefer util.gpa.destroy(seat_node);
self.* = .{
.server = server,
// These are automatically freed when the display is destroyed
.wlr_idle = c.wlr_idle_create(server.wl_display) orelse return error.OutOfMemory,
.wlr_input_inhibit_manager = c.wlr_input_inhibit_manager_create(server.wl_display) orelse
return error.OutOfMemory,
.wlr_virtual_pointer_manager = c.wlr_virtual_pointer_manager_v1_create(server.wl_display) orelse
return error.OutOfMemory,
.wlr_virtual_keyboard_manager = c.wlr_virtual_keyboard_manager_v1_create(server.wl_display) orelse
return error.OutOfMemory,
.idle = try wlr.Idle.create(server.wl_server),
.input_inhibit_manager = try wlr.InputInhibitManager.create(server.wl_server),
.virtual_pointer_manager = try wlr.VirtualPointerManagerV1.create(server.wl_server),
.virtual_keyboard_manager = try wlr.VirtualKeyboardManagerV1.create(server.wl_server),
};
self.seats.prepend(seat_node);
try seat_node.data.init(self, default_seat_name);
if (build_options.xwayland) c.wlr_xwayland_set_seat(server.wlr_xwayland, self.defaultSeat().wlr_seat);
if (build_options.xwayland) server.xwayland.setSeat(self.defaultSeat().wlr_seat);
// Set up all listeners
self.listen_inhibit_activate.notify = handleInhibitActivate;
c.wl_signal_add(&self.wlr_input_inhibit_manager.events.activate, &self.listen_inhibit_activate);
self.inhibit_activate.setNotify(handleInhibitActivate);
self.input_inhibit_manager.events.activate.add(&self.inhibit_activate);
self.listen_inhibit_deactivate.notify = handleInhibitDeactivate;
c.wl_signal_add(&self.wlr_input_inhibit_manager.events.deactivate, &self.listen_inhibit_deactivate);
self.inhibit_deactivate.setNotify(handleInhibitDeactivate);
self.input_inhibit_manager.events.deactivate.add(&self.inhibit_deactivate);
self.listen_new_input.notify = handleNewInput;
c.wl_signal_add(&self.server.wlr_backend.events.new_input, &self.listen_new_input);
self.new_input.setNotify(handleNewInput);
self.server.backend.events.new_input.add(&self.new_input);
self.listen_new_virtual_pointer.notify = handleNewVirtualPointer;
c.wl_signal_add(&self.wlr_virtual_pointer_manager.events.new_virtual_pointer, &self.listen_new_virtual_pointer);
self.new_virtual_pointer.setNotify(handleNewVirtualPointer);
self.virtual_pointer_manager.events.new_virtual_pointer.add(&self.new_virtual_pointer);
self.listen_new_virtual_keyboard.notify = handleNewVirtualKeyboard;
c.wl_signal_add(&self.wlr_virtual_keyboard_manager.events.new_virtual_keyboard, &self.listen_new_virtual_keyboard);
self.new_virtual_keyboard.setNotify(handleNewVirtualKeyboard);
self.virtual_keyboard_manager.events.new_virtual_keyboard.add(&self.new_virtual_keyboard);
}
pub fn deinit(self: *Self) void {
@ -105,9 +103,9 @@ pub fn handleViewUnmap(self: Self, view: *View) void {
}
/// Returns true if input is currently allowed on the passed surface.
pub fn inputAllowed(self: Self, wlr_surface: *c.wlr_surface) bool {
pub fn inputAllowed(self: Self, wlr_surface: *wlr.Surface) bool {
return if (self.exclusive_client) |exclusive_client|
exclusive_client == c.wl_resource_get_client(wlr_surface.resource)
exclusive_client == wlr_surface.resource.getClient()
else
true;
}
@ -119,8 +117,11 @@ pub fn isCursorActionTarget(self: Self, view: *View) bool {
} else false;
}
fn handleInhibitActivate(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
const self = @fieldParentPtr(Self, "listen_inhibit_activate", listener.?);
fn handleInhibitActivate(
listener: *wl.Listener(*wlr.InputInhibitManager),
input_inhibit_manager: *wlr.InputInhibitManager,
) void {
const self = @fieldParentPtr(Self, "inhibit_activate", listener);
log.debug(.input_manager, "input inhibitor activated", .{});
@ -134,11 +135,14 @@ fn handleInhibitActivate(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C)
seat_node.data.mode_id = 1;
}
self.exclusive_client = self.wlr_input_inhibit_manager.active_client;
self.exclusive_client = self.input_inhibit_manager.active_client;
}
fn handleInhibitDeactivate(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
const self = @fieldParentPtr(Self, "listen_inhibit_deactivate", listener.?);
fn handleInhibitDeactivate(
listener: *wl.Listener(*wlr.InputInhibitManager),
input_inhibit_manager: *wlr.InputInhibitManager,
) void {
const self = @fieldParentPtr(Self, "inhibit_deactivate", listener);
log.debug(.input_manager, "input inhibitor deactivated", .{});
@ -163,17 +167,17 @@ fn handleInhibitDeactivate(listener: ?*c.wl_listener, data: ?*c_void) callconv(.
}
/// This event is raised by the backend when a new input device becomes available.
fn handleNewInput(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
const self = @fieldParentPtr(Self, "listen_new_input", listener.?);
const device = util.voidCast(c.wlr_input_device, data.?);
fn handleNewInput(listener: *wl.Listener(*wlr.InputDevice), device: *wlr.InputDevice) void {
const self = @fieldParentPtr(Self, "new_input", listener);
// TODO: suport multiple seats
self.defaultSeat().addDevice(device);
}
fn handleNewVirtualPointer(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
const self = @fieldParentPtr(Self, "listen_new_virtual_pointer", listener.?);
const event = util.voidCast(c.wlr_virtual_pointer_v1_new_pointer_event, data.?);
fn handleNewVirtualPointer(
listener: *wl.Listener(*wlr.VirtualPointerManagerV1.event.NewPointer),
event: *wlr.VirtualPointerManagerV1.event.NewPointer,
) void {
const self = @fieldParentPtr(Self, "new_virtual_pointer", listener);
// TODO Support multiple seats and don't ignore
if (event.suggested_seat != null) {
@ -184,14 +188,14 @@ fn handleNewVirtualPointer(listener: ?*c.wl_listener, data: ?*c_void) callconv(.
log.debug(.input_manager, "Ignoring output suggestion from virtual pointer", .{});
}
const new_pointer: *c.wlr_virtual_pointer_v1 = event.new_pointer;
self.defaultSeat().addDevice(&new_pointer.input_device);
self.defaultSeat().addDevice(&event.new_pointer.input_device);
}
fn handleNewVirtualKeyboard(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
const self = @fieldParentPtr(Self, "listen_new_virtual_keyboard", listener.?);
const virtual_keyboard = util.voidCast(c.wlr_virtual_keyboard_v1, data.?);
const seat = util.voidCast(Seat, @as(*c.wlr_seat, virtual_keyboard.seat).data.?);
fn handleNewVirtualKeyboard(
listener: *wl.Listener(*wlr.VirtualKeyboardV1),
virtual_keyboard: *wlr.VirtualKeyboardV1,
) void {
const self = @fieldParentPtr(Self, "new_virtual_keyboard", listener);
const seat = @intToPtr(*Seat, virtual_keyboard.seat.data);
seat.addDevice(&virtual_keyboard.input_device);
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -19,8 +19,10 @@ const Self = @This();
const build_options = @import("build_options");
const std = @import("std");
const wlr = @import("wlroots");
const wl = @import("wayland").server.wl;
const xkb = @import("xkbcommon");
const c = @import("c.zig");
const command = @import("command.zig");
const log = @import("log.zig");
const util = @import("util.zig");
@ -42,7 +44,7 @@ const FocusTarget = union(enum) {
};
input_manager: *InputManager,
wlr_seat: *c.wlr_seat,
wlr_seat: *wlr.Seat,
/// Multiple mice are handled by the same Cursor
cursor: Cursor = undefined,
@ -56,7 +58,8 @@ mode_id: usize = 0,
/// ID of previous keymap mode, used when returning from "locked" mode
prev_mode_id: usize = 0,
/// Currently focused output, may be the noop output if no
/// Currently focused output, may be the noop output if no real output
/// is currently available for focus.
focused_output: *Output,
/// Currently focused view/layer surface if any
@ -69,33 +72,33 @@ focus_stack: ViewStack(*View) = .{},
/// List of status tracking objects relaying changes to this seat to clients.
status_trackers: std.SinglyLinkedList(SeatStatus) = .{},
listen_request_set_selection: c.wl_listener = undefined,
listen_request_start_drag: c.wl_listener = undefined,
listen_start_drag: c.wl_listener = undefined,
listen_request_set_primary_selection: c.wl_listener = undefined,
request_set_selection: wl.Listener(*wlr.Seat.event.RequestSetSelection) = undefined,
request_start_drag: wl.Listener(*wlr.Seat.event.RequestStartDrag) = undefined,
start_drag: wl.Listener(*wlr.Drag) = undefined,
request_set_primary_selection: wl.Listener(*wlr.Seat.event.RequestSetPrimarySelection) = undefined,
pub fn init(self: *Self, input_manager: *InputManager, name: [*:0]const u8) !void {
self.* = .{
.input_manager = input_manager,
// This will be automatically destroyed when the display is destroyed
.wlr_seat = c.wlr_seat_create(input_manager.server.wl_display, name) orelse return error.OutOfMemory,
.wlr_seat = try wlr.Seat.create(input_manager.server.wl_server, name),
.focused_output = &self.input_manager.server.root.noop_output,
};
self.wlr_seat.data = self;
self.wlr_seat.data = @ptrToInt(self);
try self.cursor.init(self);
self.listen_request_set_selection.notify = handleRequestSetSelection;
c.wl_signal_add(&self.wlr_seat.events.request_set_selection, &self.listen_request_set_selection);
self.request_set_selection.setNotify(handleRequestSetSelection);
self.wlr_seat.events.request_set_selection.add(&self.request_set_selection);
self.listen_request_start_drag.notify = handleRequestStartDrag;
c.wl_signal_add(&self.wlr_seat.events.request_start_drag, &self.listen_request_start_drag);
self.request_start_drag.setNotify(handleRequestStartDrag);
self.wlr_seat.events.request_start_drag.add(&self.request_start_drag);
self.listen_start_drag.notify = handleStartDrag;
c.wl_signal_add(&self.wlr_seat.events.start_drag, &self.listen_start_drag);
self.start_drag.setNotify(handleStartDrag);
self.wlr_seat.events.start_drag.add(&self.start_drag);
self.listen_request_set_primary_selection.notify = handleRequestPrimarySelection;
c.wl_signal_add(&self.wlr_seat.events.request_set_primary_selection, &self.listen_request_set_primary_selection);
self.request_set_primary_selection.setNotify(handleRequestPrimarySelection);
self.wlr_seat.events.request_set_primary_selection.add(&self.request_set_primary_selection);
}
pub fn deinit(self: *Self) void {
@ -180,29 +183,28 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void {
// If the target is already focused, do nothing
if (std.meta.eql(new_focus, self.focused)) return;
// Obtain the target wlr_surface
const target_wlr_surface = switch (new_focus) {
.view => |target_view| target_view.wlr_surface.?,
.layer => |target_layer| target_layer.wlr_layer_surface.surface.?,
// Obtain the target surface
const target_surface = switch (new_focus) {
.view => |target_view| target_view.surface.?,
.layer => |target_layer| target_layer.wlr_layer_surface.surface,
.none => null,
};
// If input is not allowed on the target surface (e.g. due to an active
// input inhibitor) do not set focus. If there is no target surface we
// still clear the focus.
if (if (target_wlr_surface) |wlr_surface| self.input_manager.inputAllowed(wlr_surface) else true) {
if (if (target_surface) |wlr_surface| self.input_manager.inputAllowed(wlr_surface) else true) {
// First clear the current focus
if (self.focused == .view) {
self.focused.view.pending.focus -= 1;
// This is needed because xwayland views don't double buffer
// activated state.
if (build_options.xwayland and self.focused.view.impl == .xwayland_view)
c.wlr_xwayland_surface_activate(self.focused.view.impl.xwayland_view.wlr_xwayland_surface, false);
self.focused.view.impl.xwayland_view.xwayland_surface.activate(false);
if (self.focused.view.pending.focus == 0 and !self.focused.view.pending.fullscreen) {
self.focused.view.pending.target_opacity = self.input_manager.server.config.view_opacity_unfocused;
}
}
c.wlr_seat_keyboard_clear_focus(self.wlr_seat);
// Set the new focus
switch (new_focus) {
@ -212,7 +214,7 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void {
// This is needed because xwayland views don't double buffer
// activated state.
if (build_options.xwayland and target_view.impl == .xwayland_view)
c.wlr_xwayland_surface_activate(target_view.impl.xwayland_view.wlr_xwayland_surface, true);
target_view.impl.xwayland_view.xwayland_surface.activate(true);
if (!target_view.pending.fullscreen) {
target_view.pending.target_opacity = self.input_manager.server.config.view_opacity_focused;
}
@ -222,16 +224,20 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void {
}
self.focused = new_focus;
// Tell wlroots to send the new keyboard focus if we have a target
if (target_wlr_surface) |wlr_surface| {
const keyboard: *c.wlr_keyboard = c.wlr_seat_get_keyboard(self.wlr_seat);
c.wlr_seat_keyboard_notify_enter(
self.wlr_seat,
// Send surface enter/leave events
if (target_surface) |wlr_surface| {
if (self.wlr_seat.getKeyboard()) |keyboard| {
self.wlr_seat.keyboardNotifyEnter(
wlr_surface,
&keyboard.keycodes,
keyboard.num_keycodes,
&keyboard.modifiers,
);
} else {
self.wlr_seat.keyboardNotifyEnter(wlr_surface, null, 0, null);
}
} else {
self.wlr_seat.keyboardClearFocus();
}
}
@ -256,7 +262,7 @@ pub fn focusOutput(self: *Self, output: *Output) void {
}
pub fn handleActivity(self: Self) void {
c.wlr_idle_notify_activity(self.input_manager.wlr_idle, self.wlr_seat);
self.input_manager.idle.notifyActivity(self.wlr_seat);
}
/// Handle the unmapping of a view, removing it from the focus stack and
@ -280,10 +286,15 @@ pub fn handleViewUnmap(self: *Self, view: *View) void {
/// Handle any user-defined mapping for the passed keysym and modifiers
/// Returns true if the key was handled
pub fn handleMapping(self: *Self, keysym: c.xkb_keysym_t, modifiers: u32, released: bool) bool {
pub fn handleMapping(
self: *Self,
keysym: xkb.Keysym,
modifiers: wlr.Keyboard.ModifierMask,
released: bool,
) bool {
const modes = &self.input_manager.server.config.modes;
for (modes.items[self.mode_id].mappings.items) |mapping| {
if (modifiers == mapping.modifiers and keysym == mapping.keysym and released == mapping.release) {
if (std.meta.eql(modifiers, mapping.modifiers) and keysym == mapping.keysym and released == mapping.release) {
// Execute the bound command
const args = mapping.command_args;
var out: ?[]const u8 = null;
@ -309,22 +320,23 @@ pub fn handleMapping(self: *Self, keysym: c.xkb_keysym_t, modifiers: u32, releas
/// Add a newly created input device to the seat and update the reported
/// capabilities.
pub fn addDevice(self: *Self, device: *c.wlr_input_device) void {
pub fn addDevice(self: *Self, device: *wlr.InputDevice) void {
switch (device.type) {
.WLR_INPUT_DEVICE_KEYBOARD => self.addKeyboard(device) catch return,
.WLR_INPUT_DEVICE_POINTER => self.addPointer(device),
.keyboard => self.addKeyboard(device) catch return,
.pointer => self.addPointer(device),
else => return,
}
// We need to let the wlr_seat know what our capabilities are, which is
// communiciated to the client. We always have a cursor, even if
// there are no pointer devices, so we always include that capability.
var caps = @intCast(u32, c.WL_SEAT_CAPABILITY_POINTER);
if (self.keyboards.len > 0) caps |= @intCast(u32, c.WL_SEAT_CAPABILITY_KEYBOARD);
c.wlr_seat_set_capabilities(self.wlr_seat, caps);
self.wlr_seat.setCapabilities(.{
.pointer = true,
.keyboard = self.keyboards.len > 0,
});
}
fn addKeyboard(self: *Self, device: *c.wlr_input_device) !void {
fn addKeyboard(self: *Self, device: *wlr.InputDevice) !void {
const node = try util.gpa.create(std.TailQueue(Keyboard).Node);
node.data.init(self, device) catch |err| {
switch (err) {
@ -335,40 +347,46 @@ fn addKeyboard(self: *Self, device: *c.wlr_input_device) !void {
return;
};
self.keyboards.append(node);
c.wlr_seat_set_keyboard(self.wlr_seat, device);
self.wlr_seat.setKeyboard(device);
}
fn addPointer(self: Self, device: *c.struct_wlr_input_device) void {
fn addPointer(self: Self, device: *wlr.InputDevice) void {
// We don't do anything special with pointers. All of our pointer handling
// is proxied through wlr_cursor. On another compositor, you might take this
// opportunity to do libinput configuration on the device to set
// acceleration, etc.
c.wlr_cursor_attach_input_device(self.cursor.wlr_cursor, device);
self.cursor.wlr_cursor.attachInputDevice(device);
}
fn handleRequestSetSelection(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
const self = @fieldParentPtr(Self, "listen_request_set_selection", listener.?);
const event = util.voidCast(c.wlr_seat_request_set_selection_event, data.?);
c.wlr_seat_set_selection(self.wlr_seat, event.source, event.serial);
fn handleRequestSetSelection(
listener: *wl.Listener(*wlr.Seat.event.RequestSetSelection),
event: *wlr.Seat.event.RequestSetSelection,
) void {
const self = @fieldParentPtr(Self, "request_set_selection", listener);
self.wlr_seat.setSelection(event.source, event.serial);
}
fn handleRequestStartDrag(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
const self = @fieldParentPtr(Self, "listen_request_start_drag", listener.?);
const event = util.voidCast(c.wlr_seat_request_start_drag_event, data.?);
fn handleRequestStartDrag(
listener: *wl.Listener(*wlr.Seat.event.RequestStartDrag),
event: *wlr.Seat.event.RequestStartDrag,
) void {
const self = @fieldParentPtr(Self, "request_start_drag", listener);
if (c.wlr_seat_validate_pointer_grab_serial(self.wlr_seat, event.origin, event.serial)) {
log.debug(.seat, "starting pointer drag", .{});
c.wlr_seat_start_pointer_drag(self.wlr_seat, event.drag, event.serial);
if (!self.wlr_seat.validatePointerGrabSerial(event.origin, event.serial)) {
log.debug(.seat, "ignoring request to start drag, failed to validate serial {}", .{event.serial});
if (event.drag.source) |source| source.destroy();
return;
}
log.debug(.seat, "ignoring request to start drag, failed to validate serial {}", .{event.serial});
c.wlr_data_source_destroy(event.drag.*.source);
log.debug(.seat, "starting pointer drag", .{});
self.wlr_seat.startPointerDrag(event.drag, event.serial);
}
fn handleStartDrag(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
const self = @fieldParentPtr(Self, "listen_start_drag", listener.?);
const wlr_drag = util.voidCast(c.wlr_drag, data.?);
fn handleStartDrag(
listener: *wl.Listener(*wlr.Drag),
wlr_drag: *wlr.Drag,
) void {
const self = @fieldParentPtr(Self, "start_drag", listener);
if (wlr_drag.icon) |wlr_drag_icon| {
const node = util.gpa.create(std.SinglyLinkedList(DragIcon).Node) catch {
@ -381,8 +399,10 @@ fn handleStartDrag(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void
self.cursor.mode = .passthrough;
}
fn handleRequestPrimarySelection(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
const self = @fieldParentPtr(Self, "listen_request_set_primary_selection", listener.?);
const event = util.voidCast(c.wlr_seat_request_set_primary_selection_event, data.?);
c.wlr_seat_set_primary_selection(self.wlr_seat, event.source, event.serial);
fn handleRequestPrimarySelection(
listener: *wl.Listener(*wlr.Seat.event.RequestSetPrimarySelection),
event: *wlr.Seat.event.RequestSetPrimarySelection,
) void {
const self = @fieldParentPtr(Self, "request_set_primary_selection", listener);
self.wlr_seat.setPrimarySelection(event.source, event.serial);
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -17,63 +17,10 @@
pub usingnamespace @cImport({
@cDefine("_POSIX_C_SOURCE", "200809L");
@cDefine("WLR_USE_UNSTABLE", {});
@cInclude("stdlib.h");
@cInclude("time.h");
@cInclude("unistd.h");
@cInclude("linux/input-event-codes.h");
@cInclude("libevdev/libevdev.h");
@cInclude("wayland-server-core.h");
@cInclude("wlr/backend.h");
@cInclude("wlr/backend/multi.h");
@cInclude("wlr/backend/noop.h");
//@cInclude("wlr/render/wlr_renderer.h");
@cInclude("wlr/types/wlr_buffer.h");
@cInclude("wlr/types/wlr_compositor.h");
@cInclude("wlr/types/wlr_cursor.h");
@cInclude("wlr/types/wlr_data_control_v1.h");
@cInclude("wlr/types/wlr_data_device.h");
@cInclude("wlr/types/wlr_export_dmabuf_v1.h");
@cInclude("wlr/types/wlr_gamma_control_v1.h");
@cInclude("wlr/types/wlr_idle.h");
@cInclude("wlr/types/wlr_input_device.h");
@cInclude("wlr/types/wlr_input_inhibitor.h");
@cInclude("wlr/types/wlr_keyboard.h");
@cInclude("wlr/types/wlr_layer_shell_v1.h");
@cInclude("wlr/types/wlr_matrix.h");
@cInclude("wlr/types/wlr_output.h");
@cInclude("wlr/types/wlr_output_layout.h");
@cInclude("wlr/types/wlr_output_management_v1.h");
@cInclude("wlr/types/wlr_output_power_management_v1.h");
@cInclude("wlr/types/wlr_pointer.h");
@cInclude("wlr/types/wlr_primary_selection.h");
@cInclude("wlr/types/wlr_primary_selection_v1.h");
@cInclude("wlr/types/wlr_screencopy_v1.h");
@cInclude("wlr/types/wlr_seat.h");
@cInclude("wlr/types/wlr_viewporter.h");
@cInclude("wlr/types/wlr_virtual_pointer_v1.h");
@cInclude("wlr/types/wlr_virtual_keyboard_v1.h");
@cInclude("wlr/types/wlr_xcursor_manager.h");
@cInclude("wlr/types/wlr_xdg_decoration_v1.h");
@cInclude("wlr/types/wlr_xdg_output_v1.h");
@cInclude("wlr/types/wlr_xdg_shell.h");
if (@import("build_options").xwayland) @cInclude("wlr/xwayland.h");
@cInclude("wlr/util/log.h");
@cInclude("xkbcommon/xkbcommon.h");
// Contains a subset of functions from wlr/backend.h and wlr/render/wlr_renderer.h
// that can be automatically imported
@cInclude("include/bindings.h");
@cInclude("river-control-unstable-v1-protocol.h");
@cInclude("river-status-unstable-v1-protocol.h");
});
// These are needed because zig currently names translated anonymous unions
// with a global counter, which makes code unportable.
// See https://github.com/ifreund/river/issues/17
pub const wlr_xdg_surface_union = @typeInfo(wlr_xdg_surface).Struct.fields[5].name;
pub const wlr_input_device_union = @typeInfo(wlr_input_device).Struct.fields[8].name;

View file

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

View file

@ -16,6 +16,9 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const mem = std.mem;
const wlr = @import("wlroots");
const xkb = @import("xkbcommon");
const c = @import("../c.zig");
const util = @import("../util.zig");
@ -25,21 +28,6 @@ const Mapping = @import("../Mapping.zig");
const PointerMapping = @import("../PointerMapping.zig");
const Seat = @import("../Seat.zig");
const modifier_names = [_]struct {
name: []const u8,
modifier: u32,
}{
.{ .name = "None", .modifier = 0 },
.{ .name = "Shift", .modifier = c.WLR_MODIFIER_SHIFT },
.{ .name = "Lock", .modifier = c.WLR_MODIFIER_CAPS },
.{ .name = "Control", .modifier = c.WLR_MODIFIER_CTRL },
.{ .name = "Mod1", .modifier = c.WLR_MODIFIER_ALT },
.{ .name = "Mod2", .modifier = c.WLR_MODIFIER_MOD2 },
.{ .name = "Mod3", .modifier = c.WLR_MODIFIER_MOD3 },
.{ .name = "Mod4", .modifier = c.WLR_MODIFIER_LOGO },
.{ .name = "Mod5", .modifier = c.WLR_MODIFIER_MOD5 },
};
/// Create a new mapping for a given mode
///
/// Example:
@ -133,9 +121,14 @@ fn modeNameToId(allocator: *std.mem.Allocator, seat: *Seat, mode_name: []const u
}
/// Returns the index of the Mapping with matching modifiers, keysym and release, if any.
fn mappingExists(mappings: *std.ArrayList(Mapping), modifiers: u32, keysym: u32, release: bool) ?usize {
fn mappingExists(
mappings: *std.ArrayList(Mapping),
modifiers: wlr.Keyboard.ModifierMask,
keysym: xkb.Keysym,
release: bool,
) ?usize {
for (mappings.items) |mapping, i| {
if (mapping.modifiers == modifiers and mapping.keysym == keysym and mapping.release == release) {
if (std.meta.eql(mapping.modifiers, modifiers) and mapping.keysym == keysym and mapping.release == release) {
return i;
}
}
@ -144,9 +137,13 @@ fn mappingExists(mappings: *std.ArrayList(Mapping), modifiers: u32, keysym: u32,
}
/// Returns the index of the PointerMapping with matching modifiers and event code, if any.
fn pointerMappingExists(pointer_mappings: *std.ArrayList(PointerMapping), modifiers: u32, event_code: u32) ?usize {
fn pointerMappingExists(
pointer_mappings: *std.ArrayList(PointerMapping),
modifiers: wlr.Keyboard.ModifierMask,
event_code: u32,
) ?usize {
for (pointer_mappings.items) |mapping, i| {
if (mapping.modifiers == modifiers and mapping.event_code == event_code) {
if (std.meta.eql(mapping.modifiers, modifiers) and mapping.event_code == event_code) {
return i;
}
}
@ -166,32 +163,41 @@ fn parseEventCode(allocator: *std.mem.Allocator, event_code_str: []const u8, out
return @intCast(u32, ret);
}
fn parseKeysym(allocator: *std.mem.Allocator, keysym_str: []const u8, out: *?[]const u8) !u32 {
fn parseKeysym(allocator: *std.mem.Allocator, keysym_str: []const u8, out: *?[]const u8) !xkb.Keysym {
const keysym_name = try std.cstr.addNullByte(allocator, keysym_str);
defer allocator.free(keysym_name);
const keysym = c.xkb_keysym_from_name(keysym_name, .XKB_KEYSYM_CASE_INSENSITIVE);
if (keysym == c.XKB_KEY_NoSymbol) {
out.* = try std.fmt.allocPrint(
allocator,
"invalid keysym '{}'",
.{keysym_str},
);
const keysym = xkb.Keysym.fromName(keysym_name, .case_insensitive);
if (keysym == .NoSymbol) {
out.* = try std.fmt.allocPrint(allocator, "invalid keysym '{}'", .{keysym_str});
return Error.Other;
}
return keysym;
}
fn parseModifiers(allocator: *std.mem.Allocator, modifiers_str: []const u8, out: *?[]const u8) !u32 {
fn parseModifiers(
allocator: *std.mem.Allocator,
modifiers_str: []const u8,
out: *?[]const u8,
) !wlr.Keyboard.ModifierMask {
var it = std.mem.split(modifiers_str, "+");
var modifiers: u32 = 0;
while (it.next()) |mod_name| {
for (modifier_names) |def| {
var modifiers = wlr.Keyboard.ModifierMask{};
outer: while (it.next()) |mod_name| {
if (mem.eql(u8, mod_name, "None")) continue;
inline for ([_]struct { name: []const u8, field_name: []const u8 }{
.{ .name = "Shift", .field_name = "shift" },
.{ .name = "Lock", .field_name = "caps" },
.{ .name = "Control", .field_name = "ctrl" },
.{ .name = "Mod1", .field_name = "alt" },
.{ .name = "Mod2", .field_name = "mod2" },
.{ .name = "Mod3", .field_name = "mod3" },
.{ .name = "Mod4", .field_name = "logo" },
.{ .name = "Mod5", .field_name = "mod5" },
}) |def| {
if (std.mem.eql(u8, def.name, mod_name)) {
modifiers |= def.modifier;
break;
@field(modifiers, def.field_name) = true;
continue :outer;
}
}
} else {
out.* = try std.fmt.allocPrint(
allocator,
"invalid modifier '{}'",
@ -199,7 +205,6 @@ fn parseModifiers(allocator: *std.mem.Allocator, modifiers_str: []const u8, out:
);
return Error.Other;
}
}
return modifiers;
}

View file

@ -17,8 +17,6 @@
const std = @import("std");
const c = @import("../c.zig");
const Error = @import("../command.zig").Error;
const Seat = @import("../Seat.zig");
@ -37,6 +35,6 @@ pub fn setRepeat(
var it = seat.keyboards.first;
while (it) |node| : (it = node.next) {
c.wlr_keyboard_set_repeat_info(node.data.wlr_keyboard, rate, delay);
node.data.input_device.device.keyboard.setRepeatInfo(rate, delay);
}
}

View file

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

View file

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

View file

@ -19,10 +19,10 @@ const std = @import("std");
const wayland = @import("wayland");
const wl = wayland.client.wl;
const river = wayland.client.river;
const zriver = wayland.client.zriver;
const SetupContext = struct {
river_control: ?*river.ControlV1 = null,
river_control: ?*zriver.ControlV1 = null,
seat: ?*wl.Seat = null,
};
@ -54,17 +54,17 @@ pub fn main() !void {
fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *SetupContext) void {
switch (event) {
.global => |global| {
if (context.seat == null and std.cstr.cmp(global.interface, wl.Seat.interface().name) == 0) {
if (context.seat == null and std.cstr.cmp(global.interface, wl.Seat.getInterface().name) == 0) {
context.seat = registry.bind(global.name, wl.Seat, 1) catch return;
} else if (std.cstr.cmp(global.interface, river.ControlV1.interface().name) == 0) {
context.river_control = registry.bind(global.name, river.ControlV1, 1) catch return;
} else if (std.cstr.cmp(global.interface, zriver.ControlV1.getInterface().name) == 0) {
context.river_control = registry.bind(global.name, zriver.ControlV1, 1) catch return;
}
},
.global_remove => {},
}
}
fn callbackListener(callback: *river.CommandCallbackV1, event: river.CommandCallbackV1.Event, _: ?*c_void) void {
fn callbackListener(callback: *zriver.CommandCallbackV1, event: zriver.CommandCallbackV1.Event, _: ?*c_void) void {
switch (event) {
.success => |success| {
if (std.mem.len(success.output) > 0) {