diff --git a/VERSION b/VERSION index 5c49468..2aa9160 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.43 +0.0.44 diff --git a/sovereign_browser-0.0.43.tar.gz b/sovereign_browser-0.0.43.tar.gz new file mode 100644 index 0000000..46a334b Binary files /dev/null and b/sovereign_browser-0.0.43.tar.gz differ diff --git a/src/main.c b/src/main.c index ee5b4ff..3aaa713 100644 --- a/src/main.c +++ b/src/main.c @@ -505,7 +505,83 @@ static void agent_login_callback(void) { g_print("[login] Agent login detected.\n"); } -/* ---- Window destroy ------------------------------------------------- */ +/* ---- Window close / destroy ----------------------------------------- * + * The main window uses a delete-event handler to intercept the window + * manager's close request BEFORE the window is destroyed. This lets us + * close the main window's tabs cleanly (updating g_tab_count) and keep + * the app running if auxiliary windows still have tabs. The app only + * quits when the last window is closed (no tabs remain anywhere). + * + * delete-event: runs first. Closes all main-window tabs. If aux windows + * still have tabs, returns TRUE to suppress destroy and hides the + * main window. If no tabs remain, returns FALSE to let destroy + * proceed → on_window_destroy → gtk_main_quit(). + * destroy: runs only when the app is truly shutting down. Performs + * session save, net_services_shutdown, agent_server_stop, etc. + */ + +/* Guard flag: set while on_window_delete_event is closing the main + * window's tabs. Prevents re-entrancy when tab_manager_close_tab() + * calls gtk_window_close(g_window) after the last tab is closed, which + * would re-emit delete-event. */ +static gboolean g_main_window_closing = FALSE; + +static gboolean on_window_delete_event(GtkWidget *widget, + GdkEvent *event, + gpointer data) { + (void)event; + (void)data; + + /* If we're already in the process of closing (re-entrant call from + * tab_manager_close_tab → gtk_window_close), let the destroy + * proceed. */ + if (g_main_window_closing) { + return FALSE; + } + g_main_window_closing = TRUE; + + /* Close all tabs that belong to the main window's notebook. We + * identify main-window tabs by checking gtk_notebook_page_num on + * the main notebook. tab_manager_close_tab handles removing from + * the notebook and the g_tabs array. Re-scan each iteration because + * closing shifts indices. */ + for (;;) { + gboolean found = FALSE; + GtkWidget *main_nb = tab_manager_get_main_notebook(); + if (main_nb == NULL) break; + /* Close from the highest index down so removal doesn't shift + * unprocessed indices. Find the highest-index tab in the main + * notebook and close it. */ + for (int i = tab_manager_count() - 1; i >= 0; i--) { + tab_info_t *tab = tab_manager_get(i); + if (tab == NULL || tab->page == NULL) continue; + if (gtk_notebook_page_num(GTK_NOTEBOOK(main_nb), + tab->page) >= 0) { + tab_manager_close_tab(i); + found = TRUE; + break; + } + } + if (!found) break; + } + + /* If auxiliary windows still have tabs, keep the app running. + * Suppress the default destroy by returning TRUE, and hide the + * main window. The aux windows continue independently. */ + if (tab_manager_count() > 0) { + g_print("[windows] Main window closed but %d tab(s) remain in " + "other window(s) — keeping app alive.\n", + tab_manager_count()); + gtk_widget_hide(widget); + g_main_window_closing = FALSE; /* allow re-opening later */ + return TRUE; /* suppress destroy */ + } + + /* No tabs left anywhere — let the destroy proceed, which triggers + * on_window_destroy and quits the app. Keep the guard set so any + * re-entrant delete-event from the destroy path doesn't re-enter. */ + return FALSE; +} static void on_window_destroy(GtkWidget *widget, gpointer data) { (void)widget; @@ -761,6 +837,8 @@ int main(int argc, char **argv) { GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(window), "sovereign browser " SB_VERSION); gtk_window_set_default_size(GTK_WINDOW(window), 1024, 768); + g_signal_connect(window, "delete-event", + G_CALLBACK(on_window_delete_event), NULL); g_signal_connect(window, "destroy", G_CALLBACK(on_window_destroy), NULL); g_signal_connect(window, "key-press-event", G_CALLBACK(on_key_press), NULL); g_window = GTK_WINDOW(window); diff --git a/src/tab_manager.c b/src/tab_manager.c index ce02f75..3beea5d 100644 --- a/src/tab_manager.c +++ b/src/tab_manager.c @@ -3274,6 +3274,14 @@ int tab_manager_count(void) { return g_tab_count; } +/* Return the main window's notebook widget. Used by main.c's + * delete-event handler to identify which tabs belong to the main window + * (vs auxiliary windows) when closing the main window but keeping aux + * windows alive. */ +GtkWidget *tab_manager_get_main_notebook(void) { + return g_notebook; +} + void tab_manager_set_title(int index, const char *title) { if (index < 0 || index >= g_tab_count) return; tab_info_t *tab = g_tabs[index]; @@ -3344,10 +3352,31 @@ void tab_manager_open_in_new_window(int index) { if (index < 0 || index >= g_tab_count) return; tab_info_t *tab = g_tabs[index]; if (tab == NULL) return; - /* Open the tab's current URL in a new window. Pass the active - * webview as the related view so the new window shares the - * WebProcess. */ - tab_manager_new_window(tab->current_url, tab->webview); + + /* Capture the URL before opening the new window — the original tab + * will be closed below, which frees the tab_info_t. */ + char url_buf[TAB_URL_MAX]; + snprintf(url_buf, sizeof(url_buf), "%s", tab->current_url); + + /* Open the URL in a new window. Pass the original tab's webview as + * the related view so the new window shares the WebProcess. The new + * window takes its own ref on the related view, so it's safe to + * close the original tab afterwards. */ + GtkWidget *new_wv = tab_manager_new_window(url_buf, tab->webview); + if (new_wv == NULL) { + /* New window creation failed — keep the original tab open so the + * user doesn't lose the page. */ + return; + } + + /* Close the original tab — "Open in New Window" moves the tab to a + * new window rather than duplicating it. The index may have shifted + * if tab_manager_new_window added tabs to the array, so re-resolve + * the tab's current index by pointer. */ + int cur_index = tab_array_find(tab); + if (cur_index >= 0) { + tab_manager_close_tab(cur_index); + } } void tab_manager_new_window_blank(void) { diff --git a/src/tab_manager.h b/src/tab_manager.h index 3a140d6..ebe9b70 100644 --- a/src/tab_manager.h +++ b/src/tab_manager.h @@ -208,6 +208,14 @@ WebKitWebView *tab_manager_get_main_webview(void); */ gboolean tab_manager_sidebar_visible(void); +/* + * Returns the main window's GtkNotebook widget. Used by main.c's + * delete-event handler to identify which tabs belong to the main window + * (vs auxiliary windows) when closing the main window but keeping aux + * windows alive. + */ +GtkWidget *tab_manager_get_main_notebook(void); + /* * Re-hide the sidebar container after gtk_widget_show_all(window). * Call this in main.c after show_all so the sidebar (which is packed diff --git a/src/version.h b/src/version.h index 1f71bd9..4c287ca 100644 --- a/src/version.h +++ b/src/version.h @@ -11,9 +11,9 @@ #ifndef SOVEREIGN_BROWSER_VERSION_H #define SOVEREIGN_BROWSER_VERSION_H -#define SB_VERSION "v0.0.43" +#define SB_VERSION "v0.0.44" #define SB_VERSION_MAJOR 0 #define SB_VERSION_MINOR 0 -#define SB_VERSION_PATCH 43 +#define SB_VERSION_PATCH 44 #endif /* SOVEREIGN_BROWSER_VERSION_H */