river-options: implement

This commit is contained in:
Isaac Freund
2021-01-15 19:54:19 +01:00
parent 875e3c325d
commit 8cbccbfb6e
6 changed files with 353 additions and 10 deletions

135
river/Option.zig Normal file
View 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
View 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);
},
}
}

View File

@ -27,6 +27,7 @@ const log = @import("log.zig");
const util = @import("util.zig");
const Config = @import("Config.zig");
const OptionsManager = @import("OptionsManager.zig");
const Control = @import("Control.zig");
const DecorationManager = @import("DecorationManager.zig");
const InputManager = @import("InputManager.zig");
@ -63,6 +64,7 @@ root: Root,
config: Config,
control: Control,
status_manager: StatusManager,
options_manager: OptionsManager,
pub fn init(self: *Self) !void {
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.control.init(self);
try self.status_manager.init(self);
try self.options_manager.init(self);
// These all free themselves when the wl_server is destroyed
_ = try wlr.DataDeviceManager.create(self.wl_server);