Compare commits

..

No commits in common. "3b4a736ce6036cdb2acb8cd00930ffea14e93933" and "b47cb482f52909a55b3c74c3ecd1b9eba2c210e6" have entirely different histories.

19 changed files with 46 additions and 127 deletions

View File

@ -21,7 +21,7 @@ def write_desktop_file(exe_path: Path) -> Path:
Terminal=false Terminal=false
Exec={exe_path} Exec={exe_path}
Name=DZGUI Name=DZGUI
Comment=DayZ server browser and mod manager Comment=dzgui
Icon={icon} Icon={icon}
Categories=Game""" Categories=Game"""

View File

@ -259,7 +259,7 @@ class ProxyModelManager:
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 self.filter_cache[filters] = filtered_rows
# @deprecated("Currently unused") @deprecated("Currently unused")
# def convert_model_to_list(self, model: "FastInsertListStore") -> list: # def convert_model_to_list(self, model: "FastInsertListStore") -> list:
# return [[el for el in row] for row in model] # return [[el for el in row] for row in model]

View File

@ -1,7 +1,4 @@
heading = "Play offline" heading = "Play offline"
heading_disclaimer = (
"Not all mod combinations will function offline. Use at your own discretion."
)
mission_button = "Select mission folder" mission_button = "Select mission folder"
mission_frame = "Mission" mission_frame = "Mission"

View File

@ -4,9 +4,8 @@ from typing import Literal
api_filter = r"(.*&key=)([^&]*)(.*)" api_filter = r"(.*&key=)([^&]*)(.*)"
home_filter = r"(/home/)([^\s'\/]*)(.*)" home_filter = r"(/home/)([^\s'\/]*)(.*)"
user_filter = r"(.*Steam/userdata/)([^/]*)(.*)"
REDACTED = r"\1REDACTED\3" REDACTED = r"\1REDACTED\3"
REDACTION_PATTERNS = [api_filter, home_filter, user_filter] REDACTION_PATTERNS = [api_filter, home_filter]
def redact_home(text: str) -> str: def redact_home(text: str) -> str:

View File

@ -1,4 +1,4 @@
from typing import Callable, Literal, Self, TYPE_CHECKING, Union from typing import Callable, Self, TYPE_CHECKING, Union
from dzgui.util.clip import copy_clipboard from dzgui.util.clip import copy_clipboard
from dzgui.util.format import pluralize from dzgui.util.format import pluralize
@ -66,15 +66,6 @@ class IconButton(Gtk.Button):
# self.set_image_position(Gtk.PositionType.RIGHT) # self.set_image_position(Gtk.PositionType.RIGHT)
self.set_focus_on_click(False) self.set_focus_on_click(False)
def swap_icon(self, icon: str) -> None:
start = self.icon.get_margin_start()
end = self.icon.get_margin_end()
alt_icon = Icon(icon, margin_start=start, margin_end=end)
self.set_image(alt_icon)
def revert_icon(self) -> None:
self.set_image(self.icon)
class IconTextButton(IconButton): class IconTextButton(IconButton):
def __init__( def __init__(
@ -105,18 +96,11 @@ class ClipboardButton(IconTextButton):
self.controller = controller self.controller = controller
self.connect("clicked", self._on_button_clicked, func) self.connect("clicked", self._on_button_clicked, func)
# TODO: strings
self.set_tooltip_text("Copy to clipboard") 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) -> None:
def revert() -> Literal[False]:
self.revert_icon()
return False
self.swap_icon("object-select-symbolic")
data = func() data = func()
copy_clipboard(data) copy_clipboard(data)
GLib.timeout_add(600, revert)
# TODO: determine when controller would be passed to this button or drop # TODO: determine when controller would be passed to this button or drop
@ -124,7 +108,6 @@ class CopyIpButton(ClipboardButton):
def __init__(self, controller: Union["Controller", None], func: Callable) -> None: def __init__(self, controller: Union["Controller", None], func: Callable) -> None:
super().__init__(controller, func) super().__init__(controller, func)
# TODO: strings
self.set_tooltip_text("Copy IP to clipboard") self.set_tooltip_text("Copy IP to clipboard")

View File

@ -162,8 +162,7 @@ class APIEntry(Gtk.Box):
self.entry.connect("icon-release", self._on_icon_release) self.entry.connect("icon-release", self._on_icon_release)
self.entry.connect("activate", self._on_field_activated) self.entry.connect("activate", self._on_field_activated)
# TODO: strings self.submit = Gtk.Button(label="Submit")
self.submit = Gtk.Button(label="Validate")
self.submit.set_sensitive(False) self.submit.set_sensitive(False)
self.submit.connect("clicked", self._on_submit) self.submit.connect("clicked", self._on_submit)

View File

@ -30,9 +30,6 @@ class InfoEventBox(Gtk.EventBox):
self.connect("leave-notify-event", self._on_leave_tooltip) self.connect("leave-notify-event", self._on_leave_tooltip)
self.add(box) self.add(box)
def set_icon_yalign(self, offset: float) -> None:
self.icon.set_property("yalign", offset)
def _on_enter_tooltip( def _on_enter_tooltip(
self, eventbox: Gtk.EventBox, eventcrossing: Gdk.EventCrossing self, eventbox: Gtk.EventBox, eventcrossing: Gdk.EventCrossing
) -> None: ) -> None:

View File

@ -131,7 +131,6 @@ class RightPanel(Gtk.Box):
return return
self.copying = True self.copying = True
version = self.version_label.get_text() version = self.version_label.get_text()
# TODO: strings
self.version_label.set_text("Copied!") self.version_label.set_text("Copied!")
copy_clipboard(version) copy_clipboard(version)
GLib.timeout_add_seconds(1, revert) GLib.timeout_add_seconds(1, revert)

View File

@ -41,7 +41,7 @@ class BootDialog(Gtk.Dialog):
def __init__(self, parent: "BootWindow", xdg: "Xdg", version: str) -> None: def __init__(self, parent: "BootWindow", xdg: "Xdg", version: str) -> None:
super().__init__( super().__init__(
title=dialog_header, title=dialog_header,
transient_for=parent, parent=parent,
modal=True, modal=True,
) )

View File

@ -13,7 +13,7 @@ class FilePicker(Gtk.FileChooserDialog):
super().__init__( super().__init__(
title=picker.title, title=picker.title,
action=Gtk.FileChooserAction.SAVE, action=Gtk.FileChooserAction.SAVE,
transient_for=parent, parent=parent,
resizable=True, resizable=True,
) )
self.add_buttons("_Cancel", Gtk.ResponseType.CANCEL) self.add_buttons("_Cancel", Gtk.ResponseType.CANCEL)

View File

@ -50,13 +50,6 @@ class PageNum(Enum):
FINAL = 8 FINAL = 8
class OptionalPageMixin:
"""Marks optional pages as advanceable"""
def _on_map(self, page: "ScrolledWizardPage") -> None:
EMITTER.emit("step_complete")
class DescriptionArea(Gtk.Box): class DescriptionArea(Gtk.Box):
def __init__(self, text: str): def __init__(self, text: str):
super().__init__(orientation=Gtk.Orientation.VERTICAL) super().__init__(orientation=Gtk.Orientation.VERTICAL)
@ -98,7 +91,6 @@ class ScrolledWizardPage(Gtk.ScrolledWindow):
margin_top=50, margin_top=50,
spacing=20, spacing=20,
) )
self.add(self.box) self.add(self.box)
self.prog = Progress() self.prog = Progress()
self.box.pack_end(self.prog, expand=False, fill=False, padding=0) self.box.pack_end(self.prog, expand=False, fill=False, padding=0)
@ -223,7 +215,7 @@ class APIValidationPage(ScrolledWizardPage):
self.spinner.stop() self.spinner.stop()
class BMValidationPage(OptionalPageMixin, APIValidationPage): # type: ignore class BMValidationPage(APIValidationPage):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__( super().__init__(
enum=PageNum.BM_API, enum=PageNum.BM_API,
@ -232,7 +224,6 @@ class BMValidationPage(OptionalPageMixin, APIValidationPage): # type: ignore
link=BM_API_SETUP, link=BM_API_SETUP,
func=self._validate, func=self._validate,
) )
self.connect("map", self._on_map)
@call_on_thread("", show_dialog=False) @call_on_thread("", show_dialog=False)
def _validate(self, key: str) -> None: def _validate(self, key: str) -> None:
@ -454,7 +445,6 @@ class Assistant(Gtk.Assistant):
else: else:
self.set_default_size(1500, 900) self.set_default_size(1500, 900)
self.is_binary = False if os.getenv("PYAPP") is None else True
self.config_path = XDG.config self.config_path = XDG.config
self.config_values: dict[str, Any] = config_boilerplate self.config_values: dict[str, Any] = config_boilerplate
@ -495,7 +485,7 @@ class Assistant(Gtk.Assistant):
): ):
continue continue
# NOTE: disabled for now on system-provided packages # NOTE: disabled for now on system-provided packages
if isinstance(page, ShortcutCreationPage) and not self.is_binary: if isinstance(page, ShortcutCreationPage) and os.getenv("PYAPP") is None:
continue continue
self._add_page(page, page.get_page_type()) self._add_page(page, page.get_page_type())
@ -510,33 +500,32 @@ class Assistant(Gtk.Assistant):
def _advance_page(self, index: int) -> int: def _advance_page(self, index: int) -> int:
page = self.get_nth_page(index) page = self.get_nth_page(index)
# TODO: use enums/isinstance
match page: match page:
case IntroductionPage(): case self.page1:
pass pass
case ConfigMigrationPage(): case self.page2:
if page.is_migrated(): if self.page2.is_migrated():
steam_path = lookup(self.config_path, Preferences.DEFAULT) steam_path = lookup(self.config_path, Preferences.DEFAULT)
self.page7.set_steam_path(steam_path) self.page7.set_steam_path(steam_path)
offset = 1 if not self.is_binary else 2 return self.get_n_pages() - 2
self.setup_complete = True case self.page3:
return self.get_n_pages() - offset
case SteamPathPage():
self.config_values["default_steam_path"] = page.get_path_from_radio() self.config_values["default_steam_path"] = page.get_path_from_radio()
case SteamValidationPage(): case self.page4:
self.config_values["steam_api"] = page.get_api_key() self.config_values["steam_api"] = page.get_api_key()
case BMValidationPage(): case self.page5:
self.config_values["bm_api"] = page.get_api_key() self.config_values["bm_api"] = page.get_api_key()
case PreferencesPage():
# NOTE: collects config values before advancing to last page # NOTE: collects config values before advancing to last page
case self.page6:
name, use_miles, client = self.page6.get_prefs() name, use_miles, client = self.page6.get_prefs()
self.config_values["name"] = name self.config_values["name"] = name
self.config_values["use_miles"] = use_miles self.config_values["use_miles"] = use_miles
self.config_values["client"] = client self.config_values["client"] = client
self.write_config() self.write_config()
self.page7.set_steam_path(self.config_values["default_steam_path"]) self.page7.set_steam_path(self.config_values["default_steam_path"])
case self.page7:
self.page7.create_shortcuts()
self.setup_complete = True self.setup_complete = True
case ShortcutCreationPage():
page.create_shortcuts()
case _: case _:
raise AttributeError("Trying to advance a non-canonical page") raise AttributeError("Trying to advance a non-canonical page")
return index + 1 return index + 1
@ -580,7 +569,11 @@ class Assistant(Gtk.Assistant):
bar.set_fraction(fraction) bar.set_fraction(fraction)
bar.set_text(f"{page_num}/{total}") bar.set_text(f"{page_num}/{total}")
if not isinstance(page, IntroductionPage): # NOTE: disable forward action
# TODO: use page enums
if page == self.page5:
return
if page != self.page1:
EMITTER.emit("step_pending") EMITTER.emit("step_pending")
@ -607,7 +600,7 @@ class CheckboxWithLabel(Gtk.Box):
self.button.set_active(state) self.button.set_active(state)
class ShortcutCreationPage(OptionalPageMixin, ScrolledWizardPage): # type: ignore class ShortcutCreationPage(ScrolledWizardPage):
def __init__(self, shortcut: Path) -> None: def __init__(self, shortcut: Path) -> None:
super().__init__( super().__init__(
enum=PageNum.SHORTCUTS, enum=PageNum.SHORTCUTS,
@ -650,6 +643,9 @@ class ShortcutCreationPage(OptionalPageMixin, ScrolledWizardPage): # type: igno
self.desktop_checkbox.set_active(state) self.desktop_checkbox.set_active(state)
self.desktop_checkbox.set_sensitive(state) self.desktop_checkbox.set_sensitive(state)
def _on_map(self, page: "ScrolledWizardPage") -> None:
EMITTER.emit("step_complete")
def set_steam_path(self, path: Path) -> None: def set_steam_path(self, path: Path) -> None:
self.steam_path = path self.steam_path = path

View File

@ -41,18 +41,9 @@ class FolderError(Enum):
FOLDER_CHANGED = 3 FOLDER_CHANGED = 3
class PageHeading(Gtk.Box): class PageHeading(Gtk.Label):
def __init__(self, label: str, controller: "Controller") -> None: def __init__(self, label: str) -> None:
super().__init__( super().__init__(label=label, halign=Gtk.Align.CENTER)
halign=Gtk.Align.CENTER, orientation=Gtk.Orientation.HORIZONTAL
)
self.label = Gtk.Label(label=label)
self.eb = InfoEventBox(offline.heading_disclaimer, controller)
self.eb.set_icon_yalign(0.7)
for el in self.label, self.eb:
self.add(el)
css.add_class(self, "page-heading") css.add_class(self, "page-heading")
@ -443,8 +434,7 @@ class OfflineLoader(Gtk.Box):
self.emitter = controller.get_emitter() self.emitter = controller.get_emitter()
self.offline_man = OfflineManager(controller) self.offline_man = OfflineManager(controller)
heading = PageHeading(offline.heading, controller) self.add(PageHeading(offline.heading))
self.add(heading)
self.local_frame = ModFrame(self, controller, offline.local_frame) self.local_frame = ModFrame(self, controller, offline.local_frame)
self.custom_frame = CustomModFrame(self, controller, offline.custom_frame) self.custom_frame = CustomModFrame(self, controller, offline.custom_frame)

View File

@ -1,6 +1,7 @@
import logging import logging
from typing import Any, Optional, TYPE_CHECKING from typing import Any, Optional, TYPE_CHECKING
from warnings import deprecated
from dzgui.const.constants import APP_NAME, SEPARATOR from dzgui.const.constants import APP_NAME, SEPARATOR
from dzgui.util.keys import is_ctrl_mask, is_navkey from dzgui.util.keys import is_ctrl_mask, is_navkey
@ -172,7 +173,7 @@ class TreeView(CursorMixin, Gtk.TreeView): # type: ignore
(model, pathlist) = sels (model, pathlist) = sels
return (model, pathlist) return (model, pathlist)
# @deprecated("Currently unused") @deprecated("Currently unused")
# def get_mpath(self) -> Optional[Gtk.TreePath]: # def get_mpath(self) -> Optional[Gtk.TreePath]:
# (model, pathlist) = self.get_model_and_pathlist() # (model, pathlist) = self.get_model_and_pathlist()
# if len(pathlist) < 1: # if len(pathlist) < 1:
@ -194,7 +195,7 @@ class TreeView(CursorMixin, Gtk.TreeView): # type: ignore
return True return True
return False return False
# @deprecated("unused") @deprecated("unused")
# def get_selected_row(self) -> Optional[Gtk.TreeModelRow]: # def get_selected_row(self) -> Optional[Gtk.TreeModelRow]:
# ind = self.get_selected_row_index() # ind = self.get_selected_row_index()
# model = self.get_model() # model = self.get_model()

View File

@ -53,7 +53,7 @@ class LogTreeView(ContextMixin, TreeView): # type: ignore
model = ModelFactory().new_model_from_logfile(filepath) model = ModelFactory().new_model_from_logfile(filepath)
_filter = model.filter_new() _filter = model.filter_new()
_filter.set_visible_func(self._filter_rows) _filter.set_visible_func(self._filter_rows)
sortable = Gtk.TreeModelSort.new_with_model(_filter) sortable = Gtk.TreeModelSort(_filter)
self.set_model(sortable) self.set_model(sortable)
_filter.refilter() _filter.refilter()
path = Gtk.TreePath.new_from_indices([0]) path = Gtk.TreePath.new_from_indices([0])

View File

@ -4,7 +4,7 @@ description = "DayZ server browser and mod manager for Linux"
authors = [ authors = [
{name = "aclist"} {name = "aclist"}
] ]
version = "7.0.0b20" version = "7.0.0b19"
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
license-files = ["LICENSE"] license-files = ["LICENSE"]
readme = "README.md" readme = "README.md"
@ -107,13 +107,11 @@ config-settings-package = { pygobject-stubs = { config = "Gtk3,Gdk3,Soup2" } }
[tool.pytest.ini_options] [tool.pytest.ini_options]
markers = [ markers = [
"apitest: checks remote endpoints",
"config: config file keys/values", "config: config file keys/values",
"realconfig: depends on actual generated local config file",
"mods: tests mod metadata/link creation", "mods: tests mod metadata/link creation",
"pefile: validate PE files", "pefile: validate PE files",
"post_install: requires a completed installation", "post_install: requires a completed installation",
"redact: log redaction mechanisms", "redact: log redaction mechanisms",
"slow: long-running tests", "slow: long-running tests",
"webtest: depends on remote endpoint", "webtest: checks remote endpoints"
] ]

View File

@ -7,12 +7,13 @@ from dzgui.config.xdg import get_xdg_paths, parse_filepaths
from dzgui.config import convert from dzgui.config import convert
from tests.fixtures import fixture_path from tests.fixtures import fixture_path
pytestmark = pytest.mark.config
@pytest.fixture @pytest.fixture
def legacy_config(): def legacy_config():
return fixture_path("dztuirc_1") return fixture_path("dztuirc_1")
@pytest.fixture @pytest.fixture
def unset_values(): def unset_values():
return fixture_path("dztuirc_3") return fixture_path("dztuirc_3")
@ -37,7 +38,6 @@ def keys():
# TODO: use a static fixture instead of system config # TODO: use a static fixture instead of system config
@pytest.fixture @pytest.fixture
@pytest.mark.realconfig
def config(): def config():
paths = get_xdg_paths() paths = get_xdg_paths()
xdg = parse_filepaths(paths) xdg = parse_filepaths(paths)
@ -45,21 +45,18 @@ def config():
@pytest.mark.post_install @pytest.mark.post_install
@pytest.mark.realconfig
def test_invalid_config_value(config): def test_invalid_config_value(config):
with pytest.raises(Exception): with pytest.raises(Exception):
assert config["foo"] is None assert config["foo"] is None
@pytest.mark.post_install @pytest.mark.post_install
@pytest.mark.realconfig
def test_default_config_values(keys, config): def test_default_config_values(keys, config):
for key in keys: for key in keys:
assert config[key] is not None assert config[key] is not None
@pytest.mark.post_install @pytest.mark.post_install
@pytest.mark.realconfig
def test_contains_invalid_values(keys, config): def test_contains_invalid_values(keys, config):
for key in config: for key in config:
assert key in keys assert key in keys
@ -94,7 +91,6 @@ def test_key_conversion(legacy_config):
for key in keys: for key in keys:
assert key not in j assert key not in j
def test_missing_values(unset_values): def test_missing_values(unset_values):
j = convert.rc2json(unset_values) j = convert.rc2json(unset_values)
j = json.loads(j) j = json.loads(j)

View File

@ -37,14 +37,6 @@ class RecordsListHandler(logging.Handler):
"Error in directory: '/home/SENSITIVE_USERNAME'", "Error in directory: '/home/SENSITIVE_USERNAME'",
"Error in directory: '/home/REDACTED'", "Error in directory: '/home/REDACTED'",
), ),
(
"User directory: /home/user/.local/share/Steam/userdata/999999/grid",
"User directory: /home/REDACTED/.local/share/Steam/userdata/REDACTED/grid",
),
(
"User directory: /drive/.local/share/Steam/userdata/999999",
"User directory: /drive/.local/share/Steam/userdata/REDACTED",
),
], ],
) )
def test_log_redaction(log_error: str, expect: str) -> None: def test_log_redaction(log_error: str, expect: str) -> None:

View File

@ -13,17 +13,13 @@ def config():
xdg = parse_filepaths(paths) xdg = parse_filepaths(paths)
return get_config(xdg.config) return get_config(xdg.config)
def test_ipdb(): def test_ipdb():
assert probe.test_ipdb() assert probe.test_ipdb()
def test_steam(config): def test_steam(config):
key = config["steam_api"] key = config["steam_api"]
assert probe.test_steam_api(key) assert probe.test_steam_api(key)
def test_bm(config): def test_bm(config):
# NOTE: see ticket #417; expected to return False
key = config["bm_api"] key = config["bm_api"]
assert probe.test_bm_api(key) is False assert probe.test_bm_api(key)

View File

@ -38,14 +38,14 @@ def dummy_app() -> None:
return d return d
def test_wrap_exe(dummy_app: dict[str, str]) -> None: def test_wrap_exe(dummy_app) -> None:
s = Shortcuts(Path("")) s = Shortcuts(Path(""))
s.add_shortcut(*dummy_app.values()) s.add_shortcut(*dummy_app.values())
assert s.shortcuts["shortcuts"]["0"]["Exe"][0] == '"' assert s.shortcuts["shortcuts"]["0"]["Exe"][0] == '"'
assert s.shortcuts["shortcuts"]["0"]["Exe"][-1] == '"' assert s.shortcuts["shortcuts"]["0"]["Exe"][-1] == '"'
def test_add_shortcut(dummy_app: dict[str, str]) -> None: def test_add_shortcut(dummy_app) -> None:
s = Shortcuts(Path("")) s = Shortcuts(Path(""))
s.add_shortcut(*dummy_app.values()) s.add_shortcut(*dummy_app.values())
new = s.shortcuts["shortcuts"] new = s.shortcuts["shortcuts"]
@ -56,7 +56,7 @@ def test_add_shortcut(dummy_app: dict[str, str]) -> None:
assert new[ind][k] == v assert new[ind][k] == v
def test_save_shortcut(dummy_app: dict[str, str]) -> None: def test_save_shortcut(dummy_app) -> None:
s = Shortcuts(Path("")) s = Shortcuts(Path(""))
s.add_shortcut(*dummy_app.values()) s.add_shortcut(*dummy_app.values())
with tempfile.NamedTemporaryFile() as f: with tempfile.NamedTemporaryFile() as f:
@ -65,27 +65,3 @@ def test_save_shortcut(dummy_app: dict[str, str]) -> None:
s.save_shortcuts() s.save_shortcuts()
s._load_shortcuts(tmp) s._load_shortcuts(tmp)
assert len(s.shortcuts["shortcuts"]) == 1 assert len(s.shortcuts["shortcuts"]) == 1
def test_shortcut_crc(dummy_app: dict[str, str]) -> None:
s = Shortcuts(Path(""))
s.add_shortcut(*dummy_app.values())
for key in s.shortcuts["shortcuts"].keys():
entry = s.shortcuts["shortcuts"][key]
name = entry["AppName"]
exe = entry["Exe"]
uid = name + exe
bpid = s.gen_bpid(uid)
assert entry["appid"] & 0xFFFFFFFF == bpid
def test_reverse_crc(dummy_app: dict[str, str]) -> None:
s = Shortcuts(Path(""))
s.add_shortcut(*dummy_app.values())
for key in s.shortcuts["shortcuts"].keys():
entry = s.shortcuts["shortcuts"][key]
name = entry["AppName"]
exe = entry["Exe"]
uid = name + exe
bpid = s.gen_bpid(uid)
assert s.find_appname_by_unsigned_id(bpid) == name