diff --git a/dzgui/api/mods.py b/dzgui/api/mods.py index 30eb2f6..4ff646b 100644 --- a/dzgui/api/mods.py +++ b/dzgui/api/mods.py @@ -49,7 +49,6 @@ def get_local_mods(workshop_path: Path) -> list[Path]: return mods -# TODO: TEST: mock bad meta files with fixtures and skip them def parse_meta(file: Path) -> ModMeta: mod = file / "meta.cpp" if mod.exists() is False: @@ -163,6 +162,10 @@ def find_stale_mods(config: Path) -> list[int]: def get_mod_dir_size(path: Path) -> int: + """ + This is not a guarantee of parity, as user may adjusted contents of local mods, + so upstream epoch time is still used + """ size = 0 for i in Path(path).iterdir(): if i.is_file(): diff --git a/dzgui/api/servers.py b/dzgui/api/servers.py index 6c5f6e0..b3eebd1 100644 --- a/dzgui/api/servers.py +++ b/dzgui/api/servers.py @@ -276,7 +276,7 @@ def query_direct(ip: str, qport: int, timeout: float = 3.0) -> dict[str, Any] | info = a2s.info((ip, qport), timeout) return source_info_to_dict(ip, qport, info) except Exception as e: - logger.critical(e) + logger.warning(e) return None diff --git a/dzgui/config/userprefs.py b/dzgui/config/userprefs.py index c28b63c..b0d7c4a 100644 --- a/dzgui/config/userprefs.py +++ b/dzgui/config/userprefs.py @@ -1,6 +1,9 @@ from dataclasses import dataclass -from dzgui.config.xdg import Xdg -from dzgui.util.ip import Coords +from typing import TYPE_CHECKING, Union + +if TYPE_CHECKING: + from dzgui.config.xdg import Xdg + from dzgui.util.ip import Coords # NOTE: mutable dataclass, 'use_miles' key changes on demand @@ -9,8 +12,9 @@ class UserPrefs: is_steam_deck: bool is_game_mode: bool is_debug: bool - coords: Coords | None + coords: Union["Coords", None] version: str allow_updates: bool - paths: Xdg + paths: "Xdg" use_miles: bool + foreground_cmd: str | None diff --git a/dzgui/const/constants.py b/dzgui/const/constants.py index c50f0fe..5cbda52 100644 --- a/dzgui/const/constants.py +++ b/dzgui/const/constants.py @@ -65,3 +65,4 @@ CHANGELOG_PATH = "data/CHANGELOG.md" CSS_PATH = "data/app.css" LOG_FILTERS = ("CRITICAL", "WARNING", "INFO", "DEBUG") +FOREGROUND_CMDS = ("wmctrlu", "xdotool") diff --git a/dzgui/controllers/mc.py b/dzgui/controllers/mc.py index 2bc6b32..5cb5d9a 100644 --- a/dzgui/controllers/mc.py +++ b/dzgui/controllers/mc.py @@ -470,8 +470,8 @@ class Controller(GObject.GObject): ind = self.config_man.get_start_tab() self.get_servers().notebook.set_current_page(ind) - def update_and_connect(self) -> None: - self.connection_man.update_and_connect() + def update_and_connect(self, raise_window: bool) -> None: + self.connection_man.update_and_connect(raise_window) def update_status(self, mod: str, mark_finished: bool = False) -> None: self.mediator.preconnect.update_mod(mod, mark_finished) diff --git a/dzgui/init/proc.py b/dzgui/init/proc.py index a50be49..e23c80d 100644 --- a/dzgui/init/proc.py +++ b/dzgui/init/proc.py @@ -1,10 +1,14 @@ import psutil +import subprocess +import shutil import sys from dzgui.const.constants import DAYZ_BINARY, STEAM_CMD from dzgui.views.dialogs.early_alert import EarlyAlertDialog from dzgui.util.strings import init +# TODO: move to util.proc + # TODO: simplify def is_dayz_running(dialog: bool = False) -> bool | None: @@ -29,3 +33,18 @@ def is_running(proc: str) -> bool: if process.info["name"] == proc: return True return False + + +def has_cmd(cmd: str) -> bool: + if shutil.which(cmd) is not None: + return True + return False + + +def foreground(cmd: str, pid: int) -> None: + if cmd == "wmctrl": + # TODO: convert wid from hexadecimal + args = [cmd, "-a", "DZGUI"] + elif cmd == "xdotool": + args = [cmd, "search", "--onlyvisible", "--name", "DZGUI", "windowactivate"] + subprocess.Popen([*args]) diff --git a/dzgui/main.py b/dzgui/main.py index da6ead1..47278df 100644 --- a/dzgui/main.py +++ b/dzgui/main.py @@ -7,7 +7,7 @@ import warnings from typing import TYPE_CHECKING from dzgui.api.mods import remove_stale_signatures -from dzgui.const.constants import APP_NAME +from dzgui.const.constants import APP_NAME, FOREGROUND_CMDS from dzgui.const.enum import Preferences from dzgui.config.ipdb import get_ipdb from dzgui.config.query import lookup @@ -25,7 +25,7 @@ from dzgui.init.migrate import ( ) from dzgui.init.prefix import get_version from dzgui.init.prereqs import has_steam_client -from dzgui.init.proc import is_dayz_running, is_steam_running +from dzgui.init.proc import has_cmd, is_dayz_running, is_steam_running from dzgui.init.update import allow_updates, check_updates from dzgui.util.map_count import get_map_count @@ -142,15 +142,29 @@ def main() -> None: local_coords = get_local_coords(XDG.ips) use_miles = lookup(XDG.config, Preferences.DIST) + tool = None + for cmd in FOREGROUND_CMDS: + if has_cmd(cmd) is True: + tool = cmd + break + + print(os.getpid()) + # TODO: try getting pid on demand later + # pid = os.getpid() + + # TODO: push is_game_mode to preconnect dialog prefs = UserPrefs( - _is_steam_deck, - _is_game_mode, - args.debug, - local_coords, - version, - allow, - XDG, - use_miles, + is_steam_deck=_is_steam_deck, + is_game_mode=_is_game_mode, + is_debug=args.debug, + coords=local_coords, + version=version, + allow_updates=allow, + paths=XDG, + use_miles=use_miles, + foreground_cmd=tool, ) + # TODO: drop allow updates + # TODO: strings print("All OK. Loading UI...") App(prefs) diff --git a/dzgui/managers/connection.py b/dzgui/managers/connection.py index b66c730..ee72e17 100644 --- a/dzgui/managers/connection.py +++ b/dzgui/managers/connection.py @@ -1,4 +1,5 @@ import logging +import os import shutil import time @@ -26,7 +27,7 @@ from dzgui.const.constants import ( APPNAME_DAYZ_EXP, ) from dzgui.const.enum import Preferences -from dzgui.init.proc import is_dayz_running, is_steam_running +from dzgui.init.proc import foreground, is_dayz_running, is_steam_running from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager from dzgui.util.format import format_mib from dzgui.util.strings import dialog, server_timeout, checkmark @@ -60,6 +61,7 @@ class Prerequisites: dayz_running: bool steam_running: bool mods: list[str] + foreground_cmd: str | None class ConnectionManager: @@ -141,6 +143,8 @@ class ConnectionManager: dayz_running = is_dayz_running() steam_running = is_steam_running() + self.foreground_cmd = self.controller.get_prefs().foreground_cmd + prereqs = Prerequisites( name=info.server_name, appid=info.game_id, @@ -154,6 +158,7 @@ class ConnectionManager: dayz_running=dayz_running, steam_running=steam_running, mods=remote_mods, + foreground_cmd=self.foreground_cmd, # TODO: change to bool ) func = StoredFunc(self.controller.open_connection_assistant, prereqs) @@ -227,7 +232,7 @@ class ConnectionManager: # TODO: add to history file and list store @call_on_thread("Waiting for Steam to update mods") - def update_mods(self) -> None: + def update_mods(self, raise_window: bool) -> None: # NOTE: fast enqueue all mods in auto mode for title, mod, stamp, size in self.missing_mods: # TODO: boolean cancel button on threadman dialog sets event inside threadman @@ -235,6 +240,10 @@ class ConnectionManager: enqueue_mod(mod, self.appid) time.sleep(2) + if self.foreground_cmd is not None and raise_window is True: + pid = os.getpid() + foreground(self.foreground_cmd, pid) + for title, mod, stamp, size in self.missing_mods: mod_path = self.workshop / mod @@ -258,8 +267,8 @@ class ConnectionManager: ) self.thread_man.set_cleanup_func(func) - def update_and_connect(self) -> None: + def update_and_connect(self, raise_window: bool) -> None: if len(self.missing_mods) > 0: - self.update_mods() + self.update_mods(raise_window) else: self.connect_steam() diff --git a/dzgui/views/dialogs/note.py b/dzgui/views/dialogs/note.py index bda0b75..04f9f42 100644 --- a/dzgui/views/dialogs/note.py +++ b/dzgui/views/dialogs/note.py @@ -43,7 +43,7 @@ class NoteDialog(GenericDialog): self.entry.set_text(note) if len(note) > 0: delete_button.set_sensitive(True) - self.format_secondary_text(f"Current note: {note}") + self.format_secondary_text(f"Current note: '{note}'") elif len(note) == 0: self.ok.set_sensitive(False) diff --git a/dzgui/views/pages/preconnect.py b/dzgui/views/pages/preconnect.py index c0a8e36..f8dee6e 100644 --- a/dzgui/views/pages/preconnect.py +++ b/dzgui/views/pages/preconnect.py @@ -115,6 +115,7 @@ class PreConnectionAssistant(Gtk.ScrolledWindow): has_tooltip=True, sensitive=False, tooltip_text="This option is available if wmctrl or xdotool is installed on the system", + active=True, ) self.button_box = Gtk.Box( orientation=Gtk.Orientation.VERTICAL, @@ -142,20 +143,18 @@ class PreConnectionAssistant(Gtk.ScrolledWindow): margin_top=10, margin_bottom=10, ) - self.spinner = Gtk.Spinner() self.progress_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=15) self.progress_box.add(self.mod_count) - self.progress_box.add(self.spinner) - self.cancel = Gtk.Button( - label=preconnect.cancel, - halign=Gtk.Align.START, - hexpand=True, - margin_bottom=5, - margin_top=5, - ) - self.progress_box.add(self.cancel) - self.cancel.connect("clicked", self._on_cancel_clicked) - self.cancel.set_visible(False) + # self.cancel = Gtk.Button( + # label=preconnect.cancel, + # halign=Gtk.Align.START, + # hexpand=True, + # margin_bottom=5, + # margin_top=5, + # ) + # self.progress_box.add(self.cancel) + # self.cancel.connect("clicked", self._on_cancel_clicked) + # self.cancel.set_visible(False) # TODO: live count of remaining downloads # "Steam is downloading: {mod_name}" @@ -219,8 +218,7 @@ class PreConnectionAssistant(Gtk.ScrolledWindow): # TODO: enable button if wmctrl or xdotool is available # TODO: check this at boot time and pass in via connection manager - - self.cancel.set_visible(False) + self.raise_window.set_sensitive(False) self.ok.set_sensitive(True) self.ok.set_label(preconnect.update_mods) @@ -228,8 +226,8 @@ class PreConnectionAssistant(Gtk.ScrolledWindow): if event.keyval == Gdk.KEY_Escape: self.back.emit("clicked") - def _on_cancel_clicked(self, button: Gtk.Button) -> None: - print("user canceled") + # def _on_cancel_clicked(self, button: Gtk.Button) -> None: + # print("user canceled") def _on_ok_clicked(self, button: Gtk.Button) -> None: # TODO: cancel mod downloads @@ -237,11 +235,10 @@ class PreConnectionAssistant(Gtk.ScrolledWindow): if self.mod_count.get_visible(): # TODO: set ready mode # TODO: strings - self.mod_count.set_label("Enqueuing downloads") - self.spinner.start() - self.cancel.set_visible(True) + # self.mod_count.set_label("Enqueuing downloads") + # self.cancel.set_visible(True) self.ok.set_label(preconnect.connect) - self.controller.update_and_connect() + self.controller.update_and_connect(self.raise_window.get_active()) pass def _on_back_clicked(self, button: Gtk.Button) -> None: @@ -295,6 +292,9 @@ class PreConnectionAssistant(Gtk.ScrolledWindow): name = prereqs.name self.title.set_text(name) + if prereqs.foreground_cmd is not None: + self.raise_window.set_sensitive(True) + if total_mods < 1: self.scrolled.set_visible(False) self.progress_box.set_visible(False) @@ -318,9 +318,8 @@ class PreConnectionAssistant(Gtk.ScrolledWindow): def update_mod(self, text: str, mark_finished: bool = False) -> None: self.mod_count.set_label(text) - if mark_finished: - self.cancel.set_visible(False) - self.spinner.stop() + # if mark_finished: + # self.cancel.set_visible(False) def add_errors(self, errors: list[str]) -> None: self.error_tree.extend(errors)