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/
|
||||
|
||||
541
CHANGELOG.md
541
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
|
||||
|
||||
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
|
||||
|
||||
191
dzgui.sh
191
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}')
|
||||
@ -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