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:
@ -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
|
||||
@ -14,15 +14,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/>.
|
||||
//
|
||||
|
||||
//
|
||||
// This is an implementation of the default "tiled" layout of dwm and the
|
||||
// 3 other orientations thereof. This code is written with the left
|
||||
// orientation in mind and then the input/output values are adjusted to apply
|
||||
// the necessary transformations to derive the other 3.
|
||||
// 3 other orientations thereof. This code is written for the main stack
|
||||
// to the left and then the input/output values are adjusted to apply
|
||||
// the necessary transformations to derive the other orientations.
|
||||
//
|
||||
// With 4 views and one main, the left layout looks something like this:
|
||||
// With 4 views and one main on the left, the layout looks something like this:
|
||||
//
|
||||
// +-----------------------+------------+
|
||||
// | | |
|
||||
@ -37,229 +35,164 @@
|
||||
// | | |
|
||||
// | | |
|
||||
// +-----------------------+------------+
|
||||
//
|
||||
|
||||
const std = @import("std");
|
||||
const mem = std.mem;
|
||||
const assert = std.debug.assert;
|
||||
|
||||
const wayland = @import("wayland");
|
||||
const wl = wayland.client.wl;
|
||||
const zriver = wayland.client.zriver;
|
||||
const river = wayland.client.river;
|
||||
|
||||
const Location = enum {
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
left,
|
||||
};
|
||||
|
||||
const default_main_location: Location = .left;
|
||||
const default_main_count = 1;
|
||||
const default_main_factor = 0.6;
|
||||
const default_view_padding = 6;
|
||||
const default_outer_padding = 6;
|
||||
|
||||
/// We don't free resources on exit, only when output globals are removed.
|
||||
const gpa = std.heap.c_allocator;
|
||||
|
||||
const Context = struct {
|
||||
running: bool = true,
|
||||
initialized: bool = false,
|
||||
layout_manager: ?*river.LayoutManagerV1 = null,
|
||||
options_manager: ?*zriver.OptionsManagerV1 = null,
|
||||
options_manager: ?*river.OptionsManagerV2 = null,
|
||||
outputs: std.TailQueue(Output) = .{},
|
||||
|
||||
pub fn addOutput(self: *Context, registry: *wl.Registry, name: u32) !void {
|
||||
const output = try registry.bind(name, wl.Output, 3);
|
||||
fn addOutput(context: *Context, registry: *wl.Registry, name: u32) !void {
|
||||
const wl_output = try registry.bind(name, wl.Output, 3);
|
||||
errdefer wl_output.release();
|
||||
const node = try gpa.create(std.TailQueue(Output).Node);
|
||||
node.data.init(self, output);
|
||||
self.outputs.append(node);
|
||||
}
|
||||
|
||||
pub fn destroyAllOutputs(self: *Context) void {
|
||||
while (self.outputs.pop()) |node| {
|
||||
node.data.deinit();
|
||||
gpa.destroy(node);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn configureAllOutputs(self: *Context) void {
|
||||
var it = self.outputs.first;
|
||||
while (it) |node| : (it = node.next) {
|
||||
node.data.configure(self);
|
||||
}
|
||||
errdefer gpa.destroy(node);
|
||||
try node.data.init(context, wl_output, name);
|
||||
context.outputs.append(node);
|
||||
}
|
||||
};
|
||||
|
||||
const Option = struct {
|
||||
pub const Value = union(enum) {
|
||||
unset: void,
|
||||
double: f64,
|
||||
uint: u32,
|
||||
};
|
||||
fn Option(comptime key: [:0]const u8, comptime T: type, comptime default: T) type {
|
||||
return struct {
|
||||
const Self = @This();
|
||||
output: *Output,
|
||||
handle: *river.OptionHandleV2,
|
||||
value: T = default,
|
||||
|
||||
handle: ?*zriver.OptionHandleV1 = null,
|
||||
value: Value = .unset,
|
||||
output: *Output = undefined,
|
||||
fn init(option: *Self, context: *Context, output: *Output) !void {
|
||||
option.* = .{
|
||||
.output = output,
|
||||
.handle = try context.options_manager.?.getOptionHandle(key, output.wl_output),
|
||||
};
|
||||
option.handle.setListener(*Self, optionListener, option) catch unreachable;
|
||||
}
|
||||
|
||||
pub fn init(self: *Option, output: *Output, comptime key: [*:0]const u8, initial: Value) !void {
|
||||
self.* = .{
|
||||
.value = initial,
|
||||
.output = output,
|
||||
.handle = try output.context.options_manager.?.getOptionHandle(
|
||||
key,
|
||||
output.output,
|
||||
),
|
||||
};
|
||||
self.handle.?.setListener(*Option, optionListener, self) catch |err| {
|
||||
self.handle.?.destroy();
|
||||
self.handle = null;
|
||||
return err;
|
||||
};
|
||||
}
|
||||
fn deinit(option: *Self) void {
|
||||
option.handle.destroy();
|
||||
option.* = undefined;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Option) void {
|
||||
if (self.handle) |handle| handle.destroy();
|
||||
}
|
||||
|
||||
fn optionListener(handle: *zriver.OptionHandleV1, event: zriver.OptionHandleV1.Event, self: *Option) void {
|
||||
switch (event) {
|
||||
.unset => switch (self.value) {
|
||||
.uint => handle.setUintValue(self.value.uint),
|
||||
.double => handle.setFixedValue(wl.Fixed.fromDouble(self.value.double)),
|
||||
fn optionListener(handle: *river.OptionHandleV2, event: river.OptionHandleV2.Event, option: *Self) void {
|
||||
const prev_value = option.value;
|
||||
assert(event != .undeclared); // We declare all options used in main()
|
||||
switch (T) {
|
||||
u32 => switch (event) {
|
||||
.uint_value => |ev| option.value = ev.value,
|
||||
else => std.log.err("expected value of uint type for " ++ key ++
|
||||
" option, falling back to default", .{}),
|
||||
},
|
||||
f64 => switch (event) {
|
||||
.fixed_value => |ev| option.value = ev.value.toDouble(),
|
||||
else => std.log.err("expected value of fixed type for " ++ key ++
|
||||
" option, falling back to default", .{}),
|
||||
},
|
||||
Location => switch (event) {
|
||||
.string_value => |ev| if (ev.value) |value| {
|
||||
if (std.meta.stringToEnum(Location, mem.span(value))) |location| {
|
||||
option.value = location;
|
||||
} else {
|
||||
std.log.err(
|
||||
\\invalid main_location "{s}", must be "top", "bottom", "left", or "right"
|
||||
, .{value});
|
||||
}
|
||||
},
|
||||
else => std.log.err("expected value of string type for " ++ key ++
|
||||
" option, falling back to default", .{}),
|
||||
},
|
||||
else => unreachable,
|
||||
},
|
||||
.int_value => {},
|
||||
.uint_value => |data| self.value = .{ .uint = data.value },
|
||||
.fixed_value => |data| self.value = .{ .double = data.value.toDouble() },
|
||||
.string_value => {},
|
||||
}
|
||||
if (option.value != prev_value) option.output.layout.parametersChanged();
|
||||
}
|
||||
if (self.output.top.layout) |layout| layout.parametersChanged();
|
||||
if (self.output.right.layout) |layout| layout.parametersChanged();
|
||||
if (self.output.bottom.layout) |layout| layout.parametersChanged();
|
||||
if (self.output.left.layout) |layout| layout.parametersChanged();
|
||||
}
|
||||
|
||||
pub fn getValueOrElse(self: *Option, comptime T: type, comptime otherwise: T) T {
|
||||
switch (T) {
|
||||
u32 => return if (self.value == .uint) self.value.uint else otherwise,
|
||||
f64 => return if (self.value == .double) self.value.double else otherwise,
|
||||
else => @compileError("Unsupported type for Option.getValueOrElse()"),
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const Output = struct {
|
||||
context: *Context,
|
||||
output: *wl.Output,
|
||||
wl_output: *wl.Output,
|
||||
name: u32,
|
||||
|
||||
top: Layout = undefined,
|
||||
right: Layout = undefined,
|
||||
bottom: Layout = undefined,
|
||||
left: Layout = undefined,
|
||||
main_location: Option("main_location", Location, default_main_location) = undefined,
|
||||
main_count: Option("main_count", u32, default_main_count) = undefined,
|
||||
main_factor: Option("main_factor", f64, default_main_factor) = undefined,
|
||||
view_padding: Option("view_padding", u32, default_view_padding) = undefined,
|
||||
outer_padding: Option("outer_padding", u32, default_outer_padding) = undefined,
|
||||
|
||||
main_amount: Option = .{},
|
||||
main_factor: Option = .{},
|
||||
view_padding: Option = .{},
|
||||
outer_padding: Option = .{},
|
||||
layout: *river.LayoutV1 = undefined,
|
||||
|
||||
configured: bool = false,
|
||||
|
||||
pub fn init(self: *Output, context: *Context, wl_output: *wl.Output) void {
|
||||
self.* = .{
|
||||
.output = wl_output,
|
||||
.context = context,
|
||||
};
|
||||
self.configure(context);
|
||||
fn init(output: *Output, context: *Context, wl_output: *wl.Output, name: u32) !void {
|
||||
output.* = .{ .wl_output = wl_output, .name = name };
|
||||
if (context.initialized) try output.initOptionsAndLayout(context);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Output) void {
|
||||
self.output.release();
|
||||
fn initOptionsAndLayout(output: *Output, context: *Context) !void {
|
||||
assert(context.initialized);
|
||||
try output.main_location.init(context, output);
|
||||
errdefer output.main_location.deinit();
|
||||
try output.main_count.init(context, output);
|
||||
errdefer output.main_count.deinit();
|
||||
try output.main_factor.init(context, output);
|
||||
errdefer output.main_factor.deinit();
|
||||
try output.view_padding.init(context, output);
|
||||
errdefer output.view_padding.deinit();
|
||||
try output.outer_padding.init(context, output);
|
||||
errdefer output.outer_padding.deinit();
|
||||
|
||||
if (self.configured) {
|
||||
self.top.deinit();
|
||||
self.right.deinit();
|
||||
self.bottom.deinit();
|
||||
self.left.deinit();
|
||||
|
||||
self.main_amount.deinit();
|
||||
self.main_factor.deinit();
|
||||
self.view_padding.deinit();
|
||||
self.outer_padding.deinit();
|
||||
}
|
||||
output.layout = try context.layout_manager.?.getLayout(output.wl_output, "rivertile");
|
||||
output.layout.setListener(*Output, layoutListener, output) catch unreachable;
|
||||
}
|
||||
|
||||
pub fn configure(self: *Output, context: *Context) void {
|
||||
if (self.configured) return;
|
||||
if (context.layout_manager == null) return;
|
||||
if (context.options_manager == null) return;
|
||||
fn deinit(output: *Output) void {
|
||||
output.wl_output.release();
|
||||
|
||||
self.configured = true;
|
||||
output.main_count.deinit();
|
||||
output.main_factor.deinit();
|
||||
output.view_padding.deinit();
|
||||
output.outer_padding.deinit();
|
||||
|
||||
self.main_amount.init(self, "main_amount", .{ .uint = 1 }) catch {};
|
||||
self.main_factor.init(self, "main_factor", .{ .double = 0.6 }) catch {};
|
||||
self.view_padding.init(self, "view_padding", .{ .uint = 10 }) catch {};
|
||||
self.outer_padding.init(self, "outer_padding", .{ .uint = 10 }) catch {};
|
||||
|
||||
self.top.init(self, .top) catch {};
|
||||
self.right.init(self, .right) catch {};
|
||||
self.bottom.init(self, .bottom) catch {};
|
||||
self.left.init(self, .left) catch {};
|
||||
}
|
||||
};
|
||||
|
||||
const Layout = struct {
|
||||
output: *Output,
|
||||
layout: ?*river.LayoutV1,
|
||||
orientation: Orientation,
|
||||
|
||||
const Orientation = enum {
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
left,
|
||||
};
|
||||
|
||||
pub fn init(self: *Layout, output: *Output, orientation: Orientation) !void {
|
||||
self.output = output;
|
||||
self.orientation = orientation;
|
||||
self.layout = try output.context.layout_manager.?.getLayout(
|
||||
self.output.output,
|
||||
self.getNamespace(),
|
||||
);
|
||||
self.layout.?.setListener(*Layout, layoutListener, self) catch |err| {
|
||||
self.layout.?.destroy();
|
||||
self.layout = null;
|
||||
return err;
|
||||
};
|
||||
output.layout.destroy();
|
||||
}
|
||||
|
||||
fn getNamespace(self: *Layout) [*:0]const u8 {
|
||||
return switch (self.orientation) {
|
||||
.top => "tile-top",
|
||||
.right => "tile-right",
|
||||
.bottom => "tile-bottom",
|
||||
.left => "tile-left",
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Layout) void {
|
||||
if (self.layout) |layout| {
|
||||
layout.destroy();
|
||||
self.layout = null;
|
||||
}
|
||||
}
|
||||
|
||||
fn layoutListener(layout: *river.LayoutV1, event: river.LayoutV1.Event, self: *Layout) void {
|
||||
fn layoutListener(layout: *river.LayoutV1, event: river.LayoutV1.Event, output: *Output) void {
|
||||
switch (event) {
|
||||
.namespace_in_use => {
|
||||
std.debug.warn("{}: Namespace already in use.\n", .{self.getNamespace()});
|
||||
self.deinit();
|
||||
},
|
||||
.namespace_in_use => fatal("namespace 'rivertile' already in use.", .{}),
|
||||
|
||||
.layout_demand => |data| {
|
||||
const main_amount = self.output.main_amount.getValueOrElse(u32, 1);
|
||||
const main_factor = std.math.clamp(self.output.main_factor.getValueOrElse(f64, 0.6), 0.1, 0.9);
|
||||
const view_padding = self.output.view_padding.getValueOrElse(u32, 0);
|
||||
const outer_padding = self.output.outer_padding.getValueOrElse(u32, 0);
|
||||
|
||||
const secondary_count = if (data.view_count > main_amount)
|
||||
data.view_count - main_amount
|
||||
.layout_demand => |ev| {
|
||||
const secondary_count = if (ev.view_count > output.main_count.value)
|
||||
ev.view_count - output.main_count.value
|
||||
else
|
||||
0;
|
||||
|
||||
const usable_width = if (self.orientation == .left or self.orientation == .right)
|
||||
data.usable_width - (2 * outer_padding)
|
||||
else
|
||||
data.usable_height - (2 * outer_padding);
|
||||
const usable_height = if (self.orientation == .left or self.orientation == .right)
|
||||
data.usable_height - (2 * outer_padding)
|
||||
else
|
||||
data.usable_width - (2 * outer_padding);
|
||||
const usable_width = switch (output.main_location.value) {
|
||||
.left, .right => ev.usable_width - (2 * output.outer_padding.value),
|
||||
.top, .bottom => ev.usable_height - (2 * output.outer_padding.value),
|
||||
};
|
||||
const usable_height = switch (output.main_location.value) {
|
||||
.left, .right => ev.usable_height - (2 * output.outer_padding.value),
|
||||
.top, .bottom => ev.usable_width - (2 * output.outer_padding.value),
|
||||
};
|
||||
|
||||
// to make things pixel-perfect, we make the first main and first secondary
|
||||
// view slightly larger if the height is not evenly divisible
|
||||
@ -271,18 +204,18 @@ const Layout = struct {
|
||||
var secondary_height: u32 = undefined;
|
||||
var secondary_height_rem: u32 = undefined;
|
||||
|
||||
if (main_amount > 0 and secondary_count > 0) {
|
||||
main_width = @floatToInt(u32, main_factor * @intToFloat(f64, usable_width));
|
||||
main_height = usable_height / main_amount;
|
||||
main_height_rem = usable_height % main_amount;
|
||||
if (output.main_count.value > 0 and secondary_count > 0) {
|
||||
main_width = @floatToInt(u32, output.main_factor.value * @intToFloat(f64, usable_width));
|
||||
main_height = usable_height / output.main_count.value;
|
||||
main_height_rem = usable_height % output.main_count.value;
|
||||
|
||||
secondary_width = usable_width - main_width;
|
||||
secondary_height = usable_height / secondary_count;
|
||||
secondary_height_rem = usable_height % secondary_count;
|
||||
} else if (main_amount > 0) {
|
||||
} else if (output.main_count.value > 0) {
|
||||
main_width = usable_width;
|
||||
main_height = usable_height / main_amount;
|
||||
main_height_rem = usable_height % main_amount;
|
||||
main_height = usable_height / output.main_count.value;
|
||||
main_height_rem = usable_height % output.main_count.value;
|
||||
} else if (secondary_width > 0) {
|
||||
main_width = 0;
|
||||
secondary_width = usable_width;
|
||||
@ -291,63 +224,63 @@ const Layout = struct {
|
||||
}
|
||||
|
||||
var i: u32 = 0;
|
||||
while (i < data.view_count) : (i += 1) {
|
||||
while (i < ev.view_count) : (i += 1) {
|
||||
var x: i32 = undefined;
|
||||
var y: i32 = undefined;
|
||||
var width: u32 = undefined;
|
||||
var height: u32 = undefined;
|
||||
|
||||
if (i < main_amount) {
|
||||
if (i < output.main_count.value) {
|
||||
x = 0;
|
||||
y = @intCast(i32, (i * main_height) + if (i > 0) main_height_rem else 0);
|
||||
width = main_width;
|
||||
height = main_height + if (i == 0) main_height_rem else 0;
|
||||
} else {
|
||||
x = @intCast(i32, main_width);
|
||||
y = @intCast(i32, (i - main_amount) * secondary_height +
|
||||
if (i > main_amount) secondary_height_rem else 0);
|
||||
y = @intCast(i32, (i - output.main_count.value) * secondary_height +
|
||||
if (i > output.main_count.value) secondary_height_rem else 0);
|
||||
width = secondary_width;
|
||||
height = secondary_height + if (i == main_amount) secondary_height_rem else 0;
|
||||
height = secondary_height + if (i == output.main_count.value) secondary_height_rem else 0;
|
||||
}
|
||||
|
||||
x += @intCast(i32, view_padding);
|
||||
y += @intCast(i32, view_padding);
|
||||
width -= 2 * view_padding;
|
||||
height -= 2 * view_padding;
|
||||
x += @intCast(i32, output.view_padding.value);
|
||||
y += @intCast(i32, output.view_padding.value);
|
||||
width -= 2 * output.view_padding.value;
|
||||
height -= 2 * output.view_padding.value;
|
||||
|
||||
switch (self.orientation) {
|
||||
switch (output.main_location.value) {
|
||||
.left => layout.pushViewDimensions(
|
||||
data.serial,
|
||||
x + @intCast(i32, outer_padding),
|
||||
y + @intCast(i32, outer_padding),
|
||||
ev.serial,
|
||||
x + @intCast(i32, output.outer_padding.value),
|
||||
y + @intCast(i32, output.outer_padding.value),
|
||||
width,
|
||||
height,
|
||||
),
|
||||
.right => layout.pushViewDimensions(
|
||||
data.serial,
|
||||
@intCast(i32, usable_width - width) - x + @intCast(i32, outer_padding),
|
||||
y + @intCast(i32, outer_padding),
|
||||
ev.serial,
|
||||
@intCast(i32, usable_width - width) - x + @intCast(i32, output.outer_padding.value),
|
||||
y + @intCast(i32, output.outer_padding.value),
|
||||
width,
|
||||
height,
|
||||
),
|
||||
.top => layout.pushViewDimensions(
|
||||
data.serial,
|
||||
y + @intCast(i32, outer_padding),
|
||||
x + @intCast(i32, outer_padding),
|
||||
ev.serial,
|
||||
y + @intCast(i32, output.outer_padding.value),
|
||||
x + @intCast(i32, output.outer_padding.value),
|
||||
height,
|
||||
width,
|
||||
),
|
||||
.bottom => layout.pushViewDimensions(
|
||||
data.serial,
|
||||
y + @intCast(i32, outer_padding),
|
||||
@intCast(i32, usable_width - width) - x + @intCast(i32, outer_padding),
|
||||
ev.serial,
|
||||
y + @intCast(i32, output.outer_padding.value),
|
||||
@intCast(i32, usable_width - width) - x + @intCast(i32, output.outer_padding.value),
|
||||
height,
|
||||
width,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
layout.commit(data.serial);
|
||||
layout.commit(ev.serial);
|
||||
},
|
||||
|
||||
.advertise_view => {},
|
||||
@ -366,25 +299,32 @@ pub fn main() !void {
|
||||
var context: Context = .{};
|
||||
|
||||
const registry = try display.getRegistry();
|
||||
try registry.setListener(*Context, registryListener, &context);
|
||||
registry.setListener(*Context, registryListener, &context) catch unreachable;
|
||||
_ = try display.roundtrip();
|
||||
|
||||
if (context.layout_manager == null) {
|
||||
std.debug.warn("Wayland server does not support river_layout_unstable_v1.\n", .{});
|
||||
std.os.exit(1);
|
||||
fatal("wayland compositor does not support river_layout_v1.\n", .{});
|
||||
}
|
||||
|
||||
if (context.options_manager == null) {
|
||||
std.debug.warn("Wayland server does not support river_options_unstable_v1.\n", .{});
|
||||
std.os.exit(1);
|
||||
fatal("wayland compositor does not support river_options_v2.\n", .{});
|
||||
}
|
||||
|
||||
context.configureAllOutputs();
|
||||
defer context.destroyAllOutputs();
|
||||
// TODO: should be @tagName(default_main_location), https://github.com/ziglang/zig/issues/3779
|
||||
context.options_manager.?.declareStringOption("main_location", "left");
|
||||
context.options_manager.?.declareUintOption("main_count", default_main_count);
|
||||
context.options_manager.?.declareFixedOption("main_factor", wl.Fixed.fromDouble(default_main_factor));
|
||||
context.options_manager.?.declareUintOption("view_padding", default_view_padding);
|
||||
context.options_manager.?.declareUintOption("outer_padding", default_outer_padding);
|
||||
|
||||
while (context.running) {
|
||||
_ = try display.dispatch();
|
||||
context.initialized = true;
|
||||
|
||||
var it = context.outputs.first;
|
||||
while (it) |node| : (it = node.next) {
|
||||
const output = &node.data;
|
||||
try output.initOptionsAndLayout(&context);
|
||||
}
|
||||
|
||||
while (true) _ = try display.dispatch();
|
||||
}
|
||||
|
||||
fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *Context) void {
|
||||
@ -392,15 +332,28 @@ fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *
|
||||
.global => |global| {
|
||||
if (std.cstr.cmp(global.interface, river.LayoutManagerV1.getInterface().name) == 0) {
|
||||
context.layout_manager = registry.bind(global.name, river.LayoutManagerV1, 1) catch return;
|
||||
} else 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, river.OptionsManagerV2.getInterface().name) == 0) {
|
||||
context.options_manager = registry.bind(global.name, river.OptionsManagerV2, 1) catch return;
|
||||
} else if (std.cstr.cmp(global.interface, wl.Output.getInterface().name) == 0) {
|
||||
context.addOutput(registry, global.name) catch {
|
||||
std.debug.warn("Failed to bind output.\n", .{});
|
||||
context.running = false;
|
||||
};
|
||||
context.addOutput(registry, global.name) catch |err| fatal("failed to bind output: {}", .{err});
|
||||
}
|
||||
},
|
||||
.global_remove => |ev| {
|
||||
var it = context.outputs.first;
|
||||
while (it) |node| : (it = node.next) {
|
||||
const output = &node.data;
|
||||
if (output.name == ev.name) {
|
||||
context.outputs.remove(node);
|
||||
output.deinit();
|
||||
gpa.destroy(node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
.global_remove => |global| {},
|
||||
}
|
||||
}
|
||||
|
||||
fn fatal(comptime format: []const u8, args: anytype) noreturn {
|
||||
std.log.err(format, args);
|
||||
std.os.exit(1);
|
||||
}
|
||||
|
Reference in New Issue
Block a user