From 062748967ccae3b901ea9305017f35b96634e3a1 Mon Sep 17 00:00:00 2001 From: Ben Buhse Date: Thu, 26 Feb 2026 17:29:22 -0600 Subject: [PATCH] Move Utf-8 -> codepoint conversion to utils Once we add more text to the bar, it makes sense to move this into a helper function. --- src/Bar.zig | 13 ++---------- src/Window.zig | 2 +- src/utils.zig | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 12 deletions(-) diff --git a/src/Bar.zig b/src/Bar.zig index 4935bed..5a4922a 100644 --- a/src/Bar.zig +++ b/src/Bar.zig @@ -224,17 +224,9 @@ pub fn draw(bar: *Bar) !void { var fbs = io.fixedBufferStream(&buf); try dt.strftime(fbs.writer(), "%H:%M"); - // Convert ASCII text string to unicode - // XXX: Not sure if this even needs to be converted to unicode - var codepoint_it = (try unicode.Utf8View.init(fbs.getWritten())).iterator(); - const codepoint_count = try unicode.utf8CountCodepoints(fbs.getWritten()); - // We use u32 for fcft even if zig uses u21 - var codepoints: []u32 = try utils.gpa.alloc(u32, codepoint_count); + // Convert date string to Unicode codepoints + const codepoints = try utils.utf8ToCodepoints(fbs.getWritten()); defer utils.gpa.free(codepoints); - var i: usize = 0; - while (codepoint_it.nextCodepoint()) |cp| : (i += 1) { - codepoints[i] = cp; - } const text_width = try bar.textWidth(codepoints); var x: i32 = @divFloor(buffer.width - text_width, 2); @@ -393,7 +385,6 @@ const assert = std.debug.assert; const io = std.io; const mem = std.mem; const process = std.process; -const unicode = std.unicode; const wayland = @import("wayland"); const wl = wayland.client.wl; diff --git a/src/Window.zig b/src/Window.zig index 2cbe4fa..38cb860 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -145,7 +145,7 @@ fn windowListener(river_window_v1: *river.WindowV1, event: river.WindowV1.Event, while (it.next()) |seat| { if (seat.focused_window == window) { // Find another window to focus and warp pointer there - if (output.prevWindow(window)) |next_focus| { + if (output.nextWindow(window)) |next_focus| { if (next_focus != window) { seat.pending_manage.window = .{ .window = next_focus }; seat.pending_manage.should_warp_pointer = true; diff --git a/src/utils.zig b/src/utils.zig index 094b494..0243443 100644 --- a/src/utils.zig +++ b/src/utils.zig @@ -193,6 +193,21 @@ pub fn stripQuotes(s: []const u8) []const u8 { return s; } +/// Convert a Utf-8 string into codepoints +/// Caller owns the returned slice and is responsible for freeing it. +pub fn utf8ToCodepoints(utf8: []const u8) ![]u32 { + var codepoint_it = (try unicode.Utf8View.init(utf8)).iterator(); + const codepoint_count = try unicode.utf8CountCodepoints(utf8); + // We use u32 for fcft even if zig uses u21 + const codepoints: []u32 = try gpa.alloc(u32, codepoint_count); + var i: usize = 0; + while (codepoint_it.nextCodepoint()) |cp| : (i += 1) { + codepoints[i] = cp; + } + + return codepoints; +} + /// Report that the given WaylandGlobal wasn't advertised and exit the program pub fn interfaceNotAdvertised(comptime WaylandGlobal: type) noreturn { fatal("{s} not advertised. Exiting", .{WaylandGlobal.interface.name}); @@ -207,6 +222,7 @@ const std = @import("std"); const fatal = std.process.fatal; const fmt = std.fmt; const mem = std.mem; +const unicode = std.unicode; const wayland = @import("wayland"); const river = wayland.client.river; @@ -447,3 +463,41 @@ test "tokenizeShell quotes mid-token" { try testing.expectEqual(1, result.len); try testing.expectEqualStrings("foobar bazqux", result[0]); } + +test "utf8ToCodepoints ASCII" { + const codepoints = try utf8ToCodepoints("hello"); + defer gpa.free(codepoints); + try testing.expectEqual(5, codepoints.len); + try testing.expectEqual('h', codepoints[0]); + try testing.expectEqual('e', codepoints[1]); + try testing.expectEqual('l', codepoints[2]); + try testing.expectEqual('l', codepoints[3]); + try testing.expectEqual('o', codepoints[4]); +} + +test "utf8ToCodepoints multi-byte" { + const codepoints = try utf8ToCodepoints("grüezi"); + defer gpa.free(codepoints); + try testing.expectEqual(6, codepoints.len); + try testing.expectEqual('g', codepoints[0]); + try testing.expectEqual('r', codepoints[1]); + try testing.expectEqual(0x00FC, codepoints[2]); // ü + try testing.expectEqual('e', codepoints[3]); + try testing.expectEqual('z', codepoints[4]); + try testing.expectEqual('i', codepoints[5]); +} + +test "utf8ToCodepoints empty" { + const codepoints = try utf8ToCodepoints(""); + defer gpa.free(codepoints); + try testing.expectEqual(0, codepoints.len); +} + +test "utf8ToCodepoints emoji" { + // 🇨🇦 is two regional indicator symbols: U+1F1E8 U+1F1E6 + const codepoints = try utf8ToCodepoints("🇨🇦"); + defer gpa.free(codepoints); + try testing.expectEqual(2, codepoints.len); + try testing.expectEqual(0x1F1E8, codepoints[0]); + try testing.expectEqual(0x1F1E6, codepoints[1]); +}