v0.0.19 - Fix new window tabs: full tab infrastructure (toolbar, URL bar, tab label, favicon) in new windows, new tabs open in active window's notebook, multi-window tab management

This commit is contained in:
Laan Tungir
2026-07-13 06:31:16 -04:00
parent a722a7cc67
commit 02a92f7a26
3 changed files with 200 additions and 94 deletions
+1 -1
View File
@@ -1 +1 @@
0.0.18
0.0.19
+197 -91
View File
@@ -67,6 +67,25 @@ static GtkWindow *g_window = NULL;
static GtkWindow *g_active_window = NULL;
static GtkWidget *g_active_notebook = NULL;
/* Target notebook for the next tab_create() call.
*
* When non-NULL, tab_create()/tab_manager_new_tab() add the new tab to
* this notebook instead of g_notebook. Used by tab_manager_new_window()
* to place the first tab into the newly created window's notebook.
*
* When NULL, tab_manager_new_tab() falls back to g_active_notebook (the
* focused window's notebook) so Ctrl+T / "New Tab" opens in the active
* window rather than always the main window. */
static GtkWidget *g_target_notebook = NULL;
/* Related view for the next tab_create() call.
*
* When non-NULL, tab_create() creates the webview with
* webkit_web_view_new_with_related_view() so it shares the parent's
* WebProcess and window features (avoids the WindowFeatures assertion
* crash). Used by tab_manager_new_window(). */
static WebKitWebView *g_target_related_view = NULL;
/* Dynamic array of tab_info_t pointers, indexed by notebook page number. */
static tab_info_t **g_tabs = NULL;
static int g_tab_count = 0;
@@ -76,6 +95,9 @@ static int g_tab_cap = 0;
static tab_info_t *tab_create(const char *url);
static GtkWidget *build_tab_label(tab_info_t *tab);
static GtkWidget *tab_find_notebook(GtkWidget *page);
static int tab_array_add(tab_info_t *tab);
static int tab_array_find(tab_info_t *tab);
static GtkWidget *build_hamburger_menu(tab_info_t *tab);
static char *normalize_url(const char *input);
static void on_tab_close_clicked_proxy_new(GtkMenuItem *item, gpointer data);
@@ -490,7 +512,10 @@ static gboolean on_completion_match_selected(GtkEntryCompletion *completion,
static void on_tab_close_clicked(GtkButton *btn, gpointer user_data) {
tab_info_t *tab = (tab_info_t *)user_data;
(void)btn;
int index = gtk_notebook_page_num(GTK_NOTEBOOK(g_notebook), tab->page);
/* Look up the tab's index in the global g_tabs array by pointer.
* This works for tabs in any window's notebook (the index returned
* by gtk_notebook_page_num only matches g_tabs for the main window). */
int index = tab_array_find(tab);
if (index >= 0) {
tab_manager_close_tab(index);
}
@@ -501,7 +526,8 @@ static gboolean on_tab_label_button_press(GtkWidget *widget,
gpointer user_data) {
(void)widget;
tab_info_t *tab = (tab_info_t *)user_data;
int index = gtk_notebook_page_num(GTK_NOTEBOOK(g_notebook), tab->page);
/* Look up the tab's index in the global g_tabs array by pointer. */
int index = tab_array_find(tab);
if (index < 0) return FALSE;
@@ -761,79 +787,60 @@ static GtkWidget *tab_manager_new_window(const char *url,
gtk_window_set_title(GTK_WINDOW(window), "sovereign browser");
gtk_window_set_default_size(GTK_WINDOW(window), 1024, 768);
/* Create a notebook for this window (so future tabs could be added;
* for now it holds a single page). */
/* Create a notebook for this window. It holds the full tab
* infrastructure (toolbar, URL entry, tab label, favicon, etc.)
* built by tab_create(), so the new window looks and behaves like
* the main window. */
GtkWidget *notebook = gtk_notebook_new();
gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook), FALSE);
gtk_notebook_set_show_border(GTK_NOTEBOOK(notebook), FALSE);
gtk_notebook_set_show_tabs(GTK_NOTEBOOK(notebook), TRUE);
gtk_container_add(GTK_CONTAINER(window), notebook);
/* Create the webview. Prefer webkit_web_view_new_with_related_view
* so the new webview shares the parent's WebProcess and window
* features (avoids the WindowFeatures assertion crash). Fall back
* to the shared context if no related view is provided. */
WebKitWebView *wv = NULL;
if (related_view != NULL && WEBKIT_IS_WEB_VIEW(related_view)) {
wv = WEBKIT_WEB_VIEW(webkit_web_view_new_with_related_view(related_view));
} else {
wv = WEBKIT_WEB_VIEW(webkit_web_view_new_with_context(g_ctx));
}
if (wv == NULL) {
/* Build a full tab via tab_create(). Set g_target_notebook so the
* tab is added to this window's notebook (not the main notebook),
* and g_target_related_view so the webview is created with
* webkit_web_view_new_with_related_view() (shares the parent's
* WebProcess and window features, avoiding the WindowFeatures
* assertion crash). Restore both globals afterwards. */
g_target_notebook = notebook;
g_target_related_view = (related_view != NULL &&
WEBKIT_IS_WEB_VIEW(related_view))
? related_view : NULL;
tab_info_t *tab = tab_create(url);
g_target_related_view = NULL;
g_target_notebook = NULL;
if (tab == NULL) {
gtk_widget_destroy(window);
return NULL;
}
/* Apply the same WebKitSettings as regular tabs. */
WebKitSettings *settings = webkit_web_view_get_settings(wv);
webkit_settings_set_enable_developer_extras(settings, TRUE);
webkit_settings_set_enable_javascript(settings, TRUE);
webkit_settings_set_javascript_can_open_windows_automatically(settings, TRUE);
webkit_settings_set_allow_file_access_from_file_urls(settings, TRUE);
webkit_settings_set_allow_universal_access_from_file_urls(settings, TRUE);
webkit_settings_set_allow_modal_dialogs(settings, TRUE);
/* Register the tab in the global g_tabs array and add it to the
* new window's notebook. tab_create() already loaded the URL and
* wired all per-tab signals (load-changed, decide-policy, create,
* context-menu, favicon, url-entry, etc.) with the tab_info_t as
* user_data, so the new window's tab gets the same behavior as a
* main-window tab. */
int index = tab_array_add(tab);
if (index < 0) {
g_free(tab);
gtk_widget_destroy(window);
return NULL;
}
/* Inject window.nostr into this webview. */
nostr_inject_setup(wv);
const browser_settings_t *s = settings_get();
int page_num = gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
tab->page, tab->tab_label);
gtk_notebook_set_tab_reorderable(GTK_NOTEBOOK(notebook), tab->page,
s->tab_drag_reorder);
/* Connect the key-press handler (browser shortcuts). */
g_signal_connect(G_OBJECT(wv), "key-press-event",
G_CALLBACK(on_key_press), NULL);
/* Ensure the webview expands to fill the window. */
gtk_widget_set_vexpand(GTK_WIDGET(wv), TRUE);
gtk_widget_set_hexpand(GTK_WIDGET(wv), TRUE);
/* Build a minimal tab label for the notebook page. */
GtkWidget *label_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
GtkWidget *title_lbl = gtk_label_new("Loading…");
gtk_label_set_ellipsize(GTK_LABEL(title_lbl), PANGO_ELLIPSIZE_END);
gtk_label_set_max_width_chars(GTK_LABEL(title_lbl), 20);
gtk_widget_set_hexpand(title_lbl, TRUE);
gtk_box_pack_start(GTK_BOX(label_box), title_lbl, TRUE, TRUE, 0);
/* Add the webview as a notebook page. */
gtk_notebook_append_page(GTK_NOTEBOOK(notebook), GTK_WIDGET(wv),
label_box);
gtk_notebook_set_tab_reorderable(GTK_NOTEBOOK(notebook), GTK_WIDGET(wv),
TRUE);
/* Wire the same webview signals as tab_create(). We pass NULL as
* user_data for handlers that expect a tab_info_t* — those handlers
* only dereference user_data for UI updates (URL bar, tab title,
* favicon) that don't apply to a standalone window webview. The
* navigation-relevant handlers (decide-policy, create, load-failed)
* don't need the tab pointer. */
g_signal_connect(wv, "load-changed",
G_CALLBACK(on_load_changed), NULL);
g_signal_connect(wv, "load-failed",
G_CALLBACK(on_load_failed), NULL);
g_signal_connect(wv, "decide-policy",
G_CALLBACK(on_decide_policy), NULL);
g_signal_connect(wv, "create",
G_CALLBACK(on_create_webview), NULL);
g_signal_connect(wv, "context-menu",
G_CALLBACK(on_webview_context_menu), NULL);
/* Show the tab widgets before switching to it. */
gtk_widget_show_all(tab->page);
gtk_widget_show_all(tab->tab_label);
gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook), page_num);
/* Window lifecycle: focus-in updates the active window/notebook
* pointers; destroy reverts to the main window but does NOT quit. */
@@ -842,11 +849,6 @@ static GtkWidget *tab_manager_new_window(const char *url,
g_signal_connect(window, "destroy",
G_CALLBACK(on_aux_window_destroy), NULL);
/* Load the URL. */
if (url && url[0]) {
webkit_web_view_load_uri(wv, url);
}
/* Show everything and present the window. */
gtk_widget_show_all(window);
gtk_window_present(GTK_WINDOW(window));
@@ -855,9 +857,9 @@ static GtkWidget *tab_manager_new_window(const char *url,
g_active_window = GTK_WINDOW(window);
g_active_notebook = notebook;
g_print("[windows] Created new window %p for %s\n",
(void *)window, url ? url : "(none)");
return GTK_WIDGET(wv);
g_print("[windows] Created new window %p (tab %d) for %s\n",
(void *)window, index, url ? url : "(none)");
return GTK_WIDGET(tab->webview);
}
static GtkWidget *build_tab_label(tab_info_t *tab) {
@@ -1049,8 +1051,7 @@ static void on_load_changed(WebKitWebView *webview,
char *slash = strchr(host_buf, '/');
if (slash) *slash = '\0';
if (host_buf[0]) {
int index = gtk_notebook_page_num(GTK_NOTEBOOK(g_notebook),
tab->page);
int index = tab_array_find(tab);
if (index >= 0) {
tab_manager_set_title(index, host_buf);
}
@@ -1063,10 +1064,9 @@ static void on_load_changed(WebKitWebView *webview,
uri ? uri : "(null)",
(title && title[0]) ? title : "(none)");
/* Update tab title (only for tabs in the main notebook). */
/* Update tab title (works for tabs in any window's notebook). */
if (tab != NULL && title && title[0]) {
int index = gtk_notebook_page_num(GTK_NOTEBOOK(g_notebook),
tab->page);
int index = tab_array_find(tab);
if (index >= 0) {
tab_manager_set_title(index, title);
}
@@ -1745,8 +1745,59 @@ static void tab_array_remove(int index) {
g_tab_count--;
}
/* Find the index of a tab in the global g_tabs array by pointer.
*
* The g_tabs array is a flat list of all open tabs across all windows.
* Notebook page numbers only match g_tabs indices for the main window;
* auxiliary windows have their own page numbering. Internal handlers
* (close button, tab label clicks, load-changed title updates) should
* use this to look up the g_tabs index rather than gtk_notebook_page_num,
* which returns the page number within a single notebook. */
static int tab_array_find(tab_info_t *tab) {
if (tab == NULL) return -1;
for (int i = 0; i < g_tab_count; i++) {
if (g_tabs[i] == tab) return i;
}
return -1;
}
/* ── Tab creation ─────────────────────────────────────────────────── */
/* Return the notebook that the next new tab should be added to.
*
* Priority:
* 1. g_target_notebook — explicitly set by tab_manager_new_window()
* to place the first tab into the new window's notebook.
* 2. g_active_notebook — the currently focused window's notebook, so
* Ctrl+T / "New Tab" opens in the active window.
* 3. g_notebook — the main window's notebook (fallback). */
static GtkWidget *get_effective_notebook(void) {
if (g_target_notebook != NULL) return g_target_notebook;
if (g_active_notebook != NULL) return g_active_notebook;
return g_notebook;
}
/* Find the GtkNotebook that contains the given tab page widget.
*
* Tabs can live in either the main window's notebook (g_notebook) or an
* auxiliary window's notebook (g_active_notebook when it differs). This
* checks the active notebook first, then the main notebook, and returns
* whichever one actually contains the page (gtk_notebook_page_num >= 0).
*
* Returns NULL if the page is not found in either notebook. */
static GtkWidget *tab_find_notebook(GtkWidget *page) {
if (page == NULL) return NULL;
if (g_active_notebook != NULL && g_active_notebook != g_notebook) {
if (gtk_notebook_page_num(GTK_NOTEBOOK(g_active_notebook), page) >= 0)
return g_active_notebook;
}
if (g_notebook != NULL) {
if (gtk_notebook_page_num(GTK_NOTEBOOK(g_notebook), page) >= 0)
return g_notebook;
}
return NULL;
}
static tab_info_t *tab_create(const char *url) {
const browser_settings_t *s = settings_get();
@@ -1767,8 +1818,19 @@ static tab_info_t *tab_create(const char *url) {
snprintf(tab->current_url, sizeof(tab->current_url), "%s", default_url);
snprintf(tab->title, sizeof(tab->title), "Loading…");
/* Create the webview from the shared context. */
tab->webview = WEBKIT_WEB_VIEW(webkit_web_view_new_with_context(g_ctx));
/* Create the webview. Prefer a related view (set by
* tab_manager_new_window) so the new webview shares the parent's
* WebProcess and window features — this avoids the
* std::optional<WindowFeatures> assertion crash that occurs with
* webkit_web_view_new_with_context() for target="_blank" windows.
* Fall back to the shared context for normal new tabs. */
if (g_target_related_view != NULL &&
WEBKIT_IS_WEB_VIEW(g_target_related_view)) {
tab->webview = WEBKIT_WEB_VIEW(
webkit_web_view_new_with_related_view(g_target_related_view));
} else {
tab->webview = WEBKIT_WEB_VIEW(webkit_web_view_new_with_context(g_ctx));
}
/* Enable developer extras + security settings (same as original main.c). */
WebKitSettings *settings = webkit_web_view_get_settings(tab->webview);
@@ -2257,11 +2319,14 @@ int tab_manager_new_tab(const char *url) {
return -1;
}
/* Add to notebook. */
int page_num = gtk_notebook_append_page(GTK_NOTEBOOK(g_notebook),
/* Add to the effective notebook (active window's notebook, or the
* main notebook as fallback). This makes Ctrl+T / "New Tab" open in
* the focused window instead of always the main window. */
GtkWidget *nb = get_effective_notebook();
int page_num = gtk_notebook_append_page(GTK_NOTEBOOK(nb),
tab->page, tab->tab_label);
gtk_notebook_set_tab_reorderable(GTK_NOTEBOOK(g_notebook), tab->page,
s->tab_drag_reorder);
gtk_notebook_set_tab_reorderable(GTK_NOTEBOOK(nb), tab->page,
s->tab_drag_reorder);
/* Show the tab widgets before switching to it — the page must be
* realized/mapped before it can become the current page and receive
@@ -2270,7 +2335,7 @@ int tab_manager_new_tab(const char *url) {
gtk_widget_show_all(tab->tab_label);
/* Switch to the new tab (now that it's visible). */
gtk_notebook_set_current_page(GTK_NOTEBOOK(g_notebook), page_num);
gtk_notebook_set_current_page(GTK_NOTEBOOK(nb), page_num);
/* Give the URL entry keyboard focus so the user can immediately
* type a URL. Select all text so typing replaces the current URL.
@@ -2289,15 +2354,33 @@ void tab_manager_close_tab(int index) {
tab_info_t *tab = g_tabs[index];
if (tab == NULL) return;
/* Find the notebook this tab belongs to — it may be the main
* window's notebook or an auxiliary window's notebook. */
GtkWidget *nb = tab_find_notebook(tab->page);
if (nb == NULL) nb = g_notebook; /* fallback */
/* Remove from notebook (this destroys the page widget). */
gtk_notebook_remove_page(GTK_NOTEBOOK(g_notebook), index);
gtk_notebook_remove_page(GTK_NOTEBOOK(nb), index);
/* Remove from our array. */
tab_array_remove(index);
g_print("[tabs] Closed tab %d, remaining: %d\n", index, g_tab_count);
/* If no tabs left, quit the app. */
/* If this was an auxiliary window's last tab, close that window.
* The destroy handler (on_aux_window_destroy) reverts the active
* window pointer to the main window. We detect "aux window" by
* checking that the notebook we removed from is not g_notebook and
* now has zero pages. */
if (nb != g_notebook &&
gtk_notebook_get_n_pages(GTK_NOTEBOOK(nb)) == 0) {
GtkWidget *toplevel = gtk_widget_get_toplevel(nb);
if (toplevel != NULL && GTK_IS_WINDOW(toplevel)) {
gtk_window_close(GTK_WINDOW(toplevel));
}
}
/* If the main window has no tabs left, quit the app. */
if (g_tab_count == 0) {
g_print("[tabs] Last tab closed, exiting.\n");
if (g_window) {
@@ -2307,15 +2390,26 @@ void tab_manager_close_tab(int index) {
}
void tab_manager_close_active(void) {
int index = tab_manager_get_active_index();
/* Resolve the active tab by widget pointer (works across windows),
* then look up its g_tabs index. Using tab_manager_get_active_index()
* directly would return the active notebook's page number, which only
* matches the g_tabs index for the main window. */
tab_info_t *tab = tab_manager_get_active();
int index = tab_array_find(tab);
if (index >= 0) {
tab_manager_close_tab(index);
}
}
int tab_manager_get_active_index(void) {
if (g_notebook == NULL) return -1;
return gtk_notebook_get_current_page(GTK_NOTEBOOK(g_notebook));
/* Use the active window's notebook (defaults to g_notebook via
* get_effective_notebook). The returned page number is only a valid
* g_tabs index for the main window; callers that need the tab_info_t
* should use tab_manager_get_active(), which resolves by widget
* pointer to support auxiliary windows. */
GtkWidget *nb = get_effective_notebook();
if (nb == NULL) return -1;
return gtk_notebook_get_current_page(GTK_NOTEBOOK(nb));
}
void tab_manager_switch_to(int index) {
@@ -2325,9 +2419,21 @@ void tab_manager_switch_to(int index) {
}
tab_info_t *tab_manager_get_active(void) {
int index = tab_manager_get_active_index();
if (index < 0 || index >= g_tab_count) return NULL;
return g_tabs[index];
/* Resolve the active tab by looking at the active notebook's current
* page widget and finding the matching tab_info_t in g_tabs by
* pointer. This works across multiple windows: the notebook page
* number only matches the g_tabs index for the main window, so we
* can't rely on it for auxiliary windows. */
GtkWidget *nb = get_effective_notebook();
if (nb == NULL) return NULL;
gint page = gtk_notebook_get_current_page(GTK_NOTEBOOK(nb));
if (page < 0) return NULL;
GtkWidget *page_widget = gtk_notebook_get_nth_page(GTK_NOTEBOOK(nb), page);
if (page_widget == NULL) return NULL;
for (int i = 0; i < g_tab_count; i++) {
if (g_tabs[i] && g_tabs[i]->page == page_widget) return g_tabs[i];
}
return NULL;
}
tab_info_t *tab_manager_get(int index) {
+2 -2
View File
@@ -11,9 +11,9 @@
#ifndef SOVEREIGN_BROWSER_VERSION_H
#define SOVEREIGN_BROWSER_VERSION_H
#define SB_VERSION "v0.0.18"
#define SB_VERSION "v0.0.19"
#define SB_VERSION_MAJOR 0
#define SB_VERSION_MINOR 0
#define SB_VERSION_PATCH 18
#define SB_VERSION_PATCH 19
#endif /* SOVEREIGN_BROWSER_VERSION_H */