Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e26c502ee | ||
|
|
45aa46f6c4 | ||
|
|
480d758241 | ||
|
|
eb093ee16a | ||
|
|
618d52ff69 | ||
|
|
3d32c64b0a | ||
|
|
ca2907b357 | ||
|
|
0892a9c7e1 |
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user