river-options: implement
This commit is contained in:
parent
875e3c325d
commit
8cbccbfb6e
21
build.zig
21
build.zig
@ -41,6 +41,7 @@ pub fn build(b: *zbs.Builder) !void {
|
|||||||
const scanner = ScanProtocolsStep.create(b);
|
const scanner = ScanProtocolsStep.create(b);
|
||||||
scanner.addSystemProtocol("stable/xdg-shell/xdg-shell.xml");
|
scanner.addSystemProtocol("stable/xdg-shell/xdg-shell.xml");
|
||||||
scanner.addProtocolPath("protocol/river-control-unstable-v1.xml");
|
scanner.addProtocolPath("protocol/river-control-unstable-v1.xml");
|
||||||
|
scanner.addProtocolPath("protocol/river-options-unstable-v1.xml");
|
||||||
scanner.addProtocolPath("protocol/river-status-unstable-v1.xml");
|
scanner.addProtocolPath("protocol/river-status-unstable-v1.xml");
|
||||||
scanner.addProtocolPath("protocol/wlr-layer-shell-unstable-v1.xml");
|
scanner.addProtocolPath("protocol/wlr-layer-shell-unstable-v1.xml");
|
||||||
scanner.addProtocolPath("protocol/wlr-output-power-management-unstable-v1.xml");
|
scanner.addProtocolPath("protocol/wlr-output-power-management-unstable-v1.xml");
|
||||||
@ -85,18 +86,20 @@ pub fn build(b: *zbs.Builder) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (examples) {
|
if (examples) {
|
||||||
const status = b.addExecutable("status", "example/status.zig");
|
inline for (.{ "status", "options" }) |example_name| {
|
||||||
status.setTarget(target);
|
const example = b.addExecutable(example_name, "example/" ++ example_name ++ ".zig");
|
||||||
status.setBuildMode(mode);
|
example.setTarget(target);
|
||||||
|
example.setBuildMode(mode);
|
||||||
|
|
||||||
status.step.dependOn(&scanner.step);
|
example.step.dependOn(&scanner.step);
|
||||||
status.addPackage(scanner.getPkg());
|
example.addPackage(scanner.getPkg());
|
||||||
status.linkLibC();
|
example.linkLibC();
|
||||||
status.linkSystemLibrary("wayland-client");
|
example.linkSystemLibrary("wayland-client");
|
||||||
|
|
||||||
scanner.addCSource(status);
|
scanner.addCSource(example);
|
||||||
|
|
||||||
status.install();
|
example.install();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
2
deps/zig-wayland
vendored
2
deps/zig-wayland
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 0dd1bee2dea065d45ce674c5b3ece2fda38f775d
|
Subproject commit 05f539c8934f18f93ac50aad654fa469bf5121f8
|
102
example/options.zig
Normal file
102
example/options.zig
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
// This file is part of river, a dynamic tiling wayland compositor.
|
||||||
|
//
|
||||||
|
// Copyright 2020 The River Developers
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// 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 std = @import("std");
|
||||||
|
const os = std.os;
|
||||||
|
const mem = std.mem;
|
||||||
|
const fmt = std.fmt;
|
||||||
|
|
||||||
|
const wayland = @import("wayland");
|
||||||
|
const wl = wayland.client.wl;
|
||||||
|
const zriver = wayland.client.zriver;
|
||||||
|
|
||||||
|
const SetupContext = struct {
|
||||||
|
options_manager: ?*zriver.OptionsManagerV1 = null,
|
||||||
|
outputs: std.ArrayList(*wl.Output) = std.ArrayList(*wl.Output).init(std.heap.c_allocator),
|
||||||
|
};
|
||||||
|
|
||||||
|
const ValueType = enum {
|
||||||
|
int,
|
||||||
|
uint,
|
||||||
|
fixed,
|
||||||
|
string,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Disclaimer, the output handling implemented here is by no means robust. A
|
||||||
|
/// proper client should likely use xdg-output to identify outputs by name.
|
||||||
|
///
|
||||||
|
/// Usage: ./options <key> output_num|NULL [<value_type> <value>]
|
||||||
|
/// Examples:
|
||||||
|
/// ./options foo
|
||||||
|
/// ./options foo NULL uint 42
|
||||||
|
/// ./options foo 1 string ziggy
|
||||||
|
pub fn main() !void {
|
||||||
|
const display = try wl.Display.connect(null);
|
||||||
|
const registry = try display.getRegistry();
|
||||||
|
|
||||||
|
var context = SetupContext{};
|
||||||
|
|
||||||
|
registry.setListener(*SetupContext, registryListener, &context) catch unreachable;
|
||||||
|
_ = try display.roundtrip();
|
||||||
|
|
||||||
|
const options_manager = context.options_manager orelse return error.RiverOptionsManagerNotAdvertised;
|
||||||
|
|
||||||
|
const key = os.argv[1];
|
||||||
|
const output = if (mem.eql(u8, "NULL", mem.span(os.argv[2])))
|
||||||
|
null
|
||||||
|
else
|
||||||
|
context.outputs.items[fmt.parseInt(u32, mem.span(os.argv[2]), 10) catch return error.InvalidOutput];
|
||||||
|
const handle = try options_manager.getOptionHandle(key, output);
|
||||||
|
handle.setListener([*:0]u8, optionListener, key) catch unreachable;
|
||||||
|
|
||||||
|
if (os.argv.len > 3) {
|
||||||
|
const value_type = std.meta.stringToEnum(ValueType, mem.span(os.argv[3])) orelse return error.InvalidType;
|
||||||
|
switch (value_type) {
|
||||||
|
.int => handle.setIntValue(fmt.parseInt(i32, mem.span(os.argv[4]), 10) catch return error.InvalidInt),
|
||||||
|
.uint => handle.setUintValue(fmt.parseInt(u32, mem.span(os.argv[4]), 10) catch return error.InvalidUint),
|
||||||
|
.fixed => handle.setFixedValue(wl.Fixed.fromDouble(fmt.parseFloat(f64, mem.span(os.argv[4])) catch return error.InvalidFixed)),
|
||||||
|
.string => handle.setStringValue(os.argv[4]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop forever, listening for new events.
|
||||||
|
while (true) _ = try display.dispatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *SetupContext) void {
|
||||||
|
switch (event) {
|
||||||
|
.global => |global| {
|
||||||
|
if (std.cstr.cmp(global.interface, zriver.OptionsManagerV1.getInterface().name) == 0) {
|
||||||
|
context.options_manager = registry.bind(global.name, zriver.OptionsManagerV1, 1) catch return;
|
||||||
|
} 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 optionListener(handle: *zriver.OptionHandleV1, event: zriver.OptionHandleV1.Event, key: [*:0]const u8) void {
|
||||||
|
switch (event) {
|
||||||
|
.unset => std.debug.print("option '{}' unset\n", .{key}),
|
||||||
|
.int_value => |ev| std.debug.print("option '{}' of type int has value {}\n", .{ key, ev.value }),
|
||||||
|
.uint_value => |ev| std.debug.print("option '{}' of type uint has value {}\n", .{ key, ev.value }),
|
||||||
|
.fixed_value => |ev| std.debug.print("option '{}' of type fixed has value {}\n", .{ key, ev.value.toDouble() }),
|
||||||
|
.string_value => |ev| std.debug.print("option '{}' of type string has value {}\n", .{ key, ev.value }),
|
||||||
|
}
|
||||||
|
}
|
135
river/Option.zig
Normal file
135
river/Option.zig
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
// This file is part of river, a dynamic tiling wayland compositor.
|
||||||
|
//
|
||||||
|
// Copyright 2020 The River Developers
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// 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 Self = @This();
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const mem = std.mem;
|
||||||
|
const meta = std.meta;
|
||||||
|
|
||||||
|
const wayland = @import("wayland");
|
||||||
|
const wl = wayland.server.wl;
|
||||||
|
const zriver = wayland.server.zriver;
|
||||||
|
|
||||||
|
const util = @import("util.zig");
|
||||||
|
|
||||||
|
const Output = @import("Output.zig");
|
||||||
|
const OptionsManager = @import("OptionsManager.zig");
|
||||||
|
|
||||||
|
pub const Value = union(enum) {
|
||||||
|
unset: void,
|
||||||
|
int: i32,
|
||||||
|
uint: u32,
|
||||||
|
fixed: wl.Fixed,
|
||||||
|
string: ?[*:0]const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
options_manager: *OptionsManager,
|
||||||
|
link: wl.list.Link = undefined,
|
||||||
|
|
||||||
|
output: ?*Output,
|
||||||
|
key: [*:0]const u8,
|
||||||
|
value: Value = .unset,
|
||||||
|
|
||||||
|
handles: wl.list.Head(zriver.OptionHandleV1, null) = undefined,
|
||||||
|
|
||||||
|
pub fn create(options_manager: *OptionsManager, output: ?*Output, key: [*:0]const u8) !*Self {
|
||||||
|
const self = try util.gpa.create(Self);
|
||||||
|
errdefer util.gpa.destroy(self);
|
||||||
|
|
||||||
|
self.* = .{
|
||||||
|
.options_manager = options_manager,
|
||||||
|
.output = output,
|
||||||
|
.key = try util.gpa.dupeZ(u8, mem.span(key)),
|
||||||
|
};
|
||||||
|
self.handles.init();
|
||||||
|
|
||||||
|
options_manager.options.append(self);
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(self: *Self) void {
|
||||||
|
var it = self.handles.safeIterator(.forward);
|
||||||
|
while (it.next()) |handle| handle.destroy();
|
||||||
|
if (self.value == .string) if (self.value.string) |s| util.gpa.free(mem.span(s));
|
||||||
|
util.gpa.destroy(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts that the new value is not .unset.
|
||||||
|
/// Ignores the new value if the value is currently set and the type does not match.
|
||||||
|
/// If the value is a string, the string is cloned.
|
||||||
|
/// If the value is changed, send the proper event to all clients
|
||||||
|
pub fn set(self: *Self, value: Value) !void {
|
||||||
|
std.debug.assert(value != .unset);
|
||||||
|
if (self.value != .unset and meta.activeTag(value) != meta.activeTag(self.value)) return;
|
||||||
|
|
||||||
|
if (self.value == .unset and value == .string) {
|
||||||
|
self.value = .{
|
||||||
|
.string = if (value.string) |s| (try util.gpa.dupeZ(u8, mem.span(s))).ptr else null,
|
||||||
|
};
|
||||||
|
} else if (self.value == .string and
|
||||||
|
// TODO: std.mem needs a good way to compare optional sentinel pointers
|
||||||
|
((self.value.string == null and value.string == null) or
|
||||||
|
(self.value.string != null and value.string != null and
|
||||||
|
std.cstr.cmp(self.value.string.?, value.string.?) != 0)))
|
||||||
|
{
|
||||||
|
const owned_string = if (value.string) |s| (try util.gpa.dupeZ(u8, mem.span(s))).ptr else null;
|
||||||
|
if (self.value.string) |s| util.gpa.free(mem.span(s));
|
||||||
|
self.value.string = owned_string;
|
||||||
|
} else if (self.value == .unset or (self.value != .string and !std.meta.eql(self.value, value))) {
|
||||||
|
self.value = value;
|
||||||
|
} else {
|
||||||
|
// The value was not changed
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var it = self.handles.iterator(.forward);
|
||||||
|
while (it.next()) |handle| self.sendValue(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sendValue(self: Self, handle: *zriver.OptionHandleV1) void {
|
||||||
|
switch (self.value) {
|
||||||
|
.unset => handle.sendUnset(),
|
||||||
|
.int => |v| handle.sendIntValue(v),
|
||||||
|
.uint => |v| handle.sendUintValue(v),
|
||||||
|
.fixed => |v| handle.sendFixedValue(v),
|
||||||
|
.string => |v| handle.sendStringValue(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addHandle(self: *Self, handle: *zriver.OptionHandleV1) void {
|
||||||
|
self.handles.append(handle);
|
||||||
|
self.sendValue(handle);
|
||||||
|
handle.setHandler(*Self, handleRequest, handleDestroy, self);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handleRequest(handle: *zriver.OptionHandleV1, request: zriver.OptionHandleV1.Request, self: *Self) void {
|
||||||
|
switch (request) {
|
||||||
|
.destroy => handle.destroy(),
|
||||||
|
.set_int_value => |req| self.set(.{ .int = req.value }) catch unreachable,
|
||||||
|
.set_uint_value => |req| self.set(.{ .uint = req.value }) catch unreachable,
|
||||||
|
.set_fixed_value => |req| self.set(.{ .fixed = req.value }) catch unreachable,
|
||||||
|
.set_string_value => |req| self.set(.{ .string = req.value }) catch {
|
||||||
|
handle.getClient().postNoMemory();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handleDestroy(handle: *zriver.OptionHandleV1, self: *Self) void {
|
||||||
|
handle.getLink().remove();
|
||||||
|
}
|
100
river/OptionsManager.zig
Normal file
100
river/OptionsManager.zig
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
// This file is part of river, a dynamic tiling wayland compositor.
|
||||||
|
//
|
||||||
|
// Copyright 2020 The River Developers
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// 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 Self = @This();
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const wayland = @import("wayland");
|
||||||
|
const wl = wayland.server.wl;
|
||||||
|
const zriver = wayland.server.zriver;
|
||||||
|
|
||||||
|
const wlr = @import("wlroots");
|
||||||
|
|
||||||
|
const util = @import("util.zig");
|
||||||
|
|
||||||
|
const Option = @import("Option.zig");
|
||||||
|
const Output = @import("Output.zig");
|
||||||
|
const Server = @import("Server.zig");
|
||||||
|
|
||||||
|
global: *wl.Global,
|
||||||
|
server_destroy: wl.Listener(*wl.Server) = wl.Listener(*wl.Server).init(handleServerDestroy),
|
||||||
|
|
||||||
|
options: wl.list.Head(Option, "link") = undefined,
|
||||||
|
|
||||||
|
pub fn init(self: *Self, server: *Server) !void {
|
||||||
|
self.* = .{
|
||||||
|
.global = try wl.Global.create(server.wl_server, zriver.OptionsManagerV1, 1, *Self, self, bind),
|
||||||
|
};
|
||||||
|
self.options.init();
|
||||||
|
server.wl_server.addDestroyListener(&self.server_destroy);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handleServerDestroy(listener: *wl.Listener(*wl.Server), wl_server: *wl.Server) void {
|
||||||
|
const self = @fieldParentPtr(Self, "server_destroy", listener);
|
||||||
|
self.global.destroy();
|
||||||
|
var it = self.options.safeIterator(.forward);
|
||||||
|
while (it.next()) |option| option.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bind(client: *wl.Client, self: *Self, version: u32, id: u32) callconv(.C) void {
|
||||||
|
const options_manager = zriver.OptionsManagerV1.create(client, 1, id) catch {
|
||||||
|
client.postNoMemory();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
options_manager.setHandler(*Self, handleRequest, null, self);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handleRequest(
|
||||||
|
options_manager: *zriver.OptionsManagerV1,
|
||||||
|
request: zriver.OptionsManagerV1.Request,
|
||||||
|
self: *Self,
|
||||||
|
) void {
|
||||||
|
switch (request) {
|
||||||
|
.destroy => options_manager.destroy(),
|
||||||
|
.get_option_handle => |req| {
|
||||||
|
const output = if (req.output) |wl_output| blk: {
|
||||||
|
// Ignore if the wl_output is inert
|
||||||
|
const wlr_output = wlr.Output.fromWlOutput(wl_output) orelse return;
|
||||||
|
break :blk @intToPtr(*Output, wlr_output.data);
|
||||||
|
} else null;
|
||||||
|
|
||||||
|
// Look for an existing Option, if not found create a new one
|
||||||
|
var it = self.options.iterator(.forward);
|
||||||
|
const option = while (it.next()) |option| {
|
||||||
|
if (option.output == output and std.cstr.cmp(option.key, req.key) == 0) {
|
||||||
|
break option;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
Option.create(self, output, req.key) catch {
|
||||||
|
options_manager.getClient().postNoMemory();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handle = zriver.OptionHandleV1.create(
|
||||||
|
options_manager.getClient(),
|
||||||
|
options_manager.getVersion(),
|
||||||
|
req.handle,
|
||||||
|
) catch {
|
||||||
|
options_manager.getClient().postNoMemory();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
option.addHandle(handle);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -27,6 +27,7 @@ const log = @import("log.zig");
|
|||||||
const util = @import("util.zig");
|
const util = @import("util.zig");
|
||||||
|
|
||||||
const Config = @import("Config.zig");
|
const Config = @import("Config.zig");
|
||||||
|
const OptionsManager = @import("OptionsManager.zig");
|
||||||
const Control = @import("Control.zig");
|
const Control = @import("Control.zig");
|
||||||
const DecorationManager = @import("DecorationManager.zig");
|
const DecorationManager = @import("DecorationManager.zig");
|
||||||
const InputManager = @import("InputManager.zig");
|
const InputManager = @import("InputManager.zig");
|
||||||
@ -63,6 +64,7 @@ root: Root,
|
|||||||
config: Config,
|
config: Config,
|
||||||
control: Control,
|
control: Control,
|
||||||
status_manager: StatusManager,
|
status_manager: StatusManager,
|
||||||
|
options_manager: OptionsManager,
|
||||||
|
|
||||||
pub fn init(self: *Self) !void {
|
pub fn init(self: *Self) !void {
|
||||||
self.wl_server = try wl.Server.create();
|
self.wl_server = try wl.Server.create();
|
||||||
@ -115,6 +117,7 @@ pub fn init(self: *Self) !void {
|
|||||||
try self.input_manager.init(self);
|
try self.input_manager.init(self);
|
||||||
try self.control.init(self);
|
try self.control.init(self);
|
||||||
try self.status_manager.init(self);
|
try self.status_manager.init(self);
|
||||||
|
try self.options_manager.init(self);
|
||||||
|
|
||||||
// These all free themselves when the wl_server is destroyed
|
// These all free themselves when the wl_server is destroyed
|
||||||
_ = try wlr.DataDeviceManager.create(self.wl_server);
|
_ = try wlr.DataDeviceManager.create(self.wl_server);
|
||||||
|
Loading…
Reference in New Issue
Block a user