Fix wallpaper and bar rendering when scale >1

We track the scale for wallpaper and render now and have to re-render
when the  scale changes. For the bar, this includes recreated the
fcft fonts.
This commit is contained in:
Ben Buhse 2026-02-14 19:09:03 -06:00
commit 83946ce97a
No known key found for this signature in database
GPG key ID: 7916ACFCD38FD0B4
5 changed files with 77 additions and 18 deletions

View file

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

View file

@ -4,14 +4,17 @@
const Bar = @This(); const Bar = @This();
/// Standard base DPI at a scale of 1
const base_dpi = 96;
context: *Context, context: *Context,
// TODO: Get this in Config then save in Context
fonts: *fcft.Font,
/// The timezone of the computer. /// The timezone of the computer.
timezone: zeit.timezone.TimeZone, timezone: zeit.timezone.TimeZone,
/// The output that this Bar is on fonts: *fcft.Font,
font_scale: u31 = 0,
output: *Output, output: *Output,
// Bar geometry // Bar geometry
@ -34,7 +37,7 @@ pub fn init(context: *Context, output: *Output) !Bar {
return .{ return .{
.context = context, .context = context,
.fonts = try getFcftFonts("monospace:size=14"), .fonts = try getFcftFonts("monospace:size=14", 1),
.timezone = timezone, .timezone = timezone,
.output = output, .output = output,
}; };
@ -50,8 +53,8 @@ pub fn initSurface(bar: *Bar) !void {
const context = bar.context; const context = bar.context;
// TODO: Add padding to config // TODO: Add padding to config
const padding = 5; const vertical_padding = 5;
const bar_height: u31 = @intCast(bar.fonts.height + 2 * padding); const bar_height: u31 = @intCast(bar.fonts.height + 2 * vertical_padding);
const wl_surface = try context.wl_compositor.createSurface(); const wl_surface = try context.wl_compositor.createSurface();
errdefer wl_surface.destroy(); errdefer wl_surface.destroy();
@ -99,7 +102,11 @@ pub fn layerSurfaceListener(
const width: u31 = @intCast(ev.width); const width: u31 = @intCast(ev.width);
const height: u31 = @intCast(ev.height); const height: u31 = @intCast(ev.height);
if (bar.configured and bar.width == width and bar.height == height) { if (bar.configured and
bar.width == width and
bar.height == height and
bar.output.scale == bar.font_scale)
{
if (bar.wl_surface) |wl_surface| { if (bar.wl_surface) |wl_surface| {
wl_surface.commit(); wl_surface.commit();
} else { } else {
@ -143,6 +150,13 @@ pub fn render(bar: *Bar) !void {
const scale = bar.output.scale; 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.font_scale = scale;
}
// Scaled width/height // Scaled width/height
const render_width = bar.width * scale; const render_width = bar.width * scale;
const render_height = bar.height * scale; const render_height = bar.height * scale;
@ -210,8 +224,9 @@ pub fn render(bar: *Bar) !void {
// Finally, attach the buffer to the surface // Finally, attach the buffer to the surface
const wl_surface = bar.wl_surface orelse return; const wl_surface = bar.wl_surface orelse return;
wl_surface.setBufferScale(scale);
wl_surface.attach(buffer.wl_buffer, 0, 0); wl_surface.attach(buffer.wl_buffer, 0, 0);
wl_surface.damageBuffer(0, 0, bar.width, bar.height); wl_surface.damageBuffer(0, 0, render_width, render_height);
wl_surface.commit(); wl_surface.commit();
} }
@ -318,8 +333,8 @@ fn renderGlyphs(
} }
} }
// Borrowed from https://git.sr.ht/~novakane/zig-fcft-example // Borrowed and modified from https://git.sr.ht/~novakane/zig-fcft-example
fn getFcftFonts(fonts: []const u8) !*fcft.Font { fn getFcftFonts(fonts: []const u8, scale: u31) !*fcft.Font {
// Create an arena to free just for this function; // Create an arena to free just for this function;
// It makes cleaning up the ArrayList easier. // It makes cleaning up the ArrayList easier.
var arena = std.heap.ArenaAllocator.init(utils.gpa); var arena = std.heap.ArenaAllocator.init(utils.gpa);
@ -330,8 +345,17 @@ fn getFcftFonts(fonts: []const u8) !*fcft.Font {
var it = mem.tokenizeScalar(u8, fonts, ','); var it = mem.tokenizeScalar(u8, fonts, ',');
while (it.next()) |font| { while (it.next()) |font| {
if (scale > 1) {
// If scale >1, we append :dpi so we can scale the font
const scaled = try arena_alloc.dupeZ(
u8,
try std.fmt.allocPrint(arena_alloc, "{s}:dpi={}", .{ font, @as(u32, base_dpi) * scale }),
);
try list.append(arena_alloc, scaled);
} else {
try list.append(arena_alloc, try arena_alloc.dupeZ(u8, font)); try list.append(arena_alloc, try arena_alloc.dupeZ(u8, font));
} }
}
const fcft_fonts = try fcft.Font.fromName(list.items[0..], null); const fcft_fonts = try fcft.Font.fromName(list.items[0..], null);
errdefer fcft_fonts.destroy(); errdefer fcft_fonts.destroy();

View file

@ -477,7 +477,22 @@ fn loadKeybindsChildBlock(config: *Config, parser: *kdl.Parser, hostname: ?[]con
logWarnMissingNodeArg(name, "command"); logWarnMissingNodeArg(name, "command");
continue; continue;
}); });
const split_exec = try utils.tokenizeToOwnedSlices(exec_str, ' '); var split_exec = try utils.tokenizeToOwnedSlices(exec_str, ' ');
if (split_exec.len > 0) {
// Expand ~ in executable paths
const expanded = expandTilde(split_exec[0]) catch |e| {
if (e == error.HomeNotSet) {
// No ~, just return what we had.
break :sw .{ .spawn = split_exec };
} else {
return e;
}
};
// tokenizeToOwnedSlices dupes each token, so we have to
// free the original value before replacing it.
utils.gpa.free(split_exec[0]);
split_exec[0] = expanded;
}
break :sw .{ .spawn = split_exec }; break :sw .{ .spawn = split_exec };
}, },
.change_ratio => { .change_ratio => {

View file

@ -29,6 +29,7 @@ usable_width: u31 = 0,
usable_height: u31 = 0, usable_height: u31 = 0,
// Information for this Output's wallpaper // Information for this Output's wallpaper
wallpaper_render_scale: u31 = 0,
wallpaper_render_width: u31 = 0, wallpaper_render_width: u31 = 0,
wallpaper_render_height: u31 = 0, wallpaper_render_height: u31 = 0,
wl_surface: ?*wl.Surface = null, wl_surface: ?*wl.Surface = null,
@ -265,6 +266,18 @@ fn wlOutputListener(_: *wl.Output, event: wl.Output.Event, output: *Output) void
log.err("failed to init bar for {s}: {}", .{ output_name, err }); log.err("failed to init bar for {s}: {}", .{ output_name, err });
return; return;
}; };
// Re-render bar if the scale changed
if (bar.configured and output.scale != bar.font_scale) {
bar.render() catch |err| {
log.err("Bar render failed: {}", .{err});
};
}
}
// Re-render wallpaper if scale changed
if (output.configured and output.scale != output.wallpaper_render_scale) {
output.renderWallpaper() catch |err| {
log.err("Wallpaper render failed: {}", .{err});
};
} }
}, },
.scale => |ev| { .scale => |ev| {
@ -361,7 +374,11 @@ fn wallpaperLayerSurfaceListener(layer_surface: *zwlr.LayerSurfaceV1, event: zwl
const width: u31 = @intCast(ev.width); const width: u31 = @intCast(ev.width);
const height: u31 = @intCast(ev.height); const height: u31 = @intCast(ev.height);
if (output.configured and output.wallpaper_render_width == width and output.wallpaper_render_height == height) { if (output.configured and
output.wallpaper_render_width == width and
output.wallpaper_render_height == height and
output.scale == output.wallpaper_render_scale)
{
if (output.wl_surface) |wl_surface| { if (output.wl_surface) |wl_surface| {
wl_surface.commit(); wl_surface.commit();
} else { } else {
@ -393,7 +410,7 @@ fn calculateScale(image_dimension: c_int, output_dimension: u31, scale: u31) f64
return numerator / denominator; return numerator / denominator;
} }
/// Calculates (image_dimension / dimension_scale - output_dimension) / 2 / dimension_scale; /// Calculates (image_dimension / dimension_scale - output_dimension) / 2 / dimension_scale
fn calculateTransform(image_dimension: c_int, output_dimension: u31, dimension_scale: f64) f64 { fn calculateTransform(image_dimension: c_int, output_dimension: u31, dimension_scale: f64) f64 {
const numerator1: f64 = @floatFromInt(image_dimension); const numerator1: f64 = @floatFromInt(image_dimension);
const denominator1: f64 = dimension_scale; const denominator1: f64 = dimension_scale;
@ -441,8 +458,8 @@ pub fn renderWallpaper(output: *Output) !void {
// Calculate translation offsets to center the image on the output. // Calculate translation offsets to center the image on the output.
// If the scaled image is larger than the output, the offset crops equally from both sides. // If the scaled image is larger than the output, the offset crops equally from both sides.
const tx: f64 = calculateTransform(image_width, width, sx); const tx: f64 = calculateTransform(image_width, width * scale, sx);
const ty: f64 = calculateTransform(image_height, height, sy); const ty: f64 = calculateTransform(image_height, height * scale, sy);
// Build a combined source-to-destination transform matrix. // Build a combined source-to-destination transform matrix.
// Pixman transforms map destination pixels back to source pixels, so: // Pixman transforms map destination pixels back to source pixels, so:
@ -473,6 +490,8 @@ pub fn renderWallpaper(output: *Output) !void {
wl_surface.attach(buffer.wl_buffer, 0, 0); wl_surface.attach(buffer.wl_buffer, 0, 0);
wl_surface.damageBuffer(0, 0, width * scale, height * scale); wl_surface.damageBuffer(0, 0, width * scale, height * scale);
wl_surface.commit(); wl_surface.commit();
output.wallpaper_render_scale = scale;
} }
pub fn manage(output: *Output) void { pub fn manage(output: *Output) void {

View file

@ -103,7 +103,7 @@ pub fn parseModifiers(s: []const u8) !?river.SeatV1.Modifiers {
return modifiers; return modifiers;
} }
pub fn tokenizeToOwnedSlices(input: []const u8, delimiter: u8) ![]const []const u8 { pub fn tokenizeToOwnedSlices(input: []const u8, delimiter: u8) ![][]const u8 {
var list: std.ArrayList([]const u8) = .empty; var list: std.ArrayList([]const u8) = .empty;
var it = std.mem.tokenizeScalar(u8, input, delimiter); var it = std.mem.tokenizeScalar(u8, input, delimiter);
while (it.next()) |part| { while (it.next()) |part| {