git.lucas.co / hou-control
SideFX Houdini customization package
git clone https://git.lucas.co/hou-control.git

commit745bb1f7766852f3936436a192b5986db0110887
parent406ececd8a
authorLucas Galante <[email protected]>
date2026-09-11 14:53
hc: float the HC Settings panel as a dialog so the WM does not tile it

Houdini creates floating panels as plain _NET_WM_WINDOW_TYPE_NORMAL
toplevels with no transient parent, so a wlroots tiling compositor tiles
the HC Settings panel like any other window -- it came up 3700x2272, the
same size as the main Houdini window, no matter what size was passed to
createFloatingPanel(). Setting Qt.Dialog and a transient parent on the
panel's toplevel makes the compositor float it and honour 720x600.

The size is applied only when the panel is created, so re-invoking the
command on an open panel focuses it instead of undoing a manual resize.

Co-Authored-By: Claude Opus 5 <[email protected]>

 python3.13libs/hc/hcsession.py | 42 +++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 41 insertions(+), 1 deletion(-)

diff --git a/python3.13libs/hc/hcsession.py b/python3.13libs/hc/hcsession.py
index 0a6a2df..b1fb162 100644
--- a/python3.13libs/hc/hcsession.py
+++ b/python3.13libs/hc/hcsession.py
@@ -11,6 +11,41 @@ from .hcwidgets import HCWidgets
 from .hccommands import command
 
 
+SETTINGS_PANEL_SIZE = (720, 600)
+
+
+def floatWindow(window, size=None):
+    """Make a Houdini floating panel's toplevel read as a dialog to the WM.
+
+    Houdini creates every floating panel as a plain _NET_WM_WINDOW_TYPE_NORMAL
+    toplevel with no transient parent, so a tiling compositor (wlroots/niri
+    here) tiles it like any other window -- which is why HC Settings came up
+    the same size as the main Houdini window no matter what `size` was passed
+    to createFloatingPanel(). Qt.Dialog plus a transient parent makes the
+    compositor float it instead and honour the requested size.
+
+    Changing the flag re-creates the native window, so it is only done once;
+    on an already-converted window this is a no-op and the panel does not
+    flicker.
+    """
+    from PySide6 import QtCore
+
+    # Qt.Dialog is Qt.Window|0x2, so a plain `flags & Qt.Dialog` test is true
+    # for every toplevel. Mask off the hint bits and compare the type.
+    if window.windowFlags() & QtCore.Qt.WindowType_Mask == QtCore.Qt.Dialog:
+        return
+
+    window.hide()
+    window.setWindowFlag(QtCore.Qt.Dialog, True)
+    handle = window.windowHandle()
+    main = hou.qt.mainWindow()
+    main_handle = main.windowHandle() if main is not None else None
+    if handle is not None and main_handle is not None:
+        handle.setTransientParent(main_handle)
+    if size is not None:
+        window.resize(*size)
+
+
 class HCSession:
     def __init__(self):
         return
@@ -235,6 +270,7 @@ class HCSession:
         """
         from .hcsettings import HCSettingsPanel
 
+        created = False
         hou_tab = self.settingsTab()
         if hou_tab is None:
             if hou.pypanel.interfaceByName(HCSettingsPanel.INTERFACE_NAME) is None:
@@ -247,14 +283,18 @@ class HCSession:
             # also suppresses the Python Panel interface-picker toolbar.
             panel = self.desktop().createFloatingPanel(
                 hou.paneTabType.PythonPanel,
-                size=(720, 600),
+                size=SETTINGS_PANEL_SIZE,
                 python_panel_interface=HCSettingsPanel.INTERFACE_NAME,
                 immediate=True)
             hou_tab = panel.paneTabs()[0]
+            created = True
 
         hou_tab.setIsCurrentTab()
         window = hou_tab.qtParentWindow()
         if window is not None:
+            # Only size it on the way up: re-invoking the command on an open
+            # panel should focus it, not undo a resize the user has made.
+            floatWindow(window, size=SETTINGS_PANEL_SIZE if created else None)
             window.show()
             window.raise_()
             window.activateWindow()