From 1256bbfec75077e90d77656f7cc16b1794ebfe05 Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Thu, 17 Sep 2026 14:02:23 +0300 Subject: [PATCH] fix(window): the title a program passes to gl_open / gvk_open reaches the window The runtime opens a game's window before main, titled with the program's name, and gl_open attached to it without passing its title on (render3d's gvk_open called win_open, which returns early on Windows and opened a second window on macOS). win_set_title in cocoa.ll / win32.ll (setTitle: / SetWindowTextW, UTF-8 -> UTF-16; a no-op without a window, stubbed headless) retitles it from gl_open, and win_open on an open window retitles it on both platforms. Co-Authored-By: Claude Opus 5 --- changes/window-title.md | 8 ++++++ packages/ludic.render3d/gpu_vk_draw.ludic | 2 +- runtime/native/cocoa.ll | 35 ++++++++++++++++++++++- runtime/native/gl.ll | 6 ++++ runtime/native/gl.ludic | 5 ++++ runtime/native/gl_win_nowin.ll | 5 ++++ runtime/native/win32.ll | 35 ++++++++++++++++++++++- 7 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 changes/window-title.md diff --git a/changes/window-title.md b/changes/window-title.md new file mode 100644 index 00000000..ecdba2c7 --- /dev/null +++ b/changes/window-title.md @@ -0,0 +1,8 @@ +bump: patch +type: fix +A game's window takes the title it asks for. The runtime opens a game's window before `main`, +titled with the `program` name, and `Gl.open` / render3d's `gl_open` and `gvk_open` attached +to it without passing their title on, so a `program Valley` that opened "Maroon Lake" showed +"Valley". `win_set_title` (cocoa.ll `setTitle:`, win32.ll `SetWindowTextW`, UTF-8 to UTF-16) +now retitles it, and a second `win_open` on macOS retitles the open window instead of making +another, as it already did on Windows. diff --git a/packages/ludic.render3d/gpu_vk_draw.ludic b/packages/ludic.render3d/gpu_vk_draw.ludic index 05a1dc87..66c431db 100644 --- a/packages/ludic.render3d/gpu_vk_draw.ludic +++ b/packages/ludic.render3d/gpu_vk_draw.ludic @@ -1411,7 +1411,7 @@ function gvk_open(w: int, h: int, title: string) -> bool { gl_w = w gl_h = h if is_windowed() { - win_open(w, h, 1, title) + win_open(w, h, 1, title) # the window exists before main: this retitles it win_gl_resize(w, h) if gvk_size_buf == null { gvk_size_buf = words(4) } win_gl_drawable(gvk_size_buf) diff --git a/runtime/native/cocoa.ll b/runtime/native/cocoa.ll index 12cdf826..9b439e2c 100644 --- a/runtime/native/cocoa.ll +++ b/runtime/native/cocoa.ll @@ -229,7 +229,15 @@ entry: store i32 %w, ptr @W_fbw store i32 %h, ptr @W_fbh store i32 %scale, ptr @W_scale - + ; a window already open (the runtime opens a game's before main) is retitled, not + ; opened again - as win32.ll's win_open does + %have = load ptr, ptr @W_win + %again = icmp ne ptr %have, null + br i1 %again, label %retitle, label %make +retitle: + call void @win_set_title(ptr %title) + ret void +make: %appcls = call ptr @objc_getClass(ptr @.c_app) %sel_sh = call ptr @sel_registerName(ptr @.s_shared) %app = call ptr @objc_msgSend(ptr %appcls, ptr %sel_sh) @@ -1700,6 +1708,31 @@ out: ret void } +; ---- win_set_title -------------------------------------------------------------------- +; Retitle the window the runtime opened before main: gl_open / gvk_open attach to that window +; rather than opening one, and the title the program asked for would otherwise be dropped in +; favour of the program's name. A no-op without a window. +define void @win_set_title(ptr %title) { +entry: + %win = load ptr, ptr @W_win + %nowin = icmp eq ptr %win, null + %notitle = icmp eq ptr %title, null + %skip = or i1 %nowin, %notitle + br i1 %skip, label %out, label %go +go: + %strcls = call ptr @objc_getClass(ptr @.c_str) + %sel_utf8 = call ptr @sel_registerName(ptr @.s_utf8) + %ns = call ptr (ptr, ptr, ptr) @objc_msgSend(ptr %strcls, ptr %sel_utf8, ptr %title) + %none = icmp eq ptr %ns, null + br i1 %none, label %out, label %set +set: + %sel_title = call ptr @sel_registerName(ptr @.s_title) + %r = call ptr (ptr, ptr, ptr) @objc_msgSend(ptr %win, ptr %sel_title, ptr %ns) + br label %out +out: + ret void +} + ; ---- App.window_hide / App.window_show ------------------------------------------------ ; The game's window leaves the screen and comes back with its view, layer and GL context ; untouched: a launcher steps aside while the game it started runs. The run does not end diff --git a/runtime/native/gl.ll b/runtime/native/gl.ll index 2683fe4a..c28c9967 100644 --- a/runtime/native/gl.ll +++ b/runtime/native/gl.ll @@ -378,6 +378,12 @@ entry: } ; the drawable's size, for a graphics API that follows the window without a GL context (render3d's ; Vulkan resize check); headless there is no window, so 0 x 0. cocoa.ll's real one wins when linked. +; win_set_title: cocoa.ll's in a windowed build; nothing to retitle headless +define weak void @win_set_title(ptr %title) { +entry: + ret void +} + define weak void @win_gl_drawable(ptr %out) { entry: store i32 0, ptr %out diff --git a/runtime/native/gl.ludic b/runtime/native/gl.ludic index d22d2cf9..ad36ded2 100644 --- a/runtime/native/gl.ludic +++ b/runtime/native/gl.ludic @@ -20,6 +20,7 @@ import "gl_api.ludic" # ---- native glue (cocoa.ll / gl.ll) ------------------------------------------- extern function win_gl_attach() -> int = "win_gl_attach" +extern function win_set_title(title: pointer) -> void = "win_set_title" extern function win_gl_resize(w: int, h: int) = "win_gl_resize" extern function win_gl_swap() = "win_gl_swap" extern function win_gl_scale() -> int = "win_gl_scale" @@ -91,6 +92,10 @@ function gl_open(width: int, height: int, title: pointer) -> bool { if win_gl_attach() == 0 { win_open(width, height, 1, title) # a plain program: no window yet if win_gl_attach() == 0 { return false } + } else { + # a game's window was opened before main, titled with the program's name: give it + # the title this call asks for + win_set_title(title) } win_gl_resize(width, height) gl_scale = win_gl_scale() diff --git a/runtime/native/gl_win_nowin.ll b/runtime/native/gl_win_nowin.ll index 4b442a84..539ed6f6 100644 --- a/runtime/native/gl_win_nowin.ll +++ b/runtime/native/gl_win_nowin.ll @@ -52,3 +52,8 @@ define ptr @win_native_instance() { entry: ret ptr null } + +define void @win_set_title(ptr %title) { +entry: + ret void +} diff --git a/runtime/native/win32.ll b/runtime/native/win32.ll index 77847199..4f29aff5 100644 --- a/runtime/native/win32.ll +++ b/runtime/native/win32.ll @@ -50,6 +50,8 @@ declare i64 @DefWindowProcA(ptr, i32, i64, i64) declare i32 @ShowWindow(ptr, i32) declare i32 @SetForegroundWindow(ptr) declare i32 @BringWindowToTop(ptr) +declare i32 @SetWindowTextW(ptr, ptr) +declare i32 @MultiByteToWideChar(i32, i32, ptr, i32, ptr, i32) declare i32 @PeekMessageA(ptr, ptr, i32, i32, i32) declare i64 @DispatchMessageA(ptr) declare ptr @GetDC(ptr) @@ -188,7 +190,11 @@ entry: store i32 %scale, ptr @W_scale %have = load ptr, ptr @W_hwnd %again = icmp ne ptr %have, null - br i1 %again, label %out, label %make + br i1 %again, label %retitle, label %make +retitle: + ; already open (before main, for a game): take the title this call asks for + call void @win_set_title(ptr %title) + br label %out make: ; DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = -4 %dpi = call i32 @SetProcessDpiAwarenessContext(ptr inttoptr (i64 -4 to ptr)) @@ -268,6 +274,33 @@ out: ret void } +; ---- win_set_title -------------------------------------------------------------------- +; Retitle the window opened before main (gl_open / gvk_open attach to it rather than opening +; one, and would otherwise leave the program's name on it). UTF-8 in, UTF-16 to the window. +; A no-op without a window. +define void @win_set_title(ptr %title) { +entry: + %h = load ptr, ptr @W_hwnd + %nowin = icmp eq ptr %h, null + %notitle = icmp eq ptr %title, null + %skip = or i1 %nowin, %notitle + br i1 %skip, label %out, label %size +size: + %n = call i32 @MultiByteToWideChar(i32 65001, i32 0, ptr %title, i32 -1, ptr null, i32 0) + %bad = icmp sle i32 %n, 0 + br i1 %bad, label %out, label %conv +conv: + %n64 = sext i32 %n to i64 + %bytes = mul i64 %n64, 2 + %w = call ptr @malloc(i64 %bytes) + %c = call i32 @MultiByteToWideChar(i32 65001, i32 0, ptr %title, i32 -1, ptr %w, i32 %n) + %r = call i32 @SetWindowTextW(ptr %h, ptr %w) + call void @free(ptr %w) + br label %out +out: + ret void +} + ; ---- App.window_hide / App.window_show ------------------------------------------------ ; The game's window leaves the screen and comes back with its GL context or Vulkan surface ; untouched: a launcher steps aside while the game it started runs. A hidden window is