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:
+197
-91
@@ -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
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user