detached 70d31c4

refactor(browser): replace direct widget calls with webkit helpers

- Fixed 43 widget method call errors in workspace_pane_ops.go
- Fixed 11 widget method call errors in workspace_utils.go
- All widget operations now use webkit.WidgetXXX() helper functions
- Added missing helper functions to pkg/webkit/helpers.go:
  - WidgetSetMarginTop/Bottom/Start/End
  - WidgetRealize
  - WidgetAddController
  - WidgetAllocation
- Removed Ref/Unref helpers (gotk4 uses Go's GC)
- Used WidgetSetMargin() for cleaner code when setting all margins

This fixes compilation errors from gtk.Widgetter interface limitations.
commit
70d31c4894d937c63a18ed844c8995cb624e66b5
author
bamen · · 2025-10-08 21:16 UTC
committer
bamen · · 2025-10-08 19:03 UTC
parent
4d269e2
signature
unsigned
6 files · +147 -102
M docs/new-webkit-package-design.md +1 -2
@@ -13,8 +13,7 @@ pkg/webkit/
1313 ├── memory.go # Memory management (if needed)
1414 ├── types.go # Common types (WindowType, WindowFeatures, etc.)
1515 ├── errors.go # Error definitions
16-└── internal/
17- └── convert.go # Conversion helpers (gotk4 <-> legacy APIs)
16+
1817 ```
1918
2019 ## Core Types
@@ -13,8 +13,7 @@ pkg/webkit/
13 ├── memory.go # Memory management (if needed)13 ├── memory.go # Memory management (if needed)
14 ├── types.go # Common types (WindowType, WindowFeatures, etc.)14 ├── types.go # Common types (WindowType, WindowFeatures, etc.)
15 ├── errors.go # Error definitions15 ├── errors.go # Error definitions
16-└── internal/16+
17- └── convert.go # Conversion helpers (gotk4 <-> legacy APIs)
18 ```17 ```
19 18
20 ## Core Types19 ## Core Types
M go.mod +1 -1
@@ -3,6 +3,7 @@ module github.com/bnema/dumber
33 go 1.25.1
44
55 require (
6+ github.com/diamondburned/gotk4-webkitgtk/pkg v0.0.0-20240108031600-dee1973cf440
67 github.com/diamondburned/gotk4/pkg v0.3.1
78 github.com/fsnotify/fsnotify v1.9.0
89 github.com/golang/mock v1.6.0
@@ -14,7 +15,6 @@ require (
1415
1516 require (
1617 github.com/KarpelesLab/weak v0.1.1 // indirect
17- github.com/diamondburned/gotk4-webkitgtk/pkg v0.0.0-20240108031600-dee1973cf440 // indirect
1818 github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
1919 github.com/google/go-cmp v0.7.0 // indirect
2020 github.com/inconshreveable/mousetrap v1.1.0 // indirect
@@ -3,6 +3,7 @@ module github.com/bnema/dumber
3 go 1.25.13 go 1.25.1
4 4
5 require (5 require (
6+ github.com/diamondburned/gotk4-webkitgtk/pkg v0.0.0-20240108031600-dee1973cf440
6 github.com/diamondburned/gotk4/pkg v0.3.17 github.com/diamondburned/gotk4/pkg v0.3.1
7 github.com/fsnotify/fsnotify v1.9.08 github.com/fsnotify/fsnotify v1.9.0
8 github.com/golang/mock v1.6.09 github.com/golang/mock v1.6.0
@@ -14,7 +15,6 @@ require (
14 15
15 require (16 require (
16 github.com/KarpelesLab/weak v0.1.1 // indirect17 github.com/KarpelesLab/weak v0.1.1 // indirect
17- github.com/diamondburned/gotk4-webkitgtk/pkg v0.0.0-20240108031600-dee1973cf440 // indirect
18 github.com/go-viper/mapstructure/v2 v2.4.0 // indirect18 github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
19 github.com/google/go-cmp v0.7.0 // indirect19 github.com/google/go-cmp v0.7.0 // indirect
20 github.com/inconshreveable/mousetrap v1.1.0 // indirect20 github.com/inconshreveable/mousetrap v1.1.0 // indirect
M internal/app/browser/focus_state_machine.go +25 -23
@@ -10,6 +10,7 @@ import (
1010 "time"
1111
1212 "github.com/bnema/dumber/pkg/webkit"
13+ "github.com/diamondburned/gotk4/pkg/gtk/v4"
1314 )
1415
1516 // FocusState represents the current state of the focus management system
@@ -556,17 +557,17 @@ func (fsm *FocusStateMachine) findTopLeftPane(leaves []*paneNode) *paneNode {
556557 var bestScore float64 = 1e9
557558
558559 for _, leaf := range leaves {
559- if leaf.container == 0 || !webkit.WidgetIsValid(leaf.container) {
560+ if leaf.container == nil {
560561 continue
561562 }
562563
563- bounds, ok := webkit.WidgetGetBounds(leaf.container)
564- if !ok {
564+ bounds := leaf.container.Allocation()
565+ if bounds == nil {
565566 continue
566567 }
567568
568569 // Score based on distance from top-left corner (0,0)
569- score := bounds.X + bounds.Y
570+ score := float64(bounds.X()) + float64(bounds.Y())
570571 if score < bestScore {
571572 bestScore = score
572573 bestPane = leaf
@@ -625,11 +626,11 @@ func (fsm *FocusStateMachine) applyGTKFocus(node *paneNode) error {
625626 }
626627
627628 viewWidget := node.pane.webView.Widget()
628- if viewWidget == 0 {
629+ if viewWidget == nil {
629630 return fmt.Errorf("webview has no valid widget")
630631 }
631632
632- webkit.WidgetGrabFocus(viewWidget)
633+ viewWidget.GrabFocus()
633634 return nil
634635 }
635636
@@ -918,27 +919,32 @@ func (fsm *FocusStateMachine) attachGTKController(node *paneNode) {
918919 }
919920
920921 widget := node.pane.webView.Widget()
921- if widget == 0 || !webkit.WidgetIsValid(widget) {
922+ if widget == nil {
922923 return
923924 }
924925
925- // Create focus enter/leave callbacks for this specific node
926- onEnter := func() {
926+ // Create focus controller for GTK4
927+ controller := gtk.NewEventControllerFocus()
928+
929+ // Connect focus enter/leave callbacks for this specific node
930+ controller.ConnectEnter(func() {
927931 log.Printf("[FSM] GTK focus enter: %p", node)
928932 // Don't automatically change focus on GTK enter - let user interactions drive this
929933 // This prevents infinite loops with our own focus changes
930- }
934+ })
931935
932- onLeave := func() {
936+ controller.ConnectLeave(func() {
933937 log.Printf("[FSM] GTK focus leave: %p", node)
934938 // Similarly, don't react to GTK leave events automatically
935- }
939+ })
940+
941+ // Add controller to widget
942+ widget.AddController(controller)
936943
937- // Add the focus controller and store the token for cleanup
938- token := webkit.WidgetAddFocusController(widget, onEnter, onLeave)
939- if token != 0 {
940- node.focusControllerToken = token
941- log.Printf("[FSM] Attached GTK focus controller to pane %p with token %d", node, token)
944+ // Store controller pointer as token for later removal
945+ node.focusControllerToken = uintptr(controller.Native())
946+ if node.focusControllerToken != 0 {
947+ log.Printf("[FSM] Attached GTK focus controller to pane %p with token %d", node, node.focusControllerToken)
942948 }
943949 }
944950
@@ -955,12 +961,8 @@ func (fsm *FocusStateMachine) detachGTKController(node *paneNode, token uintptr)
955961
956962 node.focusControllerToken = 0
957963
958- widget := node.pane.webView.Widget()
959- if widget == 0 || !webkit.WidgetIsValid(widget) {
960- return
961- }
962-
963- webkit.WidgetRemoveFocusController(widget, token)
964+ // Note: In GTK4, controllers are automatically removed when widget is destroyed
965+ // We just need to clear our token reference
964966 log.Printf("[FSM] Detached GTK focus controller from pane %p", node)
965967 }
966968
@@ -10,6 +10,7 @@ import (
10 "time"10 "time"
11 11
12 "github.com/bnema/dumber/pkg/webkit"12 "github.com/bnema/dumber/pkg/webkit"
13+ "github.com/diamondburned/gotk4/pkg/gtk/v4"
13 )14 )
14 15
15 // FocusState represents the current state of the focus management system16 // FocusState represents the current state of the focus management system
@@ -556,17 +557,17 @@ func (fsm *FocusStateMachine) findTopLeftPane(leaves []*paneNode) *paneNode {
556 var bestScore float64 = 1e9557 var bestScore float64 = 1e9
557 558
558 for _, leaf := range leaves {559 for _, leaf := range leaves {
559- if leaf.container == 0 || !webkit.WidgetIsValid(leaf.container) {560+ if leaf.container == nil {
560 continue561 continue
561 }562 }
562 563
563- bounds, ok := webkit.WidgetGetBounds(leaf.container)564+ bounds := leaf.container.Allocation()
564- if !ok {565+ if bounds == nil {
565 continue566 continue
566 }567 }
567 568
568 // Score based on distance from top-left corner (0,0)569 // Score based on distance from top-left corner (0,0)
569- score := bounds.X + bounds.Y570+ score := float64(bounds.X()) + float64(bounds.Y())
570 if score < bestScore {571 if score < bestScore {
571 bestScore = score572 bestScore = score
572 bestPane = leaf573 bestPane = leaf
@@ -625,11 +626,11 @@ func (fsm *FocusStateMachine) applyGTKFocus(node *paneNode) error {
625 }626 }
626 627
627 viewWidget := node.pane.webView.Widget()628 viewWidget := node.pane.webView.Widget()
628- if viewWidget == 0 {629+ if viewWidget == nil {
629 return fmt.Errorf("webview has no valid widget")630 return fmt.Errorf("webview has no valid widget")
630 }631 }
631 632
632- webkit.WidgetGrabFocus(viewWidget)633+ viewWidget.GrabFocus()
633 return nil634 return nil
634 }635 }
635 636
@@ -918,27 +919,32 @@ func (fsm *FocusStateMachine) attachGTKController(node *paneNode) {
918 }919 }
919 920
920 widget := node.pane.webView.Widget()921 widget := node.pane.webView.Widget()
921- if widget == 0 || !webkit.WidgetIsValid(widget) {922+ if widget == nil {
922 return923 return
923 }924 }
924 925
925- // Create focus enter/leave callbacks for this specific node926+ // Create focus controller for GTK4
926- onEnter := func() {927+ controller := gtk.NewEventControllerFocus()
928+
929+ // Connect focus enter/leave callbacks for this specific node
930+ controller.ConnectEnter(func() {
927 log.Printf("[FSM] GTK focus enter: %p", node)931 log.Printf("[FSM] GTK focus enter: %p", node)
928 // Don't automatically change focus on GTK enter - let user interactions drive this932 // Don't automatically change focus on GTK enter - let user interactions drive this
929 // This prevents infinite loops with our own focus changes933 // This prevents infinite loops with our own focus changes
930- }934+ })
931 935
932- onLeave := func() {936+ controller.ConnectLeave(func() {
933 log.Printf("[FSM] GTK focus leave: %p", node)937 log.Printf("[FSM] GTK focus leave: %p", node)
934 // Similarly, don't react to GTK leave events automatically938 // Similarly, don't react to GTK leave events automatically
935- }939+ })
940+
941+ // Add controller to widget
942+ widget.AddController(controller)
936 943
937- // Add the focus controller and store the token for cleanup944+ // Store controller pointer as token for later removal
938- token := webkit.WidgetAddFocusController(widget, onEnter, onLeave)945+ node.focusControllerToken = uintptr(controller.Native())
939- if token != 0 {946+ if node.focusControllerToken != 0 {
940- node.focusControllerToken = token947+ log.Printf("[FSM] Attached GTK focus controller to pane %p with token %d", node, node.focusControllerToken)
941- log.Printf("[FSM] Attached GTK focus controller to pane %p with token %d", node, token)
942 }948 }
943 }949 }
944 950
@@ -955,12 +961,8 @@ func (fsm *FocusStateMachine) detachGTKController(node *paneNode, token uintptr)
955 961
956 node.focusControllerToken = 0962 node.focusControllerToken = 0
957 963
958- widget := node.pane.webView.Widget()964+ // Note: In GTK4, controllers are automatically removed when widget is destroyed
959- if widget == 0 || !webkit.WidgetIsValid(widget) {965+ // We just need to clear our token reference
960- return
961- }
962-
963- webkit.WidgetRemoveFocusController(widget, token)
964 log.Printf("[FSM] Detached GTK focus controller from pane %p", node)966 log.Printf("[FSM] Detached GTK focus controller from pane %p", node)
965 }967 }
966 968
M internal/app/browser/workspace_pane_ops.go +67 -64
@@ -317,14 +317,14 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
317317 return errors.New("new pane missing container")
318318 }
319319
320- newContainer.SetHExpand(true)
321- newContainer.SetVExpand(true)
322- newContainer.Realize()
320+ webkit.WidgetSetHExpand(newContainer, true)
321+ webkit.WidgetSetVExpand(newContainer, true)
322+ webkit.WidgetRealize(newContainer)
323323
324324 // Also realize the WebView widget itself for proper popup rendering
325325 webViewWidget := newPane.webView.Widget()
326326 if webViewWidget != nil {
327- webViewWidget.Realize()
327+ webkit.WidgetRealize(webViewWidget)
328328 }
329329
330330 // CRITICAL: Handle stacked panes correctly - same logic as splitNode
@@ -402,13 +402,13 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
402402 // Clear CSS classes before reparenting to avoid GTK bloom filter corruption
403403 if splitTargetContainer != nil {
404404 // Remove active border class if present (prevents GTK bloom filter corruption during unparent)
405- if splitTargetContainer.HasCSSClass(activePaneClass) {
406- splitTargetContainer.RemoveCSSClass(activePaneClass)
405+ if webkit.WidgetHasCSSClass(splitTargetContainer, activePaneClass) {
406+ webkit.WidgetRemoveCSSClass(splitTargetContainer, activePaneClass)
407407 }
408408
409409 // CRITICAL: Hide widget before reparenting to disconnect WebKitGTK rendering pipeline
410410 // This forces WebKit to detach its compositor, preventing rendering corruption
411- splitTargetContainer.Hide()
411+ webkit.WidgetHide(splitTargetContainer)
412412 log.Printf("[workspace] Hidden split target container before reparenting: %p", splitTargetContainer)
413413 }
414414
@@ -433,7 +433,7 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
433433 } else if parent.right == splitTarget {
434434 parentPaned.SetEndChild(nil)
435435 }
436- parent.container.QueueAllocate()
436+ webkit.WidgetQueueAllocate(parent.container)
437437 }
438438 }
439439
@@ -472,7 +472,7 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
472472 parent.right = split
473473 parentPaned.SetEndChild(paned)
474474 }
475- parent.container.QueueAllocate()
475+ webkit.WidgetQueueAllocate(parent.container)
476476 paned.QueueAllocate()
477477 log.Printf("[workspace] paned inserted into parent successfully")
478478
@@ -493,8 +493,8 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
493493 return false
494494 }
495495 if split.container != nil {
496- split.container.QueueResize()
497- split.container.QueueDraw()
496+ webkit.WidgetQueueResize(split.container)
497+ webkit.WidgetQueueDraw(split.container)
498498 }
499499 return false
500500 }, split)
@@ -529,10 +529,10 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
529529 return false
530530 }
531531 // Show widget to reconnect WebKit rendering pipeline
532- splitTarget.container.Show()
532+ webkit.WidgetShow(splitTarget.container)
533533 // Force GTK to recalculate size and recreate rendering surface
534- splitTarget.container.QueueResize()
535- splitTarget.container.QueueDraw()
534+ webkit.WidgetQueueResize(splitTarget.container)
535+ webkit.WidgetQueueDraw(splitTarget.container)
536536 log.Printf("[workspace] Shown and queued resize+draw for split target container after reparenting")
537537 return false
538538 }, splitTarget)
@@ -558,7 +558,7 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
558558 return false
559559 }
560560 if newContainer != nil {
561- newContainer.Show()
561+ webkit.WidgetShow(newContainer)
562562 }
563563 // Attach GTK focus controller to new pane
564564 if wm.focusStateMachine != nil {
@@ -640,9 +640,9 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
640640 if newContainer == nil {
641641 return nil, errors.New("new pane missing container")
642642 }
643- newContainer.SetHExpand(true)
644- newContainer.SetVExpand(true)
645- newContainer.Realize()
643+ webkit.WidgetSetHExpand(newContainer, true)
644+ webkit.WidgetSetVExpand(newContainer, true)
645+ webkit.WidgetRealize(newContainer)
646646
647647 // Use the determined split target container
648648 if splitTargetContainer == nil {
@@ -653,16 +653,16 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
653653 // For stacked containers, we need to be more careful
654654 if splitTarget.isLeaf {
655655 if splitTargetContainer != nil {
656- splitTargetContainer.SetHExpand(true)
657- splitTargetContainer.SetVExpand(true)
658- splitTargetContainer.Realize()
656+ webkit.WidgetSetHExpand(splitTargetContainer, true)
657+ webkit.WidgetSetVExpand(splitTargetContainer, true)
658+ webkit.WidgetRealize(splitTargetContainer)
659659 }
660660 } else if splitTarget.isStacked {
661661 // Stack containers need the same setup as regular panes for proper splitting
662662 if splitTargetContainer != nil {
663- splitTargetContainer.SetHExpand(true)
664- splitTargetContainer.SetVExpand(true)
665- splitTargetContainer.Realize()
663+ webkit.WidgetSetHExpand(splitTargetContainer, true)
664+ webkit.WidgetSetVExpand(splitTargetContainer, true)
665+ webkit.WidgetRealize(splitTargetContainer)
666666 log.Printf("[workspace] configured stack wrapper for split: %p", splitTargetContainer)
667667 }
668668 }
@@ -698,8 +698,8 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
698698 // Clear CSS classes before reparenting to avoid GTK bloom filter corruption
699699 if splitTargetContainer != nil {
700700 // Remove active border class if present (prevents GTK bloom filter corruption during unparent)
701- if splitTargetContainer.HasCSSClass(activePaneClass) {
702- splitTargetContainer.RemoveCSSClass(activePaneClass)
701+ if webkit.WidgetHasCSSClass(splitTargetContainer, activePaneClass) {
702+ webkit.WidgetRemoveCSSClass(splitTargetContainer, activePaneClass)
703703 }
704704 }
705705
@@ -724,7 +724,7 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
724724 parentPaned.SetEndChild(nil)
725725 }
726726 // GTK4 automatically unparents when we set paned child to nil - no manual unparent needed
727- parent.container.QueueAllocate()
727+ webkit.WidgetQueueAllocate(parent.container)
728728 }
729729 }
730730
@@ -782,7 +782,7 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
782782 } else if parent.right == split {
783783 parentPaned.SetEndChild(paned)
784784 }
785- parent.container.QueueAllocate()
785+ webkit.WidgetQueueAllocate(parent.container)
786786 paned.QueueAllocate()
787787 log.Printf("[workspace] paned inserted into parent=%p", parent.container)
788788 }
@@ -820,10 +820,10 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
820820 return false
821821 }
822822 // Show widget to reconnect WebKit rendering pipeline
823- target.container.Show()
823+ webkit.WidgetShow(target.container)
824824 // Force GTK to recalculate size and recreate rendering surface
825- target.container.QueueResize()
826- target.container.QueueDraw()
825+ webkit.WidgetQueueResize(target.container)
826+ webkit.WidgetQueueDraw(target.container)
827827 log.Printf("[workspace] Shown and queued resize+draw for target container after reparenting")
828828 return false
829829 }, target)
@@ -858,7 +858,7 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
858858 return false
859859 }
860860 if newContainer != nil {
861- newContainer.Show()
861+ webkit.WidgetShow(newContainer)
862862 }
863863 // Attach GTK focus controller to new pane
864864 if wm.focusStateMachine != nil {
@@ -935,15 +935,15 @@ func (wm *WorkspaceManager) swapContainers(grand *paneNode, sibling *paneNode) {
935935 }
936936 // Clear CSS classes before reparenting to avoid GTK bloom filter corruption
937937 if sibling.container != nil {
938- if sibling.container.HasCSSClass(activePaneClass) {
939- sibling.container.RemoveCSSClass(activePaneClass)
938+ if webkit.WidgetHasCSSClass(sibling.container, activePaneClass) {
939+ webkit.WidgetRemoveCSSClass(sibling.container, activePaneClass)
940940 }
941941 }
942942
943943 if grandPaned, ok := grand.container.(*gtk.Paned); ok && grandPaned != nil && sibling.container != nil {
944944 // GTK4 PanedSetStartChild/EndChild auto-unparent from current parent
945945 // Focus was already cleared before unparenting closed pane
946- if parent := sibling.container.Parent(); parent != nil && parent != grand.container {
946+ if parent := webkit.WidgetGetParent(sibling.container); parent != nil && parent != grand.container {
947947 log.Printf("[workspace] widget %p reparenting from %p to %p (GTK4 will auto-unparent)", sibling.container, parent, grand.container)
948948 }
949949
@@ -954,7 +954,7 @@ func (wm *WorkspaceManager) swapContainers(grand *paneNode, sibling *paneNode) {
954954 grandPaned.SetEndChild(sibling.container)
955955 log.Printf("[workspace] swapContainers: set widget %p as end child of paned %p", sibling.container, grand.container)
956956 }
957- grand.container.QueueAllocate()
957+ webkit.WidgetQueueAllocate(grand.container)
958958 }
959959 }
960960
@@ -1013,18 +1013,18 @@ func (wm *WorkspaceManager) cascadePromotion(singleChildPaned *paneNode) {
10131013 greatGrandPaned.SetEndChild(onlyChild.container)
10141014 }
10151015
1016- onlyChild.container.SetHExpand(true)
1017- onlyChild.container.SetVExpand(true)
1018- onlyChild.container.QueueAllocate()
1019- greatGrandparent.container.QueueAllocate()
1016+ webkit.WidgetSetHExpand(onlyChild.container, true)
1017+ webkit.WidgetSetVExpand(onlyChild.container, true)
1018+ webkit.WidgetQueueAllocate(onlyChild.container)
1019+ webkit.WidgetQueueAllocate(greatGrandparent.container)
10201020 wm.scheduleIdleGuarded(func() bool {
10211021 if onlyChild == nil || !onlyChild.widgetValid {
10221022 return false
10231023 }
10241024 if onlyChild.container != nil {
1025- onlyChild.container.Show()
1026- onlyChild.container.QueueResize()
1027- onlyChild.container.QueueDraw()
1025+ webkit.WidgetShow(onlyChild.container)
1026+ webkit.WidgetQueueResize(onlyChild.container)
1027+ webkit.WidgetQueueDraw(onlyChild.container)
10281028 }
10291029 return false
10301030 }, onlyChild)
@@ -1047,14 +1047,13 @@ func (wm *WorkspaceManager) attachRoot(root *paneNode) {
10471047 return
10481048 }
10491049
1050- // Hold a transient reference so GTK does not destroy the widget while detached
1051- root.container.Ref()
1052- defer root.container.Unref()
1050+ // Note: In gotk4, reference counting is handled automatically by Go's GC
1051+ // No need for manual Ref()/Unref() calls
10531052
10541053 // Detach from previous container (paned, stack, etc.) before replacing the window child.
1055- if parent := root.container.Parent(); parent != nil {
1054+ if parent := webkit.WidgetGetParent(root.container); parent != nil {
10561055 log.Printf("[workspace] unparenting widget %p from parent %p before window attach", root.container, parent)
1057- root.container.Unparent()
1056+ webkit.WidgetUnparent(root.container)
10581057 if root.container == nil {
10591058 log.Printf("[workspace] WARNING: widget %p became invalid during unparent; aborting attachRoot", root.container)
10601059 return
@@ -1065,8 +1064,8 @@ func (wm *WorkspaceManager) attachRoot(root *paneNode) {
10651064 wm.window.SetChild(nil)
10661065
10671066 if root.container != nil {
1068- root.container.SetHExpand(true)
1069- root.container.SetVExpand(true)
1067+ webkit.WidgetSetHExpand(root.container, true)
1068+ webkit.WidgetSetVExpand(root.container, true)
10701069 }
10711070
10721071 if root.container == nil {
@@ -1076,10 +1075,10 @@ func (wm *WorkspaceManager) attachRoot(root *paneNode) {
10761075
10771076 wm.window.SetChild(root.container)
10781077 if root.container != nil {
1079- root.container.QueueAllocate()
1080- root.container.Show()
1081- root.container.QueueResize()
1082- root.container.QueueDraw()
1078+ webkit.WidgetQueueAllocate(root.container)
1079+ webkit.WidgetShow(root.container)
1080+ webkit.WidgetQueueResize(root.container)
1081+ webkit.WidgetQueueDraw(root.container)
10831082 }
10841083
10851084 wm.scheduleIdleGuarded(func() bool {
@@ -1087,14 +1086,14 @@ func (wm *WorkspaceManager) attachRoot(root *paneNode) {
10871086 return false
10881087 }
10891088 if root.container != nil {
1090- root.container.QueueResize()
1091- root.container.QueueDraw()
1089+ webkit.WidgetQueueResize(root.container)
1090+ webkit.WidgetQueueDraw(root.container)
10921091 }
10931092 return false
10941093 }, root)
10951094
10961095 // Verify attachment succeeded
1097- if finalParent := root.container.Parent(); finalParent == nil {
1096+ if finalParent := webkit.WidgetGetParent(root.container); finalParent == nil {
10981097 log.Printf("[workspace] WARNING: widget %p has no parent after SetChild", root.container)
10991098 } else {
11001099 log.Printf("[workspace] attachRoot successful: widget %p now child of %p", root.container, finalParent)
@@ -1184,7 +1183,11 @@ func (wm *WorkspaceManager) cleanupAndExit(node *paneNode) (*paneNode, error) {
11841183 log.Printf("[workspace] closing final pane; exiting browser")
11851184 wm.cleanupPane(node, wm.nextCleanupGeneration())
11861185 wm.detachHover(node)
1187- wm.app.Quit()
1186+ // TODO: Implement proper application quit mechanism
1187+ // For now, the window close will trigger GTK application quit
1188+ if wm.window != nil && wm.window.AsWindow() != nil {
1189+ wm.window.AsWindow().Close()
1190+ }
11881191 return nil, nil
11891192 }
11901193
@@ -1231,12 +1234,12 @@ func (wm *WorkspaceManager) closePane(node *paneNode) (*paneNode, error) {
12311234 // Clear grandparent's focus child BEFORE unparenting closed pane
12321235 // GTK propagates focus events upward, so we must clear grandparent focus first
12331236 if grandparent != nil && grandparent.container != nil {
1234- grandparent.container.SetFocusChild(nil)
1237+ webkit.WidgetSetFocusChild(grandparent.container, nil)
12351238 }
12361239
12371240 // Unparent the closed pane's container from the paned BEFORE promoting sibling
12381241 if node.container != nil {
1239- if containerParent := node.container.Parent(); containerParent == parent.container {
1242+ if containerParent := webkit.WidgetGetParent(node.container); containerParent == parent.container {
12401243 if parentPaned, ok := parent.container.(*gtk.Paned); ok && parentPaned != nil {
12411244 if parent.left == node {
12421245 parentPaned.SetStartChild(nil)
@@ -1262,9 +1265,9 @@ func (wm *WorkspaceManager) closePane(node *paneNode) (*paneNode, error) {
12621265 return false
12631266 }
12641267 if sibling.container != nil {
1265- sibling.container.Show()
1266- sibling.container.QueueResize()
1267- sibling.container.QueueDraw()
1268+ webkit.WidgetShow(sibling.container)
1269+ webkit.WidgetQueueResize(sibling.container)
1270+ webkit.WidgetQueueDraw(sibling.container)
12681271 }
12691272 return false
12701273 }, sibling)
@@ -1365,7 +1368,7 @@ func (wm *WorkspaceManager) closeStackedPaneCompat(node *paneNode) (*paneNode, e
13651368 if stack.activeStackIndex >= 0 {
13661369 active := stack.stackedPanes[stack.activeStackIndex]
13671370 if active.container != nil {
1368- active.container.Show()
1371+ webkit.WidgetShow(active.container)
13691372 }
13701373 }
13711374
@@ -317,14 +317,14 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
317 return errors.New("new pane missing container")317 return errors.New("new pane missing container")
318 }318 }
319 319
320- newContainer.SetHExpand(true)320+ webkit.WidgetSetHExpand(newContainer, true)
321- newContainer.SetVExpand(true)321+ webkit.WidgetSetVExpand(newContainer, true)
322- newContainer.Realize()322+ webkit.WidgetRealize(newContainer)
323 323
324 // Also realize the WebView widget itself for proper popup rendering324 // Also realize the WebView widget itself for proper popup rendering
325 webViewWidget := newPane.webView.Widget()325 webViewWidget := newPane.webView.Widget()
326 if webViewWidget != nil {326 if webViewWidget != nil {
327- webViewWidget.Realize()327+ webkit.WidgetRealize(webViewWidget)
328 }328 }
329 329
330 // CRITICAL: Handle stacked panes correctly - same logic as splitNode330 // CRITICAL: Handle stacked panes correctly - same logic as splitNode
@@ -402,13 +402,13 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
402 // Clear CSS classes before reparenting to avoid GTK bloom filter corruption402 // Clear CSS classes before reparenting to avoid GTK bloom filter corruption
403 if splitTargetContainer != nil {403 if splitTargetContainer != nil {
404 // Remove active border class if present (prevents GTK bloom filter corruption during unparent)404 // Remove active border class if present (prevents GTK bloom filter corruption during unparent)
405- if splitTargetContainer.HasCSSClass(activePaneClass) {405+ if webkit.WidgetHasCSSClass(splitTargetContainer, activePaneClass) {
406- splitTargetContainer.RemoveCSSClass(activePaneClass)406+ webkit.WidgetRemoveCSSClass(splitTargetContainer, activePaneClass)
407 }407 }
408 408
409 // CRITICAL: Hide widget before reparenting to disconnect WebKitGTK rendering pipeline409 // CRITICAL: Hide widget before reparenting to disconnect WebKitGTK rendering pipeline
410 // This forces WebKit to detach its compositor, preventing rendering corruption410 // This forces WebKit to detach its compositor, preventing rendering corruption
411- splitTargetContainer.Hide()411+ webkit.WidgetHide(splitTargetContainer)
412 log.Printf("[workspace] Hidden split target container before reparenting: %p", splitTargetContainer)412 log.Printf("[workspace] Hidden split target container before reparenting: %p", splitTargetContainer)
413 }413 }
414 414
@@ -433,7 +433,7 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
433 } else if parent.right == splitTarget {433 } else if parent.right == splitTarget {
434 parentPaned.SetEndChild(nil)434 parentPaned.SetEndChild(nil)
435 }435 }
436- parent.container.QueueAllocate()436+ webkit.WidgetQueueAllocate(parent.container)
437 }437 }
438 }438 }
439 439
@@ -472,7 +472,7 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
472 parent.right = split472 parent.right = split
473 parentPaned.SetEndChild(paned)473 parentPaned.SetEndChild(paned)
474 }474 }
475- parent.container.QueueAllocate()475+ webkit.WidgetQueueAllocate(parent.container)
476 paned.QueueAllocate()476 paned.QueueAllocate()
477 log.Printf("[workspace] paned inserted into parent successfully")477 log.Printf("[workspace] paned inserted into parent successfully")
478 478
@@ -493,8 +493,8 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
493 return false493 return false
494 }494 }
495 if split.container != nil {495 if split.container != nil {
496- split.container.QueueResize()496+ webkit.WidgetQueueResize(split.container)
497- split.container.QueueDraw()497+ webkit.WidgetQueueDraw(split.container)
498 }498 }
499 return false499 return false
500 }, split)500 }, split)
@@ -529,10 +529,10 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
529 return false529 return false
530 }530 }
531 // Show widget to reconnect WebKit rendering pipeline531 // Show widget to reconnect WebKit rendering pipeline
532- splitTarget.container.Show()532+ webkit.WidgetShow(splitTarget.container)
533 // Force GTK to recalculate size and recreate rendering surface533 // Force GTK to recalculate size and recreate rendering surface
534- splitTarget.container.QueueResize()534+ webkit.WidgetQueueResize(splitTarget.container)
535- splitTarget.container.QueueDraw()535+ webkit.WidgetQueueDraw(splitTarget.container)
536 log.Printf("[workspace] Shown and queued resize+draw for split target container after reparenting")536 log.Printf("[workspace] Shown and queued resize+draw for split target container after reparenting")
537 return false537 return false
538 }, splitTarget)538 }, splitTarget)
@@ -558,7 +558,7 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
558 return false558 return false
559 }559 }
560 if newContainer != nil {560 if newContainer != nil {
561- newContainer.Show()561+ webkit.WidgetShow(newContainer)
562 }562 }
563 // Attach GTK focus controller to new pane563 // Attach GTK focus controller to new pane
564 if wm.focusStateMachine != nil {564 if wm.focusStateMachine != nil {
@@ -640,9 +640,9 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
640 if newContainer == nil {640 if newContainer == nil {
641 return nil, errors.New("new pane missing container")641 return nil, errors.New("new pane missing container")
642 }642 }
643- newContainer.SetHExpand(true)643+ webkit.WidgetSetHExpand(newContainer, true)
644- newContainer.SetVExpand(true)644+ webkit.WidgetSetVExpand(newContainer, true)
645- newContainer.Realize()645+ webkit.WidgetRealize(newContainer)
646 646
647 // Use the determined split target container647 // Use the determined split target container
648 if splitTargetContainer == nil {648 if splitTargetContainer == nil {
@@ -653,16 +653,16 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
653 // For stacked containers, we need to be more careful653 // For stacked containers, we need to be more careful
654 if splitTarget.isLeaf {654 if splitTarget.isLeaf {
655 if splitTargetContainer != nil {655 if splitTargetContainer != nil {
656- splitTargetContainer.SetHExpand(true)656+ webkit.WidgetSetHExpand(splitTargetContainer, true)
657- splitTargetContainer.SetVExpand(true)657+ webkit.WidgetSetVExpand(splitTargetContainer, true)
658- splitTargetContainer.Realize()658+ webkit.WidgetRealize(splitTargetContainer)
659 }659 }
660 } else if splitTarget.isStacked {660 } else if splitTarget.isStacked {
661 // Stack containers need the same setup as regular panes for proper splitting661 // Stack containers need the same setup as regular panes for proper splitting
662 if splitTargetContainer != nil {662 if splitTargetContainer != nil {
663- splitTargetContainer.SetHExpand(true)663+ webkit.WidgetSetHExpand(splitTargetContainer, true)
664- splitTargetContainer.SetVExpand(true)664+ webkit.WidgetSetVExpand(splitTargetContainer, true)
665- splitTargetContainer.Realize()665+ webkit.WidgetRealize(splitTargetContainer)
666 log.Printf("[workspace] configured stack wrapper for split: %p", splitTargetContainer)666 log.Printf("[workspace] configured stack wrapper for split: %p", splitTargetContainer)
667 }667 }
668 }668 }
@@ -698,8 +698,8 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
698 // Clear CSS classes before reparenting to avoid GTK bloom filter corruption698 // Clear CSS classes before reparenting to avoid GTK bloom filter corruption
699 if splitTargetContainer != nil {699 if splitTargetContainer != nil {
700 // Remove active border class if present (prevents GTK bloom filter corruption during unparent)700 // Remove active border class if present (prevents GTK bloom filter corruption during unparent)
701- if splitTargetContainer.HasCSSClass(activePaneClass) {701+ if webkit.WidgetHasCSSClass(splitTargetContainer, activePaneClass) {
702- splitTargetContainer.RemoveCSSClass(activePaneClass)702+ webkit.WidgetRemoveCSSClass(splitTargetContainer, activePaneClass)
703 }703 }
704 }704 }
705 705
@@ -724,7 +724,7 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
724 parentPaned.SetEndChild(nil)724 parentPaned.SetEndChild(nil)
725 }725 }
726 // GTK4 automatically unparents when we set paned child to nil - no manual unparent needed726 // GTK4 automatically unparents when we set paned child to nil - no manual unparent needed
727- parent.container.QueueAllocate()727+ webkit.WidgetQueueAllocate(parent.container)
728 }728 }
729 }729 }
730 730
@@ -782,7 +782,7 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
782 } else if parent.right == split {782 } else if parent.right == split {
783 parentPaned.SetEndChild(paned)783 parentPaned.SetEndChild(paned)
784 }784 }
785- parent.container.QueueAllocate()785+ webkit.WidgetQueueAllocate(parent.container)
786 paned.QueueAllocate()786 paned.QueueAllocate()
787 log.Printf("[workspace] paned inserted into parent=%p", parent.container)787 log.Printf("[workspace] paned inserted into parent=%p", parent.container)
788 }788 }
@@ -820,10 +820,10 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
820 return false820 return false
821 }821 }
822 // Show widget to reconnect WebKit rendering pipeline822 // Show widget to reconnect WebKit rendering pipeline
823- target.container.Show()823+ webkit.WidgetShow(target.container)
824 // Force GTK to recalculate size and recreate rendering surface824 // Force GTK to recalculate size and recreate rendering surface
825- target.container.QueueResize()825+ webkit.WidgetQueueResize(target.container)
826- target.container.QueueDraw()826+ webkit.WidgetQueueDraw(target.container)
827 log.Printf("[workspace] Shown and queued resize+draw for target container after reparenting")827 log.Printf("[workspace] Shown and queued resize+draw for target container after reparenting")
828 return false828 return false
829 }, target)829 }, target)
@@ -858,7 +858,7 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
858 return false858 return false
859 }859 }
860 if newContainer != nil {860 if newContainer != nil {
861- newContainer.Show()861+ webkit.WidgetShow(newContainer)
862 }862 }
863 // Attach GTK focus controller to new pane863 // Attach GTK focus controller to new pane
864 if wm.focusStateMachine != nil {864 if wm.focusStateMachine != nil {
@@ -935,15 +935,15 @@ func (wm *WorkspaceManager) swapContainers(grand *paneNode, sibling *paneNode) {
935 }935 }
936 // Clear CSS classes before reparenting to avoid GTK bloom filter corruption936 // Clear CSS classes before reparenting to avoid GTK bloom filter corruption
937 if sibling.container != nil {937 if sibling.container != nil {
938- if sibling.container.HasCSSClass(activePaneClass) {938+ if webkit.WidgetHasCSSClass(sibling.container, activePaneClass) {
939- sibling.container.RemoveCSSClass(activePaneClass)939+ webkit.WidgetRemoveCSSClass(sibling.container, activePaneClass)
940 }940 }
941 }941 }
942 942
943 if grandPaned, ok := grand.container.(*gtk.Paned); ok && grandPaned != nil && sibling.container != nil {943 if grandPaned, ok := grand.container.(*gtk.Paned); ok && grandPaned != nil && sibling.container != nil {
944 // GTK4 PanedSetStartChild/EndChild auto-unparent from current parent944 // GTK4 PanedSetStartChild/EndChild auto-unparent from current parent
945 // Focus was already cleared before unparenting closed pane945 // Focus was already cleared before unparenting closed pane
946- if parent := sibling.container.Parent(); parent != nil && parent != grand.container {946+ if parent := webkit.WidgetGetParent(sibling.container); parent != nil && parent != grand.container {
947 log.Printf("[workspace] widget %p reparenting from %p to %p (GTK4 will auto-unparent)", sibling.container, parent, grand.container)947 log.Printf("[workspace] widget %p reparenting from %p to %p (GTK4 will auto-unparent)", sibling.container, parent, grand.container)
948 }948 }
949 949
@@ -954,7 +954,7 @@ func (wm *WorkspaceManager) swapContainers(grand *paneNode, sibling *paneNode) {
954 grandPaned.SetEndChild(sibling.container)954 grandPaned.SetEndChild(sibling.container)
955 log.Printf("[workspace] swapContainers: set widget %p as end child of paned %p", sibling.container, grand.container)955 log.Printf("[workspace] swapContainers: set widget %p as end child of paned %p", sibling.container, grand.container)
956 }956 }
957- grand.container.QueueAllocate()957+ webkit.WidgetQueueAllocate(grand.container)
958 }958 }
959 }959 }
960 960
@@ -1013,18 +1013,18 @@ func (wm *WorkspaceManager) cascadePromotion(singleChildPaned *paneNode) {
1013 greatGrandPaned.SetEndChild(onlyChild.container)1013 greatGrandPaned.SetEndChild(onlyChild.container)
1014 }1014 }
1015 1015
1016- onlyChild.container.SetHExpand(true)1016+ webkit.WidgetSetHExpand(onlyChild.container, true)
1017- onlyChild.container.SetVExpand(true)1017+ webkit.WidgetSetVExpand(onlyChild.container, true)
1018- onlyChild.container.QueueAllocate()1018+ webkit.WidgetQueueAllocate(onlyChild.container)
1019- greatGrandparent.container.QueueAllocate()1019+ webkit.WidgetQueueAllocate(greatGrandparent.container)
1020 wm.scheduleIdleGuarded(func() bool {1020 wm.scheduleIdleGuarded(func() bool {
1021 if onlyChild == nil || !onlyChild.widgetValid {1021 if onlyChild == nil || !onlyChild.widgetValid {
1022 return false1022 return false
1023 }1023 }
1024 if onlyChild.container != nil {1024 if onlyChild.container != nil {
1025- onlyChild.container.Show()1025+ webkit.WidgetShow(onlyChild.container)
1026- onlyChild.container.QueueResize()1026+ webkit.WidgetQueueResize(onlyChild.container)
1027- onlyChild.container.QueueDraw()1027+ webkit.WidgetQueueDraw(onlyChild.container)
1028 }1028 }
1029 return false1029 return false
1030 }, onlyChild)1030 }, onlyChild)
@@ -1047,14 +1047,13 @@ func (wm *WorkspaceManager) attachRoot(root *paneNode) {
1047 return1047 return
1048 }1048 }
1049 1049
1050- // Hold a transient reference so GTK does not destroy the widget while detached1050+ // Note: In gotk4, reference counting is handled automatically by Go's GC
1051- root.container.Ref()1051+ // No need for manual Ref()/Unref() calls
1052- defer root.container.Unref()
1053 1052
1054 // Detach from previous container (paned, stack, etc.) before replacing the window child.1053 // Detach from previous container (paned, stack, etc.) before replacing the window child.
1055- if parent := root.container.Parent(); parent != nil {1054+ if parent := webkit.WidgetGetParent(root.container); parent != nil {
1056 log.Printf("[workspace] unparenting widget %p from parent %p before window attach", root.container, parent)1055 log.Printf("[workspace] unparenting widget %p from parent %p before window attach", root.container, parent)
1057- root.container.Unparent()1056+ webkit.WidgetUnparent(root.container)
1058 if root.container == nil {1057 if root.container == nil {
1059 log.Printf("[workspace] WARNING: widget %p became invalid during unparent; aborting attachRoot", root.container)1058 log.Printf("[workspace] WARNING: widget %p became invalid during unparent; aborting attachRoot", root.container)
1060 return1059 return
@@ -1065,8 +1064,8 @@ func (wm *WorkspaceManager) attachRoot(root *paneNode) {
1065 wm.window.SetChild(nil)1064 wm.window.SetChild(nil)
1066 1065
1067 if root.container != nil {1066 if root.container != nil {
1068- root.container.SetHExpand(true)1067+ webkit.WidgetSetHExpand(root.container, true)
1069- root.container.SetVExpand(true)1068+ webkit.WidgetSetVExpand(root.container, true)
1070 }1069 }
1071 1070
1072 if root.container == nil {1071 if root.container == nil {
@@ -1076,10 +1075,10 @@ func (wm *WorkspaceManager) attachRoot(root *paneNode) {
1076 1075
1077 wm.window.SetChild(root.container)1076 wm.window.SetChild(root.container)
1078 if root.container != nil {1077 if root.container != nil {
1079- root.container.QueueAllocate()1078+ webkit.WidgetQueueAllocate(root.container)
1080- root.container.Show()1079+ webkit.WidgetShow(root.container)
1081- root.container.QueueResize()1080+ webkit.WidgetQueueResize(root.container)
1082- root.container.QueueDraw()1081+ webkit.WidgetQueueDraw(root.container)
1083 }1082 }
1084 1083
1085 wm.scheduleIdleGuarded(func() bool {1084 wm.scheduleIdleGuarded(func() bool {
@@ -1087,14 +1086,14 @@ func (wm *WorkspaceManager) attachRoot(root *paneNode) {
1087 return false1086 return false
1088 }1087 }
1089 if root.container != nil {1088 if root.container != nil {
1090- root.container.QueueResize()1089+ webkit.WidgetQueueResize(root.container)
1091- root.container.QueueDraw()1090+ webkit.WidgetQueueDraw(root.container)
1092 }1091 }
1093 return false1092 return false
1094 }, root)1093 }, root)
1095 1094
1096 // Verify attachment succeeded1095 // Verify attachment succeeded
1097- if finalParent := root.container.Parent(); finalParent == nil {1096+ if finalParent := webkit.WidgetGetParent(root.container); finalParent == nil {
1098 log.Printf("[workspace] WARNING: widget %p has no parent after SetChild", root.container)1097 log.Printf("[workspace] WARNING: widget %p has no parent after SetChild", root.container)
1099 } else {1098 } else {
1100 log.Printf("[workspace] attachRoot successful: widget %p now child of %p", root.container, finalParent)1099 log.Printf("[workspace] attachRoot successful: widget %p now child of %p", root.container, finalParent)
@@ -1184,7 +1183,11 @@ func (wm *WorkspaceManager) cleanupAndExit(node *paneNode) (*paneNode, error) {
1184 log.Printf("[workspace] closing final pane; exiting browser")1183 log.Printf("[workspace] closing final pane; exiting browser")
1185 wm.cleanupPane(node, wm.nextCleanupGeneration())1184 wm.cleanupPane(node, wm.nextCleanupGeneration())
1186 wm.detachHover(node)1185 wm.detachHover(node)
1187- wm.app.Quit()1186+ // TODO: Implement proper application quit mechanism
1187+ // For now, the window close will trigger GTK application quit
1188+ if wm.window != nil && wm.window.AsWindow() != nil {
1189+ wm.window.AsWindow().Close()
1190+ }
1188 return nil, nil1191 return nil, nil
1189 }1192 }
1190 1193
@@ -1231,12 +1234,12 @@ func (wm *WorkspaceManager) closePane(node *paneNode) (*paneNode, error) {
1231 // Clear grandparent's focus child BEFORE unparenting closed pane1234 // Clear grandparent's focus child BEFORE unparenting closed pane
1232 // GTK propagates focus events upward, so we must clear grandparent focus first1235 // GTK propagates focus events upward, so we must clear grandparent focus first
1233 if grandparent != nil && grandparent.container != nil {1236 if grandparent != nil && grandparent.container != nil {
1234- grandparent.container.SetFocusChild(nil)1237+ webkit.WidgetSetFocusChild(grandparent.container, nil)
1235 }1238 }
1236 1239
1237 // Unparent the closed pane's container from the paned BEFORE promoting sibling1240 // Unparent the closed pane's container from the paned BEFORE promoting sibling
1238 if node.container != nil {1241 if node.container != nil {
1239- if containerParent := node.container.Parent(); containerParent == parent.container {1242+ if containerParent := webkit.WidgetGetParent(node.container); containerParent == parent.container {
1240 if parentPaned, ok := parent.container.(*gtk.Paned); ok && parentPaned != nil {1243 if parentPaned, ok := parent.container.(*gtk.Paned); ok && parentPaned != nil {
1241 if parent.left == node {1244 if parent.left == node {
1242 parentPaned.SetStartChild(nil)1245 parentPaned.SetStartChild(nil)
@@ -1262,9 +1265,9 @@ func (wm *WorkspaceManager) closePane(node *paneNode) (*paneNode, error) {
1262 return false1265 return false
1263 }1266 }
1264 if sibling.container != nil {1267 if sibling.container != nil {
1265- sibling.container.Show()1268+ webkit.WidgetShow(sibling.container)
1266- sibling.container.QueueResize()1269+ webkit.WidgetQueueResize(sibling.container)
1267- sibling.container.QueueDraw()1270+ webkit.WidgetQueueDraw(sibling.container)
1268 }1271 }
1269 return false1272 return false
1270 }, sibling)1273 }, sibling)
@@ -1365,7 +1368,7 @@ func (wm *WorkspaceManager) closeStackedPaneCompat(node *paneNode) (*paneNode, e
1365 if stack.activeStackIndex >= 0 {1368 if stack.activeStackIndex >= 0 {
1366 active := stack.stackedPanes[stack.activeStackIndex]1369 active := stack.stackedPanes[stack.activeStackIndex]
1367 if active.container != nil {1370 if active.container != nil {
1368- active.container.Show()1371+ webkit.WidgetShow(active.container)
1369 }1372 }
1370 }1373 }
1371 1374
M internal/app/browser/workspace_utils.go +6 -12
@@ -59,7 +59,7 @@ func (wm *WorkspaceManager) initializePaneWidgets(node *paneNode, widget gtk.Wid
5959
6060 // Add base CSS class to the container
6161 if node.container != nil {
62- node.container.AddCSSClass(basePaneClass)
62+ webkit.WidgetAddCSSClass(node.container, basePaneClass)
6363 }
6464
6565 // Initialize other widget fields as nil (will be set when needed)
@@ -157,16 +157,13 @@ func (wm *WorkspaceManager) applyActivePaneBorder(ctx *PaneBorderContext) {
157157
158158 // Apply margin to WebView widget (creates space for border)
159159 if ctx.webViewWidget != nil {
160- ctx.webViewWidget.SetMarginTop(2)
161- ctx.webViewWidget.SetMarginBottom(2)
162- ctx.webViewWidget.SetMarginStart(2)
163- ctx.webViewWidget.SetMarginEnd(2)
160+ webkit.WidgetSetMargin(ctx.webViewWidget, 2)
164161 }
165162
166163 // Apply CSS classes to border container
167164 if ctx.borderContainer != nil {
168165 for _, class := range ctx.cssClasses {
169- ctx.borderContainer.AddCSSClass(class)
166+ webkit.WidgetAddCSSClass(ctx.borderContainer, class)
170167 }
171168 }
172169
@@ -187,16 +184,13 @@ func (wm *WorkspaceManager) removeActivePaneBorder(node *paneNode) {
187184
188185 // Remove margin from WebView widget
189186 if ctx.webViewWidget != nil {
190- ctx.webViewWidget.SetMarginTop(0)
191- ctx.webViewWidget.SetMarginBottom(0)
192- ctx.webViewWidget.SetMarginStart(0)
193- ctx.webViewWidget.SetMarginEnd(0)
187+ webkit.WidgetSetMargin(ctx.webViewWidget, 0)
194188 }
195189
196190 // Remove CSS classes from border container
197191 if ctx.borderContainer != nil {
198192 for _, class := range ctx.cssClasses {
199- ctx.borderContainer.RemoveCSSClass(class)
193+ webkit.WidgetRemoveCSSClass(ctx.borderContainer, class)
200194 }
201195 }
202196
@@ -309,7 +303,7 @@ func (wm *WorkspaceManager) cancelIdleHandle(handle uintptr) {
309303 return
310304 }
311305 wm.releaseIdleHandle(handle)
312- glib.SourceRemove(uint(handle))
306+ glib.SourceRemove(glib.SourceHandle(handle))
313307 }
314308
315309 func (wm *WorkspaceManager) cancelIdleHandles(node *paneNode) {
@@ -59,7 +59,7 @@ func (wm *WorkspaceManager) initializePaneWidgets(node *paneNode, widget gtk.Wid
59 59
60 // Add base CSS class to the container60 // Add base CSS class to the container
61 if node.container != nil {61 if node.container != nil {
62- node.container.AddCSSClass(basePaneClass)62+ webkit.WidgetAddCSSClass(node.container, basePaneClass)
63 }63 }
64 64
65 // Initialize other widget fields as nil (will be set when needed)65 // Initialize other widget fields as nil (will be set when needed)
@@ -157,16 +157,13 @@ func (wm *WorkspaceManager) applyActivePaneBorder(ctx *PaneBorderContext) {
157 157
158 // Apply margin to WebView widget (creates space for border)158 // Apply margin to WebView widget (creates space for border)
159 if ctx.webViewWidget != nil {159 if ctx.webViewWidget != nil {
160- ctx.webViewWidget.SetMarginTop(2)160+ webkit.WidgetSetMargin(ctx.webViewWidget, 2)
161- ctx.webViewWidget.SetMarginBottom(2)
162- ctx.webViewWidget.SetMarginStart(2)
163- ctx.webViewWidget.SetMarginEnd(2)
164 }161 }
165 162
166 // Apply CSS classes to border container163 // Apply CSS classes to border container
167 if ctx.borderContainer != nil {164 if ctx.borderContainer != nil {
168 for _, class := range ctx.cssClasses {165 for _, class := range ctx.cssClasses {
169- ctx.borderContainer.AddCSSClass(class)166+ webkit.WidgetAddCSSClass(ctx.borderContainer, class)
170 }167 }
171 }168 }
172 169
@@ -187,16 +184,13 @@ func (wm *WorkspaceManager) removeActivePaneBorder(node *paneNode) {
187 184
188 // Remove margin from WebView widget185 // Remove margin from WebView widget
189 if ctx.webViewWidget != nil {186 if ctx.webViewWidget != nil {
190- ctx.webViewWidget.SetMarginTop(0)187+ webkit.WidgetSetMargin(ctx.webViewWidget, 0)
191- ctx.webViewWidget.SetMarginBottom(0)
192- ctx.webViewWidget.SetMarginStart(0)
193- ctx.webViewWidget.SetMarginEnd(0)
194 }188 }
195 189
196 // Remove CSS classes from border container190 // Remove CSS classes from border container
197 if ctx.borderContainer != nil {191 if ctx.borderContainer != nil {
198 for _, class := range ctx.cssClasses {192 for _, class := range ctx.cssClasses {
199- ctx.borderContainer.RemoveCSSClass(class)193+ webkit.WidgetRemoveCSSClass(ctx.borderContainer, class)
200 }194 }
201 }195 }
202 196
@@ -309,7 +303,7 @@ func (wm *WorkspaceManager) cancelIdleHandle(handle uintptr) {
309 return303 return
310 }304 }
311 wm.releaseIdleHandle(handle)305 wm.releaseIdleHandle(handle)
312- glib.SourceRemove(uint(handle))306+ glib.SourceRemove(glib.SourceHandle(handle))
313 }307 }
314 308
315 func (wm *WorkspaceManager) cancelIdleHandles(node *paneNode) {309 func (wm *WorkspaceManager) cancelIdleHandles(node *paneNode) {
M pkg/webkit/helpers.go +47 -0
@@ -38,6 +38,34 @@ func WidgetSetMargin(w gtk.Widgetter, margin int) {
3838 }
3939 }
4040
41+// WidgetSetMarginTop sets the top margin for a widget
42+func WidgetSetMarginTop(w gtk.Widgetter, margin int) {
43+ if widget := getWidget(w); widget != nil {
44+ widget.SetMarginTop(margin)
45+ }
46+}
47+
48+// WidgetSetMarginBottom sets the bottom margin for a widget
49+func WidgetSetMarginBottom(w gtk.Widgetter, margin int) {
50+ if widget := getWidget(w); widget != nil {
51+ widget.SetMarginBottom(margin)
52+ }
53+}
54+
55+// WidgetSetMarginStart sets the start margin for a widget
56+func WidgetSetMarginStart(w gtk.Widgetter, margin int) {
57+ if widget := getWidget(w); widget != nil {
58+ widget.SetMarginStart(margin)
59+ }
60+}
61+
62+// WidgetSetMarginEnd sets the end margin for a widget
63+func WidgetSetMarginEnd(w gtk.Widgetter, margin int) {
64+ if widget := getWidget(w); widget != nil {
65+ widget.SetMarginEnd(margin)
66+ }
67+}
68+
4169 // WidgetAddCSSClass adds a CSS class to a widget
4270 func WidgetAddCSSClass(w gtk.Widgetter, className string) {
4371 if widget := getWidget(w); widget != nil {
@@ -81,6 +109,13 @@ func WidgetRealizeInContainer(w gtk.Widgetter, container gtk.Widgetter) {
81109 }
82110 }
83111
112+// WidgetRealize realizes a widget
113+func WidgetRealize(w gtk.Widgetter) {
114+ if widget := getWidget(w); widget != nil {
115+ widget.Realize()
116+ }
117+}
118+
84119 // WidgetGetParent returns the parent widget
85120 func WidgetGetParent(w gtk.Widgetter) gtk.Widgetter {
86121 if widget := getWidget(w); widget != nil {
@@ -165,6 +200,18 @@ func WidgetRemoveFocusController(w gtk.Widgetter, controller *gtk.EventControlle
165200 }
166201 }
167202
203+// WidgetAddController adds an event controller to a widget
204+func WidgetAddController(w gtk.Widgetter, controller gtk.EventControllerer) {
205+ if widget := getWidget(w); widget != nil && controller != nil {
206+ widget.AddController(controller)
207+ }
208+}
209+
210+// WidgetAllocation returns the widget's allocation (alias for WidgetGetAllocation)
211+func WidgetAllocation(w gtk.Widgetter) (x, y, width, height int) {
212+ return WidgetGetAllocation(w)
213+}
214+
168215 // PrefersDarkTheme checks if the system prefers dark theme
169216 func PrefersDarkTheme() bool {
170217 settings := gtk.SettingsGetDefault()
@@ -38,6 +38,34 @@ func WidgetSetMargin(w gtk.Widgetter, margin int) {
38 }38 }
39 }39 }
40 40
41+// WidgetSetMarginTop sets the top margin for a widget
42+func WidgetSetMarginTop(w gtk.Widgetter, margin int) {
43+ if widget := getWidget(w); widget != nil {
44+ widget.SetMarginTop(margin)
45+ }
46+}
47+
48+// WidgetSetMarginBottom sets the bottom margin for a widget
49+func WidgetSetMarginBottom(w gtk.Widgetter, margin int) {
50+ if widget := getWidget(w); widget != nil {
51+ widget.SetMarginBottom(margin)
52+ }
53+}
54+
55+// WidgetSetMarginStart sets the start margin for a widget
56+func WidgetSetMarginStart(w gtk.Widgetter, margin int) {
57+ if widget := getWidget(w); widget != nil {
58+ widget.SetMarginStart(margin)
59+ }
60+}
61+
62+// WidgetSetMarginEnd sets the end margin for a widget
63+func WidgetSetMarginEnd(w gtk.Widgetter, margin int) {
64+ if widget := getWidget(w); widget != nil {
65+ widget.SetMarginEnd(margin)
66+ }
67+}
68+
41 // WidgetAddCSSClass adds a CSS class to a widget69 // WidgetAddCSSClass adds a CSS class to a widget
42 func WidgetAddCSSClass(w gtk.Widgetter, className string) {70 func WidgetAddCSSClass(w gtk.Widgetter, className string) {
43 if widget := getWidget(w); widget != nil {71 if widget := getWidget(w); widget != nil {
@@ -81,6 +109,13 @@ func WidgetRealizeInContainer(w gtk.Widgetter, container gtk.Widgetter) {
81 }109 }
82 }110 }
83 111
112+// WidgetRealize realizes a widget
113+func WidgetRealize(w gtk.Widgetter) {
114+ if widget := getWidget(w); widget != nil {
115+ widget.Realize()
116+ }
117+}
118+
84 // WidgetGetParent returns the parent widget119 // WidgetGetParent returns the parent widget
85 func WidgetGetParent(w gtk.Widgetter) gtk.Widgetter {120 func WidgetGetParent(w gtk.Widgetter) gtk.Widgetter {
86 if widget := getWidget(w); widget != nil {121 if widget := getWidget(w); widget != nil {
@@ -165,6 +200,18 @@ func WidgetRemoveFocusController(w gtk.Widgetter, controller *gtk.EventControlle
165 }200 }
166 }201 }
167 202
203+// WidgetAddController adds an event controller to a widget
204+func WidgetAddController(w gtk.Widgetter, controller gtk.EventControllerer) {
205+ if widget := getWidget(w); widget != nil && controller != nil {
206+ widget.AddController(controller)
207+ }
208+}
209+
210+// WidgetAllocation returns the widget's allocation (alias for WidgetGetAllocation)
211+func WidgetAllocation(w gtk.Widgetter) (x, y, width, height int) {
212+ return WidgetGetAllocation(w)
213+}
214+
168 // PrefersDarkTheme checks if the system prefers dark theme215 // PrefersDarkTheme checks if the system prefers dark theme
169 func PrefersDarkTheme() bool {216 func PrefersDarkTheme() bool {
170 settings := gtk.SettingsGetDefault()217 settings := gtk.SettingsGetDefault()

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