detached b9db1df

fix(webkit): implement graceful shutdown on window close with GTK4 close-request signal handler and OS signal handling to prevent process hanging

commit
b9db1df87bbd22d84f39601fbf8aec570a73ae05
author
Brice · · 2025-09-10 19:57 UTC
parent
3b55dd8
signature
unsigned
3 files · +38 -2
M main.go +16 -0
@@ -10,10 +10,12 @@ import (
1010 neturl "net/url"
1111 "os"
1212 "os/exec"
13+ "os/signal"
1314 "path/filepath"
1415 "runtime"
1516 "strconv"
1617 "strings"
18+ "syscall"
1719
1820 "github.com/bnema/dumber/internal/cli"
1921 "github.com/bnema/dumber/internal/config"
@@ -566,13 +568,27 @@ func runBrowser() {
566568 // Zoom handled natively in webkit package (built-in shortcuts)
567569 }
568570
571+ // Set up signal handling for graceful shutdown
572+ sigChan := make(chan os.Signal, 1)
573+ signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
574+
569575 // Enter GTK main loop only when native backend is available.
570576 if webkit.IsNativeAvailable() {
577+ // Handle signals in a goroutine to quit the main loop
578+ go func() {
579+ sig := <-sigChan
580+ log.Printf("Received signal %v - shutting down gracefully", sig)
581+ webkit.QuitMainLoop()
582+ }()
583+
571584 log.Printf("Entering GTK main loop…")
572585 webkit.RunMainLoop()
573586 log.Printf("GTK main loop exited")
574587 } else {
575588 log.Printf("Not entering GUI loop (non-CGO build)")
589+ // In non-CGO mode, just wait for signals
590+ sig := <-sigChan
591+ log.Printf("Received signal %v - exiting", sig)
576592 }
577593 }
578594
@@ -10,10 +10,12 @@ import (
10 neturl "net/url"10 neturl "net/url"
11 "os"11 "os"
12 "os/exec"12 "os/exec"
13+ "os/signal"
13 "path/filepath"14 "path/filepath"
14 "runtime"15 "runtime"
15 "strconv"16 "strconv"
16 "strings"17 "strings"
18+ "syscall"
17 19
18 "github.com/bnema/dumber/internal/cli"20 "github.com/bnema/dumber/internal/cli"
19 "github.com/bnema/dumber/internal/config"21 "github.com/bnema/dumber/internal/config"
@@ -566,13 +568,27 @@ func runBrowser() {
566 // Zoom handled natively in webkit package (built-in shortcuts)568 // Zoom handled natively in webkit package (built-in shortcuts)
567 }569 }
568 570
571+ // Set up signal handling for graceful shutdown
572+ sigChan := make(chan os.Signal, 1)
573+ signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
574+
569 // Enter GTK main loop only when native backend is available.575 // Enter GTK main loop only when native backend is available.
570 if webkit.IsNativeAvailable() {576 if webkit.IsNativeAvailable() {
577+ // Handle signals in a goroutine to quit the main loop
578+ go func() {
579+ sig := <-sigChan
580+ log.Printf("Received signal %v - shutting down gracefully", sig)
581+ webkit.QuitMainLoop()
582+ }()
583+
571 log.Printf("Entering GTK main loop…")584 log.Printf("Entering GTK main loop…")
572 webkit.RunMainLoop()585 webkit.RunMainLoop()
573 log.Printf("GTK main loop exited")586 log.Printf("GTK main loop exited")
574 } else {587 } else {
575 log.Printf("Not entering GUI loop (non-CGO build)")588 log.Printf("Not entering GUI loop (non-CGO build)")
589+ // In non-CGO mode, just wait for signals
590+ sig := <-sigChan
591+ log.Printf("Received signal %v - exiting", sig)
576 }592 }
577 }593 }
578 594
M pkg/webkit/keyboard_cgo.go +6 -0
@@ -350,3 +350,9 @@ func goOnButtonPress(id C.ulong, button C.guint, state C.GdkModifierType) {
350350 _ = vw.GoForward()
351351 }
352352 }
353+
354+//export goQuitMainLoop
355+func goQuitMainLoop() {
356+ log.Printf("[webkit] Window close requested - quitting main loop")
357+ QuitMainLoop()
358+}
@@ -350,3 +350,9 @@ func goOnButtonPress(id C.ulong, button C.guint, state C.GdkModifierType) {
350 _ = vw.GoForward()350 _ = vw.GoForward()
351 }351 }
352 }352 }
353+
354+//export goQuitMainLoop
355+func goQuitMainLoop() {
356+ log.Printf("[webkit] Window close requested - quitting main loop")
357+ QuitMainLoop()
358+}
M pkg/webkit/webview_cgo.go +16 -2
@@ -14,8 +14,22 @@ package webkit
1414 #include <gio/gio.h>
1515
1616 static GtkWidget* new_window() { return GTK_WIDGET(gtk_window_new()); }
17-// GTK4: gtk_main_quit removed; leave as no-op for now (Quit handled in Go)
18-static void connect_destroy_quit(GtkWidget* w) { (void)w; }
17+
18+// Forward declaration
19+extern void goQuitMainLoop();
20+
21+// GTK4 close-request signal handler
22+static gboolean on_close_request(GtkWindow* window, gpointer user_data) {
23+ (void)window; (void)user_data;
24+ goQuitMainLoop();
25+ return FALSE; // Allow the window to close
26+}
27+
28+// Connect window close signal to quit main loop
29+static void connect_destroy_quit(GtkWidget* w) {
30+ if (!w) return;
31+ g_signal_connect(G_OBJECT(w), "close-request", G_CALLBACK(on_close_request), NULL);
32+}
1933 static WebKitWebView* as_webview(GtkWidget* w) { return WEBKIT_WEB_VIEW(w); }
2034 // WebsiteDataManager creation will be handled via GTK4/WebKit6 APIs in Go code.
2135
@@ -14,8 +14,22 @@ package webkit
14 #include <gio/gio.h>14 #include <gio/gio.h>
15 15
16 static GtkWidget* new_window() { return GTK_WIDGET(gtk_window_new()); }16 static GtkWidget* new_window() { return GTK_WIDGET(gtk_window_new()); }
17-// GTK4: gtk_main_quit removed; leave as no-op for now (Quit handled in Go)17+
18-static void connect_destroy_quit(GtkWidget* w) { (void)w; }18+// Forward declaration
19+extern void goQuitMainLoop();
20+
21+// GTK4 close-request signal handler
22+static gboolean on_close_request(GtkWindow* window, gpointer user_data) {
23+ (void)window; (void)user_data;
24+ goQuitMainLoop();
25+ return FALSE; // Allow the window to close
26+}
27+
28+// Connect window close signal to quit main loop
29+static void connect_destroy_quit(GtkWidget* w) {
30+ if (!w) return;
31+ g_signal_connect(G_OBJECT(w), "close-request", G_CALLBACK(on_close_request), NULL);
32+}
19 static WebKitWebView* as_webview(GtkWidget* w) { return WEBKIT_WEB_VIEW(w); }33 static WebKitWebView* as_webview(GtkWidget* w) { return WEBKIT_WEB_VIEW(w); }
20 // WebsiteDataManager creation will be handled via GTK4/WebKit6 APIs in Go code.34 // WebsiteDataManager creation will be handled via GTK4/WebKit6 APIs in Go code.
21 35

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close