chore(arch): clean up low-risk architecture backlog items
- commit
- c55079e5c0a3331c838d9394c287f3ac03b38068
- author
- brice · · 2026-06-25 09:06 UTC
- parent
- d3cb888
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
21 files · +193 -54
M README.md +5 -3
| @@ -109,7 +109,7 @@ The floating pane is a temporary browser pane that can be toggled without changi | |||
| 109 | 109 | - `Alt+F` toggles the floating pane and preserves its state while hidden. | |
| 110 | 110 | - `Ctrl+W` closes the active pane; if the floating pane is active, it is fully released for a fresh next open. | |
| 111 | 111 | - Profile shortcuts such as `Alt+G` are optional and configured under `workspace.floating_pane.profiles`. | |
| 112 | - | - Some `Alt+<key>` bindings may conflict with WebKit shortcuts or desktop-level handlers. | |
| 112 | + | - Some `Alt+<key>` bindings may conflict with browser-engine shortcuts or desktop-level handlers. | |
| 113 | 113 | ||
| 114 | 114 | See the [floating pane reference](https://dumber.bnema.dev/docs/reference/floating-pane) for setup and behavior details. | |
| 115 | 115 | ||
| @@ -200,7 +200,7 @@ Bug reports and reproducible Wayland/backend issues are welcome. | |||
| 200 | 200 | ||
| 201 | 201 | ## Development | |
| 202 | 202 | ||
| 203 | - | Dumber uses pure-Go bindings and requires GTK4/WebKitGTK runtime libraries for the GUI. | |
| 203 | + | Dumber uses pure-Go bindings. The GUI uses GTK4, runs on CEF by default, and can use WebKitGTK as a fallback backend. | |
| 204 | 204 | ||
| 205 | 205 | Set `ENV=dev` to use `.dev/dumber/` for config and data instead of XDG paths. | |
| 206 | 206 | ||
| @@ -209,7 +209,9 @@ Set `ENV=dev` to use `.dev/dumber/` for config and data instead of XDG paths. | |||
| 209 | 209 | **Prerequisites:** | |
| 210 | 210 | ||
| 211 | 211 | - Go 1.26+ | |
| 212 | - | - WebKitGTK 6.0 and GTK4 development packages | |
| 212 | + | - GTK4 development packages | |
| 213 | + | - CEF runtime (default backend) | |
| 214 | + | - WebKitGTK 6.0 development/runtime packages (fallback backend and runtime checks) | |
| 213 | 215 | - Brotli for compressed systemviews assets | |
| 214 | 216 | ||
| 215 | 217 | Systemviews assets are generated with `go tool templ` and Go's `js/wasm` toolchain; no root Node toolchain is required. | |
| @@ -109,7 +109,7 @@ The floating pane is a temporary browser pane that can be toggled without changi | |||||
| 109 | - `Alt+F` toggles the floating pane and preserves its state while hidden. | 109 | - `Alt+F` toggles the floating pane and preserves its state while hidden. | ||
| 110 | - `Ctrl+W` closes the active pane; if the floating pane is active, it is fully released for a fresh next open. | 110 | - `Ctrl+W` closes the active pane; if the floating pane is active, it is fully released for a fresh next open. | ||
| 111 | - Profile shortcuts such as `Alt+G` are optional and configured under `workspace.floating_pane.profiles`. | 111 | - Profile shortcuts such as `Alt+G` are optional and configured under `workspace.floating_pane.profiles`. | ||
| 112 | - | - Some `Alt+<key>` bindings may conflict with WebKit shortcuts or desktop-level handlers. | 112 | + | - Some `Alt+<key>` bindings may conflict with browser-engine shortcuts or desktop-level handlers. |
| 113 | 113 | ||||
| 114 | See the [floating pane reference](https://dumber.bnema.dev/docs/reference/floating-pane) for setup and behavior details. | 114 | See the [floating pane reference](https://dumber.bnema.dev/docs/reference/floating-pane) for setup and behavior details. | ||
| 115 | 115 | ||||
| @@ -200,7 +200,7 @@ Bug reports and reproducible Wayland/backend issues are welcome. | |||||
| 200 | 200 | ||||
| 201 | ## Development | 201 | ## Development | ||
| 202 | 202 | ||||
| 203 | - | Dumber uses pure-Go bindings and requires GTK4/WebKitGTK runtime libraries for the GUI. | 203 | + | Dumber uses pure-Go bindings. The GUI uses GTK4, runs on CEF by default, and can use WebKitGTK as a fallback backend. |
| 204 | 204 | ||||
| 205 | Set `ENV=dev` to use `.dev/dumber/` for config and data instead of XDG paths. | 205 | Set `ENV=dev` to use `.dev/dumber/` for config and data instead of XDG paths. | ||
| 206 | 206 | ||||
| @@ -209,7 +209,9 @@ Set `ENV=dev` to use `.dev/dumber/` for config and data instead of XDG paths. | |||||
| 209 | **Prerequisites:** | 209 | **Prerequisites:** | ||
| 210 | 210 | ||||
| 211 | - Go 1.26+ | 211 | - Go 1.26+ | ||
| 212 | - | - WebKitGTK 6.0 and GTK4 development packages | 212 | + | - GTK4 development packages |
| 213 | + | - CEF runtime (default backend) | |||
| 214 | + | - WebKitGTK 6.0 development/runtime packages (fallback backend and runtime checks) | |||
| 213 | - Brotli for compressed systemviews assets | 215 | - Brotli for compressed systemviews assets | ||
| 214 | 216 | ||||
| 215 | Systemviews assets are generated with `go tool templ` and Go's `js/wasm` toolchain; no root Node toolchain is required. | 217 | Systemviews assets are generated with `go tool templ` and Go's `js/wasm` toolchain; no root Node toolchain is required. | ||
M docs/cli/index.md +1 -1
| @@ -141,7 +141,7 @@ dumber doctor [flags] | |||
| 141 | 141 | ||
| 142 | 142 | | Flag | Description | | |
| 143 | 143 | |------|-------------| | |
| 144 | - | | `--runtime` | Only run runtime checks (GTK4/WebKitGTK) | | |
| 144 | + | | `--runtime` | Only run runtime checks for GTK and WebKit fallback libraries | | |
| 145 | 145 | | `--media` | Only run media checks (GStreamer/VA-API) | | |
| 146 | 146 | ||
| 147 | 147 | ### setup | |
| @@ -141,7 +141,7 @@ dumber doctor [flags] | |||||
| 141 | 141 | ||||
| 142 | | Flag | Description | | 142 | | Flag | Description | | ||
| 143 | |------|-------------| | 143 | |------|-------------| | ||
| 144 | - | | `--runtime` | Only run runtime checks (GTK4/WebKitGTK) | | 144 | + | | `--runtime` | Only run runtime checks for GTK and WebKit fallback libraries | |
| 145 | | `--media` | Only run media checks (GStreamer/VA-API) | | 145 | | `--media` | Only run media checks (GStreamer/VA-API) | | ||
| 146 | 146 | ||||
| 147 | ### setup | 147 | ### setup | ||
M docs/getting-started.md +2 -2
| @@ -39,7 +39,7 @@ Press `Ctrl+L` to open the omnibox for: | |||
| 39 | 39 | ||
| 40 | 40 | - Press `Alt+F` to toggle the floating pane. | |
| 41 | 41 | - Press `Ctrl+W` to close the active pane; if the floating pane is active, this fully releases it so the next open starts fresh. | |
| 42 | - | - Some `Alt+<key>` bindings can conflict with default WebKit shortcuts or desktop-level handlers. | |
| 42 | + | - Some `Alt+<key>` bindings can conflict with browser-engine shortcuts or desktop-level handlers. | |
| 43 | 43 | ||
| 44 | 44 | See [Floating Pane](./reference/floating-pane.md) for profile shortcuts and configuration. | |
| 45 | 45 | ||
| @@ -70,7 +70,7 @@ Dumber includes a built-in permissions system for camera, microphone, and screen | |||
| 70 | 70 | - **Privacy-focused** - All permissions stored locally | |
| 71 | 71 | - **Camera & Microphone** - Fully working on Wayland/PipeWire | |
| 72 | 72 | ||
| 73 | - | > ⚠️ **Note**: Screen sharing does not currently work on Wayland with WebKitGTK 6.0. This is a known WebKitGTK limitation. | |
| 73 | + | > ⚠️ **WebKit fallback note**: Screen sharing does not currently work on Wayland with WebKitGTK 6.0. This is a known WebKitGTK limitation and does not describe the default CEF backend. | |
| 74 | 74 | ||
| 75 | 75 | When a website requests camera or microphone access, you'll see a permission dialog with options to allow once, always allow, deny, or always deny. | |
| 76 | 76 | ||
| @@ -39,7 +39,7 @@ Press `Ctrl+L` to open the omnibox for: | |||||
| 39 | 39 | ||||
| 40 | - Press `Alt+F` to toggle the floating pane. | 40 | - Press `Alt+F` to toggle the floating pane. | ||
| 41 | - Press `Ctrl+W` to close the active pane; if the floating pane is active, this fully releases it so the next open starts fresh. | 41 | - Press `Ctrl+W` to close the active pane; if the floating pane is active, this fully releases it so the next open starts fresh. | ||
| 42 | - | - Some `Alt+<key>` bindings can conflict with default WebKit shortcuts or desktop-level handlers. | 42 | + | - Some `Alt+<key>` bindings can conflict with browser-engine shortcuts or desktop-level handlers. |
| 43 | 43 | ||||
| 44 | See [Floating Pane](./reference/floating-pane.md) for profile shortcuts and configuration. | 44 | See [Floating Pane](./reference/floating-pane.md) for profile shortcuts and configuration. | ||
| 45 | 45 | ||||
| @@ -70,7 +70,7 @@ Dumber includes a built-in permissions system for camera, microphone, and screen | |||||
| 70 | - **Privacy-focused** - All permissions stored locally | 70 | - **Privacy-focused** - All permissions stored locally | ||
| 71 | - **Camera & Microphone** - Fully working on Wayland/PipeWire | 71 | - **Camera & Microphone** - Fully working on Wayland/PipeWire | ||
| 72 | 72 | ||||
| 73 | - | > ⚠️ **Note**: Screen sharing does not currently work on Wayland with WebKitGTK 6.0. This is a known WebKitGTK limitation. | 73 | + | > ⚠️ **WebKit fallback note**: Screen sharing does not currently work on Wayland with WebKitGTK 6.0. This is a known WebKitGTK limitation and does not describe the default CEF backend. |
| 74 | 74 | ||||
| 75 | When a website requests camera or microphone access, you'll see a permission dialog with options to allow once, always allow, deny, or always deny. | 75 | When a website requests camera or microphone access, you'll see a permission dialog with options to allow once, always allow, deny, or always deny. | ||
| 76 | 76 | ||||
M docs/index.md +3 -3
| @@ -1,6 +1,6 @@ | |||
| 1 | 1 | # Dumber Documentation | |
| 2 | 2 | ||
| 3 | - | Dumber is a keyboard-driven browser for tiling window managers, built with GTK4 and WebKitGTK. | |
| 3 | + | Dumber is a keyboard-driven browser for tiling window managers, built with GTK4. It uses CEF by default, with WebKitGTK available as a fallback backend. | |
| 4 | 4 | ||
| 5 | 5 | ## What is Dumber? | |
| 6 | 6 | ||
| @@ -41,5 +41,5 @@ A browser designed for Wayland compositors that works like your favorite termina | |||
| 41 | 41 | ## Requirements | |
| 42 | 42 | ||
| 43 | 43 | - Linux with Wayland (Sway, Hyprland, River, Niri, etc.) | |
| 44 | - | - GTK4 and WebKitGTK 2.42+ | |
| 45 | - | - GStreamer (for media playback) | |
| 44 | + | - GTK4, CEF for the default backend, and WebKitGTK for the fallback backend | |
| 45 | + | - GStreamer (for WebKit media playback and diagnostics) | |
| @@ -1,6 +1,6 @@ | |||||
| 1 | # Dumber Documentation | 1 | # Dumber Documentation | ||
| 2 | 2 | ||||
| 3 | - | Dumber is a keyboard-driven browser for tiling window managers, built with GTK4 and WebKitGTK. | 3 | + | Dumber is a keyboard-driven browser for tiling window managers, built with GTK4. It uses CEF by default, with WebKitGTK available as a fallback backend. |
| 4 | 4 | ||||
| 5 | ## What is Dumber? | 5 | ## What is Dumber? | ||
| 6 | 6 | ||||
| @@ -41,5 +41,5 @@ A browser designed for Wayland compositors that works like your favorite termina | |||||
| 41 | ## Requirements | 41 | ## Requirements | ||
| 42 | 42 | ||||
| 43 | - Linux with Wayland (Sway, Hyprland, River, Niri, etc.) | 43 | - Linux with Wayland (Sway, Hyprland, River, Niri, etc.) | ||
| 44 | - | - GTK4 and WebKitGTK 2.42+ | 44 | + | - GTK4, CEF for the default backend, and WebKitGTK for the fallback backend |
| 45 | - | - GStreamer (for media playback) | 45 | + | - GStreamer (for WebKit media playback and diagnostics) |
M docs/installation.md +3 -2
| @@ -51,8 +51,9 @@ make build | |||
| 51 | 51 | ||
| 52 | 52 | - Go 1.26+ | |
| 53 | 53 | - GTK4 development libraries | |
| 54 | - | - WebKitGTK 6.0 development libraries | |
| 55 | - | - GStreamer development libraries | |
| 54 | + | - CEF runtime for the default backend | |
| 55 | + | - WebKitGTK 6.0 development/runtime libraries for the fallback backend and runtime checks | |
| 56 | + | - GStreamer development libraries for WebKit media playback and diagnostics | |
| 56 | 57 | - Brotli for compressed systemviews assets | |
| 57 | 58 | ||
| 58 | 59 | Systemviews assets are generated with `go tool templ` and Go's `js/wasm` toolchain; no root Node toolchain is required. | |
| @@ -51,8 +51,9 @@ make build | |||||
| 51 | 51 | ||||
| 52 | - Go 1.26+ | 52 | - Go 1.26+ | ||
| 53 | - GTK4 development libraries | 53 | - GTK4 development libraries | ||
| 54 | - | - WebKitGTK 6.0 development libraries | 54 | + | - CEF runtime for the default backend |
| 55 | - | - GStreamer development libraries | 55 | + | - WebKitGTK 6.0 development/runtime libraries for the fallback backend and runtime checks |
| 56 | + | - GStreamer development libraries for WebKit media playback and diagnostics | |||
| 56 | - Brotli for compressed systemviews assets | 57 | - Brotli for compressed systemviews assets | ||
| 57 | 58 | ||||
| 58 | Systemviews assets are generated with `go tool templ` and Go's `js/wasm` toolchain; no root Node toolchain is required. | 59 | Systemviews assets are generated with `go tool templ` and Go's `js/wasm` toolchain; no root Node toolchain is required. | ||
M docs/reference/dependencies.md +3 -2
| @@ -61,7 +61,7 @@ sudo pacman -S vulkan-radeon | |||
| 61 | 61 | sudo pacman -S nvidia-utils # includes Vulkan | |
| 62 | 62 | ``` | |
| 63 | 63 | ||
| 64 | - | ### GStreamer Plugins (for WebKit Media Playback) | |
| 64 | + | ### GStreamer Plugins (for WebKit fallback media playback) | |
| 65 | 65 | ||
| 66 | 66 | ```bash | |
| 67 | 67 | sudo pacman -S gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly gst-libav | |
| @@ -79,10 +79,11 @@ CEF is Dumber's default engine. It uses the GPU-first Wayland stack: GDK DMABUF | |||
| 79 | 79 | ||
| 80 | 80 | ## Debian/Ubuntu | |
| 81 | 81 | ||
| 82 | - | ### Core GTK4 and WebKit | |
| 82 | + | ### Core GTK4, CEF, and WebKit fallback | |
| 83 | 83 | ||
| 84 | 84 | ```bash | |
| 85 | 85 | sudo apt install libgtk-4-1 libwebkitgtk-6.0-4 | |
| 86 | + | # Also provide a CEF runtime for the default backend (package name varies by distribution). | |
| 86 | 87 | ``` | |
| 87 | 88 | ||
| 88 | 89 | ### VA-API | |
| @@ -61,7 +61,7 @@ sudo pacman -S vulkan-radeon | |||||
| 61 | sudo pacman -S nvidia-utils # includes Vulkan | 61 | sudo pacman -S nvidia-utils # includes Vulkan | ||
| 62 | ``` | 62 | ``` | ||
| 63 | 63 | ||||
| 64 | - | ### GStreamer Plugins (for WebKit Media Playback) | 64 | + | ### GStreamer Plugins (for WebKit fallback media playback) |
| 65 | 65 | ||||
| 66 | ```bash | 66 | ```bash | ||
| 67 | sudo pacman -S gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly gst-libav | 67 | sudo pacman -S gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly gst-libav | ||
| @@ -79,10 +79,11 @@ CEF is Dumber's default engine. It uses the GPU-first Wayland stack: GDK DMABUF | |||||
| 79 | 79 | ||||
| 80 | ## Debian/Ubuntu | 80 | ## Debian/Ubuntu | ||
| 81 | 81 | ||||
| 82 | - | ### Core GTK4 and WebKit | 82 | + | ### Core GTK4, CEF, and WebKit fallback |
| 83 | 83 | ||||
| 84 | ```bash | 84 | ```bash | ||
| 85 | sudo apt install libgtk-4-1 libwebkitgtk-6.0-4 | 85 | sudo apt install libgtk-4-1 libwebkitgtk-6.0-4 | ||
| 86 | + | # Also provide a CEF runtime for the default backend (package name varies by distribution). | |||
| 86 | ``` | 87 | ``` | ||
| 87 | 88 | ||||
| 88 | ### VA-API | 89 | ### VA-API | ||
M docs/reference/floating-pane.md +1 -1
| @@ -53,5 +53,5 @@ desc = "Open Google in floating pane" | |||
| 53 | 53 | - The default install only binds `Alt+F` for floating pane toggle. | |
| 54 | 54 | - Profile shortcuts are opt-in and empty by default. | |
| 55 | 55 | - Profile shortcuts support multi-modifier combos using `ctrl`, `shift`, and `alt` (for example `ctrl+shift+y` or `ctrl+alt+m`). | |
| 56 | - | - Some `Alt+<key>` shortcuts can conflict with default WebKit shortcuts, website handlers, or your desktop environment. | |
| 56 | + | - Some `Alt+<key>` shortcuts can conflict with browser-engine shortcuts, website handlers, or your desktop environment. | |
| 57 | 57 | - If a shortcut does not fire in Dumber, bind a different key. | |
| @@ -53,5 +53,5 @@ desc = "Open Google in floating pane" | |||||
| 53 | - The default install only binds `Alt+F` for floating pane toggle. | 53 | - The default install only binds `Alt+F` for floating pane toggle. | ||
| 54 | - Profile shortcuts are opt-in and empty by default. | 54 | - Profile shortcuts are opt-in and empty by default. | ||
| 55 | - Profile shortcuts support multi-modifier combos using `ctrl`, `shift`, and `alt` (for example `ctrl+shift+y` or `ctrl+alt+m`). | 55 | - Profile shortcuts support multi-modifier combos using `ctrl`, `shift`, and `alt` (for example `ctrl+shift+y` or `ctrl+alt+m`). | ||
| 56 | - | - Some `Alt+<key>` shortcuts can conflict with default WebKit shortcuts, website handlers, or your desktop environment. | 56 | + | - Some `Alt+<key>` shortcuts can conflict with browser-engine shortcuts, website handlers, or your desktop environment. |
| 57 | - If a shortcut does not fire in Dumber, bind a different key. | 57 | - If a shortcut does not fire in Dumber, bind a different key. | ||
M docs/reference/keybindings.md +1 -1
| @@ -101,7 +101,7 @@ These work outside modal modes: | |||
| 101 | 101 | - Any URL shortcut (for example `Alt+G`) must be defined explicitly in `workspace.floating_pane.profiles`. | |
| 102 | 102 | - Floating profile shortcuts support modifier combos with `ctrl`, `shift`, and `alt` (for example `ctrl+shift+y` or `ctrl+alt+m`). | |
| 103 | 103 | ||
| 104 | - | Warning: some `Alt+<key>` combinations may conflict with default WebKit shortcuts, website handlers, or your desktop environment. | |
| 104 | + | Warning: some `Alt+<key>` combinations may conflict with browser-engine shortcuts, website handlers, or your desktop environment. | |
| 105 | 105 | If a shortcut does not trigger in Dumber, choose a different keybinding. | |
| 106 | 106 | ||
| 107 | 107 | For details, see [Floating Pane](./floating-pane.md). | |
| @@ -101,7 +101,7 @@ These work outside modal modes: | |||||
| 101 | - Any URL shortcut (for example `Alt+G`) must be defined explicitly in `workspace.floating_pane.profiles`. | 101 | - Any URL shortcut (for example `Alt+G`) must be defined explicitly in `workspace.floating_pane.profiles`. | ||
| 102 | - Floating profile shortcuts support modifier combos with `ctrl`, `shift`, and `alt` (for example `ctrl+shift+y` or `ctrl+alt+m`). | 102 | - Floating profile shortcuts support modifier combos with `ctrl`, `shift`, and `alt` (for example `ctrl+shift+y` or `ctrl+alt+m`). | ||
| 103 | 103 | ||||
| 104 | - | Warning: some `Alt+<key>` combinations may conflict with default WebKit shortcuts, website handlers, or your desktop environment. | 104 | + | Warning: some `Alt+<key>` combinations may conflict with browser-engine shortcuts, website handlers, or your desktop environment. |
| 105 | If a shortcut does not trigger in Dumber, choose a different keybinding. | 105 | If a shortcut does not trigger in Dumber, choose a different keybinding. | ||
| 106 | 106 | ||||
| 107 | For details, see [Floating Pane](./floating-pane.md). | 107 | For details, see [Floating Pane](./floating-pane.md). | ||
M docs/reference/permissions.md +1 -1
| @@ -10,7 +10,7 @@ These permissions are automatically granted without user interaction: | |||
| 10 | 10 | ||
| 11 | 11 | - **Display Capture** (screen sharing) - The XDG Desktop Portal handles the UI | |
| 12 | 12 | ||
| 13 | - | > ⚠️ **Known Issue**: Screen sharing does **not work** on Wayland with WebKitGTK 2.50.x due to a GStreamer caps negotiation failure ([WebKit bug #306440](https://bugs.webkit.org/show_bug.cgi?id=306440)). This was fixed on January 29, 2026 and backported to the 2.52 stable branch. **Workaround**: Upgrade to WebKitGTK 2.52+ when available from your distribution. Camera and microphone permissions work correctly. | |
| 13 | + | > ⚠️ **WebKit fallback known issue**: Screen sharing does **not work** on Wayland with WebKitGTK 2.50.x due to a GStreamer caps negotiation failure ([WebKit bug #306440](https://bugs.webkit.org/show_bug.cgi?id=306440)). This was fixed on January 29, 2026 and backported to the 2.52 stable branch. **Workaround for the WebKit backend**: Upgrade to WebKitGTK 2.52+ when available from your distribution. Camera and microphone permissions work correctly. | |
| 14 | 14 | ||
| 15 | 15 | - **Device Enumeration** (listing microphones/cameras) - Low security risk | |
| 16 | 16 | ||
| @@ -10,7 +10,7 @@ These permissions are automatically granted without user interaction: | |||||
| 10 | 10 | ||||
| 11 | - **Display Capture** (screen sharing) - The XDG Desktop Portal handles the UI | 11 | - **Display Capture** (screen sharing) - The XDG Desktop Portal handles the UI | ||
| 12 | 12 | ||||
| 13 | - | > ⚠️ **Known Issue**: Screen sharing does **not work** on Wayland with WebKitGTK 2.50.x due to a GStreamer caps negotiation failure ([WebKit bug #306440](https://bugs.webkit.org/show_bug.cgi?id=306440)). This was fixed on January 29, 2026 and backported to the 2.52 stable branch. **Workaround**: Upgrade to WebKitGTK 2.52+ when available from your distribution. Camera and microphone permissions work correctly. | 13 | + | > ⚠️ **WebKit fallback known issue**: Screen sharing does **not work** on Wayland with WebKitGTK 2.50.x due to a GStreamer caps negotiation failure ([WebKit bug #306440](https://bugs.webkit.org/show_bug.cgi?id=306440)). This was fixed on January 29, 2026 and backported to the 2.52 stable branch. **Workaround for the WebKit backend**: Upgrade to WebKitGTK 2.52+ when available from your distribution. Camera and microphone permissions work correctly. |
| 14 | 14 | ||||
| 15 | - **Device Enumeration** (listing microphones/cameras) - Low security risk | 15 | - **Device Enumeration** (listing microphones/cameras) - Low security risk | ||
| 16 | 16 | ||||
M docs/reference/troubleshooting.md +7 -7
| @@ -6,7 +6,7 @@ Run the doctor command to check your system: | |||
| 6 | 6 | ||
| 7 | 7 | ```bash | |
| 8 | 8 | dumber doctor # Full check | |
| 9 | - | dumber doctor --runtime # GTK4/WebKitGTK only | |
| 9 | + | dumber doctor --runtime # GTK and WebKit fallback runtime checks only | |
| 10 | 10 | dumber doctor --media # GStreamer/VA-API only | |
| 11 | 11 | ``` | |
| 12 | 12 | ||
| @@ -14,14 +14,14 @@ dumber doctor --media # GStreamer/VA-API only | |||
| 14 | 14 | ||
| 15 | 15 | ### Browser won't start | |
| 16 | 16 | ||
| 17 | - | **Symptoms:** Error about missing libraries or GTK/WebKitGTK version | |
| 17 | + | **Symptoms:** Error about missing GUI runtime libraries or GTK/WebKitGTK fallback version | |
| 18 | 18 | ||
| 19 | 19 | **Solution:** | |
| 20 | 20 | 1. Run `dumber doctor --runtime` | |
| 21 | 21 | 2. Install missing dependencies: | |
| 22 | - | - Arch: `pacman -S webkitgtk-6.0 gtk4` | |
| 23 | - | - Fedora: `dnf install webkitgtk6.0 gtk4` | |
| 24 | - | - Ubuntu: `apt install libwebkitgtk-6.0-4 libgtk-4-1` | |
| 22 | + | - Arch: `pacman -S cef webkitgtk-6.0 gtk4` | |
| 23 | + | - Fedora: install GTK4 and WebKitGTK packages from your distribution and a CEF runtime/package if available | |
| 24 | + | - Ubuntu: `apt install libwebkitgtk-6.0-4 libgtk-4-1` and provide a CEF runtime for the default backend | |
| 25 | 25 | ||
| 26 | 26 | ### Video playback issues | |
| 27 | 27 | ||
| @@ -58,9 +58,9 @@ dumber doctor --media # GStreamer/VA-API only | |||
| 58 | 58 | gstreamer_debug_level = 0 | |
| 59 | 59 | ``` | |
| 60 | 60 | ||
| 61 | - | ### Screen flickering on Wayland | |
| 61 | + | ### Screen flickering on Wayland with the WebKit fallback | |
| 62 | 62 | ||
| 63 | - | **Symptoms:** Flicker when scrolling or rendering | |
| 63 | + | **Symptoms:** Flicker when scrolling or rendering while using `engine.type = "webkit"` | |
| 64 | 64 | ||
| 65 | 65 | **Solution:** | |
| 66 | 66 | ```toml | |
| @@ -6,7 +6,7 @@ Run the doctor command to check your system: | |||||
| 6 | 6 | ||||
| 7 | ```bash | 7 | ```bash | ||
| 8 | dumber doctor # Full check | 8 | dumber doctor # Full check | ||
| 9 | - | dumber doctor --runtime # GTK4/WebKitGTK only | 9 | + | dumber doctor --runtime # GTK and WebKit fallback runtime checks only |
| 10 | dumber doctor --media # GStreamer/VA-API only | 10 | dumber doctor --media # GStreamer/VA-API only | ||
| 11 | ``` | 11 | ``` | ||
| 12 | 12 | ||||
| @@ -14,14 +14,14 @@ dumber doctor --media # GStreamer/VA-API only | |||||
| 14 | 14 | ||||
| 15 | ### Browser won't start | 15 | ### Browser won't start | ||
| 16 | 16 | ||||
| 17 | - | **Symptoms:** Error about missing libraries or GTK/WebKitGTK version | 17 | + | **Symptoms:** Error about missing GUI runtime libraries or GTK/WebKitGTK fallback version |
| 18 | 18 | ||||
| 19 | **Solution:** | 19 | **Solution:** | ||
| 20 | 1. Run `dumber doctor --runtime` | 20 | 1. Run `dumber doctor --runtime` | ||
| 21 | 2. Install missing dependencies: | 21 | 2. Install missing dependencies: | ||
| 22 | - | - Arch: `pacman -S webkitgtk-6.0 gtk4` | 22 | + | - Arch: `pacman -S cef webkitgtk-6.0 gtk4` |
| 23 | - | - Fedora: `dnf install webkitgtk6.0 gtk4` | 23 | + | - Fedora: install GTK4 and WebKitGTK packages from your distribution and a CEF runtime/package if available |
| 24 | - | - Ubuntu: `apt install libwebkitgtk-6.0-4 libgtk-4-1` | 24 | + | - Ubuntu: `apt install libwebkitgtk-6.0-4 libgtk-4-1` and provide a CEF runtime for the default backend |
| 25 | 25 | ||||
| 26 | ### Video playback issues | 26 | ### Video playback issues | ||
| 27 | 27 | ||||
| @@ -58,9 +58,9 @@ dumber doctor --media # GStreamer/VA-API only | |||||
| 58 | gstreamer_debug_level = 0 | 58 | gstreamer_debug_level = 0 | ||
| 59 | ``` | 59 | ``` | ||
| 60 | 60 | ||||
| 61 | - | ### Screen flickering on Wayland | 61 | + | ### Screen flickering on Wayland with the WebKit fallback |
| 62 | 62 | ||||
| 63 | - | **Symptoms:** Flicker when scrolling or rendering | 63 | + | **Symptoms:** Flicker when scrolling or rendering while using `engine.type = "webkit"` |
| 64 | 64 | ||||
| 65 | **Solution:** | 65 | **Solution:** | ||
| 66 | ```toml | 66 | ```toml | ||
M internal/bootstrap/gui.go +1 -1
| @@ -227,7 +227,7 @@ func RunDeferredInit(input DeferredInitInput) DeferredInitResult { | |||
| 227 | 227 | } | |
| 228 | 228 | } | |
| 229 | 229 | ||
| 230 | - | // CheckRuntimeRequirements verifies WebKitGTK and other runtime dependencies. | |
| 230 | + | // CheckRuntimeRequirements verifies GUI runtime dependencies currently covered by runtime checks. | |
| 231 | 231 | // Returns error if requirements are not met; caller should log details and exit. | |
| 232 | 232 | // Note: When running in a Flatpak sandbox, runtime checks are skipped because | |
| 233 | 233 | // the Flatpak runtime provides all required libraries. | |
| @@ -227,7 +227,7 @@ func RunDeferredInit(input DeferredInitInput) DeferredInitResult { | |||||
| 227 | } | 227 | } | ||
| 228 | } | 228 | } | ||
| 229 | 229 | ||||
| 230 | - | // CheckRuntimeRequirements verifies WebKitGTK and other runtime dependencies. | 230 | + | // CheckRuntimeRequirements verifies GUI runtime dependencies currently covered by runtime checks. |
| 231 | // Returns error if requirements are not met; caller should log details and exit. | 231 | // Returns error if requirements are not met; caller should log details and exit. | ||
| 232 | // Note: When running in a Flatpak sandbox, runtime checks are skipped because | 232 | // Note: When running in a Flatpak sandbox, runtime checks are skipped because | ||
| 233 | // the Flatpak runtime provides all required libraries. | 233 | // the Flatpak runtime provides all required libraries. | ||
M internal/cli/cmd/doctor.go +2 -2
| @@ -22,7 +22,7 @@ var doctorCmd = &cobra.Command{ | |||
| 22 | 22 | Long: `Doctor checks system prerequisites for running the GUI browser. | |
| 23 | 23 | ||
| 24 | 24 | By default it runs both: | |
| 25 | - | - Runtime checks (GTK4 + WebKitGTK 6.0) | |
| 25 | + | - Runtime checks for GTK and WebKit fallback libraries | |
| 26 | 26 | - Media checks (GStreamer/VA-API) | |
| 27 | 27 | ||
| 28 | 28 | Use flags to run only one category. | |
| @@ -36,7 +36,7 @@ Examples: | |||
| 36 | 36 | ||
| 37 | 37 | func init() { | |
| 38 | 38 | rootCmd.AddCommand(doctorCmd) | |
| 39 | - | doctorCmd.Flags().BoolVar(&doctorOnlyRuntime, "runtime", false, "Only run runtime checks (GTK4/WebKitGTK)") | |
| 39 | + | doctorCmd.Flags().BoolVar(&doctorOnlyRuntime, "runtime", false, "Only run runtime checks for GTK and WebKit fallback libraries") | |
| 40 | 40 | doctorCmd.Flags().BoolVar(&doctorOnlyMedia, "media", false, "Only run media checks (GStreamer/VA-API)") | |
| 41 | 41 | } | |
| 42 | 42 | func runDoctor(_ *cobra.Command, _ []string) error { | |
| @@ -22,7 +22,7 @@ var doctorCmd = &cobra.Command{ | |||||
| 22 | Long: `Doctor checks system prerequisites for running the GUI browser. | 22 | Long: `Doctor checks system prerequisites for running the GUI browser. | ||
| 23 | 23 | ||||
| 24 | By default it runs both: | 24 | By default it runs both: | ||
| 25 | - | - Runtime checks (GTK4 + WebKitGTK 6.0) | 25 | + | - Runtime checks for GTK and WebKit fallback libraries |
| 26 | - Media checks (GStreamer/VA-API) | 26 | - Media checks (GStreamer/VA-API) | ||
| 27 | 27 | ||||
| 28 | Use flags to run only one category. | 28 | Use flags to run only one category. | ||
| @@ -36,7 +36,7 @@ Examples: | |||||
| 36 | 36 | ||||
| 37 | func init() { | 37 | func init() { | ||
| 38 | rootCmd.AddCommand(doctorCmd) | 38 | rootCmd.AddCommand(doctorCmd) | ||
| 39 | - | doctorCmd.Flags().BoolVar(&doctorOnlyRuntime, "runtime", false, "Only run runtime checks (GTK4/WebKitGTK)") | 39 | + | doctorCmd.Flags().BoolVar(&doctorOnlyRuntime, "runtime", false, "Only run runtime checks for GTK and WebKit fallback libraries") |
| 40 | doctorCmd.Flags().BoolVar(&doctorOnlyMedia, "media", false, "Only run media checks (GStreamer/VA-API)") | 40 | doctorCmd.Flags().BoolVar(&doctorOnlyMedia, "media", false, "Only run media checks (GStreamer/VA-API)") | ||
| 41 | } | 41 | } | ||
| 42 | func runDoctor(_ *cobra.Command, _ []string) error { | 42 | func runDoctor(_ *cobra.Command, _ []string) error { | ||
M internal/cli/cmd/root.go +1 -1
| @@ -22,7 +22,7 @@ var ( | |||
| 22 | 22 | SilenceUsage: true, | |
| 23 | 23 | Long: `Dumber - a dumb browser that works like your favorite terminal multiplexer. | |
| 24 | 24 | ||
| 25 | - | A fully unfeatured unbloated browser for tiling WMs, built with GTK4 and WebKitGTK. | |
| 25 | + | A fully unfeatured unbloated browser for tiling WMs, built with GTK4. CEF is the default backend; WebKitGTK is available as a fallback. | |
| 26 | 26 | ||
| 27 | 27 | Features: | |
| 28 | 28 | - Wayland native (Sway, Hyprland, River, Niri, etc.) | |
| @@ -22,7 +22,7 @@ var ( | |||||
| 22 | SilenceUsage: true, | 22 | SilenceUsage: true, | ||
| 23 | Long: `Dumber - a dumb browser that works like your favorite terminal multiplexer. | 23 | Long: `Dumber - a dumb browser that works like your favorite terminal multiplexer. | ||
| 24 | 24 | ||||
| 25 | - | A fully unfeatured unbloated browser for tiling WMs, built with GTK4 and WebKitGTK. | 25 | + | A fully unfeatured unbloated browser for tiling WMs, built with GTK4. CEF is the default backend; WebKitGTK is available as a fallback. |
| 26 | 26 | ||||
| 27 | Features: | 27 | Features: | ||
| 28 | - Wayland native (Sway, Hyprland, River, Niri, etc.) | 28 | - Wayland native (Sway, Hyprland, River, Niri, etc.) | ||
M internal/infrastructure/gtkutil/scale.go +1 -13
| @@ -1,10 +1,6 @@ | |||
| 1 | 1 | package gtkutil | |
| 2 | 2 | ||
| 3 | - | import ( | |
| 4 | - | "math" | |
| 5 | - | ||
| 6 | - | "github.com/bnema/puregotk/v4/gtk" | |
| 7 | - | ) | |
| 3 | + | import "math" | |
| 8 | 4 | ||
| 9 | 5 | // NormalizeScale clamps invalid GTK/GDK scale values to 1. | |
| 10 | 6 | func NormalizeScale(scale float64) float64 { | |
| @@ -14,14 +10,6 @@ func NormalizeScale(scale float64) float64 { | |||
| 14 | 10 | return scale | |
| 15 | 11 | } | |
| 16 | 12 | ||
| 17 | - | // WidgetFromNativePointer wraps a native GTK widget pointer. | |
| 18 | - | func WidgetFromNativePointer(ptr uintptr) *gtk.Widget { | |
| 19 | - | if ptr == 0 { | |
| 20 | - | return nil | |
| 21 | - | } | |
| 22 | - | return gtk.WidgetNewFromInternalPtr(ptr) | |
| 23 | - | } | |
| 24 | - | ||
| 25 | 13 | // DeviceToLogical converts a device-pixel coordinate into a logical coordinate. | |
| 26 | 14 | func DeviceToLogical(value int32, scale float64) int32 { | |
| 27 | 15 | return int32(math.Floor(float64(value) / NormalizeScale(scale))) | |
| @@ -1,10 +1,6 @@ | |||||
| 1 | package gtkutil | 1 | package gtkutil | ||
| 2 | 2 | ||||
| 3 | - | import ( | 3 | + | import "math" |
| 4 | - | "math" | |||
| 5 | - | ||||
| 6 | - | "github.com/bnema/puregotk/v4/gtk" | |||
| 7 | - | ) | |||
| 8 | 4 | ||||
| 9 | // NormalizeScale clamps invalid GTK/GDK scale values to 1. | 5 | // NormalizeScale clamps invalid GTK/GDK scale values to 1. | ||
| 10 | func NormalizeScale(scale float64) float64 { | 6 | func NormalizeScale(scale float64) float64 { | ||
| @@ -14,14 +10,6 @@ func NormalizeScale(scale float64) float64 { | |||||
| 14 | return scale | 10 | return scale | ||
| 15 | } | 11 | } | ||
| 16 | 12 | ||||
| 17 | - | // WidgetFromNativePointer wraps a native GTK widget pointer. | |||
| 18 | - | func WidgetFromNativePointer(ptr uintptr) *gtk.Widget { | |||
| 19 | - | if ptr == 0 { | |||
| 20 | - | return nil | |||
| 21 | - | } | |||
| 22 | - | return gtk.WidgetNewFromInternalPtr(ptr) | |||
| 23 | - | } | |||
| 24 | - | ||||
| 25 | // DeviceToLogical converts a device-pixel coordinate into a logical coordinate. | 13 | // DeviceToLogical converts a device-pixel coordinate into a logical coordinate. | ||
| 26 | func DeviceToLogical(value int32, scale float64) int32 { | 14 | func DeviceToLogical(value int32, scale float64) int32 { | ||
| 27 | return int32(math.Floor(float64(value) / NormalizeScale(scale))) | 15 | return int32(math.Floor(float64(value) / NormalizeScale(scale))) | ||
M internal/infrastructure/updater/github.go +2 -8
| @@ -275,14 +275,8 @@ func isRetryableRequestError(err error) bool { | |||
| 275 | 275 | } | |
| 276 | 276 | ||
| 277 | 277 | var netErr net.Error | |
| 278 | - | if errors.As(err, &netErr) { | |
| 279 | - | if netErr.Timeout() { | |
| 280 | - | return true | |
| 281 | - | } | |
| 282 | - | //nolint:staticcheck // net.Error.Temporary is deprecated but still useful for transient detection | |
| 283 | - | if netErr.Temporary() { | |
| 284 | - | return true | |
| 285 | - | } | |
| 278 | + | if errors.As(err, &netErr) && netErr.Timeout() { | |
| 279 | + | return true | |
| 286 | 280 | } | |
| 287 | 281 | ||
| 288 | 282 | var opErr *net.OpError | |
| @@ -275,14 +275,8 @@ func isRetryableRequestError(err error) bool { | |||||
| 275 | } | 275 | } | ||
| 276 | 276 | ||||
| 277 | var netErr net.Error | 277 | var netErr net.Error | ||
| 278 | - | if errors.As(err, &netErr) { | 278 | + | if errors.As(err, &netErr) && netErr.Timeout() { |
| 279 | - | if netErr.Timeout() { | 279 | + | return true |
| 280 | - | return true | |||
| 281 | - | } | |||
| 282 | - | //nolint:staticcheck // net.Error.Temporary is deprecated but still useful for transient detection | |||
| 283 | - | if netErr.Temporary() { | |||
| 284 | - | return true | |||
| 285 | - | } | |||
| 286 | } | 280 | } | ||
| 287 | 281 | ||||
| 288 | var opErr *net.OpError | 282 | var opErr *net.OpError | ||
M internal/infrastructure/updater/github_test.go +27 -0
| @@ -6,11 +6,13 @@ import ( | |||
| 6 | 6 | "encoding/hex" | |
| 7 | 7 | "errors" | |
| 8 | 8 | "io" | |
| 9 | + | "net" | |
| 9 | 10 | "net/http" | |
| 10 | 11 | "os" | |
| 11 | 12 | "path/filepath" | |
| 12 | 13 | "strings" | |
| 13 | 14 | "sync/atomic" | |
| 15 | + | "syscall" | |
| 14 | 16 | "testing" | |
| 15 | 17 | "time" | |
| 16 | 18 | ||
| @@ -393,6 +395,31 @@ func TestRetryDelayForAttemptAddsJitter(t *testing.T) { | |||
| 393 | 395 | } | |
| 394 | 396 | } | |
| 395 | 397 | ||
| 398 | + | func TestIsRetryableRequestError(t *testing.T) { | |
| 399 | + | tests := []struct { | |
| 400 | + | name string | |
| 401 | + | err error | |
| 402 | + | want bool | |
| 403 | + | }{ | |
| 404 | + | {name: "nil", err: nil, want: false}, | |
| 405 | + | {name: "context canceled", err: context.Canceled, want: false}, | |
| 406 | + | {name: "context deadline exceeded", err: context.DeadlineExceeded, want: true}, | |
| 407 | + | {name: "timeout net error", err: &net.DNSError{IsTimeout: true}, want: true}, | |
| 408 | + | {name: "non-timeout net error", err: &net.DNSError{Err: "lookup failed"}, want: false}, | |
| 409 | + | {name: "transient errno through net op error", err: &net.OpError{Op: "read", Net: "tcp", Err: syscall.ECONNRESET}, want: true}, | |
| 410 | + | {name: "non-transient errno", err: syscall.EINVAL, want: false}, | |
| 411 | + | {name: "ordinary error", err: errors.New("ordinary"), want: false}, | |
| 412 | + | } | |
| 413 | + | ||
| 414 | + | for _, tt := range tests { | |
| 415 | + | t.Run(tt.name, func(t *testing.T) { | |
| 416 | + | if got := isRetryableRequestError(tt.err); got != tt.want { | |
| 417 | + | t.Fatalf("isRetryableRequestError(%v) = %v, want %v", tt.err, got, tt.want) | |
| 418 | + | } | |
| 419 | + | }) | |
| 420 | + | } | |
| 421 | + | } | |
| 422 | + | ||
| 396 | 423 | func TestGitHubCheckerDoRequestWithRetry_RetryableStatus(t *testing.T) { | |
| 397 | 424 | var calls int32 | |
| 398 | 425 | tr := roundTripFunc(func(_ *http.Request) (*http.Response, error) { | |
| @@ -6,11 +6,13 @@ import ( | |||||
| 6 | "encoding/hex" | 6 | "encoding/hex" | ||
| 7 | "errors" | 7 | "errors" | ||
| 8 | "io" | 8 | "io" | ||
| 9 | + | "net" | |||
| 9 | "net/http" | 10 | "net/http" | ||
| 10 | "os" | 11 | "os" | ||
| 11 | "path/filepath" | 12 | "path/filepath" | ||
| 12 | "strings" | 13 | "strings" | ||
| 13 | "sync/atomic" | 14 | "sync/atomic" | ||
| 15 | + | "syscall" | |||
| 14 | "testing" | 16 | "testing" | ||
| 15 | "time" | 17 | "time" | ||
| 16 | 18 | ||||
| @@ -393,6 +395,31 @@ func TestRetryDelayForAttemptAddsJitter(t *testing.T) { | |||||
| 393 | } | 395 | } | ||
| 394 | } | 396 | } | ||
| 395 | 397 | ||||
| 398 | + | func TestIsRetryableRequestError(t *testing.T) { | |||
| 399 | + | tests := []struct { | |||
| 400 | + | name string | |||
| 401 | + | err error | |||
| 402 | + | want bool | |||
| 403 | + | }{ | |||
| 404 | + | {name: "nil", err: nil, want: false}, | |||
| 405 | + | {name: "context canceled", err: context.Canceled, want: false}, | |||
| 406 | + | {name: "context deadline exceeded", err: context.DeadlineExceeded, want: true}, | |||
| 407 | + | {name: "timeout net error", err: &net.DNSError{IsTimeout: true}, want: true}, | |||
| 408 | + | {name: "non-timeout net error", err: &net.DNSError{Err: "lookup failed"}, want: false}, | |||
| 409 | + | {name: "transient errno through net op error", err: &net.OpError{Op: "read", Net: "tcp", Err: syscall.ECONNRESET}, want: true}, | |||
| 410 | + | {name: "non-transient errno", err: syscall.EINVAL, want: false}, | |||
| 411 | + | {name: "ordinary error", err: errors.New("ordinary"), want: false}, | |||
| 412 | + | } | |||
| 413 | + | ||||
| 414 | + | for _, tt := range tests { | |||
| 415 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 416 | + | if got := isRetryableRequestError(tt.err); got != tt.want { | |||
| 417 | + | t.Fatalf("isRetryableRequestError(%v) = %v, want %v", tt.err, got, tt.want) | |||
| 418 | + | } | |||
| 419 | + | }) | |||
| 420 | + | } | |||
| 421 | + | } | |||
| 422 | + | ||||
| 396 | func TestGitHubCheckerDoRequestWithRetry_RetryableStatus(t *testing.T) { | 423 | func TestGitHubCheckerDoRequestWithRetry_RetryableStatus(t *testing.T) { | ||
| 397 | var calls int32 | 424 | var calls int32 | ||
| 398 | tr := roundTripFunc(func(_ *http.Request) (*http.Response, error) { | 425 | tr := roundTripFunc(func(_ *http.Request) (*http.Response, error) { | ||
M internal/ui/coordinator/content/lifecycle.go +1 -6
| @@ -6,7 +6,6 @@ import ( | |||
| 6 | 6 | ||
| 7 | 7 | "github.com/bnema/dumber/internal/application/port" | |
| 8 | 8 | "github.com/bnema/dumber/internal/domain/entity" | |
| 9 | - | "github.com/bnema/dumber/internal/infrastructure/gtkutil" | |
| 10 | 9 | "github.com/bnema/dumber/internal/logging" | |
| 11 | 10 | "github.com/bnema/dumber/internal/ui/component" | |
| 12 | 11 | "github.com/bnema/dumber/internal/ui/input" | |
| @@ -167,11 +166,7 @@ func (c *Coordinator) WrapWidget(ctx context.Context, wv port.WebView) layout.Wi | |||
| 167 | 166 | return nil | |
| 168 | 167 | } | |
| 169 | 168 | ||
| 170 | - | gtkWidget := gtkutil.WidgetFromNativePointer(ptr) | |
| 171 | - | if gtkWidget == nil { | |
| 172 | - | return nil | |
| 173 | - | } | |
| 174 | - | widget := c.widgetFactory.WrapWidget(gtkWidget) | |
| 169 | + | widget := c.widgetFactory.WrapNativeWidget(ptr) | |
| 175 | 170 | ||
| 176 | 171 | // Attach gesture handler for mouse button 8/9 navigation | |
| 177 | 172 | if widget != nil { | |
| @@ -6,7 +6,6 @@ import ( | |||||
| 6 | 6 | ||||
| 7 | "github.com/bnema/dumber/internal/application/port" | 7 | "github.com/bnema/dumber/internal/application/port" | ||
| 8 | "github.com/bnema/dumber/internal/domain/entity" | 8 | "github.com/bnema/dumber/internal/domain/entity" | ||
| 9 | - | "github.com/bnema/dumber/internal/infrastructure/gtkutil" | |||
| 10 | "github.com/bnema/dumber/internal/logging" | 9 | "github.com/bnema/dumber/internal/logging" | ||
| 11 | "github.com/bnema/dumber/internal/ui/component" | 10 | "github.com/bnema/dumber/internal/ui/component" | ||
| 12 | "github.com/bnema/dumber/internal/ui/input" | 11 | "github.com/bnema/dumber/internal/ui/input" | ||
| @@ -167,11 +166,7 @@ func (c *Coordinator) WrapWidget(ctx context.Context, wv port.WebView) layout.Wi | |||||
| 167 | return nil | 166 | return nil | ||
| 168 | } | 167 | } | ||
| 169 | 168 | ||||
| 170 | - | gtkWidget := gtkutil.WidgetFromNativePointer(ptr) | 169 | + | widget := c.widgetFactory.WrapNativeWidget(ptr) |
| 171 | - | if gtkWidget == nil { | |||
| 172 | - | return nil | |||
| 173 | - | } | |||
| 174 | - | widget := c.widgetFactory.WrapWidget(gtkWidget) | |||
| 175 | 170 | ||||
| 176 | // Attach gesture handler for mouse button 8/9 navigation | 171 | // Attach gesture handler for mouse button 8/9 navigation | ||
| 177 | if widget != nil { | 172 | if widget != nil { | ||
M internal/ui/coordinator/content/lifecycle_test.go +70 -0
| @@ -348,6 +348,76 @@ func TestLifecycle_RegisterPopupWebView_IgnoresNil(t *testing.T) { | |||
| 348 | 348 | assert.Nil(t, c.GetWebView("popup-1")) | |
| 349 | 349 | } | |
| 350 | 350 | ||
| 351 | + | type nativeWebViewStub struct { | |
| 352 | + | port.WebView | |
| 353 | + | ptr uintptr | |
| 354 | + | } | |
| 355 | + | ||
| 356 | + | func (s *nativeWebViewStub) NativeWidget() uintptr { | |
| 357 | + | return s.ptr | |
| 358 | + | } | |
| 359 | + | ||
| 360 | + | func TestLifecycle_WrapWidget_ReturnsNilForNilWebView(t *testing.T) { | |
| 361 | + | t.Parallel() | |
| 362 | + | ||
| 363 | + | factory := layoutmocks.NewMockWidgetFactory(t) | |
| 364 | + | c := newMinimalCoordinator() | |
| 365 | + | c.widgetFactory = factory | |
| 366 | + | ||
| 367 | + | assert.Nil(t, c.WrapWidget(context.Background(), nil)) | |
| 368 | + | } | |
| 369 | + | ||
| 370 | + | func TestLifecycle_WrapWidget_ReturnsNilForMissingNativeProvider(t *testing.T) { | |
| 371 | + | t.Parallel() | |
| 372 | + | ||
| 373 | + | wv := mocks.NewMockWebView(t) | |
| 374 | + | factory := layoutmocks.NewMockWidgetFactory(t) | |
| 375 | + | c := newMinimalCoordinator() | |
| 376 | + | c.widgetFactory = factory | |
| 377 | + | ||
| 378 | + | assert.Nil(t, c.WrapWidget(context.Background(), wv)) | |
| 379 | + | } | |
| 380 | + | ||
| 381 | + | func TestLifecycle_WrapWidget_ReturnsNilForZeroNativePointer(t *testing.T) { | |
| 382 | + | t.Parallel() | |
| 383 | + | ||
| 384 | + | base := mocks.NewMockWebView(t) | |
| 385 | + | wv := &nativeWebViewStub{WebView: base} | |
| 386 | + | factory := layoutmocks.NewMockWidgetFactory(t) | |
| 387 | + | c := newMinimalCoordinator() | |
| 388 | + | c.widgetFactory = factory | |
| 389 | + | ||
| 390 | + | assert.Nil(t, c.WrapWidget(context.Background(), wv)) | |
| 391 | + | } | |
| 392 | + | ||
| 393 | + | func TestLifecycle_WrapWidget_ReturnsNilForNilWrappedWidget(t *testing.T) { | |
| 394 | + | t.Parallel() | |
| 395 | + | ||
| 396 | + | base := mocks.NewMockWebView(t) | |
| 397 | + | wv := &nativeWebViewStub{WebView: base, ptr: 123} | |
| 398 | + | factory := layoutmocks.NewMockWidgetFactory(t) | |
| 399 | + | factory.EXPECT().WrapNativeWidget(uintptr(123)).Return(nil).Once() | |
| 400 | + | c := newMinimalCoordinator() | |
| 401 | + | c.widgetFactory = factory | |
| 402 | + | ||
| 403 | + | assert.Nil(t, c.WrapWidget(context.Background(), wv)) | |
| 404 | + | } | |
| 405 | + | ||
| 406 | + | func TestLifecycle_WrapWidget_RequestsGtkWidgetForGestureSetup(t *testing.T) { | |
| 407 | + | t.Parallel() | |
| 408 | + | ||
| 409 | + | base := mocks.NewMockWebView(t) | |
| 410 | + | wv := &nativeWebViewStub{WebView: base, ptr: 456} | |
| 411 | + | factory := layoutmocks.NewMockWidgetFactory(t) | |
| 412 | + | widget := layoutmocks.NewMockWidget(t) | |
| 413 | + | factory.EXPECT().WrapNativeWidget(uintptr(456)).Return(widget).Once() | |
| 414 | + | widget.EXPECT().GtkWidget().Return(nil).Once() | |
| 415 | + | c := newMinimalCoordinator() | |
| 416 | + | c.widgetFactory = factory | |
| 417 | + | ||
| 418 | + | assert.Same(t, widget, c.WrapWidget(context.Background(), wv)) | |
| 419 | + | } | |
| 420 | + | ||
| 351 | 421 | type syncViewportContextKey struct{} | |
| 352 | 422 | ||
| 353 | 423 | type syncViewportCapableStub struct { | |
| @@ -348,6 +348,76 @@ func TestLifecycle_RegisterPopupWebView_IgnoresNil(t *testing.T) { | |||||
| 348 | assert.Nil(t, c.GetWebView("popup-1")) | 348 | assert.Nil(t, c.GetWebView("popup-1")) | ||
| 349 | } | 349 | } | ||
| 350 | 350 | ||||
| 351 | + | type nativeWebViewStub struct { | |||
| 352 | + | port.WebView | |||
| 353 | + | ptr uintptr | |||
| 354 | + | } | |||
| 355 | + | ||||
| 356 | + | func (s *nativeWebViewStub) NativeWidget() uintptr { | |||
| 357 | + | return s.ptr | |||
| 358 | + | } | |||
| 359 | + | ||||
| 360 | + | func TestLifecycle_WrapWidget_ReturnsNilForNilWebView(t *testing.T) { | |||
| 361 | + | t.Parallel() | |||
| 362 | + | ||||
| 363 | + | factory := layoutmocks.NewMockWidgetFactory(t) | |||
| 364 | + | c := newMinimalCoordinator() | |||
| 365 | + | c.widgetFactory = factory | |||
| 366 | + | ||||
| 367 | + | assert.Nil(t, c.WrapWidget(context.Background(), nil)) | |||
| 368 | + | } | |||
| 369 | + | ||||
| 370 | + | func TestLifecycle_WrapWidget_ReturnsNilForMissingNativeProvider(t *testing.T) { | |||
| 371 | + | t.Parallel() | |||
| 372 | + | ||||
| 373 | + | wv := mocks.NewMockWebView(t) | |||
| 374 | + | factory := layoutmocks.NewMockWidgetFactory(t) | |||
| 375 | + | c := newMinimalCoordinator() | |||
| 376 | + | c.widgetFactory = factory | |||
| 377 | + | ||||
| 378 | + | assert.Nil(t, c.WrapWidget(context.Background(), wv)) | |||
| 379 | + | } | |||
| 380 | + | ||||
| 381 | + | func TestLifecycle_WrapWidget_ReturnsNilForZeroNativePointer(t *testing.T) { | |||
| 382 | + | t.Parallel() | |||
| 383 | + | ||||
| 384 | + | base := mocks.NewMockWebView(t) | |||
| 385 | + | wv := &nativeWebViewStub{WebView: base} | |||
| 386 | + | factory := layoutmocks.NewMockWidgetFactory(t) | |||
| 387 | + | c := newMinimalCoordinator() | |||
| 388 | + | c.widgetFactory = factory | |||
| 389 | + | ||||
| 390 | + | assert.Nil(t, c.WrapWidget(context.Background(), wv)) | |||
| 391 | + | } | |||
| 392 | + | ||||
| 393 | + | func TestLifecycle_WrapWidget_ReturnsNilForNilWrappedWidget(t *testing.T) { | |||
| 394 | + | t.Parallel() | |||
| 395 | + | ||||
| 396 | + | base := mocks.NewMockWebView(t) | |||
| 397 | + | wv := &nativeWebViewStub{WebView: base, ptr: 123} | |||
| 398 | + | factory := layoutmocks.NewMockWidgetFactory(t) | |||
| 399 | + | factory.EXPECT().WrapNativeWidget(uintptr(123)).Return(nil).Once() | |||
| 400 | + | c := newMinimalCoordinator() | |||
| 401 | + | c.widgetFactory = factory | |||
| 402 | + | ||||
| 403 | + | assert.Nil(t, c.WrapWidget(context.Background(), wv)) | |||
| 404 | + | } | |||
| 405 | + | ||||
| 406 | + | func TestLifecycle_WrapWidget_RequestsGtkWidgetForGestureSetup(t *testing.T) { | |||
| 407 | + | t.Parallel() | |||
| 408 | + | ||||
| 409 | + | base := mocks.NewMockWebView(t) | |||
| 410 | + | wv := &nativeWebViewStub{WebView: base, ptr: 456} | |||
| 411 | + | factory := layoutmocks.NewMockWidgetFactory(t) | |||
| 412 | + | widget := layoutmocks.NewMockWidget(t) | |||
| 413 | + | factory.EXPECT().WrapNativeWidget(uintptr(456)).Return(widget).Once() | |||
| 414 | + | widget.EXPECT().GtkWidget().Return(nil).Once() | |||
| 415 | + | c := newMinimalCoordinator() | |||
| 416 | + | c.widgetFactory = factory | |||
| 417 | + | ||||
| 418 | + | assert.Same(t, widget, c.WrapWidget(context.Background(), wv)) | |||
| 419 | + | } | |||
| 420 | + | ||||
| 351 | type syncViewportContextKey struct{} | 421 | type syncViewportContextKey struct{} | ||
| 352 | 422 | ||||
| 353 | type syncViewportCapableStub struct { | 423 | type syncViewportCapableStub struct { | ||
M internal/ui/layout/gtk_widgets.go +7 -0
| @@ -827,3 +827,10 @@ func (f *GtkWidgetFactory) WrapWidget(w *gtk.Widget) Widget { | |||
| 827 | 827 | } | |
| 828 | 828 | return >kWidget{inner: w} | |
| 829 | 829 | } | |
| 830 | + | ||
| 831 | + | func (f *GtkWidgetFactory) WrapNativeWidget(ptr uintptr) Widget { | |
| 832 | + | if ptr == 0 { | |
| 833 | + | return nil | |
| 834 | + | } | |
| 835 | + | return f.WrapWidget(gtk.WidgetNewFromInternalPtr(ptr)) | |
| 836 | + | } | |
| @@ -827,3 +827,10 @@ func (f *GtkWidgetFactory) WrapWidget(w *gtk.Widget) Widget { | |||||
| 827 | } | 827 | } | ||
| 828 | return >kWidget{inner: w} | 828 | return >kWidget{inner: w} | ||
| 829 | } | 829 | } | ||
| 830 | + | ||||
| 831 | + | func (f *GtkWidgetFactory) WrapNativeWidget(ptr uintptr) Widget { | |||
| 832 | + | if ptr == 0 { | |||
| 833 | + | return nil | |||
| 834 | + | } | |||
| 835 | + | return f.WrapWidget(gtk.WidgetNewFromInternalPtr(ptr)) | |||
| 836 | + | } | |||
M internal/ui/layout/mocks/widgets.go +53 -0
| @@ -13898,6 +13898,59 @@ func (_c *MockWidgetFactory_NewSpinner_Call) RunAndReturn(run func() layout.Spin | |||
| 13898 | 13898 | return _c | |
| 13899 | 13899 | } | |
| 13900 | 13900 | ||
| 13901 | + | // WrapNativeWidget provides a mock function for the type MockWidgetFactory | |
| 13902 | + | func (_mock *MockWidgetFactory) WrapNativeWidget(ptr uintptr) layout.Widget { | |
| 13903 | + | ret := _mock.Called(ptr) | |
| 13904 | + | ||
| 13905 | + | if len(ret) == 0 { | |
| 13906 | + | panic("no return value specified for WrapNativeWidget") | |
| 13907 | + | } | |
| 13908 | + | ||
| 13909 | + | var r0 layout.Widget | |
| 13910 | + | if returnFunc, ok := ret.Get(0).(func(uintptr) layout.Widget); ok { | |
| 13911 | + | r0 = returnFunc(ptr) | |
| 13912 | + | } else { | |
| 13913 | + | if ret.Get(0) != nil { | |
| 13914 | + | r0 = ret.Get(0).(layout.Widget) | |
| 13915 | + | } | |
| 13916 | + | } | |
| 13917 | + | return r0 | |
| 13918 | + | } | |
| 13919 | + | ||
| 13920 | + | // MockWidgetFactory_WrapNativeWidget_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WrapNativeWidget' | |
| 13921 | + | type MockWidgetFactory_WrapNativeWidget_Call struct { | |
| 13922 | + | *mock.Call | |
| 13923 | + | } | |
| 13924 | + | ||
| 13925 | + | // WrapNativeWidget is a helper method to define mock.On call | |
| 13926 | + | // - ptr uintptr | |
| 13927 | + | func (_e *MockWidgetFactory_Expecter) WrapNativeWidget(ptr any) *MockWidgetFactory_WrapNativeWidget_Call { | |
| 13928 | + | return &MockWidgetFactory_WrapNativeWidget_Call{Call: _e.mock.On("WrapNativeWidget", ptr)} | |
| 13929 | + | } | |
| 13930 | + | ||
| 13931 | + | func (_c *MockWidgetFactory_WrapNativeWidget_Call) Run(run func(ptr uintptr)) *MockWidgetFactory_WrapNativeWidget_Call { | |
| 13932 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 13933 | + | var arg0 uintptr | |
| 13934 | + | if args[0] != nil { | |
| 13935 | + | arg0 = args[0].(uintptr) | |
| 13936 | + | } | |
| 13937 | + | run( | |
| 13938 | + | arg0, | |
| 13939 | + | ) | |
| 13940 | + | }) | |
| 13941 | + | return _c | |
| 13942 | + | } | |
| 13943 | + | ||
| 13944 | + | func (_c *MockWidgetFactory_WrapNativeWidget_Call) Return(widget layout.Widget) *MockWidgetFactory_WrapNativeWidget_Call { | |
| 13945 | + | _c.Call.Return(widget) | |
| 13946 | + | return _c | |
| 13947 | + | } | |
| 13948 | + | ||
| 13949 | + | func (_c *MockWidgetFactory_WrapNativeWidget_Call) RunAndReturn(run func(ptr uintptr) layout.Widget) *MockWidgetFactory_WrapNativeWidget_Call { | |
| 13950 | + | _c.Call.Return(run) | |
| 13951 | + | return _c | |
| 13952 | + | } | |
| 13953 | + | ||
| 13901 | 13954 | // WrapWidget provides a mock function for the type MockWidgetFactory | |
| 13902 | 13955 | func (_mock *MockWidgetFactory) WrapWidget(w *gtk.Widget) layout.Widget { | |
| 13903 | 13956 | ret := _mock.Called(w) | |
| @@ -13898,6 +13898,59 @@ func (_c *MockWidgetFactory_NewSpinner_Call) RunAndReturn(run func() layout.Spin | |||||
| 13898 | return _c | 13898 | return _c | ||
| 13899 | } | 13899 | } | ||
| 13900 | 13900 | ||||
| 13901 | + | // WrapNativeWidget provides a mock function for the type MockWidgetFactory | |||
| 13902 | + | func (_mock *MockWidgetFactory) WrapNativeWidget(ptr uintptr) layout.Widget { | |||
| 13903 | + | ret := _mock.Called(ptr) | |||
| 13904 | + | ||||
| 13905 | + | if len(ret) == 0 { | |||
| 13906 | + | panic("no return value specified for WrapNativeWidget") | |||
| 13907 | + | } | |||
| 13908 | + | ||||
| 13909 | + | var r0 layout.Widget | |||
| 13910 | + | if returnFunc, ok := ret.Get(0).(func(uintptr) layout.Widget); ok { | |||
| 13911 | + | r0 = returnFunc(ptr) | |||
| 13912 | + | } else { | |||
| 13913 | + | if ret.Get(0) != nil { | |||
| 13914 | + | r0 = ret.Get(0).(layout.Widget) | |||
| 13915 | + | } | |||
| 13916 | + | } | |||
| 13917 | + | return r0 | |||
| 13918 | + | } | |||
| 13919 | + | ||||
| 13920 | + | // MockWidgetFactory_WrapNativeWidget_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WrapNativeWidget' | |||
| 13921 | + | type MockWidgetFactory_WrapNativeWidget_Call struct { | |||
| 13922 | + | *mock.Call | |||
| 13923 | + | } | |||
| 13924 | + | ||||
| 13925 | + | // WrapNativeWidget is a helper method to define mock.On call | |||
| 13926 | + | // - ptr uintptr | |||
| 13927 | + | func (_e *MockWidgetFactory_Expecter) WrapNativeWidget(ptr any) *MockWidgetFactory_WrapNativeWidget_Call { | |||
| 13928 | + | return &MockWidgetFactory_WrapNativeWidget_Call{Call: _e.mock.On("WrapNativeWidget", ptr)} | |||
| 13929 | + | } | |||
| 13930 | + | ||||
| 13931 | + | func (_c *MockWidgetFactory_WrapNativeWidget_Call) Run(run func(ptr uintptr)) *MockWidgetFactory_WrapNativeWidget_Call { | |||
| 13932 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 13933 | + | var arg0 uintptr | |||
| 13934 | + | if args[0] != nil { | |||
| 13935 | + | arg0 = args[0].(uintptr) | |||
| 13936 | + | } | |||
| 13937 | + | run( | |||
| 13938 | + | arg0, | |||
| 13939 | + | ) | |||
| 13940 | + | }) | |||
| 13941 | + | return _c | |||
| 13942 | + | } | |||
| 13943 | + | ||||
| 13944 | + | func (_c *MockWidgetFactory_WrapNativeWidget_Call) Return(widget layout.Widget) *MockWidgetFactory_WrapNativeWidget_Call { | |||
| 13945 | + | _c.Call.Return(widget) | |||
| 13946 | + | return _c | |||
| 13947 | + | } | |||
| 13948 | + | ||||
| 13949 | + | func (_c *MockWidgetFactory_WrapNativeWidget_Call) RunAndReturn(run func(ptr uintptr) layout.Widget) *MockWidgetFactory_WrapNativeWidget_Call { | |||
| 13950 | + | _c.Call.Return(run) | |||
| 13951 | + | return _c | |||
| 13952 | + | } | |||
| 13953 | + | ||||
| 13901 | // WrapWidget provides a mock function for the type MockWidgetFactory | 13954 | // WrapWidget provides a mock function for the type MockWidgetFactory | ||
| 13902 | func (_mock *MockWidgetFactory) WrapWidget(w *gtk.Widget) layout.Widget { | 13955 | func (_mock *MockWidgetFactory) WrapWidget(w *gtk.Widget) layout.Widget { | ||
| 13903 | ret := _mock.Called(w) | 13956 | ret := _mock.Called(w) | ||
M internal/ui/layout/widgets.go +1 -0
| @@ -241,6 +241,7 @@ type WidgetFactory interface { | |||
| 241 | 241 | ||
| 242 | 242 | // Wrap existing GTK widget | |
| 243 | 243 | WrapWidget(w *gtk.Widget) Widget | |
| 244 | + | WrapNativeWidget(ptr uintptr) Widget | |
| 244 | 245 | } | |
| 245 | 246 | ||
| 246 | 247 | // PaneRenderer creates pane view widgets from domain entities. | |
| @@ -241,6 +241,7 @@ type WidgetFactory interface { | |||||
| 241 | 241 | ||||
| 242 | // Wrap existing GTK widget | 242 | // Wrap existing GTK widget | ||
| 243 | WrapWidget(w *gtk.Widget) Widget | 243 | WrapWidget(w *gtk.Widget) Widget | ||
| 244 | + | WrapNativeWidget(ptr uintptr) Widget | |||
| 244 | } | 245 | } | ||
| 245 | 246 | ||||
| 246 | // PaneRenderer creates pane view widgets from domain entities. | 247 | // PaneRenderer creates pane view widgets from domain entities. | ||