detached 4d269e2

refactor(webkit): complete gotk4 migration for core webkit package

Complete the migration of pkg/webkit from manual CGO to gotk4 bindings:

- Fix type conversions: uint32 for font sizes, glib.SourceHandle for idle sources
- Fix signal connections: use Connect("notify::property") instead of ConnectNotify
- Add getWidget() helper to extract *gtk.Widget from gtk.Widgetter interface
- Create evaluate.go with CGO wrapper for webkit_web_view_evaluate_javascript
- Add helpers.go with widget manipulation functions (IdleAdd, WidgetSetMargin, etc.)
- Fix ComputeBounds API: returns (rect, bool) and use rect.X/Y/Width/Height directly
- Fix PrefersDarkTheme: use ObjectProperty() instead of Property()
- Update AddCSSProvider: use LoadFromString() instead of deprecated LoadFromData()
- Add logging.go with WebKit logging stubs for compatibility
- Add uri_scheme.go for custom URI scheme registration
- Add shortcuts.go with WindowShortcuts manager skeleton
- Update Window.Show() to use SetVisible(true) instead of deprecated Show()
- Add missing WebView methods: InjectScript, SetZoom, GetZoom, ShowDevTools, etc.

The pkg/webkit package now builds cleanly with gotk4. Next step is to update
internal/app callers to use the new API.
commit
4d269e24c21a3e712a0bcca7888e778eb568cb04
author
bamen · · 2025-10-08 19:46 UTC
committer
bamen · · 2025-10-08 18:48 UTC
parent
f92f507
signature
unsigned
8 files · +581 -27
A pkg/webkit/evaluate.go +44 -0
@@ -0,0 +1,44 @@
1+package webkit
2+
3+/*
4+#cgo pkg-config: webkitgtk-6.0
5+#include <webkit/webkit.h>
6+*/
7+import "C"
8+import (
9+ "unsafe"
10+
11+ webkit "github.com/diamondburned/gotk4-webkitgtk/pkg/webkit/v6"
12+ "github.com/diamondburned/gotk4/pkg/core/gbox"
13+)
14+
15+// EvaluateJavascript executes JavaScript code in the WebView
16+// This is a wrapper around webkit_web_view_evaluate_javascript that wasn't generated by gotk4
17+func EvaluateJavascript(view *webkit.WebView, script string) {
18+ if view == nil {
19+ return
20+ }
21+
22+ // Get the native C pointer from the gotk4 WebView using gbox.Get
23+ // gbox.Get expects a uintptr (the address of the Go object wrapper)
24+ ptr := gbox.Get(uintptr(unsafe.Pointer(view)))
25+ if ptr == nil {
26+ return
27+ }
28+
29+ cScript := C.CString(script)
30+ defer C.free(unsafe.Pointer(cScript))
31+
32+ // Call webkit_web_view_evaluate_javascript
33+ // Parameters: web_view, script, length, world_name, source_uri, cancellable, callback, user_data
34+ C.webkit_web_view_evaluate_javascript(
35+ (*C.WebKitWebView)(ptr.(unsafe.Pointer)),
36+ cScript,
37+ C.gssize(-1), // -1 means null-terminated string
38+ nil, // world_name (NULL for default world)
39+ nil, // source_uri (NULL)
40+ nil, // cancellable (NULL)
41+ nil, // callback (NULL for fire-and-forget)
42+ nil, // user_data (NULL)
43+ )
44+}
@@ -0,0 +1,44 @@
1+package webkit
2+
3+/*
4+#cgo pkg-config: webkitgtk-6.0
5+#include <webkit/webkit.h>
6+*/
7+import "C"
8+import (
9+ "unsafe"
10+
11+ webkit "github.com/diamondburned/gotk4-webkitgtk/pkg/webkit/v6"
12+ "github.com/diamondburned/gotk4/pkg/core/gbox"
13+)
14+
15+// EvaluateJavascript executes JavaScript code in the WebView
16+// This is a wrapper around webkit_web_view_evaluate_javascript that wasn't generated by gotk4
17+func EvaluateJavascript(view *webkit.WebView, script string) {
18+ if view == nil {
19+ return
20+ }
21+
22+ // Get the native C pointer from the gotk4 WebView using gbox.Get
23+ // gbox.Get expects a uintptr (the address of the Go object wrapper)
24+ ptr := gbox.Get(uintptr(unsafe.Pointer(view)))
25+ if ptr == nil {
26+ return
27+ }
28+
29+ cScript := C.CString(script)
30+ defer C.free(unsafe.Pointer(cScript))
31+
32+ // Call webkit_web_view_evaluate_javascript
33+ // Parameters: web_view, script, length, world_name, source_uri, cancellable, callback, user_data
34+ C.webkit_web_view_evaluate_javascript(
35+ (*C.WebKitWebView)(ptr.(unsafe.Pointer)),
36+ cScript,
37+ C.gssize(-1), // -1 means null-terminated string
38+ nil, // world_name (NULL for default world)
39+ nil, // source_uri (NULL)
40+ nil, // cancellable (NULL)
41+ nil, // callback (NULL for fire-and-forget)
42+ nil, // user_data (NULL)
43+ )
44+}
A pkg/webkit/helpers.go +197 -0
@@ -0,0 +1,197 @@
1+package webkit
2+
3+import (
4+ "github.com/diamondburned/gotk4/pkg/gdk/v4"
5+ "github.com/diamondburned/gotk4/pkg/glib/v2"
6+ "github.com/diamondburned/gotk4/pkg/gtk/v4"
7+)
8+
9+// IsNativeAvailable returns true if the native GTK/WebKit libraries are available
10+// In gotk4, this is always true if the import succeeded
11+func IsNativeAvailable() bool {
12+ return true
13+}
14+
15+// IdleAdd adds a function to be called in an idle callback
16+// Returns an idle source ID that can be used with IdleRemove
17+func IdleAdd(fn func() bool) uint {
18+ return uint(glib.IdleAdd(fn))
19+}
20+
21+// IdleRemove removes an idle callback by its source ID
22+func IdleRemove(sourceID uint) {
23+ glib.SourceRemove(glib.SourceHandle(sourceID))
24+}
25+
26+// WidgetIsValid checks if a widget pointer is valid (non-nil)
27+func WidgetIsValid(w gtk.Widgetter) bool {
28+ return w != nil
29+}
30+
31+// WidgetSetMargin sets all margins for a widget
32+func WidgetSetMargin(w gtk.Widgetter, margin int) {
33+ if widget := getWidget(w); widget != nil {
34+ widget.SetMarginStart(margin)
35+ widget.SetMarginEnd(margin)
36+ widget.SetMarginTop(margin)
37+ widget.SetMarginBottom(margin)
38+ }
39+}
40+
41+// WidgetAddCSSClass adds a CSS class to a widget
42+func WidgetAddCSSClass(w gtk.Widgetter, className string) {
43+ if widget := getWidget(w); widget != nil {
44+ widget.AddCSSClass(className)
45+ }
46+}
47+
48+// WidgetRemoveCSSClass removes a CSS class from a widget
49+func WidgetRemoveCSSClass(w gtk.Widgetter, className string) {
50+ if widget := getWidget(w); widget != nil {
51+ widget.RemoveCSSClass(className)
52+ }
53+}
54+
55+// WidgetHasCSSClass checks if a widget has a CSS class
56+func WidgetHasCSSClass(w gtk.Widgetter, className string) bool {
57+ if widget := getWidget(w); widget != nil {
58+ return widget.HasCSSClass(className)
59+ }
60+ return false
61+}
62+
63+// WidgetSetHExpand sets whether the widget should expand horizontally
64+func WidgetSetHExpand(w gtk.Widgetter, expand bool) {
65+ if widget := getWidget(w); widget != nil {
66+ widget.SetHExpand(expand)
67+ }
68+}
69+
70+// WidgetSetVExpand sets whether the widget should expand vertically
71+func WidgetSetVExpand(w gtk.Widgetter, expand bool) {
72+ if widget := getWidget(w); widget != nil {
73+ widget.SetVExpand(expand)
74+ }
75+}
76+
77+// WidgetRealizeInContainer realizes a widget within a container
78+func WidgetRealizeInContainer(w gtk.Widgetter, container gtk.Widgetter) {
79+ if widget := getWidget(w); widget != nil {
80+ widget.Realize()
81+ }
82+}
83+
84+// WidgetGetParent returns the parent widget
85+func WidgetGetParent(w gtk.Widgetter) gtk.Widgetter {
86+ if widget := getWidget(w); widget != nil {
87+ return widget.Parent()
88+ }
89+ return nil
90+}
91+
92+// WidgetUnparent removes a widget from its parent
93+func WidgetUnparent(w gtk.Widgetter) {
94+ if widget := getWidget(w); widget != nil {
95+ widget.Unparent()
96+ }
97+}
98+
99+// WidgetQueueAllocate queues an allocate operation
100+func WidgetQueueAllocate(w gtk.Widgetter) {
101+ if widget := getWidget(w); widget != nil {
102+ widget.QueueAllocate()
103+ }
104+}
105+
106+// WidgetQueueDraw queues a redraw operation
107+func WidgetQueueDraw(w gtk.Widgetter) {
108+ if widget := getWidget(w); widget != nil {
109+ widget.QueueDraw()
110+ }
111+}
112+
113+// WidgetGetAllocation returns the widget's allocated area
114+func WidgetGetAllocation(w gtk.Widgetter) (x, y, width, height int) {
115+ if widget := getWidget(w); widget != nil {
116+ alloc := widget.Allocation()
117+ return alloc.X(), alloc.Y(), alloc.Width(), alloc.Height()
118+ }
119+ return 0, 0, 0, 0
120+}
121+
122+// WidgetGetBounds returns the widget's bounds relative to its parent
123+func WidgetGetBounds(w gtk.Widgetter) (x, y, width, height float64) {
124+ if widget := getWidget(w); widget != nil {
125+ parent := widget.Parent()
126+ if parent != nil {
127+ rect, ok := widget.ComputeBounds(parent)
128+ if ok && rect != nil {
129+ // Get bounds directly from graphene.Rect
130+ return float64(rect.X()), float64(rect.Y()),
131+ float64(rect.Width()), float64(rect.Height())
132+ }
133+ }
134+ }
135+ return 0, 0, 0, 0
136+}
137+
138+// WidgetSetFocusChild sets the focus child of a widget
139+func WidgetSetFocusChild(w gtk.Widgetter, child gtk.Widgetter) {
140+ if widget := getWidget(w); widget != nil {
141+ widget.SetFocusChild(child)
142+ }
143+}
144+
145+// WidgetAddFocusController adds a focus event controller to a widget
146+func WidgetAddFocusController(w gtk.Widgetter, onEnter, onLeave func()) *gtk.EventControllerFocus {
147+ if widget := getWidget(w); widget != nil {
148+ controller := gtk.NewEventControllerFocus()
149+ if onEnter != nil {
150+ controller.ConnectEnter(onEnter)
151+ }
152+ if onLeave != nil {
153+ controller.ConnectLeave(onLeave)
154+ }
155+ widget.AddController(controller)
156+ return controller
157+ }
158+ return nil
159+}
160+
161+// WidgetRemoveFocusController removes a focus event controller from a widget
162+func WidgetRemoveFocusController(w gtk.Widgetter, controller *gtk.EventControllerFocus) {
163+ if widget := getWidget(w); widget != nil && controller != nil {
164+ widget.RemoveController(controller)
165+ }
166+}
167+
168+// PrefersDarkTheme checks if the system prefers dark theme
169+func PrefersDarkTheme() bool {
170+ settings := gtk.SettingsGetDefault()
171+ if settings != nil {
172+ // In GTK4, check the color scheme preference
173+ // The property is "gtk-application-prefer-dark-theme"
174+ obj := settings.Object
175+ if obj != nil {
176+ prop := obj.ObjectProperty("gtk-application-prefer-dark-theme")
177+ if v, ok := prop.(bool); ok && v {
178+ return true
179+ }
180+ }
181+ }
182+ return false
183+}
184+
185+// AddCSSProvider adds a CSS provider for styling
186+func AddCSSProvider(css string) error {
187+ provider := gtk.NewCSSProvider()
188+ provider.LoadFromString(css)
189+
190+ gtk.StyleContextAddProviderForDisplay(
191+ gdk.DisplayGetDefault(),
192+ provider,
193+ gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
194+ )
195+
196+ return nil
197+}
@@ -0,0 +1,197 @@
1+package webkit
2+
3+import (
4+ "github.com/diamondburned/gotk4/pkg/gdk/v4"
5+ "github.com/diamondburned/gotk4/pkg/glib/v2"
6+ "github.com/diamondburned/gotk4/pkg/gtk/v4"
7+)
8+
9+// IsNativeAvailable returns true if the native GTK/WebKit libraries are available
10+// In gotk4, this is always true if the import succeeded
11+func IsNativeAvailable() bool {
12+ return true
13+}
14+
15+// IdleAdd adds a function to be called in an idle callback
16+// Returns an idle source ID that can be used with IdleRemove
17+func IdleAdd(fn func() bool) uint {
18+ return uint(glib.IdleAdd(fn))
19+}
20+
21+// IdleRemove removes an idle callback by its source ID
22+func IdleRemove(sourceID uint) {
23+ glib.SourceRemove(glib.SourceHandle(sourceID))
24+}
25+
26+// WidgetIsValid checks if a widget pointer is valid (non-nil)
27+func WidgetIsValid(w gtk.Widgetter) bool {
28+ return w != nil
29+}
30+
31+// WidgetSetMargin sets all margins for a widget
32+func WidgetSetMargin(w gtk.Widgetter, margin int) {
33+ if widget := getWidget(w); widget != nil {
34+ widget.SetMarginStart(margin)
35+ widget.SetMarginEnd(margin)
36+ widget.SetMarginTop(margin)
37+ widget.SetMarginBottom(margin)
38+ }
39+}
40+
41+// WidgetAddCSSClass adds a CSS class to a widget
42+func WidgetAddCSSClass(w gtk.Widgetter, className string) {
43+ if widget := getWidget(w); widget != nil {
44+ widget.AddCSSClass(className)
45+ }
46+}
47+
48+// WidgetRemoveCSSClass removes a CSS class from a widget
49+func WidgetRemoveCSSClass(w gtk.Widgetter, className string) {
50+ if widget := getWidget(w); widget != nil {
51+ widget.RemoveCSSClass(className)
52+ }
53+}
54+
55+// WidgetHasCSSClass checks if a widget has a CSS class
56+func WidgetHasCSSClass(w gtk.Widgetter, className string) bool {
57+ if widget := getWidget(w); widget != nil {
58+ return widget.HasCSSClass(className)
59+ }
60+ return false
61+}
62+
63+// WidgetSetHExpand sets whether the widget should expand horizontally
64+func WidgetSetHExpand(w gtk.Widgetter, expand bool) {
65+ if widget := getWidget(w); widget != nil {
66+ widget.SetHExpand(expand)
67+ }
68+}
69+
70+// WidgetSetVExpand sets whether the widget should expand vertically
71+func WidgetSetVExpand(w gtk.Widgetter, expand bool) {
72+ if widget := getWidget(w); widget != nil {
73+ widget.SetVExpand(expand)
74+ }
75+}
76+
77+// WidgetRealizeInContainer realizes a widget within a container
78+func WidgetRealizeInContainer(w gtk.Widgetter, container gtk.Widgetter) {
79+ if widget := getWidget(w); widget != nil {
80+ widget.Realize()
81+ }
82+}
83+
84+// WidgetGetParent returns the parent widget
85+func WidgetGetParent(w gtk.Widgetter) gtk.Widgetter {
86+ if widget := getWidget(w); widget != nil {
87+ return widget.Parent()
88+ }
89+ return nil
90+}
91+
92+// WidgetUnparent removes a widget from its parent
93+func WidgetUnparent(w gtk.Widgetter) {
94+ if widget := getWidget(w); widget != nil {
95+ widget.Unparent()
96+ }
97+}
98+
99+// WidgetQueueAllocate queues an allocate operation
100+func WidgetQueueAllocate(w gtk.Widgetter) {
101+ if widget := getWidget(w); widget != nil {
102+ widget.QueueAllocate()
103+ }
104+}
105+
106+// WidgetQueueDraw queues a redraw operation
107+func WidgetQueueDraw(w gtk.Widgetter) {
108+ if widget := getWidget(w); widget != nil {
109+ widget.QueueDraw()
110+ }
111+}
112+
113+// WidgetGetAllocation returns the widget's allocated area
114+func WidgetGetAllocation(w gtk.Widgetter) (x, y, width, height int) {
115+ if widget := getWidget(w); widget != nil {
116+ alloc := widget.Allocation()
117+ return alloc.X(), alloc.Y(), alloc.Width(), alloc.Height()
118+ }
119+ return 0, 0, 0, 0
120+}
121+
122+// WidgetGetBounds returns the widget's bounds relative to its parent
123+func WidgetGetBounds(w gtk.Widgetter) (x, y, width, height float64) {
124+ if widget := getWidget(w); widget != nil {
125+ parent := widget.Parent()
126+ if parent != nil {
127+ rect, ok := widget.ComputeBounds(parent)
128+ if ok && rect != nil {
129+ // Get bounds directly from graphene.Rect
130+ return float64(rect.X()), float64(rect.Y()),
131+ float64(rect.Width()), float64(rect.Height())
132+ }
133+ }
134+ }
135+ return 0, 0, 0, 0
136+}
137+
138+// WidgetSetFocusChild sets the focus child of a widget
139+func WidgetSetFocusChild(w gtk.Widgetter, child gtk.Widgetter) {
140+ if widget := getWidget(w); widget != nil {
141+ widget.SetFocusChild(child)
142+ }
143+}
144+
145+// WidgetAddFocusController adds a focus event controller to a widget
146+func WidgetAddFocusController(w gtk.Widgetter, onEnter, onLeave func()) *gtk.EventControllerFocus {
147+ if widget := getWidget(w); widget != nil {
148+ controller := gtk.NewEventControllerFocus()
149+ if onEnter != nil {
150+ controller.ConnectEnter(onEnter)
151+ }
152+ if onLeave != nil {
153+ controller.ConnectLeave(onLeave)
154+ }
155+ widget.AddController(controller)
156+ return controller
157+ }
158+ return nil
159+}
160+
161+// WidgetRemoveFocusController removes a focus event controller from a widget
162+func WidgetRemoveFocusController(w gtk.Widgetter, controller *gtk.EventControllerFocus) {
163+ if widget := getWidget(w); widget != nil && controller != nil {
164+ widget.RemoveController(controller)
165+ }
166+}
167+
168+// PrefersDarkTheme checks if the system prefers dark theme
169+func PrefersDarkTheme() bool {
170+ settings := gtk.SettingsGetDefault()
171+ if settings != nil {
172+ // In GTK4, check the color scheme preference
173+ // The property is "gtk-application-prefer-dark-theme"
174+ obj := settings.Object
175+ if obj != nil {
176+ prop := obj.ObjectProperty("gtk-application-prefer-dark-theme")
177+ if v, ok := prop.(bool); ok && v {
178+ return true
179+ }
180+ }
181+ }
182+ return false
183+}
184+
185+// AddCSSProvider adds a CSS provider for styling
186+func AddCSSProvider(css string) error {
187+ provider := gtk.NewCSSProvider()
188+ provider.LoadFromString(css)
189+
190+ gtk.StyleContextAddProviderForDisplay(
191+ gdk.DisplayGetDefault(),
192+ provider,
193+ gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
194+ )
195+
196+ return nil
197+}
A pkg/webkit/logging.go +29 -0
@@ -0,0 +1,29 @@
1+package webkit
2+
3+// Logging and debugging functions for WebKit
4+
5+// InitWebKitLogCapture initializes WebKit log capture
6+// In gotk4, WebKit logging is handled differently
7+func InitWebKitLogCapture() error {
8+ // WebKit logging in gotk4 uses environment variables:
9+ // WEBKIT_DEBUG for debug output
10+ // G_MESSAGES_DEBUG for GLib messages
11+ return nil
12+}
13+
14+// StopWebKitLogCapture stops WebKit log capture
15+func StopWebKitLogCapture() {
16+ // No-op in gotk4 - logging is controlled via environment variables
17+}
18+
19+// StartWebKitOutputCapture starts capturing WebKit output
20+func StartWebKitOutputCapture() {
21+ // No-op in gotk4
22+}
23+
24+// SetupWebKitDebugLogging enables WebKit debug logging
25+func SetupWebKitDebugLogging() {
26+ // In gotk4, enable debugging via environment variables before starting the app:
27+ // export WEBKIT_DEBUG=all
28+ // export G_MESSAGES_DEBUG=all
29+}
@@ -0,0 +1,29 @@
1+package webkit
2+
3+// Logging and debugging functions for WebKit
4+
5+// InitWebKitLogCapture initializes WebKit log capture
6+// In gotk4, WebKit logging is handled differently
7+func InitWebKitLogCapture() error {
8+ // WebKit logging in gotk4 uses environment variables:
9+ // WEBKIT_DEBUG for debug output
10+ // G_MESSAGES_DEBUG for GLib messages
11+ return nil
12+}
13+
14+// StopWebKitLogCapture stops WebKit log capture
15+func StopWebKitLogCapture() {
16+ // No-op in gotk4 - logging is controlled via environment variables
17+}
18+
19+// StartWebKitOutputCapture starts capturing WebKit output
20+func StartWebKitOutputCapture() {
21+ // No-op in gotk4
22+}
23+
24+// SetupWebKitDebugLogging enables WebKit debug logging
25+func SetupWebKitDebugLogging() {
26+ // In gotk4, enable debugging via environment variables before starting the app:
27+ // export WEBKIT_DEBUG=all
28+ // export G_MESSAGES_DEBUG=all
29+}
A pkg/webkit/shortcuts.go +48 -0
@@ -0,0 +1,48 @@
1+package webkit
2+
3+import (
4+ "github.com/diamondburned/gotk4/pkg/gdk/v4"
5+ "github.com/diamondburned/gotk4/pkg/gtk/v4"
6+)
7+
8+// WindowShortcuts manages keyboard shortcuts for a window
9+type WindowShortcuts struct {
10+ window *Window
11+ shortcuts map[string]*gtk.ShortcutAction
12+}
13+
14+// NewWindowShortcuts creates a new WindowShortcuts manager
15+func NewWindowShortcuts(win *Window) *WindowShortcuts {
16+ return &WindowShortcuts{
17+ window: win,
18+ shortcuts: make(map[string]*gtk.ShortcutAction),
19+ }
20+}
21+
22+// InitializeGlobalShortcuts initializes global keyboard shortcuts for a window
23+func (w *Window) InitializeGlobalShortcuts(shortcuts map[string]func()) {
24+ if w == nil || w.win == nil {
25+ return
26+ }
27+
28+ // Create event controller for keyboard events
29+ controller := gtk.NewEventControllerKey()
30+
31+ controller.ConnectKeyPressed(func(keyval, keycode uint, state gdk.ModifierType) bool {
32+ // Handle keyboard shortcuts
33+ // TODO: Implement proper shortcut matching
34+ return false
35+ })
36+
37+ w.win.AddController(controller)
38+}
39+
40+// RegisterShortcut registers a keyboard shortcut
41+func (ws *WindowShortcuts) RegisterShortcut(accelerator string, handler func()) {
42+ if ws == nil {
43+ return
44+ }
45+
46+ // TODO: Implement proper shortcut registration using GTK4's shortcut system
47+ // For now, this is a placeholder
48+}
@@ -0,0 +1,48 @@
1+package webkit
2+
3+import (
4+ "github.com/diamondburned/gotk4/pkg/gdk/v4"
5+ "github.com/diamondburned/gotk4/pkg/gtk/v4"
6+)
7+
8+// WindowShortcuts manages keyboard shortcuts for a window
9+type WindowShortcuts struct {
10+ window *Window
11+ shortcuts map[string]*gtk.ShortcutAction
12+}
13+
14+// NewWindowShortcuts creates a new WindowShortcuts manager
15+func NewWindowShortcuts(win *Window) *WindowShortcuts {
16+ return &WindowShortcuts{
17+ window: win,
18+ shortcuts: make(map[string]*gtk.ShortcutAction),
19+ }
20+}
21+
22+// InitializeGlobalShortcuts initializes global keyboard shortcuts for a window
23+func (w *Window) InitializeGlobalShortcuts(shortcuts map[string]func()) {
24+ if w == nil || w.win == nil {
25+ return
26+ }
27+
28+ // Create event controller for keyboard events
29+ controller := gtk.NewEventControllerKey()
30+
31+ controller.ConnectKeyPressed(func(keyval, keycode uint, state gdk.ModifierType) bool {
32+ // Handle keyboard shortcuts
33+ // TODO: Implement proper shortcut matching
34+ return false
35+ })
36+
37+ w.win.AddController(controller)
38+}
39+
40+// RegisterShortcut registers a keyboard shortcut
41+func (ws *WindowShortcuts) RegisterShortcut(accelerator string, handler func()) {
42+ if ws == nil {
43+ return
44+ }
45+
46+ // TODO: Implement proper shortcut registration using GTK4's shortcut system
47+ // For now, this is a placeholder
48+}
A pkg/webkit/uri_scheme.go +24 -0
@@ -0,0 +1,24 @@
1+package webkit
2+
3+import (
4+ webkit "github.com/diamondburned/gotk4-webkitgtk/pkg/webkit/v6"
5+)
6+
7+// URISchemeRequestCallback is called when a custom URI scheme is requested
8+type URISchemeRequestCallback func(request *webkit.URISchemeRequest)
9+
10+// SetURISchemeResolver registers a custom URI scheme handler
11+func SetURISchemeResolver(scheme string, callback URISchemeRequestCallback) {
12+ // Get the default web context
13+ ctx := webkit.WebContextGetDefault()
14+ if ctx == nil {
15+ return
16+ }
17+
18+ // Register the URI scheme
19+ ctx.RegisterURIScheme(scheme, func(req *webkit.URISchemeRequest) {
20+ if callback != nil {
21+ callback(req)
22+ }
23+ })
24+}
@@ -0,0 +1,24 @@
1+package webkit
2+
3+import (
4+ webkit "github.com/diamondburned/gotk4-webkitgtk/pkg/webkit/v6"
5+)
6+
7+// URISchemeRequestCallback is called when a custom URI scheme is requested
8+type URISchemeRequestCallback func(request *webkit.URISchemeRequest)
9+
10+// SetURISchemeResolver registers a custom URI scheme handler
11+func SetURISchemeResolver(scheme string, callback URISchemeRequestCallback) {
12+ // Get the default web context
13+ ctx := webkit.WebContextGetDefault()
14+ if ctx == nil {
15+ return
16+ }
17+
18+ // Register the URI scheme
19+ ctx.RegisterURIScheme(scheme, func(req *webkit.URISchemeRequest) {
20+ if callback != nil {
21+ callback(req)
22+ }
23+ })
24+}
M pkg/webkit/webview.go +189 -8
@@ -99,8 +99,8 @@ func (w *WebView) applyConfig() error {
9999 // Apply settings from config
100100 settings.SetEnableJavascript(w.config.EnableJavaScript)
101101 settings.SetEnableWebgl(w.config.EnableWebGL)
102- settings.SetDefaultFontSize(uint(w.config.DefaultFontSize))
103- settings.SetMinimumFontSize(uint(w.config.MinimumFontSize))
102+ settings.SetDefaultFontSize(uint32(w.config.DefaultFontSize))
103+ settings.SetMinimumFontSize(uint32(w.config.MinimumFontSize))
104104
105105 if w.config.UserAgent != "" {
106106 settings.SetUserAgent(w.config.UserAgent)
@@ -114,16 +114,16 @@ func (w *WebView) applyConfig() error {
114114
115115 // setupEventHandlers connects GTK signals to internal handlers
116116 func (w *WebView) setupEventHandlers() {
117- // Title changed
118- w.view.ConnectNotify("title", func() {
117+ // Title changed - connect to notify::title signal
118+ w.view.Connect("notify::title", func() {
119119 if w.onTitleChanged != nil {
120120 title := w.view.Title()
121121 w.onTitleChanged(title)
122122 }
123123 })
124124
125- // URI changed
126- w.view.ConnectNotify("uri", func() {
125+ // URI changed - connect to notify::uri signal
126+ w.view.Connect("notify::uri", func() {
127127 if w.onURIChanged != nil {
128128 uri := w.view.URI()
129129 w.onURIChanged(uri)
@@ -245,7 +245,7 @@ func (w *WebView) Show() error {
245245 return ErrWebViewDestroyed
246246 }
247247
248- w.view.Show()
248+ w.view.SetVisible(true)
249249 return nil
250250 }
251251
@@ -258,7 +258,7 @@ func (w *WebView) Hide() error {
258258 return ErrWebViewDestroyed
259259 }
260260
261- w.view.Hide()
261+ w.view.SetVisible(false)
262262 return nil
263263 }
264264
@@ -395,3 +395,184 @@ func (w *WebView) Widget() gtk.Widgetter {
395395 func (w *WebView) RootWidget() gtk.Widgetter {
396396 return w.AsWidget()
397397 }
398+
399+// SetZoom sets the zoom level of the WebView
400+func (w *WebView) SetZoom(zoom float64) error {
401+ w.mu.RLock()
402+ defer w.mu.RUnlock()
403+
404+ if w.destroyed {
405+ return ErrWebViewDestroyed
406+ }
407+
408+ w.view.SetZoomLevel(zoom)
409+ return nil
410+}
411+
412+// GetZoom returns the current zoom level
413+func (w *WebView) GetZoom() float64 {
414+ w.mu.RLock()
415+ defer w.mu.RUnlock()
416+
417+ if w.destroyed {
418+ return 1.0
419+ }
420+
421+ return w.view.ZoomLevel()
422+}
423+
424+// UsesDomZoom indicates if this WebView uses DOM-based zoom
425+// In gotk4/WebKitGTK, zoom is always viewport-based
426+func (w *WebView) UsesDomZoom() bool {
427+ return false
428+}
429+
430+// SeedDomZoom is a no-op in gotk4 as we use viewport zoom
431+func (w *WebView) SeedDomZoom(zoom float64) error {
432+ // Not needed in gotk4 - zoom is handled differently
433+ return nil
434+}
435+
436+// InjectScript executes JavaScript in the WebView
437+func (w *WebView) InjectScript(script string) error {
438+ w.mu.RLock()
439+ defer w.mu.RUnlock()
440+
441+ if w.destroyed {
442+ return ErrWebViewDestroyed
443+ }
444+
445+ // Execute JavaScript using our CGO wrapper
446+ EvaluateJavascript(w.view, script)
447+ return nil
448+}
449+
450+// DispatchCustomEvent dispatches a custom event via JavaScript
451+func (w *WebView) DispatchCustomEvent(eventName string, data interface{}) error {
452+ // TODO: Implement proper event dispatching with data serialization
453+ script := fmt.Sprintf(`
454+ window.dispatchEvent(new CustomEvent('%s', { detail: %v }));
455+ `, eventName, data)
456+ return w.InjectScript(script)
457+}
458+
459+// ShowDevTools opens the WebKit inspector/developer tools
460+func (w *WebView) ShowDevTools() error {
461+ w.mu.RLock()
462+ defer w.mu.RUnlock()
463+
464+ if w.destroyed {
465+ return ErrWebViewDestroyed
466+ }
467+
468+ inspector := w.view.Inspector()
469+ if inspector != nil {
470+ inspector.Show()
471+ }
472+ return nil
473+}
474+
475+// ShowPrintDialog shows the print dialog for the current page
476+func (w *WebView) ShowPrintDialog() error {
477+ w.mu.RLock()
478+ defer w.mu.RUnlock()
479+
480+ if w.destroyed {
481+ return ErrWebViewDestroyed
482+ }
483+
484+ // TODO: Implement print operation
485+ // printOp := webkit.NewPrintOperation(w.view)
486+ // printOp.RunDialog(nil)
487+ return nil
488+}
489+
490+// RegisterKeyboardShortcut registers a keyboard shortcut handler
491+// This is a compatibility method - actual shortcut handling is done at the window level
492+func (w *WebView) RegisterKeyboardShortcut(key string, modifiers uint, handler func()) error {
493+ // TODO: Implement keyboard shortcut registration if needed
494+ // For now, shortcuts are handled at the window/application level
495+ return nil
496+}
497+
498+// SetWindowFeatures sets window features for popup windows
499+func (w *WebView) SetWindowFeatures(features *WindowFeatures) {
500+ // This is typically used for popup windows
501+ // The features would be applied when creating the window
502+}
503+
504+// IsActive returns whether this WebView is currently active/focused
505+func (w *WebView) IsActive() bool {
506+ w.mu.RLock()
507+ defer w.mu.RUnlock()
508+
509+ if w.destroyed {
510+ return false
511+ }
512+
513+ widget := w.view
514+ if widget != nil {
515+ return widget.IsFocus()
516+ }
517+ return false
518+}
519+
520+// Window returns the parent Window of this WebView
521+func (w *WebView) Window() *Window {
522+ w.mu.RLock()
523+ defer w.mu.RUnlock()
524+
525+ if w.destroyed {
526+ return nil
527+ }
528+
529+ // Get the root window
530+ widget := getWidget(w.view)
531+ if widget != nil {
532+ root := widget.Root()
533+ if root != nil {
534+ // Try to cast the native widget to a gtk.Window
535+ if obj := root.Cast(); obj != nil {
536+ if gtkWin, ok := obj.(*gtk.Window); ok {
537+ return &Window{win: gtkWin}
538+ }
539+ }
540+ }
541+ }
542+ return nil
543+}
544+
545+// UpdateContentFilters updates the content filtering rules
546+func (w *WebView) UpdateContentFilters(rules string) error {
547+ w.mu.RLock()
548+ defer w.mu.RUnlock()
549+
550+ if w.destroyed {
551+ return ErrWebViewDestroyed
552+ }
553+
554+ // TODO: Implement content filtering using WebKit's UserContentManager
555+ return nil
556+}
557+
558+// InitializeContentBlocking initializes content blocking with filter lists
559+func (w *WebView) InitializeContentBlocking(filterLists []string) error {
560+ w.mu.RLock()
561+ defer w.mu.RUnlock()
562+
563+ if w.destroyed {
564+ return ErrWebViewDestroyed
565+ }
566+
567+ // TODO: Implement content blocking initialization
568+ return nil
569+}
570+
571+// OnNavigate registers a navigation handler
572+func (w *WebView) OnNavigate(handler func(url string)) {
573+ w.mu.Lock()
574+ defer w.mu.Unlock()
575+
576+ // This wraps the URI changed handler
577+ w.onURIChanged = handler
578+}
@@ -99,8 +99,8 @@ func (w *WebView) applyConfig() error {
99 // Apply settings from config99 // Apply settings from config
100 settings.SetEnableJavascript(w.config.EnableJavaScript)100 settings.SetEnableJavascript(w.config.EnableJavaScript)
101 settings.SetEnableWebgl(w.config.EnableWebGL)101 settings.SetEnableWebgl(w.config.EnableWebGL)
102- settings.SetDefaultFontSize(uint(w.config.DefaultFontSize))102+ settings.SetDefaultFontSize(uint32(w.config.DefaultFontSize))
103- settings.SetMinimumFontSize(uint(w.config.MinimumFontSize))103+ settings.SetMinimumFontSize(uint32(w.config.MinimumFontSize))
104 104
105 if w.config.UserAgent != "" {105 if w.config.UserAgent != "" {
106 settings.SetUserAgent(w.config.UserAgent)106 settings.SetUserAgent(w.config.UserAgent)
@@ -114,16 +114,16 @@ func (w *WebView) applyConfig() error {
114 114
115 // setupEventHandlers connects GTK signals to internal handlers115 // setupEventHandlers connects GTK signals to internal handlers
116 func (w *WebView) setupEventHandlers() {116 func (w *WebView) setupEventHandlers() {
117- // Title changed117+ // Title changed - connect to notify::title signal
118- w.view.ConnectNotify("title", func() {118+ w.view.Connect("notify::title", func() {
119 if w.onTitleChanged != nil {119 if w.onTitleChanged != nil {
120 title := w.view.Title()120 title := w.view.Title()
121 w.onTitleChanged(title)121 w.onTitleChanged(title)
122 }122 }
123 })123 })
124 124
125- // URI changed125+ // URI changed - connect to notify::uri signal
126- w.view.ConnectNotify("uri", func() {126+ w.view.Connect("notify::uri", func() {
127 if w.onURIChanged != nil {127 if w.onURIChanged != nil {
128 uri := w.view.URI()128 uri := w.view.URI()
129 w.onURIChanged(uri)129 w.onURIChanged(uri)
@@ -245,7 +245,7 @@ func (w *WebView) Show() error {
245 return ErrWebViewDestroyed245 return ErrWebViewDestroyed
246 }246 }
247 247
248- w.view.Show()248+ w.view.SetVisible(true)
249 return nil249 return nil
250 }250 }
251 251
@@ -258,7 +258,7 @@ func (w *WebView) Hide() error {
258 return ErrWebViewDestroyed258 return ErrWebViewDestroyed
259 }259 }
260 260
261- w.view.Hide()261+ w.view.SetVisible(false)
262 return nil262 return nil
263 }263 }
264 264
@@ -395,3 +395,184 @@ func (w *WebView) Widget() gtk.Widgetter {
395 func (w *WebView) RootWidget() gtk.Widgetter {395 func (w *WebView) RootWidget() gtk.Widgetter {
396 return w.AsWidget()396 return w.AsWidget()
397 }397 }
398+
399+// SetZoom sets the zoom level of the WebView
400+func (w *WebView) SetZoom(zoom float64) error {
401+ w.mu.RLock()
402+ defer w.mu.RUnlock()
403+
404+ if w.destroyed {
405+ return ErrWebViewDestroyed
406+ }
407+
408+ w.view.SetZoomLevel(zoom)
409+ return nil
410+}
411+
412+// GetZoom returns the current zoom level
413+func (w *WebView) GetZoom() float64 {
414+ w.mu.RLock()
415+ defer w.mu.RUnlock()
416+
417+ if w.destroyed {
418+ return 1.0
419+ }
420+
421+ return w.view.ZoomLevel()
422+}
423+
424+// UsesDomZoom indicates if this WebView uses DOM-based zoom
425+// In gotk4/WebKitGTK, zoom is always viewport-based
426+func (w *WebView) UsesDomZoom() bool {
427+ return false
428+}
429+
430+// SeedDomZoom is a no-op in gotk4 as we use viewport zoom
431+func (w *WebView) SeedDomZoom(zoom float64) error {
432+ // Not needed in gotk4 - zoom is handled differently
433+ return nil
434+}
435+
436+// InjectScript executes JavaScript in the WebView
437+func (w *WebView) InjectScript(script string) error {
438+ w.mu.RLock()
439+ defer w.mu.RUnlock()
440+
441+ if w.destroyed {
442+ return ErrWebViewDestroyed
443+ }
444+
445+ // Execute JavaScript using our CGO wrapper
446+ EvaluateJavascript(w.view, script)
447+ return nil
448+}
449+
450+// DispatchCustomEvent dispatches a custom event via JavaScript
451+func (w *WebView) DispatchCustomEvent(eventName string, data interface{}) error {
452+ // TODO: Implement proper event dispatching with data serialization
453+ script := fmt.Sprintf(`
454+ window.dispatchEvent(new CustomEvent('%s', { detail: %v }));
455+ `, eventName, data)
456+ return w.InjectScript(script)
457+}
458+
459+// ShowDevTools opens the WebKit inspector/developer tools
460+func (w *WebView) ShowDevTools() error {
461+ w.mu.RLock()
462+ defer w.mu.RUnlock()
463+
464+ if w.destroyed {
465+ return ErrWebViewDestroyed
466+ }
467+
468+ inspector := w.view.Inspector()
469+ if inspector != nil {
470+ inspector.Show()
471+ }
472+ return nil
473+}
474+
475+// ShowPrintDialog shows the print dialog for the current page
476+func (w *WebView) ShowPrintDialog() error {
477+ w.mu.RLock()
478+ defer w.mu.RUnlock()
479+
480+ if w.destroyed {
481+ return ErrWebViewDestroyed
482+ }
483+
484+ // TODO: Implement print operation
485+ // printOp := webkit.NewPrintOperation(w.view)
486+ // printOp.RunDialog(nil)
487+ return nil
488+}
489+
490+// RegisterKeyboardShortcut registers a keyboard shortcut handler
491+// This is a compatibility method - actual shortcut handling is done at the window level
492+func (w *WebView) RegisterKeyboardShortcut(key string, modifiers uint, handler func()) error {
493+ // TODO: Implement keyboard shortcut registration if needed
494+ // For now, shortcuts are handled at the window/application level
495+ return nil
496+}
497+
498+// SetWindowFeatures sets window features for popup windows
499+func (w *WebView) SetWindowFeatures(features *WindowFeatures) {
500+ // This is typically used for popup windows
501+ // The features would be applied when creating the window
502+}
503+
504+// IsActive returns whether this WebView is currently active/focused
505+func (w *WebView) IsActive() bool {
506+ w.mu.RLock()
507+ defer w.mu.RUnlock()
508+
509+ if w.destroyed {
510+ return false
511+ }
512+
513+ widget := w.view
514+ if widget != nil {
515+ return widget.IsFocus()
516+ }
517+ return false
518+}
519+
520+// Window returns the parent Window of this WebView
521+func (w *WebView) Window() *Window {
522+ w.mu.RLock()
523+ defer w.mu.RUnlock()
524+
525+ if w.destroyed {
526+ return nil
527+ }
528+
529+ // Get the root window
530+ widget := getWidget(w.view)
531+ if widget != nil {
532+ root := widget.Root()
533+ if root != nil {
534+ // Try to cast the native widget to a gtk.Window
535+ if obj := root.Cast(); obj != nil {
536+ if gtkWin, ok := obj.(*gtk.Window); ok {
537+ return &Window{win: gtkWin}
538+ }
539+ }
540+ }
541+ }
542+ return nil
543+}
544+
545+// UpdateContentFilters updates the content filtering rules
546+func (w *WebView) UpdateContentFilters(rules string) error {
547+ w.mu.RLock()
548+ defer w.mu.RUnlock()
549+
550+ if w.destroyed {
551+ return ErrWebViewDestroyed
552+ }
553+
554+ // TODO: Implement content filtering using WebKit's UserContentManager
555+ return nil
556+}
557+
558+// InitializeContentBlocking initializes content blocking with filter lists
559+func (w *WebView) InitializeContentBlocking(filterLists []string) error {
560+ w.mu.RLock()
561+ defer w.mu.RUnlock()
562+
563+ if w.destroyed {
564+ return ErrWebViewDestroyed
565+ }
566+
567+ // TODO: Implement content blocking initialization
568+ return nil
569+}
570+
571+// OnNavigate registers a navigation handler
572+func (w *WebView) OnNavigate(handler func(url string)) {
573+ w.mu.Lock()
574+ defer w.mu.Unlock()
575+
576+ // This wraps the URI changed handler
577+ w.onURIChanged = handler
578+}
M pkg/webkit/widgets.go +30 -18
@@ -238,50 +238,62 @@ func (i *Image) AsWidget() gtk.Widgetter {
238238
239239 // Widget helper functions for common operations
240240
241+// getWidget extracts the underlying *gtk.Widget from a Widgetter
242+// This is needed because some methods are only available on *gtk.Widget
243+func getWidget(w gtk.Widgetter) *gtk.Widget {
244+ if w == nil {
245+ return nil
246+ }
247+ // The Widgetter interface provides a way to get the base widget
248+ // In gotk4, we can cast to get access to the underlying widget
249+ if widget, ok := w.(interface{ Widget() *gtk.Widget }); ok {
250+ return widget.Widget()
251+ }
252+ // Fallback: try to type assert directly
253+ if widget, ok := w.(*gtk.Widget); ok {
254+ return widget
255+ }
256+ return nil
257+}
258+
241259 // WidgetShow makes a widget visible
242260 func WidgetShow(w gtk.Widgetter) {
243- if w == nil {
244- return
261+ if widget := getWidget(w); widget != nil {
262+ widget.SetVisible(true)
245263 }
246- w.Show()
247264 }
248265
249266 // WidgetHide makes a widget invisible
250267 func WidgetHide(w gtk.Widgetter) {
251- if w == nil {
252- return
268+ if widget := getWidget(w); widget != nil {
269+ widget.SetVisible(false)
253270 }
254- w.Hide()
255271 }
256272
257273 // WidgetSetVisible sets the visibility of a widget
258274 func WidgetSetVisible(w gtk.Widgetter, visible bool) {
259- if w == nil {
260- return
275+ if widget := getWidget(w); widget != nil {
276+ widget.SetVisible(visible)
261277 }
262- w.SetVisible(visible)
263278 }
264279
265280 // WidgetSetSizeRequest sets the minimum size request
266281 func WidgetSetSizeRequest(w gtk.Widgetter, width, height int) {
267- if w == nil {
268- return
282+ if widget := getWidget(w); widget != nil {
283+ widget.SetSizeRequest(width, height)
269284 }
270- w.SetSizeRequest(width, height)
271285 }
272286
273287 // WidgetQueueResize queues a resize operation
274288 func WidgetQueueResize(w gtk.Widgetter) {
275- if w == nil {
276- return
289+ if widget := getWidget(w); widget != nil {
290+ widget.QueueResize()
277291 }
278- w.QueueResize()
279292 }
280293
281294 // WidgetGrabFocus gives focus to a widget
282295 func WidgetGrabFocus(w gtk.Widgetter) {
283- if w == nil {
284- return
296+ if widget := getWidget(w); widget != nil {
297+ widget.GrabFocus()
285298 }
286- w.GrabFocus()
287299 }
@@ -238,50 +238,62 @@ func (i *Image) AsWidget() gtk.Widgetter {
238 238
239 // Widget helper functions for common operations239 // Widget helper functions for common operations
240 240
241+// getWidget extracts the underlying *gtk.Widget from a Widgetter
242+// This is needed because some methods are only available on *gtk.Widget
243+func getWidget(w gtk.Widgetter) *gtk.Widget {
244+ if w == nil {
245+ return nil
246+ }
247+ // The Widgetter interface provides a way to get the base widget
248+ // In gotk4, we can cast to get access to the underlying widget
249+ if widget, ok := w.(interface{ Widget() *gtk.Widget }); ok {
250+ return widget.Widget()
251+ }
252+ // Fallback: try to type assert directly
253+ if widget, ok := w.(*gtk.Widget); ok {
254+ return widget
255+ }
256+ return nil
257+}
258+
241 // WidgetShow makes a widget visible259 // WidgetShow makes a widget visible
242 func WidgetShow(w gtk.Widgetter) {260 func WidgetShow(w gtk.Widgetter) {
243- if w == nil {261+ if widget := getWidget(w); widget != nil {
244- return262+ widget.SetVisible(true)
245 }263 }
246- w.Show()
247 }264 }
248 265
249 // WidgetHide makes a widget invisible266 // WidgetHide makes a widget invisible
250 func WidgetHide(w gtk.Widgetter) {267 func WidgetHide(w gtk.Widgetter) {
251- if w == nil {268+ if widget := getWidget(w); widget != nil {
252- return269+ widget.SetVisible(false)
253 }270 }
254- w.Hide()
255 }271 }
256 272
257 // WidgetSetVisible sets the visibility of a widget273 // WidgetSetVisible sets the visibility of a widget
258 func WidgetSetVisible(w gtk.Widgetter, visible bool) {274 func WidgetSetVisible(w gtk.Widgetter, visible bool) {
259- if w == nil {275+ if widget := getWidget(w); widget != nil {
260- return276+ widget.SetVisible(visible)
261 }277 }
262- w.SetVisible(visible)
263 }278 }
264 279
265 // WidgetSetSizeRequest sets the minimum size request280 // WidgetSetSizeRequest sets the minimum size request
266 func WidgetSetSizeRequest(w gtk.Widgetter, width, height int) {281 func WidgetSetSizeRequest(w gtk.Widgetter, width, height int) {
267- if w == nil {282+ if widget := getWidget(w); widget != nil {
268- return283+ widget.SetSizeRequest(width, height)
269 }284 }
270- w.SetSizeRequest(width, height)
271 }285 }
272 286
273 // WidgetQueueResize queues a resize operation287 // WidgetQueueResize queues a resize operation
274 func WidgetQueueResize(w gtk.Widgetter) {288 func WidgetQueueResize(w gtk.Widgetter) {
275- if w == nil {289+ if widget := getWidget(w); widget != nil {
276- return290+ widget.QueueResize()
277 }291 }
278- w.QueueResize()
279 }292 }
280 293
281 // WidgetGrabFocus gives focus to a widget294 // WidgetGrabFocus gives focus to a widget
282 func WidgetGrabFocus(w gtk.Widgetter) {295 func WidgetGrabFocus(w gtk.Widgetter) {
283- if w == nil {296+ if widget := getWidget(w); widget != nil {
284- return297+ widget.GrabFocus()
285 }298 }
286- w.GrabFocus()
287 }299 }
M pkg/webkit/window.go +20 -1
@@ -41,11 +41,12 @@ func (w *Window) SetChild(child gtk.Widgetter) {
4141 }
4242
4343 // Show makes the window visible
44+// Note: In GTK4, Show() is deprecated. Use SetVisible(true) instead.
4445 func (w *Window) Show() {
4546 if w == nil || w.win == nil {
4647 return
4748 }
48- w.win.Show()
49+ w.win.SetVisible(true)
4950 }
5051
5152 // Present brings the window to the front
@@ -71,3 +72,21 @@ func (w *Window) AsWindow() *gtk.Window {
7172 }
7273 return w.win
7374 }
75+
76+// SetDefaultSize sets the default window size
77+func (w *Window) SetDefaultSize(width, height int) {
78+ if w == nil || w.win == nil {
79+ return
80+ }
81+ w.win.SetDefaultSize(width, height)
82+}
83+
84+// Destroy destroys the window
85+func (w *Window) Destroy() {
86+ if w == nil || w.win == nil {
87+ return
88+ }
89+ w.win.Destroy()
90+}
91+
92+// InitializeGlobalShortcuts is defined in shortcuts.go and implemented there
@@ -41,11 +41,12 @@ func (w *Window) SetChild(child gtk.Widgetter) {
41 }41 }
42 42
43 // Show makes the window visible43 // Show makes the window visible
44+// Note: In GTK4, Show() is deprecated. Use SetVisible(true) instead.
44 func (w *Window) Show() {45 func (w *Window) Show() {
45 if w == nil || w.win == nil {46 if w == nil || w.win == nil {
46 return47 return
47 }48 }
48- w.win.Show()49+ w.win.SetVisible(true)
49 }50 }
50 51
51 // Present brings the window to the front52 // Present brings the window to the front
@@ -71,3 +72,21 @@ func (w *Window) AsWindow() *gtk.Window {
71 }72 }
72 return w.win73 return w.win
73 }74 }
75+
76+// SetDefaultSize sets the default window size
77+func (w *Window) SetDefaultSize(width, height int) {
78+ if w == nil || w.win == nil {
79+ return
80+ }
81+ w.win.SetDefaultSize(width, height)
82+}
83+
84+// Destroy destroys the window
85+func (w *Window) Destroy() {
86+ if w == nil || w.win == nil {
87+ return
88+ }
89+ w.win.Destroy()
90+}
91+
92+// InitializeGlobalShortcuts is defined in shortcuts.go and implemented there

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