diff --git a/dzgui/app_init.py b/dzgui/app_init.py index 45df2a7..1a942ab 100644 --- a/dzgui/app_init.py +++ b/dzgui/app_init.py @@ -121,7 +121,7 @@ def load_steam_icon() -> None: logger.critical(e) -def load_gui(version: str, is_debug: bool) -> None: +def build_gui(version: str, is_debug: bool) -> App | None: lock = lock_acquire() # noqa set_locale() @@ -151,7 +151,7 @@ def load_gui(version: str, is_debug: bool) -> None: wizard = SetupWizard(_is_steam_deck, XDG, xdg_paths) if not wizard.is_setup_complete(): - return + return None setup_logger(XDG.debug) with open(XDG.debug, "w") as f: @@ -186,4 +186,4 @@ def load_gui(version: str, is_debug: bool) -> None: print(boot.all_ok) load_steam_icon() app = App(prefs) - app.run(None) + return app diff --git a/dzgui/main.py b/dzgui/main.py index 68ed0b2..5e660cf 100644 --- a/dzgui/main.py +++ b/dzgui/main.py @@ -42,6 +42,9 @@ def main() -> None: test_libgi_missing() # NOTE: only import ligbirepository modules after the above check - from dzgui.app_init import load_gui + from dzgui.app_init import build_gui - load_gui(version, args.debug) + app = build_gui(version, args.debug) + if app is None: + sys.exit(0) + app.run(None) diff --git a/dzgui/util/save_screenshot.py b/dzgui/util/save_screenshot.py new file mode 100644 index 0000000..8c106bc --- /dev/null +++ b/dzgui/util/save_screenshot.py @@ -0,0 +1,80 @@ +from pathlib import Path +from typing import cast, TYPE_CHECKING + +import gi + +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk, Gdk, GLib # noqa E402 + +if TYPE_CHECKING: + from dzgui.controllers.mc import Controller + from dzgui.views.base import OuterWindow + from dzgui.views.mixins.context_mixin import ContextMixin + from gi.repository import GdkPixbuf + +# TODO: randomize/mock PII distance values + + +class IdleScreenshot: + def __init__( + self, controller: "Controller", window: "OuterWindow", scrot_path: Path + ) -> None: + self.window = window + self.controller = controller + self.scrot_path = scrot_path + + clock = window.get_frame_clock() + if clock is None: + raise TypeError("Widget not realized, FrameClock not initialized") + clock.connect("after-paint", self.callback) + + def callback(self, clock: Gdk.FrameClock) -> None: + GLib.timeout_add(1000, self.check_idle) + + def check_idle(self) -> bool: + tv = self.controller.get_active_treeview() + loaded = tv.is_loaded() + if loaded is True: + self.emit_key_press(tv) + GLib.timeout_add(1000, self.save_and_quit) + return True + return False + + def emit_key_press(self, widget: "ContextMixin") -> None: + window = widget.get_parent_window() + if window is None: + raise TypeError(f"Failed to find a valid parent window for {widget}") + event = Gdk.EventKey.new(Gdk.EventType.KEY_PRESS) + # NOTE: GTK introspection returns Gdk.Event type + event_key = cast(Gdk.EventKey, event) + event_key.keyval = Gdk.KEY_l + event_key.state = Gdk.ModifierType.CONTROL_MASK + event_key.window = window + event_key.time = Gdk.CURRENT_TIME + event_key.send_event = True + # TODO: suppress errors from synthetic key events + widget.present_menu(widget, event) + + def save_and_quit(self) -> None: + self.save_pixbuf() + self.controller.save_res_and_quit() + + def save_pixbuf(self) -> None: + pixbuf = self.capture_application() + if pixbuf: + pixbuf.savev(str(self.scrot_path), "png", [], []) + + def capture_application(self) -> "GdkPixbuf.Pixbuf": + # NOTE: composited context menu over X11 window + root = Gdk.get_default_root_window() + + xwindow = self.window.get_window() + if xwindow is None: + raise ValueError("Failed to get a valid X11 window") + index, x, y = xwindow.get_origin() + width, height = self.window.get_size() + + application = Gdk.pixbuf_get_from_window(root, x, y, width, height) + if application is None: + raise ValueError("Failed to get a valid GdkPixbuf") + return application diff --git a/dzgui/views/base.py b/dzgui/views/base.py index cbbb3b5..79fde50 100644 --- a/dzgui/views/base.py +++ b/dzgui/views/base.py @@ -1,7 +1,7 @@ import logging import signal -from typing import TYPE_CHECKING, Literal, Self +from typing import Any, Literal, Self, TYPE_CHECKING from dzgui.const.constants import ( APP_NAME, @@ -47,7 +47,7 @@ logger = logging.getLogger(APP_NAME) class OuterWindow(Gtk.ApplicationWindow): - def __init__(self, app: Gtk.Application) -> None: + def __init__(self, app: "App") -> None: super().__init__( application=app, title=APP_NAME, border_width=10, icon_name=APP_NAME_LOWER ) @@ -78,6 +78,15 @@ class OuterWindow(Gtk.ApplicationWindow): MainController.loaded = True MainController.populate_model(MainController.get_active_treeview()) + try: + from dzgui.util.save_screenshot import IdleScreenshot + + scrot = app.get_automation_param("screenshot_path") + self.fullscreen() + IdleScreenshot(MainController, self, scrot) + except KeyError: + pass + def _on_delete_event(self, window: "OuterWindow", event: Gdk.EventKey) -> None: self.halt_proc_and_quit() @@ -291,6 +300,14 @@ class App(ColorAwareApp, Gtk.Application): # type: ignore self._setup_signals() self.connect("activate", self._on_activate) + self.automation_params: dict[str, Any] = {} + + def get_automation_param(self, key: str) -> Any: + return self.automation_params[key] + + def set_automation_param(self, key: str, value: Any) -> None: + self.automation_params[key] = value + def _on_activate(self, app: Self) -> None: self.win = OuterWindow(app) @@ -331,4 +348,5 @@ class App(ColorAwareApp, Gtk.Application): # type: ignore ) -> None: self.win.halt_proc_and_quit() + MainController = Controller() diff --git a/dzgui/views/mixins/context_mixin.py b/dzgui/views/mixins/context_mixin.py index 358c65a..5befa2c 100644 --- a/dzgui/views/mixins/context_mixin.py +++ b/dzgui/views/mixins/context_mixin.py @@ -16,9 +16,7 @@ logger = logging.getLogger(APP_NAME) class ContextMixin(TreeView): def present_menu( - self, - widget: Gtk.Widget, - event: Gdk.EventButton | Gdk.EventKey, + self, widget: Gtk.Widget, event: Gdk.Event ) -> bool: if self.menu is None: diff --git a/scripts/parse_toml.py b/scripts/parse_toml.py index 88d53cf..dc0afe0 100644 --- a/scripts/parse_toml.py +++ b/scripts/parse_toml.py @@ -3,6 +3,7 @@ from pathlib import Path root = Path(__file__).resolve().parents[1] + def get_entrypoint() -> None: p = root.joinpath("pyproject.toml") with open(p, "rb") as f: