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 | |||
| 392 | 392 | if wm.window != nil { | |
| 393 | 393 | wm.window.SetChild(0) | |
| 394 | 394 | } | |
| 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 | |
| 402 | 396 | } else if parent.container != 0 { | |
| 403 | 397 | // Target has a parent paned - unparent it from there | |
| 404 | 398 | log.Printf("[workspace] unparenting existing container from parent paned") | |
| @@ -676,12 +670,7 @@ func (wm *WorkspaceManager) splitNode(target *paneNode, direction string) (*pane | |||
| 676 | 670 | if wm.window != nil { | |
| 677 | 671 | wm.window.SetChild(0) | |
| 678 | 672 | } | |
| 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 | |
| 685 | 674 | } else if parent.container != 0 { | |
| 686 | 675 | // Split target has a parent paned - remove it (automatically unparents in GTK4) | |
| 687 | 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 | 947 | // Attach to window | |
| 959 | 948 | 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 | |
| 965 | 950 | wm.window.SetChild(onlyChild.container) | |
| 966 | 951 | webkit.WidgetSetHExpand(onlyChild.container, true) | |
| 967 | 952 | webkit.WidgetSetVExpand(onlyChild.container, true) | |
| @@ -983,12 +968,7 @@ func (wm *WorkspaceManager) cascadePromotion(singleChildPaned *paneNode) { | |||
| 983 | 968 | ||
| 984 | 969 | // Reparent widget in GTK | |
| 985 | 970 | 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 | |
| 992 | 972 | if greatGrandparent.left == onlyChild { | |
| 993 | 973 | webkit.PanedSetStartChild(greatGrandparent.container, onlyChild.container) | |
| 994 | 974 | } else { | |
| @@ -1029,13 +1009,8 @@ func (wm *WorkspaceManager) attachRoot(root *paneNode) { | |||
| 1029 | 1009 | return | |
| 1030 | 1010 | } | |
| 1031 | 1011 | 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 | |
| 1039 | 1014 | wm.window.SetChild(root.container) | |
| 1040 | 1015 | webkit.WidgetQueueAllocate(root.container) | |
| 1041 | 1016 | 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 there | 397 | // 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 parent | 673 | + | // 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 window | 947 | // 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 needed | 949 | + | // 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 GTK | 969 | // 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 container | 971 | + | // 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 | return | 1009 | 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 first | 1012 | + | // 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) | ||