From e1e3d0e466ac8acc782b1e4d1e76c3304b0fc2b9 Mon Sep 17 00:00:00 2001 From: Julian Appel Date: Sat, 29 Aug 2026 01:02:07 +0200 Subject: [PATCH] Bring back the native title bar so the app has a taskbar entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Das randlose Fenster (overrideredirect) kostet unter Windows zwingend den Taskleisten-Eintrag. Nachruesten liesse er sich nur per SetWindowLongW (WS_EX_APPWINDOW) -- genau der Aufruf, der laut AGENTS.md schon AV-Fehlalarme ausgeloest hat. Der User hat sich deshalb fuer die native Titelleiste entschieden. Damit kommen Taskleiste, Alt+Tab, Aero-Snap sowie Ziehen und Groessenaendern am Rahmen nativ zurueck, und die Eigenbau-Loesungen dafuer entfallen ersatzlos: ziehbare Kopfzeile, Anfasser unten rechts (ersetzt durch self.minsize()), eigene ✕/—-Knoepfe und der -Handler, der Minimieren ins Tray umgeleitet hat. Schliessen legt weiterhin ins Tray statt zu beenden (wie die offizielle VersaGUI). Minimieren geht jetzt normal in die Taskleiste, und Escape minimiert ebenfalls statt ins Tray zu legen -- mit Taskleisten-Eintrag waere "verschwindet spurlos" die unangenehmere Ueberraschung. Co-Authored-By: Claude Opus 5 --- desktop_viewer.py | 102 ++++++++++++++-------------------------------- 1 file changed, 31 insertions(+), 71 deletions(-) diff --git a/desktop_viewer.py b/desktop_viewer.py index b2a4af1..6909ed5 100644 --- a/desktop_viewer.py +++ b/desktop_viewer.py @@ -56,8 +56,9 @@ WARN_RED = "#e0895a" POLL_MS = 1500 CARD_W, CARD_H = 150, 114 -# Untergrenze beim Ziehen am Anfasser -- knapp unter der Groesse, die das -# 4x5-Grid + Encoder mindestens brauchen, damit nichts abgeschnitten wird. +# Untergrenze fuers Verkleinern (self.minsize) -- knapp unter der Groesse, +# die das 4x5-Grid + Encoder mindestens brauchen, damit nichts +# abgeschnitten wird. MIN_W, MIN_H = 700, 500 SERIAL_POLL_S = 1.5 SERIAL_IDLE_S = 3.0 @@ -130,8 +131,8 @@ class VersaPadViewer(tk.Tk): self.configure(bg=BG) # Hoehe muss Kopfzeile + Tabs + 5 Kartenreihen + Encoder + Fusszeile # fassen -- seit die Karten eine Notizzeile haben (CARD_H 84 -> 114) - # reichten die alten 760px nicht mehr: Encoder und der Groessen- - # Anfasser lagen unterhalb des Fensterrands und waren unerreichbar. + # reichten die alten 760px nicht mehr: der Encoder-Bereich lag + # unterhalb des Fensterrands. Wer CARD_H aendert, muss hier mit. self.geometry("790x960") self.profile = 0 self._mtimes = {} @@ -165,32 +166,23 @@ class VersaPadViewer(tk.Tk): ) self._tray_icon.run_detached() - # Titelleiste ausgeblendet (reines Tk `overrideredirect`, KEINE - # ctypes/WinAPI-Fenstertricks -- siehe Speicher-Notiz "keine - # Selbstversteck-Fenstertricks": das Nachruesten eines Taskleisten- - # Icons per SetWindowLongW hat frueher AV-Fehlalarme ausgeloest). - # Ersatz fuer die fehlende Systemleiste: Kopfzeile ist ziehbar, und - # die Buttons rechts uebernehmen Minimieren/Schliessen (beides ins - # Tray, wie vorher schon das X der echten Titelleiste). - self.overrideredirect(True) + # Normales Fenster MIT Windows-Titelleiste. Bis 2026-08-28 lief es + # randlos (`overrideredirect(True)`) -- das kostet unter Windows aber + # zwingend den Taskleisten-Eintrag, und der wurde ausdruecklich + # gebraucht. Nachruesten liesse er sich nur per `SetWindowLongW` + # (WS_EX_APPWINDOW) -- genau der Aufruf, der laut Speicher-Notiz + # "keine Selbstversteck-Fenstertricks" schon AV-Fehlalarme + # ausgeloest hat und deshalb nicht in Frage kommt. Mit der echten + # Titelleiste kommen Taskleiste, Alt+Tab, Aero-Snap, Ziehen und + # Groessenaendern am Rahmen nativ zurueck; die Eigenbau-Loesungen + # dafuer (ziehbare Kopfzeile, Anfasser unten rechts, eigene + # ✕/—-Knoepfe) sind damit ersatzlos entfallen. + self.minsize(MIN_W, MIN_H) self.toggles_row = header = tk.Frame(self, bg=BG) header.pack(fill="x", padx=20, pady=(10, 6)) - title = tk.Label(header, text="VersaPad", bg=BG, fg=TEXT, - font=("Segoe UI", 11, "bold")) - title.pack(side="left", anchor="w", padx=(0, 16)) - - for widget in (header, title): - widget.bind("", self._start_move) - widget.bind("", self._on_move) - - for text in ("✕", "—"): - btn = tk.Label(header, text=text, bg=BG, fg=TEXT_DIM, - font=("Segoe UI", 11), cursor="hand2", padx=6) - btn.pack(side="right") - btn.bind("", lambda e: self._hide_to_tray()) - btn.bind("", lambda e, b=btn: b.configure(fg=TEXT)) - btn.bind("", lambda e, b=btn: b.configure(fg=TEXT_DIM)) + tk.Label(header, text="VersaPad", bg=BG, fg=TEXT, + font=("Segoe UI", 11, "bold")).pack(side="left", anchor="w", padx=(0, 16)) info_btn = tk.Label(header, text="ⓘ", bg=BG, fg=TEXT_DIM, font=("Segoe UI", 12), cursor="hand2", padx=6) @@ -264,29 +256,18 @@ class VersaPadViewer(tk.Tk): self.enc_frame = tk.Frame(self, bg=BG) self.enc_frame.pack(fill="x", padx=20) - # Fusszeile + Anfasser zum Groessenaendern: mit ausgeblendeter - # Titelleiste (overrideredirect) entfernt Windows auch die - # Fensterraender, an denen man sonst zieht -- ohne diesen Griff - # liesse sich das Fenster gar nicht mehr skalieren. footer_row = tk.Frame(self, bg=BG) footer_row.pack(fill="x", padx=20, pady=(14, 8)) self.footer = tk.Label(footer_row, text="", bg=BG, fg=TEXT_DIM, font=("Segoe UI", 8)) self.footer.pack(side="left", anchor="w") - # Der Griff haengt per place() an der FENSTER-Ecke, nicht am Ende des - # gepackten Inhalts: sonst wandert er mit dem Inhalt aus dem Bild, - # sobald das Fenster kleiner als der Inhalt ist -- also genau dann, - # wenn man ihn zum Vergroessern braucht. - grip = tk.Label(self, text="◢", bg=BG, fg=TEXT_DIM, - font=("Segoe UI", 11), cursor="sizing") - grip.place(relx=1.0, rely=1.0, anchor="se", x=-3, y=-1) - grip.bind("", self._start_resize) - grip.bind("", self._on_resize) - grip.bind("", lambda e: grip.configure(fg=TEXT)) - grip.bind("", lambda e: grip.configure(fg=TEXT_DIM)) - + # Schliessen legt weiterhin ins Tray statt zu beenden (wie die + # offizielle VersaGUI -- das Programm soll im Hintergrund + # weiterlaufen). Minimieren geht jetzt aber ganz normal in die + # Taskleiste; frueher hat ein -Handler auch das ins Tray + # umgeleitet, was ohne Taskleisten-Eintrag sinnvoll war und mit + # einem das Fenster nur unauffindbar machen wuerde. self.protocol("WM_DELETE_WINDOW", self._hide_to_tray) - self.bind("", self._on_unmap) self._bind_shortcuts() self._serial_thread.start() @@ -388,25 +369,6 @@ class VersaPadViewer(tk.Tk): def _on_toggle_topmost(self): self.attributes("-topmost", self.always_on_top.get()) - # ── Fenster ziehen (Ersatz fuer die ausgeblendete Titelleiste) ── - - def _start_move(self, event): - self._drag_origin = (event.x_root - self.winfo_x(), event.y_root - self.winfo_y()) - - def _on_move(self, event): - dx, dy = self._drag_origin - self.geometry(f"+{event.x_root - dx}+{event.y_root - dy}") - - def _start_resize(self, event): - self._resize_origin = (event.x_root, event.y_root, - self.winfo_width(), self.winfo_height()) - - def _on_resize(self, event): - x0, y0, w0, h0 = self._resize_origin - width = max(MIN_W, w0 + (event.x_root - x0)) - height = max(MIN_H, h0 + (event.y_root - y0)) - self.geometry(f"{width}x{height}") - # ── Live-Sync mit dem Board ──────────────────────────────────── def _on_toggle_sync(self): @@ -452,13 +414,7 @@ class VersaPadViewer(tk.Tk): text = SERIAL_STATUS_TEXT.get(error, error or "Fehler") self.sync_status.configure(text=text, fg=WARN_RED) - # ── Tray-Icon (kein Taskleisten-Eintrag beim Minimieren, wie VersaGUI) ── - - def _on_unmap(self, event): - """Minimieren faengt Windows normalerweise als Taskleisten-Icon ab -- - wir wollen stattdessen: Fenster komplett weg, nur noch Tray-Icon.""" - if event.widget is self and self.state() == "iconic": - self._hide_to_tray() + # ── Tray-Icon (Schliessen beendet nicht, wie bei der VersaGUI) ── def _hide_to_tray(self): self.withdraw() @@ -636,7 +592,11 @@ class VersaPadViewer(tk.Tk): haelt dessen grab_set() die Tastatur -- die Kuerzel hier koennen ihm also nicht dazwischenfunken.""" for seq, handler in ( - ("", lambda e: self._hide_to_tray()), + # Minimieren statt ins Tray: seit das Fenster eine Titelleiste + # und damit einen Taskleisten-Eintrag hat, waere "verschwindet + # spurlos" die unangenehmere Ueberraschung. Ins Tray legt + # weiterhin das ✕ der Titelleiste. + ("", lambda e: self.iconify()), ("", lambda e: self._rename_tab(self.profile)), ("", lambda e: self._toggle_editing_shortcut()), ("", lambda e: self._toggle_editing_shortcut()),