Implement configuration for the Bar

This adds a few new options for the bar (instead of hardcoding all of
them). fonts, text_color, background_color, positoon, and margins.

Also fixed a couple of bugs when reloading the config and destroying
layer shell and wl surfaces in the wrong order.
This commit is contained in:
Ben Buhse 2026-02-16 16:07:02 -06:00
commit 5922107579
No known key found for this signature in database
GPG key ID: 7916ACFCD38FD0B4
9 changed files with 371 additions and 84 deletions

View file

@ -71,6 +71,38 @@ borders {
Colors are specified in `0xRRGGBB` or `0xRRGGBBAA` hex format.
## Bar
The bar is an optional widget that shows the time on your screen. Right now, that's it.
It is only created when a `bar` block is present in the config. All settings have
defaults, with the color based on the Catppuccin Mocha theme. An empty block can be used
to enable the widget with all defaults:
```kdl
bar {
}
```
### Bar Settings
| Setting | Type | Default | Description |
|--------------------|--------|--------------------|-----------------------------------|
| `fonts` | string | `monospace:size=14` | Comma-separated FontConfig fonts |
| `text_color` | color | `0xcdd6f4` | Text color |
| `background_color` | color | `0x1e1e2e` | Background color |
| `position` | enum | `top` | Bar position (`top` or `bottom`) |
### Margins
The `margins` child block sets pixel offsets from the anchored edge(s).
| Setting | Type | Default |
|----------|------|---------|
| `top` | i32 | `0` |
| `right` | i32 | `0` |
| `bottom` | i32 | `0` |
| `left` | i32 | `0` |
## Tag Overlay
The tag overlay is an optional widget that briefly shows your tag state when switching tags.

View file

@ -2,7 +2,6 @@
These are in rough order of my priority, though no promises I do them in this order.
- [ ] Add options to the bar
- [ ] Make a Rect struct to combine x, y, width, and height
- [ ] Support window rules (float/tags/SSD by app-id/title)
- [ ] Support overriding config location
@ -31,3 +30,4 @@ These are in rough order of my priority, though no promises I do them in this or
- [x] Implement an optional clock bar
- [x] Implement a river-tag-overlay clone
- [x] Add options to the tag overlay
- [x] Add options to the bar

View file

@ -18,7 +18,11 @@ borders {
color_focused "0x89b4fa"
color_unfocused "0x1e1e2e"
}
// Tag overlay widget — shown briefly when switching tags
// Bar widget - shows the time
bar {
position top
}
// Tag overlay widget - shown briefly when switching tags
// Remove this block to disable the overlay entirely
tag_overlay {
tag_amount 10

View file

@ -12,8 +12,10 @@ context: *Context,
/// The timezone of the computer.
timezone: zeit.timezone.TimeZone,
fonts: *fcft.Font,
font_scale: u31 = 0,
options: Options,
fcft_fonts: *fcft.Font,
font_scale: u31,
output: *Output,
@ -28,22 +30,40 @@ surfaces: ?struct {
configured: bool = false,
pub fn init(context: *Context, output: *Output) !Bar {
pub const Position = enum { top, bottom };
pub const Options = struct {
/// Comma separated list of FontConfig formatted font specifications
fonts: []const u8 = "monospace:size=14",
/// Color of text on the bar
text_color: pixman.Color = utils.parseRgbaPixmanComptime("0xcdd6f4"),
/// Background color of the bar
background_color: pixman.Color = utils.parseRgbaPixmanComptime("0x1e1e2e"),
/// Whether the bar is at the top or bottom of the screen
position: Position = .top,
/// Directional margins top, right, bottom, left, in pixels
margins: struct { top: i32 = 0, right: i32 = 0, bottom: i32 = 0, left: i32 = 0 } = .{},
};
pub fn init(context: *Context, output: *Output, options: Options) !Bar {
const timezone = try zeit.local(utils.gpa, &context.env);
errdefer timezone.deinit();
const fonts = try getFcftFonts("monospace:size=14", 1);
errdefer fonts.destroy();
const scale = output.scale;
const fcft_fonts = try getFcftFonts(options.fonts, scale);
errdefer fcft_fonts.destroy();
return .{
.context = context,
.fonts = fonts,
.options = options,
.fcft_fonts = fcft_fonts,
.font_scale = scale,
.timezone = timezone,
.output = output,
};
}
// TODO: Add config options for whether it's top or bottom
pub fn initSurface(bar: *Bar) !void {
if (bar.surfaces) |_| {
// This bar already has a surface, we can exit early
@ -51,6 +71,7 @@ pub fn initSurface(bar: *Bar) !void {
}
const context = bar.context;
const options = bar.options;
const wl_surface = try context.wl_compositor.createSurface();
errdefer wl_surface.destroy();
@ -60,7 +81,6 @@ pub fn initSurface(bar: *Bar) !void {
.getLayerSurface(wl_surface, bar.output.wl_output, .top, "beansprout-bar");
errdefer layer_surface.destroy();
// TODO: Allow clicking on tags to switch between them?
// We don't want our surface to have any input region (default is infinite)
const empty_region = try context.wl_compositor.createRegion();
defer empty_region.destroy();
@ -68,9 +88,11 @@ pub fn initSurface(bar: *Bar) !void {
// TODO: Add padding to config
const vertical_padding = 5;
const bar_height: u31 = @intCast(bar.fonts.height + 2 * vertical_padding);
const bar_height: u31 = @intCast(bar.fcft_fonts.height + 2 * vertical_padding);
layer_surface.setSize(0, bar_height);
layer_surface.setAnchor(.{ .top = true, .right = true, .left = true });
layer_surface.setAnchor(.{ .top = options.position == .top, .bottom = options.position == .bottom, .left = true, .right = true });
layer_surface.setMargin(options.margins.top, options.margins.right, options.margins.bottom, options.margins.left);
bar.surfaces = .{
.wl_surface = wl_surface,
@ -85,10 +107,9 @@ pub fn initSurface(bar: *Bar) !void {
pub fn deinit(bar: *Bar) void {
bar.configured = false;
bar.timezone.deinit();
bar.fonts.destroy();
if (bar.surfaces) |surfaces| {
surfaces.wl_surface.destroy();
surfaces.layer_surface.destroy();
surfaces.wl_surface.destroy();
bar.context.buffer_pool.surface_count -= 1;
}
}
@ -144,13 +165,14 @@ pub fn layerSurfaceListener(
/// Renders the bar and its components
pub fn render(bar: *Bar) !void {
const context = bar.context;
const options = bar.options;
const scale = bar.output.scale;
// Recreate fonts at the output's new scale
if (scale != bar.font_scale) {
bar.fonts.destroy();
bar.fonts = try getFcftFonts("monospace:size=14", scale);
bar.fcft_fonts.destroy();
bar.fcft_fonts = try getFcftFonts(bar.options.fonts, scale);
bar.font_scale = scale;
}
@ -165,8 +187,7 @@ pub fn render(bar: *Bar) !void {
const buffer = try context.buffer_pool.nextBuffer(bar.context.wl_shm, render_width, render_height);
// Fill with a solid color (e.g., dark background)
// TODO: Configure text/bg colors
const bg_color: pixman.Color = .{ .red = 0x1e1e, .green = 0x1e1e, .blue = 0x2e2e, .alpha = 0xffff };
const bg_color = options.background_color;
_ = pixman.Image.fillRectangles(
.src,
buffer.pixman_image,
@ -181,7 +202,7 @@ pub fn render(bar: *Bar) !void {
);
// Set-up text color
const text_color: pixman.Color = .{ .red = 0xffff, .green = 0xffff, .blue = 0xffff, .alpha = 0xffff };
const text_color = options.text_color;
const color = pixman.Image.createSolidFill(&text_color) orelse return error.FailedToCreatePixmanImage;
defer _ = color.unref();
@ -214,7 +235,7 @@ pub fn render(bar: *Bar) !void {
const text_width = try bar.textWidth(codepoints);
var x: i32 = @divFloor(buffer.width - text_width, 2);
const y: i32 = @divFloor(buffer.height - bar.fonts.height, 2);
const y: i32 = @divFloor(buffer.height - bar.fcft_fonts.height, 2);
// Actually render the unicode codepoints
try bar.renderChars(codepoints, buffer, &x, y, color);
@ -233,11 +254,11 @@ pub fn render(bar: *Bar) !void {
fn textWidth(bar: *Bar, text: []const u32) !i32 {
var width: i32 = 0;
for (text, 0..) |cp, i| {
const glyph = try bar.fonts.rasterizeCharUtf32(cp, .default);
const glyph = try bar.fcft_fonts.rasterizeCharUtf32(cp, .default);
width += glyph.advance.x;
if (i > 0) {
var x_kern: c_long = 0;
if (bar.fonts.kerning(text[i - 1], cp, &x_kern, null)) {
if (bar.fcft_fonts.kerning(text[i - 1], cp, &x_kern, null)) {
width += @intCast(x_kern);
}
}
@ -261,12 +282,12 @@ fn renderChars(
var i: usize = 0;
while (i < text.len) : (i += 1) {
glyphs[i] = try bar.fonts.rasterizeCharUtf32(text[i], .default);
glyphs[i] = try bar.fcft_fonts.rasterizeCharUtf32(text[i], .default);
kerns[i] = 0;
if (i > 0) {
var x_kern: c_long = 0;
if (bar.fonts.kerning(text[i - 1], text[i], &x_kern, null)) {
if (bar.fcft_fonts.kerning(text[i - 1], text[i], &x_kern, null)) {
kerns[i] = x_kern;
}
}
@ -303,7 +324,7 @@ fn renderGlyphs(
0,
0,
x.* + @as(i32, @intCast(glyphs[i].x)),
y + bar.fonts.ascent - @as(i32, @intCast(glyphs[i].y)),
y + bar.fcft_fonts.ascent - @as(i32, @intCast(glyphs[i].y)),
glyphs[i].width,
glyphs[i].height,
);
@ -320,7 +341,7 @@ fn renderGlyphs(
0,
0,
x.* + @as(i32, @intCast(glyphs[i].x)),
y + bar.fonts.ascent - @as(i32, @intCast(glyphs[i].y)),
y + bar.fcft_fonts.ascent - @as(i32, @intCast(glyphs[i].y)),
glyphs[i].width,
glyphs[i].height,
);

View file

@ -32,7 +32,9 @@ pointer_warp_on_focus_change: bool = true,
wallpaper_image_path: ?[]const u8 = null,
/// Tag overlay configuration. If null, no overlay is created.
tag_overlay: ?TagOverlayConfig = null,
tag_overlay_config: ?TagOverlayConfig = null,
/// Bar configuration. If null, no bar is created.
bar_config: ?BarConfig = null,
/// Tag bind entries parsed from config (tag_bind nodes in keybinds block)
tag_binds: std.ArrayList(Keybind) = .{},
@ -44,6 +46,7 @@ input_configs: std.ArrayList(InputConfig) = .{},
pub const Keybind = keybind_helper.Keybind;
pub const PointerBind = pointer_bind_helper.PointerBind;
pub const BarConfig = bar_helper.BarConfig;
pub const TagOverlayConfig = tag_overlay_helper.TagOverlayConfig;
pub const InputConfig = input_helper.InputConfig;
@ -60,10 +63,11 @@ const NodeName = enum {
pointer_warp_on_focus_change,
wallpaper_image_path,
// Sections with child blocks
bar,
borders,
input,
keybinds,
pointer_binds,
input,
tag_overlay,
};
@ -102,6 +106,9 @@ pub fn create() !*Config {
if (ic.name) |name| utils.gpa.free(name);
}
config.input_configs.clearAndFree(utils.gpa);
if (config.bar_config) |bc| {
if (bc.fonts) |fonts| utils.gpa.free(fonts);
}
if (config.wallpaper_image_path) |path| {
utils.gpa.free(path);
}
@ -129,6 +136,9 @@ pub fn destroy(config: *Config) void {
if (ic.name) |name| utils.gpa.free(name);
}
config.input_configs.deinit(utils.gpa);
if (config.bar_config) |bc| {
if (bc.fonts) |fonts| utils.gpa.free(fonts);
}
if (config.wallpaper_image_path) |path| {
utils.gpa.free(path);
}
@ -231,15 +241,8 @@ fn load(config: *Config, reader: *Io.Reader) !void {
};
logDebugSettingNode(name, path_str);
},
.borders => {
next_child_block = .borders;
},
.keybinds => {
next_child_block = .keybinds;
},
.pointer_binds => {
next_child_block = .pointer_binds;
},
.bar => next_child_block = .bar,
.borders => next_child_block = .borders,
.input => {
pending_input_name = if (node.prop(&parser, "name")) |n|
try utils.gpa.dupe(u8, utils.stripQuotes(n))
@ -247,6 +250,12 @@ fn load(config: *Config, reader: *Io.Reader) !void {
null;
next_child_block = .input;
},
.keybinds => {
next_child_block = .keybinds;
},
.pointer_binds => {
next_child_block = .pointer_binds;
},
.tag_overlay => {
next_child_block = .tag_overlay;
},
@ -258,6 +267,7 @@ fn load(config: *Config, reader: *Io.Reader) !void {
.child_block_begin => {
if (next_child_block) |child_block| {
switch (child_block) {
.bar => try bar_helper.load(config, &parser, hostname),
.borders => try borders_helper.load(config, &parser, hostname),
.keybinds => try keybind_helper.load(config, &parser, hostname),
.pointer_binds => try pointer_bind_helper.load(config, &parser, hostname),
@ -311,6 +321,7 @@ const utils = @import("utils.zig");
const RiverColor = utils.RiverColor;
const XkbBindings = @import("XkbBindings.zig");
const bar_helper = @import("config/bar.zig");
const borders_helper = @import("config/borders.zig");
const input_helper = @import("config/input.zig");
const keybind_helper = @import("config/keybinds.zig");

View file

@ -78,7 +78,7 @@ pub fn create(options: Options) !*Context {
const env = try process.getEnvMap(utils.gpa);
errdefer env.deinit();
const tag_overlay_timer_fd: ?posix.fd_t = if (options.config.tag_overlay) |_|
const tag_overlay_timer_fd: ?posix.fd_t = if (options.config.tag_overlay_config) |_|
posix.timerfd_create(.MONOTONIC, .{ .CLOEXEC = true }) catch |e| blk: {
log.err("Failed to create tag overlay timer: {}", .{e});
break :blk null;
@ -133,6 +133,10 @@ pub fn manage(context: *Context) void {
binding.destroy();
}
// Capture old config state before destroying
const had_overlay = context.config.tag_overlay_config != null;
const had_bar = context.config.bar_config != null;
// Check if wallpaper path changed before destroying old config
const wallpaper_changed = !pathsEqual(
context.config.wallpaper_image_path,
@ -150,8 +154,7 @@ pub fn manage(context: *Context) void {
}
// Handle tag overlay config changes
const had_overlay = context.config.tag_overlay != null;
const has_overlay = new_config.tag_overlay != null;
const has_overlay = new_config.tag_overlay_config != null;
if (!had_overlay and has_overlay) {
// Create timerfd for newly enabled tag overlay
@ -176,7 +179,7 @@ pub fn manage(context: *Context) void {
}
// Create new overlay if configured
// Create new overlay struct if configured (surfaces created on-demand)
if (new_config.tag_overlay) |tag_overlay_config| {
if (new_config.tag_overlay_config) |tag_overlay_config| {
output.tag_overlay = TagOverlay.init(context, output, tag_overlay_config.toTagOverlayOptions()) catch |e| {
log.err("Failed to create tag overlay: {}", .{e});
continue;
@ -185,6 +188,32 @@ pub fn manage(context: *Context) void {
}
}
// Recreate or destroy bars on all outputs
const has_bar = new_config.bar_config != null;
if (had_bar or has_bar) {
var out_it = context.wm.outputs.iterator(.forward);
while (out_it.next()) |output| {
// Destroy existing bar
if (output.bar) |*bar| {
bar.deinit();
output.bar = null;
}
// Create new bar if configured
if (new_config.bar_config) |bar_config| {
output.bar = Bar.init(context, output, bar_config.toBarOptions()) catch |e| {
log.err("Failed to create bar: {}", .{e});
continue;
};
// If the output already has a wl_output, init the surface immediately
if (output.wl_output != null) {
output.bar.?.initSurface() catch |e| {
log.err("Failed to init bar surface: {}", .{e});
};
}
}
}
}
if (wallpaper_changed) {
if (context.wallpaper_image) |img| img.destroy();
context.wallpaper_image = loadWallpaperImage(new_config);
@ -239,6 +268,7 @@ const wl = wayland.client.wl;
const zwlr = wayland.client.zwlr;
const utils = @import("utils.zig");
const Bar = @import("Bar.zig");
const BufferPool = @import("BufferPool.zig");
const Config = @import("Config.zig");
const InputManager = @import("InputManager.zig");

View file

@ -92,13 +92,15 @@ pub fn create(context: *Context, river_output_v1: *river.OutputV1) !*Output {
var output = try utils.gpa.create(Output);
errdefer utils.gpa.destroy(output);
var bar = Bar.init(context, output) catch |e| blk: {
var bar = if (context.config.bar_config) |bar_config| blk: {
break :blk Bar.init(context, output, bar_config.toBarOptions()) catch |e| {
log.err("Failed to create a bar: {}", .{e});
break :blk null;
};
} else null;
errdefer if (bar) |*b| b.deinit();
var tag_overlay = if (context.config.tag_overlay) |tag_overlay_config| blk: {
var tag_overlay = if (context.config.tag_overlay_config) |tag_overlay_config| blk: {
break :blk TagOverlay.init(context, output, tag_overlay_config.toTagOverlayOptions()) catch |e| {
log.err("Failed to create a tag overlay: {}", .{e});
break :blk null;
@ -377,8 +379,8 @@ pub fn initWallpaperLayerSurface(output: *Output) !void {
pub fn deinitWallpaperLayerSurface(output: *Output) void {
if (output.surfaces) |surfaces| {
surfaces.wl_surface.destroy();
surfaces.layer_surface.destroy();
surfaces.wl_surface.destroy();
output.context.buffer_pool.surface_count -= 1;
}

186
src/config/bar.zig Normal file
View file

@ -0,0 +1,186 @@
// SPDX-FileCopyrightText: 2026 Ben Buhse <me@benbuhse.email>
//
// SPDX-License-Identifier: GPL-3.0-only
pub const NodeName = enum {
fonts,
text_color,
background_color,
position,
margins,
};
pub const MarginsNodeName = enum { top, right, bottom, left };
pub const BarConfig = struct {
// Comma separated list of FontConfig formatted font specifications.
// null means use the default ("monospace:size=14").
fonts: ?[]const u8 = null,
text_color: pixman.Color = utils.parseRgbaPixmanComptime("0xcdd6f4"),
background_color: pixman.Color = utils.parseRgbaPixmanComptime("0x1e1e2e"),
position: Bar.Position = .top,
margin_top: i32 = 0,
margin_right: i32 = 0,
margin_bottom: i32 = 0,
margin_left: i32 = 0,
// TODO: Support only having the bar on specific outputs
// output: []const u8,
pub fn toBarOptions(config: BarConfig) Bar.Options {
return .{
.fonts = config.fonts orelse "monospace:size=14",
.text_color = config.text_color,
.background_color = config.background_color,
.position = config.position,
.margins = .{
.top = config.margin_top,
.right = config.margin_right,
.bottom = config.margin_bottom,
.left = config.margin_left,
},
};
}
};
pub fn load(config: *Config, parser: *kdl.Parser, hostname: ?[]const u8) !void {
config.bar_config = .{}; // Presence of block = enabled; initialize with defaults
const BarChild = enum { margins };
var next_child_block: ?BarChild = null;
while (try parser.next()) |event| {
switch (event) {
.node => |node| {
if (next_child_block) |child| {
log.warn("Expected child block for bar.{s}, got node instead. Ignoring", .{@tagName(child)});
next_child_block = null;
}
const node_name = std.meta.stringToEnum(NodeName, node.name);
if (node_name) |name| {
if (!helpers.hostMatches(node, parser, hostname)) {
logDebugHostMismatch(name);
continue;
}
const val_str = utils.stripQuotes(node.arg(parser, 0) orelse "");
switch (name) {
.fonts => {
if (node.argcount() < 1) {
logWarnMissingNodeArg(name, "font specification");
continue;
}
config.bar_config.?.fonts = utils.gpa.dupe(u8, val_str) catch @panic("Out of memory");
logDebugSettingNode(name, val_str);
},
.position => {
if (std.meta.stringToEnum(Bar.Position, val_str)) |pos| {
config.bar_config.?.position = pos;
logDebugSettingNode(name, val_str);
} else {
logWarnInvalidNodeArg(name, val_str);
}
},
.margins => next_child_block = .margins,
inline .background_color,
.text_color,
=> |tag| {
@field(config.bar_config.?, @tagName(tag)) = utils.parseRgbaPixman(val_str) catch {
logWarnInvalidNodeArg(name, val_str);
continue;
};
logDebugSettingNode(name, val_str);
},
}
} else {
helpers.logWarnInvalidNode(node.name);
}
},
.child_block_begin => {
if (next_child_block) |child| {
switch (child) {
.margins => try loadMarginsBlock(config, parser, hostname),
}
next_child_block = null;
} else {
try helpers.skipChildBlock(parser);
}
},
.child_block_end => return,
}
}
}
fn loadMarginsBlock(config: *Config, parser: *kdl.Parser, hostname: ?[]const u8) !void {
while (try parser.next()) |event| {
switch (event) {
.node => |node| {
const node_name = std.meta.stringToEnum(MarginsNodeName, node.name);
if (node_name) |name| {
if (!helpers.hostMatches(node, parser, hostname)) {
logDebugHostMismatch(name);
continue;
}
const val_str = utils.stripQuotes(node.arg(parser, 0) orelse "");
const val = fmt.parseInt(i32, val_str, 10) catch {
logWarnInvalidNodeArg(name, val_str);
continue;
};
switch (name) {
.top => config.bar_config.?.margin_top = val,
.right => config.bar_config.?.margin_right = val,
.bottom => config.bar_config.?.margin_bottom = val,
.left => config.bar_config.?.margin_left = val,
}
logDebugSettingNode(name, val_str);
} else {
helpers.logWarnInvalidNode(node.name);
}
},
.child_block_begin => try helpers.skipChildBlock(parser),
.child_block_end => return,
}
}
}
inline fn logDebugSettingNode(node_name: anytype, node_value: []const u8) void {
const node_name_type = @TypeOf(node_name);
switch (node_name_type) {
NodeName => log.debug("Setting bar.{s} to {s}", .{ @tagName(node_name), node_value }),
MarginsNodeName => log.debug("Setting bar.margins.{s} to {s}", .{ @tagName(node_name), node_value }),
else => @compileError("This function does not (yet) support type \"" ++ @typeName(@TypeOf(node_name)) ++ "\""),
}
}
inline fn logDebugHostMismatch(node_name: anytype) void {
const node_name_type = @TypeOf(node_name);
switch (node_name_type) {
NodeName => log.debug("Skipping \"bar.{s}\" (host mismatch)", .{@tagName(node_name)}),
MarginsNodeName => log.debug("Skipping \"bar.margins.{s}\" (host mismatch)", .{@tagName(node_name)}),
else => @compileError("This function does not (yet) support type \"" ++ @typeName(node_name_type) ++ "\""),
}
}
inline fn logWarnInvalidNodeArg(node_name: anytype, node_value: []const u8) void {
const node_name_type = @TypeOf(node_name);
switch (node_name_type) {
NodeName => log.warn("Invalid \"bar.{s}\" ({s}). Using default value", .{ @tagName(node_name), node_value }),
MarginsNodeName => log.warn("Invalid \"bar.margins.{s}\" ({s}). Using default value", .{ @tagName(node_name), node_value }),
else => @compileError("This function does not (yet) support type \"" ++ @typeName(node_name_type) ++ "\""),
}
}
inline fn logWarnMissingNodeArg(node_name: NodeName, comptime arg: []const u8) void {
log.warn("\"bar.{s}\" missing " ++ arg ++ " argument. Ignoring", .{@tagName(node_name)});
}
const std = @import("std");
const fmt = std.fmt;
const kdl = @import("kdl");
const pixman = @import("pixman");
const utils = @import("../utils.zig");
const Bar = @import("../Bar.zig");
const Config = @import("../Config.zig");
const helpers = @import("helpers.zig");
const log = std.log.scoped(.config_bar);

View file

@ -52,42 +52,42 @@ pub const TagOverlayConfig = struct {
margin_bottom: i32 = 0,
margin_left: i32 = 0,
pub fn toTagOverlayOptions(self: TagOverlayConfig) TagOverlay.Options {
pub fn toTagOverlayOptions(config: TagOverlayConfig) TagOverlay.Options {
return .{
.border_width = self.border_width,
.tag_amount = @intCast(std.math.clamp(@as(u32, self.tag_amount), 1, 32)),
.tags_per_row = @intCast(std.math.clamp(@as(u32, self.tags_per_row), 1, 32)),
.square_size = self.square_size,
.square_inner_padding = self.square_inner_padding,
.square_padding = self.square_padding,
.square_border_width = self.square_border_width,
.background_color = self.background_color,
.border_color = self.border_color,
.square_active_background_color = self.square_active_background_color,
.square_active_border_color = self.square_active_border_color,
.square_active_occupied_color = self.square_active_occupied_color,
.square_inactive_background_color = self.square_inactive_background_color,
.square_inactive_border_color = self.square_inactive_border_color,
.square_inactive_occupied_color = self.square_inactive_occupied_color,
.border_width = config.border_width,
.tag_amount = @intCast(std.math.clamp(@as(u32, config.tag_amount), 1, 32)),
.tags_per_row = @intCast(std.math.clamp(@as(u32, config.tags_per_row), 1, 32)),
.square_size = config.square_size,
.square_inner_padding = config.square_inner_padding,
.square_padding = config.square_padding,
.square_border_width = config.square_border_width,
.background_color = config.background_color,
.border_color = config.border_color,
.square_active_background_color = config.square_active_background_color,
.square_active_border_color = config.square_active_border_color,
.square_active_occupied_color = config.square_active_occupied_color,
.square_inactive_background_color = config.square_inactive_background_color,
.square_inactive_border_color = config.square_inactive_border_color,
.square_inactive_occupied_color = config.square_inactive_occupied_color,
.anchors = .{
.top = self.anchor_top,
.right = self.anchor_right,
.bottom = self.anchor_bottom,
.left = self.anchor_left,
.top = config.anchor_top,
.right = config.anchor_right,
.bottom = config.anchor_bottom,
.left = config.anchor_left,
},
.margins = .{
.top = self.margin_top,
.right = self.margin_right,
.bottom = self.margin_bottom,
.left = self.margin_left,
.top = config.margin_top,
.right = config.margin_right,
.bottom = config.margin_bottom,
.left = config.margin_left,
},
.timeout = self.timeout,
.timeout = config.timeout,
};
}
};
pub fn load(config: *Config, parser: *kdl.Parser, hostname: ?[]const u8) !void {
config.tag_overlay = .{}; // Presence of block = enabled; initialize with defaults
config.tag_overlay_config = .{}; // Presence of block = enabled; initialize with defaults
const TagOverlayChild = enum { anchors, margins };
var next_child_block: ?TagOverlayChild = null;
@ -122,11 +122,11 @@ pub fn load(config: *Config, parser: *kdl.Parser, hostname: ?[]const u8) !void {
logWarnInvalidNodeArg(name, val_str);
continue;
};
@field(config.tag_overlay.?, @tagName(tag)) = val;
@field(config.tag_overlay_config.?, @tagName(tag)) = val;
logDebugSettingNode(name, val_str);
},
.timeout => {
config.tag_overlay.?.timeout = fmt.parseInt(u32, val_str, 10) catch {
config.tag_overlay_config.?.timeout = fmt.parseInt(u32, val_str, 10) catch {
logWarnInvalidNodeArg(name, val_str);
continue;
};
@ -141,7 +141,7 @@ pub fn load(config: *Config, parser: *kdl.Parser, hostname: ?[]const u8) !void {
.square_inactive_border_color,
.square_inactive_occupied_color,
=> |tag| {
@field(config.tag_overlay.?, @tagName(tag)) = utils.parseRgbaPixman(val_str) catch {
@field(config.tag_overlay_config.?, @tagName(tag)) = utils.parseRgbaPixman(val_str) catch {
logWarnInvalidNodeArg(name, val_str);
continue;
};
@ -181,10 +181,10 @@ fn loadAnchorsBlock(config: *Config, parser: *kdl.Parser, hostname: ?[]const u8)
const val_str = utils.stripQuotes(node.arg(parser, 0) orelse "");
if (helpers.boolFromKdlStr(val_str)) |val| {
switch (name) {
.top => config.tag_overlay.?.anchor_top = val,
.right => config.tag_overlay.?.anchor_right = val,
.bottom => config.tag_overlay.?.anchor_bottom = val,
.left => config.tag_overlay.?.anchor_left = val,
.top => config.tag_overlay_config.?.anchor_top = val,
.right => config.tag_overlay_config.?.anchor_right = val,
.bottom => config.tag_overlay_config.?.anchor_bottom = val,
.left => config.tag_overlay_config.?.anchor_left = val,
}
logDebugSettingNode(name, val_str);
} else {
@ -216,10 +216,10 @@ fn loadMarginsBlock(config: *Config, parser: *kdl.Parser, hostname: ?[]const u8)
continue;
};
switch (name) {
.top => config.tag_overlay.?.margin_top = val,
.right => config.tag_overlay.?.margin_right = val,
.bottom => config.tag_overlay.?.margin_bottom = val,
.left => config.tag_overlay.?.margin_left = val,
.top => config.tag_overlay_config.?.margin_top = val,
.right => config.tag_overlay_config.?.margin_right = val,
.bottom => config.tag_overlay_config.?.margin_bottom = val,
.left => config.tag_overlay_config.?.margin_left = val,
}
logDebugSettingNode(name, val_str);
} else {
@ -231,6 +231,7 @@ fn loadMarginsBlock(config: *Config, parser: *kdl.Parser, hostname: ?[]const u8)
}
}
}
inline fn logDebugSettingNode(node_name: anytype, node_value: []const u8) void {
const node_name_type = @TypeOf(node_name);
switch (node_name_type) {