refactor(bootstrap): wire ColorSchemeResolver through initialization, enable adwaita detector after adw.Init()
- commit
- 82506a7499a8fccaadc878aedbdeb546e8209d72
- author
- bnema · · 2025-12-29 20:51 UTC
- parent
- 2553067
- signature
- GPG Unverified
2B82207D370127A0The key is unknown
5 files · +167 -77
M cmd/dumber/main.go +25 -7
| @@ -18,6 +18,7 @@ import ( | |||
| 18 | 18 | "github.com/bnema/dumber/internal/domain/repository" | |
| 19 | 19 | "github.com/bnema/dumber/internal/infrastructure/cache" | |
| 20 | 20 | "github.com/bnema/dumber/internal/infrastructure/clipboard" | |
| 21 | + | "github.com/bnema/dumber/internal/infrastructure/colorscheme" | |
| 21 | 22 | "github.com/bnema/dumber/internal/infrastructure/config" | |
| 22 | 23 | "github.com/bnema/dumber/internal/infrastructure/deps" | |
| 23 | 24 | "github.com/bnema/dumber/internal/infrastructure/favicon" | |
| @@ -130,7 +131,11 @@ func runGUI() int { | |||
| 130 | 131 | timer.Mark("use_cases") | |
| 131 | 132 | ||
| 132 | 133 | // Build UI | |
| 133 | - | uiDeps := buildUIDependencies(ctx, cfg, initResult.ThemeManager, &stack, repos, useCases, idleInhibitor, browserSession.Session.ID) | |
| 134 | + | uiDeps := buildUIDependencies( | |
| 135 | + | ctx, cfg, initResult.ThemeManager, | |
| 136 | + | initResult.ColorResolver, initResult.AdwaitaDetector, | |
| 137 | + | &stack, repos, useCases, idleInhibitor, browserSession.Session.ID, | |
| 138 | + | ) | |
| 134 | 139 | configureDeferredInit(uiDeps, cfg, browserSession) | |
| 135 | 140 | app, err := ui.New(uiDeps) | |
| 136 | 141 | if err != nil { | |
| @@ -171,11 +176,12 @@ func initStackAndRepos( | |||
| 171 | 176 | if needsEagerDB { | |
| 172 | 177 | // Parallel phase 2: Database + WebKit stack initialize concurrently | |
| 173 | 178 | dbWebKit, err := bootstrap.RunParallelDBWebKit(bootstrap.ParallelDBWebKitInput{ | |
| 174 | - | Ctx: ctx, | |
| 175 | - | Config: cfg, | |
| 176 | - | DataDir: initResult.DataDir, | |
| 177 | - | CacheDir: initResult.CacheDir, | |
| 178 | - | ThemeManager: initResult.ThemeManager, | |
| 179 | + | Ctx: ctx, | |
| 180 | + | Config: cfg, | |
| 181 | + | DataDir: initResult.DataDir, | |
| 182 | + | CacheDir: initResult.CacheDir, | |
| 183 | + | ThemeManager: initResult.ThemeManager, | |
| 184 | + | ColorResolver: initResult.ColorResolver, | |
| 179 | 185 | }) | |
| 180 | 186 | if err != nil { | |
| 181 | 187 | return bootstrap.WebKitStack{}, nil, nil, err | |
| @@ -184,7 +190,15 @@ func initStackAndRepos( | |||
| 184 | 190 | } | |
| 185 | 191 | ||
| 186 | 192 | log := logging.FromContext(ctx) | |
| 187 | - | stack := bootstrap.BuildWebKitStack(ctx, cfg, initResult.DataDir, initResult.CacheDir, initResult.ThemeManager, *log) | |
| 193 | + | stack := bootstrap.BuildWebKitStack(bootstrap.WebKitStackInput{ | |
| 194 | + | Ctx: ctx, | |
| 195 | + | Config: cfg, | |
| 196 | + | DataDir: initResult.DataDir, | |
| 197 | + | CacheDir: initResult.CacheDir, | |
| 198 | + | ThemeManager: initResult.ThemeManager, | |
| 199 | + | ColorResolver: initResult.ColorResolver, | |
| 200 | + | Logger: *log, | |
| 201 | + | }) | |
| 188 | 202 | ||
| 189 | 203 | lazyDB, err := bootstrap.CreateLazyDatabase() | |
| 190 | 204 | if err != nil { | |
| @@ -398,6 +412,8 @@ func buildUIDependencies( | |||
| 398 | 412 | ctx context.Context, | |
| 399 | 413 | cfg *config.Config, | |
| 400 | 414 | themeManager *theme.Manager, | |
| 415 | + | colorResolver port.ColorSchemeResolver, | |
| 416 | + | adwaitaDetector *colorscheme.AdwaitaDetector, | |
| 401 | 417 | stack *bootstrap.WebKitStack, | |
| 402 | 418 | repos *repositories, | |
| 403 | 419 | uc *useCases, | |
| @@ -410,6 +426,8 @@ func buildUIDependencies( | |||
| 410 | 426 | InitialURL: initialURL, | |
| 411 | 427 | RestoreSessionID: restoreSessionID, | |
| 412 | 428 | Theme: themeManager, | |
| 429 | + | ColorResolver: colorResolver, | |
| 430 | + | AdwaitaDetector: adwaitaDetector, | |
| 413 | 431 | WebContext: stack.Context, | |
| 414 | 432 | Pool: stack.Pool, | |
| 415 | 433 | Settings: stack.Settings, | |
| @@ -18,6 +18,7 @@ import ( | |||||
| 18 | "github.com/bnema/dumber/internal/domain/repository" | 18 | "github.com/bnema/dumber/internal/domain/repository" | ||
| 19 | "github.com/bnema/dumber/internal/infrastructure/cache" | 19 | "github.com/bnema/dumber/internal/infrastructure/cache" | ||
| 20 | "github.com/bnema/dumber/internal/infrastructure/clipboard" | 20 | "github.com/bnema/dumber/internal/infrastructure/clipboard" | ||
| 21 | + | "github.com/bnema/dumber/internal/infrastructure/colorscheme" | |||
| 21 | "github.com/bnema/dumber/internal/infrastructure/config" | 22 | "github.com/bnema/dumber/internal/infrastructure/config" | ||
| 22 | "github.com/bnema/dumber/internal/infrastructure/deps" | 23 | "github.com/bnema/dumber/internal/infrastructure/deps" | ||
| 23 | "github.com/bnema/dumber/internal/infrastructure/favicon" | 24 | "github.com/bnema/dumber/internal/infrastructure/favicon" | ||
| @@ -130,7 +131,11 @@ func runGUI() int { | |||||
| 130 | timer.Mark("use_cases") | 131 | timer.Mark("use_cases") | ||
| 131 | 132 | ||||
| 132 | // Build UI | 133 | // Build UI | ||
| 133 | - | uiDeps := buildUIDependencies(ctx, cfg, initResult.ThemeManager, &stack, repos, useCases, idleInhibitor, browserSession.Session.ID) | 134 | + | uiDeps := buildUIDependencies( |
| 135 | + | ctx, cfg, initResult.ThemeManager, | |||
| 136 | + | initResult.ColorResolver, initResult.AdwaitaDetector, | |||
| 137 | + | &stack, repos, useCases, idleInhibitor, browserSession.Session.ID, | |||
| 138 | + | ) | |||
| 134 | configureDeferredInit(uiDeps, cfg, browserSession) | 139 | configureDeferredInit(uiDeps, cfg, browserSession) | ||
| 135 | app, err := ui.New(uiDeps) | 140 | app, err := ui.New(uiDeps) | ||
| 136 | if err != nil { | 141 | if err != nil { | ||
| @@ -171,11 +176,12 @@ func initStackAndRepos( | |||||
| 171 | if needsEagerDB { | 176 | if needsEagerDB { | ||
| 172 | // Parallel phase 2: Database + WebKit stack initialize concurrently | 177 | // Parallel phase 2: Database + WebKit stack initialize concurrently | ||
| 173 | dbWebKit, err := bootstrap.RunParallelDBWebKit(bootstrap.ParallelDBWebKitInput{ | 178 | dbWebKit, err := bootstrap.RunParallelDBWebKit(bootstrap.ParallelDBWebKitInput{ | ||
| 174 | - | Ctx: ctx, | 179 | + | Ctx: ctx, |
| 175 | - | Config: cfg, | 180 | + | Config: cfg, |
| 176 | - | DataDir: initResult.DataDir, | 181 | + | DataDir: initResult.DataDir, |
| 177 | - | CacheDir: initResult.CacheDir, | 182 | + | CacheDir: initResult.CacheDir, |
| 178 | - | ThemeManager: initResult.ThemeManager, | 183 | + | ThemeManager: initResult.ThemeManager, |
| 184 | + | ColorResolver: initResult.ColorResolver, | |||
| 179 | }) | 185 | }) | ||
| 180 | if err != nil { | 186 | if err != nil { | ||
| 181 | return bootstrap.WebKitStack{}, nil, nil, err | 187 | return bootstrap.WebKitStack{}, nil, nil, err | ||
| @@ -184,7 +190,15 @@ func initStackAndRepos( | |||||
| 184 | } | 190 | } | ||
| 185 | 191 | ||||
| 186 | log := logging.FromContext(ctx) | 192 | log := logging.FromContext(ctx) | ||
| 187 | - | stack := bootstrap.BuildWebKitStack(ctx, cfg, initResult.DataDir, initResult.CacheDir, initResult.ThemeManager, *log) | 193 | + | stack := bootstrap.BuildWebKitStack(bootstrap.WebKitStackInput{ |
| 194 | + | Ctx: ctx, | |||
| 195 | + | Config: cfg, | |||
| 196 | + | DataDir: initResult.DataDir, | |||
| 197 | + | CacheDir: initResult.CacheDir, | |||
| 198 | + | ThemeManager: initResult.ThemeManager, | |||
| 199 | + | ColorResolver: initResult.ColorResolver, | |||
| 200 | + | Logger: *log, | |||
| 201 | + | }) | |||
| 188 | 202 | ||||
| 189 | lazyDB, err := bootstrap.CreateLazyDatabase() | 203 | lazyDB, err := bootstrap.CreateLazyDatabase() | ||
| 190 | if err != nil { | 204 | if err != nil { | ||
| @@ -398,6 +412,8 @@ func buildUIDependencies( | |||||
| 398 | ctx context.Context, | 412 | ctx context.Context, | ||
| 399 | cfg *config.Config, | 413 | cfg *config.Config, | ||
| 400 | themeManager *theme.Manager, | 414 | themeManager *theme.Manager, | ||
| 415 | + | colorResolver port.ColorSchemeResolver, | |||
| 416 | + | adwaitaDetector *colorscheme.AdwaitaDetector, | |||
| 401 | stack *bootstrap.WebKitStack, | 417 | stack *bootstrap.WebKitStack, | ||
| 402 | repos *repositories, | 418 | repos *repositories, | ||
| 403 | uc *useCases, | 419 | uc *useCases, | ||
| @@ -410,6 +426,8 @@ func buildUIDependencies( | |||||
| 410 | InitialURL: initialURL, | 426 | InitialURL: initialURL, | ||
| 411 | RestoreSessionID: restoreSessionID, | 427 | RestoreSessionID: restoreSessionID, | ||
| 412 | Theme: themeManager, | 428 | Theme: themeManager, | ||
| 429 | + | ColorResolver: colorResolver, | |||
| 430 | + | AdwaitaDetector: adwaitaDetector, | |||
| 413 | WebContext: stack.Context, | 431 | WebContext: stack.Context, | ||
| 414 | Pool: stack.Pool, | 432 | Pool: stack.Pool, | ||
| 415 | Settings: stack.Settings, | 433 | Settings: stack.Settings, | ||
M internal/bootstrap/gui.go +46 -17
| @@ -10,7 +10,9 @@ import ( | |||
| 10 | 10 | "sync" | |
| 11 | 11 | "time" | |
| 12 | 12 | ||
| 13 | + | "github.com/bnema/dumber/internal/application/port" | |
| 13 | 14 | "github.com/bnema/dumber/internal/application/usecase" | |
| 15 | + | "github.com/bnema/dumber/internal/infrastructure/colorscheme" | |
| 14 | 16 | "github.com/bnema/dumber/internal/infrastructure/config" | |
| 15 | 17 | "github.com/bnema/dumber/internal/infrastructure/deps" | |
| 16 | 18 | "github.com/bnema/dumber/internal/infrastructure/media" | |
| @@ -28,10 +30,12 @@ type DatabaseResult struct { | |||
| 28 | 30 | ||
| 29 | 31 | // ParallelInitResult holds the results of parallel initialization phase. | |
| 30 | 32 | type ParallelInitResult struct { | |
| 31 | - | DataDir string | |
| 32 | - | CacheDir string | |
| 33 | - | ThemeManager *theme.Manager | |
| 34 | - | Duration time.Duration | |
| 33 | + | DataDir string | |
| 34 | + | CacheDir string | |
| 35 | + | ThemeManager *theme.Manager | |
| 36 | + | ColorResolver port.ColorSchemeResolver | |
| 37 | + | AdwaitaDetector *colorscheme.AdwaitaDetector | |
| 38 | + | Duration time.Duration | |
| 35 | 39 | } | |
| 36 | 40 | ||
| 37 | 41 | // DeferredInitResult holds results from deferred initialization checks. | |
| @@ -84,17 +88,30 @@ func (e *RuntimeRequirementsError) LogDetails(ctx context.Context) { | |||
| 84 | 88 | } | |
| 85 | 89 | ||
| 86 | 90 | // RunParallelInit runs the essential parallel initialization phase. | |
| 87 | - | // This includes directory resolution and theme creation. | |
| 91 | + | // This includes directory resolution, color scheme resolver creation, and theme creation. | |
| 88 | 92 | // Returns the first fatal error encountered, or nil with the results. | |
| 89 | 93 | func RunParallelInit(input ParallelInitInput) (*ParallelInitResult, error) { | |
| 90 | 94 | var ( | |
| 91 | 95 | dataDir, cacheDir string | |
| 92 | - | themeManager *theme.Manager | |
| 93 | 96 | dirsErr error | |
| 94 | 97 | wg sync.WaitGroup | |
| 95 | 98 | ) | |
| 96 | 99 | ||
| 97 | 100 | start := time.Now() | |
| 101 | + | ||
| 102 | + | // Create color scheme resolver with initial detectors. | |
| 103 | + | // AdwaitaDetector is created but NOT marked available yet (requires adw.Init()). | |
| 104 | + | configAdapter := colorscheme.NewConfigAdapter(input.Config) | |
| 105 | + | resolver := colorscheme.NewResolver(configAdapter) | |
| 106 | + | ||
| 107 | + | // Register detectors that are available before GTK init | |
| 108 | + | resolver.RegisterDetector(colorscheme.NewEnvDetector()) | |
| 109 | + | resolver.RegisterDetector(colorscheme.NewGsettingsDetector()) | |
| 110 | + | ||
| 111 | + | // Create adwaita detector (will be marked available after adw.Init()) | |
| 112 | + | adwaitaDetector := colorscheme.NewAdwaitaDetector() | |
| 113 | + | resolver.RegisterDetector(adwaitaDetector) | |
| 114 | + | ||
| 98 | 115 | wg.Add(2) | |
| 99 | 116 | ||
| 100 | 117 | // Resolve directories | |
| @@ -104,9 +121,10 @@ func RunParallelInit(input ParallelInitInput) (*ParallelInitResult, error) { | |||
| 104 | 121 | }() | |
| 105 | 122 | ||
| 106 | 123 | // Theme manager (CPU-bound, no I/O) | |
| 124 | + | var themeManager *theme.Manager | |
| 107 | 125 | go func() { | |
| 108 | 126 | defer wg.Done() | |
| 109 | - | themeManager = theme.NewManager(input.Ctx, input.Config) | |
| 127 | + | themeManager = theme.NewManager(input.Ctx, input.Config, resolver) | |
| 110 | 128 | }() | |
| 111 | 129 | ||
| 112 | 130 | wg.Wait() | |
| @@ -118,10 +136,12 @@ func RunParallelInit(input ParallelInitInput) (*ParallelInitResult, error) { | |||
| 118 | 136 | } | |
| 119 | 137 | ||
| 120 | 138 | return &ParallelInitResult{ | |
| 121 | - | DataDir: dataDir, | |
| 122 | - | CacheDir: cacheDir, | |
| 123 | - | ThemeManager: themeManager, | |
| 124 | - | Duration: duration, | |
| 139 | + | DataDir: dataDir, | |
| 140 | + | CacheDir: cacheDir, | |
| 141 | + | ThemeManager: themeManager, | |
| 142 | + | ColorResolver: resolver, | |
| 143 | + | AdwaitaDetector: adwaitaDetector, | |
| 144 | + | Duration: duration, | |
| 125 | 145 | }, nil | |
| 126 | 146 | } | |
| 127 | 147 | ||
| @@ -253,11 +273,12 @@ type ParallelDBWebKitResult struct { | |||
| 253 | 273 | ||
| 254 | 274 | // ParallelDBWebKitInput holds inputs for parallel DB and WebKit initialization. | |
| 255 | 275 | type ParallelDBWebKitInput struct { | |
| 256 | - | Ctx context.Context | |
| 257 | - | Config *config.Config | |
| 258 | - | DataDir string // For WebKit context | |
| 259 | - | CacheDir string // For WebKit cache | |
| 260 | - | ThemeManager *theme.Manager | |
| 276 | + | Ctx context.Context | |
| 277 | + | Config *config.Config | |
| 278 | + | DataDir string // For WebKit context | |
| 279 | + | CacheDir string // For WebKit cache | |
| 280 | + | ThemeManager *theme.Manager | |
| 281 | + | ColorResolver port.ColorSchemeResolver | |
| 261 | 282 | } | |
| 262 | 283 | ||
| 263 | 284 | // Note: Database path is resolved via config.GetDatabaseFile() internally. | |
| @@ -291,7 +312,15 @@ func RunParallelDBWebKit(input ParallelDBWebKitInput) (*ParallelDBWebKitResult, | |||
| 291 | 312 | }() | |
| 292 | 313 | ||
| 293 | 314 | // WebKit stack on main thread (GTK requirement) | |
| 294 | - | stack := BuildWebKitStack(input.Ctx, input.Config, input.DataDir, input.CacheDir, input.ThemeManager, *log) | |
| 315 | + | stack := BuildWebKitStack(WebKitStackInput{ | |
| 316 | + | Ctx: input.Ctx, | |
| 317 | + | Config: input.Config, | |
| 318 | + | DataDir: input.DataDir, | |
| 319 | + | CacheDir: input.CacheDir, | |
| 320 | + | ThemeManager: input.ThemeManager, | |
| 321 | + | ColorResolver: input.ColorResolver, | |
| 322 | + | Logger: *log, | |
| 323 | + | }) | |
| 295 | 324 | ||
| 296 | 325 | // Wait for database | |
| 297 | 326 | dbRes := <-dbCh | |
| @@ -10,7 +10,9 @@ import ( | |||||
| 10 | "sync" | 10 | "sync" | ||
| 11 | "time" | 11 | "time" | ||
| 12 | 12 | ||||
| 13 | + | "github.com/bnema/dumber/internal/application/port" | |||
| 13 | "github.com/bnema/dumber/internal/application/usecase" | 14 | "github.com/bnema/dumber/internal/application/usecase" | ||
| 15 | + | "github.com/bnema/dumber/internal/infrastructure/colorscheme" | |||
| 14 | "github.com/bnema/dumber/internal/infrastructure/config" | 16 | "github.com/bnema/dumber/internal/infrastructure/config" | ||
| 15 | "github.com/bnema/dumber/internal/infrastructure/deps" | 17 | "github.com/bnema/dumber/internal/infrastructure/deps" | ||
| 16 | "github.com/bnema/dumber/internal/infrastructure/media" | 18 | "github.com/bnema/dumber/internal/infrastructure/media" | ||
| @@ -28,10 +30,12 @@ type DatabaseResult struct { | |||||
| 28 | 30 | ||||
| 29 | // ParallelInitResult holds the results of parallel initialization phase. | 31 | // ParallelInitResult holds the results of parallel initialization phase. | ||
| 30 | type ParallelInitResult struct { | 32 | type ParallelInitResult struct { | ||
| 31 | - | DataDir string | 33 | + | DataDir string |
| 32 | - | CacheDir string | 34 | + | CacheDir string |
| 33 | - | ThemeManager *theme.Manager | 35 | + | ThemeManager *theme.Manager |
| 34 | - | Duration time.Duration | 36 | + | ColorResolver port.ColorSchemeResolver |
| 37 | + | AdwaitaDetector *colorscheme.AdwaitaDetector | |||
| 38 | + | Duration time.Duration | |||
| 35 | } | 39 | } | ||
| 36 | 40 | ||||
| 37 | // DeferredInitResult holds results from deferred initialization checks. | 41 | // DeferredInitResult holds results from deferred initialization checks. | ||
| @@ -84,17 +88,30 @@ func (e *RuntimeRequirementsError) LogDetails(ctx context.Context) { | |||||
| 84 | } | 88 | } | ||
| 85 | 89 | ||||
| 86 | // RunParallelInit runs the essential parallel initialization phase. | 90 | // RunParallelInit runs the essential parallel initialization phase. | ||
| 87 | - | // This includes directory resolution and theme creation. | 91 | + | // This includes directory resolution, color scheme resolver creation, and theme creation. |
| 88 | // Returns the first fatal error encountered, or nil with the results. | 92 | // Returns the first fatal error encountered, or nil with the results. | ||
| 89 | func RunParallelInit(input ParallelInitInput) (*ParallelInitResult, error) { | 93 | func RunParallelInit(input ParallelInitInput) (*ParallelInitResult, error) { | ||
| 90 | var ( | 94 | var ( | ||
| 91 | dataDir, cacheDir string | 95 | dataDir, cacheDir string | ||
| 92 | - | themeManager *theme.Manager | |||
| 93 | dirsErr error | 96 | dirsErr error | ||
| 94 | wg sync.WaitGroup | 97 | wg sync.WaitGroup | ||
| 95 | ) | 98 | ) | ||
| 96 | 99 | ||||
| 97 | start := time.Now() | 100 | start := time.Now() | ||
| 101 | + | ||||
| 102 | + | // Create color scheme resolver with initial detectors. | |||
| 103 | + | // AdwaitaDetector is created but NOT marked available yet (requires adw.Init()). | |||
| 104 | + | configAdapter := colorscheme.NewConfigAdapter(input.Config) | |||
| 105 | + | resolver := colorscheme.NewResolver(configAdapter) | |||
| 106 | + | ||||
| 107 | + | // Register detectors that are available before GTK init | |||
| 108 | + | resolver.RegisterDetector(colorscheme.NewEnvDetector()) | |||
| 109 | + | resolver.RegisterDetector(colorscheme.NewGsettingsDetector()) | |||
| 110 | + | ||||
| 111 | + | // Create adwaita detector (will be marked available after adw.Init()) | |||
| 112 | + | adwaitaDetector := colorscheme.NewAdwaitaDetector() | |||
| 113 | + | resolver.RegisterDetector(adwaitaDetector) | |||
| 114 | + | ||||
| 98 | wg.Add(2) | 115 | wg.Add(2) | ||
| 99 | 116 | ||||
| 100 | // Resolve directories | 117 | // Resolve directories | ||
| @@ -104,9 +121,10 @@ func RunParallelInit(input ParallelInitInput) (*ParallelInitResult, error) { | |||||
| 104 | }() | 121 | }() | ||
| 105 | 122 | ||||
| 106 | // Theme manager (CPU-bound, no I/O) | 123 | // Theme manager (CPU-bound, no I/O) | ||
| 124 | + | var themeManager *theme.Manager | |||
| 107 | go func() { | 125 | go func() { | ||
| 108 | defer wg.Done() | 126 | defer wg.Done() | ||
| 109 | - | themeManager = theme.NewManager(input.Ctx, input.Config) | 127 | + | themeManager = theme.NewManager(input.Ctx, input.Config, resolver) |
| 110 | }() | 128 | }() | ||
| 111 | 129 | ||||
| 112 | wg.Wait() | 130 | wg.Wait() | ||
| @@ -118,10 +136,12 @@ func RunParallelInit(input ParallelInitInput) (*ParallelInitResult, error) { | |||||
| 118 | } | 136 | } | ||
| 119 | 137 | ||||
| 120 | return &ParallelInitResult{ | 138 | return &ParallelInitResult{ | ||
| 121 | - | DataDir: dataDir, | 139 | + | DataDir: dataDir, |
| 122 | - | CacheDir: cacheDir, | 140 | + | CacheDir: cacheDir, |
| 123 | - | ThemeManager: themeManager, | 141 | + | ThemeManager: themeManager, |
| 124 | - | Duration: duration, | 142 | + | ColorResolver: resolver, |
| 143 | + | AdwaitaDetector: adwaitaDetector, | |||
| 144 | + | Duration: duration, | |||
| 125 | }, nil | 145 | }, nil | ||
| 126 | } | 146 | } | ||
| 127 | 147 | ||||
| @@ -253,11 +273,12 @@ type ParallelDBWebKitResult struct { | |||||
| 253 | 273 | ||||
| 254 | // ParallelDBWebKitInput holds inputs for parallel DB and WebKit initialization. | 274 | // ParallelDBWebKitInput holds inputs for parallel DB and WebKit initialization. | ||
| 255 | type ParallelDBWebKitInput struct { | 275 | type ParallelDBWebKitInput struct { | ||
| 256 | - | Ctx context.Context | 276 | + | Ctx context.Context |
| 257 | - | Config *config.Config | 277 | + | Config *config.Config |
| 258 | - | DataDir string // For WebKit context | 278 | + | DataDir string // For WebKit context |
| 259 | - | CacheDir string // For WebKit cache | 279 | + | CacheDir string // For WebKit cache |
| 260 | - | ThemeManager *theme.Manager | 280 | + | ThemeManager *theme.Manager |
| 281 | + | ColorResolver port.ColorSchemeResolver | |||
| 261 | } | 282 | } | ||
| 262 | 283 | ||||
| 263 | // Note: Database path is resolved via config.GetDatabaseFile() internally. | 284 | // Note: Database path is resolved via config.GetDatabaseFile() internally. | ||
| @@ -291,7 +312,15 @@ func RunParallelDBWebKit(input ParallelDBWebKitInput) (*ParallelDBWebKitResult, | |||||
| 291 | }() | 312 | }() | ||
| 292 | 313 | ||||
| 293 | // WebKit stack on main thread (GTK requirement) | 314 | // WebKit stack on main thread (GTK requirement) | ||
| 294 | - | stack := BuildWebKitStack(input.Ctx, input.Config, input.DataDir, input.CacheDir, input.ThemeManager, *log) | 315 | + | stack := BuildWebKitStack(WebKitStackInput{ |
| 316 | + | Ctx: input.Ctx, | |||
| 317 | + | Config: input.Config, | |||
| 318 | + | DataDir: input.DataDir, | |||
| 319 | + | CacheDir: input.CacheDir, | |||
| 320 | + | ThemeManager: input.ThemeManager, | |||
| 321 | + | ColorResolver: input.ColorResolver, | |||
| 322 | + | Logger: *log, | |||
| 323 | + | }) | |||
| 295 | 324 | ||||
| 296 | // Wait for database | 325 | // Wait for database | ||
| 297 | dbRes := <-dbCh | 326 | dbRes := <-dbCh | ||
M internal/bootstrap/webkit_stack.go +69 -48
| @@ -15,6 +15,17 @@ import ( | |||
| 15 | 15 | "github.com/rs/zerolog" | |
| 16 | 16 | ) | |
| 17 | 17 | ||
| 18 | + | // WebKitStackInput holds the input for BuildWebKitStack. | |
| 19 | + | type WebKitStackInput struct { | |
| 20 | + | Ctx context.Context | |
| 21 | + | Config *config.Config | |
| 22 | + | DataDir string | |
| 23 | + | CacheDir string | |
| 24 | + | ThemeManager *theme.Manager | |
| 25 | + | ColorResolver port.ColorSchemeResolver | |
| 26 | + | Logger zerolog.Logger | |
| 27 | + | } | |
| 28 | + | ||
| 18 | 29 | type WebKitStack struct { | |
| 19 | 30 | Context *webkit.WebKitContext | |
| 20 | 31 | Settings *webkit.SettingsManager | |
| @@ -24,14 +35,14 @@ type WebKitStack struct { | |||
| 24 | 35 | FilterManager *filtering.Manager | |
| 25 | 36 | } | |
| 26 | 37 | ||
| 27 | - | func BuildWebKitStack( | |
| 28 | - | ctx context.Context, | |
| 29 | - | cfg *config.Config, | |
| 30 | - | dataDir string, | |
| 31 | - | cacheDir string, | |
| 32 | - | themeManager *theme.Manager, | |
| 33 | - | logger zerolog.Logger, | |
| 34 | - | ) WebKitStack { | |
| 38 | + | func BuildWebKitStack(input WebKitStackInput) WebKitStack { | |
| 39 | + | ctx := input.Ctx | |
| 40 | + | cfg := input.Config | |
| 41 | + | dataDir := input.DataDir | |
| 42 | + | cacheDir := input.CacheDir | |
| 43 | + | themeManager := input.ThemeManager | |
| 44 | + | colorResolver := input.ColorResolver | |
| 45 | + | logger := input.Logger | |
| 35 | 46 | // Detect hardware for performance profile scaling | |
| 36 | 47 | hwSurveyor := env.NewHardwareSurveyor() | |
| 37 | 48 | hwInfo := hwSurveyor.Survey(ctx) | |
| @@ -52,45 +63,8 @@ func BuildWebKitStack( | |||
| 52 | 63 | Int("webview_pool_prewarm", perfSettings.WebViewPoolPrewarmCount). | |
| 53 | 64 | Msg("resolved performance profile") | |
| 54 | 65 | ||
| 55 | - | // CRITICAL: Configure rendering environment BEFORE WebKit/GTK initialization. | |
| 56 | - | // Environment variables must be set before GTK/WebKit/GStreamer initializes. | |
| 57 | - | renderEnv := env.NewManager() | |
| 58 | - | gpuVendor := renderEnv.DetectGPUVendor(ctx) | |
| 59 | - | renderSettings := port.RenderingEnvSettings{ | |
| 60 | - | // GStreamer settings | |
| 61 | - | ForceVSync: cfg.Media.ForceVSync, | |
| 62 | - | GLRenderingMode: string(cfg.Media.GLRenderingMode), | |
| 63 | - | GStreamerDebugLevel: cfg.Media.GStreamerDebugLevel, | |
| 64 | - | VideoBufferSizeMB: cfg.Media.VideoBufferSizeMB, | |
| 65 | - | QueueBufferTimeSec: cfg.Media.QueueBufferTimeSec, | |
| 66 | - | ||
| 67 | - | // WebKit compositor settings | |
| 68 | - | DisableDMABufRenderer: cfg.Rendering.DisableDMABufRenderer, | |
| 69 | - | ForceCompositingMode: cfg.Rendering.ForceCompositingMode, | |
| 70 | - | DisableCompositingMode: cfg.Rendering.DisableCompositingMode, | |
| 71 | - | ||
| 72 | - | // GTK/GSK settings | |
| 73 | - | GSKRenderer: string(cfg.Rendering.GSKRenderer), | |
| 74 | - | DisableMipmaps: cfg.Rendering.DisableMipmaps, | |
| 75 | - | PreferGL: cfg.Rendering.PreferGL, | |
| 76 | - | ||
| 77 | - | // Debug settings | |
| 78 | - | ShowFPS: cfg.Rendering.ShowFPS, | |
| 79 | - | SampleMemory: cfg.Rendering.SampleMemory, | |
| 80 | - | DebugFrames: cfg.Rendering.DebugFrames, | |
| 81 | - | ||
| 82 | - | // Skia rendering thread settings (from resolved profile) | |
| 83 | - | SkiaCPUPaintingThreads: perfSettings.SkiaCPUPaintingThreads, | |
| 84 | - | SkiaGPUPaintingThreads: perfSettings.SkiaGPUPaintingThreads, | |
| 85 | - | SkiaEnableCPURendering: perfSettings.SkiaEnableCPURendering, | |
| 86 | - | } | |
| 87 | - | if err := renderEnv.ApplyEnvironment(ctx, renderSettings); err != nil { | |
| 88 | - | logger.Warn().Err(err).Msg("failed to apply rendering environment") | |
| 89 | - | } | |
| 90 | - | logger.Info(). | |
| 91 | - | Str("gpu", string(gpuVendor)). | |
| 92 | - | Interface("vars", renderEnv.GetAppliedVars()). | |
| 93 | - | Msg("rendering environment configured") | |
| 66 | + | // Configure rendering environment (must happen before WebKit/GTK init) | |
| 67 | + | configureRenderingEnvironment(ctx, cfg, &perfSettings, logger) | |
| 94 | 68 | ||
| 95 | 69 | // Build WebKitContext options with memory pressure settings | |
| 96 | 70 | wkOpts := port.WebKitContextOptions{ | |
| @@ -146,7 +120,7 @@ func BuildWebKitStack( | |||
| 146 | 120 | schemeHandler.RegisterWithContext(wkCtx) | |
| 147 | 121 | ||
| 148 | 122 | settings := webkit.NewSettingsManager(ctx, cfg) | |
| 149 | - | injector := webkit.NewContentInjector(themeManager.PrefersDark()) | |
| 123 | + | injector := webkit.NewContentInjector(colorResolver) | |
| 150 | 124 | ||
| 151 | 125 | prepareThemeUC := usecase.NewPrepareWebUIThemeUseCase(injector) | |
| 152 | 126 | themeCSSText := themeManager.GetWebUIThemeCSS() | |
| @@ -203,3 +177,50 @@ func hasNetworkProcessMemoryConfig(p *config.ResolvedPerformanceSettings) bool { | |||
| 203 | 177 | p.NetworkProcessMemoryStrictThreshold > 0 || | |
| 204 | 178 | p.NetworkProcessMemoryKillThreshold >= 0 | |
| 205 | 179 | } | |
| 180 | + | ||
| 181 | + | // configureRenderingEnvironment sets up the rendering environment before WebKit/GTK initialization. | |
| 182 | + | // Environment variables must be set before GTK/WebKit/GStreamer initializes. | |
| 183 | + | func configureRenderingEnvironment( | |
| 184 | + | ctx context.Context, | |
| 185 | + | cfg *config.Config, | |
| 186 | + | perfSettings *config.ResolvedPerformanceSettings, | |
| 187 | + | logger zerolog.Logger, | |
| 188 | + | ) { | |
| 189 | + | renderEnv := env.NewManager() | |
| 190 | + | gpuVendor := renderEnv.DetectGPUVendor(ctx) | |
| 191 | + | renderSettings := port.RenderingEnvSettings{ | |
| 192 | + | // GStreamer settings | |
| 193 | + | ForceVSync: cfg.Media.ForceVSync, | |
| 194 | + | GLRenderingMode: string(cfg.Media.GLRenderingMode), | |
| 195 | + | GStreamerDebugLevel: cfg.Media.GStreamerDebugLevel, | |
| 196 | + | VideoBufferSizeMB: cfg.Media.VideoBufferSizeMB, | |
| 197 | + | QueueBufferTimeSec: cfg.Media.QueueBufferTimeSec, | |
| 198 | + | ||
| 199 | + | // WebKit compositor settings | |
| 200 | + | DisableDMABufRenderer: cfg.Rendering.DisableDMABufRenderer, | |
| 201 | + | ForceCompositingMode: cfg.Rendering.ForceCompositingMode, | |
| 202 | + | DisableCompositingMode: cfg.Rendering.DisableCompositingMode, | |
| 203 | + | ||
| 204 | + | // GTK/GSK settings | |
| 205 | + | GSKRenderer: string(cfg.Rendering.GSKRenderer), | |
| 206 | + | DisableMipmaps: cfg.Rendering.DisableMipmaps, | |
| 207 | + | PreferGL: cfg.Rendering.PreferGL, | |
| 208 | + | ||
| 209 | + | // Debug settings | |
| 210 | + | ShowFPS: cfg.Rendering.ShowFPS, | |
| 211 | + | SampleMemory: cfg.Rendering.SampleMemory, | |
| 212 | + | DebugFrames: cfg.Rendering.DebugFrames, | |
| 213 | + | ||
| 214 | + | // Skia rendering thread settings (from resolved profile) | |
| 215 | + | SkiaCPUPaintingThreads: perfSettings.SkiaCPUPaintingThreads, | |
| 216 | + | SkiaGPUPaintingThreads: perfSettings.SkiaGPUPaintingThreads, | |
| 217 | + | SkiaEnableCPURendering: perfSettings.SkiaEnableCPURendering, | |
| 218 | + | } | |
| 219 | + | if err := renderEnv.ApplyEnvironment(ctx, renderSettings); err != nil { | |
| 220 | + | logger.Warn().Err(err).Msg("failed to apply rendering environment") | |
| 221 | + | } | |
| 222 | + | logger.Info(). | |
| 223 | + | Str("gpu", string(gpuVendor)). | |
| 224 | + | Interface("vars", renderEnv.GetAppliedVars()). | |
| 225 | + | Msg("rendering environment configured") | |
| 226 | + | } | |
| @@ -15,6 +15,17 @@ import ( | |||||
| 15 | "github.com/rs/zerolog" | 15 | "github.com/rs/zerolog" | ||
| 16 | ) | 16 | ) | ||
| 17 | 17 | ||||
| 18 | + | // WebKitStackInput holds the input for BuildWebKitStack. | |||
| 19 | + | type WebKitStackInput struct { | |||
| 20 | + | Ctx context.Context | |||
| 21 | + | Config *config.Config | |||
| 22 | + | DataDir string | |||
| 23 | + | CacheDir string | |||
| 24 | + | ThemeManager *theme.Manager | |||
| 25 | + | ColorResolver port.ColorSchemeResolver | |||
| 26 | + | Logger zerolog.Logger | |||
| 27 | + | } | |||
| 28 | + | ||||
| 18 | type WebKitStack struct { | 29 | type WebKitStack struct { | ||
| 19 | Context *webkit.WebKitContext | 30 | Context *webkit.WebKitContext | ||
| 20 | Settings *webkit.SettingsManager | 31 | Settings *webkit.SettingsManager | ||
| @@ -24,14 +35,14 @@ type WebKitStack struct { | |||||
| 24 | FilterManager *filtering.Manager | 35 | FilterManager *filtering.Manager | ||
| 25 | } | 36 | } | ||
| 26 | 37 | ||||
| 27 | - | func BuildWebKitStack( | 38 | + | func BuildWebKitStack(input WebKitStackInput) WebKitStack { |
| 28 | - | ctx context.Context, | 39 | + | ctx := input.Ctx |
| 29 | - | cfg *config.Config, | 40 | + | cfg := input.Config |
| 30 | - | dataDir string, | 41 | + | dataDir := input.DataDir |
| 31 | - | cacheDir string, | 42 | + | cacheDir := input.CacheDir |
| 32 | - | themeManager *theme.Manager, | 43 | + | themeManager := input.ThemeManager |
| 33 | - | logger zerolog.Logger, | 44 | + | colorResolver := input.ColorResolver |
| 34 | - | ) WebKitStack { | 45 | + | logger := input.Logger |
| 35 | // Detect hardware for performance profile scaling | 46 | // Detect hardware for performance profile scaling | ||
| 36 | hwSurveyor := env.NewHardwareSurveyor() | 47 | hwSurveyor := env.NewHardwareSurveyor() | ||
| 37 | hwInfo := hwSurveyor.Survey(ctx) | 48 | hwInfo := hwSurveyor.Survey(ctx) | ||
| @@ -52,45 +63,8 @@ func BuildWebKitStack( | |||||
| 52 | Int("webview_pool_prewarm", perfSettings.WebViewPoolPrewarmCount). | 63 | Int("webview_pool_prewarm", perfSettings.WebViewPoolPrewarmCount). | ||
| 53 | Msg("resolved performance profile") | 64 | Msg("resolved performance profile") | ||
| 54 | 65 | ||||
| 55 | - | // CRITICAL: Configure rendering environment BEFORE WebKit/GTK initialization. | 66 | + | // Configure rendering environment (must happen before WebKit/GTK init) |
| 56 | - | // Environment variables must be set before GTK/WebKit/GStreamer initializes. | 67 | + | configureRenderingEnvironment(ctx, cfg, &perfSettings, logger) |
| 57 | - | renderEnv := env.NewManager() | |||
| 58 | - | gpuVendor := renderEnv.DetectGPUVendor(ctx) | |||
| 59 | - | renderSettings := port.RenderingEnvSettings{ | |||
| 60 | - | // GStreamer settings | |||
| 61 | - | ForceVSync: cfg.Media.ForceVSync, | |||
| 62 | - | GLRenderingMode: string(cfg.Media.GLRenderingMode), | |||
| 63 | - | GStreamerDebugLevel: cfg.Media.GStreamerDebugLevel, | |||
| 64 | - | VideoBufferSizeMB: cfg.Media.VideoBufferSizeMB, | |||
| 65 | - | QueueBufferTimeSec: cfg.Media.QueueBufferTimeSec, | |||
| 66 | - | ||||
| 67 | - | // WebKit compositor settings | |||
| 68 | - | DisableDMABufRenderer: cfg.Rendering.DisableDMABufRenderer, | |||
| 69 | - | ForceCompositingMode: cfg.Rendering.ForceCompositingMode, | |||
| 70 | - | DisableCompositingMode: cfg.Rendering.DisableCompositingMode, | |||
| 71 | - | ||||
| 72 | - | // GTK/GSK settings | |||
| 73 | - | GSKRenderer: string(cfg.Rendering.GSKRenderer), | |||
| 74 | - | DisableMipmaps: cfg.Rendering.DisableMipmaps, | |||
| 75 | - | PreferGL: cfg.Rendering.PreferGL, | |||
| 76 | - | ||||
| 77 | - | // Debug settings | |||
| 78 | - | ShowFPS: cfg.Rendering.ShowFPS, | |||
| 79 | - | SampleMemory: cfg.Rendering.SampleMemory, | |||
| 80 | - | DebugFrames: cfg.Rendering.DebugFrames, | |||
| 81 | - | ||||
| 82 | - | // Skia rendering thread settings (from resolved profile) | |||
| 83 | - | SkiaCPUPaintingThreads: perfSettings.SkiaCPUPaintingThreads, | |||
| 84 | - | SkiaGPUPaintingThreads: perfSettings.SkiaGPUPaintingThreads, | |||
| 85 | - | SkiaEnableCPURendering: perfSettings.SkiaEnableCPURendering, | |||
| 86 | - | } | |||
| 87 | - | if err := renderEnv.ApplyEnvironment(ctx, renderSettings); err != nil { | |||
| 88 | - | logger.Warn().Err(err).Msg("failed to apply rendering environment") | |||
| 89 | - | } | |||
| 90 | - | logger.Info(). | |||
| 91 | - | Str("gpu", string(gpuVendor)). | |||
| 92 | - | Interface("vars", renderEnv.GetAppliedVars()). | |||
| 93 | - | Msg("rendering environment configured") | |||
| 94 | 68 | ||||
| 95 | // Build WebKitContext options with memory pressure settings | 69 | // Build WebKitContext options with memory pressure settings | ||
| 96 | wkOpts := port.WebKitContextOptions{ | 70 | wkOpts := port.WebKitContextOptions{ | ||
| @@ -146,7 +120,7 @@ func BuildWebKitStack( | |||||
| 146 | schemeHandler.RegisterWithContext(wkCtx) | 120 | schemeHandler.RegisterWithContext(wkCtx) | ||
| 147 | 121 | ||||
| 148 | settings := webkit.NewSettingsManager(ctx, cfg) | 122 | settings := webkit.NewSettingsManager(ctx, cfg) | ||
| 149 | - | injector := webkit.NewContentInjector(themeManager.PrefersDark()) | 123 | + | injector := webkit.NewContentInjector(colorResolver) |
| 150 | 124 | ||||
| 151 | prepareThemeUC := usecase.NewPrepareWebUIThemeUseCase(injector) | 125 | prepareThemeUC := usecase.NewPrepareWebUIThemeUseCase(injector) | ||
| 152 | themeCSSText := themeManager.GetWebUIThemeCSS() | 126 | themeCSSText := themeManager.GetWebUIThemeCSS() | ||
| @@ -203,3 +177,50 @@ func hasNetworkProcessMemoryConfig(p *config.ResolvedPerformanceSettings) bool { | |||||
| 203 | p.NetworkProcessMemoryStrictThreshold > 0 || | 177 | p.NetworkProcessMemoryStrictThreshold > 0 || | ||
| 204 | p.NetworkProcessMemoryKillThreshold >= 0 | 178 | p.NetworkProcessMemoryKillThreshold >= 0 | ||
| 205 | } | 179 | } | ||
| 180 | + | ||||
| 181 | + | // configureRenderingEnvironment sets up the rendering environment before WebKit/GTK initialization. | |||
| 182 | + | // Environment variables must be set before GTK/WebKit/GStreamer initializes. | |||
| 183 | + | func configureRenderingEnvironment( | |||
| 184 | + | ctx context.Context, | |||
| 185 | + | cfg *config.Config, | |||
| 186 | + | perfSettings *config.ResolvedPerformanceSettings, | |||
| 187 | + | logger zerolog.Logger, | |||
| 188 | + | ) { | |||
| 189 | + | renderEnv := env.NewManager() | |||
| 190 | + | gpuVendor := renderEnv.DetectGPUVendor(ctx) | |||
| 191 | + | renderSettings := port.RenderingEnvSettings{ | |||
| 192 | + | // GStreamer settings | |||
| 193 | + | ForceVSync: cfg.Media.ForceVSync, | |||
| 194 | + | GLRenderingMode: string(cfg.Media.GLRenderingMode), | |||
| 195 | + | GStreamerDebugLevel: cfg.Media.GStreamerDebugLevel, | |||
| 196 | + | VideoBufferSizeMB: cfg.Media.VideoBufferSizeMB, | |||
| 197 | + | QueueBufferTimeSec: cfg.Media.QueueBufferTimeSec, | |||
| 198 | + | ||||
| 199 | + | // WebKit compositor settings | |||
| 200 | + | DisableDMABufRenderer: cfg.Rendering.DisableDMABufRenderer, | |||
| 201 | + | ForceCompositingMode: cfg.Rendering.ForceCompositingMode, | |||
| 202 | + | DisableCompositingMode: cfg.Rendering.DisableCompositingMode, | |||
| 203 | + | ||||
| 204 | + | // GTK/GSK settings | |||
| 205 | + | GSKRenderer: string(cfg.Rendering.GSKRenderer), | |||
| 206 | + | DisableMipmaps: cfg.Rendering.DisableMipmaps, | |||
| 207 | + | PreferGL: cfg.Rendering.PreferGL, | |||
| 208 | + | ||||
| 209 | + | // Debug settings | |||
| 210 | + | ShowFPS: cfg.Rendering.ShowFPS, | |||
| 211 | + | SampleMemory: cfg.Rendering.SampleMemory, | |||
| 212 | + | DebugFrames: cfg.Rendering.DebugFrames, | |||
| 213 | + | ||||
| 214 | + | // Skia rendering thread settings (from resolved profile) | |||
| 215 | + | SkiaCPUPaintingThreads: perfSettings.SkiaCPUPaintingThreads, | |||
| 216 | + | SkiaGPUPaintingThreads: perfSettings.SkiaGPUPaintingThreads, | |||
| 217 | + | SkiaEnableCPURendering: perfSettings.SkiaEnableCPURendering, | |||
| 218 | + | } | |||
| 219 | + | if err := renderEnv.ApplyEnvironment(ctx, renderSettings); err != nil { | |||
| 220 | + | logger.Warn().Err(err).Msg("failed to apply rendering environment") | |||
| 221 | + | } | |||
| 222 | + | logger.Info(). | |||
| 223 | + | Str("gpu", string(gpuVendor)). | |||
| 224 | + | Interface("vars", renderEnv.GetAppliedVars()). | |||
| 225 | + | Msg("rendering environment configured") | |||
| 226 | + | } | |||
M internal/ui/app.go +22 -3
| @@ -160,6 +160,13 @@ func (a *App) Run(ctx context.Context, args []string) int { | |||
| 160 | 160 | // This also initializes GTK implicitly. | |
| 161 | 161 | adw.Init() | |
| 162 | 162 | ||
| 163 | + | // Mark adwaita detector as available now that adw.Init() is complete. | |
| 164 | + | // This enables the highest-priority color scheme detector. | |
| 165 | + | if a.deps != nil && a.deps.AdwaitaDetector != nil { | |
| 166 | + | a.deps.AdwaitaDetector.MarkAvailable() | |
| 167 | + | log.Debug().Msg("adwaita detector marked available") | |
| 168 | + | } | |
| 169 | + | ||
| 163 | 170 | // TODO: Use AppID once puregotk GC bug is fixed (nullable-string-gc-memory-corruption) | |
| 164 | 171 | a.gtkApp = gtk.NewApplication(nil, gio.GApplicationFlagsNoneValue) | |
| 165 | 172 | if a.gtkApp == nil { | |
| @@ -249,11 +256,24 @@ func (a *App) applyGTKColorSchemePreference(ctx context.Context) { | |||
| 249 | 256 | } | |
| 250 | 257 | ||
| 251 | 258 | styleMgr.SetColorScheme(adwScheme) | |
| 252 | - | prefersDark := styleMgr.GetDark() | |
| 259 | + | ||
| 260 | + | // Refresh the color resolver now that adwaita detector is available. | |
| 261 | + | // This ensures all components get the correct preference. | |
| 262 | + | var pref string | |
| 263 | + | var prefersDark bool | |
| 264 | + | if a.deps != nil && a.deps.ColorResolver != nil { | |
| 265 | + | result := a.deps.ColorResolver.Refresh() | |
| 266 | + | prefersDark = result.PrefersDark | |
| 267 | + | pref = result.Source | |
| 268 | + | } else { | |
| 269 | + | prefersDark = styleMgr.GetDark() | |
| 270 | + | pref = "adw.StyleManager" | |
| 271 | + | } | |
| 253 | 272 | ||
| 254 | 273 | log.Debug(). | |
| 255 | 274 | Str("scheme", scheme). | |
| 256 | 275 | Bool("prefers_dark", prefersDark). | |
| 276 | + | Str("source", pref). | |
| 257 | 277 | Msg("applied color scheme via adw.StyleManager") | |
| 258 | 278 | } | |
| 259 | 279 | ||
| @@ -1732,9 +1752,8 @@ func (a *App) applyAppearanceConfig(ctx context.Context, cfg *config.Config) { | |||
| 1732 | 1752 | } | |
| 1733 | 1753 | a.deps.Theme.UpdateFromConfig(ctx, cfg, display) | |
| 1734 | 1754 | ||
| 1735 | - | // Keep injector's dark-mode flag in sync for future navigations | |
| 1755 | + | // Update find highlight CSS for future navigations | |
| 1736 | 1756 | if a.injector != nil { | |
| 1737 | - | a.injector.SetPrefersDark(a.deps.Theme.PrefersDark()) | |
| 1738 | 1757 | findCSS := theme.GenerateFindHighlightCSS(a.deps.Theme.GetCurrentPalette()) | |
| 1739 | 1758 | if err := a.injector.InjectFindHighlightCSS(ctx, findCSS); err != nil { | |
| 1740 | 1759 | log.Warn().Err(err).Msg("failed to update find highlight CSS") | |
| @@ -160,6 +160,13 @@ func (a *App) Run(ctx context.Context, args []string) int { | |||||
| 160 | // This also initializes GTK implicitly. | 160 | // This also initializes GTK implicitly. | ||
| 161 | adw.Init() | 161 | adw.Init() | ||
| 162 | 162 | ||||
| 163 | + | // Mark adwaita detector as available now that adw.Init() is complete. | |||
| 164 | + | // This enables the highest-priority color scheme detector. | |||
| 165 | + | if a.deps != nil && a.deps.AdwaitaDetector != nil { | |||
| 166 | + | a.deps.AdwaitaDetector.MarkAvailable() | |||
| 167 | + | log.Debug().Msg("adwaita detector marked available") | |||
| 168 | + | } | |||
| 169 | + | ||||
| 163 | // TODO: Use AppID once puregotk GC bug is fixed (nullable-string-gc-memory-corruption) | 170 | // TODO: Use AppID once puregotk GC bug is fixed (nullable-string-gc-memory-corruption) | ||
| 164 | a.gtkApp = gtk.NewApplication(nil, gio.GApplicationFlagsNoneValue) | 171 | a.gtkApp = gtk.NewApplication(nil, gio.GApplicationFlagsNoneValue) | ||
| 165 | if a.gtkApp == nil { | 172 | if a.gtkApp == nil { | ||
| @@ -249,11 +256,24 @@ func (a *App) applyGTKColorSchemePreference(ctx context.Context) { | |||||
| 249 | } | 256 | } | ||
| 250 | 257 | ||||
| 251 | styleMgr.SetColorScheme(adwScheme) | 258 | styleMgr.SetColorScheme(adwScheme) | ||
| 252 | - | prefersDark := styleMgr.GetDark() | 259 | + | |
| 260 | + | // Refresh the color resolver now that adwaita detector is available. | |||
| 261 | + | // This ensures all components get the correct preference. | |||
| 262 | + | var pref string | |||
| 263 | + | var prefersDark bool | |||
| 264 | + | if a.deps != nil && a.deps.ColorResolver != nil { | |||
| 265 | + | result := a.deps.ColorResolver.Refresh() | |||
| 266 | + | prefersDark = result.PrefersDark | |||
| 267 | + | pref = result.Source | |||
| 268 | + | } else { | |||
| 269 | + | prefersDark = styleMgr.GetDark() | |||
| 270 | + | pref = "adw.StyleManager" | |||
| 271 | + | } | |||
| 253 | 272 | ||||
| 254 | log.Debug(). | 273 | log.Debug(). | ||
| 255 | Str("scheme", scheme). | 274 | Str("scheme", scheme). | ||
| 256 | Bool("prefers_dark", prefersDark). | 275 | Bool("prefers_dark", prefersDark). | ||
| 276 | + | Str("source", pref). | |||
| 257 | Msg("applied color scheme via adw.StyleManager") | 277 | Msg("applied color scheme via adw.StyleManager") | ||
| 258 | } | 278 | } | ||
| 259 | 279 | ||||
| @@ -1732,9 +1752,8 @@ func (a *App) applyAppearanceConfig(ctx context.Context, cfg *config.Config) { | |||||
| 1732 | } | 1752 | } | ||
| 1733 | a.deps.Theme.UpdateFromConfig(ctx, cfg, display) | 1753 | a.deps.Theme.UpdateFromConfig(ctx, cfg, display) | ||
| 1734 | 1754 | ||||
| 1735 | - | // Keep injector's dark-mode flag in sync for future navigations | 1755 | + | // Update find highlight CSS for future navigations |
| 1736 | if a.injector != nil { | 1756 | if a.injector != nil { | ||
| 1737 | - | a.injector.SetPrefersDark(a.deps.Theme.PrefersDark()) | |||
| 1738 | findCSS := theme.GenerateFindHighlightCSS(a.deps.Theme.GetCurrentPalette()) | 1757 | findCSS := theme.GenerateFindHighlightCSS(a.deps.Theme.GetCurrentPalette()) | ||
| 1739 | if err := a.injector.InjectFindHighlightCSS(ctx, findCSS); err != nil { | 1758 | if err := a.injector.InjectFindHighlightCSS(ctx, findCSS); err != nil { | ||
| 1740 | log.Warn().Err(err).Msg("failed to update find highlight CSS") | 1759 | log.Warn().Err(err).Msg("failed to update find highlight CSS") | ||
M internal/ui/deps.go +5 -2
| @@ -8,6 +8,7 @@ import ( | |||
| 8 | 8 | "github.com/bnema/dumber/internal/application/usecase" | |
| 9 | 9 | "github.com/bnema/dumber/internal/domain/entity" | |
| 10 | 10 | "github.com/bnema/dumber/internal/domain/repository" | |
| 11 | + | "github.com/bnema/dumber/internal/infrastructure/colorscheme" | |
| 11 | 12 | "github.com/bnema/dumber/internal/infrastructure/config" | |
| 12 | 13 | "github.com/bnema/dumber/internal/infrastructure/favicon" | |
| 13 | 14 | "github.com/bnema/dumber/internal/infrastructure/filtering" | |
| @@ -25,8 +26,10 @@ type Dependencies struct { | |||
| 25 | 26 | RestoreSessionID string // Session ID to restore on startup (optional) | |
| 26 | 27 | OnFirstWebViewShown func(context.Context) | |
| 27 | 28 | ||
| 28 | - | // Theme management | |
| 29 | - | Theme *theme.Manager | |
| 29 | + | // Theme and color scheme management | |
| 30 | + | Theme *theme.Manager | |
| 31 | + | ColorResolver port.ColorSchemeResolver | |
| 32 | + | AdwaitaDetector *colorscheme.AdwaitaDetector | |
| 30 | 33 | ||
| 31 | 34 | // WebKit infrastructure | |
| 32 | 35 | WebContext *webkit.WebKitContext | |
| @@ -8,6 +8,7 @@ import ( | |||||
| 8 | "github.com/bnema/dumber/internal/application/usecase" | 8 | "github.com/bnema/dumber/internal/application/usecase" | ||
| 9 | "github.com/bnema/dumber/internal/domain/entity" | 9 | "github.com/bnema/dumber/internal/domain/entity" | ||
| 10 | "github.com/bnema/dumber/internal/domain/repository" | 10 | "github.com/bnema/dumber/internal/domain/repository" | ||
| 11 | + | "github.com/bnema/dumber/internal/infrastructure/colorscheme" | |||
| 11 | "github.com/bnema/dumber/internal/infrastructure/config" | 12 | "github.com/bnema/dumber/internal/infrastructure/config" | ||
| 12 | "github.com/bnema/dumber/internal/infrastructure/favicon" | 13 | "github.com/bnema/dumber/internal/infrastructure/favicon" | ||
| 13 | "github.com/bnema/dumber/internal/infrastructure/filtering" | 14 | "github.com/bnema/dumber/internal/infrastructure/filtering" | ||
| @@ -25,8 +26,10 @@ type Dependencies struct { | |||||
| 25 | RestoreSessionID string // Session ID to restore on startup (optional) | 26 | RestoreSessionID string // Session ID to restore on startup (optional) | ||
| 26 | OnFirstWebViewShown func(context.Context) | 27 | OnFirstWebViewShown func(context.Context) | ||
| 27 | 28 | ||||
| 28 | - | // Theme management | 29 | + | // Theme and color scheme management |
| 29 | - | Theme *theme.Manager | 30 | + | Theme *theme.Manager |
| 31 | + | ColorResolver port.ColorSchemeResolver | |||
| 32 | + | AdwaitaDetector *colorscheme.AdwaitaDetector | |||
| 30 | 33 | ||||
| 31 | // WebKit infrastructure | 34 | // WebKit infrastructure | ||
| 32 | WebContext *webkit.WebKitContext | 35 | WebContext *webkit.WebKitContext | ||