gui: fix Wayland SIGSEGV on launch; guard stderr for windowed bundles
e15329a's window-activation code crashed the GUI at startup on Wayland: raise_()/activateWindow()/move() go through Qt-Wayland's xdg restack/ xdg-activation paths, which SEGSEGV in PySide6 6.9 as shipped on Guix (faulthandler pinpointed win.raise_(); QT_QPA_PLATFORM=wayland reproduces, --selftest — plain show() — never crashed). Gate the whole center/raise/ activate block behind platformName() == "xcb": on X11 it is the canonical show-then-activate sequence and verified working under XWayland; on Wayland the compositor maps and focuses the new toplevel itself (raise/activate were no-ops there even before they crashed). Strict equality keeps wayland-egl and any unknown platform on the safe plain-show path. Centering is computed before show() again, as pre-e15329a, so X11 windows map already-centered. Also make every sys.stderr access None-safe: PyInstaller --windowed (>=5.8, i.e. the shipped Windows .exe) sets sys.stderr = None, so the unguarded startup notice would have raised AttributeError right after show() and killed the window; a dead pipe similarly raises on flush. Guard the notice (+ try/except OSError), the import-error message, and the debug discovery echo. Verified: launch stays alive on wayland, xcb, and offscreen-with- fd2-closed; --selftest passes offscreen/wayland incl. VAPTVUPT_DEBUG=1 with stderr closed.
This commit is contained in:
parent
e15329a7c9
commit
92488d47b9
1 changed files with 39 additions and 22 deletions
|
|
@ -38,6 +38,7 @@ except ImportError:
|
|||
from PyQt6.QtGui import QPalette, QColor, QIcon, QPixmap
|
||||
QT_BINDING = "PyQt6"
|
||||
except ImportError:
|
||||
if sys.stderr is not None: # None under PyInstaller --windowed
|
||||
sys.stderr.write(
|
||||
"ERROR: vaptvupt-gui requires PySide6 or PyQt6. Install one of:\n"
|
||||
" Debian/Ubuntu: sudo apt install python3-pyqt6\n"
|
||||
|
|
@ -64,7 +65,8 @@ _DISCOVERY_LOG = []
|
|||
def _discovery_log(msg):
|
||||
_DISCOVERY_LOG.append(msg)
|
||||
# Echo to stderr if VAPTVUPT_DEBUG or ZUPT_DEBUG is set
|
||||
if os.environ.get("VAPTVUPT_DEBUG") or os.environ.get("ZUPT_DEBUG"):
|
||||
if ((os.environ.get("VAPTVUPT_DEBUG") or os.environ.get("ZUPT_DEBUG"))
|
||||
and sys.stderr is not None): # None under PyInstaller --windowed
|
||||
sys.stderr.write(f" [discovery] {msg}\n")
|
||||
|
||||
def _is_runnable(path):
|
||||
|
|
@ -908,24 +910,39 @@ def main():
|
|||
f"event loop ran (rc={rc}); CLI={VAPTVUPT}")
|
||||
return rc
|
||||
|
||||
# Center on the active screen and force focus. Without this the window can
|
||||
# open off-screen or behind the last-focused frame on a tiling compositor
|
||||
# (Sway/i3/Hyprland) — the usual cause of "the GUI won't start / is stuck":
|
||||
# it launched, but you can't see it.
|
||||
# Center + raise + focus ONLY on X11 (xcb), where a stacking WM may place
|
||||
# the window off-screen or leave it unfocused. On Wayland the compositor
|
||||
# owns placement and focus, and these calls (self-move / xdg restack /
|
||||
# xdg-activation) SEGFAULT some Qt-Wayland builds — including PySide6 6.9 as
|
||||
# shipped on Guix — so they must not run there. Plain show() is what
|
||||
# --selftest exercises and is stable; the compositor maps and focuses the
|
||||
# new toplevel itself. On Windows/macOS Qt's automatic placement centers
|
||||
# first windows and the OS foregrounds a freshly launched app, so skipping
|
||||
# is safe there too. Strict == "xcb" keeps wayland-egl etc. on the safe path.
|
||||
is_x11 = app.platformName() == "xcb"
|
||||
if is_x11:
|
||||
scr = app.primaryScreen()
|
||||
if scr is not None:
|
||||
fg = win.frameGeometry()
|
||||
fg.moveCenter(scr.availableGeometry().center())
|
||||
win.move(fg.topLeft())
|
||||
win.show()
|
||||
if is_x11:
|
||||
win.raise_()
|
||||
win.activateWindow()
|
||||
|
||||
# A GUI blocks the launching shell, so a working launch otherwise looks like
|
||||
# a "stuck" terminal. Emit one line to stderr so it's unambiguous.
|
||||
sys.stderr.write(f"VaptVupt {ZUPT_VER_NUMBER} GUI started — window open "
|
||||
f"(close it to exit).\n")
|
||||
# a "stuck" terminal. Emit one line to stderr so it's unambiguous. Guarded:
|
||||
# PyInstaller --windowed sets sys.stderr to None (any write would raise and
|
||||
# kill the window we just showed), and a dead pipe raises OSError on flush —
|
||||
# a courtesy notice must never take the GUI down.
|
||||
if sys.stderr is not None:
|
||||
try:
|
||||
sys.stderr.write(f"VaptVupt {ZUPT_VER_NUMBER} GUI started — "
|
||||
f"window open (close it to exit).\n")
|
||||
sys.stderr.flush()
|
||||
except OSError:
|
||||
pass
|
||||
return app.exec()
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
Loading…
Reference in a new issue