river-options: remove protocol
This protocol involves far too much accidental complexity. The original motivating use-case was to provide a convenient way to send arbitrary data to layout clients at runtime in order to avoid layout clients needing to implement their own IPC and do this over a side-channel. Instead of implementing a quite complex but still rigid options protocol and storing this state in the compositor, instead we will simply add events to the layout protocol to support this use case. Consider the status quo event sequence: 1. send get_option_handle request (riverctl) 2. roundtrip waiting for first event (riverctl) 3. send set_foo_value request (riverctl) 4. receive set_foo_value request (river) 5. send foo_value event to all current handles (river) 6. receive foo_value event (rivertile) 7. send parameters_changed request (rivertile) 8. receive parameters_changed request (river) 9. send layout_demand (river) And compare with the event sequence after the proposed change: 1. send set_foo_value request (riverctl) 2. receive set_foo_value request (river) 3. send set_foo_value event (river) 4. send layout_demand (river) This requires *much* less back and forth between the server and clients and is clearly much simpler.
This commit is contained in:
184
river/Option.zig
184
river/Option.zig
@ -1,184 +0,0 @@
|
||||
// This file is part of river, a dynamic tiling wayland compositor.
|
||||
//
|
||||
// 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
|
||||
// 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 OutputOption = @import("OutputOption.zig");
|
||||
|
||||
const log = std.log.scoped(.river_options);
|
||||
|
||||
pub const Value = union(enum) {
|
||||
int: i32,
|
||||
uint: u32,
|
||||
fixed: wl.Fixed,
|
||||
string: ?[*:0]const u8,
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(value: *Value) void {
|
||||
if (value.* == .string) if (value.string) |s| util.gpa.free(mem.span(s));
|
||||
}
|
||||
};
|
||||
|
||||
options_manager: *OptionsManager,
|
||||
link: wl.list.Link = undefined,
|
||||
|
||||
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(*Value),
|
||||
} = 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, key: [*:0]const u8, value: Value) !void {
|
||||
const self = try util.gpa.create(Self);
|
||||
errdefer util.gpa.destroy(self);
|
||||
|
||||
var owned_value = try value.dupe();
|
||||
errdefer owned_value.deinit();
|
||||
|
||||
self.* = .{
|
||||
.options_manager = options_manager,
|
||||
.key = try util.gpa.dupeZ(u8, mem.span(key)),
|
||||
.value = owned_value,
|
||||
};
|
||||
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);
|
||||
}
|
||||
|
||||
pub fn destroy(self: *Self) void {
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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 {
|
||||
if (meta.activeTag(value) != meta.activeTag(self.value)) return error.TypeMismatch;
|
||||
|
||||
self.value.deinit();
|
||||
self.value = try value.dupe();
|
||||
|
||||
{
|
||||
var it = self.handles.iterator(.forward);
|
||||
while (it.next()) |handle| self.sendValue(handle);
|
||||
}
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
pub fn sendValue(self: Self, handle: *river.OptionHandleV2) void {
|
||||
switch (self.value) {
|
||||
.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, 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: *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();
|
||||
}
|
@ -1,188 +0,0 @@
|
||||
// This file is part of river, a dynamic tiling wayland compositor.
|
||||
//
|
||||
// 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
|
||||
// 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 wayland = @import("wayland");
|
||||
const wl = wayland.server.wl;
|
||||
const river = wayland.server.river;
|
||||
|
||||
const wlr = @import("wlroots");
|
||||
|
||||
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),
|
||||
|
||||
options: wl.list.Head(Option, "link") = undefined,
|
||||
|
||||
pub fn init(self: *Self, server: *Server) !void {
|
||||
self.* = .{
|
||||
.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 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.getOutputOption(output)) |output_option| output_option.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 = 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: *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
|
||||
const wlr_output = wlr.Output.fromWlOutput(wl_output) orelse return;
|
||||
break :blk @intToPtr(*Output, wlr_output.data);
|
||||
} else null;
|
||||
|
||||
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 = river.OptionHandleV2.create(
|
||||
options_manager.getClient(),
|
||||
options_manager.getVersion(),
|
||||
req.handle,
|
||||
) catch {
|
||||
options_manager.getClient().postNoMemory();
|
||||
return;
|
||||
};
|
||||
|
||||
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",
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
@ -38,8 +38,6 @@ const View = @import("View.zig");
|
||||
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
|
||||
@ -150,10 +148,6 @@ pub fn init(self: *Self, root: *Root, wlr_output: *wlr.Output) !void {
|
||||
.height = effective_resolution.height,
|
||||
};
|
||||
|
||||
const options_manager = &root.server.options_manager;
|
||||
try options_manager.createOutputOptions(self);
|
||||
errdefer options_manager.destroyOutputOptions(self);
|
||||
|
||||
self.setTitle();
|
||||
}
|
||||
}
|
||||
@ -428,8 +422,6 @@ 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.destroyOutputOptions(self);
|
||||
|
||||
// Remove the destroyed output from root if it wasn't already removed
|
||||
root.removeOutput(self);
|
||||
|
||||
|
@ -1,145 +0,0 @@
|
||||
// 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();
|
||||
}
|
@ -26,7 +26,6 @@ const c = @import("c.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");
|
||||
@ -66,7 +65,6 @@ root: Root,
|
||||
config: Config,
|
||||
control: Control,
|
||||
status_manager: StatusManager,
|
||||
options_manager: OptionsManager,
|
||||
layout_manager: LayoutManager,
|
||||
|
||||
pub fn init(self: *Self) !void {
|
||||
@ -117,7 +115,6 @@ pub fn init(self: *Self) !void {
|
||||
try self.decoration_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