Přeskočit obsah

Launcher2 — Tray Widget & Background Mode

Spec pro fázi L2.9 — změna chování launcher2 tak, aby aplikace běžela primárně na pozadí a hlavní okno bylo jen jedna z forem zobrazení. Křížek hl. okna už neodhlašuje, místo toho zůstává session + sync agent + chat aktivní v tray. K nim přibývá mini widget — kompaktní okno vpravo dole nad taskbarem.

Status: draft (2026-05-13)

1. Cíle

  1. Mini widget v pravém dolním rohu se souhrnným stavem záloh + chat notifikacemi + tlačítkem pro otevření hlavní app. Otevírá se z tray ikonky a sám se objevuje při klíčových událostech.
  2. Křížek (X) na hlavním okně schová okno do tray, nikoli odhlášení. Session, sync agent, chat běží dál.
  3. Auto-login při startu Windows rozhoduje co zobrazit podle toho, v jakém stavu byla aplikace před vypnutím PC.

2. Komponenty (high-level)

                 ┌─────────────────────────────────────┐
                 │  customtkinter root (skrytý CTk())  │
                 └────────────────┬────────────────────┘
        ┌─────────────────────────┼──────────────────────┐
        ▼                         ▼                      ▼
  Hlavní okno              Mini widget              Chat okna
  (MainWindow,             (TrayWidget,             (ChatWindow,
   CTkToplevel)             CTkToplevel,             plovoucí)
                            borderless)
                                  │ otevřít / skrýt
                            ┌─────┴─────┐
                            │ Tray ikona│  ◄── pystray (existující)
                            └───────────┘

Všechno v jednom procesu (potvrzeno). Hlavní okno je další toplevel vedle widgetu, ne root.

3. Mini widget

3.1 Vzhled a pozice

  • Borderless CTkToplevel (overrideredirect(True)), bez systémového titulu i křížku.
  • Pevná pozice: vpravo dole, kotvení k pravému dolnímu rohu pracovní plochy (mimo taskbar). Offset 12 px od kraje obrazovky.
  • Šířka: 360 px. Výška dynamická podle obsahu (max 480 px, jinak scrollable).
  • Always-on-top (-topmost, True).
  • Skrývání: klik mimo widget (focus-out event) → withdraw(). Při auto-pop-up (viz 3.4) navíc timer na 20 s → withdraw().
  • Žádný křížek na widgetu (borderless), ukončení procesu jen přes tray menu → "Ukončit".
  • Multi-monitor: widget se kotví k monitoru, na kterém je kurzor / aktivní okno v čase zobrazení.

3.2 Obsah widgetu

Zhora dolů:

┌─────────────────────────────────────────────┐
│  AVAX                              [☰ menu] │  ← hlavička, jméno user
│  Firma: AVAXIS.CZ            [👤 přepnout]  │  ← jen pokud má 2+ firem
├─────────────────────────────────────────────┤
│  Zálohy (souhrnný progress)                 │
│  ████████████░░░░░░  47 %    1 240/2 632    │  ← jen pokud sync běží
│  Příští plánovaná: 19:42 (za 12 min)        │
│                                             │
│  [🔴 D:\Faktury — selhalo 14:22 (3 chyby)]  │  ← jen složky s chybou
│  [🔴 E:\Sklad   — selhalo 14:23]            │  ← klik = otevře detail
├─────────────────────────────────────────────┤
│  Sekce 'Firma 2' (DALŠÍ FIRMA, s.r.o.)      │  ← jen multi-firma
│  ... totéž ...                              │
├─────────────────────────────────────────────┤
│  💬 Chat (3 nepřečtené)                     │
│  • Petr Novák — 2 zprávy                    │  ← klik = otevře ChatWindow
│  • support — 1 zpráva                       │     na konkrétní konverzaci
├─────────────────────────────────────────────┤
│  [Synchronizovat]    [AVAX aplikace]        │
└─────────────────────────────────────────────┘

Pravidla zobrazení

Sekce Kdy zobrazit
Progress bar souhrnný Pouze pokud sync právě běží
"Příští plánovaná" Vždy (formát relativní + absolutní)
Řádky složek s chybou Pouze složky se stavem failed (nejnovější selhání). OK složky se nezobrazují.
Sekce per firma Vždy, pokud má uživatel 2+ firem. Při 1 firmě bez sekce.
Chat sekce Pouze pokud má aspoň 1 nepřečtenou zprávu.
[Synchronizovat] Schovaný/disabled během běžící synchronizace, jinak vždy.
[AVAX aplikace] Vždy.

3.3 Akce

  • [Synchronizovat] — spustí okamžitou synchronizaci VŠECH sync složek aktivní firmy (volá existující sync agent, ignoruje plánovaný interval, resetuje plánovač).
  • [AVAX aplikace] — otevře hlavní okno. Pokud MainWindow instance existuje a je withdrawndeiconify() + lift(). Pokud neexistuje (ještě nebyl login) → spustí přihlašovací flow (otevře LoginScreen).
  • Klik na řádek chyby — otevře hl. okno přepnuté na obrazovku Zálohy s detailem dané složky a chybovým logem.
  • Klik na řádek chatu — otevře samostatné plovoucí ChatWindow (jak funguje dnes v v0.3.9) na konkrétní konverzaci. Hlavní okno se NEOTEVÍRÁ.
  • [👤 přepnout] — popup s výběrem firmy. Změna se promítne do hl. app i widgetu (sdílený company context).
  • [☰ menu] — to samé co pravoklik na tray ikoně (viz 6).

3.4 Auto-pop-up scénáře

Widget se sám zobrazí v těchto případech (jinak je skrytý a otevírá se jen přes tray):

  1. Auto-login při startu Windows (viz 5). Zůstává 20 s, pak withdraw().
  2. Začátek "velké zálohy" — záloha s >= 50 souborů nebo total size

    = 100 MB v jedné operaci. Malé sync změny (1-2 soubory) nepop-upují. Konstanty WIDGET_AUTO_POPUP_FILES = 50, WIDGET_AUTO_POPUP_BYTES = 100*1024*1024.

  3. Dokončení zálohy s chybou — pop-up na 20 s.
  4. Nová příchozí chat zpráva — pop-up na 6 s (toast-like). Pokud uživatel během toho zaktivuje widget kurzorem (<Enter>), timer se zruší dokud kurzor neopustí widget (focus-out).

Pop-up logika nesmí překrývat: pokud widget už je viditelný, novou událost jen aktualizuje obsah a zresetuje 20 s timer (vyjma chat toast 6 s který nesnižuje delší timer).

4. Křížek (X) hlavního okna

Dnes: X = process exit (ukončení). Nově: X = withdraw() hlavního okna. Proces dál běží:

  • sync agent jede dál (plánované zálohy + watcher)
  • chat WebSocket zůstává připojený
  • tray ikona zůstává
  • session token v paměti (auto-login disk state nezměněn)

Hlavní okno lze znovu otevřít: - klikem na tray ikonu (jednoduchý LMB) — pravoklik = tray menu - tlačítkem AVAX aplikace v mini widgetu

Ukončit aplikaci úplně lze jen: - tray menu → Ukončit (sync agent se zastaví, chat WS odpojí, proces končí) - z hl. okna menu → Soubor → Ukončit (totožné chování)

4.1 Stav last_main_window_visible

Při každé změně viditelnosti hlavního okna se zapíše do settings.json:

{
  "last_main_window_visible": true|false,
  "last_main_window_visible_ts": "2026-05-13T19:42:11"
}

Triggery: - MainWindow.deiconify() / <Map> event → true - MainWindow.withdraw() / <Unmap> event (i přes X i přes minimalizaci) → false - Při Ukončit se stav NEAKTUALIZUJE (zachová poslední živý stav, aby start po vypnutí věděl jak to bylo)

Důležité: minimalizace do taskbaru (iconify()) se počítá jako neviditelné (false). Tj. pokud uživatel minimalizuje a vypne PC, příští start zobrazí widget, ne hl. okno.

4.2 Logout

  • Tray menu: Odhlásit (vedle Ukončit)
  • Hl. okno menu: Soubor → Odhlásit (existující, zachovat)
  • Akce: vyčistit session token (paměť + perzistentní store pokud byl remember_password=False), odpojit WS, zastavit sync agent, schovat widget, otevřít LoginScreen (proces běží dál ale ve stavu "nepřihlášený launcher").

5. Auto-login flow při startu Windows

Předpoklad: registry entry HKCU \…\Run\AVAX spustí pythonw.exe main.py (bez konzole — opraveno 2026-05-13).

[Start Windows]
  main.py
     ├── Načti settings.json
     ├── auto_login_enabled? ──── NE ──► LoginScreen (klasický flow)
     │      │
     │     ANO
     │      │
     │      ▼
     │   Proveď silent login (existující flow z v0.3.1)
     │      │
     │      ├── selhání → LoginScreen (klasický flow)
     │      │
     │      ▼ úspěch
  Načti last_main_window_visible
     ├── true  ──► Otevři MainWindow (normal), widget skrytý
     └── false ──► MainWindow zůstává withdrawn,
                   zobraz Mini widget pop-up
                   po 20 s widget.withdraw()

5.1 Detail "20 s pak schovat"

  • Widget se zobrazí (deiconify()) s plným obsahem.
  • widget.after(20_000, widget.withdraw) naplánovaný timer.
  • Pokud uživatel během 20 s klikne na widget / hover nad ním → timer se zruší a widget zůstává dokud uživatel nekliknul mimo.
  • Pokud uživatel klikne mimo widget před uplynutím 20 s → standardní focus-out → withdraw() (timer se přeskočí).

6. Tray menu

Pravoklik na tray ikoně:

┌──────────────────────────────┐
│  Otevřít AVAX                │  ← LMB klik = stejné jako "Otevřít AVAX"
│  Zobrazit widget             │
│  ─────────────────────────── │
│  Synchronizovat teď          │
│  ─────────────────────────── │
│  Nastavení                   │
│  Odhlásit                    │
│  Ukončit                     │
└──────────────────────────────┘

LMB klik na tray = Zobrazit widget (otevírá widget, ne hl. okno). Dvojklik (volitelně) = Otevřít AVAX hl. okno.

7. Perzistence stavu

Klíče v settings.json (rozšíření existujícího):

Klíč Typ Default Význam
last_main_window_visible bool false Stav hl. okna při posledním změně viditelnosti
last_main_window_visible_ts str (ISO) null Časová značka poslední změny
widget_enabled bool true Zda widget vůbec používat (master switch v Nastavení)
widget_auto_popup_files int 50 Práh pro auto-popup při velké záloze
widget_auto_popup_bytes int 104857600 Práh velikosti (B) pro auto-popup
widget_startup_visible_seconds int 20 Timeout pro auto-login pop-up

8. Změny v existujícím kódu (hrubě)

Soubor / komponenta Změna
desktop/launcher2/main.py Nový boot flow (viz 5), instanciace TrayWidget před MainWindow
desktop/launcher2/screens.py (MainWindow) protocol("WM_DELETE_WINDOW")withdraw() místo destroy(). <Map>/<Unmap> handlery pro last_main_window_visible.
desktop/launcher2/settings.py Nové klíče v DEFAULTS, gettery/settery
desktop/launcher2/tray.py (existující) LMB klik → otevři widget. Rozšíření menu (Odhlásit, Zobrazit widget).
desktop/launcher2/tray_widget.py (nové) TrayWidget(ctk.CTkToplevel) — borderless, position bottom-right, sekce per firma, chat sekce, auto-popup logika, focus-out hide.
desktop/launcher2/sync_agent.py Při startu / dokončení / chybě zálohy publikovat event do widgetu (existující event bus nebo přímé widget.on_sync_event(...)). Detekce "velké zálohy" pro auto-popup.
desktop/launcher2/chat.py (existující) Příchozí zpráva → notifikace do widgetu (počet nepřečtených per peer).

9. Otevřené body / pozdější iterace

  • Multi-monitor preferovaný monitor: aktuálně "kde je kurzor". Možno rozšířit o per-monitor pinning v nastavení.
  • Drag-to-move widget: zatím pevná pozice, později volitelně.
  • Notifikace Windows native (win10toast/winrt) jako alternativa / doplněk widgetu pro toast chování — zvážit po prvním uživatelském testu.
  • Snooze sync v widgetu (odložit příští zálohu o 10/30 min) — nice-to-have.

10. Akceptační kritéria

  1. Po pip install + python main.py aplikace běží, tray ikona viditelná, hlavní okno otevřené (po login).
  2. Křížek hl. okna → okno zmizí, tray ikona zůstane, sync agent loguje plánované zálohy, chat zprávy přicházejí.
  3. LMB klik na tray → mini widget se otevře vpravo dole, zobrazuje plánovanou zálohu (čas) a "AVAX aplikace" tlačítko.
  4. Klik mimo widget → widget zmizí (tray zůstává).
  5. Spuštění sync (manuálně i plánovaně) s 60+ soubory → widget se objeví s progress barem, zmizí po dokončení (s 20s pop-up oknem ak selhalo).
  6. Příchozí chat zpráva, když je hl. okno zavřené → widget toastem 6 s. Klik na řádek → ChatWindow té konverzace.
  7. Restart PC s auto_login_enabled=True + zavřeným hl. oknem před shutdown → po loginu se objeví widget na 20 s, pak zmizí.
  8. Restart PC se stejným nastavením, ale s viditelným hl. oknem před shutdown → po loginu se otevře hl. okno (widget se neukáže).
  9. Tray menu → Odhlásit → widget zmizí, otevře se LoginScreen.
  10. Tray menu → Ukončit → proces končí, tray ikona zmizí.