From 2bf9776076ce91ff1d16eb54f18d23b7670d8a60 Mon Sep 17 00:00:00 2001 From: lm Date: Sun, 19 Oct 2025 18:44:49 +0200 Subject: [PATCH] Reapply Windows borderless styling without hiding taskbar entry --- app/app.py | 79 +++++++++++++++++++++++++++++++++++++++++++++------ app/gui/ui.py | 14 +++++---- 2 files changed, 78 insertions(+), 15 deletions(-) diff --git a/app/app.py b/app/app.py index 53865e7..23109ab 100644 --- a/app/app.py +++ b/app/app.py @@ -5,6 +5,7 @@ from __future__ import annotations import ctypes import platform import tkinter as tk +from ctypes import wintypes from importlib import resources from .gui import ColorPickerMixin, ExclusionMixin, ThemeMixin, UIBuilderMixin @@ -84,9 +85,17 @@ class ICRAApp( default_y = (screen_height - default_height) // 4 self._window_geometry = f"{default_width}x{default_height}+{default_x}+{default_y}" self._is_maximized = True + self._use_overrideredirect = platform.system() != "Windows" self.root.geometry(f"{screen_width}x{screen_height}+0+0") self.root.configure(bg="#f2f2f7") - self.root.overrideredirect(True) + try: + self.root.overrideredirect(self._use_overrideredirect) + except Exception: + if self._use_overrideredirect: + try: + self.root.attributes("-type", "splash") + except Exception: + pass self._window_icon_ref = None self._apply_window_icon() self._init_window_chrome() @@ -145,22 +154,74 @@ class ICRAApp( def _init_window_chrome(self) -> None: """Configure a borderless window while retaining a taskbar entry.""" try: - self.root.bind("", self._restore_borderless) - self.root.after(0, self._restore_borderless) - self.root.after(0, self._ensure_taskbar_entry) + if platform.system() == "Windows": + self.root.after(0, self._ensure_taskbar_entry) + self.root.after(0, self._apply_windows_borderless_style) + self.root.bind("", lambda _e: self._apply_windows_borderless_style(), add="+") + self.root.bind("", lambda _e: self._ensure_taskbar_entry(), add="+") + else: + self.root.bind("", self._restore_borderless) + self.root.after(0, self._restore_borderless) + self.root.after(0, self._ensure_taskbar_entry) except Exception: - try: - self.root.overrideredirect(True) - except Exception: - pass + pass def _restore_borderless(self, _event=None) -> None: try: - self.root.overrideredirect(True) + if self._use_overrideredirect: + self.root.overrideredirect(True) self._ensure_taskbar_entry() except Exception: pass + def _apply_windows_borderless_style(self) -> None: + try: + if platform.system() != "Windows": + return + + hwnd = self.root.winfo_id() + if not hwnd: + self.root.after(50, self._apply_windows_borderless_style) + return + + user32 = ctypes.windll.user32 # type: ignore[attr-defined] + GWL_STYLE = -16 + WS_CAPTION = 0x00C00000 + WS_THICKFRAME = 0x00040000 + WS_MINIMIZEBOX = 0x00020000 + WS_MAXIMIZEBOX = 0x00010000 + WS_SYSMENU = 0x00080000 + SWP_NOSIZE = 0x0001 + SWP_NOMOVE = 0x0002 + SWP_NOZORDER = 0x0004 + SWP_FRAMECHANGED = 0x0020 + + set_window_long = getattr(user32, "SetWindowLongPtrW", user32.SetWindowLongW) + get_window_long = getattr(user32, "GetWindowLongPtrW", user32.GetWindowLongW) + ptr_type = ctypes.c_longlong if ctypes.sizeof(ctypes.c_void_p) == 8 else ctypes.c_long + get_window_long.restype = ptr_type # type: ignore[attr-defined] + get_window_long.argtypes = [wintypes.HWND, ctypes.c_int] # type: ignore[attr-defined] + set_window_long.restype = ptr_type # type: ignore[attr-defined] + set_window_long.argtypes = [wintypes.HWND, ctypes.c_int, ptr_type] # type: ignore[attr-defined] + + style = get_window_long(hwnd, GWL_STYLE) + new_style = style & ~( + WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU + ) + if new_style != style: + set_window_long(hwnd, GWL_STYLE, ptr_type(new_style)) + user32.SetWindowPos( + hwnd, + 0, + 0, + 0, + 0, + 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED, + ) + except Exception: + pass + def start_app() -> None: """Entry point used by the CLI script.""" diff --git a/app/gui/ui.py b/app/gui/ui.py index 575d766..af36ad5 100644 --- a/app/gui/ui.py +++ b/app/gui/ui.py @@ -566,17 +566,19 @@ class UIBuilderMixin: def _minimize_window(self) -> None: try: self._remember_window_geometry() - if hasattr(self.root, "overrideredirect"): + use_or = getattr(self, "_use_overrideredirect", False) + if use_or and hasattr(self.root, "overrideredirect"): try: self.root.overrideredirect(False) except Exception: pass self.root.iconify() - restorer = getattr(self, "_restore_borderless", None) - if callable(restorer): - self.root.after(120, restorer) - elif hasattr(self.root, "overrideredirect"): - self.root.after(120, lambda: self.root.overrideredirect(True)) # type: ignore[arg-type] + if use_or: + restorer = getattr(self, "_restore_borderless", None) + if callable(restorer): + self.root.after(120, restorer) + elif hasattr(self.root, "overrideredirect"): + self.root.after(120, lambda: self.root.overrideredirect(True)) # type: ignore[arg-type] except Exception: pass