Compare commits

...
8 Commits
11 changed files with 188 additions and 35 deletions
+1 -1
View File
@@ -1 +1 @@
0.0.47
0.0.54
+4
View File
@@ -286,6 +286,10 @@ create_source_tarball() {
--exclude='*.log' \
--exclude='*.tar.gz' \
--exclude='nostr_core_lib' \
--exclude='tests/local-site/assets/*.mp4' \
--exclude='tests/local-site/assets/*.m4a' \
--exclude='tests/local-site/assets/*.webm' \
--exclude='tests/local-site/assets/*.ogg' \
. > /dev/null 2>&1; then
echo "$tarball_name"
else
+26 -3
View File
@@ -12,6 +12,7 @@
#include "login_dialog.h"
#include "key_store.h"
#include "agent_login.h"
#include "version.h"
#include <string.h>
#include <stdlib.h>
@@ -111,6 +112,7 @@ static GtkWidget *create_seed_screen(login_ctx_t *ctx);
static GtkWidget *create_readonly_screen(login_ctx_t *ctx);
static GtkWidget *create_nip46_screen(login_ctx_t *ctx);
static GtkWidget *create_nsigner_screen(login_ctx_t *ctx);
static void on_index_changed(GtkWidget *spin, gpointer user_data);
#if defined(NOSTR_ENABLE_NSIGNER_CLIENT)
static void on_detect_serial(GtkWidget *btn, gpointer user_data);
#endif
@@ -1016,17 +1018,24 @@ static GtkWidget *create_nsigner_screen(login_ctx_t *ctx) {
g_signal_connect(transport_combo, "changed",
G_CALLBACK(on_transport_changed), box);
/* Nostr index spinner. */
/* Nostr index spinner. The label shows the derived BIP-32 path
* m/44'/1237'/N'/0/0 and updates N' live as the user changes the
* spin button value, so they can see which key they are selecting. */
GtkWidget *index_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
gtk_box_pack_start(GTK_BOX(box), index_box, FALSE, FALSE, 0);
GtkWidget *index_label = gtk_label_new("Key Index (NIP-06 m/44'/1237'/N'/0/0):");
GtkWidget *index_label = gtk_label_new("Key Index (m/44'/1237'/0'/0/0):");
gtk_widget_set_halign(index_label, GTK_ALIGN_START);
gtk_box_pack_start(GTK_BOX(index_box), index_label, FALSE, FALSE, 0);
GtkWidget *index_spin = gtk_spin_button_new_with_range(0, 1000, 1);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(index_spin), 0);
gtk_box_pack_start(GTK_BOX(index_box), index_spin, FALSE, FALSE, 0);
/* Update the path label whenever the index value changes. */
g_signal_connect(index_spin, "value-changed",
G_CALLBACK(on_index_changed), index_label);
GtkWidget *hint = gtk_label_new("n_signer is a foreground, RAM-only hardware signer. Your private key never leaves the device.");
gtk_widget_set_sensitive(hint, FALSE);
gtk_widget_set_halign(hint, GTK_ALIGN_START);
@@ -1040,9 +1049,23 @@ static GtkWidget *create_nsigner_screen(login_ctx_t *ctx) {
g_object_set_data(G_OBJECT(box), "service-box", service_box);
g_object_set_data(G_OBJECT(box), "service-entry", service_entry);
g_object_set_data(G_OBJECT(box), "index-spin", index_spin);
g_object_set_data(G_OBJECT(box), "index-label", index_label);
return box;
}
/* Update the key-index path label when the spin button value changes.
* Shows the live BIP-32 derivation path m/44'/1237'/N'/0/0 with the
* current N value substituted in. */
static void on_index_changed(GtkWidget *spin, gpointer user_data) {
GtkSpinButton *sb = GTK_SPIN_BUTTON(spin);
GtkWidget *label = GTK_WIDGET(user_data);
int idx = gtk_spin_button_get_value_as_int(sb);
char text[96];
snprintf(text, sizeof(text),
"Key Index (m/44'/1237'/%d'/0/0):", idx);
gtk_label_set_text(GTK_LABEL(label), text);
}
/* ── Serial device enumeration callback ───────────────────────── */
#if defined(NOSTR_ENABLE_NSIGNER_CLIENT)
@@ -1092,7 +1115,7 @@ int login_dialog_run(GtkWindow *parent, login_result_t *result) {
/* Create dialog without auto-responding buttons — we add custom
* buttons so we control whether the dialog closes. */
GtkWidget *dialog = gtk_dialog_new();
gtk_window_set_title(GTK_WINDOW(dialog), "sovereign browser — Sign In");
gtk_window_set_title(GTK_WINDOW(dialog), "sovereign browser " SB_VERSION);
gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
if (parent) gtk_window_set_transient_for(GTK_WINDOW(dialog), parent);
gtk_window_set_default_size(GTK_WINDOW(dialog), 560, 380);
+5 -1
View File
@@ -3984,7 +3984,11 @@ static void on_sovereign_scheme(WebKitURISchemeRequest *request,
* nostr bridge shim calls to avoid terminal spam. */
if (strncmp(uri, "sovereign://agents/status", 25) != 0 &&
strncmp(uri, "sovereign://agents/messages", 27) != 0 &&
strncmp(uri, "sovereign://nostr/", 18) != 0) {
strncmp(uri, "sovereign://nostr/", 18) != 0 &&
strncmp(uri, "sovereign://processes/list", 25) != 0 &&
strncmp(uri, "sovereign://processes/tabs", 25) != 0 &&
strncmp(uri, "sovereign://processes/probe-report", 33) != 0 &&
strncmp(uri, "sovereign://processes/tab_probe", 30) != 0) {
g_print("[bridge] %s\n", uri);
}
+43 -1
View File
@@ -128,6 +128,39 @@ static const char *NOSTR_SHIM_JS =
" }\n"
"})();\n";
/* Wheel-scroll workaround for WebKitGTK.
*
* On some pages (e.g. jumble.social) WebKitGTK dispatches JS wheel events
* but does not perform the native scroll action — so the scrollbar works
* (native GDK scroll) but the mouse wheel doesn't scroll the page. This
* script installs a non-passive wheel listener on window that, when the
* page's main scroll container is the document (the common case), calls
* preventDefault() and manually scrolls by the wheel delta. It only
* intervenes when the target is the document/body/html or an element with
* no own scroll (overflow: visible), so nested scroll containers (which
* WebKitGTK handles correctly) are not broken. */
static const char *WHEEL_SCROLL_JS =
"(function(){\n"
" 'use strict';\n"
" function isMainScrollTarget(el){\n"
" if(!el) return false;\n"
" if(el===document||el===document.documentElement||el===document.body) return true;\n"
" var cs=getComputedStyle(el);\n"
" return (cs.overflowY==='visible'||cs.overflowY==='') && el.scrollHeight<=el.clientHeight;\n"
" }\n"
" window.addEventListener('wheel',function(e){\n"
" if(e.defaultPrevented) return;\n"
" if(!isMainScrollTarget(e.target)) return;\n"
" var html=document.documentElement;\n"
" if(html.scrollHeight<=html.clientHeight) return;\n"
" e.preventDefault();\n"
" var dy=e.deltaY;\n"
" if(e.deltaMode===1) dy*=40;\n"
" else if(e.deltaMode===2) dy*=html.clientHeight;\n"
" window.scrollBy(0,dy);\n"
" },{capture:false,passive:false});\n"
"})();\n";
void nostr_inject_setup(WebKitWebView *webview) {
WebKitUserContentManager *manager =
webkit_web_view_get_user_content_manager(webview);
@@ -148,5 +181,14 @@ void nostr_inject_setup(WebKitWebView *webview) {
/* The manager takes ownership of the script. */
webkit_user_script_unref(script);
g_print("[inject] window.nostr shim installed\n");
/* Wheel-scroll workaround — see WHEEL_SCROLL_JS comment above. */
WebKitUserScript *wheel = webkit_user_script_new(
WHEEL_SCROLL_JS,
WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START,
NULL,
NULL
);
webkit_user_content_manager_add_script(manager, wheel);
webkit_user_script_unref(wheel);
}
-1
View File
@@ -98,5 +98,4 @@ void perf_probe_setup(WebKitWebView *webview, int tab_index) {
webkit_user_script_unref(probe_script);
g_free(probe_js);
g_print("[perf-probe] perf probe installed for tab %d\n", tab_index);
}
-2
View File
@@ -347,8 +347,6 @@ static cJSON *build_proc_obj(guint pid, proc_own_t own,
double djiffies = (double)(cur_jiffies - s->prev_jiffies);
cpu_pct = (djiffies / (double)clk_tck()) / dt * 100.0;
if (cpu_pct < 0) cpu_pct = 0;
/* Cap at 100 * cpu_count to avoid absurd values; we report
* per-process so >100 is valid on multicore. */
}
}
s->prev_jiffies = cur_jiffies;
+95 -2
View File
@@ -176,6 +176,10 @@ static gboolean on_window_focus_in(GtkWidget *widget,
gpointer user_data);
static void on_aux_window_destroy(GtkWidget *widget,
gpointer user_data);
static void on_notebook_switch_page(GtkNotebook *notebook,
GtkWidget *page,
guint page_num,
gpointer user_data);
/* Forward declarations for signal handlers used by tab_manager_new_window
* (which is defined before these handlers in the file). */
static void on_load_changed(WebKitWebView *webview,
@@ -1208,6 +1212,11 @@ static GtkWidget *tab_manager_new_window(const char *url,
* stays pointing at the main window's image). */
setup_notebook_action_widgets(notebook, FALSE);
/* Grab focus on the webview when a tab is switched to (same as the
* main notebook) so wheel/scroll events work in auxiliary windows. */
g_signal_connect(notebook, "switch-page",
G_CALLBACK(on_notebook_switch_page), NULL);
/* Build a window-level GtkPaned: left = sidebar container, right =
* notebook. The sidebar is per-window (not per-tab), so it persists
* across tab switches within this window. Hidden by default. */
@@ -2644,6 +2653,56 @@ static void bookmark_bar_refresh_all(void *user_data) {
}
}
/* Grab GTK focus on the webview when the user clicks anywhere in it.
* Also called from the notebook "switch-page" handler so the webview
* gets focus whenever its tab becomes active.
*
* Without this, the webview frequently doesn't have GTK focus (the URL
* entry grabs it on new-tab, toolbar buttons steal it, etc.), and
* WebKitGTK then drops mouse-wheel scroll events because the webview
* isn't the GTK focus widget the scrollbar still works (native GDK
* scroll) but the wheel doesn't. This is especially visible on pages
* like Jumble that have focusable sidebar elements. */
static void webview_grab_focus(WebKitWebView *webview) {
if (webview == NULL) return;
GtkWidget *w = GTK_WIDGET(webview);
if (!gtk_widget_has_focus(w)) {
gtk_widget_grab_focus(w);
}
}
static gboolean on_webview_button_press(GtkWidget *widget,
GdkEventButton *event,
gpointer user_data) {
(void)event;
(void)user_data;
if (widget != NULL && !gtk_widget_has_focus(widget)) {
gtk_widget_grab_focus(widget);
}
return FALSE;
}
/* notebook "switch-page" handler: grab focus on the newly-selected tab's
* webview so wheel/scroll events work without the user having to click
* the page first. The page_num is the notebook page index; we map it to
* our tab array via the tab_info_t stored on the page widget. */
static void on_notebook_switch_page(GtkNotebook *notebook,
GtkWidget *page,
guint page_num,
gpointer user_data) {
(void)notebook;
(void)page_num;
(void)user_data;
if (page == NULL) return;
/* The page widget is the tab_info_t->page GtkBox. The webview is
* packed into it as a child. Find the webview by walking children. */
tab_info_t *tab = (tab_info_t *)g_object_get_data(G_OBJECT(page),
"tab-info");
if (tab != NULL && tab->webview != NULL) {
webview_grab_focus(tab->webview);
}
}
static tab_info_t *tab_create(const char *url) {
const browser_settings_t *s = settings_get();
@@ -2723,8 +2782,25 @@ static tab_info_t *tab_create(const char *url) {
g_signal_connect(G_OBJECT(tab->webview), "key-press-event",
G_CALLBACK(on_key_press), NULL);
/* Grab GTK focus on button-press so the webview receives wheel/scroll
* events. Without this, clicking a focusable element inside the page
* (e.g. a sidebar button) can leave GTK focus on a sibling widget (URL
* entry, toolbar button), and WebKitGTK then drops mouse-wheel events
* because the webview isn't the GTK focus widget the scrollbar still
* works (it's a native GDK scroll on the widget) but the wheel doesn't.
* Grabbing focus on click fixes wheel scrolling on pages like Jumble
* that have focusable sidebar elements. We return FALSE so WebKit still
* receives the click normally. */
g_signal_connect(G_OBJECT(tab->webview), "button-press-event",
G_CALLBACK(on_webview_button_press), NULL);
/* Build the per-tab page: toolbar on top, webview below. */
tab->page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
/* Store the tab_info_t pointer on the page widget so the notebook
* "switch-page" handler (on_notebook_switch_page) can find the webview
* to grab focus. Uses a weak ref (no destroy notify) the tab_info_t
* is freed separately in tab_manager_close_tab. */
g_object_set_data(G_OBJECT(tab->page), "tab-info", tab);
GtkWidget *toolbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
gtk_widget_set_margin_top(toolbar, 4);
@@ -2888,8 +2964,21 @@ static tab_info_t *tab_create(const char *url) {
g_signal_connect(tab->webview, "create",
G_CALLBACK(on_create_webview), tab);
/* Load the URL. */
webkit_web_view_load_uri(tab->webview, default_url);
/* Load the URL.
*
* IMPORTANT: When this webview is being created in response to the
* "create" signal (target="_blank" / window.open()), g_target_related_view
* is set. In that case WebKit already has a pending NavigationAction with
* WindowFeatures that it will apply to the returned webview itself. If we
* call webkit_web_view_load_uri() here first, our load races with WebKit's
* own navigation setup and the std::optional<WindowFeatures> never gets
* engaged leading to the assertion crash:
* _M_get() const: Assertion 'this->_M_is_engaged()' failed.
* So we skip the explicit load and let WebKit drive the navigation. The
* tab's URL bar / title were already initialized from default_url above. */
if (g_target_related_view == NULL) {
webkit_web_view_load_uri(tab->webview, default_url);
}
g_free(default_url);
return tab;
@@ -3347,6 +3436,10 @@ void tab_manager_init(GtkContainer *parent,
* keep targeting the auxiliary window's tab. */
g_signal_connect(window, "focus-in-event",
G_CALLBACK(on_window_focus_in), g_notebook);
/* Grab focus on the webview when a tab is switched to, so wheel/scroll
* events work without the user having to click the page first. */
g_signal_connect(g_notebook, "switch-page",
G_CALLBACK(on_notebook_switch_page), NULL);
g_active_window = window;
g_active_notebook = g_notebook;
g_active_ws = &g_main_window;
+2 -2
View File
@@ -11,9 +11,9 @@
#ifndef SOVEREIGN_BROWSER_VERSION_H
#define SOVEREIGN_BROWSER_VERSION_H
#define SB_VERSION "v0.0.47"
#define SB_VERSION "v0.0.54"
#define SB_VERSION_MAJOR 0
#define SB_VERSION_MINOR 0
#define SB_VERSION_PATCH 47
#define SB_VERSION_PATCH 54
#endif /* SOVEREIGN_BROWSER_VERSION_H */
+4 -12
View File
@@ -75,21 +75,13 @@ code { font-family: var(--font); background: var(--border); padding: 1px 4px; bo
text-overflow: ellipsis;
}
/* Heat bar for CPU columns. A colored background behind the numeric
* value: green < 10%, yellow < 50%, red >= 50%. */
/* CPU values: plain text, no shaded background. Red only for >= 90%,
* otherwise the default foreground color. */
.heat {
display: inline-block;
min-width: 48px;
padding: 1px 6px;
border-radius: 3px;
text-align: right;
color: var(--bg);
font-weight: bold;
color: var(--fg);
}
.heat.cool { background: #4caf50; color: #fff; }
.heat.warm { background: #ff9800; color: #fff; }
.heat.hot { background: #f44336; color: #fff; }
.heat.cold { background: var(--border); color: var(--muted); }
.heat.hot { color: var(--accent); }
/* Renderer row: clickable to expand hosted tabs. */
.renderer-row { cursor: pointer; }
+8 -10
View File
@@ -63,14 +63,13 @@ function fmtUptime(sec) {
}
function heatClass(pct) {
if (pct == null || pct <= 0) return 'cold';
if (pct < 10) return 'cool';
if (pct < 50) return 'warm';
return 'hot';
if (pct == null) return '';
if (pct >= 90) return 'hot';
return '';
}
function heatHtml(pct) {
if (pct == null) return '<span class="heat cold">—</span>';
if (pct == null) return '<span class="heat">—</span>';
return '<span class="heat ' + heatClass(pct) + '">' +
pct.toFixed(1) + '</span>';
}
@@ -144,8 +143,7 @@ function renderProcTable() {
var tabsCount = (p.hosted_tabs || []).length;
var tabsCell;
if (isRenderer) {
tabsCell = '<span class="heat ' + (tabsCount ? 'cool' : 'cold') +
'">' + tabsCount + '</span>';
tabsCell = String(tabsCount);
} else {
tabsCell = '—';
}
@@ -194,7 +192,7 @@ function renderProcTable() {
async function fetchProcesses() {
try {
var data = await sovereignGet('sovereign://processes/list');
var data = await sovereignGet('sovereign://processes/list?_t=' + Date.now());
gProcData = Array.isArray(data) ? data : [];
renderProcTable();
} catch (e) {
@@ -272,7 +270,7 @@ function renderTabsTable() {
esc(t.title || '(untitled)') + '</span>' +
(t.is_internal ? ' (internal)' : '') +
'<br><span class="url-sub">' + esc(t.url) + '</span></td>' +
'<td>' + (t.is_internal ? '<span class="heat cold">—</span>'
'<td>' + (t.is_internal ? '<span class="heat">—</span>'
: heatHtml(busy)) + '</td>' +
'<td>' + probeField(t, 'fps') + '</td>' +
'<td>' + probeField(t, 'timer_count') + '</td>' +
@@ -293,7 +291,7 @@ function renderTabsTable() {
async function fetchTabs() {
try {
var data = await sovereignGet('sovereign://processes/tabs');
var data = await sovereignGet('sovereign://processes/tabs?_t=' + Date.now());
gTabsData = Array.isArray(data) ? data : [];
renderTabsTable();
// If a tab is selected, refresh its drill-down too.