sevaht_gui.tkapp¶
A tkinter application base that owns the main thread (Option A).
tkinter is not thread-safe: every widget call must happen on the one thread
that created the root, and on macOS/Windows that loop is happiest on the main
thread. So TkApp runs root.mainloop() on the thread that
constructed it (the main thread) and runs a cross-platform tray icon – which
brings its own event loop – on a worker thread instead.
Work originating off the UI thread (e.g. a tray menu callback, fired on the
tray worker thread) must be marshalled back with run_on_ui_thread() /
call_on_ui_thread(). A periodic poll drains that queue and, as a side
effect, gives the interpreter a chance to run signal handlers while the C-level
mainloop is blocked.
- class sevaht_gui.tkapp.WindowPlacement(position: tuple[int, int] | None = None)[source]¶
Bases:
objectRemembers a toplevel’s on-screen position across hide/recreate cycles.
Call
remember()before hiding or destroying a window andapply()after showing or re-creating it, so it reopens where it was left.positionis plain(x, y)data you can store in config and restore via the constructor; nothing is persisted automatically.TkAppuses one of these for its main window; create your own for any additional toplevels you want to behave the same way.
- class sevaht_gui.tkapp.TkApp(*, theme: str | None = 'clam', checkmark_indicator: bool = True, quit_confirm: str | None = 'Are you sure you want to quit?', quit_confirm_title: str = 'Quit', center_on_show: bool = True, window_position: tuple[int, int] | None = None, poll_interval_ms: int = 25)[source]¶
Bases:
objectOwns a
tkinter.Tkroot and runs its loop on the main thread.Construct this on the main thread, build your widgets on
self.root, then callrun()(optionally passing a tray icon to run alongside it).Sensible defaults (each overridable via the constructor / method args):
a ttk theme with proper checkbox indicators;
the window is centered on the primary monitor the first time it is shown (
center_on_show), then re-opens where it was last positioned;quit()asks for confirmation (quit_confirm);create_tray_icon()also sets the window’s title-bar icon to match the tray icon (window_icon);once a tray icon exists, the window-close button hides the window (the tray houses it); with no tray it quits instead;
notify()shows a desktop notification via the tray when present and a standalone notifier otherwise (so it works with or without a tray).
To persist the window position across runs, read
window_positionwhen saving config and pass it back aswindow_positionnext launch; the library never writes it to disk itself.- property window_position: tuple[int, int] | None¶
The window’s
(x, y)– current if on screen, else last known.Save this in your config and pass it back as
window_positionto re-open the window where it was left between runs.
- run_on_ui_thread(callback: Callable[[], object]) None[source]¶
Schedule
callbackto run on the UI thread (fire and forget).
- call_on_ui_thread(callback: Callable[[], object], *, timeout: float = 5.0) object[source]¶
Run
callbackon the UI thread and return its result.Returns
Noneif the app is shutting down or the call times out.
- set_window_icon(icon: IconSource) None[source]¶
Set (or replace) the window/title-bar icon from any IconSource.
Accepts a prepared PIL image, an image file path, or a renderer; call it again to change the icon (e.g. to reflect state).
- set_tray_icon(icon: IconSource) None[source]¶
Set (or replace) the tray icon’s image, if a tray icon exists.
- set_app_icon(icon: IconSource) None[source]¶
Set both the window icon and the tray icon (if any) at once.
A convenience for keeping them in sync. To control them independently – or use different images – call
set_window_icon()andset_tray_icon()separately instead.
- track_window_icon(window: Tk | Toplevel) None[source]¶
Keep
window’s title-bar icon in sync withset_window_icon().Applies the current icon immediately and updates it whenever the icon changes, until
windowis destroyed. Useful for dialogs/toplevels that should mirror the main window’s (possibly state-dependent) icon.
- create_tray_icon(name: str, title: str, icon: IconSource, *, on_activate: Callable[[], None] | None = None, on_quit: Callable[[], None] | None = None, activate_label: str = 'Open', quit_label: str = 'Quit', window_icon: bool = True) TrayIcon | None[source]¶
Create a tray icon bound to this app, or
Noneif no tray exists.When no system tray is available the app keeps working without one (the window close button then quits instead of hiding).
on_activatedefaults toshow()andon_quittoquit().window_icon(default) also sets the window’s title-bar icon toiconso the two match; passFalseto manage the window icon separately. Pass the result torun().
- notify(title: str, message: str, *, icon: str | None = None) None[source]¶
Show a desktop notification.
Uses the tray icon’s native notification when a tray exists; otherwise falls back to a standalone notifier (notify-send/dbus-send/console) so notifications still work without a tray – callers need not handle that case themselves.
iconis a freedesktop icon name where supported.
- confirm(message: str, *, title: str = 'Confirm') bool[source]¶
Show a modal Yes/No dialog; return True if confirmed. Thread-safe.
- primary_monitor_bounds() tuple[int, int, int, int][source]¶
Return the primary monitor’s (x, y, width, height).
Queries X11 RandR via python-xlib (works under X11 and XWayland) and falls back to the full Tk screen size off-X11.
- center_window(window: Tk | Toplevel) None[source]¶
Center
windowon the main window (if visible) else the primary monitor. Works for the root or any toplevel (e.g. your own dialogs).The window is positioned while withdrawn and its prior shown/hidden state is restored, so it never flashes at the window manager’s default location first –
update_idletaskswould otherwise map a visible (or just-created) window before the geometry is applied. This makes the flicker impossible to reintroduce at the call site.
- run(tray_icon: TrayIcon | None = None, *, tray_setup: Callable[[], None] | None = None, start_hidden: bool = False, handle_signals: bool = True) None[source]¶
Run the UI loop on the current (main) thread until
stop().If
tray_iconis given, its event loop runs on a worker thread for the lifetime of the app and is stopped when the UI loop exits.tray_setupis passed through totray_icon.run(setup=...)and is therefore invoked on the tray’s own thread, once the tray’s event loop is running. Use it for tray work that cannot happen before the loop starts – e.g. setting the initial icon image on Windows, where the HICON cannot be updated until pystray’s loop is live. UI work triggered from here must still be marshalled withrun_on_ui_thread().If
start_hiddenis true the window starts withdrawn (only the tray icon is visible); callshow()to reveal it. It is ignored when there is notray_icon, since then nothing could restore the window.