From 93d6ee26268993a83abb76dd0981f0c49667415e Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Mon, 6 Jan 2025 16:49:57 +0100 Subject: [PATCH] fix(win32 gui): set window title after gui window is created --- src/renderer/win32/renderer.zig | 73 ++++++++------------------------- src/win32/gui.zig | 18 +++----- 2 files changed, 23 insertions(+), 68 deletions(-) diff --git a/src/renderer/win32/renderer.zig b/src/renderer/win32/renderer.zig index cc75d09..c82fe73 100644 --- a/src/renderer/win32/renderer.zig +++ b/src/renderer/win32/renderer.zig @@ -30,6 +30,8 @@ dispatch_event: ?*const fn (ctx: *anyopaque, cbor_msg: []const u8) void = null, thread: ?std.Thread = null, +title_buf: std.ArrayList(u16), + const global = struct { var init_called: bool = false; }; @@ -59,10 +61,11 @@ pub fn init( }, .system_clipboard_allocator = allocator, }; - var result = .{ + var result: Self = .{ .allocator = allocator, .vx = try vaxis.init(allocator, opts), .handler_ctx = handler_ctx, + .title_buf = std.ArrayList(u16).init(allocator), }; result.vx.caps.unicode = .unicode; result.vx.screen.width_method = .unicode; @@ -73,6 +76,7 @@ pub fn deinit(self: *Self) void { std.log.warn("TODO: implement win32 renderer deinit", .{}); var drop_writer = DropWriter{}; self.vx.deinit(self.allocator, drop_writer.writer().any()); + self.title_buf.deinit(); } threadlocal var thread_is_panicing = false; @@ -203,6 +207,8 @@ pub fn process_renderer_event(self: *Self, msg: []const u8) !void { var buf: [200]u8 = undefined; if (self.dispatch_event) |f| f(self.handler_ctx, fmtmsg(&buf, .{"resize"})); } + if (self.title_buf.items.len > 0) + self.set_terminal_title_internal(); return; } } @@ -305,65 +311,22 @@ pub fn process_renderer_event(self: *Self, msg: []const u8) !void { return error.UnexpectedRendererEvent; } -fn setEllipsis(str: []u16) void { - std.debug.assert(str.len >= 3); - str[str.len - 1] = '.'; - str[str.len - 2] = '.'; - str[str.len - 3] = '.'; -} - -const ConversionSizes = struct { - src_len: usize, - dst_len: usize, -}; -fn calcUtf8ToUtf16LeWithMax(utf8: []const u8, max_dst_len: usize) !ConversionSizes { - var src_len: usize = 0; - var dst_len: usize = 0; - while (src_len < utf8.len) { - if (dst_len >= max_dst_len) break; - const n = try std.unicode.utf8ByteSequenceLength(utf8[src_len]); - const next_src_len = src_len + n; - const codepoint = try std.unicode.utf8Decode(utf8[src_len..next_src_len]); - if (codepoint < 0x10000) { - dst_len += 1; - } else { - if (dst_len + 2 > max_dst_len) break; - dst_len += 2; - } - src_len = next_src_len; - } - return .{ .src_len = src_len, .dst_len = dst_len }; -} - -pub fn set_terminal_title(self: *Self, title_utf8: []const u8) void { - _ = self; - - const max_title_wide = 500; - const conversion_sizes = calcUtf8ToUtf16LeWithMax(title_utf8, max_title_wide) catch { +pub fn set_terminal_title(self: *Self, text: []const u8) void { + std.unicode.utf8ToUtf16LeArrayList(&self.title_buf, text) catch { std.log.err("title is invalid UTF-8", .{}); return; }; + self.set_terminal_title_internal(); +} - var title_wide_buf: [max_title_wide + 1]u16 = undefined; - const len = @min(max_title_wide, conversion_sizes.dst_len); - title_wide_buf[len] = 0; - const title_wide = title_wide_buf[0..len :0]; - - const size = std.unicode.utf8ToUtf16Le(title_wide, title_utf8[0..conversion_sizes.src_len]) catch |err| switch (err) { - error.InvalidUtf8 => { - std.log.err("title is invalid UTF-8", .{}); - return; - }, - }; - std.debug.assert(size == conversion_sizes.dst_len); - if (conversion_sizes.src_len != title_utf8.len) { - setEllipsis(title_wide); - } - var win32_err: gui.Win32Error = undefined; - gui.setWindowTitle(title_wide, &win32_err) catch |err| switch (err) { - error.NoWindow => std.log.warn("no window to set the title for", .{}), - error.Win32 => std.log.err("{s} failed with {}", .{ win32_err.what, win32_err.code.fmt() }), +fn set_terminal_title_internal(self: *Self) void { + const title = self.title_buf.toOwnedSliceSentinel(0) catch @panic("OOM:set_terminal_title"); + gui.set_window_title(title) catch { + // leave self.title_buf to try again later + self.title_buf = std.ArrayList(u16).fromOwnedSlice(self.allocator, title); + return; }; + self.allocator.free(title); } pub fn set_terminal_style(self: *Self, style_: Style) void { diff --git a/src/win32/gui.zig b/src/win32/gui.zig index c7e3714..c79d2af 100644 --- a/src/win32/gui.zig +++ b/src/win32/gui.zig @@ -485,27 +485,19 @@ fn entry(pid: thespian.pid) !void { pid.send(.{"quit"}) catch |e| onexit(e); } -pub const Win32Error = struct { - what: [:0]const u8, - code: win32.WIN32_ERROR, - pub fn set(self: *Win32Error, what: [:0]const u8, code: win32.WIN32_ERROR) error{Win32} { - self.* = .{ .what = what, .code = code }; - return error.Win32; - } -}; - pub fn stop() void { const hwnd = global.hwnd orelse return; _ = win32.SendMessageW(hwnd, WM_APP_EXIT, 0, 0); } -pub fn setWindowTitle(title: [*:0]const u16, err: *Win32Error) error{ NoWindow, Win32 }!void { +pub fn set_window_title(title: [*:0]const u16) error{ NoWindow, Win32 }!void { global.mutex.lock(); defer global.mutex.unlock(); - const hwnd = global.hwnd orelse return error.NoWindow; - if (0 == win32.SetWindowTextW(hwnd, title)) - return err.set("SetWindowText", win32.GetLastError()); + if (win32.SetWindowTextW(hwnd, title) == 0) { + std.log.warn("error in SetWindowText: {}", .{win32.GetLastError()}); + return error.Win32; + } } // returns false if there is no hwnd