river-options: rework, bump to v2
Options are now all global but may be overridden per-output. If an output local value is requested but none has been set, the global value is provided instead. This makes for much better ergonomics when configuring layout related options in particular.
This commit is contained in:
@ -61,7 +61,7 @@ pub fn create(client: *wl.Client, version: u32, id: u32, output: *Output, namesp
|
||||
|
||||
// If the namespace matches that of the output, set the layout as
|
||||
// the active one of the output and arrange it.
|
||||
if (output.layout_option.value.string) |current_layout| {
|
||||
if (output.layout_option.get().string) |current_layout| {
|
||||
if (mem.eql(u8, namespace, mem.span(current_layout))) {
|
||||
output.pending.layout = &node.data;
|
||||
output.arrangeViews();
|
||||
|
125
river/Option.zig
125
river/Option.zig
@ -1,6 +1,6 @@
|
||||
// This file is part of river, a dynamic tiling wayland compositor.
|
||||
//
|
||||
// Copyright 2020 The River Developers
|
||||
// Copyright 2020-2021 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
|
||||
@ -23,28 +23,30 @@ const meta = std.meta;
|
||||
|
||||
const wayland = @import("wayland");
|
||||
const wl = wayland.server.wl;
|
||||
const zriver = wayland.server.zriver;
|
||||
const river = wayland.server.river;
|
||||
|
||||
const util = @import("util.zig");
|
||||
|
||||
const Output = @import("Output.zig");
|
||||
const OptionsManager = @import("OptionsManager.zig");
|
||||
const OutputOption = @import("OutputOption.zig");
|
||||
|
||||
const log = std.log.scoped(.river_options);
|
||||
|
||||
pub const Value = union(enum) {
|
||||
unset: void,
|
||||
int: i32,
|
||||
uint: u32,
|
||||
fixed: wl.Fixed,
|
||||
string: ?[*:0]const u8,
|
||||
|
||||
fn dupe(value: Value) !Value {
|
||||
pub fn dupe(value: Value) !Value {
|
||||
return switch (value) {
|
||||
.string => |v| Value{ .string = if (v) |s| try util.gpa.dupeZ(u8, mem.span(s)) else null },
|
||||
else => value,
|
||||
};
|
||||
}
|
||||
|
||||
fn deinit(value: *Value) void {
|
||||
pub fn deinit(value: *Value) void {
|
||||
if (value.* == .string) if (value.string) |s| util.gpa.free(mem.span(s));
|
||||
}
|
||||
};
|
||||
@ -52,19 +54,20 @@ pub const Value = union(enum) {
|
||||
options_manager: *OptionsManager,
|
||||
link: wl.list.Link = undefined,
|
||||
|
||||
output: ?*Output,
|
||||
key: [*:0]const u8,
|
||||
key: [:0]const u8,
|
||||
value: Value,
|
||||
|
||||
output_options: wl.list.Head(OutputOption, "link") = undefined,
|
||||
|
||||
event: struct {
|
||||
/// Emitted whenever the value of the option changes.
|
||||
update: wl.Signal(*Self),
|
||||
update: wl.Signal(*Value),
|
||||
} = undefined,
|
||||
|
||||
handles: wl.list.Head(zriver.OptionHandleV1, null) = undefined,
|
||||
handles: wl.list.Head(river.OptionHandleV2, null) = undefined,
|
||||
|
||||
/// Allocate a new option, duping the provided key and value
|
||||
pub fn create(options_manager: *OptionsManager, output: ?*Output, key: [*:0]const u8, value: Value) !*Self {
|
||||
pub fn create(options_manager: *OptionsManager, key: [*:0]const u8, value: Value) !void {
|
||||
const self = try util.gpa.create(Self);
|
||||
errdefer util.gpa.destroy(self);
|
||||
|
||||
@ -73,56 +76,70 @@ pub fn create(options_manager: *OptionsManager, output: ?*Output, key: [*:0]cons
|
||||
|
||||
self.* = .{
|
||||
.options_manager = options_manager,
|
||||
.output = output,
|
||||
.key = try util.gpa.dupeZ(u8, mem.span(key)),
|
||||
.value = owned_value,
|
||||
};
|
||||
self.handles.init();
|
||||
errdefer util.gpa.free(self.key);
|
||||
|
||||
self.output_options.init();
|
||||
errdefer {
|
||||
var it = self.output_options.safeIterator(.forward);
|
||||
while (it.next()) |output_option| output_option.destroy();
|
||||
}
|
||||
var it = options_manager.server.root.all_outputs.first;
|
||||
while (it) |node| : (it = node.next) try OutputOption.create(self, node.data);
|
||||
|
||||
self.event.update.init();
|
||||
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));
|
||||
{
|
||||
var it = self.handles.safeIterator(.forward);
|
||||
while (it.next()) |handle| handle.destroy();
|
||||
}
|
||||
{
|
||||
var it = self.output_options.safeIterator(.forward);
|
||||
while (it.next()) |output_option| output_option.destroy();
|
||||
}
|
||||
self.value.deinit();
|
||||
self.link.remove();
|
||||
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.
|
||||
pub fn getOutputOption(self: *Self, output: *Output) ?*OutputOption {
|
||||
var it = self.output_options.iterator(.forward);
|
||||
while (it.next()) |output_option| {
|
||||
if (output_option.output == output) return output_option;
|
||||
} else return null;
|
||||
}
|
||||
|
||||
/// 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 (meta.activeTag(value) != meta.activeTag(self.value)) return error.TypeMismatch;
|
||||
|
||||
if (switch (self.value) {
|
||||
.unset => true,
|
||||
// TODO: std.mem needs a good way to compare optional sentinel pointers
|
||||
.string => ((self.value.string == null) != (value.string == null)) or
|
||||
(self.value.string != null and value.string != null and
|
||||
std.cstr.cmp(self.value.string.?, value.string.?) != 0),
|
||||
else => !std.meta.eql(self.value, value),
|
||||
}) {
|
||||
self.value.deinit();
|
||||
self.value = try value.dupe();
|
||||
self.value.deinit();
|
||||
self.value = try value.dupe();
|
||||
|
||||
{
|
||||
var it = self.handles.iterator(.forward);
|
||||
while (it.next()) |handle| self.sendValue(handle);
|
||||
|
||||
// Call listeners, if any.
|
||||
self.event.update.emit(self);
|
||||
}
|
||||
{
|
||||
var it = self.output_options.iterator(.forward);
|
||||
while (it.next()) |output_option| {
|
||||
if (output_option.value == null) output_option.notifyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
self.event.update.emit(&self.value);
|
||||
}
|
||||
|
||||
fn sendValue(self: Self, handle: *zriver.OptionHandleV1) void {
|
||||
pub fn sendValue(self: Self, handle: *river.OptionHandleV2) void {
|
||||
switch (self.value) {
|
||||
.unset => handle.sendUnset(),
|
||||
.int => |v| handle.sendIntValue(v),
|
||||
.uint => |v| handle.sendUintValue(v),
|
||||
.fixed => |v| handle.sendFixedValue(v),
|
||||
@ -130,24 +147,38 @@ fn sendValue(self: Self, handle: *zriver.OptionHandleV1) void {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn addHandle(self: *Self, handle: *zriver.OptionHandleV1) void {
|
||||
self.handles.append(handle);
|
||||
self.sendValue(handle);
|
||||
handle.setHandler(*Self, handleRequest, handleDestroy, self);
|
||||
pub fn addHandle(self: *Self, output: ?*Output, handle: *river.OptionHandleV2) void {
|
||||
if (output) |o| {
|
||||
self.getOutputOption(o).?.addHandle(handle);
|
||||
} else {
|
||||
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 {
|
||||
fn handleRequest(handle: *river.OptionHandleV2, request: river.OptionHandleV2.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();
|
||||
.set_int_value => |req| self.set(.{ .int = req.value }) catch |err| switch (err) {
|
||||
error.TypeMismatch => handle.postError(.type_mismatch, "option is not of type int"),
|
||||
error.OutOfMemory => unreachable,
|
||||
},
|
||||
.set_uint_value => |req| self.set(.{ .uint = req.value }) catch |err| switch (err) {
|
||||
error.TypeMismatch => handle.postError(.type_mismatch, "option is not of type uint"),
|
||||
error.OutOfMemory => unreachable,
|
||||
},
|
||||
.set_fixed_value => |req| self.set(.{ .fixed = req.value }) catch |err| switch (err) {
|
||||
error.TypeMismatch => handle.postError(.type_mismatch, "option is not of type fixed"),
|
||||
error.OutOfMemory => unreachable,
|
||||
},
|
||||
.set_string_value => |req| self.set(.{ .string = req.value }) catch |err| switch (err) {
|
||||
error.TypeMismatch => handle.postError(.type_mismatch, "option is not of type string"),
|
||||
error.OutOfMemory => handle.getClient().postNoMemory(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn handleDestroy(handle: *zriver.OptionHandleV1, self: *Self) void {
|
||||
fn handleDestroy(handle: *river.OptionHandleV2, self: *Self) void {
|
||||
handle.getLink().remove();
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
// This file is part of river, a dynamic tiling wayland compositor.
|
||||
//
|
||||
// Copyright 2020 The River Developers
|
||||
// Copyright 2020-2021 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
|
||||
@ -18,10 +18,11 @@
|
||||
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 river = wayland.server.river;
|
||||
|
||||
const wlr = @import("wlroots");
|
||||
|
||||
@ -29,8 +30,12 @@ const util = @import("util.zig");
|
||||
|
||||
const Option = @import("Option.zig");
|
||||
const Output = @import("Output.zig");
|
||||
const OutputOption = @import("OutputOption.zig");
|
||||
const Server = @import("Server.zig");
|
||||
|
||||
const log = std.log.scoped(.river_options);
|
||||
|
||||
server: *Server,
|
||||
global: *wl.Global,
|
||||
server_destroy: wl.Listener(*wl.Server) = wl.Listener(*wl.Server).init(handleServerDestroy),
|
||||
|
||||
@ -38,16 +43,25 @@ 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),
|
||||
.server = server,
|
||||
.global = try wl.Global.create(server.wl_server, river.OptionsManagerV2, 1, *Self, self, bind),
|
||||
};
|
||||
self.options.init();
|
||||
server.wl_server.addDestroyListener(&self.server_destroy);
|
||||
|
||||
try Option.create(self, "layout", .{ .string = null });
|
||||
try Option.create(self, "output_title", .{ .string = null });
|
||||
}
|
||||
|
||||
pub fn handleOutputDestroy(self: *Self, output: *Output) void {
|
||||
var it = self.options.safeIterator(.forward);
|
||||
pub fn createOutputOptions(self: *Self, output: *Output) !void {
|
||||
var it = self.options.iterator(.forward);
|
||||
while (it.next()) |option| try OutputOption.create(option, output);
|
||||
}
|
||||
|
||||
pub fn destroyOutputOptions(self: *Self, output: *Output) void {
|
||||
var it = self.options.iterator(.forward);
|
||||
while (it.next()) |option| {
|
||||
if (option.output == output) option.destroy();
|
||||
if (option.getOutputOption(output)) |output_option| output_option.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,20 +73,53 @@ fn handleServerDestroy(listener: *wl.Listener(*wl.Server), wl_server: *wl.Server
|
||||
}
|
||||
|
||||
fn bind(client: *wl.Client, self: *Self, version: u32, id: u32) callconv(.C) void {
|
||||
const options_manager = zriver.OptionsManagerV1.create(client, 1, id) catch {
|
||||
const options_manager = river.OptionsManagerV2.create(client, version, id) catch {
|
||||
client.postNoMemory();
|
||||
return;
|
||||
};
|
||||
options_manager.setHandler(*Self, handleRequest, null, self);
|
||||
}
|
||||
|
||||
pub fn getOption(self: *Self, key: [:0]const u8) ?*Option {
|
||||
var it = self.options.iterator(.forward);
|
||||
while (it.next()) |option| {
|
||||
if (mem.eql(u8, option.key, key)) return option;
|
||||
} else return null;
|
||||
}
|
||||
|
||||
fn handleRequest(
|
||||
options_manager: *zriver.OptionsManagerV1,
|
||||
request: zriver.OptionsManagerV1.Request,
|
||||
options_manager: *river.OptionsManagerV2,
|
||||
request: river.OptionsManagerV2.Request,
|
||||
self: *Self,
|
||||
) void {
|
||||
switch (request) {
|
||||
.destroy => options_manager.destroy(),
|
||||
|
||||
.declare_int_option => |req| if (self.getOption(mem.span(req.key)) == null) {
|
||||
Option.create(self, req.key, .{ .int = req.value }) catch {
|
||||
options_manager.getClient().postNoMemory();
|
||||
return;
|
||||
};
|
||||
},
|
||||
.declare_uint_option => |req| if (self.getOption(mem.span(req.key)) == null) {
|
||||
Option.create(self, req.key, .{ .uint = req.value }) catch {
|
||||
options_manager.getClient().postNoMemory();
|
||||
return;
|
||||
};
|
||||
},
|
||||
.declare_string_option => |req| if (self.getOption(mem.span(req.key)) == null) {
|
||||
Option.create(self, req.key, .{ .string = req.value }) catch {
|
||||
options_manager.getClient().postNoMemory();
|
||||
return;
|
||||
};
|
||||
},
|
||||
.declare_fixed_option => |req| if (self.getOption(mem.span(req.key)) == null) {
|
||||
Option.create(self, req.key, .{ .fixed = req.value }) catch {
|
||||
options_manager.getClient().postNoMemory();
|
||||
return;
|
||||
};
|
||||
},
|
||||
|
||||
.get_option_handle => |req| {
|
||||
const output = if (req.output) |wl_output| blk: {
|
||||
// Ignore if the wl_output is inert
|
||||
@ -80,19 +127,24 @@ fn handleRequest(
|
||||
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, .unset) catch {
|
||||
const option = self.getOption(mem.span(req.key)) orelse {
|
||||
// There is no option with the requested key. In this case
|
||||
// all we do is send an undeclared event and wait for the
|
||||
// client to destroy the resource.
|
||||
const handle = river.OptionHandleV2.create(
|
||||
options_manager.getClient(),
|
||||
options_manager.getVersion(),
|
||||
req.handle,
|
||||
) catch {
|
||||
options_manager.getClient().postNoMemory();
|
||||
return;
|
||||
};
|
||||
handle.sendUndeclared();
|
||||
handle.setHandler(*Self, undeclaredHandleRequest, null, self);
|
||||
return;
|
||||
};
|
||||
|
||||
const handle = zriver.OptionHandleV1.create(
|
||||
const handle = river.OptionHandleV2.create(
|
||||
options_manager.getClient(),
|
||||
options_manager.getVersion(),
|
||||
req.handle,
|
||||
@ -101,7 +153,36 @@ fn handleRequest(
|
||||
return;
|
||||
};
|
||||
|
||||
option.addHandle(handle);
|
||||
option.addHandle(output, handle);
|
||||
},
|
||||
|
||||
.unset_option => |req| {
|
||||
// Ignore if the wl_output is inert
|
||||
const wlr_output = wlr.Output.fromWlOutput(req.output) orelse return;
|
||||
const output = @intToPtr(*Output, wlr_output.data);
|
||||
|
||||
const option = self.getOption(mem.span(req.key)) orelse return;
|
||||
option.getOutputOption(output).?.unset();
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn undeclaredHandleRequest(
|
||||
handle: *river.OptionHandleV2,
|
||||
request: river.OptionHandleV2.Request,
|
||||
self: *Self,
|
||||
) void {
|
||||
switch (request) {
|
||||
.destroy => handle.destroy(),
|
||||
.set_int_value,
|
||||
.set_uint_value,
|
||||
.set_fixed_value,
|
||||
.set_string_value,
|
||||
=> {
|
||||
handle.postError(
|
||||
.request_while_undeclared,
|
||||
"a request other than destroy was made on a handle to an undeclared option",
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ const ViewStack = @import("view_stack.zig").ViewStack;
|
||||
const AttachMode = @import("view_stack.zig").AttachMode;
|
||||
const OutputStatus = @import("OutputStatus.zig");
|
||||
const Option = @import("Option.zig");
|
||||
const OutputOption = @import("OutputOption.zig");
|
||||
|
||||
const State = struct {
|
||||
/// A bit field of focused tags
|
||||
@ -94,11 +95,10 @@ enable: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleEnable),
|
||||
frame: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleFrame),
|
||||
mode: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleMode),
|
||||
|
||||
layout_option: *Option,
|
||||
layout_option: *OutputOption,
|
||||
|
||||
/// Listeners for options
|
||||
output_title: wl.Listener(*Option) = wl.Listener(*Option).init(handleTitleChange),
|
||||
layout_change: wl.Listener(*Option) = wl.Listener(*Option).init(handleLayoutChange),
|
||||
output_title: wl.Listener(*Option.Value) = wl.Listener(*Option.Value).init(handleTitleChange),
|
||||
layout_change: wl.Listener(*Option.Value) = wl.Listener(*Option.Value).init(handleLayoutChange),
|
||||
|
||||
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
|
||||
@ -150,24 +150,29 @@ pub fn init(self: *Self, root: *Root, wlr_output: *wlr.Output) !void {
|
||||
.width = effective_resolution.width,
|
||||
.height = effective_resolution.height,
|
||||
};
|
||||
|
||||
const options_manager = &root.server.options_manager;
|
||||
try options_manager.createOutputOptions(self);
|
||||
errdefer options_manager.destroyOutputOptions(self);
|
||||
|
||||
// Set the default title of this output
|
||||
var buf: ["river - ".len + wlr_output.name.len + 1]u8 = undefined;
|
||||
const default_title = fmt.bufPrintZ(&buf, "river - {}", .{mem.spanZ(&wlr_output.name)}) catch unreachable;
|
||||
self.setTitle(default_title);
|
||||
|
||||
const global_title_option = options_manager.getOption("output_title") orelse unreachable;
|
||||
const title_option = global_title_option.getOutputOption(self).?;
|
||||
title_option.set(.{ .string = default_title }) catch |err| switch (err) {
|
||||
error.TypeMismatch => unreachable,
|
||||
error.OutOfMemory => return err,
|
||||
};
|
||||
|
||||
const global_layout_option = options_manager.getOption("layout") orelse unreachable;
|
||||
self.layout_option = global_layout_option.getOutputOption(self).?;
|
||||
|
||||
self.layout_option.event.update.add(&self.layout_change);
|
||||
title_option.event.update.add(&self.output_title);
|
||||
}
|
||||
|
||||
// Set the default title of this output
|
||||
var buf: ["river - ".len + wlr_output.name.len + 1]u8 = undefined;
|
||||
const default_title = fmt.bufPrintZ(&buf, "river - {}", .{mem.spanZ(&wlr_output.name)}) catch unreachable;
|
||||
self.setTitle(default_title);
|
||||
|
||||
// Create all default output options
|
||||
const options_manager = &root.server.options_manager;
|
||||
self.layout_option = try Option.create(options_manager, self, "layout", .{ .string = null });
|
||||
const title_option = try Option.create(options_manager, self, "output_title", .{ .string = default_title.ptr });
|
||||
_ = try Option.create(options_manager, self, "main_amount", .{ .uint = 1 });
|
||||
_ = try Option.create(options_manager, self, "main_factor", .{ .fixed = wl.Fixed.fromDouble(0.6) });
|
||||
_ = try Option.create(options_manager, self, "view_padding", .{ .uint = 10 });
|
||||
_ = try Option.create(options_manager, self, "outer_padding", .{ .uint = 10 });
|
||||
|
||||
self.layout_option.event.update.add(&self.layout_change);
|
||||
title_option.event.update.add(&self.output_title);
|
||||
}
|
||||
|
||||
pub fn getLayer(self: *Self, layer: zwlr.LayerShellV1.Layer) *std.TailQueue(LayerSurface) {
|
||||
@ -440,7 +445,7 @@ fn handleDestroy(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) v
|
||||
|
||||
std.log.scoped(.server).debug("output '{}' destroyed", .{self.wlr_output.name});
|
||||
|
||||
root.server.options_manager.handleOutputDestroy(self);
|
||||
root.server.options_manager.destroyOutputOptions(self);
|
||||
|
||||
// Remove the destroyed output from root if it wasn't already removed
|
||||
root.removeOutput(self);
|
||||
@ -509,20 +514,21 @@ pub fn setTitle(self: *Self, title: [*:0]const u8) void {
|
||||
}
|
||||
}
|
||||
|
||||
fn handleTitleChange(listener: *wl.Listener(*Option), option: *Option) void {
|
||||
if (option.value.string) |title| option.output.?.setTitle(title);
|
||||
fn handleTitleChange(listener: *wl.Listener(*Option.Value), value: *Option.Value) void {
|
||||
const self = @fieldParentPtr(Self, "output_title", listener);
|
||||
if (value.string) |title| self.setTitle(title);
|
||||
}
|
||||
|
||||
fn handleLayoutChange(listener: *wl.Listener(*Option), option: *Option) void {
|
||||
fn handleLayoutChange(listener: *wl.Listener(*Option.Value), value: *Option.Value) void {
|
||||
const self = @fieldParentPtr(Self, "layout_change", listener);
|
||||
// The user changed the layout namespace of this output. Try to find a
|
||||
// matching layout.
|
||||
const output = option.output.?;
|
||||
output.pending.layout = if (option.value.string) |namespace| blk: {
|
||||
var layout_it = output.layouts.first;
|
||||
self.pending.layout = if (value.string) |namespace| blk: {
|
||||
var layout_it = self.layouts.first;
|
||||
break :blk while (layout_it) |node| : (layout_it = node.next) {
|
||||
if (mem.eql(u8, mem.span(namespace), node.data.namespace)) break &node.data;
|
||||
} else null;
|
||||
} else null;
|
||||
output.arrangeViews();
|
||||
output.root.startTransaction();
|
||||
self.arrangeViews();
|
||||
self.root.startTransaction();
|
||||
}
|
||||
|
145
river/OutputOption.zig
Normal file
145
river/OutputOption.zig
Normal file
@ -0,0 +1,145 @@
|
||||
// This file is part of river, a dynamic tiling wayland compositor.
|
||||
//
|
||||
// Copyright 2021 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 river = wayland.server.river;
|
||||
|
||||
const util = @import("util.zig");
|
||||
|
||||
const Output = @import("Output.zig");
|
||||
const OptionsManager = @import("OptionsManager.zig");
|
||||
const Option = @import("Option.zig");
|
||||
|
||||
const Value = Option.Value;
|
||||
|
||||
option: *Option,
|
||||
|
||||
link: wl.list.Link = undefined,
|
||||
|
||||
output: *Output,
|
||||
value: ?Value = null,
|
||||
|
||||
event: struct {
|
||||
/// Emitted whenever the value of the option changes.
|
||||
update: wl.Signal(*Value),
|
||||
} = undefined,
|
||||
|
||||
handles: wl.list.Head(river.OptionHandleV2, null) = undefined,
|
||||
|
||||
pub fn create(option: *Option, output: *Output) !void {
|
||||
const self = try util.gpa.create(Self);
|
||||
errdefer util.gpa.destroy(self);
|
||||
|
||||
self.* = .{ .option = option, .output = output };
|
||||
self.event.update.init();
|
||||
self.handles.init();
|
||||
|
||||
option.output_options.append(self);
|
||||
}
|
||||
|
||||
pub fn destroy(self: *Self) void {
|
||||
if (self.value) |*value| value.deinit();
|
||||
self.link.remove();
|
||||
util.gpa.destroy(self);
|
||||
}
|
||||
|
||||
pub fn addHandle(self: *Self, handle: *river.OptionHandleV2) void {
|
||||
self.handles.append(handle);
|
||||
self.sendValue(handle);
|
||||
handle.setHandler(*Self, handleRequest, handleDestroy, self);
|
||||
}
|
||||
|
||||
pub fn unset(self: *Self) void {
|
||||
if (self.value) |*value| value.deinit();
|
||||
self.value = null;
|
||||
|
||||
// Unsetting the output-specific value causes us to fall back to the
|
||||
// global value. Send this new value to all clients.
|
||||
var it = self.handles.iterator(.forward);
|
||||
while (it.next()) |handle| {
|
||||
self.option.sendValue(handle);
|
||||
}
|
||||
|
||||
self.event.update.emit(&self.option.value);
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
if (meta.activeTag(value) != meta.activeTag(self.option.value)) return error.TypeMismatch;
|
||||
|
||||
if (self.value) |*v| v.deinit();
|
||||
self.value = try value.dupe();
|
||||
|
||||
self.notifyChanged();
|
||||
}
|
||||
|
||||
pub fn notifyChanged(self: *Self) void {
|
||||
var it = self.handles.iterator(.forward);
|
||||
while (it.next()) |handle| self.sendValue(handle);
|
||||
self.event.update.emit(self.get());
|
||||
}
|
||||
|
||||
pub fn get(self: *Self) *Value {
|
||||
return if (self.value) |*value| value else &self.option.value;
|
||||
}
|
||||
|
||||
fn sendValue(self: Self, handle: *river.OptionHandleV2) void {
|
||||
if (self.value) |value| {
|
||||
switch (value) {
|
||||
.int => |v| handle.sendIntValue(v),
|
||||
.uint => |v| handle.sendUintValue(v),
|
||||
.fixed => |v| handle.sendFixedValue(v),
|
||||
.string => |v| handle.sendStringValue(v),
|
||||
}
|
||||
} else {
|
||||
self.option.sendValue(handle);
|
||||
}
|
||||
}
|
||||
|
||||
fn handleRequest(handle: *river.OptionHandleV2, request: river.OptionHandleV2.Request, self: *Self) void {
|
||||
switch (request) {
|
||||
.destroy => handle.destroy(),
|
||||
.set_int_value => |req| self.set(.{ .int = req.value }) catch |err| switch (err) {
|
||||
error.TypeMismatch => handle.postError(.type_mismatch, "option is not of type int"),
|
||||
error.OutOfMemory => unreachable,
|
||||
},
|
||||
.set_uint_value => |req| self.set(.{ .uint = req.value }) catch |err| switch (err) {
|
||||
error.TypeMismatch => handle.postError(.type_mismatch, "option is not of type uint"),
|
||||
error.OutOfMemory => unreachable,
|
||||
},
|
||||
.set_fixed_value => |req| self.set(.{ .fixed = req.value }) catch |err| switch (err) {
|
||||
error.TypeMismatch => handle.postError(.type_mismatch, "option is not of type fixed"),
|
||||
error.OutOfMemory => unreachable,
|
||||
},
|
||||
.set_string_value => |req| self.set(.{ .string = req.value }) catch |err| switch (err) {
|
||||
error.TypeMismatch => handle.postError(.type_mismatch, "option is not of type string"),
|
||||
error.OutOfMemory => handle.getClient().postNoMemory(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn handleDestroy(handle: *river.OptionHandleV2, self: *Self) void {
|
||||
handle.getLink().remove();
|
||||
}
|
@ -115,9 +115,9 @@ pub fn init(self: *Self) !void {
|
||||
|
||||
self.config = try Config.init();
|
||||
try self.decoration_manager.init(self);
|
||||
try self.options_manager.init(self);
|
||||
try self.root.init(self);
|
||||
// Must be called after root is initialized
|
||||
try self.options_manager.init(self);
|
||||
try self.input_manager.init(self);
|
||||
try self.control.init(self);
|
||||
try self.status_manager.init(self);
|
||||
|
Reference in New Issue
Block a user