diff --git a/docs/TODO.md b/docs/TODO.md index 6bcd6f5..6872db3 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -3,7 +3,7 @@ These are in rough order of my priority, though no promises I do them in this order. - [ ] 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 - [ ] Support window rules (float/tags/SSD by app-id/title) - [ ] 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] Implement primary count/ratio per tagmask - [x] Add primary_count and primary_ratio to Config +- [x] Implement an optional clock bar diff --git a/src/Bar.zig b/src/Bar.zig index 6eefa61..3055092 100644 --- a/src/Bar.zig +++ b/src/Bar.zig @@ -4,14 +4,17 @@ const Bar = @This(); +/// Standard base DPI at a scale of 1 +const base_dpi = 96; + context: *Context, -// TODO: Get this in Config then save in Context -fonts: *fcft.Font, /// The timezone of the computer. timezone: zeit.timezone.TimeZone, -/// The output that this Bar is on +fonts: *fcft.Font, +font_scale: u31 = 0, + output: *Output, // Bar geometry @@ -34,7 +37,7 @@ pub fn init(context: *Context, output: *Output) !Bar { return .{ .context = context, - .fonts = try getFcftFonts("monospace:size=14"), + .fonts = try getFcftFonts("monospace:size=14", 1), .timezone = timezone, .output = output, }; @@ -50,8 +53,8 @@ pub fn initSurface(bar: *Bar) !void { const context = bar.context; // TODO: Add padding to config - const padding = 5; - const bar_height: u31 = @intCast(bar.fonts.height + 2 * padding); + const vertical_padding = 5; + const bar_height: u31 = @intCast(bar.fonts.height + 2 * vertical_padding); const wl_surface = try context.wl_compositor.createSurface(); errdefer wl_surface.destroy(); @@ -99,7 +102,11 @@ pub fn layerSurfaceListener( const width: u31 = @intCast(ev.width); 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| { wl_surface.commit(); } else { @@ -143,6 +150,13 @@ pub fn render(bar: *Bar) !void { 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 const render_width = bar.width * scale; const render_height = bar.height * scale; @@ -210,8 +224,9 @@ pub fn render(bar: *Bar) !void { // Finally, attach the buffer to the surface const wl_surface = bar.wl_surface orelse return; + wl_surface.setBufferScale(scale); 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(); } @@ -318,8 +333,8 @@ fn renderGlyphs( } } -// Borrowed from https://git.sr.ht/~novakane/zig-fcft-example -fn getFcftFonts(fonts: []const u8) !*fcft.Font { +// Borrowed and modified from https://git.sr.ht/~novakane/zig-fcft-example +fn getFcftFonts(fonts: []const u8, scale: u31) !*fcft.Font { // Create an arena to free just for this function; // It makes cleaning up the ArrayList easier. var arena = std.heap.ArenaAllocator.init(utils.gpa); @@ -330,7 +345,16 @@ fn getFcftFonts(fonts: []const u8) !*fcft.Font { var it = mem.tokenizeScalar(u8, fonts, ','); while (it.next()) |font| { - try list.append(arena_alloc, try arena_alloc.dupeZ(u8, 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)); + } } const fcft_fonts = try fcft.Font.fromName(list.items[0..], null); diff --git a/src/Config.zig b/src/Config.zig index 2c2376d..be6746b 100644 --- a/src/Config.zig +++ b/src/Config.zig @@ -477,7 +477,22 @@ fn loadKeybindsChildBlock(config: *Config, parser: *kdl.Parser, hostname: ?[]con logWarnMissingNodeArg(name, "command"); 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 }; }, .change_ratio => { diff --git a/src/Output.zig b/src/Output.zig index 02bd96d..02df08a 100644 --- a/src/Output.zig +++ b/src/Output.zig @@ -29,6 +29,7 @@ usable_width: u31 = 0, usable_height: u31 = 0, // Information for this Output's wallpaper +wallpaper_render_scale: u31 = 0, wallpaper_render_width: u31 = 0, wallpaper_render_height: u31 = 0, 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 }); 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| { @@ -361,7 +374,11 @@ fn wallpaperLayerSurfaceListener(layer_surface: *zwlr.LayerSurfaceV1, event: zwl const width: u31 = @intCast(ev.width); 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| { wl_surface.commit(); } else { @@ -393,7 +410,7 @@ fn calculateScale(image_dimension: c_int, output_dimension: u31, scale: u31) f64 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 { const numerator1: f64 = @floatFromInt(image_dimension); 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. // 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 ty: f64 = calculateTransform(image_height, height, sy); + const tx: f64 = calculateTransform(image_width, width * scale, sx); + const ty: f64 = calculateTransform(image_height, height * scale, sy); // Build a combined source-to-destination transform matrix. // 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.damageBuffer(0, 0, width * scale, height * scale); wl_surface.commit(); + + output.wallpaper_render_scale = scale; } pub fn manage(output: *Output) void { diff --git a/src/utils.zig b/src/utils.zig index 7691461..94cadba 100644 --- a/src/utils.zig +++ b/src/utils.zig @@ -103,7 +103,7 @@ pub fn parseModifiers(s: []const u8) !?river.SeatV1.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 it = std.mem.tokenizeScalar(u8, input, delimiter); while (it.next()) |part| {