mirror of
https://github.com/aclist/dztui.git
synced 2026-10-27 21:22:46 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dac02d75f6 | ||
|
|
3a5c061dbf | ||
|
|
3851b76ca0 | ||
|
|
387930b147 | ||
|
|
c0c3ee8c64 | ||
|
|
1212c09110 | ||
|
|
86e2249a23 | ||
|
|
302a43e65e | ||
|
|
3dc58c1a69 | ||
|
|
9af2145ee8 | ||
|
|
7b4382b178 | ||
|
|
ea3631072b | ||
|
|
67bae3c3e0 | ||
|
|
1ea30270ce | ||
|
|
2c9b36ba84 | ||
|
|
fd9eb33714 | ||
|
|
15d928026a | ||
|
|
24d3bc8209 | ||
|
|
881847cee9 | ||
|
|
d3a06cf970 | ||
|
|
741344e75e | ||
|
|
a8a7aba338 | ||
|
|
fafd4c6000 | ||
|
|
7e5e326ebe | ||
|
|
e7644b49f5 |
No files matched your search
@@ -8,7 +8,7 @@ jobs:
|
|||||||
mirror-to-codeberg:
|
mirror-to-codeberg:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: pixta-dev/repository-mirroring-action@v1
|
- uses: pixta-dev/repository-mirroring-action@v1
|
||||||
|
|||||||
+2
-1
@@ -36,6 +36,7 @@
|
|||||||
- Preconnect warnings/failsafes like filesize
|
- Preconnect warnings/failsafes like filesize
|
||||||
- Save filters per server context between sessions
|
- Save filters per server context between sessions
|
||||||
- Preboot progress dialog
|
- Preboot progress dialog
|
||||||
|
- Choose to jump into splash screen instead of server
|
||||||
|
|
||||||
|
|
||||||
## Changed
|
## Changed
|
||||||
@@ -52,6 +53,7 @@
|
|||||||
- Embed Workshop link in Options menu
|
- Embed Workshop link in Options menu
|
||||||
- Disable overlay scrollbars on server tables
|
- Disable overlay scrollbars on server tables
|
||||||
- Reduce size of geolocation DB on disk (~100MB)
|
- Reduce size of geolocation DB on disk (~100MB)
|
||||||
|
- Enable LAN page Empty/Full filters on first run of app
|
||||||
|
|
||||||
## Dropped
|
## Dropped
|
||||||
- Debug mode
|
- Debug mode
|
||||||
@@ -67,7 +69,6 @@
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
- Load offline mods
|
- Load offline mods
|
||||||
- Choose to jump into splash screen instead of server
|
|
||||||
- Local documentation
|
- Local documentation
|
||||||
- Raw debug command in context menu
|
- Raw debug command in context menu
|
||||||
|
|
||||||
|
|||||||
+11
-6
@@ -3,16 +3,21 @@ Knowledge Base
|
|||||||
|
|
||||||
.. _DZG-001:
|
.. _DZG-001:
|
||||||
|
|
||||||
DZG-001: Periodically getting dropped from servers, or servers appear unreachable
|
DZG-001: Periodically getting dropped from servers, or servers time out in DZGUI
|
||||||
------------------------------------------------------------------------------------
|
------------------------------------------------------------------------------------
|
||||||
It is a longstanding issue in DayZ that the game opens a large number of connections when querying servers and/or while connected to servers.
|
DayZ opens a large number of connections while querying/connected to servers.
|
||||||
This can result in excess traffic on the user's PC which, depending on how much headroom your network has, can lead to getting dropped, unresponsiveness, or a timeout.
|
|
||||||
|
|
||||||
If you are on Wi-Fi, try switching to a wired connection and see if the problem resolves itself. If it does, your wireless router settings do not have enough headroom for max simultaneous connections. Use a wired connection, or update your network settings to a more permissive setup.
|
In addition, packets sent from server responses are expected to be a standard size (see warning below).
|
||||||
|
|
||||||
Bohemia has acknowledged this issue going back 10+ years and stated that DayZ has a heavy impact on the network, but there is as yet no proposed solution on the DayZ side.
|
If your network does not have enough headroom or has settings departing from defaults, this may lead to getting dropped from servers,
|
||||||
|
unresponsiveness, or a timeout.
|
||||||
|
|
||||||
This issue is frequently seen on Steam Deck, due to the tendency for users to use it in an untethered Wi-Fi setup.
|
If you are on Wi-Fi, try switching to a wired connection and see if the problem resolves itself. Consumer Wi-Fi routers
|
||||||
|
tend to have less headroom than their wired counterparts.
|
||||||
|
|
||||||
|
.. important::
|
||||||
|
Ensure that MTU (maximum tranmission unit) on your network is set to the standard size of 1,500 bytes.
|
||||||
|
If your network preferences or router have "jumbo" frames enabled, packets may be dropped, causing server queries to time out.
|
||||||
|
|
||||||
.. _DZG-002:
|
.. _DZG-002:
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -456,6 +456,5 @@ def get_pretty_version(steam_path: Path, appid: int) -> str | None:
|
|||||||
vers = get_dayz_version(pe_file_path)
|
vers = get_dayz_version(pe_file_path)
|
||||||
dayz_version = dayz_version_to_str(vers)
|
dayz_version = dayz_version_to_str(vers)
|
||||||
return dayz_version
|
return dayz_version
|
||||||
except Exception as e:
|
except Exception:
|
||||||
print(e)
|
|
||||||
return None
|
return None
|
||||||
@@ -61,6 +61,8 @@ class Details:
|
|||||||
|
|
||||||
@dataclass(slots=True)
|
@dataclass(slots=True)
|
||||||
class Record:
|
class Record:
|
||||||
|
# TODO: investigate whether this enum is still being used;
|
||||||
|
# can this be frozen?
|
||||||
"""
|
"""
|
||||||
The gameport field is manipulated by the RowType.CONN_BY_IP method
|
The gameport field is manipulated by the RowType.CONN_BY_IP method
|
||||||
"""
|
"""
|
||||||
@@ -509,6 +511,10 @@ def fqip_to_record(addr: str) -> Optional[Record]:
|
|||||||
return Record(r[0], int(r[1]), int(r[2]))
|
return Record(r[0], int(r[1]), int(r[2]))
|
||||||
|
|
||||||
|
|
||||||
|
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:
|
||||||
ip = res["addr"].split(":")[0]
|
ip = res["addr"].split(":")[0]
|
||||||
gameport = res["gameport"]
|
gameport = res["gameport"]
|
||||||
|
|||||||
@@ -146,6 +146,23 @@ def connect(client: str, addr: str, appid: int, name: str, mods: list[str]) -> i
|
|||||||
return proc.returncode
|
return proc.returncode
|
||||||
|
|
||||||
|
|
||||||
|
def load_to_menu(client: str, addr: str, appid: int, name: str, mods: list[str]) -> int:
|
||||||
|
"""Loads to the menu screen with the selected mods; used for AFK/pre-joining"""
|
||||||
|
concat = concat_mods(mods)
|
||||||
|
client_args = concat_bash_args(client)
|
||||||
|
params = [
|
||||||
|
"-applaunch",
|
||||||
|
str(appid),
|
||||||
|
"-nolauncher",
|
||||||
|
"-nosplash",
|
||||||
|
"-skipintro",
|
||||||
|
f"-name={name}",
|
||||||
|
f"-mod={concat}",
|
||||||
|
]
|
||||||
|
proc = subprocess.run([*client_args, *params])
|
||||||
|
return proc.returncode
|
||||||
|
|
||||||
|
|
||||||
def find_user_id(path: Path) -> str | None:
|
def find_user_id(path: Path) -> str | None:
|
||||||
resolved_path = path / "config" / "loginusers.vdf"
|
resolved_path = path / "config" / "loginusers.vdf"
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -409,9 +409,12 @@ class Controller(GObject.GObject):
|
|||||||
saved_tree = self.get_servers().get_saved()
|
saved_tree = self.get_servers().get_saved()
|
||||||
ServerModelManager(self, saved_tree).remove_by_record(record)
|
ServerModelManager(self, saved_tree).remove_by_record(record)
|
||||||
|
|
||||||
def add_to_history(self, record: dict[str, Any]) -> None:
|
def add_to_history(self, row: dict[str, Any], record: "Record") -> None:
|
||||||
tv = self.get_servers().get_recent()
|
tv = self.get_servers().get_recent()
|
||||||
ServerModelManager(self, tv).add_to_history(record)
|
ServerModelManager(self, tv).add_to_history((row, record))
|
||||||
|
|
||||||
|
# def append_to_history(self, record: "Record") -> None:
|
||||||
|
# self.config_man.append_to_history_file(record)
|
||||||
|
|
||||||
def remove_from_history(self, record: "Record") -> None:
|
def remove_from_history(self, record: "Record") -> None:
|
||||||
# NOTE: remove action is only possible from history tree context menu,
|
# NOTE: remove action is only possible from history tree context menu,
|
||||||
@@ -508,6 +511,9 @@ class Controller(GObject.GObject):
|
|||||||
ind = self.config_man.get_start_tab()
|
ind = self.config_man.get_start_tab()
|
||||||
self.get_servers().notebook.set_current_page(ind)
|
self.get_servers().notebook.set_current_page(ind)
|
||||||
|
|
||||||
|
def update_and_load_to_menu(self, raise_window: bool) -> None:
|
||||||
|
self.connection_man.update_and_connect(raise_window, menu_only=True)
|
||||||
|
|
||||||
def update_and_connect(self, raise_window: bool) -> None:
|
def update_and_connect(self, raise_window: bool) -> None:
|
||||||
self.connection_man.update_and_connect(raise_window)
|
self.connection_man.update_and_connect(raise_window)
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
- Preconnect warnings/failsafes like filesize
|
- Preconnect warnings/failsafes like filesize
|
||||||
- Save filters per server context between sessions
|
- Save filters per server context between sessions
|
||||||
- Preboot progress dialog
|
- Preboot progress dialog
|
||||||
|
- Choose to jump into splash screen instead of server
|
||||||
|
|
||||||
|
|
||||||
## Changed
|
## Changed
|
||||||
@@ -51,6 +52,8 @@
|
|||||||
- Suppress log messages from imported modules
|
- Suppress log messages from imported modules
|
||||||
- Embed Workshop link in Options menu
|
- Embed Workshop link in Options menu
|
||||||
- Disable overlay scrollbars on server tables
|
- Disable overlay scrollbars on server tables
|
||||||
|
- Reduce size of geolocation DB on disk (~100MB)
|
||||||
|
- Enable LAN page Empty/Full filters on first run of app
|
||||||
|
|
||||||
## Dropped
|
## Dropped
|
||||||
- Debug mode
|
- Debug mode
|
||||||
@@ -66,7 +69,6 @@
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
- Load offline mods
|
- Load offline mods
|
||||||
- Choose to jump into splash screen instead of server
|
|
||||||
- Local documentation
|
- Local documentation
|
||||||
- Raw debug command in context menu
|
- Raw debug command in context menu
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
from collections import deque
|
||||||
from typing import Any, TYPE_CHECKING
|
from typing import Any, TYPE_CHECKING
|
||||||
|
|
||||||
from dzgui.const.constants import (
|
from dzgui.const.constants import (
|
||||||
@@ -52,11 +53,25 @@ class ConfigManager:
|
|||||||
ips.append(record)
|
ips.append(record)
|
||||||
self.update_config(Preferences.IP_LIST, ips)
|
self.update_config(Preferences.IP_LIST, ips)
|
||||||
|
|
||||||
def update_history_file(self, records: list[Any]) -> None:
|
def update_history_file(self, fqip: str) -> None:
|
||||||
|
with open(self.prefs.paths.history, "r") as f:
|
||||||
|
ips = [line.rstrip() for line in f.readlines()]
|
||||||
|
|
||||||
|
seen = set()
|
||||||
|
ips.append(fqip)
|
||||||
|
# NOTE: Preserve linear order of records while deduplicating
|
||||||
|
unique = deque(maxlen=10) # type: ignore
|
||||||
|
for i in range(len(ips) - 1, -1, -1):
|
||||||
|
if ips[i] not in seen:
|
||||||
|
seen.add(ips[i])
|
||||||
|
unique.appendleft(ips[i])
|
||||||
|
# NOTE: appendleft will push 11th item off right edge
|
||||||
|
if len(unique) == 10:
|
||||||
|
break
|
||||||
|
|
||||||
with open(self.prefs.paths.history, "w") as f:
|
with open(self.prefs.paths.history, "w") as f:
|
||||||
for record in records:
|
for record in unique:
|
||||||
addr = f"{record[7]}:{record[8]}"
|
f.write(f"{record}\n")
|
||||||
f.write(f"{addr}\n")
|
|
||||||
|
|
||||||
def remove_saved_server(self, record: str) -> None:
|
def remove_saved_server(self, record: str) -> None:
|
||||||
ips = self.lookup(Preferences.IP_LIST)
|
ips = self.lookup(Preferences.IP_LIST)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from dzgui.api.steam import (
|
|||||||
enqueue_mod,
|
enqueue_mod,
|
||||||
get_remote_signatures,
|
get_remote_signatures,
|
||||||
get_needs_update,
|
get_needs_update,
|
||||||
|
load_to_menu,
|
||||||
)
|
)
|
||||||
|
|
||||||
from dzgui.api.mods import (
|
from dzgui.api.mods import (
|
||||||
@@ -74,6 +75,7 @@ class Prerequisites:
|
|||||||
steam_proc: SteamProcess
|
steam_proc: SteamProcess
|
||||||
mods: list[list[str]]
|
mods: list[list[str]]
|
||||||
game_mode: bool
|
game_mode: bool
|
||||||
|
is_last_server: bool
|
||||||
|
|
||||||
|
|
||||||
class ConnectionManager:
|
class ConnectionManager:
|
||||||
@@ -172,6 +174,8 @@ class ConnectionManager:
|
|||||||
|
|
||||||
game_mode = prefs.is_game_mode
|
game_mode = prefs.is_game_mode
|
||||||
|
|
||||||
|
is_last = self.is_last_server()
|
||||||
|
|
||||||
prereqs = Prerequisites(
|
prereqs = Prerequisites(
|
||||||
name=info.server_name,
|
name=info.server_name,
|
||||||
appid=info.game_id,
|
appid=info.game_id,
|
||||||
@@ -186,11 +190,23 @@ class ConnectionManager:
|
|||||||
steam_proc=steam_proc,
|
steam_proc=steam_proc,
|
||||||
mods=remote_mods,
|
mods=remote_mods,
|
||||||
game_mode=game_mode,
|
game_mode=game_mode,
|
||||||
|
is_last_server=is_last,
|
||||||
)
|
)
|
||||||
|
|
||||||
func = StoredFunc(self.controller.open_connection_assistant, prereqs)
|
func = StoredFunc(self.controller.open_connection_assistant, prereqs)
|
||||||
self.thread_man.set_cleanup_func(func, destroy_first=True)
|
self.thread_man.set_cleanup_func(func, destroy_first=True)
|
||||||
|
|
||||||
|
def is_last_server(self) -> bool:
|
||||||
|
prefs = self.controller.get_prefs()
|
||||||
|
history = prefs.paths.history
|
||||||
|
try:
|
||||||
|
lines = history.read_text().splitlines()
|
||||||
|
last = lines[-1]
|
||||||
|
current = Servers.record_to_fqip(self.record)
|
||||||
|
return last == current
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
@call_on_thread(dialog.querying)
|
@call_on_thread(dialog.querying)
|
||||||
def query_details(self, record: Servers.Record) -> None:
|
def query_details(self, record: Servers.Record) -> None:
|
||||||
details = Servers.get_details(record)
|
details = Servers.get_details(record)
|
||||||
@@ -245,11 +261,14 @@ class ConnectionManager:
|
|||||||
dialog = ExceptionDialog(self.controller, server_timeout)
|
dialog = ExceptionDialog(self.controller, server_timeout)
|
||||||
dialog.run()
|
dialog.run()
|
||||||
|
|
||||||
def _connect_steam(self) -> None:
|
def _connect_steam(self, menu_only: bool) -> None:
|
||||||
addr = f"{self.record.ip}:{self.record.gameport}"
|
addr = f"{self.record.ip}:{self.record.gameport}"
|
||||||
playername = self.controller.query_config(Preferences.NAME)
|
playername = self.controller.query_config(Preferences.NAME)
|
||||||
client = self.controller.query_config(Preferences.CLIENT)
|
client = self.controller.query_config(Preferences.CLIENT)
|
||||||
rc = connect(client, addr, self.appid, playername, self.remote_mod_ids)
|
if menu_only:
|
||||||
|
rc = load_to_menu(client, addr, self.appid, playername, self.remote_mod_ids)
|
||||||
|
else:
|
||||||
|
rc = connect(client, addr, self.appid, playername, self.remote_mod_ids)
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
# TODO: log/pop the error
|
# TODO: log/pop the error
|
||||||
func = StoredFunc(self.controller.update_status)
|
func = StoredFunc(self.controller.update_status)
|
||||||
@@ -271,10 +290,10 @@ class ConnectionManager:
|
|||||||
self.thread_man.set_cleanup_func(func)
|
self.thread_man.set_cleanup_func(func)
|
||||||
|
|
||||||
def _add_to_history_and_return(self) -> None:
|
def _add_to_history_and_return(self) -> None:
|
||||||
self.controller.add_to_history(self.history)
|
self.controller.add_to_history(self.history, self.record)
|
||||||
self.controller.open_page(NotebookPage.SERVERS)
|
self.controller.open_page(NotebookPage.SERVERS)
|
||||||
|
|
||||||
def _update_mods(self, raise_window: bool) -> None:
|
def _update_mods(self, raise_window: bool, menu_only: bool = False) -> None:
|
||||||
# NOTE: fast enqueue all mods in auto mode
|
# NOTE: fast enqueue all mods in auto mode
|
||||||
prefs = self.controller.get_prefs()
|
prefs = self.controller.get_prefs()
|
||||||
|
|
||||||
@@ -312,11 +331,11 @@ class ConnectionManager:
|
|||||||
update_signatures(self.missing_mods, prefs.paths.version)
|
update_signatures(self.missing_mods, prefs.paths.version)
|
||||||
# TODO: just push steam path directly
|
# TODO: just push steam path directly
|
||||||
rebuild_symlinks(prefs.paths.config)
|
rebuild_symlinks(prefs.paths.config)
|
||||||
self._connect_steam()
|
self._connect_steam(menu_only)
|
||||||
|
|
||||||
@call_on_thread(waiting_for_mods, show_cancel=True)
|
@call_on_thread(waiting_for_mods, show_cancel=True)
|
||||||
def update_and_connect(self, raise_window: bool) -> None:
|
def update_and_connect(self, raise_window: bool, menu_only: bool = False) -> None:
|
||||||
if len(self.missing_mods) > 0:
|
if len(self.missing_mods) > 0:
|
||||||
self._update_mods(raise_window)
|
self._update_mods(raise_window, menu_only)
|
||||||
else:
|
else:
|
||||||
self._connect_steam()
|
self._connect_steam(menu_only)
|
||||||
@@ -9,7 +9,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class FilterManager:
|
class FilterManager:
|
||||||
def __init__(self) -> None:
|
def __init__(self, show_empty: bool = False, show_full: bool = False) -> None:
|
||||||
|
|
||||||
self.map_store = ModelFactory().make_map_store()
|
self.map_store = ModelFactory().make_map_store()
|
||||||
|
|
||||||
@@ -17,10 +17,10 @@ class FilterManager:
|
|||||||
self.default_filters = {
|
self.default_filters = {
|
||||||
strings.filter_1pp: True,
|
strings.filter_1pp: True,
|
||||||
strings.filter_day: True,
|
strings.filter_day: True,
|
||||||
strings.filter_empty: False,
|
strings.filter_empty: show_empty,
|
||||||
strings.filter_3pp: True,
|
strings.filter_3pp: True,
|
||||||
strings.filter_night: True,
|
strings.filter_night: True,
|
||||||
strings.filter_full: False,
|
strings.filter_full: show_full,
|
||||||
strings.filter_lowpop: True,
|
strings.filter_lowpop: True,
|
||||||
strings.filter_nonascii: False,
|
strings.filter_nonascii: False,
|
||||||
strings.filter_duplicate: False,
|
strings.filter_duplicate: False,
|
||||||
|
|||||||
@@ -85,8 +85,9 @@ class ProxyModelManager:
|
|||||||
|
|
||||||
if found is False:
|
if found is False:
|
||||||
self.control_model.append(history)
|
self.control_model.append(history)
|
||||||
if len(self.control_model) == 11:
|
|
||||||
del self.control_model[0]
|
# if len(self.control_model) == 11:
|
||||||
|
# del self.control_model[0]
|
||||||
|
|
||||||
self.filter(FilterMode.INITIAL, skip_cache=True)
|
self.filter(FilterMode.INITIAL, skip_cache=True)
|
||||||
|
|
||||||
|
|||||||
+22
-9
@@ -14,6 +14,7 @@ from dzgui.const.constants import (
|
|||||||
)
|
)
|
||||||
from dzgui.const.enum import FilterMode, Preferences, ServerTab
|
from dzgui.const.enum import FilterMode, Preferences, ServerTab
|
||||||
from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager
|
from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager
|
||||||
|
from dzgui.strings import dialogs
|
||||||
from dzgui.util.strings import api_warn_msg, dialog
|
from dzgui.util.strings import api_warn_msg, dialog
|
||||||
from dzgui.views.dialogs.generic import ExceptionDialog
|
from dzgui.views.dialogs.generic import ExceptionDialog
|
||||||
|
|
||||||
@@ -23,7 +24,7 @@ gi.require_version("Gtk", "3.0")
|
|||||||
from gi.repository import Gtk # noqa E402
|
from gi.repository import Gtk # noqa E402
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.api.servers import A2SInfo
|
from dzgui.api.servers import A2SInfo, Record
|
||||||
from dzgui.controllers.mc import Controller
|
from dzgui.controllers.mc import Controller
|
||||||
from dzgui.model.proxy_model import ProxyModelManager
|
from dzgui.model.proxy_model import ProxyModelManager
|
||||||
from dzgui.views.trees.tree_servers import ServerTreeView
|
from dzgui.views.trees.tree_servers import ServerTreeView
|
||||||
@@ -130,7 +131,7 @@ class ServerModelManager:
|
|||||||
|
|
||||||
servers = []
|
servers = []
|
||||||
ports = range(1, 256)
|
ports = range(1, 256)
|
||||||
failure_func = StoredFunc(self._cleanup_on_failure)
|
failure_func = StoredFunc(self._cleanup_on_lan_failure)
|
||||||
|
|
||||||
event = threading.Event()
|
event = threading.Event()
|
||||||
with ThreadPoolExecutor() as executor:
|
with ThreadPoolExecutor() as executor:
|
||||||
@@ -225,15 +226,12 @@ class ServerModelManager:
|
|||||||
) = None,
|
) = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
proxy_man = self._get_proxy_man()
|
proxy_man = self._get_proxy_man()
|
||||||
config_man = self.controller.get_config_man()
|
|
||||||
if rows is not None:
|
if rows is not None:
|
||||||
records = rows
|
records = rows
|
||||||
else:
|
else:
|
||||||
control_model = proxy_man.get_control()
|
control_model = proxy_man.get_control()
|
||||||
records = control_model
|
records = control_model
|
||||||
|
|
||||||
config_man.update_history_file(records)
|
|
||||||
config_man.update_history_file(records)
|
|
||||||
self._sort_unique_maps(records)
|
self._sort_unique_maps(records)
|
||||||
proxy = proxy_man.get_proxy_model()
|
proxy = proxy_man.get_proxy_model()
|
||||||
self.tv.set_model(proxy)
|
self.tv.set_model(proxy)
|
||||||
@@ -249,15 +247,20 @@ class ServerModelManager:
|
|||||||
self.emitter.emit("load_maps", store)
|
self.emitter.emit("load_maps", store)
|
||||||
# self.emitter.emit("servers_loaded_init")
|
# self.emitter.emit("servers_loaded_init")
|
||||||
|
|
||||||
def add_to_history(self, record: dict[str, Any]) -> None:
|
# TODO: dataclass for record rows; check for other dict annotations
|
||||||
|
def add_to_history(self, data: tuple[dict[str, Any], "Record"]) -> None:
|
||||||
|
row, record = data
|
||||||
proxy_man = self._get_proxy_man()
|
proxy_man = self._get_proxy_man()
|
||||||
rows = Servers.parse_json([record])
|
rows = Servers.parse_json([row])
|
||||||
try:
|
try:
|
||||||
proxy_man.append_row_to_history(rows[0])
|
proxy_man.append_row_to_history(rows[0])
|
||||||
|
self.update_history()
|
||||||
except Exception:
|
except Exception:
|
||||||
self.update_history(rows)
|
self.update_history(rows)
|
||||||
return
|
|
||||||
self.update_history()
|
fqip = Servers.record_to_fqip(record)
|
||||||
|
config_man = self.controller.get_config_man()
|
||||||
|
config_man.update_history_file(fqip)
|
||||||
|
|
||||||
def remove_from_history(self, record: Servers.Record) -> None:
|
def remove_from_history(self, record: Servers.Record) -> None:
|
||||||
proxy_man = self._get_proxy_man()
|
proxy_man = self._get_proxy_man()
|
||||||
@@ -403,6 +406,16 @@ class ServerModelManager:
|
|||||||
if self.first_iteration:
|
if self.first_iteration:
|
||||||
self._update_maps()
|
self._update_maps()
|
||||||
|
|
||||||
|
def _cleanup_on_lan_failure(self, show_dialog: bool = True) -> None:
|
||||||
|
if self.preserve_on_fail is False:
|
||||||
|
self.tv.set_model(None)
|
||||||
|
filter_man = self.tv.get_filter_man()
|
||||||
|
filter_man.set_unique_maps([])
|
||||||
|
|
||||||
|
if show_dialog:
|
||||||
|
dialog = ExceptionDialog(self.controller, dialogs.load_error_lan)
|
||||||
|
dialog.run()
|
||||||
|
|
||||||
def _cleanup_on_failure(self, show_dialog: bool = True) -> None:
|
def _cleanup_on_failure(self, show_dialog: bool = True) -> None:
|
||||||
# TODO: disable map, keyword, and filter widgets if model is None
|
# TODO: disable map, keyword, and filter widgets if model is None
|
||||||
# signal driven (servers_empty, servers_failed_to_load)
|
# signal driven (servers_empty, servers_failed_to_load)
|
||||||
|
|||||||
@@ -4,3 +4,5 @@ waiting_for_mods = "Waiting for Steam to update mods"
|
|||||||
fetching_update = "Fetching update"
|
fetching_update = "Fetching update"
|
||||||
failed_to_update = "Failed to update DZGUI executable"
|
failed_to_update = "Failed to update DZGUI executable"
|
||||||
update_success = "Updated successfully. Please exit and relaunch."
|
update_success = "Updated successfully. Please exit and relaunch."
|
||||||
|
|
||||||
|
load_error_lan = "Failed to find any servers on your network.\nCheck the server query port or your firewall settings."
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
update_mods = "Update mods and connect"
|
update_mods = "Update mods and connect"
|
||||||
connect = "Connect"
|
connect = "Connect"
|
||||||
|
connect_last = "Load to menu screen"
|
||||||
|
connect_last_tooltip = (
|
||||||
|
"Launch DayZ with these mods (if any) and\ngo to the menu screen without connecting."
|
||||||
|
)
|
||||||
back = "Back"
|
back = "Back"
|
||||||
cancel = "Cancel"
|
cancel = "Cancel"
|
||||||
warnings = "Warnings"
|
warnings = "Warnings"
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def is_writeable(path: "Path") -> bool:
|
||||||
|
now = int(datetime.now().timestamp())
|
||||||
|
suffix = ".dzgtmp"
|
||||||
|
file = str(now) + suffix
|
||||||
|
filepath = Path(str(path) + "_" + file)
|
||||||
|
try:
|
||||||
|
filepath.touch()
|
||||||
|
filepath.unlink()
|
||||||
|
return True
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
@@ -611,6 +611,7 @@ atomic_buttons = AtomicButton(
|
|||||||
keys_tooltip="Toggles the keybindings dialog",
|
keys_tooltip="Toggles the keybindings dialog",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
gtk_theme_missing = "No GTK theme provided"
|
||||||
steam_icon_missing = "Steam icon not found in IconTheme"
|
steam_icon_missing = "Steam icon not found in IconTheme"
|
||||||
|
|
||||||
missing_changelog = "Error: failed to read changelog"
|
missing_changelog = "Error: failed to read changelog"
|
||||||
|
|||||||
+9
-5
@@ -323,8 +323,12 @@ class App(Gtk.Application):
|
|||||||
|
|
||||||
MainController = Controller()
|
MainController = Controller()
|
||||||
|
|
||||||
theme = Gtk.IconTheme.get_default()
|
try:
|
||||||
icons = theme.list_icons(None)
|
theme = Gtk.IconTheme.get_default()
|
||||||
if STEAM_ICON not in icons:
|
icons = theme.list_icons(None)
|
||||||
logger.warn(strings.steam_icon_missing)
|
if STEAM_ICON not in icons:
|
||||||
warnings.warn(strings.steam_icon_missing, stacklevel=2)
|
logger.warn(strings.steam_icon_missing)
|
||||||
|
warnings.warn(strings.steam_icon_missing, stacklevel=2)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(e)
|
||||||
|
warnings.warn(strings.gtk_theme_missing, stacklevel=2)
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
from typing import Literal, TYPE_CHECKING
|
from typing import Literal, TYPE_CHECKING
|
||||||
|
|
||||||
from dzgui.const.constants import (
|
from dzgui.const.constants import (
|
||||||
@@ -13,6 +14,7 @@ from dzgui.const.constants import (
|
|||||||
from dzgui.const.enum import ServerTab
|
from dzgui.const.enum import ServerTab
|
||||||
from dzgui.managers.update import UpdateManager
|
from dzgui.managers.update import UpdateManager
|
||||||
from dzgui.util.clip import copy_clipboard
|
from dzgui.util.clip import copy_clipboard
|
||||||
|
from dzgui.util.file import is_writeable
|
||||||
from dzgui.views.components.buttonbox import ButtonBox
|
from dzgui.views.components.buttonbox import ButtonBox
|
||||||
from dzgui.views.components.filter_panel import FilterPanel
|
from dzgui.views.components.filter_panel import FilterPanel
|
||||||
from dzgui.views.components.mod_panel import ModSelectionPanel
|
from dzgui.views.components.mod_panel import ModSelectionPanel
|
||||||
@@ -87,11 +89,12 @@ class RightPanel(Gtk.Box):
|
|||||||
)
|
)
|
||||||
exe_path = os.getenv("PYAPP")
|
exe_path = os.getenv("PYAPP")
|
||||||
if exe_path and update is not None:
|
if exe_path and update is not None:
|
||||||
self.update_button.set_halign(Gtk.Align.END)
|
if is_writeable(Path(exe_path)) is True:
|
||||||
self.gutter_box.add(self.update_button)
|
self.update_button.set_halign(Gtk.Align.END)
|
||||||
self.update_button.connect(
|
self.gutter_box.add(self.update_button)
|
||||||
"clicked", self._on_update_button_clicked, exe_path, update
|
self.update_button.connect(
|
||||||
)
|
"clicked", self._on_update_button_clicked, exe_path, update
|
||||||
|
)
|
||||||
self.gutter_box.add(eb)
|
self.gutter_box.add(eb)
|
||||||
self.pack_start(self.gutter_box, NO_EXPAND, FILL, NO_PADDING)
|
self.pack_start(self.gutter_box, NO_EXPAND, FILL, NO_PADDING)
|
||||||
|
|
||||||
@@ -108,7 +111,7 @@ class RightPanel(Gtk.Box):
|
|||||||
return
|
return
|
||||||
self.filters_vbox.set_sensitive(False)
|
self.filters_vbox.set_sensitive(False)
|
||||||
# TODO: unless it is lan page
|
# TODO: unless it is lan page
|
||||||
#self.refresh_button.set_sensitive(False)
|
# self.refresh_button.set_sensitive(False)
|
||||||
|
|
||||||
def _on_lan_page_init(self, emitter: "Emitter") -> None:
|
def _on_lan_page_init(self, emitter: "Emitter") -> None:
|
||||||
self.filters_vbox.set_sensitive(False)
|
self.filters_vbox.set_sensitive(False)
|
||||||
|
|||||||
@@ -106,6 +106,9 @@ class PreConnectionAssistant(Gtk.ScrolledWindow):
|
|||||||
label=preconnect.back, halign=Gtk.Align.END, hexpand=True
|
label=preconnect.back, halign=Gtk.Align.END, hexpand=True
|
||||||
)
|
)
|
||||||
self.ok = Gtk.Button(label=preconnect.update_mods, halign=Gtk.Align.END)
|
self.ok = Gtk.Button(label=preconnect.update_mods, halign=Gtk.Align.END)
|
||||||
|
self.connect_last = Gtk.Button(
|
||||||
|
label=preconnect.connect_last, halign=Gtk.Align.END, tooltip_text=preconnect.connect_last_tooltip
|
||||||
|
)
|
||||||
|
|
||||||
# TODO: abstract
|
# TODO: abstract
|
||||||
self.raise_window = Gtk.CheckButton(
|
self.raise_window = Gtk.CheckButton(
|
||||||
@@ -126,13 +129,14 @@ class PreConnectionAssistant(Gtk.ScrolledWindow):
|
|||||||
spacing=5,
|
spacing=5,
|
||||||
)
|
)
|
||||||
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
|
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
|
||||||
for button in self.back, self.ok:
|
for button in self.back, self.ok, self.connect_last:
|
||||||
box.add(button)
|
box.add(button)
|
||||||
for el in self.raise_window, box:
|
for el in self.raise_window, box:
|
||||||
self.button_box.add(el)
|
self.button_box.add(el)
|
||||||
|
|
||||||
self.back.connect("clicked", self._on_back_clicked)
|
self.back.connect("clicked", self._on_back_clicked)
|
||||||
self.ok.connect("clicked", self._on_ok_clicked)
|
self.ok.connect("clicked", self._on_ok_clicked)
|
||||||
|
self.connect_last.connect("clicked", self._on_connect_last_clicked)
|
||||||
|
|
||||||
self.title = Gtk.Label(label="")
|
self.title = Gtk.Label(label="")
|
||||||
add_class(self.title, "preconnect-heading")
|
add_class(self.title, "preconnect-heading")
|
||||||
@@ -213,6 +217,9 @@ class PreConnectionAssistant(Gtk.ScrolledWindow):
|
|||||||
if event.keyval == Gdk.KEY_u:
|
if event.keyval == Gdk.KEY_u:
|
||||||
self.ok.emit("clicked")
|
self.ok.emit("clicked")
|
||||||
|
|
||||||
|
def _on_connect_last_clicked(self, button: Gtk.Button) -> None:
|
||||||
|
self.controller.update_and_load_to_menu(self.raise_window.get_active())
|
||||||
|
|
||||||
def _on_ok_clicked(self, button: Gtk.Button) -> None:
|
def _on_ok_clicked(self, button: Gtk.Button) -> None:
|
||||||
self.controller.update_and_connect(self.raise_window.get_active())
|
self.controller.update_and_connect(self.raise_window.get_active())
|
||||||
|
|
||||||
@@ -308,6 +315,11 @@ class PreConnectionAssistant(Gtk.ScrolledWindow):
|
|||||||
prefix = preconnect.total_mods
|
prefix = preconnect.total_mods
|
||||||
self.mod_count.set_text(f"{prefix}{str(total_mods)}.{suffix}")
|
self.mod_count.set_text(f"{prefix}{str(total_mods)}.{suffix}")
|
||||||
|
|
||||||
|
if prereqs.is_last_server:
|
||||||
|
self.connect_last.show()
|
||||||
|
else:
|
||||||
|
self.connect_last.hide()
|
||||||
|
|
||||||
self._process_warnings(prereqs)
|
self._process_warnings(prereqs)
|
||||||
if self.tree.is_visible():
|
if self.tree.is_visible():
|
||||||
self.tree.grab_focus()
|
self.tree.grab_focus()
|
||||||
|
|||||||
@@ -47,7 +47,13 @@ class ServerNotebook(Gtk.ScrolledWindow):
|
|||||||
self.recent = ServerTreeView(
|
self.recent = ServerTreeView(
|
||||||
controller, ServerTab.RECENT, ContextMenuGroup.RECENT
|
controller, ServerTab.RECENT, ContextMenuGroup.RECENT
|
||||||
)
|
)
|
||||||
self.lan = ServerTreeView(controller, ServerTab.LAN, ContextMenuGroup.SCAN_LAN)
|
self.lan = ServerTreeView(
|
||||||
|
controller,
|
||||||
|
ServerTab.LAN,
|
||||||
|
ContextMenuGroup.SCAN_LAN,
|
||||||
|
show_empty=True,
|
||||||
|
show_full=True,
|
||||||
|
)
|
||||||
|
|
||||||
tabs = [
|
tabs = [
|
||||||
(self.browser, server_labels.browser),
|
(self.browser, server_labels.browser),
|
||||||
|
|||||||
@@ -38,7 +38,12 @@ QUEUE_CHECK_DELAY = 200
|
|||||||
|
|
||||||
class ServerTreeView(ContextMixin, TreeView): # type: ignore
|
class ServerTreeView(ContextMixin, TreeView): # type: ignore
|
||||||
def __init__(
|
def __init__(
|
||||||
self, controller: "Controller", enum: ServerTab, menu: ContextMenuGroup
|
self,
|
||||||
|
controller: "Controller",
|
||||||
|
enum: ServerTab,
|
||||||
|
menu: ContextMenuGroup,
|
||||||
|
show_empty: bool = False,
|
||||||
|
show_full: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(controller, menu=menu)
|
super().__init__(controller, menu=menu)
|
||||||
|
|
||||||
@@ -48,7 +53,7 @@ class ServerTreeView(ContextMixin, TreeView): # type: ignore
|
|||||||
|
|
||||||
self.loaded = False
|
self.loaded = False
|
||||||
|
|
||||||
self.filter_man = FilterManager()
|
self.filter_man = FilterManager(show_empty, show_full)
|
||||||
self.proxy_man = ProxyModelManager(self.filter_man)
|
self.proxy_man = ProxyModelManager(self.filter_man)
|
||||||
model = self.proxy_man.get_proxy_model()
|
model = self.proxy_man.get_proxy_model()
|
||||||
self.set_model(model)
|
self.set_model(model)
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ description = "DayZ server browser and mod manager for Linux"
|
|||||||
authors = [
|
authors = [
|
||||||
{name = "aclist"}
|
{name = "aclist"}
|
||||||
]
|
]
|
||||||
version = "7.0.0b7"
|
version = "7.0.0b8"
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
license-files = ["LICENSE"]
|
license-files = ["LICENSE"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|||||||
+5
-2
@@ -53,7 +53,8 @@ def untar(file: str, glob: str, renamed: str) -> None:
|
|||||||
|
|
||||||
def install_deps() -> None:
|
def install_deps() -> None:
|
||||||
pip = build_dir.joinpath(f"{CPYTHON_BUILD}/bin/pip")
|
pip = build_dir.joinpath(f"{CPYTHON_BUILD}/bin/pip")
|
||||||
subprocess.run([pip, "install", root])
|
proc = subprocess.run([pip, "install", root])
|
||||||
|
return proc.returncode
|
||||||
|
|
||||||
|
|
||||||
def repackage() -> None:
|
def repackage() -> None:
|
||||||
@@ -66,7 +67,9 @@ def repackage() -> None:
|
|||||||
def rebuild_cpython() -> None:
|
def rebuild_cpython() -> None:
|
||||||
if cpython_dir.is_dir() is False:
|
if cpython_dir.is_dir() is False:
|
||||||
get_cpython()
|
get_cpython()
|
||||||
install_deps()
|
code = install_deps()
|
||||||
|
if code != 0:
|
||||||
|
return
|
||||||
repackage()
|
repackage()
|
||||||
return get_packaged_version()
|
return get_packaged_version()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
from dzgui.config.ipdb import get_ipdb
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
import pytest
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.slow
|
||||||
|
def test_stripping():
|
||||||
|
d = tempfile.TemporaryDirectory()
|
||||||
|
path = Path(d.name).joinpath("ips.csv")
|
||||||
|
get_ipdb(path)
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
first = f.readline().decode().rstrip()
|
||||||
|
try:
|
||||||
|
f.seek(-2, os.SEEK_END)
|
||||||
|
while f.read(1) != b"\n":
|
||||||
|
f.seek(-2, os.SEEK_CUR)
|
||||||
|
except OSError:
|
||||||
|
f.seek(0)
|
||||||
|
last = f.read().decode().rstrip()
|
||||||
|
|
||||||
|
first_els = first.split(",")
|
||||||
|
last_els = last.split(",")
|
||||||
|
|
||||||
|
assert first_els[0] == "0.0.0.0"
|
||||||
|
assert last_els[0] == "224.0.0.0"
|
||||||
Reference in new issue
Block a user