detached 4130d3e

feat(webkit): enable favicon support and expose FaviconDatabase

Enable favicon collection in WebKit context, expose FaviconDatabase reference, add notify::favicon signal handler to WebView, and implement Favicon() getter to retrieve current page favicon as GdkTexture.
commit
4130d3edf4d82280d4063b8552e9d5cee5cb8b0f
author
brice · · 2025-12-15 21:38 UTC
committer
brice · · 2025-12-15 18:16 UTC
parent
cb46c3d
signature
unsigned
2 files · +43 -0
M internal/infrastructure/webkit/context.go +15 -0
@@ -16,6 +16,7 @@ import (
1616 type WebKitContext struct {
1717 webContext *webkit.WebContext
1818 networkSession *webkit.NetworkSession
19+ faviconDB *webkit.FaviconDatabase
1920
2021 dataDir string
2122 cacheDir string
@@ -93,6 +94,13 @@ func (c *WebKitContext) initNetworkSession() error {
9394 return fmt.Errorf("website data manager is ephemeral")
9495 }
9596
97+ // Enable favicon collection and get database reference
98+ dataManager.SetFaviconsEnabled(true)
99+ c.faviconDB = dataManager.GetFaviconDatabase()
100+ if c.faviconDB != nil {
101+ c.logger.Debug().Msg("favicon database enabled")
102+ }
103+
96104 // Configure cookie storage
97105 cookieManager := session.GetCookieManager()
98106 if cookieManager == nil {
@@ -139,6 +147,13 @@ func (c *WebKitContext) NetworkSession() *webkit.NetworkSession {
139147 return c.networkSession
140148 }
141149
150+// FaviconDatabase returns the favicon database for persistent favicon storage.
151+func (c *WebKitContext) FaviconDatabase() *webkit.FaviconDatabase {
152+ c.mu.RLock()
153+ defer c.mu.RUnlock()
154+ return c.faviconDB
155+}
156+
142157 // DataDir returns the data directory path.
143158 func (c *WebKitContext) DataDir() string {
144159 return c.dataDir
@@ -16,6 +16,7 @@ import (
16 type WebKitContext struct {16 type WebKitContext struct {
17 webContext *webkit.WebContext17 webContext *webkit.WebContext
18 networkSession *webkit.NetworkSession18 networkSession *webkit.NetworkSession
19+ faviconDB *webkit.FaviconDatabase
19 20
20 dataDir string21 dataDir string
21 cacheDir string22 cacheDir string
@@ -93,6 +94,13 @@ func (c *WebKitContext) initNetworkSession() error {
93 return fmt.Errorf("website data manager is ephemeral")94 return fmt.Errorf("website data manager is ephemeral")
94 }95 }
95 96
97+ // Enable favicon collection and get database reference
98+ dataManager.SetFaviconsEnabled(true)
99+ c.faviconDB = dataManager.GetFaviconDatabase()
100+ if c.faviconDB != nil {
101+ c.logger.Debug().Msg("favicon database enabled")
102+ }
103+
96 // Configure cookie storage104 // Configure cookie storage
97 cookieManager := session.GetCookieManager()105 cookieManager := session.GetCookieManager()
98 if cookieManager == nil {106 if cookieManager == nil {
@@ -139,6 +147,13 @@ func (c *WebKitContext) NetworkSession() *webkit.NetworkSession {
139 return c.networkSession147 return c.networkSession
140 }148 }
141 149
150+// FaviconDatabase returns the favicon database for persistent favicon storage.
151+func (c *WebKitContext) FaviconDatabase() *webkit.FaviconDatabase {
152+ c.mu.RLock()
153+ defer c.mu.RUnlock()
154+ return c.faviconDB
155+}
156+
142 // DataDir returns the data directory path.157 // DataDir returns the data directory path.
143 func (c *WebKitContext) DataDir() string {158 func (c *WebKitContext) DataDir() string {
144 return c.dataDir159 return c.dataDir
M internal/infrastructure/webkit/webview.go +28 -0
@@ -9,6 +9,7 @@ import (
99 "github.com/bnema/dumber/internal/application/port"
1010 "github.com/bnema/dumber/internal/logging"
1111 "github.com/bnema/puregotk-webkit/webkit"
12+ "github.com/jwijenbergh/puregotk/v4/gdk"
1213 "github.com/jwijenbergh/puregotk/v4/gio"
1314 "github.com/jwijenbergh/puregotk/v4/glib"
1415 "github.com/jwijenbergh/puregotk/v4/gobject"
@@ -101,6 +102,7 @@ type WebView struct {
101102 OnTitleChanged func(string)
102103 OnURIChanged func(string)
103104 OnProgressChanged func(float64)
105+ OnFaviconChanged func(*gdk.Texture) // Called when page favicon changes
104106 OnClose func()
105107 OnCreate func(PopupRequest) *WebView // Return new WebView or nil to block popup
106108 OnReadyToShow func() // Called when popup is ready to display
@@ -293,6 +295,17 @@ func (wv *WebView) connectSignals() {
293295 }
294296 sigID = gobject.SignalConnect(wv.inner.GoPointer(), "notify::title", glib.NewCallback(&titleCb))
295297 wv.signalIDs = append(wv.signalIDs, sigID)
298+
299+ // notify::favicon signal for favicon changes
300+ // This fires when the page favicon changes (after page load or dynamic updates)
301+ faviconCb := func() {
302+ favicon := wv.inner.GetFavicon()
303+ if wv.OnFaviconChanged != nil {
304+ wv.OnFaviconChanged(favicon)
305+ }
306+ }
307+ sigID = gobject.SignalConnect(wv.inner.GoPointer(), "notify::favicon", glib.NewCallback(&faviconCb))
308+ wv.signalIDs = append(wv.signalIDs, sigID)
296309 }
297310
298311 // ID returns the unique identifier for this WebView.
@@ -412,6 +425,15 @@ func (wv *WebView) Title() string {
412425 return wv.title
413426 }
414427
428+// Favicon returns the current page favicon as a GdkTexture.
429+// Returns nil if no favicon is available.
430+func (wv *WebView) Favicon() *gdk.Texture {
431+ if wv.destroyed.Load() {
432+ return nil
433+ }
434+ return wv.inner.GetFavicon()
435+}
436+
415437 // IsLoading returns true if a page is currently loading.
416438 func (wv *WebView) IsLoading() bool {
417439 wv.mu.RLock()
@@ -464,6 +486,7 @@ func (wv *WebView) SetCallbacks(callbacks *port.WebViewCallbacks) {
464486 wv.OnTitleChanged = nil
465487 wv.OnURIChanged = nil
466488 wv.OnProgressChanged = nil
489+ wv.OnFaviconChanged = nil
467490 wv.OnClose = nil
468491 wv.OnCreate = nil
469492 return
@@ -478,6 +501,11 @@ func (wv *WebView) SetCallbacks(callbacks *port.WebViewCallbacks) {
478501 wv.OnTitleChanged = callbacks.OnTitleChanged
479502 wv.OnURIChanged = callbacks.OnURIChanged
480503 wv.OnProgressChanged = callbacks.OnProgressChanged
504+ if callbacks.OnFaviconChanged != nil {
505+ wv.OnFaviconChanged = func(texture *gdk.Texture) {
506+ callbacks.OnFaviconChanged(texture)
507+ }
508+ }
481509 wv.OnClose = callbacks.OnClose
482510 if callbacks.OnCreate != nil {
483511 wv.OnCreate = func(req PopupRequest) *WebView {
@@ -9,6 +9,7 @@ import (
9 "github.com/bnema/dumber/internal/application/port"9 "github.com/bnema/dumber/internal/application/port"
10 "github.com/bnema/dumber/internal/logging"10 "github.com/bnema/dumber/internal/logging"
11 "github.com/bnema/puregotk-webkit/webkit"11 "github.com/bnema/puregotk-webkit/webkit"
12+ "github.com/jwijenbergh/puregotk/v4/gdk"
12 "github.com/jwijenbergh/puregotk/v4/gio"13 "github.com/jwijenbergh/puregotk/v4/gio"
13 "github.com/jwijenbergh/puregotk/v4/glib"14 "github.com/jwijenbergh/puregotk/v4/glib"
14 "github.com/jwijenbergh/puregotk/v4/gobject"15 "github.com/jwijenbergh/puregotk/v4/gobject"
@@ -101,6 +102,7 @@ type WebView struct {
101 OnTitleChanged func(string)102 OnTitleChanged func(string)
102 OnURIChanged func(string)103 OnURIChanged func(string)
103 OnProgressChanged func(float64)104 OnProgressChanged func(float64)
105+ OnFaviconChanged func(*gdk.Texture) // Called when page favicon changes
104 OnClose func()106 OnClose func()
105 OnCreate func(PopupRequest) *WebView // Return new WebView or nil to block popup107 OnCreate func(PopupRequest) *WebView // Return new WebView or nil to block popup
106 OnReadyToShow func() // Called when popup is ready to display108 OnReadyToShow func() // Called when popup is ready to display
@@ -293,6 +295,17 @@ func (wv *WebView) connectSignals() {
293 }295 }
294 sigID = gobject.SignalConnect(wv.inner.GoPointer(), "notify::title", glib.NewCallback(&titleCb))296 sigID = gobject.SignalConnect(wv.inner.GoPointer(), "notify::title", glib.NewCallback(&titleCb))
295 wv.signalIDs = append(wv.signalIDs, sigID)297 wv.signalIDs = append(wv.signalIDs, sigID)
298+
299+ // notify::favicon signal for favicon changes
300+ // This fires when the page favicon changes (after page load or dynamic updates)
301+ faviconCb := func() {
302+ favicon := wv.inner.GetFavicon()
303+ if wv.OnFaviconChanged != nil {
304+ wv.OnFaviconChanged(favicon)
305+ }
306+ }
307+ sigID = gobject.SignalConnect(wv.inner.GoPointer(), "notify::favicon", glib.NewCallback(&faviconCb))
308+ wv.signalIDs = append(wv.signalIDs, sigID)
296 }309 }
297 310
298 // ID returns the unique identifier for this WebView.311 // ID returns the unique identifier for this WebView.
@@ -412,6 +425,15 @@ func (wv *WebView) Title() string {
412 return wv.title425 return wv.title
413 }426 }
414 427
428+// Favicon returns the current page favicon as a GdkTexture.
429+// Returns nil if no favicon is available.
430+func (wv *WebView) Favicon() *gdk.Texture {
431+ if wv.destroyed.Load() {
432+ return nil
433+ }
434+ return wv.inner.GetFavicon()
435+}
436+
415 // IsLoading returns true if a page is currently loading.437 // IsLoading returns true if a page is currently loading.
416 func (wv *WebView) IsLoading() bool {438 func (wv *WebView) IsLoading() bool {
417 wv.mu.RLock()439 wv.mu.RLock()
@@ -464,6 +486,7 @@ func (wv *WebView) SetCallbacks(callbacks *port.WebViewCallbacks) {
464 wv.OnTitleChanged = nil486 wv.OnTitleChanged = nil
465 wv.OnURIChanged = nil487 wv.OnURIChanged = nil
466 wv.OnProgressChanged = nil488 wv.OnProgressChanged = nil
489+ wv.OnFaviconChanged = nil
467 wv.OnClose = nil490 wv.OnClose = nil
468 wv.OnCreate = nil491 wv.OnCreate = nil
469 return492 return
@@ -478,6 +501,11 @@ func (wv *WebView) SetCallbacks(callbacks *port.WebViewCallbacks) {
478 wv.OnTitleChanged = callbacks.OnTitleChanged501 wv.OnTitleChanged = callbacks.OnTitleChanged
479 wv.OnURIChanged = callbacks.OnURIChanged502 wv.OnURIChanged = callbacks.OnURIChanged
480 wv.OnProgressChanged = callbacks.OnProgressChanged503 wv.OnProgressChanged = callbacks.OnProgressChanged
504+ if callbacks.OnFaviconChanged != nil {
505+ wv.OnFaviconChanged = func(texture *gdk.Texture) {
506+ callbacks.OnFaviconChanged(texture)
507+ }
508+ }
481 wv.OnClose = callbacks.OnClose509 wv.OnClose = callbacks.OnClose
482 if callbacks.OnCreate != nil {510 if callbacks.OnCreate != nil {
483 wv.OnCreate = func(req PopupRequest) *WebView {511 wv.OnCreate = func(req PopupRequest) *WebView {

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close