Compare commits

..
Author SHA1 Message Date
aclist 0c4026daf8 chore: clear typehinting errors 2026-11-10 10:02:41 +09:00
aclist e03c6905c6 chore: clear typehinting errors 2026-11-10 06:30:12 +09:00
aclist 8fb3462f35 chore: add placeholder for row struct 2026-11-10 05:58:22 +09:00
aclist bfe9b9d147 chore: clear typehinting errors 2026-11-10 05:58:02 +09:00
aclist 9f36569fd4 drop: LoggerAlertsButton 2026-11-10 05:10:56 +09:00
aclist da18b41136 chore: drop Soup2 stubs 2026-11-10 05:09:02 +09:00
aclist f092207c61 chore: clear strict typehinting errors 2026-11-10 04:37:51 +09:00
aclist 15d4b71350 Merge pull request #533 from aclist/docs/more-attribution
Mirror to Codeberg / mirror-to-codeberg (push) Waiting to run
chore: add remarks to README
2026-11-10 03:08:12 +09:00
aclist 16d3df618a chore: add remarks to README 2026-11-10 03:07:30 +09:00
aclist 22dd8fc67f Merge pull request #525 from aclist/change/get-git-root
change: get git root
2026-11-10 02:44:51 +09:00
aclist 03fa707305 chore: remove unused imports 2026-11-09 11:04:56 +09:00
aclist 2479cbd41b change: parse repository root 2026-11-09 11:03:50 +09:00
51 changed files with 172 additions and 237 deletions

No files matched your search

+2
View File
@@ -37,6 +37,8 @@ Users wishing to review the licenses to these components can inspect the `LICENS
Steam and the Steam logo are trademarks and/or registered trademarks of Valve Corporation in the U.S. and/or other countries.
DayZ is a trademark and brand of Bohemia Interactive a.s.
## Disclaimer
DZGUI is beta-quality software and is provided as-is.
+2 -1
View File
@@ -111,7 +111,8 @@ class WorkshopACF(ACF):
def __init__(self, file: Path) -> None:
super().__init__(file)
def unpack(self, d: dict, lines: list[Any] = []) -> str:
def unpack(self, d: dict[str, Any], lines: list[Any] = []) -> str:
self.indent: int
t1 = "AppWorkshop"
t2 = ("WorkshopItemsInstalled", "WorkshopItemDetails")
for k, v in d.items():
+5 -10
View File
@@ -2,10 +2,10 @@ import hashlib
import logging
import re
from concurrent.futures import ThreadPoolExecutor, wait
from concurrent.futures import wait
from concurrent.futures import ThreadPoolExecutor
from dataclasses import dataclass
from pathlib import Path
from typing import Any
from dzgui.api.steam import get_app_path
from dzgui.api.servers import get_rules, fqip_to_record
@@ -18,7 +18,8 @@ from dzgui.const.constants import (
from dzgui.config.query import lookup
from dzgui.const.enum import Preferences
from dzgui.init.migrate import clean_legacy_versions_file
from typing import Any
logger = logging.getLogger(APP_NAME)
@@ -113,10 +114,6 @@ def get_delimited_mods(steam_path: Path) -> list[Any]:
return parse_mods(mods)
def get_missing_mods(local: list, remote: list) -> list:
return [mod for mod in remote if mod not in local]
def _hash(uid: str, use_custom: bool = False) -> str:
md5 = hashlib.md5()
md5.update(uid.encode("ascii"))
@@ -132,8 +129,6 @@ def remove_stale_signatures(
versions.touch()
return
clean_legacy_versions_file(versions)
path = lookup(config, Preferences.DEFAULT)
steam_path = Path(path)
if ids is None:
@@ -153,7 +148,7 @@ def remove_stale_signatures(
def find_stale_mods(config: Path) -> list[int]:
def push_record(rec: str) -> list:
def push_record(rec: str) -> list[int]:
record = fqip_to_record(rec)
if record is None:
return []
+6 -4
View File
@@ -111,7 +111,7 @@ def get_netmask() -> str:
# TODO: rename
def test_ip(suffix: int, port: int, event: threading.Event) -> dict | None:
def test_ip(suffix: int, port: int, event: threading.Event) -> dict[str, Any] | None:
if event.is_set():
return None
netmask = get_netmask()
@@ -139,7 +139,9 @@ def sanitize(name: str) -> str:
return name
def parse_json(json: list) -> list:
def parse_json(
json: list[dict[str, Any]],
) -> list[tuple[str, str, str, str, int, int, int, str, int, int, str, bool]]:
"""
Server metadata is underspecified and server operators
tend to insert random garbage in the headers. In case
@@ -507,14 +509,14 @@ def record_to_fqip(record: Record) -> str:
return f"{record.ip}:{record.gameport}:{record.qport}"
def response_to_fqip(res: dict) -> str:
def response_to_fqip(res: dict[str, Any]) -> str:
ip = res["addr"].split(":")[0]
gameport = res["gameport"]
qport = res["addr"].split(":")[1]
return f"{ip}:{gameport}:{qport}"
def response_to_record(res: dict) -> Record:
def response_to_record(res: dict[str, Any]) -> Record:
ip = res["addr"].split(":")[0]
gameport = res["gameport"]
qport = res["addr"].split(":")[1]
+1 -1
View File
@@ -18,8 +18,8 @@ from dzgui.config.query import lookup
from dzgui.config.userprefs import UserPrefs
from dzgui.config.xdg import get_xdg_paths, parse_filepaths
from dzgui.init.migrate import (
copy_state_files,
has_new_config,
copy_state_files,
migrate_cols_file,
)
from dzgui.init.flock import lock_acquire
+1 -2
View File
@@ -6,8 +6,7 @@ if TYPE_CHECKING:
from dzgui.util.ip import Coords
# NOTE: mutable dataclass: 'use_miles' key changes on demand
# 'frozen' cannot be True
# NOTE: mutable dataclass, 'use_miles' key changes on demand
@dataclass(slots=True)
class UserPrefs:
is_steam_deck: bool
+2 -2
View File
@@ -51,7 +51,7 @@ def is_writeable(path_str: str) -> bool:
return True
def get_xdg_paths() -> dict:
def get_xdg_paths() -> dict[str, Path]:
home = Path.home()
xdg_paths = {
"XDG_CONFIG_HOME": home / ".config",
@@ -73,7 +73,7 @@ def get_xdg_paths() -> dict:
return resolved_paths
def parse_filepaths(xdg: dict) -> Xdg:
def parse_filepaths(xdg: dict[str, Path]) -> Xdg:
config = xdg["XDG_CONFIG_HOME"]
state = xdg["XDG_STATE_HOME"]
share = xdg["XDG_DATA_HOME"]
+6 -6
View File
@@ -77,7 +77,7 @@ class AppNavigation:
class Controller(GObject.GObject):
def __init__(self) -> None:
self.dist_cache: dict[str, "Haversine"] = {}
self.dist_cache: dict[str, "Haversine" | None] = {}
self.mediator = AppNavigation()
self.prefs: UserPrefs
@@ -274,7 +274,7 @@ class Controller(GObject.GObject):
except Exception as e:
dialog = ExceptionDialog(self, str(e))
dialog.show_all()
dialog.run()
dialog.run() # type: ignore[no-untyped-call]
def select_colorized(self) -> None:
mod_man = self.mediator.modtreeview.get_mod_man()
@@ -308,7 +308,7 @@ class Controller(GObject.GObject):
except Exception as e:
dialog = ExceptionDialog(self, str(e))
dialog.show_all()
dialog.run()
dialog.run() # type: ignore[no-untyped-call]
def update_steam_api_key(self, text: str) -> None:
self.config_man.update_steam_api_key(text)
@@ -355,17 +355,17 @@ class Controller(GObject.GObject):
filter_man.set_filter(filt, state)
self.mediator.filters.set_filters(filters)
def get_dist_cache(self) -> dict[str, "Haversine"]:
def get_dist_cache(self) -> dict[str, Union["Haversine", None]]:
return self.dist_cache
def set_dist_cache(self, addr: str, haversine: "Haversine") -> None:
def set_dist_cache(self, addr: str, haversine: Union["Haversine", None]) -> None:
self.dist_cache[addr] = haversine
def get_map_store(self) -> Gtk.ListStore:
filter_man = self.get_filter_man()
return filter_man.get_map_store()
def get_enabled_filters(self) -> dict:
def get_enabled_filters(self) -> dict[str, bool]:
filter_man = self.get_filter_man()
return filter_man.get_filters()
+1 -22
View File
@@ -1,21 +1,12 @@
import json
import logging
import re
import shutil
from pathlib import Path
from typing import Any
from dzgui.const.constants import (
APP_NAME,
LEGACY_CONFIG_PATH,
LEGACY_COLS_PATH,
LEGACY_IPS_PATH,
)
from dzgui.const.constants import LEGACY_CONFIG_PATH, LEGACY_COLS_PATH, LEGACY_IPS_PATH
from dzgui.config.convert import rc2json
from dzgui.util._json import read_json, write_json
logger = logging.getLogger(APP_NAME)
def migrate_legacy_conf(config: Path) -> Any:
old_conf = Path.home() / LEGACY_CONFIG_PATH
@@ -79,15 +70,3 @@ def copy_ipdb(ips_path: Path) -> None:
return
# TODO: ensure that month file is copied
shutil.copy(legacy, ips_path)
def clean_legacy_versions_file(versions: Path) -> None:
# NOTE: handling for #523
try:
pat = r"^(\d+),(\d+)$"
lines = versions.read_text().splitlines()
clean = [line for line in lines if re.match(pat, line)]
concat = "\n".join(clean)
versions.write_text(concat)
except Exception as e:
logger.warning(e)
+1 -1
View File
@@ -162,7 +162,7 @@ class ConfigManager:
trace = traceback.format_exc()
dialog = ExceptionDialog(self.controller, trace)
dialog.show_all()
dialog.run()
dialog.run() # type: ignore[no-untyped-call]
def save_res_and_quit(self, tv: "ServerTreeView", window: "OuterWindow") -> None:
columns = tv.get_columns()
+4 -4
View File
@@ -298,22 +298,22 @@ class ConnectionManager:
def _present_modlist_dialog(self, mods: list[list[str]]) -> None:
dialog = ServerModDialog(self.controller, mods)
dialog.run()
dialog.run() # type: ignore[no-untyped-call]
def _present_details_dialog(self, details: Servers.Details) -> None:
dialog = ServerDetailsDialog(self.controller, details)
dialog.run()
dialog.run() # type: ignore[no-untyped-call]
def _workshop_timeout(self, msg: str) -> None:
dialog = ExceptionDialog(self.controller, msg)
dialog.show_all()
dialog.run()
dialog.run() # type: ignore[no-untyped-call]
def _server_timeout(self) -> None:
dialog = ExceptionDialog(self.controller, server_timeout)
dialog.set_details_buffer(kb.DZG_006)
dialog.show_all()
dialog.run()
dialog.run() # type: ignore[no-untyped-call]
def get_debug_args(self) -> str:
addr = f"{self.record.ip}:{self.record.gameport}"
+1 -1
View File
@@ -44,7 +44,7 @@ class ContextMenuManager:
# UNTHREADED
case ContextMenu.ADD_NOTE:
dialog = NoteDialog(self.controller)
dialog.run()
dialog.run() # type: ignore[no-untyped-call]
case ContextMenu.COPY_SERVER_IP:
self.copy_server_ip()
case ContextMenu.COPY_SERVER_NAME:
+3 -4
View File
@@ -51,7 +51,6 @@ class FilterManager:
self.active_map = (0, all_maps)
self.prior_map = all_maps
self.filters: list
self.enabled_filters = dict(self.default_filters)
def get_tooltips(self) -> dict[str, str]:
@@ -75,11 +74,11 @@ class FilterManager:
def set_active_keyword(self, word: str) -> None:
self.active_keyword = word
def get_default_filters(self) -> dict:
def get_default_filters(self) -> dict[str, bool]:
"""Deep copy of defaults"""
return dict(self.default_filters)
def get_filters(self) -> dict:
def get_filters(self) -> dict[str, bool]:
return self.enabled_filters
def set_filter(self, label: str, state: bool) -> None:
@@ -116,7 +115,7 @@ class FilterManager:
def get_unique_maps(self) -> list[str]:
return [row[0] for row in self.map_store if row[0] != "All maps"]
def get_all_filters(self) -> tuple:
def get_all_filters(self) -> tuple[str, ...]:
map_name = self.get_active_map_name()
enabled = self.get_filters()
kw = self.get_active_keyword()
+1 -1
View File
@@ -221,7 +221,7 @@ class ModManager:
total_size += mod[3]
return total_mods, total_size
def _on_stale_mods_found(self, stale_mods: list) -> None:
def _on_stale_mods_found(self, stale_mods: list[int]) -> None:
"""Manipulates attached ListStore in the main event loop"""
model = self.treeview.get_model()
if model is None:
+1 -1
View File
@@ -52,7 +52,7 @@ class OfflineManager:
is_valid = is_mission(folder)
self.emitter.emit("custom_mission_loaded", str(folder), is_valid)
def find_custom_mods(self, callback: Callable) -> None:
def find_custom_mods(self, callback: Callable[[], None]) -> None:
folder = self.open_folderpicker(dialogs.custom_mod_dialog)
window = self.controller.get_window()
window.set_sensitive(False)
+3 -3
View File
@@ -22,8 +22,8 @@ logger = logging.getLogger(APP_NAME)
def call_on_thread(
dialog_str: str, show_dialog: bool = True, show_cancel: bool = False
) -> Callable:
def decorator(func: Callable) -> Callable:
) -> Callable[..., Any]:
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> None:
self = args[0]
@@ -42,7 +42,7 @@ def call_on_thread(
class StoredFunc:
def __init__(self, func: Callable, *args: Any, **kwargs: Any) -> None:
def __init__(self, func: Callable[..., Any], *args: Any, **kwargs: Any) -> None:
sig = inspect.signature(func)
self.func = func
self.bindings = sig.bind(*args, **kwargs)
+2 -2
View File
@@ -60,9 +60,9 @@ class UpdateManager:
def _on_update_success(self) -> None:
msg = dialogs.update_success
dialog = QuitDialog(self.controller, msg)
dialog.run()
dialog.run() # type: ignore[no-untyped-call]
def _on_update_failure(self, msg: str) -> None:
dialog = ExceptionDialog(self.controller, msg)
dialog.show_all()
dialog.run()
dialog.run() # type: ignore[no-untyped-call]
+1 -1
View File
@@ -11,7 +11,7 @@ from gi.repository.Gtk import ListStore, TreeIter # noqa E402
from gi.repository import GObject # noqa E402
GTYPE_TO_PYTHON = {
GObject.type_from_name(GObject.type_name(ptype)): ptype
GObject.type_from_name(GObject.type_name(ptype)): ptype # type: ignore[no-untyped-call]
for ptype in (int, float, str, bool, object)
}
+20 -7
View File
@@ -1,5 +1,6 @@
import re
from dataclasses import dataclass
from typing import Any, TYPE_CHECKING, Union
from dzgui.const.enum import FilterMode
@@ -16,6 +17,22 @@ DAY_REG = r"([0][6-9]|[1][0-7])"
NIGHT_REG = r"([0][0-5]|[1][8-9]|[2][0-3])"
@dataclass(slots=True, frozen=True)
class ServerRow:
name: str
mapname: str
view: str
time: str
players: int
max_players: int
queue: int
ip: str
qport: int
ping: str
provider: str
modded: bool
class ProxyModelManager:
"""
Manages access to cached FastInsertListStore resources and
@@ -30,7 +47,7 @@ class ProxyModelManager:
def __init__(self, filter_man: "FilterManager") -> None:
self.filter_cache: dict[
tuple[str],
tuple[str, ...],
list[tuple[str, str, str, str, int, int, int, str, int, int, str, bool]],
] = {}
@@ -51,9 +68,6 @@ class ProxyModelManager:
return False
return True
def append_row(self, row: list) -> None:
self.proxy_model.append(row)
def append_row_to_control(
self, row: tuple[str, str, str, str, int, int, int, str, int, int, str, bool]
) -> None:
@@ -67,7 +81,6 @@ class ProxyModelManager:
self.proxy_model[treeiter][4] = playercount.players
self.proxy_model[treeiter][6] = playercount.queue
# TODO: use dataclass for record rows
def append_row_to_history(
self,
history: tuple[str, str, str, str, int, int, int, str, int, int, str, bool],
@@ -247,7 +260,7 @@ class ProxyModelManager:
rows = [row for row in rows if row[11]]
return rows
def filter_toggle_on(self, filters: tuple[str]) -> list:
def filter_toggle_on(self, filters: tuple[str, ...]) -> list:
"""Effectively applies all filters"""
self.set_filtered(self.control_model)
self.set_filtered(self.filter_map(filters))
@@ -258,7 +271,7 @@ class ProxyModelManager:
self.set_filtered(self.filter_toggle_off(filters, f))
return self.filtered
def set_cache(self, filters: tuple[str], filtered_rows: list[tuple]) -> None:
def set_cache(self, filters: tuple[str, ...], filtered_rows: list[tuple]) -> None:
self.filter_cache[filters] = filtered_rows
def set_filtered(self, rows: list | None) -> None:
+3 -3
View File
@@ -432,7 +432,7 @@ class ServerModelManager:
if show_dialog:
dialog = ExceptionDialog(self.controller, dialogs.load_error_lan)
dialog.show_all()
dialog.run()
dialog.run() # type: ignore[no-untyped-call]
def _cleanup_on_failure(self, show_dialog: bool = True) -> None:
# TODO: disable map, keyword, and filter widgets if model is None
@@ -451,14 +451,14 @@ class ServerModelManager:
dialog = ExceptionDialog(self.controller, server_timeout)
dialog.set_details_buffer(kb.DZG_006)
dialog.show_all()
dialog.run()
dialog.run() # type: ignore[no-untyped-call]
def _push_data(self, data: list[Any]) -> None:
self._get_proxy_man().push(data)
self._sort_unique_maps(data)
self.thread_man.set_cleanup_func(StoredFunc(self._cleanup_on_success))
def _sort_unique_maps(self, data: list) -> None:
def _sort_unique_maps(self, data: list[Any]) -> None:
u_maps = set([row[1] for row in data])
self._set_new_maps(sorted(u_maps))
+2 -2
View File
@@ -59,7 +59,7 @@ class CalcDist:
self,
addr: str,
enum: "ServerTab",
result_queue: "Queue",
result_queue: "Queue[tuple[str, Haversine | None, ServerTab]]",
controller: "Controller",
cache: dict[str, Haversine],
) -> None:
@@ -72,7 +72,7 @@ class CalcDist:
self.ip = self.addr
dist = self.compare(self.ip)
self.result_queue.put([self.addr, dist, self.enum])
self.result_queue.put((self.addr, dist, self.enum))
def compare(self, remote_ip: str) -> Haversine | None:
prefs = self.controller.get_prefs()
+6 -2
View File
@@ -1,12 +1,16 @@
import logging
import subprocess
from typing import TYPE_CHECKING
from dzgui.const.constants import APP_NAME, FLATPAK_APPID, GTK_APP_ID, TMP_PATH
logger = logging.getLogger(APP_NAME)
if TYPE_CHECKING:
from pathlib import Path
def apply_overrides(xdg: dict[str, str]) -> None:
def apply_overrides(xdg: dict[str, "Path"]) -> None:
try:
# NOTE: D-Bus registration required for sandbox
args = [
@@ -19,7 +23,7 @@ def apply_overrides(xdg: dict[str, str]) -> None:
subprocess.run([*args], capture_output=True)
paths: list[str] = []
paths.extend([*xdg.values()])
paths.extend([str(path) for path in xdg.values()])
paths.append(TMP_PATH)
for path in paths:
+2 -1
View File
@@ -1,4 +1,5 @@
import re
from typing import Any
from dzgui.util.localize import number
from dzgui.strings.generic import selectable_tree
@@ -60,7 +61,7 @@ def format_server_mods(mods: int) -> str:
return f"Found {mods:n} {plural}. {workshop}"
def format_player_count(model: Gtk.TreeModel | None, control: list) -> str:
def format_player_count(model: Gtk.TreeModel | None, control: list[Any]) -> str:
players = 0
hits: int
status: str
+1 -1
View File
@@ -31,7 +31,7 @@ class ReservedIPError(Exception):
pass
def ip2list(ip: str) -> list:
def ip2list(ip: str) -> list[int]:
return [int(digit) for digit in ip.split(".")]
+5 -33
View File
@@ -1,10 +1,8 @@
from typing import Callable, Literal, Self, TYPE_CHECKING, Union
from dzgui.util.clip import copy_clipboard
from dzgui.util.format import pluralize
from dzgui.strings import buttons
from dzgui.util.strings import (
alert_button_tooltip,
atomic_buttons,
)
from dzgui.const.constants import (
@@ -14,7 +12,6 @@ from dzgui.const.constants import (
LIST_ADD,
REFRESH_ICON,
STEAM_ICON,
WARNING,
WEB_BROWSER,
)
from dzgui.strings import connect_panel
@@ -64,7 +61,7 @@ class IconButton(Gtk.Button):
self.icon = Icon(icon, margin_start=margin_start, margin_end=margin_end)
self.set_image(self.icon)
self.set_image_position(position)
self.set_focus_on_click(False)
self.set_focus_on_click(False) # type: ignore[no-untyped-call]
self.set_always_show_image(True)
def swap_icon(self, icon: str) -> None:
@@ -101,15 +98,14 @@ class LargeIconTextButton(IconButton):
class ClipboardButton(IconTextButton):
def __init__(self, controller: Union["Controller", None], func: Callable) -> None:
def __init__(self, func: Callable[[], str]) -> None:
super().__init__(CLIPBOARD, atomic_buttons.copy)
self.controller = controller
self.connect("clicked", self._on_button_clicked, func)
# TODO: strings
self.set_tooltip_text("Copy to clipboard")
def _on_button_clicked(self, button: Self, func: Callable) -> None:
def _on_button_clicked(self, button: Self, func: Callable[[], str]) -> None:
def revert() -> Literal[False]:
self.revert_icon()
return False
@@ -120,10 +116,9 @@ class ClipboardButton(IconTextButton):
GLib.timeout_add(600, revert)
# TODO: determine when controller would be passed to this button or drop
class CopyIpButton(ClipboardButton):
def __init__(self, controller: Union["Controller", None], func: Callable) -> None:
super().__init__(controller, func)
def __init__(self, func: Callable[[], str]) -> None:
super().__init__(func)
# TODO: strings
self.set_tooltip_text("Copy IP to clipboard")
@@ -252,29 +247,6 @@ class AddButton(IconTextButton):
self.set_tooltip_text(connect_panel.add_tooltip)
class LoggerAlertsButton(IconTextButton):
def __init__(self, warnings: int, errors: int) -> None:
warnings_text = ""
errors_text = ""
separator = ""
if warnings > 0:
warnings_plural = pluralize("warnings", warnings)
warnings_text = f"{warnings} {warnings_plural}"
if errors > 0:
errors_plural = pluralize("errors", errors)
errors_text = f" {errors} {errors_plural}"
if warnings + errors > 1:
separator = ","
concat = warnings_text + separator + errors_text
super().__init__(
icon=WARNING,
label=f"Loaded with{concat}",
)
self.set_halign(Gtk.Align.END)
self.set_hexpand(True)
self.set_tooltip_text(alert_button_tooltip)
class SpinnerButton(Gtk.Button):
def __init__(self, label: str) -> None:
super().__init__()
+1 -1
View File
@@ -187,7 +187,7 @@ class FavPanel(Gtk.Frame):
self.fav_button = SteamConnectButton()
self.fav_button.connect("clicked", self._on_connect_clicked)
self.copy_button = CopyIpButton(self.controller, self.get_fav_ip)
self.copy_button = CopyIpButton(self.get_fav_ip)
# TODO: strings
self.unset_button = CloseButton("Unset")
+4 -5
View File
@@ -38,7 +38,7 @@ class ValidatedEntry(Gtk.Entry):
def __init__(
self,
controller: "Controller",
func: Callable,
func: Callable[[str], bool],
placeholder_text: str = "",
tooltip_text: str = "",
) -> None:
@@ -46,7 +46,6 @@ class ValidatedEntry(Gtk.Entry):
hexpand=True, placeholder_text=placeholder_text, tooltip_text=tooltip_text
)
self.func: Callable
self.set_validation_func(func)
self.controller = controller
@@ -72,7 +71,7 @@ class ValidatedEntry(Gtk.Entry):
self.emit("string_validated", False)
remove_class(self, self.classname)
def set_validation_func(self, func: Callable) -> None:
def set_validation_func(self, func: Callable[[str], bool]) -> None:
self.func = func
def insert_icon(self) -> None:
@@ -141,7 +140,7 @@ class APIEntry(Gtk.Box):
def __init__(self) -> None:
super().__init__(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
self.func: Callable | None = None
self.func: Callable[[str], None] | None = None
# TODO: strings
self.entry = Gtk.Entry(
@@ -222,7 +221,7 @@ class APIEntry(Gtk.Box):
res = self.func(text)
return res
def set_validation_func(self, func: Callable | None) -> None:
def set_validation_func(self, func: Callable[[str], None] | None) -> None:
self.func = func
def disable_button(self) -> None:
+7 -3
View File
@@ -30,7 +30,7 @@ class ButtonGrid(Gtk.Grid):
def __init__(
self,
controller: "Controller",
defaults: dict[str, str],
defaults: dict[str, bool],
tooltips: dict[str, str],
) -> None:
super().__init__(
@@ -42,7 +42,10 @@ class ButtonGrid(Gtk.Grid):
self.checks: list[Gtk.CheckButton] = []
flowbox = Gtk.FlowBox(
halign=Gtk.Align.CENTER, min_children_per_line=3, max_children_per_line=3, selection_mode=Gtk.SelectionMode.NONE
halign=Gtk.Align.CENTER,
min_children_per_line=3,
max_children_per_line=3,
selection_mode=Gtk.SelectionMode.NONE,
)
for check in defaults.keys():
@@ -93,7 +96,7 @@ class ButtonGrid(Gtk.Grid):
logger.info(f"User toggled button '{label}' to {state}")
self.emitter.emit("check_toggled", label, state)
def get_checkboxes(self) -> list:
def get_checkboxes(self) -> list[Gtk.CheckButton]:
return self.checks
@@ -305,6 +308,7 @@ class FilterPanel(Gtk.Box):
Gdk.KEY_0: 9,
Gdk.KEY_minus: 10,
Gdk.KEY_backslash: 11,
Gdk.KEY_apostrophe: 12,
}
if keyval not in mappings:
return False
-4
View File
@@ -2,7 +2,6 @@ from typing import Self, Union, TYPE_CHECKING
from dzgui.const.enum import NotebookPage, ServerTab
from dzgui.util.strings import esc_to_return, question_to_return
from dzgui.views.components.buttons import LoggerAlertsButton
import gi
@@ -47,9 +46,6 @@ class Statusbar(Gtk.Grid):
self.emitter.connect("servers_loaded", self._on_servers_loaded)
self.emitter.connect("mods_updated", self._on_mods_updated)
def _on_alerts_clicked(self, button: LoggerAlertsButton) -> None:
self.controller.populate_log()
def _on_mods_updated(self, emitter: "Emitter", msg: str, mods: int) -> None:
self.set_by_context(NotebookPage.MODS, msg)
+1 -1
View File
@@ -105,7 +105,7 @@ class BootDialog(ColorAwareApp, Gtk.Dialog): # type: ignore
self.error_label = Gtk.Label(margin_start=15, margin_end=15)
# TODO: abstract class
self.copy_button = ClipboardButton(None, lambda: self.error_label.get_text())
self.copy_button = ClipboardButton(self.error_label.get_text)
self.button_hbox = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL, halign=Gtk.Align.CENTER, spacing=10
)
+1 -1
View File
@@ -10,7 +10,7 @@ from dzgui.views.components.labels import BoldLabel
from dzgui.views.components.box import VBox, HBox
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib, GObject # type: ignore # noqa E402
from gi.repository import Gtk, GLib, GObject # noqa E402
DAY_START_TIME = time(hour=6, minute=0, second=0)
DAY_END_TIME = time(hour=18, minute=0, second=0)
+1 -1
View File
@@ -46,7 +46,7 @@ class AbortDialog(ColorAwareApp, Gtk.MessageDialog): # type: ignore
self.connect("response", self._on_response)
self.run()
self.run() # type: ignore[no-untyped-call]
self.destroy()
def _on_response(self, dialog: Self, response: Gtk.ResponseType) -> None:
+6 -6
View File
@@ -16,14 +16,14 @@ class FilePicker(Gtk.FileChooserDialog):
transient_for=parent,
resizable=True,
)
self.add_buttons("_Cancel", Gtk.ResponseType.CANCEL)
self.add_buttons("_Save", Gtk.ResponseType.OK)
self.add_buttons("_Cancel", Gtk.ResponseType.CANCEL) # type: ignore[no-untyped-call]
self.add_buttons("_Save", Gtk.ResponseType.OK) # type: ignore[no-untyped-call]
self.set_default_response(Gtk.ResponseType.OK)
self.set_current_name(picker.placeholder)
def pick_file(self) -> Path | None:
res = self.run()
res = self.run() # type: ignore[no-untyped-call]
if res in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
self.destroy()
return None
@@ -43,12 +43,12 @@ class FolderPicker(Gtk.FileChooserDialog):
parent=parent,
resizable=True,
)
self.add_buttons("_Cancel", Gtk.ResponseType.CANCEL)
self.add_buttons("_OK", Gtk.ResponseType.OK)
self.add_buttons("_Cancel", Gtk.ResponseType.CANCEL) # type: ignore[no-untyped-call]
self.add_buttons("_OK", Gtk.ResponseType.OK) # type: ignore[no-untyped-call]
self.set_default_response(Gtk.ResponseType.OK)
def pick_folder(self) -> Path | None:
res = self.run()
res = self.run() # type: ignore[no-untyped-call]
if res in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
self.destroy()
return None
+1 -1
View File
@@ -187,7 +187,7 @@ class TextBufferDialog(GenericDialog):
secondary=secondary,
)
self.text = text
copy_button = ClipboardButton(controller, self.get_text)
copy_button = ClipboardButton(self.get_text)
self.add_action_widget(copy_button, Gtk.ResponseType.NONE)
self.add_button("OK", Gtk.ResponseType.OK)
self.connect("response", self._on_response)
+4 -4
View File
@@ -22,7 +22,7 @@ from gi.repository import Gtk, GLib, Pango # noqa E402
class Assistant(Gtk.Assistant):
def __init__(self, is_deck: bool, paths: dict[str, str]):
def __init__(self, is_deck: bool, paths: dict[str, Path]):
super().__init__()
if is_deck:
# NOTE: deemed to be "safe" dimensions that exclude taskbar size
@@ -63,7 +63,7 @@ class Assistant(Gtk.Assistant):
class Window(Gtk.Window):
def __init__(self, is_deck: bool, paths: dict[str, str]) -> None:
def __init__(self, is_deck: bool, paths: dict[str, Path]) -> None:
super().__init__(title=APP_NAME, icon_name=APP_NAME)
self.assistant = Assistant(is_deck, paths)
@@ -121,7 +121,7 @@ class CheckboxWithPath(CheckboxWithLabel):
class UninstallPage(ScrolledWizardPage):
def __init__(self, paths: dict[str, str], pyapp: str | None):
def __init__(self, paths: dict[str, Path], pyapp: str | None):
super().__init__(heading=uninstall.pg1_heading, description=uninstall.pg1_blurb)
self.pyapp = pyapp
@@ -219,7 +219,7 @@ class UninstallPage(ScrolledWizardPage):
class UninstallWizard(ColorAwareApp, Gtk.Application): # type: ignore
def __init__(self, is_deck: bool, paths: dict[str, str]) -> None:
def __init__(self, is_deck: bool, paths: dict[str, Path]) -> None:
super().__init__()
config = Path(paths["XDG_CONFIG_HOME"])
if not config.is_dir() or not config.joinpath("config.json").is_file():
+11 -6
View File
@@ -14,7 +14,7 @@ from dzgui.const.constants import (
HERO_PATH,
FLATPAK_ROOT_PATH,
LEGACY_CONFIG_PATH,
TMP_PATH
TMP_PATH,
)
from dzgui.const.boilerplate import config_boilerplate
from dzgui.const.endpoints import STEAM_API_SETUP
@@ -168,7 +168,12 @@ class NotificationFrame(Gtk.Frame):
class APIValidationPage(EnumeratedWizardPage):
def __init__(
self, enum: PageNum, heading: str, description: str, link: str, func: Callable
self,
enum: PageNum,
heading: str,
description: str,
link: str,
func: Callable[[str], None],
) -> None:
super().__init__(
enum=enum,
@@ -421,7 +426,7 @@ class PreferencesPage(EnumeratedWizardPage):
class FlatpakOverridePage(EnumeratedWizardPage):
def __init__(self, xdg_paths: dict[str, str]) -> None:
def __init__(self, xdg_paths: dict[str, Path]) -> None:
super().__init__(
enum=PageNum.FLATPAK,
heading=wizard.heading_flatpak,
@@ -471,7 +476,7 @@ class CompletionPage(EnumeratedWizardPage):
class Assistant(Gtk.Assistant):
def __init__(self, is_deck: bool, XDG: "Xdg", xdg_paths: dict[str, str]):
def __init__(self, is_deck: bool, XDG: "Xdg", xdg_paths: dict[str, Path]):
super().__init__()
if is_deck:
# NOTE: deemed to be "safe" dimensions that exclude taskbar size
@@ -776,7 +781,7 @@ class SteamPathPage(EnumeratedWizardPage):
class SetupWizard(ColorAwareApp, Gtk.Application): # type: ignore
def __init__(self, is_deck: bool, XDG: "Xdg", xdg_paths: dict[str, str]) -> None:
def __init__(self, is_deck: bool, XDG: "Xdg", xdg_paths: dict[str, Path]) -> None:
super().__init__()
GLib.set_prgname(APP_NAME)
self.win = Window(is_deck, XDG, xdg_paths)
@@ -787,7 +792,7 @@ class SetupWizard(ColorAwareApp, Gtk.Application): # type: ignore
class Window(Gtk.Window):
def __init__(self, is_deck: bool, XDG: "Xdg", xdg_paths: dict[str, str]) -> None:
def __init__(self, is_deck: bool, XDG: "Xdg", xdg_paths: dict[str, Path]) -> None:
super().__init__(title=APP_NAME, icon_name=APP_NAME)
self.assistant = Assistant(is_deck, XDG, xdg_paths)
+2 -4
View File
@@ -15,9 +15,7 @@ logger = logging.getLogger(APP_NAME)
class ContextMixin(TreeView):
def present_menu(
self, widget: Gtk.Widget, event: Gdk.Event
) -> bool:
def present_menu(self, widget: Gtk.Widget, event: Gdk.Event) -> bool:
if self.menu is None:
return False
@@ -26,7 +24,7 @@ class ContextMixin(TreeView):
match event.type:
case Gdk.EventType.BUTTON_PRESS:
if event.button != 3:
if event.button != Gdk.BUTTON_SECONDARY: # type: ignore[comparison-overlap]
return False
self._process_button_event(event) # type: ignore
case Gdk.EventType.KEY_PRESS:
+4 -3
View File
@@ -13,6 +13,7 @@ if TYPE_CHECKING:
from dzgui.controllers.mc import Controller
# TODO: refactor into autopopulated ListStores
class Keybindings(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
"""
Notebook page holding a prearranged grid
@@ -35,7 +36,7 @@ class Keybindings(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
self.connect("key-press-event", self._on_keypress)
def build_keys(self, items: list) -> Gtk.Grid:
def build_keys(self, items: list[dict[str, str]]) -> Gtk.Grid:
grid = Gtk.Grid(row_spacing=10, column_homogeneous=True)
grid.set_halign(Gtk.Align.START)
row, col = 1, 0
@@ -72,7 +73,7 @@ class Keybindings(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
row += 1
return grid
def build_sidebar(self, categories: list) -> Gtk.Grid:
def build_sidebar(self, categories: list[str]) -> Gtk.Grid:
row, col = 0, 0
w, h = 1, 1
sidebar = Gtk.Grid(row_homogeneous=True, orientation=Gtk.Orientation.VERTICAL)
@@ -83,7 +84,7 @@ class Keybindings(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
sidebar.attach(label, col, row, w, h)
return sidebar
def build_grid(self, items: list) -> Gtk.Grid:
def build_grid(self, items: list[dict[str, str]]) -> Gtk.Grid:
grid = Gtk.Grid(
row_spacing=20,
halign=Gtk.Align.CENTER,
+7 -6
View File
@@ -1,5 +1,5 @@
from pathlib import Path
from typing import Callable, TYPE_CHECKING
from typing import Any, Callable, TYPE_CHECKING
from dzgui.api import pefile as PeFile
from dzgui.api.steam import get_last_updated
@@ -55,7 +55,7 @@ class SubmitField(Gtk.Box):
) -> None:
super().__init__(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
self.callback: Callable
self.callback: Callable[[], None]
self.controller = controller
self.context = context
@@ -143,7 +143,7 @@ class SubmitField(Gtk.Box):
return
self.save_option()
def set_callback(self, callback: Callable) -> None:
def set_callback(self, callback: Callable[[], None]) -> None:
self.callback = callback
def save_option(self) -> None:
@@ -210,7 +210,7 @@ class NameSubmitField(SubmitField):
Preferences.NAME,
private=False,
)
self.entry.set_width_chars(30) # type: ignore
self.entry.set_width_chars(30)
self.set_callback(self.save_player_name)
def save_player_name(self) -> None:
@@ -406,7 +406,8 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
def _on_link_button_clicked(self, button: Gtk.Button, url: str) -> None:
open_links.open_link_by_url(url)
def _make_grid(self, rows: list) -> Gtk.Grid:
# TODO: use FlowBox
def _make_grid(self, rows: list[list[Any]]) -> Gtk.Grid:
grid = Gtk.Grid(
orientation=Gtk.Orientation.VERTICAL,
column_spacing=10,
@@ -439,7 +440,7 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
if prefs.paths.config.is_file() is False:
dialog = ExceptionDialog(self.controller, strings.config_not_found)
dialog.show_all()
dialog.run()
dialog.run() # type: ignore[no-untyped-call]
raise OSError(f"Config file '{prefs.paths.config}' not found")
config = query.get_config(prefs.paths.config)
+1 -1
View File
@@ -231,7 +231,7 @@ class PreConnectionAssistant(Gtk.Box):
args = self.controller.get_debug_args()
d = DebugDialog(self.controller, args)
d.show_all()
d.run()
d.run() # type: ignore[no-untyped-call]
def _on_ok_clicked(self, button: Gtk.Button) -> None:
self.controller.update_and_connect()
+9 -25
View File
@@ -79,11 +79,6 @@ class TreeView(CursorMixin, Gtk.TreeView): # type: ignore
index = path.get_indices()[0]
return index
def get_selected_records(self) -> list:
sel = self.get_selection()
model, rows = sel.get_selected_rows()
return [model[row] for row in rows]
def _on_keypress(self, treeview: Gtk.TreeView, event: Gdk.EventKey) -> bool:
from dzgui.views.trees.tree_servers import ServerTreeView
@@ -145,9 +140,11 @@ class TreeView(CursorMixin, Gtk.TreeView): # type: ignore
self.set_cursor(path)
def get_value_at_index(self, index: int) -> Any:
(model, pathlist) = self.get_model_and_pathlist()
model, pathlist = self.get_model_and_pathlist()
if len(pathlist) < 1:
return ""
if model is None:
return ""
path = pathlist[0]
tree_iter = model.get_iter(path)
value = model.get_value(tree_iter, index)
@@ -161,20 +158,16 @@ class TreeView(CursorMixin, Gtk.TreeView): # type: ignore
sel = self.get_selection()
self._on_tree_selection_changed(sel)
def get_model_and_pathlist(self) -> tuple:
def get_model_and_pathlist(
self,
) -> tuple[Gtk.TreeModel | None, list[Gtk.TreePath]]:
select = self.get_selection()
sels = select.get_selected_rows()
(model, pathlist) = sels
model: Gtk.TreeModel | None
pathlist: list[Gtk.TreePath]
model, pathlist = sels
return (model, pathlist)
# @deprecated("Currently unused")
# def get_mpath(self) -> Optional[Gtk.TreePath]:
# (model, pathlist) = self.get_model_and_pathlist()
# if len(pathlist) < 1:
# return None
# path = pathlist[0]
# return path
def _on_row_activated(
self,
treeview: Gtk.TreeView,
@@ -189,15 +182,6 @@ class TreeView(CursorMixin, Gtk.TreeView): # type: ignore
return True
return False
# @deprecated("unused")
# def get_selected_row(self) -> Optional[Gtk.TreeModelRow]:
# ind = self.get_selected_row_index()
# model = self.get_model()
# if model is None:
# return None
# row = model[ind]
# return row
def get_col_value_by_path_index(self, path: Gtk.TreePath, index: int) -> Any:
model = self.get_model()
if model is None:
+3 -2
View File
@@ -32,6 +32,7 @@ if TYPE_CHECKING:
from dzgui.controllers.mc import Controller
from dzgui.controllers.emitter import Emitter
from dzgui.model.model_factory import FastInsertListStore
from dzgui.util.dist import Haversine
QUEUE_CHECK_DELAY = 200
@@ -64,7 +65,7 @@ class ServerTreeView(ContextMixin, TreeView): # type: ignore
self.queue_id: int
self.handler_id: int
self.queue: Queue = Queue()
self.queue: Queue[tuple[str, "Haversine" | None, ServerTab]] = Queue()
self.seen_cache: list[Record] = []
@@ -406,7 +407,7 @@ class ServerTreeView(ContextMixin, TreeView): # type: ignore
path = model.get_path(it)
row_index = path.get_indices()[0]
try:
start, end = self.get_visible_range()
start, end = self.get_visible_range() # type: ignore[no-untyped-call]
except Exception:
return
if row_index >= start[0] <= end[0]:
+1 -1
View File
@@ -104,7 +104,7 @@ python-a2s = { path = "./dzgui/lib/a2s", editable = true }
dayzquery = { path = "./dzgui/lib/dayzquery", editable = true }
[tool.uv]
config-settings-package = { pygobject-stubs = { config = "Gtk3,Gdk3,Soup2" } }
config-settings-package = { pygobject-stubs = { config = "Gtk3,Gdk3" } }
[tool.pytest.ini_options]
markers = [
+2 -2
View File
@@ -1,4 +1,4 @@
from pathlib import Path
from shared import get_git_root
BOILERPLATE = """
This file includes the combined licenses to the following components:
@@ -40,7 +40,7 @@ class CombinedLicense:
def concat_license() -> str:
combined = CombinedLicense()
root = Path(__file__).resolve().parents[1]
root = get_git_root()
licenses = root.joinpath("docs/licenses")
main_license = root.joinpath(LICENSE_FILE)
+5 -4
View File
@@ -1,10 +1,11 @@
import shutil
import subprocess
from pathlib import Path
par = Path(__file__).parent.parent
clog = par / "CHANGELOG.md"
target = par / "dzgui/data/CHANGELOG.md"
from shared import get_git_root
root = get_git_root()
clog = root.joinpath("CHANGELOG.md")
target = root.joinpath("dzgui/data/CHANGELOG.md")
shutil.copy(clog, target)
+2 -3
View File
@@ -1,10 +1,9 @@
import tomllib
from pathlib import Path
root = Path(__file__).resolve().parents[1]
from shared import get_git_root
def get_entrypoint() -> None:
root = get_git_root()
p = root.joinpath("pyproject.toml")
with open(p, "rb") as f:
t = tomllib.load(f)
+3 -2
View File
@@ -3,14 +3,15 @@ import requests
import subprocess
import tarfile
from pathlib import Path
from urllib.parse import unquote
from shared import get_git_root
BUILD = "3.13.9"
DATE = "20251014"
CPYTHON_BUILD = f"cpython-{BUILD}"
root = Path(__file__).resolve().parents[1]
root = get_git_root()
build_dir = root.joinpath("build")
cpython_dir = build_dir.joinpath(CPYTHON_BUILD)
+2 -1
View File
@@ -9,6 +9,7 @@ from pathlib import Path
from concat_licenses import concat_license
from parse_toml import get_entrypoint
from prebuild import get_pyapp, rebuild_cpython
from shared import get_git_root
def update_uname(info: tarfile.TarInfo) -> tarfile.TarInfo:
@@ -17,7 +18,7 @@ def update_uname(info: tarfile.TarInfo) -> tarfile.TarInfo:
return info
root = Path(__file__).resolve().parents[1]
root = get_git_root()
dist_dir = root.joinpath("dist")
build_dir = root.joinpath("build")
+1 -10
View File
@@ -1,16 +1,7 @@
import subprocess
from pathlib import Path
from dzgui.app_init import build_gui
def get_git_root() -> Path:
proc = subprocess.run(
["git", "rev-parse", "--show-toplevel"], capture_output=True, text=True
)
if proc.returncode != 0:
raise OSError("Failed to parse git root")
return Path(proc.stdout.rstrip())
from shared import get_git_root
def get_image_path() -> Path:
+11
View File
@@ -0,0 +1,11 @@
import subprocess
from pathlib import Path
def get_git_root() -> Path:
proc = subprocess.run(
["git", "rev-parse", "--show-toplevel"], capture_output=True, text=True
)
if proc.returncode != 0:
raise OSError("Failed to parse git root")
return Path(proc.stdout.rstrip())
-25
View File
@@ -1,25 +0,0 @@
from typing import TYPE_CHECKING
from dzgui.init.migrate import clean_legacy_versions_file
if TYPE_CHECKING:
from pathlib import Path
def test_null_signature_stripping(tmp_path: "Path") -> None:
tmp = tmp_path.joinpath("dzg.versions")
lines = [
"1111,2222",
"3333,4444",
"5555,null",
"foo",
",4555",
"1111",
" ",
",,,",
"-1",
]
expect = ["1111,2222", "3333,4444"]
tmp.write_text("\n".join(lines))
clean_legacy_versions_file(tmp)
assert expect == tmp.read_text().splitlines()