detached a6a793c

refactor(browser): migrate workspace_pane_ops.go to gotk4 API

commit
a6a793cb7a7591b1007824b138a6990b54c8e2ac
author
bamen · · 2025-10-08 16:40 UTC
parent
2716a73
signature
unsigned
1 file · +206 -211
M internal/app/browser/workspace_pane_ops.go +206 -211
@@ -6,6 +6,7 @@ import (
66 "log"
77
88 "github.com/bnema/dumber/pkg/webkit"
9+ "github.com/diamondburned/gotk4/pkg/gtk/v4"
910 )
1011
1112 // closeContext manages the context for a pane close operation
@@ -339,7 +340,7 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
339340
340341 // Determine what we're actually splitting
341342 var splitTarget *paneNode
342- var splitTargetContainer uintptr
343+ var splitTargetContainer gtk.Widgetter
343344
344345 if stackContainer != nil {
345346 // Case: splitting from inside a stack - we split around the entire stack
@@ -353,22 +354,22 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
353354 log.Printf("[workspace] normal split from pane: target=%p", splitTarget)
354355 }
355356
356- if splitTargetContainer == 0 {
357+ if splitTargetContainer == nil {
357358 return errors.New("split target missing container")
358359 }
359360
360361 orientation, existingFirst := mapDirection(direction)
361362 log.Printf("[workspace] inserting popup direction=%s orientation=%v existingFirst=%v splitTarget.parent=%p", direction, orientation, existingFirst, splitTarget.parent)
362363
363- paned := webkit.NewPaned(orientation)
364- if paned == 0 {
364+ paned := gtk.NewPaned(orientation)
365+ if paned == nil {
365366 return errors.New("failed to create GtkPaned")
366367 }
367- webkit.WidgetSetHExpand(paned, true)
368- webkit.WidgetSetVExpand(paned, true)
368+ paned.SetHExpand(true)
369+ paned.SetVExpand(true)
369370 // For popup panes: preserve existing pane size, let popup take remaining space
370- webkit.PanedSetResizeStart(paned, false) // existing pane keeps its size
371- webkit.PanedSetResizeEnd(paned, true) // popup can resize
371+ paned.SetResizeStartChild(false) // existing pane keeps its size
372+ paned.SetResizeEndChild(true) // popup can resize
372373 log.Printf("[workspace] configured paned for popup: start=fixed, end=flexible")
373374
374375 newLeaf := &paneNode{
@@ -389,24 +390,26 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
389390 // CRITICAL: Capture parent paned's divider position BEFORE reparenting to preserve layout
390391 // Only attempt this if the parent container is actually a paned widget
391392 var parentDividerPos int
392- if parent != nil && parent.container != 0 && webkit.WidgetIsValid(parent.container) && webkit.IsPaned(parent.container) {
393- parentDividerPos = webkit.PanedGetPosition(parent.container)
394- log.Printf("[workspace] captured parent divider position: %d", parentDividerPos)
395- } else if parent != nil && parent.container != 0 {
396- log.Printf("[workspace] parent container is not a paned widget (likely a stacked container)")
393+ if parent != nil && parent.container != nil {
394+ if parentPaned, ok := parent.container.(*gtk.Paned); ok && parentPaned != nil {
395+ parentDividerPos = parentPaned.Position()
396+ log.Printf("[workspace] captured parent divider position: %d", parentDividerPos)
397+ } else {
398+ log.Printf("[workspace] parent container is not a paned widget (likely a stacked container)")
399+ }
397400 }
398401
399402 // Clear CSS classes before reparenting to avoid GTK bloom filter corruption
400- if splitTargetContainer != 0 && webkit.WidgetIsValid(splitTargetContainer) {
403+ if splitTargetContainer != nil {
401404 // Remove active border class if present (prevents GTK bloom filter corruption during unparent)
402- if webkit.WidgetHasCSSClass(splitTargetContainer, activePaneClass) {
403- webkit.WidgetRemoveCSSClass(splitTargetContainer, activePaneClass)
405+ if splitTargetContainer.HasCSSClass(activePaneClass) {
406+ splitTargetContainer.RemoveCSSClass(activePaneClass)
404407 }
405408
406409 // CRITICAL: Hide widget before reparenting to disconnect WebKitGTK rendering pipeline
407410 // This forces WebKit to detach its compositor, preventing rendering corruption
408- webkit.WidgetHide(splitTargetContainer)
409- log.Printf("[workspace] Hidden split target container before reparenting: %#x", splitTargetContainer)
411+ splitTargetContainer.Hide()
412+ log.Printf("[workspace] Hidden split target container before reparenting: %p", splitTargetContainer)
410413 }
411414
412415 // Prevent GTK from auto-removing controllers while the widget is temporarily unparented.
@@ -415,22 +418,22 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
415418 // Detach existing container from its current GTK parent before inserting into new paned.
416419 if parent == nil {
417420 // Split target is the root - remove it from the window
418- log.Printf("[workspace] removing split target container=%#x from window", splitTargetContainer)
421+ log.Printf("[workspace] removing split target container=%p from window", splitTargetContainer)
419422 if wm.window != nil {
420- wm.window.SetChild(0)
423+ wm.window.SetChild(nil)
421424 }
422425 // GTK4 will auto-unparent when we add to new paned - no manual unparent needed
423- } else if parent.container != 0 {
426+ } else if parent.container != nil {
424427 // Split target has a parent paned - unparent it from there
425428 log.Printf("[workspace] unparenting split target container from parent paned")
426- if webkit.WidgetIsValid(parent.container) {
427- log.Printf("[workspace] executing unparent: parentPtr=%#x", parent.container)
429+ if parentPaned, ok := parent.container.(*gtk.Paned); ok && parentPaned != nil {
430+ log.Printf("[workspace] executing unparent: parentPtr=%p", parent.container)
428431 if parent.left == splitTarget {
429- webkit.PanedSetStartChild(parent.container, 0)
432+ parentPaned.SetStartChild(nil)
430433 } else if parent.right == splitTarget {
431- webkit.PanedSetEndChild(parent.container, 0)
434+ parentPaned.SetEndChild(nil)
432435 }
433- webkit.WidgetQueueAllocate(parent.container)
436+ parent.container.QueueAllocate()
434437 }
435438 }
436439
@@ -438,15 +441,15 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
438441 if existingFirst {
439442 split.left = splitTarget
440443 split.right = newLeaf
441- webkit.PanedSetStartChild(paned, splitTargetContainer)
442- webkit.PanedSetEndChild(paned, newContainer)
443- log.Printf("[workspace] added splitTarget=%#x as start child, new=%#x as end child", splitTargetContainer, newContainer)
444+ paned.SetStartChild(splitTargetContainer)
445+ paned.SetEndChild(newContainer)
446+ log.Printf("[workspace] added splitTarget=%p as start child, new=%p as end child", splitTargetContainer, newContainer)
444447 } else {
445448 split.left = newLeaf
446449 split.right = splitTarget
447- webkit.PanedSetStartChild(paned, newContainer)
448- webkit.PanedSetEndChild(paned, splitTargetContainer)
449- log.Printf("[workspace] added new=%#x as start child, splitTarget=%#x as end child", newContainer, splitTargetContainer)
450+ paned.SetStartChild(newContainer)
451+ paned.SetEndChild(splitTargetContainer)
452+ log.Printf("[workspace] added new=%p as start child, splitTarget=%p as end child", newContainer, splitTargetContainer)
450453 }
451454
452455 splitTarget.parent = split
@@ -456,42 +459,42 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
456459 wm.root = split
457460 if wm.window != nil {
458461 wm.window.SetChild(paned)
459- webkit.WidgetQueueAllocate(paned)
460- log.Printf("[workspace] paned set as window child: paned=%#x", paned)
462+ paned.QueueAllocate()
463+ log.Printf("[workspace] paned set as window child: paned=%p", paned)
461464 }
462465 } else {
463- if webkit.WidgetIsValid(parent.container) {
464- log.Printf("[workspace] executing parent reparent: parentPtr=%#x, paned=%#x", parent.container, paned)
466+ if parentPaned, ok := parent.container.(*gtk.Paned); ok && parentPaned != nil {
467+ log.Printf("[workspace] executing parent reparent: parentPtr=%p, paned=%p", parent.container, paned)
465468 if parent.left == splitTarget {
466469 parent.left = split
467- webkit.PanedSetStartChild(parent.container, paned)
470+ parentPaned.SetStartChild(paned)
468471 } else if parent.right == splitTarget {
469472 parent.right = split
470- webkit.PanedSetEndChild(parent.container, paned)
473+ parentPaned.SetEndChild(paned)
471474 }
472- webkit.WidgetQueueAllocate(parent.container)
473- webkit.WidgetQueueAllocate(paned)
475+ parent.container.QueueAllocate()
476+ paned.QueueAllocate()
474477 log.Printf("[workspace] paned inserted into parent successfully")
475478
476479 // CRITICAL: Restore parent paned's divider position to preserve existing layout
477480 // Only attempt this if we captured a position and the parent is actually a paned widget
478- if parentDividerPos > 0 && webkit.IsPaned(parent.container) {
479- webkit.PanedSetPosition(parent.container, parentDividerPos)
481+ if parentDividerPos > 0 {
482+ parentPaned.SetPosition(parentDividerPos)
480483 log.Printf("[workspace] restored parent divider position: %d", parentDividerPos)
481484 }
482485 }
483486 }
484487
485- webkit.WidgetShow(paned)
488+ paned.Show()
486489
487490 // Force GTK to re-layout the new GtkPaned container once both children are attached.
488491 wm.scheduleIdleGuarded(func() bool {
489492 if split == nil || !split.widgetValid {
490493 return false
491494 }
492- if split.container != 0 && webkit.WidgetIsValid(split.container) {
493- webkit.WidgetQueueResize(split.container)
494- webkit.WidgetQueueDraw(split.container)
495+ if split.container != nil {
496+ split.container.QueueResize()
497+ split.container.QueueDraw()
495498 }
496499 return false
497500 }, split)
@@ -500,36 +503,36 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
500503 // GTK needs the widget to be realized before we can set position based on allocation
501504 // Schedule this to run after GTK has allocated space to the paned
502505 wm.scheduleIdleGuarded(func() bool {
503- if !webkit.WidgetIsValid(paned) {
506+ if paned == nil {
504507 return false
505508 }
506509 // Get paned allocation to calculate 50% position
507- alloc := webkit.WidgetGetAllocation(paned)
510+ alloc := paned.Allocation()
508511 var splitPos int
509- if orientation == webkit.OrientationHorizontal {
510- splitPos = alloc.Width / 2
512+ if orientation == gtk.OrientationHorizontal {
513+ splitPos = alloc.Width() / 2
511514 } else {
512- splitPos = alloc.Height / 2
515+ splitPos = alloc.Height() / 2
513516 }
514517 if splitPos > 0 {
515- webkit.PanedSetPosition(paned, splitPos)
516- log.Printf("[workspace] Set initial paned position to 50%%: %d (orientation=%d, size=%dx%d)", splitPos, orientation, alloc.Width, alloc.Height)
518+ paned.SetPosition(splitPos)
519+ log.Printf("[workspace] Set initial paned position to 50%%: %d (orientation=%d, size=%dx%d)", splitPos, orientation, alloc.Width(), alloc.Height())
517520 }
518521 return false
519522 }, split)
520523
521524 // CRITICAL: Show the split target container and force GTK to recreate rendering surface after reparenting
522525 // This reconnects WebKitGTK's rendering pipeline and fixes compositor sync issues
523- if splitTarget != nil && splitTarget.container != 0 {
526+ if splitTarget != nil && splitTarget.container != nil {
524527 wm.scheduleIdleGuarded(func() bool {
525- if splitTarget == nil || !splitTarget.widgetValid || splitTarget.container == 0 {
528+ if splitTarget == nil || !splitTarget.widgetValid || splitTarget.container == nil {
526529 return false
527530 }
528531 // Show widget to reconnect WebKit rendering pipeline
529- webkit.WidgetShow(splitTarget.container)
532+ splitTarget.container.Show()
530533 // Force GTK to recalculate size and recreate rendering surface
531- webkit.WidgetQueueResize(splitTarget.container)
532- webkit.WidgetQueueDraw(splitTarget.container)
534+ splitTarget.container.QueueResize()
535+ splitTarget.container.QueueDraw()
533536 log.Printf("[workspace] Shown and queued resize+draw for split target container after reparenting")
534537 return false
535538 }, splitTarget)
@@ -554,8 +557,8 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
554557 if newLeaf == nil || !newLeaf.widgetValid {
555558 return false
556559 }
557- if newContainer != 0 && webkit.WidgetIsValid(newContainer) {
558- webkit.WidgetShow(newContainer)
560+ if newContainer != nil {
561+ newContainer.Show()
559562 }
560563 // Attach GTK focus controller to new pane
561564 if wm.focusStateMachine != nil {
@@ -605,7 +608,7 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
605608
606609 // Determine what we're actually splitting
607610 var splitTarget *paneNode
608- var splitTargetContainer uintptr
611+ var splitTargetContainer gtk.Widgetter
609612
610613 if stackContainer != nil {
611614 // Case: splitting from inside a stack - we split around the entire stack
@@ -634,33 +637,33 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
634637 }
635638
636639 newContainer := newPane.webView.RootWidget()
637- if newContainer == 0 {
640+ if newContainer == nil {
638641 return nil, errors.New("new pane missing container")
639642 }
640- webkit.WidgetSetHExpand(newContainer, true)
641- webkit.WidgetSetVExpand(newContainer, true)
642- webkit.WidgetRealizeInContainer(newContainer)
643+ newContainer.SetHExpand(true)
644+ newContainer.SetVExpand(true)
645+ newContainer.Realize()
643646
644647 // Use the determined split target container
645- if splitTargetContainer == 0 {
648+ if splitTargetContainer == nil {
646649 return nil, errors.New("split target missing container")
647650 }
648651
649652 // Only apply GTK operations if this is a simple widget (leaf pane)
650653 // For stacked containers, we need to be more careful
651654 if splitTarget.isLeaf {
652- if webkit.WidgetIsValid(splitTargetContainer) {
653- webkit.WidgetSetHExpand(splitTargetContainer, true)
654- webkit.WidgetSetVExpand(splitTargetContainer, true)
655- webkit.WidgetRealizeInContainer(splitTargetContainer)
655+ if splitTargetContainer != nil {
656+ splitTargetContainer.SetHExpand(true)
657+ splitTargetContainer.SetVExpand(true)
658+ splitTargetContainer.Realize()
656659 }
657660 } else if splitTarget.isStacked {
658661 // Stack containers need the same setup as regular panes for proper splitting
659- if webkit.WidgetIsValid(splitTargetContainer) {
660- webkit.WidgetSetHExpand(splitTargetContainer, true)
661- webkit.WidgetSetVExpand(splitTargetContainer, true)
662- webkit.WidgetRealizeInContainer(splitTargetContainer)
663- log.Printf("[workspace] configured stack wrapper for split: %#x", splitTargetContainer)
662+ if splitTargetContainer != nil {
663+ splitTargetContainer.SetHExpand(true)
664+ splitTargetContainer.SetVExpand(true)
665+ splitTargetContainer.Realize()
666+ log.Printf("[workspace] configured stack wrapper for split: %p", splitTargetContainer)
664667 }
665668 }
666669
@@ -668,14 +671,14 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
668671 log.Printf("[workspace] splitting direction=%s orientation=%v existingFirst=%v splitTarget.parent=%p splitTarget.isStacked=%v",
669672 direction, orientation, existingFirst, splitTarget.parent, splitTarget.isStacked)
670673
671- paned := webkit.NewPaned(orientation)
672- if paned == 0 {
674+ paned := gtk.NewPaned(orientation)
675+ if paned == nil {
673676 return nil, errors.New("failed to create GtkPaned")
674677 }
675- webkit.WidgetSetHExpand(paned, true)
676- webkit.WidgetSetVExpand(paned, true)
677- webkit.PanedSetResizeStart(paned, true)
678- webkit.PanedSetResizeEnd(paned, true)
678+ paned.SetHExpand(true)
679+ paned.SetVExpand(true)
680+ paned.SetResizeStartChild(true)
681+ paned.SetResizeEndChild(true)
679682
680683 newLeaf := &paneNode{
681684 pane: newPane,
@@ -693,10 +696,10 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
693696 parent := split.parent
694697
695698 // Clear CSS classes before reparenting to avoid GTK bloom filter corruption
696- if splitTargetContainer != 0 && webkit.WidgetIsValid(splitTargetContainer) {
699+ if splitTargetContainer != nil {
697700 // Remove active border class if present (prevents GTK bloom filter corruption during unparent)
698- if webkit.WidgetHasCSSClass(splitTargetContainer, activePaneClass) {
699- webkit.WidgetRemoveCSSClass(splitTargetContainer, activePaneClass)
701+ if splitTargetContainer.HasCSSClass(activePaneClass) {
702+ splitTargetContainer.RemoveCSSClass(activePaneClass)
700703 }
701704 }
702705
@@ -706,22 +709,22 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
706709 // Detach split target container from its current GTK parent before inserting into new paned.
707710 if parent == nil {
708711 // Split target is the root - remove it from the window
709- log.Printf("[workspace] removing split target container=%#x from window", splitTargetContainer)
712+ log.Printf("[workspace] removing split target container=%p from window", splitTargetContainer)
710713 if wm.window != nil {
711- wm.window.SetChild(0)
714+ wm.window.SetChild(nil)
712715 }
713716 // GTK4 will auto-unparent when we add to new paned - no manual unparent needed
714- } else if parent.container != 0 {
717+ } else if parent.container != nil {
715718 // Split target has a parent paned - remove it (automatically unparents in GTK4)
716- log.Printf("[workspace] unparenting split target container=%#x from parent paned=%#x", splitTargetContainer, parent.container)
717- if webkit.WidgetIsValid(parent.container) {
719+ log.Printf("[workspace] unparenting split target container=%p from parent paned=%p", splitTargetContainer, parent.container)
720+ if parentPaned, ok := parent.container.(*gtk.Paned); ok && parentPaned != nil {
718721 if parent.left == splitTarget {
719- webkit.PanedSetStartChild(parent.container, 0)
722+ parentPaned.SetStartChild(nil)
720723 } else if parent.right == splitTarget {
721- webkit.PanedSetEndChild(parent.container, 0)
724+ parentPaned.SetEndChild(nil)
722725 }
723- // GTK4 automatically unparents when we set paned child to 0 - no manual unparent needed
724- webkit.WidgetQueueAllocate(parent.container)
726+ // GTK4 automatically unparents when we set paned child to nil - no manual unparent needed
727+ parent.container.QueueAllocate()
725728 }
726729 }
727730
@@ -752,16 +755,16 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
752755
753756 // Add both containers to the new paned
754757 if existingFirst {
755- if webkit.WidgetIsValid(splitTargetContainer) {
756- webkit.PanedSetStartChild(paned, splitTargetContainer)
757- webkit.PanedSetEndChild(paned, newContainer)
758- log.Printf("[workspace] added splitTarget=%#x as start child, new=%#x as end child", splitTargetContainer, newContainer)
758+ if splitTargetContainer != nil {
759+ paned.SetStartChild(splitTargetContainer)
760+ paned.SetEndChild(newContainer)
761+ log.Printf("[workspace] added splitTarget=%p as start child, new=%p as end child", splitTargetContainer, newContainer)
759762 }
760763 } else {
761- if webkit.WidgetIsValid(splitTargetContainer) {
762- webkit.PanedSetStartChild(paned, newContainer)
763- webkit.PanedSetEndChild(paned, splitTargetContainer)
764- log.Printf("[workspace] added new=%#x as start child, splitTarget=%#x as end child", newContainer, splitTargetContainer)
764+ if splitTargetContainer != nil {
765+ paned.SetStartChild(newContainer)
766+ paned.SetEndChild(splitTargetContainer)
767+ log.Printf("[workspace] added new=%p as start child, splitTarget=%p as end child", newContainer, splitTargetContainer)
765768 }
766769 }
767770
@@ -769,58 +772,58 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
769772 if parent == nil {
770773 if wm.window != nil {
771774 wm.window.SetChild(paned)
772- webkit.WidgetQueueAllocate(paned)
773- log.Printf("[workspace] paned set as window child: paned=%#x", paned)
775+ paned.QueueAllocate()
776+ log.Printf("[workspace] paned set as window child: paned=%p", paned)
774777 }
775778 } else {
776- if webkit.WidgetIsValid(parent.container) {
779+ if parentPaned, ok := parent.container.(*gtk.Paned); ok && parentPaned != nil {
777780 if parent.left == split {
778- webkit.PanedSetStartChild(parent.container, paned)
781+ parentPaned.SetStartChild(paned)
779782 } else if parent.right == split {
780- webkit.PanedSetEndChild(parent.container, paned)
783+ parentPaned.SetEndChild(paned)
781784 }
782- webkit.WidgetQueueAllocate(parent.container)
783- webkit.WidgetQueueAllocate(paned)
784- log.Printf("[workspace] paned inserted into parent=%#x", parent.container)
785+ parent.container.QueueAllocate()
786+ paned.QueueAllocate()
787+ log.Printf("[workspace] paned inserted into parent=%p", parent.container)
785788 }
786789 }
787790
788- webkit.WidgetShow(paned)
791+ paned.Show()
789792
790793 // CRITICAL: Set initial 50/50 split position after showing paned
791794 // GTK needs the widget to be realized before we can set position based on allocation
792795 // Schedule this to run after GTK has allocated space to the paned
793796 wm.scheduleIdleGuarded(func() bool {
794- if !webkit.WidgetIsValid(paned) {
797+ if paned == nil {
795798 return false
796799 }
797800 // Get paned allocation to calculate 50% position
798- alloc := webkit.WidgetGetAllocation(paned)
801+ alloc := paned.Allocation()
799802 var splitPos int
800- if orientation == webkit.OrientationHorizontal {
801- splitPos = alloc.Width / 2
803+ if orientation == gtk.OrientationHorizontal {
804+ splitPos = alloc.Width() / 2
802805 } else {
803- splitPos = alloc.Height / 2
806+ splitPos = alloc.Height() / 2
804807 }
805808 if splitPos > 0 {
806- webkit.PanedSetPosition(paned, splitPos)
807- log.Printf("[workspace] Set initial paned position to 50%%: %d (orientation=%d, size=%dx%d)", splitPos, orientation, alloc.Width, alloc.Height)
809+ paned.SetPosition(splitPos)
810+ log.Printf("[workspace] Set initial paned position to 50%%: %d (orientation=%d, size=%dx%d)", splitPos, orientation, alloc.Width(), alloc.Height())
808811 }
809812 return false
810813 }, split)
811814
812815 // CRITICAL: Show the existing container and force GTK to recreate rendering surface after reparenting
813816 // This reconnects WebKitGTK's rendering pipeline and fixes compositor sync issues
814- if target != nil && target.container != 0 {
817+ if target != nil && target.container != nil {
815818 wm.scheduleIdleGuarded(func() bool {
816- if target == nil || !target.widgetValid || target.container == 0 {
819+ if target == nil || !target.widgetValid || target.container == nil {
817820 return false
818821 }
819822 // Show widget to reconnect WebKit rendering pipeline
820- webkit.WidgetShow(target.container)
823+ target.container.Show()
821824 // Force GTK to recalculate size and recreate rendering surface
822- webkit.WidgetQueueResize(target.container)
823- webkit.WidgetQueueDraw(target.container)
825+ target.container.QueueResize()
826+ target.container.QueueDraw()
824827 log.Printf("[workspace] Shown and queued resize+draw for target container after reparenting")
825828 return false
826829 }, target)
@@ -854,8 +857,8 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
854857 if newLeaf == nil || !newLeaf.widgetValid {
855858 return false
856859 }
857- if newContainer != 0 && webkit.WidgetIsValid(newContainer) {
858- webkit.WidgetShow(newContainer)
860+ if newContainer != nil {
861+ newContainer.Show()
859862 }
860863 // Attach GTK focus controller to new pane
861864 if wm.focusStateMachine != nil {
@@ -931,27 +934,27 @@ func (wm *WorkspaceManager) swapContainers(grand *paneNode, sibling *paneNode) {
931934 return
932935 }
933936 // Clear CSS classes before reparenting to avoid GTK bloom filter corruption
934- if sibling.container != 0 && webkit.WidgetIsValid(sibling.container) {
935- if webkit.WidgetHasCSSClass(sibling.container, activePaneClass) {
936- webkit.WidgetRemoveCSSClass(sibling.container, activePaneClass)
937+ if sibling.container != nil {
938+ if sibling.container.HasCSSClass(activePaneClass) {
939+ sibling.container.RemoveCSSClass(activePaneClass)
937940 }
938941 }
939942
940- if webkit.WidgetIsValid(grand.container) && webkit.WidgetIsValid(sibling.container) {
943+ if grandPaned, ok := grand.container.(*gtk.Paned); ok && grandPaned != nil && sibling.container != nil {
941944 // GTK4 PanedSetStartChild/EndChild auto-unparent from current parent
942945 // Focus was already cleared before unparenting closed pane
943- if parent := webkit.WidgetGetParent(sibling.container); parent != 0 && parent != grand.container {
944- log.Printf("[workspace] widget %#x reparenting from %#x to %#x (GTK4 will auto-unparent)", sibling.container, parent, grand.container)
946+ if parent := sibling.container.Parent(); 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)
945948 }
946949
947950 if grand.left == sibling {
948- webkit.PanedSetStartChild(grand.container, sibling.container)
949- log.Printf("[workspace] swapContainers: set widget %#x as start child of paned %#x", sibling.container, grand.container)
951+ grandPaned.SetStartChild(sibling.container)
952+ log.Printf("[workspace] swapContainers: set widget %p as start child of paned %p", sibling.container, grand.container)
950953 } else {
951- webkit.PanedSetEndChild(grand.container, sibling.container)
952- log.Printf("[workspace] swapContainers: set widget %#x as end child of paned %#x", sibling.container, grand.container)
954+ grandPaned.SetEndChild(sibling.container)
955+ log.Printf("[workspace] swapContainers: set widget %p as end child of paned %p", sibling.container, grand.container)
953956 }
954- webkit.WidgetQueueAllocate(grand.container)
957+ grand.container.QueueAllocate()
955958 }
956959 }
957960
@@ -985,7 +988,7 @@ func (wm *WorkspaceManager) cascadePromotion(singleChildPaned *paneNode) {
985988 log.Printf("[workspace] Only child %p promoted to root", onlyChild)
986989
987990 // Attach to window
988- if onlyChild.container != 0 && webkit.WidgetIsValid(onlyChild.container) {
991+ if onlyChild.container != nil {
989992 wm.attachRoot(onlyChild)
990993 }
991994
@@ -1002,26 +1005,26 @@ func (wm *WorkspaceManager) cascadePromotion(singleChildPaned *paneNode) {
10021005 log.Printf("[workspace] Only child %p attached to great-grandparent %p", onlyChild, greatGrandparent)
10031006
10041007 // Reparent widget in GTK
1005- if onlyChild.container != 0 && webkit.WidgetIsValid(onlyChild.container) && greatGrandparent.container != 0 && webkit.WidgetIsValid(greatGrandparent.container) {
1008+ if greatGrandPaned, ok := greatGrandparent.container.(*gtk.Paned); ok && greatGrandPaned != nil && onlyChild.container != nil {
10061009 // GTK4 PanedSet*Child automatically handles unparenting
10071010 if greatGrandparent.left == onlyChild {
1008- webkit.PanedSetStartChild(greatGrandparent.container, onlyChild.container)
1011+ greatGrandPaned.SetStartChild(onlyChild.container)
10091012 } else {
1010- webkit.PanedSetEndChild(greatGrandparent.container, onlyChild.container)
1013+ greatGrandPaned.SetEndChild(onlyChild.container)
10111014 }
10121015
1013- webkit.WidgetSetHExpand(onlyChild.container, true)
1014- webkit.WidgetSetVExpand(onlyChild.container, true)
1015- webkit.WidgetQueueAllocate(onlyChild.container)
1016- webkit.WidgetQueueAllocate(greatGrandparent.container)
1016+ onlyChild.container.SetHExpand(true)
1017+ onlyChild.container.SetVExpand(true)
1018+ onlyChild.container.QueueAllocate()
1019+ greatGrandparent.container.QueueAllocate()
10171020 wm.scheduleIdleGuarded(func() bool {
10181021 if onlyChild == nil || !onlyChild.widgetValid {
10191022 return false
10201023 }
1021- if onlyChild.container != 0 && webkit.WidgetIsValid(onlyChild.container) {
1022- webkit.WidgetShow(onlyChild.container)
1023- webkit.WidgetQueueResize(onlyChild.container)
1024- webkit.WidgetQueueDraw(onlyChild.container)
1024+ if onlyChild.container != nil {
1025+ onlyChild.container.Show()
1026+ onlyChild.container.QueueResize()
1027+ onlyChild.container.QueueDraw()
10251028 }
10261029 return false
10271030 }, onlyChild)
@@ -1040,72 +1043,61 @@ func (wm *WorkspaceManager) cascadePromotion(singleChildPaned *paneNode) {
10401043
10411044 // attachRoot attaches a node as the new window root
10421045 func (wm *WorkspaceManager) attachRoot(root *paneNode) {
1043- if root == nil || root.container == 0 || wm.window == nil {
1044- return
1045- }
1046- if !webkit.WidgetIsValid(root.container) {
1046+ if root == nil || root.container == nil || wm.window == nil {
10471047 return
10481048 }
10491049
10501050 // Hold a transient reference so GTK does not destroy the widget while detached
1051- releaseRef := webkit.WidgetRef(root.container)
1052- if !releaseRef {
1053- log.Printf("[workspace] WARNING: failed to acquire temporary ref for widget %#x; aborting attachRoot", root.container)
1054- return
1055- }
1056- defer func() {
1057- if webkit.WidgetIsValid(root.container) {
1058- webkit.WidgetUnref(root.container)
1059- }
1060- }()
1051+ root.container.Ref()
1052+ defer root.container.Unref()
10611053
10621054 // Detach from previous container (paned, stack, etc.) before replacing the window child.
1063- if parent := webkit.WidgetGetParent(root.container); parent != 0 {
1064- log.Printf("[workspace] unparenting widget %#x from parent %#x before window attach", root.container, parent)
1065- webkit.WidgetUnparent(root.container)
1066- if !webkit.WidgetIsValid(root.container) {
1067- log.Printf("[workspace] WARNING: widget %#x became invalid during unparent; aborting attachRoot", root.container)
1055+ if parent := root.container.Parent(); parent != nil {
1056+ log.Printf("[workspace] unparenting widget %p from parent %p before window attach", root.container, parent)
1057+ root.container.Unparent()
1058+ if root.container == nil {
1059+ log.Printf("[workspace] WARNING: widget %p became invalid during unparent; aborting attachRoot", root.container)
10681060 return
10691061 }
10701062 }
10711063
10721064 // Clear any existing window child to avoid GTK warnings when swapping roots.
1073- wm.window.SetChild(0)
1065+ wm.window.SetChild(nil)
10741066
1075- if webkit.WidgetIsValid(root.container) {
1076- webkit.WidgetSetHExpand(root.container, true)
1077- webkit.WidgetSetVExpand(root.container, true)
1067+ if root.container != nil {
1068+ root.container.SetHExpand(true)
1069+ root.container.SetVExpand(true)
10781070 }
10791071
1080- if !webkit.WidgetIsValid(root.container) {
1081- log.Printf("[workspace] WARNING: widget %#x invalid prior to window attach", root.container)
1072+ if root.container == nil {
1073+ log.Printf("[workspace] WARNING: widget %p invalid prior to window attach", root.container)
10821074 return
10831075 }
10841076
10851077 wm.window.SetChild(root.container)
1086- if webkit.WidgetIsValid(root.container) {
1087- webkit.WidgetQueueAllocate(root.container)
1088- webkit.WidgetShow(root.container)
1089- webkit.WidgetQueueResize(root.container)
1090- webkit.WidgetQueueDraw(root.container)
1078+ if root.container != nil {
1079+ root.container.QueueAllocate()
1080+ root.container.Show()
1081+ root.container.QueueResize()
1082+ root.container.QueueDraw()
10911083 }
10921084
10931085 wm.scheduleIdleGuarded(func() bool {
10941086 if root == nil || !root.widgetValid {
10951087 return false
10961088 }
1097- if root.container != 0 && webkit.WidgetIsValid(root.container) {
1098- webkit.WidgetQueueResize(root.container)
1099- webkit.WidgetQueueDraw(root.container)
1089+ if root.container != nil {
1090+ root.container.QueueResize()
1091+ root.container.QueueDraw()
11001092 }
11011093 return false
11021094 }, root)
11031095
11041096 // Verify attachment succeeded
1105- if finalParent := webkit.WidgetGetParent(root.container); finalParent == 0 {
1106- log.Printf("[workspace] WARNING: widget %#x has no parent after SetChild", root.container)
1097+ if finalParent := root.container.Parent(); finalParent == nil {
1098+ log.Printf("[workspace] WARNING: widget %p has no parent after SetChild", root.container)
11071099 } else {
1108- log.Printf("[workspace] attachRoot successful: widget %#x now child of %#x", root.container, finalParent)
1100+ log.Printf("[workspace] attachRoot successful: widget %p now child of %p", root.container, finalParent)
11091101 }
11101102 }
11111103
@@ -1138,9 +1130,9 @@ func (wm *WorkspaceManager) cleanupPane(node *paneNode, generation uint) {
11381130 node.pane = nil
11391131 }
11401132
1141- if node.container != 0 {
1133+ if node.container != nil {
11421134 // Container widget will be destroyed by GTK when unparented
1143- node.container = 0
1135+ node.container = nil
11441136 }
11451137
11461138 node.pendingIdleHandles = nil
@@ -1157,9 +1149,10 @@ func (wm *WorkspaceManager) decommissionParent(parent *paneNode, generation uint
11571149
11581150 // For branch nodes (paneds), destroy the widget
11591151 // Children have already been unparented during promotion/close, no need to clear them
1160- if !parent.isLeaf && parent.container != 0 && webkit.WidgetIsValid(parent.container) {
1161- webkit.WidgetHookDestroy(parent.container)
1162- log.Printf("[workspace] destroyed paned widget: %#x", parent.container)
1152+ if !parent.isLeaf && parent.container != nil {
1153+ // In gotk4, we don't need to manually destroy - GTK handles this when unparented
1154+ // Just mark it for cleanup
1155+ log.Printf("[workspace] marked paned widget for cleanup: %p", parent.container)
11631156 }
11641157
11651158 wm.cleanupPane(parent, generation)
@@ -1191,7 +1184,7 @@ func (wm *WorkspaceManager) cleanupAndExit(node *paneNode) (*paneNode, error) {
11911184 log.Printf("[workspace] closing final pane; exiting browser")
11921185 wm.cleanupPane(node, wm.nextCleanupGeneration())
11931186 wm.detachHover(node)
1194- webkit.QuitMainLoop()
1187+ wm.app.Quit()
11951188 return nil, nil
11961189 }
11971190
@@ -1237,19 +1230,21 @@ func (wm *WorkspaceManager) closePane(node *paneNode) (*paneNode, error) {
12371230
12381231 // Clear grandparent's focus child BEFORE unparenting closed pane
12391232 // GTK propagates focus events upward, so we must clear grandparent focus first
1240- if grandparent != nil && grandparent.container != 0 && webkit.WidgetIsValid(grandparent.container) {
1241- webkit.WidgetSetFocusChild(grandparent.container, 0)
1233+ if grandparent != nil && grandparent.container != nil {
1234+ grandparent.container.SetFocusChild(nil)
12421235 }
12431236
12441237 // Unparent the closed pane's container from the paned BEFORE promoting sibling
1245- if node.container != 0 && webkit.WidgetIsValid(node.container) {
1246- if containerParent := webkit.WidgetGetParent(node.container); containerParent == parent.container {
1247- if parent.left == node {
1248- webkit.PanedSetStartChild(parent.container, 0)
1249- } else if parent.right == node {
1250- webkit.PanedSetEndChild(parent.container, 0)
1238+ if node.container != nil {
1239+ if containerParent := node.container.Parent(); containerParent == parent.container {
1240+ if parentPaned, ok := parent.container.(*gtk.Paned); ok && parentPaned != nil {
1241+ if parent.left == node {
1242+ parentPaned.SetStartChild(nil)
1243+ } else if parent.right == node {
1244+ parentPaned.SetEndChild(nil)
1245+ }
1246+ log.Printf("[workspace] unparented closed pane container: %p", node.container)
12511247 }
1252- log.Printf("[workspace] unparented closed pane container: %#x", node.container)
12531248 }
12541249 }
12551250
@@ -1261,15 +1256,15 @@ func (wm *WorkspaceManager) closePane(node *paneNode) (*paneNode, error) {
12611256 wm.swapContainers(grandparent, sibling)
12621257
12631258 // Reconnect the promoted sibling's rendering pipeline after GTK reparenting.
1264- if sibling != nil && sibling.container != 0 {
1259+ if sibling != nil && sibling.container != nil {
12651260 wm.scheduleIdleGuarded(func() bool {
12661261 if sibling == nil || !sibling.widgetValid {
12671262 return false
12681263 }
1269- if sibling.container != 0 && webkit.WidgetIsValid(sibling.container) {
1270- webkit.WidgetShow(sibling.container)
1271- webkit.WidgetQueueResize(sibling.container)
1272- webkit.WidgetQueueDraw(sibling.container)
1264+ if sibling.container != nil {
1265+ sibling.container.Show()
1266+ sibling.container.QueueResize()
1267+ sibling.container.QueueDraw()
12731268 }
12741269 return false
12751270 }, sibling)
@@ -1335,17 +1330,17 @@ func (wm *WorkspaceManager) closeStackedPaneCompat(node *paneNode) (*paneNode, e
13351330 }
13361331
13371332 // Update GTK widgets
1338- if webkit.WidgetIsValid(stack.parent.container) && webkit.WidgetIsValid(remaining.container) {
1333+ if parentPaned, ok := stack.parent.container.(*gtk.Paned); ok && parentPaned != nil && remaining.container != nil {
13391334 if stack.parent.left == remaining {
1340- webkit.PanedSetStartChild(stack.parent.container, remaining.container)
1335+ parentPaned.SetStartChild(remaining.container)
13411336 } else {
1342- webkit.PanedSetEndChild(stack.parent.container, remaining.container)
1337+ parentPaned.SetEndChild(remaining.container)
13431338 }
13441339 }
13451340 } else {
13461341 // Stack was root
13471342 wm.root = remaining
1348- if webkit.WidgetIsValid(remaining.container) {
1343+ if remaining.container != nil {
13491344 wm.window.SetChild(remaining.container)
13501345 }
13511346 }
@@ -1362,15 +1357,15 @@ func (wm *WorkspaceManager) closeStackedPaneCompat(node *paneNode) (*paneNode, e
13621357 }
13631358
13641359 // Remove widget from GTK box
1365- if webkit.WidgetIsValid(stack.container) && webkit.WidgetIsValid(node.container) {
1366- webkit.BoxRemove(stack.container, node.container)
1360+ if stackBox, ok := stack.container.(*gtk.Box); ok && stackBox != nil && node.container != nil {
1361+ stackBox.Remove(node.container)
13671362 }
13681363
13691364 // Show new active pane
13701365 if stack.activeStackIndex >= 0 {
13711366 active := stack.stackedPanes[stack.activeStackIndex]
1372- if webkit.WidgetIsValid(active.container) {
1373- webkit.WidgetShow(active.container)
1367+ if active.container != nil {
1368+ active.container.Show()
13741369 }
13751370 }
13761371
@@ -6,6 +6,7 @@ import (
6 "log"6 "log"
7 7
8 "github.com/bnema/dumber/pkg/webkit"8 "github.com/bnema/dumber/pkg/webkit"
9+ "github.com/diamondburned/gotk4/pkg/gtk/v4"
9 )10 )
10 11
11 // closeContext manages the context for a pane close operation12 // closeContext manages the context for a pane close operation
@@ -339,7 +340,7 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
339 340
340 // Determine what we're actually splitting341 // Determine what we're actually splitting
341 var splitTarget *paneNode342 var splitTarget *paneNode
342- var splitTargetContainer uintptr343+ var splitTargetContainer gtk.Widgetter
343 344
344 if stackContainer != nil {345 if stackContainer != nil {
345 // Case: splitting from inside a stack - we split around the entire stack346 // Case: splitting from inside a stack - we split around the entire stack
@@ -353,22 +354,22 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
353 log.Printf("[workspace] normal split from pane: target=%p", splitTarget)354 log.Printf("[workspace] normal split from pane: target=%p", splitTarget)
354 }355 }
355 356
356- if splitTargetContainer == 0 {357+ if splitTargetContainer == nil {
357 return errors.New("split target missing container")358 return errors.New("split target missing container")
358 }359 }
359 360
360 orientation, existingFirst := mapDirection(direction)361 orientation, existingFirst := mapDirection(direction)
361 log.Printf("[workspace] inserting popup direction=%s orientation=%v existingFirst=%v splitTarget.parent=%p", direction, orientation, existingFirst, splitTarget.parent)362 log.Printf("[workspace] inserting popup direction=%s orientation=%v existingFirst=%v splitTarget.parent=%p", direction, orientation, existingFirst, splitTarget.parent)
362 363
363- paned := webkit.NewPaned(orientation)364+ paned := gtk.NewPaned(orientation)
364- if paned == 0 {365+ if paned == nil {
365 return errors.New("failed to create GtkPaned")366 return errors.New("failed to create GtkPaned")
366 }367 }
367- webkit.WidgetSetHExpand(paned, true)368+ paned.SetHExpand(true)
368- webkit.WidgetSetVExpand(paned, true)369+ paned.SetVExpand(true)
369 // For popup panes: preserve existing pane size, let popup take remaining space370 // For popup panes: preserve existing pane size, let popup take remaining space
370- webkit.PanedSetResizeStart(paned, false) // existing pane keeps its size371+ paned.SetResizeStartChild(false) // existing pane keeps its size
371- webkit.PanedSetResizeEnd(paned, true) // popup can resize372+ paned.SetResizeEndChild(true) // popup can resize
372 log.Printf("[workspace] configured paned for popup: start=fixed, end=flexible")373 log.Printf("[workspace] configured paned for popup: start=fixed, end=flexible")
373 374
374 newLeaf := &paneNode{375 newLeaf := &paneNode{
@@ -389,24 +390,26 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
389 // CRITICAL: Capture parent paned's divider position BEFORE reparenting to preserve layout390 // CRITICAL: Capture parent paned's divider position BEFORE reparenting to preserve layout
390 // Only attempt this if the parent container is actually a paned widget391 // Only attempt this if the parent container is actually a paned widget
391 var parentDividerPos int392 var parentDividerPos int
392- if parent != nil && parent.container != 0 && webkit.WidgetIsValid(parent.container) && webkit.IsPaned(parent.container) {393+ if parent != nil && parent.container != nil {
393- parentDividerPos = webkit.PanedGetPosition(parent.container)394+ if parentPaned, ok := parent.container.(*gtk.Paned); ok && parentPaned != nil {
394- log.Printf("[workspace] captured parent divider position: %d", parentDividerPos)395+ parentDividerPos = parentPaned.Position()
395- } else if parent != nil && parent.container != 0 {396+ log.Printf("[workspace] captured parent divider position: %d", parentDividerPos)
396- log.Printf("[workspace] parent container is not a paned widget (likely a stacked container)")397+ } else {
398+ log.Printf("[workspace] parent container is not a paned widget (likely a stacked container)")
399+ }
397 }400 }
398 401
399 // Clear CSS classes before reparenting to avoid GTK bloom filter corruption402 // Clear CSS classes before reparenting to avoid GTK bloom filter corruption
400- if splitTargetContainer != 0 && webkit.WidgetIsValid(splitTargetContainer) {403+ if splitTargetContainer != nil {
401 // 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)
402- if webkit.WidgetHasCSSClass(splitTargetContainer, activePaneClass) {405+ if splitTargetContainer.HasCSSClass(activePaneClass) {
403- webkit.WidgetRemoveCSSClass(splitTargetContainer, activePaneClass)406+ splitTargetContainer.RemoveCSSClass(activePaneClass)
404 }407 }
405 408
406 // CRITICAL: Hide widget before reparenting to disconnect WebKitGTK rendering pipeline409 // CRITICAL: Hide widget before reparenting to disconnect WebKitGTK rendering pipeline
407 // This forces WebKit to detach its compositor, preventing rendering corruption410 // This forces WebKit to detach its compositor, preventing rendering corruption
408- webkit.WidgetHide(splitTargetContainer)411+ splitTargetContainer.Hide()
409- log.Printf("[workspace] Hidden split target container before reparenting: %#x", splitTargetContainer)412+ log.Printf("[workspace] Hidden split target container before reparenting: %p", splitTargetContainer)
410 }413 }
411 414
412 // Prevent GTK from auto-removing controllers while the widget is temporarily unparented.415 // Prevent GTK from auto-removing controllers while the widget is temporarily unparented.
@@ -415,22 +418,22 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
415 // Detach existing container from its current GTK parent before inserting into new paned.418 // Detach existing container from its current GTK parent before inserting into new paned.
416 if parent == nil {419 if parent == nil {
417 // Split target is the root - remove it from the window420 // Split target is the root - remove it from the window
418- log.Printf("[workspace] removing split target container=%#x from window", splitTargetContainer)421+ log.Printf("[workspace] removing split target container=%p from window", splitTargetContainer)
419 if wm.window != nil {422 if wm.window != nil {
420- wm.window.SetChild(0)423+ wm.window.SetChild(nil)
421 }424 }
422 // GTK4 will auto-unparent when we add to new paned - no manual unparent needed425 // GTK4 will auto-unparent when we add to new paned - no manual unparent needed
423- } else if parent.container != 0 {426+ } else if parent.container != nil {
424 // Split target has a parent paned - unparent it from there427 // Split target has a parent paned - unparent it from there
425 log.Printf("[workspace] unparenting split target container from parent paned")428 log.Printf("[workspace] unparenting split target container from parent paned")
426- if webkit.WidgetIsValid(parent.container) {429+ if parentPaned, ok := parent.container.(*gtk.Paned); ok && parentPaned != nil {
427- log.Printf("[workspace] executing unparent: parentPtr=%#x", parent.container)430+ log.Printf("[workspace] executing unparent: parentPtr=%p", parent.container)
428 if parent.left == splitTarget {431 if parent.left == splitTarget {
429- webkit.PanedSetStartChild(parent.container, 0)432+ parentPaned.SetStartChild(nil)
430 } else if parent.right == splitTarget {433 } else if parent.right == splitTarget {
431- webkit.PanedSetEndChild(parent.container, 0)434+ parentPaned.SetEndChild(nil)
432 }435 }
433- webkit.WidgetQueueAllocate(parent.container)436+ parent.container.QueueAllocate()
434 }437 }
435 }438 }
436 439
@@ -438,15 +441,15 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
438 if existingFirst {441 if existingFirst {
439 split.left = splitTarget442 split.left = splitTarget
440 split.right = newLeaf443 split.right = newLeaf
441- webkit.PanedSetStartChild(paned, splitTargetContainer)444+ paned.SetStartChild(splitTargetContainer)
442- webkit.PanedSetEndChild(paned, newContainer)445+ paned.SetEndChild(newContainer)
443- log.Printf("[workspace] added splitTarget=%#x as start child, new=%#x as end child", splitTargetContainer, newContainer)446+ log.Printf("[workspace] added splitTarget=%p as start child, new=%p as end child", splitTargetContainer, newContainer)
444 } else {447 } else {
445 split.left = newLeaf448 split.left = newLeaf
446 split.right = splitTarget449 split.right = splitTarget
447- webkit.PanedSetStartChild(paned, newContainer)450+ paned.SetStartChild(newContainer)
448- webkit.PanedSetEndChild(paned, splitTargetContainer)451+ paned.SetEndChild(splitTargetContainer)
449- log.Printf("[workspace] added new=%#x as start child, splitTarget=%#x as end child", newContainer, splitTargetContainer)452+ log.Printf("[workspace] added new=%p as start child, splitTarget=%p as end child", newContainer, splitTargetContainer)
450 }453 }
451 454
452 splitTarget.parent = split455 splitTarget.parent = split
@@ -456,42 +459,42 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
456 wm.root = split459 wm.root = split
457 if wm.window != nil {460 if wm.window != nil {
458 wm.window.SetChild(paned)461 wm.window.SetChild(paned)
459- webkit.WidgetQueueAllocate(paned)462+ paned.QueueAllocate()
460- log.Printf("[workspace] paned set as window child: paned=%#x", paned)463+ log.Printf("[workspace] paned set as window child: paned=%p", paned)
461 }464 }
462 } else {465 } else {
463- if webkit.WidgetIsValid(parent.container) {466+ if parentPaned, ok := parent.container.(*gtk.Paned); ok && parentPaned != nil {
464- log.Printf("[workspace] executing parent reparent: parentPtr=%#x, paned=%#x", parent.container, paned)467+ log.Printf("[workspace] executing parent reparent: parentPtr=%p, paned=%p", parent.container, paned)
465 if parent.left == splitTarget {468 if parent.left == splitTarget {
466 parent.left = split469 parent.left = split
467- webkit.PanedSetStartChild(parent.container, paned)470+ parentPaned.SetStartChild(paned)
468 } else if parent.right == splitTarget {471 } else if parent.right == splitTarget {
469 parent.right = split472 parent.right = split
470- webkit.PanedSetEndChild(parent.container, paned)473+ parentPaned.SetEndChild(paned)
471 }474 }
472- webkit.WidgetQueueAllocate(parent.container)475+ parent.container.QueueAllocate()
473- webkit.WidgetQueueAllocate(paned)476+ paned.QueueAllocate()
474 log.Printf("[workspace] paned inserted into parent successfully")477 log.Printf("[workspace] paned inserted into parent successfully")
475 478
476 // CRITICAL: Restore parent paned's divider position to preserve existing layout479 // CRITICAL: Restore parent paned's divider position to preserve existing layout
477 // Only attempt this if we captured a position and the parent is actually a paned widget480 // Only attempt this if we captured a position and the parent is actually a paned widget
478- if parentDividerPos > 0 && webkit.IsPaned(parent.container) {481+ if parentDividerPos > 0 {
479- webkit.PanedSetPosition(parent.container, parentDividerPos)482+ parentPaned.SetPosition(parentDividerPos)
480 log.Printf("[workspace] restored parent divider position: %d", parentDividerPos)483 log.Printf("[workspace] restored parent divider position: %d", parentDividerPos)
481 }484 }
482 }485 }
483 }486 }
484 487
485- webkit.WidgetShow(paned)488+ paned.Show()
486 489
487 // Force GTK to re-layout the new GtkPaned container once both children are attached.490 // Force GTK to re-layout the new GtkPaned container once both children are attached.
488 wm.scheduleIdleGuarded(func() bool {491 wm.scheduleIdleGuarded(func() bool {
489 if split == nil || !split.widgetValid {492 if split == nil || !split.widgetValid {
490 return false493 return false
491 }494 }
492- if split.container != 0 && webkit.WidgetIsValid(split.container) {495+ if split.container != nil {
493- webkit.WidgetQueueResize(split.container)496+ split.container.QueueResize()
494- webkit.WidgetQueueDraw(split.container)497+ split.container.QueueDraw()
495 }498 }
496 return false499 return false
497 }, split)500 }, split)
@@ -500,36 +503,36 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
500 // GTK needs the widget to be realized before we can set position based on allocation503 // GTK needs the widget to be realized before we can set position based on allocation
501 // Schedule this to run after GTK has allocated space to the paned504 // Schedule this to run after GTK has allocated space to the paned
502 wm.scheduleIdleGuarded(func() bool {505 wm.scheduleIdleGuarded(func() bool {
503- if !webkit.WidgetIsValid(paned) {506+ if paned == nil {
504 return false507 return false
505 }508 }
506 // Get paned allocation to calculate 50% position509 // Get paned allocation to calculate 50% position
507- alloc := webkit.WidgetGetAllocation(paned)510+ alloc := paned.Allocation()
508 var splitPos int511 var splitPos int
509- if orientation == webkit.OrientationHorizontal {512+ if orientation == gtk.OrientationHorizontal {
510- splitPos = alloc.Width / 2513+ splitPos = alloc.Width() / 2
511 } else {514 } else {
512- splitPos = alloc.Height / 2515+ splitPos = alloc.Height() / 2
513 }516 }
514 if splitPos > 0 {517 if splitPos > 0 {
515- webkit.PanedSetPosition(paned, splitPos)518+ paned.SetPosition(splitPos)
516- log.Printf("[workspace] Set initial paned position to 50%%: %d (orientation=%d, size=%dx%d)", splitPos, orientation, alloc.Width, alloc.Height)519+ log.Printf("[workspace] Set initial paned position to 50%%: %d (orientation=%d, size=%dx%d)", splitPos, orientation, alloc.Width(), alloc.Height())
517 }520 }
518 return false521 return false
519 }, split)522 }, split)
520 523
521 // CRITICAL: Show the split target container and force GTK to recreate rendering surface after reparenting524 // CRITICAL: Show the split target container and force GTK to recreate rendering surface after reparenting
522 // This reconnects WebKitGTK's rendering pipeline and fixes compositor sync issues525 // This reconnects WebKitGTK's rendering pipeline and fixes compositor sync issues
523- if splitTarget != nil && splitTarget.container != 0 {526+ if splitTarget != nil && splitTarget.container != nil {
524 wm.scheduleIdleGuarded(func() bool {527 wm.scheduleIdleGuarded(func() bool {
525- if splitTarget == nil || !splitTarget.widgetValid || splitTarget.container == 0 {528+ if splitTarget == nil || !splitTarget.widgetValid || splitTarget.container == nil {
526 return false529 return false
527 }530 }
528 // Show widget to reconnect WebKit rendering pipeline531 // Show widget to reconnect WebKit rendering pipeline
529- webkit.WidgetShow(splitTarget.container)532+ splitTarget.container.Show()
530 // Force GTK to recalculate size and recreate rendering surface533 // Force GTK to recalculate size and recreate rendering surface
531- webkit.WidgetQueueResize(splitTarget.container)534+ splitTarget.container.QueueResize()
532- webkit.WidgetQueueDraw(splitTarget.container)535+ splitTarget.container.QueueDraw()
533 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")
534 return false537 return false
535 }, splitTarget)538 }, splitTarget)
@@ -554,8 +557,8 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
554 if newLeaf == nil || !newLeaf.widgetValid {557 if newLeaf == nil || !newLeaf.widgetValid {
555 return false558 return false
556 }559 }
557- if newContainer != 0 && webkit.WidgetIsValid(newContainer) {560+ if newContainer != nil {
558- webkit.WidgetShow(newContainer)561+ newContainer.Show()
559 }562 }
560 // Attach GTK focus controller to new pane563 // Attach GTK focus controller to new pane
561 if wm.focusStateMachine != nil {564 if wm.focusStateMachine != nil {
@@ -605,7 +608,7 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
605 608
606 // Determine what we're actually splitting609 // Determine what we're actually splitting
607 var splitTarget *paneNode610 var splitTarget *paneNode
608- var splitTargetContainer uintptr611+ var splitTargetContainer gtk.Widgetter
609 612
610 if stackContainer != nil {613 if stackContainer != nil {
611 // Case: splitting from inside a stack - we split around the entire stack614 // Case: splitting from inside a stack - we split around the entire stack
@@ -634,33 +637,33 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
634 }637 }
635 638
636 newContainer := newPane.webView.RootWidget()639 newContainer := newPane.webView.RootWidget()
637- if newContainer == 0 {640+ if newContainer == nil {
638 return nil, errors.New("new pane missing container")641 return nil, errors.New("new pane missing container")
639 }642 }
640- webkit.WidgetSetHExpand(newContainer, true)643+ newContainer.SetHExpand(true)
641- webkit.WidgetSetVExpand(newContainer, true)644+ newContainer.SetVExpand(true)
642- webkit.WidgetRealizeInContainer(newContainer)645+ newContainer.Realize()
643 646
644 // Use the determined split target container647 // Use the determined split target container
645- if splitTargetContainer == 0 {648+ if splitTargetContainer == nil {
646 return nil, errors.New("split target missing container")649 return nil, errors.New("split target missing container")
647 }650 }
648 651
649 // Only apply GTK operations if this is a simple widget (leaf pane)652 // Only apply GTK operations if this is a simple widget (leaf pane)
650 // For stacked containers, we need to be more careful653 // For stacked containers, we need to be more careful
651 if splitTarget.isLeaf {654 if splitTarget.isLeaf {
652- if webkit.WidgetIsValid(splitTargetContainer) {655+ if splitTargetContainer != nil {
653- webkit.WidgetSetHExpand(splitTargetContainer, true)656+ splitTargetContainer.SetHExpand(true)
654- webkit.WidgetSetVExpand(splitTargetContainer, true)657+ splitTargetContainer.SetVExpand(true)
655- webkit.WidgetRealizeInContainer(splitTargetContainer)658+ splitTargetContainer.Realize()
656 }659 }
657 } else if splitTarget.isStacked {660 } else if splitTarget.isStacked {
658 // Stack containers need the same setup as regular panes for proper splitting661 // Stack containers need the same setup as regular panes for proper splitting
659- if webkit.WidgetIsValid(splitTargetContainer) {662+ if splitTargetContainer != nil {
660- webkit.WidgetSetHExpand(splitTargetContainer, true)663+ splitTargetContainer.SetHExpand(true)
661- webkit.WidgetSetVExpand(splitTargetContainer, true)664+ splitTargetContainer.SetVExpand(true)
662- webkit.WidgetRealizeInContainer(splitTargetContainer)665+ splitTargetContainer.Realize()
663- log.Printf("[workspace] configured stack wrapper for split: %#x", splitTargetContainer)666+ log.Printf("[workspace] configured stack wrapper for split: %p", splitTargetContainer)
664 }667 }
665 }668 }
666 669
@@ -668,14 +671,14 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
668 log.Printf("[workspace] splitting direction=%s orientation=%v existingFirst=%v splitTarget.parent=%p splitTarget.isStacked=%v",671 log.Printf("[workspace] splitting direction=%s orientation=%v existingFirst=%v splitTarget.parent=%p splitTarget.isStacked=%v",
669 direction, orientation, existingFirst, splitTarget.parent, splitTarget.isStacked)672 direction, orientation, existingFirst, splitTarget.parent, splitTarget.isStacked)
670 673
671- paned := webkit.NewPaned(orientation)674+ paned := gtk.NewPaned(orientation)
672- if paned == 0 {675+ if paned == nil {
673 return nil, errors.New("failed to create GtkPaned")676 return nil, errors.New("failed to create GtkPaned")
674 }677 }
675- webkit.WidgetSetHExpand(paned, true)678+ paned.SetHExpand(true)
676- webkit.WidgetSetVExpand(paned, true)679+ paned.SetVExpand(true)
677- webkit.PanedSetResizeStart(paned, true)680+ paned.SetResizeStartChild(true)
678- webkit.PanedSetResizeEnd(paned, true)681+ paned.SetResizeEndChild(true)
679 682
680 newLeaf := &paneNode{683 newLeaf := &paneNode{
681 pane: newPane,684 pane: newPane,
@@ -693,10 +696,10 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
693 parent := split.parent696 parent := split.parent
694 697
695 // Clear CSS classes before reparenting to avoid GTK bloom filter corruption698 // Clear CSS classes before reparenting to avoid GTK bloom filter corruption
696- if splitTargetContainer != 0 && webkit.WidgetIsValid(splitTargetContainer) {699+ if splitTargetContainer != nil {
697 // 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)
698- if webkit.WidgetHasCSSClass(splitTargetContainer, activePaneClass) {701+ if splitTargetContainer.HasCSSClass(activePaneClass) {
699- webkit.WidgetRemoveCSSClass(splitTargetContainer, activePaneClass)702+ splitTargetContainer.RemoveCSSClass(activePaneClass)
700 }703 }
701 }704 }
702 705
@@ -706,22 +709,22 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
706 // Detach split target container from its current GTK parent before inserting into new paned.709 // Detach split target container from its current GTK parent before inserting into new paned.
707 if parent == nil {710 if parent == nil {
708 // Split target is the root - remove it from the window711 // Split target is the root - remove it from the window
709- log.Printf("[workspace] removing split target container=%#x from window", splitTargetContainer)712+ log.Printf("[workspace] removing split target container=%p from window", splitTargetContainer)
710 if wm.window != nil {713 if wm.window != nil {
711- wm.window.SetChild(0)714+ wm.window.SetChild(nil)
712 }715 }
713 // GTK4 will auto-unparent when we add to new paned - no manual unparent needed716 // GTK4 will auto-unparent when we add to new paned - no manual unparent needed
714- } else if parent.container != 0 {717+ } else if parent.container != nil {
715 // Split target has a parent paned - remove it (automatically unparents in GTK4)718 // Split target has a parent paned - remove it (automatically unparents in GTK4)
716- log.Printf("[workspace] unparenting split target container=%#x from parent paned=%#x", splitTargetContainer, parent.container)719+ log.Printf("[workspace] unparenting split target container=%p from parent paned=%p", splitTargetContainer, parent.container)
717- if webkit.WidgetIsValid(parent.container) {720+ if parentPaned, ok := parent.container.(*gtk.Paned); ok && parentPaned != nil {
718 if parent.left == splitTarget {721 if parent.left == splitTarget {
719- webkit.PanedSetStartChild(parent.container, 0)722+ parentPaned.SetStartChild(nil)
720 } else if parent.right == splitTarget {723 } else if parent.right == splitTarget {
721- webkit.PanedSetEndChild(parent.container, 0)724+ parentPaned.SetEndChild(nil)
722 }725 }
723- // GTK4 automatically unparents when we set paned child to 0 - no manual unparent needed726+ // GTK4 automatically unparents when we set paned child to nil - no manual unparent needed
724- webkit.WidgetQueueAllocate(parent.container)727+ parent.container.QueueAllocate()
725 }728 }
726 }729 }
727 730
@@ -752,16 +755,16 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
752 755
753 // Add both containers to the new paned756 // Add both containers to the new paned
754 if existingFirst {757 if existingFirst {
755- if webkit.WidgetIsValid(splitTargetContainer) {758+ if splitTargetContainer != nil {
756- webkit.PanedSetStartChild(paned, splitTargetContainer)759+ paned.SetStartChild(splitTargetContainer)
757- webkit.PanedSetEndChild(paned, newContainer)760+ paned.SetEndChild(newContainer)
758- log.Printf("[workspace] added splitTarget=%#x as start child, new=%#x as end child", splitTargetContainer, newContainer)761+ log.Printf("[workspace] added splitTarget=%p as start child, new=%p as end child", splitTargetContainer, newContainer)
759 }762 }
760 } else {763 } else {
761- if webkit.WidgetIsValid(splitTargetContainer) {764+ if splitTargetContainer != nil {
762- webkit.PanedSetStartChild(paned, newContainer)765+ paned.SetStartChild(newContainer)
763- webkit.PanedSetEndChild(paned, splitTargetContainer)766+ paned.SetEndChild(splitTargetContainer)
764- log.Printf("[workspace] added new=%#x as start child, splitTarget=%#x as end child", newContainer, splitTargetContainer)767+ log.Printf("[workspace] added new=%p as start child, splitTarget=%p as end child", newContainer, splitTargetContainer)
765 }768 }
766 }769 }
767 770
@@ -769,58 +772,58 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
769 if parent == nil {772 if parent == nil {
770 if wm.window != nil {773 if wm.window != nil {
771 wm.window.SetChild(paned)774 wm.window.SetChild(paned)
772- webkit.WidgetQueueAllocate(paned)775+ paned.QueueAllocate()
773- log.Printf("[workspace] paned set as window child: paned=%#x", paned)776+ log.Printf("[workspace] paned set as window child: paned=%p", paned)
774 }777 }
775 } else {778 } else {
776- if webkit.WidgetIsValid(parent.container) {779+ if parentPaned, ok := parent.container.(*gtk.Paned); ok && parentPaned != nil {
777 if parent.left == split {780 if parent.left == split {
778- webkit.PanedSetStartChild(parent.container, paned)781+ parentPaned.SetStartChild(paned)
779 } else if parent.right == split {782 } else if parent.right == split {
780- webkit.PanedSetEndChild(parent.container, paned)783+ parentPaned.SetEndChild(paned)
781 }784 }
782- webkit.WidgetQueueAllocate(parent.container)785+ parent.container.QueueAllocate()
783- webkit.WidgetQueueAllocate(paned)786+ paned.QueueAllocate()
784- log.Printf("[workspace] paned inserted into parent=%#x", parent.container)787+ log.Printf("[workspace] paned inserted into parent=%p", parent.container)
785 }788 }
786 }789 }
787 790
788- webkit.WidgetShow(paned)791+ paned.Show()
789 792
790 // CRITICAL: Set initial 50/50 split position after showing paned793 // CRITICAL: Set initial 50/50 split position after showing paned
791 // GTK needs the widget to be realized before we can set position based on allocation794 // GTK needs the widget to be realized before we can set position based on allocation
792 // Schedule this to run after GTK has allocated space to the paned795 // Schedule this to run after GTK has allocated space to the paned
793 wm.scheduleIdleGuarded(func() bool {796 wm.scheduleIdleGuarded(func() bool {
794- if !webkit.WidgetIsValid(paned) {797+ if paned == nil {
795 return false798 return false
796 }799 }
797 // Get paned allocation to calculate 50% position800 // Get paned allocation to calculate 50% position
798- alloc := webkit.WidgetGetAllocation(paned)801+ alloc := paned.Allocation()
799 var splitPos int802 var splitPos int
800- if orientation == webkit.OrientationHorizontal {803+ if orientation == gtk.OrientationHorizontal {
801- splitPos = alloc.Width / 2804+ splitPos = alloc.Width() / 2
802 } else {805 } else {
803- splitPos = alloc.Height / 2806+ splitPos = alloc.Height() / 2
804 }807 }
805 if splitPos > 0 {808 if splitPos > 0 {
806- webkit.PanedSetPosition(paned, splitPos)809+ paned.SetPosition(splitPos)
807- log.Printf("[workspace] Set initial paned position to 50%%: %d (orientation=%d, size=%dx%d)", splitPos, orientation, alloc.Width, alloc.Height)810+ log.Printf("[workspace] Set initial paned position to 50%%: %d (orientation=%d, size=%dx%d)", splitPos, orientation, alloc.Width(), alloc.Height())
808 }811 }
809 return false812 return false
810 }, split)813 }, split)
811 814
812 // CRITICAL: Show the existing container and force GTK to recreate rendering surface after reparenting815 // CRITICAL: Show the existing container and force GTK to recreate rendering surface after reparenting
813 // This reconnects WebKitGTK's rendering pipeline and fixes compositor sync issues816 // This reconnects WebKitGTK's rendering pipeline and fixes compositor sync issues
814- if target != nil && target.container != 0 {817+ if target != nil && target.container != nil {
815 wm.scheduleIdleGuarded(func() bool {818 wm.scheduleIdleGuarded(func() bool {
816- if target == nil || !target.widgetValid || target.container == 0 {819+ if target == nil || !target.widgetValid || target.container == nil {
817 return false820 return false
818 }821 }
819 // Show widget to reconnect WebKit rendering pipeline822 // Show widget to reconnect WebKit rendering pipeline
820- webkit.WidgetShow(target.container)823+ target.container.Show()
821 // Force GTK to recalculate size and recreate rendering surface824 // Force GTK to recalculate size and recreate rendering surface
822- webkit.WidgetQueueResize(target.container)825+ target.container.QueueResize()
823- webkit.WidgetQueueDraw(target.container)826+ target.container.QueueDraw()
824 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")
825 return false828 return false
826 }, target)829 }, target)
@@ -854,8 +857,8 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
854 if newLeaf == nil || !newLeaf.widgetValid {857 if newLeaf == nil || !newLeaf.widgetValid {
855 return false858 return false
856 }859 }
857- if newContainer != 0 && webkit.WidgetIsValid(newContainer) {860+ if newContainer != nil {
858- webkit.WidgetShow(newContainer)861+ newContainer.Show()
859 }862 }
860 // Attach GTK focus controller to new pane863 // Attach GTK focus controller to new pane
861 if wm.focusStateMachine != nil {864 if wm.focusStateMachine != nil {
@@ -931,27 +934,27 @@ func (wm *WorkspaceManager) swapContainers(grand *paneNode, sibling *paneNode) {
931 return934 return
932 }935 }
933 // Clear CSS classes before reparenting to avoid GTK bloom filter corruption936 // Clear CSS classes before reparenting to avoid GTK bloom filter corruption
934- if sibling.container != 0 && webkit.WidgetIsValid(sibling.container) {937+ if sibling.container != nil {
935- if webkit.WidgetHasCSSClass(sibling.container, activePaneClass) {938+ if sibling.container.HasCSSClass(activePaneClass) {
936- webkit.WidgetRemoveCSSClass(sibling.container, activePaneClass)939+ sibling.container.RemoveCSSClass(activePaneClass)
937 }940 }
938 }941 }
939 942
940- if webkit.WidgetIsValid(grand.container) && webkit.WidgetIsValid(sibling.container) {943+ if grandPaned, ok := grand.container.(*gtk.Paned); ok && grandPaned != nil && sibling.container != nil {
941 // GTK4 PanedSetStartChild/EndChild auto-unparent from current parent944 // GTK4 PanedSetStartChild/EndChild auto-unparent from current parent
942 // Focus was already cleared before unparenting closed pane945 // Focus was already cleared before unparenting closed pane
943- if parent := webkit.WidgetGetParent(sibling.container); parent != 0 && parent != grand.container {946+ if parent := sibling.container.Parent(); parent != nil && parent != grand.container {
944- log.Printf("[workspace] widget %#x reparenting from %#x to %#x (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)
945 }948 }
946 949
947 if grand.left == sibling {950 if grand.left == sibling {
948- webkit.PanedSetStartChild(grand.container, sibling.container)951+ grandPaned.SetStartChild(sibling.container)
949- log.Printf("[workspace] swapContainers: set widget %#x as start child of paned %#x", sibling.container, grand.container)952+ log.Printf("[workspace] swapContainers: set widget %p as start child of paned %p", sibling.container, grand.container)
950 } else {953 } else {
951- webkit.PanedSetEndChild(grand.container, sibling.container)954+ grandPaned.SetEndChild(sibling.container)
952- log.Printf("[workspace] swapContainers: set widget %#x as end child of paned %#x", sibling.container, grand.container)955+ log.Printf("[workspace] swapContainers: set widget %p as end child of paned %p", sibling.container, grand.container)
953 }956 }
954- webkit.WidgetQueueAllocate(grand.container)957+ grand.container.QueueAllocate()
955 }958 }
956 }959 }
957 960
@@ -985,7 +988,7 @@ func (wm *WorkspaceManager) cascadePromotion(singleChildPaned *paneNode) {
985 log.Printf("[workspace] Only child %p promoted to root", onlyChild)988 log.Printf("[workspace] Only child %p promoted to root", onlyChild)
986 989
987 // Attach to window990 // Attach to window
988- if onlyChild.container != 0 && webkit.WidgetIsValid(onlyChild.container) {991+ if onlyChild.container != nil {
989 wm.attachRoot(onlyChild)992 wm.attachRoot(onlyChild)
990 }993 }
991 994
@@ -1002,26 +1005,26 @@ func (wm *WorkspaceManager) cascadePromotion(singleChildPaned *paneNode) {
1002 log.Printf("[workspace] Only child %p attached to great-grandparent %p", onlyChild, greatGrandparent)1005 log.Printf("[workspace] Only child %p attached to great-grandparent %p", onlyChild, greatGrandparent)
1003 1006
1004 // Reparent widget in GTK1007 // Reparent widget in GTK
1005- if onlyChild.container != 0 && webkit.WidgetIsValid(onlyChild.container) && greatGrandparent.container != 0 && webkit.WidgetIsValid(greatGrandparent.container) {1008+ if greatGrandPaned, ok := greatGrandparent.container.(*gtk.Paned); ok && greatGrandPaned != nil && onlyChild.container != nil {
1006 // GTK4 PanedSet*Child automatically handles unparenting1009 // GTK4 PanedSet*Child automatically handles unparenting
1007 if greatGrandparent.left == onlyChild {1010 if greatGrandparent.left == onlyChild {
1008- webkit.PanedSetStartChild(greatGrandparent.container, onlyChild.container)1011+ greatGrandPaned.SetStartChild(onlyChild.container)
1009 } else {1012 } else {
1010- webkit.PanedSetEndChild(greatGrandparent.container, onlyChild.container)1013+ greatGrandPaned.SetEndChild(onlyChild.container)
1011 }1014 }
1012 1015
1013- webkit.WidgetSetHExpand(onlyChild.container, true)1016+ onlyChild.container.SetHExpand(true)
1014- webkit.WidgetSetVExpand(onlyChild.container, true)1017+ onlyChild.container.SetVExpand(true)
1015- webkit.WidgetQueueAllocate(onlyChild.container)1018+ onlyChild.container.QueueAllocate()
1016- webkit.WidgetQueueAllocate(greatGrandparent.container)1019+ greatGrandparent.container.QueueAllocate()
1017 wm.scheduleIdleGuarded(func() bool {1020 wm.scheduleIdleGuarded(func() bool {
1018 if onlyChild == nil || !onlyChild.widgetValid {1021 if onlyChild == nil || !onlyChild.widgetValid {
1019 return false1022 return false
1020 }1023 }
1021- if onlyChild.container != 0 && webkit.WidgetIsValid(onlyChild.container) {1024+ if onlyChild.container != nil {
1022- webkit.WidgetShow(onlyChild.container)1025+ onlyChild.container.Show()
1023- webkit.WidgetQueueResize(onlyChild.container)1026+ onlyChild.container.QueueResize()
1024- webkit.WidgetQueueDraw(onlyChild.container)1027+ onlyChild.container.QueueDraw()
1025 }1028 }
1026 return false1029 return false
1027 }, onlyChild)1030 }, onlyChild)
@@ -1040,72 +1043,61 @@ func (wm *WorkspaceManager) cascadePromotion(singleChildPaned *paneNode) {
1040 1043
1041 // attachRoot attaches a node as the new window root1044 // attachRoot attaches a node as the new window root
1042 func (wm *WorkspaceManager) attachRoot(root *paneNode) {1045 func (wm *WorkspaceManager) attachRoot(root *paneNode) {
1043- if root == nil || root.container == 0 || wm.window == nil {1046+ if root == nil || root.container == nil || wm.window == nil {
1044- return
1045- }
1046- if !webkit.WidgetIsValid(root.container) {
1047 return1047 return
1048 }1048 }
1049 1049
1050 // Hold a transient reference so GTK does not destroy the widget while detached1050 // Hold a transient reference so GTK does not destroy the widget while detached
1051- releaseRef := webkit.WidgetRef(root.container)1051+ root.container.Ref()
1052- if !releaseRef {1052+ defer root.container.Unref()
1053- log.Printf("[workspace] WARNING: failed to acquire temporary ref for widget %#x; aborting attachRoot", root.container)
1054- return
1055- }
1056- defer func() {
1057- if webkit.WidgetIsValid(root.container) {
1058- webkit.WidgetUnref(root.container)
1059- }
1060- }()
1061 1053
1062 // Detach from previous container (paned, stack, etc.) before replacing the window child.1054 // Detach from previous container (paned, stack, etc.) before replacing the window child.
1063- if parent := webkit.WidgetGetParent(root.container); parent != 0 {1055+ if parent := root.container.Parent(); parent != nil {
1064- log.Printf("[workspace] unparenting widget %#x from parent %#x before window attach", root.container, parent)1056+ log.Printf("[workspace] unparenting widget %p from parent %p before window attach", root.container, parent)
1065- webkit.WidgetUnparent(root.container)1057+ root.container.Unparent()
1066- if !webkit.WidgetIsValid(root.container) {1058+ if root.container == nil {
1067- log.Printf("[workspace] WARNING: widget %#x became invalid during unparent; aborting attachRoot", root.container)1059+ log.Printf("[workspace] WARNING: widget %p became invalid during unparent; aborting attachRoot", root.container)
1068 return1060 return
1069 }1061 }
1070 }1062 }
1071 1063
1072 // Clear any existing window child to avoid GTK warnings when swapping roots.1064 // Clear any existing window child to avoid GTK warnings when swapping roots.
1073- wm.window.SetChild(0)1065+ wm.window.SetChild(nil)
1074 1066
1075- if webkit.WidgetIsValid(root.container) {1067+ if root.container != nil {
1076- webkit.WidgetSetHExpand(root.container, true)1068+ root.container.SetHExpand(true)
1077- webkit.WidgetSetVExpand(root.container, true)1069+ root.container.SetVExpand(true)
1078 }1070 }
1079 1071
1080- if !webkit.WidgetIsValid(root.container) {1072+ if root.container == nil {
1081- log.Printf("[workspace] WARNING: widget %#x invalid prior to window attach", root.container)1073+ log.Printf("[workspace] WARNING: widget %p invalid prior to window attach", root.container)
1082 return1074 return
1083 }1075 }
1084 1076
1085 wm.window.SetChild(root.container)1077 wm.window.SetChild(root.container)
1086- if webkit.WidgetIsValid(root.container) {1078+ if root.container != nil {
1087- webkit.WidgetQueueAllocate(root.container)1079+ root.container.QueueAllocate()
1088- webkit.WidgetShow(root.container)1080+ root.container.Show()
1089- webkit.WidgetQueueResize(root.container)1081+ root.container.QueueResize()
1090- webkit.WidgetQueueDraw(root.container)1082+ root.container.QueueDraw()
1091 }1083 }
1092 1084
1093 wm.scheduleIdleGuarded(func() bool {1085 wm.scheduleIdleGuarded(func() bool {
1094 if root == nil || !root.widgetValid {1086 if root == nil || !root.widgetValid {
1095 return false1087 return false
1096 }1088 }
1097- if root.container != 0 && webkit.WidgetIsValid(root.container) {1089+ if root.container != nil {
1098- webkit.WidgetQueueResize(root.container)1090+ root.container.QueueResize()
1099- webkit.WidgetQueueDraw(root.container)1091+ root.container.QueueDraw()
1100 }1092 }
1101 return false1093 return false
1102 }, root)1094 }, root)
1103 1095
1104 // Verify attachment succeeded1096 // Verify attachment succeeded
1105- if finalParent := webkit.WidgetGetParent(root.container); finalParent == 0 {1097+ if finalParent := root.container.Parent(); finalParent == nil {
1106- log.Printf("[workspace] WARNING: widget %#x has no parent after SetChild", root.container)1098+ log.Printf("[workspace] WARNING: widget %p has no parent after SetChild", root.container)
1107 } else {1099 } else {
1108- log.Printf("[workspace] attachRoot successful: widget %#x now child of %#x", root.container, finalParent)1100+ log.Printf("[workspace] attachRoot successful: widget %p now child of %p", root.container, finalParent)
1109 }1101 }
1110 }1102 }
1111 1103
@@ -1138,9 +1130,9 @@ func (wm *WorkspaceManager) cleanupPane(node *paneNode, generation uint) {
1138 node.pane = nil1130 node.pane = nil
1139 }1131 }
1140 1132
1141- if node.container != 0 {1133+ if node.container != nil {
1142 // Container widget will be destroyed by GTK when unparented1134 // Container widget will be destroyed by GTK when unparented
1143- node.container = 01135+ node.container = nil
1144 }1136 }
1145 1137
1146 node.pendingIdleHandles = nil1138 node.pendingIdleHandles = nil
@@ -1157,9 +1149,10 @@ func (wm *WorkspaceManager) decommissionParent(parent *paneNode, generation uint
1157 1149
1158 // For branch nodes (paneds), destroy the widget1150 // For branch nodes (paneds), destroy the widget
1159 // Children have already been unparented during promotion/close, no need to clear them1151 // Children have already been unparented during promotion/close, no need to clear them
1160- if !parent.isLeaf && parent.container != 0 && webkit.WidgetIsValid(parent.container) {1152+ if !parent.isLeaf && parent.container != nil {
1161- webkit.WidgetHookDestroy(parent.container)1153+ // In gotk4, we don't need to manually destroy - GTK handles this when unparented
1162- log.Printf("[workspace] destroyed paned widget: %#x", parent.container)1154+ // Just mark it for cleanup
1155+ log.Printf("[workspace] marked paned widget for cleanup: %p", parent.container)
1163 }1156 }
1164 1157
1165 wm.cleanupPane(parent, generation)1158 wm.cleanupPane(parent, generation)
@@ -1191,7 +1184,7 @@ func (wm *WorkspaceManager) cleanupAndExit(node *paneNode) (*paneNode, error) {
1191 log.Printf("[workspace] closing final pane; exiting browser")1184 log.Printf("[workspace] closing final pane; exiting browser")
1192 wm.cleanupPane(node, wm.nextCleanupGeneration())1185 wm.cleanupPane(node, wm.nextCleanupGeneration())
1193 wm.detachHover(node)1186 wm.detachHover(node)
1194- webkit.QuitMainLoop()1187+ wm.app.Quit()
1195 return nil, nil1188 return nil, nil
1196 }1189 }
1197 1190
@@ -1237,19 +1230,21 @@ func (wm *WorkspaceManager) closePane(node *paneNode) (*paneNode, error) {
1237 1230
1238 // Clear grandparent's focus child BEFORE unparenting closed pane1231 // Clear grandparent's focus child BEFORE unparenting closed pane
1239 // GTK propagates focus events upward, so we must clear grandparent focus first1232 // GTK propagates focus events upward, so we must clear grandparent focus first
1240- if grandparent != nil && grandparent.container != 0 && webkit.WidgetIsValid(grandparent.container) {1233+ if grandparent != nil && grandparent.container != nil {
1241- webkit.WidgetSetFocusChild(grandparent.container, 0)1234+ grandparent.container.SetFocusChild(nil)
1242 }1235 }
1243 1236
1244 // Unparent the closed pane's container from the paned BEFORE promoting sibling1237 // Unparent the closed pane's container from the paned BEFORE promoting sibling
1245- if node.container != 0 && webkit.WidgetIsValid(node.container) {1238+ if node.container != nil {
1246- if containerParent := webkit.WidgetGetParent(node.container); containerParent == parent.container {1239+ if containerParent := node.container.Parent(); containerParent == parent.container {
1247- if parent.left == node {1240+ if parentPaned, ok := parent.container.(*gtk.Paned); ok && parentPaned != nil {
1248- webkit.PanedSetStartChild(parent.container, 0)1241+ if parent.left == node {
1249- } else if parent.right == node {1242+ parentPaned.SetStartChild(nil)
1250- webkit.PanedSetEndChild(parent.container, 0)1243+ } else if parent.right == node {
1244+ parentPaned.SetEndChild(nil)
1245+ }
1246+ log.Printf("[workspace] unparented closed pane container: %p", node.container)
1251 }1247 }
1252- log.Printf("[workspace] unparented closed pane container: %#x", node.container)
1253 }1248 }
1254 }1249 }
1255 1250
@@ -1261,15 +1256,15 @@ func (wm *WorkspaceManager) closePane(node *paneNode) (*paneNode, error) {
1261 wm.swapContainers(grandparent, sibling)1256 wm.swapContainers(grandparent, sibling)
1262 1257
1263 // Reconnect the promoted sibling's rendering pipeline after GTK reparenting.1258 // Reconnect the promoted sibling's rendering pipeline after GTK reparenting.
1264- if sibling != nil && sibling.container != 0 {1259+ if sibling != nil && sibling.container != nil {
1265 wm.scheduleIdleGuarded(func() bool {1260 wm.scheduleIdleGuarded(func() bool {
1266 if sibling == nil || !sibling.widgetValid {1261 if sibling == nil || !sibling.widgetValid {
1267 return false1262 return false
1268 }1263 }
1269- if sibling.container != 0 && webkit.WidgetIsValid(sibling.container) {1264+ if sibling.container != nil {
1270- webkit.WidgetShow(sibling.container)1265+ sibling.container.Show()
1271- webkit.WidgetQueueResize(sibling.container)1266+ sibling.container.QueueResize()
1272- webkit.WidgetQueueDraw(sibling.container)1267+ sibling.container.QueueDraw()
1273 }1268 }
1274 return false1269 return false
1275 }, sibling)1270 }, sibling)
@@ -1335,17 +1330,17 @@ func (wm *WorkspaceManager) closeStackedPaneCompat(node *paneNode) (*paneNode, e
1335 }1330 }
1336 1331
1337 // Update GTK widgets1332 // Update GTK widgets
1338- if webkit.WidgetIsValid(stack.parent.container) && webkit.WidgetIsValid(remaining.container) {1333+ if parentPaned, ok := stack.parent.container.(*gtk.Paned); ok && parentPaned != nil && remaining.container != nil {
1339 if stack.parent.left == remaining {1334 if stack.parent.left == remaining {
1340- webkit.PanedSetStartChild(stack.parent.container, remaining.container)1335+ parentPaned.SetStartChild(remaining.container)
1341 } else {1336 } else {
1342- webkit.PanedSetEndChild(stack.parent.container, remaining.container)1337+ parentPaned.SetEndChild(remaining.container)
1343 }1338 }
1344 }1339 }
1345 } else {1340 } else {
1346 // Stack was root1341 // Stack was root
1347 wm.root = remaining1342 wm.root = remaining
1348- if webkit.WidgetIsValid(remaining.container) {1343+ if remaining.container != nil {
1349 wm.window.SetChild(remaining.container)1344 wm.window.SetChild(remaining.container)
1350 }1345 }
1351 }1346 }
@@ -1362,15 +1357,15 @@ func (wm *WorkspaceManager) closeStackedPaneCompat(node *paneNode) (*paneNode, e
1362 }1357 }
1363 1358
1364 // Remove widget from GTK box1359 // Remove widget from GTK box
1365- if webkit.WidgetIsValid(stack.container) && webkit.WidgetIsValid(node.container) {1360+ if stackBox, ok := stack.container.(*gtk.Box); ok && stackBox != nil && node.container != nil {
1366- webkit.BoxRemove(stack.container, node.container)1361+ stackBox.Remove(node.container)
1367 }1362 }
1368 1363
1369 // Show new active pane1364 // Show new active pane
1370 if stack.activeStackIndex >= 0 {1365 if stack.activeStackIndex >= 0 {
1371 active := stack.stackedPanes[stack.activeStackIndex]1366 active := stack.stackedPanes[stack.activeStackIndex]
1372- if webkit.WidgetIsValid(active.container) {1367+ if active.container != nil {
1373- webkit.WidgetShow(active.container)1368+ active.container.Show()
1374 }1369 }
1375 }1370 }
1376 1371

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