mirror of
https://github.com/aclist/dztui.git
synced 2026-08-29 19:27:13 +02:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
252f07cbec | ||
|
|
a0f610ead7 | ||
|
|
ca4bcfc1a5 | ||
|
|
ac4892d4f0 | ||
|
|
240b660923 | ||
|
|
150dfbca72 | ||
|
|
4b78114174 | ||
|
|
d1bde9901d | ||
|
|
c5eba364d9 | ||
|
|
5460b53c26 | ||
|
|
99301b3312 | ||
|
|
8d30af9dee | ||
|
|
8a8109b7df | ||
|
|
e4eaf25c8f | ||
|
|
0daf6089b9 | ||
|
|
7e4197cdbc | ||
|
|
c0f9d8a508 | ||
|
|
2da2b19001 | ||
|
|
62936fa7b7 | ||
|
|
e9fecc7fb7 | ||
|
|
11c29112c2 | ||
|
|
317f60eddd | ||
|
|
9fbe5029e1 | ||
|
|
896802cd6f |
3
.gitignore
vendored
3
.gitignore
vendored
@ -1 +1,4 @@
|
||||
tags
|
||||
__pycache__
|
||||
pre-commit
|
||||
tests/
|
||||
|
||||
547
CHANGELOG.md
547
CHANGELOG.md
@ -1,56 +1,141 @@
|
||||
# Changelog
|
||||
|
||||
## [6.0.1] 2026-01-16
|
||||
## [6.0.6-beta.1] 2026-05-17
|
||||
## Changed
|
||||
- Update symlink hash logic to align with DZGUI 7 beta
|
||||
- Update A2S helper to latest commit
|
||||
|
||||
## [6.0.5-beta.2] 2026-04-17
|
||||
## Changed
|
||||
- During server connection attempts, only download mods local to specific server
|
||||
|
||||
## [6.0.5-beta.1] 2026-04-13
|
||||
## Fixed
|
||||
- Update A2S module checksums to support servers with missing description field
|
||||
## Changed
|
||||
- Bump version to reflect hotfixes on stable branch
|
||||
|
||||
## [6.0.2-beta.5] 2026-04-12
|
||||
## Fixed
|
||||
- Use conditional logic when testing GLibUnix namespaces
|
||||
|
||||
## [6.0.2-beta.4] 2026-04-10
|
||||
## Fixed
|
||||
- Use GLibUnix namespace when setting up SIGINT handling
|
||||
|
||||
## [6.0.2-beta.3] 2026-01-26
|
||||
## Fixed
|
||||
- Raise error correctly from module
|
||||
|
||||
## [6.0.2-beta.2] 2026-01-18
|
||||
## Fixed
|
||||
- Helpers not being downloaded (uncommented boilerplate)
|
||||
|
||||
## [6.0.2-beta.1] 2026-01-18
|
||||
## Fixed
|
||||
- Do not spawn dialogs from inside of thread when fetching prereqs
|
||||
## Changed
|
||||
- Optimize time complexity of startup checks
|
||||
- Optimize coordinate calculation by using local records first
|
||||
- Rename some internal functions
|
||||
- Wrap entire startup process in dialog
|
||||
|
||||
## [6.0.1-beta.1] 2026-01-16
|
||||
## Fixed
|
||||
- Explicitly use Python 3.13 when calling subprocesses
|
||||
|
||||
## [6.0.0] 2026-01-14
|
||||
## [6.0.0-beta.20] 2026-01-14
|
||||
## Fixed
|
||||
- Write cache file after wiping
|
||||
|
||||
## [6.0.0-beta.19] 2026-01-14
|
||||
## Fixed
|
||||
- Steam Deck compatible script path expansion
|
||||
|
||||
## [6.0.0-beta.18] 2026-01-14
|
||||
## Fixed
|
||||
- Update UI checksums
|
||||
|
||||
## [6.0.0-beta.17] 2026-01-14
|
||||
## Added
|
||||
- Support DayZ Experimental
|
||||
- Ping display on server tables
|
||||
- Save descriptive text notes on a per-server basis
|
||||
- Speed up load time and navigation of server tables
|
||||
- More robust threading and cache system when filtering servers
|
||||
- Show additional DayZ client information in Options menu
|
||||
- Warn user of DayZ client version mismatches
|
||||
- Support clickable hyperlinks in dialogs
|
||||
- Rich server metadata details dialog (right-click on server table)
|
||||
- Filter by modded servers
|
||||
- Breadcrumbs showing current menu context
|
||||
- Dedicated changelog page
|
||||
- Dedicated keybindings page
|
||||
- Dedicated settings page ("Options")
|
||||
- Support sandboxed version of Flatpak
|
||||
- Vim-style navigation keybindings
|
||||
- Added "Return to main menu" button to dialog windows when failing to load server table
|
||||
- Dynamic context menus for modded servers
|
||||
- Additional keybindings for new filter toggles
|
||||
- If a server is locked, prompt user when connecting
|
||||
- Internal flag to allow distro-packaged releases to disable in-app updates
|
||||
- Pre-boot checks to test whether script was invoked directly
|
||||
- Commandline usage help text (GaryBlackbourne)
|
||||
- More descriptive help dialogs when initial dependencies are missing
|
||||
|
||||
## [6.0.0-beta.16] 2026-01-04
|
||||
## Fixed
|
||||
- Regression introduced in previous hotfix
|
||||
|
||||
## [6.0.0-beta.15] 2025-12-27
|
||||
## Fixed
|
||||
- Path discovery during first-time setup when parsing filepaths with whitespaces
|
||||
- First-time setup dialog continuously triggering when DayZ install path had whitespaces in it
|
||||
|
||||
## [6.0.0-beta.14] 2025-12-09
|
||||
## Fixed
|
||||
- Dialogs with newlines breaking output in log table
|
||||
|
||||
## [6.0.0-beta.13] 2025-11-29
|
||||
## Fixed
|
||||
- Server filter panel not being hidden when entering other page contexts
|
||||
- Path to remote changelog being constructed incorrectly
|
||||
|
||||
## [6.0.0-beta.12] 2025-11-14
|
||||
## Changed
|
||||
- Require Python 3.13
|
||||
## Fixed
|
||||
- Normalize beta version
|
||||
|
||||
## [6.0.0-beta.11] 2025-11-13
|
||||
## Added
|
||||
- Support sandboxed flatpak
|
||||
|
||||
## Changed
|
||||
- Preferred client from radio toggle to combobox
|
||||
|
||||
## Fixed
|
||||
- Config file erroneously getting updated when populating settings menu
|
||||
|
||||
## [6.0.0-beta.10] 2025-11-10
|
||||
## Fixed
|
||||
- Python 3.14 error in finally block
|
||||
- Require Python version between 3.11 and 3.12
|
||||
|
||||
## [6.0.0-beta.9] 2025-11-05
|
||||
## Fixed
|
||||
- UI not being constructed correctly if CHANGELOG.md was missing
|
||||
|
||||
## [6.0.0-beta.8] 2025-11-04
|
||||
## Added
|
||||
- Internal flag to allow distribution packaged releases to disable in-app updates
|
||||
- Pre-boot check to test whether script was invoked directly
|
||||
- Commandline usage help text
|
||||
|
||||
## Fixed
|
||||
- ESC key destroying wait dialogs while a thread is pending
|
||||
- Tooltip signals being erroneously drawn on main menu
|
||||
- Leaky variable name in dialog titles
|
||||
- Prevent extraneous signals from propagating when column width is adjusted
|
||||
- Script failing to start when remote endpoints are unavailable (GaryBlackbourne)
|
||||
- Key stickiness when quickly navigating through entries in tables
|
||||
- Servers returning malformed A2S_INFO blocking server browser from loading
|
||||
- Normalized buttons in dialogs and restored proper padding
|
||||
- GTK errors being emitted to stdout when inserting debug table
|
||||
- Entry dialog sensitivity when validating API keys
|
||||
- Additional validation on entry dialogs to prevent submitting empty text
|
||||
- Suppress errors during pre-boot checks when mods are not installed
|
||||
- Centered filter checkboxes within panel
|
||||
- Do not pop unhighlight/select stale buttons if no stale mods exist
|
||||
- Improved keybinding interaction with side panels
|
||||
- Suppress typeahead search in mod dialogs
|
||||
- Do not trigger global API cooldown if no LAN/favorite servers are found
|
||||
- Fix table column expansion in server mod dialogs
|
||||
- Missing parameters when closing application via window decorations
|
||||
- Statusbar contents not updating on certain pages
|
||||
- Prevent debug button from activating when in certain input fields
|
||||
- Window size not updating correctly when unmaximizing window after changing "fullscreen at boot" setting
|
||||
- Branch toggle signal being emitted when entering Options menu from other contexts
|
||||
|
||||
## Changed
|
||||
- Optimize time complexity of pre-boot checks (GaryBlackbourne)
|
||||
- Moved location of notes file
|
||||
- Rewrote distance calculation module (GaryBlackbourne)
|
||||
|
||||
## [6.0.0-beta.7] 2025-09-20
|
||||
## Added
|
||||
- Users can save text notes to describe servers
|
||||
|
||||
## Changed
|
||||
- State file serialization methods
|
||||
|
||||
## [6.0.0-beta.6] 2025-09-12
|
||||
## Added
|
||||
- Support DayZ Experimental
|
||||
- Show additional client information in Options menu
|
||||
- Warn user of client version mismatches
|
||||
- Support clickable hyperlinks
|
||||
|
||||
## Fixed
|
||||
- Mods rarely not appearing in local mod list if download completed too quickly
|
||||
- Statusbar not updating when clicking a row after spamming keyboard input
|
||||
- Extraneous logs being generated when subscribing to mods
|
||||
@ -58,188 +143,342 @@
|
||||
- Newline terminators in history file
|
||||
- Floating point number calculation
|
||||
- Window resizing too small if no prior resolution was set
|
||||
- ESC key destroying wait dialogs while a thread is pending
|
||||
- Tooltip signals being erroneously emitted on main menu
|
||||
- UI not being constructed correctly if CHANGELOG.md was missing
|
||||
- Config file erroneously getting updated when populating settings menu
|
||||
- Leaky variable name in dialog titles
|
||||
- Prevent extraneous signals from propagating when column width is adjusted
|
||||
- Server filter panel not being hidden when entering other page contexts
|
||||
- Path to remote changelog being constructed incorrectly
|
||||
- Dialogs with newlines breaking output in log table
|
||||
- Path discovery during first-time setup when parsing filepaths with whitespaces
|
||||
- First-time setup dialog continuously triggering when DayZ install path had whitespaces in it
|
||||
|
||||
## [6.0.0-beta.5] 2025-08-20
|
||||
## Fixed
|
||||
- Servers returning malformed A2S_INFO blocking server browser from loading
|
||||
|
||||
## [6.0.0-beta.4] 2025-08-13
|
||||
## Fixed
|
||||
- Branch toggle signal being emitted when entering Options menu from other contexts
|
||||
|
||||
## [6.0.0-beta.3] 2025-08-08
|
||||
## Added
|
||||
- Additional server metadata to details dialog
|
||||
- Prompt user when connecting if a server is locked
|
||||
- Dedicated settings page ("Options")
|
||||
|
||||
## Fixed
|
||||
- Statusbar contents not updating on certain pages
|
||||
- Breadcrumbs not updating when returning from keybindings page
|
||||
- Wrap long descriptions in server details
|
||||
- Prevent debug button from activating when in certain input fields
|
||||
- Window size not updating correctly when unmaximizing window after changing "fullscreen at boot" setting
|
||||
|
||||
## Dropped
|
||||
- Extraneous information from right statusbar
|
||||
|
||||
## [6.0.0-beta.2] 2025-07-29
|
||||
## Fixed
|
||||
- Missing parameters when closing application via window decorations
|
||||
|
||||
## [6.0.0-beta.1] 2025-07-29
|
||||
## Added
|
||||
- Ping column to server browsing contexts
|
||||
- Server details dialog to server browsing contexts
|
||||
- Filter by modded servers
|
||||
- Breadcrumbs showing current menu context to top of window
|
||||
- Speed up load time and navigation of server tables
|
||||
- More robust threading and cache system when filtering servers
|
||||
- Dedicated changelog page
|
||||
- Dedicated keybindings page
|
||||
- Vim-style navigation keybindings
|
||||
- Additional validation on entry dialogs to prevent submitting empty text
|
||||
- Added "Return to main menu" button to dialog windows when failing to load server table
|
||||
- Dynamic context menus for modded servers
|
||||
- Additional keybindings for filter toggles
|
||||
|
||||
## Fixed
|
||||
- Entry dialog sensitivity when validating API keys
|
||||
- Normalized buttons in dialogs and restored proper padding
|
||||
- GTK errors being emitted to stdout when inserting debug table
|
||||
- Suppress errors during pre-boot checks when mods are not installed
|
||||
- Centered filter checkboxes within panel
|
||||
- Do not pop unhighlight/select stale buttons if no stale mods exist
|
||||
- Improved keybinding interaction with side panels
|
||||
- Key stickiness when quickly navigating through entries in tables
|
||||
- Suppress typeahead search in mod dialogs
|
||||
- Do not trigger global API cooldown if no LAN/favorite servers are found
|
||||
- Fix table column expansion in server mod dialogs
|
||||
|
||||
## Changed
|
||||
- Require Python 3.13
|
||||
- Reduced global API cooldown from 60s to 30s
|
||||
- Clarify dialog messages when DayZ path could not be found
|
||||
- Auto-focus first item when opening context menus
|
||||
- Opacity setting on side buttons when in a different context
|
||||
- Refactored BM API key validation to account for new key format
|
||||
- State file serialization methods
|
||||
- Optimize time complexity of pre-boot checks (GaryBlackbourne)
|
||||
- Rewrote distance calculation module (GaryBlackbourne)
|
||||
- Changed preferred client setting from radio toggle to combobox
|
||||
|
||||
## Dropped
|
||||
- Ping readout in statusbar
|
||||
- Extraneous information from right statusbar
|
||||
|
||||
## [5.8.3] 2026-01-04
|
||||
## Fixed
|
||||
- Normalize checksum numbers and dates
|
||||
|
||||
## [5.8.2] 2026-01-04
|
||||
## Fixed
|
||||
- Fix regression in symlink routine introduced by previous hotfix
|
||||
|
||||
## [5.8.1] 2025-12-27
|
||||
## Fixed
|
||||
- Path discovery during first-time setup when parsing filepaths with whitespaces
|
||||
- First-time setup dialog continuously triggering when DayZ install path had whitespaces in it
|
||||
|
||||
## [5.8.0] 2025-07-06
|
||||
## [5.8.0-beta.2] 2025-07-06
|
||||
## Added
|
||||
- Filter servers by official/unofficial status
|
||||
- Automatically fetch geolocation records
|
||||
## Changed
|
||||
- Updated internal versioning of helper files
|
||||
|
||||
## [5.8.0-beta.1] 2025-06-05
|
||||
## Added
|
||||
- Automatically fetch geolocation records
|
||||
## Fixed
|
||||
- Corrected erroneous 2024 date in prior changelog entries
|
||||
|
||||
## [5.7.0] 2025-04-17
|
||||
## Added
|
||||
- Save application dimensions when quitting and restore on subsequent boot
|
||||
## Fixed
|
||||
- Issues with the window exceeding the bounds of the screen when displaying some table contexts
|
||||
## [5.7.1-beta.1] 2025-04-17
|
||||
## Changed
|
||||
- More direct server ping query method that should return marginally more accurate times
|
||||
- Updated geolocation records
|
||||
|
||||
## [5.6.7] 2025-04-04
|
||||
## [5.7.0-beta.13] 2025-04-17
|
||||
## Fixed
|
||||
- Updated checksum for UI helper file
|
||||
|
||||
## [5.7.0-beta.12] 2025-04-04
|
||||
## Dropped
|
||||
- Removed extraneous pre-boot API checks that could cause error messages to be printed if the user had not set up an API key yet
|
||||
|
||||
## [5.6.6] 2025-03-16
|
||||
## Changed
|
||||
- Update IP database records for 2025-03
|
||||
## [5.7.0-beta.11] 2025-03-20
|
||||
## Fixed
|
||||
- Reduce startup time when testing remote APIs
|
||||
|
||||
## [5.6.5] 2025-03-04
|
||||
## [5.7.0-beta.10] 2025-03-15
|
||||
## Added
|
||||
- Restore application size on subsequent startup
|
||||
## Fixed
|
||||
- Grid scaling causes table to exceeds bounds of display viewport
|
||||
- Window exceeds taskbar on Steam Deck
|
||||
- Reduce possibility of timeouts when pinging servers
|
||||
## Changed
|
||||
- Packed filter checkbox buttons into a 3x3 grid
|
||||
- Updated IP database to 2025-03 records
|
||||
|
||||
## [5.7.0-beta.9] 2025-03-04
|
||||
## Fixed
|
||||
- Livonia server results being dropped from batch queries
|
||||
|
||||
## [5.6.4] 2025-02-10
|
||||
### Changed
|
||||
## [5.7.0-beta.8] 2025-02-10
|
||||
## Changed
|
||||
- Drop launch flag and check for invocation through Steam automatically
|
||||
|
||||
## [5.6.3] 2025-02-09
|
||||
### Changed
|
||||
## [5.7.0-beta.7] 2025-02-09
|
||||
## Changed
|
||||
- Update IP database records for 2025-02
|
||||
|
||||
## [5.6.2] 2025-01-22
|
||||
### Fixed
|
||||
- Resolve regression introduced with IP resolution feature in 5.6.0 (restores functionality of right-click action: Add to My Servers)
|
||||
## [5.7.0-beta.6] 2025-01-10
|
||||
## Fixed
|
||||
- Resolve regression introduced with IP resolution feature in 13c6813
|
||||
|
||||
## [5.6.1] 2025-01-10
|
||||
### Fixed
|
||||
- Add fallback support for jq 1.6
|
||||
## [5.7.0-beta.5] 2025-01-10
|
||||
## Changed
|
||||
- Stricter redacting of usernames (again)
|
||||
|
||||
## [5.6.0] 2025-01-06
|
||||
### Added
|
||||
- Application header bar and controls
|
||||
- Menu context subtitle in header bar
|
||||
- Bulk delete mods (via 'List installed mods' list). Not compatible with Manual Mod install mode
|
||||
- Highlight stale mods in mods list
|
||||
- Added --steam launch parameter
|
||||
- Open Steam workshop subscriptions dialog when switching from Manual to Auto mode
|
||||
- Hover tooltips to most buttons
|
||||
- Add in-app documentation link to Codeberg mirror
|
||||
- Additional logging
|
||||
### Fixed
|
||||
- Avoid sudo escalation if system map count is sufficient (jiriks74)
|
||||
- Duplicate dialog title on Steam Deck
|
||||
- Resolve remote IP when saving records for game servers with multiple hosts
|
||||
- More performant symlink traversal when checking for legacy links
|
||||
- Only iterate on missing symlinks at boot (400% performance uplift)
|
||||
- Fix for server list truncation causing some servers to not appear in results
|
||||
- Empty dialog popups if user manually deletes local mods while application is running
|
||||
- Update statusbar when removing servers from list/repopulating
|
||||
- Abort DayZ path discovery if Steam files are not synched
|
||||
- Race condition when checking for installed mods
|
||||
- Favorite server message not updating correctly
|
||||
- Improved handling for cases where there are no locally installed mods
|
||||
- Set up mod symlinks at boot, rather than only on server connect
|
||||
- Prevent context menus from opening when table is empty
|
||||
- When reloading table in-place, prevent duplicate panel elements from being added if already present
|
||||
- Clean up local mod signatures from versions file when deleting mods
|
||||
- Focus first row when opening mods list
|
||||
## [5.7.0-beta.4] 2025-01-10
|
||||
## Fixed
|
||||
- Clerical hotfix
|
||||
|
||||
## [5.7.0-beta.3] 2025-01-10
|
||||
## Changed
|
||||
- Support legacy jq syntax for Ubuntu variants
|
||||
|
||||
## [5.7.0-beta.2] 2025-01-07
|
||||
## Fixed
|
||||
- Stricter redacting of usernames
|
||||
- Omit nested directories when traversing symlinks
|
||||
|
||||
## [5.7.0-beta.1] 2025-01-07
|
||||
### Changed
|
||||
- Redact usernames in log files
|
||||
- Normalize version number
|
||||
|
||||
## [5.6.0-beta.21] 2025-01-06
|
||||
### Added
|
||||
- Add in-app documentation link to Codeberg mirror
|
||||
- Hover tooltips to most buttons
|
||||
### Fixed
|
||||
- Prevent ArrowUp/ArrowDown input when inside keyword field
|
||||
- Admonish user to restart Steam in error dialog if DayZ path could not be found
|
||||
- Refactor control flow for more robust contextual parsing
|
||||
- Stop sending modal dialog hints to outer window
|
||||
- Clean up signal emission
|
||||
- Clarify some error messages and normalize text formatting
|
||||
### Changed
|
||||
- Update forum URL
|
||||
- Reword Help section links to include destination
|
||||
- Update README.md
|
||||
- Update IP database to 2025-01
|
||||
- Reduce IP database size by 50%
|
||||
- Update documentation to 5.6.x standard
|
||||
### Dropped
|
||||
- Hall of Fame section from button links, to be moved inside documentation
|
||||
- Unused imports
|
||||
- Removed temporary mod ID output in debug logs
|
||||
- Removed Hall of Fame section from button links, moved inside documentation
|
||||
- Remove unused imports
|
||||
|
||||
## [5.5.3] 2024-12-13
|
||||
## [5.6.0-beta.20] 2024-12-23
|
||||
### Added
|
||||
- Output real and resolved mod ids to logs (temporary)
|
||||
- Added -steam launch parameter
|
||||
### Fixed
|
||||
- Only iterate on missing symlinks
|
||||
- Move logging up
|
||||
|
||||
## [5.6.0-beta.19] 2024-12-18
|
||||
### Added
|
||||
- Redact usernames in log files
|
||||
### Fixed
|
||||
- More performant symlink traversal when checking for legacy links
|
||||
|
||||
## [5.6.0-beta.18] 2024-12-14
|
||||
### Added
|
||||
- Open Steam workshop subscriptions dialog
|
||||
- Additional logging
|
||||
### Fixed
|
||||
- Empty dialog popups if user manually deletes local mods while application is running
|
||||
- Abort DayZ path discovery if VDF if Steam files are not synched
|
||||
- Avoid sudo escalation if system map count is sufficient (jiriks74)
|
||||
### Changed
|
||||
- Admonish user to restart Steam in error dialog if DayZ path could not be found
|
||||
|
||||
## [5.6.0-beta.17] 2024-12-14
|
||||
### Added
|
||||
- Additional logging
|
||||
|
||||
## [5.6.0-beta.16] 2024-12-13
|
||||
### Fixed
|
||||
- Add remote resource health checks before downloading updates
|
||||
### Added
|
||||
- Add fallback repository
|
||||
|
||||
## [5.5.1] 2024-12-03
|
||||
## [5.6.0-beta.11] 2024-12-07
|
||||
### Fixed
|
||||
- Support localized decimal separators when parsing installed mod sizes
|
||||
- Add missing function definition
|
||||
|
||||
## [5.5.0] 2024-11-10
|
||||
## [5.6.0-beta.10] 2024-12-04
|
||||
### Fixed
|
||||
- Untoggle highlight button when repopulating mod list
|
||||
- Resolve remote IP when saving records for game servers with multiple hosts
|
||||
- Update statusbar when removing servers from list/repopulating
|
||||
### Added:
|
||||
- "Select stale" button to bulk select mods marked as obsolete
|
||||
|
||||
## [5.6.0-beta.9] 2024-12-03
|
||||
### Fixed
|
||||
- Normalize user locale when parsing floats
|
||||
|
||||
## [5.6.0-beta.8] 2024-11-28
|
||||
### Fixed
|
||||
- Normalize user locale when parsing floats
|
||||
|
||||
## [5.6.0-beta.7] 2024-11-28
|
||||
### Changed
|
||||
- Add additional logging when fetching installed mods
|
||||
|
||||
## [5.6.0-beta.6] 2024-11-28
|
||||
### Fixed
|
||||
- Race condition when checking for installed mods
|
||||
|
||||
## [5.6.0-beta.5] 2024-11-21
|
||||
### Added
|
||||
- Support servers running DLC content (fixes Frostline servers)
|
||||
- Expose a toggle setting for whether to launch the application in fullscreen
|
||||
- Text autocompletion in maps search field (partial search)
|
||||
- Add disk space warning to popup dialog when downloading mods
|
||||
- Highlight stale mods in mods list
|
||||
### Fixed
|
||||
- Duplicate dialog title on Steam Deck
|
||||
|
||||
## [5.6.0-beta.4] 2024-11-20
|
||||
### Added
|
||||
- Application header bar and controls
|
||||
- Menu context subtitle in header bar
|
||||
### Changed
|
||||
- Refactor control flow for more robust contextual parsing
|
||||
- Stop sending modal dialog hints to outer window
|
||||
### Fixed
|
||||
- Favorite server message not updating correctly
|
||||
|
||||
## [5.6.0-beta.3] 2024-11-18
|
||||
### Fixed
|
||||
- Improved handling for cases where there are no locally installed mods
|
||||
- Set up mod symlinks at boot, rather than only on server connect
|
||||
- Prevent context menus from opening when table is empty
|
||||
- When reloading table in-place, prevent duplicate panel elements from being added if already present
|
||||
- Clean up signal emission
|
||||
|
||||
## [5.6.0-beta.2] 2024-11-15
|
||||
### Fixed
|
||||
- Clean up local mod signatures from versions file when deleting mods
|
||||
|
||||
## [5.6.0-beta.1] 2024-11-12
|
||||
### Added
|
||||
- Bulk delete mods (via 'List installed mods' list). Not compatible with Manual Mod install mode
|
||||
### Fixed
|
||||
- Fix for server list truncation causing some servers to not appear in results
|
||||
- Suppress signal emission when switching menu contexts
|
||||
- Focus first row when opening mods list
|
||||
### Changed
|
||||
- Clarify some error messages and normalize text formatting
|
||||
|
||||
## [5.5.0-beta.5] 2024-11-03
|
||||
### Changed
|
||||
- Use updated A2S_RULES logic
|
||||
### Fixed
|
||||
- Servers in saved servers list would populate context menu with same option when right-clicking in server browser
|
||||
|
||||
## [5.5.0-beta.4] 2024-10-31
|
||||
### Added
|
||||
- Expose a toggle setting for whether to launch the application in fullscreen
|
||||
### Fixed
|
||||
- Enable adding/removing servers to/from My Saved Servers when in Recent Servers context
|
||||
|
||||
## [5.5.0-beta.3] 2024-10-31
|
||||
### Fixed
|
||||
- Prevent maps combobox from duplicating contents
|
||||
|
||||
## [5.5.0-beta.2] 2024-10-31
|
||||
### Fixed
|
||||
- Restore keyboard input to keyword entry field
|
||||
### Changed
|
||||
|
||||
## [5.5.0-beta.1] 2024-10-30
|
||||
### Added
|
||||
- Support servers running DLC content (fixes Frostline servers)
|
||||
- Text autocompletion in maps search field
|
||||
- Add disk space warning to popup dialog
|
||||
### Fixed
|
||||
- Abort fallback query method if DLC is required
|
||||
|
||||
## [5.4.2] 2024-10-05
|
||||
## [5.4.2-beta.1] 2024-10-05
|
||||
### Fixed
|
||||
- Sanitize third-party API IDs to remove UGC collisions
|
||||
|
||||
## [5.4.1] 2024-09-25
|
||||
## [5.4.1-beta.2] 2024-09-12
|
||||
### Fixed
|
||||
- Use fallback logic for modlist queries when user traverses networks
|
||||
|
||||
## [5.4.1-beta.2] 2024-09-10
|
||||
### Fixed
|
||||
- Fix signal handling control flow for checkbox toggles
|
||||
|
||||
## [5.4.1-beta.1] 2024-09-10
|
||||
### Added
|
||||
- Pre-boot validation check for users with self-compiled version of jq
|
||||
### Fixed
|
||||
- Use fallback logic for modlist queries when user traverses networks
|
||||
- Fix signal handling control flow for checkbox toggles
|
||||
- When reloading the server browser, the map combobox selection would revert to the last selected map instead of All Maps
|
||||
- Server filter toggle signals were accessible from the main menu when switching between menu contexts
|
||||
- Global cooldown dialog could sometimes block filter toggles after cooldown reset
|
||||
- Normalized minor version number due to a previous clerical error
|
||||
|
||||
## [5.4.0] 2024-08-27
|
||||
## [5.4.0-beta.5] 2024-08-27
|
||||
### Added
|
||||
- Scan local area network for DayZ servers
|
||||
- Freedesktop application icons for system taskbar, tray, and other dialogs
|
||||
- Emit CPU model name when exporting system debug log
|
||||
### Fixed
|
||||
- Errors being printed to the console when Exit button was explicitly clicked
|
||||
|
||||
## [5.4.0-beta.4] 2024-08-21
|
||||
### Added
|
||||
- Emit CPU model name when exporting system debug log
|
||||
### Fixed
|
||||
- Detect Steam Deck OLED APU variant during initial setup
|
||||
- Encapsulate player names correctly so that names with whitespace in them are supported
|
||||
|
||||
## [5.4.0-beta.3] 2024-08-04
|
||||
### Added
|
||||
- Scan local area network for DayZ servers
|
||||
|
||||
## [5.4.0-beta.2] 2024-08-03
|
||||
### Fixed
|
||||
- Clerical hotfix for previous player names fix
|
||||
- Test if DayZ directory is empty at startup, implying that the game was moved to a new library collection
|
||||
- Report WM_CLASS name to the window manager
|
||||
|
||||
## [5.4.0-beta.1] 2024-07-16
|
||||
### Fixed
|
||||
- Encapsulate player names correctly so that names with whitespace in them are supported
|
||||
|
||||
## [5.3.2] 2024-07-02
|
||||
### Fixed
|
||||
@ -377,7 +616,7 @@ As part of this change, version 4.0.0 introduces the ability to connect to or ad
|
||||
|
||||
If you encounter any problems with this new release or with the migration of configs, please do not hesitate to submit a bug report.
|
||||
|
||||
Attention Fedora 38 users: problems with upstream GNOME packages causing crashes have been reported to GNOME development and a fix has been issued. You have the choice of compiling the zenity package
|
||||
Attention Fedora 38 users: problems with upstream GNOME packages causing crashes have been reported to GNOME development and a fix has been issued. You have the choice of compiling the zenity package
|
||||
from source or waiting until the latest version is merged into Fedora's package manager.
|
||||
|
||||
### Added
|
||||
@ -402,7 +641,7 @@ from source or waiting until the latest version is merged into Fedora's package
|
||||
- Store complete IP:Port instead of server IDs
|
||||
- Make Battlemetrics API key optional: this is only used for the 'Connect by ID' and 'Add server by ID' methods and is not required. If you prefer, you can simply connect/add by IP.
|
||||
- Prevent the application from launching in Game Mode on Steam Deck: Steam Deck's kiosk mode has problems sending keyboard input to third party applications. To prevent unintended usage, DZGUI now warns the user to launch the app in Desktop Mode if they attempt to use it from Game Mode. Adding DZGUI as a Non-Steam Game does work on desktop PCs, but is not recommended due to the way Steam handles subshells. For best results, launch DZGUI directly via the script/applications menu (PC) or via the desktop icon (Steam Deck).
|
||||
- Omit null servers from list: servers that time out or send an empty response are now omitted entirely from the My Servers list, as they will not return meaningful metadata unless they are online.
|
||||
- Omit null servers from list: servers that time out or send an empty response are now omitted entirely from the My Servers list, as they will not return meaningful metadata unless they are online.
|
||||
The My Servers list thus shows online and accessible servers
|
||||
|
||||
## [3.3.0] 2023-05-16
|
||||
@ -421,7 +660,7 @@ from source or waiting until the latest version is merged into Fedora's package
|
||||
- First-time setup: sudo escalation when checking system map count for the first time
|
||||
|
||||
### Fixed
|
||||
- Steam Deck: non-ASCII delimiter causing setup menu to despawn on some devices
|
||||
- Steam Deck: non-ASCII delimiter causing setup menu to despawn on some devices
|
||||
- Don't add items in My Servers multiple times to array when the list of favorites is paginated
|
||||
- Trigger progress dialogs sooner and in sequence to reduce appearance of visual lag
|
||||
- First-time setup: break out of dialogs correctly when user backs out
|
||||
|
||||
10
README.md
10
README.md
@ -11,6 +11,15 @@ This overcomes certain limitations in the Linux client and helps prepare the gam
|
||||
- Concatenate launch options to pass to Steam
|
||||
- Connect to mod-enabled LAN servers
|
||||
|
||||
## Design philosophy
|
||||
|
||||
DZGUI follows a specific set of guidelines, enumerated below:
|
||||
|
||||
- Provide a free and open-source tool under GPL
|
||||
- Do not use undocumented, third-party servers to aggregate data; no central "DZGUI" server
|
||||
- All connections go to Steam and DayZ servers directly; the tool should function in perpetuity without intermediary endpoints
|
||||
- Target Linux desktop and Steam deck specifically; not a cross-platform tool
|
||||
|
||||
## Setup and usage
|
||||
|
||||
Refer to the documentation for installation and setup instructions:
|
||||
@ -18,7 +27,6 @@ Refer to the documentation for installation and setup instructions:
|
||||
- [GitHub](https://aclist.github.io/dzgui/index.html)
|
||||
- [Mirror (Codeberg)](https://aclist.codeberg.page)
|
||||
|
||||
|
||||

|
||||
|
||||
## Attribution
|
||||
|
||||
193
dzgui.sh
193
dzgui.sh
@ -2,7 +2,7 @@
|
||||
set -o pipefail
|
||||
|
||||
src_path="$(readlink -e "$0")"
|
||||
version=6.0.1
|
||||
version=6.0.6-beta.1
|
||||
|
||||
#CONSTANTS
|
||||
aid=221100
|
||||
@ -142,7 +142,7 @@ test_gobject(){
|
||||
python3.13 -c "import gi"
|
||||
if [[ ! $? -eq 0 ]]; then
|
||||
logger CRITICAL "Missing PyGObject"
|
||||
quit_with_pdialog "Requires PyGObject (python-gobject)"
|
||||
quit_with_help_dialog "Requires PyGObject (python-gobject)"
|
||||
exit 1
|
||||
fi
|
||||
logger INFO "Found PyGObject in Python env"
|
||||
@ -247,7 +247,7 @@ open_url(){
|
||||
xdg-open "$url"
|
||||
fi
|
||||
}
|
||||
quit_with_pdialog(){
|
||||
quit_with_help_dialog(){
|
||||
local sel
|
||||
msg="$1"
|
||||
help_button="Open help page"
|
||||
@ -260,7 +260,7 @@ quit_with_pdialog(){
|
||||
check_pyver(){
|
||||
if [[ ! $(python3.13 --version) ]]; then
|
||||
local msg="Requires Python 3.13"
|
||||
quit_with_pdialog "$msg"
|
||||
quit_with_help_dialog "$msg"
|
||||
fi
|
||||
}
|
||||
watcher_deps(){
|
||||
@ -335,6 +335,7 @@ check_unmerged(){
|
||||
fi
|
||||
}
|
||||
check_version(){
|
||||
echo "# Checking version"
|
||||
[[ -n $reference_branch ]] && return
|
||||
local version_url=$(format_version_url)
|
||||
local upstream=$(curl -Ls "$version_url" | awk -F= '/^version=/ {print $2}')
|
||||
@ -385,7 +386,7 @@ prompt_dl(){
|
||||
Your branch: $branch
|
||||
Your version: $version
|
||||
Upstream version: $upstream
|
||||
|
||||
|
||||
Version updates introduce important bug fixes and are encouraged. Attempt to download the latest version?
|
||||
EOF
|
||||
}
|
||||
@ -414,6 +415,7 @@ test_display_mode(){
|
||||
fi
|
||||
}
|
||||
check_architecture(){
|
||||
echo "# Checking system architecture"
|
||||
local cpu=$(< /proc/cpuinfo awk -F": " '/AMD Custom APU [0-9]{4}$/ {print $2; exit}')
|
||||
read -a APU_MODEL <<< "$cpu"
|
||||
if [[ ${APU_MODEL[3]} != "0932" ]] && [[ ${APU_MODEL[3]} != "0405" ]]; then
|
||||
@ -430,6 +432,7 @@ check_architecture(){
|
||||
logger INFO "Setting architecture to 'Steam Deck'"
|
||||
}
|
||||
check_map_count(){
|
||||
echo "# Checking map count"
|
||||
[[ $is_steam_deck -gt 0 ]] && return 0
|
||||
local map_count_file="/proc/sys/vm/max_map_count"
|
||||
local min_count=1048576
|
||||
@ -479,6 +482,7 @@ tdialog(){
|
||||
$steamsafe_zenity --info --text="$1" "${zenity_flags[@]}"
|
||||
}
|
||||
steam_deps(){
|
||||
echo "# Checking Steam"
|
||||
local flatpak
|
||||
local steam
|
||||
[[ $(command -v flatpak) ]] && flatpak=$(flatpak list | grep valvesoftware.Steam)
|
||||
@ -489,16 +493,8 @@ steam_deps(){
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
migrate_files(){
|
||||
if [[ ! -f $config_path/dztuirc.oldapi ]]; then
|
||||
cp $config_file $config_path/dztuirc.oldapi
|
||||
logger INFO "Migrated old API file"
|
||||
fi
|
||||
[[ ! -f $hist_file ]] && return
|
||||
rm $hist_file
|
||||
logger INFO "Wiped old history file"
|
||||
}
|
||||
stale_symlinks(){
|
||||
echo "# Cleaning stale symlinks"
|
||||
local game_dir="$steam_path/steamapps/common/DayZ"
|
||||
readarray -t links < <(find "$game_dir" -xtype l)
|
||||
for link in "${links[@]}"; do
|
||||
@ -512,7 +508,7 @@ check_availability() {
|
||||
return 1
|
||||
fi
|
||||
local url=$1
|
||||
local timeout_sec="3"
|
||||
local timeout_sec="1"
|
||||
if [[ $2 ]]; then
|
||||
timeout_sec=$2
|
||||
fi
|
||||
@ -523,6 +519,7 @@ check_availability() {
|
||||
}
|
||||
|
||||
local_latlon(){
|
||||
echo "# Checking coordinates"
|
||||
if [[ -z $(command -v dig) ]]; then
|
||||
local url_ipecho="https://ipecho.net/plain"
|
||||
if ! check_availability "ipecho.net"; then
|
||||
@ -534,6 +531,9 @@ local_latlon(){
|
||||
# TODO : implement checking remote
|
||||
local local_ip=$(dig -4 +short myip.opendns.com @resolver1.opendns.com)
|
||||
fi
|
||||
if calc_local_coords "$local_ip"; then
|
||||
return 0
|
||||
fi
|
||||
local url_ip_api="http://ip-api.com/json/$local_ip"
|
||||
if ! check_availability "ip-api.com"; then
|
||||
logger WARN "Failed to get local coordinates, ip-api.com service may be down."
|
||||
@ -548,6 +548,7 @@ local_latlon(){
|
||||
}
|
||||
|
||||
lock(){
|
||||
echo "# Setting up lock file"
|
||||
[[ ! -f $lock_file ]] && touch $lock_file
|
||||
local pid=$(cat $lock_file)
|
||||
ps -p $pid -o pid= >/dev/null 2>&1
|
||||
@ -566,9 +567,12 @@ get_hash(){
|
||||
md5sum "$1" | awk '{print $1}'
|
||||
}
|
||||
fetch_a2s(){
|
||||
# this file is currently monolithic
|
||||
[[ -d $helpers_path/a2s ]] && { logger INFO "A2S helper is current"; return 0; }
|
||||
local sha=c7590ffa9a6d0c6912e17ceeab15b832a1090640
|
||||
sum="d26da2fe03df2b95436f823f0496887b"
|
||||
if [[ -d $helpers_path/a2s ]] && [[ $(get_hash "$helpers_path/a2s/info.py") == $sum ]]; then
|
||||
logger INFO "A2S helper is current"
|
||||
return 0
|
||||
fi
|
||||
local sha=b40eb24cdbb06ebd08272f224257fe5a81610e86
|
||||
local author="yepoleb"
|
||||
local repo="python-a2s"
|
||||
local url="https://github.com/$author/$repo/tarball/$sha"
|
||||
@ -582,13 +586,13 @@ fetch_a2s(){
|
||||
logger INFO "Updated A2S helper to sha '$sha'"
|
||||
}
|
||||
fetch_dzq(){
|
||||
local sum="0a334e1e144e76e560419d155435c91e"
|
||||
local sum="0d316badbb8d943c5f078e73862cac3e"
|
||||
local file="$helpers_path/a2s/dayzquery.py"
|
||||
if [[ -f $file ]] && [[ $(get_hash "$file") == $sum ]]; then
|
||||
logger INFO "DZQ is current"
|
||||
return 0
|
||||
fi
|
||||
local sha=a22a9f428cbe075d7dda62f78000296955eea92a
|
||||
local sha="07483b88ed096327ebca752f5e177011b604d7fe"
|
||||
local author="yepoleb"
|
||||
local repo="dayzquery"
|
||||
local url="https://raw.githubusercontent.com/$author/$repo/$sha/dayzquery.py"
|
||||
@ -625,12 +629,12 @@ fetch_helpers_by_sum(){
|
||||
[[ -f "$config_file" ]] && source "$config_file"
|
||||
declare -A sums
|
||||
sums=(
|
||||
["funcs"]="5e35a9812c03fc64d75843a23d024f0d"
|
||||
["funcs"]="1cd26343cd68a5382e3377a9757497fa"
|
||||
["query_v2.py"]="55d339ba02512ac69de288eb3be41067"
|
||||
["servers.py"]="ed442c3aecf33f777d59dcf53650d263"
|
||||
["ui.py"]="3d67e5e8e85a23dde1fd0e85a9be62a9"
|
||||
["ui.py"]="cf52a3d5883afe02a013d8673df494fa"
|
||||
["vdf2json.py"]="2f49f6f5d3af919bebaab2e9c220f397"
|
||||
["pefile.py"]="21531f2c0d9dfa5f110cf6779f9d22c0"
|
||||
["pefile.py"]="b452974a84bff1d821872fcebf59e380"
|
||||
)
|
||||
local author="aclist"
|
||||
local repo="dztui"
|
||||
@ -690,12 +694,12 @@ fetch_km_helper(){
|
||||
}
|
||||
get_response_code(){
|
||||
local url="$1"
|
||||
curl -Ls -I -o /dev/null -w "%{http_code}" "$url"
|
||||
curl --connect-timeout 3 -Ls -I -o /dev/null -w "%{http_code}" "$url"
|
||||
}
|
||||
|
||||
fetch_ip_db(){
|
||||
parse_dl_url(){
|
||||
curl -Ls "$url" \
|
||||
curl --connect-timeout 3 -Ls "$url" \
|
||||
| grep "csv.gz" \
|
||||
| awk -F"['']" '{print $2}'
|
||||
}
|
||||
@ -711,6 +715,9 @@ fetch_ip_db(){
|
||||
fetch(){
|
||||
logger INFO "Triggering fetch routine"
|
||||
local url="$1"
|
||||
local ip_file="${state_path}/ips.csv"
|
||||
local base_file="${state_path}/${this_month}.csv.gz"
|
||||
local extracted_file="${state_path}/${this_month}.csv"
|
||||
|
||||
curl -Ls "$url" > "$base_file"
|
||||
if [[ $? -ne 0 ]]; then
|
||||
@ -730,7 +737,7 @@ fetch_ip_db(){
|
||||
logger WARN "Abnormal exit while parsing IPs"
|
||||
return
|
||||
fi
|
||||
rm "${state_path}/${month}.csv"
|
||||
rm "${state_path}/${this_month}.csv"
|
||||
|
||||
readarray -t records < <(cat $ip_file | awk -F, 'NR==1 {print $1} END {print $1}')
|
||||
if [[ ${records[0]} != "0.0.0.0" ]] && [[ ${records[1]} != "224.0.0.0" ]]; then
|
||||
@ -745,55 +752,57 @@ fetch_ip_db(){
|
||||
rm "$ip_file"
|
||||
fi
|
||||
|
||||
echo "$month" > "$month_file"
|
||||
logger INFO "Wrote '$month' to stub '$month_file'"
|
||||
echo "$this_month" > "$month_file"
|
||||
logger INFO "Wrote '$this_month' to stub '$month_file'"
|
||||
logger INFO "Updated '$ip_file'"
|
||||
}
|
||||
|
||||
check_remote(){
|
||||
local url="$1"
|
||||
local res=$(get_response_code "$url")
|
||||
if [[ $res -ne 200 ]]; then
|
||||
logger WARN "Failed to retrieve remote resource: '$url' ($res)"
|
||||
return
|
||||
fi
|
||||
logger INFO "Resolved remote URL: '$url'"
|
||||
|
||||
# test dl url
|
||||
local dl_url="$(parse_dl_url)"
|
||||
local res=$(get_response_code "$dl_url")
|
||||
if [[ $res -ne 200 ]]; then
|
||||
logger WARN "Remote resource unavailable: '$dl_url' ($res)"
|
||||
return
|
||||
fi
|
||||
logger INFO "Resolved download URL: '$dl_url'"
|
||||
fetch "$dl_url"
|
||||
}
|
||||
|
||||
local url="https://db-ip.com/db/download/ip-to-city-lite"
|
||||
local month_file="${state_path}/.month"
|
||||
local ip_file="${state_path}/ips.csv"
|
||||
|
||||
# test main url
|
||||
local res=$(get_response_code "$url")
|
||||
if [[ $res -ne 200 ]]; then
|
||||
logger WARN "Failed to retrieve remote resource: '$url' ($res)"
|
||||
return
|
||||
fi
|
||||
logger INFO "Resolved remote URL: '$url'"
|
||||
|
||||
# test dl url
|
||||
local dl_url="$(parse_dl_url)"
|
||||
local res=$(get_response_code "$dl_url")
|
||||
if [[ $res -ne 200 ]]; then
|
||||
logger WARN "Remote resource unavailable: '$dl_url' ($res)"
|
||||
return
|
||||
fi
|
||||
logger INFO "Resolved download URL: '$dl_url'"
|
||||
|
||||
local month=$(parse_dl_url_date "$dl_url")
|
||||
local base_file="${state_path}/${month}.csv.gz"
|
||||
local extracted_file="${state_path}/${month}.csv"
|
||||
month_file="${state_path}/.month"
|
||||
this_month=$(date +%Y-%m)
|
||||
|
||||
# no stub file
|
||||
if [[ ! -f $month_file ]]; then
|
||||
logger WARN "No stub file '$month_file' present"
|
||||
fetch "$dl_url"
|
||||
return
|
||||
fi
|
||||
|
||||
# local needs update
|
||||
local last_month=$(< "$month_file")
|
||||
if [[ $last_month != "$month" ]]; then
|
||||
logger WARN "Local stub '$last_month' does not match remote stub '$month'"
|
||||
fetch "$dl_url"
|
||||
check_remote "$url"
|
||||
return
|
||||
fi
|
||||
|
||||
# if stub is same date, abort
|
||||
logger INFO "Local stub '$last_month' is identical to remote, skipping"
|
||||
local last_month=$(< "$month_file")
|
||||
if [[ $last_month == "$this_month" ]]; then
|
||||
logger INFO "Local stub '$last_month' is identical to remote, skipping"
|
||||
return
|
||||
fi
|
||||
|
||||
# local needs update
|
||||
logger WARN "Local stub '$last_month' does not match '$this_month'"
|
||||
check_remote "$url"
|
||||
return
|
||||
|
||||
}
|
||||
fetch_helpers(){
|
||||
echo "# Checking helper files"
|
||||
fetch_a2s
|
||||
fetch_dzq
|
||||
fetch_km_helper
|
||||
@ -981,6 +990,7 @@ create_config(){
|
||||
done
|
||||
}
|
||||
varcheck(){
|
||||
echo "# Checking config file"
|
||||
local msg="Config file '$config_file' missing. Start first-time setup now?"
|
||||
local msg2="The Steam paths set in your config file appear to be invalid (DayZ was moved or uninstalled). Restart first-time setup now?"
|
||||
if [[ ! -f $config_file ]]; then
|
||||
@ -1007,12 +1017,14 @@ varcheck(){
|
||||
fi
|
||||
}
|
||||
is_dzg_downloading(){
|
||||
echo "# Checking DayZ"
|
||||
if [[ -d $steam_path ]] && [[ -d $steam_path/downloading/$aid ]]; then
|
||||
logger WARN "DayZ may be scheduling updates"
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
is_steam_running(){
|
||||
echo "# Checking Steam"
|
||||
local res=$(ps aux | grep "steamwebhelper" | grep -v grep)
|
||||
if [[ -z $res ]]; then
|
||||
logger WARN "Steam may not be running"
|
||||
@ -1025,6 +1037,7 @@ get_response_code(){
|
||||
curl -Ls -I -o /dev/null -w "%{http_code}" "$url"
|
||||
}
|
||||
test_connection(){
|
||||
echo "# Testing connection"
|
||||
declare -A hr
|
||||
local res1
|
||||
local res2
|
||||
@ -1058,6 +1071,7 @@ legacy_cols(){
|
||||
mv $cols_file.new $cols_file
|
||||
}
|
||||
stale_mod_signatures(){
|
||||
echo "# Checking stale mods"
|
||||
[[ ! -f "$versions_file" ]] && return
|
||||
local workshop_dir="$steam_path/steamapps/workshop/content/$aid"
|
||||
if [[ -d $workshop_dir ]]; then
|
||||
@ -1071,18 +1085,48 @@ stale_mod_signatures(){
|
||||
|
||||
}
|
||||
create_new_links(){
|
||||
echo "# Setting up symlinks"
|
||||
"$func_helper" "update_symlinks"
|
||||
}
|
||||
calc_local_coords(){
|
||||
local ip="$1"
|
||||
[[ ! -f "$geo_helper" ]] && return 1
|
||||
|
||||
IFS="." read -ra split <<< "$ip"
|
||||
[[ ${#split[@]} -ne 4 ]] && return 1
|
||||
|
||||
prefix="^${split[0]}.${split[1]}."
|
||||
readarray -t res < <(grep "$prefix" "$geo_helper")
|
||||
|
||||
for address in "${res[@]}"; do
|
||||
IFS="," read -ra fields <<< "$address"
|
||||
upper="${fields[1]}"
|
||||
IFS="." read -ra upper_digits <<< "$upper"
|
||||
if [[ ${split[2]} -gt ${upper_digits[2]} ]]; then
|
||||
continue
|
||||
fi
|
||||
if [[ ${split[2]} -eq ${upper_digits[2]} ]]; then
|
||||
if [[ ${split[3]} -gt ${upper_digits[3]} ]]; then
|
||||
continue
|
||||
fi
|
||||
if [[ ${split[3]} -eq ${upper_digits[3]} ]]; then
|
||||
echo "${fields[-2]}" >> "$coords_file"
|
||||
echo "${fields[-1]}" >> "$coords_file"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
if [[ ${split[2]} -lt ${upper_digits[2]} ]]; then
|
||||
echo "${fields[-2]}" >> "$coords_file"
|
||||
echo "${fields[-1]}" >> "$coords_file"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
initial_setup(){
|
||||
setup_dirs
|
||||
setup_state_files
|
||||
depcheck
|
||||
check_pyver
|
||||
test_gobject
|
||||
watcher_deps
|
||||
check_architecture
|
||||
test_connection
|
||||
fetch_helpers > >(pdialog "Checking helper files")
|
||||
fetch_helpers
|
||||
varcheck
|
||||
source "$config_file"
|
||||
lock
|
||||
@ -1091,14 +1135,12 @@ initial_setup(){
|
||||
check_version
|
||||
check_map_count
|
||||
steam_deps
|
||||
migrate_files
|
||||
stale_symlinks
|
||||
stale_mod_signatures
|
||||
create_new_links
|
||||
local_latlon
|
||||
is_steam_running
|
||||
is_dzg_downloading
|
||||
print_config_vals
|
||||
}
|
||||
uninstall(){
|
||||
_full(){
|
||||
@ -1200,8 +1242,15 @@ main(){
|
||||
|
||||
set_im_module
|
||||
|
||||
printf "Initializing setup...\n"
|
||||
initial_setup
|
||||
printf "Checking dependencies...\n"
|
||||
setup_dirs
|
||||
setup_state_files
|
||||
depcheck
|
||||
check_pyver
|
||||
test_gobject
|
||||
watcher_deps
|
||||
|
||||
initial_setup > >(pdialog "Initializing setup")
|
||||
|
||||
printf "All OK. Kicking off UI...\n"
|
||||
python3.13 "$ui_helper" "--init-ui" "$version" "$is_steam_deck"
|
||||
|
||||
292
helpers/funcs
292
helpers/funcs
@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -o pipefail
|
||||
version="6.0.1"
|
||||
version="6.0.5-beta.2"
|
||||
|
||||
#CONSTANTS
|
||||
aid=221100
|
||||
@ -690,6 +690,7 @@ toggle(){
|
||||
auto_install="1"
|
||||
else
|
||||
auto_install=""
|
||||
rm "$versions_file"
|
||||
fi
|
||||
;;
|
||||
Toggle[[:space:]]debug[[:space:]]mode)
|
||||
@ -929,13 +930,19 @@ query_defunct(){
|
||||
<<< "$result" awk '{print $2}'
|
||||
}
|
||||
encode(){
|
||||
echo "$1" | md5sum | cut -c -8
|
||||
echo -n "$1" | md5sum | cut -c -8
|
||||
}
|
||||
compare(){
|
||||
local modlist="$@"
|
||||
diff=$(comm -23 <(<<< "$modlist" sort -u) <(installed_mods | sort))
|
||||
echo "$diff"
|
||||
}
|
||||
unlink_all(){
|
||||
readarray -t links < <(find "$game_dir" -maxdepth 1 -mindepth 1 -type l)
|
||||
for link in "${links[@]}"; do
|
||||
unlink "$link"
|
||||
done
|
||||
}
|
||||
legacy_symlinks(){
|
||||
logger INFO "Removing legacy symlinks"
|
||||
|
||||
@ -1026,7 +1033,10 @@ update_symlinks(){
|
||||
if [[ ! -d "$workshop_dir" ]]; then
|
||||
return
|
||||
fi
|
||||
legacy_symlinks
|
||||
if [[ ! -d "$game_dir" ]]; then
|
||||
return
|
||||
fi
|
||||
unlink_all
|
||||
symlinks
|
||||
}
|
||||
try_fallback(){
|
||||
@ -1077,41 +1087,42 @@ try_connect(){
|
||||
local appid="$2"
|
||||
local binary="$3"
|
||||
|
||||
local ip=$(<<< $record awk -F: '{print $1}')
|
||||
local gameport=$(<<< $record awk -F: '{print $2}')
|
||||
local qport=$(<<< $record awk -F: '{print $3}')
|
||||
IFS=":" read -ra address <<< "$record"
|
||||
local ip
|
||||
local gameport
|
||||
local qport
|
||||
ip=${address[0]}
|
||||
gameport=${address[1]}
|
||||
qport=${address[2]}
|
||||
|
||||
[[ $appid -eq $exp ]] && echo "$binary" > $_cache_binary
|
||||
[[ $appid -eq $exp ]] && echo "$binary" > "$_cache_binary"
|
||||
|
||||
local remote_mods
|
||||
remote_mods=$(a2s $ip $qport rules)
|
||||
remote_mods=$(a2s "$ip" "$qport" rules)
|
||||
if [[ $? -eq 1 ]]; then
|
||||
printf "Failed to fetch server modlist, possibly timed out"
|
||||
return 1
|
||||
fi
|
||||
logger INFO "Server returned modlist: $(<<< $remote_mods tr '\n' ' ')"
|
||||
local sanitized_mods=$(query_defunct "$remote_mods")
|
||||
local diff=$(compare "$sanitized_mods")
|
||||
logger INFO "Server returned modlist: $(<<< "$remote_mods" tr '\n' ' ')"
|
||||
|
||||
local sanitized_mods
|
||||
sanitized_mods=$(query_defunct "$remote_mods")
|
||||
|
||||
logger INFO "Connection attempt for $ip:$qport"
|
||||
update_history "$record"
|
||||
|
||||
if [[ -n $auto_install ]]; then
|
||||
logger INFO "Merging modlists"
|
||||
diff=$(merge_modlists "$diff")
|
||||
diff=$(query_defunct "$diff")
|
||||
fi
|
||||
if [[ -n $diff ]]; then
|
||||
if [[ $(check_architecture) -eq 1 ]] && [[ $(test_display_mode) == "gm" ]]; then
|
||||
printf "Use Desktop Mode to download mods on Steam Deck"
|
||||
return 1
|
||||
fi
|
||||
case $auto_install in
|
||||
"") manual_mod_install "$ip" "$gameport" "$diff" "$sanitized_mods" "" "$appid";;
|
||||
1|2) manual_mod_install "$ip" "$gameport" "$diff" "$sanitized_mods" "auto" "$appid" ;;
|
||||
esac
|
||||
else
|
||||
case $auto_install in
|
||||
"") install_mods "$sanitized_mods" "manual" ;;
|
||||
1|2) install_mods "$sanitized_mods" "auto" ;;
|
||||
esac
|
||||
|
||||
# NOTE: clean diff indicates that all requisite mods were downloaded
|
||||
diff=$(compare "$sanitized_mods")
|
||||
if [[ -z $diff ]]; then
|
||||
launch "$ip" "$gameport" "$sanitized_mods" "$appid"
|
||||
else
|
||||
printf "User aborted download process, or some mods may have failed to download. Try connecting again to resync."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
check_architecture(){
|
||||
@ -1127,13 +1138,15 @@ force_update(){
|
||||
printf "Only available when mod auto-install is ON"
|
||||
return 1
|
||||
fi
|
||||
# NOTE: forcibly regenerate signatures by removing file
|
||||
rm "$versions_file"
|
||||
local update=$(check_timestamps)
|
||||
manual_mod_install "null" "null" "$update" "null" "force" "null"
|
||||
local mods
|
||||
mods=$(ls -1 "$workshop_dir")
|
||||
install_mods "$mods" "force"
|
||||
echo "Finished requesting mod updates."
|
||||
return 0
|
||||
}
|
||||
get_local_stamps(){
|
||||
get_timestamps(){
|
||||
readarray -t modlist < <(printf "%s\n" "$@")
|
||||
local max="${#modlist[@]}"
|
||||
_concat(){
|
||||
@ -1158,67 +1171,6 @@ update_stamps(){
|
||||
printf "%s\n" "${stamps[$i]}" >> $versions_file
|
||||
done
|
||||
}
|
||||
check_timestamps(){
|
||||
readarray -t local_modlist < <(ls -1 $workshop_dir)
|
||||
local max=${#local_modlist[@]}
|
||||
logger INFO "Local mod count: $max"
|
||||
[[ $max -eq 0 ]] && return 1
|
||||
local local_stamps=$(get_local_stamps "${local_modlist[@]}")
|
||||
if [[ -z $local_stamps ]]; then
|
||||
logger WARN "Timestamp query returned empty response"
|
||||
return 1
|
||||
fi
|
||||
local aligned=$(<<< "$local_stamps" jq -r '.response.publishedfiledetails[]|"\(.publishedfileid),\(.time_updated)"')
|
||||
readarray -t remote_ids < <(<<< "$aligned" awk -F, '{print $1}')
|
||||
readarray -t remote_times < <(<<< "$aligned" awk -F, '{print $2}')
|
||||
|
||||
if [[ ! -f $versions_file ]]; then
|
||||
logger INFO "No prior versions file found, creating"
|
||||
update_stamps "$aligned"
|
||||
#force refresh all mods if versions file was missing
|
||||
printf "%s\n" "${remote_ids[@]}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
readarray -t old_ids < <(< $versions_file awk -F, '{print $1}')
|
||||
readarray -t old_times < <(< $versions_file awk -F, '{print $2}')
|
||||
|
||||
declare -A remote_version
|
||||
declare -A local_version
|
||||
|
||||
for((i = 0; i < ${#remote_ids[@]}; ++i)); do
|
||||
remote_version[${remote_ids[$i]}]=${remote_times[$i]}
|
||||
done
|
||||
|
||||
needs_update=()
|
||||
for((i=0;i<${#old_ids[@]};i++)); do
|
||||
local id=${old_ids[$i]}
|
||||
local time=${old_times[$i]}
|
||||
if [[ $time != ${remote_version[$id]} ]]; then
|
||||
logger WARN "Mod '$id' timestamp '$time' != '${remote_version[$id]}'"
|
||||
needs_update+=($id)
|
||||
fi
|
||||
done
|
||||
printf "%s\n" "${needs_update[@]}"
|
||||
}
|
||||
merge_modlists(){
|
||||
local diff="$1"
|
||||
_sort(){
|
||||
printf "%s\n" "$diff"
|
||||
printf "%s\n" "${needs_update[@]}"
|
||||
}
|
||||
readarray -t needs_update < <(check_timestamps)
|
||||
if [[ -z ${needs_update[@]} ]]; then
|
||||
echo "$diff"
|
||||
return 0
|
||||
fi
|
||||
if [[ -z "$diff" ]] && [[ ${#needs_update[@]} -gt 0 ]]; then
|
||||
printf "%s\n" "${needs_update[@]}"
|
||||
else
|
||||
# remove duplicates
|
||||
_sort | sort -u
|
||||
fi
|
||||
}
|
||||
concat_mods(){
|
||||
readarray -t concat_arr <<< "$@"
|
||||
local id
|
||||
@ -1260,7 +1212,7 @@ launch(){
|
||||
|
||||
if [[ $debug -eq 1 ]]; then
|
||||
local launch_options="$steam_cmd -applaunch $appid -connect=$ip:$gameport -nolauncher -nosplash -name=$name -skipintro -mod=$concat"
|
||||
printf "Debug mode: these options would have been used to launch the game: $launch_options"
|
||||
printf "Debug mode: these options would have been used to launch the game: %s" "$launch_options"
|
||||
return 0
|
||||
fi
|
||||
echo "$concat" > "$_cache_launch"
|
||||
@ -1296,75 +1248,121 @@ final_handshake(){
|
||||
printf "\n"
|
||||
return 6
|
||||
}
|
||||
manual_mod_install(){
|
||||
local ip="$1"
|
||||
local gameport="$2"
|
||||
local diff="$3"
|
||||
local sanitized_mods="$4"
|
||||
local mode="$5"
|
||||
local appid="$6"
|
||||
local ex="$state_path/dzg.watcher"
|
||||
watcher(){
|
||||
local -n to_download="$1"
|
||||
local -n signatures="$2"
|
||||
local mode="$3"
|
||||
|
||||
readarray -t stage_mods <<< "$diff"
|
||||
[[ -f $ex ]] && rm $ex
|
||||
declare -i i=0
|
||||
local remote_time
|
||||
local length
|
||||
length=${#to_download[@]}
|
||||
for mod in "${!to_download[@]}"; do
|
||||
remote_time=${to_download[$mod]}
|
||||
[[ -f $ex ]] && return 1
|
||||
case $mode in
|
||||
"auto" | "force")
|
||||
$steam_cmd "steam://url/CommunityFilePage/${mod}+workshop_download_item ${aid} ${mod}"
|
||||
echo "# Opening workshop page for ${mod}. If you see no progress, you may be out of disk space."
|
||||
;;
|
||||
*)
|
||||
$steam_cmd "steam://url/CommunityFilePage/${mod}"
|
||||
echo "# Opening workshop page for ${mod}. If you see no progress after subscribing, try unsubscribing and resubscribing again until the download commences."
|
||||
;;
|
||||
esac
|
||||
sleep 1s
|
||||
foreground
|
||||
|
||||
_watcher(){
|
||||
for((i=0;i<${#stage_mods[@]};i++)); do
|
||||
until [[ -d $downloads_dir/${mod} ]]; do
|
||||
[[ -f $ex ]] && return 1
|
||||
|
||||
if [[ $mode == "auto" ]] || [[ $mode == "force" ]]; then
|
||||
$steam_cmd "steam://url/CommunityFilePage/${stage_mods[$i]}+workshop_download_item $aid ${stage_mods[$i]}"
|
||||
echo "# Opening workshop page for ${stage_mods[$i]}. If you see no progress, you may be out of disk space."
|
||||
else
|
||||
$steam_cmd "steam://url/CommunityFilePage/${stage_mods[$i]}"
|
||||
echo "# Opening workshop page for ${stage_mods[$i]}. If you see no progress after subscribing, try unsubscribing and resubscribing again until the download commences."
|
||||
fi
|
||||
sleep 1s
|
||||
foreground
|
||||
|
||||
until [[ -d $downloads_dir/${stage_mods[$i]} ]]; do
|
||||
[[ -f $ex ]] && return 1
|
||||
sleep 0.1s
|
||||
if [[ -d $workshop_dir/${stage_mods[$i]} ]]; then
|
||||
sleep 0.1s
|
||||
# NOTE: fallback for instantaneous downloads
|
||||
if [[ $mode == "auto" || $mode == "force" ]]; then
|
||||
if [[ -d $workshop_dir/${mod} ]]; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
foreground
|
||||
if [[ $mode == "auto" ]] || [[ $mode == "force" ]]; then
|
||||
echo "# Steam is downloading ${stage_mods[$i]} (mod $((i+1)) of ${#stage_mods[@]}). You do not need to manually Subscribe."
|
||||
else
|
||||
echo "# Steam is downloading ${stage_mods[$i]} (mod $((i+1)) of ${#stage_mods[@]})"
|
||||
fi
|
||||
until [[ -d $workshop_dir/${stage_mods[$i]} ]]; do
|
||||
[[ -f $ex ]] && return 1
|
||||
sleep 0.1s
|
||||
done
|
||||
|
||||
foreground
|
||||
echo "# ${stage_mods[$i]} moved to mods dir"
|
||||
done
|
||||
echo "100"
|
||||
}
|
||||
_watcher > >($steamsafe_zenity --pulsate --progress --auto-close --title="DZG Watcher" --width=500 2>/dev/null; rc=$?; [[ $rc -eq 1 ]] && touch $ex)
|
||||
|
||||
if [[ $mode == "force" ]]; then
|
||||
rm "$versions_file"
|
||||
check_timestamps
|
||||
return 0
|
||||
foreground
|
||||
case $mode in
|
||||
"auto" | "force")
|
||||
echo "# Steam is downloading ${mod} (mod $((i+1)) of ${length}). You do not need to manually Subscribe."
|
||||
;;
|
||||
*)
|
||||
echo "# Steam is downloading ${mod} (mod $((i+1)) of ${length})"
|
||||
;;
|
||||
esac
|
||||
|
||||
until [[ -d $workshop_dir/${mod} ]]; do
|
||||
[[ -f $ex ]] && return 1
|
||||
sleep 0.1s
|
||||
done
|
||||
|
||||
foreground
|
||||
if [[ ! -v signatures["${mod}"] ]]; then
|
||||
echo "$mod,$remote_time" >> "$versions_file"
|
||||
else
|
||||
sed -i "s/$mod,${signatures[$mod]}/$mod,$remote_time/" "$versions_file"
|
||||
fi
|
||||
echo "# ${mod} moved to mods dir"
|
||||
i+=1
|
||||
done
|
||||
echo "100"
|
||||
}
|
||||
install_mods(){
|
||||
local sanitized_mods="$1"
|
||||
local mode="$2"
|
||||
local ex="$state_path/dzg.watcher"
|
||||
|
||||
[[ -f $ex ]] && rm "$ex"
|
||||
|
||||
if [[ -f "$versions_file" ]]; then
|
||||
readarray -t local_mods < "$versions_file"
|
||||
else
|
||||
local_mods=()
|
||||
fi
|
||||
|
||||
local diff=$(compare "$sanitized_mods")
|
||||
if [[ -z $diff ]]; then
|
||||
if [[ $mode == "auto" ]]; then
|
||||
rm "$versions_file"
|
||||
check_timestamps
|
||||
declare -A mod_signatures
|
||||
local local_id
|
||||
local local_time
|
||||
for mod in "${local_mods[@]}"; do
|
||||
IFS="," read -ra fields <<< "$mod"
|
||||
local_id="${fields[0]}"
|
||||
local_time="${fields[1]}"
|
||||
mod_signatures[$local_id]=$local_time
|
||||
done
|
||||
|
||||
readarray -t remote_mods <<< "$sanitized_mods"
|
||||
declare -A download
|
||||
local remote_timestamps
|
||||
local remote_id
|
||||
local remote_time
|
||||
remote_timestamps=$(get_timestamps "${remote_mods[@]}")
|
||||
for mod in $(<<< "$remote_timestamps" jq -r '.response.publishedfiledetails[]|"\(.publishedfileid),\(.time_updated)"'); do
|
||||
IFS="," read -ra fields <<< "$mod"
|
||||
remote_id="${fields[0]}"
|
||||
remote_time="${fields[1]}"
|
||||
|
||||
# NOTE: mod is not present in versions file
|
||||
if [[ ! -v mod_signatures["$remote_id"] ]]; then
|
||||
download["$remote_id"]=$remote_time
|
||||
continue
|
||||
fi
|
||||
launch "$ip" "$gameport" "$sanitized_mods" "$appid"
|
||||
else
|
||||
printf "User aborted download process, or some mods may have failed to download. Try connecting again to resync."
|
||||
# NOTE: mod timestamp is out of date
|
||||
local_time="${mod_signatures["$remote_id"]}"
|
||||
if [[ $remote_time != "$local_time" ]]; then
|
||||
download["$remote_id"]=$remote_time
|
||||
continue
|
||||
fi
|
||||
done
|
||||
|
||||
[[ ${#download[@]} -lt 1 ]] && return
|
||||
if [[ $(check_architecture) -eq 1 ]] && [[ $(test_display_mode) == "gm" ]]; then
|
||||
printf "Use Desktop Mode to download mods on Steam Deck"
|
||||
exit 1
|
||||
fi
|
||||
watcher download mod_signatures "$mode" > >($steamsafe_zenity --pulsate --progress --auto-close --title="DZG Watcher" --width=500 2>/dev/null; rc=$?; [[ $rc -eq 1 ]] && touch "$ex")
|
||||
}
|
||||
test_display_mode(){
|
||||
pgrep -a gamescope | grep -q "generate-drm-mode"
|
||||
|
||||
@ -295,7 +295,7 @@ def get_dayz_version(file: Path) -> DayZVersion | Exception:
|
||||
try:
|
||||
version = get_version(file)
|
||||
except Exception as e:
|
||||
return e
|
||||
raise e
|
||||
patch = str(version.build) + str(version.revision)
|
||||
dz_vers = DayZVersion(version.major, version.minor, int(patch))
|
||||
return dz_vers
|
||||
|
||||
415
helpers/ui.py
415
helpers/ui.py
@ -19,7 +19,7 @@ from collections.abc import Callable
|
||||
from concurrent.futures import wait
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from pathlib import Path
|
||||
from typing import Literal, Self, Any
|
||||
from typing import Literal, Self, Any, TYPE_CHECKING
|
||||
|
||||
import servers as Servers # noqa E402
|
||||
import pefile as PeFile # noqa E402
|
||||
@ -32,12 +32,16 @@ from pefile import (
|
||||
)
|
||||
from pefile import VersionMatch
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from servers import Prereqs
|
||||
|
||||
locale.setlocale(locale.LC_ALL, "")
|
||||
|
||||
import gi # noqa E402
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, GLib, Gdk, GObject, Pango # noqa E402
|
||||
gi.require_version("GLibUnix", "2.0")
|
||||
from gi.repository import Gtk, GLib, GLibUnix, Gdk, GObject, Pango # noqa E402
|
||||
|
||||
# https://bugzilla.gnome.org/show_bug.cgi?id=708676
|
||||
warnings.filterwarnings("ignore", ".*g_value_get_int", Warning)
|
||||
@ -850,9 +854,7 @@ def call_out(command: str, *args: str) -> subprocess.CompletedProcess:
|
||||
arg_ar = []
|
||||
for i in args:
|
||||
arg_ar.append(i)
|
||||
logger.info(
|
||||
f"Context '{name}' calling subprocess '{command}' with args '{arg_ar}'"
|
||||
)
|
||||
logger.info(f"Context '{name}' calling subprocess '{command}' with args '{arg_ar}'")
|
||||
proc = subprocess.run(
|
||||
["/usr/bin/env", "bash", funcs, command] + arg_ar,
|
||||
capture_output=True,
|
||||
@ -878,9 +880,7 @@ def spawn_dialog(msg: str, mode: Popup) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def process_shell_return_code(
|
||||
msg: str, code: int, original_input: RowType
|
||||
) -> None:
|
||||
def process_shell_return_code(msg: str, code: int, original_input: RowType) -> None:
|
||||
logger.info(
|
||||
f"Processing return code '{code}' for the input "
|
||||
f"'{original_input}', returned message '{msg}'"
|
||||
@ -907,9 +907,7 @@ def process_shell_return_code(
|
||||
spawn_dialog("Invalid Steam API key, reverting", Popup.NOTIFY)
|
||||
App.notebook.settings.revert(Preferences.STEAM)
|
||||
case 79: # failed settings update (bm)
|
||||
spawn_dialog(
|
||||
"Invalid Battlemetrics API key, reverting", Popup.NOTIFY
|
||||
)
|
||||
spawn_dialog("Invalid Battlemetrics API key, reverting", Popup.NOTIFY)
|
||||
App.notebook.settings.revert(Preferences.BM)
|
||||
case 80: # pop toast after successful settings change
|
||||
config_vals.clear()
|
||||
@ -975,9 +973,7 @@ def call_on_thread(
|
||||
if state:
|
||||
wait_dialog = GenericDialog(msg, Popup.WAIT)
|
||||
wait_dialog.show_all()
|
||||
thread = threading.Thread(
|
||||
target=_background, args=(subproc, args, wait_dialog)
|
||||
)
|
||||
thread = threading.Thread(target=_background, args=(subproc, args, wait_dialog))
|
||||
thread.start()
|
||||
else:
|
||||
"""
|
||||
@ -1002,18 +998,14 @@ def process_tree_option(choice: RowType) -> None:
|
||||
record = App.treeview.get_record_string()
|
||||
wait_msg = command.dict["wait_msg"]
|
||||
show_wait_dialog = True
|
||||
call_on_thread(
|
||||
show_wait_dialog, cmd_string, wait_msg, record, choice=choice
|
||||
)
|
||||
call_on_thread(show_wait_dialog, cmd_string, wait_msg, record, choice=choice)
|
||||
return
|
||||
|
||||
# modlist highlight stale action
|
||||
if context == WindowContext.TABLE_MODS and command == RowType.HIGHLIGHT:
|
||||
wait_msg = command.dict["wait_msg"]
|
||||
show_wait_dialog = True
|
||||
call_on_thread(
|
||||
show_wait_dialog, cmd_string, wait_msg, "", choice=choice
|
||||
)
|
||||
call_on_thread(show_wait_dialog, cmd_string, wait_msg, "", choice=choice)
|
||||
return
|
||||
|
||||
if command == RowType.CHANGELOG:
|
||||
@ -1027,13 +1019,7 @@ def process_tree_option(choice: RowType) -> None:
|
||||
return
|
||||
|
||||
record = str_to_record(record)
|
||||
thread_new_with_dialog(
|
||||
App.treeview.prepare_connection,
|
||||
parse_shell_output,
|
||||
"Querying server",
|
||||
command,
|
||||
[record],
|
||||
)
|
||||
prepare_connection(command, record)
|
||||
return
|
||||
|
||||
match command.dict["type"]:
|
||||
@ -1061,48 +1047,50 @@ def parse_shell_output(proc: subprocess.CompletedProcess, row: RowType):
|
||||
process_shell_return_code(msg, proc.returncode, row)
|
||||
|
||||
|
||||
def thread_new_with_dialog(
|
||||
func: Callable,
|
||||
callback: Callable | None,
|
||||
msg: str,
|
||||
row: RowType | None,
|
||||
args: list,
|
||||
) -> None:
|
||||
"""
|
||||
Pop a GenericDialog transient to App.treeview and
|
||||
call a function on a thread, with optional callback.
|
||||
Chiefly used for connection-related subprocesses.
|
||||
def prepare_connection(rowtype: RowType, record: Record) -> None:
|
||||
def background(rowtype: RowType, record: Record) -> None:
|
||||
def cleanup() -> None:
|
||||
App.treeview.wait_dialog.destroy()
|
||||
# NOTE: When using RowType.CONN_BY_IP, the gameport needs to be interpolated
|
||||
record.gameport = prereqs.gameport
|
||||
addr = record_to_str(record)
|
||||
|
||||
After completion, the dialog is destroyed in the main event loop
|
||||
and additional exception handling occurs.
|
||||
if proceed is False:
|
||||
spawn_dialog(msg, Popup.NOTIFY)
|
||||
return
|
||||
if msg != "":
|
||||
res = spawn_dialog(msg, Popup.CONFIRM)
|
||||
if res is False:
|
||||
try_connect(addr, str(prereqs.appid), str(pefile_path), rowtype)
|
||||
else:
|
||||
try_connect(addr, str(prereqs.appid), str(pefile_path), rowtype)
|
||||
|
||||
This is intended as a bridge between legacy shell methods and the UI.
|
||||
A more abstracted version of call_on_thread() for when extra threaded
|
||||
processing occurs before calls to shell subprocesses.
|
||||
"""
|
||||
proceed, msg, pefile_path, prereqs = App.treeview.get_prereqs(record)
|
||||
GLib.idle_add(cleanup)
|
||||
|
||||
def background(*args):
|
||||
msg = "Checking prerequisites"
|
||||
App.treeview.dialog_show(msg)
|
||||
thread = threading.Thread(target=background, args=(rowtype, record))
|
||||
thread.start()
|
||||
|
||||
|
||||
def try_connect(addr: str, appid: str, path: str, row: RowType) -> None:
|
||||
def background(addr, appid, path):
|
||||
def cleanup():
|
||||
App.treeview.dialog_hide()
|
||||
if exception is not None:
|
||||
spawn_dialog(str(exception), Popup.NOTIFY)
|
||||
process_user_input(row)
|
||||
return
|
||||
if callback is not None and proc is not None:
|
||||
callback(proc, row)
|
||||
parse_shell_output(proc, row)
|
||||
|
||||
exception = None
|
||||
proc = None
|
||||
try:
|
||||
proc = func(*args)
|
||||
proc = call_out("try_connect", addr, appid, path)
|
||||
except Exception as e:
|
||||
exception = e
|
||||
logger.critical(e)
|
||||
GLib.idle_add(cleanup)
|
||||
return
|
||||
GLib.idle_add(cleanup)
|
||||
|
||||
msg = "Querying server"
|
||||
App.treeview.dialog_show(msg)
|
||||
thread = threading.Thread(target=background, args=(args))
|
||||
thread = threading.Thread(target=background, args=(addr, appid, path))
|
||||
thread.start()
|
||||
|
||||
|
||||
@ -1142,27 +1130,32 @@ def record_to_str(record: Record) -> str:
|
||||
|
||||
|
||||
def connect_by_ip(enum: RowType, response: str) -> None:
|
||||
def _prep(response: str) -> None:
|
||||
try:
|
||||
record = Servers.validate_ip(response)
|
||||
proc = App.treeview.prepare_connection(record)
|
||||
return proc
|
||||
|
||||
thread_new_with_dialog(
|
||||
_prep, parse_shell_output, "Querying IP", enum, [response]
|
||||
)
|
||||
return
|
||||
except Exception as e:
|
||||
spawn_dialog(str(e), Popup.NOTIFY)
|
||||
return
|
||||
prepare_connection(enum, record)
|
||||
|
||||
|
||||
def connect_by_id(enum: RowType, response: str, key: str) -> None:
|
||||
def _prep(key: str, response: str) -> None:
|
||||
record = Servers.query_bm_api(key, response)
|
||||
proc = App.treeview.prepare_connection(record)
|
||||
return proc
|
||||
def cleanup() -> None:
|
||||
App.treeview.dialog_hide()
|
||||
prepare_connection(enum, record)
|
||||
|
||||
thread_new_with_dialog(
|
||||
_prep, parse_shell_output, "Querying API", enum, [key, response]
|
||||
)
|
||||
return
|
||||
try:
|
||||
record = Servers.query_bm_api(key, response)
|
||||
except Exception as e:
|
||||
spawn_dialog(str(e), Popup.NOTIFY)
|
||||
App.treeview.dialog_hide()
|
||||
return
|
||||
GLib.idle_add(cleanup)
|
||||
|
||||
msg = "Validating ID"
|
||||
App.treeview.dialog_show(msg)
|
||||
thread = threading.Thread(target=_prep, args=(key, response))
|
||||
thread.start()
|
||||
|
||||
|
||||
def process_user_input(enum: RowType) -> None:
|
||||
@ -1173,9 +1166,7 @@ def process_user_input(enum: RowType) -> None:
|
||||
if enum == RowType.CONN_BY_ID:
|
||||
key = query_config("api_key")[0]
|
||||
if len(key) == 0:
|
||||
spawn_dialog(
|
||||
"No Battlemetrics API key is set; see Options", Popup.NOTIFY
|
||||
)
|
||||
spawn_dialog("No Battlemetrics API key is set; see Options", Popup.NOTIFY)
|
||||
return
|
||||
|
||||
user_entry = EntryDialog(prompt, Popup.ENTRY, link_label)
|
||||
@ -1196,9 +1187,7 @@ def process_user_input(enum: RowType) -> None:
|
||||
|
||||
show_wait_dialog = True
|
||||
wait_msg = "Working"
|
||||
call_on_thread(
|
||||
show_wait_dialog, cmd_string, wait_msg, response, choice=enum
|
||||
)
|
||||
call_on_thread(show_wait_dialog, cmd_string, wait_msg, response, choice=enum)
|
||||
return
|
||||
|
||||
|
||||
@ -1286,9 +1275,7 @@ class OuterWindow(Gtk.Window):
|
||||
logger.info(f"Using default window size {w},{h}")
|
||||
self.set_default_size(w, h)
|
||||
|
||||
def _on_delete_event(
|
||||
self, window: "OuterWindow", event: Gdk.EventKey
|
||||
) -> None:
|
||||
def _on_delete_event(self, window: "OuterWindow", event: Gdk.EventKey) -> None:
|
||||
self.halt_proc_and_quit()
|
||||
|
||||
def halt_proc_and_quit(self) -> None:
|
||||
@ -1355,8 +1342,7 @@ class RightPanel(Gtk.Box):
|
||||
self.pack_start(self.filters_vbox, False, False, 0)
|
||||
|
||||
debug_tooltip = (
|
||||
"Used to perform a dry run without\n"
|
||||
"actually connecting to a server"
|
||||
"Used to perform a dry run without\n" "actually connecting to a server"
|
||||
)
|
||||
ping_tooltip = (
|
||||
"Refresh the ping for visible servers.\n"
|
||||
@ -1425,8 +1411,7 @@ class RightPanel(Gtk.Box):
|
||||
rows = ModelManager.get_filtered()
|
||||
with ThreadPoolExecutor(100) as executor:
|
||||
futures = [
|
||||
executor.submit(Servers.ping, i, row)
|
||||
for i, row in enumerate(rows)
|
||||
executor.submit(Servers.ping, i, row) for i, row in enumerate(rows)
|
||||
]
|
||||
wait(futures)
|
||||
for future in futures:
|
||||
@ -1802,9 +1787,7 @@ class ModelManagerSingleton:
|
||||
|
||||
|
||||
class TreeView(Gtk.TreeView):
|
||||
__gsignals__ = {
|
||||
"on_distcalc_started": (GObject.SignalFlags.RUN_FIRST, None, ())
|
||||
}
|
||||
__gsignals__ = {"on_distcalc_started": (GObject.SignalFlags.RUN_FIRST, None, ())}
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
@ -1923,9 +1906,7 @@ class TreeView(Gtk.TreeView):
|
||||
if self.current_proc and self.current_proc.is_alive():
|
||||
self.current_proc.terminate()
|
||||
|
||||
def _delete_note(
|
||||
self, button: Gtk.Button, user_entry: Gtk.Box, addr: str
|
||||
) -> None:
|
||||
def _delete_note(self, button: Gtk.Button, user_entry: Gtk.Box, addr: str) -> None:
|
||||
box = button.get_parent()
|
||||
dialog = box.get_parent()
|
||||
try:
|
||||
@ -2017,9 +1998,7 @@ class TreeView(Gtk.TreeView):
|
||||
model.remove(it)
|
||||
|
||||
block_signals()
|
||||
thread = threading.Thread(
|
||||
target=ModelManager.resync_model, args=(addr, qport)
|
||||
)
|
||||
thread = threading.Thread(target=ModelManager.resync_model, args=(addr, qport))
|
||||
thread.start()
|
||||
|
||||
def remove_from_history(self) -> None:
|
||||
@ -2043,16 +2022,12 @@ class TreeView(Gtk.TreeView):
|
||||
if hasattr(TreeView, menu_item.action):
|
||||
func = getattr(TreeView, menu_item.action)
|
||||
msg = (
|
||||
f"User clicked context menu '{menu_item.get_label()}', "
|
||||
f"calls {func}"
|
||||
f"User clicked context menu '{menu_item.get_label()}', " f"calls {func}"
|
||||
)
|
||||
logger.info(msg)
|
||||
func(self)
|
||||
else:
|
||||
msg = (
|
||||
f"Context menu function for '{menu_item.action}' "
|
||||
f"does not exist"
|
||||
)
|
||||
msg = f"Context menu function for '{menu_item.action}' " f"does not exist"
|
||||
u_msg = (
|
||||
f"Something went wrong when accessing the method "
|
||||
f"'{menu_item.action}'"
|
||||
@ -2093,9 +2068,7 @@ class TreeView(Gtk.TreeView):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _on_button_release(
|
||||
self, widget: Gtk.Widget, event: Gdk.EventButton
|
||||
) -> None:
|
||||
def _on_button_release(self, widget: Gtk.Widget, event: Gdk.EventButton) -> None:
|
||||
if event.type is Gdk.EventType.BUTTON_RELEASE and event.button != 3:
|
||||
return
|
||||
|
||||
@ -2179,9 +2152,7 @@ class TreeView(Gtk.TreeView):
|
||||
if event.type is Gdk.EventType.KEY_PRESS and event.keyval is Gdk.KEY_l:
|
||||
if self.is_selection_empty():
|
||||
return
|
||||
self.menu.popup_at_widget(
|
||||
widget, Gdk.Gravity.CENTER, Gdk.Gravity.WEST
|
||||
)
|
||||
self.menu.popup_at_widget(widget, Gdk.Gravity.CENTER, Gdk.Gravity.WEST)
|
||||
else:
|
||||
self.menu.popup_at_pointer(event)
|
||||
self.menu.select_first(False)
|
||||
@ -2195,9 +2166,7 @@ class TreeView(Gtk.TreeView):
|
||||
return None
|
||||
call_out("start_cooldown", "", "")
|
||||
|
||||
thread = threading.Thread(
|
||||
target=self._background_player_count, args=()
|
||||
)
|
||||
thread = threading.Thread(target=self._background_player_count, args=())
|
||||
thread.start()
|
||||
|
||||
def get_current_iter(self) -> Gtk.TreeIter | None:
|
||||
@ -2212,9 +2181,7 @@ class TreeView(Gtk.TreeView):
|
||||
|
||||
context = App.treeview.get_subpage_label()
|
||||
row_sel = self.get_value_at_index(0)
|
||||
logger.info(
|
||||
f"Tree selection for context '{context}' changed to '{row_sel}'"
|
||||
)
|
||||
logger.info(f"Tree selection for context '{context}' changed to '{row_sel}'")
|
||||
|
||||
if self.current_proc and self.current_proc.is_alive():
|
||||
self.current_proc.terminate()
|
||||
@ -2272,9 +2239,7 @@ class TreeView(Gtk.TreeView):
|
||||
self.set_cursor(path)
|
||||
return None
|
||||
|
||||
def _on_keypress(
|
||||
self, treeview: Gtk.TreeView, event: Gdk.EventKey
|
||||
) -> bool | None:
|
||||
def _on_keypress(self, treeview: Gtk.TreeView, event: Gdk.EventKey) -> bool | None:
|
||||
keyname = Gdk.keyval_name(event.keyval)
|
||||
grid = App.grid
|
||||
if event.state is Gdk.ModifierType.CONTROL_MASK:
|
||||
@ -2329,9 +2294,7 @@ class TreeView(Gtk.TreeView):
|
||||
return False
|
||||
return None
|
||||
|
||||
def _on_key_release(
|
||||
self, treeview: Gtk.TreeView, event: Gdk.EventKey
|
||||
) -> None:
|
||||
def _on_key_release(self, treeview: Gtk.TreeView, event: Gdk.EventKey) -> None:
|
||||
"""
|
||||
Suppresses spamming on keydown
|
||||
"""
|
||||
@ -2446,10 +2409,7 @@ class TreeView(Gtk.TreeView):
|
||||
params = Servers.params
|
||||
serv = []
|
||||
with ThreadPoolExecutor() as executor:
|
||||
futures = [
|
||||
executor.submit(job, key, APPID_DAYZ, param)
|
||||
for param in params
|
||||
]
|
||||
futures = [executor.submit(job, key, APPID_DAYZ, param) for param in params]
|
||||
wait(futures)
|
||||
for future in futures:
|
||||
res = future.result()
|
||||
@ -2471,10 +2431,7 @@ class TreeView(Gtk.TreeView):
|
||||
|
||||
def _dump_lan(self, port: int) -> list | None:
|
||||
with ThreadPoolExecutor() as executor:
|
||||
futures = [
|
||||
executor.submit(Servers.test_ip, i, port)
|
||||
for i in range(1, 256)
|
||||
]
|
||||
futures = [executor.submit(Servers.test_ip, i, port) for i in range(1, 256)]
|
||||
wait(futures)
|
||||
servers = []
|
||||
for future in futures:
|
||||
@ -2493,6 +2450,8 @@ class TreeView(Gtk.TreeView):
|
||||
def _dump_servers(self, ips: list) -> list | None:
|
||||
if len(ips) == 0:
|
||||
return []
|
||||
# NOTE: block malformed records
|
||||
ips = [ip for ip in ips if len(ip.split(":")) == 3 and ip.split(":")[2] != ""]
|
||||
with ThreadPoolExecutor() as executor:
|
||||
futures = [
|
||||
executor.submit(
|
||||
@ -2597,9 +2556,7 @@ class TreeView(Gtk.TreeView):
|
||||
ModelManager.set_store(App.treeview.get_model())
|
||||
App.treeview.set_model(None)
|
||||
|
||||
thread = threading.Thread(
|
||||
target=ModelManager.filter, args=(mode, *args)
|
||||
)
|
||||
thread = threading.Thread(target=ModelManager.filter, args=(mode, *args))
|
||||
thread.start()
|
||||
|
||||
def _background_quad(self, dialog: "GenericDialog", mode: RowType) -> None:
|
||||
@ -2758,13 +2715,9 @@ class TreeView(Gtk.TreeView):
|
||||
self.initialize_columns()
|
||||
self.set_selection_mode(Gtk.SelectionMode.SINGLE)
|
||||
|
||||
self.wait_dialog = GenericDialog(
|
||||
"Fetching server metadata", Popup.WAIT
|
||||
)
|
||||
self.wait_dialog = GenericDialog("Fetching server metadata", Popup.WAIT)
|
||||
self.wait_dialog.show_all()
|
||||
thread = threading.Thread(
|
||||
target=self._query_servers, args=(mode, port)
|
||||
)
|
||||
thread = threading.Thread(target=self._query_servers, args=(mode, port))
|
||||
thread.start()
|
||||
|
||||
def _format_float(
|
||||
@ -2943,9 +2896,7 @@ class TreeView(Gtk.TreeView):
|
||||
def get_view(self):
|
||||
return self.view
|
||||
|
||||
def prepare_connection(
|
||||
self, record: Record
|
||||
) -> subprocess.CompletedProcess | None:
|
||||
def get_prereqs(self, record: Record) -> tuple[bool, str, str | None, "Prereqs"]:
|
||||
"""
|
||||
Always called on a thread with a dialog on the transient parent window
|
||||
"""
|
||||
@ -2953,50 +2904,39 @@ class TreeView(Gtk.TreeView):
|
||||
if prereqs.appid is None:
|
||||
logger.warning(f"Query to '{record.ip}:{record.qport}' timed out")
|
||||
msg = "Timed out when querying server, check IP or try again later"
|
||||
spawn_dialog(msg, Popup.NOTIFY)
|
||||
return None
|
||||
return (False, msg, None, prereqs)
|
||||
|
||||
build = "DayZ" if prereqs.appid == APPID_DAYZ else "DayZ Experimental"
|
||||
steam_path = query_config("default_steam_path")[0]
|
||||
|
||||
if len(steam_path) < 1:
|
||||
logger.critical(
|
||||
"Config file has no value set for 'default_steam_path'"
|
||||
)
|
||||
logger.critical("Config file has no value set for 'default_steam_path'")
|
||||
msg = "Local Steam installation is not set, possibly malformed config file."
|
||||
spawn_dialog(msg, Popup.NOTIFY)
|
||||
return None
|
||||
return (False, msg, None, prereqs)
|
||||
|
||||
try:
|
||||
pefile_path = PeFile.get_pefile_path(steam_path, prereqs.appid)
|
||||
except AppNotInstalledError:
|
||||
logger.critical(
|
||||
f"'{prereqs.appid}' not found in user's libraryfolders"
|
||||
)
|
||||
logger.critical(f"'{prereqs.appid}' not found in user's libraryfolders")
|
||||
msg = (
|
||||
f"This server is running {build}. You can install "
|
||||
f"{build} by searching for it in your Steam library. "
|
||||
f"If you recently installed {build} or moved it to a different drive, "
|
||||
"restart Steam to allow these changes to synchronize, then try again."
|
||||
)
|
||||
spawn_dialog(msg, Popup.NOTIFY)
|
||||
return None
|
||||
return (False, msg, None, prereqs)
|
||||
except AppMovedError:
|
||||
logger.critical(
|
||||
f"Library folder synch error for '{prereqs.appid}'"
|
||||
)
|
||||
logger.critical(f"Library folder synch error for '{prereqs.appid}'")
|
||||
msg = (
|
||||
f"Steam is reporting that {build} is installed at a non-existent location. "
|
||||
f"If you recently installed {build} or moved it to a different drive, "
|
||||
"restart Steam to allow these changes to synchronize, then try again."
|
||||
)
|
||||
spawn_dialog(msg, Popup.NOTIFY)
|
||||
return None
|
||||
return (False, msg, None, prereqs)
|
||||
except (VDFLoadError, PeFileError, Exception) as e:
|
||||
logger.critical(e)
|
||||
msg = "Steam settings or DayZ installation may be corrupted. Try restarting Steam."
|
||||
spawn_dialog(msg, Popup.NOTIFY)
|
||||
return None
|
||||
return (False, msg, None, prereqs)
|
||||
|
||||
try:
|
||||
local_vers = PeFile.get_dayz_version(pefile_path)
|
||||
@ -3008,7 +2948,7 @@ class TreeView(Gtk.TreeView):
|
||||
local_vers = None
|
||||
|
||||
try:
|
||||
remote_vers = PeFile.dayz_version_from_str(prereqs.version)
|
||||
remote_vers = PeFile.dayz_version_from_str(prereqs)
|
||||
except Exception:
|
||||
remote_vers = None
|
||||
|
||||
@ -3021,18 +2961,15 @@ class TreeView(Gtk.TreeView):
|
||||
f"This server is running a newer build ({prereqs.version}) of {build} than "
|
||||
f"your local version. You may be unable to connect. Proceed anyway?"
|
||||
)
|
||||
res = spawn_dialog(msg, Popup.CONFIRM)
|
||||
if res is True:
|
||||
return None
|
||||
return (True, msg, pefile_path, prereqs)
|
||||
case VersionMatch.LOCAL_NEWER:
|
||||
msg = (
|
||||
f"This server is running an out-of-date build ({prereqs.version}) of {build}. "
|
||||
"You may be unable to connect. Proceed anyway?"
|
||||
)
|
||||
res = spawn_dialog(msg, Popup.CONFIRM)
|
||||
if res is True:
|
||||
return None
|
||||
return (True, msg, pefile_path, prereqs)
|
||||
case VersionMatch.SAME_VERSION:
|
||||
return (True, "", pefile_path, prereqs)
|
||||
pass
|
||||
|
||||
if prereqs.password is True:
|
||||
@ -3040,19 +2977,9 @@ class TreeView(Gtk.TreeView):
|
||||
"This server is password-protected and you will be "
|
||||
"prompted when connecting. Do you want to proceed?"
|
||||
)
|
||||
res = spawn_dialog(msg, Popup.CONFIRM)
|
||||
if res is True:
|
||||
return None
|
||||
return (True, msg, pefile_path, prereqs)
|
||||
|
||||
"""
|
||||
When using RowType.CONN_BY_IP, the gameport needs to be interpolated
|
||||
"""
|
||||
record.gameport = prereqs.gameport
|
||||
addr = record_to_str(record)
|
||||
proc = call_out(
|
||||
"try_connect", addr, str(prereqs.appid), str(pefile_path)
|
||||
)
|
||||
return proc
|
||||
return (True, "", pefile_path, prereqs)
|
||||
|
||||
@signal_emission
|
||||
@update_window_labels
|
||||
@ -3122,13 +3049,7 @@ class TreeView(Gtk.TreeView):
|
||||
record = self.get_record()
|
||||
if record is None:
|
||||
return
|
||||
thread_new_with_dialog(
|
||||
self.prepare_connection,
|
||||
parse_shell_output,
|
||||
"Querying server",
|
||||
None,
|
||||
[record],
|
||||
)
|
||||
prepare_connection(None, record)
|
||||
case _: # any other non-server option from the main menu
|
||||
process_tree_option(output)
|
||||
|
||||
@ -3213,7 +3134,7 @@ class GenericDialog(Gtk.MessageDialog):
|
||||
self.outer.set_margin_end(30)
|
||||
|
||||
def _on_dialog_delete(
|
||||
self, response_id: Gtk.ResponseType, event: Gdk.Event
|
||||
self, response_id: Gtk.ResponseType, event: Gdk.Event
|
||||
) -> Literal[True]:
|
||||
"""
|
||||
Prevent manual dialog destruction
|
||||
@ -3292,9 +3213,7 @@ class LanDialog(Gtk.MessageDialog):
|
||||
self.connect("delete-event", self.restore_context)
|
||||
|
||||
self.entry.connect("insert-text", self._on_text_typed)
|
||||
self.entry.get_property("buffer").connect(
|
||||
"deleted-text", self._on_text_deleted
|
||||
)
|
||||
self.entry.get_property("buffer").connect("deleted-text", self._on_text_deleted)
|
||||
|
||||
def _validate(self, text: str) -> None:
|
||||
if self._is_invalid(text):
|
||||
@ -3410,9 +3329,7 @@ class DetailsDialog(GenericDialog):
|
||||
for i, column_title in enumerate(["Item", "Details"]):
|
||||
renderer = Gtk.CellRendererText(xalign=0)
|
||||
if i == 0:
|
||||
column = Gtk.TreeViewColumn(
|
||||
column_title, renderer, text=i, weight=2
|
||||
)
|
||||
column = Gtk.TreeViewColumn(column_title, renderer, text=i, weight=2)
|
||||
else:
|
||||
column = Gtk.TreeViewColumn(column_title, renderer, text=i)
|
||||
column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
|
||||
@ -3428,12 +3345,8 @@ class DetailsDialog(GenericDialog):
|
||||
scrollable_message = Gtk.ScrolledWindow()
|
||||
desc = Gtk.Label(label="Server message", valign=Gtk.Align.START)
|
||||
add_class(desc, "details-heading")
|
||||
box = Gtk.Box(
|
||||
orientation=Gtk.Orientation.VERTICAL, halign=Gtk.Align.CENTER
|
||||
)
|
||||
self.description = Gtk.Label(
|
||||
justify=Gtk.Justification.CENTER, wrap=True
|
||||
)
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, halign=Gtk.Align.CENTER)
|
||||
self.description = Gtk.Label(justify=Gtk.Justification.CENTER, wrap=True)
|
||||
sep = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
|
||||
sep.set_margin_bottom(10)
|
||||
for el in desc, sep, self.description:
|
||||
@ -3471,9 +3384,7 @@ class DetailsDialog(GenericDialog):
|
||||
self.run()
|
||||
self.destroy()
|
||||
|
||||
def _background(
|
||||
self, dialog: "GenericDialog", ip: str, qport: int
|
||||
) -> None:
|
||||
def _background(self, dialog: "GenericDialog", ip: str, qport: int) -> None:
|
||||
response = Servers.details(self.ip, self.qport)
|
||||
if response.success:
|
||||
for row in response.data:
|
||||
@ -3530,9 +3441,7 @@ class ModDialog(GenericDialog):
|
||||
|
||||
wait_dialog = GenericDialog("Fetching modlist", Popup.WAIT)
|
||||
wait_dialog.show_all()
|
||||
thread = threading.Thread(
|
||||
target=self._background, args=(wait_dialog, record)
|
||||
)
|
||||
thread = threading.Thread(target=self._background, args=(wait_dialog, record))
|
||||
thread.start()
|
||||
|
||||
def _background(self, dialog: "GenericDialog", record: str) -> None:
|
||||
@ -3557,9 +3466,7 @@ class ModDialog(GenericDialog):
|
||||
self.view.set_model(modlist_store)
|
||||
GLib.idle_add(_load)
|
||||
|
||||
def _parse_modlist_rows(
|
||||
self, data: subprocess.CompletedProcess
|
||||
) -> bool | int:
|
||||
def _parse_modlist_rows(self, data: subprocess.CompletedProcess) -> bool | int:
|
||||
lines = data.stdout.splitlines()
|
||||
hits = len(lines)
|
||||
reader = csv.reader(lines, delimiter=delimiter)
|
||||
@ -3589,9 +3496,7 @@ class ModDialog(GenericDialog):
|
||||
|
||||
|
||||
class LinkDialog(GenericDialog):
|
||||
def __init__(
|
||||
self, text: str, link: str | None, command: RowType, uid: str = ""
|
||||
):
|
||||
def __init__(self, text: str, link: str | None, command: RowType, uid: str = ""):
|
||||
super().__init__(text, Popup.NOTIFY)
|
||||
|
||||
text = textwrap.dedent(text)
|
||||
@ -3809,7 +3714,12 @@ class Options(Gtk.Box):
|
||||
client_store = Gtk.ListStore(str, str)
|
||||
client_store.append(["steam", "steam"])
|
||||
client_store.append(["flatpak", "flatpak run com.valvesoftware.Steam"])
|
||||
client_store.append(["flatpak (container)", "flatpak-spawn --host flatpak run com.valvesoftware.Steam"])
|
||||
client_store.append(
|
||||
[
|
||||
"flatpak (container)",
|
||||
"flatpak-spawn --host flatpak run com.valvesoftware.Steam",
|
||||
]
|
||||
)
|
||||
|
||||
renderer_text = Gtk.CellRendererText(ellipsize=Pango.EllipsizeMode.END)
|
||||
self.client_combo = Gtk.ComboBox.new_with_model(client_store)
|
||||
@ -3833,8 +3743,7 @@ class Options(Gtk.Box):
|
||||
self.force_button.set_sensitive(False)
|
||||
|
||||
msg = (
|
||||
"Manual: prompt to subscribe to mods in Steam. "
|
||||
"Auto: unmanned downloads."
|
||||
"Manual: prompt to subscribe to mods in Steam. " "Auto: unmanned downloads."
|
||||
)
|
||||
eb = InfoEventBox(msg)
|
||||
|
||||
@ -3893,9 +3802,7 @@ class Options(Gtk.Box):
|
||||
self.make_frame(mods_grid, "Mods"),
|
||||
self.make_frame(version_grid, "Version"),
|
||||
]:
|
||||
grid.attach(
|
||||
frame, col, row, self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT
|
||||
)
|
||||
grid.attach(frame, col, row, self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT)
|
||||
row += 1
|
||||
|
||||
self.scrollable = Gtk.ScrolledWindow(vexpand=True)
|
||||
@ -3941,9 +3848,7 @@ class Options(Gtk.Box):
|
||||
|
||||
return box
|
||||
|
||||
def _on_field_activated(
|
||||
self, entry: Gtk.Entry, context: Preferences
|
||||
) -> None:
|
||||
def _on_field_activated(self, entry: Gtk.Entry, context: Preferences) -> None:
|
||||
text = entry.get_text()
|
||||
button = entry.sibling
|
||||
if not self._is_valid_text(text, context):
|
||||
@ -3964,9 +3869,7 @@ class Options(Gtk.Box):
|
||||
for record in rows:
|
||||
col = 1
|
||||
for el in record:
|
||||
grid.attach(
|
||||
el, col, row, self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT
|
||||
)
|
||||
grid.attach(el, col, row, self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT)
|
||||
col += 1
|
||||
row += 1
|
||||
return grid
|
||||
@ -4019,9 +3922,7 @@ class Options(Gtk.Box):
|
||||
return
|
||||
process_toggle(RowType.TGL_BRANCH)
|
||||
|
||||
def _on_radio_toggled(
|
||||
self, button: Gtk.RadioButton, context: Preferences
|
||||
) -> None:
|
||||
def _on_radio_toggled(self, button: Gtk.RadioButton, context: Preferences) -> None:
|
||||
if App.treeview.subpage is not RowType.OPTIONS:
|
||||
return
|
||||
match context:
|
||||
@ -4165,18 +4066,14 @@ class Options(Gtk.Box):
|
||||
field[1].get_children()[1].set_sensitive(False)
|
||||
|
||||
try:
|
||||
pe_file_path = PeFile.get_pefile_path(
|
||||
default_steam_path, APPID_DAYZ
|
||||
)
|
||||
pe_file_path = PeFile.get_pefile_path(default_steam_path, APPID_DAYZ)
|
||||
vers = PeFile.get_dayz_version(pe_file_path)
|
||||
dayz_version = PeFile.dayz_version_to_str(vers)
|
||||
except Exception:
|
||||
dayz_version = "-"
|
||||
|
||||
try:
|
||||
exp_file_path = PeFile.get_pefile_path(
|
||||
default_steam_path, APPID_DAYZ_EXP
|
||||
)
|
||||
exp_file_path = PeFile.get_pefile_path(default_steam_path, APPID_DAYZ_EXP)
|
||||
vers = PeFile.get_dayz_version(exp_file_path)
|
||||
dayz_exp_version = PeFile.dayz_version_to_str(vers)
|
||||
except Exception:
|
||||
@ -4285,9 +4182,7 @@ class KeybindingsDialog(Gtk.Box):
|
||||
if not sep:
|
||||
grid.attach(desc, col, row, w, h)
|
||||
else:
|
||||
grid.attach_next_to(
|
||||
desc, sep, Gtk.PositionType.BOTTOM, w, h
|
||||
)
|
||||
grid.attach_next_to(desc, sep, Gtk.PositionType.BOTTOM, w, h)
|
||||
row += 1
|
||||
sep = None
|
||||
grid.attach_next_to(frame, desc, Gtk.PositionType.RIGHT, w, h)
|
||||
@ -4295,9 +4190,7 @@ class KeybindingsDialog(Gtk.Box):
|
||||
l_spacer = Gtk.Label(label="")
|
||||
r_spacer = Gtk.Label(label="")
|
||||
grid.attach(l_spacer, col, row + 1, w, h)
|
||||
grid.attach_next_to(
|
||||
r_spacer, l_spacer, Gtk.PositionType.RIGHT, w, h
|
||||
)
|
||||
grid.attach_next_to(r_spacer, l_spacer, Gtk.PositionType.RIGHT, w, h)
|
||||
row += 1
|
||||
return grid
|
||||
|
||||
@ -4306,9 +4199,7 @@ class KeybindingsDialog(Gtk.Box):
|
||||
col = 0
|
||||
w = 1
|
||||
h = 1
|
||||
sidebar = Gtk.Grid(
|
||||
row_homogeneous=True, orientation=Gtk.Orientation.VERTICAL
|
||||
)
|
||||
sidebar = Gtk.Grid(row_homogeneous=True, orientation=Gtk.Orientation.VERTICAL)
|
||||
for cat in categories:
|
||||
label = Gtk.Label(label=cat)
|
||||
add_class(label, "left-label")
|
||||
@ -4328,9 +4219,7 @@ class KeybindingsDialog(Gtk.Box):
|
||||
column = 1
|
||||
w = 1
|
||||
h = 1
|
||||
sidebar = self.build_sidebar(
|
||||
["Servers", "Navigation", "Vim-style keys"]
|
||||
)
|
||||
sidebar = self.build_sidebar(["Servers", "Navigation", "Vim-style keys"])
|
||||
separator = Gtk.Separator()
|
||||
keys_box = self.build_keys(items)
|
||||
|
||||
@ -4548,9 +4437,7 @@ class Statusbar(Gtk.Statusbar):
|
||||
|
||||
players_pretty = pluralize("players", players)
|
||||
hits_pretty = pluralize("matches", hits)
|
||||
formatted = (
|
||||
f"Found {hits:n} {hits_pretty} with {players:n} {players_pretty}"
|
||||
)
|
||||
formatted = f"Found {hits:n} {hits_pretty} with {players:n} {players_pretty}"
|
||||
suffix = "| Distance: calculating..."
|
||||
|
||||
if players == 0:
|
||||
@ -4602,9 +4489,7 @@ class Grid(Gtk.Grid):
|
||||
self.breadcrumbs = Gtk.Label(label="Main menu", halign=Gtk.Align.START)
|
||||
|
||||
self.attach(self.notebook, 0, 0, 3, 1)
|
||||
self.attach_next_to(
|
||||
self.breadcrumbs, self.notebook, Gtk.PositionType.TOP, 3, 1
|
||||
)
|
||||
self.attach_next_to(self.breadcrumbs, self.notebook, Gtk.PositionType.TOP, 3, 1)
|
||||
self.attach_next_to(
|
||||
self.statusbar, self.notebook, Gtk.PositionType.BOTTOM, 3, 1
|
||||
)
|
||||
@ -4666,11 +4551,23 @@ class App(Gtk.Application):
|
||||
)
|
||||
self.win.add_accel_group(accel)
|
||||
|
||||
GLib.unix_signal_add(
|
||||
GLib.PRIORITY_DEFAULT, signal.SIGINT, self._catch_sigint
|
||||
)
|
||||
self.setup_signals()
|
||||
Gtk.main()
|
||||
|
||||
def setup_signals(self) -> None:
|
||||
SIGNAL_ADD = "signal_add"
|
||||
SIGNAL_ADD_FULL = "signal_add_full"
|
||||
try:
|
||||
if SIGNAL_ADD in dir(GLibUnix):
|
||||
func = GLibUnix.signal_add
|
||||
elif SIGNAL_ADD_FULL in dir(GLibUnix):
|
||||
func = GLibUnix.signal_add_full
|
||||
else:
|
||||
func = GLib.unix_signal_add
|
||||
func(GLib.PRIORITY_DEFAULT, signal.SIGINT, self._catch_sigint)
|
||||
except Exception as e:
|
||||
logger.critical(e)
|
||||
|
||||
def _catch_sigint(self) -> None:
|
||||
self.win.halt_proc_and_quit()
|
||||
|
||||
@ -4755,9 +4652,7 @@ class ModSelectionPanel(Gtk.Box):
|
||||
|
||||
def toggle_select_stale_button(self, state: bool) -> None:
|
||||
if state:
|
||||
button = Gtk.Button(
|
||||
label="Select stale", margin_start=10, margin_end=10
|
||||
)
|
||||
button = Gtk.Button(label="Select stale", margin_start=10, margin_end=10)
|
||||
text = "Bulk selects all currently highlighted mods"
|
||||
button.set_tooltip_text(text)
|
||||
button.connect("clicked", self._on_button_clicked)
|
||||
@ -4877,9 +4772,7 @@ class FilterPanel(Gtk.Box):
|
||||
self.keyword_entry = Gtk.Entry()
|
||||
self.keyword_entry.set_placeholder_text("Filter by keyword")
|
||||
self.keyword_entry.connect("activate", self._on_keyword_enter)
|
||||
self.keyword_entry.connect(
|
||||
"key-press-event", self._on_keyword_keypress
|
||||
)
|
||||
self.keyword_entry.connect("key-press-event", self._on_keyword_keypress)
|
||||
|
||||
completion = Gtk.EntryCompletion(inline_completion=True)
|
||||
completion.set_text_column(0)
|
||||
@ -4947,9 +4840,7 @@ class FilterPanel(Gtk.Box):
|
||||
state = self.default_filters[label]
|
||||
check.set_active(state)
|
||||
|
||||
def _on_map_entry_keypress(
|
||||
self, entry: Gtk.Entry, event: Gdk.EventKey
|
||||
) -> None:
|
||||
def _on_map_entry_keypress(self, entry: Gtk.Entry, event: Gdk.EventKey) -> None:
|
||||
match event.keyval:
|
||||
case Gdk.KEY_Return:
|
||||
text = entry.get_text()
|
||||
@ -4992,9 +4883,7 @@ class FilterPanel(Gtk.Box):
|
||||
App.treeview.grab_focus()
|
||||
return False
|
||||
|
||||
def _on_keyword_keypress(
|
||||
self, entry: Gtk.Entry, event: Gdk.EventKey
|
||||
) -> bool:
|
||||
def _on_keyword_keypress(self, entry: Gtk.Entry, event: Gdk.EventKey) -> bool:
|
||||
match event.keyval:
|
||||
case Gdk.KEY_Up:
|
||||
return True
|
||||
@ -5005,9 +4894,7 @@ class FilterPanel(Gtk.Box):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _on_combo_keypress(
|
||||
self, combo: Gtk.ComboBox, event: Gdk.EventKey
|
||||
) -> bool:
|
||||
def _on_combo_keypress(self, combo: Gtk.ComboBox, event: Gdk.EventKey) -> bool:
|
||||
match event.keyval:
|
||||
case Gdk.KEY_Down:
|
||||
self.maps_combo.popup()
|
||||
|
||||
BIN
images/preview.png
Normal file
BIN
images/preview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 238 KiB |
Loading…
Reference in New Issue
Block a user