Files

250 lines
9.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Browser Tabs Implementation Plan
## Overview
Add full browser tab support to sovereign_browser. Each tab gets its own
toolbar (URL bar + hamburger menu) and WebKitWebView. All tabs share a single
`WebKitWebContext`, so the `sovereign://` Nostr bridge, security settings, and
TLS policy apply automatically to every tab. Tab behavior is configurable via
a new settings module.
## Architecture
```mermaid
flowchart TB
subgraph Window
Notebook[GtkNotebook — tab strip]
NewTabBtn[New Tab Button +]
end
subgraph TabPage
Toolbar[Toolbar: hamburger + URL entry]
Webview[WebKitWebView]
end
Notebook -->|page 0| TabPage
Notebook -->|page 1| TabPage
Notebook -->|page N| TabPage
TabPage -->|shared| Ctx[WebKitWebContext — sovereign bridge, security, TLS]
Ctx --> NostrBridge[nostr_bridge.c]
Ctx --> NostrInject[nostr_inject.c — per-webview]
TabManager[tab_manager.c] -->|manages| Notebook
Settings[settings.c] -->|configures| TabManager
Session[session.c] -->|save/restore| TabManager
```
### Key design decisions
1. **Per-tab toolbar.** Each notebook page is a vertical `GtkBox` containing
the toolbar (hamburger + URL entry) on top and the `WebKitWebView` below.
This is simpler than a shared toolbar that has to swap signal handlers on
tab switch, and it matches the user's preference.
2. **Shared `WebKitWebContext`.** All `WebKitWebView` instances are created
from the same context. The `sovereign://` URI scheme registration, security
manager flags, and TLS error policy are set once on the context and apply
to all tabs. No per-tab re-registration needed.
3. **Per-webview `nostr_inject_setup()`.** The `window.nostr` injection uses
`WebKitUserContentManager`, which is per-webview. Each new tab calls
`nostr_inject_setup()` on its own webview.
4. **`tab_manager.c` owns the `GtkNotebook`.** It provides functions to
create, close, switch, and query tabs. Each tab is tracked in a struct
holding the webview, URL entry, hamburger button, and tab label widget.
5. **Settings module.** A new `settings.c`/`settings.h` persists user
preferences to `~/.sovereign_browser/settings.conf` (simple key=value
text file). Configurable options:
| Setting | Default | Description |
|---------|---------|-------------|
| `restore_session` | `true` | Restore open tabs on next launch |
| `new_tab_url` | `https://example.com` | URL loaded in new tabs |
| `tab_bar_position` | `top` | `top` / `bottom` / `left` / `right` |
| `show_tab_close_buttons` | `true` | Show per-tab close button |
| `middle_click_close` | `true` | Middle-click on tab closes it |
| `ctrl_tab_switch` | `true` | Ctrl+Tab cycles tabs |
| `max_tabs` | `50` | Maximum simultaneous tabs |
| `tab_drag_reorder` | `true` | Allow drag to reorder tabs |
6. **Session module.** `session.c`/`session.h` saves the list of open tab URLs
(one per line) to `~/.sovereign_browser/session.txt` on window destroy, and
restores them on startup if `settings.restore_session` is true.
## New files
### `src/settings.h` / `src/settings.c`
```c
typedef struct {
gboolean restore_session;
char new_tab_url[512];
int tab_bar_position; /* GTK_POS_TOP, etc. */
gboolean show_tab_close_buttons;
gboolean middle_click_close;
gboolean ctrl_tab_switch;
int max_tabs;
gboolean tab_drag_reorder;
} browser_settings_t;
void settings_load(browser_settings_t *out);
void settings_save(const browser_settings_t *s);
const browser_settings_t *settings_get(void); /* global singleton */
```
### `src/tab_manager.h` / `src/tab_manager.c`
```c
typedef struct {
WebKitWebView *webview;
GtkWidget *url_entry;
GtkWidget *hamburger;
GtkWidget *tab_label; /* composite: icon + title + close btn */
char current_url[2048];
char title[256];
} tab_info_t;
void tab_manager_init(GtkContainer *parent, WebKitWebContext *ctx);
int tab_manager_new_tab(const char *url);
void tab_manager_close_tab(int index);
void tab_manager_close_active(void);
int tab_manager_get_active_index(void);
tab_info_t *tab_manager_get_active(void);
tab_info_t *tab_manager_get(int index);
int tab_manager_count(void);
void tab_manager_set_title(int index, const char *title);
```
### `src/session.h` / `src/session.c`
```c
void session_save(void); /* called on window destroy */
int session_restore(void); /* called at startup, returns tab count created */
```
## Modified files
### `src/main.c` — major refactor
- Remove the single `webview` / `url_entry` / `toolbar` creation.
- Create `WebKitWebContext` once, configure security + `sovereign://` bridge
on it (existing code moves here, unchanged logic).
- Call `tab_manager_init()` with the window's vbox and the shared context.
- The hamburger menu builder moves into `tab_manager.c` (or a shared helper)
since it now needs to reference the per-tab webview, not a global.
- All menu callbacks (`on_menu_reload`, `on_menu_stop`, `on_menu_inspector`,
`on_menu_open_file`, `on_menu_history_item`) fetch the active tab's webview
via `tab_manager_get_active()` instead of receiving it as a parameter.
- `on_load_changed` and `on_load_failed` are connected per-tab inside
`tab_manager_new_tab()` and update the per-tab URL entry and tab title.
- `on_url_activate` loads the URL into the active tab's webview.
- Keyboard shortcuts: a `key-press-event` handler on the main window checks
for Ctrl+T / Ctrl+W / Ctrl+Tab / Ctrl+Shift+Tab / Ctrl+PageUp / Ctrl+PageDown.
- `on_window_destroy` calls `session_save()` before cleanup.
### `src/nostr_inject.c` — no changes
`nostr_inject_setup()` already takes a `WebKitWebView*` and is called
per-webview. The tab manager calls it for each new tab.
### `src/nostr_bridge.c` — no changes
The bridge is registered on the shared `WebKitWebContext`. All webviews on
that context automatically get the `sovereign://` scheme handler.
### `src/history.c` — no changes
History is already global. All tabs contribute to and read from the same
history list.
### `Makefile`
Add `src/settings.c src/tab_manager.c src/session.c` to `SRC`.
## Tab UI layout
```
┌──────────────────────────────────────────────────────┐
│ [Tab 1 ×] [Tab 2 ×] [Tab 3 ×] [+] │ ← GtkNotebook tab strip
├──────────────────────────────────────────────────────┤
│ [☰] https://example.com____________ │ ← per-tab toolbar
├──────────────────────────────────────────────────────┤
│ │
│ WebKitWebView │ ← per-tab webview
│ │
└──────────────────────────────────────────────────────┘
```
### Tab label widget (composite)
Each tab label is a horizontal `GtkBox`:
```
[favicon] [Title text…] [×]
```
- Favicon: placeholder icon for now (can be wired to WebKit favicon later)
- Title: `GtkLabel` with ellipsize end, max width ~150px
- Close button: `GtkButton` with `window-close-symbolic` icon, only shown if
`settings.show_tab_close_buttons` is true
### Right-click context menu on tab label
- New Tab
- Close Tab
- Close Other Tabs
- Close Tabs to the Right
- Duplicate Tab
- Reload Tab
### Drag reordering
`GtkNotebook` supports drag reordering natively via
`gtk_notebook_set_tab_reorderable(notebook, child, TRUE)`. Enabled when
`settings.tab_drag_reorder` is true.
## Keyboard shortcuts
| Shortcut | Action |
|----------|--------|
| Ctrl+T | New tab |
| Ctrl+W | Close active tab |
| Ctrl+Tab | Next tab |
| Ctrl+Shift+Tab | Previous tab |
| Ctrl+PageUp | Previous tab |
| Ctrl+PageDown | Next tab |
| Ctrl+L | Focus URL bar of active tab |
## Session restore
On startup, if `settings.restore_session` is true and
`~/.sovereign_browser/session.txt` exists and is non-empty, each line is
loaded as a tab URL. If the file is missing or empty, a single tab is opened
with the start URL (from command-line arg or `settings.new_tab_url`).
On window destroy, before `gtk_main_quit()`, the list of open tab URLs is
written to `~/.sovereign_browser/session.txt`.
## Settings UI
A new menu item "Settings…" in the hamburger menu opens a native GTK dialog
(not a web page, to keep it simple and avoid bootstrapping a
`sovereign://settings` renderer). The dialog has checkboxes and a text entry
for each configurable option. On "OK", `settings_save()` is called and
applicable changes (like tab bar position) are applied live.
## Implementation order
1. **settings.c/h** — foundation, no UI yet, just load/save + global singleton
2. **tab_manager.c/h** — core notebook management, per-tab page creation,
tab label widget, new/close/switch
3. **session.c/h** — save/restore using tab_manager API
4. **main.c refactor** — wire up tab_manager, move context setup, rewire
callbacks to use active tab
5. **Tab interactions** — middle-click close, right-click menu, drag reorder
6. **Keyboard shortcuts** — window-level key-press handler
7. **Session save/restore** — integrate into startup and destroy
8. **Settings dialog** — GTK dialog wired to settings module
9. **Makefile + docs** — build and documentation updates