detached b29fd2f

fix(workspace): remove manual unparent calls before GTK4 reparenting operations to prevent widget destruction

GTK4 set_child functions (window.SetChild, PanedSetStartChild, etc.) automatically handle
unparenting widgets from their old parents. Manual unparent calls before these operations
can destroy complex widgets like GtkBox (stacked containers), causing segmentation faults.
commit
b29fd2f93aa60f2feaab19fc1f553c10d7f30deb
author
Brice · · 2025-10-04 19:42 UTC
committer
Brice · · 2025-10-05 13:54 UTC
parent
a5cfffc
signature
unsigned
1 file · +6 -31
M internal/app/browser/workspace_pane_ops.go +6 -31
@@ -392,13 +392,7 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
392392 if wm.window != nil {
393393 wm.window.SetChild(0)
394394 }
395- // For root widgets, manually unparent if they have a GTK parent (like splitNode does)
396- if webkit.WidgetIsValid(existingContainer) {
397- if webkit.WidgetGetParent(existingContainer) != 0 {
398- webkit.WidgetUnparent(existingContainer)
399- log.Printf("[workspace] manually unparented root widget: %#x", existingContainer)
400- }
401- }
395+ // GTK4 will auto-unparent when we add to new paned - no manual unparent needed
402396 } else if parent.container != 0 {
403397 // Target has a parent paned - unparent it from there
404398 log.Printf("[workspace] unparenting existing container from parent paned")
@@ -676,12 +670,7 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
676670 if wm.window != nil {
677671 wm.window.SetChild(0)
678672 }
679- // For root widgets, only unparent if they actually have a GTK parent
680- if webkit.WidgetIsValid(splitTargetContainer) {
681- if webkit.WidgetGetParent(splitTargetContainer) != 0 {
682- webkit.WidgetUnparent(splitTargetContainer)
683- }
684- }
673+ // GTK4 will auto-unparent when we add to new paned - no manual unparent needed
685674 } else if parent.container != 0 {
686675 // Split target has a parent paned - remove it (automatically unparents in GTK4)
687676 log.Printf("[workspace] unparenting split target container=%#x from parent paned=%#x", splitTargetContainer, parent.container)
@@ -957,11 +946,7 @@ func (wm *WorkspaceManager) cascadePromotion(singleChildPaned *paneNode) {
957946
958947 // Attach to window
959948 if onlyChild.container != 0 && webkit.WidgetIsValid(onlyChild.container) {
960- // Unparent from old container if needed
961- if parent := webkit.WidgetGetParent(onlyChild.container); parent != 0 {
962- webkit.WidgetUnparent(onlyChild.container)
963- }
964-
949+ // GTK4 window.SetChild automatically handles unparenting
965950 wm.window.SetChild(onlyChild.container)
966951 webkit.WidgetSetHExpand(onlyChild.container, true)
967952 webkit.WidgetSetVExpand(onlyChild.container, true)
@@ -983,12 +968,7 @@ func (wm *WorkspaceManager) cascadePromotion(singleChildPaned *paneNode) {
983968
984969 // Reparent widget in GTK
985970 if onlyChild.container != 0 && webkit.WidgetIsValid(onlyChild.container) && greatGrandparent.container != 0 && webkit.WidgetIsValid(greatGrandparent.container) {
986- // Unparent from old container
987- if parent := webkit.WidgetGetParent(onlyChild.container); parent != 0 {
988- webkit.WidgetUnparent(onlyChild.container)
989- }
990-
991- // Attach to great-grandparent
971+ // GTK4 PanedSet*Child automatically handles unparenting
992972 if greatGrandparent.left == onlyChild {
993973 webkit.PanedSetStartChild(greatGrandparent.container, onlyChild.container)
994974 } else {
@@ -1029,13 +1009,8 @@ func (wm *WorkspaceManager) attachRoot(root *paneNode) {
10291009 return
10301010 }
10311011 if webkit.WidgetIsValid(root.container) {
1032- // Check if widget has a parent and unparent it first
1033- if parent := webkit.WidgetGetParent(root.container); parent != 0 {
1034- log.Printf("[workspace] unparenting widget %#x from parent %#x before window attach", root.container, parent)
1035- webkit.WidgetUnparent(root.container)
1036- }
1037-
1038- // Now safe to set as window child
1012+ // GTK4 window.SetChild automatically handles unparenting from old parent
1013+ // DO NOT unparent manually as it can destroy complex widgets like GtkBox
10391014 wm.window.SetChild(root.container)
10401015 webkit.WidgetQueueAllocate(root.container)
10411016 webkit.WidgetShow(root.container)
@@ -392,13 +392,7 @@ func (wm *WorkspaceManager) insertPopupPane(target *paneNode, newPane *BrowserPa
392 if wm.window != nil {392 if wm.window != nil {
393 wm.window.SetChild(0)393 wm.window.SetChild(0)
394 }394 }
395- // For root widgets, manually unparent if they have a GTK parent (like splitNode does)395+ // GTK4 will auto-unparent when we add to new paned - no manual unparent needed
396- if webkit.WidgetIsValid(existingContainer) {
397- if webkit.WidgetGetParent(existingContainer) != 0 {
398- webkit.WidgetUnparent(existingContainer)
399- log.Printf("[workspace] manually unparented root widget: %#x", existingContainer)
400- }
401- }
402 } else if parent.container != 0 {396 } else if parent.container != 0 {
403 // Target has a parent paned - unparent it from there397 // Target has a parent paned - unparent it from there
404 log.Printf("[workspace] unparenting existing container from parent paned")398 log.Printf("[workspace] unparenting existing container from parent paned")
@@ -676,12 +670,7 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane
676 if wm.window != nil {670 if wm.window != nil {
677 wm.window.SetChild(0)671 wm.window.SetChild(0)
678 }672 }
679- // For root widgets, only unparent if they actually have a GTK parent673+ // GTK4 will auto-unparent when we add to new paned - no manual unparent needed
680- if webkit.WidgetIsValid(splitTargetContainer) {
681- if webkit.WidgetGetParent(splitTargetContainer) != 0 {
682- webkit.WidgetUnparent(splitTargetContainer)
683- }
684- }
685 } else if parent.container != 0 {674 } else if parent.container != 0 {
686 // Split target has a parent paned - remove it (automatically unparents in GTK4)675 // Split target has a parent paned - remove it (automatically unparents in GTK4)
687 log.Printf("[workspace] unparenting split target container=%#x from parent paned=%#x", splitTargetContainer, parent.container)676 log.Printf("[workspace] unparenting split target container=%#x from parent paned=%#x", splitTargetContainer, parent.container)
@@ -957,11 +946,7 @@ func (wm *WorkspaceManager) cascadePromotion(singleChildPaned *paneNode) {
957 946
958 // Attach to window947 // Attach to window
959 if onlyChild.container != 0 && webkit.WidgetIsValid(onlyChild.container) {948 if onlyChild.container != 0 && webkit.WidgetIsValid(onlyChild.container) {
960- // Unparent from old container if needed949+ // GTK4 window.SetChild automatically handles unparenting
961- if parent := webkit.WidgetGetParent(onlyChild.container); parent != 0 {
962- webkit.WidgetUnparent(onlyChild.container)
963- }
964-
965 wm.window.SetChild(onlyChild.container)950 wm.window.SetChild(onlyChild.container)
966 webkit.WidgetSetHExpand(onlyChild.container, true)951 webkit.WidgetSetHExpand(onlyChild.container, true)
967 webkit.WidgetSetVExpand(onlyChild.container, true)952 webkit.WidgetSetVExpand(onlyChild.container, true)
@@ -983,12 +968,7 @@ func (wm *WorkspaceManager) cascadePromotion(singleChildPaned *paneNode) {
983 968
984 // Reparent widget in GTK969 // Reparent widget in GTK
985 if onlyChild.container != 0 && webkit.WidgetIsValid(onlyChild.container) && greatGrandparent.container != 0 && webkit.WidgetIsValid(greatGrandparent.container) {970 if onlyChild.container != 0 && webkit.WidgetIsValid(onlyChild.container) && greatGrandparent.container != 0 && webkit.WidgetIsValid(greatGrandparent.container) {
986- // Unparent from old container971+ // GTK4 PanedSet*Child automatically handles unparenting
987- if parent := webkit.WidgetGetParent(onlyChild.container); parent != 0 {
988- webkit.WidgetUnparent(onlyChild.container)
989- }
990-
991- // Attach to great-grandparent
992 if greatGrandparent.left == onlyChild {972 if greatGrandparent.left == onlyChild {
993 webkit.PanedSetStartChild(greatGrandparent.container, onlyChild.container)973 webkit.PanedSetStartChild(greatGrandparent.container, onlyChild.container)
994 } else {974 } else {
@@ -1029,13 +1009,8 @@ func (wm *WorkspaceManager) attachRoot(root *paneNode) {
1029 return1009 return
1030 }1010 }
1031 if webkit.WidgetIsValid(root.container) {1011 if webkit.WidgetIsValid(root.container) {
1032- // Check if widget has a parent and unparent it first1012+ // GTK4 window.SetChild automatically handles unparenting from old parent
1033- if parent := webkit.WidgetGetParent(root.container); parent != 0 {1013+ // DO NOT unparent manually as it can destroy complex widgets like GtkBox
1034- log.Printf("[workspace] unparenting widget %#x from parent %#x before window attach", root.container, parent)
1035- webkit.WidgetUnparent(root.container)
1036- }
1037-
1038- // Now safe to set as window child
1039 wm.window.SetChild(root.container)1014 wm.window.SetChild(root.container)
1040 webkit.WidgetQueueAllocate(root.container)1015 webkit.WidgetQueueAllocate(root.container)
1041 webkit.WidgetShow(root.container)1016 webkit.WidgetShow(root.container)

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