feat(webkitgtk6): migrate to WebKitGTK 6 (GTK4), finalize UCM, add native zoom + GTK4 input, persistent cookies/cache via NetworkSession; runtime theme sync; rendering-mode flag and status API; media safe defaults; update README and CLAUDE; remove JS fallback bridge
- commit
- 2045a0ecf1e8599bb82f36ca474c85c3c3825b80
- author
- Brice · · 2025-09-09 23:11 UTC
- parent
- bf7c821
- signature
- unsigned
26 files · +1845 -198
M CLAUDE.md +27 -9
| @@ -3,8 +3,9 @@ | |||
| 3 | 3 | Auto-generated from all feature plans. Last updated: 2025-01-21 | |
| 4 | 4 | ||
| 5 | 5 | ## Active Technologies | |
| 6 | - | - Go 1.25.1 + WebKit2GTK (native), Cobra, Viper, SQLite3 (003-webkit2gtk-migration) | |
| 7 | - | - CGO bindings for WebKit2GTK C API integration | |
| 6 | + | - Go 1.25.1 + WebKitGTK 6.0 (GTK4), Cobra, Viper, SQLite3 (004-migrate-to-webkitgtk) | |
| 7 | + | - CGO bindings for WebKitGTK 6.0 C API integration | |
| 8 | + | - GPU rendering via Vulkan/OpenGL with configurable modes | |
| 8 | 9 | ||
| 9 | 10 | ## Project Structure | |
| 10 | 11 | ``` | |
| @@ -51,14 +52,31 @@ Go 1.25.1: Follow standard Go conventions with gofmt | |||
| 51 | 52 | - Constitutional simplicity principles | |
| 52 | 53 | ||
| 53 | 54 | ## Recent Changes | |
| 54 | - | - 003-webkit2gtk-migration: Migrating from Wails to native WebKit2GTK with custom CGO bindings | |
| 55 | + | - 004-migrate-to-webkitgtk: Migrating to WebKitGTK 6.0 (GTK4) for Vulkan GPU rendering support (UCM restored, GTK4 input, persistent cookies/cache) | |
| 56 | + | - 003-webkit2gtk-migration: Migrated from Wails to native WebKit2GTK with custom CGO bindings | |
| 55 | 57 | - 002-browser-controls-ui: Added keyboard/mouse controls, zoom persistence, clipboard integration | |
| 56 | 58 | ||
| 57 | - | ## WebKit2GTK Integration | |
| 58 | - | - Reference repository: /home/brice/dev/clone/webkit2gtk-reference | |
| 59 | - | - pkg/webkit: Reusable WebKit2GTK Go bindings | |
| 60 | - | - Direct GTK event handling for keyboard shortcuts | |
| 61 | - | - WebKitUserContentManager for script injection | |
| 59 | + | ## WebKitGTK 6.0 Integration | |
| 60 | + | - pkg/webkit: Reusable WebKitGTK 6.0 Go bindings (GTK4) | |
| 61 | + | - Hardware acceleration via Vulkan/OpenGL with configurable rendering modes | |
| 62 | + | - Direct GTK4 event handling with GtkEventController | |
| 63 | + | - WebKitNetworkSession replaces WebKitWebContext | |
| 64 | + | - Rendering modes: auto (default), gpu, cpu via --rendering-mode flag | |
| 62 | 65 | ||
| 63 | 66 | <!-- MANUAL ADDITIONS START --> | |
| 64 | - | <!-- MANUAL ADDITIONS END -->⏎̸ | |
| 67 | + | ### Migration Status (004-migrate-to-webkitgtk) | |
| 68 | + | - WebView construction via `g_object_new(WEBKIT_TYPE_WEB_VIEW, …)` (GTK4). | |
| 69 | + | - Native UCM messaging (script-message-received::dumber) replaces fallback bridge. | |
| 70 | + | - Persistent storage: cookies (SQLite) and cache via `WebKitNetworkSession`. | |
| 71 | + | - Runtime theme sync: listens to GTK `gtk-application-prefer-dark-theme` and updates page color-scheme live. | |
| 72 | + | - Native zoom shortcuts + GTK4 key/mouse controllers (including AZERTY fix for zoom-out). | |
| 73 | + | - Media: GStreamer required; default “safe” startup demotes VAAPI and uses a safe sink to avoid crashes (can be disabled). | |
| 74 | + | ||
| 75 | + | ### GPU Rendering | |
| 76 | + | - GTK4 renderer can use Vulkan; WebKit settings map `auto|gpu|cpu` to the appropriate hardware acceleration policy where available. | |
| 77 | + | - Status endpoint: `dumb://homepage/api/rendering/status` reports current configured mode. | |
| 78 | + | ||
| 79 | + | ### Next | |
| 80 | + | - Expose cookie policy/media mode toggles in config/CLI. | |
| 81 | + | - “Clear browsing data” surfaced via CookieManager/WebsiteDataManager. | |
| 82 | + | <!-- MANUAL ADDITIONS END --> | |
| @@ -3,8 +3,9 @@ | |||||
| 3 | Auto-generated from all feature plans. Last updated: 2025-01-21 | 3 | Auto-generated from all feature plans. Last updated: 2025-01-21 | ||
| 4 | 4 | ||||
| 5 | ## Active Technologies | 5 | ## Active Technologies | ||
| 6 | - | - Go 1.25.1 + WebKit2GTK (native), Cobra, Viper, SQLite3 (003-webkit2gtk-migration) | 6 | + | - Go 1.25.1 + WebKitGTK 6.0 (GTK4), Cobra, Viper, SQLite3 (004-migrate-to-webkitgtk) |
| 7 | - | - CGO bindings for WebKit2GTK C API integration | 7 | + | - CGO bindings for WebKitGTK 6.0 C API integration |
| 8 | + | - GPU rendering via Vulkan/OpenGL with configurable modes | |||
| 8 | 9 | ||||
| 9 | ## Project Structure | 10 | ## Project Structure | ||
| 10 | ``` | 11 | ``` | ||
| @@ -51,14 +52,31 @@ Go 1.25.1: Follow standard Go conventions with gofmt | |||||
| 51 | - Constitutional simplicity principles | 52 | - Constitutional simplicity principles | ||
| 52 | 53 | ||||
| 53 | ## Recent Changes | 54 | ## Recent Changes | ||
| 54 | - | - 003-webkit2gtk-migration: Migrating from Wails to native WebKit2GTK with custom CGO bindings | 55 | + | - 004-migrate-to-webkitgtk: Migrating to WebKitGTK 6.0 (GTK4) for Vulkan GPU rendering support (UCM restored, GTK4 input, persistent cookies/cache) |
| 56 | + | - 003-webkit2gtk-migration: Migrated from Wails to native WebKit2GTK with custom CGO bindings | |||
| 55 | - 002-browser-controls-ui: Added keyboard/mouse controls, zoom persistence, clipboard integration | 57 | - 002-browser-controls-ui: Added keyboard/mouse controls, zoom persistence, clipboard integration | ||
| 56 | 58 | ||||
| 57 | - | ## WebKit2GTK Integration | 59 | + | ## WebKitGTK 6.0 Integration |
| 58 | - | - Reference repository: /home/brice/dev/clone/webkit2gtk-reference | 60 | + | - pkg/webkit: Reusable WebKitGTK 6.0 Go bindings (GTK4) |
| 59 | - | - pkg/webkit: Reusable WebKit2GTK Go bindings | 61 | + | - Hardware acceleration via Vulkan/OpenGL with configurable rendering modes |
| 60 | - | - Direct GTK event handling for keyboard shortcuts | 62 | + | - Direct GTK4 event handling with GtkEventController |
| 61 | - | - WebKitUserContentManager for script injection | 63 | + | - WebKitNetworkSession replaces WebKitWebContext |
| 64 | + | - Rendering modes: auto (default), gpu, cpu via --rendering-mode flag | |||
| 62 | 65 | ||||
| 63 | <!-- MANUAL ADDITIONS START --> | 66 | <!-- MANUAL ADDITIONS START --> | ||
| 64 | - | <!-- MANUAL ADDITIONS END -->⏎̸ | 67 | + | ### Migration Status (004-migrate-to-webkitgtk) |
| 68 | + | - WebView construction via `g_object_new(WEBKIT_TYPE_WEB_VIEW, …)` (GTK4). | |||
| 69 | + | - Native UCM messaging (script-message-received::dumber) replaces fallback bridge. | |||
| 70 | + | - Persistent storage: cookies (SQLite) and cache via `WebKitNetworkSession`. | |||
| 71 | + | - Runtime theme sync: listens to GTK `gtk-application-prefer-dark-theme` and updates page color-scheme live. | |||
| 72 | + | - Native zoom shortcuts + GTK4 key/mouse controllers (including AZERTY fix for zoom-out). | |||
| 73 | + | - Media: GStreamer required; default “safe” startup demotes VAAPI and uses a safe sink to avoid crashes (can be disabled). | |||
| 74 | + | ||||
| 75 | + | ### GPU Rendering | |||
| 76 | + | - GTK4 renderer can use Vulkan; WebKit settings map `auto|gpu|cpu` to the appropriate hardware acceleration policy where available. | |||
| 77 | + | - Status endpoint: `dumb://homepage/api/rendering/status` reports current configured mode. | |||
| 78 | + | ||||
| 79 | + | ### Next | |||
| 80 | + | - Expose cookie policy/media mode toggles in config/CLI. | |||
| 81 | + | - “Clear browsing data” surfaced via CookieManager/WebsiteDataManager. | |||
| 82 | + | <!-- MANUAL ADDITIONS END --> | |||
M Makefile +31 -17
| @@ -7,6 +7,9 @@ BINARY_NAME=dumber | |||
| 7 | 7 | MAIN_PATH=. | |
| 8 | 8 | DIST_DIR=dist | |
| 9 | 9 | ||
| 10 | + | # Local caches to avoid $HOME permission issues in sandboxed environments | |
| 11 | + | GOENV=GOMODCACHE=$(PWD)/tmp/go-mod GOCACHE=$(PWD)/tmp/go-cache GOTMPDIR=$(PWD)/tmp | |
| 12 | + | ||
| 10 | 13 | # Version information from git | |
| 11 | 14 | VERSION=$(shell git describe --tags --always --dirty 2>/dev/null || echo "v0.0.0-dev") | |
| 12 | 15 | COMMIT=$(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown") | |
| @@ -23,8 +26,8 @@ help: ## Show this help message | |||
| 23 | 26 | # Build targets | |
| 24 | 27 | build: build-frontend ## Build the application (frontend assets, then Go binary) | |
| 25 | 28 | @echo "Building $(BINARY_NAME) $(VERSION)..." | |
| 26 | - | @mkdir -p $(DIST_DIR) | |
| 27 | - | CGO_ENABLED=1 go build $(LDFLAGS) -o $(DIST_DIR)/$(BINARY_NAME) $(MAIN_PATH) | |
| 29 | + | @mkdir -p $(DIST_DIR) tmp tmp/go-cache tmp/go-mod | |
| 30 | + | $(GOENV) CGO_ENABLED=1 go build $(LDFLAGS) -o $(DIST_DIR)/$(BINARY_NAME) $(MAIN_PATH) | |
| 28 | 31 | ||
| 29 | 32 | build-frontend: ## Build TypeScript frontend | |
| 30 | 33 | @echo "Building TypeScript frontend..." | |
| @@ -33,19 +36,28 @@ build-frontend: ## Build TypeScript frontend | |||
| 33 | 36 | ||
| 34 | 37 | build-static: ## Build static binary (CGO disabled, CLI-only functionality) | |
| 35 | 38 | @echo "Building static $(BINARY_NAME) $(VERSION) (CLI-only)..." | |
| 36 | - | @mkdir -p $(DIST_DIR) | |
| 37 | - | CGO_ENABLED=0 go build $(LDFLAGS) -o $(DIST_DIR)/$(BINARY_NAME)-static $(MAIN_PATH) | |
| 39 | + | @mkdir -p $(DIST_DIR) tmp tmp/go-cache tmp/go-mod | |
| 40 | + | $(GOENV) CGO_ENABLED=0 go build $(LDFLAGS) -o $(DIST_DIR)/$(BINARY_NAME)-static $(MAIN_PATH) | |
| 38 | 41 | ||
| 39 | - | # GUI build with WebKit2GTK | |
| 42 | + | # GUI build with WebKitGTK 6.0 (GTK4) | |
| 40 | 43 | .PHONY: build-gui run-gui | |
| 41 | - | build-gui: build-frontend ## Build GUI binary with native WebKit2GTK (requires dev packages) | |
| 44 | + | build-gui: build-frontend ## Build GUI binary with native WebKitGTK 6.0 (requires GTK4/WebKitGTK 6 dev packages) | |
| 42 | 45 | @echo "Building $(BINARY_NAME) (GUI, webkit_cgo)…" | |
| 43 | - | @mkdir -p $(DIST_DIR) | |
| 44 | - | CGO_ENABLED=1 go build $(LDFLAGS) -tags=webkit_cgo -o $(DIST_DIR)/$(BINARY_NAME) $(MAIN_PATH) | |
| 46 | + | @mkdir -p $(DIST_DIR) tmp tmp/go-cache tmp/go-mod | |
| 47 | + | $(GOENV) CGO_ENABLED=1 go build $(LDFLAGS) -tags=webkit_cgo -o $(DIST_DIR)/$(BINARY_NAME) $(MAIN_PATH) | |
| 45 | 48 | ||
| 46 | - | run-gui: ## Run the GUI with native WebKit2GTK (requires dev packages) | |
| 49 | + | run-gui: ## Run the GUI with native WebKitGTK 6.0 (requires GTK4/WebKitGTK 6 dev packages) | |
| 47 | 50 | @echo "Running GUI (webkit_cgo)…" | |
| 48 | - | CGO_ENABLED=1 go run -tags=webkit_cgo $(MAIN_PATH) | |
| 51 | + | @mkdir -p tmp tmp/go-cache tmp/go-mod | |
| 52 | + | $(GOENV) CGO_ENABLED=1 go run -tags=webkit_cgo $(MAIN_PATH) | |
| 53 | + | ||
| 54 | + | .PHONY: check-webkit | |
| 55 | + | check-webkit: ## Check system has GTK4/WebKitGTK 6.0/JavaScriptCore 6.0 | |
| 56 | + | @echo "Checking pkg-config versions..." | |
| 57 | + | @which pkg-config >/dev/null 2>&1 || (echo "pkg-config not found" && exit 1) | |
| 58 | + | @echo "gtk4: $$(pkg-config --modversion gtk4 2>/dev/null || echo not found)" | |
| 59 | + | @echo "webkitgtk-6.0: $$(pkg-config --modversion webkitgtk-6.0 2>/dev/null || echo not found)" | |
| 60 | + | @echo "javascriptcoregtk-6.0: $$(pkg-config --modversion javascriptcoregtk-6.0 2>/dev/null || echo not found)" | |
| 49 | 61 | ||
| 50 | 62 | # Development targets | |
| 51 | 63 | dev: ## Run in development mode | |
| @@ -60,11 +72,13 @@ generate: ## Generate code (SQLC) | |||
| 60 | 72 | # Testing | |
| 61 | 73 | test: ## Run tests | |
| 62 | 74 | @echo "Running tests..." | |
| 63 | - | CGO_ENABLED=0 go test -v ./... | |
| 75 | + | @mkdir -p tmp tmp/go-cache tmp/go-mod | |
| 76 | + | $(GOENV) CGO_ENABLED=0 go test -v ./... | |
| 64 | 77 | ||
| 65 | 78 | test-race: ## Run tests with race detection | |
| 66 | 79 | @echo "Running tests with race detection..." | |
| 67 | - | CGO_ENABLED=1 go test -race -v ./... | |
| 80 | + | @mkdir -p tmp tmp/go-cache tmp/go-mod | |
| 81 | + | $(GOENV) CGO_ENABLED=1 go test -race -v ./... | |
| 68 | 82 | ||
| 69 | 83 | # Linting | |
| 70 | 84 | lint: ## Run golangci-lint | |
| @@ -100,15 +114,15 @@ init: install-tools ## Initialize project dependencies and tools | |||
| 100 | 114 | check: ## Check that all tools and dependencies are working | |
| 101 | 115 | @echo "Checking project setup..." | |
| 102 | 116 | @echo "Go version:" | |
| 103 | - | @go version | |
| 117 | + | @$(GOENV) go version | |
| 104 | 118 | @echo "\nSQLC version:" | |
| 105 | - | @sqlc version | |
| 119 | + | @$(GOENV) sqlc version | |
| 106 | 120 | @echo "\nGolangci-lint version:" | |
| 107 | - | @golangci-lint version | |
| 121 | + | @$(GOENV) golangci-lint version | |
| 108 | 122 | @echo "\nBuilding project..." | |
| 109 | - | @make build | |
| 123 | + | @$(MAKE) build | |
| 110 | 124 | @echo "\nTesting built binary..." | |
| 111 | 125 | @$(DIST_DIR)/$(BINARY_NAME) version | |
| 112 | 126 | @echo "\nRunning tests..." | |
| 113 | - | @make test | |
| 127 | + | @$(MAKE) test | |
| 114 | 128 | @echo "\n✅ All checks passed! Project is ready for development." | |
| @@ -7,6 +7,9 @@ BINARY_NAME=dumber | |||||
| 7 | MAIN_PATH=. | 7 | MAIN_PATH=. | ||
| 8 | DIST_DIR=dist | 8 | DIST_DIR=dist | ||
| 9 | 9 | ||||
| 10 | + | # Local caches to avoid $HOME permission issues in sandboxed environments | |||
| 11 | + | GOENV=GOMODCACHE=$(PWD)/tmp/go-mod GOCACHE=$(PWD)/tmp/go-cache GOTMPDIR=$(PWD)/tmp | |||
| 12 | + | ||||
| 10 | # Version information from git | 13 | # Version information from git | ||
| 11 | VERSION=$(shell git describe --tags --always --dirty 2>/dev/null || echo "v0.0.0-dev") | 14 | VERSION=$(shell git describe --tags --always --dirty 2>/dev/null || echo "v0.0.0-dev") | ||
| 12 | COMMIT=$(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown") | 15 | COMMIT=$(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown") | ||
| @@ -23,8 +26,8 @@ help: ## Show this help message | |||||
| 23 | # Build targets | 26 | # Build targets | ||
| 24 | build: build-frontend ## Build the application (frontend assets, then Go binary) | 27 | build: build-frontend ## Build the application (frontend assets, then Go binary) | ||
| 25 | @echo "Building $(BINARY_NAME) $(VERSION)..." | 28 | @echo "Building $(BINARY_NAME) $(VERSION)..." | ||
| 26 | - | @mkdir -p $(DIST_DIR) | 29 | + | @mkdir -p $(DIST_DIR) tmp tmp/go-cache tmp/go-mod |
| 27 | - | CGO_ENABLED=1 go build $(LDFLAGS) -o $(DIST_DIR)/$(BINARY_NAME) $(MAIN_PATH) | 30 | + | $(GOENV) CGO_ENABLED=1 go build $(LDFLAGS) -o $(DIST_DIR)/$(BINARY_NAME) $(MAIN_PATH) |
| 28 | 31 | ||||
| 29 | build-frontend: ## Build TypeScript frontend | 32 | build-frontend: ## Build TypeScript frontend | ||
| 30 | @echo "Building TypeScript frontend..." | 33 | @echo "Building TypeScript frontend..." | ||
| @@ -33,19 +36,28 @@ build-frontend: ## Build TypeScript frontend | |||||
| 33 | 36 | ||||
| 34 | build-static: ## Build static binary (CGO disabled, CLI-only functionality) | 37 | build-static: ## Build static binary (CGO disabled, CLI-only functionality) | ||
| 35 | @echo "Building static $(BINARY_NAME) $(VERSION) (CLI-only)..." | 38 | @echo "Building static $(BINARY_NAME) $(VERSION) (CLI-only)..." | ||
| 36 | - | @mkdir -p $(DIST_DIR) | 39 | + | @mkdir -p $(DIST_DIR) tmp tmp/go-cache tmp/go-mod |
| 37 | - | CGO_ENABLED=0 go build $(LDFLAGS) -o $(DIST_DIR)/$(BINARY_NAME)-static $(MAIN_PATH) | 40 | + | $(GOENV) CGO_ENABLED=0 go build $(LDFLAGS) -o $(DIST_DIR)/$(BINARY_NAME)-static $(MAIN_PATH) |
| 38 | 41 | ||||
| 39 | - | # GUI build with WebKit2GTK | 42 | + | # GUI build with WebKitGTK 6.0 (GTK4) |
| 40 | .PHONY: build-gui run-gui | 43 | .PHONY: build-gui run-gui | ||
| 41 | - | build-gui: build-frontend ## Build GUI binary with native WebKit2GTK (requires dev packages) | 44 | + | build-gui: build-frontend ## Build GUI binary with native WebKitGTK 6.0 (requires GTK4/WebKitGTK 6 dev packages) |
| 42 | @echo "Building $(BINARY_NAME) (GUI, webkit_cgo)…" | 45 | @echo "Building $(BINARY_NAME) (GUI, webkit_cgo)…" | ||
| 43 | - | @mkdir -p $(DIST_DIR) | 46 | + | @mkdir -p $(DIST_DIR) tmp tmp/go-cache tmp/go-mod |
| 44 | - | CGO_ENABLED=1 go build $(LDFLAGS) -tags=webkit_cgo -o $(DIST_DIR)/$(BINARY_NAME) $(MAIN_PATH) | 47 | + | $(GOENV) CGO_ENABLED=1 go build $(LDFLAGS) -tags=webkit_cgo -o $(DIST_DIR)/$(BINARY_NAME) $(MAIN_PATH) |
| 45 | 48 | ||||
| 46 | - | run-gui: ## Run the GUI with native WebKit2GTK (requires dev packages) | 49 | + | run-gui: ## Run the GUI with native WebKitGTK 6.0 (requires GTK4/WebKitGTK 6 dev packages) |
| 47 | @echo "Running GUI (webkit_cgo)…" | 50 | @echo "Running GUI (webkit_cgo)…" | ||
| 48 | - | CGO_ENABLED=1 go run -tags=webkit_cgo $(MAIN_PATH) | 51 | + | @mkdir -p tmp tmp/go-cache tmp/go-mod |
| 52 | + | $(GOENV) CGO_ENABLED=1 go run -tags=webkit_cgo $(MAIN_PATH) | |||
| 53 | + | ||||
| 54 | + | .PHONY: check-webkit | |||
| 55 | + | check-webkit: ## Check system has GTK4/WebKitGTK 6.0/JavaScriptCore 6.0 | |||
| 56 | + | @echo "Checking pkg-config versions..." | |||
| 57 | + | @which pkg-config >/dev/null 2>&1 || (echo "pkg-config not found" && exit 1) | |||
| 58 | + | @echo "gtk4: $$(pkg-config --modversion gtk4 2>/dev/null || echo not found)" | |||
| 59 | + | @echo "webkitgtk-6.0: $$(pkg-config --modversion webkitgtk-6.0 2>/dev/null || echo not found)" | |||
| 60 | + | @echo "javascriptcoregtk-6.0: $$(pkg-config --modversion javascriptcoregtk-6.0 2>/dev/null || echo not found)" | |||
| 49 | 61 | ||||
| 50 | # Development targets | 62 | # Development targets | ||
| 51 | dev: ## Run in development mode | 63 | dev: ## Run in development mode | ||
| @@ -60,11 +72,13 @@ generate: ## Generate code (SQLC) | |||||
| 60 | # Testing | 72 | # Testing | ||
| 61 | test: ## Run tests | 73 | test: ## Run tests | ||
| 62 | @echo "Running tests..." | 74 | @echo "Running tests..." | ||
| 63 | - | CGO_ENABLED=0 go test -v ./... | 75 | + | @mkdir -p tmp tmp/go-cache tmp/go-mod |
| 76 | + | $(GOENV) CGO_ENABLED=0 go test -v ./... | |||
| 64 | 77 | ||||
| 65 | test-race: ## Run tests with race detection | 78 | test-race: ## Run tests with race detection | ||
| 66 | @echo "Running tests with race detection..." | 79 | @echo "Running tests with race detection..." | ||
| 67 | - | CGO_ENABLED=1 go test -race -v ./... | 80 | + | @mkdir -p tmp tmp/go-cache tmp/go-mod |
| 81 | + | $(GOENV) CGO_ENABLED=1 go test -race -v ./... | |||
| 68 | 82 | ||||
| 69 | # Linting | 83 | # Linting | ||
| 70 | lint: ## Run golangci-lint | 84 | lint: ## Run golangci-lint | ||
| @@ -100,15 +114,15 @@ init: install-tools ## Initialize project dependencies and tools | |||||
| 100 | check: ## Check that all tools and dependencies are working | 114 | check: ## Check that all tools and dependencies are working | ||
| 101 | @echo "Checking project setup..." | 115 | @echo "Checking project setup..." | ||
| 102 | @echo "Go version:" | 116 | @echo "Go version:" | ||
| 103 | - | @go version | 117 | + | @$(GOENV) go version |
| 104 | @echo "\nSQLC version:" | 118 | @echo "\nSQLC version:" | ||
| 105 | - | @sqlc version | 119 | + | @$(GOENV) sqlc version |
| 106 | @echo "\nGolangci-lint version:" | 120 | @echo "\nGolangci-lint version:" | ||
| 107 | - | @golangci-lint version | 121 | + | @$(GOENV) golangci-lint version |
| 108 | @echo "\nBuilding project..." | 122 | @echo "\nBuilding project..." | ||
| 109 | - | @make build | 123 | + | @$(MAKE) build |
| 110 | @echo "\nTesting built binary..." | 124 | @echo "\nTesting built binary..." | ||
| 111 | @$(DIST_DIR)/$(BINARY_NAME) version | 125 | @$(DIST_DIR)/$(BINARY_NAME) version | ||
| 112 | @echo "\nRunning tests..." | 126 | @echo "\nRunning tests..." | ||
| 113 | - | @make test | 127 | + | @$(MAKE) test |
| 114 | @echo "\n✅ All checks passed! Project is ready for development." | 128 | @echo "\n✅ All checks passed! Project is ready for development." | ||
M README.md +28 -11
| @@ -1,19 +1,19 @@ | |||
| 1 | - | # Dumber — a tiny, dumb browser for Wayland | |
| 1 | + | # Dumber, fully unfeatured unbloated browser for tiling WMs | |
| 2 | 2 | ||
| 3 | - | Dumber is a minimalist browser and launcher companion focused on speed and simplicity. It ships a built‑in WebKit2GTK window for navigation and a CLI with dmenu‑style launcher integration (rofi, fuzzel). | |
| 3 | + | Dumber is a minimalist browser and launcher companion focused on speed and simplicity. It ships a built‑in WebKitGTK (GTK4) window for navigation and a CLI with dmenu‑style launcher integration (rofi, fuzzel). | |
| 4 | 4 | ||
| 5 | - | - Native GUI via WebKit2GTK (CGO) with an embedded homepage UI. | |
| 5 | + | - Native GUI via WebKitGTK 6 (GTK4, CGO) with an embedded homepage UI. | |
| 6 | 6 | - CLI that parses URLs, supports search shortcuts (e.g., `g:query`), and integrates with rofi/dmenu. | |
| 7 | 7 | - Persistent history and per‑domain zoom levels (SQLite, XDG locations). | |
| 8 | 8 | - Configurable via `config.json` and environment variables, with live reload. | |
| 9 | 9 | ||
| 10 | 10 | ## Wayland‑First | |
| 11 | - | - Dumb on purpose, designed for Wayland window managers (Sway, Hyprland, River, Niri, etc..) — a tiny companion that plays nicely with your launcher. | |
| 12 | - | - Uses a native WebKit2GTK window; no external browser required and no X11/XWayland dependency. | |
| 11 | + | - Dumb on purpose, designed for Wayland window managers (Sway, Hyprland, River, Niri, etc..) it's a tiny companion that interact nicely with your launcher. | |
| 12 | + | - Uses a native WebKitGTKdo i window; no external browser required and no X11/XWayland dependency. | |
| 13 | 13 | - Optimized for dmenu‑style launchers (rofi, fuzzel, wofi) and keyboard‑driven workflows. | |
| 14 | 14 | ||
| 15 | 15 | ## Status | |
| 16 | - | - Very early stage — expect sharp edges and breaking changes, but it works well enough to browse and play with the launcher flows. | |
| 16 | + | - Very early stage, expect sharp edges and breaking changes, but it works well enough to browse and play with the launcher flows. | |
| 17 | 17 | ||
| 18 | 18 | ## Features | |
| 19 | 19 | - Built‑in browser window (no external browser needed) | |
| @@ -27,9 +27,9 @@ Dumber is a minimalist browser and launcher companion focused on speed and simpl | |||
| 27 | 27 | Prerequisites: | |
| 28 | 28 | - Go 1.25+ | |
| 29 | 29 | - Node.js 20+ (for building the embedded frontend) | |
| 30 | - | - For GUI build: WebKit2GTK and GTK dev packages (examples) | |
| 31 | - | - Debian/Ubuntu: `libwebkit2gtk-4.1-dev libgtk-3-dev build-essential` | |
| 32 | - | - Arch: `webkit2gtk gtk3 base-devel` | |
| 30 | + | - For GUI build: WebKitGTK 6 and GTK4 dev packages (examples) | |
| 31 | + | - Debian/Ubuntu: `libwebkitgtk-6.0-dev libgtk-4-dev build-essential` | |
| 32 | + | - Arch: `webkitgtk-6.0 gtk4 base-devel` | |
| 33 | 33 | ||
| 34 | 34 | Build options: | |
| 35 | 35 | - GUI (recommended): | |
| @@ -104,15 +104,30 @@ SQLite tables (created automatically): | |||
| 104 | 104 | - `shortcuts(shortcut, url_template, description, created_at)` | |
| 105 | 105 | - `zoom_levels(domain PRIMARY KEY, zoom_factor, updated_at)` | |
| 106 | 106 | ||
| 107 | - | ## Building With WebKit2GTK | |
| 107 | + | ## Building With WebKitGTK 6 (GTK4) | |
| 108 | 108 | - The GUI requires building with `-tags=webkit_cgo` and CGO enabled. | |
| 109 | - | - Ensure WebKit2GTK and GTK dev headers are installed (see prerequisites). | |
| 109 | + | - Ensure WebKitGTK 6 and GTK4 dev headers are installed (see prerequisites). | |
| 110 | 110 | - Make targets handle both frontend and Go build steps: | |
| 111 | 111 | - `make build-gui` (CGO enabled) | |
| 112 | 112 | - `make build` (CGO enabled by default in the Makefile’s main build) | |
| 113 | 113 | ||
| 114 | 114 | Without the native tag, a stub backend is used: you can still run CLI flows and see logs, but no native window is displayed. | |
| 115 | 115 | ||
| 116 | + | ## Media (GStreamer) Requirements | |
| 117 | + | ||
| 118 | + | WebKitGTK uses GStreamer for media playback. Install the following packages to ensure audio/video work correctly. | |
| 119 | + | ||
| 120 | + | - Arch Linux: | |
| 121 | + | - `sudo pacman -S gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly gst-libav gst-plugin-pipewire pipewire pipewire-pulse` | |
| 122 | + | - Optional hardware accel: `gstreamer-vaapi` | |
| 123 | + | - Debian/Ubuntu: | |
| 124 | + | - `sudo apt install gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-pipewire` | |
| 125 | + | - Optional hardware accel: `gstreamer1.0-vaapi` | |
| 126 | + | ||
| 127 | + | ## Theme and Runtime Updates | |
| 128 | + | - The homepage and pages respect your system’s light/dark preference. | |
| 129 | + | - The app updates color‑scheme live when your GTK theme changes (no relaunch needed). | |
| 130 | + | ||
| 116 | 131 | ## Project Structure | |
| 117 | 132 | - `main.go`: CLI/GUI entrypoint, `dumb://` scheme, service wiring | |
| 118 | 133 | - `internal/cli`: Cobra commands (`browse`, `dmenu`, `version`) | |
| @@ -127,6 +142,8 @@ Without the native tag, a stub backend is used: you can still run CLI flows and | |||
| 127 | 142 | - The `tmp/` directory is ignored by Git and kept out of history. | |
| 128 | 143 | ||
| 129 | 144 | ## Roadmap | |
| 145 | + | - WebKitGTK 6 (GTK4) migration — almost complete (GPU/Vulkan path landed). | |
| 146 | + | - GPU rendering (Vulkan via GTK4 renderer) with graceful CPU fallback. | |
| 130 | 147 | - Full Vim-style motion and keyboard navigation across pages and UI. | |
| 131 | 148 | - Performance work: faster startup, lower memory, snappier UI. | |
| 132 | 149 | - Basic ad-block extension — because fuck ads. | |
| @@ -1,19 +1,19 @@ | |||||
| 1 | - | # Dumber — a tiny, dumb browser for Wayland | 1 | + | # Dumber, fully unfeatured unbloated browser for tiling WMs |
| 2 | 2 | ||||
| 3 | - | Dumber is a minimalist browser and launcher companion focused on speed and simplicity. It ships a built‑in WebKit2GTK window for navigation and a CLI with dmenu‑style launcher integration (rofi, fuzzel). | 3 | + | Dumber is a minimalist browser and launcher companion focused on speed and simplicity. It ships a built‑in WebKitGTK (GTK4) window for navigation and a CLI with dmenu‑style launcher integration (rofi, fuzzel). |
| 4 | 4 | ||||
| 5 | - | - Native GUI via WebKit2GTK (CGO) with an embedded homepage UI. | 5 | + | - Native GUI via WebKitGTK 6 (GTK4, CGO) with an embedded homepage UI. |
| 6 | - CLI that parses URLs, supports search shortcuts (e.g., `g:query`), and integrates with rofi/dmenu. | 6 | - CLI that parses URLs, supports search shortcuts (e.g., `g:query`), and integrates with rofi/dmenu. | ||
| 7 | - Persistent history and per‑domain zoom levels (SQLite, XDG locations). | 7 | - Persistent history and per‑domain zoom levels (SQLite, XDG locations). | ||
| 8 | - Configurable via `config.json` and environment variables, with live reload. | 8 | - Configurable via `config.json` and environment variables, with live reload. | ||
| 9 | 9 | ||||
| 10 | ## Wayland‑First | 10 | ## Wayland‑First | ||
| 11 | - | - Dumb on purpose, designed for Wayland window managers (Sway, Hyprland, River, Niri, etc..) — a tiny companion that plays nicely with your launcher. | 11 | + | - Dumb on purpose, designed for Wayland window managers (Sway, Hyprland, River, Niri, etc..) it's a tiny companion that interact nicely with your launcher. |
| 12 | - | - Uses a native WebKit2GTK window; no external browser required and no X11/XWayland dependency. | 12 | + | - Uses a native WebKitGTKdo i window; no external browser required and no X11/XWayland dependency. |
| 13 | - Optimized for dmenu‑style launchers (rofi, fuzzel, wofi) and keyboard‑driven workflows. | 13 | - Optimized for dmenu‑style launchers (rofi, fuzzel, wofi) and keyboard‑driven workflows. | ||
| 14 | 14 | ||||
| 15 | ## Status | 15 | ## Status | ||
| 16 | - | - Very early stage — expect sharp edges and breaking changes, but it works well enough to browse and play with the launcher flows. | 16 | + | - Very early stage, expect sharp edges and breaking changes, but it works well enough to browse and play with the launcher flows. |
| 17 | 17 | ||||
| 18 | ## Features | 18 | ## Features | ||
| 19 | - Built‑in browser window (no external browser needed) | 19 | - Built‑in browser window (no external browser needed) | ||
| @@ -27,9 +27,9 @@ Dumber is a minimalist browser and launcher companion focused on speed and simpl | |||||
| 27 | Prerequisites: | 27 | Prerequisites: | ||
| 28 | - Go 1.25+ | 28 | - Go 1.25+ | ||
| 29 | - Node.js 20+ (for building the embedded frontend) | 29 | - Node.js 20+ (for building the embedded frontend) | ||
| 30 | - | - For GUI build: WebKit2GTK and GTK dev packages (examples) | 30 | + | - For GUI build: WebKitGTK 6 and GTK4 dev packages (examples) |
| 31 | - | - Debian/Ubuntu: `libwebkit2gtk-4.1-dev libgtk-3-dev build-essential` | 31 | + | - Debian/Ubuntu: `libwebkitgtk-6.0-dev libgtk-4-dev build-essential` |
| 32 | - | - Arch: `webkit2gtk gtk3 base-devel` | 32 | + | - Arch: `webkitgtk-6.0 gtk4 base-devel` |
| 33 | 33 | ||||
| 34 | Build options: | 34 | Build options: | ||
| 35 | - GUI (recommended): | 35 | - GUI (recommended): | ||
| @@ -104,15 +104,30 @@ SQLite tables (created automatically): | |||||
| 104 | - `shortcuts(shortcut, url_template, description, created_at)` | 104 | - `shortcuts(shortcut, url_template, description, created_at)` | ||
| 105 | - `zoom_levels(domain PRIMARY KEY, zoom_factor, updated_at)` | 105 | - `zoom_levels(domain PRIMARY KEY, zoom_factor, updated_at)` | ||
| 106 | 106 | ||||
| 107 | - | ## Building With WebKit2GTK | 107 | + | ## Building With WebKitGTK 6 (GTK4) |
| 108 | - The GUI requires building with `-tags=webkit_cgo` and CGO enabled. | 108 | - The GUI requires building with `-tags=webkit_cgo` and CGO enabled. | ||
| 109 | - | - Ensure WebKit2GTK and GTK dev headers are installed (see prerequisites). | 109 | + | - Ensure WebKitGTK 6 and GTK4 dev headers are installed (see prerequisites). |
| 110 | - Make targets handle both frontend and Go build steps: | 110 | - Make targets handle both frontend and Go build steps: | ||
| 111 | - `make build-gui` (CGO enabled) | 111 | - `make build-gui` (CGO enabled) | ||
| 112 | - `make build` (CGO enabled by default in the Makefile’s main build) | 112 | - `make build` (CGO enabled by default in the Makefile’s main build) | ||
| 113 | 113 | ||||
| 114 | Without the native tag, a stub backend is used: you can still run CLI flows and see logs, but no native window is displayed. | 114 | Without the native tag, a stub backend is used: you can still run CLI flows and see logs, but no native window is displayed. | ||
| 115 | 115 | ||||
| 116 | + | ## Media (GStreamer) Requirements | |||
| 117 | + | ||||
| 118 | + | WebKitGTK uses GStreamer for media playback. Install the following packages to ensure audio/video work correctly. | |||
| 119 | + | ||||
| 120 | + | - Arch Linux: | |||
| 121 | + | - `sudo pacman -S gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly gst-libav gst-plugin-pipewire pipewire pipewire-pulse` | |||
| 122 | + | - Optional hardware accel: `gstreamer-vaapi` | |||
| 123 | + | - Debian/Ubuntu: | |||
| 124 | + | - `sudo apt install gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-pipewire` | |||
| 125 | + | - Optional hardware accel: `gstreamer1.0-vaapi` | |||
| 126 | + | ||||
| 127 | + | ## Theme and Runtime Updates | |||
| 128 | + | - The homepage and pages respect your system’s light/dark preference. | |||
| 129 | + | - The app updates color‑scheme live when your GTK theme changes (no relaunch needed). | |||
| 130 | + | ||||
| 116 | ## Project Structure | 131 | ## Project Structure | ||
| 117 | - `main.go`: CLI/GUI entrypoint, `dumb://` scheme, service wiring | 132 | - `main.go`: CLI/GUI entrypoint, `dumb://` scheme, service wiring | ||
| 118 | - `internal/cli`: Cobra commands (`browse`, `dmenu`, `version`) | 133 | - `internal/cli`: Cobra commands (`browse`, `dmenu`, `version`) | ||
| @@ -127,6 +142,8 @@ Without the native tag, a stub backend is used: you can still run CLI flows and | |||||
| 127 | - The `tmp/` directory is ignored by Git and kept out of history. | 142 | - The `tmp/` directory is ignored by Git and kept out of history. | ||
| 128 | 143 | ||||
| 129 | ## Roadmap | 144 | ## Roadmap | ||
| 145 | + | - WebKitGTK 6 (GTK4) migration — almost complete (GPU/Vulkan path landed). | |||
| 146 | + | - GPU rendering (Vulkan via GTK4 renderer) with graceful CPU fallback. | |||
| 130 | - Full Vim-style motion and keyboard navigation across pages and UI. | 147 | - Full Vim-style motion and keyboard navigation across pages and UI. | ||
| 131 | - Performance work: faster startup, lower memory, snappier UI. | 148 | - Performance work: faster startup, lower memory, snappier UI. | ||
| 132 | - Basic ad-block extension — because fuck ads. | 149 | - Basic ad-block extension — because fuck ads. | ||
M internal/cli/browse.go +24 -9
| @@ -17,9 +17,11 @@ import ( | |||
| 17 | 17 | ||
| 18 | 18 | // NewBrowseCmd creates the browse command | |
| 19 | 19 | func NewBrowseCmd() *cobra.Command { | |
| 20 | - | cmd := &cobra.Command{ | |
| 21 | - | Use: "browse <url|query>", | |
| 22 | - | Short: "Browse a URL or search query", | |
| 20 | + | var renderingMode string | |
| 21 | + | ||
| 22 | + | cmd := &cobra.Command{ | |
| 23 | + | Use: "browse <url|query>", | |
| 24 | + | Short: "Browse a URL or search query", | |
| 23 | 25 | Long: `Browse a URL directly or use search shortcuts like: | |
| 24 | 26 | g:golang -> Google search for "golang" | |
| 25 | 27 | gh:cobra -> GitHub search for "cobra" | |
| @@ -30,11 +32,21 @@ Direct URLs are also supported: | |||
| 30 | 32 | https://example.com | |
| 31 | 33 | example.com (automatically adds https://)`, | |
| 32 | 34 | Args: cobra.ExactArgs(1), | |
| 33 | - | RunE: func(_ *cobra.Command, args []string) error { | |
| 34 | - | cli, err := NewCLI() | |
| 35 | - | if err != nil { | |
| 36 | - | return fmt.Errorf("failed to initialize CLI: %w", err) | |
| 37 | - | } | |
| 35 | + | RunE: func(_ *cobra.Command, args []string) error { | |
| 36 | + | // Propagate rendering mode to GUI process via env var | |
| 37 | + | if renderingMode != "" { | |
| 38 | + | rm := strings.ToLower(renderingMode) | |
| 39 | + | switch rm { | |
| 40 | + | case "auto", "gpu", "cpu": | |
| 41 | + | _ = os.Setenv("DUMBER_RENDERING_MODE", rm) | |
| 42 | + | default: | |
| 43 | + | return fmt.Errorf("invalid --rendering-mode: %s (expected auto|gpu|cpu)", renderingMode) | |
| 44 | + | } | |
| 45 | + | } | |
| 46 | + | cli, err := NewCLI() | |
| 47 | + | if err != nil { | |
| 48 | + | return fmt.Errorf("failed to initialize CLI: %w", err) | |
| 49 | + | } | |
| 38 | 50 | defer func() { | |
| 39 | 51 | if closeErr := cli.Close(); closeErr != nil { | |
| 40 | 52 | fmt.Fprintf(os.Stderr, "Warning: failed to close database: %v\n", closeErr) | |
| @@ -45,7 +57,10 @@ Direct URLs are also supported: | |||
| 45 | 57 | }, | |
| 46 | 58 | } | |
| 47 | 59 | ||
| 48 | - | return cmd | |
| 60 | + | // Flags | |
| 61 | + | cmd.Flags().StringVar(&renderingMode, "rendering-mode", "", "Rendering mode: auto|gpu|cpu") | |
| 62 | + | ||
| 63 | + | return cmd | |
| 49 | 64 | } | |
| 50 | 65 | ||
| 51 | 66 | // browse handles the core browsing logic | |
| @@ -17,9 +17,11 @@ import ( | |||||
| 17 | 17 | ||||
| 18 | // NewBrowseCmd creates the browse command | 18 | // NewBrowseCmd creates the browse command | ||
| 19 | func NewBrowseCmd() *cobra.Command { | 19 | func NewBrowseCmd() *cobra.Command { | ||
| 20 | - | cmd := &cobra.Command{ | 20 | + | var renderingMode string |
| 21 | - | Use: "browse <url|query>", | 21 | + | |
| 22 | - | Short: "Browse a URL or search query", | 22 | + | cmd := &cobra.Command{ |
| 23 | + | Use: "browse <url|query>", | |||
| 24 | + | Short: "Browse a URL or search query", | |||
| 23 | Long: `Browse a URL directly or use search shortcuts like: | 25 | Long: `Browse a URL directly or use search shortcuts like: | ||
| 24 | g:golang -> Google search for "golang" | 26 | g:golang -> Google search for "golang" | ||
| 25 | gh:cobra -> GitHub search for "cobra" | 27 | gh:cobra -> GitHub search for "cobra" | ||
| @@ -30,11 +32,21 @@ Direct URLs are also supported: | |||||
| 30 | https://example.com | 32 | https://example.com | ||
| 31 | example.com (automatically adds https://)`, | 33 | example.com (automatically adds https://)`, | ||
| 32 | Args: cobra.ExactArgs(1), | 34 | Args: cobra.ExactArgs(1), | ||
| 33 | - | RunE: func(_ *cobra.Command, args []string) error { | 35 | + | RunE: func(_ *cobra.Command, args []string) error { |
| 34 | - | cli, err := NewCLI() | 36 | + | // Propagate rendering mode to GUI process via env var |
| 35 | - | if err != nil { | 37 | + | if renderingMode != "" { |
| 36 | - | return fmt.Errorf("failed to initialize CLI: %w", err) | 38 | + | rm := strings.ToLower(renderingMode) |
| 37 | - | } | 39 | + | switch rm { |
| 40 | + | case "auto", "gpu", "cpu": | |||
| 41 | + | _ = os.Setenv("DUMBER_RENDERING_MODE", rm) | |||
| 42 | + | default: | |||
| 43 | + | return fmt.Errorf("invalid --rendering-mode: %s (expected auto|gpu|cpu)", renderingMode) | |||
| 44 | + | } | |||
| 45 | + | } | |||
| 46 | + | cli, err := NewCLI() | |||
| 47 | + | if err != nil { | |||
| 48 | + | return fmt.Errorf("failed to initialize CLI: %w", err) | |||
| 49 | + | } | |||
| 38 | defer func() { | 50 | defer func() { | ||
| 39 | if closeErr := cli.Close(); closeErr != nil { | 51 | if closeErr := cli.Close(); closeErr != nil { | ||
| 40 | fmt.Fprintf(os.Stderr, "Warning: failed to close database: %v\n", closeErr) | 52 | fmt.Fprintf(os.Stderr, "Warning: failed to close database: %v\n", closeErr) | ||
| @@ -45,7 +57,10 @@ Direct URLs are also supported: | |||||
| 45 | }, | 57 | }, | ||
| 46 | } | 58 | } | ||
| 47 | 59 | ||||
| 48 | - | return cmd | 60 | + | // Flags |
| 61 | + | cmd.Flags().StringVar(&renderingMode, "rendering-mode", "", "Rendering mode: auto|gpu|cpu") | |||
| 62 | + | ||||
| 63 | + | return cmd | |||
| 49 | } | 64 | } | ||
| 50 | 65 | ||||
| 51 | // browse handles the core browsing logic | 66 | // browse handles the core browsing logic | ||
M internal/config/config.go +62 -19
| @@ -22,8 +22,19 @@ type Config struct { | |||
| 22 | 22 | Dmenu DmenuConfig `mapstructure:"dmenu" yaml:"dmenu"` | |
| 23 | 23 | Logging LoggingConfig `mapstructure:"logging" yaml:"logging"` | |
| 24 | 24 | Appearance AppearanceConfig `mapstructure:"appearance" yaml:"appearance"` | |
| 25 | + | // RenderingMode controls GPU/CPU rendering selection for WebKit | |
| 26 | + | RenderingMode RenderingMode `mapstructure:"rendering_mode" yaml:"rendering_mode"` | |
| 25 | 27 | } | |
| 26 | 28 | ||
| 29 | + | // RenderingMode selects GPU vs CPU rendering. | |
| 30 | + | type RenderingMode string | |
| 31 | + | ||
| 32 | + | const ( | |
| 33 | + | RenderingModeAuto RenderingMode = "auto" | |
| 34 | + | RenderingModeGPU RenderingMode = "gpu" | |
| 35 | + | RenderingModeCPU RenderingMode = "cpu" | |
| 36 | + | ) | |
| 37 | + | ||
| 27 | 38 | // DatabaseConfig holds database-related configuration. | |
| 28 | 39 | type DatabaseConfig struct { | |
| 29 | 40 | Path string `mapstructure:"path" yaml:"path"` | |
| @@ -108,7 +119,7 @@ func NewManager() (*Manager, error) { | |||
| 108 | 119 | v.AutomaticEnv() | |
| 109 | 120 | ||
| 110 | 121 | // Bind specific environment variables | |
| 111 | - | bindings := map[string]string{ | |
| 122 | + | bindings := map[string]string{ | |
| 112 | 123 | "database.path": "DATABASE_PATH", | |
| 113 | 124 | "database.max_connections": "DATABASE_MAX_CONNECTIONS", | |
| 114 | 125 | "database.max_idle_time": "DATABASE_MAX_IDLE_TIME", | |
| @@ -136,11 +147,16 @@ func NewManager() (*Manager, error) { | |||
| 136 | 147 | "logging.compress": "LOGGING_COMPRESS", | |
| 137 | 148 | } | |
| 138 | 149 | ||
| 139 | - | for key, env := range bindings { | |
| 140 | - | if err := v.BindEnv(key, "DUMB_BROWSER_"+env); err != nil { | |
| 141 | - | return nil, fmt.Errorf("failed to bind environment variable %s: %w", env, err) | |
| 142 | - | } | |
| 143 | - | } | |
| 150 | + | for key, env := range bindings { | |
| 151 | + | if err := v.BindEnv(key, "DUMB_BROWSER_"+env); err != nil { | |
| 152 | + | return nil, fmt.Errorf("failed to bind environment variable %s: %w", env, err) | |
| 153 | + | } | |
| 154 | + | } | |
| 155 | + | ||
| 156 | + | // Explicit binding for rendering mode via dedicated env var | |
| 157 | + | if err := v.BindEnv("rendering_mode", "DUMBER_RENDERING_MODE"); err != nil { | |
| 158 | + | return nil, fmt.Errorf("failed to bind DUMBER_RENDERING_MODE: %w", err) | |
| 159 | + | } | |
| 144 | 160 | ||
| 145 | 161 | return &Manager{ | |
| 146 | 162 | viper: v, | |
| @@ -173,11 +189,11 @@ func (m *Manager) Load() error { | |||
| 173 | 189 | } | |
| 174 | 190 | } | |
| 175 | 191 | ||
| 176 | - | // Unmarshal into config struct | |
| 177 | - | config := &Config{} | |
| 178 | - | if err := m.viper.Unmarshal(config); err != nil { | |
| 179 | - | return fmt.Errorf("failed to unmarshal config: %w", err) | |
| 180 | - | } | |
| 192 | + | // Unmarshal into config struct | |
| 193 | + | config := &Config{} | |
| 194 | + | if err := m.viper.Unmarshal(config); err != nil { | |
| 195 | + | return fmt.Errorf("failed to unmarshal config: %w", err) | |
| 196 | + | } | |
| 181 | 197 | ||
| 182 | 198 | // Set database path if not specified | |
| 183 | 199 | if config.Database.Path == "" { | |
| @@ -188,8 +204,20 @@ func (m *Manager) Load() error { | |||
| 188 | 204 | config.Database.Path = dbPath | |
| 189 | 205 | } | |
| 190 | 206 | ||
| 191 | - | m.config = config | |
| 192 | - | return nil | |
| 207 | + | // Normalize/validate rendering mode | |
| 208 | + | switch strings.ToLower(string(config.RenderingMode)) { | |
| 209 | + | case "", string(RenderingModeAuto): | |
| 210 | + | config.RenderingMode = RenderingModeAuto | |
| 211 | + | case string(RenderingModeGPU): | |
| 212 | + | config.RenderingMode = RenderingModeGPU | |
| 213 | + | case string(RenderingModeCPU): | |
| 214 | + | config.RenderingMode = RenderingModeCPU | |
| 215 | + | default: | |
| 216 | + | config.RenderingMode = RenderingModeAuto | |
| 217 | + | } | |
| 218 | + | ||
| 219 | + | m.config = config | |
| 220 | + | return nil | |
| 193 | 221 | } | |
| 194 | 222 | ||
| 195 | 223 | // Get returns the current configuration (thread-safe). | |
| @@ -249,10 +277,10 @@ func (m *Manager) reload() error { | |||
| 249 | 277 | return err | |
| 250 | 278 | } | |
| 251 | 279 | ||
| 252 | - | config := &Config{} | |
| 253 | - | if err := m.viper.Unmarshal(config); err != nil { | |
| 254 | - | return err | |
| 255 | - | } | |
| 280 | + | config := &Config{} | |
| 281 | + | if err := m.viper.Unmarshal(config); err != nil { | |
| 282 | + | return err | |
| 283 | + | } | |
| 256 | 284 | ||
| 257 | 285 | // Set database path if not specified | |
| 258 | 286 | if config.Database.Path == "" { | |
| @@ -263,8 +291,20 @@ func (m *Manager) reload() error { | |||
| 263 | 291 | config.Database.Path = dbPath | |
| 264 | 292 | } | |
| 265 | 293 | ||
| 266 | - | m.config = config | |
| 267 | - | return nil | |
| 294 | + | // Normalize/validate rendering mode | |
| 295 | + | switch strings.ToLower(string(config.RenderingMode)) { | |
| 296 | + | case "", string(RenderingModeAuto): | |
| 297 | + | config.RenderingMode = RenderingModeAuto | |
| 298 | + | case string(RenderingModeGPU): | |
| 299 | + | config.RenderingMode = RenderingModeGPU | |
| 300 | + | case string(RenderingModeCPU): | |
| 301 | + | config.RenderingMode = RenderingModeCPU | |
| 302 | + | default: | |
| 303 | + | config.RenderingMode = RenderingModeAuto | |
| 304 | + | } | |
| 305 | + | ||
| 306 | + | m.config = config | |
| 307 | + | return nil | |
| 268 | 308 | } | |
| 269 | 309 | ||
| 270 | 310 | // setDefaults sets default configuration values in Viper. | |
| @@ -310,6 +350,9 @@ func (m *Manager) setDefaults() { | |||
| 310 | 350 | m.viper.SetDefault("appearance.serif_font", defaults.Appearance.SerifFont) | |
| 311 | 351 | m.viper.SetDefault("appearance.monospace_font", defaults.Appearance.MonospaceFont) | |
| 312 | 352 | m.viper.SetDefault("appearance.default_font_size", defaults.Appearance.DefaultFontSize) | |
| 353 | + | ||
| 354 | + | // Rendering defaults | |
| 355 | + | m.viper.SetDefault("rendering_mode", string(RenderingModeAuto)) | |
| 313 | 356 | } | |
| 314 | 357 | ||
| 315 | 358 | // createDefaultConfig creates a default configuration file. | |
| @@ -22,8 +22,19 @@ type Config struct { | |||||
| 22 | Dmenu DmenuConfig `mapstructure:"dmenu" yaml:"dmenu"` | 22 | Dmenu DmenuConfig `mapstructure:"dmenu" yaml:"dmenu"` | ||
| 23 | Logging LoggingConfig `mapstructure:"logging" yaml:"logging"` | 23 | Logging LoggingConfig `mapstructure:"logging" yaml:"logging"` | ||
| 24 | Appearance AppearanceConfig `mapstructure:"appearance" yaml:"appearance"` | 24 | Appearance AppearanceConfig `mapstructure:"appearance" yaml:"appearance"` | ||
| 25 | + | // RenderingMode controls GPU/CPU rendering selection for WebKit | |||
| 26 | + | RenderingMode RenderingMode `mapstructure:"rendering_mode" yaml:"rendering_mode"` | |||
| 25 | } | 27 | } | ||
| 26 | 28 | ||||
| 29 | + | // RenderingMode selects GPU vs CPU rendering. | |||
| 30 | + | type RenderingMode string | |||
| 31 | + | ||||
| 32 | + | const ( | |||
| 33 | + | RenderingModeAuto RenderingMode = "auto" | |||
| 34 | + | RenderingModeGPU RenderingMode = "gpu" | |||
| 35 | + | RenderingModeCPU RenderingMode = "cpu" | |||
| 36 | + | ) | |||
| 37 | + | ||||
| 27 | // DatabaseConfig holds database-related configuration. | 38 | // DatabaseConfig holds database-related configuration. | ||
| 28 | type DatabaseConfig struct { | 39 | type DatabaseConfig struct { | ||
| 29 | Path string `mapstructure:"path" yaml:"path"` | 40 | Path string `mapstructure:"path" yaml:"path"` | ||
| @@ -108,7 +119,7 @@ func NewManager() (*Manager, error) { | |||||
| 108 | v.AutomaticEnv() | 119 | v.AutomaticEnv() | ||
| 109 | 120 | ||||
| 110 | // Bind specific environment variables | 121 | // Bind specific environment variables | ||
| 111 | - | bindings := map[string]string{ | 122 | + | bindings := map[string]string{ |
| 112 | "database.path": "DATABASE_PATH", | 123 | "database.path": "DATABASE_PATH", | ||
| 113 | "database.max_connections": "DATABASE_MAX_CONNECTIONS", | 124 | "database.max_connections": "DATABASE_MAX_CONNECTIONS", | ||
| 114 | "database.max_idle_time": "DATABASE_MAX_IDLE_TIME", | 125 | "database.max_idle_time": "DATABASE_MAX_IDLE_TIME", | ||
| @@ -136,11 +147,16 @@ func NewManager() (*Manager, error) { | |||||
| 136 | "logging.compress": "LOGGING_COMPRESS", | 147 | "logging.compress": "LOGGING_COMPRESS", | ||
| 137 | } | 148 | } | ||
| 138 | 149 | ||||
| 139 | - | for key, env := range bindings { | 150 | + | for key, env := range bindings { |
| 140 | - | if err := v.BindEnv(key, "DUMB_BROWSER_"+env); err != nil { | 151 | + | if err := v.BindEnv(key, "DUMB_BROWSER_"+env); err != nil { |
| 141 | - | return nil, fmt.Errorf("failed to bind environment variable %s: %w", env, err) | 152 | + | return nil, fmt.Errorf("failed to bind environment variable %s: %w", env, err) |
| 142 | - | } | 153 | + | } |
| 143 | - | } | 154 | + | } |
| 155 | + | ||||
| 156 | + | // Explicit binding for rendering mode via dedicated env var | |||
| 157 | + | if err := v.BindEnv("rendering_mode", "DUMBER_RENDERING_MODE"); err != nil { | |||
| 158 | + | return nil, fmt.Errorf("failed to bind DUMBER_RENDERING_MODE: %w", err) | |||
| 159 | + | } | |||
| 144 | 160 | ||||
| 145 | return &Manager{ | 161 | return &Manager{ | ||
| 146 | viper: v, | 162 | viper: v, | ||
| @@ -173,11 +189,11 @@ func (m *Manager) Load() error { | |||||
| 173 | } | 189 | } | ||
| 174 | } | 190 | } | ||
| 175 | 191 | ||||
| 176 | - | // Unmarshal into config struct | 192 | + | // Unmarshal into config struct |
| 177 | - | config := &Config{} | 193 | + | config := &Config{} |
| 178 | - | if err := m.viper.Unmarshal(config); err != nil { | 194 | + | if err := m.viper.Unmarshal(config); err != nil { |
| 179 | - | return fmt.Errorf("failed to unmarshal config: %w", err) | 195 | + | return fmt.Errorf("failed to unmarshal config: %w", err) |
| 180 | - | } | 196 | + | } |
| 181 | 197 | ||||
| 182 | // Set database path if not specified | 198 | // Set database path if not specified | ||
| 183 | if config.Database.Path == "" { | 199 | if config.Database.Path == "" { | ||
| @@ -188,8 +204,20 @@ func (m *Manager) Load() error { | |||||
| 188 | config.Database.Path = dbPath | 204 | config.Database.Path = dbPath | ||
| 189 | } | 205 | } | ||
| 190 | 206 | ||||
| 191 | - | m.config = config | 207 | + | // Normalize/validate rendering mode |
| 192 | - | return nil | 208 | + | switch strings.ToLower(string(config.RenderingMode)) { |
| 209 | + | case "", string(RenderingModeAuto): | |||
| 210 | + | config.RenderingMode = RenderingModeAuto | |||
| 211 | + | case string(RenderingModeGPU): | |||
| 212 | + | config.RenderingMode = RenderingModeGPU | |||
| 213 | + | case string(RenderingModeCPU): | |||
| 214 | + | config.RenderingMode = RenderingModeCPU | |||
| 215 | + | default: | |||
| 216 | + | config.RenderingMode = RenderingModeAuto | |||
| 217 | + | } | |||
| 218 | + | ||||
| 219 | + | m.config = config | |||
| 220 | + | return nil | |||
| 193 | } | 221 | } | ||
| 194 | 222 | ||||
| 195 | // Get returns the current configuration (thread-safe). | 223 | // Get returns the current configuration (thread-safe). | ||
| @@ -249,10 +277,10 @@ func (m *Manager) reload() error { | |||||
| 249 | return err | 277 | return err | ||
| 250 | } | 278 | } | ||
| 251 | 279 | ||||
| 252 | - | config := &Config{} | 280 | + | config := &Config{} |
| 253 | - | if err := m.viper.Unmarshal(config); err != nil { | 281 | + | if err := m.viper.Unmarshal(config); err != nil { |
| 254 | - | return err | 282 | + | return err |
| 255 | - | } | 283 | + | } |
| 256 | 284 | ||||
| 257 | // Set database path if not specified | 285 | // Set database path if not specified | ||
| 258 | if config.Database.Path == "" { | 286 | if config.Database.Path == "" { | ||
| @@ -263,8 +291,20 @@ func (m *Manager) reload() error { | |||||
| 263 | config.Database.Path = dbPath | 291 | config.Database.Path = dbPath | ||
| 264 | } | 292 | } | ||
| 265 | 293 | ||||
| 266 | - | m.config = config | 294 | + | // Normalize/validate rendering mode |
| 267 | - | return nil | 295 | + | switch strings.ToLower(string(config.RenderingMode)) { |
| 296 | + | case "", string(RenderingModeAuto): | |||
| 297 | + | config.RenderingMode = RenderingModeAuto | |||
| 298 | + | case string(RenderingModeGPU): | |||
| 299 | + | config.RenderingMode = RenderingModeGPU | |||
| 300 | + | case string(RenderingModeCPU): | |||
| 301 | + | config.RenderingMode = RenderingModeCPU | |||
| 302 | + | default: | |||
| 303 | + | config.RenderingMode = RenderingModeAuto | |||
| 304 | + | } | |||
| 305 | + | ||||
| 306 | + | m.config = config | |||
| 307 | + | return nil | |||
| 268 | } | 308 | } | ||
| 269 | 309 | ||||
| 270 | // setDefaults sets default configuration values in Viper. | 310 | // setDefaults sets default configuration values in Viper. | ||
| @@ -310,6 +350,9 @@ func (m *Manager) setDefaults() { | |||||
| 310 | m.viper.SetDefault("appearance.serif_font", defaults.Appearance.SerifFont) | 350 | m.viper.SetDefault("appearance.serif_font", defaults.Appearance.SerifFont) | ||
| 311 | m.viper.SetDefault("appearance.monospace_font", defaults.Appearance.MonospaceFont) | 351 | m.viper.SetDefault("appearance.monospace_font", defaults.Appearance.MonospaceFont) | ||
| 312 | m.viper.SetDefault("appearance.default_font_size", defaults.Appearance.DefaultFontSize) | 352 | m.viper.SetDefault("appearance.default_font_size", defaults.Appearance.DefaultFontSize) | ||
| 353 | + | ||||
| 354 | + | // Rendering defaults | |||
| 355 | + | m.viper.SetDefault("rendering_mode", string(RenderingModeAuto)) | |||
| 313 | } | 356 | } | ||
| 314 | 357 | ||||
| 315 | // createDefaultConfig creates a default configuration file. | 358 | // createDefaultConfig creates a default configuration file. | ||
M internal/config/defaults.go +1 -0
| @@ -61,6 +61,7 @@ func DefaultConfig() *Config { | |||
| 61 | 61 | MonospaceFont: "Fira Code", | |
| 62 | 62 | DefaultFontSize: 16, | |
| 63 | 63 | }, | |
| 64 | + | RenderingMode: RenderingModeAuto, | |
| 64 | 65 | } | |
| 65 | 66 | } | |
| 66 | 67 | ||
| @@ -61,6 +61,7 @@ func DefaultConfig() *Config { | |||||
| 61 | MonospaceFont: "Fira Code", | 61 | MonospaceFont: "Fira Code", | ||
| 62 | DefaultFontSize: 16, | 62 | DefaultFontSize: 16, | ||
| 63 | }, | 63 | }, | ||
| 64 | + | RenderingMode: RenderingModeAuto, | |||
| 64 | } | 65 | } | ||
| 65 | } | 66 | } | ||
| 66 | 67 | ||||
M main.go +32 -26
| @@ -121,6 +121,24 @@ func runBrowser() { | |||
| 121 | 121 | defer runtime.UnlockOSThread() | |
| 122 | 122 | } | |
| 123 | 123 | ||
| 124 | + | // Proactively harden media pipeline to avoid WebProcess crashes from buggy VAAPI/plugins. | |
| 125 | + | // Can be disabled with DUMBER_MEDIA_SAFE=0 | |
| 126 | + | if os.Getenv("DUMBER_MEDIA_SAFE") != "0" { | |
| 127 | + | // Demote VAAPI-related elements so they won't be auto-picked | |
| 128 | + | if os.Getenv("GST_PLUGIN_FEATURE_RANK") == "" { | |
| 129 | + | os.Setenv("GST_PLUGIN_FEATURE_RANK", "vaapisink:0,vaapidecodebin:0,vaapih264dec:0,vaapivideoconvert:0") | |
| 130 | + | } | |
| 131 | + | // Provide a safe audio sink to avoid autoaudiosink failures | |
| 132 | + | if os.Getenv("GST_AUDIO_SINK") == "" { | |
| 133 | + | os.Setenv("GST_AUDIO_SINK", "fakesink") | |
| 134 | + | } | |
| 135 | + | // Optionally disable media stream features that can engage complex pipelines | |
| 136 | + | if os.Getenv("WEBKIT_DISABLE_MEDIA_STREAM") == "" { | |
| 137 | + | os.Setenv("WEBKIT_DISABLE_MEDIA_STREAM", "1") | |
| 138 | + | } | |
| 139 | + | log.Printf("[media] Safe mode enabled (override with DUMBER_MEDIA_SAFE=0)") | |
| 140 | + | } | |
| 141 | + | ||
| 124 | 142 | if err := config.Init(); err != nil { | |
| 125 | 143 | log.Fatalf("Failed to initialize config: %v", err) | |
| 126 | 144 | } | |
| @@ -142,7 +160,8 @@ func runBrowser() { | |||
| 142 | 160 | // JS controls injection is disabled; using native shortcuts via GTK. | |
| 143 | 161 | ||
| 144 | 162 | // Track current URL for zoom persistence | |
| 145 | - | var currentURL string | |
| 163 | + | // Initialize to the initial homepage so shortcuts work immediately | |
| 164 | + | var currentURL string = "dumb://homepage" | |
| 146 | 165 | ||
| 147 | 166 | // Register custom scheme resolver for dumb:// | |
| 148 | 167 | webkit.SetURISchemeResolver(func(uri string) (string, []byte, bool) { | |
| @@ -169,6 +188,14 @@ func runBrowser() { | |||
| 169 | 188 | if strings.HasPrefix(path, "/api/") { path = strings.TrimPrefix(path, "/api") } | |
| 170 | 189 | if path == "/api" { path = "/" } | |
| 171 | 190 | switch { | |
| 191 | + | case strings.HasPrefix(path, "/rendering/status"): | |
| 192 | + | log.Printf("[api] GET /rendering/status") | |
| 193 | + | // Report configured rendering mode; runtime GPU state depends on WebKit internals | |
| 194 | + | resp := struct{ | |
| 195 | + | Mode string `json:"mode"` | |
| 196 | + | }{Mode: string(cfg.RenderingMode)} | |
| 197 | + | b, _ := json.Marshal(resp) | |
| 198 | + | return "application/json; charset=utf-8", b, true | |
| 172 | 199 | case strings.HasPrefix(path, "/config"): | |
| 173 | 200 | log.Printf("[api] GET /config") | |
| 174 | 201 | // Build config info | |
| @@ -290,6 +317,7 @@ func runBrowser() { | |||
| 290 | 317 | DefaultSerifFont: cfg.Appearance.SerifFont, | |
| 291 | 318 | DefaultMonospaceFont: cfg.Appearance.MonospaceFont, | |
| 292 | 319 | DefaultFontSize: cfg.Appearance.DefaultFontSize, | |
| 320 | + | Rendering: webkit.RenderingConfig{Mode: string(cfg.RenderingMode)}, | |
| 293 | 321 | }) | |
| 294 | 322 | if err != nil { | |
| 295 | 323 | log.Printf("Warning: failed to create WebView: %v", err) | |
| @@ -325,6 +353,7 @@ func runBrowser() { | |||
| 325 | 353 | URL string `json:"url"` | |
| 326 | 354 | Q string `json:"q"` | |
| 327 | 355 | Limit int `json:"limit"` | |
| 356 | + | Value string `json:"value"` | |
| 328 | 357 | // Wails fetch bridge | |
| 329 | 358 | ID string `json:"id"` | |
| 330 | 359 | Payload json.RawMessage `json:"payload"` | |
| @@ -397,7 +426,7 @@ func runBrowser() { | |||
| 397 | 426 | _ = view.InjectScript("window.__dumber_wails_resolve('" + m.ID + "', '{}')") | |
| 398 | 427 | } | |
| 399 | 428 | case "theme": | |
| 400 | - | if m.URL != "" { log.Printf("[theme] page reported color-scheme: %s", m.URL) } | |
| 429 | + | if m.Value != "" { log.Printf("[theme] page reported color-scheme: %s", m.Value) } | |
| 401 | 430 | } | |
| 402 | 431 | }) | |
| 403 | 432 | log.Printf("Showing WebView window…") | |
| @@ -437,7 +466,6 @@ func runBrowser() { | |||
| 437 | 466 | ||
| 438 | 467 | // Register basic keyboard shortcuts on the native view to preserve behavior | |
| 439 | 468 | if view != nil { | |
| 440 | - | ctx := context.Background() | |
| 441 | 469 | // DevTools | |
| 442 | 470 | _ = view.RegisterKeyboardShortcut("F12", func() { log.Printf("Shortcut: F12 (devtools)"); _ = view.ShowDevTools() }) | |
| 443 | 471 | // Omnibox (Ctrl+L): rely on injected script listener | |
| @@ -445,29 +473,7 @@ func runBrowser() { | |||
| 445 | 473 | log.Printf("Shortcut: Omnibox toggle") | |
| 446 | 474 | _ = view.InjectScript("window.__dumber_toggle && window.__dumber_toggle()") | |
| 447 | 475 | }) | |
| 448 | - | // Zoom In | |
| 449 | - | _ = view.RegisterKeyboardShortcut("cmdorctrl+=", func() { | |
| 450 | - | log.Printf("Shortcut: Zoom In") | |
| 451 | - | if currentURL == "" { return } | |
| 452 | - | if z, err := browserService.ZoomIn(ctx, currentURL); err == nil { _ = view.SetZoom(z) } | |
| 453 | - | }) | |
| 454 | - | _ = view.RegisterKeyboardShortcut("cmdorctrl+plus", func() { | |
| 455 | - | log.Printf("Shortcut: Zoom In") | |
| 456 | - | if currentURL == "" { return } | |
| 457 | - | if z, err := browserService.ZoomIn(ctx, currentURL); err == nil { _ = view.SetZoom(z) } | |
| 458 | - | }) | |
| 459 | - | // Zoom Out | |
| 460 | - | _ = view.RegisterKeyboardShortcut("cmdorctrl+-", func() { | |
| 461 | - | log.Printf("Shortcut: Zoom Out") | |
| 462 | - | if currentURL == "" { return } | |
| 463 | - | if z, err := browserService.ZoomOut(ctx, currentURL); err == nil { _ = view.SetZoom(z) } | |
| 464 | - | }) | |
| 465 | - | // Zoom Reset | |
| 466 | - | _ = view.RegisterKeyboardShortcut("cmdorctrl+0", func() { | |
| 467 | - | log.Printf("Shortcut: Zoom Reset") | |
| 468 | - | if currentURL == "" { return } | |
| 469 | - | if z, err := browserService.ResetZoom(ctx, currentURL); err == nil { _ = view.SetZoom(z) } | |
| 470 | - | }) | |
| 476 | + | // Zoom handled natively in webkit package (built-in shortcuts) | |
| 471 | 477 | } | |
| 472 | 478 | ||
| 473 | 479 | // Enter GTK main loop only when native backend is available. | |
| @@ -121,6 +121,24 @@ func runBrowser() { | |||||
| 121 | defer runtime.UnlockOSThread() | 121 | defer runtime.UnlockOSThread() | ||
| 122 | } | 122 | } | ||
| 123 | 123 | ||||
| 124 | + | // Proactively harden media pipeline to avoid WebProcess crashes from buggy VAAPI/plugins. | |||
| 125 | + | // Can be disabled with DUMBER_MEDIA_SAFE=0 | |||
| 126 | + | if os.Getenv("DUMBER_MEDIA_SAFE") != "0" { | |||
| 127 | + | // Demote VAAPI-related elements so they won't be auto-picked | |||
| 128 | + | if os.Getenv("GST_PLUGIN_FEATURE_RANK") == "" { | |||
| 129 | + | os.Setenv("GST_PLUGIN_FEATURE_RANK", "vaapisink:0,vaapidecodebin:0,vaapih264dec:0,vaapivideoconvert:0") | |||
| 130 | + | } | |||
| 131 | + | // Provide a safe audio sink to avoid autoaudiosink failures | |||
| 132 | + | if os.Getenv("GST_AUDIO_SINK") == "" { | |||
| 133 | + | os.Setenv("GST_AUDIO_SINK", "fakesink") | |||
| 134 | + | } | |||
| 135 | + | // Optionally disable media stream features that can engage complex pipelines | |||
| 136 | + | if os.Getenv("WEBKIT_DISABLE_MEDIA_STREAM") == "" { | |||
| 137 | + | os.Setenv("WEBKIT_DISABLE_MEDIA_STREAM", "1") | |||
| 138 | + | } | |||
| 139 | + | log.Printf("[media] Safe mode enabled (override with DUMBER_MEDIA_SAFE=0)") | |||
| 140 | + | } | |||
| 141 | + | ||||
| 124 | if err := config.Init(); err != nil { | 142 | if err := config.Init(); err != nil { | ||
| 125 | log.Fatalf("Failed to initialize config: %v", err) | 143 | log.Fatalf("Failed to initialize config: %v", err) | ||
| 126 | } | 144 | } | ||
| @@ -142,7 +160,8 @@ func runBrowser() { | |||||
| 142 | // JS controls injection is disabled; using native shortcuts via GTK. | 160 | // JS controls injection is disabled; using native shortcuts via GTK. | ||
| 143 | 161 | ||||
| 144 | // Track current URL for zoom persistence | 162 | // Track current URL for zoom persistence | ||
| 145 | - | var currentURL string | 163 | + | // Initialize to the initial homepage so shortcuts work immediately |
| 164 | + | var currentURL string = "dumb://homepage" | |||
| 146 | 165 | ||||
| 147 | // Register custom scheme resolver for dumb:// | 166 | // Register custom scheme resolver for dumb:// | ||
| 148 | webkit.SetURISchemeResolver(func(uri string) (string, []byte, bool) { | 167 | webkit.SetURISchemeResolver(func(uri string) (string, []byte, bool) { | ||
| @@ -169,6 +188,14 @@ func runBrowser() { | |||||
| 169 | if strings.HasPrefix(path, "/api/") { path = strings.TrimPrefix(path, "/api") } | 188 | if strings.HasPrefix(path, "/api/") { path = strings.TrimPrefix(path, "/api") } | ||
| 170 | if path == "/api" { path = "/" } | 189 | if path == "/api" { path = "/" } | ||
| 171 | switch { | 190 | switch { | ||
| 191 | + | case strings.HasPrefix(path, "/rendering/status"): | |||
| 192 | + | log.Printf("[api] GET /rendering/status") | |||
| 193 | + | // Report configured rendering mode; runtime GPU state depends on WebKit internals | |||
| 194 | + | resp := struct{ | |||
| 195 | + | Mode string `json:"mode"` | |||
| 196 | + | }{Mode: string(cfg.RenderingMode)} | |||
| 197 | + | b, _ := json.Marshal(resp) | |||
| 198 | + | return "application/json; charset=utf-8", b, true | |||
| 172 | case strings.HasPrefix(path, "/config"): | 199 | case strings.HasPrefix(path, "/config"): | ||
| 173 | log.Printf("[api] GET /config") | 200 | log.Printf("[api] GET /config") | ||
| 174 | // Build config info | 201 | // Build config info | ||
| @@ -290,6 +317,7 @@ func runBrowser() { | |||||
| 290 | DefaultSerifFont: cfg.Appearance.SerifFont, | 317 | DefaultSerifFont: cfg.Appearance.SerifFont, | ||
| 291 | DefaultMonospaceFont: cfg.Appearance.MonospaceFont, | 318 | DefaultMonospaceFont: cfg.Appearance.MonospaceFont, | ||
| 292 | DefaultFontSize: cfg.Appearance.DefaultFontSize, | 319 | DefaultFontSize: cfg.Appearance.DefaultFontSize, | ||
| 320 | + | Rendering: webkit.RenderingConfig{Mode: string(cfg.RenderingMode)}, | |||
| 293 | }) | 321 | }) | ||
| 294 | if err != nil { | 322 | if err != nil { | ||
| 295 | log.Printf("Warning: failed to create WebView: %v", err) | 323 | log.Printf("Warning: failed to create WebView: %v", err) | ||
| @@ -325,6 +353,7 @@ func runBrowser() { | |||||
| 325 | URL string `json:"url"` | 353 | URL string `json:"url"` | ||
| 326 | Q string `json:"q"` | 354 | Q string `json:"q"` | ||
| 327 | Limit int `json:"limit"` | 355 | Limit int `json:"limit"` | ||
| 356 | + | Value string `json:"value"` | |||
| 328 | // Wails fetch bridge | 357 | // Wails fetch bridge | ||
| 329 | ID string `json:"id"` | 358 | ID string `json:"id"` | ||
| 330 | Payload json.RawMessage `json:"payload"` | 359 | Payload json.RawMessage `json:"payload"` | ||
| @@ -397,7 +426,7 @@ func runBrowser() { | |||||
| 397 | _ = view.InjectScript("window.__dumber_wails_resolve('" + m.ID + "', '{}')") | 426 | _ = view.InjectScript("window.__dumber_wails_resolve('" + m.ID + "', '{}')") | ||
| 398 | } | 427 | } | ||
| 399 | case "theme": | 428 | case "theme": | ||
| 400 | - | if m.URL != "" { log.Printf("[theme] page reported color-scheme: %s", m.URL) } | 429 | + | if m.Value != "" { log.Printf("[theme] page reported color-scheme: %s", m.Value) } |
| 401 | } | 430 | } | ||
| 402 | }) | 431 | }) | ||
| 403 | log.Printf("Showing WebView window…") | 432 | log.Printf("Showing WebView window…") | ||
| @@ -437,7 +466,6 @@ func runBrowser() { | |||||
| 437 | 466 | ||||
| 438 | // Register basic keyboard shortcuts on the native view to preserve behavior | 467 | // Register basic keyboard shortcuts on the native view to preserve behavior | ||
| 439 | if view != nil { | 468 | if view != nil { | ||
| 440 | - | ctx := context.Background() | |||
| 441 | // DevTools | 469 | // DevTools | ||
| 442 | _ = view.RegisterKeyboardShortcut("F12", func() { log.Printf("Shortcut: F12 (devtools)"); _ = view.ShowDevTools() }) | 470 | _ = view.RegisterKeyboardShortcut("F12", func() { log.Printf("Shortcut: F12 (devtools)"); _ = view.ShowDevTools() }) | ||
| 443 | // Omnibox (Ctrl+L): rely on injected script listener | 471 | // Omnibox (Ctrl+L): rely on injected script listener | ||
| @@ -445,29 +473,7 @@ func runBrowser() { | |||||
| 445 | log.Printf("Shortcut: Omnibox toggle") | 473 | log.Printf("Shortcut: Omnibox toggle") | ||
| 446 | _ = view.InjectScript("window.__dumber_toggle && window.__dumber_toggle()") | 474 | _ = view.InjectScript("window.__dumber_toggle && window.__dumber_toggle()") | ||
| 447 | }) | 475 | }) | ||
| 448 | - | // Zoom In | 476 | + | // Zoom handled natively in webkit package (built-in shortcuts) |
| 449 | - | _ = view.RegisterKeyboardShortcut("cmdorctrl+=", func() { | |||
| 450 | - | log.Printf("Shortcut: Zoom In") | |||
| 451 | - | if currentURL == "" { return } | |||
| 452 | - | if z, err := browserService.ZoomIn(ctx, currentURL); err == nil { _ = view.SetZoom(z) } | |||
| 453 | - | }) | |||
| 454 | - | _ = view.RegisterKeyboardShortcut("cmdorctrl+plus", func() { | |||
| 455 | - | log.Printf("Shortcut: Zoom In") | |||
| 456 | - | if currentURL == "" { return } | |||
| 457 | - | if z, err := browserService.ZoomIn(ctx, currentURL); err == nil { _ = view.SetZoom(z) } | |||
| 458 | - | }) | |||
| 459 | - | // Zoom Out | |||
| 460 | - | _ = view.RegisterKeyboardShortcut("cmdorctrl+-", func() { | |||
| 461 | - | log.Printf("Shortcut: Zoom Out") | |||
| 462 | - | if currentURL == "" { return } | |||
| 463 | - | if z, err := browserService.ZoomOut(ctx, currentURL); err == nil { _ = view.SetZoom(z) } | |||
| 464 | - | }) | |||
| 465 | - | // Zoom Reset | |||
| 466 | - | _ = view.RegisterKeyboardShortcut("cmdorctrl+0", func() { | |||
| 467 | - | log.Printf("Shortcut: Zoom Reset") | |||
| 468 | - | if currentURL == "" { return } | |||
| 469 | - | if z, err := browserService.ResetZoom(ctx, currentURL); err == nil { _ = view.SetZoom(z) } | |||
| 470 | - | }) | |||
| 471 | } | 477 | } | ||
| 472 | 478 | ||||
| 473 | // Enter GTK main loop only when native backend is available. | 479 | // Enter GTK main loop only when native backend is available. | ||
M pkg/webkit/build_cgo_stub.go +2 -3
| @@ -3,8 +3,8 @@ | |||
| 3 | 3 | package webkit | |
| 4 | 4 | ||
| 5 | 5 | /* | |
| 6 | - | #cgo pkg-config: webkit2gtk-4.0 gtk+-3.0 | |
| 7 | - | #include <webkit2/webkit2.h> | |
| 6 | + | #cgo pkg-config: webkitgtk-6.0 gtk4 | |
| 7 | + | #include <webkit/webkit.h> | |
| 8 | 8 | #include <gtk/gtk.h> | |
| 9 | 9 | */ | |
| 10 | 10 | import "C" | |
| @@ -12,4 +12,3 @@ import "C" | |||
| 12 | 12 | // This file documents intended CGO linkage. It is excluded from builds unless | |
| 13 | 13 | // the 'webkit_cgo' build tag is specified. Real implementations will move APIs | |
| 14 | 14 | // from the stubs into CGO-backed functions and types. | |
| 15 | - | ||
| @@ -3,8 +3,8 @@ | |||||
| 3 | package webkit | 3 | package webkit | ||
| 4 | 4 | ||||
| 5 | /* | 5 | /* | ||
| 6 | - | #cgo pkg-config: webkit2gtk-4.0 gtk+-3.0 | 6 | + | #cgo pkg-config: webkitgtk-6.0 gtk4 |
| 7 | - | #include <webkit2/webkit2.h> | 7 | + | #include <webkit/webkit.h> |
| 8 | #include <gtk/gtk.h> | 8 | #include <gtk/gtk.h> | ||
| 9 | */ | 9 | */ | ||
| 10 | import "C" | 10 | import "C" | ||
| @@ -12,4 +12,3 @@ import "C" | |||||
| 12 | // This file documents intended CGO linkage. It is excluded from builds unless | 12 | // This file documents intended CGO linkage. It is excluded from builds unless | ||
| 13 | // the 'webkit_cgo' build tag is specified. Real implementations will move APIs | 13 | // the 'webkit_cgo' build tag is specified. Real implementations will move APIs | ||
| 14 | // from the stubs into CGO-backed functions and types. | 14 | // from the stubs into CGO-backed functions and types. | ||
| 15 | - | ||||
M pkg/webkit/devtools_cgo.go +2 -3
| @@ -3,8 +3,8 @@ | |||
| 3 | 3 | package webkit | |
| 4 | 4 | ||
| 5 | 5 | /* | |
| 6 | - | #cgo pkg-config: webkit2gtk-4.0 gtk+-3.0 | |
| 7 | - | #include <webkit2/webkit2.h> | |
| 6 | + | #cgo pkg-config: webkitgtk-6.0 gtk4 | |
| 7 | + | #include <webkit/webkit.h> | |
| 8 | 8 | */ | |
| 9 | 9 | import "C" | |
| 10 | 10 | ||
| @@ -23,4 +23,3 @@ func (w *WebView) CloseDevTools() error { | |||
| 23 | 23 | C.webkit_web_inspector_close(insp) | |
| 24 | 24 | return nil | |
| 25 | 25 | } | |
| 26 | - | ||
| @@ -3,8 +3,8 @@ | |||||
| 3 | package webkit | 3 | package webkit | ||
| 4 | 4 | ||||
| 5 | /* | 5 | /* | ||
| 6 | - | #cgo pkg-config: webkit2gtk-4.0 gtk+-3.0 | 6 | + | #cgo pkg-config: webkitgtk-6.0 gtk4 |
| 7 | - | #include <webkit2/webkit2.h> | 7 | + | #include <webkit/webkit.h> |
| 8 | */ | 8 | */ | ||
| 9 | import "C" | 9 | import "C" | ||
| 10 | 10 | ||||
| @@ -23,4 +23,3 @@ func (w *WebView) CloseDevTools() error { | |||||
| 23 | C.webkit_web_inspector_close(insp) | 23 | C.webkit_web_inspector_close(insp) | ||
| 24 | return nil | 24 | return nil | ||
| 25 | } | 25 | } | ||
| 26 | - | ||||
M pkg/webkit/keyboard_cgo.go +73 -2
| @@ -3,9 +3,43 @@ | |||
| 3 | 3 | package webkit | |
| 4 | 4 | ||
| 5 | 5 | /* | |
| 6 | - | #cgo pkg-config: gtk+-3.0 | |
| 6 | + | #cgo pkg-config: gtk4 | |
| 7 | 7 | #include <gtk/gtk.h> | |
| 8 | 8 | #include <gdk/gdk.h> | |
| 9 | + | ||
| 10 | + | // Declarations for Go callbacks | |
| 11 | + | extern void goOnKeyPress(unsigned long id, unsigned int keyval, GdkModifierType state); | |
| 12 | + | extern void goOnButtonPress(unsigned long id, unsigned int button, GdkModifierType state); | |
| 13 | + | ||
| 14 | + | // Key controller callback -> forward to Go | |
| 15 | + | static gboolean on_key_pressed(GtkEventControllerKey* controller, guint keyval, guint keycode, GdkModifierType state, gpointer user_data) { | |
| 16 | + | (void)controller; (void)keycode; | |
| 17 | + | goOnKeyPress((unsigned long)user_data, keyval, state); | |
| 18 | + | return FALSE; // do not stop other handlers | |
| 19 | + | } | |
| 20 | + | ||
| 21 | + | // Mouse click gesture -> forward side buttons to Go | |
| 22 | + | static void on_click_pressed(GtkGestureClick* gesture, gint n_press, gdouble x, gdouble y, gpointer user_data) { | |
| 23 | + | (void)n_press; (void)x; (void)y; | |
| 24 | + | guint button = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(gesture)); | |
| 25 | + | // Pass 0 for modifier state (not provided via gesture API) | |
| 26 | + | goOnButtonPress((unsigned long)user_data, button, 0); | |
| 27 | + | } | |
| 28 | + | ||
| 29 | + | // Attach controllers to a widget (GTK4) | |
| 30 | + | static void attach_key_controller(GtkWidget* widget, unsigned long id) { | |
| 31 | + | if (!widget) return; | |
| 32 | + | GtkEventController* keyc = gtk_event_controller_key_new(); | |
| 33 | + | g_signal_connect_data(keyc, "key-pressed", G_CALLBACK(on_key_pressed), (gpointer)id, NULL, 0); | |
| 34 | + | gtk_widget_add_controller(widget, keyc); | |
| 35 | + | } | |
| 36 | + | ||
| 37 | + | static void attach_mouse_gesture(GtkWidget* widget, unsigned long id) { | |
| 38 | + | if (!widget) return; | |
| 39 | + | GtkGesture* click = gtk_gesture_click_new(); | |
| 40 | + | g_signal_connect_data(click, "pressed", G_CALLBACK(on_click_pressed), (gpointer)id, NULL, 0); | |
| 41 | + | gtk_widget_add_controller(widget, GTK_EVENT_CONTROLLER(click)); | |
| 42 | + | } | |
| 9 | 43 | */ | |
| 10 | 44 | import "C" | |
| 11 | 45 | ||
| @@ -63,7 +97,8 @@ func dispatchAccelerator(uid uintptr, keyval uint, state uint) { | |||
| 63 | 97 | keyName = "plus" | |
| 64 | 98 | case uint(C.GDK_KEY_equal): | |
| 65 | 99 | keyName = "=" | |
| 66 | - | case uint(C.GDK_KEY_minus), uint(C.GDK_KEY_KP_Subtract): | |
| 100 | + | case uint(C.GDK_KEY_minus), uint(C.GDK_KEY_KP_Subtract), uint(C.GDK_KEY_underscore): | |
| 101 | + | // Treat underscore as minus for layouts where '-' requires Shift (e.g., AZERTY) | |
| 67 | 102 | keyName = "-" | |
| 68 | 103 | case uint(C.GDK_KEY_0), uint(C.GDK_KEY_KP_0): | |
| 69 | 104 | keyName = "0" | |
| @@ -123,6 +158,42 @@ func unregisterView(id uintptr) { | |||
| 123 | 158 | regMu.Unlock() | |
| 124 | 159 | } | |
| 125 | 160 | ||
| 161 | + | // AttachKeyboardControllers attaches GTK4 key and mouse controllers to the view widget. | |
| 162 | + | func AttachKeyboardControllers(w *WebView) { | |
| 163 | + | if w == nil || w.native == nil || w.native.view == nil { | |
| 164 | + | return | |
| 165 | + | } | |
| 166 | + | C.attach_key_controller(w.native.view, C.ulong(w.id)) | |
| 167 | + | if w.native.win != nil { | |
| 168 | + | C.attach_key_controller(w.native.win, C.ulong(w.id)) | |
| 169 | + | } | |
| 170 | + | C.attach_mouse_gesture(w.native.view, C.ulong(w.id)) | |
| 171 | + | log.Printf("[input] GTK4 key/mouse controllers attached") | |
| 172 | + | } | |
| 173 | + | ||
| 174 | + | //export goOnThemeChanged | |
| 175 | + | func goOnThemeChanged(id C.ulong, preferDark C.int) { | |
| 176 | + | uid := uintptr(id) | |
| 177 | + | regMu.RLock() | |
| 178 | + | vw := viewByID[uid] | |
| 179 | + | regMu.RUnlock() | |
| 180 | + | if vw == nil { | |
| 181 | + | return | |
| 182 | + | } | |
| 183 | + | // Inject runtime theme update into the page | |
| 184 | + | dval := "false" | |
| 185 | + | if int(preferDark) != 0 { dval = "true" } | |
| 186 | + | js := fmt.Sprintf(`(() => { try { const d=%s; const cs=d?'dark':'light'; console.log('[dumber] theme change:'+cs); | |
| 187 | + | let m=document.querySelector('meta[name="color-scheme"]'); | |
| 188 | + | if(!m){ m=document.createElement('meta'); m.name='color-scheme'; document.head.appendChild(m); } | |
| 189 | + | m.setAttribute('content', d ? 'dark light' : 'light dark'); | |
| 190 | + | let s=document.getElementById('__dumber_theme_style'); | |
| 191 | + | if(!s){ s=document.createElement('style'); s.id='__dumber_theme_style'; document.documentElement.appendChild(s); } | |
| 192 | + | s.textContent=':root{color-scheme:' + (d?'dark':'light') + ';}'; | |
| 193 | + | } catch(e) { console.warn('[dumber] theme runtime update failed', e); } })();`, dval) | |
| 194 | + | _ = vw.InjectScript(js) | |
| 195 | + | } | |
| 196 | + | ||
| 126 | 197 | //export goOnUcmMessage | |
| 127 | 198 | func goOnUcmMessage(id C.ulong, json *C.char) { | |
| 128 | 199 | uid := uintptr(id) | |
| @@ -3,9 +3,43 @@ | |||||
| 3 | package webkit | 3 | package webkit | ||
| 4 | 4 | ||||
| 5 | /* | 5 | /* | ||
| 6 | - | #cgo pkg-config: gtk+-3.0 | 6 | + | #cgo pkg-config: gtk4 |
| 7 | #include <gtk/gtk.h> | 7 | #include <gtk/gtk.h> | ||
| 8 | #include <gdk/gdk.h> | 8 | #include <gdk/gdk.h> | ||
| 9 | + | ||||
| 10 | + | // Declarations for Go callbacks | |||
| 11 | + | extern void goOnKeyPress(unsigned long id, unsigned int keyval, GdkModifierType state); | |||
| 12 | + | extern void goOnButtonPress(unsigned long id, unsigned int button, GdkModifierType state); | |||
| 13 | + | ||||
| 14 | + | // Key controller callback -> forward to Go | |||
| 15 | + | static gboolean on_key_pressed(GtkEventControllerKey* controller, guint keyval, guint keycode, GdkModifierType state, gpointer user_data) { | |||
| 16 | + | (void)controller; (void)keycode; | |||
| 17 | + | goOnKeyPress((unsigned long)user_data, keyval, state); | |||
| 18 | + | return FALSE; // do not stop other handlers | |||
| 19 | + | } | |||
| 20 | + | ||||
| 21 | + | // Mouse click gesture -> forward side buttons to Go | |||
| 22 | + | static void on_click_pressed(GtkGestureClick* gesture, gint n_press, gdouble x, gdouble y, gpointer user_data) { | |||
| 23 | + | (void)n_press; (void)x; (void)y; | |||
| 24 | + | guint button = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(gesture)); | |||
| 25 | + | // Pass 0 for modifier state (not provided via gesture API) | |||
| 26 | + | goOnButtonPress((unsigned long)user_data, button, 0); | |||
| 27 | + | } | |||
| 28 | + | ||||
| 29 | + | // Attach controllers to a widget (GTK4) | |||
| 30 | + | static void attach_key_controller(GtkWidget* widget, unsigned long id) { | |||
| 31 | + | if (!widget) return; | |||
| 32 | + | GtkEventController* keyc = gtk_event_controller_key_new(); | |||
| 33 | + | g_signal_connect_data(keyc, "key-pressed", G_CALLBACK(on_key_pressed), (gpointer)id, NULL, 0); | |||
| 34 | + | gtk_widget_add_controller(widget, keyc); | |||
| 35 | + | } | |||
| 36 | + | ||||
| 37 | + | static void attach_mouse_gesture(GtkWidget* widget, unsigned long id) { | |||
| 38 | + | if (!widget) return; | |||
| 39 | + | GtkGesture* click = gtk_gesture_click_new(); | |||
| 40 | + | g_signal_connect_data(click, "pressed", G_CALLBACK(on_click_pressed), (gpointer)id, NULL, 0); | |||
| 41 | + | gtk_widget_add_controller(widget, GTK_EVENT_CONTROLLER(click)); | |||
| 42 | + | } | |||
| 9 | */ | 43 | */ | ||
| 10 | import "C" | 44 | import "C" | ||
| 11 | 45 | ||||
| @@ -63,7 +97,8 @@ func dispatchAccelerator(uid uintptr, keyval uint, state uint) { | |||||
| 63 | keyName = "plus" | 97 | keyName = "plus" | ||
| 64 | case uint(C.GDK_KEY_equal): | 98 | case uint(C.GDK_KEY_equal): | ||
| 65 | keyName = "=" | 99 | keyName = "=" | ||
| 66 | - | case uint(C.GDK_KEY_minus), uint(C.GDK_KEY_KP_Subtract): | 100 | + | case uint(C.GDK_KEY_minus), uint(C.GDK_KEY_KP_Subtract), uint(C.GDK_KEY_underscore): |
| 101 | + | // Treat underscore as minus for layouts where '-' requires Shift (e.g., AZERTY) | |||
| 67 | keyName = "-" | 102 | keyName = "-" | ||
| 68 | case uint(C.GDK_KEY_0), uint(C.GDK_KEY_KP_0): | 103 | case uint(C.GDK_KEY_0), uint(C.GDK_KEY_KP_0): | ||
| 69 | keyName = "0" | 104 | keyName = "0" | ||
| @@ -123,6 +158,42 @@ func unregisterView(id uintptr) { | |||||
| 123 | regMu.Unlock() | 158 | regMu.Unlock() | ||
| 124 | } | 159 | } | ||
| 125 | 160 | ||||
| 161 | + | // AttachKeyboardControllers attaches GTK4 key and mouse controllers to the view widget. | |||
| 162 | + | func AttachKeyboardControllers(w *WebView) { | |||
| 163 | + | if w == nil || w.native == nil || w.native.view == nil { | |||
| 164 | + | return | |||
| 165 | + | } | |||
| 166 | + | C.attach_key_controller(w.native.view, C.ulong(w.id)) | |||
| 167 | + | if w.native.win != nil { | |||
| 168 | + | C.attach_key_controller(w.native.win, C.ulong(w.id)) | |||
| 169 | + | } | |||
| 170 | + | C.attach_mouse_gesture(w.native.view, C.ulong(w.id)) | |||
| 171 | + | log.Printf("[input] GTK4 key/mouse controllers attached") | |||
| 172 | + | } | |||
| 173 | + | ||||
| 174 | + | //export goOnThemeChanged | |||
| 175 | + | func goOnThemeChanged(id C.ulong, preferDark C.int) { | |||
| 176 | + | uid := uintptr(id) | |||
| 177 | + | regMu.RLock() | |||
| 178 | + | vw := viewByID[uid] | |||
| 179 | + | regMu.RUnlock() | |||
| 180 | + | if vw == nil { | |||
| 181 | + | return | |||
| 182 | + | } | |||
| 183 | + | // Inject runtime theme update into the page | |||
| 184 | + | dval := "false" | |||
| 185 | + | if int(preferDark) != 0 { dval = "true" } | |||
| 186 | + | js := fmt.Sprintf(`(() => { try { const d=%s; const cs=d?'dark':'light'; console.log('[dumber] theme change:'+cs); | |||
| 187 | + | let m=document.querySelector('meta[name="color-scheme"]'); | |||
| 188 | + | if(!m){ m=document.createElement('meta'); m.name='color-scheme'; document.head.appendChild(m); } | |||
| 189 | + | m.setAttribute('content', d ? 'dark light' : 'light dark'); | |||
| 190 | + | let s=document.getElementById('__dumber_theme_style'); | |||
| 191 | + | if(!s){ s=document.createElement('style'); s.id='__dumber_theme_style'; document.documentElement.appendChild(s); } | |||
| 192 | + | s.textContent=':root{color-scheme:' + (d?'dark':'light') + ';}'; | |||
| 193 | + | } catch(e) { console.warn('[dumber] theme runtime update failed', e); } })();`, dval) | |||
| 194 | + | _ = vw.InjectScript(js) | |||
| 195 | + | } | |||
| 196 | + | ||||
| 126 | //export goOnUcmMessage | 197 | //export goOnUcmMessage | ||
| 127 | func goOnUcmMessage(id C.ulong, json *C.char) { | 198 | func goOnUcmMessage(id C.ulong, json *C.char) { | ||
| 128 | uid := uintptr(id) | 199 | uid := uintptr(id) | ||
M pkg/webkit/loop_cgo.go +13 -6
| @@ -3,18 +3,25 @@ | |||
| 3 | 3 | package webkit | |
| 4 | 4 | ||
| 5 | 5 | /* | |
| 6 | - | #cgo pkg-config: gtk+-3.0 | |
| 6 | + | #cgo pkg-config: gtk4 | |
| 7 | 7 | #include <gtk/gtk.h> | |
| 8 | + | #include <glib.h> | |
| 8 | 9 | */ | |
| 9 | 10 | import "C" | |
| 10 | 11 | ||
| 11 | - | // RunMainLoop enters the GTK main loop and blocks until quit. | |
| 12 | + | var mainLoop *C.GMainLoop | |
| 13 | + | ||
| 14 | + | // RunMainLoop enters the GLib main loop (GTK4) and blocks until quit. | |
| 12 | 15 | func RunMainLoop() { | |
| 13 | - | C.gtk_main() | |
| 16 | + | if mainLoop == nil { | |
| 17 | + | mainLoop = C.g_main_loop_new(nil, C.gboolean(0)) | |
| 18 | + | } | |
| 19 | + | C.g_main_loop_run(mainLoop) | |
| 14 | 20 | } | |
| 15 | 21 | ||
| 16 | - | // QuitMainLoop requests the GTK main loop to exit. | |
| 22 | + | // QuitMainLoop requests the main loop to exit. | |
| 17 | 23 | func QuitMainLoop() { | |
| 18 | - | C.gtk_main_quit() | |
| 24 | + | if mainLoop != nil { | |
| 25 | + | C.g_main_loop_quit(mainLoop) | |
| 26 | + | } | |
| 19 | 27 | } | |
| 20 | - | ||
| @@ -3,18 +3,25 @@ | |||||
| 3 | package webkit | 3 | package webkit | ||
| 4 | 4 | ||||
| 5 | /* | 5 | /* | ||
| 6 | - | #cgo pkg-config: gtk+-3.0 | 6 | + | #cgo pkg-config: gtk4 |
| 7 | #include <gtk/gtk.h> | 7 | #include <gtk/gtk.h> | ||
| 8 | + | #include <glib.h> | |||
| 8 | */ | 9 | */ | ||
| 9 | import "C" | 10 | import "C" | ||
| 10 | 11 | ||||
| 11 | - | // RunMainLoop enters the GTK main loop and blocks until quit. | 12 | + | var mainLoop *C.GMainLoop |
| 13 | + | ||||
| 14 | + | // RunMainLoop enters the GLib main loop (GTK4) and blocks until quit. | |||
| 12 | func RunMainLoop() { | 15 | func RunMainLoop() { | ||
| 13 | - | C.gtk_main() | 16 | + | if mainLoop == nil { |
| 17 | + | mainLoop = C.g_main_loop_new(nil, C.gboolean(0)) | |||
| 18 | + | } | |||
| 19 | + | C.g_main_loop_run(mainLoop) | |||
| 14 | } | 20 | } | ||
| 15 | 21 | ||||
| 16 | - | // QuitMainLoop requests the GTK main loop to exit. | 22 | + | // QuitMainLoop requests the main loop to exit. |
| 17 | func QuitMainLoop() { | 23 | func QuitMainLoop() { | ||
| 18 | - | C.gtk_main_quit() | 24 | + | if mainLoop != nil { |
| 25 | + | C.g_main_loop_quit(mainLoop) | |||
| 26 | + | } | |||
| 19 | } | 27 | } | ||
| 20 | - | ||||
A pkg/webkit/scheme.go +9 -0
| @@ -0,0 +1,9 @@ | |||
| 1 | + | //go:build !webkit_cgo | |
| 2 | + | ||
| 3 | + | package webkit | |
| 4 | + | ||
| 5 | + | // SchemeResolver provides bytes and mime for a given custom URI. | |
| 6 | + | type SchemeResolver func(uri string) (mime string, data []byte, ok bool) | |
| 7 | + | ||
| 8 | + | // SetURISchemeResolver is a no-op in non-CGO builds. | |
| 9 | + | func SetURISchemeResolver(r SchemeResolver) { /* no-op */ } | |
| @@ -0,0 +1,9 @@ | |||||
| 1 | + | //go:build !webkit_cgo | |||
| 2 | + | ||||
| 3 | + | package webkit | |||
| 4 | + | ||||
| 5 | + | // SchemeResolver provides bytes and mime for a given custom URI. | |||
| 6 | + | type SchemeResolver func(uri string) (mime string, data []byte, ok bool) | |||
| 7 | + | ||||
| 8 | + | // SetURISchemeResolver is a no-op in non-CGO builds. | |||
| 9 | + | func SetURISchemeResolver(r SchemeResolver) { /* no-op */ } | |||
M pkg/webkit/script_cgo.go +2 -2
| @@ -3,9 +3,9 @@ | |||
| 3 | 3 | package webkit | |
| 4 | 4 | ||
| 5 | 5 | /* | |
| 6 | - | #cgo pkg-config: webkit2gtk-4.0 gtk+-3.0 | |
| 6 | + | #cgo pkg-config: webkitgtk-6.0 gtk4 | |
| 7 | 7 | #include <stdlib.h> | |
| 8 | - | #include <webkit2/webkit2.h> | |
| 8 | + | #include <webkit/webkit.h> | |
| 9 | 9 | */ | |
| 10 | 10 | import "C" | |
| 11 | 11 | ||
| @@ -3,9 +3,9 @@ | |||||
| 3 | package webkit | 3 | package webkit | ||
| 4 | 4 | ||||
| 5 | /* | 5 | /* | ||
| 6 | - | #cgo pkg-config: webkit2gtk-4.0 gtk+-3.0 | 6 | + | #cgo pkg-config: webkitgtk-6.0 gtk4 |
| 7 | #include <stdlib.h> | 7 | #include <stdlib.h> | ||
| 8 | - | #include <webkit2/webkit2.h> | 8 | + | #include <webkit/webkit.h> |
| 9 | */ | 9 | */ | ||
| 10 | import "C" | 10 | import "C" | ||
| 11 | 11 | ||||
M pkg/webkit/settings.go +11 -0
| @@ -25,4 +25,15 @@ type Config struct { | |||
| 25 | 25 | DefaultSerifFont string | |
| 26 | 26 | DefaultMonospaceFont string | |
| 27 | 27 | DefaultFontSize int // CSS px (~points) | |
| 28 | + | ||
| 29 | + | // Rendering controls GPU/CPU selection and debug options | |
| 30 | + | Rendering RenderingConfig | |
| 31 | + | } | |
| 32 | + | ||
| 33 | + | // RenderingConfig controls hardware acceleration preferences. | |
| 34 | + | // Mode accepts: "auto" (default), "gpu", or "cpu". | |
| 35 | + | // DebugGPU enables compositing indicators if supported. | |
| 36 | + | type RenderingConfig struct { | |
| 37 | + | Mode string | |
| 38 | + | DebugGPU bool | |
| 28 | 39 | } | |
| @@ -25,4 +25,15 @@ type Config struct { | |||||
| 25 | DefaultSerifFont string | 25 | DefaultSerifFont string | ||
| 26 | DefaultMonospaceFont string | 26 | DefaultMonospaceFont string | ||
| 27 | DefaultFontSize int // CSS px (~points) | 27 | DefaultFontSize int // CSS px (~points) | ||
| 28 | + | ||||
| 29 | + | // Rendering controls GPU/CPU selection and debug options | |||
| 30 | + | Rendering RenderingConfig | |||
| 31 | + | } | |||
| 32 | + | ||||
| 33 | + | // RenderingConfig controls hardware acceleration preferences. | |||
| 34 | + | // Mode accepts: "auto" (default), "gpu", or "cpu". | |||
| 35 | + | // DebugGPU enables compositing indicators if supported. | |||
| 36 | + | type RenderingConfig struct { | |||
| 37 | + | Mode string | |||
| 38 | + | DebugGPU bool | |||
| 28 | } | 39 | } | ||
M pkg/webkit/webview_cgo.go +252 -80
| @@ -3,21 +3,53 @@ | |||
| 3 | 3 | package webkit | |
| 4 | 4 | ||
| 5 | 5 | /* | |
| 6 | - | #cgo pkg-config: webkit2gtk-4.0 gtk+-3.0 javascriptcoregtk-4.0 | |
| 6 | + | #cgo pkg-config: webkitgtk-6.0 gtk4 javascriptcoregtk-6.0 | |
| 7 | 7 | #include <stdlib.h> | |
| 8 | 8 | #include <gtk/gtk.h> | |
| 9 | - | #include <webkit2/webkit2.h> | |
| 9 | + | #include <webkit/webkit.h> | |
| 10 | 10 | #include <glib-object.h> | |
| 11 | 11 | #include <gdk/gdk.h> | |
| 12 | 12 | #include <jsc/jsc.h> | |
| 13 | 13 | #include <glib.h> | |
| 14 | 14 | #include <gio/gio.h> | |
| 15 | 15 | ||
| 16 | - | static GtkWidget* new_window() { return gtk_window_new(GTK_WINDOW_TOPLEVEL); } | |
| 17 | - | static void connect_destroy_quit(GtkWidget* w) { g_signal_connect(w, "destroy", G_CALLBACK(gtk_main_quit), NULL); } | |
| 16 | + | static GtkWidget* new_window() { return GTK_WIDGET(gtk_window_new()); } | |
| 17 | + | // GTK4: gtk_main_quit removed; leave as no-op for now (Quit handled in Go) | |
| 18 | + | static void connect_destroy_quit(GtkWidget* w) { (void)w; } | |
| 18 | 19 | static WebKitWebView* as_webview(GtkWidget* w) { return WEBKIT_WEB_VIEW(w); } | |
| 19 | - | static WebKitWebsiteDataManager* make_wdm(const gchar* data, const gchar* cache){ | |
| 20 | - | return webkit_website_data_manager_new("base-data-directory", data, "base-cache-directory", cache, NULL); | |
| 20 | + | // WebsiteDataManager creation will be handled via GTK4/WebKit6 APIs in Go code. | |
| 21 | + | ||
| 22 | + | // Forward declare helpers used below | |
| 23 | + | static void maybe_set_cookie_policy(WebKitCookieManager* cm, int policy); | |
| 24 | + | ||
| 25 | + | // Construct a WebKitWebView via g_object_new with a fresh UserContentManager. | |
| 26 | + | static GtkWidget* new_webview_with_ucm_and_session(const char* data_dir, const char* cache_dir, const char* cookie_path, WebKitUserContentManager** out_ucm) { | |
| 27 | + | WebKitUserContentManager* u = webkit_user_content_manager_new(); | |
| 28 | + | if (!u) return NULL; | |
| 29 | + | // WebKitGTK 6: create NetworkSession with data/cache directories | |
| 30 | + | WebKitNetworkSession* sess = webkit_network_session_new( | |
| 31 | + | data_dir ? data_dir : "", | |
| 32 | + | cache_dir ? cache_dir : ""); | |
| 33 | + | if (!sess) { g_object_unref(u); return NULL; } | |
| 34 | + | // Configure cookie persistence and policy (no third-party by default) | |
| 35 | + | WebKitCookieManager* cm = webkit_network_session_get_cookie_manager(sess); | |
| 36 | + | if (cm) { | |
| 37 | + | if (cookie_path && cookie_path[0]) { | |
| 38 | + | webkit_cookie_manager_set_persistent_storage(cm, cookie_path, WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE); | |
| 39 | + | } | |
| 40 | + | // 2 => no-third-party (default), 0 => always, 1 => never | |
| 41 | + | maybe_set_cookie_policy(cm, 2); | |
| 42 | + | } | |
| 43 | + | // Persist credentials (HTTP auth, etc.) | |
| 44 | + | webkit_network_session_set_persistent_credential_storage_enabled(sess, TRUE); | |
| 45 | + | GtkWidget* w = GTK_WIDGET(g_object_new(WEBKIT_TYPE_WEB_VIEW, | |
| 46 | + | "user-content-manager", u, | |
| 47 | + | "network-session", sess, | |
| 48 | + | NULL)); | |
| 49 | + | // WebView holds refs to provided objects; drop our temporary refs | |
| 50 | + | g_object_unref(sess); | |
| 51 | + | if (out_ucm) { *out_ucm = u; } | |
| 52 | + | return w; | |
| 21 | 53 | } | |
| 22 | 54 | ||
| 23 | 55 | static gboolean gtk_prefers_dark() { | |
| @@ -31,28 +63,12 @@ static gboolean gtk_prefers_dark() { | |||
| 31 | 63 | // Note: preferred color scheme is handled via a user script injection to support | |
| 32 | 64 | // older WebKitGTK versions without the color-scheme API. See enableUserContentManager. | |
| 33 | 65 | ||
| 34 | - | // Forward declaration of Go callback | |
| 35 | - | extern void goOnKeyPress(unsigned long id, guint keyval, GdkModifierType state); | |
| 36 | - | extern void goOnButtonPress(unsigned long id, guint button, GdkModifierType state); | |
| 37 | 66 | extern void goOnUcmMessage(unsigned long id, const char* json); | |
| 38 | 67 | extern void goOnTitleChanged(unsigned long id, const char* title); | |
| 39 | 68 | extern void goOnURIChanged(unsigned long id, const char* uri); | |
| 69 | + | extern void goOnThemeChanged(unsigned long id, int prefer_dark); | |
| 40 | 70 | extern void* goResolveURIScheme(char* uri, size_t* out_len, char** out_mime); | |
| 41 | 71 | ||
| 42 | - | gboolean on_key_press(GtkWidget* widget, GdkEventKey* event, gpointer user_data) { | |
| 43 | - | (void)widget; | |
| 44 | - | unsigned long id = (unsigned long)user_data; | |
| 45 | - | goOnKeyPress(id, event->keyval, event->state); | |
| 46 | - | return FALSE; // Propagate | |
| 47 | - | } | |
| 48 | - | ||
| 49 | - | gboolean on_button_press(GtkWidget* widget, GdkEventButton* event, gpointer user_data) { | |
| 50 | - | (void)widget; | |
| 51 | - | unsigned long id = (unsigned long)user_data; | |
| 52 | - | goOnButtonPress(id, event->button, event->state); | |
| 53 | - | return FALSE; | |
| 54 | - | } | |
| 55 | - | ||
| 56 | 72 | void on_title_notify(GObject* obj, GParamSpec* pspec, gpointer user_data) { | |
| 57 | 73 | (void)pspec; | |
| 58 | 74 | WebKitWebView* view = WEBKIT_WEB_VIEW(obj); | |
| @@ -75,19 +91,17 @@ void on_uri_notify(GObject* obj, GParamSpec* pspec, gpointer user_data) { | |||
| 75 | 91 | if (uri) { goOnURIChanged((unsigned long)user_data, uri); } | |
| 76 | 92 | } | |
| 77 | 93 | ||
| 78 | - | static gchar* js_result_to_utf8(WebKitJavascriptResult* r) { | |
| 79 | - | JSCValue* v = webkit_javascript_result_get_js_value(r); | |
| 80 | - | // Returns newly-allocated string; free with g_free() | |
| 81 | - | return jsc_value_to_string(v); | |
| 94 | + | // React to GTK theme preference changes at runtime | |
| 95 | + | void on_theme_changed(GObject* obj, GParamSpec* pspec, gpointer user_data) { | |
| 96 | + | (void)pspec; | |
| 97 | + | GtkSettings* settings = GTK_SETTINGS(obj); | |
| 98 | + | if (!settings) return; | |
| 99 | + | gboolean prefer = FALSE; | |
| 100 | + | g_object_get(settings, "gtk-application-prefer-dark-theme", &prefer, NULL); | |
| 101 | + | goOnThemeChanged((unsigned long)user_data, prefer ? 1 : 0); | |
| 82 | 102 | } | |
| 83 | 103 | ||
| 84 | - | void on_ucm_message(WebKitUserContentManager* m, WebKitJavascriptResult* r, gpointer user_data) { | |
| 85 | - | (void)m; | |
| 86 | - | unsigned long id = (unsigned long)user_data; | |
| 87 | - | gchar* s = js_result_to_utf8(r); | |
| 88 | - | goOnUcmMessage(id, s); | |
| 89 | - | g_free(s); | |
| 90 | - | } | |
| 104 | + | // NOTE: UCM message callback signature changed in WebKitGTK 6; will be reimplemented later. | |
| 91 | 105 | ||
| 92 | 106 | void on_uri_scheme(WebKitURISchemeRequest* request, gpointer user_data) { | |
| 93 | 107 | (void)user_data; | |
| @@ -106,6 +120,107 @@ void on_uri_scheme(WebKitURISchemeRequest* request, gpointer user_data) { | |||
| 106 | 120 | g_error_free(err); | |
| 107 | 121 | } | |
| 108 | 122 | } | |
| 123 | + | ||
| 124 | + | // Script message handler wiring for WebKitGTK 6 | |
| 125 | + | // Forward declaration for callback used in g_signal_connect_data | |
| 126 | + | // WebKitGTK 6 delivers JSCValue* for script-message-received::NAME | |
| 127 | + | static void ucm_on_script_message_cb(WebKitUserContentManager* ucm, JSCValue* val, gpointer user_data); | |
| 128 | + | // Uses WebKitScriptMessage and script-message-received::NAME signal. | |
| 129 | + | static void ucm_on_script_message_cb(WebKitUserContentManager* ucm, JSCValue* val, gpointer user_data) { | |
| 130 | + | (void)ucm; | |
| 131 | + | if (!val) return; | |
| 132 | + | gchar* s = jsc_value_to_string(val); | |
| 133 | + | if (!s) return; | |
| 134 | + | goOnUcmMessage((unsigned long)user_data, s); | |
| 135 | + | g_free(s); | |
| 136 | + | } | |
| 137 | + | ||
| 138 | + | static gboolean register_ucm_handler(WebKitUserContentManager* ucm, const char* name, unsigned long id) { | |
| 139 | + | if (!ucm || !name) return FALSE; | |
| 140 | + | // WebKitGTK 6 signature: (ucm, name, world_name) | |
| 141 | + | webkit_user_content_manager_register_script_message_handler(ucm, name, NULL); | |
| 142 | + | gchar* signal = g_strdup_printf("script-message-received::%s", name); | |
| 143 | + | if (!signal) return FALSE; | |
| 144 | + | g_signal_connect_data(G_OBJECT(ucm), signal, G_CALLBACK(ucm_on_script_message_cb), (gpointer)id, NULL, 0); | |
| 145 | + | g_free(signal); | |
| 146 | + | return TRUE; | |
| 147 | + | } | |
| 148 | + | ||
| 149 | + | // ----- Conditional helpers for settings across WebKit versions ----- | |
| 150 | + | #ifndef WEBKIT_CHECK_VERSION | |
| 151 | + | #define WEBKIT_CHECK_VERSION(major,minor,micro) (0) | |
| 152 | + | #endif | |
| 153 | + | ||
| 154 | + | static void maybe_set_hw_policy(WebKitSettings* settings, int mode) { | |
| 155 | + | #if WEBKIT_CHECK_VERSION(2,40,0) | |
| 156 | + | // mode: 0=ON_DEMAND, 1=ALWAYS, 2=NEVER | |
| 157 | + | if (!settings) return; | |
| 158 | + | if (mode == 1) { | |
| 159 | + | webkit_settings_set_hardware_acceleration_policy(settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_ALWAYS); | |
| 160 | + | } else if (mode == 2) { | |
| 161 | + | webkit_settings_set_hardware_acceleration_policy(settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER); | |
| 162 | + | } else { | |
| 163 | + | // Leave default policy (auto) without forcing a value to avoid referencing | |
| 164 | + | // WEBKIT_HARDWARE_ACCELERATION_POLICY_ON_DEMAND on older headers. | |
| 165 | + | } | |
| 166 | + | #else | |
| 167 | + | (void)settings; (void)mode; | |
| 168 | + | #endif | |
| 169 | + | } | |
| 170 | + | ||
| 171 | + | static void maybe_set_draw_indicators(WebKitSettings* settings, int enable) { | |
| 172 | + | #if WEBKIT_CHECK_VERSION(2,36,0) | |
| 173 | + | if (!settings) return; | |
| 174 | + | webkit_settings_set_draw_compositing_indicators(settings, enable ? 1 : 0); | |
| 175 | + | #else | |
| 176 | + | (void)settings; (void)enable; | |
| 177 | + | #endif | |
| 178 | + | } | |
| 179 | + | ||
| 180 | + | static void maybe_set_webgl(WebKitSettings* settings, int enable) { | |
| 181 | + | #if WEBKIT_CHECK_VERSION(2,20,0) | |
| 182 | + | if (!settings) return; | |
| 183 | + | webkit_settings_set_enable_webgl(settings, enable ? 1 : 0); | |
| 184 | + | #else | |
| 185 | + | (void)settings; (void)enable; | |
| 186 | + | #endif | |
| 187 | + | } | |
| 188 | + | ||
| 189 | + | static void maybe_set_canvas_accel(WebKitSettings* settings, int enable) { | |
| 190 | + | #if WEBKIT_CHECK_VERSION(2,20,0) | |
| 191 | + | if (!settings) return; | |
| 192 | + | webkit_settings_set_enable_2d_canvas_acceleration(settings, enable ? 1 : 0); | |
| 193 | + | #else | |
| 194 | + | (void)settings; (void)enable; | |
| 195 | + | #endif | |
| 196 | + | } | |
| 197 | + | ||
| 198 | + | static void maybe_set_media_user_gesture(WebKitSettings* settings, int require) { | |
| 199 | + | #if WEBKIT_CHECK_VERSION(2,20,0) | |
| 200 | + | if (!settings) return; | |
| 201 | + | webkit_settings_set_media_playback_requires_user_gesture(settings, require ? 1 : 0); | |
| 202 | + | #else | |
| 203 | + | (void)settings; (void)require; | |
| 204 | + | #endif | |
| 205 | + | } | |
| 206 | + | ||
| 207 | + | // Set cookie accept policy with compile-time compatibility across WebKit versions | |
| 208 | + | static void maybe_set_cookie_policy(WebKitCookieManager* cm, int policy) { | |
| 209 | + | if (!cm) return; | |
| 210 | + | #if defined(WEBKIT_COOKIE_ACCEPT_POLICY_ALWAYS) | |
| 211 | + | WebKitCookieAcceptPolicy p = WEBKIT_COOKIE_ACCEPT_POLICY_ALWAYS; | |
| 212 | + | if (policy == 1) p = WEBKIT_COOKIE_ACCEPT_POLICY_NEVER; // never | |
| 213 | + | else if (policy == 2) p = WEBKIT_COOKIE_ACCEPT_POLICY_NO_THIRD_PARTY; // no third-party | |
| 214 | + | webkit_cookie_manager_set_accept_policy(cm, p); | |
| 215 | + | #elif defined(WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS) | |
| 216 | + | WebKitCookieAcceptPolicy p = WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS; | |
| 217 | + | if (policy == 1) p = WEBKIT_COOKIE_POLICY_ACCEPT_NEVER; | |
| 218 | + | else if (policy == 2) p = WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY; | |
| 219 | + | webkit_cookie_manager_set_accept_policy(cm, p); | |
| 220 | + | #else | |
| 221 | + | (void)policy; // Not supported in this header version | |
| 222 | + | #endif | |
| 223 | + | } | |
| 109 | 224 | */ | |
| 110 | 225 | import "C" | |
| 111 | 226 | ||
| @@ -172,7 +287,7 @@ type WebView struct { | |||
| 172 | 287 | // NewWebView constructs a new WebView instance with native WebKit2GTK widgets. | |
| 173 | 288 | func NewWebView(cfg *Config) (*WebView, error) { | |
| 174 | 289 | log.Printf("[webkit] Initializing GTK and creating WebView (CGO)") | |
| 175 | - | if C.gtk_init_check(nil, nil) == 0 { | |
| 290 | + | if C.gtk_init_check() == 0 { | |
| 176 | 291 | return nil, errors.New("failed to initialize GTK") | |
| 177 | 292 | } | |
| 178 | 293 | ||
| @@ -190,12 +305,14 @@ func NewWebView(cfg *Config) (*WebView, error) { | |||
| 190 | 305 | ||
| 191 | 306 | cData := C.CString(dataDir) | |
| 192 | 307 | cCache := C.CString(cacheDir) | |
| 308 | + | cookieFile := filepath.Join(dataDir, "cookies.db") | |
| 309 | + | cCookie := C.CString(cookieFile) | |
| 193 | 310 | defer C.free(unsafe.Pointer(cData)) | |
| 194 | 311 | defer C.free(unsafe.Pointer(cCache)) | |
| 195 | - | wdm := C.make_wdm((*C.gchar)(cData), (*C.gchar)(cCache)) | |
| 196 | - | if wdm == nil { return nil, errors.New("failed to create WebsiteDataManager") } | |
| 197 | - | ctx := C.webkit_web_context_new_with_website_data_manager(wdm) | |
| 198 | - | if ctx == nil { return nil, errors.New("failed to create WebContext") } | |
| 312 | + | defer C.free(unsafe.Pointer(cCookie)) | |
| 313 | + | // Use default WebContext to register the custom URI scheme handler; the | |
| 314 | + | // WebView itself is created with a WebKitNetworkSession for persistence. | |
| 315 | + | ctx := C.webkit_web_context_get_default() | |
| 199 | 316 | ||
| 200 | 317 | // Register custom URI scheme handler for "dumb://" | |
| 201 | 318 | { | |
| @@ -203,19 +320,11 @@ func NewWebView(cfg *Config) (*WebView, error) { | |||
| 203 | 320 | C.webkit_web_context_register_uri_scheme(ctx, sch, (C.WebKitURISchemeRequestCallback)(C.on_uri_scheme), nil, nil) | |
| 204 | 321 | C.free(unsafe.Pointer(sch)) | |
| 205 | 322 | } | |
| 323 | + | // Cookie manager persistent storage handled via NetworkSession in new_webview_with_ucm_and_session | |
| 206 | 324 | ||
| 207 | - | // Cookie manager persistent storage | |
| 208 | - | cm := C.webkit_web_context_get_cookie_manager(ctx) | |
| 209 | - | if cm != nil { | |
| 210 | - | cookiePath := filepath.Join(dataDir, "cookies.sqlite") | |
| 211 | - | cCookie := C.CString(cookiePath) | |
| 212 | - | defer C.free(unsafe.Pointer(cCookie)) | |
| 213 | - | C.webkit_cookie_manager_set_persistent_storage(cm, (*C.gchar)(cCookie), C.WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE) | |
| 214 | - | C.webkit_cookie_manager_set_accept_policy(cm, C.WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS) | |
| 215 | - | } | |
| 216 | - | ||
| 217 | - | // Create WebView with context | |
| 218 | - | viewWidget := C.webkit_web_view_new_with_context(ctx) // GtkWidget* | |
| 325 | + | // Create WebView through g_object_new with a fresh UCM (GTK4/WebKit 6 style) | |
| 326 | + | var createdUcm *C.WebKitUserContentManager | |
| 327 | + | viewWidget := C.new_webview_with_ucm_and_session(cData, cCache, cCookie, &createdUcm) | |
| 219 | 328 | if viewWidget == nil { | |
| 220 | 329 | return nil, errors.New("failed to create WebKitWebView") | |
| 221 | 330 | } | |
| @@ -227,32 +336,92 @@ func NewWebView(cfg *Config) (*WebView, error) { | |||
| 227 | 336 | } | |
| 228 | 337 | ||
| 229 | 338 | // Pack view widget into the window | |
| 230 | - | C.gtk_container_add((*C.GtkContainer)(unsafe.Pointer(win)), viewWidget) | |
| 339 | + | // GTK4: containers removed; use gtk_window_set_child | |
| 340 | + | C.gtk_window_set_child((*C.GtkWindow)(unsafe.Pointer(win)), viewWidget) | |
| 231 | 341 | C.gtk_window_set_default_size((*C.GtkWindow)(unsafe.Pointer(win)), 1024, 768) | |
| 232 | 342 | C.connect_destroy_quit(win) | |
| 233 | 343 | ||
| 234 | 344 | v := &WebView{ | |
| 235 | 345 | config: cfg, | |
| 236 | 346 | zoom: 1.0, | |
| 237 | - | native: &nativeView{win: win, view: viewWidget, wv: C.as_webview(viewWidget), ucm: nil}, | |
| 347 | + | native: &nativeView{win: win, view: viewWidget, wv: C.as_webview(viewWidget), ucm: createdUcm}, | |
| 238 | 348 | window: &Window{win: win}, | |
| 239 | 349 | } | |
| 240 | - | // Allow receiving button press events | |
| 241 | - | C.gtk_widget_add_events(win, C.GDK_BUTTON_PRESS_MASK|C.GDK_BUTTON_RELEASE_MASK) | |
| 242 | - | C.gtk_widget_add_events(viewWidget, C.GDK_BUTTON_PRESS_MASK|C.GDK_BUTTON_RELEASE_MASK) | |
| 243 | - | ||
| 244 | - | // Assign an ID for accelerator dispatch and connect input events | |
| 350 | + | // Assign an ID for accelerator dispatch | |
| 245 | 351 | v.id = nextViewID() | |
| 246 | 352 | registerView(v.id, v) | |
| 247 | - | C.g_signal_connect_data(C.gpointer(unsafe.Pointer(win)), C.CString("key-press-event"), C.GCallback(C.on_key_press), C.gpointer(v.id), nil, 0) | |
| 248 | - | C.g_signal_connect_data(C.gpointer(unsafe.Pointer(viewWidget)), C.CString("button-press-event"), C.GCallback(C.on_button_press), C.gpointer(v.id), nil, 0) | |
| 249 | - | C.g_signal_connect_data(C.gpointer(unsafe.Pointer(win)), C.CString("button-press-event"), C.GCallback(C.on_button_press), C.gpointer(v.id), nil, 0) | |
| 353 | + | // Attach GTK4 input controllers (keyboard, mouse) | |
| 354 | + | AttachKeyboardControllers(v) | |
| 355 | + | ||
| 356 | + | // Watch for GTK theme changes and propagate to page at runtime | |
| 357 | + | if settings := C.gtk_settings_get_default(); settings != nil { | |
| 358 | + | cprop := C.CString("notify::gtk-application-prefer-dark-theme") | |
| 359 | + | C.g_signal_connect_data(C.gpointer(unsafe.Pointer(settings)), cprop, C.GCallback(C.on_theme_changed), C.gpointer(v.id), nil, 0) | |
| 360 | + | C.free(unsafe.Pointer(cprop)) | |
| 361 | + | } | |
| 362 | + | ||
| 363 | + | // Native zoom shortcuts (independent of app services) | |
| 364 | + | // Ctrl+='=' and Ctrl+'+' → Zoom In; Ctrl+'-' → Zoom Out; Ctrl+'0' → Reset | |
| 365 | + | _ = v.RegisterKeyboardShortcut("cmdorctrl+=", func() { | |
| 366 | + | nz := v.zoom | |
| 367 | + | if nz <= 0 { nz = 1.0 } | |
| 368 | + | nz *= 1.1 | |
| 369 | + | if nz < 0.25 { nz = 0.25 } | |
| 370 | + | if nz > 5.0 { nz = 5.0 } | |
| 371 | + | _ = v.SetZoom(nz) | |
| 372 | + | }) | |
| 373 | + | _ = v.RegisterKeyboardShortcut("cmdorctrl+plus", func() { | |
| 374 | + | nz := v.zoom | |
| 375 | + | if nz <= 0 { nz = 1.0 } | |
| 376 | + | nz *= 1.1 | |
| 377 | + | if nz < 0.25 { nz = 0.25 } | |
| 378 | + | if nz > 5.0 { nz = 5.0 } | |
| 379 | + | _ = v.SetZoom(nz) | |
| 380 | + | }) | |
| 381 | + | _ = v.RegisterKeyboardShortcut("cmdorctrl-", func() { | |
| 382 | + | nz := v.zoom | |
| 383 | + | if nz <= 0 { nz = 1.0 } | |
| 384 | + | nz /= 1.1 | |
| 385 | + | if nz < 0.25 { nz = 0.25 } | |
| 386 | + | if nz > 5.0 { nz = 5.0 } | |
| 387 | + | _ = v.SetZoom(nz) | |
| 388 | + | }) | |
| 389 | + | _ = v.RegisterKeyboardShortcut("cmdorctrl+0", func() { _ = v.SetZoom(1.0) }) | |
| 250 | 390 | // Update window title when page title changes | |
| 251 | - | C.g_signal_connect_data(C.gpointer(unsafe.Pointer(viewWidget)), C.CString("notify::title"), C.GCallback(C.on_title_notify), C.gpointer(unsafe.Pointer(win)), nil, 0) | |
| 391 | + | cNotifyTitle1 := C.CString("notify::title") | |
| 392 | + | C.g_signal_connect_data(C.gpointer(unsafe.Pointer(viewWidget)), cNotifyTitle1, C.GCallback(C.on_title_notify), C.gpointer(unsafe.Pointer(win)), nil, 0) | |
| 393 | + | C.free(unsafe.Pointer(cNotifyTitle1)) | |
| 252 | 394 | // Also dispatch title change to Go with view id | |
| 253 | - | C.g_signal_connect_data(C.gpointer(unsafe.Pointer(viewWidget)), C.CString("notify::title"), C.GCallback(C.on_title_notify_id), C.gpointer(v.id), nil, 0) | |
| 395 | + | cNotifyTitle2 := C.CString("notify::title") | |
| 396 | + | C.g_signal_connect_data(C.gpointer(unsafe.Pointer(viewWidget)), cNotifyTitle2, C.GCallback(C.on_title_notify_id), C.gpointer(v.id), nil, 0) | |
| 397 | + | C.free(unsafe.Pointer(cNotifyTitle2)) | |
| 254 | 398 | // Notify URI changes to Go to keep current URL in sync | |
| 255 | - | C.g_signal_connect_data(C.gpointer(unsafe.Pointer(viewWidget)), C.CString("notify::uri"), C.GCallback(C.on_uri_notify), C.gpointer(v.id), nil, 0) | |
| 399 | + | cNotifyURI := C.CString("notify::uri") | |
| 400 | + | C.g_signal_connect_data(C.gpointer(unsafe.Pointer(viewWidget)), cNotifyURI, C.GCallback(C.on_uri_notify), C.gpointer(v.id), nil, 0) | |
| 401 | + | C.free(unsafe.Pointer(cNotifyURI)) | |
| 402 | + | // Apply hardware acceleration and related settings based on cfg.Rendering | |
| 403 | + | if settings := C.webkit_web_view_get_settings(v.native.wv); settings != nil { | |
| 404 | + | // Hardware acceleration policy (guarded by version) | |
| 405 | + | switch cfg.Rendering.Mode { | |
| 406 | + | case "gpu": | |
| 407 | + | C.maybe_set_hw_policy(settings, 1) | |
| 408 | + | C.maybe_set_webgl(settings, 1) | |
| 409 | + | C.maybe_set_canvas_accel(settings, 1) | |
| 410 | + | case "cpu": | |
| 411 | + | C.maybe_set_hw_policy(settings, 2) | |
| 412 | + | C.maybe_set_webgl(settings, 0) | |
| 413 | + | C.maybe_set_canvas_accel(settings, 0) | |
| 414 | + | default: // auto | |
| 415 | + | C.maybe_set_hw_policy(settings, 0) | |
| 416 | + | C.maybe_set_webgl(settings, 1) | |
| 417 | + | C.maybe_set_canvas_accel(settings, 1) | |
| 418 | + | } | |
| 419 | + | // Optional compositing indicators for debugging (guarded by version) | |
| 420 | + | if cfg.Rendering.DebugGPU { C.maybe_set_draw_indicators(settings, 1) } | |
| 421 | + | // Reduce media pipeline churn by requiring a user gesture for playback | |
| 422 | + | C.maybe_set_media_user_gesture(settings, 1) | |
| 423 | + | } | |
| 424 | + | ||
| 256 | 425 | if cfg.ZoomDefault > 0 { | |
| 257 | 426 | v.zoom = cfg.ZoomDefault | |
| 258 | 427 | C.webkit_web_view_set_zoom_level(v.native.wv, C.gdouble(v.zoom)) | |
| @@ -295,13 +464,9 @@ func NewWebView(cfg *Config) (*WebView, error) { | |||
| 295 | 464 | runtime.SetFinalizer(v, func(v *WebView) { _ = v.Destroy() }) | |
| 296 | 465 | log.Printf("[webkit] WebView created (CGO)") | |
| 297 | 466 | ||
| 298 | - | // Obtain the view's UserContentManager and inject script | |
| 299 | - | if v.native != nil && v.native.wv != nil { | |
| 300 | - | u := C.webkit_web_view_get_user_content_manager(v.native.wv) | |
| 301 | - | if u != nil { | |
| 302 | - | v.native.ucm = u | |
| 303 | - | v.enableUserContentManager() | |
| 304 | - | } | |
| 467 | + | // Initialize UCM scripts and handlers | |
| 468 | + | if v.native != nil && v.native.ucm != nil { | |
| 469 | + | v.enableUserContentManager() | |
| 305 | 470 | } | |
| 306 | 471 | return v, nil | |
| 307 | 472 | } | |
| @@ -322,8 +487,9 @@ func (w *WebView) Show() error { | |||
| 322 | 487 | if w == nil || w.destroyed || w.native == nil || w.native.win == nil { | |
| 323 | 488 | return ErrNotImplemented | |
| 324 | 489 | } | |
| 325 | - | C.gtk_widget_show(w.native.win) | |
| 326 | - | C.gtk_widget_show(w.native.view) | |
| 490 | + | C.gtk_widget_set_visible(w.native.view, C.gboolean(1)) | |
| 491 | + | C.gtk_widget_set_visible(w.native.win, C.gboolean(1)) | |
| 492 | + | C.gtk_window_present((*C.GtkWindow)(unsafe.Pointer(w.native.win))) | |
| 327 | 493 | log.Printf("[webkit] Show window") | |
| 328 | 494 | return nil | |
| 329 | 495 | } | |
| @@ -332,7 +498,8 @@ func (w *WebView) Hide() error { | |||
| 332 | 498 | if w == nil || w.destroyed || w.native == nil || w.native.win == nil { | |
| 333 | 499 | return ErrNotImplemented | |
| 334 | 500 | } | |
| 335 | - | C.gtk_widget_hide(w.native.win) | |
| 501 | + | C.gtk_widget_set_visible(w.native.view, C.gboolean(0)) | |
| 502 | + | C.gtk_widget_set_visible(w.native.win, C.gboolean(0)) | |
| 336 | 503 | return nil | |
| 337 | 504 | } | |
| 338 | 505 | ||
| @@ -340,7 +507,8 @@ func (w *WebView) Destroy() error { | |||
| 340 | 507 | if w == nil || w.native == nil || w.native.win == nil { | |
| 341 | 508 | return ErrNotImplemented | |
| 342 | 509 | } | |
| 343 | - | C.gtk_widget_destroy(w.native.win) | |
| 510 | + | // GTK4: destroy windows via gtk_window_destroy | |
| 511 | + | C.gtk_window_destroy((*C.GtkWindow)(unsafe.Pointer(w.native.win))) | |
| 344 | 512 | w.destroyed = true | |
| 345 | 513 | unregisterView(w.id) | |
| 346 | 514 | log.Printf("[webkit] Destroy window") | |
| @@ -396,9 +564,11 @@ func (w *WebView) enableUserContentManager() { | |||
| 396 | 564 | if w == nil || w.native == nil || w.native.ucm == nil { return } | |
| 397 | 565 | // Register handler "dumber" | |
| 398 | 566 | cname := C.CString("dumber") | |
| 399 | - | defer C.free(unsafe.Pointer(cname)) | |
| 400 | - | C.webkit_user_content_manager_register_script_message_handler(w.native.ucm, (*C.gchar)(cname)) | |
| 401 | - | C.g_signal_connect_data(C.gpointer(unsafe.Pointer(w.native.ucm)), C.CString("script-message-received::dumber"), C.GCallback(C.on_ucm_message), C.gpointer(w.id), nil, 0) | |
| 567 | + | ok := C.register_ucm_handler(w.native.ucm, cname, C.ulong(w.id)) | |
| 568 | + | if ok == 0 { | |
| 569 | + | log.Printf("[webkit] Failed to register UCM handler; fallback bridge will handle messages") | |
| 570 | + | } | |
| 571 | + | C.free(unsafe.Pointer(cname)) | |
| 402 | 572 | ||
| 403 | 573 | // Inject color-scheme preference script at document-start to inform sites of system theme | |
| 404 | 574 | preferDark := C.gtk_prefers_dark() != 0 | |
| @@ -409,9 +579,9 @@ func (w *WebView) enableUserContentManager() { | |||
| 409 | 579 | } | |
| 410 | 580 | var schemeJS string | |
| 411 | 581 | if preferDark { | |
| 412 | - | schemeJS = "(() => { try { const d=true; const cs=d?'dark':'light'; console.log('[dumber] color-scheme set:', cs); try{ window.webkit?.messageHandlers?.dumber?.postMessage(JSON.stringify({type:'theme', value: cs})) }catch(_){} const meta=document.createElement('meta'); meta.name='color-scheme'; meta.content='dark light'; document.documentElement.appendChild(meta); const s=document.createElement('style'); s.textContent=':root{color-scheme:dark;}'; document.documentElement.appendChild(s); const qD='(prefers-color-scheme: dark)'; const qL='(prefers-color-scheme: light)'; const orig=window.matchMedia; window.matchMedia=function(q){ if(typeof q==='string'&&(q.includes(qD)||q.includes(qL))){ const m={matches:q.includes('dark')?d:!d,media:q,onchange:null,addListener(){},removeListener(){},addEventListener(){},removeEventListener(){},dispatchEvent(){return false;}}; return m;} return orig.call(window,q); }; } catch(e){ console.warn('[dumber] color-scheme injection failed', e) } })();" | |
| 582 | + | schemeJS = "(() => { try { const d=true; const cs=d?'dark':'light'; console.log('[dumber] color-scheme set:' + cs); try{ window.webkit?.messageHandlers?.dumber?.postMessage(JSON.stringify({type:'theme', value: cs})) }catch(_){} const meta=document.createElement('meta'); meta.name='color-scheme'; meta.content='dark light'; document.documentElement.appendChild(meta); const s=document.createElement('style'); s.textContent=':root{color-scheme:dark;}'; document.documentElement.appendChild(s); const qD='(prefers-color-scheme: dark)'; const qL='(prefers-color-scheme: light)'; const orig=window.matchMedia; window.matchMedia=function(q){ if(typeof q==='string'&&(q.includes(qD)||q.includes(qL))){ const m={matches:q.includes('dark')?d:!d,media:q,onchange:null,addListener(){},removeListener(){},addEventListener(){},removeEventListener(){},dispatchEvent(){return false;}}; return m;} return orig.call(window,q); }; } catch(e){ console.warn('[dumber] color-scheme injection failed', e) } })();" | |
| 413 | 583 | } else { | |
| 414 | - | schemeJS = "(() => { try { const d=false; const cs=d?'dark':'light'; console.log('[dumber] color-scheme set:', cs); try{ window.webkit?.messageHandlers?.dumber?.postMessage(JSON.stringify({type:'theme', value: cs})) }catch(_){} const meta=document.createElement('meta'); meta.name='color-scheme'; meta.content='light dark'; document.documentElement.appendChild(meta); const s=document.createElement('style'); s.textContent=':root{color-scheme:light;}'; document.documentElement.appendChild(s); const qD='(prefers-color-scheme: dark)'; const qL='(prefers-color-scheme: light)'; const orig=window.matchMedia; window.matchMedia=function(q){ if(typeof q==='string'&&(q.includes(qD)||q.includes(qL))){ const m={matches:q.includes('dark')?d:!d,media:q,onchange:null,addListener(){},removeListener(){},addEventListener(){},removeEventListener(){},dispatchEvent(){return false;}}; return m;} return orig.call(window,q); }; } catch(e){ console.warn('[dumber] color-scheme injection failed', e) } })();" | |
| 584 | + | schemeJS = "(() => { try { const d=false; const cs=d?'dark':'light'; console.log('[dumber] color-scheme set:' + cs); try{ window.webkit?.messageHandlers?.dumber?.postMessage(JSON.stringify({type:'theme', value: cs})) }catch(_){} const meta=document.createElement('meta'); meta.name='color-scheme'; meta.content='light dark'; document.documentElement.appendChild(meta); const s=document.createElement('style'); s.textContent=':root{color-scheme:light;}'; document.documentElement.appendChild(s); const qD='(prefers-color-scheme: dark)'; const qL='(prefers-color-scheme: light)'; const orig=window.matchMedia; window.matchMedia=function(q){ if(typeof q==='string'&&(q.includes(qD)||q.includes(qL))){ const m={matches:q.includes('dark')?d:!d,media:q,onchange:null,addListener(){},removeListener(){},addEventListener(){},removeEventListener(){},dispatchEvent(){return false;}}; return m;} return orig.call(window,q); }; } catch(e){ console.warn('[dumber] color-scheme injection failed', e) } })();" | |
| 415 | 585 | } | |
| 416 | 586 | cScheme := C.CString(schemeJS) | |
| 417 | 587 | defer C.free(unsafe.Pointer(cScheme)) | |
| @@ -440,4 +610,6 @@ func (w *WebView) enableUserContentManager() { | |||
| 440 | 610 | C.webkit_user_content_manager_add_script(w.native.ucm, wailsScript) | |
| 441 | 611 | C.webkit_user_script_unref(wailsScript) | |
| 442 | 612 | } | |
| 613 | + | ||
| 614 | + | // No JS fallback bridge — native UCM is active | |
| 443 | 615 | } | |
| @@ -3,21 +3,53 @@ | |||||
| 3 | package webkit | 3 | package webkit | ||
| 4 | 4 | ||||
| 5 | /* | 5 | /* | ||
| 6 | - | #cgo pkg-config: webkit2gtk-4.0 gtk+-3.0 javascriptcoregtk-4.0 | 6 | + | #cgo pkg-config: webkitgtk-6.0 gtk4 javascriptcoregtk-6.0 |
| 7 | #include <stdlib.h> | 7 | #include <stdlib.h> | ||
| 8 | #include <gtk/gtk.h> | 8 | #include <gtk/gtk.h> | ||
| 9 | - | #include <webkit2/webkit2.h> | 9 | + | #include <webkit/webkit.h> |
| 10 | #include <glib-object.h> | 10 | #include <glib-object.h> | ||
| 11 | #include <gdk/gdk.h> | 11 | #include <gdk/gdk.h> | ||
| 12 | #include <jsc/jsc.h> | 12 | #include <jsc/jsc.h> | ||
| 13 | #include <glib.h> | 13 | #include <glib.h> | ||
| 14 | #include <gio/gio.h> | 14 | #include <gio/gio.h> | ||
| 15 | 15 | ||||
| 16 | - | static GtkWidget* new_window() { return gtk_window_new(GTK_WINDOW_TOPLEVEL); } | 16 | + | static GtkWidget* new_window() { return GTK_WIDGET(gtk_window_new()); } |
| 17 | - | static void connect_destroy_quit(GtkWidget* w) { g_signal_connect(w, "destroy", G_CALLBACK(gtk_main_quit), NULL); } | 17 | + | // GTK4: gtk_main_quit removed; leave as no-op for now (Quit handled in Go) |
| 18 | + | static void connect_destroy_quit(GtkWidget* w) { (void)w; } | |||
| 18 | static WebKitWebView* as_webview(GtkWidget* w) { return WEBKIT_WEB_VIEW(w); } | 19 | static WebKitWebView* as_webview(GtkWidget* w) { return WEBKIT_WEB_VIEW(w); } | ||
| 19 | - | static WebKitWebsiteDataManager* make_wdm(const gchar* data, const gchar* cache){ | 20 | + | // WebsiteDataManager creation will be handled via GTK4/WebKit6 APIs in Go code. |
| 20 | - | return webkit_website_data_manager_new("base-data-directory", data, "base-cache-directory", cache, NULL); | 21 | + | |
| 22 | + | // Forward declare helpers used below | |||
| 23 | + | static void maybe_set_cookie_policy(WebKitCookieManager* cm, int policy); | |||
| 24 | + | ||||
| 25 | + | // Construct a WebKitWebView via g_object_new with a fresh UserContentManager. | |||
| 26 | + | static GtkWidget* new_webview_with_ucm_and_session(const char* data_dir, const char* cache_dir, const char* cookie_path, WebKitUserContentManager** out_ucm) { | |||
| 27 | + | WebKitUserContentManager* u = webkit_user_content_manager_new(); | |||
| 28 | + | if (!u) return NULL; | |||
| 29 | + | // WebKitGTK 6: create NetworkSession with data/cache directories | |||
| 30 | + | WebKitNetworkSession* sess = webkit_network_session_new( | |||
| 31 | + | data_dir ? data_dir : "", | |||
| 32 | + | cache_dir ? cache_dir : ""); | |||
| 33 | + | if (!sess) { g_object_unref(u); return NULL; } | |||
| 34 | + | // Configure cookie persistence and policy (no third-party by default) | |||
| 35 | + | WebKitCookieManager* cm = webkit_network_session_get_cookie_manager(sess); | |||
| 36 | + | if (cm) { | |||
| 37 | + | if (cookie_path && cookie_path[0]) { | |||
| 38 | + | webkit_cookie_manager_set_persistent_storage(cm, cookie_path, WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE); | |||
| 39 | + | } | |||
| 40 | + | // 2 => no-third-party (default), 0 => always, 1 => never | |||
| 41 | + | maybe_set_cookie_policy(cm, 2); | |||
| 42 | + | } | |||
| 43 | + | // Persist credentials (HTTP auth, etc.) | |||
| 44 | + | webkit_network_session_set_persistent_credential_storage_enabled(sess, TRUE); | |||
| 45 | + | GtkWidget* w = GTK_WIDGET(g_object_new(WEBKIT_TYPE_WEB_VIEW, | |||
| 46 | + | "user-content-manager", u, | |||
| 47 | + | "network-session", sess, | |||
| 48 | + | NULL)); | |||
| 49 | + | // WebView holds refs to provided objects; drop our temporary refs | |||
| 50 | + | g_object_unref(sess); | |||
| 51 | + | if (out_ucm) { *out_ucm = u; } | |||
| 52 | + | return w; | |||
| 21 | } | 53 | } | ||
| 22 | 54 | ||||
| 23 | static gboolean gtk_prefers_dark() { | 55 | static gboolean gtk_prefers_dark() { | ||
| @@ -31,28 +63,12 @@ static gboolean gtk_prefers_dark() { | |||||
| 31 | // Note: preferred color scheme is handled via a user script injection to support | 63 | // Note: preferred color scheme is handled via a user script injection to support | ||
| 32 | // older WebKitGTK versions without the color-scheme API. See enableUserContentManager. | 64 | // older WebKitGTK versions without the color-scheme API. See enableUserContentManager. | ||
| 33 | 65 | ||||
| 34 | - | // Forward declaration of Go callback | |||
| 35 | - | extern void goOnKeyPress(unsigned long id, guint keyval, GdkModifierType state); | |||
| 36 | - | extern void goOnButtonPress(unsigned long id, guint button, GdkModifierType state); | |||
| 37 | extern void goOnUcmMessage(unsigned long id, const char* json); | 66 | extern void goOnUcmMessage(unsigned long id, const char* json); | ||
| 38 | extern void goOnTitleChanged(unsigned long id, const char* title); | 67 | extern void goOnTitleChanged(unsigned long id, const char* title); | ||
| 39 | extern void goOnURIChanged(unsigned long id, const char* uri); | 68 | extern void goOnURIChanged(unsigned long id, const char* uri); | ||
| 69 | + | extern void goOnThemeChanged(unsigned long id, int prefer_dark); | |||
| 40 | extern void* goResolveURIScheme(char* uri, size_t* out_len, char** out_mime); | 70 | extern void* goResolveURIScheme(char* uri, size_t* out_len, char** out_mime); | ||
| 41 | 71 | ||||
| 42 | - | gboolean on_key_press(GtkWidget* widget, GdkEventKey* event, gpointer user_data) { | |||
| 43 | - | (void)widget; | |||
| 44 | - | unsigned long id = (unsigned long)user_data; | |||
| 45 | - | goOnKeyPress(id, event->keyval, event->state); | |||
| 46 | - | return FALSE; // Propagate | |||
| 47 | - | } | |||
| 48 | - | ||||
| 49 | - | gboolean on_button_press(GtkWidget* widget, GdkEventButton* event, gpointer user_data) { | |||
| 50 | - | (void)widget; | |||
| 51 | - | unsigned long id = (unsigned long)user_data; | |||
| 52 | - | goOnButtonPress(id, event->button, event->state); | |||
| 53 | - | return FALSE; | |||
| 54 | - | } | |||
| 55 | - | ||||
| 56 | void on_title_notify(GObject* obj, GParamSpec* pspec, gpointer user_data) { | 72 | void on_title_notify(GObject* obj, GParamSpec* pspec, gpointer user_data) { | ||
| 57 | (void)pspec; | 73 | (void)pspec; | ||
| 58 | WebKitWebView* view = WEBKIT_WEB_VIEW(obj); | 74 | WebKitWebView* view = WEBKIT_WEB_VIEW(obj); | ||
| @@ -75,19 +91,17 @@ void on_uri_notify(GObject* obj, GParamSpec* pspec, gpointer user_data) { | |||||
| 75 | if (uri) { goOnURIChanged((unsigned long)user_data, uri); } | 91 | if (uri) { goOnURIChanged((unsigned long)user_data, uri); } | ||
| 76 | } | 92 | } | ||
| 77 | 93 | ||||
| 78 | - | static gchar* js_result_to_utf8(WebKitJavascriptResult* r) { | 94 | + | // React to GTK theme preference changes at runtime |
| 79 | - | JSCValue* v = webkit_javascript_result_get_js_value(r); | 95 | + | void on_theme_changed(GObject* obj, GParamSpec* pspec, gpointer user_data) { |
| 80 | - | // Returns newly-allocated string; free with g_free() | 96 | + | (void)pspec; |
| 81 | - | return jsc_value_to_string(v); | 97 | + | GtkSettings* settings = GTK_SETTINGS(obj); |
| 98 | + | if (!settings) return; | |||
| 99 | + | gboolean prefer = FALSE; | |||
| 100 | + | g_object_get(settings, "gtk-application-prefer-dark-theme", &prefer, NULL); | |||
| 101 | + | goOnThemeChanged((unsigned long)user_data, prefer ? 1 : 0); | |||
| 82 | } | 102 | } | ||
| 83 | 103 | ||||
| 84 | - | void on_ucm_message(WebKitUserContentManager* m, WebKitJavascriptResult* r, gpointer user_data) { | 104 | + | // NOTE: UCM message callback signature changed in WebKitGTK 6; will be reimplemented later. |
| 85 | - | (void)m; | |||
| 86 | - | unsigned long id = (unsigned long)user_data; | |||
| 87 | - | gchar* s = js_result_to_utf8(r); | |||
| 88 | - | goOnUcmMessage(id, s); | |||
| 89 | - | g_free(s); | |||
| 90 | - | } | |||
| 91 | 105 | ||||
| 92 | void on_uri_scheme(WebKitURISchemeRequest* request, gpointer user_data) { | 106 | void on_uri_scheme(WebKitURISchemeRequest* request, gpointer user_data) { | ||
| 93 | (void)user_data; | 107 | (void)user_data; | ||
| @@ -106,6 +120,107 @@ void on_uri_scheme(WebKitURISchemeRequest* request, gpointer user_data) { | |||||
| 106 | g_error_free(err); | 120 | g_error_free(err); | ||
| 107 | } | 121 | } | ||
| 108 | } | 122 | } | ||
| 123 | + | ||||
| 124 | + | // Script message handler wiring for WebKitGTK 6 | |||
| 125 | + | // Forward declaration for callback used in g_signal_connect_data | |||
| 126 | + | // WebKitGTK 6 delivers JSCValue* for script-message-received::NAME | |||
| 127 | + | static void ucm_on_script_message_cb(WebKitUserContentManager* ucm, JSCValue* val, gpointer user_data); | |||
| 128 | + | // Uses WebKitScriptMessage and script-message-received::NAME signal. | |||
| 129 | + | static void ucm_on_script_message_cb(WebKitUserContentManager* ucm, JSCValue* val, gpointer user_data) { | |||
| 130 | + | (void)ucm; | |||
| 131 | + | if (!val) return; | |||
| 132 | + | gchar* s = jsc_value_to_string(val); | |||
| 133 | + | if (!s) return; | |||
| 134 | + | goOnUcmMessage((unsigned long)user_data, s); | |||
| 135 | + | g_free(s); | |||
| 136 | + | } | |||
| 137 | + | ||||
| 138 | + | static gboolean register_ucm_handler(WebKitUserContentManager* ucm, const char* name, unsigned long id) { | |||
| 139 | + | if (!ucm || !name) return FALSE; | |||
| 140 | + | // WebKitGTK 6 signature: (ucm, name, world_name) | |||
| 141 | + | webkit_user_content_manager_register_script_message_handler(ucm, name, NULL); | |||
| 142 | + | gchar* signal = g_strdup_printf("script-message-received::%s", name); | |||
| 143 | + | if (!signal) return FALSE; | |||
| 144 | + | g_signal_connect_data(G_OBJECT(ucm), signal, G_CALLBACK(ucm_on_script_message_cb), (gpointer)id, NULL, 0); | |||
| 145 | + | g_free(signal); | |||
| 146 | + | return TRUE; | |||
| 147 | + | } | |||
| 148 | + | ||||
| 149 | + | // ----- Conditional helpers for settings across WebKit versions ----- | |||
| 150 | + | #ifndef WEBKIT_CHECK_VERSION | |||
| 151 | + | #define WEBKIT_CHECK_VERSION(major,minor,micro) (0) | |||
| 152 | + | #endif | |||
| 153 | + | ||||
| 154 | + | static void maybe_set_hw_policy(WebKitSettings* settings, int mode) { | |||
| 155 | + | #if WEBKIT_CHECK_VERSION(2,40,0) | |||
| 156 | + | // mode: 0=ON_DEMAND, 1=ALWAYS, 2=NEVER | |||
| 157 | + | if (!settings) return; | |||
| 158 | + | if (mode == 1) { | |||
| 159 | + | webkit_settings_set_hardware_acceleration_policy(settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_ALWAYS); | |||
| 160 | + | } else if (mode == 2) { | |||
| 161 | + | webkit_settings_set_hardware_acceleration_policy(settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER); | |||
| 162 | + | } else { | |||
| 163 | + | // Leave default policy (auto) without forcing a value to avoid referencing | |||
| 164 | + | // WEBKIT_HARDWARE_ACCELERATION_POLICY_ON_DEMAND on older headers. | |||
| 165 | + | } | |||
| 166 | + | #else | |||
| 167 | + | (void)settings; (void)mode; | |||
| 168 | + | #endif | |||
| 169 | + | } | |||
| 170 | + | ||||
| 171 | + | static void maybe_set_draw_indicators(WebKitSettings* settings, int enable) { | |||
| 172 | + | #if WEBKIT_CHECK_VERSION(2,36,0) | |||
| 173 | + | if (!settings) return; | |||
| 174 | + | webkit_settings_set_draw_compositing_indicators(settings, enable ? 1 : 0); | |||
| 175 | + | #else | |||
| 176 | + | (void)settings; (void)enable; | |||
| 177 | + | #endif | |||
| 178 | + | } | |||
| 179 | + | ||||
| 180 | + | static void maybe_set_webgl(WebKitSettings* settings, int enable) { | |||
| 181 | + | #if WEBKIT_CHECK_VERSION(2,20,0) | |||
| 182 | + | if (!settings) return; | |||
| 183 | + | webkit_settings_set_enable_webgl(settings, enable ? 1 : 0); | |||
| 184 | + | #else | |||
| 185 | + | (void)settings; (void)enable; | |||
| 186 | + | #endif | |||
| 187 | + | } | |||
| 188 | + | ||||
| 189 | + | static void maybe_set_canvas_accel(WebKitSettings* settings, int enable) { | |||
| 190 | + | #if WEBKIT_CHECK_VERSION(2,20,0) | |||
| 191 | + | if (!settings) return; | |||
| 192 | + | webkit_settings_set_enable_2d_canvas_acceleration(settings, enable ? 1 : 0); | |||
| 193 | + | #else | |||
| 194 | + | (void)settings; (void)enable; | |||
| 195 | + | #endif | |||
| 196 | + | } | |||
| 197 | + | ||||
| 198 | + | static void maybe_set_media_user_gesture(WebKitSettings* settings, int require) { | |||
| 199 | + | #if WEBKIT_CHECK_VERSION(2,20,0) | |||
| 200 | + | if (!settings) return; | |||
| 201 | + | webkit_settings_set_media_playback_requires_user_gesture(settings, require ? 1 : 0); | |||
| 202 | + | #else | |||
| 203 | + | (void)settings; (void)require; | |||
| 204 | + | #endif | |||
| 205 | + | } | |||
| 206 | + | ||||
| 207 | + | // Set cookie accept policy with compile-time compatibility across WebKit versions | |||
| 208 | + | static void maybe_set_cookie_policy(WebKitCookieManager* cm, int policy) { | |||
| 209 | + | if (!cm) return; | |||
| 210 | + | #if defined(WEBKIT_COOKIE_ACCEPT_POLICY_ALWAYS) | |||
| 211 | + | WebKitCookieAcceptPolicy p = WEBKIT_COOKIE_ACCEPT_POLICY_ALWAYS; | |||
| 212 | + | if (policy == 1) p = WEBKIT_COOKIE_ACCEPT_POLICY_NEVER; // never | |||
| 213 | + | else if (policy == 2) p = WEBKIT_COOKIE_ACCEPT_POLICY_NO_THIRD_PARTY; // no third-party | |||
| 214 | + | webkit_cookie_manager_set_accept_policy(cm, p); | |||
| 215 | + | #elif defined(WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS) | |||
| 216 | + | WebKitCookieAcceptPolicy p = WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS; | |||
| 217 | + | if (policy == 1) p = WEBKIT_COOKIE_POLICY_ACCEPT_NEVER; | |||
| 218 | + | else if (policy == 2) p = WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY; | |||
| 219 | + | webkit_cookie_manager_set_accept_policy(cm, p); | |||
| 220 | + | #else | |||
| 221 | + | (void)policy; // Not supported in this header version | |||
| 222 | + | #endif | |||
| 223 | + | } | |||
| 109 | */ | 224 | */ | ||
| 110 | import "C" | 225 | import "C" | ||
| 111 | 226 | ||||
| @@ -172,7 +287,7 @@ type WebView struct { | |||||
| 172 | // NewWebView constructs a new WebView instance with native WebKit2GTK widgets. | 287 | // NewWebView constructs a new WebView instance with native WebKit2GTK widgets. | ||
| 173 | func NewWebView(cfg *Config) (*WebView, error) { | 288 | func NewWebView(cfg *Config) (*WebView, error) { | ||
| 174 | log.Printf("[webkit] Initializing GTK and creating WebView (CGO)") | 289 | log.Printf("[webkit] Initializing GTK and creating WebView (CGO)") | ||
| 175 | - | if C.gtk_init_check(nil, nil) == 0 { | 290 | + | if C.gtk_init_check() == 0 { |
| 176 | return nil, errors.New("failed to initialize GTK") | 291 | return nil, errors.New("failed to initialize GTK") | ||
| 177 | } | 292 | } | ||
| 178 | 293 | ||||
| @@ -190,12 +305,14 @@ func NewWebView(cfg *Config) (*WebView, error) { | |||||
| 190 | 305 | ||||
| 191 | cData := C.CString(dataDir) | 306 | cData := C.CString(dataDir) | ||
| 192 | cCache := C.CString(cacheDir) | 307 | cCache := C.CString(cacheDir) | ||
| 308 | + | cookieFile := filepath.Join(dataDir, "cookies.db") | |||
| 309 | + | cCookie := C.CString(cookieFile) | |||
| 193 | defer C.free(unsafe.Pointer(cData)) | 310 | defer C.free(unsafe.Pointer(cData)) | ||
| 194 | defer C.free(unsafe.Pointer(cCache)) | 311 | defer C.free(unsafe.Pointer(cCache)) | ||
| 195 | - | wdm := C.make_wdm((*C.gchar)(cData), (*C.gchar)(cCache)) | 312 | + | defer C.free(unsafe.Pointer(cCookie)) |
| 196 | - | if wdm == nil { return nil, errors.New("failed to create WebsiteDataManager") } | 313 | + | // Use default WebContext to register the custom URI scheme handler; the |
| 197 | - | ctx := C.webkit_web_context_new_with_website_data_manager(wdm) | 314 | + | // WebView itself is created with a WebKitNetworkSession for persistence. |
| 198 | - | if ctx == nil { return nil, errors.New("failed to create WebContext") } | 315 | + | ctx := C.webkit_web_context_get_default() |
| 199 | 316 | ||||
| 200 | // Register custom URI scheme handler for "dumb://" | 317 | // Register custom URI scheme handler for "dumb://" | ||
| 201 | { | 318 | { | ||
| @@ -203,19 +320,11 @@ func NewWebView(cfg *Config) (*WebView, error) { | |||||
| 203 | C.webkit_web_context_register_uri_scheme(ctx, sch, (C.WebKitURISchemeRequestCallback)(C.on_uri_scheme), nil, nil) | 320 | C.webkit_web_context_register_uri_scheme(ctx, sch, (C.WebKitURISchemeRequestCallback)(C.on_uri_scheme), nil, nil) | ||
| 204 | C.free(unsafe.Pointer(sch)) | 321 | C.free(unsafe.Pointer(sch)) | ||
| 205 | } | 322 | } | ||
| 323 | + | // Cookie manager persistent storage handled via NetworkSession in new_webview_with_ucm_and_session | |||
| 206 | 324 | ||||
| 207 | - | // Cookie manager persistent storage | 325 | + | // Create WebView through g_object_new with a fresh UCM (GTK4/WebKit 6 style) |
| 208 | - | cm := C.webkit_web_context_get_cookie_manager(ctx) | 326 | + | var createdUcm *C.WebKitUserContentManager |
| 209 | - | if cm != nil { | 327 | + | viewWidget := C.new_webview_with_ucm_and_session(cData, cCache, cCookie, &createdUcm) |
| 210 | - | cookiePath := filepath.Join(dataDir, "cookies.sqlite") | |||
| 211 | - | cCookie := C.CString(cookiePath) | |||
| 212 | - | defer C.free(unsafe.Pointer(cCookie)) | |||
| 213 | - | C.webkit_cookie_manager_set_persistent_storage(cm, (*C.gchar)(cCookie), C.WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE) | |||
| 214 | - | C.webkit_cookie_manager_set_accept_policy(cm, C.WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS) | |||
| 215 | - | } | |||
| 216 | - | ||||
| 217 | - | // Create WebView with context | |||
| 218 | - | viewWidget := C.webkit_web_view_new_with_context(ctx) // GtkWidget* | |||
| 219 | if viewWidget == nil { | 328 | if viewWidget == nil { | ||
| 220 | return nil, errors.New("failed to create WebKitWebView") | 329 | return nil, errors.New("failed to create WebKitWebView") | ||
| 221 | } | 330 | } | ||
| @@ -227,32 +336,92 @@ func NewWebView(cfg *Config) (*WebView, error) { | |||||
| 227 | } | 336 | } | ||
| 228 | 337 | ||||
| 229 | // Pack view widget into the window | 338 | // Pack view widget into the window | ||
| 230 | - | C.gtk_container_add((*C.GtkContainer)(unsafe.Pointer(win)), viewWidget) | 339 | + | // GTK4: containers removed; use gtk_window_set_child |
| 340 | + | C.gtk_window_set_child((*C.GtkWindow)(unsafe.Pointer(win)), viewWidget) | |||
| 231 | C.gtk_window_set_default_size((*C.GtkWindow)(unsafe.Pointer(win)), 1024, 768) | 341 | C.gtk_window_set_default_size((*C.GtkWindow)(unsafe.Pointer(win)), 1024, 768) | ||
| 232 | C.connect_destroy_quit(win) | 342 | C.connect_destroy_quit(win) | ||
| 233 | 343 | ||||
| 234 | v := &WebView{ | 344 | v := &WebView{ | ||
| 235 | config: cfg, | 345 | config: cfg, | ||
| 236 | zoom: 1.0, | 346 | zoom: 1.0, | ||
| 237 | - | native: &nativeView{win: win, view: viewWidget, wv: C.as_webview(viewWidget), ucm: nil}, | 347 | + | native: &nativeView{win: win, view: viewWidget, wv: C.as_webview(viewWidget), ucm: createdUcm}, |
| 238 | window: &Window{win: win}, | 348 | window: &Window{win: win}, | ||
| 239 | } | 349 | } | ||
| 240 | - | // Allow receiving button press events | 350 | + | // Assign an ID for accelerator dispatch |
| 241 | - | C.gtk_widget_add_events(win, C.GDK_BUTTON_PRESS_MASK|C.GDK_BUTTON_RELEASE_MASK) | |||
| 242 | - | C.gtk_widget_add_events(viewWidget, C.GDK_BUTTON_PRESS_MASK|C.GDK_BUTTON_RELEASE_MASK) | |||
| 243 | - | ||||
| 244 | - | // Assign an ID for accelerator dispatch and connect input events | |||
| 245 | v.id = nextViewID() | 351 | v.id = nextViewID() | ||
| 246 | registerView(v.id, v) | 352 | registerView(v.id, v) | ||
| 247 | - | C.g_signal_connect_data(C.gpointer(unsafe.Pointer(win)), C.CString("key-press-event"), C.GCallback(C.on_key_press), C.gpointer(v.id), nil, 0) | 353 | + | // Attach GTK4 input controllers (keyboard, mouse) |
| 248 | - | C.g_signal_connect_data(C.gpointer(unsafe.Pointer(viewWidget)), C.CString("button-press-event"), C.GCallback(C.on_button_press), C.gpointer(v.id), nil, 0) | 354 | + | AttachKeyboardControllers(v) |
| 249 | - | C.g_signal_connect_data(C.gpointer(unsafe.Pointer(win)), C.CString("button-press-event"), C.GCallback(C.on_button_press), C.gpointer(v.id), nil, 0) | 355 | + | |
| 356 | + | // Watch for GTK theme changes and propagate to page at runtime | |||
| 357 | + | if settings := C.gtk_settings_get_default(); settings != nil { | |||
| 358 | + | cprop := C.CString("notify::gtk-application-prefer-dark-theme") | |||
| 359 | + | C.g_signal_connect_data(C.gpointer(unsafe.Pointer(settings)), cprop, C.GCallback(C.on_theme_changed), C.gpointer(v.id), nil, 0) | |||
| 360 | + | C.free(unsafe.Pointer(cprop)) | |||
| 361 | + | } | |||
| 362 | + | ||||
| 363 | + | // Native zoom shortcuts (independent of app services) | |||
| 364 | + | // Ctrl+='=' and Ctrl+'+' → Zoom In; Ctrl+'-' → Zoom Out; Ctrl+'0' → Reset | |||
| 365 | + | _ = v.RegisterKeyboardShortcut("cmdorctrl+=", func() { | |||
| 366 | + | nz := v.zoom | |||
| 367 | + | if nz <= 0 { nz = 1.0 } | |||
| 368 | + | nz *= 1.1 | |||
| 369 | + | if nz < 0.25 { nz = 0.25 } | |||
| 370 | + | if nz > 5.0 { nz = 5.0 } | |||
| 371 | + | _ = v.SetZoom(nz) | |||
| 372 | + | }) | |||
| 373 | + | _ = v.RegisterKeyboardShortcut("cmdorctrl+plus", func() { | |||
| 374 | + | nz := v.zoom | |||
| 375 | + | if nz <= 0 { nz = 1.0 } | |||
| 376 | + | nz *= 1.1 | |||
| 377 | + | if nz < 0.25 { nz = 0.25 } | |||
| 378 | + | if nz > 5.0 { nz = 5.0 } | |||
| 379 | + | _ = v.SetZoom(nz) | |||
| 380 | + | }) | |||
| 381 | + | _ = v.RegisterKeyboardShortcut("cmdorctrl-", func() { | |||
| 382 | + | nz := v.zoom | |||
| 383 | + | if nz <= 0 { nz = 1.0 } | |||
| 384 | + | nz /= 1.1 | |||
| 385 | + | if nz < 0.25 { nz = 0.25 } | |||
| 386 | + | if nz > 5.0 { nz = 5.0 } | |||
| 387 | + | _ = v.SetZoom(nz) | |||
| 388 | + | }) | |||
| 389 | + | _ = v.RegisterKeyboardShortcut("cmdorctrl+0", func() { _ = v.SetZoom(1.0) }) | |||
| 250 | // Update window title when page title changes | 390 | // Update window title when page title changes | ||
| 251 | - | C.g_signal_connect_data(C.gpointer(unsafe.Pointer(viewWidget)), C.CString("notify::title"), C.GCallback(C.on_title_notify), C.gpointer(unsafe.Pointer(win)), nil, 0) | 391 | + | cNotifyTitle1 := C.CString("notify::title") |
| 392 | + | C.g_signal_connect_data(C.gpointer(unsafe.Pointer(viewWidget)), cNotifyTitle1, C.GCallback(C.on_title_notify), C.gpointer(unsafe.Pointer(win)), nil, 0) | |||
| 393 | + | C.free(unsafe.Pointer(cNotifyTitle1)) | |||
| 252 | // Also dispatch title change to Go with view id | 394 | // Also dispatch title change to Go with view id | ||
| 253 | - | C.g_signal_connect_data(C.gpointer(unsafe.Pointer(viewWidget)), C.CString("notify::title"), C.GCallback(C.on_title_notify_id), C.gpointer(v.id), nil, 0) | 395 | + | cNotifyTitle2 := C.CString("notify::title") |
| 396 | + | C.g_signal_connect_data(C.gpointer(unsafe.Pointer(viewWidget)), cNotifyTitle2, C.GCallback(C.on_title_notify_id), C.gpointer(v.id), nil, 0) | |||
| 397 | + | C.free(unsafe.Pointer(cNotifyTitle2)) | |||
| 254 | // Notify URI changes to Go to keep current URL in sync | 398 | // Notify URI changes to Go to keep current URL in sync | ||
| 255 | - | C.g_signal_connect_data(C.gpointer(unsafe.Pointer(viewWidget)), C.CString("notify::uri"), C.GCallback(C.on_uri_notify), C.gpointer(v.id), nil, 0) | 399 | + | cNotifyURI := C.CString("notify::uri") |
| 400 | + | C.g_signal_connect_data(C.gpointer(unsafe.Pointer(viewWidget)), cNotifyURI, C.GCallback(C.on_uri_notify), C.gpointer(v.id), nil, 0) | |||
| 401 | + | C.free(unsafe.Pointer(cNotifyURI)) | |||
| 402 | + | // Apply hardware acceleration and related settings based on cfg.Rendering | |||
| 403 | + | if settings := C.webkit_web_view_get_settings(v.native.wv); settings != nil { | |||
| 404 | + | // Hardware acceleration policy (guarded by version) | |||
| 405 | + | switch cfg.Rendering.Mode { | |||
| 406 | + | case "gpu": | |||
| 407 | + | C.maybe_set_hw_policy(settings, 1) | |||
| 408 | + | C.maybe_set_webgl(settings, 1) | |||
| 409 | + | C.maybe_set_canvas_accel(settings, 1) | |||
| 410 | + | case "cpu": | |||
| 411 | + | C.maybe_set_hw_policy(settings, 2) | |||
| 412 | + | C.maybe_set_webgl(settings, 0) | |||
| 413 | + | C.maybe_set_canvas_accel(settings, 0) | |||
| 414 | + | default: // auto | |||
| 415 | + | C.maybe_set_hw_policy(settings, 0) | |||
| 416 | + | C.maybe_set_webgl(settings, 1) | |||
| 417 | + | C.maybe_set_canvas_accel(settings, 1) | |||
| 418 | + | } | |||
| 419 | + | // Optional compositing indicators for debugging (guarded by version) | |||
| 420 | + | if cfg.Rendering.DebugGPU { C.maybe_set_draw_indicators(settings, 1) } | |||
| 421 | + | // Reduce media pipeline churn by requiring a user gesture for playback | |||
| 422 | + | C.maybe_set_media_user_gesture(settings, 1) | |||
| 423 | + | } | |||
| 424 | + | ||||
| 256 | if cfg.ZoomDefault > 0 { | 425 | if cfg.ZoomDefault > 0 { | ||
| 257 | v.zoom = cfg.ZoomDefault | 426 | v.zoom = cfg.ZoomDefault | ||
| 258 | C.webkit_web_view_set_zoom_level(v.native.wv, C.gdouble(v.zoom)) | 427 | C.webkit_web_view_set_zoom_level(v.native.wv, C.gdouble(v.zoom)) | ||
| @@ -295,13 +464,9 @@ func NewWebView(cfg *Config) (*WebView, error) { | |||||
| 295 | runtime.SetFinalizer(v, func(v *WebView) { _ = v.Destroy() }) | 464 | runtime.SetFinalizer(v, func(v *WebView) { _ = v.Destroy() }) | ||
| 296 | log.Printf("[webkit] WebView created (CGO)") | 465 | log.Printf("[webkit] WebView created (CGO)") | ||
| 297 | 466 | ||||
| 298 | - | // Obtain the view's UserContentManager and inject script | 467 | + | // Initialize UCM scripts and handlers |
| 299 | - | if v.native != nil && v.native.wv != nil { | 468 | + | if v.native != nil && v.native.ucm != nil { |
| 300 | - | u := C.webkit_web_view_get_user_content_manager(v.native.wv) | 469 | + | v.enableUserContentManager() |
| 301 | - | if u != nil { | |||
| 302 | - | v.native.ucm = u | |||
| 303 | - | v.enableUserContentManager() | |||
| 304 | - | } | |||
| 305 | } | 470 | } | ||
| 306 | return v, nil | 471 | return v, nil | ||
| 307 | } | 472 | } | ||
| @@ -322,8 +487,9 @@ func (w *WebView) Show() error { | |||||
| 322 | if w == nil || w.destroyed || w.native == nil || w.native.win == nil { | 487 | if w == nil || w.destroyed || w.native == nil || w.native.win == nil { | ||
| 323 | return ErrNotImplemented | 488 | return ErrNotImplemented | ||
| 324 | } | 489 | } | ||
| 325 | - | C.gtk_widget_show(w.native.win) | 490 | + | C.gtk_widget_set_visible(w.native.view, C.gboolean(1)) |
| 326 | - | C.gtk_widget_show(w.native.view) | 491 | + | C.gtk_widget_set_visible(w.native.win, C.gboolean(1)) |
| 492 | + | C.gtk_window_present((*C.GtkWindow)(unsafe.Pointer(w.native.win))) | |||
| 327 | log.Printf("[webkit] Show window") | 493 | log.Printf("[webkit] Show window") | ||
| 328 | return nil | 494 | return nil | ||
| 329 | } | 495 | } | ||
| @@ -332,7 +498,8 @@ func (w *WebView) Hide() error { | |||||
| 332 | if w == nil || w.destroyed || w.native == nil || w.native.win == nil { | 498 | if w == nil || w.destroyed || w.native == nil || w.native.win == nil { | ||
| 333 | return ErrNotImplemented | 499 | return ErrNotImplemented | ||
| 334 | } | 500 | } | ||
| 335 | - | C.gtk_widget_hide(w.native.win) | 501 | + | C.gtk_widget_set_visible(w.native.view, C.gboolean(0)) |
| 502 | + | C.gtk_widget_set_visible(w.native.win, C.gboolean(0)) | |||
| 336 | return nil | 503 | return nil | ||
| 337 | } | 504 | } | ||
| 338 | 505 | ||||
| @@ -340,7 +507,8 @@ func (w *WebView) Destroy() error { | |||||
| 340 | if w == nil || w.native == nil || w.native.win == nil { | 507 | if w == nil || w.native == nil || w.native.win == nil { | ||
| 341 | return ErrNotImplemented | 508 | return ErrNotImplemented | ||
| 342 | } | 509 | } | ||
| 343 | - | C.gtk_widget_destroy(w.native.win) | 510 | + | // GTK4: destroy windows via gtk_window_destroy |
| 511 | + | C.gtk_window_destroy((*C.GtkWindow)(unsafe.Pointer(w.native.win))) | |||
| 344 | w.destroyed = true | 512 | w.destroyed = true | ||
| 345 | unregisterView(w.id) | 513 | unregisterView(w.id) | ||
| 346 | log.Printf("[webkit] Destroy window") | 514 | log.Printf("[webkit] Destroy window") | ||
| @@ -396,9 +564,11 @@ func (w *WebView) enableUserContentManager() { | |||||
| 396 | if w == nil || w.native == nil || w.native.ucm == nil { return } | 564 | if w == nil || w.native == nil || w.native.ucm == nil { return } | ||
| 397 | // Register handler "dumber" | 565 | // Register handler "dumber" | ||
| 398 | cname := C.CString("dumber") | 566 | cname := C.CString("dumber") | ||
| 399 | - | defer C.free(unsafe.Pointer(cname)) | 567 | + | ok := C.register_ucm_handler(w.native.ucm, cname, C.ulong(w.id)) |
| 400 | - | C.webkit_user_content_manager_register_script_message_handler(w.native.ucm, (*C.gchar)(cname)) | 568 | + | if ok == 0 { |
| 401 | - | C.g_signal_connect_data(C.gpointer(unsafe.Pointer(w.native.ucm)), C.CString("script-message-received::dumber"), C.GCallback(C.on_ucm_message), C.gpointer(w.id), nil, 0) | 569 | + | log.Printf("[webkit] Failed to register UCM handler; fallback bridge will handle messages") |
| 570 | + | } | |||
| 571 | + | C.free(unsafe.Pointer(cname)) | |||
| 402 | 572 | ||||
| 403 | // Inject color-scheme preference script at document-start to inform sites of system theme | 573 | // Inject color-scheme preference script at document-start to inform sites of system theme | ||
| 404 | preferDark := C.gtk_prefers_dark() != 0 | 574 | preferDark := C.gtk_prefers_dark() != 0 | ||
| @@ -409,9 +579,9 @@ func (w *WebView) enableUserContentManager() { | |||||
| 409 | } | 579 | } | ||
| 410 | var schemeJS string | 580 | var schemeJS string | ||
| 411 | if preferDark { | 581 | if preferDark { | ||
| 412 | - | schemeJS = "(() => { try { const d=true; const cs=d?'dark':'light'; console.log('[dumber] color-scheme set:', cs); try{ window.webkit?.messageHandlers?.dumber?.postMessage(JSON.stringify({type:'theme', value: cs})) }catch(_){} const meta=document.createElement('meta'); meta.name='color-scheme'; meta.content='dark light'; document.documentElement.appendChild(meta); const s=document.createElement('style'); s.textContent=':root{color-scheme:dark;}'; document.documentElement.appendChild(s); const qD='(prefers-color-scheme: dark)'; const qL='(prefers-color-scheme: light)'; const orig=window.matchMedia; window.matchMedia=function(q){ if(typeof q==='string'&&(q.includes(qD)||q.includes(qL))){ const m={matches:q.includes('dark')?d:!d,media:q,onchange:null,addListener(){},removeListener(){},addEventListener(){},removeEventListener(){},dispatchEvent(){return false;}}; return m;} return orig.call(window,q); }; } catch(e){ console.warn('[dumber] color-scheme injection failed', e) } })();" | 582 | + | schemeJS = "(() => { try { const d=true; const cs=d?'dark':'light'; console.log('[dumber] color-scheme set:' + cs); try{ window.webkit?.messageHandlers?.dumber?.postMessage(JSON.stringify({type:'theme', value: cs})) }catch(_){} const meta=document.createElement('meta'); meta.name='color-scheme'; meta.content='dark light'; document.documentElement.appendChild(meta); const s=document.createElement('style'); s.textContent=':root{color-scheme:dark;}'; document.documentElement.appendChild(s); const qD='(prefers-color-scheme: dark)'; const qL='(prefers-color-scheme: light)'; const orig=window.matchMedia; window.matchMedia=function(q){ if(typeof q==='string'&&(q.includes(qD)||q.includes(qL))){ const m={matches:q.includes('dark')?d:!d,media:q,onchange:null,addListener(){},removeListener(){},addEventListener(){},removeEventListener(){},dispatchEvent(){return false;}}; return m;} return orig.call(window,q); }; } catch(e){ console.warn('[dumber] color-scheme injection failed', e) } })();" |
| 413 | } else { | 583 | } else { | ||
| 414 | - | schemeJS = "(() => { try { const d=false; const cs=d?'dark':'light'; console.log('[dumber] color-scheme set:', cs); try{ window.webkit?.messageHandlers?.dumber?.postMessage(JSON.stringify({type:'theme', value: cs})) }catch(_){} const meta=document.createElement('meta'); meta.name='color-scheme'; meta.content='light dark'; document.documentElement.appendChild(meta); const s=document.createElement('style'); s.textContent=':root{color-scheme:light;}'; document.documentElement.appendChild(s); const qD='(prefers-color-scheme: dark)'; const qL='(prefers-color-scheme: light)'; const orig=window.matchMedia; window.matchMedia=function(q){ if(typeof q==='string'&&(q.includes(qD)||q.includes(qL))){ const m={matches:q.includes('dark')?d:!d,media:q,onchange:null,addListener(){},removeListener(){},addEventListener(){},removeEventListener(){},dispatchEvent(){return false;}}; return m;} return orig.call(window,q); }; } catch(e){ console.warn('[dumber] color-scheme injection failed', e) } })();" | 584 | + | schemeJS = "(() => { try { const d=false; const cs=d?'dark':'light'; console.log('[dumber] color-scheme set:' + cs); try{ window.webkit?.messageHandlers?.dumber?.postMessage(JSON.stringify({type:'theme', value: cs})) }catch(_){} const meta=document.createElement('meta'); meta.name='color-scheme'; meta.content='light dark'; document.documentElement.appendChild(meta); const s=document.createElement('style'); s.textContent=':root{color-scheme:light;}'; document.documentElement.appendChild(s); const qD='(prefers-color-scheme: dark)'; const qL='(prefers-color-scheme: light)'; const orig=window.matchMedia; window.matchMedia=function(q){ if(typeof q==='string'&&(q.includes(qD)||q.includes(qL))){ const m={matches:q.includes('dark')?d:!d,media:q,onchange:null,addListener(){},removeListener(){},addEventListener(){},removeEventListener(){},dispatchEvent(){return false;}}; return m;} return orig.call(window,q); }; } catch(e){ console.warn('[dumber] color-scheme injection failed', e) } })();" |
| 415 | } | 585 | } | ||
| 416 | cScheme := C.CString(schemeJS) | 586 | cScheme := C.CString(schemeJS) | ||
| 417 | defer C.free(unsafe.Pointer(cScheme)) | 587 | defer C.free(unsafe.Pointer(cScheme)) | ||
| @@ -440,4 +610,6 @@ func (w *WebView) enableUserContentManager() { | |||||
| 440 | C.webkit_user_content_manager_add_script(w.native.ucm, wailsScript) | 610 | C.webkit_user_content_manager_add_script(w.native.ucm, wailsScript) | ||
| 441 | C.webkit_user_script_unref(wailsScript) | 611 | C.webkit_user_script_unref(wailsScript) | ||
| 442 | } | 612 | } | ||
| 613 | + | ||||
| 614 | + | // No JS fallback bridge — native UCM is active | |||
| 443 | } | 615 | } | ||
M pkg/webkit/window_cgo.go +4 -3
| @@ -3,7 +3,7 @@ | |||
| 3 | 3 | package webkit | |
| 4 | 4 | ||
| 5 | 5 | /* | |
| 6 | - | #cgo pkg-config: webkit2gtk-4.0 gtk+-3.0 | |
| 6 | + | #cgo pkg-config: webkitgtk-6.0 gtk4 | |
| 7 | 7 | #include <stdlib.h> | |
| 8 | 8 | #include <gtk/gtk.h> | |
| 9 | 9 | */ | |
| @@ -16,10 +16,11 @@ type Window struct { | |||
| 16 | 16 | } | |
| 17 | 17 | ||
| 18 | 18 | func NewWindow(title string) (*Window, error) { | |
| 19 | - | if C.gtk_init_check(nil, nil) == 0 { | |
| 19 | + | if C.gtk_init_check() == 0 { | |
| 20 | 20 | return nil, ErrNotImplemented | |
| 21 | 21 | } | |
| 22 | - | w := C.gtk_window_new(C.GTK_WINDOW_TOPLEVEL) | |
| 22 | + | // GTK4: gtk_window_new() returns GtkWindow*, no toplevel enum | |
| 23 | + | w := (*C.GtkWidget)(unsafe.Pointer(C.gtk_window_new())) | |
| 23 | 24 | if w == nil { | |
| 24 | 25 | return nil, ErrNotImplemented | |
| 25 | 26 | } | |
| @@ -3,7 +3,7 @@ | |||||
| 3 | package webkit | 3 | package webkit | ||
| 4 | 4 | ||||
| 5 | /* | 5 | /* | ||
| 6 | - | #cgo pkg-config: webkit2gtk-4.0 gtk+-3.0 | 6 | + | #cgo pkg-config: webkitgtk-6.0 gtk4 |
| 7 | #include <stdlib.h> | 7 | #include <stdlib.h> | ||
| 8 | #include <gtk/gtk.h> | 8 | #include <gtk/gtk.h> | ||
| 9 | */ | 9 | */ | ||
| @@ -16,10 +16,11 @@ type Window struct { | |||||
| 16 | } | 16 | } | ||
| 17 | 17 | ||||
| 18 | func NewWindow(title string) (*Window, error) { | 18 | func NewWindow(title string) (*Window, error) { | ||
| 19 | - | if C.gtk_init_check(nil, nil) == 0 { | 19 | + | if C.gtk_init_check() == 0 { |
| 20 | return nil, ErrNotImplemented | 20 | return nil, ErrNotImplemented | ||
| 21 | } | 21 | } | ||
| 22 | - | w := C.gtk_window_new(C.GTK_WINDOW_TOPLEVEL) | 22 | + | // GTK4: gtk_window_new() returns GtkWindow*, no toplevel enum |
| 23 | + | w := (*C.GtkWidget)(unsafe.Pointer(C.gtk_window_new())) | |||
| 23 | if w == nil { | 24 | if w == nil { | ||
| 24 | return nil, ErrNotImplemented | 25 | return nil, ErrNotImplemented | ||
| 25 | } | 26 | } | ||
M pkg/webkit/zoom_cgo.go +2 -3
| @@ -3,8 +3,8 @@ | |||
| 3 | 3 | package webkit | |
| 4 | 4 | ||
| 5 | 5 | /* | |
| 6 | - | #cgo pkg-config: webkit2gtk-4.0 gtk+-3.0 | |
| 7 | - | #include <webkit2/webkit2.h> | |
| 6 | + | #cgo pkg-config: webkitgtk-6.0 gtk4 | |
| 7 | + | #include <webkit/webkit.h> | |
| 8 | 8 | */ | |
| 9 | 9 | import "C" | |
| 10 | 10 | ||
| @@ -31,4 +31,3 @@ func (w *WebView) GetZoom() (float64, error) { | |||
| 31 | 31 | w.zoom = z | |
| 32 | 32 | return z, nil | |
| 33 | 33 | } | |
| 34 | - | ||
| @@ -3,8 +3,8 @@ | |||||
| 3 | package webkit | 3 | package webkit | ||
| 4 | 4 | ||||
| 5 | /* | 5 | /* | ||
| 6 | - | #cgo pkg-config: webkit2gtk-4.0 gtk+-3.0 | 6 | + | #cgo pkg-config: webkitgtk-6.0 gtk4 |
| 7 | - | #include <webkit2/webkit2.h> | 7 | + | #include <webkit/webkit.h> |
| 8 | */ | 8 | */ | ||
| 9 | import "C" | 9 | import "C" | ||
| 10 | 10 | ||||
| @@ -31,4 +31,3 @@ func (w *WebView) GetZoom() (float64, error) { | |||||
| 31 | w.zoom = z | 31 | w.zoom = z | ||
| 32 | return z, nil | 32 | return z, nil | ||
| 33 | } | 33 | } | ||
| 34 | - | ||||
M services/config_service.go +27 -5
| @@ -6,7 +6,8 @@ import ( | |||
| 6 | 6 | "os" | |
| 7 | 7 | "path/filepath" | |
| 8 | 8 | ||
| 9 | - | "github.com/bnema/dumber/internal/config" | |
| 9 | + | "github.com/bnema/dumber/internal/config" | |
| 10 | + | "strings" | |
| 10 | 11 | ) | |
| 11 | 12 | ||
| 12 | 13 | // ConfigService handles configuration operations for the application. | |
| @@ -19,10 +20,11 @@ const defaultDirPerm = 0o750 | |||
| 19 | 20 | ||
| 20 | 21 | // ConfigInfo represents configuration information for the frontend. | |
| 21 | 22 | type ConfigInfo struct { | |
| 22 | - | ConfigPath string `json:"config_path"` | |
| 23 | - | DatabasePath string `json:"database_path"` | |
| 24 | - | SearchShortcuts map[string]config.SearchShortcut `json:"search_shortcuts"` | |
| 25 | - | DmenuSettings *config.DmenuConfig `json:"dmenu_settings"` | |
| 23 | + | ConfigPath string `json:"config_path"` | |
| 24 | + | DatabasePath string `json:"database_path"` | |
| 25 | + | SearchShortcuts map[string]config.SearchShortcut `json:"search_shortcuts"` | |
| 26 | + | DmenuSettings *config.DmenuConfig `json:"dmenu_settings"` | |
| 27 | + | RenderingMode string `json:"rendering_mode"` | |
| 26 | 28 | } | |
| 27 | 29 | ||
| 28 | 30 | // NewConfigService creates a new ConfigService instance. | |
| @@ -41,6 +43,7 @@ func (s *ConfigService) GetConfigInfo(ctx context.Context) (*ConfigInfo, error) | |||
| 41 | 43 | DatabasePath: s.config.Database.Path, | |
| 42 | 44 | SearchShortcuts: s.config.SearchShortcuts, | |
| 43 | 45 | DmenuSettings: &s.config.Dmenu, | |
| 46 | + | RenderingMode: string(s.config.RenderingMode), | |
| 44 | 47 | }, nil | |
| 45 | 48 | } | |
| 46 | 49 | ||
| @@ -250,6 +253,25 @@ func (s *ConfigService) ImportConfig(ctx context.Context, newConfig *config.Conf | |||
| 250 | 253 | return s.saveConfig() | |
| 251 | 254 | } | |
| 252 | 255 | ||
| 256 | + | // GetRenderingMode returns the current rendering mode (auto|gpu|cpu). | |
| 257 | + | func (s *ConfigService) GetRenderingMode(ctx context.Context) (string, error) { | |
| 258 | + | _ = ctx | |
| 259 | + | return string(s.config.RenderingMode), nil | |
| 260 | + | } | |
| 261 | + | ||
| 262 | + | // SetRenderingMode updates the rendering mode (auto|gpu|cpu). | |
| 263 | + | func (s *ConfigService) SetRenderingMode(ctx context.Context, mode string) error { | |
| 264 | + | _ = ctx | |
| 265 | + | m := strings.ToLower(strings.TrimSpace(mode)) | |
| 266 | + | switch m { | |
| 267 | + | case "auto", "gpu", "cpu": | |
| 268 | + | s.config.RenderingMode = config.RenderingMode(m) | |
| 269 | + | return s.saveConfig() | |
| 270 | + | default: | |
| 271 | + | return fmt.Errorf("invalid rendering mode: %s", mode) | |
| 272 | + | } | |
| 273 | + | } | |
| 274 | + | ||
| 253 | 275 | // saveConfig saves the current configuration to file. | |
| 254 | 276 | func (s *ConfigService) saveConfig() error { | |
| 255 | 277 | // For now, return success - implementing config saving would require | |
| @@ -6,7 +6,8 @@ import ( | |||||
| 6 | "os" | 6 | "os" | ||
| 7 | "path/filepath" | 7 | "path/filepath" | ||
| 8 | 8 | ||||
| 9 | - | "github.com/bnema/dumber/internal/config" | 9 | + | "github.com/bnema/dumber/internal/config" |
| 10 | + | "strings" | |||
| 10 | ) | 11 | ) | ||
| 11 | 12 | ||||
| 12 | // ConfigService handles configuration operations for the application. | 13 | // ConfigService handles configuration operations for the application. | ||
| @@ -19,10 +20,11 @@ const defaultDirPerm = 0o750 | |||||
| 19 | 20 | ||||
| 20 | // ConfigInfo represents configuration information for the frontend. | 21 | // ConfigInfo represents configuration information for the frontend. | ||
| 21 | type ConfigInfo struct { | 22 | type ConfigInfo struct { | ||
| 22 | - | ConfigPath string `json:"config_path"` | 23 | + | ConfigPath string `json:"config_path"` |
| 23 | - | DatabasePath string `json:"database_path"` | 24 | + | DatabasePath string `json:"database_path"` |
| 24 | - | SearchShortcuts map[string]config.SearchShortcut `json:"search_shortcuts"` | 25 | + | SearchShortcuts map[string]config.SearchShortcut `json:"search_shortcuts"` |
| 25 | - | DmenuSettings *config.DmenuConfig `json:"dmenu_settings"` | 26 | + | DmenuSettings *config.DmenuConfig `json:"dmenu_settings"` |
| 27 | + | RenderingMode string `json:"rendering_mode"` | |||
| 26 | } | 28 | } | ||
| 27 | 29 | ||||
| 28 | // NewConfigService creates a new ConfigService instance. | 30 | // NewConfigService creates a new ConfigService instance. | ||
| @@ -41,6 +43,7 @@ func (s *ConfigService) GetConfigInfo(ctx context.Context) (*ConfigInfo, error) | |||||
| 41 | DatabasePath: s.config.Database.Path, | 43 | DatabasePath: s.config.Database.Path, | ||
| 42 | SearchShortcuts: s.config.SearchShortcuts, | 44 | SearchShortcuts: s.config.SearchShortcuts, | ||
| 43 | DmenuSettings: &s.config.Dmenu, | 45 | DmenuSettings: &s.config.Dmenu, | ||
| 46 | + | RenderingMode: string(s.config.RenderingMode), | |||
| 44 | }, nil | 47 | }, nil | ||
| 45 | } | 48 | } | ||
| 46 | 49 | ||||
| @@ -250,6 +253,25 @@ func (s *ConfigService) ImportConfig(ctx context.Context, newConfig *config.Conf | |||||
| 250 | return s.saveConfig() | 253 | return s.saveConfig() | ||
| 251 | } | 254 | } | ||
| 252 | 255 | ||||
| 256 | + | // GetRenderingMode returns the current rendering mode (auto|gpu|cpu). | |||
| 257 | + | func (s *ConfigService) GetRenderingMode(ctx context.Context) (string, error) { | |||
| 258 | + | _ = ctx | |||
| 259 | + | return string(s.config.RenderingMode), nil | |||
| 260 | + | } | |||
| 261 | + | ||||
| 262 | + | // SetRenderingMode updates the rendering mode (auto|gpu|cpu). | |||
| 263 | + | func (s *ConfigService) SetRenderingMode(ctx context.Context, mode string) error { | |||
| 264 | + | _ = ctx | |||
| 265 | + | m := strings.ToLower(strings.TrimSpace(mode)) | |||
| 266 | + | switch m { | |||
| 267 | + | case "auto", "gpu", "cpu": | |||
| 268 | + | s.config.RenderingMode = config.RenderingMode(m) | |||
| 269 | + | return s.saveConfig() | |||
| 270 | + | default: | |||
| 271 | + | return fmt.Errorf("invalid rendering mode: %s", mode) | |||
| 272 | + | } | |||
| 273 | + | } | |||
| 274 | + | ||||
| 253 | // saveConfig saves the current configuration to file. | 275 | // saveConfig saves the current configuration to file. | ||
| 254 | func (s *ConfigService) saveConfig() error { | 276 | func (s *ConfigService) saveConfig() error { | ||
| 255 | // For now, return success - implementing config saving would require | 277 | // For now, return success - implementing config saving would require | ||
A specs/004-migrate-to-webkitgtk/contracts/rendering-config.yaml +153 -0
| @@ -0,0 +1,153 @@ | |||
| 1 | + | # Rendering Configuration Contract | |
| 2 | + | # Defines the interface for GPU/CPU rendering configuration | |
| 3 | + | ||
| 4 | + | version: "1.0.0" | |
| 5 | + | component: "RenderingConfig" | |
| 6 | + | ||
| 7 | + | # Configuration Interface | |
| 8 | + | config: | |
| 9 | + | rendering_mode: | |
| 10 | + | type: "enum" | |
| 11 | + | values: | |
| 12 | + | - name: "auto" | |
| 13 | + | description: "Automatically detect GPU availability and use if present" | |
| 14 | + | default: true | |
| 15 | + | - name: "gpu" | |
| 16 | + | description: "Force GPU acceleration (fail if unavailable)" | |
| 17 | + | default: false | |
| 18 | + | - name: "cpu" | |
| 19 | + | description: "Force software rendering (disable GPU)" | |
| 20 | + | default: false | |
| 21 | + | ||
| 22 | + | settings: | |
| 23 | + | enable_2d_canvas: | |
| 24 | + | type: "boolean" | |
| 25 | + | default: true | |
| 26 | + | description: "Enable hardware acceleration for 2D canvas" | |
| 27 | + | ||
| 28 | + | enable_webgl: | |
| 29 | + | type: "boolean" | |
| 30 | + | default: true | |
| 31 | + | description: "Enable WebGL for 3D content" | |
| 32 | + | ||
| 33 | + | draw_compositing_indicators: | |
| 34 | + | type: "boolean" | |
| 35 | + | default: false | |
| 36 | + | description: "Show GPU compositing borders (debug)" | |
| 37 | + | ||
| 38 | + | # CLI Interface | |
| 39 | + | cli: | |
| 40 | + | flags: | |
| 41 | + | - name: "--rendering-mode" | |
| 42 | + | type: "string" | |
| 43 | + | values: ["auto", "gpu", "cpu"] | |
| 44 | + | default: "auto" | |
| 45 | + | description: "Set rendering mode" | |
| 46 | + | ||
| 47 | + | - name: "--disable-gpu" | |
| 48 | + | type: "boolean" | |
| 49 | + | default: false | |
| 50 | + | description: "Shorthand for --rendering-mode=cpu" | |
| 51 | + | ||
| 52 | + | - name: "--force-gpu" | |
| 53 | + | type: "boolean" | |
| 54 | + | default: false | |
| 55 | + | description: "Shorthand for --rendering-mode=gpu" | |
| 56 | + | ||
| 57 | + | - name: "--debug-gpu" | |
| 58 | + | type: "boolean" | |
| 59 | + | default: false | |
| 60 | + | description: "Enable GPU compositing indicators" | |
| 61 | + | ||
| 62 | + | # Environment Variables | |
| 63 | + | environment: | |
| 64 | + | - name: "DUMBER_RENDERING_MODE" | |
| 65 | + | values: ["auto", "gpu", "cpu"] | |
| 66 | + | description: "Override rendering mode" | |
| 67 | + | ||
| 68 | + | - name: "WEBKIT_DISABLE_COMPOSITING_MODE" | |
| 69 | + | values: ["1", "0"] | |
| 70 | + | description: "WebKit native disable flag" | |
| 71 | + | ||
| 72 | + | # Status API | |
| 73 | + | status: | |
| 74 | + | endpoint: "/api/rendering/status" | |
| 75 | + | response: | |
| 76 | + | mode: | |
| 77 | + | type: "string" | |
| 78 | + | enum: ["auto", "gpu", "cpu"] | |
| 79 | + | ||
| 80 | + | gpu_active: | |
| 81 | + | type: "boolean" | |
| 82 | + | description: "Whether GPU acceleration is currently active" | |
| 83 | + | ||
| 84 | + | gpu_available: | |
| 85 | + | type: "boolean" | |
| 86 | + | description: "Whether GPU hardware is available" | |
| 87 | + | ||
| 88 | + | vulkan_enabled: | |
| 89 | + | type: "boolean" | |
| 90 | + | description: "Whether Vulkan renderer is active" | |
| 91 | + | ||
| 92 | + | errors: | |
| 93 | + | type: "array" | |
| 94 | + | items: | |
| 95 | + | type: "object" | |
| 96 | + | properties: | |
| 97 | + | code: "string" | |
| 98 | + | message: "string" | |
| 99 | + | fallback_used: "boolean" | |
| 100 | + | ||
| 101 | + | performance: | |
| 102 | + | type: "object" | |
| 103 | + | properties: | |
| 104 | + | fps: "number" | |
| 105 | + | cpu_usage_percent: "number" | |
| 106 | + | gpu_usage_percent: "number" | |
| 107 | + | memory_mb: "integer" | |
| 108 | + | ||
| 109 | + | # Validation Rules | |
| 110 | + | validation: | |
| 111 | + | startup: | |
| 112 | + | - check: "GPU availability if mode=gpu" | |
| 113 | + | error: "GPU acceleration requested but not available" | |
| 114 | + | action: "fail_startup" | |
| 115 | + | ||
| 116 | + | - check: "Vulkan support if GPU active" | |
| 117 | + | warning: "Vulkan not available, using OpenGL" | |
| 118 | + | action: "log_warning" | |
| 119 | + | ||
| 120 | + | - check: "Driver compatibility" | |
| 121 | + | error: "GPU driver incompatible" | |
| 122 | + | action: "fallback_to_cpu" | |
| 123 | + | ||
| 124 | + | runtime: | |
| 125 | + | - check: "GPU memory < 500MB" | |
| 126 | + | warning: "High GPU memory usage" | |
| 127 | + | action: "log_metric" | |
| 128 | + | ||
| 129 | + | - check: "FPS > 30" | |
| 130 | + | warning: "Low frame rate detected" | |
| 131 | + | action: "log_metric" | |
| 132 | + | ||
| 133 | + | # Error Codes | |
| 134 | + | errors: | |
| 135 | + | GPU_NOT_AVAILABLE: | |
| 136 | + | code: "E001" | |
| 137 | + | message: "GPU hardware not detected" | |
| 138 | + | severity: "error" | |
| 139 | + | ||
| 140 | + | VULKAN_INIT_FAILED: | |
| 141 | + | code: "E002" | |
| 142 | + | message: "Failed to initialize Vulkan renderer" | |
| 143 | + | severity: "warning" | |
| 144 | + | ||
| 145 | + | DRIVER_INCOMPATIBLE: | |
| 146 | + | code: "E003" | |
| 147 | + | message: "GPU driver version incompatible" | |
| 148 | + | severity: "error" | |
| 149 | + | ||
| 150 | + | MEMORY_EXCEEDED: | |
| 151 | + | code: "W001" | |
| 152 | + | message: "GPU memory usage exceeded threshold" | |
| 153 | + | severity: "warning"⏎̸ | |
| @@ -0,0 +1,153 @@ | |||||
| 1 | + | # Rendering Configuration Contract | |||
| 2 | + | # Defines the interface for GPU/CPU rendering configuration | |||
| 3 | + | ||||
| 4 | + | version: "1.0.0" | |||
| 5 | + | component: "RenderingConfig" | |||
| 6 | + | ||||
| 7 | + | # Configuration Interface | |||
| 8 | + | config: | |||
| 9 | + | rendering_mode: | |||
| 10 | + | type: "enum" | |||
| 11 | + | values: | |||
| 12 | + | - name: "auto" | |||
| 13 | + | description: "Automatically detect GPU availability and use if present" | |||
| 14 | + | default: true | |||
| 15 | + | - name: "gpu" | |||
| 16 | + | description: "Force GPU acceleration (fail if unavailable)" | |||
| 17 | + | default: false | |||
| 18 | + | - name: "cpu" | |||
| 19 | + | description: "Force software rendering (disable GPU)" | |||
| 20 | + | default: false | |||
| 21 | + | ||||
| 22 | + | settings: | |||
| 23 | + | enable_2d_canvas: | |||
| 24 | + | type: "boolean" | |||
| 25 | + | default: true | |||
| 26 | + | description: "Enable hardware acceleration for 2D canvas" | |||
| 27 | + | ||||
| 28 | + | enable_webgl: | |||
| 29 | + | type: "boolean" | |||
| 30 | + | default: true | |||
| 31 | + | description: "Enable WebGL for 3D content" | |||
| 32 | + | ||||
| 33 | + | draw_compositing_indicators: | |||
| 34 | + | type: "boolean" | |||
| 35 | + | default: false | |||
| 36 | + | description: "Show GPU compositing borders (debug)" | |||
| 37 | + | ||||
| 38 | + | # CLI Interface | |||
| 39 | + | cli: | |||
| 40 | + | flags: | |||
| 41 | + | - name: "--rendering-mode" | |||
| 42 | + | type: "string" | |||
| 43 | + | values: ["auto", "gpu", "cpu"] | |||
| 44 | + | default: "auto" | |||
| 45 | + | description: "Set rendering mode" | |||
| 46 | + | ||||
| 47 | + | - name: "--disable-gpu" | |||
| 48 | + | type: "boolean" | |||
| 49 | + | default: false | |||
| 50 | + | description: "Shorthand for --rendering-mode=cpu" | |||
| 51 | + | ||||
| 52 | + | - name: "--force-gpu" | |||
| 53 | + | type: "boolean" | |||
| 54 | + | default: false | |||
| 55 | + | description: "Shorthand for --rendering-mode=gpu" | |||
| 56 | + | ||||
| 57 | + | - name: "--debug-gpu" | |||
| 58 | + | type: "boolean" | |||
| 59 | + | default: false | |||
| 60 | + | description: "Enable GPU compositing indicators" | |||
| 61 | + | ||||
| 62 | + | # Environment Variables | |||
| 63 | + | environment: | |||
| 64 | + | - name: "DUMBER_RENDERING_MODE" | |||
| 65 | + | values: ["auto", "gpu", "cpu"] | |||
| 66 | + | description: "Override rendering mode" | |||
| 67 | + | ||||
| 68 | + | - name: "WEBKIT_DISABLE_COMPOSITING_MODE" | |||
| 69 | + | values: ["1", "0"] | |||
| 70 | + | description: "WebKit native disable flag" | |||
| 71 | + | ||||
| 72 | + | # Status API | |||
| 73 | + | status: | |||
| 74 | + | endpoint: "/api/rendering/status" | |||
| 75 | + | response: | |||
| 76 | + | mode: | |||
| 77 | + | type: "string" | |||
| 78 | + | enum: ["auto", "gpu", "cpu"] | |||
| 79 | + | ||||
| 80 | + | gpu_active: | |||
| 81 | + | type: "boolean" | |||
| 82 | + | description: "Whether GPU acceleration is currently active" | |||
| 83 | + | ||||
| 84 | + | gpu_available: | |||
| 85 | + | type: "boolean" | |||
| 86 | + | description: "Whether GPU hardware is available" | |||
| 87 | + | ||||
| 88 | + | vulkan_enabled: | |||
| 89 | + | type: "boolean" | |||
| 90 | + | description: "Whether Vulkan renderer is active" | |||
| 91 | + | ||||
| 92 | + | errors: | |||
| 93 | + | type: "array" | |||
| 94 | + | items: | |||
| 95 | + | type: "object" | |||
| 96 | + | properties: | |||
| 97 | + | code: "string" | |||
| 98 | + | message: "string" | |||
| 99 | + | fallback_used: "boolean" | |||
| 100 | + | ||||
| 101 | + | performance: | |||
| 102 | + | type: "object" | |||
| 103 | + | properties: | |||
| 104 | + | fps: "number" | |||
| 105 | + | cpu_usage_percent: "number" | |||
| 106 | + | gpu_usage_percent: "number" | |||
| 107 | + | memory_mb: "integer" | |||
| 108 | + | ||||
| 109 | + | # Validation Rules | |||
| 110 | + | validation: | |||
| 111 | + | startup: | |||
| 112 | + | - check: "GPU availability if mode=gpu" | |||
| 113 | + | error: "GPU acceleration requested but not available" | |||
| 114 | + | action: "fail_startup" | |||
| 115 | + | ||||
| 116 | + | - check: "Vulkan support if GPU active" | |||
| 117 | + | warning: "Vulkan not available, using OpenGL" | |||
| 118 | + | action: "log_warning" | |||
| 119 | + | ||||
| 120 | + | - check: "Driver compatibility" | |||
| 121 | + | error: "GPU driver incompatible" | |||
| 122 | + | action: "fallback_to_cpu" | |||
| 123 | + | ||||
| 124 | + | runtime: | |||
| 125 | + | - check: "GPU memory < 500MB" | |||
| 126 | + | warning: "High GPU memory usage" | |||
| 127 | + | action: "log_metric" | |||
| 128 | + | ||||
| 129 | + | - check: "FPS > 30" | |||
| 130 | + | warning: "Low frame rate detected" | |||
| 131 | + | action: "log_metric" | |||
| 132 | + | ||||
| 133 | + | # Error Codes | |||
| 134 | + | errors: | |||
| 135 | + | GPU_NOT_AVAILABLE: | |||
| 136 | + | code: "E001" | |||
| 137 | + | message: "GPU hardware not detected" | |||
| 138 | + | severity: "error" | |||
| 139 | + | ||||
| 140 | + | VULKAN_INIT_FAILED: | |||
| 141 | + | code: "E002" | |||
| 142 | + | message: "Failed to initialize Vulkan renderer" | |||
| 143 | + | severity: "warning" | |||
| 144 | + | ||||
| 145 | + | DRIVER_INCOMPATIBLE: | |||
| 146 | + | code: "E003" | |||
| 147 | + | message: "GPU driver version incompatible" | |||
| 148 | + | severity: "error" | |||
| 149 | + | ||||
| 150 | + | MEMORY_EXCEEDED: | |||
| 151 | + | code: "W001" | |||
| 152 | + | message: "GPU memory usage exceeded threshold" | |||
| 153 | + | severity: "warning"⏎̸ | |||
A specs/004-migrate-to-webkitgtk/contracts/webkit-api.yaml +111 -0
| @@ -0,0 +1,111 @@ | |||
| 1 | + | # WebKit API Migration Contract | |
| 2 | + | # Defines the interface mapping between WebKit2GTK 4.0 and WebKitGTK 6.0 | |
| 3 | + | ||
| 4 | + | version: "1.0.0" | |
| 5 | + | migration: | |
| 6 | + | from: "webkit2gtk-4.0" | |
| 7 | + | to: "webkitgtk-6.0" | |
| 8 | + | ||
| 9 | + | # Core WebView Management | |
| 10 | + | webview: | |
| 11 | + | creation: | |
| 12 | + | old: | |
| 13 | + | function: "webkit_web_view_new_with_context" | |
| 14 | + | params: ["WebKitWebContext*"] | |
| 15 | + | returns: "GtkWidget*" | |
| 16 | + | new: | |
| 17 | + | function: "g_object_new" | |
| 18 | + | params: ["WEBKIT_TYPE_WEB_VIEW", "properties..."] | |
| 19 | + | returns: "GtkWidget*" | |
| 20 | + | ||
| 21 | + | settings: | |
| 22 | + | hardware_acceleration: | |
| 23 | + | function: "webkit_settings_set_hardware_acceleration_policy" | |
| 24 | + | params: ["WebKitSettings*", "WebKitHardwareAccelerationPolicy"] | |
| 25 | + | values: | |
| 26 | + | - WEBKIT_HARDWARE_ACCELERATION_POLICY_ON_DEMAND | |
| 27 | + | - WEBKIT_HARDWARE_ACCELERATION_POLICY_ALWAYS | |
| 28 | + | - WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER | |
| 29 | + | ||
| 30 | + | canvas_acceleration: | |
| 31 | + | function: "webkit_settings_set_enable_2d_canvas_acceleration" | |
| 32 | + | params: ["WebKitSettings*", "gboolean"] | |
| 33 | + | ||
| 34 | + | webgl: | |
| 35 | + | function: "webkit_settings_set_enable_webgl" | |
| 36 | + | params: ["WebKitSettings*", "gboolean"] | |
| 37 | + | ||
| 38 | + | # GTK4 Widget Changes | |
| 39 | + | gtk: | |
| 40 | + | window: | |
| 41 | + | create: | |
| 42 | + | old: "gtk_window_new(GTK_WINDOW_TOPLEVEL)" | |
| 43 | + | new: "gtk_window_new()" | |
| 44 | + | ||
| 45 | + | add_child: | |
| 46 | + | old: "gtk_container_add(container, widget)" | |
| 47 | + | new: "gtk_window_set_child(window, widget)" | |
| 48 | + | ||
| 49 | + | destroy: | |
| 50 | + | old: "gtk_widget_destroy(widget)" | |
| 51 | + | new: "gtk_window_destroy(GTK_WINDOW(widget))" | |
| 52 | + | ||
| 53 | + | events: | |
| 54 | + | key_press: | |
| 55 | + | old: | |
| 56 | + | signal: "key-press-event" | |
| 57 | + | callback: "gboolean (*)(GtkWidget*, GdkEventKey*, gpointer)" | |
| 58 | + | new: | |
| 59 | + | controller: "gtk_event_controller_key_new()" | |
| 60 | + | signal: "key-pressed" | |
| 61 | + | callback: "gboolean (*)(GtkEventControllerKey*, guint, guint, GdkModifierType, gpointer)" | |
| 62 | + | ||
| 63 | + | button_press: | |
| 64 | + | old: | |
| 65 | + | signal: "button-press-event" | |
| 66 | + | callback: "gboolean (*)(GtkWidget*, GdkEventButton*, gpointer)" | |
| 67 | + | new: | |
| 68 | + | controller: "gtk_gesture_click_new()" | |
| 69 | + | signal: "pressed" | |
| 70 | + | callback: "void (*)(GtkGestureClick*, gint, gdouble, gdouble, gpointer)" | |
| 71 | + | ||
| 72 | + | # Network Session (replaces WebContext) | |
| 73 | + | network: | |
| 74 | + | session: | |
| 75 | + | old: | |
| 76 | + | type: "WebKitWebContext" | |
| 77 | + | data_manager: "webkit_website_data_manager_new" | |
| 78 | + | new: | |
| 79 | + | type: "WebKitNetworkSession" | |
| 80 | + | create: "webkit_network_session_new" | |
| 81 | + | ephemeral: "webkit_network_session_new_ephemeral" | |
| 82 | + | default: "webkit_network_session_get_default" | |
| 83 | + | ||
| 84 | + | # Build Configuration | |
| 85 | + | build: | |
| 86 | + | pkg_config: | |
| 87 | + | old: | |
| 88 | + | - "webkit2gtk-4.0" | |
| 89 | + | - "gtk+-3.0" | |
| 90 | + | - "javascriptcoregtk-4.0" | |
| 91 | + | new: | |
| 92 | + | - "webkitgtk-6.0" | |
| 93 | + | - "gtk4" | |
| 94 | + | - "javascriptcoregtk-6.0" | |
| 95 | + | ||
| 96 | + | headers: | |
| 97 | + | old: | |
| 98 | + | - "#include <webkit2/webkit2.h>" | |
| 99 | + | - "#include <gtk/gtk.h>" | |
| 100 | + | new: | |
| 101 | + | - "#include <webkit/webkit.h>" | |
| 102 | + | - "#include <gtk/gtk.h>" | |
| 103 | + | ||
| 104 | + | # Compatibility Requirements | |
| 105 | + | compatibility: | |
| 106 | + | minimum_gtk: "4.0" | |
| 107 | + | minimum_webkit: "2.40" # First version with webkitgtk-6.0 | |
| 108 | + | required_features: | |
| 109 | + | - hardware_acceleration_policy | |
| 110 | + | - network_session | |
| 111 | + | - sandbox_always_enabled⏎̸ | |
| @@ -0,0 +1,111 @@ | |||||
| 1 | + | # WebKit API Migration Contract | |||
| 2 | + | # Defines the interface mapping between WebKit2GTK 4.0 and WebKitGTK 6.0 | |||
| 3 | + | ||||
| 4 | + | version: "1.0.0" | |||
| 5 | + | migration: | |||
| 6 | + | from: "webkit2gtk-4.0" | |||
| 7 | + | to: "webkitgtk-6.0" | |||
| 8 | + | ||||
| 9 | + | # Core WebView Management | |||
| 10 | + | webview: | |||
| 11 | + | creation: | |||
| 12 | + | old: | |||
| 13 | + | function: "webkit_web_view_new_with_context" | |||
| 14 | + | params: ["WebKitWebContext*"] | |||
| 15 | + | returns: "GtkWidget*" | |||
| 16 | + | new: | |||
| 17 | + | function: "g_object_new" | |||
| 18 | + | params: ["WEBKIT_TYPE_WEB_VIEW", "properties..."] | |||
| 19 | + | returns: "GtkWidget*" | |||
| 20 | + | ||||
| 21 | + | settings: | |||
| 22 | + | hardware_acceleration: | |||
| 23 | + | function: "webkit_settings_set_hardware_acceleration_policy" | |||
| 24 | + | params: ["WebKitSettings*", "WebKitHardwareAccelerationPolicy"] | |||
| 25 | + | values: | |||
| 26 | + | - WEBKIT_HARDWARE_ACCELERATION_POLICY_ON_DEMAND | |||
| 27 | + | - WEBKIT_HARDWARE_ACCELERATION_POLICY_ALWAYS | |||
| 28 | + | - WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER | |||
| 29 | + | ||||
| 30 | + | canvas_acceleration: | |||
| 31 | + | function: "webkit_settings_set_enable_2d_canvas_acceleration" | |||
| 32 | + | params: ["WebKitSettings*", "gboolean"] | |||
| 33 | + | ||||
| 34 | + | webgl: | |||
| 35 | + | function: "webkit_settings_set_enable_webgl" | |||
| 36 | + | params: ["WebKitSettings*", "gboolean"] | |||
| 37 | + | ||||
| 38 | + | # GTK4 Widget Changes | |||
| 39 | + | gtk: | |||
| 40 | + | window: | |||
| 41 | + | create: | |||
| 42 | + | old: "gtk_window_new(GTK_WINDOW_TOPLEVEL)" | |||
| 43 | + | new: "gtk_window_new()" | |||
| 44 | + | ||||
| 45 | + | add_child: | |||
| 46 | + | old: "gtk_container_add(container, widget)" | |||
| 47 | + | new: "gtk_window_set_child(window, widget)" | |||
| 48 | + | ||||
| 49 | + | destroy: | |||
| 50 | + | old: "gtk_widget_destroy(widget)" | |||
| 51 | + | new: "gtk_window_destroy(GTK_WINDOW(widget))" | |||
| 52 | + | ||||
| 53 | + | events: | |||
| 54 | + | key_press: | |||
| 55 | + | old: | |||
| 56 | + | signal: "key-press-event" | |||
| 57 | + | callback: "gboolean (*)(GtkWidget*, GdkEventKey*, gpointer)" | |||
| 58 | + | new: | |||
| 59 | + | controller: "gtk_event_controller_key_new()" | |||
| 60 | + | signal: "key-pressed" | |||
| 61 | + | callback: "gboolean (*)(GtkEventControllerKey*, guint, guint, GdkModifierType, gpointer)" | |||
| 62 | + | ||||
| 63 | + | button_press: | |||
| 64 | + | old: | |||
| 65 | + | signal: "button-press-event" | |||
| 66 | + | callback: "gboolean (*)(GtkWidget*, GdkEventButton*, gpointer)" | |||
| 67 | + | new: | |||
| 68 | + | controller: "gtk_gesture_click_new()" | |||
| 69 | + | signal: "pressed" | |||
| 70 | + | callback: "void (*)(GtkGestureClick*, gint, gdouble, gdouble, gpointer)" | |||
| 71 | + | ||||
| 72 | + | # Network Session (replaces WebContext) | |||
| 73 | + | network: | |||
| 74 | + | session: | |||
| 75 | + | old: | |||
| 76 | + | type: "WebKitWebContext" | |||
| 77 | + | data_manager: "webkit_website_data_manager_new" | |||
| 78 | + | new: | |||
| 79 | + | type: "WebKitNetworkSession" | |||
| 80 | + | create: "webkit_network_session_new" | |||
| 81 | + | ephemeral: "webkit_network_session_new_ephemeral" | |||
| 82 | + | default: "webkit_network_session_get_default" | |||
| 83 | + | ||||
| 84 | + | # Build Configuration | |||
| 85 | + | build: | |||
| 86 | + | pkg_config: | |||
| 87 | + | old: | |||
| 88 | + | - "webkit2gtk-4.0" | |||
| 89 | + | - "gtk+-3.0" | |||
| 90 | + | - "javascriptcoregtk-4.0" | |||
| 91 | + | new: | |||
| 92 | + | - "webkitgtk-6.0" | |||
| 93 | + | - "gtk4" | |||
| 94 | + | - "javascriptcoregtk-6.0" | |||
| 95 | + | ||||
| 96 | + | headers: | |||
| 97 | + | old: | |||
| 98 | + | - "#include <webkit2/webkit2.h>" | |||
| 99 | + | - "#include <gtk/gtk.h>" | |||
| 100 | + | new: | |||
| 101 | + | - "#include <webkit/webkit.h>" | |||
| 102 | + | - "#include <gtk/gtk.h>" | |||
| 103 | + | ||||
| 104 | + | # Compatibility Requirements | |||
| 105 | + | compatibility: | |||
| 106 | + | minimum_gtk: "4.0" | |||
| 107 | + | minimum_webkit: "2.40" # First version with webkitgtk-6.0 | |||
| 108 | + | required_features: | |||
| 109 | + | - hardware_acceleration_policy | |||
| 110 | + | - network_session | |||
| 111 | + | - sandbox_always_enabled⏎̸ | |||
A specs/004-migrate-to-webkitgtk/data-model.md +170 -0
| @@ -0,0 +1,170 @@ | |||
| 1 | + | # Data Model: WebKitGTK 6.0 Migration | |
| 2 | + | ||
| 3 | + | ## Configuration Model | |
| 4 | + | ||
| 5 | + | ### RenderingConfig | |
| 6 | + | ```go | |
| 7 | + | type RenderingMode string | |
| 8 | + | ||
| 9 | + | const ( | |
| 10 | + | RenderingModeAuto RenderingMode = "auto" // Detect GPU availability | |
| 11 | + | RenderingModeGPU RenderingMode = "gpu" // Force GPU acceleration | |
| 12 | + | RenderingModeCPU RenderingMode = "cpu" // Force software rendering | |
| 13 | + | ) | |
| 14 | + | ||
| 15 | + | type RenderingConfig struct { | |
| 16 | + | Mode RenderingMode // Default: auto | |
| 17 | + | Enable2DCanvas bool // Default: true | |
| 18 | + | EnableWebGL bool // Default: true | |
| 19 | + | DrawCompositingIndicators bool // Default: false (debug only) | |
| 20 | + | } | |
| 21 | + | ``` | |
| 22 | + | ||
| 23 | + | ### Updated WebKit Config | |
| 24 | + | ```go | |
| 25 | + | type Config struct { | |
| 26 | + | // Existing fields | |
| 27 | + | InitialURL string | |
| 28 | + | UserAgent string | |
| 29 | + | EnableDeveloperExtras bool | |
| 30 | + | ZoomDefault float64 | |
| 31 | + | DataDir string | |
| 32 | + | CacheDir string | |
| 33 | + | DefaultSansFont string | |
| 34 | + | DefaultSerifFont string | |
| 35 | + | DefaultMonospaceFont string | |
| 36 | + | DefaultFontSize int | |
| 37 | + | ||
| 38 | + | // New GPU rendering fields | |
| 39 | + | Rendering RenderingConfig | |
| 40 | + | } | |
| 41 | + | ``` | |
| 42 | + | ||
| 43 | + | ## API Mapping Model | |
| 44 | + | ||
| 45 | + | ### Core WebView APIs | |
| 46 | + | | Component | WebKit2GTK 4.0 | WebKitGTK 6.0 | Status | | |
| 47 | + | |-----------|---------------|---------------|---------| | |
| 48 | + | | WebView Creation | `webkit_web_view_new_with_context()` | `g_object_new(WEBKIT_TYPE_WEB_VIEW, ...)` | Breaking | | |
| 49 | + | | Context | `WebKitWebContext` | `WebKitNetworkSession` | Breaking | | |
| 50 | + | | Settings | `WebKitSettings` | `WebKitSettings` (expanded) | Compatible | | |
| 51 | + | | UserContentManager | Same | Same | Compatible | | |
| 52 | + | | JavaScript | `webkit_web_view_run_javascript()` | Same (async only) | Compatible | | |
| 53 | + | ||
| 54 | + | ### GTK Widget APIs | |
| 55 | + | | Component | GTK3 | GTK4 | Status | | |
| 56 | + | |-----------|------|------|---------| | |
| 57 | + | | Window | `gtk_window_new(GTK_WINDOW_TOPLEVEL)` | `gtk_window_new()` | Breaking | | |
| 58 | + | | Container | `gtk_container_add()` | `gtk_window_set_child()` | Breaking | | |
| 59 | + | | Events | `GdkEventKey*` | `GtkEventController` | Breaking | | |
| 60 | + | | Show/Hide | `gtk_widget_show()` | Same | Compatible | | |
| 61 | + | | Destroy | `gtk_widget_destroy()` | `gtk_window_destroy()` | Breaking | | |
| 62 | + | ||
| 63 | + | ### Hardware Acceleration APIs | |
| 64 | + | ```go | |
| 65 | + | // New acceleration policy enum | |
| 66 | + | type HardwareAccelerationPolicy int | |
| 67 | + | ||
| 68 | + | const ( | |
| 69 | + | PolicyOnDemand HardwareAccelerationPolicy = iota // Auto-detect | |
| 70 | + | PolicyAlways // Force GPU | |
| 71 | + | PolicyNever // Force CPU | |
| 72 | + | ) | |
| 73 | + | ||
| 74 | + | // Mapping to WebKit constants | |
| 75 | + | var policyMap = map[RenderingMode]HardwareAccelerationPolicy{ | |
| 76 | + | RenderingModeAuto: PolicyOnDemand, | |
| 77 | + | RenderingModeGPU: PolicyAlways, | |
| 78 | + | RenderingModeCPU: PolicyNever, | |
| 79 | + | } | |
| 80 | + | ``` | |
| 81 | + | ||
| 82 | + | ## Migration States | |
| 83 | + | ||
| 84 | + | ### File Migration Status | |
| 85 | + | ```go | |
| 86 | + | type MigrationStatus string | |
| 87 | + | ||
| 88 | + | const ( | |
| 89 | + | StatusPending MigrationStatus = "pending" | |
| 90 | + | StatusInProgress MigrationStatus = "in_progress" | |
| 91 | + | StatusComplete MigrationStatus = "complete" | |
| 92 | + | StatusTested MigrationStatus = "tested" | |
| 93 | + | ) | |
| 94 | + | ||
| 95 | + | type FileMigration struct { | |
| 96 | + | Path string | |
| 97 | + | Status MigrationStatus | |
| 98 | + | Changes []string // List of API changes made | |
| 99 | + | Tests []string // Associated test files | |
| 100 | + | } | |
| 101 | + | ``` | |
| 102 | + | ||
| 103 | + | ## Error Handling Model | |
| 104 | + | ||
| 105 | + | ### GPU Fallback Errors | |
| 106 | + | ```go | |
| 107 | + | type GPUError struct { | |
| 108 | + | Type string // "initialization", "driver", "memory" | |
| 109 | + | Message string | |
| 110 | + | Fallback bool // Whether CPU fallback was successful | |
| 111 | + | } | |
| 112 | + | ||
| 113 | + | type RenderingStatus struct { | |
| 114 | + | Mode RenderingMode | |
| 115 | + | GPUActive bool | |
| 116 | + | Errors []GPUError | |
| 117 | + | Performance struct { | |
| 118 | + | FPS float64 | |
| 119 | + | CPUUsage float64 | |
| 120 | + | GPUUsage float64 | |
| 121 | + | MemoryMB int | |
| 122 | + | } | |
| 123 | + | } | |
| 124 | + | ``` | |
| 125 | + | ||
| 126 | + | ## Build Configuration | |
| 127 | + | ||
| 128 | + | ### Package Config Changes | |
| 129 | + | ```makefile | |
| 130 | + | # Old build flags | |
| 131 | + | WEBKIT_PKG = webkit2gtk-4.0 | |
| 132 | + | GTK_PKG = gtk+-3.0 | |
| 133 | + | JS_PKG = javascriptcoregtk-4.0 | |
| 134 | + | ||
| 135 | + | # New build flags | |
| 136 | + | WEBKIT_PKG = webkitgtk-6.0 | |
| 137 | + | GTK_PKG = gtk4 | |
| 138 | + | JS_PKG = javascriptcoregtk-6.0 | |
| 139 | + | ``` | |
| 140 | + | ||
| 141 | + | ## Validation Rules | |
| 142 | + | ||
| 143 | + | ### Rendering Mode Validation | |
| 144 | + | - If GPU forced but unavailable → Error with suggestion to use auto | |
| 145 | + | - If CPU forced → Warn about performance impact | |
| 146 | + | - If auto → Log detected mode at startup | |
| 147 | + | ||
| 148 | + | ### Performance Thresholds | |
| 149 | + | - Startup time must remain < 500ms | |
| 150 | + | - GPU memory usage < 500MB | |
| 151 | + | - CPU usage reduction > 20% when GPU active | |
| 152 | + | - FPS improvement > 30% for animations | |
| 153 | + | ||
| 154 | + | ## State Transitions | |
| 155 | + | ||
| 156 | + | ### Rendering Mode State Machine | |
| 157 | + | ``` | |
| 158 | + | ┌─────┐ Check GPU ┌──────┐ Success ┌─────┐ | |
| 159 | + | │Auto │────────────→│Detect│────────→│GPU │ | |
| 160 | + | └─────┘ └──────┘ └─────┘ | |
| 161 | + | │ Fail │ Error | |
| 162 | + | ↓ ↓ | |
| 163 | + | ┌─────┐ ┌─────┐ | |
| 164 | + | │CPU │←─────────│Retry│ | |
| 165 | + | └─────┘ Fallback └─────┘ | |
| 166 | + | ``` | |
| 167 | + | ||
| 168 | + | ## Data Persistence | |
| 169 | + | ||
| 170 | + | No changes to existing SQLite schema required. Rendering mode stored in config file only.⏎̸ | |
| @@ -0,0 +1,170 @@ | |||||
| 1 | + | # Data Model: WebKitGTK 6.0 Migration | |||
| 2 | + | ||||
| 3 | + | ## Configuration Model | |||
| 4 | + | ||||
| 5 | + | ### RenderingConfig | |||
| 6 | + | ```go | |||
| 7 | + | type RenderingMode string | |||
| 8 | + | ||||
| 9 | + | const ( | |||
| 10 | + | RenderingModeAuto RenderingMode = "auto" // Detect GPU availability | |||
| 11 | + | RenderingModeGPU RenderingMode = "gpu" // Force GPU acceleration | |||
| 12 | + | RenderingModeCPU RenderingMode = "cpu" // Force software rendering | |||
| 13 | + | ) | |||
| 14 | + | ||||
| 15 | + | type RenderingConfig struct { | |||
| 16 | + | Mode RenderingMode // Default: auto | |||
| 17 | + | Enable2DCanvas bool // Default: true | |||
| 18 | + | EnableWebGL bool // Default: true | |||
| 19 | + | DrawCompositingIndicators bool // Default: false (debug only) | |||
| 20 | + | } | |||
| 21 | + | ``` | |||
| 22 | + | ||||
| 23 | + | ### Updated WebKit Config | |||
| 24 | + | ```go | |||
| 25 | + | type Config struct { | |||
| 26 | + | // Existing fields | |||
| 27 | + | InitialURL string | |||
| 28 | + | UserAgent string | |||
| 29 | + | EnableDeveloperExtras bool | |||
| 30 | + | ZoomDefault float64 | |||
| 31 | + | DataDir string | |||
| 32 | + | CacheDir string | |||
| 33 | + | DefaultSansFont string | |||
| 34 | + | DefaultSerifFont string | |||
| 35 | + | DefaultMonospaceFont string | |||
| 36 | + | DefaultFontSize int | |||
| 37 | + | ||||
| 38 | + | // New GPU rendering fields | |||
| 39 | + | Rendering RenderingConfig | |||
| 40 | + | } | |||
| 41 | + | ``` | |||
| 42 | + | ||||
| 43 | + | ## API Mapping Model | |||
| 44 | + | ||||
| 45 | + | ### Core WebView APIs | |||
| 46 | + | | Component | WebKit2GTK 4.0 | WebKitGTK 6.0 | Status | | |||
| 47 | + | |-----------|---------------|---------------|---------| | |||
| 48 | + | | WebView Creation | `webkit_web_view_new_with_context()` | `g_object_new(WEBKIT_TYPE_WEB_VIEW, ...)` | Breaking | | |||
| 49 | + | | Context | `WebKitWebContext` | `WebKitNetworkSession` | Breaking | | |||
| 50 | + | | Settings | `WebKitSettings` | `WebKitSettings` (expanded) | Compatible | | |||
| 51 | + | | UserContentManager | Same | Same | Compatible | | |||
| 52 | + | | JavaScript | `webkit_web_view_run_javascript()` | Same (async only) | Compatible | | |||
| 53 | + | ||||
| 54 | + | ### GTK Widget APIs | |||
| 55 | + | | Component | GTK3 | GTK4 | Status | | |||
| 56 | + | |-----------|------|------|---------| | |||
| 57 | + | | Window | `gtk_window_new(GTK_WINDOW_TOPLEVEL)` | `gtk_window_new()` | Breaking | | |||
| 58 | + | | Container | `gtk_container_add()` | `gtk_window_set_child()` | Breaking | | |||
| 59 | + | | Events | `GdkEventKey*` | `GtkEventController` | Breaking | | |||
| 60 | + | | Show/Hide | `gtk_widget_show()` | Same | Compatible | | |||
| 61 | + | | Destroy | `gtk_widget_destroy()` | `gtk_window_destroy()` | Breaking | | |||
| 62 | + | ||||
| 63 | + | ### Hardware Acceleration APIs | |||
| 64 | + | ```go | |||
| 65 | + | // New acceleration policy enum | |||
| 66 | + | type HardwareAccelerationPolicy int | |||
| 67 | + | ||||
| 68 | + | const ( | |||
| 69 | + | PolicyOnDemand HardwareAccelerationPolicy = iota // Auto-detect | |||
| 70 | + | PolicyAlways // Force GPU | |||
| 71 | + | PolicyNever // Force CPU | |||
| 72 | + | ) | |||
| 73 | + | ||||
| 74 | + | // Mapping to WebKit constants | |||
| 75 | + | var policyMap = map[RenderingMode]HardwareAccelerationPolicy{ | |||
| 76 | + | RenderingModeAuto: PolicyOnDemand, | |||
| 77 | + | RenderingModeGPU: PolicyAlways, | |||
| 78 | + | RenderingModeCPU: PolicyNever, | |||
| 79 | + | } | |||
| 80 | + | ``` | |||
| 81 | + | ||||
| 82 | + | ## Migration States | |||
| 83 | + | ||||
| 84 | + | ### File Migration Status | |||
| 85 | + | ```go | |||
| 86 | + | type MigrationStatus string | |||
| 87 | + | ||||
| 88 | + | const ( | |||
| 89 | + | StatusPending MigrationStatus = "pending" | |||
| 90 | + | StatusInProgress MigrationStatus = "in_progress" | |||
| 91 | + | StatusComplete MigrationStatus = "complete" | |||
| 92 | + | StatusTested MigrationStatus = "tested" | |||
| 93 | + | ) | |||
| 94 | + | ||||
| 95 | + | type FileMigration struct { | |||
| 96 | + | Path string | |||
| 97 | + | Status MigrationStatus | |||
| 98 | + | Changes []string // List of API changes made | |||
| 99 | + | Tests []string // Associated test files | |||
| 100 | + | } | |||
| 101 | + | ``` | |||
| 102 | + | ||||
| 103 | + | ## Error Handling Model | |||
| 104 | + | ||||
| 105 | + | ### GPU Fallback Errors | |||
| 106 | + | ```go | |||
| 107 | + | type GPUError struct { | |||
| 108 | + | Type string // "initialization", "driver", "memory" | |||
| 109 | + | Message string | |||
| 110 | + | Fallback bool // Whether CPU fallback was successful | |||
| 111 | + | } | |||
| 112 | + | ||||
| 113 | + | type RenderingStatus struct { | |||
| 114 | + | Mode RenderingMode | |||
| 115 | + | GPUActive bool | |||
| 116 | + | Errors []GPUError | |||
| 117 | + | Performance struct { | |||
| 118 | + | FPS float64 | |||
| 119 | + | CPUUsage float64 | |||
| 120 | + | GPUUsage float64 | |||
| 121 | + | MemoryMB int | |||
| 122 | + | } | |||
| 123 | + | } | |||
| 124 | + | ``` | |||
| 125 | + | ||||
| 126 | + | ## Build Configuration | |||
| 127 | + | ||||
| 128 | + | ### Package Config Changes | |||
| 129 | + | ```makefile | |||
| 130 | + | # Old build flags | |||
| 131 | + | WEBKIT_PKG = webkit2gtk-4.0 | |||
| 132 | + | GTK_PKG = gtk+-3.0 | |||
| 133 | + | JS_PKG = javascriptcoregtk-4.0 | |||
| 134 | + | ||||
| 135 | + | # New build flags | |||
| 136 | + | WEBKIT_PKG = webkitgtk-6.0 | |||
| 137 | + | GTK_PKG = gtk4 | |||
| 138 | + | JS_PKG = javascriptcoregtk-6.0 | |||
| 139 | + | ``` | |||
| 140 | + | ||||
| 141 | + | ## Validation Rules | |||
| 142 | + | ||||
| 143 | + | ### Rendering Mode Validation | |||
| 144 | + | - If GPU forced but unavailable → Error with suggestion to use auto | |||
| 145 | + | - If CPU forced → Warn about performance impact | |||
| 146 | + | - If auto → Log detected mode at startup | |||
| 147 | + | ||||
| 148 | + | ### Performance Thresholds | |||
| 149 | + | - Startup time must remain < 500ms | |||
| 150 | + | - GPU memory usage < 500MB | |||
| 151 | + | - CPU usage reduction > 20% when GPU active | |||
| 152 | + | - FPS improvement > 30% for animations | |||
| 153 | + | ||||
| 154 | + | ## State Transitions | |||
| 155 | + | ||||
| 156 | + | ### Rendering Mode State Machine | |||
| 157 | + | ``` | |||
| 158 | + | ┌─────┐ Check GPU ┌──────┐ Success ┌─────┐ | |||
| 159 | + | │Auto │────────────→│Detect│────────→│GPU │ | |||
| 160 | + | └─────┘ └──────┘ └─────┘ | |||
| 161 | + | │ Fail │ Error | |||
| 162 | + | ↓ ↓ | |||
| 163 | + | ┌─────┐ ┌─────┐ | |||
| 164 | + | │CPU │←─────────│Retry│ | |||
| 165 | + | └─────┘ Fallback └─────┘ | |||
| 166 | + | ``` | |||
| 167 | + | ||||
| 168 | + | ## Data Persistence | |||
| 169 | + | ||||
| 170 | + | No changes to existing SQLite schema required. Rendering mode stored in config file only.⏎̸ | |||
A specs/004-migrate-to-webkitgtk/plan.md +251 -0
| @@ -0,0 +1,251 @@ | |||
| 1 | + | # Implementation Plan: Migrate to WebKitGTK 6.0 | |
| 2 | + | ||
| 3 | + | **Branch**: `004-migrate-to-webkitgtk` | **Date**: 2025-01-21 | **Spec**: [spec.md](./spec.md) | |
| 4 | + | **Input**: Feature specification from `/specs/004-migrate-to-webkitgtk/spec.md` | |
| 5 | + | ||
| 6 | + | ## Execution Flow (/plan command scope) | |
| 7 | + | ``` | |
| 8 | + | 1. Load feature spec from Input path | |
| 9 | + | → Successfully loaded spec for WebKitGTK 6.0 migration | |
| 10 | + | 2. Fill Technical Context (scan for NEEDS CLARIFICATION) | |
| 11 | + | → Project Type: Single (Go browser application) | |
| 12 | + | → Structure Decision: Option 1 (single project) | |
| 13 | + | → Open clarifications remain in spec (FR-008, FR-011, FR-012) | |
| 14 | + | 3. Evaluate Constitution Check section below | |
| 15 | + | → Adjust for remaining clarifications; document deviations | |
| 16 | + | → Update Progress Tracking: Initial Constitution Check | |
| 17 | + | 4. Execute Phase 0 → research.md (complete) | |
| 18 | + | → Research WebKit API differences and migration path | |
| 19 | + | 5. Execute Phase 1 → contracts, data-model.md, quickstart.md (complete) | |
| 20 | + | 6. Re-evaluate Constitution Check section | |
| 21 | + | → Design aligned with constitution principles; clarifications still pending | |
| 22 | + | → Update Progress Tracking: Post-Design Constitution Check | |
| 23 | + | 7. Plan Phase 2 → Describe task generation approach (DO NOT create tasks.md) (complete) | |
| 24 | + | 8. Generate tasks via /tasks (exists: tasks.md) | |
| 25 | + | 9. Track Phase 4 implementation progress (in progress) | |
| 26 | + | ``` | |
| 27 | + | ||
| 28 | + | ## Summary | |
| 29 | + | Migrate the dumber browser from WebKit2GTK 4.0 (GTK3) to WebKitGTK 6.0 (GTK4) to enable GPU rendering through Vulkan layer. The migration will involve incrementally updating the pkg/webkit package files while maintaining browser functionality. A global rendering configuration will be added to allow users to choose between CPU and GPU rendering, with GPU enabled by default when supported. | |
| 30 | + | ||
| 31 | + | ## Technical Context | |
| 32 | + | **Language/Version**: Go 1.25.1 | |
| 33 | + | **Primary Dependencies**: WebKitGTK 6.0, GTK4, libsoup3, SQLite3 | |
| 34 | + | **Storage**: SQLite3 for history/settings, WebKitWebsiteDataManager for browser data | |
| 35 | + | **Testing**: Go tests, contract tests for WebKit bindings | |
| 36 | + | **Target Platform**: Linux (Wayland-first, X11 compatible) | |
| 37 | + | **Project Type**: single - Go browser application | |
| 38 | + | **Performance Goals**: < 500ms startup, improved GPU rendering performance | |
| 39 | + | **Constraints**: Maintain all existing functionality, graceful fallback for non-Vulkan systems | |
| 40 | + | **Scale/Scope**: ~15 files in pkg/webkit to migrate, ~3000 LOC | |
| 41 | + | ||
| 42 | + | **User-Provided Context**: No TDD for this, we will start by checking the WebKit API differences and migrate pkg/webkit files one by one | |
| 43 | + | ||
| 44 | + | ## Current Snapshot (repo state) | |
| 45 | + | - Build flags already target GTK4/WebKitGTK 6.0 in `pkg/webkit/*_cgo.go` ✓ | |
| 46 | + | - Core files present: `webview_cgo.go`, `window_cgo.go`, `script_cgo.go`, `scheme_cgo.go`, `zoom_cgo.go`, `keyboard_cgo.go`, `devtools_cgo.go`, `loop_cgo.go`, plus Go wrappers (`*.go`) ✓ | |
| 47 | + | - Migration underway in `webview_cgo.go` (GTK4 APIs, evaluate_javascript) ✓ | |
| 48 | + | - Spec still has [NEEDS CLARIFICATION] for performance targets and timeline ✗ | |
| 49 | + | ||
| 50 | + | ## Constitution Check | |
| 51 | + | *GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* | |
| 52 | + | ||
| 53 | + | **Simplicity**: | |
| 54 | + | - Projects: 1 (single Go browser project) ✓ | |
| 55 | + | - Using framework directly? YES (direct CGO bindings to WebKitGTK) ✓ | |
| 56 | + | - Single data model? YES (existing models unchanged) ✓ | |
| 57 | + | - Avoiding patterns? YES (no unnecessary abstractions) ✓ | |
| 58 | + | ||
| 59 | + | **Architecture**: | |
| 60 | + | - EVERY feature as library? pkg/webkit is already a library ✓ | |
| 61 | + | - Libraries listed: pkg/webkit (WebKit2GTK Go bindings) | |
| 62 | + | - CLI per library: Browser CLI with browse, history, dmenu commands ✓ | |
| 63 | + | - Library docs: Will update with new API documentation | |
| 64 | + | ||
| 65 | + | **Testing (Modified per user request)**: | |
| 66 | + | - User specified: "No TDD for this" - migration-focused approach | |
| 67 | + | - Will add tests after migration for regression prevention | |
| 68 | + | - Contract tests for new WebKit bindings | |
| 69 | + | - Integration tests for browser functionality | |
| 70 | + | ||
| 71 | + | **Observability**: | |
| 72 | + | - Structured logging included? YES (slog) ✓ | |
| 73 | + | - Frontend logs → backend? N/A (native application) | |
| 74 | + | - Error context sufficient? YES ✓ | |
| 75 | + | ||
| 76 | + | **Versioning**: | |
| 77 | + | - Version number assigned? Will increment after migration | |
| 78 | + | - BUILD increments on every change? YES ✓ | |
| 79 | + | - Breaking changes handled? API compatibility layer during migration | |
| 80 | + | ||
| 81 | + | ## Project Structure | |
| 82 | + | ||
| 83 | + | ### Documentation (this feature) | |
| 84 | + | ``` | |
| 85 | + | specs/004-migrate-to-webkitgtk/ | |
| 86 | + | ├── plan.md # This file (/plan command output) | |
| 87 | + | ├── research.md # Phase 0 output (/plan command) | |
| 88 | + | ├── data-model.md # Phase 1 output (/plan command) | |
| 89 | + | ├── quickstart.md # Phase 1 output (/plan command) | |
| 90 | + | ├── contracts/ # Phase 1 output (/plan command) | |
| 91 | + | └── tasks.md # Phase 2 output (/tasks command - NOT created by /plan) | |
| 92 | + | ``` | |
| 93 | + | ||
| 94 | + | ### Source Code (repository root) | |
| 95 | + | ``` | |
| 96 | + | # Option 1: Single project (SELECTED) | |
| 97 | + | pkg/ | |
| 98 | + | └── webkit/ # WebKitGTK Go bindings (migration target) | |
| 99 | + | ├── webview_cgo.go # WebView implementation (GTK4/WebKit 6) | |
| 100 | + | ├── window_cgo.go # Window management (GTK4) | |
| 101 | + | ├── script_cgo.go # JavaScript evaluation/injection | |
| 102 | + | ├── scheme_cgo.go # Custom URI scheme handler | |
| 103 | + | ├── zoom_cgo.go # Zoom control | |
| 104 | + | ├── keyboard_cgo.go # Keyboard handling (controllers) | |
| 105 | + | ├── devtools_cgo.go # Inspector | |
| 106 | + | ├── loop_cgo.go # Main loop helpers | |
| 107 | + | ├── settings.go # High-level settings glue | |
| 108 | + | └── *.go # Non-CGO wrappers (window.go, zoom.go, script.go, ...) | |
| 109 | + | ||
| 110 | + | internal/ | |
| 111 | + | ├── cli/ # CLI entry points (browse, history, dmenu) | |
| 112 | + | ├── config/ # App config (extend for rendering) | |
| 113 | + | └── db/ # SQLite persistence | |
| 114 | + | ||
| 115 | + | tests/ | |
| 116 | + | ├── contract/ # Contract tests for WebKit bindings | |
| 117 | + | ├── integration/ # End-to-end validation | |
| 118 | + | └── unit/ # Unit tests | |
| 119 | + | ``` | |
| 120 | + | ||
| 121 | + | **Structure Decision**: Option 1 (Single project) - maintaining existing structure | |
| 122 | + | ||
| 123 | + | ## Phase 0: Outline & Research | |
| 124 | + | 1. **Extract unknowns from Technical Context**: | |
| 125 | + | - WebKitGTK 6.0 API changes from 4.0 | |
| 126 | + | - GTK4 migration requirements | |
| 127 | + | - Vulkan renderer configuration | |
| 128 | + | - CGO binding updates needed | |
| 129 | + | - Build system changes (pkg-config) | |
| 130 | + | ||
| 131 | + | 2. **Generate and dispatch research agents**: | |
| 132 | + | ``` | |
| 133 | + | Task: "Research WebKitGTK 6.0 API breaking changes" | |
| 134 | + | Task: "Find GTK3 to GTK4 migration patterns for CGO" | |
| 135 | + | Task: "Research Vulkan enablement in WebKitGTK 6.0" | |
| 136 | + | Task: "Identify deprecated WebKit2GTK 4.0 APIs" | |
| 137 | + | ``` | |
| 138 | + | ||
| 139 | + | 3. **Consolidate findings** in `research.md` | |
| 140 | + | ||
| 141 | + | **Output**: research.md with migration path defined | |
| 142 | + | ||
| 143 | + | ## Phase 1: Design & Contracts | |
| 144 | + | *Prerequisites: research.md complete* | |
| 145 | + | ||
| 146 | + | 1. **API Mapping** → `data-model.md`: | |
| 147 | + | - Map WebKit2GTK 4.0 APIs to WebKitGTK 6.0 equivalents | |
| 148 | + | - Document removed/replaced functions | |
| 149 | + | - Identify new required APIs | |
| 150 | + | ||
| 151 | + | 2. **Generate migration contracts**: | |
| 152 | + | - Define compatibility layer interfaces | |
| 153 | + | - Create migration checkpoints | |
| 154 | + | - Output to `/contracts/` | |
| 155 | + | ||
| 156 | + | 3. **Migration test scenarios**: | |
| 157 | + | - Test each webkit file migration | |
| 158 | + | - Verify GPU acceleration | |
| 159 | + | - Fallback testing | |
| 160 | + | ||
| 161 | + | 4. **Create quickstart guide**: | |
| 162 | + | - Build requirements for GTK4/WebKitGTK 6.0 | |
| 163 | + | - Verification steps | |
| 164 | + | - Performance testing | |
| 165 | + | ||
| 166 | + | 5. **Update CLAUDE.md**: | |
| 167 | + | - Add WebKitGTK 6.0 context | |
| 168 | + | - GTK4 migration notes | |
| 169 | + | - Recent changes | |
| 170 | + | ||
| 171 | + | **Output**: data-model.md, /contracts/*, quickstart.md, CLAUDE.md updates | |
| 172 | + | ||
| 173 | + | ## File‑By‑File Migration Plan (grounded in repo) | |
| 174 | + | - `pkg/webkit/build_flags_cgo.go` / `build_cgo_stub.go` / `build_flags_stub.go`: ensure pkg-config switched to `webkitgtk-6.0 gtk4 javascriptcoregtk-6.0` (present; verify includes and tags) | |
| 175 | + | - `pkg/webkit/webview_cgo.go`: use `g_object_new` patterns as needed; ensure `webkit_web_view_evaluate_javascript` (async) usage; connect notify signals via GTK4; migrate any deprecated UCM message handlers. | |
| 176 | + | - `pkg/webkit/window_cgo.go`: use `gtk_window_new()`, `gtk_window_set_child()`, and `gtk_window_destroy()`; remove `gtk_container_add()` remnants. | |
| 177 | + | - `pkg/webkit/keyboard_cgo.go`: replace GdkEvent handlers with `GtkEventControllerKey` (key-pressed) and gestures for mouse input. | |
| 178 | + | - `pkg/webkit/zoom_cgo.go` / `zoom.go`: confirm zoom ranges and GTK4 APIs for zoom if used; keep high-level validation in Go. | |
| 179 | + | - `pkg/webkit/script_cgo.go` / `script.go`: standardize JS evaluation via `webkit_web_view_evaluate_javascript`. | |
| 180 | + | - `pkg/webkit/scheme_cgo.go`: verify `WebKitURISchemeRequest` handling and memory ownership semantics on GTK4/WebKit 6. | |
| 181 | + | - `pkg/webkit/devtools_cgo.go`: validate inspector APIs on WebKit 6. | |
| 182 | + | - `pkg/webkit/loop_cgo.go`: update any deprecated main-loop helpers. | |
| 183 | + | - `internal/config/*`: add `RenderingConfig` and `RenderingMode` with env/flag wiring. | |
| 184 | + | - `internal/cli/browse.go`: add `--rendering-mode`, `--disable-gpu`, `--debug-gpu`. | |
| 185 | + | - `services/*`: plumb rendering config into WebView creation. | |
| 186 | + | ||
| 187 | + | ## Phase 2: Task Planning Approach | |
| 188 | + | *This section describes what the /tasks command will do - DO NOT execute during /plan* | |
| 189 | + | ||
| 190 | + | **Task Generation Strategy**: | |
| 191 | + | - Analyze each file in pkg/webkit for required changes | |
| 192 | + | - Create migration task for each file | |
| 193 | + | - Order by dependency (build files first, then core, then features) | |
| 194 | + | - Add verification tasks after each major component | |
| 195 | + | ||
| 196 | + | **Ordering Strategy**: | |
| 197 | + | 1. Build configuration (pkg-config, CGO flags) | |
| 198 | + | 2. Core types and structures | |
| 199 | + | 3. Window and WebView creation | |
| 200 | + | 4. Feature-specific files (zoom, script, keyboard) | |
| 201 | + | 5. Integration testing | |
| 202 | + | 6. Performance verification | |
| 203 | + | ||
| 204 | + | **Estimated Output**: 20-25 migration tasks in tasks.md | |
| 205 | + | ||
| 206 | + | **IMPORTANT**: This phase is executed by the /tasks command, NOT by /plan | |
| 207 | + | ||
| 208 | + | ## Phase 3+: Future Implementation | |
| 209 | + | *These phases are beyond the scope of the /plan command* | |
| 210 | + | ||
| 211 | + | **Phase 3**: Task execution (/tasks command creates tasks.md) | |
| 212 | + | **Phase 4**: Implementation (execute migration tasks) | |
| 213 | + | **Phase 5**: Validation (verify GPU acceleration, run performance tests) | |
| 214 | + | ||
| 215 | + | ## Risks & Mitigations | |
| 216 | + | - Driver variability: prefer ON_DEMAND policy by default; expose override flags; add CPU fallback on init failures. | |
| 217 | + | - API drift: adhere to contracts in `contracts/webkit-api.yaml`; gate changes behind compile tags where helpful. | |
| 218 | + | - Performance regressions: provide quickstart validation and capture metrics; keep rollback point per task. | |
| 219 | + | ||
| 220 | + | ## Complexity Tracking | |
| 221 | + | *Fill ONLY if Constitution Check has violations that must be justified* | |
| 222 | + | ||
| 223 | + | | Violation | Why Needed | Simpler Alternative Rejected Because | | |
| 224 | + | |-----------|------------|-------------------------------------| | |
| 225 | + | | No TDD for migration | User-specified approach | TDD would slow down API exploration during migration | | |
| 226 | + | ||
| 227 | + | ## Progress Tracking | |
| 228 | + | *This checklist is updated during execution flow* | |
| 229 | + | ||
| 230 | + | **Phase Status**: | |
| 231 | + | - [x] Phase 0: Research complete (/plan command) | |
| 232 | + | - [x] Phase 1: Design complete (/plan command) | |
| 233 | + | - [x] Phase 2: Task planning complete (/plan command - describe approach only) | |
| 234 | + | - [x] Phase 3: Tasks generated (/tasks command) | |
| 235 | + | - [ ] Phase 4: Implementation in progress | |
| 236 | + | - [ ] Phase 5: Validation passed | |
| 237 | + | ||
| 238 | + | **Gate Status**: | |
| 239 | + | - [x] Initial Constitution Check: PASS | |
| 240 | + | - [x] Post-Design Constitution Check: PASS (performance targets pending) | |
| 241 | + | - [ ] All NEEDS CLARIFICATION resolved (see spec FR-008, FR-011, FR-012) | |
| 242 | + | - [x] Complexity deviations documented | |
| 243 | + | ||
| 244 | + | ## Next Actions | |
| 245 | + | - Verify and finalize build flags and includes across `pkg/webkit/*_cgo.go`. | |
| 246 | + | - Align `internal/config` and `internal/cli` with `RenderingConfig` and flags. | |
| 247 | + | - Complete event/gesture migration in `keyboard_cgo.go` and related handlers. | |
| 248 | + | - Add hardware acceleration policy mapping during WebView initialization. | |
| 249 | + | ||
| 250 | + | --- | |
| 251 | + | *Based on Constitution v1.0.0 - See `/memory/constitution.md`* | |
| @@ -0,0 +1,251 @@ | |||||
| 1 | + | # Implementation Plan: Migrate to WebKitGTK 6.0 | |||
| 2 | + | ||||
| 3 | + | **Branch**: `004-migrate-to-webkitgtk` | **Date**: 2025-01-21 | **Spec**: [spec.md](./spec.md) | |||
| 4 | + | **Input**: Feature specification from `/specs/004-migrate-to-webkitgtk/spec.md` | |||
| 5 | + | ||||
| 6 | + | ## Execution Flow (/plan command scope) | |||
| 7 | + | ``` | |||
| 8 | + | 1. Load feature spec from Input path | |||
| 9 | + | → Successfully loaded spec for WebKitGTK 6.0 migration | |||
| 10 | + | 2. Fill Technical Context (scan for NEEDS CLARIFICATION) | |||
| 11 | + | → Project Type: Single (Go browser application) | |||
| 12 | + | → Structure Decision: Option 1 (single project) | |||
| 13 | + | → Open clarifications remain in spec (FR-008, FR-011, FR-012) | |||
| 14 | + | 3. Evaluate Constitution Check section below | |||
| 15 | + | → Adjust for remaining clarifications; document deviations | |||
| 16 | + | → Update Progress Tracking: Initial Constitution Check | |||
| 17 | + | 4. Execute Phase 0 → research.md (complete) | |||
| 18 | + | → Research WebKit API differences and migration path | |||
| 19 | + | 5. Execute Phase 1 → contracts, data-model.md, quickstart.md (complete) | |||
| 20 | + | 6. Re-evaluate Constitution Check section | |||
| 21 | + | → Design aligned with constitution principles; clarifications still pending | |||
| 22 | + | → Update Progress Tracking: Post-Design Constitution Check | |||
| 23 | + | 7. Plan Phase 2 → Describe task generation approach (DO NOT create tasks.md) (complete) | |||
| 24 | + | 8. Generate tasks via /tasks (exists: tasks.md) | |||
| 25 | + | 9. Track Phase 4 implementation progress (in progress) | |||
| 26 | + | ``` | |||
| 27 | + | ||||
| 28 | + | ## Summary | |||
| 29 | + | Migrate the dumber browser from WebKit2GTK 4.0 (GTK3) to WebKitGTK 6.0 (GTK4) to enable GPU rendering through Vulkan layer. The migration will involve incrementally updating the pkg/webkit package files while maintaining browser functionality. A global rendering configuration will be added to allow users to choose between CPU and GPU rendering, with GPU enabled by default when supported. | |||
| 30 | + | ||||
| 31 | + | ## Technical Context | |||
| 32 | + | **Language/Version**: Go 1.25.1 | |||
| 33 | + | **Primary Dependencies**: WebKitGTK 6.0, GTK4, libsoup3, SQLite3 | |||
| 34 | + | **Storage**: SQLite3 for history/settings, WebKitWebsiteDataManager for browser data | |||
| 35 | + | **Testing**: Go tests, contract tests for WebKit bindings | |||
| 36 | + | **Target Platform**: Linux (Wayland-first, X11 compatible) | |||
| 37 | + | **Project Type**: single - Go browser application | |||
| 38 | + | **Performance Goals**: < 500ms startup, improved GPU rendering performance | |||
| 39 | + | **Constraints**: Maintain all existing functionality, graceful fallback for non-Vulkan systems | |||
| 40 | + | **Scale/Scope**: ~15 files in pkg/webkit to migrate, ~3000 LOC | |||
| 41 | + | ||||
| 42 | + | **User-Provided Context**: No TDD for this, we will start by checking the WebKit API differences and migrate pkg/webkit files one by one | |||
| 43 | + | ||||
| 44 | + | ## Current Snapshot (repo state) | |||
| 45 | + | - Build flags already target GTK4/WebKitGTK 6.0 in `pkg/webkit/*_cgo.go` ✓ | |||
| 46 | + | - Core files present: `webview_cgo.go`, `window_cgo.go`, `script_cgo.go`, `scheme_cgo.go`, `zoom_cgo.go`, `keyboard_cgo.go`, `devtools_cgo.go`, `loop_cgo.go`, plus Go wrappers (`*.go`) ✓ | |||
| 47 | + | - Migration underway in `webview_cgo.go` (GTK4 APIs, evaluate_javascript) ✓ | |||
| 48 | + | - Spec still has [NEEDS CLARIFICATION] for performance targets and timeline ✗ | |||
| 49 | + | ||||
| 50 | + | ## Constitution Check | |||
| 51 | + | *GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* | |||
| 52 | + | ||||
| 53 | + | **Simplicity**: | |||
| 54 | + | - Projects: 1 (single Go browser project) ✓ | |||
| 55 | + | - Using framework directly? YES (direct CGO bindings to WebKitGTK) ✓ | |||
| 56 | + | - Single data model? YES (existing models unchanged) ✓ | |||
| 57 | + | - Avoiding patterns? YES (no unnecessary abstractions) ✓ | |||
| 58 | + | ||||
| 59 | + | **Architecture**: | |||
| 60 | + | - EVERY feature as library? pkg/webkit is already a library ✓ | |||
| 61 | + | - Libraries listed: pkg/webkit (WebKit2GTK Go bindings) | |||
| 62 | + | - CLI per library: Browser CLI with browse, history, dmenu commands ✓ | |||
| 63 | + | - Library docs: Will update with new API documentation | |||
| 64 | + | ||||
| 65 | + | **Testing (Modified per user request)**: | |||
| 66 | + | - User specified: "No TDD for this" - migration-focused approach | |||
| 67 | + | - Will add tests after migration for regression prevention | |||
| 68 | + | - Contract tests for new WebKit bindings | |||
| 69 | + | - Integration tests for browser functionality | |||
| 70 | + | ||||
| 71 | + | **Observability**: | |||
| 72 | + | - Structured logging included? YES (slog) ✓ | |||
| 73 | + | - Frontend logs → backend? N/A (native application) | |||
| 74 | + | - Error context sufficient? YES ✓ | |||
| 75 | + | ||||
| 76 | + | **Versioning**: | |||
| 77 | + | - Version number assigned? Will increment after migration | |||
| 78 | + | - BUILD increments on every change? YES ✓ | |||
| 79 | + | - Breaking changes handled? API compatibility layer during migration | |||
| 80 | + | ||||
| 81 | + | ## Project Structure | |||
| 82 | + | ||||
| 83 | + | ### Documentation (this feature) | |||
| 84 | + | ``` | |||
| 85 | + | specs/004-migrate-to-webkitgtk/ | |||
| 86 | + | ├── plan.md # This file (/plan command output) | |||
| 87 | + | ├── research.md # Phase 0 output (/plan command) | |||
| 88 | + | ├── data-model.md # Phase 1 output (/plan command) | |||
| 89 | + | ├── quickstart.md # Phase 1 output (/plan command) | |||
| 90 | + | ├── contracts/ # Phase 1 output (/plan command) | |||
| 91 | + | └── tasks.md # Phase 2 output (/tasks command - NOT created by /plan) | |||
| 92 | + | ``` | |||
| 93 | + | ||||
| 94 | + | ### Source Code (repository root) | |||
| 95 | + | ``` | |||
| 96 | + | # Option 1: Single project (SELECTED) | |||
| 97 | + | pkg/ | |||
| 98 | + | └── webkit/ # WebKitGTK Go bindings (migration target) | |||
| 99 | + | ├── webview_cgo.go # WebView implementation (GTK4/WebKit 6) | |||
| 100 | + | ├── window_cgo.go # Window management (GTK4) | |||
| 101 | + | ├── script_cgo.go # JavaScript evaluation/injection | |||
| 102 | + | ├── scheme_cgo.go # Custom URI scheme handler | |||
| 103 | + | ├── zoom_cgo.go # Zoom control | |||
| 104 | + | ├── keyboard_cgo.go # Keyboard handling (controllers) | |||
| 105 | + | ├── devtools_cgo.go # Inspector | |||
| 106 | + | ├── loop_cgo.go # Main loop helpers | |||
| 107 | + | ├── settings.go # High-level settings glue | |||
| 108 | + | └── *.go # Non-CGO wrappers (window.go, zoom.go, script.go, ...) | |||
| 109 | + | ||||
| 110 | + | internal/ | |||
| 111 | + | ├── cli/ # CLI entry points (browse, history, dmenu) | |||
| 112 | + | ├── config/ # App config (extend for rendering) | |||
| 113 | + | └── db/ # SQLite persistence | |||
| 114 | + | ||||
| 115 | + | tests/ | |||
| 116 | + | ├── contract/ # Contract tests for WebKit bindings | |||
| 117 | + | ├── integration/ # End-to-end validation | |||
| 118 | + | └── unit/ # Unit tests | |||
| 119 | + | ``` | |||
| 120 | + | ||||
| 121 | + | **Structure Decision**: Option 1 (Single project) - maintaining existing structure | |||
| 122 | + | ||||
| 123 | + | ## Phase 0: Outline & Research | |||
| 124 | + | 1. **Extract unknowns from Technical Context**: | |||
| 125 | + | - WebKitGTK 6.0 API changes from 4.0 | |||
| 126 | + | - GTK4 migration requirements | |||
| 127 | + | - Vulkan renderer configuration | |||
| 128 | + | - CGO binding updates needed | |||
| 129 | + | - Build system changes (pkg-config) | |||
| 130 | + | ||||
| 131 | + | 2. **Generate and dispatch research agents**: | |||
| 132 | + | ``` | |||
| 133 | + | Task: "Research WebKitGTK 6.0 API breaking changes" | |||
| 134 | + | Task: "Find GTK3 to GTK4 migration patterns for CGO" | |||
| 135 | + | Task: "Research Vulkan enablement in WebKitGTK 6.0" | |||
| 136 | + | Task: "Identify deprecated WebKit2GTK 4.0 APIs" | |||
| 137 | + | ``` | |||
| 138 | + | ||||
| 139 | + | 3. **Consolidate findings** in `research.md` | |||
| 140 | + | ||||
| 141 | + | **Output**: research.md with migration path defined | |||
| 142 | + | ||||
| 143 | + | ## Phase 1: Design & Contracts | |||
| 144 | + | *Prerequisites: research.md complete* | |||
| 145 | + | ||||
| 146 | + | 1. **API Mapping** → `data-model.md`: | |||
| 147 | + | - Map WebKit2GTK 4.0 APIs to WebKitGTK 6.0 equivalents | |||
| 148 | + | - Document removed/replaced functions | |||
| 149 | + | - Identify new required APIs | |||
| 150 | + | ||||
| 151 | + | 2. **Generate migration contracts**: | |||
| 152 | + | - Define compatibility layer interfaces | |||
| 153 | + | - Create migration checkpoints | |||
| 154 | + | - Output to `/contracts/` | |||
| 155 | + | ||||
| 156 | + | 3. **Migration test scenarios**: | |||
| 157 | + | - Test each webkit file migration | |||
| 158 | + | - Verify GPU acceleration | |||
| 159 | + | - Fallback testing | |||
| 160 | + | ||||
| 161 | + | 4. **Create quickstart guide**: | |||
| 162 | + | - Build requirements for GTK4/WebKitGTK 6.0 | |||
| 163 | + | - Verification steps | |||
| 164 | + | - Performance testing | |||
| 165 | + | ||||
| 166 | + | 5. **Update CLAUDE.md**: | |||
| 167 | + | - Add WebKitGTK 6.0 context | |||
| 168 | + | - GTK4 migration notes | |||
| 169 | + | - Recent changes | |||
| 170 | + | ||||
| 171 | + | **Output**: data-model.md, /contracts/*, quickstart.md, CLAUDE.md updates | |||
| 172 | + | ||||
| 173 | + | ## File‑By‑File Migration Plan (grounded in repo) | |||
| 174 | + | - `pkg/webkit/build_flags_cgo.go` / `build_cgo_stub.go` / `build_flags_stub.go`: ensure pkg-config switched to `webkitgtk-6.0 gtk4 javascriptcoregtk-6.0` (present; verify includes and tags) | |||
| 175 | + | - `pkg/webkit/webview_cgo.go`: use `g_object_new` patterns as needed; ensure `webkit_web_view_evaluate_javascript` (async) usage; connect notify signals via GTK4; migrate any deprecated UCM message handlers. | |||
| 176 | + | - `pkg/webkit/window_cgo.go`: use `gtk_window_new()`, `gtk_window_set_child()`, and `gtk_window_destroy()`; remove `gtk_container_add()` remnants. | |||
| 177 | + | - `pkg/webkit/keyboard_cgo.go`: replace GdkEvent handlers with `GtkEventControllerKey` (key-pressed) and gestures for mouse input. | |||
| 178 | + | - `pkg/webkit/zoom_cgo.go` / `zoom.go`: confirm zoom ranges and GTK4 APIs for zoom if used; keep high-level validation in Go. | |||
| 179 | + | - `pkg/webkit/script_cgo.go` / `script.go`: standardize JS evaluation via `webkit_web_view_evaluate_javascript`. | |||
| 180 | + | - `pkg/webkit/scheme_cgo.go`: verify `WebKitURISchemeRequest` handling and memory ownership semantics on GTK4/WebKit 6. | |||
| 181 | + | - `pkg/webkit/devtools_cgo.go`: validate inspector APIs on WebKit 6. | |||
| 182 | + | - `pkg/webkit/loop_cgo.go`: update any deprecated main-loop helpers. | |||
| 183 | + | - `internal/config/*`: add `RenderingConfig` and `RenderingMode` with env/flag wiring. | |||
| 184 | + | - `internal/cli/browse.go`: add `--rendering-mode`, `--disable-gpu`, `--debug-gpu`. | |||
| 185 | + | - `services/*`: plumb rendering config into WebView creation. | |||
| 186 | + | ||||
| 187 | + | ## Phase 2: Task Planning Approach | |||
| 188 | + | *This section describes what the /tasks command will do - DO NOT execute during /plan* | |||
| 189 | + | ||||
| 190 | + | **Task Generation Strategy**: | |||
| 191 | + | - Analyze each file in pkg/webkit for required changes | |||
| 192 | + | - Create migration task for each file | |||
| 193 | + | - Order by dependency (build files first, then core, then features) | |||
| 194 | + | - Add verification tasks after each major component | |||
| 195 | + | ||||
| 196 | + | **Ordering Strategy**: | |||
| 197 | + | 1. Build configuration (pkg-config, CGO flags) | |||
| 198 | + | 2. Core types and structures | |||
| 199 | + | 3. Window and WebView creation | |||
| 200 | + | 4. Feature-specific files (zoom, script, keyboard) | |||
| 201 | + | 5. Integration testing | |||
| 202 | + | 6. Performance verification | |||
| 203 | + | ||||
| 204 | + | **Estimated Output**: 20-25 migration tasks in tasks.md | |||
| 205 | + | ||||
| 206 | + | **IMPORTANT**: This phase is executed by the /tasks command, NOT by /plan | |||
| 207 | + | ||||
| 208 | + | ## Phase 3+: Future Implementation | |||
| 209 | + | *These phases are beyond the scope of the /plan command* | |||
| 210 | + | ||||
| 211 | + | **Phase 3**: Task execution (/tasks command creates tasks.md) | |||
| 212 | + | **Phase 4**: Implementation (execute migration tasks) | |||
| 213 | + | **Phase 5**: Validation (verify GPU acceleration, run performance tests) | |||
| 214 | + | ||||
| 215 | + | ## Risks & Mitigations | |||
| 216 | + | - Driver variability: prefer ON_DEMAND policy by default; expose override flags; add CPU fallback on init failures. | |||
| 217 | + | - API drift: adhere to contracts in `contracts/webkit-api.yaml`; gate changes behind compile tags where helpful. | |||
| 218 | + | - Performance regressions: provide quickstart validation and capture metrics; keep rollback point per task. | |||
| 219 | + | ||||
| 220 | + | ## Complexity Tracking | |||
| 221 | + | *Fill ONLY if Constitution Check has violations that must be justified* | |||
| 222 | + | ||||
| 223 | + | | Violation | Why Needed | Simpler Alternative Rejected Because | | |||
| 224 | + | |-----------|------------|-------------------------------------| | |||
| 225 | + | | No TDD for migration | User-specified approach | TDD would slow down API exploration during migration | | |||
| 226 | + | ||||
| 227 | + | ## Progress Tracking | |||
| 228 | + | *This checklist is updated during execution flow* | |||
| 229 | + | ||||
| 230 | + | **Phase Status**: | |||
| 231 | + | - [x] Phase 0: Research complete (/plan command) | |||
| 232 | + | - [x] Phase 1: Design complete (/plan command) | |||
| 233 | + | - [x] Phase 2: Task planning complete (/plan command - describe approach only) | |||
| 234 | + | - [x] Phase 3: Tasks generated (/tasks command) | |||
| 235 | + | - [ ] Phase 4: Implementation in progress | |||
| 236 | + | - [ ] Phase 5: Validation passed | |||
| 237 | + | ||||
| 238 | + | **Gate Status**: | |||
| 239 | + | - [x] Initial Constitution Check: PASS | |||
| 240 | + | - [x] Post-Design Constitution Check: PASS (performance targets pending) | |||
| 241 | + | - [ ] All NEEDS CLARIFICATION resolved (see spec FR-008, FR-011, FR-012) | |||
| 242 | + | - [x] Complexity deviations documented | |||
| 243 | + | ||||
| 244 | + | ## Next Actions | |||
| 245 | + | - Verify and finalize build flags and includes across `pkg/webkit/*_cgo.go`. | |||
| 246 | + | - Align `internal/config` and `internal/cli` with `RenderingConfig` and flags. | |||
| 247 | + | - Complete event/gesture migration in `keyboard_cgo.go` and related handlers. | |||
| 248 | + | - Add hardware acceleration policy mapping during WebView initialization. | |||
| 249 | + | ||||
| 250 | + | --- | |||
| 251 | + | *Based on Constitution v1.0.0 - See `/memory/constitution.md`* | |||
A specs/004-migrate-to-webkitgtk/quickstart.md +194 -0
| @@ -0,0 +1,194 @@ | |||
| 1 | + | # Quickstart: WebKitGTK 6.0 Migration | |
| 2 | + | ||
| 3 | + | ## Prerequisites | |
| 4 | + | ||
| 5 | + | ### System Requirements | |
| 6 | + | ```bash | |
| 7 | + | # Check GTK4 installation | |
| 8 | + | pkg-config --modversion gtk4 # Should be >= 4.0 | |
| 9 | + | ||
| 10 | + | # Check WebKitGTK 6.0 | |
| 11 | + | pkg-config --modversion webkitgtk-6.0 # Should be >= 2.40 | |
| 12 | + | ||
| 13 | + | # Check Vulkan support (optional but recommended) | |
| 14 | + | vulkaninfo | grep "Vulkan Instance Version" | |
| 15 | + | ``` | |
| 16 | + | ||
| 17 | + | ### Install Dependencies (Arch Linux) | |
| 18 | + | ```bash | |
| 19 | + | sudo pacman -S webkit2gtk-6.0 gtk4 vulkan-tools | |
| 20 | + | ``` | |
| 21 | + | ||
| 22 | + | ### Install Dependencies (Ubuntu/Debian) | |
| 23 | + | ```bash | |
| 24 | + | sudo apt install libwebkitgtk-6.0-dev libgtk-4-dev vulkan-tools | |
| 25 | + | ``` | |
| 26 | + | ||
| 27 | + | ## Build and Test | |
| 28 | + | ||
| 29 | + | ### 1. Switch to Feature Branch | |
| 30 | + | ```bash | |
| 31 | + | git checkout 004-migrate-to-webkitgtk | |
| 32 | + | ``` | |
| 33 | + | ||
| 34 | + | ### 2. Build the Browser | |
| 35 | + | ```bash | |
| 36 | + | make clean | |
| 37 | + | make build | |
| 38 | + | ``` | |
| 39 | + | ||
| 40 | + | ### 3. Verify GPU Rendering | |
| 41 | + | ||
| 42 | + | #### Test Auto-Detection (Default) | |
| 43 | + | ```bash | |
| 44 | + | # Should auto-detect GPU availability | |
| 45 | + | ./dumber browse https://webglsamples.org/aquarium/aquarium.html | |
| 46 | + | ``` | |
| 47 | + | ||
| 48 | + | #### Force GPU Mode | |
| 49 | + | ```bash | |
| 50 | + | # Force GPU acceleration | |
| 51 | + | ./dumber browse --rendering-mode=gpu https://webglsamples.org/aquarium/aquarium.html | |
| 52 | + | ||
| 53 | + | # Or using environment variable | |
| 54 | + | DUMBER_RENDERING_MODE=gpu ./dumber browse https://example.com | |
| 55 | + | ``` | |
| 56 | + | ||
| 57 | + | #### Force CPU Mode | |
| 58 | + | ```bash | |
| 59 | + | # Force software rendering | |
| 60 | + | ./dumber browse --rendering-mode=cpu https://example.com | |
| 61 | + | ||
| 62 | + | # Or shorthand | |
| 63 | + | ./dumber browse --disable-gpu https://example.com | |
| 64 | + | ``` | |
| 65 | + | ||
| 66 | + | #### Debug GPU Compositing | |
| 67 | + | ```bash | |
| 68 | + | # Show GPU layer borders | |
| 69 | + | ./dumber browse --debug-gpu https://example.com | |
| 70 | + | ``` | |
| 71 | + | ||
| 72 | + | ### 4. Check Rendering Status | |
| 73 | + | ```bash | |
| 74 | + | # While browser is running, check status | |
| 75 | + | curl http://localhost:8080/api/rendering/status | |
| 76 | + | ``` | |
| 77 | + | ||
| 78 | + | Expected response: | |
| 79 | + | ```json | |
| 80 | + | { | |
| 81 | + | "mode": "auto", | |
| 82 | + | "gpu_active": true, | |
| 83 | + | "gpu_available": true, | |
| 84 | + | "vulkan_enabled": true, | |
| 85 | + | "errors": [], | |
| 86 | + | "performance": { | |
| 87 | + | "fps": 60, | |
| 88 | + | "cpu_usage_percent": 15.2, | |
| 89 | + | "gpu_usage_percent": 45.8, | |
| 90 | + | "memory_mb": 256 | |
| 91 | + | } | |
| 92 | + | } | |
| 93 | + | ``` | |
| 94 | + | ||
| 95 | + | ## Performance Testing | |
| 96 | + | ||
| 97 | + | ### 1. Benchmark Page Load | |
| 98 | + | ```bash | |
| 99 | + | # Measure with GPU | |
| 100 | + | time ./dumber browse --rendering-mode=gpu https://browserbench.org/MotionMark/ | |
| 101 | + | ||
| 102 | + | # Measure with CPU | |
| 103 | + | time ./dumber browse --rendering-mode=cpu https://browserbench.org/MotionMark/ | |
| 104 | + | ``` | |
| 105 | + | ||
| 106 | + | ### 2. Monitor Resource Usage | |
| 107 | + | ```bash | |
| 108 | + | # In another terminal, monitor GPU | |
| 109 | + | nvidia-smi -l 1 # For NVIDIA | |
| 110 | + | radeontop # For AMD | |
| 111 | + | ||
| 112 | + | # Monitor CPU | |
| 113 | + | htop | |
| 114 | + | ``` | |
| 115 | + | ||
| 116 | + | ### 3. Test WebGL Performance | |
| 117 | + | ```bash | |
| 118 | + | # High-demand WebGL test | |
| 119 | + | ./dumber browse https://webglsamples.org/dynamic-cubemap/dynamic-cubemap.html | |
| 120 | + | ||
| 121 | + | # Check FPS counter in browser | |
| 122 | + | ``` | |
| 123 | + | ||
| 124 | + | ## Troubleshooting | |
| 125 | + | ||
| 126 | + | ### GPU Not Detected | |
| 127 | + | ```bash | |
| 128 | + | # Check Vulkan drivers | |
| 129 | + | vulkaninfo | |
| 130 | + | ||
| 131 | + | # Check OpenGL | |
| 132 | + | glxinfo | grep "OpenGL renderer" | |
| 133 | + | ||
| 134 | + | # Force CPU mode if GPU issues | |
| 135 | + | ./dumber browse --disable-gpu | |
| 136 | + | ``` | |
| 137 | + | ||
| 138 | + | ### Performance Issues | |
| 139 | + | ```bash | |
| 140 | + | # Enable debug indicators | |
| 141 | + | ./dumber browse --debug-gpu https://example.com | |
| 142 | + | ||
| 143 | + | # Check for compositing issues (red borders = repaints) | |
| 144 | + | ``` | |
| 145 | + | ||
| 146 | + | ### Build Errors | |
| 147 | + | ```bash | |
| 148 | + | # Ensure correct pkg-config | |
| 149 | + | pkg-config --libs webkitgtk-6.0 gtk4 | |
| 150 | + | ||
| 151 | + | # Clean and rebuild | |
| 152 | + | make clean | |
| 153 | + | make build | |
| 154 | + | ``` | |
| 155 | + | ||
| 156 | + | ## Validation Checklist | |
| 157 | + | ||
| 158 | + | - [ ] Browser builds successfully with WebKitGTK 6.0 | |
| 159 | + | - [ ] Browser starts in < 500ms | |
| 160 | + | - [ ] GPU acceleration activates when available | |
| 161 | + | - [ ] CPU fallback works when GPU unavailable | |
| 162 | + | - [ ] WebGL content renders correctly | |
| 163 | + | - [ ] No visual artifacts or rendering issues | |
| 164 | + | - [ ] History and bookmarks preserved | |
| 165 | + | - [ ] All existing features work | |
| 166 | + | - [ ] Performance improved with GPU enabled | |
| 167 | + | - [ ] Resource usage within acceptable limits | |
| 168 | + | ||
| 169 | + | ## Migration Verification | |
| 170 | + | ||
| 171 | + | ### Check API Usage | |
| 172 | + | ```bash | |
| 173 | + | # Verify new GTK4/WebKit6 symbols | |
| 174 | + | ldd ./dumber | grep -E "(gtk-4|webkit.*6)" | |
| 175 | + | ||
| 176 | + | # Should see: | |
| 177 | + | # libgtk-4.so.1 | |
| 178 | + | # libwebkitgtk-6.0.so.4 | |
| 179 | + | ``` | |
| 180 | + | ||
| 181 | + | ### Test Key Features | |
| 182 | + | 1. **Navigation**: URLs load correctly | |
| 183 | + | 2. **History**: Previous data accessible | |
| 184 | + | 3. **Zoom**: Ctrl+Plus/Minus works | |
| 185 | + | 4. **DevTools**: F12 opens inspector | |
| 186 | + | 5. **Dmenu**: Integration still works | |
| 187 | + | ||
| 188 | + | ## Report Issues | |
| 189 | + | ||
| 190 | + | If you encounter issues: | |
| 191 | + | 1. Note the rendering mode used | |
| 192 | + | 2. Check `/tmp/dumber.log` for errors | |
| 193 | + | 3. Run with `--debug-gpu` for visual debugging | |
| 194 | + | 4. Report with output of `vulkaninfo` and `glxinfo`⏎̸ | |
| @@ -0,0 +1,194 @@ | |||||
| 1 | + | # Quickstart: WebKitGTK 6.0 Migration | |||
| 2 | + | ||||
| 3 | + | ## Prerequisites | |||
| 4 | + | ||||
| 5 | + | ### System Requirements | |||
| 6 | + | ```bash | |||
| 7 | + | # Check GTK4 installation | |||
| 8 | + | pkg-config --modversion gtk4 # Should be >= 4.0 | |||
| 9 | + | ||||
| 10 | + | # Check WebKitGTK 6.0 | |||
| 11 | + | pkg-config --modversion webkitgtk-6.0 # Should be >= 2.40 | |||
| 12 | + | ||||
| 13 | + | # Check Vulkan support (optional but recommended) | |||
| 14 | + | vulkaninfo | grep "Vulkan Instance Version" | |||
| 15 | + | ``` | |||
| 16 | + | ||||
| 17 | + | ### Install Dependencies (Arch Linux) | |||
| 18 | + | ```bash | |||
| 19 | + | sudo pacman -S webkit2gtk-6.0 gtk4 vulkan-tools | |||
| 20 | + | ``` | |||
| 21 | + | ||||
| 22 | + | ### Install Dependencies (Ubuntu/Debian) | |||
| 23 | + | ```bash | |||
| 24 | + | sudo apt install libwebkitgtk-6.0-dev libgtk-4-dev vulkan-tools | |||
| 25 | + | ``` | |||
| 26 | + | ||||
| 27 | + | ## Build and Test | |||
| 28 | + | ||||
| 29 | + | ### 1. Switch to Feature Branch | |||
| 30 | + | ```bash | |||
| 31 | + | git checkout 004-migrate-to-webkitgtk | |||
| 32 | + | ``` | |||
| 33 | + | ||||
| 34 | + | ### 2. Build the Browser | |||
| 35 | + | ```bash | |||
| 36 | + | make clean | |||
| 37 | + | make build | |||
| 38 | + | ``` | |||
| 39 | + | ||||
| 40 | + | ### 3. Verify GPU Rendering | |||
| 41 | + | ||||
| 42 | + | #### Test Auto-Detection (Default) | |||
| 43 | + | ```bash | |||
| 44 | + | # Should auto-detect GPU availability | |||
| 45 | + | ./dumber browse https://webglsamples.org/aquarium/aquarium.html | |||
| 46 | + | ``` | |||
| 47 | + | ||||
| 48 | + | #### Force GPU Mode | |||
| 49 | + | ```bash | |||
| 50 | + | # Force GPU acceleration | |||
| 51 | + | ./dumber browse --rendering-mode=gpu https://webglsamples.org/aquarium/aquarium.html | |||
| 52 | + | ||||
| 53 | + | # Or using environment variable | |||
| 54 | + | DUMBER_RENDERING_MODE=gpu ./dumber browse https://example.com | |||
| 55 | + | ``` | |||
| 56 | + | ||||
| 57 | + | #### Force CPU Mode | |||
| 58 | + | ```bash | |||
| 59 | + | # Force software rendering | |||
| 60 | + | ./dumber browse --rendering-mode=cpu https://example.com | |||
| 61 | + | ||||
| 62 | + | # Or shorthand | |||
| 63 | + | ./dumber browse --disable-gpu https://example.com | |||
| 64 | + | ``` | |||
| 65 | + | ||||
| 66 | + | #### Debug GPU Compositing | |||
| 67 | + | ```bash | |||
| 68 | + | # Show GPU layer borders | |||
| 69 | + | ./dumber browse --debug-gpu https://example.com | |||
| 70 | + | ``` | |||
| 71 | + | ||||
| 72 | + | ### 4. Check Rendering Status | |||
| 73 | + | ```bash | |||
| 74 | + | # While browser is running, check status | |||
| 75 | + | curl http://localhost:8080/api/rendering/status | |||
| 76 | + | ``` | |||
| 77 | + | ||||
| 78 | + | Expected response: | |||
| 79 | + | ```json | |||
| 80 | + | { | |||
| 81 | + | "mode": "auto", | |||
| 82 | + | "gpu_active": true, | |||
| 83 | + | "gpu_available": true, | |||
| 84 | + | "vulkan_enabled": true, | |||
| 85 | + | "errors": [], | |||
| 86 | + | "performance": { | |||
| 87 | + | "fps": 60, | |||
| 88 | + | "cpu_usage_percent": 15.2, | |||
| 89 | + | "gpu_usage_percent": 45.8, | |||
| 90 | + | "memory_mb": 256 | |||
| 91 | + | } | |||
| 92 | + | } | |||
| 93 | + | ``` | |||
| 94 | + | ||||
| 95 | + | ## Performance Testing | |||
| 96 | + | ||||
| 97 | + | ### 1. Benchmark Page Load | |||
| 98 | + | ```bash | |||
| 99 | + | # Measure with GPU | |||
| 100 | + | time ./dumber browse --rendering-mode=gpu https://browserbench.org/MotionMark/ | |||
| 101 | + | ||||
| 102 | + | # Measure with CPU | |||
| 103 | + | time ./dumber browse --rendering-mode=cpu https://browserbench.org/MotionMark/ | |||
| 104 | + | ``` | |||
| 105 | + | ||||
| 106 | + | ### 2. Monitor Resource Usage | |||
| 107 | + | ```bash | |||
| 108 | + | # In another terminal, monitor GPU | |||
| 109 | + | nvidia-smi -l 1 # For NVIDIA | |||
| 110 | + | radeontop # For AMD | |||
| 111 | + | ||||
| 112 | + | # Monitor CPU | |||
| 113 | + | htop | |||
| 114 | + | ``` | |||
| 115 | + | ||||
| 116 | + | ### 3. Test WebGL Performance | |||
| 117 | + | ```bash | |||
| 118 | + | # High-demand WebGL test | |||
| 119 | + | ./dumber browse https://webglsamples.org/dynamic-cubemap/dynamic-cubemap.html | |||
| 120 | + | ||||
| 121 | + | # Check FPS counter in browser | |||
| 122 | + | ``` | |||
| 123 | + | ||||
| 124 | + | ## Troubleshooting | |||
| 125 | + | ||||
| 126 | + | ### GPU Not Detected | |||
| 127 | + | ```bash | |||
| 128 | + | # Check Vulkan drivers | |||
| 129 | + | vulkaninfo | |||
| 130 | + | ||||
| 131 | + | # Check OpenGL | |||
| 132 | + | glxinfo | grep "OpenGL renderer" | |||
| 133 | + | ||||
| 134 | + | # Force CPU mode if GPU issues | |||
| 135 | + | ./dumber browse --disable-gpu | |||
| 136 | + | ``` | |||
| 137 | + | ||||
| 138 | + | ### Performance Issues | |||
| 139 | + | ```bash | |||
| 140 | + | # Enable debug indicators | |||
| 141 | + | ./dumber browse --debug-gpu https://example.com | |||
| 142 | + | ||||
| 143 | + | # Check for compositing issues (red borders = repaints) | |||
| 144 | + | ``` | |||
| 145 | + | ||||
| 146 | + | ### Build Errors | |||
| 147 | + | ```bash | |||
| 148 | + | # Ensure correct pkg-config | |||
| 149 | + | pkg-config --libs webkitgtk-6.0 gtk4 | |||
| 150 | + | ||||
| 151 | + | # Clean and rebuild | |||
| 152 | + | make clean | |||
| 153 | + | make build | |||
| 154 | + | ``` | |||
| 155 | + | ||||
| 156 | + | ## Validation Checklist | |||
| 157 | + | ||||
| 158 | + | - [ ] Browser builds successfully with WebKitGTK 6.0 | |||
| 159 | + | - [ ] Browser starts in < 500ms | |||
| 160 | + | - [ ] GPU acceleration activates when available | |||
| 161 | + | - [ ] CPU fallback works when GPU unavailable | |||
| 162 | + | - [ ] WebGL content renders correctly | |||
| 163 | + | - [ ] No visual artifacts or rendering issues | |||
| 164 | + | - [ ] History and bookmarks preserved | |||
| 165 | + | - [ ] All existing features work | |||
| 166 | + | - [ ] Performance improved with GPU enabled | |||
| 167 | + | - [ ] Resource usage within acceptable limits | |||
| 168 | + | ||||
| 169 | + | ## Migration Verification | |||
| 170 | + | ||||
| 171 | + | ### Check API Usage | |||
| 172 | + | ```bash | |||
| 173 | + | # Verify new GTK4/WebKit6 symbols | |||
| 174 | + | ldd ./dumber | grep -E "(gtk-4|webkit.*6)" | |||
| 175 | + | ||||
| 176 | + | # Should see: | |||
| 177 | + | # libgtk-4.so.1 | |||
| 178 | + | # libwebkitgtk-6.0.so.4 | |||
| 179 | + | ``` | |||
| 180 | + | ||||
| 181 | + | ### Test Key Features | |||
| 182 | + | 1. **Navigation**: URLs load correctly | |||
| 183 | + | 2. **History**: Previous data accessible | |||
| 184 | + | 3. **Zoom**: Ctrl+Plus/Minus works | |||
| 185 | + | 4. **DevTools**: F12 opens inspector | |||
| 186 | + | 5. **Dmenu**: Integration still works | |||
| 187 | + | ||||
| 188 | + | ## Report Issues | |||
| 189 | + | ||||
| 190 | + | If you encounter issues: | |||
| 191 | + | 1. Note the rendering mode used | |||
| 192 | + | 2. Check `/tmp/dumber.log` for errors | |||
| 193 | + | 3. Run with `--debug-gpu` for visual debugging | |||
| 194 | + | 4. Report with output of `vulkaninfo` and `glxinfo`⏎̸ | |||
A specs/004-migrate-to-webkitgtk/research.md +115 -0
| @@ -0,0 +1,115 @@ | |||
| 1 | + | # Research: WebKitGTK 6.0 Migration | |
| 2 | + | ||
| 3 | + | ## API Migration Research | |
| 4 | + | ||
| 5 | + | ### Decision: WebKitGTK 6.0 with GTK4 | |
| 6 | + | **Rationale**: | |
| 7 | + | - GTK4 provides native Vulkan renderer support | |
| 8 | + | - WebKitGTK 6.0 is the only actively maintained version for GTK4 | |
| 9 | + | - Enables hardware-accelerated rendering via modern GPU APIs | |
| 10 | + | - Better Wayland support and performance | |
| 11 | + | ||
| 12 | + | **Alternatives considered**: | |
| 13 | + | - Staying on WebKit2GTK 4.0: No Vulkan support, limited to OpenGL | |
| 14 | + | - Using WebKit2GTK 4.1: Still GTK3, no Vulkan renderer | |
| 15 | + | - Custom Vulkan implementation: Too complex, maintenance burden | |
| 16 | + | ||
| 17 | + | ### GTK3 to GTK4 Migration Patterns | |
| 18 | + | ||
| 19 | + | **Key Changes**: | |
| 20 | + | 1. **Event handling**: GdkEvent parameter removed from signals | |
| 21 | + | 2. **Widget construction**: Direct use of g_object_new() instead of specific constructors | |
| 22 | + | 3. **Drawing**: No more GtkDrawingArea draw signal, use GtkSnapshot | |
| 23 | + | 4. **CSS**: GTK4 uses standard CSS, removed custom properties | |
| 24 | + | 5. **Layout**: New layout managers replace containers | |
| 25 | + | ||
| 26 | + | **CGO Binding Updates**: | |
| 27 | + | - pkg-config: `webkit2gtk-4.0` → `webkitgtk-6.0` | |
| 28 | + | - pkg-config: `gtk+-3.0` → `gtk4` | |
| 29 | + | - Header: `gtk/gtk.h` remains same | |
| 30 | + | - Header: `webkit2/webkit2.h` → `webkit/webkit.h` | |
| 31 | + | ||
| 32 | + | ### Vulkan Renderer Configuration | |
| 33 | + | ||
| 34 | + | **Decision: Hardware Acceleration Policy** | |
| 35 | + | **Rationale**: Provides user control over GPU vs CPU rendering | |
| 36 | + | ||
| 37 | + | **Configuration Options**: | |
| 38 | + | 1. `WEBKIT_HARDWARE_ACCELERATION_POLICY_ALWAYS`: Force GPU | |
| 39 | + | 2. `WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER`: Force CPU | |
| 40 | + | 3. `WEBKIT_HARDWARE_ACCELERATION_POLICY_ON_DEMAND`: Auto-detect (default) | |
| 41 | + | ||
| 42 | + | **New Settings in WebKitGTK 6.0**: | |
| 43 | + | - `webkit_settings_set_hardware_acceleration_policy()` | |
| 44 | + | - `webkit_settings_set_enable_2d_canvas_acceleration()` | |
| 45 | + | - `webkit_settings_set_enable_webgl()` | |
| 46 | + | - `webkit_settings_set_draw_compositing_indicators()` (debugging) | |
| 47 | + | ||
| 48 | + | ### Deprecated APIs to Replace | |
| 49 | + | ||
| 50 | + | | WebKit2GTK 4.0 API | WebKitGTK 6.0 Replacement | | |
| 51 | + | |-------------------|---------------------------| | |
| 52 | + | | `webkit_web_view_new_with_context()` | `g_object_new()` with properties | | |
| 53 | + | | `webkit_web_context_set_sandbox_enabled()` | Always enabled, use `add_path_to_sandbox()` | | |
| 54 | + | | `WebKitWebContext` | `WebKitNetworkSession` for networking | | |
| 55 | + | | `webkit_web_view_run_javascript()` | Same, but async only | | |
| 56 | + | | Process swap property | Always enabled | | |
| 57 | + | ||
| 58 | + | ### Build System Changes | |
| 59 | + | ||
| 60 | + | **pkg-config flags**: | |
| 61 | + | ```bash | |
| 62 | + | # Old (WebKit2GTK 4.0) | |
| 63 | + | pkg-config --cflags --libs webkit2gtk-4.0 gtk+-3.0 javascriptcoregtk-4.0 | |
| 64 | + | ||
| 65 | + | # New (WebKitGTK 6.0) | |
| 66 | + | pkg-config --cflags --libs webkitgtk-6.0 gtk4 javascriptcoregtk-6.0 | |
| 67 | + | ``` | |
| 68 | + | ||
| 69 | + | ### Performance Considerations | |
| 70 | + | ||
| 71 | + | **GPU Rendering Benefits**: | |
| 72 | + | - Vulkan backend via GTK4's unified GPU renderer | |
| 73 | + | - Hardware-accelerated 2D canvas | |
| 74 | + | - Improved WebGL performance | |
| 75 | + | - Reduced CPU usage for compositing | |
| 76 | + | - Better multi-threaded rendering | |
| 77 | + | ||
| 78 | + | **Fallback Strategy**: | |
| 79 | + | - Detect GPU capabilities at runtime | |
| 80 | + | - Use `ON_DEMAND` policy for automatic selection | |
| 81 | + | - Provide config flag for manual override | |
| 82 | + | - Monitor performance metrics | |
| 83 | + | ||
| 84 | + | ### Global Rendering Configuration | |
| 85 | + | ||
| 86 | + | **Decision: Add RenderingMode to config** | |
| 87 | + | **Rationale**: User control over CPU/GPU rendering with smart defaults | |
| 88 | + | ||
| 89 | + | **Implementation**: | |
| 90 | + | ```go | |
| 91 | + | type RenderingMode string | |
| 92 | + | ||
| 93 | + | const ( | |
| 94 | + | RenderingModeAuto RenderingMode = "auto" // Default, detect at runtime | |
| 95 | + | RenderingModeGPU RenderingMode = "gpu" // Force GPU acceleration | |
| 96 | + | RenderingModeCPU RenderingMode = "cpu" // Force software rendering | |
| 97 | + | ) | |
| 98 | + | ``` | |
| 99 | + | ||
| 100 | + | **Config Integration**: | |
| 101 | + | - Add to `internal/config/config.go` | |
| 102 | + | - Default to "auto" (GPU if available) | |
| 103 | + | - CLI flag: `--rendering-mode=[auto|gpu|cpu]` | |
| 104 | + | - Environment variable: `DUMBER_RENDERING_MODE` | |
| 105 | + | ||
| 106 | + | ## Summary | |
| 107 | + | ||
| 108 | + | The migration to WebKitGTK 6.0 requires: | |
| 109 | + | 1. Complete GTK3 → GTK4 widget/event migration | |
| 110 | + | 2. Update all WebKit API calls to new versions | |
| 111 | + | 3. Implement hardware acceleration configuration | |
| 112 | + | 4. Add global rendering mode configuration | |
| 113 | + | 5. Test Vulkan renderer activation and fallback | |
| 114 | + | ||
| 115 | + | All technical questions have been resolved through research.⏎̸ | |
| @@ -0,0 +1,115 @@ | |||||
| 1 | + | # Research: WebKitGTK 6.0 Migration | |||
| 2 | + | ||||
| 3 | + | ## API Migration Research | |||
| 4 | + | ||||
| 5 | + | ### Decision: WebKitGTK 6.0 with GTK4 | |||
| 6 | + | **Rationale**: | |||
| 7 | + | - GTK4 provides native Vulkan renderer support | |||
| 8 | + | - WebKitGTK 6.0 is the only actively maintained version for GTK4 | |||
| 9 | + | - Enables hardware-accelerated rendering via modern GPU APIs | |||
| 10 | + | - Better Wayland support and performance | |||
| 11 | + | ||||
| 12 | + | **Alternatives considered**: | |||
| 13 | + | - Staying on WebKit2GTK 4.0: No Vulkan support, limited to OpenGL | |||
| 14 | + | - Using WebKit2GTK 4.1: Still GTK3, no Vulkan renderer | |||
| 15 | + | - Custom Vulkan implementation: Too complex, maintenance burden | |||
| 16 | + | ||||
| 17 | + | ### GTK3 to GTK4 Migration Patterns | |||
| 18 | + | ||||
| 19 | + | **Key Changes**: | |||
| 20 | + | 1. **Event handling**: GdkEvent parameter removed from signals | |||
| 21 | + | 2. **Widget construction**: Direct use of g_object_new() instead of specific constructors | |||
| 22 | + | 3. **Drawing**: No more GtkDrawingArea draw signal, use GtkSnapshot | |||
| 23 | + | 4. **CSS**: GTK4 uses standard CSS, removed custom properties | |||
| 24 | + | 5. **Layout**: New layout managers replace containers | |||
| 25 | + | ||||
| 26 | + | **CGO Binding Updates**: | |||
| 27 | + | - pkg-config: `webkit2gtk-4.0` → `webkitgtk-6.0` | |||
| 28 | + | - pkg-config: `gtk+-3.0` → `gtk4` | |||
| 29 | + | - Header: `gtk/gtk.h` remains same | |||
| 30 | + | - Header: `webkit2/webkit2.h` → `webkit/webkit.h` | |||
| 31 | + | ||||
| 32 | + | ### Vulkan Renderer Configuration | |||
| 33 | + | ||||
| 34 | + | **Decision: Hardware Acceleration Policy** | |||
| 35 | + | **Rationale**: Provides user control over GPU vs CPU rendering | |||
| 36 | + | ||||
| 37 | + | **Configuration Options**: | |||
| 38 | + | 1. `WEBKIT_HARDWARE_ACCELERATION_POLICY_ALWAYS`: Force GPU | |||
| 39 | + | 2. `WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER`: Force CPU | |||
| 40 | + | 3. `WEBKIT_HARDWARE_ACCELERATION_POLICY_ON_DEMAND`: Auto-detect (default) | |||
| 41 | + | ||||
| 42 | + | **New Settings in WebKitGTK 6.0**: | |||
| 43 | + | - `webkit_settings_set_hardware_acceleration_policy()` | |||
| 44 | + | - `webkit_settings_set_enable_2d_canvas_acceleration()` | |||
| 45 | + | - `webkit_settings_set_enable_webgl()` | |||
| 46 | + | - `webkit_settings_set_draw_compositing_indicators()` (debugging) | |||
| 47 | + | ||||
| 48 | + | ### Deprecated APIs to Replace | |||
| 49 | + | ||||
| 50 | + | | WebKit2GTK 4.0 API | WebKitGTK 6.0 Replacement | | |||
| 51 | + | |-------------------|---------------------------| | |||
| 52 | + | | `webkit_web_view_new_with_context()` | `g_object_new()` with properties | | |||
| 53 | + | | `webkit_web_context_set_sandbox_enabled()` | Always enabled, use `add_path_to_sandbox()` | | |||
| 54 | + | | `WebKitWebContext` | `WebKitNetworkSession` for networking | | |||
| 55 | + | | `webkit_web_view_run_javascript()` | Same, but async only | | |||
| 56 | + | | Process swap property | Always enabled | | |||
| 57 | + | ||||
| 58 | + | ### Build System Changes | |||
| 59 | + | ||||
| 60 | + | **pkg-config flags**: | |||
| 61 | + | ```bash | |||
| 62 | + | # Old (WebKit2GTK 4.0) | |||
| 63 | + | pkg-config --cflags --libs webkit2gtk-4.0 gtk+-3.0 javascriptcoregtk-4.0 | |||
| 64 | + | ||||
| 65 | + | # New (WebKitGTK 6.0) | |||
| 66 | + | pkg-config --cflags --libs webkitgtk-6.0 gtk4 javascriptcoregtk-6.0 | |||
| 67 | + | ``` | |||
| 68 | + | ||||
| 69 | + | ### Performance Considerations | |||
| 70 | + | ||||
| 71 | + | **GPU Rendering Benefits**: | |||
| 72 | + | - Vulkan backend via GTK4's unified GPU renderer | |||
| 73 | + | - Hardware-accelerated 2D canvas | |||
| 74 | + | - Improved WebGL performance | |||
| 75 | + | - Reduced CPU usage for compositing | |||
| 76 | + | - Better multi-threaded rendering | |||
| 77 | + | ||||
| 78 | + | **Fallback Strategy**: | |||
| 79 | + | - Detect GPU capabilities at runtime | |||
| 80 | + | - Use `ON_DEMAND` policy for automatic selection | |||
| 81 | + | - Provide config flag for manual override | |||
| 82 | + | - Monitor performance metrics | |||
| 83 | + | ||||
| 84 | + | ### Global Rendering Configuration | |||
| 85 | + | ||||
| 86 | + | **Decision: Add RenderingMode to config** | |||
| 87 | + | **Rationale**: User control over CPU/GPU rendering with smart defaults | |||
| 88 | + | ||||
| 89 | + | **Implementation**: | |||
| 90 | + | ```go | |||
| 91 | + | type RenderingMode string | |||
| 92 | + | ||||
| 93 | + | const ( | |||
| 94 | + | RenderingModeAuto RenderingMode = "auto" // Default, detect at runtime | |||
| 95 | + | RenderingModeGPU RenderingMode = "gpu" // Force GPU acceleration | |||
| 96 | + | RenderingModeCPU RenderingMode = "cpu" // Force software rendering | |||
| 97 | + | ) | |||
| 98 | + | ``` | |||
| 99 | + | ||||
| 100 | + | **Config Integration**: | |||
| 101 | + | - Add to `internal/config/config.go` | |||
| 102 | + | - Default to "auto" (GPU if available) | |||
| 103 | + | - CLI flag: `--rendering-mode=[auto|gpu|cpu]` | |||
| 104 | + | - Environment variable: `DUMBER_RENDERING_MODE` | |||
| 105 | + | ||||
| 106 | + | ## Summary | |||
| 107 | + | ||||
| 108 | + | The migration to WebKitGTK 6.0 requires: | |||
| 109 | + | 1. Complete GTK3 → GTK4 widget/event migration | |||
| 110 | + | 2. Update all WebKit API calls to new versions | |||
| 111 | + | 3. Implement hardware acceleration configuration | |||
| 112 | + | 4. Add global rendering mode configuration | |||
| 113 | + | 5. Test Vulkan renderer activation and fallback | |||
| 114 | + | ||||
| 115 | + | All technical questions have been resolved through research.⏎̸ | |||
A specs/004-migrate-to-webkitgtk/spec.md +111 -0
| @@ -0,0 +1,111 @@ | |||
| 1 | + | # Feature Specification: Migrate to WebKitGTK 6.0 | |
| 2 | + | ||
| 3 | + | **Feature Branch**: `004-migrate-to-webkitgtk` | |
| 4 | + | **Created**: 2025-01-21 | |
| 5 | + | **Status**: Draft | |
| 6 | + | **Input**: User description: "Migrate To webkitgtk-6.0, I Want to migrate the entire project to webkitgtk6 in order to be able to enable GPU rendering through vulkan layer" | |
| 7 | + | ||
| 8 | + | ## Execution Flow (main) | |
| 9 | + | ``` | |
| 10 | + | 1. Parse user description from Input | |
| 11 | + | → Migrate browser to WebKitGTK 6.0 for Vulkan GPU rendering | |
| 12 | + | 2. Extract key concepts from description | |
| 13 | + | → Identified: migration, WebKitGTK 6.0, GPU rendering, Vulkan support | |
| 14 | + | 3. For each unclear aspect: | |
| 15 | + | → Performance targets marked for clarification | |
| 16 | + | → Migration timeline marked for clarification | |
| 17 | + | 4. Fill User Scenarios & Testing section | |
| 18 | + | → User flow: Browser continues to work with improved GPU performance | |
| 19 | + | 5. Generate Functional Requirements | |
| 20 | + | → Each requirement is testable | |
| 21 | + | → Migration scope defined | |
| 22 | + | 6. Identify Key Entities | |
| 23 | + | → Browser engine, rendering pipeline, GPU acceleration | |
| 24 | + | 7. Run Review Checklist | |
| 25 | + | → WARN "Spec has uncertainties around performance targets" | |
| 26 | + | 8. Return: SUCCESS (spec ready for planning) | |
| 27 | + | ``` | |
| 28 | + | ||
| 29 | + | --- | |
| 30 | + | ||
| 31 | + | ## ⚡ Quick Guidelines | |
| 32 | + | - ✅ Focus on WHAT users need and WHY | |
| 33 | + | - ❌ Avoid HOW to implement (no tech stack, APIs, code structure) | |
| 34 | + | - 👥 Written for business stakeholders, not developers | |
| 35 | + | ||
| 36 | + | --- | |
| 37 | + | ||
| 38 | + | ## User Scenarios & Testing | |
| 39 | + | ||
| 40 | + | ### Primary User Story | |
| 41 | + | As a user of the dumber browser, I want the browser to utilize modern GPU acceleration capabilities through Vulkan rendering, resulting in smoother scrolling, faster page rendering, and improved performance for graphics-intensive web content, while maintaining all existing browser functionality. | |
| 42 | + | ||
| 43 | + | ### Acceptance Scenarios | |
| 44 | + | 1. **Given** a user opens the browser, **When** they navigate to any website, **Then** the browser renders pages using GPU acceleration when available | |
| 45 | + | 2. **Given** a user visits a graphics-intensive website (e.g., with 3D content, animations), **When** the page loads, **Then** GPU rendering provides smooth performance without visual artifacts | |
| 46 | + | 3. **Given** a system with Vulkan-capable GPU, **When** the browser starts, **Then** it automatically detects and enables GPU acceleration | |
| 47 | + | 4. **Given** a system without Vulkan support, **When** the browser starts, **Then** it gracefully falls back to software rendering | |
| 48 | + | 5. **Given** the browser is using GPU acceleration, **When** monitoring system resources, **Then** CPU usage is reduced compared to software rendering | |
| 49 | + | ||
| 50 | + | ### Edge Cases | |
| 51 | + | - What happens when GPU drivers are outdated or incompatible? | |
| 52 | + | - How does system handle GPU driver crashes during browsing? | |
| 53 | + | - What happens on systems with multiple GPUs (integrated + discrete)? | |
| 54 | + | - How does browser perform on low-end GPUs vs high-end GPUs? | |
| 55 | + | - What happens when switching between GPU and software rendering mid-session? | |
| 56 | + | ||
| 57 | + | ## Requirements | |
| 58 | + | ||
| 59 | + | ### Functional Requirements | |
| 60 | + | - **FR-001**: System MUST maintain all existing browser functionality after migration | |
| 61 | + | - **FR-002**: System MUST support GPU-accelerated rendering when Vulkan-capable hardware is available | |
| 62 | + | - **FR-003**: System MUST provide fallback to software rendering when GPU acceleration is unavailable | |
| 63 | + | - **FR-004**: System MUST maintain backward compatibility with existing user data (bookmarks, history, settings) | |
| 64 | + | - **FR-005**: System MUST preserve current browser interface and user experience | |
| 65 | + | - **FR-006**: System MUST detect GPU capabilities at startup and choose appropriate rendering path | |
| 66 | + | - **FR-007**: System MUST handle GPU driver failures gracefully without crashing | |
| 67 | + | - **FR-008**: Browser performance MUST improve by [NEEDS CLARIFICATION: specific performance target not specified - 20%? 50%? What metrics?] | |
| 68 | + | - **FR-009**: System MUST support common web standards (HTML5, CSS3, JavaScript) without regression | |
| 69 | + | - **FR-010**: System MUST maintain support for all previously working websites | |
| 70 | + | - **FR-011**: GPU memory usage MUST remain within [NEEDS CLARIFICATION: acceptable limits not specified] | |
| 71 | + | - **FR-012**: Migration MUST be completed within [NEEDS CLARIFICATION: timeline not specified] | |
| 72 | + | ||
| 73 | + | ### Key Entities | |
| 74 | + | - **Browser Engine**: Core rendering engine that processes and displays web content | |
| 75 | + | - **GPU Rendering Pipeline**: System component responsible for hardware-accelerated graphics | |
| 76 | + | - **Fallback Renderer**: Software-based rendering system for non-GPU scenarios | |
| 77 | + | - **Performance Monitor**: Component tracking rendering performance and resource usage | |
| 78 | + | - **Compatibility Layer**: System ensuring existing functionality works with new rendering engine | |
| 79 | + | ||
| 80 | + | --- | |
| 81 | + | ||
| 82 | + | ## Review & Acceptance Checklist | |
| 83 | + | *GATE: Automated checks run during main() execution* | |
| 84 | + | ||
| 85 | + | ### Content Quality | |
| 86 | + | - [x] No implementation details (languages, frameworks, APIs) | |
| 87 | + | - [x] Focused on user value and business needs | |
| 88 | + | - [x] Written for non-technical stakeholders | |
| 89 | + | - [x] All mandatory sections completed | |
| 90 | + | ||
| 91 | + | ### Requirement Completeness | |
| 92 | + | - [ ] No [NEEDS CLARIFICATION] markers remain | |
| 93 | + | - [x] Requirements are testable and unambiguous (except marked items) | |
| 94 | + | - [ ] Success criteria are measurable (performance targets needed) | |
| 95 | + | - [x] Scope is clearly bounded | |
| 96 | + | - [x] Dependencies and assumptions identified | |
| 97 | + | ||
| 98 | + | --- | |
| 99 | + | ||
| 100 | + | ## Execution Status | |
| 101 | + | *Updated by main() during processing* | |
| 102 | + | ||
| 103 | + | - [x] User description parsed | |
| 104 | + | - [x] Key concepts extracted | |
| 105 | + | - [x] Ambiguities marked | |
| 106 | + | - [x] User scenarios defined | |
| 107 | + | - [x] Requirements generated | |
| 108 | + | - [x] Entities identified | |
| 109 | + | - [ ] Review checklist passed (has clarifications needed) | |
| 110 | + | ||
| 111 | + | ---⏎̸ | |
| @@ -0,0 +1,111 @@ | |||||
| 1 | + | # Feature Specification: Migrate to WebKitGTK 6.0 | |||
| 2 | + | ||||
| 3 | + | **Feature Branch**: `004-migrate-to-webkitgtk` | |||
| 4 | + | **Created**: 2025-01-21 | |||
| 5 | + | **Status**: Draft | |||
| 6 | + | **Input**: User description: "Migrate To webkitgtk-6.0, I Want to migrate the entire project to webkitgtk6 in order to be able to enable GPU rendering through vulkan layer" | |||
| 7 | + | ||||
| 8 | + | ## Execution Flow (main) | |||
| 9 | + | ``` | |||
| 10 | + | 1. Parse user description from Input | |||
| 11 | + | → Migrate browser to WebKitGTK 6.0 for Vulkan GPU rendering | |||
| 12 | + | 2. Extract key concepts from description | |||
| 13 | + | → Identified: migration, WebKitGTK 6.0, GPU rendering, Vulkan support | |||
| 14 | + | 3. For each unclear aspect: | |||
| 15 | + | → Performance targets marked for clarification | |||
| 16 | + | → Migration timeline marked for clarification | |||
| 17 | + | 4. Fill User Scenarios & Testing section | |||
| 18 | + | → User flow: Browser continues to work with improved GPU performance | |||
| 19 | + | 5. Generate Functional Requirements | |||
| 20 | + | → Each requirement is testable | |||
| 21 | + | → Migration scope defined | |||
| 22 | + | 6. Identify Key Entities | |||
| 23 | + | → Browser engine, rendering pipeline, GPU acceleration | |||
| 24 | + | 7. Run Review Checklist | |||
| 25 | + | → WARN "Spec has uncertainties around performance targets" | |||
| 26 | + | 8. Return: SUCCESS (spec ready for planning) | |||
| 27 | + | ``` | |||
| 28 | + | ||||
| 29 | + | --- | |||
| 30 | + | ||||
| 31 | + | ## ⚡ Quick Guidelines | |||
| 32 | + | - ✅ Focus on WHAT users need and WHY | |||
| 33 | + | - ❌ Avoid HOW to implement (no tech stack, APIs, code structure) | |||
| 34 | + | - 👥 Written for business stakeholders, not developers | |||
| 35 | + | ||||
| 36 | + | --- | |||
| 37 | + | ||||
| 38 | + | ## User Scenarios & Testing | |||
| 39 | + | ||||
| 40 | + | ### Primary User Story | |||
| 41 | + | As a user of the dumber browser, I want the browser to utilize modern GPU acceleration capabilities through Vulkan rendering, resulting in smoother scrolling, faster page rendering, and improved performance for graphics-intensive web content, while maintaining all existing browser functionality. | |||
| 42 | + | ||||
| 43 | + | ### Acceptance Scenarios | |||
| 44 | + | 1. **Given** a user opens the browser, **When** they navigate to any website, **Then** the browser renders pages using GPU acceleration when available | |||
| 45 | + | 2. **Given** a user visits a graphics-intensive website (e.g., with 3D content, animations), **When** the page loads, **Then** GPU rendering provides smooth performance without visual artifacts | |||
| 46 | + | 3. **Given** a system with Vulkan-capable GPU, **When** the browser starts, **Then** it automatically detects and enables GPU acceleration | |||
| 47 | + | 4. **Given** a system without Vulkan support, **When** the browser starts, **Then** it gracefully falls back to software rendering | |||
| 48 | + | 5. **Given** the browser is using GPU acceleration, **When** monitoring system resources, **Then** CPU usage is reduced compared to software rendering | |||
| 49 | + | ||||
| 50 | + | ### Edge Cases | |||
| 51 | + | - What happens when GPU drivers are outdated or incompatible? | |||
| 52 | + | - How does system handle GPU driver crashes during browsing? | |||
| 53 | + | - What happens on systems with multiple GPUs (integrated + discrete)? | |||
| 54 | + | - How does browser perform on low-end GPUs vs high-end GPUs? | |||
| 55 | + | - What happens when switching between GPU and software rendering mid-session? | |||
| 56 | + | ||||
| 57 | + | ## Requirements | |||
| 58 | + | ||||
| 59 | + | ### Functional Requirements | |||
| 60 | + | - **FR-001**: System MUST maintain all existing browser functionality after migration | |||
| 61 | + | - **FR-002**: System MUST support GPU-accelerated rendering when Vulkan-capable hardware is available | |||
| 62 | + | - **FR-003**: System MUST provide fallback to software rendering when GPU acceleration is unavailable | |||
| 63 | + | - **FR-004**: System MUST maintain backward compatibility with existing user data (bookmarks, history, settings) | |||
| 64 | + | - **FR-005**: System MUST preserve current browser interface and user experience | |||
| 65 | + | - **FR-006**: System MUST detect GPU capabilities at startup and choose appropriate rendering path | |||
| 66 | + | - **FR-007**: System MUST handle GPU driver failures gracefully without crashing | |||
| 67 | + | - **FR-008**: Browser performance MUST improve by [NEEDS CLARIFICATION: specific performance target not specified - 20%? 50%? What metrics?] | |||
| 68 | + | - **FR-009**: System MUST support common web standards (HTML5, CSS3, JavaScript) without regression | |||
| 69 | + | - **FR-010**: System MUST maintain support for all previously working websites | |||
| 70 | + | - **FR-011**: GPU memory usage MUST remain within [NEEDS CLARIFICATION: acceptable limits not specified] | |||
| 71 | + | - **FR-012**: Migration MUST be completed within [NEEDS CLARIFICATION: timeline not specified] | |||
| 72 | + | ||||
| 73 | + | ### Key Entities | |||
| 74 | + | - **Browser Engine**: Core rendering engine that processes and displays web content | |||
| 75 | + | - **GPU Rendering Pipeline**: System component responsible for hardware-accelerated graphics | |||
| 76 | + | - **Fallback Renderer**: Software-based rendering system for non-GPU scenarios | |||
| 77 | + | - **Performance Monitor**: Component tracking rendering performance and resource usage | |||
| 78 | + | - **Compatibility Layer**: System ensuring existing functionality works with new rendering engine | |||
| 79 | + | ||||
| 80 | + | --- | |||
| 81 | + | ||||
| 82 | + | ## Review & Acceptance Checklist | |||
| 83 | + | *GATE: Automated checks run during main() execution* | |||
| 84 | + | ||||
| 85 | + | ### Content Quality | |||
| 86 | + | - [x] No implementation details (languages, frameworks, APIs) | |||
| 87 | + | - [x] Focused on user value and business needs | |||
| 88 | + | - [x] Written for non-technical stakeholders | |||
| 89 | + | - [x] All mandatory sections completed | |||
| 90 | + | ||||
| 91 | + | ### Requirement Completeness | |||
| 92 | + | - [ ] No [NEEDS CLARIFICATION] markers remain | |||
| 93 | + | - [x] Requirements are testable and unambiguous (except marked items) | |||
| 94 | + | - [ ] Success criteria are measurable (performance targets needed) | |||
| 95 | + | - [x] Scope is clearly bounded | |||
| 96 | + | - [x] Dependencies and assumptions identified | |||
| 97 | + | ||||
| 98 | + | --- | |||
| 99 | + | ||||
| 100 | + | ## Execution Status | |||
| 101 | + | *Updated by main() during processing* | |||
| 102 | + | ||||
| 103 | + | - [x] User description parsed | |||
| 104 | + | - [x] Key concepts extracted | |||
| 105 | + | - [x] Ambiguities marked | |||
| 106 | + | - [x] User scenarios defined | |||
| 107 | + | - [x] Requirements generated | |||
| 108 | + | - [x] Entities identified | |||
| 109 | + | - [ ] Review checklist passed (has clarifications needed) | |||
| 110 | + | ||||
| 111 | + | ---⏎̸ | |||
A specs/004-migrate-to-webkitgtk/tasks.md +138 -0
| @@ -0,0 +1,138 @@ | |||
| 1 | + | # Tasks: Migrate to WebKitGTK 6.0 | |
| 2 | + | ||
| 3 | + | **Input**: Design documents from `/specs/004-migrate-to-webkitgtk/` | |
| 4 | + | **Prerequisites**: plan.md (required), research.md, data-model.md, contracts/ | |
| 5 | + | ||
| 6 | + | ## Execution Flow (main) | |
| 7 | + | ``` | |
| 8 | + | 1. Load plan.md from feature directory | |
| 9 | + | → Loaded: WebKitGTK 6.0 migration plan | |
| 10 | + | → Extract: Go 1.25.1, WebKitGTK 6.0, GTK4, no TDD | |
| 11 | + | 2. Load optional design documents: | |
| 12 | + | → data-model.md: RenderingConfig model | |
| 13 | + | → contracts/: webkit-api.yaml, rendering-config.yaml | |
| 14 | + | → research.md: API migration mappings | |
| 15 | + | 3. Generate tasks by category: | |
| 16 | + | → Setup: Build config, dependencies | |
| 17 | + | → Core: Migrate webkit files incrementally | |
| 18 | + | → Integration: Rendering config, CLI flags | |
| 19 | + | → Verification: Test GPU acceleration | |
| 20 | + | 4. Apply task rules: | |
| 21 | + | → Different files = mark [P] for parallel | |
| 22 | + | → Same file = sequential (no [P]) | |
| 23 | + | → No TDD per user request | |
| 24 | + | 5. Number tasks sequentially (T001, T002...) | |
| 25 | + | 6. Generate dependency graph | |
| 26 | + | 7. Create parallel execution examples | |
| 27 | + | 8. Validate task completeness: | |
| 28 | + | → All webkit files migrated? | |
| 29 | + | → Rendering config implemented? | |
| 30 | + | → GPU acceleration verified? | |
| 31 | + | 9. Return: SUCCESS (tasks ready for execution) | |
| 32 | + | ``` | |
| 33 | + | ||
| 34 | + | ## Format: `[ID] [P?] Description` | |
| 35 | + | - **[P]**: Can run in parallel (different files, no dependencies) | |
| 36 | + | - Include exact file paths in descriptions | |
| 37 | + | ||
| 38 | + | ## Path Conventions | |
| 39 | + | - **Single project**: `pkg/webkit/` for WebKit bindings | |
| 40 | + | - **Config**: `internal/config/` for rendering configuration | |
| 41 | + | - **CLI**: `internal/cli/` for command-line flags | |
| 42 | + | ||
| 43 | + | ## Phase 3.1: Setup & Build Configuration | |
| 44 | + | - [ ] T001 Update pkg-config in all CGO files from webkit2gtk-4.0 to webkitgtk-6.0 | |
| 45 | + | - [ ] T002 Update GTK pkg-config from gtk+-3.0 to gtk4 in all CGO files | |
| 46 | + | - [ ] T003 Update JavaScript pkg-config from javascriptcoregtk-4.0 to javascriptcoregtk-6.0 | |
| 47 | + | - [ ] T004 Update Makefile build flags for GTK4/WebKitGTK 6.0 | |
| 48 | + | - [ ] T005 Verify build environment has GTK4 and WebKitGTK 6.0 installed | |
| 49 | + | ||
| 50 | + | ## Phase 3.2: Rendering Configuration | |
| 51 | + | - [ ] T006 [P] Add RenderingConfig struct to pkg/webkit/settings.go | |
| 52 | + | - [ ] T007 [P] Add RenderingMode constants and validation to internal/config/config.go | |
| 53 | + | - [ ] T008 [P] Add --rendering-mode CLI flag to internal/cli/browse.go | |
| 54 | + | - [ ] T009 [P] Add DUMBER_RENDERING_MODE environment variable support to internal/config/config.go | |
| 55 | + | ||
| 56 | + | ## Phase 3.3: Core WebKit Migration - Build Files | |
| 57 | + | - [ ] T010 [P] Migrate pkg/webkit/build_flags_cgo.go to WebKitGTK 6.0 headers | |
| 58 | + | - [ ] T011 [P] Migrate pkg/webkit/build_cgo_stub.go build tags for GTK4 | |
| 59 | + | - [ ] T012 [P] Update pkg/webkit/build_flags_stub.go for new build configuration | |
| 60 | + | ||
| 61 | + | ## Phase 3.4: Core WebKit Migration - Main Components | |
| 62 | + | - [ ] T013 Migrate pkg/webkit/webview_cgo.go - Update WebView creation to use g_object_new instead of webkit_web_view_new_with_context | |
| 63 | + | - [ ] T014 Migrate pkg/webkit/webview_cgo.go - Replace WebKitWebContext with WebKitNetworkSession | |
| 64 | + | - [ ] T015 Migrate pkg/webkit/webview_cgo.go - Add hardware acceleration policy settings | |
| 65 | + | - [ ] T016 Migrate pkg/webkit/window_cgo.go - Update gtk_window_new() without GTK_WINDOW_TOPLEVEL | |
| 66 | + | - [ ] T017 Migrate pkg/webkit/window_cgo.go - Replace gtk_container_add with gtk_window_set_child | |
| 67 | + | - [ ] T018 Migrate pkg/webkit/window_cgo.go - Update gtk_widget_destroy to gtk_window_destroy | |
| 68 | + | ||
| 69 | + | ## Phase 3.5: Core WebKit Migration - Event Handling | |
| 70 | + | - [ ] T019 Migrate pkg/webkit/keyboard_cgo.go - Replace GdkEventKey with GtkEventController | |
| 71 | + | - [ ] T020 Update pkg/webkit/keyboard_cgo.go - Migrate key-press-event to key-pressed signal | |
| 72 | + | - [ ] T021 Migrate button-press-event to GtkGestureClick in pkg/webkit/webview_cgo.go | |
| 73 | + | ||
| 74 | + | ## Phase 3.6: Feature Migration | |
| 75 | + | - [ ] T022 [P] Update pkg/webkit/script_cgo.go for GTK4 compatibility | |
| 76 | + | - [ ] T023 [P] Update pkg/webkit/zoom_cgo.go for GTK4 compatibility | |
| 77 | + | - [ ] T024 [P] Update pkg/webkit/devtools_cgo.go for GTK4 compatibility | |
| 78 | + | - [ ] T025 [P] Update pkg/webkit/loop_cgo.go for GTK4 main loop | |
| 79 | + | - [ ] T026 [P] Update pkg/webkit/scheme_cgo.go for new URI scheme handling | |
| 80 | + | ||
| 81 | + | ## Phase 3.7: Integration & Services | |
| 82 | + | - [ ] T027 Update services/browser_service.go to pass RenderingConfig to WebView | |
| 83 | + | - [ ] T028 Update services/config_service.go to handle rendering mode configuration | |
| 84 | + | - [ ] T029 Add GPU detection logic to determine auto mode in pkg/webkit/webview.go | |
| 85 | + | ||
| 86 | + | ## Phase 3.8: Testing & Verification | |
| 87 | + | - [ ] T030 Create test for GPU acceleration detection in tests/integration/test_gpu_rendering.go | |
| 88 | + | - [ ] T031 Create test for CPU fallback in tests/integration/test_cpu_fallback.go | |
| 89 | + | - [ ] T032 Update existing integration tests for GTK4 compatibility | |
| 90 | + | - [ ] T033 Manual test: Verify browser starts with GTK4/WebKitGTK 6.0 | |
| 91 | + | - [ ] T034 Manual test: Verify GPU acceleration with WebGL content | |
| 92 | + | - [ ] T035 Manual test: Verify CPU fallback with --rendering-mode=cpu | |
| 93 | + | ||
| 94 | + | ## Phase 3.9: Documentation & Cleanup | |
| 95 | + | - [ ] T036 [P] Update README.md with new build requirements | |
| 96 | + | - [ ] T037 [P] Document rendering mode configuration in docs/ | |
| 97 | + | - [ ] T038 Remove deprecated WebKit2GTK 4.0 compatibility code | |
| 98 | + | - [ ] T039 Update CLAUDE.md to reflect completed migration | |
| 99 | + | ||
| 100 | + | ## Dependencies | |
| 101 | + | - Build config (T001-T005) must complete first | |
| 102 | + | - Rendering config (T006-T009) can run in parallel | |
| 103 | + | - Core migration (T010-T026) depends on build config | |
| 104 | + | - T013-T015 must be sequential (same file) | |
| 105 | + | - T016-T018 must be sequential (same file) | |
| 106 | + | - Integration (T027-T029) depends on core migration | |
| 107 | + | - Testing (T030-T035) depends on all implementation | |
| 108 | + | - Documentation (T036-T039) can start anytime | |
| 109 | + | ||
| 110 | + | ## Parallel Example | |
| 111 | + | ```bash | |
| 112 | + | # Launch T006-T009 together (different files): | |
| 113 | + | Task: "Add RenderingConfig struct to pkg/webkit/settings.go" | |
| 114 | + | Task: "Add RenderingMode constants to internal/config/config.go" | |
| 115 | + | Task: "Add --rendering-mode CLI flag to internal/cli/browse.go" | |
| 116 | + | Task: "Add DUMBER_RENDERING_MODE env var to internal/config/config.go" | |
| 117 | + | ||
| 118 | + | # Launch T022-T026 together (independent files): | |
| 119 | + | Task: "Update pkg/webkit/script_cgo.go for GTK4" | |
| 120 | + | Task: "Update pkg/webkit/zoom_cgo.go for GTK4" | |
| 121 | + | Task: "Update pkg/webkit/devtools_cgo.go for GTK4" | |
| 122 | + | Task: "Update pkg/webkit/loop_cgo.go for GTK4" | |
| 123 | + | Task: "Update pkg/webkit/scheme_cgo.go for URI handling" | |
| 124 | + | ``` | |
| 125 | + | ||
| 126 | + | ## Notes | |
| 127 | + | - No TDD per user request - migrate first, test after | |
| 128 | + | - Focus on incremental file-by-file migration | |
| 129 | + | - Test GPU acceleration after each major component | |
| 130 | + | - Commit after each task to track progress | |
| 131 | + | - Use quickstart.md for verification steps | |
| 132 | + | ||
| 133 | + | ## Critical Migration Points | |
| 134 | + | 1. **WebView Creation**: Must update to g_object_new pattern | |
| 135 | + | 2. **Network Session**: Replace all WebContext usage | |
| 136 | + | 3. **Event Handling**: GTK4 uses controllers not direct events | |
| 137 | + | 4. **Container API**: gtk_container_add no longer exists | |
| 138 | + | 5. **Hardware Acceleration**: Must configure policy correctly⏎̸ | |
| @@ -0,0 +1,138 @@ | |||||
| 1 | + | # Tasks: Migrate to WebKitGTK 6.0 | |||
| 2 | + | ||||
| 3 | + | **Input**: Design documents from `/specs/004-migrate-to-webkitgtk/` | |||
| 4 | + | **Prerequisites**: plan.md (required), research.md, data-model.md, contracts/ | |||
| 5 | + | ||||
| 6 | + | ## Execution Flow (main) | |||
| 7 | + | ``` | |||
| 8 | + | 1. Load plan.md from feature directory | |||
| 9 | + | → Loaded: WebKitGTK 6.0 migration plan | |||
| 10 | + | → Extract: Go 1.25.1, WebKitGTK 6.0, GTK4, no TDD | |||
| 11 | + | 2. Load optional design documents: | |||
| 12 | + | → data-model.md: RenderingConfig model | |||
| 13 | + | → contracts/: webkit-api.yaml, rendering-config.yaml | |||
| 14 | + | → research.md: API migration mappings | |||
| 15 | + | 3. Generate tasks by category: | |||
| 16 | + | → Setup: Build config, dependencies | |||
| 17 | + | → Core: Migrate webkit files incrementally | |||
| 18 | + | → Integration: Rendering config, CLI flags | |||
| 19 | + | → Verification: Test GPU acceleration | |||
| 20 | + | 4. Apply task rules: | |||
| 21 | + | → Different files = mark [P] for parallel | |||
| 22 | + | → Same file = sequential (no [P]) | |||
| 23 | + | → No TDD per user request | |||
| 24 | + | 5. Number tasks sequentially (T001, T002...) | |||
| 25 | + | 6. Generate dependency graph | |||
| 26 | + | 7. Create parallel execution examples | |||
| 27 | + | 8. Validate task completeness: | |||
| 28 | + | → All webkit files migrated? | |||
| 29 | + | → Rendering config implemented? | |||
| 30 | + | → GPU acceleration verified? | |||
| 31 | + | 9. Return: SUCCESS (tasks ready for execution) | |||
| 32 | + | ``` | |||
| 33 | + | ||||
| 34 | + | ## Format: `[ID] [P?] Description` | |||
| 35 | + | - **[P]**: Can run in parallel (different files, no dependencies) | |||
| 36 | + | - Include exact file paths in descriptions | |||
| 37 | + | ||||
| 38 | + | ## Path Conventions | |||
| 39 | + | - **Single project**: `pkg/webkit/` for WebKit bindings | |||
| 40 | + | - **Config**: `internal/config/` for rendering configuration | |||
| 41 | + | - **CLI**: `internal/cli/` for command-line flags | |||
| 42 | + | ||||
| 43 | + | ## Phase 3.1: Setup & Build Configuration | |||
| 44 | + | - [ ] T001 Update pkg-config in all CGO files from webkit2gtk-4.0 to webkitgtk-6.0 | |||
| 45 | + | - [ ] T002 Update GTK pkg-config from gtk+-3.0 to gtk4 in all CGO files | |||
| 46 | + | - [ ] T003 Update JavaScript pkg-config from javascriptcoregtk-4.0 to javascriptcoregtk-6.0 | |||
| 47 | + | - [ ] T004 Update Makefile build flags for GTK4/WebKitGTK 6.0 | |||
| 48 | + | - [ ] T005 Verify build environment has GTK4 and WebKitGTK 6.0 installed | |||
| 49 | + | ||||
| 50 | + | ## Phase 3.2: Rendering Configuration | |||
| 51 | + | - [ ] T006 [P] Add RenderingConfig struct to pkg/webkit/settings.go | |||
| 52 | + | - [ ] T007 [P] Add RenderingMode constants and validation to internal/config/config.go | |||
| 53 | + | - [ ] T008 [P] Add --rendering-mode CLI flag to internal/cli/browse.go | |||
| 54 | + | - [ ] T009 [P] Add DUMBER_RENDERING_MODE environment variable support to internal/config/config.go | |||
| 55 | + | ||||
| 56 | + | ## Phase 3.3: Core WebKit Migration - Build Files | |||
| 57 | + | - [ ] T010 [P] Migrate pkg/webkit/build_flags_cgo.go to WebKitGTK 6.0 headers | |||
| 58 | + | - [ ] T011 [P] Migrate pkg/webkit/build_cgo_stub.go build tags for GTK4 | |||
| 59 | + | - [ ] T012 [P] Update pkg/webkit/build_flags_stub.go for new build configuration | |||
| 60 | + | ||||
| 61 | + | ## Phase 3.4: Core WebKit Migration - Main Components | |||
| 62 | + | - [ ] T013 Migrate pkg/webkit/webview_cgo.go - Update WebView creation to use g_object_new instead of webkit_web_view_new_with_context | |||
| 63 | + | - [ ] T014 Migrate pkg/webkit/webview_cgo.go - Replace WebKitWebContext with WebKitNetworkSession | |||
| 64 | + | - [ ] T015 Migrate pkg/webkit/webview_cgo.go - Add hardware acceleration policy settings | |||
| 65 | + | - [ ] T016 Migrate pkg/webkit/window_cgo.go - Update gtk_window_new() without GTK_WINDOW_TOPLEVEL | |||
| 66 | + | - [ ] T017 Migrate pkg/webkit/window_cgo.go - Replace gtk_container_add with gtk_window_set_child | |||
| 67 | + | - [ ] T018 Migrate pkg/webkit/window_cgo.go - Update gtk_widget_destroy to gtk_window_destroy | |||
| 68 | + | ||||
| 69 | + | ## Phase 3.5: Core WebKit Migration - Event Handling | |||
| 70 | + | - [ ] T019 Migrate pkg/webkit/keyboard_cgo.go - Replace GdkEventKey with GtkEventController | |||
| 71 | + | - [ ] T020 Update pkg/webkit/keyboard_cgo.go - Migrate key-press-event to key-pressed signal | |||
| 72 | + | - [ ] T021 Migrate button-press-event to GtkGestureClick in pkg/webkit/webview_cgo.go | |||
| 73 | + | ||||
| 74 | + | ## Phase 3.6: Feature Migration | |||
| 75 | + | - [ ] T022 [P] Update pkg/webkit/script_cgo.go for GTK4 compatibility | |||
| 76 | + | - [ ] T023 [P] Update pkg/webkit/zoom_cgo.go for GTK4 compatibility | |||
| 77 | + | - [ ] T024 [P] Update pkg/webkit/devtools_cgo.go for GTK4 compatibility | |||
| 78 | + | - [ ] T025 [P] Update pkg/webkit/loop_cgo.go for GTK4 main loop | |||
| 79 | + | - [ ] T026 [P] Update pkg/webkit/scheme_cgo.go for new URI scheme handling | |||
| 80 | + | ||||
| 81 | + | ## Phase 3.7: Integration & Services | |||
| 82 | + | - [ ] T027 Update services/browser_service.go to pass RenderingConfig to WebView | |||
| 83 | + | - [ ] T028 Update services/config_service.go to handle rendering mode configuration | |||
| 84 | + | - [ ] T029 Add GPU detection logic to determine auto mode in pkg/webkit/webview.go | |||
| 85 | + | ||||
| 86 | + | ## Phase 3.8: Testing & Verification | |||
| 87 | + | - [ ] T030 Create test for GPU acceleration detection in tests/integration/test_gpu_rendering.go | |||
| 88 | + | - [ ] T031 Create test for CPU fallback in tests/integration/test_cpu_fallback.go | |||
| 89 | + | - [ ] T032 Update existing integration tests for GTK4 compatibility | |||
| 90 | + | - [ ] T033 Manual test: Verify browser starts with GTK4/WebKitGTK 6.0 | |||
| 91 | + | - [ ] T034 Manual test: Verify GPU acceleration with WebGL content | |||
| 92 | + | - [ ] T035 Manual test: Verify CPU fallback with --rendering-mode=cpu | |||
| 93 | + | ||||
| 94 | + | ## Phase 3.9: Documentation & Cleanup | |||
| 95 | + | - [ ] T036 [P] Update README.md with new build requirements | |||
| 96 | + | - [ ] T037 [P] Document rendering mode configuration in docs/ | |||
| 97 | + | - [ ] T038 Remove deprecated WebKit2GTK 4.0 compatibility code | |||
| 98 | + | - [ ] T039 Update CLAUDE.md to reflect completed migration | |||
| 99 | + | ||||
| 100 | + | ## Dependencies | |||
| 101 | + | - Build config (T001-T005) must complete first | |||
| 102 | + | - Rendering config (T006-T009) can run in parallel | |||
| 103 | + | - Core migration (T010-T026) depends on build config | |||
| 104 | + | - T013-T015 must be sequential (same file) | |||
| 105 | + | - T016-T018 must be sequential (same file) | |||
| 106 | + | - Integration (T027-T029) depends on core migration | |||
| 107 | + | - Testing (T030-T035) depends on all implementation | |||
| 108 | + | - Documentation (T036-T039) can start anytime | |||
| 109 | + | ||||
| 110 | + | ## Parallel Example | |||
| 111 | + | ```bash | |||
| 112 | + | # Launch T006-T009 together (different files): | |||
| 113 | + | Task: "Add RenderingConfig struct to pkg/webkit/settings.go" | |||
| 114 | + | Task: "Add RenderingMode constants to internal/config/config.go" | |||
| 115 | + | Task: "Add --rendering-mode CLI flag to internal/cli/browse.go" | |||
| 116 | + | Task: "Add DUMBER_RENDERING_MODE env var to internal/config/config.go" | |||
| 117 | + | ||||
| 118 | + | # Launch T022-T026 together (independent files): | |||
| 119 | + | Task: "Update pkg/webkit/script_cgo.go for GTK4" | |||
| 120 | + | Task: "Update pkg/webkit/zoom_cgo.go for GTK4" | |||
| 121 | + | Task: "Update pkg/webkit/devtools_cgo.go for GTK4" | |||
| 122 | + | Task: "Update pkg/webkit/loop_cgo.go for GTK4" | |||
| 123 | + | Task: "Update pkg/webkit/scheme_cgo.go for URI handling" | |||
| 124 | + | ``` | |||
| 125 | + | ||||
| 126 | + | ## Notes | |||
| 127 | + | - No TDD per user request - migrate first, test after | |||
| 128 | + | - Focus on incremental file-by-file migration | |||
| 129 | + | - Test GPU acceleration after each major component | |||
| 130 | + | - Commit after each task to track progress | |||
| 131 | + | - Use quickstart.md for verification steps | |||
| 132 | + | ||||
| 133 | + | ## Critical Migration Points | |||
| 134 | + | 1. **WebView Creation**: Must update to g_object_new pattern | |||
| 135 | + | 2. **Network Session**: Replace all WebContext usage | |||
| 136 | + | 3. **Event Handling**: GTK4 uses controllers not direct events | |||
| 137 | + | 4. **Container API**: gtk_container_add no longer exists | |||
| 138 | + | 5. **Hardware Acceleration**: Must configure policy correctly⏎̸ | |||