v0.0.6 - Fix Ctrl+Shift+M toolbar toggle (uppercase keyval) and add Toggle Toolbars to hamburger and page context menus
This commit is contained in:
Generated
+3
-2
@@ -3055,8 +3055,8 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sovereign-browser"
|
||||
version = "0.0.4"
|
||||
name = "sovereign_browser"
|
||||
version = "0.0.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
@@ -3064,6 +3064,7 @@ dependencies = [
|
||||
"cairo-rs",
|
||||
"chrono",
|
||||
"env_logger",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"gdk",
|
||||
"gdk-pixbuf",
|
||||
|
||||
+3
-2
@@ -5,8 +5,8 @@ members = [
|
||||
]
|
||||
|
||||
[package]
|
||||
name = "sovereign-browser"
|
||||
version = "0.0.5"
|
||||
name = "sovereign_browser"
|
||||
version = "0.0.6"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
description = "A Linux x86 web browser built on WebKitGTK with Nostr identity"
|
||||
@@ -32,6 +32,7 @@ pango = "0.18"
|
||||
# ── Async Runtime ───────────────────────────────────────────────────
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
futures-util = "0.3"
|
||||
futures-channel = "0.3"
|
||||
|
||||
# ── Serialization ───────────────────────────────────────────────────
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
||||
@@ -45,7 +45,7 @@ register_cleanup() {
|
||||
|
||||
# Gitea repo for release uploads (derived from the origin remote).
|
||||
GITEA_API_URL="https://git.laantungir.net/api/v1/repos/laantungir/sovereign_browser_rust"
|
||||
BIN_NAME="sovereign-browser"
|
||||
BIN_NAME="sovereign_browser"
|
||||
RELEASE_BIN_PATH="target/release/${BIN_NAME}"
|
||||
|
||||
# --- Args ---------------------------------------------------------------
|
||||
|
||||
Executable
+118
@@ -0,0 +1,118 @@
|
||||
#!/bin/bash
|
||||
# install_local.sh — Build sovereign_browser from local source and install to
|
||||
# /usr/local/bin (or $INSTALL_PREFIX/bin).
|
||||
#
|
||||
# This is a lightweight alternative to install.sh for developers who already
|
||||
# have the build environment set up. It does NOT install apt dependencies,
|
||||
# FIPS, or Tor — it just runs `cargo build --release` and copies the binary.
|
||||
#
|
||||
# Usage:
|
||||
# ./install_local.sh # build + install to /usr/local
|
||||
# INSTALL_PREFIX=/opt ./install_local.sh # install elsewhere
|
||||
# ./install_local.sh --prefix /opt # same
|
||||
# ./install_local.sh --help # show help
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
INSTALL_PREFIX="${INSTALL_PREFIX:-/usr/local}"
|
||||
|
||||
# --- Output helpers ------------------------------------------------------
|
||||
|
||||
if [[ -t 2 && -z "${NO_COLOR:-}" ]]; then
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
else
|
||||
RED='' GREEN='' YELLOW='' BLUE='' NC=''
|
||||
fi
|
||||
|
||||
print_info() { echo -e "${BLUE}[INFO]${NC} $*" >&2; }
|
||||
print_success() { echo -e "${GREEN}[SUCCESS]${NC} $*" >&2; }
|
||||
print_warning() { echo -e "${YELLOW}[WARNING]${NC} $*" >&2; }
|
||||
print_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
|
||||
die() { print_error "$*"; exit 1; }
|
||||
|
||||
# --- Privilege helper ----------------------------------------------------
|
||||
# Returns "sudo" if the target path is not user-writable, else "".
|
||||
sudo_for() {
|
||||
local target="$1"
|
||||
if [[ -w "$target" ]]; then
|
||||
echo ""
|
||||
else
|
||||
command -v sudo >/dev/null 2>&1 || die "sudo is required to write to $target but is not available."
|
||||
echo "sudo"
|
||||
fi
|
||||
}
|
||||
|
||||
# --- Usage ---------------------------------------------------------------
|
||||
|
||||
show_usage() {
|
||||
cat <<'EOF'
|
||||
install_local.sh — Build sovereign_browser from local source and install
|
||||
|
||||
Usage: ./install_local.sh [options]
|
||||
|
||||
Options:
|
||||
--prefix <dir> Install prefix (default: /usr/local)
|
||||
-h, --help Show this help
|
||||
|
||||
Environment:
|
||||
INSTALL_PREFIX Alternative to --prefix (default: /usr/local)
|
||||
EOF
|
||||
}
|
||||
|
||||
# --- Args ----------------------------------------------------------------
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--prefix) INSTALL_PREFIX="${2:-}"; shift 2 ;;
|
||||
-h|--help) show_usage; exit 0 ;;
|
||||
*) die "Unknown option: $1 (try --help)" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# --- Config --------------------------------------------------------------
|
||||
# Resolve the project root as the directory containing this script.
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
BIN_NAME="sovereign_browser"
|
||||
BUILT_BIN="target/release/${BIN_NAME}"
|
||||
|
||||
# --- Build ---------------------------------------------------------------
|
||||
|
||||
print_info "Building ${BIN_NAME} from local source (release)..."
|
||||
if ! cargo build --release; then
|
||||
die "Build failed. Ensure build dependencies are installed (see README)."
|
||||
fi
|
||||
|
||||
[[ -x "$BUILT_BIN" ]] || die "Build finished but ${BUILT_BIN} was not produced."
|
||||
|
||||
print_success "Build complete."
|
||||
|
||||
# --- Install binary ------------------------------------------------------
|
||||
|
||||
prefix_bin="$INSTALL_PREFIX/bin"
|
||||
SUDO="$(sudo_for "$INSTALL_PREFIX")"
|
||||
|
||||
print_info "Installing binary to $prefix_bin/${BIN_NAME}"
|
||||
$SUDO mkdir -p "$prefix_bin"
|
||||
$SUDO cp -f "$BUILT_BIN" "$prefix_bin/${BIN_NAME}"
|
||||
$SUDO chmod +x "$prefix_bin/${BIN_NAME}"
|
||||
print_success "Binary installed: $prefix_bin/${BIN_NAME}"
|
||||
|
||||
# --- Done ----------------------------------------------------------------
|
||||
|
||||
print_success "${BIN_NAME} installed to $prefix_bin"
|
||||
if [[ ":${PATH}:" != *":$prefix_bin:"* ]]; then
|
||||
print_warning "$prefix_bin is not in your PATH. Add it: export PATH=\"$prefix_bin:\$PATH\""
|
||||
fi
|
||||
|
||||
cat >&2 <<EOF
|
||||
${GREEN}[SUCCESS]${NC} Next steps:
|
||||
Run: ${BIN_NAME}
|
||||
Or: ${BIN_NAME} --login-method generate
|
||||
Or: ${BIN_NAME} --login-method generate --url https://example.com
|
||||
EOF
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# run.sh - Build (only if needed) and run the sovereign-browser binary.
|
||||
# run.sh - Build (only if needed) and run the sovereign_browser binary.
|
||||
#
|
||||
# Usage:
|
||||
# ./run.sh [args...] # build if stale, then run with args
|
||||
@@ -28,7 +28,7 @@ print_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; }
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
BIN_NAME="sovereign-browser"
|
||||
BIN_NAME="sovereign_browser"
|
||||
BIN_PATH="target/release/${BIN_NAME}"
|
||||
|
||||
# --- Args ---------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
//! Browser-level keyboard shortcut dispatch.
|
||||
//!
|
||||
//! Port of the `on_key_press` handler from the C project (`main.c:501`).
|
||||
//!
|
||||
//! In the C version this handler is connected to **two** places:
|
||||
//! 1. Each webview's `key-press-event` (in `tab_manager.c` `tab_create()`)
|
||||
//! 2. The main window's `key-press-event` (in `main.c` setup)
|
||||
//!
|
||||
//! The webview connection is required because WebKit consumes key events
|
||||
//! when the webview has focus, so a window-only handler never sees them.
|
||||
//! The window connection is a fallback that fires after the webview handler
|
||||
//! (event propagation: child → parent → window).
|
||||
//!
|
||||
//! The `from_webview` flag distinguishes the two connections so that
|
||||
//! `sovereign://` internal pages (e.g. the settings page's shortcut
|
||||
//! capture) can receive raw key events on the webview connection while the
|
||||
//! window connection still processes browser shortcuts.
|
||||
|
||||
use webkit2gtk::*;
|
||||
|
||||
use crate::tab_manager;
|
||||
|
||||
/// Build a WebContext configured for the currently signed-in user.
|
||||
/// Port of `build_context_for_current_user` (main.c).
|
||||
pub fn build_context_for_current_user() -> WebContext {
|
||||
let ctx = crate::web_context::web_context_create();
|
||||
let pubkey_hex = crate::menu::app_get_pubkey_hex();
|
||||
let signer = crate::menu::app_get_signer();
|
||||
let readonly = crate::menu::app_get_readonly();
|
||||
if !pubkey_hex.is_empty() {
|
||||
crate::webkit_data::webkit_data_configure(&ctx, &pubkey_hex);
|
||||
}
|
||||
crate::nostr_bridge::nostr_bridge_register(&ctx, signer, &pubkey_hex, readonly);
|
||||
ctx
|
||||
}
|
||||
|
||||
/// Handle a key-press event, dispatching browser-level shortcuts.
|
||||
///
|
||||
/// `from_webview` must be `true` when connected to a WebView's
|
||||
/// `key-press-event` and `false` when connected to the window's.
|
||||
/// Mirrors the C version (`main.c:501`).
|
||||
pub fn handle_key_press(event: &gdk::EventKey, from_webview: bool) -> glib::Propagation {
|
||||
let ctrl = event.state().contains(gdk::ModifierType::CONTROL_MASK);
|
||||
let shift = event.state().contains(gdk::ModifierType::SHIFT_MASK);
|
||||
let alt = event.state().contains(gdk::ModifierType::MOD1_MASK);
|
||||
// Normalize the keyval to lowercase. When Shift is held, GDK delivers
|
||||
// the uppercase keyval (e.g. `M` for Ctrl+Shift+M), which would never
|
||||
// match the lowercase `constants::m`. Mirrors the C version's
|
||||
// `gdk_keyval_to_lower(event->keyval)` (shortcuts.c:199).
|
||||
let key = event.keyval().to_lower();
|
||||
|
||||
// When the active tab is showing a sovereign:// internal page (e.g.
|
||||
// the settings page's keyboard-shortcut capture), bypass shortcut
|
||||
// interception on the webview connection only — let the event pass
|
||||
// through to the web page's JS so it can capture arbitrary key combos.
|
||||
// The window-level connection still processes shortcuts so Ctrl+T,
|
||||
// Ctrl+N, etc. work on internal pages. Mirrors main.c:516.
|
||||
if from_webview {
|
||||
let tabs = tab_manager::tab_manager_get_tabs();
|
||||
let active_id = tab_manager::tab_manager_get_active_tab();
|
||||
if let Some(tab) = tabs.iter().find(|t| t.id == active_id) {
|
||||
if tab.url.starts_with("sovereign://") || tab.url.starts_with("about:settings") {
|
||||
return glib::Propagation::Proceed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Alt+Left/Right (back/forward) — no modifier mask check needed.
|
||||
if alt && !ctrl && !shift {
|
||||
match key {
|
||||
gdk::keys::constants::Left => {
|
||||
tab_manager::tab_manager_go_back();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::Right => {
|
||||
tab_manager::tab_manager_go_forward();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// F-key shortcuts (no modifiers).
|
||||
if !ctrl && !shift && !alt {
|
||||
match key {
|
||||
gdk::keys::constants::F5 => {
|
||||
tab_manager::tab_manager_reload_active();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::F11 => {
|
||||
tab_manager::tab_manager_toggle_fullscreen();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Shift+F5 (force reload).
|
||||
if shift && !ctrl && !alt && key == gdk::keys::constants::F5 {
|
||||
if let Some(nb) = tab_manager::tab_manager_get_active_notebook() {
|
||||
if let Some(wv) = tab_manager::tab_manager_get_active_webview(&nb) {
|
||||
wv.reload_bypass_cache();
|
||||
}
|
||||
}
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
|
||||
// Ctrl+ key combinations.
|
||||
if ctrl && !shift && !alt {
|
||||
match key {
|
||||
gdk::keys::constants::t => {
|
||||
if let Some(nb) = tab_manager::tab_manager_get_active_notebook() {
|
||||
let ctx = build_context_for_current_user();
|
||||
tab_manager::tab_manager_new_tab(&nb, &ctx, None);
|
||||
}
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::n => {
|
||||
tab_manager::tab_manager_new_window_blank();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::o => {
|
||||
tab_manager::tab_manager_open_file();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::w => {
|
||||
tab_manager::tab_manager_close_active();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::l => {
|
||||
tab_manager::tab_manager_focus_url();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::r => {
|
||||
tab_manager::tab_manager_reload_active();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::f => {
|
||||
if let Some(nb) = tab_manager::tab_manager_get_active_notebook() {
|
||||
if let Some(wv) = tab_manager::tab_manager_get_active_webview(&nb) {
|
||||
// WebKit find controller.
|
||||
// run_javascript is deprecated in webkit2gtk 2.0.2 but
|
||||
// there is no replacement in the Rust bindings. Suppress.
|
||||
#[allow(deprecated)]
|
||||
wv.run_javascript("document.body.innerHTML += '<div id=\"sb-find-bar\" style=\"position:fixed;top:0;left:0;right:0;z-index:9999;background:#333;color:#fff;padding:8px;\"><input id=\"sb-find-input\" type=\"text\" placeholder=\"Find in page…\" style=\"width:80%;padding:4px;\"></div>'; document.getElementById('sb-find-input').focus();", None::<&gio::Cancellable>, |_| {});
|
||||
}
|
||||
}
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::d => {
|
||||
// Bookmark current page.
|
||||
if let Some(nb) = tab_manager::tab_manager_get_active_notebook() {
|
||||
if let Some(wv) = tab_manager::tab_manager_get_active_webview(&nb) {
|
||||
let url = wv.uri().unwrap_or_default().to_string();
|
||||
let title = wv.title().unwrap_or_default().to_string();
|
||||
let _ = crate::db::db_add_bookmark(&title, &url, "");
|
||||
}
|
||||
}
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::h => {
|
||||
tab_manager::tab_manager_open_internal("sovereign://profile");
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::b => {
|
||||
tab_manager::tab_manager_open_internal("sovereign://bookmarks");
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::j => {
|
||||
tab_manager::tab_manager_open_internal("sovereign://processes");
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::q => {
|
||||
crate::session::session_save();
|
||||
gtk::main_quit();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::comma => {
|
||||
tab_manager::tab_manager_open_internal("sovereign://settings");
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::Tab => {
|
||||
tab_manager::tab_manager_next();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::Page_Down => {
|
||||
tab_manager::tab_manager_next();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::Page_Up => {
|
||||
tab_manager::tab_manager_prev();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::plus | gdk::keys::constants::equal => {
|
||||
tab_manager::tab_manager_zoom_in();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::minus => {
|
||||
tab_manager::tab_manager_zoom_out();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::_0 => {
|
||||
tab_manager::tab_manager_zoom_reset();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::Shift_L | gdk::keys::constants::Shift_R => {
|
||||
return glib::Propagation::Proceed;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Ctrl+Shift+ key combinations.
|
||||
if ctrl && shift && !alt {
|
||||
match key {
|
||||
gdk::keys::constants::Tab => {
|
||||
tab_manager::tab_manager_prev();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::i => {
|
||||
tab_manager::tab_manager_toggle_inspector();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::a => {
|
||||
tab_manager::tab_manager_toggle_sidebar();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::m => {
|
||||
tab_manager::tab_manager_toggle_toolbars();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::u => {
|
||||
crate::menu::menu_switch_identity();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::Escape => {
|
||||
tab_manager::tab_manager_open_internal("sovereign://processes");
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
glib::Propagation::Proceed
|
||||
}
|
||||
@@ -43,6 +43,7 @@ pub mod agent_llm;
|
||||
pub mod db;
|
||||
pub mod settings;
|
||||
pub mod tab_manager;
|
||||
pub mod key_handler;
|
||||
pub mod menu;
|
||||
pub mod session;
|
||||
pub mod bookmarks;
|
||||
|
||||
+9
-207
@@ -34,6 +34,7 @@ mod agent_llm;
|
||||
mod db;
|
||||
mod settings;
|
||||
mod tab_manager;
|
||||
mod key_handler;
|
||||
mod session;
|
||||
mod bookmarks;
|
||||
mod history;
|
||||
@@ -290,211 +291,10 @@ fn do_cli_login(args: &cli::CliArgs) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a WebContext configured for the currently signed-in user.
|
||||
/// Delegates to the shared implementation in `key_handler`.
|
||||
fn build_context_for_current_user() -> WebContext {
|
||||
let ctx = web_context::web_context_create();
|
||||
let pubkey_hex = menu::app_get_pubkey_hex();
|
||||
let signer = menu::app_get_signer();
|
||||
let readonly = menu::app_get_readonly();
|
||||
if !pubkey_hex.is_empty() {
|
||||
webkit_data::webkit_data_configure(&ctx, &pubkey_hex);
|
||||
}
|
||||
nostr_bridge::nostr_bridge_register(&ctx, signer, &pubkey_hex, readonly);
|
||||
ctx
|
||||
}
|
||||
|
||||
/// Handle a key-press event, dispatching browser-level shortcuts.
|
||||
/// Mirrors the C version (main.c) with all shortcuts.
|
||||
fn handle_key_press(_window: >k::Window, event: &gdk::EventKey) -> glib::Propagation {
|
||||
let ctrl = event.state().contains(gdk::ModifierType::CONTROL_MASK);
|
||||
let shift = event.state().contains(gdk::ModifierType::SHIFT_MASK);
|
||||
let alt = event.state().contains(gdk::ModifierType::MOD1_MASK);
|
||||
let key = event.keyval();
|
||||
|
||||
// When the active tab is showing a sovereign:// internal page (e.g.
|
||||
// the settings page's keyboard-shortcut capture), bypass shortcut
|
||||
// interception so the page's JS can capture arbitrary key combos.
|
||||
let tabs = tab_manager::tab_manager_get_tabs();
|
||||
let active_id = tab_manager::tab_manager_get_active_tab();
|
||||
if let Some(tab) = tabs.iter().find(|t| t.id == active_id) {
|
||||
if tab.url.starts_with("sovereign://") || tab.url.starts_with("about:settings") {
|
||||
return glib::Propagation::Proceed;
|
||||
}
|
||||
}
|
||||
|
||||
// Alt+Left/Right (back/forward) — no modifier mask check needed.
|
||||
if alt && !ctrl && !shift {
|
||||
match key {
|
||||
gdk::keys::constants::Left => {
|
||||
tab_manager::tab_manager_go_back();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::Right => {
|
||||
tab_manager::tab_manager_go_forward();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// F-key shortcuts (no modifiers).
|
||||
if !ctrl && !shift && !alt {
|
||||
match key {
|
||||
gdk::keys::constants::F5 => {
|
||||
tab_manager::tab_manager_reload_active();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::F11 => {
|
||||
tab_manager::tab_manager_toggle_fullscreen();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Shift+F5 (force reload).
|
||||
if shift && !ctrl && !alt && key == gdk::keys::constants::F5 {
|
||||
if let Some(nb) = tab_manager::tab_manager_get_active_notebook() {
|
||||
if let Some(wv) = tab_manager::tab_manager_get_active_webview(&nb) {
|
||||
wv.reload_bypass_cache();
|
||||
}
|
||||
}
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
|
||||
// Ctrl+ key combinations.
|
||||
if ctrl && !shift && !alt {
|
||||
match key {
|
||||
gdk::keys::constants::t => {
|
||||
if let Some(nb) = tab_manager::tab_manager_get_active_notebook() {
|
||||
let ctx = build_context_for_current_user();
|
||||
tab_manager::tab_manager_new_tab(&nb, &ctx, None);
|
||||
}
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::n => {
|
||||
tab_manager::tab_manager_new_window_blank();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::o => {
|
||||
tab_manager::tab_manager_open_file();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::w => {
|
||||
tab_manager::tab_manager_close_active();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::l => {
|
||||
tab_manager::tab_manager_focus_url();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::r => {
|
||||
tab_manager::tab_manager_reload_active();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::f => {
|
||||
if let Some(nb) = tab_manager::tab_manager_get_active_notebook() {
|
||||
if let Some(wv) = tab_manager::tab_manager_get_active_webview(&nb) {
|
||||
// WebKit find controller.
|
||||
wv.run_javascript("document.body.innerHTML += '<div id=\"sb-find-bar\" style=\"position:fixed;top:0;left:0;right:0;z-index:9999;background:#333;color:#fff;padding:8px;\"><input id=\"sb-find-input\" type=\"text\" placeholder=\"Find in page…\" style=\"width:80%;padding:4px;\"></div>'; document.getElementById('sb-find-input').focus();", None::<&gio::Cancellable>, |_| {});
|
||||
}
|
||||
}
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::d => {
|
||||
// Bookmark current page.
|
||||
if let Some(nb) = tab_manager::tab_manager_get_active_notebook() {
|
||||
if let Some(wv) = tab_manager::tab_manager_get_active_webview(&nb) {
|
||||
let url = wv.uri().unwrap_or_default().to_string();
|
||||
let title = wv.title().unwrap_or_default().to_string();
|
||||
let _ = db::db_add_bookmark(&title, &url, "");
|
||||
}
|
||||
}
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::h => {
|
||||
tab_manager::tab_manager_open_internal("sovereign://profile");
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::b => {
|
||||
tab_manager::tab_manager_open_internal("sovereign://bookmarks");
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::j => {
|
||||
tab_manager::tab_manager_open_internal("sovereign://processes");
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::q => {
|
||||
session::session_save();
|
||||
gtk::main_quit();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::comma => {
|
||||
tab_manager::tab_manager_open_internal("sovereign://settings");
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::Tab => {
|
||||
tab_manager::tab_manager_next();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::Page_Down => {
|
||||
tab_manager::tab_manager_next();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::Page_Up => {
|
||||
tab_manager::tab_manager_prev();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::plus | gdk::keys::constants::equal => {
|
||||
tab_manager::tab_manager_zoom_in();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::minus => {
|
||||
tab_manager::tab_manager_zoom_out();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::_0 => {
|
||||
tab_manager::tab_manager_zoom_reset();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::Shift_L | gdk::keys::constants::Shift_R => {
|
||||
return glib::Propagation::Proceed;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Ctrl+Shift+ key combinations.
|
||||
if ctrl && shift && !alt {
|
||||
match key {
|
||||
gdk::keys::constants::Tab => {
|
||||
tab_manager::tab_manager_prev();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::i => {
|
||||
tab_manager::tab_manager_toggle_inspector();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::a => {
|
||||
tab_manager::tab_manager_toggle_sidebar();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::m => {
|
||||
tab_manager::tab_manager_toggle_toolbars();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::u => {
|
||||
crate::menu::menu_switch_identity();
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
gdk::keys::constants::Escape => {
|
||||
tab_manager::tab_manager_open_internal("sovereign://processes");
|
||||
return glib::Propagation::Stop;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
glib::Propagation::Proceed
|
||||
key_handler::build_context_for_current_user()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@@ -597,10 +397,12 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
// Wire keyboard shortcuts.
|
||||
let window_shortcuts = window.clone();
|
||||
// Wire keyboard shortcuts on the window (fallback connection). The
|
||||
// primary connection is on each webview (see tab_manager_new_tab),
|
||||
// because WebKit consumes key events when the webview has focus.
|
||||
// Mirrors the C version (main.c:1054 + tab_manager.c:3076).
|
||||
window.connect_key_press_event(move |_w, event| {
|
||||
handle_key_press(&window_shortcuts, event)
|
||||
key_handler::handle_key_press(event, false)
|
||||
});
|
||||
|
||||
window.connect_delete_event(|_, _| {
|
||||
|
||||
+10
-6
@@ -6,6 +6,7 @@
|
||||
use webkit2gtk::*;
|
||||
use gio::MemoryInputStream;
|
||||
use glib::Bytes;
|
||||
use futures_util::StreamExt;
|
||||
use std::thread;
|
||||
|
||||
use crate::nostr_url;
|
||||
@@ -516,17 +517,20 @@ fn handle_nostr_scheme(request: &URISchemeRequest) {
|
||||
// Create job and run in thread
|
||||
let job = SchemeJob::new(entity_str.clone(), decoded);
|
||||
|
||||
// Use glib's MainContext channel to send the result back to the main thread
|
||||
let (tx, rx) = glib::MainContext::channel(glib::Priority::default());
|
||||
// Send the result back to the main thread via an async channel, then
|
||||
// handle it on the main context. (glib::MainContext::channel is
|
||||
// deprecated in favour of async channels + spawn_local.)
|
||||
let (tx, mut rx) = futures_channel::mpsc::unbounded();
|
||||
|
||||
thread::spawn(move || {
|
||||
let completed_job = scheme_worker(job);
|
||||
let _ = tx.send(completed_job);
|
||||
let _ = tx.unbounded_send(completed_job);
|
||||
});
|
||||
|
||||
// Attach the receiver to the main context to handle the response
|
||||
// Handle the response on the main context.
|
||||
let request_clone = request.clone();
|
||||
rx.attach(None, move |completed_job| {
|
||||
glib::MainContext::default().spawn_local(async move {
|
||||
if let Some(completed_job) = rx.next().await {
|
||||
let json = completed_job.response_json.unwrap_or_else(|| {
|
||||
serde_json::json!({
|
||||
"error": "Internal error",
|
||||
@@ -534,7 +538,7 @@ fn handle_nostr_scheme(request: &URISchemeRequest) {
|
||||
}).to_string()
|
||||
});
|
||||
respond_json(&request_clone, &json);
|
||||
glib::ControlFlow::Break
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+70
-6
@@ -238,6 +238,15 @@ pub fn tab_manager_new_tab(notebook: >k::Notebook, ctx: &WebContext, url: Opti
|
||||
ws.set_hardware_acceleration_policy(webkit2gtk::HardwareAccelerationPolicy::Always);
|
||||
}
|
||||
|
||||
// Connect the browser shortcut handler to the webview. This is the
|
||||
// primary connection: WebKit consumes key events when the webview has
|
||||
// focus, so a window-only handler never sees them. Mirrors the C
|
||||
// version (tab_manager.c:3076). The window connection in main.rs is
|
||||
// the fallback.
|
||||
webview.connect_key_press_event(|_wv, event| {
|
||||
crate::key_handler::handle_key_press(event, true)
|
||||
});
|
||||
|
||||
// Build the per-tab page: toolbar on top, webview below.
|
||||
let page = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||
|
||||
@@ -389,6 +398,17 @@ pub fn tab_manager_new_tab(notebook: >k::Notebook, ctx: &WebContext, url: Opti
|
||||
// are not automatically visible.
|
||||
page.show_all();
|
||||
tab_label.show_all();
|
||||
|
||||
// Apply the current chrome visibility so a tab opened while the
|
||||
// toolbars are hidden stays hidden. Mirrors the C version
|
||||
// (tab_manager.c:3235). The tab strip itself is per-notebook
|
||||
// (set_show_tabs), already handled by the toggle; here we sync the
|
||||
// per-tab toolbar + bookmark bar.
|
||||
if !*G_TOOLBARS_VISIBLE.lock().unwrap() {
|
||||
toolbar.set_visible(false);
|
||||
bookmark_bar.set_visible(false);
|
||||
}
|
||||
|
||||
let page_num = notebook.n_pages();
|
||||
notebook.set_current_page(Some(if page_num > 0 { page_num - 1 } else { 0 }));
|
||||
notebook.set_tab_reorderable(&page, s.tab_drag_reorder);
|
||||
@@ -659,7 +679,21 @@ pub fn tab_manager_new_tab(notebook: >k::Notebook, ctx: &WebContext, url: Opti
|
||||
context_menu.insert(&item, 0);
|
||||
}
|
||||
|
||||
// Return false to let WebKit show the default menu (with our item added).
|
||||
// "Toggle Toolbars" — lets the user restore the hidden chrome
|
||||
// (tab strip, toolbar, bookmark bar) from the page context menu,
|
||||
// which is reachable even when the toolbars are hidden.
|
||||
let toggle_action = gio::SimpleAction::new("toggle-toolbars", None);
|
||||
toggle_action.connect_activate(|_, _| {
|
||||
tab_manager_toggle_toolbars();
|
||||
});
|
||||
let toggle_item = webkit2gtk::ContextMenuItem::from_gaction(
|
||||
&toggle_action,
|
||||
"Toggle Toolbars",
|
||||
None,
|
||||
);
|
||||
context_menu.append(&toggle_item);
|
||||
|
||||
// Return false to let WebKit show the default menu (with our items added).
|
||||
false
|
||||
});
|
||||
|
||||
@@ -1307,6 +1341,11 @@ fn build_hamburger_menu() -> gtk::MenuButton {
|
||||
item_sidebar.connect_activate(|_| tab_manager_toggle_sidebar());
|
||||
menu.append(&item_sidebar);
|
||||
|
||||
let item_toolbars = gtk::MenuItem::with_label("Toggle Toolbars");
|
||||
item_toolbars.set_tooltip_text(Some("Show or hide the tab strip, toolbars, and bookmark bars (Ctrl+Shift+M)"));
|
||||
item_toolbars.connect_activate(|_| tab_manager_toggle_toolbars());
|
||||
menu.append(&item_toolbars);
|
||||
|
||||
menu.append(>k::SeparatorMenuItem::new());
|
||||
|
||||
// ── Internal pages ──────────────────────────────────────────────
|
||||
@@ -1493,17 +1532,42 @@ fn create_sidebar_webview(container: >k::Box) {
|
||||
webview.load_uri(AGENT_CHAT_URL);
|
||||
}
|
||||
|
||||
/// Toggle toolbar visibility on the active window.
|
||||
/// Whether the browser chrome (tab strip, toolbars, bookmark bars) is
|
||||
/// currently visible. Mirrors the C `window_state_t.toolbars_visible`
|
||||
/// (tab_manager.c:4322). Starts visible.
|
||||
static G_TOOLBARS_VISIBLE: Lazy<Mutex<bool>> = Lazy::new(|| Mutex::new(true));
|
||||
|
||||
/// Toggle browser chrome visibility on the active window.
|
||||
///
|
||||
/// Mirrors the C `tab_manager_toggle_toolbars()` (tab_manager.c:4318):
|
||||
/// hides/shows the tab strip (GtkNotebook header), the per-tab toolbar,
|
||||
/// and the per-tab bookmark bar, leaving only the page content. Pressing
|
||||
/// the shortcut again restores them.
|
||||
pub fn tab_manager_toggle_toolbars() {
|
||||
if let Some(nb) = tab_manager_get_active_notebook() {
|
||||
let nb = match tab_manager_get_active_notebook() {
|
||||
Some(n) => n,
|
||||
None => return,
|
||||
};
|
||||
|
||||
// Flip the shared visibility flag.
|
||||
let vis = {
|
||||
let mut visible = G_TOOLBARS_VISIBLE.lock().unwrap();
|
||||
*visible = !*visible;
|
||||
*visible
|
||||
};
|
||||
|
||||
// Tab strip (GtkNotebook header) — per-notebook.
|
||||
nb.set_show_tabs(vis);
|
||||
|
||||
// Per-tab toolbar + bookmark bar.
|
||||
let n = nb.n_pages();
|
||||
for i in 0..n {
|
||||
if let Some(page) = nb.nth_page(Some(i)) {
|
||||
if let Ok(box_) = page.downcast::<gtk::Box>() {
|
||||
for child in box_.children() {
|
||||
if child.widget_name() == "main-toolbar" {
|
||||
child.set_visible(!child.is_visible());
|
||||
}
|
||||
let name = child.widget_name();
|
||||
if name == "main-toolbar" || name == "bookmark-bar" {
|
||||
child.set_visible(vis);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
//! Version information for sovereign_browser
|
||||
|
||||
/// The current version of sovereign_browser (with leading 'v').
|
||||
pub const VERSION: &str = "v0.0.5";
|
||||
pub const VERSION: &str = "v0.0.6";
|
||||
|
||||
/// Major version number.
|
||||
pub const VERSION_MAJOR: u32 = 0;
|
||||
@@ -10,4 +10,4 @@ pub const VERSION_MAJOR: u32 = 0;
|
||||
pub const VERSION_MINOR: u32 = 0;
|
||||
|
||||
/// Patch version number.
|
||||
pub const VERSION_PATCH: u32 = 5;
|
||||
pub const VERSION_PATCH: u32 = 6;
|
||||
|
||||
Reference in New Issue
Block a user