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:
parent
0c5e5a7b4a
commit
20d804cdb5
9
.gitmodules
vendored
9
.gitmodules
vendored
@ -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
|
||||
|
@ -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
179
build.zig
@ -1,16 +1,10 @@
|
||||
const std = @import("std");
|
||||
const zbs = std.build;
|
||||
|
||||
const ScanProtocolsStep = @import("deps/zig-wayland/build.zig").ScanProtocolsStep;
|
||||
|
||||
pub fn build(b: *std.build.Builder) !void {
|
||||
// Standard target options allows the person running `zig build` to choose
|
||||
// what target to build for. Here we do not override the defaults, which
|
||||
// means any target is allowed, and the default is native. Other options
|
||||
// for restricting supported target set are available.
|
||||
pub fn build(b: *zbs.Builder) !void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
|
||||
// Standard release options allow the person running `zig build` to select
|
||||
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
|
||||
const mode = b.standardReleaseOptions();
|
||||
|
||||
const xwayland = b.option(
|
||||
@ -31,17 +25,14 @@ pub fn build(b: *std.build.Builder) !void {
|
||||
break :scdoc_found true;
|
||||
};
|
||||
|
||||
const examples = b.option(
|
||||
bool,
|
||||
"examples",
|
||||
"Set to true to build examples",
|
||||
) orelse false;
|
||||
const examples = b.option(bool, "examples", "Set to true to build examples") orelse false;
|
||||
|
||||
// TODO: port all parts of river to zig-wayland and delete this
|
||||
const scan_protocols = OldScanProtocolsStep.create(b);
|
||||
|
||||
const scanner = ScanProtocolsStep.create(b, "deps/zig-wayland/");
|
||||
const scanner = ScanProtocolsStep.create(b);
|
||||
scanner.addSystemProtocol("stable/xdg-shell/xdg-shell.xml");
|
||||
scanner.addProtocolPath("protocol/river-control-unstable-v1.xml");
|
||||
scanner.addProtocolPath("protocol/river-status-unstable-v1.xml");
|
||||
scanner.addProtocolPath("protocol/wlr-layer-shell-unstable-v1.xml");
|
||||
scanner.addProtocolPath("protocol/wlr-output-power-management-unstable-v1.xml");
|
||||
|
||||
{
|
||||
const river = b.addExecutable("river", "river/main.zig");
|
||||
@ -49,16 +40,9 @@ pub fn build(b: *std.build.Builder) !void {
|
||||
river.setBuildMode(mode);
|
||||
river.addBuildOption(bool, "xwayland", xwayland);
|
||||
|
||||
addProtocolDeps(river, &scan_protocols.step);
|
||||
addServerDeps(river);
|
||||
addServerDeps(river, scanner);
|
||||
|
||||
river.install();
|
||||
|
||||
const run_cmd = river.run();
|
||||
run_cmd.step.dependOn(b.getInstallStep());
|
||||
|
||||
const run_step = b.step("run", "Run the compositor");
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
}
|
||||
|
||||
{
|
||||
@ -93,11 +77,13 @@ pub fn build(b: *std.build.Builder) !void {
|
||||
status.setTarget(target);
|
||||
status.setBuildMode(mode);
|
||||
|
||||
addProtocolDeps(status, &scan_protocols.step);
|
||||
|
||||
status.step.dependOn(&scanner.step);
|
||||
status.addPackage(scanner.getPkg());
|
||||
status.linkLibC();
|
||||
status.linkSystemLibrary("wayland-client");
|
||||
|
||||
scanner.addCSource(status);
|
||||
|
||||
status.install();
|
||||
}
|
||||
|
||||
@ -107,123 +93,44 @@ pub fn build(b: *std.build.Builder) !void {
|
||||
river_test.setBuildMode(mode);
|
||||
river_test.addBuildOption(bool, "xwayland", xwayland);
|
||||
|
||||
addProtocolDeps(river_test, &scan_protocols.step);
|
||||
addServerDeps(river_test);
|
||||
addServerDeps(river_test, scanner);
|
||||
|
||||
const test_step = b.step("test", "Run the tests");
|
||||
test_step.dependOn(&river_test.step);
|
||||
}
|
||||
}
|
||||
|
||||
fn addServerDeps(exe: *std.build.LibExeObjStep) void {
|
||||
exe.addCSourceFile("include/bindings.c", &[_][]const u8{"-std=c99"});
|
||||
exe.addIncludeDir(".");
|
||||
fn addServerDeps(exe: *zbs.LibExeObjStep, scanner: *ScanProtocolsStep) void {
|
||||
const wayland = scanner.getPkg();
|
||||
const xkbcommon = zbs.Pkg{ .name = "xkbcommon", .path = "deps/zig-xkbcommon/src/xkbcommon.zig" };
|
||||
const pixman = zbs.Pkg{ .name = "pixman", .path = "deps/zig-pixman/pixman.zig" };
|
||||
const wlroots = zbs.Pkg{
|
||||
.name = "wlroots",
|
||||
.path = "deps/zig-wlroots/src/wlroots.zig",
|
||||
.dependencies = &[_]zbs.Pkg{ wayland, xkbcommon, pixman },
|
||||
};
|
||||
|
||||
exe.step.dependOn(&scanner.step);
|
||||
|
||||
exe.linkLibC();
|
||||
exe.linkSystemLibrary("libevdev");
|
||||
|
||||
exe.addPackage(wayland);
|
||||
exe.linkSystemLibrary("wayland-server");
|
||||
exe.linkSystemLibrary("wlroots");
|
||||
|
||||
exe.addPackage(xkbcommon);
|
||||
exe.linkSystemLibrary("xkbcommon");
|
||||
|
||||
exe.addPackage(pixman);
|
||||
exe.linkSystemLibrary("pixman-1");
|
||||
|
||||
exe.addPackage(wlroots);
|
||||
exe.linkSystemLibrary("wlroots");
|
||||
|
||||
// TODO: remove when zig issue #131 is implemented
|
||||
scanner.addCSource(exe);
|
||||
}
|
||||
|
||||
fn addProtocolDeps(exe: *std.build.LibExeObjStep, protocol_step: *std.build.Step) void {
|
||||
exe.step.dependOn(protocol_step);
|
||||
exe.addIncludeDir("protocol");
|
||||
exe.addCSourceFile("protocol/river-control-unstable-v1-protocol.c", &[_][]const u8{"-std=c99"});
|
||||
exe.addCSourceFile("protocol/river-status-unstable-v1-protocol.c", &[_][]const u8{"-std=c99"});
|
||||
}
|
||||
|
||||
const OldScanProtocolsStep = struct {
|
||||
builder: *std.build.Builder,
|
||||
step: std.build.Step,
|
||||
|
||||
fn create(builder: *std.build.Builder) *OldScanProtocolsStep {
|
||||
const self = builder.allocator.create(OldScanProtocolsStep) catch @panic("out of memory");
|
||||
self.* = init(builder);
|
||||
return self;
|
||||
}
|
||||
|
||||
fn init(builder: *std.build.Builder) OldScanProtocolsStep {
|
||||
return OldScanProtocolsStep{
|
||||
.builder = builder,
|
||||
.step = std.build.Step.init(.Custom, "Scan Protocols", builder.allocator, make),
|
||||
};
|
||||
}
|
||||
|
||||
fn make(step: *std.build.Step) !void {
|
||||
const self = @fieldParentPtr(OldScanProtocolsStep, "step", step);
|
||||
|
||||
const protocol_dir = std.mem.trim(u8, try self.builder.exec(
|
||||
&[_][]const u8{ "pkg-config", "--variable=pkgdatadir", "wayland-protocols" },
|
||||
), &std.ascii.spaces);
|
||||
|
||||
const protocol_dir_paths = [_][]const []const u8{
|
||||
&[_][]const u8{ protocol_dir, "stable/xdg-shell/xdg-shell.xml" },
|
||||
&[_][]const u8{ "protocol", "wlr-layer-shell-unstable-v1.xml" },
|
||||
&[_][]const u8{ "protocol", "wlr-output-power-management-unstable-v1.xml" },
|
||||
&[_][]const u8{ "protocol", "river-control-unstable-v1.xml" },
|
||||
&[_][]const u8{ "protocol", "river-status-unstable-v1.xml" },
|
||||
};
|
||||
|
||||
const server_protocols = [_][]const u8{
|
||||
"xdg-shell",
|
||||
"wlr-layer-shell-unstable-v1",
|
||||
"wlr-output-power-management-unstable-v1",
|
||||
"river-control-unstable-v1",
|
||||
"river-status-unstable-v1",
|
||||
};
|
||||
|
||||
const client_protocols = [_][]const u8{
|
||||
"river-control-unstable-v1",
|
||||
"river-status-unstable-v1",
|
||||
};
|
||||
|
||||
for (protocol_dir_paths) |dir_path| {
|
||||
const xml_in_path = try std.fs.path.join(self.builder.allocator, dir_path);
|
||||
|
||||
// Extension is .xml, so slice off the last 4 characters
|
||||
const basename = std.fs.path.basename(xml_in_path);
|
||||
const basename_no_ext = basename[0..(basename.len - 4)];
|
||||
|
||||
const code_out_path = try std.mem.concat(
|
||||
self.builder.allocator,
|
||||
u8,
|
||||
&[_][]const u8{ "protocol/", basename_no_ext, "-protocol.c" },
|
||||
);
|
||||
_ = try self.builder.exec(
|
||||
&[_][]const u8{ "wayland-scanner", "private-code", xml_in_path, code_out_path },
|
||||
);
|
||||
|
||||
for (server_protocols) |server_protocol| {
|
||||
if (std.mem.eql(u8, basename_no_ext, server_protocol)) {
|
||||
const header_out_path = try std.mem.concat(
|
||||
self.builder.allocator,
|
||||
u8,
|
||||
&[_][]const u8{ "protocol/", basename_no_ext, "-protocol.h" },
|
||||
);
|
||||
_ = try self.builder.exec(
|
||||
&[_][]const u8{ "wayland-scanner", "server-header", xml_in_path, header_out_path },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (client_protocols) |client_protocol| {
|
||||
if (std.mem.eql(u8, basename_no_ext, client_protocol)) {
|
||||
const header_out_path = try std.mem.concat(
|
||||
self.builder.allocator,
|
||||
u8,
|
||||
&[_][]const u8{ "protocol/", basename_no_ext, "-client-protocol.h" },
|
||||
);
|
||||
_ = try self.builder.exec(
|
||||
&[_][]const u8{ "wayland-scanner", "client-header", xml_in_path, header_out_path },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const ScdocStep = struct {
|
||||
const scd_paths = [_][]const u8{
|
||||
"doc/river.1.scd",
|
||||
@ -232,23 +139,23 @@ const ScdocStep = struct {
|
||||
"doc/river-layouts.7.scd",
|
||||
};
|
||||
|
||||
builder: *std.build.Builder,
|
||||
step: std.build.Step,
|
||||
builder: *zbs.Builder,
|
||||
step: zbs.Step,
|
||||
|
||||
fn create(builder: *std.build.Builder) *ScdocStep {
|
||||
fn create(builder: *zbs.Builder) *ScdocStep {
|
||||
const self = builder.allocator.create(ScdocStep) catch @panic("out of memory");
|
||||
self.* = init(builder);
|
||||
return self;
|
||||
}
|
||||
|
||||
fn init(builder: *std.build.Builder) ScdocStep {
|
||||
fn init(builder: *zbs.Builder) ScdocStep {
|
||||
return ScdocStep{
|
||||
.builder = builder,
|
||||
.step = std.build.Step.init(.Custom, "Generate man pages", builder.allocator, make),
|
||||
.step = zbs.Step.init(.Custom, "Generate man pages", builder.allocator, make),
|
||||
};
|
||||
}
|
||||
|
||||
fn make(step: *std.build.Step) !void {
|
||||
fn make(step: *zbs.Step) !void {
|
||||
const self = @fieldParentPtr(ScdocStep, "step", step);
|
||||
for (scd_paths) |path| {
|
||||
const command = try std.fmt.allocPrint(
|
||||
|
1
deps/zig-pixman
vendored
Submodule
1
deps/zig-pixman
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 7847fd1bae7021cdb572e77eac93676c551fd1eb
|
2
deps/zig-wayland
vendored
2
deps/zig-wayland
vendored
@ -1 +1 @@
|
||||
Subproject commit ba49b2b6f984b788aea5e752bfeb64e3381472e7
|
||||
Subproject commit 52326e7ee09d7acb6b55855f7a697af083ae973a
|
1
deps/zig-wlroots
vendored
Submodule
1
deps/zig-wlroots
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 16d9039b5c345b2cc26118032261df9782e24946
|
1
deps/zig-xkbcommon
vendored
Submodule
1
deps/zig-xkbcommon
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 9e4d41fe9414094db31c873c2ad9cadcd8999cf6
|
@ -17,145 +17,77 @@
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const c = @cImport({
|
||||
@cInclude("wayland-client.h");
|
||||
@cInclude("river-status-unstable-v1-client-protocol.h");
|
||||
});
|
||||
const wayland = @import("wayland");
|
||||
const wl = wayland.client.wl;
|
||||
const zriver = wayland.client.zriver;
|
||||
|
||||
const wl_registry_listener = c.wl_registry_listener{
|
||||
.global = handleGlobal,
|
||||
.global_remove = handleGlobalRemove,
|
||||
const SetupContext = struct {
|
||||
status_manager: ?*zriver.StatusManagerV1 = null,
|
||||
outputs: std.ArrayList(*wl.Output) = std.ArrayList(*wl.Output).init(std.heap.c_allocator),
|
||||
seats: std.ArrayList(*wl.Seat) = std.ArrayList(*wl.Seat).init(std.heap.c_allocator),
|
||||
};
|
||||
|
||||
const river_output_status_listener = c.zriver_output_status_v1_listener{
|
||||
.focused_tags = handleFocusedTags,
|
||||
.view_tags = handleViewTags,
|
||||
};
|
||||
|
||||
const river_seat_status_listener = c.zriver_seat_status_v1_listener{
|
||||
.focused_output = handleFocusedOutput,
|
||||
.unfocused_output = handleUnfocusedOutput,
|
||||
.focused_view = handleFocusedView,
|
||||
};
|
||||
|
||||
var river_status_manager: ?*c.zriver_status_manager_v1 = null;
|
||||
|
||||
var outputs = std.ArrayList(*c.wl_output).init(std.heap.c_allocator);
|
||||
var seats = std.ArrayList(*c.wl_seat).init(std.heap.c_allocator);
|
||||
|
||||
pub fn main() !void {
|
||||
const wl_display = c.wl_display_connect(null) orelse return error.CantConnectToDisplay;
|
||||
const wl_registry = c.wl_display_get_registry(wl_display);
|
||||
const display = try wl.Display.connect(null);
|
||||
const registry = try display.getRegistry();
|
||||
|
||||
if (c.wl_registry_add_listener(wl_registry, &wl_registry_listener, null) < 0)
|
||||
return error.FailedToAddListener;
|
||||
if (c.wl_display_roundtrip(wl_display) < 0) return error.RoundtripFailed;
|
||||
var context = SetupContext{};
|
||||
|
||||
if (river_status_manager == null) return error.RiverStatusManagerNotAdvertised;
|
||||
registry.setListener(*SetupContext, registryListener, &context) catch unreachable;
|
||||
_ = try display.roundtrip();
|
||||
|
||||
for (outputs.items) |wl_output| createOutputStatus(wl_output);
|
||||
for (seats.items) |wl_seat| createSeatStatus(wl_seat);
|
||||
outputs.deinit();
|
||||
seats.deinit();
|
||||
const status_manager = context.status_manager orelse return error.RiverStatusManagerNotAdvertised;
|
||||
|
||||
for (context.outputs.items) |output| {
|
||||
const output_status = try status_manager.getRiverOutputStatus(output);
|
||||
output_status.setListener(?*c_void, outputStatusListener, null) catch unreachable;
|
||||
}
|
||||
for (context.seats.items) |seat| {
|
||||
const seat_status = try status_manager.getRiverSeatStatus(seat);
|
||||
seat_status.setListener(?*c_void, seatStatusListener, null) catch unreachable;
|
||||
}
|
||||
context.outputs.deinit();
|
||||
context.seats.deinit();
|
||||
|
||||
// Loop forever, listening for new events.
|
||||
while (true) if (c.wl_display_dispatch(wl_display) < 0) return error.DispatchFailed;
|
||||
while (true) _ = try display.dispatch();
|
||||
}
|
||||
|
||||
fn handleGlobal(
|
||||
data: ?*c_void,
|
||||
wl_registry: ?*c.wl_registry,
|
||||
name: u32,
|
||||
interface: ?[*:0]const u8,
|
||||
version: u32,
|
||||
) callconv(.C) void {
|
||||
// Global advertisement order is not defined, so save any outputs or seats
|
||||
// advertised before the river_status_manager.
|
||||
if (std.cstr.cmp(interface.?, @ptrCast([*:0]const u8, c.zriver_status_manager_v1_interface.name.?)) == 0) {
|
||||
river_status_manager = @ptrCast(
|
||||
*c.zriver_status_manager_v1,
|
||||
c.wl_registry_bind(wl_registry, name, &c.zriver_status_manager_v1_interface, version),
|
||||
);
|
||||
} else if (std.cstr.cmp(interface.?, @ptrCast([*:0]const u8, c.wl_output_interface.name.?)) == 0) {
|
||||
const wl_output = @ptrCast(
|
||||
*c.wl_output,
|
||||
c.wl_registry_bind(wl_registry, name, &c.wl_output_interface, version),
|
||||
);
|
||||
outputs.append(wl_output) catch @panic("out of memory");
|
||||
} else if (std.cstr.cmp(interface.?, @ptrCast([*:0]const u8, c.wl_seat_interface.name.?)) == 0) {
|
||||
const wl_seat = @ptrCast(
|
||||
*c.wl_seat,
|
||||
c.wl_registry_bind(wl_registry, name, &c.wl_seat_interface, version),
|
||||
);
|
||||
seats.append(wl_seat) catch @panic("out of memory");
|
||||
fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *SetupContext) void {
|
||||
switch (event) {
|
||||
.global => |global| {
|
||||
if (std.cstr.cmp(global.interface, zriver.StatusManagerV1.getInterface().name) == 0) {
|
||||
context.status_manager = registry.bind(global.name, zriver.StatusManagerV1, 1) catch return;
|
||||
} else if (std.cstr.cmp(global.interface, wl.Seat.getInterface().name) == 0) {
|
||||
const seat = registry.bind(global.name, wl.Seat, 1) catch return;
|
||||
context.seats.append(seat) catch @panic("out of memory");
|
||||
} else if (std.cstr.cmp(global.interface, wl.Output.getInterface().name) == 0) {
|
||||
const output = registry.bind(global.name, wl.Output, 1) catch return;
|
||||
context.outputs.append(output) catch @panic("out of memory");
|
||||
}
|
||||
},
|
||||
.global_remove => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn createOutputStatus(wl_output: *c.wl_output) void {
|
||||
const river_output_status = c.zriver_status_manager_v1_get_river_output_status(
|
||||
river_status_manager.?,
|
||||
wl_output,
|
||||
);
|
||||
_ = c.zriver_output_status_v1_add_listener(
|
||||
river_output_status,
|
||||
&river_output_status_listener,
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
fn createSeatStatus(wl_seat: *c.wl_seat) void {
|
||||
const river_seat_status = c.zriver_status_manager_v1_get_river_seat_status(
|
||||
river_status_manager.?,
|
||||
wl_seat,
|
||||
);
|
||||
_ = c.zriver_seat_status_v1_add_listener(river_seat_status, &river_seat_status_listener, null);
|
||||
}
|
||||
|
||||
fn handleGlobalRemove(data: ?*c_void, wl_registry: ?*c.wl_registry, name: u32) callconv(.C) void {
|
||||
// Ignore the event
|
||||
}
|
||||
|
||||
fn handleFocusedTags(
|
||||
data: ?*c_void,
|
||||
output_status: ?*c.zriver_output_status_v1,
|
||||
tags: u32,
|
||||
) callconv(.C) void {
|
||||
std.debug.warn("Focused tags: {b:0>10}\n", .{tags});
|
||||
}
|
||||
|
||||
fn handleViewTags(
|
||||
data: ?*c_void,
|
||||
output_status: ?*c.zriver_output_status_v1,
|
||||
tags: ?*c.wl_array,
|
||||
) callconv(.C) void {
|
||||
std.debug.warn("View tags:\n", .{});
|
||||
var offset: usize = 0;
|
||||
while (offset < tags.?.size) : (offset += @sizeOf(u32)) {
|
||||
const ptr = @ptrCast([*]u8, tags.?.data) + offset;
|
||||
std.debug.warn("{b:0>10}\n", .{std.mem.bytesToValue(u32, ptr[0..4])});
|
||||
fn outputStatusListener(output_status: *zriver.OutputStatusV1, event: zriver.OutputStatusV1.Event, data: ?*c_void) void {
|
||||
switch (event) {
|
||||
.focused_tags => |focused_tags| std.debug.warn("Focused tags: {b:0>10}\n", .{focused_tags.tags}),
|
||||
.view_tags => |view_tags| {
|
||||
std.debug.warn("View tags:\n", .{});
|
||||
for (view_tags.tags.slice(u32)) |t| std.debug.warn("{b:0>10}\n", .{t});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn handleFocusedOutput(
|
||||
data: ?*c_void,
|
||||
seat_status: ?*c.zriver_seat_status_v1,
|
||||
wl_output: ?*c.wl_output,
|
||||
) callconv(.C) void {
|
||||
std.debug.warn("Output id {} focused\n", .{c.wl_proxy_get_id(@ptrCast(*c.wl_proxy, wl_output))});
|
||||
}
|
||||
|
||||
fn handleUnfocusedOutput(
|
||||
data: ?*c_void,
|
||||
seat_status: ?*c.zriver_seat_status_v1,
|
||||
wl_output: ?*c.wl_output,
|
||||
) callconv(.C) void {
|
||||
std.debug.warn("Output id {} unfocused\n", .{c.wl_proxy_get_id(@ptrCast(*c.wl_proxy, wl_output))});
|
||||
}
|
||||
|
||||
fn handleFocusedView(
|
||||
data: ?*c_void,
|
||||
seat_status: ?*c.zriver_seat_status_v1,
|
||||
title: ?[*:0]const u8,
|
||||
) callconv(.C) void {
|
||||
std.debug.warn("Focused view title: {}\n", .{title.?});
|
||||
fn seatStatusListener(seat_status: *zriver.SeatStatusV1, event: zriver.SeatStatusV1.Event, data: ?*c_void) void {
|
||||
switch (event) {
|
||||
.focused_output => |focused_output| std.debug.warn("Output id {} focused\n", .{
|
||||
@ptrCast(*wl.Proxy, focused_output.output orelse return).getId(),
|
||||
}),
|
||||
.unfocused_output => |unfocused_output| std.debug.warn("Output id {} focused\n", .{
|
||||
@ptrCast(*wl.Proxy, unfocused_output.output orelse return).getId(),
|
||||
}),
|
||||
.focused_view => |focused_view| std.debug.warn("Focused view title: {}\n", .{focused_view.title}),
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
@ -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
|
@ -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),
|
||||
|
@ -18,6 +18,13 @@
|
||||
const Self = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const mem = std.mem;
|
||||
|
||||
const wayland = @import("wayland");
|
||||
const wl = wayland.server.wl;
|
||||
const zriver = wayland.server.zriver;
|
||||
|
||||
const wlr = @import("wlroots");
|
||||
|
||||
const c = @import("c.zig");
|
||||
const command = @import("command.zig");
|
||||
@ -26,141 +33,106 @@ const util = @import("util.zig");
|
||||
const Seat = @import("Seat.zig");
|
||||
const Server = @import("Server.zig");
|
||||
|
||||
const protocol_version = 1;
|
||||
|
||||
const implementation = c.struct_zriver_control_v1_interface{
|
||||
.destroy = destroy,
|
||||
.add_argument = addArgument,
|
||||
.run_command = runCommand,
|
||||
};
|
||||
|
||||
wl_global: *c.wl_global,
|
||||
global: *wl.Global,
|
||||
|
||||
args_map: std.AutoHashMap(u32, std.ArrayList([]const u8)),
|
||||
|
||||
listen_display_destroy: c.wl_listener = undefined,
|
||||
server_destroy: wl.Listener(*wl.Server) = undefined,
|
||||
|
||||
pub fn init(self: *Self, server: *Server) !void {
|
||||
self.* = .{
|
||||
.wl_global = c.wl_global_create(
|
||||
server.wl_display,
|
||||
&c.zriver_control_v1_interface,
|
||||
protocol_version,
|
||||
self,
|
||||
bind,
|
||||
) orelse return error.OutOfMemory,
|
||||
.global = try wl.Global.create(server.wl_server, zriver.ControlV1, 1, *Self, self, bind),
|
||||
.args_map = std.AutoHashMap(u32, std.ArrayList([]const u8)).init(util.gpa),
|
||||
};
|
||||
|
||||
self.listen_display_destroy.notify = handleDisplayDestroy;
|
||||
c.wl_display_add_destroy_listener(server.wl_display, &self.listen_display_destroy);
|
||||
self.server_destroy.setNotify(handleServerDestroy);
|
||||
server.wl_server.addDestroyListener(&self.server_destroy);
|
||||
}
|
||||
|
||||
fn handleDisplayDestroy(wl_listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||
const self = @fieldParentPtr(Self, "listen_display_destroy", wl_listener.?);
|
||||
c.wl_global_destroy(self.wl_global);
|
||||
fn handleServerDestroy(listener: *wl.Listener(*wl.Server), wl_server: *wl.Server) void {
|
||||
const self = @fieldParentPtr(Self, "server_destroy", listener);
|
||||
self.global.destroy();
|
||||
self.args_map.deinit();
|
||||
}
|
||||
|
||||
/// Called when a client binds our global
|
||||
fn bind(wl_client: ?*c.wl_client, data: ?*c_void, version: u32, id: u32) callconv(.C) void {
|
||||
const self = util.voidCast(Self, data.?);
|
||||
const wl_resource = c.wl_resource_create(
|
||||
wl_client,
|
||||
&c.zriver_control_v1_interface,
|
||||
@intCast(c_int, version),
|
||||
id,
|
||||
) orelse {
|
||||
c.wl_client_post_no_memory(wl_client);
|
||||
fn bind(client: *wl.Client, self: *Self, version: u32, id: u32) callconv(.C) void {
|
||||
const control = zriver.ControlV1.create(client, version, id) catch {
|
||||
client.postNoMemory();
|
||||
return;
|
||||
};
|
||||
self.args_map.putNoClobber(id, std.ArrayList([]const u8).init(util.gpa)) catch {
|
||||
c.wl_resource_destroy(wl_resource);
|
||||
c.wl_client_post_no_memory(wl_client);
|
||||
control.destroy();
|
||||
client.postNoMemory();
|
||||
return;
|
||||
};
|
||||
c.wl_resource_set_implementation(wl_resource, &implementation, self, handleResourceDestroy);
|
||||
control.setHandler(*Self, handleRequest, handleDestroy, self);
|
||||
}
|
||||
|
||||
fn handleRequest(control: *zriver.ControlV1, request: zriver.ControlV1.Request, self: *Self) void {
|
||||
switch (request) {
|
||||
.destroy => control.destroy(),
|
||||
.add_argument => |add_argument| {
|
||||
const owned_slice = mem.dupe(util.gpa, u8, mem.span(add_argument.argument)) catch {
|
||||
control.getClient().postNoMemory();
|
||||
return;
|
||||
};
|
||||
|
||||
self.args_map.getEntry(control.getId()).?.value.append(owned_slice) catch {
|
||||
control.getClient().postNoMemory();
|
||||
util.gpa.free(owned_slice);
|
||||
return;
|
||||
};
|
||||
},
|
||||
.run_command => |run_command| {
|
||||
const seat = @intToPtr(*Seat, wlr.Seat.Client.fromWlSeat(run_command.seat).?.seat.data);
|
||||
|
||||
const callback = zriver.CommandCallbackV1.create(
|
||||
control.getClient(),
|
||||
control.getVersion(),
|
||||
run_command.callback,
|
||||
) catch {
|
||||
control.getClient().postNoMemory();
|
||||
return;
|
||||
};
|
||||
|
||||
const args = self.args_map.get(control.getId()).?.items;
|
||||
|
||||
var out: ?[]const u8 = null;
|
||||
defer if (out) |s| util.gpa.free(s);
|
||||
command.run(util.gpa, seat, args, &out) catch |err| {
|
||||
const failure_message = switch (err) {
|
||||
command.Error.OutOfMemory => {
|
||||
callback.getClient().postNoMemory();
|
||||
return;
|
||||
},
|
||||
command.Error.Other => std.cstr.addNullByte(util.gpa, out.?) catch {
|
||||
callback.getClient().postNoMemory();
|
||||
return;
|
||||
},
|
||||
else => command.errToMsg(err),
|
||||
};
|
||||
defer if (err == command.Error.Other) util.gpa.free(failure_message);
|
||||
callback.sendFailure(failure_message);
|
||||
return;
|
||||
};
|
||||
|
||||
const success_message = if (out) |s|
|
||||
std.cstr.addNullByte(util.gpa, s) catch {
|
||||
callback.getClient().postNoMemory();
|
||||
return;
|
||||
}
|
||||
else
|
||||
"";
|
||||
defer if (out != null) util.gpa.free(success_message);
|
||||
callback.sendSuccess(success_message);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove the resource from the hash map and free all stored args
|
||||
fn handleResourceDestroy(wl_resource: ?*c.wl_resource) callconv(.C) void {
|
||||
const self = util.voidCast(Self, c.wl_resource_get_user_data(wl_resource).?);
|
||||
const id = c.wl_resource_get_id(wl_resource);
|
||||
const list = self.args_map.remove(id).?.value;
|
||||
fn handleDestroy(control: *zriver.ControlV1, self: *Self) void {
|
||||
const list = self.args_map.remove(control.getId()).?.value;
|
||||
for (list.items) |arg| list.allocator.free(arg);
|
||||
list.deinit();
|
||||
}
|
||||
|
||||
fn destroy(wl_client: ?*c.wl_client, wl_resource: ?*c.wl_resource) callconv(.C) void {
|
||||
c.wl_resource_destroy(wl_resource);
|
||||
}
|
||||
|
||||
fn addArgument(wl_client: ?*c.wl_client, wl_resource: ?*c.wl_resource, arg: ?[*:0]const u8) callconv(.C) void {
|
||||
const self = util.voidCast(Self, c.wl_resource_get_user_data(wl_resource).?);
|
||||
const id = c.wl_resource_get_id(wl_resource);
|
||||
|
||||
const owned_slice = std.mem.dupe(util.gpa, u8, std.mem.span(arg.?)) catch {
|
||||
c.wl_client_post_no_memory(wl_client);
|
||||
return;
|
||||
};
|
||||
|
||||
self.args_map.getEntry(id).?.value.append(owned_slice) catch {
|
||||
c.wl_client_post_no_memory(wl_client);
|
||||
util.gpa.free(owned_slice);
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
fn runCommand(
|
||||
wl_client: ?*c.wl_client,
|
||||
wl_resource: ?*c.wl_resource,
|
||||
seat_wl_resource: ?*c.wl_resource,
|
||||
callback_id: u32,
|
||||
) callconv(.C) void {
|
||||
const self = util.voidCast(Self, c.wl_resource_get_user_data(wl_resource).?);
|
||||
// This can be null if the seat is inert, in which case we ignore the request
|
||||
const wlr_seat_client = c.wlr_seat_client_from_resource(seat_wl_resource) orelse return;
|
||||
const seat = util.voidCast(Seat, wlr_seat_client.*.seat.*.data.?);
|
||||
|
||||
const callback_resource = c.wl_resource_create(
|
||||
wl_client,
|
||||
&c.zriver_command_callback_v1_interface,
|
||||
protocol_version,
|
||||
callback_id,
|
||||
) orelse {
|
||||
c.wl_client_post_no_memory(wl_client);
|
||||
return;
|
||||
};
|
||||
c.wl_resource_set_implementation(callback_resource, null, null, null);
|
||||
|
||||
const args = self.args_map.get(c.wl_resource_get_id(wl_resource)).?.items;
|
||||
|
||||
var out: ?[]const u8 = null;
|
||||
defer if (out) |s| util.gpa.free(s);
|
||||
command.run(util.gpa, seat, args, &out) catch |err| {
|
||||
const failure_message = switch (err) {
|
||||
command.Error.OutOfMemory => {
|
||||
c.wl_client_post_no_memory(wl_client);
|
||||
return;
|
||||
},
|
||||
command.Error.Other => std.cstr.addNullByte(util.gpa, out.?) catch {
|
||||
c.wl_client_post_no_memory(wl_client);
|
||||
return;
|
||||
},
|
||||
else => command.errToMsg(err),
|
||||
};
|
||||
defer if (err == command.Error.Other) util.gpa.free(failure_message);
|
||||
c.zriver_command_callback_v1_send_failure(callback_resource, failure_message);
|
||||
return;
|
||||
};
|
||||
|
||||
const success_message = if (out) |s|
|
||||
std.cstr.addNullByte(util.gpa, s) catch {
|
||||
c.wl_client_post_no_memory(wl_client);
|
||||
return;
|
||||
}
|
||||
else
|
||||
"";
|
||||
defer if (out != null) util.gpa.free(success_message);
|
||||
c.zriver_command_callback_v1_send_success(callback_resource, success_message);
|
||||
}
|
||||
|
302
river/Cursor.zig
302
river/Cursor.zig
@ -19,6 +19,10 @@ const Self = @This();
|
||||
|
||||
const build_options = @import("build_options");
|
||||
const std = @import("std");
|
||||
const wlr = @import("wlroots");
|
||||
const wayland = @import("wayland");
|
||||
const wl = wayland.server.wl;
|
||||
const zwlr = wayland.server.zwlr;
|
||||
|
||||
const c = @import("c.zig");
|
||||
const log = @import("log.zig");
|
||||
@ -50,34 +54,34 @@ const default_size = 24;
|
||||
mode: Mode = .passthrough,
|
||||
|
||||
seat: *Seat,
|
||||
wlr_cursor: *c.wlr_cursor,
|
||||
wlr_xcursor_manager: *c.wlr_xcursor_manager,
|
||||
wlr_cursor: *wlr.Cursor,
|
||||
xcursor_manager: *wlr.XcursorManager,
|
||||
|
||||
/// Number of distinct buttons currently pressed
|
||||
pressed_count: u32 = 0,
|
||||
|
||||
listen_axis: c.wl_listener = undefined,
|
||||
listen_button: c.wl_listener = undefined,
|
||||
listen_frame: c.wl_listener = undefined,
|
||||
listen_motion_absolute: c.wl_listener = undefined,
|
||||
listen_motion: c.wl_listener = undefined,
|
||||
listen_request_set_cursor: c.wl_listener = undefined,
|
||||
axis: wl.Listener(*wlr.Pointer.event.Axis) = undefined,
|
||||
button: wl.Listener(*wlr.Pointer.event.Button) = undefined,
|
||||
frame: wl.Listener(*wlr.Cursor) = undefined,
|
||||
motion_absolute: wl.Listener(*wlr.Pointer.event.MotionAbsolute) = undefined,
|
||||
motion: wl.Listener(*wlr.Pointer.event.Motion) = undefined,
|
||||
request_set_cursor: wl.Listener(*wlr.Seat.event.RequestSetCursor) = undefined,
|
||||
|
||||
pub fn init(self: *Self, seat: *Seat) !void {
|
||||
const wlr_cursor = c.wlr_cursor_create() orelse return error.OutOfMemory;
|
||||
errdefer c.wlr_cursor_destroy(wlr_cursor);
|
||||
c.wlr_cursor_attach_output_layout(wlr_cursor, seat.input_manager.server.root.wlr_output_layout);
|
||||
const wlr_cursor = try wlr.Cursor.create();
|
||||
errdefer wlr_cursor.destroy();
|
||||
wlr_cursor.attachOutputLayout(seat.input_manager.server.root.output_layout);
|
||||
|
||||
// This is here so that self.wlr_xcursor_manager doesn't need to be an
|
||||
// This is here so that self.xcursor_manager doesn't need to be an
|
||||
// optional pointer. This isn't optimal as it does a needless allocation,
|
||||
// but this is not a hot path.
|
||||
const wlr_xcursor_manager = c.wlr_xcursor_manager_create(null, default_size) orelse return error.OutOfMemory;
|
||||
errdefer c.wlr_xcursor_manager_destroy(wlr_xcursor_manager);
|
||||
const xcursor_manager = try wlr.XcursorManager.create(null, default_size);
|
||||
errdefer xcursor_manager.destroy();
|
||||
|
||||
self.* = .{
|
||||
.seat = seat,
|
||||
.wlr_cursor = wlr_cursor,
|
||||
.wlr_xcursor_manager = wlr_xcursor_manager,
|
||||
.xcursor_manager = xcursor_manager,
|
||||
};
|
||||
try self.setTheme(null, null);
|
||||
|
||||
@ -87,28 +91,28 @@ pub fn init(self: *Self, seat: *Seat) !void {
|
||||
// can choose how we want to process them, forwarding them to clients and
|
||||
// moving the cursor around. See following post for more detail:
|
||||
// https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html
|
||||
self.listen_axis.notify = handleAxis;
|
||||
c.wl_signal_add(&self.wlr_cursor.events.axis, &self.listen_axis);
|
||||
self.axis.setNotify(handleAxis);
|
||||
self.wlr_cursor.events.axis.add(&self.axis);
|
||||
|
||||
self.listen_button.notify = handleButton;
|
||||
c.wl_signal_add(&self.wlr_cursor.events.button, &self.listen_button);
|
||||
self.button.setNotify(handleButton);
|
||||
self.wlr_cursor.events.button.add(&self.button);
|
||||
|
||||
self.listen_frame.notify = handleFrame;
|
||||
c.wl_signal_add(&self.wlr_cursor.events.frame, &self.listen_frame);
|
||||
self.frame.setNotify(handleFrame);
|
||||
self.wlr_cursor.events.frame.add(&self.frame);
|
||||
|
||||
self.listen_motion_absolute.notify = handleMotionAbsolute;
|
||||
c.wl_signal_add(&self.wlr_cursor.events.motion_absolute, &self.listen_motion_absolute);
|
||||
self.motion_absolute.setNotify(handleMotionAbsolute);
|
||||
self.wlr_cursor.events.motion_absolute.add(&self.motion_absolute);
|
||||
|
||||
self.listen_motion.notify = handleMotion;
|
||||
c.wl_signal_add(&self.wlr_cursor.events.motion, &self.listen_motion);
|
||||
self.motion.setNotify(handleMotion);
|
||||
self.wlr_cursor.events.motion.add(&self.motion);
|
||||
|
||||
self.listen_request_set_cursor.notify = handleRequestSetCursor;
|
||||
c.wl_signal_add(&self.seat.wlr_seat.events.request_set_cursor, &self.listen_request_set_cursor);
|
||||
self.request_set_cursor.setNotify(handleRequestSetCursor);
|
||||
self.seat.wlr_seat.events.request_set_cursor.add(&self.request_set_cursor);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
c.wlr_xcursor_manager_destroy(self.wlr_xcursor_manager);
|
||||
c.wlr_cursor_destroy(self.wlr_cursor);
|
||||
self.xcursor_manager.destroy();
|
||||
self.wlr_cursor.destroy();
|
||||
}
|
||||
|
||||
/// Set the cursor theme for the given seat, as well as the xwayland theme if
|
||||
@ -118,15 +122,14 @@ pub fn setTheme(self: *Self, theme: ?[*:0]const u8, _size: ?u32) !void {
|
||||
const server = self.seat.input_manager.server;
|
||||
const size = _size orelse default_size;
|
||||
|
||||
c.wlr_xcursor_manager_destroy(self.wlr_xcursor_manager);
|
||||
self.wlr_xcursor_manager = c.wlr_xcursor_manager_create(theme, size) orelse
|
||||
return error.OutOfMemory;
|
||||
self.xcursor_manager.destroy();
|
||||
self.xcursor_manager = try wlr.XcursorManager.create(theme, size);
|
||||
|
||||
// For each output, ensure a theme of the proper scale is loaded
|
||||
var it = server.root.outputs.first;
|
||||
while (it) |node| : (it = node.next) {
|
||||
const wlr_output = node.data.wlr_output;
|
||||
if (!c.wlr_xcursor_manager_load(self.wlr_xcursor_manager, wlr_output.scale))
|
||||
self.xcursor_manager.load(wlr_output.scale) catch
|
||||
log.err(.cursor, "failed to load xcursor theme '{}' at scale {}", .{ theme, wlr_output.scale });
|
||||
}
|
||||
|
||||
@ -139,19 +142,20 @@ pub fn setTheme(self: *Self, theme: ?[*:0]const u8, _size: ?u32) !void {
|
||||
if (theme) |t| if (c.setenv("XCURSOR_THEME", t, 1) < 0) return error.OutOfMemory;
|
||||
|
||||
if (build_options.xwayland) {
|
||||
if (c.wlr_xcursor_manager_load(self.wlr_xcursor_manager, 1)) {
|
||||
const wlr_xcursor = c.wlr_xcursor_manager_get_xcursor(self.wlr_xcursor_manager, "left_ptr", 1).?;
|
||||
const image: *c.wlr_xcursor_image = wlr_xcursor.*.images[0];
|
||||
c.wlr_xwayland_set_cursor(
|
||||
server.wlr_xwayland,
|
||||
image.buffer,
|
||||
image.width * 4,
|
||||
image.width,
|
||||
image.height,
|
||||
@intCast(i32, image.hotspot_x),
|
||||
@intCast(i32, image.hotspot_y),
|
||||
);
|
||||
} else log.err(.cursor, "failed to load xcursor theme '{}' at scale 1", .{theme});
|
||||
self.xcursor_manager.load(1) catch {
|
||||
log.err(.cursor, "failed to load xcursor theme '{}' at scale 1", .{theme});
|
||||
return;
|
||||
};
|
||||
const wlr_xcursor = self.xcursor_manager.getXcursor("left_ptr", 1).?;
|
||||
const image = wlr_xcursor.images[0];
|
||||
server.xwayland.setCursor(
|
||||
image.buffer,
|
||||
image.width * 4,
|
||||
image.width,
|
||||
image.height,
|
||||
@intCast(i32, image.hotspot_x),
|
||||
@intCast(i32, image.hotspot_y),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -173,25 +177,18 @@ pub fn handleViewUnmap(self: *Self, view: *View) void {
|
||||
}
|
||||
|
||||
fn clearFocus(self: Self) void {
|
||||
c.wlr_xcursor_manager_set_cursor_image(
|
||||
self.wlr_xcursor_manager,
|
||||
"left_ptr",
|
||||
self.wlr_cursor,
|
||||
);
|
||||
c.wlr_seat_pointer_clear_focus(self.seat.wlr_seat);
|
||||
self.xcursor_manager.setCursorImage("left_ptr", self.wlr_cursor);
|
||||
self.seat.wlr_seat.pointerNotifyClearFocus();
|
||||
}
|
||||
|
||||
fn handleAxis(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||
// This event is forwarded by the cursor when a pointer emits an axis event,
|
||||
// for example when you move the scroll wheel.
|
||||
const self = @fieldParentPtr(Self, "listen_axis", listener.?);
|
||||
const event = util.voidCast(c.wlr_event_pointer_axis, data.?);
|
||||
/// Axis event is a scroll wheel or similiar
|
||||
fn handleAxis(listener: *wl.Listener(*wlr.Pointer.event.Axis), event: *wlr.Pointer.event.Axis) void {
|
||||
const self = @fieldParentPtr(Self, "axis", listener);
|
||||
|
||||
self.seat.handleActivity();
|
||||
|
||||
// Notify the client with pointer focus of the axis event.
|
||||
c.wlr_seat_pointer_notify_axis(
|
||||
self.seat.wlr_seat,
|
||||
self.seat.wlr_seat.pointerNotifyAxis(
|
||||
event.time_msec,
|
||||
event.orientation,
|
||||
event.delta,
|
||||
@ -200,15 +197,12 @@ fn handleAxis(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||
);
|
||||
}
|
||||
|
||||
fn handleButton(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||
// This event is forwarded by the cursor when a pointer emits a button
|
||||
// event.
|
||||
const self = @fieldParentPtr(Self, "listen_button", listener.?);
|
||||
const event = util.voidCast(c.wlr_event_pointer_button, data.?);
|
||||
fn handleButton(listener: *wl.Listener(*wlr.Pointer.event.Button), event: *wlr.Pointer.event.Button) void {
|
||||
const self = @fieldParentPtr(Self, "button", listener);
|
||||
|
||||
self.seat.handleActivity();
|
||||
|
||||
if (event.state == .WLR_BUTTON_PRESSED) {
|
||||
if (event.state == .pressed) {
|
||||
self.pressed_count += 1;
|
||||
} else {
|
||||
std.debug.assert(self.pressed_count > 0);
|
||||
@ -221,21 +215,21 @@ fn handleButton(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||
|
||||
var sx: f64 = undefined;
|
||||
var sy: f64 = undefined;
|
||||
if (self.surfaceAt(self.wlr_cursor.x, self.wlr_cursor.y, &sx, &sy)) |wlr_surface| {
|
||||
if (self.surfaceAt(self.wlr_cursor.x, self.wlr_cursor.y, &sx, &sy)) |surface| {
|
||||
// If the found surface is a keyboard inteactive layer surface,
|
||||
// give it keyboard focus.
|
||||
if (c.wlr_surface_is_layer_surface(wlr_surface)) {
|
||||
const wlr_layer_surface = c.wlr_layer_surface_v1_from_wlr_surface(wlr_surface);
|
||||
if (wlr_layer_surface.*.current.keyboard_interactive) {
|
||||
const layer_surface = util.voidCast(LayerSurface, wlr_layer_surface.*.data.?);
|
||||
if (surface.isLayerSurface()) {
|
||||
const wlr_layer_surface = wlr.LayerSurfaceV1.fromWlrSurface(surface);
|
||||
if (wlr_layer_surface.current.keyboard_interactive) {
|
||||
const layer_surface = @intToPtr(*LayerSurface, wlr_layer_surface.data);
|
||||
self.seat.setFocusRaw(.{ .layer = layer_surface });
|
||||
}
|
||||
}
|
||||
|
||||
// If the target surface has a view, give that view keyboard focus and
|
||||
// perhaps enter move/resize mode.
|
||||
if (View.fromWlrSurface(wlr_surface)) |view| {
|
||||
if (event.state == .WLR_BUTTON_PRESSED and self.pressed_count == 1) {
|
||||
if (View.fromWlrSurface(surface)) |view| {
|
||||
if (event.state == .pressed and self.pressed_count == 1) {
|
||||
// If there is an active mapping for this button which is
|
||||
// handled we are done here
|
||||
if (self.handlePointerMapping(event, view)) return;
|
||||
@ -244,26 +238,21 @@ fn handleButton(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||
}
|
||||
}
|
||||
|
||||
_ = c.wlr_seat_pointer_notify_button(
|
||||
self.seat.wlr_seat,
|
||||
event.time_msec,
|
||||
event.button,
|
||||
event.state,
|
||||
);
|
||||
_ = self.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle the mapping for the passed button if any. Returns true if there
|
||||
/// was a mapping and the button was handled.
|
||||
fn handlePointerMapping(self: *Self, event: *c.wlr_event_pointer_button, view: *View) bool {
|
||||
const wlr_keyboard = c.wlr_seat_get_keyboard(self.seat.wlr_seat);
|
||||
const modifiers = c.wlr_keyboard_get_modifiers(wlr_keyboard);
|
||||
fn handlePointerMapping(self: *Self, event: *wlr.Pointer.event.Button, view: *View) bool {
|
||||
const wlr_keyboard = self.seat.wlr_seat.getKeyboard() orelse return false;
|
||||
const modifiers = wlr_keyboard.getModifiers();
|
||||
|
||||
const fullscreen = view.current.fullscreen or view.pending.fullscreen;
|
||||
|
||||
const config = self.seat.input_manager.server.config;
|
||||
return for (config.modes.items[self.seat.mode_id].pointer_mappings.items) |mapping| {
|
||||
if (event.button == mapping.event_code and modifiers == mapping.modifiers) {
|
||||
if (event.button == mapping.event_code and std.meta.eql(modifiers, mapping.modifiers)) {
|
||||
switch (mapping.action) {
|
||||
.move => if (!fullscreen) self.enterMode(.move, view),
|
||||
.resize => if (!fullscreen) self.enterMode(.resize, view),
|
||||
@ -273,50 +262,54 @@ fn handlePointerMapping(self: *Self, event: *c.wlr_event_pointer_button, view: *
|
||||
} else false;
|
||||
}
|
||||
|
||||
fn handleFrame(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||
// This event is forwarded by the cursor when a pointer emits an frame
|
||||
// event. Frame events are sent after regular pointer events to group
|
||||
// multiple events together. For instance, two axis events may happen at the
|
||||
// same time, in which case a frame event won't be sent in between.
|
||||
const self = @fieldParentPtr(Self, "listen_frame", listener.?);
|
||||
// Notify the client with pointer focus of the frame event.
|
||||
c.wlr_seat_pointer_notify_frame(self.seat.wlr_seat);
|
||||
/// Frame events are sent after regular pointer events to group multiple
|
||||
/// events together. For instance, two axis events may happen at the same
|
||||
/// time, in which case a frame event won't be sent in between.
|
||||
fn handleFrame(listener: *wl.Listener(*wlr.Cursor), wlr_cursor: *wlr.Cursor) void {
|
||||
const self = @fieldParentPtr(Self, "frame", listener);
|
||||
self.seat.wlr_seat.pointerNotifyFrame();
|
||||
}
|
||||
|
||||
fn handleMotionAbsolute(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||
// This event is forwarded by the cursor when a pointer emits an _absolute_
|
||||
// motion event, from 0..1 on each axis. This happens, for example, when
|
||||
// wlroots is running under a Wayland window rather than KMS+DRM, and you
|
||||
// move the mouse over the window. You could enter the window from any edge,
|
||||
// so we have to warp the mouse there. There is also some hardware which
|
||||
// emits these events.
|
||||
const self = @fieldParentPtr(Self, "listen_motion_absolute", listener.?);
|
||||
const event = util.voidCast(c.wlr_event_pointer_motion_absolute, data.?);
|
||||
/// This event is forwarded by the cursor when a pointer emits an _absolute_
|
||||
/// motion event, from 0..1 on each axis. This happens, for example, when
|
||||
/// wlroots is running under a Wayland window rather than KMS+DRM, and you
|
||||
/// move the mouse over the window. You could enter the window from any edge,
|
||||
/// so we have to warp the mouse there. There is also some hardware which
|
||||
/// emits these events.
|
||||
fn handleMotionAbsolute(
|
||||
listener: *wl.Listener(*wlr.Pointer.event.MotionAbsolute),
|
||||
event: *wlr.Pointer.event.MotionAbsolute,
|
||||
) void {
|
||||
const self = @fieldParentPtr(Self, "motion_absolute", listener);
|
||||
|
||||
self.seat.handleActivity();
|
||||
|
||||
var lx: f64 = undefined;
|
||||
var ly: f64 = undefined;
|
||||
c.wlr_cursor_absolute_to_layout_coords(self.wlr_cursor, event.device, event.x, event.y, &lx, &ly);
|
||||
self.wlr_cursor.absoluteToLayoutCoords(event.device, event.x, event.y, &lx, &ly);
|
||||
|
||||
self.processMotion(event.device, event.time_msec, lx - self.wlr_cursor.x, ly - self.wlr_cursor.y);
|
||||
}
|
||||
|
||||
fn handleMotion(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||
// This event is forwarded by the cursor when a pointer emits a _relative_
|
||||
// pointer motion event (i.e. a delta)
|
||||
const self = @fieldParentPtr(Self, "listen_motion", listener.?);
|
||||
const event = util.voidCast(c.wlr_event_pointer_motion, data.?);
|
||||
/// This event is forwarded by the cursor when a pointer emits a _relative_
|
||||
/// pointer motion event (i.e. a delta)
|
||||
fn handleMotion(
|
||||
listener: *wl.Listener(*wlr.Pointer.event.Motion),
|
||||
event: *wlr.Pointer.event.Motion,
|
||||
) void {
|
||||
const self = @fieldParentPtr(Self, "motion", listener);
|
||||
|
||||
self.seat.handleActivity();
|
||||
|
||||
self.processMotion(event.device, event.time_msec, event.delta_x, event.delta_y);
|
||||
}
|
||||
|
||||
fn handleRequestSetCursor(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||
fn handleRequestSetCursor(
|
||||
listener: *wl.Listener(*wlr.Seat.event.RequestSetCursor),
|
||||
event: *wlr.Seat.event.RequestSetCursor,
|
||||
) void {
|
||||
// This event is rasied by the seat when a client provides a cursor image
|
||||
const self = @fieldParentPtr(Self, "listen_request_set_cursor", listener.?);
|
||||
const event = util.voidCast(c.wlr_seat_pointer_request_set_cursor_event, data.?);
|
||||
const self = @fieldParentPtr(Self, "request_set_cursor", listener);
|
||||
const focused_client = self.seat.wlr_seat.pointer_state.focused_client;
|
||||
|
||||
// This can be sent by any client, so we check to make sure this one is
|
||||
@ -327,53 +320,40 @@ fn handleRequestSetCursor(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C
|
||||
// on the output that it's currently on and continue to do so as the
|
||||
// cursor moves between outputs.
|
||||
log.debug(.cursor, "focused client set cursor", .{});
|
||||
c.wlr_cursor_set_surface(
|
||||
self.wlr_cursor,
|
||||
event.surface,
|
||||
event.hotspot_x,
|
||||
event.hotspot_y,
|
||||
);
|
||||
self.wlr_cursor.setSurface(event.surface, event.hotspot_x, event.hotspot_y);
|
||||
}
|
||||
}
|
||||
|
||||
/// Find the topmost surface under the output layout coordinates lx/ly
|
||||
/// returns the surface if found and sets the sx/sy parametes to the
|
||||
/// surface coordinates.
|
||||
fn surfaceAt(self: Self, lx: f64, ly: f64, sx: *f64, sy: *f64) ?*c.wlr_surface {
|
||||
fn surfaceAt(self: Self, lx: f64, ly: f64, sx: *f64, sy: *f64) ?*wlr.Surface {
|
||||
// Find the output to check
|
||||
const root = self.seat.input_manager.server.root;
|
||||
const wlr_output = c.wlr_output_layout_output_at(root.wlr_output_layout, lx, ly) orelse return null;
|
||||
const output = util.voidCast(Output, wlr_output.*.data orelse return null);
|
||||
const wlr_output = root.output_layout.outputAt(lx, ly) orelse return null;
|
||||
const output = @intToPtr(*Output, wlr_output.data);
|
||||
|
||||
// Get output-local coords from the layout coords
|
||||
var ox = lx;
|
||||
var oy = ly;
|
||||
c.wlr_output_layout_output_coords(root.wlr_output_layout, wlr_output, &ox, &oy);
|
||||
|
||||
// Check layers and views from top to bottom
|
||||
const layer_idxs = [_]usize{
|
||||
c.ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY,
|
||||
c.ZWLR_LAYER_SHELL_V1_LAYER_TOP,
|
||||
c.ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM,
|
||||
c.ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND,
|
||||
};
|
||||
root.output_layout.outputCoords(wlr_output, &ox, &oy);
|
||||
|
||||
// Check overlay layer incl. popups
|
||||
if (layerSurfaceAt(output.*, output.layers[layer_idxs[0]], ox, oy, sx, sy, false)) |s| return s;
|
||||
if (layerSurfaceAt(output.*, output.getLayer(.overlay).*, ox, oy, sx, sy, false)) |s| return s;
|
||||
|
||||
// Check top-background popups only
|
||||
for (layer_idxs[1..4]) |idx|
|
||||
if (layerSurfaceAt(output.*, output.layers[idx], ox, oy, sx, sy, true)) |s| return s;
|
||||
for ([_]zwlr.LayerShellV1.Layer{ .top, .bottom, .background }) |layer|
|
||||
if (layerSurfaceAt(output.*, output.getLayer(layer).*, ox, oy, sx, sy, true)) |s| return s;
|
||||
|
||||
// Check top layer
|
||||
if (layerSurfaceAt(output.*, output.layers[layer_idxs[1]], ox, oy, sx, sy, false)) |s| return s;
|
||||
if (layerSurfaceAt(output.*, output.getLayer(.top).*, ox, oy, sx, sy, false)) |s| return s;
|
||||
|
||||
// Check views
|
||||
if (viewSurfaceAt(output.*, ox, oy, sx, sy)) |s| return s;
|
||||
|
||||
// Check the bottom-background layers
|
||||
for (layer_idxs[2..4]) |idx|
|
||||
if (layerSurfaceAt(output.*, output.layers[idx], ox, oy, sx, sy, false)) |s| return s;
|
||||
for ([_]zwlr.LayerShellV1.Layer{ .bottom, .background }) |layer|
|
||||
if (layerSurfaceAt(output.*, output.getLayer(layer).*, ox, oy, sx, sy, false)) |s| return s;
|
||||
|
||||
return null;
|
||||
}
|
||||
@ -388,25 +368,20 @@ fn layerSurfaceAt(
|
||||
sx: *f64,
|
||||
sy: *f64,
|
||||
popups_only: bool,
|
||||
) ?*c.wlr_surface {
|
||||
) ?*wlr.Surface {
|
||||
var it = layer.first;
|
||||
while (it) |node| : (it = node.next) {
|
||||
const layer_surface = &node.data;
|
||||
const surface = c.wlr_layer_surface_v1_surface_at(
|
||||
layer_surface.wlr_layer_surface,
|
||||
if (layer_surface.wlr_layer_surface.surfaceAt(
|
||||
ox - @intToFloat(f64, layer_surface.box.x),
|
||||
oy - @intToFloat(f64, layer_surface.box.y),
|
||||
sx,
|
||||
sy,
|
||||
);
|
||||
if (surface) |found| {
|
||||
)) |found| {
|
||||
if (!popups_only) {
|
||||
return found;
|
||||
} else if (c.wlr_surface_is_xdg_surface(found)) {
|
||||
const wlr_xdg_surface = c.wlr_xdg_surface_from_wlr_surface(found);
|
||||
if (wlr_xdg_surface.*.role == .WLR_XDG_SURFACE_ROLE_POPUP) {
|
||||
return found;
|
||||
}
|
||||
} else if (found.isXdgSurface() and wlr.XdgSurface.fromWlrSurface(found).role == .popup) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -414,7 +389,7 @@ fn layerSurfaceAt(
|
||||
}
|
||||
|
||||
/// Find the topmost visible view surface (incl. popups) at ox,oy.
|
||||
fn viewSurfaceAt(output: Output, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_surface {
|
||||
fn viewSurfaceAt(output: Output, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*wlr.Surface {
|
||||
// Focused views are rendered on top, so look for them first.
|
||||
var it = ViewStack(View).iter(output.views.first, .forward, output.current.tags, surfaceAtFilter);
|
||||
while (it.next()) |view| {
|
||||
@ -468,10 +443,9 @@ pub fn enterMode(self: *Self, mode: @TagType(Mode), view: *View) void {
|
||||
}
|
||||
|
||||
// Clear cursor focus, so that the surface does not receive events
|
||||
c.wlr_seat_pointer_clear_focus(self.seat.wlr_seat);
|
||||
self.seat.wlr_seat.pointerNotifyClearFocus();
|
||||
|
||||
c.wlr_xcursor_manager_set_cursor_image(
|
||||
self.wlr_xcursor_manager,
|
||||
self.xcursor_manager.setCursorImage(
|
||||
if (mode == .move) "move" else "se-resize",
|
||||
self.wlr_cursor,
|
||||
);
|
||||
@ -480,36 +454,30 @@ pub fn enterMode(self: *Self, mode: @TagType(Mode), view: *View) void {
|
||||
}
|
||||
|
||||
/// Return from down/move/resize to passthrough
|
||||
fn leaveMode(self: *Self, event: *c.wlr_event_pointer_button) void {
|
||||
fn leaveMode(self: *Self, event: *wlr.Pointer.event.Button) void {
|
||||
std.debug.assert(self.mode != .passthrough);
|
||||
|
||||
log.debug(.cursor, "leave {} mode", .{@tagName(self.mode)});
|
||||
|
||||
// If we were in down mode, we need pass along the release event
|
||||
if (self.mode == .down)
|
||||
_ = c.wlr_seat_pointer_notify_button(
|
||||
self.seat.wlr_seat,
|
||||
event.time_msec,
|
||||
event.button,
|
||||
event.state,
|
||||
);
|
||||
_ = self.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state);
|
||||
|
||||
self.mode = .passthrough;
|
||||
self.passthrough(event.time_msec);
|
||||
}
|
||||
|
||||
fn processMotion(self: *Self, device: *c.wlr_input_device, time: u32, delta_x: f64, delta_y: f64) void {
|
||||
fn processMotion(self: *Self, device: *wlr.InputDevice, time: u32, delta_x: f64, delta_y: f64) void {
|
||||
const config = self.seat.input_manager.server.config;
|
||||
|
||||
switch (self.mode) {
|
||||
.passthrough => {
|
||||
c.wlr_cursor_move(self.wlr_cursor, device, delta_x, delta_y);
|
||||
self.wlr_cursor.move(device, delta_x, delta_y);
|
||||
self.passthrough(time);
|
||||
},
|
||||
.down => |view| {
|
||||
c.wlr_cursor_move(self.wlr_cursor, device, delta_x, delta_y);
|
||||
c.wlr_seat_pointer_notify_motion(
|
||||
self.seat.wlr_seat,
|
||||
self.wlr_cursor.move(device, delta_x, delta_y);
|
||||
self.seat.wlr_seat.pointerNotifyMotion(
|
||||
time,
|
||||
self.wlr_cursor.x - @intToFloat(f64, view.current.box.x),
|
||||
self.wlr_cursor.y - @intToFloat(f64, view.current.box.y),
|
||||
@ -531,8 +499,7 @@ fn processMotion(self: *Self, device: *c.wlr_input_device, time: u32, delta_x: f
|
||||
@intCast(i32, output_resolution.height - view.pending.box.height - border_width),
|
||||
);
|
||||
|
||||
c.wlr_cursor_move(
|
||||
self.wlr_cursor,
|
||||
self.wlr_cursor.move(
|
||||
device,
|
||||
@intToFloat(f64, view.pending.box.x - view.current.box.x),
|
||||
@intToFloat(f64, view.pending.box.y - view.current.box.y),
|
||||
@ -557,8 +524,7 @@ fn processMotion(self: *Self, device: *c.wlr_input_device, time: u32, delta_x: f
|
||||
data.view.applyPending();
|
||||
|
||||
// Keep cursor locked to the original offset from the bottom right corner
|
||||
c.wlr_cursor_warp_closest(
|
||||
self.wlr_cursor,
|
||||
self.wlr_cursor.warpClosest(
|
||||
device,
|
||||
@intToFloat(f64, box.x + @intCast(i32, box.width) - data.offset_x),
|
||||
@intToFloat(f64, box.y + @intCast(i32, box.height) - data.offset_y),
|
||||
@ -574,20 +540,20 @@ fn passthrough(self: *Self, time: u32) void {
|
||||
|
||||
var sx: f64 = undefined;
|
||||
var sy: f64 = undefined;
|
||||
if (self.surfaceAt(self.wlr_cursor.x, self.wlr_cursor.y, &sx, &sy)) |wlr_surface| {
|
||||
if (self.surfaceAt(self.wlr_cursor.x, self.wlr_cursor.y, &sx, &sy)) |surface| {
|
||||
// If input is allowed on the surface, send pointer enter and motion
|
||||
// events. Note that wlroots won't actually send an enter event if
|
||||
// the surface has already been entered.
|
||||
if (self.seat.input_manager.inputAllowed(wlr_surface)) {
|
||||
if (self.seat.input_manager.inputAllowed(surface)) {
|
||||
// The focus change must be checked before sending enter events
|
||||
const focus_change = self.seat.wlr_seat.pointer_state.focused_surface != wlr_surface;
|
||||
const focus_change = self.seat.wlr_seat.pointer_state.focused_surface != surface;
|
||||
|
||||
c.wlr_seat_pointer_notify_enter(self.seat.wlr_seat, wlr_surface, sx, sy);
|
||||
c.wlr_seat_pointer_notify_motion(self.seat.wlr_seat, time, sx, sy);
|
||||
self.seat.wlr_seat.pointerNotifyEnter(surface, sx, sy);
|
||||
self.seat.wlr_seat.pointerNotifyMotion(time, sx, sy);
|
||||
|
||||
const follow_mode = config.focus_follows_cursor;
|
||||
if (follow_mode == .strict or (follow_mode == .normal and focus_change)) {
|
||||
if (View.fromWlrSurface(wlr_surface)) |view| {
|
||||
if (View.fromWlrSurface(surface)) |view| {
|
||||
self.seat.focus(view);
|
||||
self.seat.focusOutput(view.output);
|
||||
root.startTransaction();
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -18,122 +18,102 @@
|
||||
const Self = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const wlr = @import("wlroots");
|
||||
const wl = @import("wayland").server.wl;
|
||||
const xkb = @import("xkbcommon");
|
||||
|
||||
const c = @import("c.zig");
|
||||
const log = @import("log.zig");
|
||||
const util = @import("util.zig");
|
||||
|
||||
const Seat = @import("Seat.zig");
|
||||
|
||||
seat: *Seat,
|
||||
wlr_input_device: *c.wlr_input_device,
|
||||
wlr_keyboard: *c.wlr_keyboard,
|
||||
input_device: *wlr.InputDevice,
|
||||
|
||||
listen_key: c.wl_listener = undefined,
|
||||
listen_modifiers: c.wl_listener = undefined,
|
||||
listen_destroy: c.wl_listener = undefined,
|
||||
key: wl.Listener(*wlr.Keyboard.event.Key) = undefined,
|
||||
modifiers: wl.Listener(*wlr.Keyboard) = undefined,
|
||||
destroy: wl.Listener(*wlr.Keyboard) = undefined,
|
||||
|
||||
pub fn init(self: *Self, seat: *Seat, wlr_input_device: *c.wlr_input_device) !void {
|
||||
pub fn init(self: *Self, seat: *Seat, input_device: *wlr.InputDevice) !void {
|
||||
self.* = .{
|
||||
.seat = seat,
|
||||
.wlr_input_device = wlr_input_device,
|
||||
.wlr_keyboard = @field(wlr_input_device, c.wlr_input_device_union).keyboard,
|
||||
.input_device = input_device,
|
||||
};
|
||||
|
||||
// We need to prepare an XKB keymap and assign it to the keyboard. This
|
||||
// assumes the defaults (e.g. layout = "us").
|
||||
const rules = c.xkb_rule_names{
|
||||
const rules = xkb.RuleNames{
|
||||
.rules = null,
|
||||
.model = null,
|
||||
.layout = null,
|
||||
.variant = null,
|
||||
.options = null,
|
||||
};
|
||||
const context = c.xkb_context_new(.XKB_CONTEXT_NO_FLAGS) orelse return error.XkbContextFailed;
|
||||
defer c.xkb_context_unref(context);
|
||||
const context = xkb.Context.new(.no_flags) orelse return error.XkbContextFailed;
|
||||
defer context.unref();
|
||||
|
||||
const keymap = c.xkb_keymap_new_from_names(
|
||||
context,
|
||||
&rules,
|
||||
.XKB_KEYMAP_COMPILE_NO_FLAGS,
|
||||
) orelse return error.XkbKeymapFailed;
|
||||
defer c.xkb_keymap_unref(keymap);
|
||||
const keymap = xkb.Keymap.newFromNames(context, &rules, .no_flags) orelse return error.XkbKeymapFailed;
|
||||
defer keymap.unref();
|
||||
|
||||
if (!c.wlr_keyboard_set_keymap(self.wlr_keyboard, keymap)) return error.SetKeymapFailed;
|
||||
c.wlr_keyboard_set_repeat_info(self.wlr_keyboard, 25, 600);
|
||||
const wlr_keyboard = self.input_device.device.keyboard;
|
||||
|
||||
// Setup listeners for keyboard events
|
||||
self.listen_key.notify = handleKey;
|
||||
c.wl_signal_add(&self.wlr_keyboard.events.key, &self.listen_key);
|
||||
if (!wlr_keyboard.setKeymap(keymap)) return error.SetKeymapFailed;
|
||||
wlr_keyboard.setRepeatInfo(25, 600);
|
||||
|
||||
self.listen_modifiers.notify = handleModifiers;
|
||||
c.wl_signal_add(&self.wlr_keyboard.events.modifiers, &self.listen_modifiers);
|
||||
self.key.setNotify(handleKey);
|
||||
wlr_keyboard.events.key.add(&self.key);
|
||||
|
||||
self.listen_destroy.notify = handleDestroy;
|
||||
c.wl_signal_add(&self.wlr_keyboard.events.destroy, &self.listen_destroy);
|
||||
self.modifiers.setNotify(handleModifiers);
|
||||
wlr_keyboard.events.modifiers.add(&self.modifiers);
|
||||
|
||||
self.destroy.setNotify(handleDestroy);
|
||||
wlr_keyboard.events.destroy.add(&self.destroy);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
c.wl_list_remove(&self.listen_key.link);
|
||||
c.wl_list_remove(&self.listen_modifiers.link);
|
||||
c.wl_list_remove(&self.listen_destroy.link);
|
||||
self.key.link.remove();
|
||||
self.modifiers.link.remove();
|
||||
self.destroy.link.remove();
|
||||
}
|
||||
|
||||
fn handleKey(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||
fn handleKey(listener: *wl.Listener(*wlr.Keyboard.event.Key), event: *wlr.Keyboard.event.Key) void {
|
||||
// This event is raised when a key is pressed or released.
|
||||
const self = @fieldParentPtr(Self, "listen_key", listener.?);
|
||||
const event = util.voidCast(c.wlr_event_keyboard_key, data.?);
|
||||
const wlr_keyboard = self.wlr_keyboard;
|
||||
const self = @fieldParentPtr(Self, "key", listener);
|
||||
const wlr_keyboard = self.input_device.device.keyboard;
|
||||
|
||||
self.seat.handleActivity();
|
||||
|
||||
// Translate libinput keycode -> xkbcommon
|
||||
const keycode = event.keycode + 8;
|
||||
|
||||
// Get a list of keysyms as xkb reports them
|
||||
var translated_keysyms: ?[*]c.xkb_keysym_t = undefined;
|
||||
const translated_keysyms_len = c.xkb_state_key_get_syms(
|
||||
wlr_keyboard.xkb_state,
|
||||
keycode,
|
||||
&translated_keysyms,
|
||||
);
|
||||
|
||||
// Get a list of keysyms ignoring modifiers (e.g. 1 instead of !)
|
||||
// Important for mappings like Mod+Shift+1
|
||||
var raw_keysyms: ?[*]c.xkb_keysym_t = undefined;
|
||||
const layout_index = c.xkb_state_key_get_layout(wlr_keyboard.xkb_state, keycode);
|
||||
const raw_keysyms_len = c.xkb_keymap_key_get_syms_by_level(
|
||||
wlr_keyboard.keymap,
|
||||
keycode,
|
||||
layout_index,
|
||||
0,
|
||||
&raw_keysyms,
|
||||
);
|
||||
// TODO: These modifiers aren't properly handled, see sway's code
|
||||
const modifiers = wlr_keyboard.getModifiers();
|
||||
const released = event.state == .released;
|
||||
|
||||
var handled = false;
|
||||
// TODO: These modifiers aren't properly handled, see sway's code
|
||||
const modifiers = c.wlr_keyboard_get_modifiers(wlr_keyboard);
|
||||
const released = event.state == .WLR_KEY_RELEASED;
|
||||
|
||||
var i: usize = 0;
|
||||
while (i < translated_keysyms_len) : (i += 1) {
|
||||
// First check translated keysyms as xkb reports them
|
||||
for (wlr_keyboard.xkb_state.?.keyGetSyms(keycode)) |sym| {
|
||||
// Handle builtin mapping only when keys are pressed
|
||||
if (!released and self.handleBuiltinMapping(translated_keysyms.?[i])) {
|
||||
if (!released and self.handleBuiltinMapping(sym)) {
|
||||
handled = true;
|
||||
break;
|
||||
} else if (self.seat.handleMapping(translated_keysyms.?[i], modifiers, released)) {
|
||||
} else if (self.seat.handleMapping(sym, modifiers, released)) {
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If not yet handled, check keysyms ignoring modifiers (e.g. 1 instead of !)
|
||||
// Important for mappings like Mod+Shift+1
|
||||
if (!handled) {
|
||||
i = 0;
|
||||
while (i < raw_keysyms_len) : (i += 1) {
|
||||
const layout_index = wlr_keyboard.xkb_state.?.keyGetLayout(keycode);
|
||||
for (wlr_keyboard.keymap.?.keyGetSymsByLevel(keycode, layout_index, 0)) |sym| {
|
||||
// Handle builtin mapping only when keys are pressed
|
||||
if (!released and self.handleBuiltinMapping(raw_keysyms.?[i])) {
|
||||
if (!released and self.handleBuiltinMapping(sym)) {
|
||||
handled = true;
|
||||
break;
|
||||
} else if (self.seat.handleMapping(raw_keysyms.?[i], modifiers, released)) {
|
||||
} else if (self.seat.handleMapping(sym, modifiers, released)) {
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
@ -143,52 +123,44 @@ fn handleKey(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||
if (!handled) {
|
||||
// Otherwise, we pass it along to the client.
|
||||
const wlr_seat = self.seat.wlr_seat;
|
||||
c.wlr_seat_set_keyboard(wlr_seat, self.wlr_input_device);
|
||||
c.wlr_seat_keyboard_notify_key(
|
||||
wlr_seat,
|
||||
event.time_msec,
|
||||
event.keycode,
|
||||
@intCast(u32, @enumToInt(event.state)),
|
||||
);
|
||||
wlr_seat.setKeyboard(self.input_device);
|
||||
wlr_seat.keyboardNotifyKey(event.time_msec, event.keycode, event.state);
|
||||
}
|
||||
}
|
||||
|
||||
fn handleModifiers(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||
// This event is raised when a modifier key, such as shift or alt, is
|
||||
// pressed. We simply communicate this to the client. */
|
||||
const self = @fieldParentPtr(Self, "listen_modifiers", listener.?);
|
||||
/// Simply pass modifiers along to the client
|
||||
fn handleModifiers(listener: *wl.Listener(*wlr.Keyboard), wlr_keyboard: *wlr.Keyboard) void {
|
||||
const self = @fieldParentPtr(Self, "modifiers", listener);
|
||||
|
||||
// A seat can only have one keyboard, but this is a limitation of the
|
||||
// Wayland protocol - not wlroots. We assign all connected keyboards to the
|
||||
// same seat. You can swap out the underlying wlr_keyboard like this and
|
||||
// wlr_seat handles this transparently.
|
||||
c.wlr_seat_set_keyboard(self.seat.wlr_seat, self.wlr_input_device);
|
||||
|
||||
// Send modifiers to the client.
|
||||
c.wlr_seat_keyboard_notify_modifiers(self.seat.wlr_seat, &self.wlr_keyboard.modifiers);
|
||||
self.seat.wlr_seat.setKeyboard(self.input_device);
|
||||
self.seat.wlr_seat.keyboardNotifyModifiers(&self.input_device.device.keyboard.modifiers);
|
||||
}
|
||||
fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||
const self = @fieldParentPtr(Self, "listen_destroy", listener.?);
|
||||
self.deinit();
|
||||
|
||||
fn handleDestroy(listener: *wl.Listener(*wlr.Keyboard), wlr_keyboard: *wlr.Keyboard) void {
|
||||
const self = @fieldParentPtr(Self, "destroy", listener);
|
||||
const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self);
|
||||
|
||||
self.seat.keyboards.remove(node);
|
||||
self.deinit();
|
||||
util.gpa.destroy(node);
|
||||
}
|
||||
|
||||
/// Handle any builtin, harcoded compsitor mappings such as VT switching.
|
||||
/// Returns true if the keysym was handled.
|
||||
fn handleBuiltinMapping(self: Self, keysym: c.xkb_keysym_t) bool {
|
||||
if (keysym >= c.XKB_KEY_XF86Switch_VT_1 and keysym <= c.XKB_KEY_XF86Switch_VT_12) {
|
||||
log.debug(.keyboard, "switch VT keysym received", .{});
|
||||
const wlr_backend = self.seat.input_manager.server.wlr_backend;
|
||||
if (c.wlr_backend_is_multi(wlr_backend)) {
|
||||
if (c.wlr_backend_get_session(wlr_backend)) |session| {
|
||||
const vt = keysym - c.XKB_KEY_XF86Switch_VT_1 + 1;
|
||||
log.notice(.server, "switching to VT {}", .{vt});
|
||||
_ = c.wlr_session_change_vt(session, vt);
|
||||
fn handleBuiltinMapping(self: Self, keysym: xkb.Keysym) bool {
|
||||
switch (@enumToInt(keysym)) {
|
||||
@enumToInt(xkb.Keysym.XF86Switch_VT_1)...@enumToInt(xkb.Keysym.XF86Switch_VT_12) => {
|
||||
log.debug(.keyboard, "switch VT keysym received", .{});
|
||||
const backend = self.seat.input_manager.server.backend;
|
||||
if (backend.isMulti()) {
|
||||
if (backend.getSession()) |session| {
|
||||
const vt = @enumToInt(keysym) - @enumToInt(xkb.Keysym.XF86Switch_VT_1) + 1;
|
||||
log.notice(.server, "switching to VT {}", .{vt});
|
||||
session.changeVt(vt) catch log.err(.server, "changing VT failed", .{});
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return true;
|
||||
},
|
||||
else => return false,
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
160
river/Output.zig
160
river/Output.zig
@ -18,6 +18,10 @@
|
||||
const Self = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const wlr = @import("wlroots");
|
||||
const wayland = @import("wayland");
|
||||
const wl = wayland.server.wl;
|
||||
const zwlr = wayland.server.zwlr;
|
||||
|
||||
const c = @import("c.zig");
|
||||
const log = @import("log.zig");
|
||||
@ -38,7 +42,7 @@ const State = struct {
|
||||
};
|
||||
|
||||
root: *Root,
|
||||
wlr_output: *c.wlr_output,
|
||||
wlr_output: *wlr.Output,
|
||||
|
||||
/// All layer surfaces on the output, indexed by the layer enum.
|
||||
layers: [4]std.TailQueue(LayerSurface) = [1]std.TailQueue(LayerSurface){.{}} ** 4,
|
||||
@ -76,22 +80,21 @@ status_trackers: std.SinglyLinkedList(OutputStatus) = .{},
|
||||
/// An active output can have focus (e.g. an output turned off by dpms is active)
|
||||
active: bool = false,
|
||||
|
||||
// All listeners for this output, in alphabetical order
|
||||
listen_destroy: c.wl_listener = undefined,
|
||||
listen_enable: c.wl_listener = undefined,
|
||||
listen_frame: c.wl_listener = undefined,
|
||||
listen_mode: c.wl_listener = undefined,
|
||||
destroy: wl.Listener(*wlr.Output) = undefined,
|
||||
enable: wl.Listener(*wlr.Output) = undefined,
|
||||
frame: wl.Listener(*wlr.Output) = undefined,
|
||||
mode: wl.Listener(*wlr.Output) = undefined,
|
||||
|
||||
pub fn init(self: *Self, root: *Root, wlr_output: *c.wlr_output) !void {
|
||||
pub fn init(self: *Self, root: *Root, wlr_output: *wlr.Output) !void {
|
||||
// Some backends don't have modes. DRM+KMS does, and we need to set a mode
|
||||
// before we can use the output. The mode is a tuple of (width, height,
|
||||
// refresh rate), and each monitor supports only a specific set of modes. We
|
||||
// just pick the monitor's preferred mode, a more sophisticated compositor
|
||||
// would let the user configure it.
|
||||
if (c.wlr_output_preferred_mode(wlr_output)) |mode| {
|
||||
c.wlr_output_set_mode(wlr_output, mode);
|
||||
c.wlr_output_enable(wlr_output, true);
|
||||
if (!c.wlr_output_commit(wlr_output)) return error.OutputCommitFailed;
|
||||
if (wlr_output.preferredMode()) |mode| {
|
||||
wlr_output.setMode(mode);
|
||||
wlr_output.enable(true);
|
||||
try wlr_output.commit();
|
||||
}
|
||||
|
||||
const layout = try std.mem.dupe(util.gpa, u8, "full");
|
||||
@ -103,22 +106,21 @@ pub fn init(self: *Self, root: *Root, wlr_output: *c.wlr_output) !void {
|
||||
.layout = layout,
|
||||
.usable_box = undefined,
|
||||
};
|
||||
wlr_output.data = self;
|
||||
wlr_output.data = @ptrToInt(self);
|
||||
|
||||
// Set up listeners
|
||||
self.listen_destroy.notify = handleDestroy;
|
||||
c.wl_signal_add(&wlr_output.events.destroy, &self.listen_destroy);
|
||||
self.destroy.setNotify(handleDestroy);
|
||||
wlr_output.events.destroy.add(&self.destroy);
|
||||
|
||||
self.listen_enable.notify = handleEnable;
|
||||
c.wl_signal_add(&wlr_output.events.enable, &self.listen_enable);
|
||||
self.enable.setNotify(handleEnable);
|
||||
wlr_output.events.enable.add(&self.enable);
|
||||
|
||||
self.listen_frame.notify = handleFrame;
|
||||
c.wl_signal_add(&wlr_output.events.frame, &self.listen_frame);
|
||||
self.frame.setNotify(handleFrame);
|
||||
wlr_output.events.frame.add(&self.frame);
|
||||
|
||||
self.listen_mode.notify = handleMode;
|
||||
c.wl_signal_add(&wlr_output.events.mode, &self.listen_mode);
|
||||
self.mode.setNotify(handleMode);
|
||||
wlr_output.events.mode.add(&self.mode);
|
||||
|
||||
if (c.wlr_output_is_noop(wlr_output)) {
|
||||
if (wlr_output.isNoop()) {
|
||||
// A noop output is always 0 x 0
|
||||
self.usable_box = .{
|
||||
.x = 0,
|
||||
@ -132,7 +134,7 @@ pub fn init(self: *Self, root: *Root, wlr_output: *c.wlr_output) !void {
|
||||
var it = root.server.input_manager.seats.first;
|
||||
while (it) |node| : (it = node.next) {
|
||||
const seat = &node.data;
|
||||
if (!c.wlr_xcursor_manager_load(seat.cursor.wlr_xcursor_manager, wlr_output.scale))
|
||||
seat.cursor.xcursor_manager.load(wlr_output.scale) catch
|
||||
log.err(.cursor, "failed to load xcursor theme at scale {}", .{wlr_output.scale});
|
||||
}
|
||||
|
||||
@ -146,8 +148,8 @@ pub fn init(self: *Self, root: *Root, wlr_output: *c.wlr_output) !void {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getRenderer(self: Self) *c.wlr_renderer {
|
||||
return c.wlr_backend_get_renderer(self.wlr_output.backend);
|
||||
pub fn getLayer(self: *Self, layer: zwlr.LayerShellV1.Layer) *std.TailQueue(LayerSurface) {
|
||||
return &self.layers[@intCast(usize, @enumToInt(layer))];
|
||||
}
|
||||
|
||||
pub fn sendViewTags(self: Self) void {
|
||||
@ -321,16 +323,11 @@ pub fn arrangeLayers(self: *Self) void {
|
||||
// This box is modified as exclusive zones are applied
|
||||
var usable_box = full_box;
|
||||
|
||||
const layer_idxs = [_]usize{
|
||||
c.ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY,
|
||||
c.ZWLR_LAYER_SHELL_V1_LAYER_TOP,
|
||||
c.ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM,
|
||||
c.ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND,
|
||||
};
|
||||
const layers = [_]zwlr.LayerShellV1.Layer{ .overlay, .top, .bottom, .background };
|
||||
|
||||
// Arrange all layer surfaces with exclusive zones, applying them to the
|
||||
// usable box along the way.
|
||||
for (layer_idxs) |layer| self.arrangeLayer(self.layers[layer], full_box, &usable_box, true);
|
||||
for (layers) |layer| self.arrangeLayer(self.getLayer(layer).*, full_box, &usable_box, true);
|
||||
|
||||
// If the the usable_box has changed, we need to rearrange the output
|
||||
if (!std.meta.eql(self.usable_box, usable_box)) {
|
||||
@ -339,13 +336,13 @@ pub fn arrangeLayers(self: *Self) void {
|
||||
}
|
||||
|
||||
// Arrange the layers without exclusive zones
|
||||
for (layer_idxs) |layer| self.arrangeLayer(self.layers[layer], full_box, &usable_box, false);
|
||||
for (layers) |layer| self.arrangeLayer(self.getLayer(layer).*, full_box, &usable_box, false);
|
||||
|
||||
// Find the topmost layer surface in the top or overlay layers which
|
||||
// requests keyboard interactivity if any.
|
||||
const topmost_surface = outer: for (layer_idxs[0..2]) |layer| {
|
||||
const topmost_surface = outer: for (layers[0..2]) |layer| {
|
||||
// Iterate in reverse order since the last layer is rendered on top
|
||||
var it = self.layers[layer].last;
|
||||
var it = self.getLayer(layer).last;
|
||||
while (it) |node| : (it = node.prev) {
|
||||
const layer_surface = &node.data;
|
||||
if (layer_surface.wlr_layer_surface.current.keyboard_interactive) {
|
||||
@ -400,19 +397,14 @@ fn arrangeLayer(
|
||||
var new_box: Box = undefined;
|
||||
|
||||
// Horizontal alignment
|
||||
const anchor_left = @as(u32, c.ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);
|
||||
const anchor_right = @as(u32, c.ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT);
|
||||
if (current_state.desired_width == 0) {
|
||||
const anchor_left_right = anchor_left | anchor_right;
|
||||
if (current_state.anchor & anchor_left_right == anchor_left_right) {
|
||||
new_box.x = bounds.x + @intCast(i32, current_state.margin.left);
|
||||
new_box.width = bounds.width -
|
||||
(current_state.margin.left + current_state.margin.right);
|
||||
}
|
||||
} else if (current_state.anchor & anchor_left != 0) {
|
||||
std.debug.assert(current_state.anchor.right and current_state.anchor.left);
|
||||
new_box.x = bounds.x + @intCast(i32, current_state.margin.left);
|
||||
new_box.width = bounds.width - (current_state.margin.left + current_state.margin.right);
|
||||
} else if (current_state.anchor.left) {
|
||||
new_box.x = bounds.x + @intCast(i32, current_state.margin.left);
|
||||
new_box.width = current_state.desired_width;
|
||||
} else if (current_state.anchor & anchor_right != 0) {
|
||||
} else if (current_state.anchor.right) {
|
||||
new_box.x = bounds.x + @intCast(i32, bounds.width - current_state.desired_width -
|
||||
current_state.margin.right);
|
||||
new_box.width = current_state.desired_width;
|
||||
@ -422,19 +414,14 @@ fn arrangeLayer(
|
||||
}
|
||||
|
||||
// Vertical alignment
|
||||
const anchor_top = @as(u32, c.ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP);
|
||||
const anchor_bottom = @as(u32, c.ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM);
|
||||
if (current_state.desired_height == 0) {
|
||||
const anchor_top_bottom = anchor_top | anchor_bottom;
|
||||
if (current_state.anchor & anchor_top_bottom == anchor_top_bottom) {
|
||||
new_box.y = bounds.y + @intCast(i32, current_state.margin.top);
|
||||
new_box.height = bounds.height -
|
||||
(current_state.margin.top + current_state.margin.bottom);
|
||||
}
|
||||
} else if (current_state.anchor & anchor_top != 0) {
|
||||
std.debug.assert(current_state.anchor.top and current_state.anchor.bottom);
|
||||
new_box.y = bounds.y + @intCast(i32, current_state.margin.top);
|
||||
new_box.height = bounds.height - (current_state.margin.top + current_state.margin.bottom);
|
||||
} else if (current_state.anchor.top) {
|
||||
new_box.y = bounds.y + @intCast(i32, current_state.margin.top);
|
||||
new_box.height = current_state.desired_height;
|
||||
} else if (current_state.anchor & anchor_bottom != 0) {
|
||||
} else if (current_state.anchor.bottom) {
|
||||
new_box.y = bounds.y + @intCast(i32, bounds.height - current_state.desired_height -
|
||||
current_state.margin.bottom);
|
||||
new_box.height = current_state.desired_height;
|
||||
@ -447,36 +434,36 @@ fn arrangeLayer(
|
||||
|
||||
// Apply the exclusive zone to the current bounds
|
||||
const edges = [4]struct {
|
||||
single: u32,
|
||||
triple: u32,
|
||||
single: zwlr.LayerSurfaceV1.Anchor,
|
||||
triple: zwlr.LayerSurfaceV1.Anchor,
|
||||
to_increase: ?*i32,
|
||||
to_decrease: *u32,
|
||||
margin: u32,
|
||||
}{
|
||||
.{
|
||||
.single = anchor_top,
|
||||
.triple = anchor_top | anchor_left | anchor_right,
|
||||
.single = .{ .top = true },
|
||||
.triple = .{ .top = true, .left = true, .right = true },
|
||||
.to_increase = &usable_box.y,
|
||||
.to_decrease = &usable_box.height,
|
||||
.margin = current_state.margin.top,
|
||||
},
|
||||
.{
|
||||
.single = anchor_bottom,
|
||||
.triple = anchor_bottom | anchor_left | anchor_right,
|
||||
.single = .{ .bottom = true },
|
||||
.triple = .{ .bottom = true, .left = true, .right = true },
|
||||
.to_increase = null,
|
||||
.to_decrease = &usable_box.height,
|
||||
.margin = current_state.margin.bottom,
|
||||
},
|
||||
.{
|
||||
.single = anchor_left,
|
||||
.triple = anchor_left | anchor_top | anchor_bottom,
|
||||
.single = .{ .left = true },
|
||||
.triple = .{ .left = true, .top = true, .bottom = true },
|
||||
.to_increase = &usable_box.x,
|
||||
.to_decrease = &usable_box.width,
|
||||
.margin = current_state.margin.left,
|
||||
},
|
||||
.{
|
||||
.single = anchor_right,
|
||||
.triple = anchor_right | anchor_top | anchor_bottom,
|
||||
.single = .{ .right = true },
|
||||
.triple = .{ .right = true, .top = true, .bottom = true },
|
||||
.to_increase = null,
|
||||
.to_decrease = &usable_box.width,
|
||||
.margin = current_state.margin.right,
|
||||
@ -484,7 +471,7 @@ fn arrangeLayer(
|
||||
};
|
||||
|
||||
for (edges) |edge| {
|
||||
if ((current_state.anchor == edge.single or current_state.anchor == edge.triple) and
|
||||
if ((std.meta.eql(current_state.anchor, edge.single) or std.meta.eql(current_state.anchor, edge.triple)) and
|
||||
current_state.exclusive_zone + @intCast(i32, edge.margin) > 0)
|
||||
{
|
||||
const delta = current_state.exclusive_zone + @intCast(i32, edge.margin);
|
||||
@ -496,21 +483,18 @@ fn arrangeLayer(
|
||||
|
||||
// Tell the client to assume the new size
|
||||
log.debug(.layer_shell, "send configure, {} x {}", .{ layer_surface.box.width, layer_surface.box.height });
|
||||
c.wlr_layer_surface_v1_configure(
|
||||
layer_surface.wlr_layer_surface,
|
||||
layer_surface.box.width,
|
||||
layer_surface.box.height,
|
||||
);
|
||||
layer_surface.wlr_layer_surface.configure(layer_surface.box.width, layer_surface.box.height);
|
||||
}
|
||||
}
|
||||
|
||||
/// Called when the output is destroyed. Evacuate all views from the output
|
||||
/// and then remove it from the list of outputs.
|
||||
fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||
const self = @fieldParentPtr(Self, "listen_destroy", listener.?);
|
||||
fn handleDestroy(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void {
|
||||
const self = @fieldParentPtr(Self, "destroy", listener);
|
||||
const root = self.root;
|
||||
|
||||
log.debug(.server, "output '{}' destroyed", .{self.wlr_output.name});
|
||||
|
||||
// Remove the destroyed output from root if it wasn't already removed
|
||||
const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self);
|
||||
if (self.active) {
|
||||
@ -526,37 +510,35 @@ fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||
}
|
||||
|
||||
// Remove all listeners
|
||||
c.wl_list_remove(&self.listen_destroy.link);
|
||||
c.wl_list_remove(&self.listen_enable.link);
|
||||
c.wl_list_remove(&self.listen_frame.link);
|
||||
c.wl_list_remove(&self.listen_mode.link);
|
||||
self.destroy.link.remove();
|
||||
self.enable.link.remove();
|
||||
self.frame.link.remove();
|
||||
self.mode.link.remove();
|
||||
|
||||
// Free the layout command
|
||||
// Free all memory and clean up the wlr.Output
|
||||
self.wlr_output.data = undefined;
|
||||
util.gpa.free(self.layout);
|
||||
|
||||
// Clean up the wlr_output
|
||||
self.wlr_output.data = null;
|
||||
util.gpa.destroy(node);
|
||||
}
|
||||
|
||||
fn handleEnable(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||
const self = @fieldParentPtr(Self, "listen_enable", listener.?);
|
||||
fn handleEnable(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void {
|
||||
const self = @fieldParentPtr(Self, "enable", listener);
|
||||
|
||||
if (self.wlr_output.enabled and !self.active) {
|
||||
if (wlr_output.enabled and !self.active) {
|
||||
const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self);
|
||||
self.root.addOutput(node);
|
||||
}
|
||||
}
|
||||
|
||||
fn handleFrame(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||
fn handleFrame(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void {
|
||||
// This function is called every time an output is ready to display a frame,
|
||||
// generally at the output's refresh rate (e.g. 60Hz).
|
||||
const self = @fieldParentPtr(Self, "listen_frame", listener.?);
|
||||
const self = @fieldParentPtr(Self, "frame", listener);
|
||||
render.renderOutput(self);
|
||||
}
|
||||
|
||||
fn handleMode(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||
const self = @fieldParentPtr(Self, "listen_mode", listener.?);
|
||||
fn handleMode(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void {
|
||||
const self = @fieldParentPtr(Self, "mode", listener);
|
||||
self.arrangeLayers();
|
||||
self.arrangeViews();
|
||||
self.root.startTransaction();
|
||||
@ -565,7 +547,7 @@ fn handleMode(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||
pub fn getEffectiveResolution(self: *Self) struct { width: u32, height: u32 } {
|
||||
var width: c_int = undefined;
|
||||
var height: c_int = undefined;
|
||||
c.wlr_output_effective_resolution(self.wlr_output, &width, &height);
|
||||
self.wlr_output.effectiveResolution(&width, &height);
|
||||
return .{
|
||||
.width = @intCast(u32, width),
|
||||
.height = @intCast(u32, height),
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
154
river/Seat.zig
154
river/Seat.zig
@ -19,8 +19,10 @@ const Self = @This();
|
||||
|
||||
const build_options = @import("build_options");
|
||||
const std = @import("std");
|
||||
const wlr = @import("wlroots");
|
||||
const wl = @import("wayland").server.wl;
|
||||
const xkb = @import("xkbcommon");
|
||||
|
||||
const c = @import("c.zig");
|
||||
const command = @import("command.zig");
|
||||
const log = @import("log.zig");
|
||||
const util = @import("util.zig");
|
||||
@ -42,7 +44,7 @@ const FocusTarget = union(enum) {
|
||||
};
|
||||
|
||||
input_manager: *InputManager,
|
||||
wlr_seat: *c.wlr_seat,
|
||||
wlr_seat: *wlr.Seat,
|
||||
|
||||
/// Multiple mice are handled by the same Cursor
|
||||
cursor: Cursor = undefined,
|
||||
@ -56,7 +58,8 @@ mode_id: usize = 0,
|
||||
/// ID of previous keymap mode, used when returning from "locked" mode
|
||||
prev_mode_id: usize = 0,
|
||||
|
||||
/// Currently focused output, may be the noop output if no
|
||||
/// Currently focused output, may be the noop output if no real output
|
||||
/// is currently available for focus.
|
||||
focused_output: *Output,
|
||||
|
||||
/// Currently focused view/layer surface if any
|
||||
@ -69,33 +72,33 @@ focus_stack: ViewStack(*View) = .{},
|
||||
/// List of status tracking objects relaying changes to this seat to clients.
|
||||
status_trackers: std.SinglyLinkedList(SeatStatus) = .{},
|
||||
|
||||
listen_request_set_selection: c.wl_listener = undefined,
|
||||
listen_request_start_drag: c.wl_listener = undefined,
|
||||
listen_start_drag: c.wl_listener = undefined,
|
||||
listen_request_set_primary_selection: c.wl_listener = undefined,
|
||||
request_set_selection: wl.Listener(*wlr.Seat.event.RequestSetSelection) = undefined,
|
||||
request_start_drag: wl.Listener(*wlr.Seat.event.RequestStartDrag) = undefined,
|
||||
start_drag: wl.Listener(*wlr.Drag) = undefined,
|
||||
request_set_primary_selection: wl.Listener(*wlr.Seat.event.RequestSetPrimarySelection) = undefined,
|
||||
|
||||
pub fn init(self: *Self, input_manager: *InputManager, name: [*:0]const u8) !void {
|
||||
self.* = .{
|
||||
.input_manager = input_manager,
|
||||
// This will be automatically destroyed when the display is destroyed
|
||||
.wlr_seat = c.wlr_seat_create(input_manager.server.wl_display, name) orelse return error.OutOfMemory,
|
||||
.wlr_seat = try wlr.Seat.create(input_manager.server.wl_server, name),
|
||||
.focused_output = &self.input_manager.server.root.noop_output,
|
||||
};
|
||||
self.wlr_seat.data = self;
|
||||
self.wlr_seat.data = @ptrToInt(self);
|
||||
|
||||
try self.cursor.init(self);
|
||||
|
||||
self.listen_request_set_selection.notify = handleRequestSetSelection;
|
||||
c.wl_signal_add(&self.wlr_seat.events.request_set_selection, &self.listen_request_set_selection);
|
||||
self.request_set_selection.setNotify(handleRequestSetSelection);
|
||||
self.wlr_seat.events.request_set_selection.add(&self.request_set_selection);
|
||||
|
||||
self.listen_request_start_drag.notify = handleRequestStartDrag;
|
||||
c.wl_signal_add(&self.wlr_seat.events.request_start_drag, &self.listen_request_start_drag);
|
||||
self.request_start_drag.setNotify(handleRequestStartDrag);
|
||||
self.wlr_seat.events.request_start_drag.add(&self.request_start_drag);
|
||||
|
||||
self.listen_start_drag.notify = handleStartDrag;
|
||||
c.wl_signal_add(&self.wlr_seat.events.start_drag, &self.listen_start_drag);
|
||||
self.start_drag.setNotify(handleStartDrag);
|
||||
self.wlr_seat.events.start_drag.add(&self.start_drag);
|
||||
|
||||
self.listen_request_set_primary_selection.notify = handleRequestPrimarySelection;
|
||||
c.wl_signal_add(&self.wlr_seat.events.request_set_primary_selection, &self.listen_request_set_primary_selection);
|
||||
self.request_set_primary_selection.setNotify(handleRequestPrimarySelection);
|
||||
self.wlr_seat.events.request_set_primary_selection.add(&self.request_set_primary_selection);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
@ -180,29 +183,28 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void {
|
||||
// If the target is already focused, do nothing
|
||||
if (std.meta.eql(new_focus, self.focused)) return;
|
||||
|
||||
// Obtain the target wlr_surface
|
||||
const target_wlr_surface = switch (new_focus) {
|
||||
.view => |target_view| target_view.wlr_surface.?,
|
||||
.layer => |target_layer| target_layer.wlr_layer_surface.surface.?,
|
||||
// Obtain the target surface
|
||||
const target_surface = switch (new_focus) {
|
||||
.view => |target_view| target_view.surface.?,
|
||||
.layer => |target_layer| target_layer.wlr_layer_surface.surface,
|
||||
.none => null,
|
||||
};
|
||||
|
||||
// If input is not allowed on the target surface (e.g. due to an active
|
||||
// input inhibitor) do not set focus. If there is no target surface we
|
||||
// still clear the focus.
|
||||
if (if (target_wlr_surface) |wlr_surface| self.input_manager.inputAllowed(wlr_surface) else true) {
|
||||
if (if (target_surface) |wlr_surface| self.input_manager.inputAllowed(wlr_surface) else true) {
|
||||
// First clear the current focus
|
||||
if (self.focused == .view) {
|
||||
self.focused.view.pending.focus -= 1;
|
||||
// This is needed because xwayland views don't double buffer
|
||||
// activated state.
|
||||
if (build_options.xwayland and self.focused.view.impl == .xwayland_view)
|
||||
c.wlr_xwayland_surface_activate(self.focused.view.impl.xwayland_view.wlr_xwayland_surface, false);
|
||||
self.focused.view.impl.xwayland_view.xwayland_surface.activate(false);
|
||||
if (self.focused.view.pending.focus == 0 and !self.focused.view.pending.fullscreen) {
|
||||
self.focused.view.pending.target_opacity = self.input_manager.server.config.view_opacity_unfocused;
|
||||
}
|
||||
}
|
||||
c.wlr_seat_keyboard_clear_focus(self.wlr_seat);
|
||||
|
||||
// Set the new focus
|
||||
switch (new_focus) {
|
||||
@ -212,7 +214,7 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void {
|
||||
// This is needed because xwayland views don't double buffer
|
||||
// activated state.
|
||||
if (build_options.xwayland and target_view.impl == .xwayland_view)
|
||||
c.wlr_xwayland_surface_activate(target_view.impl.xwayland_view.wlr_xwayland_surface, true);
|
||||
target_view.impl.xwayland_view.xwayland_surface.activate(true);
|
||||
if (!target_view.pending.fullscreen) {
|
||||
target_view.pending.target_opacity = self.input_manager.server.config.view_opacity_focused;
|
||||
}
|
||||
@ -222,16 +224,20 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void {
|
||||
}
|
||||
self.focused = new_focus;
|
||||
|
||||
// Tell wlroots to send the new keyboard focus if we have a target
|
||||
if (target_wlr_surface) |wlr_surface| {
|
||||
const keyboard: *c.wlr_keyboard = c.wlr_seat_get_keyboard(self.wlr_seat);
|
||||
c.wlr_seat_keyboard_notify_enter(
|
||||
self.wlr_seat,
|
||||
wlr_surface,
|
||||
&keyboard.keycodes,
|
||||
keyboard.num_keycodes,
|
||||
&keyboard.modifiers,
|
||||
);
|
||||
// Send surface enter/leave events
|
||||
if (target_surface) |wlr_surface| {
|
||||
if (self.wlr_seat.getKeyboard()) |keyboard| {
|
||||
self.wlr_seat.keyboardNotifyEnter(
|
||||
wlr_surface,
|
||||
&keyboard.keycodes,
|
||||
keyboard.num_keycodes,
|
||||
&keyboard.modifiers,
|
||||
);
|
||||
} else {
|
||||
self.wlr_seat.keyboardNotifyEnter(wlr_surface, null, 0, null);
|
||||
}
|
||||
} else {
|
||||
self.wlr_seat.keyboardClearFocus();
|
||||
}
|
||||
}
|
||||
|
||||
@ -256,7 +262,7 @@ pub fn focusOutput(self: *Self, output: *Output) void {
|
||||
}
|
||||
|
||||
pub fn handleActivity(self: Self) void {
|
||||
c.wlr_idle_notify_activity(self.input_manager.wlr_idle, self.wlr_seat);
|
||||
self.input_manager.idle.notifyActivity(self.wlr_seat);
|
||||
}
|
||||
|
||||
/// Handle the unmapping of a view, removing it from the focus stack and
|
||||
@ -280,10 +286,15 @@ pub fn handleViewUnmap(self: *Self, view: *View) void {
|
||||
|
||||
/// Handle any user-defined mapping for the passed keysym and modifiers
|
||||
/// Returns true if the key was handled
|
||||
pub fn handleMapping(self: *Self, keysym: c.xkb_keysym_t, modifiers: u32, released: bool) bool {
|
||||
pub fn handleMapping(
|
||||
self: *Self,
|
||||
keysym: xkb.Keysym,
|
||||
modifiers: wlr.Keyboard.ModifierMask,
|
||||
released: bool,
|
||||
) bool {
|
||||
const modes = &self.input_manager.server.config.modes;
|
||||
for (modes.items[self.mode_id].mappings.items) |mapping| {
|
||||
if (modifiers == mapping.modifiers and keysym == mapping.keysym and released == mapping.release) {
|
||||
if (std.meta.eql(modifiers, mapping.modifiers) and keysym == mapping.keysym and released == mapping.release) {
|
||||
// Execute the bound command
|
||||
const args = mapping.command_args;
|
||||
var out: ?[]const u8 = null;
|
||||
@ -309,22 +320,23 @@ pub fn handleMapping(self: *Self, keysym: c.xkb_keysym_t, modifiers: u32, releas
|
||||
|
||||
/// Add a newly created input device to the seat and update the reported
|
||||
/// capabilities.
|
||||
pub fn addDevice(self: *Self, device: *c.wlr_input_device) void {
|
||||
pub fn addDevice(self: *Self, device: *wlr.InputDevice) void {
|
||||
switch (device.type) {
|
||||
.WLR_INPUT_DEVICE_KEYBOARD => self.addKeyboard(device) catch return,
|
||||
.WLR_INPUT_DEVICE_POINTER => self.addPointer(device),
|
||||
.keyboard => self.addKeyboard(device) catch return,
|
||||
.pointer => self.addPointer(device),
|
||||
else => return,
|
||||
}
|
||||
|
||||
// We need to let the wlr_seat know what our capabilities are, which is
|
||||
// communiciated to the client. We always have a cursor, even if
|
||||
// there are no pointer devices, so we always include that capability.
|
||||
var caps = @intCast(u32, c.WL_SEAT_CAPABILITY_POINTER);
|
||||
if (self.keyboards.len > 0) caps |= @intCast(u32, c.WL_SEAT_CAPABILITY_KEYBOARD);
|
||||
c.wlr_seat_set_capabilities(self.wlr_seat, caps);
|
||||
self.wlr_seat.setCapabilities(.{
|
||||
.pointer = true,
|
||||
.keyboard = self.keyboards.len > 0,
|
||||
});
|
||||
}
|
||||
|
||||
fn addKeyboard(self: *Self, device: *c.wlr_input_device) !void {
|
||||
fn addKeyboard(self: *Self, device: *wlr.InputDevice) !void {
|
||||
const node = try util.gpa.create(std.TailQueue(Keyboard).Node);
|
||||
node.data.init(self, device) catch |err| {
|
||||
switch (err) {
|
||||
@ -335,40 +347,46 @@ fn addKeyboard(self: *Self, device: *c.wlr_input_device) !void {
|
||||
return;
|
||||
};
|
||||
self.keyboards.append(node);
|
||||
c.wlr_seat_set_keyboard(self.wlr_seat, device);
|
||||
self.wlr_seat.setKeyboard(device);
|
||||
}
|
||||
|
||||
fn addPointer(self: Self, device: *c.struct_wlr_input_device) void {
|
||||
fn addPointer(self: Self, device: *wlr.InputDevice) void {
|
||||
// We don't do anything special with pointers. All of our pointer handling
|
||||
// is proxied through wlr_cursor. On another compositor, you might take this
|
||||
// opportunity to do libinput configuration on the device to set
|
||||
// acceleration, etc.
|
||||
c.wlr_cursor_attach_input_device(self.cursor.wlr_cursor, device);
|
||||
self.cursor.wlr_cursor.attachInputDevice(device);
|
||||
}
|
||||
|
||||
fn handleRequestSetSelection(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||
const self = @fieldParentPtr(Self, "listen_request_set_selection", listener.?);
|
||||
const event = util.voidCast(c.wlr_seat_request_set_selection_event, data.?);
|
||||
c.wlr_seat_set_selection(self.wlr_seat, event.source, event.serial);
|
||||
fn handleRequestSetSelection(
|
||||
listener: *wl.Listener(*wlr.Seat.event.RequestSetSelection),
|
||||
event: *wlr.Seat.event.RequestSetSelection,
|
||||
) void {
|
||||
const self = @fieldParentPtr(Self, "request_set_selection", listener);
|
||||
self.wlr_seat.setSelection(event.source, event.serial);
|
||||
}
|
||||
|
||||
fn handleRequestStartDrag(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||
const self = @fieldParentPtr(Self, "listen_request_start_drag", listener.?);
|
||||
const event = util.voidCast(c.wlr_seat_request_start_drag_event, data.?);
|
||||
fn handleRequestStartDrag(
|
||||
listener: *wl.Listener(*wlr.Seat.event.RequestStartDrag),
|
||||
event: *wlr.Seat.event.RequestStartDrag,
|
||||
) void {
|
||||
const self = @fieldParentPtr(Self, "request_start_drag", listener);
|
||||
|
||||
if (c.wlr_seat_validate_pointer_grab_serial(self.wlr_seat, event.origin, event.serial)) {
|
||||
log.debug(.seat, "starting pointer drag", .{});
|
||||
c.wlr_seat_start_pointer_drag(self.wlr_seat, event.drag, event.serial);
|
||||
if (!self.wlr_seat.validatePointerGrabSerial(event.origin, event.serial)) {
|
||||
log.debug(.seat, "ignoring request to start drag, failed to validate serial {}", .{event.serial});
|
||||
if (event.drag.source) |source| source.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug(.seat, "ignoring request to start drag, failed to validate serial {}", .{event.serial});
|
||||
c.wlr_data_source_destroy(event.drag.*.source);
|
||||
log.debug(.seat, "starting pointer drag", .{});
|
||||
self.wlr_seat.startPointerDrag(event.drag, event.serial);
|
||||
}
|
||||
|
||||
fn handleStartDrag(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||
const self = @fieldParentPtr(Self, "listen_start_drag", listener.?);
|
||||
const wlr_drag = util.voidCast(c.wlr_drag, data.?);
|
||||
fn handleStartDrag(
|
||||
listener: *wl.Listener(*wlr.Drag),
|
||||
wlr_drag: *wlr.Drag,
|
||||
) void {
|
||||
const self = @fieldParentPtr(Self, "start_drag", listener);
|
||||
|
||||
if (wlr_drag.icon) |wlr_drag_icon| {
|
||||
const node = util.gpa.create(std.SinglyLinkedList(DragIcon).Node) catch {
|
||||
@ -381,8 +399,10 @@ fn handleStartDrag(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void
|
||||
self.cursor.mode = .passthrough;
|
||||
}
|
||||
|
||||
fn handleRequestPrimarySelection(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||
const self = @fieldParentPtr(Self, "listen_request_set_primary_selection", listener.?);
|
||||
const event = util.voidCast(c.wlr_seat_request_set_primary_selection_event, data.?);
|
||||
c.wlr_seat_set_primary_selection(self.wlr_seat, event.source, event.serial);
|
||||
fn handleRequestPrimarySelection(
|
||||
listener: *wl.Listener(*wlr.Seat.event.RequestSetPrimarySelection),
|
||||
event: *wlr.Seat.event.RequestSetPrimarySelection,
|
||||
) void {
|
||||
const self = @fieldParentPtr(Self, "request_set_primary_selection", listener);
|
||||
self.wlr_seat.setPrimarySelection(event.source, event.serial);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
177
river/Server.zig
177
river/Server.zig
@ -19,6 +19,8 @@ const Self = @This();
|
||||
|
||||
const build_options = @import("build_options");
|
||||
const std = @import("std");
|
||||
const wlr = @import("wlroots");
|
||||
const wl = @import("wayland").server.wl;
|
||||
|
||||
const c = @import("c.zig");
|
||||
const log = @import("log.zig");
|
||||
@ -37,22 +39,22 @@ const View = @import("View.zig");
|
||||
const ViewStack = @import("view_stack.zig").ViewStack;
|
||||
const XwaylandUnmanaged = @import("XwaylandUnmanaged.zig");
|
||||
|
||||
wl_display: *c.wl_display,
|
||||
wl_server: *wl.Server,
|
||||
|
||||
sigint_source: *c.wl_event_source,
|
||||
sigterm_source: *c.wl_event_source,
|
||||
sigint_source: *wl.EventSource,
|
||||
sigterm_source: *wl.EventSource,
|
||||
|
||||
wlr_backend: *c.wlr_backend,
|
||||
noop_backend: *c.wlr_backend,
|
||||
backend: *wlr.Backend,
|
||||
noop_backend: *wlr.Backend,
|
||||
|
||||
wlr_xdg_shell: *c.wlr_xdg_shell,
|
||||
listen_new_xdg_surface: c.wl_listener,
|
||||
xdg_shell: *wlr.XdgShell,
|
||||
new_xdg_surface: wl.Listener(*wlr.XdgSurface),
|
||||
|
||||
wlr_layer_shell: *c.wlr_layer_shell_v1,
|
||||
listen_new_layer_surface: c.wl_listener,
|
||||
layer_shell: *wlr.LayerShellV1,
|
||||
new_layer_surface: wl.Listener(*wlr.LayerSurfaceV1),
|
||||
|
||||
wlr_xwayland: if (build_options.xwayland) *c.wlr_xwayland else void,
|
||||
listen_new_xwayland_surface: if (build_options.xwayland) c.wl_listener else void,
|
||||
xwayland: if (build_options.xwayland) *wlr.Xwayland else void,
|
||||
new_xwayland_surface: if (build_options.xwayland) wl.Listener(*wlr.XwaylandSurface) else void,
|
||||
|
||||
decoration_manager: DecorationManager,
|
||||
input_manager: InputManager,
|
||||
@ -63,61 +65,46 @@ control: Control,
|
||||
status_manager: StatusManager,
|
||||
|
||||
pub fn init(self: *Self) !void {
|
||||
// The Wayland display is managed by libwayland. It handles accepting
|
||||
// clients from the Unix socket, managing Wayland globals, and so on.
|
||||
self.wl_display = c.wl_display_create() orelse return error.CreateDisplayError;
|
||||
errdefer c.wl_display_destroy(self.wl_display);
|
||||
self.wl_server = try wl.Server.create();
|
||||
errdefer self.wl_server.destroy();
|
||||
|
||||
// Never returns null if the display was created successfully
|
||||
const wl_event_loop = c.wl_display_get_event_loop(self.wl_display);
|
||||
self.sigint_source = c.wl_event_loop_add_signal(wl_event_loop, std.os.SIGINT, terminate, self.wl_display) orelse
|
||||
return error.AddEventSourceFailed;
|
||||
errdefer _ = c.wl_event_source_remove(self.sigint_source);
|
||||
self.sigterm_source = c.wl_event_loop_add_signal(wl_event_loop, std.os.SIGTERM, terminate, self.wl_display) orelse
|
||||
return error.AddEventSourceFailed;
|
||||
errdefer _ = c.wl_event_source_remove(self.sigterm_source);
|
||||
const loop = self.wl_server.getEventLoop();
|
||||
self.sigint_source = try loop.addSignal(*wl.Server, std.os.SIGINT, terminate, self.wl_server);
|
||||
errdefer self.sigint_source.remove();
|
||||
self.sigterm_source = try loop.addSignal(*wl.Server, std.os.SIGTERM, terminate, self.wl_server);
|
||||
errdefer self.sigterm_source.remove();
|
||||
|
||||
// The wlr_backend abstracts the input/output hardware. Autocreate chooses
|
||||
// the best option based on the environment, for example DRM when run from
|
||||
// a tty or wayland if WAYLAND_DISPLAY is set. This frees itself when the
|
||||
// wl_display is destroyed.
|
||||
self.wlr_backend = c.river_wlr_backend_autocreate(self.wl_display) orelse
|
||||
return error.CreateBackendError;
|
||||
// This frees itself when the wl.Server is destroyed
|
||||
self.backend = try wlr.Backend.autocreate(self.wl_server, null);
|
||||
|
||||
// This backend is used to create a noop output for use when no actual
|
||||
// outputs are available. This frees itself when the wl_display is destroyed.
|
||||
self.noop_backend = c.wlr_noop_backend_create(self.wl_display) orelse
|
||||
return error.OutOfMemory;
|
||||
// outputs are available. This frees itself when the wl.Server is destroyed.
|
||||
self.noop_backend = try wlr.Backend.createNoop(self.wl_server);
|
||||
|
||||
// If we don't provide a renderer, autocreate makes a GLES2 renderer for us.
|
||||
// The renderer is responsible for defining the various pixel formats it
|
||||
// supports for shared memory, this configures that for clients.
|
||||
const wlr_renderer = c.wlr_backend_get_renderer(self.wlr_backend).?;
|
||||
if (!c.wlr_renderer_init_wl_display(wlr_renderer, self.wl_display)) return error.DisplayInitFailed;
|
||||
// This will never be null for the non-custom backends in wlroots
|
||||
const renderer = self.backend.getRenderer().?;
|
||||
try renderer.initServer(self.wl_server);
|
||||
|
||||
const wlr_compositor = c.wlr_compositor_create(self.wl_display, wlr_renderer) orelse
|
||||
return error.OutOfMemory;
|
||||
const compositor = try wlr.Compositor.create(self.wl_server, renderer);
|
||||
|
||||
// Set up xdg shell
|
||||
self.wlr_xdg_shell = c.wlr_xdg_shell_create(self.wl_display) orelse return error.OutOfMemory;
|
||||
self.listen_new_xdg_surface.notify = handleNewXdgSurface;
|
||||
c.wl_signal_add(&self.wlr_xdg_shell.events.new_surface, &self.listen_new_xdg_surface);
|
||||
self.xdg_shell = try wlr.XdgShell.create(self.wl_server);
|
||||
self.new_xdg_surface.setNotify(handleNewXdgSurface);
|
||||
self.xdg_shell.events.new_surface.add(&self.new_xdg_surface);
|
||||
|
||||
// Set up layer shell
|
||||
self.wlr_layer_shell = c.wlr_layer_shell_v1_create(self.wl_display) orelse return error.OutOfMemory;
|
||||
self.listen_new_layer_surface.notify = handleNewLayerSurface;
|
||||
c.wl_signal_add(&self.wlr_layer_shell.events.new_surface, &self.listen_new_layer_surface);
|
||||
self.layer_shell = try wlr.LayerShellV1.create(self.wl_server);
|
||||
self.new_layer_surface.setNotify(handleNewLayerSurface);
|
||||
self.layer_shell.events.new_surface.add(&self.new_layer_surface);
|
||||
|
||||
// Set up xwayland if built with support
|
||||
if (build_options.xwayland) {
|
||||
self.wlr_xwayland = c.wlr_xwayland_create(self.wl_display, wlr_compositor, false) orelse
|
||||
return error.CreateXwaylandError;
|
||||
self.listen_new_xwayland_surface.notify = handleNewXwaylandSurface;
|
||||
c.wl_signal_add(&self.wlr_xwayland.events.new_surface, &self.listen_new_xwayland_surface);
|
||||
self.xwayland = try wlr.Xwayland.create(self.wl_server, compositor, false);
|
||||
self.new_xwayland_surface.setNotify(handleNewXwaylandSurface);
|
||||
self.xwayland.events.new_surface.add(&self.new_xwayland_surface);
|
||||
}
|
||||
|
||||
// Set up primary selection
|
||||
_ = c.wlr_primary_selection_v1_device_manager_create(self.wl_display);
|
||||
_ = try wlr.PrimarySelectionDeviceManagerV1.create(self.wl_server);
|
||||
|
||||
self.config = try Config.init();
|
||||
try self.decoration_manager.init(self);
|
||||
@ -128,31 +115,28 @@ pub fn init(self: *Self) !void {
|
||||
try self.status_manager.init(self);
|
||||
try self.output_manager.init(self);
|
||||
|
||||
// These all free themselves when the wl_display is destroyed
|
||||
_ = c.wlr_data_device_manager_create(self.wl_display) orelse return error.OutOfMemory;
|
||||
_ = c.wlr_data_control_manager_v1_create(self.wl_display) orelse return error.OutOfMemory;
|
||||
_ = c.wlr_export_dmabuf_manager_v1_create(self.wl_display) orelse return error.OutOfMemory;
|
||||
_ = c.wlr_gamma_control_manager_v1_create(self.wl_display) orelse return error.OutOfMemory;
|
||||
_ = c.wlr_screencopy_manager_v1_create(self.wl_display) orelse return error.OutOfMemory;
|
||||
_ = c.wlr_viewporter_create(self.wl_display) orelse return error.OutOfMemory;
|
||||
_ = c.wlr_xdg_output_manager_v1_create(self.wl_display, self.root.wlr_output_layout) orelse
|
||||
return error.OutOfMemory;
|
||||
// These all free themselves when the wl_server is destroyed
|
||||
_ = try wlr.DataDeviceManager.create(self.wl_server);
|
||||
_ = try wlr.DataControlManagerV1.create(self.wl_server);
|
||||
_ = try wlr.ExportDmabufManagerV1.create(self.wl_server);
|
||||
_ = try wlr.GammaControlManagerV1.create(self.wl_server);
|
||||
_ = try wlr.ScreencopyManagerV1.create(self.wl_server);
|
||||
_ = try wlr.Viewporter.create(self.wl_server);
|
||||
}
|
||||
|
||||
/// Free allocated memory and clean up
|
||||
/// Free allocated memory and clean up. Note: order is important here
|
||||
pub fn deinit(self: *Self) void {
|
||||
// Note: order is important here
|
||||
_ = c.wl_event_source_remove(self.sigint_source);
|
||||
_ = c.wl_event_source_remove(self.sigterm_source);
|
||||
self.sigint_source.remove();
|
||||
self.sigterm_source.remove();
|
||||
|
||||
if (build_options.xwayland) c.wlr_xwayland_destroy(self.wlr_xwayland);
|
||||
if (build_options.xwayland) self.xwayland.destroy();
|
||||
|
||||
c.wl_display_destroy_clients(self.wl_display);
|
||||
self.wl_server.destroyClients();
|
||||
|
||||
self.root.deinit();
|
||||
|
||||
c.wl_display_destroy(self.wl_display);
|
||||
c.wlr_backend_destroy(self.noop_backend);
|
||||
self.wl_server.destroy();
|
||||
self.noop_backend.destroy();
|
||||
|
||||
self.input_manager.deinit();
|
||||
self.config.deinit();
|
||||
@ -160,33 +144,26 @@ pub fn deinit(self: *Self) void {
|
||||
|
||||
/// Create the socket, start the backend, and setup the environment
|
||||
pub fn start(self: Self) !void {
|
||||
const socket = c.wl_display_add_socket_auto(self.wl_display) orelse return error.AddSocketError;
|
||||
if (!c.wlr_backend_start(self.wlr_backend)) return error.StartBackendError;
|
||||
var buf: [11]u8 = undefined;
|
||||
const socket = try self.wl_server.addSocketAuto(&buf);
|
||||
try self.backend.start();
|
||||
// TODO: don't use libc's setenv
|
||||
if (c.setenv("WAYLAND_DISPLAY", socket, 1) < 0) return error.SetenvError;
|
||||
if (build_options.xwayland) {
|
||||
if (c.setenv("DISPLAY", self.wlr_xwayland.display_name, 1) < 0) return error.SetenvError;
|
||||
if (c.setenv("DISPLAY", self.xwayland.display_name, 1) < 0) return error.SetenvError;
|
||||
}
|
||||
}
|
||||
|
||||
/// Enter the wayland event loop and block until the compositor is exited
|
||||
pub fn run(self: Self) void {
|
||||
c.wl_display_run(self.wl_display);
|
||||
}
|
||||
|
||||
/// Handle SIGINT and SIGTERM by gracefully stopping the server
|
||||
fn terminate(signal: c_int, data: ?*c_void) callconv(.C) c_int {
|
||||
const wl_display = util.voidCast(c.wl_display, data.?);
|
||||
c.wl_display_terminate(wl_display);
|
||||
fn terminate(signal: c_int, wl_server: *wl.Server) callconv(.C) c_int {
|
||||
wl_server.terminate();
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn handleNewXdgSurface(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||
// This event is raised when wlr_xdg_shell receives a new xdg surface from a
|
||||
// client, either a toplevel (application window) or popup.
|
||||
const self = @fieldParentPtr(Self, "listen_new_xdg_surface", listener.?);
|
||||
const wlr_xdg_surface = util.voidCast(c.wlr_xdg_surface, data.?);
|
||||
fn handleNewXdgSurface(listener: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSurface) void {
|
||||
const self = @fieldParentPtr(Self, "new_xdg_surface", listener);
|
||||
|
||||
if (wlr_xdg_surface.role == .WLR_XDG_SURFACE_ROLE_POPUP) {
|
||||
if (xdg_surface.role == .popup) {
|
||||
log.debug(.server, "new xdg_popup", .{});
|
||||
return;
|
||||
}
|
||||
@ -196,16 +173,15 @@ fn handleNewXdgSurface(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) v
|
||||
// The View will add itself to the output's view stack on map
|
||||
const output = self.input_manager.defaultSeat().focused_output;
|
||||
const node = util.gpa.create(ViewStack(View).Node) catch {
|
||||
c.wl_resource_post_no_memory(wlr_xdg_surface.resource);
|
||||
xdg_surface.resource.postNoMemory();
|
||||
return;
|
||||
};
|
||||
node.view.init(output, output.current.tags, wlr_xdg_surface);
|
||||
node.view.init(output, output.current.tags, xdg_surface);
|
||||
}
|
||||
|
||||
/// This event is raised when the layer_shell recieves a new surface from a client.
|
||||
fn handleNewLayerSurface(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||
const self = @fieldParentPtr(Self, "listen_new_layer_surface", listener.?);
|
||||
const wlr_layer_surface = util.voidCast(c.wlr_layer_surface_v1, data.?);
|
||||
fn handleNewLayerSurface(listener: *wl.Listener(*wlr.LayerSurfaceV1), wlr_layer_surface: *wlr.LayerSurfaceV1) void {
|
||||
const self = @fieldParentPtr(Self, "new_layer_surface", listener);
|
||||
|
||||
log.debug(
|
||||
.server,
|
||||
@ -229,31 +205,28 @@ fn handleNewLayerSurface(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C)
|
||||
if (wlr_layer_surface.output == null) {
|
||||
const output = self.input_manager.defaultSeat().focused_output;
|
||||
if (output == &self.root.noop_output) {
|
||||
log.err(.server, "no output available for layer surface '{s}'", .{wlr_layer_surface.namespace});
|
||||
c.wlr_layer_surface_v1_close(wlr_layer_surface);
|
||||
log.err(.server, "no output available for layer surface '{}'", .{wlr_layer_surface.namespace});
|
||||
wlr_layer_surface.close();
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug(
|
||||
.server,
|
||||
"new layer surface had null output, assigning it to output '{s}'",
|
||||
.{output.wlr_output.name},
|
||||
);
|
||||
log.debug(.server, "new layer surface had null output, assigning it to output '{}'", .{
|
||||
output.wlr_output.name,
|
||||
});
|
||||
wlr_layer_surface.output = output.wlr_output;
|
||||
}
|
||||
|
||||
// The layer surface will add itself to the proper list of the output on map
|
||||
const output = util.voidCast(Output, wlr_layer_surface.output.*.data.?);
|
||||
const output = @intToPtr(*Output, wlr_layer_surface.output.?.data);
|
||||
const node = util.gpa.create(std.TailQueue(LayerSurface).Node) catch {
|
||||
c.wl_resource_post_no_memory(wlr_layer_surface.resource);
|
||||
wlr_layer_surface.resource.postNoMemory();
|
||||
return;
|
||||
};
|
||||
node.data.init(output, wlr_layer_surface);
|
||||
}
|
||||
|
||||
fn handleNewXwaylandSurface(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||
const self = @fieldParentPtr(Self, "listen_new_xwayland_surface", listener.?);
|
||||
const wlr_xwayland_surface = util.voidCast(c.wlr_xwayland_surface, data.?);
|
||||
fn handleNewXwaylandSurface(listener: *wl.Listener(*wlr.XwaylandSurface), wlr_xwayland_surface: *wlr.XwaylandSurface) void {
|
||||
const self = @fieldParentPtr(Self, "new_xwayland_surface", listener);
|
||||
|
||||
if (wlr_xwayland_surface.override_redirect) {
|
||||
log.debug(.server, "new unmanaged xwayland surface", .{});
|
||||
|
@ -18,8 +18,11 @@
|
||||
const Self = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const wlr = @import("wlroots");
|
||||
const wayland = @import("wayland");
|
||||
const wl = wayland.server.wl;
|
||||
const zriver = wayland.server.zriver;
|
||||
|
||||
const c = @import("c.zig");
|
||||
const log = @import("log.zig");
|
||||
const util = @import("util.zig");
|
||||
|
||||
@ -29,120 +32,89 @@ const Seat = @import("Seat.zig");
|
||||
const SeatStatus = @import("SeatStatus.zig");
|
||||
const Server = @import("Server.zig");
|
||||
|
||||
const protocol_version = 1;
|
||||
global: *wl.Global,
|
||||
|
||||
const implementation = c.struct_zriver_status_manager_v1_interface{
|
||||
.destroy = destroy,
|
||||
.get_river_output_status = getRiverOutputStatus,
|
||||
.get_river_seat_status = getRiverSeatStatus,
|
||||
};
|
||||
|
||||
wl_global: *c.wl_global,
|
||||
|
||||
listen_display_destroy: c.wl_listener = undefined,
|
||||
server_destroy: wl.Listener(*wl.Server) = undefined,
|
||||
|
||||
pub fn init(self: *Self, server: *Server) !void {
|
||||
self.* = .{
|
||||
.wl_global = c.wl_global_create(
|
||||
server.wl_display,
|
||||
&c.zriver_status_manager_v1_interface,
|
||||
protocol_version,
|
||||
self,
|
||||
bind,
|
||||
) orelse return error.OutOfMemory,
|
||||
.global = try wl.Global.create(server.wl_server, zriver.StatusManagerV1, 1, *Self, self, bind),
|
||||
};
|
||||
|
||||
self.listen_display_destroy.notify = handleDisplayDestroy;
|
||||
c.wl_display_add_destroy_listener(server.wl_display, &self.listen_display_destroy);
|
||||
self.server_destroy.setNotify(handleServerDestroy);
|
||||
server.wl_server.addDestroyListener(&self.server_destroy);
|
||||
}
|
||||
|
||||
fn handleDisplayDestroy(wl_listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||
const self = @fieldParentPtr(Self, "listen_display_destroy", wl_listener.?);
|
||||
c.wl_global_destroy(self.wl_global);
|
||||
fn handleServerDestroy(listener: *wl.Listener(*wl.Server), wl_server: *wl.Server) void {
|
||||
const self = @fieldParentPtr(Self, "server_destroy", listener);
|
||||
self.global.destroy();
|
||||
}
|
||||
|
||||
/// Called when a client binds our global
|
||||
fn bind(wl_client: ?*c.wl_client, data: ?*c_void, version: u32, id: u32) callconv(.C) void {
|
||||
const self = util.voidCast(Self, data.?);
|
||||
const wl_resource = c.wl_resource_create(
|
||||
wl_client,
|
||||
&c.zriver_status_manager_v1_interface,
|
||||
@intCast(c_int, version),
|
||||
id,
|
||||
) orelse {
|
||||
c.wl_client_post_no_memory(wl_client);
|
||||
fn bind(client: *wl.Client, self: *Self, version: u32, id: u32) callconv(.C) void {
|
||||
const status_manager = zriver.StatusManagerV1.create(client, version, id) catch {
|
||||
client.postNoMemory();
|
||||
log.crit(.river_status, "out of memory", .{});
|
||||
return;
|
||||
};
|
||||
c.wl_resource_set_implementation(wl_resource, &implementation, self, null);
|
||||
status_manager.setHandler(*Self, handleRequest, null, self);
|
||||
}
|
||||
|
||||
fn destroy(wl_client: ?*c.wl_client, wl_resource: ?*c.wl_resource) callconv(.C) void {
|
||||
c.wl_resource_destroy(wl_resource);
|
||||
}
|
||||
|
||||
fn getRiverOutputStatus(
|
||||
wl_client: ?*c.wl_client,
|
||||
wl_resource: ?*c.wl_resource,
|
||||
new_id: u32,
|
||||
output_wl_resource: ?*c.wl_resource,
|
||||
) callconv(.C) void {
|
||||
const self = util.voidCast(Self, c.wl_resource_get_user_data(wl_resource).?);
|
||||
// This can be null if the output is inert, in which case we ignore the request
|
||||
const wlr_output = c.wlr_output_from_resource(output_wl_resource) orelse return;
|
||||
const output = util.voidCast(Output, wlr_output.*.data.?);
|
||||
|
||||
const node = util.gpa.create(std.SinglyLinkedList(OutputStatus).Node) catch {
|
||||
c.wl_client_post_no_memory(wl_client);
|
||||
log.crit(.river_status, "out of memory", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
const output_status_resource = c.wl_resource_create(
|
||||
wl_client,
|
||||
&c.zriver_output_status_v1_interface,
|
||||
protocol_version,
|
||||
new_id,
|
||||
) orelse {
|
||||
c.wl_client_post_no_memory(wl_client);
|
||||
util.gpa.destroy(node);
|
||||
log.crit(.river_status, "out of memory", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
node.data.init(output, output_status_resource);
|
||||
output.status_trackers.prepend(node);
|
||||
}
|
||||
|
||||
fn getRiverSeatStatus(
|
||||
wl_client: ?*c.wl_client,
|
||||
wl_resource: ?*c.wl_resource,
|
||||
new_id: u32,
|
||||
seat_wl_resource: ?*c.wl_resource,
|
||||
) callconv(.C) void {
|
||||
const self = util.voidCast(Self, c.wl_resource_get_user_data(wl_resource).?);
|
||||
// This can be null if the seat is inert, in which case we ignore the request
|
||||
const wlr_seat_client = c.wlr_seat_client_from_resource(seat_wl_resource) orelse return;
|
||||
const seat = util.voidCast(Seat, wlr_seat_client.*.seat.*.data.?);
|
||||
|
||||
const node = util.gpa.create(std.SinglyLinkedList(SeatStatus).Node) catch {
|
||||
c.wl_client_post_no_memory(wl_client);
|
||||
log.crit(.river_status, "out of memory", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
const seat_status_resource = c.wl_resource_create(
|
||||
wl_client,
|
||||
&c.zriver_seat_status_v1_interface,
|
||||
protocol_version,
|
||||
new_id,
|
||||
) orelse {
|
||||
c.wl_client_post_no_memory(wl_client);
|
||||
util.gpa.destroy(node);
|
||||
log.crit(.river_status, "out of memory", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
node.data.init(seat, seat_status_resource);
|
||||
seat.status_trackers.prepend(node);
|
||||
fn handleRequest(
|
||||
status_manager: *zriver.StatusManagerV1,
|
||||
request: zriver.StatusManagerV1.Request,
|
||||
self: *Self,
|
||||
) void {
|
||||
switch (request) {
|
||||
.destroy => status_manager.destroy(),
|
||||
.get_river_output_status => |req| {
|
||||
// ignore if the output is inert
|
||||
const wlr_output = wlr.Output.fromWlOutput(req.output) orelse return;
|
||||
const output = @intToPtr(*Output, wlr_output.data);
|
||||
|
||||
const node = util.gpa.create(std.SinglyLinkedList(OutputStatus).Node) catch {
|
||||
status_manager.getClient().postNoMemory();
|
||||
log.crit(.river_status, "out of memory", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
const output_status = zriver.OutputStatusV1.create(
|
||||
status_manager.getClient(),
|
||||
status_manager.getVersion(),
|
||||
req.id,
|
||||
) catch {
|
||||
status_manager.getClient().postNoMemory();
|
||||
util.gpa.destroy(node);
|
||||
log.crit(.river_status, "out of memory", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
node.data.init(output, output_status);
|
||||
output.status_trackers.prepend(node);
|
||||
},
|
||||
.get_river_seat_status => |req| {
|
||||
// ignore if the seat is inert
|
||||
const wlr_seat = wlr.Seat.Client.fromWlSeat(req.seat) orelse return;
|
||||
const seat = @intToPtr(*Seat, wlr_seat.seat.data);
|
||||
|
||||
const node = util.gpa.create(std.SinglyLinkedList(SeatStatus).Node) catch {
|
||||
status_manager.getClient().postNoMemory();
|
||||
log.crit(.river_status, "out of memory", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
const seat_status = zriver.SeatStatusV1.create(
|
||||
status_manager.getClient(),
|
||||
status_manager.getVersion(),
|
||||
req.id,
|
||||
) catch {
|
||||
status_manager.getClient().postNoMemory();
|
||||
util.gpa.destroy(node);
|
||||
log.crit(.river_status, "out of memory", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
node.data.init(seat, seat_status);
|
||||
seat.status_trackers.prepend(node);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
123
river/View.zig
123
river/View.zig
@ -19,8 +19,10 @@ const Self = @This();
|
||||
|
||||
const build_options = @import("build_options");
|
||||
const std = @import("std");
|
||||
const os = std.os;
|
||||
const wlr = @import("wlroots");
|
||||
const wl = @import("wayland").server.wl;
|
||||
|
||||
const c = @import("c.zig");
|
||||
const log = @import("log.zig");
|
||||
const util = @import("util.zig");
|
||||
|
||||
@ -71,9 +73,9 @@ const State = struct {
|
||||
};
|
||||
|
||||
const SavedBuffer = struct {
|
||||
wlr_client_buffer: *c.wlr_client_buffer,
|
||||
client_buffer: *wlr.ClientBuffer,
|
||||
box: Box,
|
||||
transform: c.wl_output_transform,
|
||||
transform: wl.Output.Transform,
|
||||
};
|
||||
|
||||
/// The implementation of this view
|
||||
@ -84,10 +86,10 @@ output: *Output,
|
||||
|
||||
/// This is from the point where the view is mapped until the surface
|
||||
/// is destroyed by wlroots.
|
||||
wlr_surface: ?*c.wlr_surface = null,
|
||||
surface: ?*wlr.Surface = null,
|
||||
|
||||
/// This View struct outlasts the wlroots object it wraps. This bool is set to
|
||||
/// true when the backing wlr_xdg_toplevel or equivalent has been destroyed.
|
||||
/// true when the backing wlr.XdgToplevel or equivalent has been destroyed.
|
||||
destroying: bool = false,
|
||||
|
||||
/// The double-buffered state of the view
|
||||
@ -116,7 +118,7 @@ float_box: Box = undefined,
|
||||
opacity: f32,
|
||||
|
||||
/// Opacity change timer event source
|
||||
opacity_timer: ?*c.wl_event_source = null,
|
||||
opacity_timer: ?*wl.EventSource = null,
|
||||
|
||||
draw_borders: bool = true,
|
||||
|
||||
@ -135,10 +137,10 @@ pub fn init(self: *Self, output: *Output, tags: u32, surface: anytype) void {
|
||||
.opacity = output.root.server.config.view_opacity_initial,
|
||||
};
|
||||
|
||||
if (@TypeOf(surface) == *c.wlr_xdg_surface) {
|
||||
if (@TypeOf(surface) == *wlr.XdgSurface) {
|
||||
self.impl = .{ .xdg_toplevel = undefined };
|
||||
self.impl.xdg_toplevel.init(self, surface);
|
||||
} else if (build_options.xwayland and @TypeOf(surface) == *c.wlr_xwayland_surface) {
|
||||
} else if (build_options.xwayland and @TypeOf(surface) == *wlr.XwaylandSurface) {
|
||||
self.impl = .{ .xwayland_view = undefined };
|
||||
self.impl.xwayland_view.init(self, surface);
|
||||
} else unreachable;
|
||||
@ -146,7 +148,7 @@ pub fn init(self: *Self, output: *Output, tags: u32, surface: anytype) void {
|
||||
|
||||
/// Deinit the view, remove it from the view stack and free the memory.
|
||||
pub fn destroy(self: *Self) void {
|
||||
for (self.saved_buffers.items) |buffer| c.wlr_buffer_unlock(&buffer.wlr_client_buffer.*.base);
|
||||
self.dropSavedBuffers();
|
||||
self.saved_buffers.deinit();
|
||||
switch (self.impl) {
|
||||
.xdg_toplevel => |*xdg_toplevel| xdg_toplevel.deinit(),
|
||||
@ -183,12 +185,12 @@ pub fn applyPending(self: *Self) void {
|
||||
// and turn the view fully opaque
|
||||
if (!self.current.fullscreen and self.pending.fullscreen) {
|
||||
self.pending.target_opacity = 1.0;
|
||||
const layout_box = c.wlr_output_layout_get_box(self.output.root.wlr_output_layout, self.output.wlr_output);
|
||||
const layout_box = self.output.root.output_layout.getBox(self.output.wlr_output).?;
|
||||
self.pending.box = .{
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = @intCast(u32, layout_box.*.width),
|
||||
.height = @intCast(u32, layout_box.*.height),
|
||||
.width = @intCast(u32, layout_box.width),
|
||||
.height = @intCast(u32, layout_box.height),
|
||||
};
|
||||
}
|
||||
|
||||
@ -225,20 +227,20 @@ pub fn configure(self: Self) void {
|
||||
}
|
||||
|
||||
pub fn sendFrameDone(self: Self) void {
|
||||
var now: c.timespec = undefined;
|
||||
_ = c.clock_gettime(c.CLOCK_MONOTONIC, &now);
|
||||
c.wlr_surface_send_frame_done(self.wlr_surface.?, &now);
|
||||
var now: os.timespec = undefined;
|
||||
os.clock_gettime(os.CLOCK_MONOTONIC, &now) catch @panic("CLOCK_MONOTONIC not supported");
|
||||
self.surface.?.sendFrameDone(&now);
|
||||
}
|
||||
|
||||
pub fn dropSavedBuffers(self: *Self) void {
|
||||
for (self.saved_buffers.items) |buffer| c.wlr_buffer_unlock(&buffer.wlr_client_buffer.*.base);
|
||||
for (self.saved_buffers.items) |buffer| buffer.client_buffer.base.unlock();
|
||||
self.saved_buffers.items.len = 0;
|
||||
}
|
||||
|
||||
pub fn saveBuffers(self: *Self) void {
|
||||
std.debug.assert(self.saved_buffers.items.len == 0);
|
||||
self.saved_surface_box = self.surface_box;
|
||||
self.forEachSurface(saveBuffersIterator, &self.saved_buffers);
|
||||
self.forEachSurface(*std.ArrayList(SavedBuffer), saveBuffersIterator, &self.saved_buffers);
|
||||
}
|
||||
|
||||
/// If this commit is in response to our configure and the
|
||||
@ -257,26 +259,23 @@ pub fn notifyConfiguredOrApplyPending(self: *Self) void {
|
||||
}
|
||||
|
||||
fn saveBuffersIterator(
|
||||
wlr_surface: ?*c.wlr_surface,
|
||||
surface: *wlr.Surface,
|
||||
surface_x: c_int,
|
||||
surface_y: c_int,
|
||||
data: ?*c_void,
|
||||
saved_buffers: *std.ArrayList(SavedBuffer),
|
||||
) callconv(.C) void {
|
||||
const saved_buffers = util.voidCast(std.ArrayList(SavedBuffer), data.?);
|
||||
if (wlr_surface) |surface| {
|
||||
if (c.wlr_surface_has_buffer(surface)) {
|
||||
saved_buffers.append(.{
|
||||
.wlr_client_buffer = surface.buffer,
|
||||
.box = Box{
|
||||
.x = surface_x,
|
||||
.y = surface_y,
|
||||
.width = @intCast(u32, surface.current.width),
|
||||
.height = @intCast(u32, surface.current.height),
|
||||
},
|
||||
.transform = surface.current.transform,
|
||||
}) catch return;
|
||||
_ = c.wlr_buffer_lock(&surface.buffer.*.base);
|
||||
}
|
||||
if (surface.buffer) |buffer| {
|
||||
saved_buffers.append(.{
|
||||
.client_buffer = buffer,
|
||||
.box = Box{
|
||||
.x = surface_x,
|
||||
.y = surface_y,
|
||||
.width = @intCast(u32, surface.current.width),
|
||||
.height = @intCast(u32, surface.current.height),
|
||||
},
|
||||
.transform = surface.current.transform,
|
||||
}) catch return;
|
||||
_ = buffer.base.lock();
|
||||
}
|
||||
}
|
||||
|
||||
@ -291,8 +290,8 @@ pub fn sendToOutput(self: *Self, destination_output: *Output) void {
|
||||
self.output.sendViewTags();
|
||||
destination_output.sendViewTags();
|
||||
|
||||
c.wlr_surface_send_leave(self.wlr_surface, self.output.wlr_output);
|
||||
c.wlr_surface_send_enter(self.wlr_surface, destination_output.wlr_output);
|
||||
self.surface.?.sendLeave(self.output.wlr_output);
|
||||
self.surface.?.sendEnter(destination_output.wlr_output);
|
||||
|
||||
self.output = destination_output;
|
||||
}
|
||||
@ -304,20 +303,21 @@ pub fn close(self: Self) void {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn forEachSurface(
|
||||
pub inline fn forEachSurface(
|
||||
self: Self,
|
||||
iterator: c.wlr_surface_iterator_func_t,
|
||||
user_data: ?*c_void,
|
||||
comptime T: type,
|
||||
iterator: fn (surface: *wlr.Surface, sx: c_int, sy: c_int, data: T) callconv(.C) void,
|
||||
user_data: T,
|
||||
) void {
|
||||
switch (self.impl) {
|
||||
.xdg_toplevel => |xdg_toplevel| xdg_toplevel.forEachSurface(iterator, user_data),
|
||||
.xwayland_view => |xwayland_view| xwayland_view.forEachSurface(iterator, user_data),
|
||||
.xdg_toplevel => |xdg_toplevel| xdg_toplevel.forEachSurface(T, iterator, user_data),
|
||||
.xwayland_view => |xwayland_view| xwayland_view.forEachSurface(T, iterator, user_data),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the surface at output coordinates ox, oy and set sx, sy to the
|
||||
/// corresponding surface-relative coordinates, if there is a surface.
|
||||
pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_surface {
|
||||
pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*wlr.Surface {
|
||||
return switch (self.impl) {
|
||||
.xdg_toplevel => |xdg_toplevel| xdg_toplevel.surfaceAt(ox, oy, sx, sy),
|
||||
.xwayland_view => |xwayland_view| xwayland_view.surfaceAt(ox, oy, sx, sy),
|
||||
@ -348,18 +348,18 @@ pub fn getConstraints(self: Self) Constraints {
|
||||
};
|
||||
}
|
||||
|
||||
/// Find and return the view corresponding to a given wlr_surface, if any
|
||||
pub fn fromWlrSurface(wlr_surface: *c.wlr_surface) ?*Self {
|
||||
if (c.wlr_surface_is_xdg_surface(wlr_surface)) {
|
||||
const wlr_xdg_surface = c.wlr_xdg_surface_from_wlr_surface(wlr_surface);
|
||||
if (wlr_xdg_surface.*.role == .WLR_XDG_SURFACE_ROLE_TOPLEVEL) {
|
||||
return util.voidCast(Self, wlr_xdg_surface.*.data.?);
|
||||
/// Find and return the view corresponding to a given surface, if any
|
||||
pub fn fromWlrSurface(surface: *wlr.Surface) ?*Self {
|
||||
if (surface.isXdgSurface()) {
|
||||
const xdg_surface = wlr.XdgSurface.fromWlrSurface(surface);
|
||||
if (xdg_surface.role == .toplevel) {
|
||||
return @intToPtr(*Self, xdg_surface.data);
|
||||
}
|
||||
}
|
||||
if (build_options.xwayland) {
|
||||
if (c.wlr_surface_is_xwayland_surface(wlr_surface)) {
|
||||
const wlr_xwayland_surface = c.wlr_xwayland_surface_from_wlr_surface(wlr_surface);
|
||||
return util.voidCast(Self, wlr_xwayland_surface.*.data.?);
|
||||
if (surface.isXWaylandSurface()) {
|
||||
const xwayland_surface = wlr.XwaylandSurface.fromWlrSurface(surface);
|
||||
return @intToPtr(*Self, xwayland_surface.data);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@ -392,7 +392,7 @@ pub fn map(self: *Self) void {
|
||||
var it = root.server.input_manager.seats.first;
|
||||
while (it) |seat_node| : (it = seat_node.next) seat_node.data.focus(self);
|
||||
|
||||
c.wlr_surface_send_enter(self.wlr_surface.?, self.output.wlr_output);
|
||||
self.surface.?.sendEnter(self.output.wlr_output);
|
||||
|
||||
self.output.sendViewTags();
|
||||
|
||||
@ -446,22 +446,21 @@ fn incrementOpacity(self: *Self) bool {
|
||||
|
||||
/// Destroy a views opacity timer
|
||||
fn killOpacityTimer(self: *Self) void {
|
||||
if (c.wl_event_source_remove(self.opacity_timer) < 0) unreachable;
|
||||
self.opacity_timer.?.remove();
|
||||
self.opacity_timer = null;
|
||||
}
|
||||
|
||||
/// Set the timeout on a views opacity timer
|
||||
fn armOpacityTimer(self: *Self) void {
|
||||
const delta_t = self.output.root.server.config.view_opacity_delta_t;
|
||||
if (c.wl_event_source_timer_update(self.opacity_timer, delta_t) < 0) {
|
||||
log.err(.view, "failed to update opacity timer", .{});
|
||||
self.opacity_timer.?.timerUpdate(delta_t) catch |err| {
|
||||
log.err(.view, "failed to update opacity timer: {}", .{err});
|
||||
self.killOpacityTimer();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Called by the opacity timer
|
||||
fn handleOpacityTimer(data: ?*c_void) callconv(.C) c_int {
|
||||
const self = util.voidCast(Self, data.?);
|
||||
fn handleOpacityTimer(self: *Self) callconv(.C) c_int {
|
||||
if (self.incrementOpacity()) {
|
||||
self.killOpacityTimer();
|
||||
} else {
|
||||
@ -472,12 +471,8 @@ fn handleOpacityTimer(data: ?*c_void) callconv(.C) c_int {
|
||||
|
||||
/// Create an opacity timer for a view and arm it
|
||||
fn attachOpacityTimer(self: *Self) void {
|
||||
const server = self.output.root.server;
|
||||
self.opacity_timer = c.wl_event_loop_add_timer(
|
||||
c.wl_display_get_event_loop(server.wl_display),
|
||||
handleOpacityTimer,
|
||||
self,
|
||||
) orelse {
|
||||
const event_loop = self.output.root.server.wl_server.getEventLoop();
|
||||
self.opacity_timer = event_loop.addTimer(*Self, handleOpacityTimer, self) catch {
|
||||
log.err(.view, "failed to create opacity timer for view '{}'", .{self.getTitle()});
|
||||
return;
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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;
|
||||
|
53
river/c.zig
53
river/c.zig
@ -17,63 +17,10 @@
|
||||
|
||||
pub usingnamespace @cImport({
|
||||
@cDefine("_POSIX_C_SOURCE", "200809L");
|
||||
@cDefine("WLR_USE_UNSTABLE", {});
|
||||
|
||||
@cInclude("stdlib.h");
|
||||
@cInclude("time.h");
|
||||
@cInclude("unistd.h");
|
||||
|
||||
@cInclude("linux/input-event-codes.h");
|
||||
@cInclude("libevdev/libevdev.h");
|
||||
|
||||
@cInclude("wayland-server-core.h");
|
||||
@cInclude("wlr/backend.h");
|
||||
@cInclude("wlr/backend/multi.h");
|
||||
@cInclude("wlr/backend/noop.h");
|
||||
//@cInclude("wlr/render/wlr_renderer.h");
|
||||
@cInclude("wlr/types/wlr_buffer.h");
|
||||
@cInclude("wlr/types/wlr_compositor.h");
|
||||
@cInclude("wlr/types/wlr_cursor.h");
|
||||
@cInclude("wlr/types/wlr_data_control_v1.h");
|
||||
@cInclude("wlr/types/wlr_data_device.h");
|
||||
@cInclude("wlr/types/wlr_export_dmabuf_v1.h");
|
||||
@cInclude("wlr/types/wlr_gamma_control_v1.h");
|
||||
@cInclude("wlr/types/wlr_idle.h");
|
||||
@cInclude("wlr/types/wlr_input_device.h");
|
||||
@cInclude("wlr/types/wlr_input_inhibitor.h");
|
||||
@cInclude("wlr/types/wlr_keyboard.h");
|
||||
@cInclude("wlr/types/wlr_layer_shell_v1.h");
|
||||
@cInclude("wlr/types/wlr_matrix.h");
|
||||
@cInclude("wlr/types/wlr_output.h");
|
||||
@cInclude("wlr/types/wlr_output_layout.h");
|
||||
@cInclude("wlr/types/wlr_output_management_v1.h");
|
||||
@cInclude("wlr/types/wlr_output_power_management_v1.h");
|
||||
@cInclude("wlr/types/wlr_pointer.h");
|
||||
@cInclude("wlr/types/wlr_primary_selection.h");
|
||||
@cInclude("wlr/types/wlr_primary_selection_v1.h");
|
||||
@cInclude("wlr/types/wlr_screencopy_v1.h");
|
||||
@cInclude("wlr/types/wlr_seat.h");
|
||||
@cInclude("wlr/types/wlr_viewporter.h");
|
||||
@cInclude("wlr/types/wlr_virtual_pointer_v1.h");
|
||||
@cInclude("wlr/types/wlr_virtual_keyboard_v1.h");
|
||||
@cInclude("wlr/types/wlr_xcursor_manager.h");
|
||||
@cInclude("wlr/types/wlr_xdg_decoration_v1.h");
|
||||
@cInclude("wlr/types/wlr_xdg_output_v1.h");
|
||||
@cInclude("wlr/types/wlr_xdg_shell.h");
|
||||
if (@import("build_options").xwayland) @cInclude("wlr/xwayland.h");
|
||||
@cInclude("wlr/util/log.h");
|
||||
@cInclude("xkbcommon/xkbcommon.h");
|
||||
|
||||
// Contains a subset of functions from wlr/backend.h and wlr/render/wlr_renderer.h
|
||||
// that can be automatically imported
|
||||
@cInclude("include/bindings.h");
|
||||
|
||||
@cInclude("river-control-unstable-v1-protocol.h");
|
||||
@cInclude("river-status-unstable-v1-protocol.h");
|
||||
});
|
||||
|
||||
// These are needed because zig currently names translated anonymous unions
|
||||
// with a global counter, which makes code unportable.
|
||||
// See https://github.com/ifreund/river/issues/17
|
||||
pub const wlr_xdg_surface_union = @typeInfo(wlr_xdg_surface).Struct.fields[5].name;
|
||||
pub const wlr_input_device_union = @typeInfo(wlr_input_device).Struct.fields[8].name;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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,39 +163,47 @@ fn parseEventCode(allocator: *std.mem.Allocator, event_code_str: []const u8, out
|
||||
return @intCast(u32, ret);
|
||||
}
|
||||
|
||||
fn parseKeysym(allocator: *std.mem.Allocator, keysym_str: []const u8, out: *?[]const u8) !u32 {
|
||||
fn parseKeysym(allocator: *std.mem.Allocator, keysym_str: []const u8, out: *?[]const u8) !xkb.Keysym {
|
||||
const keysym_name = try std.cstr.addNullByte(allocator, keysym_str);
|
||||
defer allocator.free(keysym_name);
|
||||
const keysym = c.xkb_keysym_from_name(keysym_name, .XKB_KEYSYM_CASE_INSENSITIVE);
|
||||
if (keysym == c.XKB_KEY_NoSymbol) {
|
||||
out.* = try std.fmt.allocPrint(
|
||||
allocator,
|
||||
"invalid keysym '{}'",
|
||||
.{keysym_str},
|
||||
);
|
||||
const keysym = xkb.Keysym.fromName(keysym_name, .case_insensitive);
|
||||
if (keysym == .NoSymbol) {
|
||||
out.* = try std.fmt.allocPrint(allocator, "invalid keysym '{}'", .{keysym_str});
|
||||
return Error.Other;
|
||||
}
|
||||
|
||||
return keysym;
|
||||
}
|
||||
|
||||
fn parseModifiers(allocator: *std.mem.Allocator, modifiers_str: []const u8, out: *?[]const u8) !u32 {
|
||||
fn parseModifiers(
|
||||
allocator: *std.mem.Allocator,
|
||||
modifiers_str: []const u8,
|
||||
out: *?[]const u8,
|
||||
) !wlr.Keyboard.ModifierMask {
|
||||
var it = std.mem.split(modifiers_str, "+");
|
||||
var modifiers: u32 = 0;
|
||||
while (it.next()) |mod_name| {
|
||||
for (modifier_names) |def| {
|
||||
var modifiers = wlr.Keyboard.ModifierMask{};
|
||||
outer: while (it.next()) |mod_name| {
|
||||
if (mem.eql(u8, mod_name, "None")) continue;
|
||||
inline for ([_]struct { name: []const u8, field_name: []const u8 }{
|
||||
.{ .name = "Shift", .field_name = "shift" },
|
||||
.{ .name = "Lock", .field_name = "caps" },
|
||||
.{ .name = "Control", .field_name = "ctrl" },
|
||||
.{ .name = "Mod1", .field_name = "alt" },
|
||||
.{ .name = "Mod2", .field_name = "mod2" },
|
||||
.{ .name = "Mod3", .field_name = "mod3" },
|
||||
.{ .name = "Mod4", .field_name = "logo" },
|
||||
.{ .name = "Mod5", .field_name = "mod5" },
|
||||
}) |def| {
|
||||
if (std.mem.eql(u8, def.name, mod_name)) {
|
||||
modifiers |= def.modifier;
|
||||
break;
|
||||
@field(modifiers, def.field_name) = true;
|
||||
continue :outer;
|
||||
}
|
||||
} else {
|
||||
out.* = try std.fmt.allocPrint(
|
||||
allocator,
|
||||
"invalid modifier '{}'",
|
||||
.{mod_name},
|
||||
);
|
||||
return Error.Other;
|
||||
}
|
||||
out.* = try std.fmt.allocPrint(
|
||||
allocator,
|
||||
"invalid modifier '{}'",
|
||||
.{mod_name},
|
||||
);
|
||||
return Error.Other;
|
||||
}
|
||||
return modifiers;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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", .{});
|
||||
}
|
||||
|
134
river/render.zig
134
river/render.zig
@ -17,8 +17,11 @@
|
||||
|
||||
const build_options = @import("build_options");
|
||||
const std = @import("std");
|
||||
const os = std.os;
|
||||
const wlr = @import("wlroots");
|
||||
const wl = @import("wayland").server.wl;
|
||||
const pixman = @import("pixman");
|
||||
|
||||
const c = @import("c.zig");
|
||||
const log = @import("log.zig");
|
||||
const util = @import("util.zig");
|
||||
|
||||
@ -36,24 +39,21 @@ const SurfaceRenderData = struct {
|
||||
output_x: i32,
|
||||
output_y: i32,
|
||||
|
||||
when: *c.timespec,
|
||||
when: *os.timespec,
|
||||
|
||||
opacity: f32,
|
||||
};
|
||||
|
||||
pub fn renderOutput(output: *Output) void {
|
||||
const config = &output.root.server.config;
|
||||
const wlr_renderer = output.getRenderer();
|
||||
const renderer = output.wlr_output.backend.getRenderer().?;
|
||||
|
||||
var now: c.timespec = undefined;
|
||||
_ = c.clock_gettime(c.CLOCK_MONOTONIC, &now);
|
||||
var now: os.timespec = undefined;
|
||||
os.clock_gettime(os.CLOCK_MONOTONIC, &now) catch unreachable;
|
||||
|
||||
// wlr_output_attach_render makes the OpenGL context current.
|
||||
if (!c.wlr_output_attach_render(output.wlr_output, null)) return;
|
||||
output.wlr_output.attachRender(null) catch return;
|
||||
|
||||
// Begin the renderer (calls glViewport and some other GL sanity checks)
|
||||
// Here we don't want the output_effective_resolution since we want to render the whole output
|
||||
c.wlr_renderer_begin(wlr_renderer, output.wlr_output.width, output.wlr_output.height);
|
||||
renderer.begin(output.wlr_output.width, output.wlr_output.height);
|
||||
|
||||
// Find the first visible fullscreen view in the stack if there is one
|
||||
var it = ViewStack(View).iter(output.views.first, .forward, output.current.tags, renderFilter);
|
||||
@ -64,15 +64,15 @@ pub fn renderOutput(output: *Output) void {
|
||||
// If we have a fullscreen view to render, render it.
|
||||
if (fullscreen_view) |view| {
|
||||
// Always clear with solid black for fullscreen
|
||||
c.wlr_renderer_clear(wlr_renderer, &[_]f32{ 0, 0, 0, 1 });
|
||||
renderer.clear(&[_]f32{ 0, 0, 0, 1 });
|
||||
renderView(output.*, view, &now);
|
||||
if (build_options.xwayland) renderXwaylandUnmanaged(output.*, &now);
|
||||
} else {
|
||||
// No fullscreen view, so render normal layers/views
|
||||
c.wlr_renderer_clear(wlr_renderer, &config.background_color);
|
||||
renderer.clear(&config.background_color);
|
||||
|
||||
renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], &now, .toplevels);
|
||||
renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], &now, .toplevels);
|
||||
renderLayer(output.*, output.getLayer(.background).*, &now, .toplevels);
|
||||
renderLayer(output.*, output.getLayer(.bottom).*, &now, .toplevels);
|
||||
|
||||
// The first view in the list is "on top" so iterate in reverse.
|
||||
it = ViewStack(View).iter(output.views.last, .reverse, output.current.tags, renderFilter);
|
||||
@ -96,16 +96,16 @@ pub fn renderOutput(output: *Output) void {
|
||||
|
||||
if (build_options.xwayland) renderXwaylandUnmanaged(output.*, &now);
|
||||
|
||||
renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_TOP], &now, .toplevels);
|
||||
renderLayer(output.*, output.getLayer(.top).*, &now, .toplevels);
|
||||
|
||||
renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], &now, .popups);
|
||||
renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], &now, .popups);
|
||||
renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_TOP], &now, .popups);
|
||||
renderLayer(output.*, output.getLayer(.background).*, &now, .popups);
|
||||
renderLayer(output.*, output.getLayer(.bottom).*, &now, .popups);
|
||||
renderLayer(output.*, output.getLayer(.top).*, &now, .popups);
|
||||
}
|
||||
|
||||
// The overlay layer is rendered in both fullscreen and normal cases
|
||||
renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], &now, .toplevels);
|
||||
renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], &now, .popups);
|
||||
renderLayer(output.*, output.getLayer(.overlay).*, &now, .toplevels);
|
||||
renderLayer(output.*, output.getLayer(.overlay).*, &now, .popups);
|
||||
|
||||
renderDragIcons(output.*, &now);
|
||||
|
||||
@ -115,28 +115,27 @@ pub fn renderOutput(output: *Output) void {
|
||||
// reason, wlroots provides a software fallback, which we ask it to render
|
||||
// here. wlr_cursor handles configuring hardware vs software cursors for you,
|
||||
// and this function is a no-op when hardware cursors are in use.
|
||||
c.wlr_output_render_software_cursors(output.wlr_output, null);
|
||||
output.wlr_output.renderSoftwareCursors(null);
|
||||
|
||||
// Conclude rendering and swap the buffers, showing the final frame
|
||||
// on-screen.
|
||||
c.wlr_renderer_end(wlr_renderer);
|
||||
renderer.end();
|
||||
|
||||
// TODO(wlroots): remove this with the next release. It is here due to
|
||||
// a wlroots bug in the screencopy damage implementation
|
||||
{
|
||||
var w: c_int = undefined;
|
||||
var h: c_int = undefined;
|
||||
c.wlr_output_transformed_resolution(output.wlr_output, &w, &h);
|
||||
var damage: c.pixman_region32_t = undefined;
|
||||
c.pixman_region32_init(&damage);
|
||||
_ = c.pixman_region32_union_rect(&damage, &damage, 0, 0, @intCast(c_uint, w), @intCast(c_uint, h));
|
||||
c.wlr_output_set_damage(output.wlr_output, &damage);
|
||||
output.wlr_output.transformedResolution(&w, &h);
|
||||
var damage: pixman.Region32 = undefined;
|
||||
damage.init();
|
||||
_ = damage.unionRect(&damage, 0, 0, @intCast(c_uint, w), @intCast(c_uint, h));
|
||||
output.wlr_output.setDamage(&damage);
|
||||
}
|
||||
|
||||
// TODO: handle failure
|
||||
if (!c.wlr_output_commit(output.wlr_output)) {
|
||||
log.err(.render, "wlr_output_commit failed for {}", .{output.wlr_output.name});
|
||||
}
|
||||
output.wlr_output.commit() catch
|
||||
log.err(.render, "output commit failed for {}", .{output.wlr_output.name});
|
||||
}
|
||||
|
||||
fn renderFilter(view: *View, filter_tags: u32) bool {
|
||||
@ -151,7 +150,7 @@ fn renderFilter(view: *View, filter_tags: u32) bool {
|
||||
fn renderLayer(
|
||||
output: Output,
|
||||
layer: std.TailQueue(LayerSurface),
|
||||
now: *c.timespec,
|
||||
now: *os.timespec,
|
||||
role: enum { toplevels, popups },
|
||||
) void {
|
||||
var it = layer.first;
|
||||
@ -165,13 +164,13 @@ fn renderLayer(
|
||||
.opacity = 1.0,
|
||||
};
|
||||
switch (role) {
|
||||
.toplevels => c.wlr_surface_for_each_surface(
|
||||
layer_surface.wlr_layer_surface.surface,
|
||||
.toplevels => layer_surface.wlr_layer_surface.surface.forEachSurface(
|
||||
*SurfaceRenderData,
|
||||
renderSurfaceIterator,
|
||||
&rdata,
|
||||
),
|
||||
.popups => c.wlr_layer_surface_v1_for_each_popup(
|
||||
layer_surface.wlr_layer_surface,
|
||||
.popups => layer_surface.wlr_layer_surface.forEachPopup(
|
||||
*SurfaceRenderData,
|
||||
renderSurfaceIterator,
|
||||
&rdata,
|
||||
),
|
||||
@ -179,14 +178,14 @@ fn renderLayer(
|
||||
}
|
||||
}
|
||||
|
||||
fn renderView(output: Output, view: *View, now: *c.timespec) void {
|
||||
fn renderView(output: Output, view: *View, now: *os.timespec) void {
|
||||
// If we have saved buffers, we are in the middle of a transaction
|
||||
// and need to render those buffers until the transaction is complete.
|
||||
if (view.saved_buffers.items.len != 0) {
|
||||
for (view.saved_buffers.items) |saved_buffer|
|
||||
renderTexture(
|
||||
output,
|
||||
saved_buffer.wlr_client_buffer.texture,
|
||||
saved_buffer.client_buffer.texture orelse continue,
|
||||
.{
|
||||
.x = saved_buffer.box.x + view.current.box.x - view.saved_surface_box.x,
|
||||
.y = saved_buffer.box.y + view.current.box.y - view.saved_surface_box.y,
|
||||
@ -207,12 +206,12 @@ fn renderView(output: Output, view: *View, now: *c.timespec) void {
|
||||
.opacity = view.opacity,
|
||||
};
|
||||
|
||||
view.forEachSurface(renderSurfaceIterator, &rdata);
|
||||
view.forEachSurface(*SurfaceRenderData, renderSurfaceIterator, &rdata);
|
||||
}
|
||||
}
|
||||
|
||||
fn renderDragIcons(output: Output, now: *c.timespec) void {
|
||||
const output_box = c.wlr_output_layout_get_box(output.root.wlr_output_layout, output.wlr_output);
|
||||
fn renderDragIcons(output: Output, now: *os.timespec) void {
|
||||
const output_box = output.root.output_layout.getBox(output.wlr_output).?;
|
||||
|
||||
var it = output.root.drag_icons.first;
|
||||
while (it) |node| : (it = node.next) {
|
||||
@ -221,70 +220,67 @@ fn renderDragIcons(output: Output, now: *c.timespec) void {
|
||||
var rdata = SurfaceRenderData{
|
||||
.output = &output,
|
||||
.output_x = @floatToInt(i32, drag_icon.seat.cursor.wlr_cursor.x) +
|
||||
drag_icon.wlr_drag_icon.surface.*.sx - output_box.*.x,
|
||||
drag_icon.wlr_drag_icon.surface.sx - output_box.x,
|
||||
.output_y = @floatToInt(i32, drag_icon.seat.cursor.wlr_cursor.y) +
|
||||
drag_icon.wlr_drag_icon.surface.*.sy - output_box.*.y,
|
||||
drag_icon.wlr_drag_icon.surface.sy - output_box.y,
|
||||
.when = now,
|
||||
.opacity = 1.0,
|
||||
};
|
||||
c.wlr_surface_for_each_surface(drag_icon.wlr_drag_icon.surface, renderSurfaceIterator, &rdata);
|
||||
drag_icon.wlr_drag_icon.surface.forEachSurface(*SurfaceRenderData, renderSurfaceIterator, &rdata);
|
||||
}
|
||||
}
|
||||
|
||||
/// Render all xwayland unmanaged windows that appear on the output
|
||||
fn renderXwaylandUnmanaged(output: Output, now: *c.timespec) void {
|
||||
const output_box = c.wlr_output_layout_get_box(output.root.wlr_output_layout, output.wlr_output);
|
||||
fn renderXwaylandUnmanaged(output: Output, now: *os.timespec) void {
|
||||
const output_box = output.root.output_layout.getBox(output.wlr_output).?;
|
||||
|
||||
var it = output.root.xwayland_unmanaged_views.first;
|
||||
while (it) |node| : (it = node.next) {
|
||||
const wlr_xwayland_surface = node.data.wlr_xwayland_surface;
|
||||
const xwayland_surface = node.data.xwayland_surface;
|
||||
|
||||
var rdata = SurfaceRenderData{
|
||||
.output = &output,
|
||||
.output_x = wlr_xwayland_surface.x - output_box.*.x,
|
||||
.output_y = wlr_xwayland_surface.y - output_box.*.y,
|
||||
.output_x = xwayland_surface.x - output_box.x,
|
||||
.output_y = xwayland_surface.y - output_box.y,
|
||||
.when = now,
|
||||
.opacity = 1.0,
|
||||
};
|
||||
c.wlr_surface_for_each_surface(wlr_xwayland_surface.surface, renderSurfaceIterator, &rdata);
|
||||
xwayland_surface.surface.?.forEachSurface(*SurfaceRenderData, renderSurfaceIterator, &rdata);
|
||||
}
|
||||
}
|
||||
|
||||
/// This function is passed to wlroots to render each surface during iteration
|
||||
fn renderSurfaceIterator(
|
||||
surface: ?*c.wlr_surface,
|
||||
surface: *wlr.Surface,
|
||||
surface_x: c_int,
|
||||
surface_y: c_int,
|
||||
data: ?*c_void,
|
||||
rdata: *SurfaceRenderData,
|
||||
) callconv(.C) void {
|
||||
const rdata = util.voidCast(SurfaceRenderData, data.?);
|
||||
|
||||
renderTexture(
|
||||
rdata.output.*,
|
||||
c.wlr_surface_get_texture(surface),
|
||||
surface.getTexture() orelse return,
|
||||
.{
|
||||
.x = rdata.output_x + surface_x,
|
||||
.y = rdata.output_y + surface_y,
|
||||
.width = surface.?.current.width,
|
||||
.height = surface.?.current.height,
|
||||
.width = surface.current.width,
|
||||
.height = surface.current.height,
|
||||
},
|
||||
surface.?.current.transform,
|
||||
surface.current.transform,
|
||||
rdata.opacity,
|
||||
);
|
||||
|
||||
c.wlr_surface_send_frame_done(surface, rdata.when);
|
||||
surface.sendFrameDone(rdata.when);
|
||||
}
|
||||
|
||||
/// Render the given texture at the given box, taking the scale and transform
|
||||
/// of the output into account.
|
||||
fn renderTexture(
|
||||
output: Output,
|
||||
wlr_texture: ?*c.wlr_texture,
|
||||
wlr_box: c.wlr_box,
|
||||
transform: c.wl_output_transform,
|
||||
texture: *wlr.Texture,
|
||||
wlr_box: wlr.Box,
|
||||
transform: wl.Output.Transform,
|
||||
opacity: f32,
|
||||
) void {
|
||||
const texture = wlr_texture orelse return;
|
||||
var box = wlr_box;
|
||||
|
||||
// Scale the box to the output's current scaling factor
|
||||
@ -295,15 +291,16 @@ fn renderTexture(
|
||||
// prepares an orthographic projection and multiplies the necessary
|
||||
// transforms to produce a model-view-projection matrix.
|
||||
var matrix: [9]f32 = undefined;
|
||||
const inverted = c.wlr_output_transform_invert(transform);
|
||||
c.wlr_matrix_project_box(&matrix, &box, inverted, 0.0, &output.wlr_output.transform_matrix);
|
||||
const inverted = wlr.Output.transformInvert(transform);
|
||||
wlr.matrix.projectBox(&matrix, &box, inverted, 0.0, &output.wlr_output.transform_matrix);
|
||||
|
||||
// This takes our matrix, the texture, and an alpha, and performs the actual
|
||||
// rendering on the GPU.
|
||||
_ = c.wlr_render_texture_with_matrix(output.getRenderer(), texture, &matrix, opacity);
|
||||
const renderer = output.wlr_output.backend.getRenderer().?;
|
||||
renderer.renderTextureWithMatrix(texture, &matrix, opacity) catch return;
|
||||
}
|
||||
|
||||
fn renderBorders(output: Output, view: *View, now: *c.timespec) void {
|
||||
fn renderBorders(output: Output, view: *View, now: *os.timespec) void {
|
||||
const config = &output.root.server.config;
|
||||
const color = if (view.current.focus != 0) &config.border_color_focused else &config.border_color_unfocused;
|
||||
const border_width = config.border_width;
|
||||
@ -341,8 +338,7 @@ fn renderBorders(output: Output, view: *View, now: *c.timespec) void {
|
||||
fn renderRect(output: Output, box: Box, color: *const [4]f32) void {
|
||||
var wlr_box = box.toWlrBox();
|
||||
scaleBox(&wlr_box, output.wlr_output.scale);
|
||||
c.wlr_render_rect(
|
||||
output.getRenderer(),
|
||||
output.wlr_output.backend.getRenderer().?.renderRect(
|
||||
&wlr_box,
|
||||
color,
|
||||
&output.wlr_output.transform_matrix,
|
||||
@ -350,7 +346,7 @@ fn renderRect(output: Output, box: Box, color: *const [4]f32) void {
|
||||
}
|
||||
|
||||
/// Scale a wlr_box, taking the possibility of fractional scaling into account.
|
||||
fn scaleBox(box: *c.wlr_box, scale: f64) void {
|
||||
fn scaleBox(box: *wlr.Box, scale: f64) void {
|
||||
box.x = @floatToInt(c_int, @round(@intToFloat(f64, box.x) * scale));
|
||||
box.y = @floatToInt(c_int, @round(@intToFloat(f64, box.y) * scale));
|
||||
box.width = scaleLength(box.width, box.x, scale);
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user