Compare commits

..

24 Commits

Author SHA1 Message Date
aclist
252f07cbec
Merge pull request #287 from aclist/prerelease/6.0.6-beta.1
Some checks failed
Mirror to Codeberg / mirror-to-codeberg (push) Has been cancelled
chore: update symlink hash logic
2026-05-17 23:42:18 +09:00
aclist
a0f610ead7 chore: update symlink hash logic 2026-05-17 23:40:35 +09:00
aclist
ca4bcfc1a5
Merge pull request #285 from aclist/fix/server-mods-only
Some checks failed
Mirror to Codeberg / mirror-to-codeberg (push) Has been cancelled
change: only update mods local to server
2026-04-17 20:57:03 +09:00
aclist
ac4892d4f0 change: only update mods local to server 2026-04-17 20:47:25 +09:00
aclist
240b660923
Merge pull request #284 from aclist/prerelease/6.0.5-beta.1
Some checks failed
Mirror to Codeberg / mirror-to-codeberg (push) Has been cancelled
fix: update module checksums
2026-04-13 16:56:02 +09:00
aclist
150dfbca72 fix: update module checksums 2026-04-13 16:54:46 +09:00
aclist
4b78114174
Merge pull request #281 from aclist/fix/glib-unix-allversions
Some checks are pending
Mirror to Codeberg / mirror-to-codeberg (push) Waiting to run
fix: use conditional logic when testing GLibUnix
2026-04-13 04:38:50 +09:00
aclist
d1bde9901d fix: use conditional logic when testing GLibUnix 2026-04-13 03:43:22 +09:00
aclist
c5eba364d9
Merge pull request #278 from aclist/fix/glib-unix
Some checks failed
Mirror to Codeberg / mirror-to-codeberg (push) Has been cancelled
fix: use GLibUnix namespace
2026-04-10 19:03:06 +09:00
aclist
5460b53c26 fix: version number in changelog 2026-04-10 18:52:04 +09:00
aclist
99301b3312 fix: uncomment functions 2026-04-10 18:50:58 +09:00
aclist
8d30af9dee fix: use GLibUnix namespace 2026-04-10 16:50:31 +09:00
aclist
8a8109b7df chore: add preview image
Some checks failed
Mirror to Codeberg / mirror-to-codeberg (push) Has been cancelled
2026-03-30 20:56:16 +09:00
aclist
e4eaf25c8f chore: update readme 2026-03-30 20:16:58 +09:00
aclist
0daf6089b9 fix: raise error correctly
Some checks failed
Mirror to Codeberg / mirror-to-codeberg (push) Has been cancelled
2026-01-26 16:53:42 +09:00
aclist
7e4197cdbc fix: uncomment code
Some checks failed
Mirror to Codeberg / mirror-to-codeberg (push) Has been cancelled
2026-01-18 15:04:26 +09:00
aclist
c0f9d8a508
Merge pull request #267 from aclist/fix/threaded-dialog
fix/threaded dialog
2026-01-18 15:02:43 +09:00
aclist
2da2b19001
Merge branch 'testing' into fix/threaded-dialog 2026-01-18 15:01:48 +09:00
aclist
62936fa7b7 fix: threaded dialog 2026-01-18 15:00:12 +09:00
aclist
e9fecc7fb7 fix: explicitly use python 3.13
Some checks failed
Mirror to Codeberg / mirror-to-codeberg (push) Has been cancelled
2026-01-17 01:21:19 +09:00
aclist
11c29112c2 fix: write file after wiping cache
Some checks failed
Mirror to Codeberg / mirror-to-codeberg (push) Has been cancelled
2026-01-15 05:18:13 +09:00
aclist
317f60eddd chore: update checksums 2026-01-15 04:36:21 +09:00
aclist
9fbe5029e1 fix: steam deck compatible source path expansion 2026-01-15 04:35:56 +09:00
aclist
896802cd6f chore: update checksums
Some checks are pending
Mirror to Codeberg / mirror-to-codeberg (push) Waiting to run
2026-01-15 02:49:24 +09:00
5 changed files with 624 additions and 493 deletions

View File

@ -1,9 +1,38 @@
# Changelog # Changelog
## [6.0.2] 2026-01-26 ## [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 ## Fixed
- Explicitly use Python 3.13 when calling subprocesses
- Raise error correctly from module - 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 - Do not spawn dialogs from inside of thread when fetching prereqs
## Changed ## Changed
- Optimize time complexity of startup checks - Optimize time complexity of startup checks
@ -11,57 +40,102 @@
- Rename some internal functions - Rename some internal functions
- Wrap entire startup process in dialog - Wrap entire startup process in dialog
## [6.0.1] 2026-01-16 ## [6.0.1-beta.1] 2026-01-16
## Fixed ## Fixed
- Explicitly use Python 3.13 when calling subprocesses - 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 ## 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 - More descriptive help dialogs when initial dependencies are missing
## [6.0.0-beta.16] 2026-01-04
## Fixed ## 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) - 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 ## Changed
- Normalized buttons in dialogs and restored proper padding - Optimize time complexity of pre-boot checks (GaryBlackbourne)
- GTK errors being emitted to stdout when inserting debug table - Moved location of notes file
- Entry dialog sensitivity when validating API keys - Rewrote distance calculation module (GaryBlackbourne)
- Additional validation on entry dialogs to prevent submitting empty text
- Suppress errors during pre-boot checks when mods are not installed ## [6.0.0-beta.7] 2025-09-20
- Centered filter checkboxes within panel ## Added
- Do not pop unhighlight/select stale buttons if no stale mods exist - Users can save text notes to describe servers
- Improved keybinding interaction with side panels
- Suppress typeahead search in mod dialogs ## Changed
- Do not trigger global API cooldown if no LAN/favorite servers are found - State file serialization methods
- Fix table column expansion in server mod dialogs
- Missing parameters when closing application via window decorations ## [6.0.0-beta.6] 2025-09-12
- Statusbar contents not updating on certain pages ## Added
- Prevent debug button from activating when in certain input fields - Support DayZ Experimental
- Window size not updating correctly when unmaximizing window after changing "fullscreen at boot" setting - Show additional client information in Options menu
- Branch toggle signal being emitted when entering Options menu from other contexts - Warn user of client version mismatches
- Support clickable hyperlinks
## Fixed
- Mods rarely not appearing in local mod list if download completed too quickly - Mods rarely not appearing in local mod list if download completed too quickly
- Statusbar not updating when clicking a row after spamming keyboard input - Statusbar not updating when clicking a row after spamming keyboard input
- Extraneous logs being generated when subscribing to mods - Extraneous logs being generated when subscribing to mods
@ -69,188 +143,342 @@
- Newline terminators in history file - Newline terminators in history file
- Floating point number calculation - Floating point number calculation
- Window resizing too small if no prior resolution was set - 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 ## [6.0.0-beta.5] 2025-08-20
- UI not being constructed correctly if CHANGELOG.md was missing ## Fixed
- Config file erroneously getting updated when populating settings menu - Servers returning malformed A2S_INFO blocking server browser from loading
- Leaky variable name in dialog titles
- Prevent extraneous signals from propagating when column width is adjusted ## [6.0.0-beta.4] 2025-08-13
- Server filter panel not being hidden when entering other page contexts ## Fixed
- Path to remote changelog being constructed incorrectly - Branch toggle signal being emitted when entering Options menu from other contexts
- Dialogs with newlines breaking output in log table
- Path discovery during first-time setup when parsing filepaths with whitespaces ## [6.0.0-beta.3] 2025-08-08
- First-time setup dialog continuously triggering when DayZ install path had whitespaces in it ## 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 ## Changed
- Require Python 3.13
- Reduced global API cooldown from 60s to 30s - Reduced global API cooldown from 60s to 30s
- Clarify dialog messages when DayZ path could not be found - Clarify dialog messages when DayZ path could not be found
- Auto-focus first item when opening context menus - Auto-focus first item when opening context menus
- Opacity setting on side buttons when in a different context - Opacity setting on side buttons when in a different context
- Refactored BM API key validation to account for new key format - 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 ## Dropped
- Ping readout in statusbar - Ping readout in statusbar
- Extraneous information from right statusbar
## [5.8.3] 2026-01-04 ## [5.8.0-beta.2] 2025-07-06
## 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
## Added ## Added
- Filter servers by official/unofficial status - Filter servers by official/unofficial status
- Automatically fetch geolocation records
## Changed ## Changed
- Updated internal versioning of helper files - Updated internal versioning of helper files
## [5.8.0-beta.1] 2025-06-05
## Added
- Automatically fetch geolocation records
## Fixed ## Fixed
- Corrected erroneous 2024 date in prior changelog entries - Corrected erroneous 2024 date in prior changelog entries
## [5.7.0] 2025-04-17 ## [5.7.1-beta.1] 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
## Changed ## 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 ## 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 - 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 ## [5.7.0-beta.11] 2025-03-20
## Changed ## Fixed
- Update IP database records for 2025-03 - 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 ## Fixed
- Livonia server results being dropped from batch queries - Livonia server results being dropped from batch queries
## [5.6.4] 2025-02-10 ## [5.7.0-beta.8] 2025-02-10
### Changed ## Changed
- Drop launch flag and check for invocation through Steam automatically - Drop launch flag and check for invocation through Steam automatically
## [5.6.3] 2025-02-09 ## [5.7.0-beta.7] 2025-02-09
### Changed ## Changed
- Update IP database records for 2025-02 - Update IP database records for 2025-02
## [5.6.2] 2025-01-22 ## [5.7.0-beta.6] 2025-01-10
### Fixed ## Fixed
- Resolve regression introduced with IP resolution feature in 5.6.0 (restores functionality of right-click action: Add to My Servers) - Resolve regression introduced with IP resolution feature in 13c6813
## [5.6.1] 2025-01-10 ## [5.7.0-beta.5] 2025-01-10
### Fixed ## Changed
- Add fallback support for jq 1.6 - Stricter redacting of usernames (again)
## [5.6.0] 2025-01-06 ## [5.7.0-beta.4] 2025-01-10
### Added ## Fixed
- Application header bar and controls - Clerical hotfix
- Menu context subtitle in header bar
- Bulk delete mods (via 'List installed mods' list). Not compatible with Manual Mod install mode ## [5.7.0-beta.3] 2025-01-10
- Highlight stale mods in mods list ## Changed
- Added --steam launch parameter - Support legacy jq syntax for Ubuntu variants
- Open Steam workshop subscriptions dialog when switching from Manual to Auto mode
- Hover tooltips to most buttons ## [5.7.0-beta.2] 2025-01-07
- Add in-app documentation link to Codeberg mirror ## Fixed
- Additional logging - Stricter redacting of usernames
### Fixed - Omit nested directories when traversing symlinks
- Avoid sudo escalation if system map count is sufficient (jiriks74)
- Duplicate dialog title on Steam Deck ## [5.7.0-beta.1] 2025-01-07
- 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
### Changed ### 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 - Prevent ArrowUp/ArrowDown input when inside keyword field
- Admonish user to restart Steam in error dialog if DayZ path could not be found ### Changed
- 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
- Update forum URL - Update forum URL
- Reword Help section links to include destination
- Update README.md - Update README.md
- Update IP database to 2025-01 - Update IP database to 2025-01
- Reduce IP database size by 50% - Reduce IP database size by 50%
- Update documentation to 5.6.x standard - Update documentation to 5.6.x standard
### Dropped ### Dropped
- Hall of Fame section from button links, to be moved inside documentation - Removed temporary mod ID output in debug logs
- Unused imports - 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 ### Fixed
- Add remote resource health checks before downloading updates - Add remote resource health checks before downloading updates
### Added ### Added
- Add fallback repository - Add fallback repository
## [5.5.1] 2024-12-03 ## [5.6.0-beta.11] 2024-12-07
### Fixed ### 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 ### Added
- Support servers running DLC content (fixes Frostline servers) - Highlight stale mods in mods list
- Expose a toggle setting for whether to launch the application in fullscreen ### Fixed
- Text autocompletion in maps search field (partial search) - Duplicate dialog title on Steam Deck
- Add disk space warning to popup dialog when downloading mods
## [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 ### Fixed
- Servers in saved servers list would populate context menu with same option when right-clicking in server browser - 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 - 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 - Prevent maps combobox from duplicating contents
## [5.5.0-beta.2] 2024-10-31
### Fixed
- Restore keyboard input to keyword entry field - 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 - Abort fallback query method if DLC is required
## [5.4.2] 2024-10-05 ## [5.4.2-beta.1] 2024-10-05
### Fixed ### Fixed
- Sanitize third-party API IDs to remove UGC collisions - 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 ### Added
- Pre-boot validation check for users with self-compiled version of jq - Pre-boot validation check for users with self-compiled version of jq
### Fixed ### 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 - 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 - 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 - Global cooldown dialog could sometimes block filter toggles after cooldown reset
- Normalized minor version number due to a previous clerical error - 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 ### Added
- Scan local area network for DayZ servers
- Freedesktop application icons for system taskbar, tray, and other dialogs - Freedesktop application icons for system taskbar, tray, and other dialogs
- Emit CPU model name when exporting system debug log
### Fixed ### Fixed
- Errors being printed to the console when Exit button was explicitly clicked - 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 - 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 - 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 ## [5.3.2] 2024-07-02
### Fixed ### Fixed

View File

@ -2,7 +2,7 @@
set -o pipefail set -o pipefail
src_path="$(readlink -e "$0")" src_path="$(readlink -e "$0")"
version=6.0.2 version=6.0.6-beta.1
#CONSTANTS #CONSTANTS
aid=221100 aid=221100
@ -508,7 +508,7 @@ check_availability() {
return 1 return 1
fi fi
local url=$1 local url=$1
local timeout_sec="2" local timeout_sec="1"
if [[ $2 ]]; then if [[ $2 ]]; then
timeout_sec=$2 timeout_sec=$2
fi fi
@ -567,9 +567,12 @@ get_hash(){
md5sum "$1" | awk '{print $1}' md5sum "$1" | awk '{print $1}'
} }
fetch_a2s(){ fetch_a2s(){
# this file is currently monolithic sum="d26da2fe03df2b95436f823f0496887b"
[[ -d $helpers_path/a2s ]] && { logger INFO "A2S helper is current"; return 0; } if [[ -d $helpers_path/a2s ]] && [[ $(get_hash "$helpers_path/a2s/info.py") == $sum ]]; then
local sha=c7590ffa9a6d0c6912e17ceeab15b832a1090640 logger INFO "A2S helper is current"
return 0
fi
local sha=b40eb24cdbb06ebd08272f224257fe5a81610e86
local author="yepoleb" local author="yepoleb"
local repo="python-a2s" local repo="python-a2s"
local url="https://github.com/$author/$repo/tarball/$sha" local url="https://github.com/$author/$repo/tarball/$sha"
@ -583,13 +586,13 @@ fetch_a2s(){
logger INFO "Updated A2S helper to sha '$sha'" logger INFO "Updated A2S helper to sha '$sha'"
} }
fetch_dzq(){ fetch_dzq(){
local sum="0a334e1e144e76e560419d155435c91e" local sum="0d316badbb8d943c5f078e73862cac3e"
local file="$helpers_path/a2s/dayzquery.py" local file="$helpers_path/a2s/dayzquery.py"
if [[ -f $file ]] && [[ $(get_hash "$file") == $sum ]]; then if [[ -f $file ]] && [[ $(get_hash "$file") == $sum ]]; then
logger INFO "DZQ is current" logger INFO "DZQ is current"
return 0 return 0
fi fi
local sha=a22a9f428cbe075d7dda62f78000296955eea92a local sha="07483b88ed096327ebca752f5e177011b604d7fe"
local author="yepoleb" local author="yepoleb"
local repo="dayzquery" local repo="dayzquery"
local url="https://raw.githubusercontent.com/$author/$repo/$sha/dayzquery.py" local url="https://raw.githubusercontent.com/$author/$repo/$sha/dayzquery.py"
@ -626,10 +629,10 @@ fetch_helpers_by_sum(){
[[ -f "$config_file" ]] && source "$config_file" [[ -f "$config_file" ]] && source "$config_file"
declare -A sums declare -A sums
sums=( sums=(
["funcs"]="5523d933e9f0668fc14b3faf0d4aafd8" ["funcs"]="1cd26343cd68a5382e3377a9757497fa"
["query_v2.py"]="55d339ba02512ac69de288eb3be41067" ["query_v2.py"]="55d339ba02512ac69de288eb3be41067"
["servers.py"]="ed442c3aecf33f777d59dcf53650d263" ["servers.py"]="ed442c3aecf33f777d59dcf53650d263"
["ui.py"]="d09b9d8bed3854efd51377289786c5ac" ["ui.py"]="cf52a3d5883afe02a013d8673df494fa"
["vdf2json.py"]="2f49f6f5d3af919bebaab2e9c220f397" ["vdf2json.py"]="2f49f6f5d3af919bebaab2e9c220f397"
["pefile.py"]="b452974a84bff1d821872fcebf59e380" ["pefile.py"]="b452974a84bff1d821872fcebf59e380"
) )

View File

@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -o pipefail set -o pipefail
version="6.0.2" version="6.0.5-beta.2"
#CONSTANTS #CONSTANTS
aid=221100 aid=221100
@ -690,6 +690,7 @@ toggle(){
auto_install="1" auto_install="1"
else else
auto_install="" auto_install=""
rm "$versions_file"
fi fi
;; ;;
Toggle[[:space:]]debug[[:space:]]mode) Toggle[[:space:]]debug[[:space:]]mode)
@ -929,13 +930,19 @@ query_defunct(){
<<< "$result" awk '{print $2}' <<< "$result" awk '{print $2}'
} }
encode(){ encode(){
echo "$1" | md5sum | cut -c -8 echo -n "$1" | md5sum | cut -c -8
} }
compare(){ compare(){
local modlist="$@" local modlist="$@"
diff=$(comm -23 <(<<< "$modlist" sort -u) <(installed_mods | sort)) diff=$(comm -23 <(<<< "$modlist" sort -u) <(installed_mods | sort))
echo "$diff" 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(){ legacy_symlinks(){
logger INFO "Removing legacy symlinks" logger INFO "Removing legacy symlinks"
@ -1026,7 +1033,10 @@ update_symlinks(){
if [[ ! -d "$workshop_dir" ]]; then if [[ ! -d "$workshop_dir" ]]; then
return return
fi fi
legacy_symlinks if [[ ! -d "$game_dir" ]]; then
return
fi
unlink_all
symlinks symlinks
} }
try_fallback(){ try_fallback(){
@ -1077,41 +1087,42 @@ try_connect(){
local appid="$2" local appid="$2"
local binary="$3" local binary="$3"
local ip=$(<<< $record awk -F: '{print $1}') IFS=":" read -ra address <<< "$record"
local gameport=$(<<< $record awk -F: '{print $2}') local ip
local qport=$(<<< $record awk -F: '{print $3}') 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 local remote_mods
remote_mods=$(a2s $ip $qport rules) remote_mods=$(a2s "$ip" "$qport" rules)
if [[ $? -eq 1 ]]; then if [[ $? -eq 1 ]]; then
printf "Failed to fetch server modlist, possibly timed out" printf "Failed to fetch server modlist, possibly timed out"
return 1 return 1
fi fi
logger INFO "Server returned modlist: $(<<< $remote_mods tr '\n' ' ')" logger INFO "Server returned modlist: $(<<< "$remote_mods" tr '\n' ' ')"
local sanitized_mods=$(query_defunct "$remote_mods")
local diff=$(compare "$sanitized_mods") local sanitized_mods
sanitized_mods=$(query_defunct "$remote_mods")
logger INFO "Connection attempt for $ip:$qport" logger INFO "Connection attempt for $ip:$qport"
update_history "$record" 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 case $auto_install in
"") manual_mod_install "$ip" "$gameport" "$diff" "$sanitized_mods" "" "$appid";; "") install_mods "$sanitized_mods" "manual" ;;
1|2) manual_mod_install "$ip" "$gameport" "$diff" "$sanitized_mods" "auto" "$appid" ;; 1|2) install_mods "$sanitized_mods" "auto" ;;
esac esac
else
# NOTE: clean diff indicates that all requisite mods were downloaded
diff=$(compare "$sanitized_mods")
if [[ -z $diff ]]; then
launch "$ip" "$gameport" "$sanitized_mods" "$appid" 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 fi
} }
check_architecture(){ check_architecture(){
@ -1127,13 +1138,15 @@ force_update(){
printf "Only available when mod auto-install is ON" printf "Only available when mod auto-install is ON"
return 1 return 1
fi fi
# NOTE: forcibly regenerate signatures by removing file
rm "$versions_file" rm "$versions_file"
local update=$(check_timestamps) local mods
manual_mod_install "null" "null" "$update" "null" "force" "null" mods=$(ls -1 "$workshop_dir")
install_mods "$mods" "force"
echo "Finished requesting mod updates." echo "Finished requesting mod updates."
return 0 return 0
} }
get_local_stamps(){ get_timestamps(){
readarray -t modlist < <(printf "%s\n" "$@") readarray -t modlist < <(printf "%s\n" "$@")
local max="${#modlist[@]}" local max="${#modlist[@]}"
_concat(){ _concat(){
@ -1158,67 +1171,6 @@ update_stamps(){
printf "%s\n" "${stamps[$i]}" >> $versions_file printf "%s\n" "${stamps[$i]}" >> $versions_file
done 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(){ concat_mods(){
readarray -t concat_arr <<< "$@" readarray -t concat_arr <<< "$@"
local id local id
@ -1260,7 +1212,7 @@ launch(){
if [[ $debug -eq 1 ]]; then if [[ $debug -eq 1 ]]; then
local launch_options="$steam_cmd -applaunch $appid -connect=$ip:$gameport -nolauncher -nosplash -name=$name -skipintro -mod=$concat" 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 return 0
fi fi
echo "$concat" > "$_cache_launch" echo "$concat" > "$_cache_launch"
@ -1296,75 +1248,121 @@ final_handshake(){
printf "\n" printf "\n"
return 6 return 6
} }
manual_mod_install(){ watcher(){
local ip="$1" local -n to_download="$1"
local gameport="$2" local -n signatures="$2"
local diff="$3" local mode="$3"
local sanitized_mods="$4"
local mode="$5"
local appid="$6"
local ex="$state_path/dzg.watcher"
readarray -t stage_mods <<< "$diff" declare -i i=0
[[ -f $ex ]] && rm $ex local remote_time
local length
_watcher(){ length=${#to_download[@]}
for((i=0;i<${#stage_mods[@]};i++)); do for mod in "${!to_download[@]}"; do
remote_time=${to_download[$mod]}
[[ -f $ex ]] && return 1 [[ -f $ex ]] && return 1
case $mode in
if [[ $mode == "auto" ]] || [[ $mode == "force" ]]; then "auto" | "force")
$steam_cmd "steam://url/CommunityFilePage/${stage_mods[$i]}+workshop_download_item $aid ${stage_mods[$i]}" $steam_cmd "steam://url/CommunityFilePage/${mod}+workshop_download_item ${aid} ${mod}"
echo "# Opening workshop page for ${stage_mods[$i]}. If you see no progress, you may be out of disk space." echo "# Opening workshop page for ${mod}. 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." $steam_cmd "steam://url/CommunityFilePage/${mod}"
fi 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 sleep 1s
foreground foreground
until [[ -d $downloads_dir/${stage_mods[$i]} ]]; do until [[ -d $downloads_dir/${mod} ]]; do
[[ -f $ex ]] && return 1 [[ -f $ex ]] && return 1
sleep 0.1s sleep 0.1s
if [[ -d $workshop_dir/${stage_mods[$i]} ]]; then # NOTE: fallback for instantaneous downloads
if [[ $mode == "auto" || $mode == "force" ]]; then
if [[ -d $workshop_dir/${mod} ]]; then
break break
fi fi
fi
done done
foreground foreground
if [[ $mode == "auto" ]] || [[ $mode == "force" ]]; then case $mode in
echo "# Steam is downloading ${stage_mods[$i]} (mod $((i+1)) of ${#stage_mods[@]}). You do not need to manually Subscribe." "auto" | "force")
else echo "# Steam is downloading ${mod} (mod $((i+1)) of ${length}). You do not need to manually Subscribe."
echo "# Steam is downloading ${stage_mods[$i]} (mod $((i+1)) of ${#stage_mods[@]})" ;;
fi *)
until [[ -d $workshop_dir/${stage_mods[$i]} ]]; do echo "# Steam is downloading ${mod} (mod $((i+1)) of ${length})"
;;
esac
until [[ -d $workshop_dir/${mod} ]]; do
[[ -f $ex ]] && return 1 [[ -f $ex ]] && return 1
sleep 0.1s sleep 0.1s
done done
foreground foreground
echo "# ${stage_mods[$i]} moved to mods dir" 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 done
echo "100" echo "100"
} }
_watcher > >($steamsafe_zenity --pulsate --progress --auto-close --title="DZG Watcher" --width=500 2>/dev/null; rc=$?; [[ $rc -eq 1 ]] && touch $ex) install_mods(){
local sanitized_mods="$1"
local mode="$2"
local ex="$state_path/dzg.watcher"
if [[ $mode == "force" ]]; then [[ -f $ex ]] && rm "$ex"
rm "$versions_file"
check_timestamps
return 0
fi
local diff=$(compare "$sanitized_mods") if [[ -f "$versions_file" ]]; then
if [[ -z $diff ]]; then readarray -t local_mods < "$versions_file"
if [[ $mode == "auto" ]]; then
rm "$versions_file"
check_timestamps
fi
launch "$ip" "$gameport" "$sanitized_mods" "$appid"
else else
printf "User aborted download process, or some mods may have failed to download. Try connecting again to resync." local_mods=()
fi
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
# 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 exit 1
fi 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(){ test_display_mode(){
pgrep -a gamescope | grep -q "generate-drm-mode" pgrep -a gamescope | grep -q "generate-drm-mode"

View File

@ -38,8 +38,10 @@ if TYPE_CHECKING:
locale.setlocale(locale.LC_ALL, "") locale.setlocale(locale.LC_ALL, "")
import gi # noqa E402 import gi # noqa E402
gi.require_version("Gtk", "3.0") 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 # https://bugzilla.gnome.org/show_bug.cgi?id=708676
warnings.filterwarnings("ignore", ".*g_value_get_int", Warning) warnings.filterwarnings("ignore", ".*g_value_get_int", Warning)
@ -852,9 +854,7 @@ def call_out(command: str, *args: str) -> subprocess.CompletedProcess:
arg_ar = [] arg_ar = []
for i in args: for i in args:
arg_ar.append(i) arg_ar.append(i)
logger.info( logger.info(f"Context '{name}' calling subprocess '{command}' with args '{arg_ar}'")
f"Context '{name}' calling subprocess '{command}' with args '{arg_ar}'"
)
proc = subprocess.run( proc = subprocess.run(
["/usr/bin/env", "bash", funcs, command] + arg_ar, ["/usr/bin/env", "bash", funcs, command] + arg_ar,
capture_output=True, capture_output=True,
@ -880,9 +880,7 @@ def spawn_dialog(msg: str, mode: Popup) -> bool:
return False return False
def process_shell_return_code( def process_shell_return_code(msg: str, code: int, original_input: RowType) -> None:
msg: str, code: int, original_input: RowType
) -> None:
logger.info( logger.info(
f"Processing return code '{code}' for the input " f"Processing return code '{code}' for the input "
f"'{original_input}', returned message '{msg}'" f"'{original_input}', returned message '{msg}'"
@ -909,9 +907,7 @@ def process_shell_return_code(
spawn_dialog("Invalid Steam API key, reverting", Popup.NOTIFY) spawn_dialog("Invalid Steam API key, reverting", Popup.NOTIFY)
App.notebook.settings.revert(Preferences.STEAM) App.notebook.settings.revert(Preferences.STEAM)
case 79: # failed settings update (bm) case 79: # failed settings update (bm)
spawn_dialog( spawn_dialog("Invalid Battlemetrics API key, reverting", Popup.NOTIFY)
"Invalid Battlemetrics API key, reverting", Popup.NOTIFY
)
App.notebook.settings.revert(Preferences.BM) App.notebook.settings.revert(Preferences.BM)
case 80: # pop toast after successful settings change case 80: # pop toast after successful settings change
config_vals.clear() config_vals.clear()
@ -977,9 +973,7 @@ def call_on_thread(
if state: if state:
wait_dialog = GenericDialog(msg, Popup.WAIT) wait_dialog = GenericDialog(msg, Popup.WAIT)
wait_dialog.show_all() wait_dialog.show_all()
thread = threading.Thread( thread = threading.Thread(target=_background, args=(subproc, args, wait_dialog))
target=_background, args=(subproc, args, wait_dialog)
)
thread.start() thread.start()
else: else:
""" """
@ -1004,18 +998,14 @@ def process_tree_option(choice: RowType) -> None:
record = App.treeview.get_record_string() record = App.treeview.get_record_string()
wait_msg = command.dict["wait_msg"] wait_msg = command.dict["wait_msg"]
show_wait_dialog = True show_wait_dialog = True
call_on_thread( call_on_thread(show_wait_dialog, cmd_string, wait_msg, record, choice=choice)
show_wait_dialog, cmd_string, wait_msg, record, choice=choice
)
return return
# modlist highlight stale action # modlist highlight stale action
if context == WindowContext.TABLE_MODS and command == RowType.HIGHLIGHT: if context == WindowContext.TABLE_MODS and command == RowType.HIGHLIGHT:
wait_msg = command.dict["wait_msg"] wait_msg = command.dict["wait_msg"]
show_wait_dialog = True show_wait_dialog = True
call_on_thread( call_on_thread(show_wait_dialog, cmd_string, wait_msg, "", choice=choice)
show_wait_dialog, cmd_string, wait_msg, "", choice=choice
)
return return
if command == RowType.CHANGELOG: if command == RowType.CHANGELOG:
@ -1083,12 +1073,8 @@ def prepare_connection(rowtype: RowType, record: Record) -> None:
thread = threading.Thread(target=background, args=(rowtype, record)) thread = threading.Thread(target=background, args=(rowtype, record))
thread.start() thread.start()
def try_connect(
addr: str, def try_connect(addr: str, appid: str, path: str, row: RowType) -> None:
appid: str,
path: str,
row: RowType
) -> None:
def background(addr, appid, path): def background(addr, appid, path):
def cleanup(): def cleanup():
App.treeview.dialog_hide() App.treeview.dialog_hide()
@ -1151,6 +1137,7 @@ def connect_by_ip(enum: RowType, response: str) -> None:
return return
prepare_connection(enum, record) prepare_connection(enum, record)
def connect_by_id(enum: RowType, response: str, key: str) -> None: def connect_by_id(enum: RowType, response: str, key: str) -> None:
def _prep(key: str, response: str) -> None: def _prep(key: str, response: str) -> None:
def cleanup() -> None: def cleanup() -> None:
@ -1179,9 +1166,7 @@ def process_user_input(enum: RowType) -> None:
if enum == RowType.CONN_BY_ID: if enum == RowType.CONN_BY_ID:
key = query_config("api_key")[0] key = query_config("api_key")[0]
if len(key) == 0: if len(key) == 0:
spawn_dialog( spawn_dialog("No Battlemetrics API key is set; see Options", Popup.NOTIFY)
"No Battlemetrics API key is set; see Options", Popup.NOTIFY
)
return return
user_entry = EntryDialog(prompt, Popup.ENTRY, link_label) user_entry = EntryDialog(prompt, Popup.ENTRY, link_label)
@ -1202,9 +1187,7 @@ def process_user_input(enum: RowType) -> None:
show_wait_dialog = True show_wait_dialog = True
wait_msg = "Working" wait_msg = "Working"
call_on_thread( call_on_thread(show_wait_dialog, cmd_string, wait_msg, response, choice=enum)
show_wait_dialog, cmd_string, wait_msg, response, choice=enum
)
return return
@ -1292,9 +1275,7 @@ class OuterWindow(Gtk.Window):
logger.info(f"Using default window size {w},{h}") logger.info(f"Using default window size {w},{h}")
self.set_default_size(w, h) self.set_default_size(w, h)
def _on_delete_event( def _on_delete_event(self, window: "OuterWindow", event: Gdk.EventKey) -> None:
self, window: "OuterWindow", event: Gdk.EventKey
) -> None:
self.halt_proc_and_quit() self.halt_proc_and_quit()
def halt_proc_and_quit(self) -> None: def halt_proc_and_quit(self) -> None:
@ -1361,8 +1342,7 @@ class RightPanel(Gtk.Box):
self.pack_start(self.filters_vbox, False, False, 0) self.pack_start(self.filters_vbox, False, False, 0)
debug_tooltip = ( debug_tooltip = (
"Used to perform a dry run without\n" "Used to perform a dry run without\n" "actually connecting to a server"
"actually connecting to a server"
) )
ping_tooltip = ( ping_tooltip = (
"Refresh the ping for visible servers.\n" "Refresh the ping for visible servers.\n"
@ -1431,8 +1411,7 @@ class RightPanel(Gtk.Box):
rows = ModelManager.get_filtered() rows = ModelManager.get_filtered()
with ThreadPoolExecutor(100) as executor: with ThreadPoolExecutor(100) as executor:
futures = [ futures = [
executor.submit(Servers.ping, i, row) executor.submit(Servers.ping, i, row) for i, row in enumerate(rows)
for i, row in enumerate(rows)
] ]
wait(futures) wait(futures)
for future in futures: for future in futures:
@ -1808,9 +1787,7 @@ class ModelManagerSingleton:
class TreeView(Gtk.TreeView): class TreeView(Gtk.TreeView):
__gsignals__ = { __gsignals__ = {"on_distcalc_started": (GObject.SignalFlags.RUN_FIRST, None, ())}
"on_distcalc_started": (GObject.SignalFlags.RUN_FIRST, None, ())
}
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@ -1929,9 +1906,7 @@ class TreeView(Gtk.TreeView):
if self.current_proc and self.current_proc.is_alive(): if self.current_proc and self.current_proc.is_alive():
self.current_proc.terminate() self.current_proc.terminate()
def _delete_note( def _delete_note(self, button: Gtk.Button, user_entry: Gtk.Box, addr: str) -> None:
self, button: Gtk.Button, user_entry: Gtk.Box, addr: str
) -> None:
box = button.get_parent() box = button.get_parent()
dialog = box.get_parent() dialog = box.get_parent()
try: try:
@ -2023,9 +1998,7 @@ class TreeView(Gtk.TreeView):
model.remove(it) model.remove(it)
block_signals() block_signals()
thread = threading.Thread( thread = threading.Thread(target=ModelManager.resync_model, args=(addr, qport))
target=ModelManager.resync_model, args=(addr, qport)
)
thread.start() thread.start()
def remove_from_history(self) -> None: def remove_from_history(self) -> None:
@ -2049,16 +2022,12 @@ class TreeView(Gtk.TreeView):
if hasattr(TreeView, menu_item.action): if hasattr(TreeView, menu_item.action):
func = getattr(TreeView, menu_item.action) func = getattr(TreeView, menu_item.action)
msg = ( msg = (
f"User clicked context menu '{menu_item.get_label()}', " f"User clicked context menu '{menu_item.get_label()}', " f"calls {func}"
f"calls {func}"
) )
logger.info(msg) logger.info(msg)
func(self) func(self)
else: else:
msg = ( msg = f"Context menu function for '{menu_item.action}' " f"does not exist"
f"Context menu function for '{menu_item.action}' "
f"does not exist"
)
u_msg = ( u_msg = (
f"Something went wrong when accessing the method " f"Something went wrong when accessing the method "
f"'{menu_item.action}'" f"'{menu_item.action}'"
@ -2099,9 +2068,7 @@ class TreeView(Gtk.TreeView):
return True return True
return False return False
def _on_button_release( def _on_button_release(self, widget: Gtk.Widget, event: Gdk.EventButton) -> None:
self, widget: Gtk.Widget, event: Gdk.EventButton
) -> None:
if event.type is Gdk.EventType.BUTTON_RELEASE and event.button != 3: if event.type is Gdk.EventType.BUTTON_RELEASE and event.button != 3:
return return
@ -2185,9 +2152,7 @@ class TreeView(Gtk.TreeView):
if event.type is Gdk.EventType.KEY_PRESS and event.keyval is Gdk.KEY_l: if event.type is Gdk.EventType.KEY_PRESS and event.keyval is Gdk.KEY_l:
if self.is_selection_empty(): if self.is_selection_empty():
return return
self.menu.popup_at_widget( self.menu.popup_at_widget(widget, Gdk.Gravity.CENTER, Gdk.Gravity.WEST)
widget, Gdk.Gravity.CENTER, Gdk.Gravity.WEST
)
else: else:
self.menu.popup_at_pointer(event) self.menu.popup_at_pointer(event)
self.menu.select_first(False) self.menu.select_first(False)
@ -2201,9 +2166,7 @@ class TreeView(Gtk.TreeView):
return None return None
call_out("start_cooldown", "", "") call_out("start_cooldown", "", "")
thread = threading.Thread( thread = threading.Thread(target=self._background_player_count, args=())
target=self._background_player_count, args=()
)
thread.start() thread.start()
def get_current_iter(self) -> Gtk.TreeIter | None: def get_current_iter(self) -> Gtk.TreeIter | None:
@ -2218,9 +2181,7 @@ class TreeView(Gtk.TreeView):
context = App.treeview.get_subpage_label() context = App.treeview.get_subpage_label()
row_sel = self.get_value_at_index(0) row_sel = self.get_value_at_index(0)
logger.info( logger.info(f"Tree selection for context '{context}' changed to '{row_sel}'")
f"Tree selection for context '{context}' changed to '{row_sel}'"
)
if self.current_proc and self.current_proc.is_alive(): if self.current_proc and self.current_proc.is_alive():
self.current_proc.terminate() self.current_proc.terminate()
@ -2278,9 +2239,7 @@ class TreeView(Gtk.TreeView):
self.set_cursor(path) self.set_cursor(path)
return None return None
def _on_keypress( def _on_keypress(self, treeview: Gtk.TreeView, event: Gdk.EventKey) -> bool | None:
self, treeview: Gtk.TreeView, event: Gdk.EventKey
) -> bool | None:
keyname = Gdk.keyval_name(event.keyval) keyname = Gdk.keyval_name(event.keyval)
grid = App.grid grid = App.grid
if event.state is Gdk.ModifierType.CONTROL_MASK: if event.state is Gdk.ModifierType.CONTROL_MASK:
@ -2335,9 +2294,7 @@ class TreeView(Gtk.TreeView):
return False return False
return None return None
def _on_key_release( def _on_key_release(self, treeview: Gtk.TreeView, event: Gdk.EventKey) -> None:
self, treeview: Gtk.TreeView, event: Gdk.EventKey
) -> None:
""" """
Suppresses spamming on keydown Suppresses spamming on keydown
""" """
@ -2452,10 +2409,7 @@ class TreeView(Gtk.TreeView):
params = Servers.params params = Servers.params
serv = [] serv = []
with ThreadPoolExecutor() as executor: with ThreadPoolExecutor() as executor:
futures = [ futures = [executor.submit(job, key, APPID_DAYZ, param) for param in params]
executor.submit(job, key, APPID_DAYZ, param)
for param in params
]
wait(futures) wait(futures)
for future in futures: for future in futures:
res = future.result() res = future.result()
@ -2477,10 +2431,7 @@ class TreeView(Gtk.TreeView):
def _dump_lan(self, port: int) -> list | None: def _dump_lan(self, port: int) -> list | None:
with ThreadPoolExecutor() as executor: with ThreadPoolExecutor() as executor:
futures = [ futures = [executor.submit(Servers.test_ip, i, port) for i in range(1, 256)]
executor.submit(Servers.test_ip, i, port)
for i in range(1, 256)
]
wait(futures) wait(futures)
servers = [] servers = []
for future in futures: for future in futures:
@ -2605,9 +2556,7 @@ class TreeView(Gtk.TreeView):
ModelManager.set_store(App.treeview.get_model()) ModelManager.set_store(App.treeview.get_model())
App.treeview.set_model(None) App.treeview.set_model(None)
thread = threading.Thread( thread = threading.Thread(target=ModelManager.filter, args=(mode, *args))
target=ModelManager.filter, args=(mode, *args)
)
thread.start() thread.start()
def _background_quad(self, dialog: "GenericDialog", mode: RowType) -> None: def _background_quad(self, dialog: "GenericDialog", mode: RowType) -> None:
@ -2766,13 +2715,9 @@ class TreeView(Gtk.TreeView):
self.initialize_columns() self.initialize_columns()
self.set_selection_mode(Gtk.SelectionMode.SINGLE) self.set_selection_mode(Gtk.SelectionMode.SINGLE)
self.wait_dialog = GenericDialog( self.wait_dialog = GenericDialog("Fetching server metadata", Popup.WAIT)
"Fetching server metadata", Popup.WAIT
)
self.wait_dialog.show_all() self.wait_dialog.show_all()
thread = threading.Thread( thread = threading.Thread(target=self._query_servers, args=(mode, port))
target=self._query_servers, args=(mode, port)
)
thread.start() thread.start()
def _format_float( def _format_float(
@ -2951,9 +2896,7 @@ class TreeView(Gtk.TreeView):
def get_view(self): def get_view(self):
return self.view return self.view
def get_prereqs( def get_prereqs(self, record: Record) -> tuple[bool, str, str | None, "Prereqs"]:
self, record: Record
) -> tuple[bool, str, str|None, "Prereqs"]:
""" """
Always called on a thread with a dialog on the transient parent window Always called on a thread with a dialog on the transient parent window
""" """
@ -2967,18 +2910,14 @@ class TreeView(Gtk.TreeView):
steam_path = query_config("default_steam_path")[0] steam_path = query_config("default_steam_path")[0]
if len(steam_path) < 1: if len(steam_path) < 1:
logger.critical( logger.critical("Config file has no value set for 'default_steam_path'")
"Config file has no value set for 'default_steam_path'"
)
msg = "Local Steam installation is not set, possibly malformed config file." msg = "Local Steam installation is not set, possibly malformed config file."
return (False, msg, None, prereqs) return (False, msg, None, prereqs)
try: try:
pefile_path = PeFile.get_pefile_path(steam_path, prereqs.appid) pefile_path = PeFile.get_pefile_path(steam_path, prereqs.appid)
except AppNotInstalledError: except AppNotInstalledError:
logger.critical( logger.critical(f"'{prereqs.appid}' not found in user's libraryfolders")
f"'{prereqs.appid}' not found in user's libraryfolders"
)
msg = ( msg = (
f"This server is running {build}. You can install " f"This server is running {build}. You can install "
f"{build} by searching for it in your Steam library. " f"{build} by searching for it in your Steam library. "
@ -2987,9 +2926,7 @@ class TreeView(Gtk.TreeView):
) )
return (False, msg, None, prereqs) return (False, msg, None, prereqs)
except AppMovedError: except AppMovedError:
logger.critical( logger.critical(f"Library folder synch error for '{prereqs.appid}'")
f"Library folder synch error for '{prereqs.appid}'"
)
msg = ( msg = (
f"Steam is reporting that {build} is installed at a non-existent location. " 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, " f"If you recently installed {build} or moved it to a different drive, "
@ -3044,7 +2981,6 @@ class TreeView(Gtk.TreeView):
return (True, "", pefile_path, prereqs) return (True, "", pefile_path, prereqs)
@signal_emission @signal_emission
@update_window_labels @update_window_labels
def _on_row_activated( def _on_row_activated(
@ -3277,9 +3213,7 @@ class LanDialog(Gtk.MessageDialog):
self.connect("delete-event", self.restore_context) self.connect("delete-event", self.restore_context)
self.entry.connect("insert-text", self._on_text_typed) self.entry.connect("insert-text", self._on_text_typed)
self.entry.get_property("buffer").connect( self.entry.get_property("buffer").connect("deleted-text", self._on_text_deleted)
"deleted-text", self._on_text_deleted
)
def _validate(self, text: str) -> None: def _validate(self, text: str) -> None:
if self._is_invalid(text): if self._is_invalid(text):
@ -3395,9 +3329,7 @@ class DetailsDialog(GenericDialog):
for i, column_title in enumerate(["Item", "Details"]): for i, column_title in enumerate(["Item", "Details"]):
renderer = Gtk.CellRendererText(xalign=0) renderer = Gtk.CellRendererText(xalign=0)
if i == 0: if i == 0:
column = Gtk.TreeViewColumn( column = Gtk.TreeViewColumn(column_title, renderer, text=i, weight=2)
column_title, renderer, text=i, weight=2
)
else: else:
column = Gtk.TreeViewColumn(column_title, renderer, text=i) column = Gtk.TreeViewColumn(column_title, renderer, text=i)
column.set_sizing(Gtk.TreeViewColumnSizing.FIXED) column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
@ -3413,12 +3345,8 @@ class DetailsDialog(GenericDialog):
scrollable_message = Gtk.ScrolledWindow() scrollable_message = Gtk.ScrolledWindow()
desc = Gtk.Label(label="Server message", valign=Gtk.Align.START) desc = Gtk.Label(label="Server message", valign=Gtk.Align.START)
add_class(desc, "details-heading") add_class(desc, "details-heading")
box = Gtk.Box( box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, halign=Gtk.Align.CENTER)
orientation=Gtk.Orientation.VERTICAL, halign=Gtk.Align.CENTER self.description = Gtk.Label(justify=Gtk.Justification.CENTER, wrap=True)
)
self.description = Gtk.Label(
justify=Gtk.Justification.CENTER, wrap=True
)
sep = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL) sep = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
sep.set_margin_bottom(10) sep.set_margin_bottom(10)
for el in desc, sep, self.description: for el in desc, sep, self.description:
@ -3456,9 +3384,7 @@ class DetailsDialog(GenericDialog):
self.run() self.run()
self.destroy() self.destroy()
def _background( def _background(self, dialog: "GenericDialog", ip: str, qport: int) -> None:
self, dialog: "GenericDialog", ip: str, qport: int
) -> None:
response = Servers.details(self.ip, self.qport) response = Servers.details(self.ip, self.qport)
if response.success: if response.success:
for row in response.data: for row in response.data:
@ -3515,9 +3441,7 @@ class ModDialog(GenericDialog):
wait_dialog = GenericDialog("Fetching modlist", Popup.WAIT) wait_dialog = GenericDialog("Fetching modlist", Popup.WAIT)
wait_dialog.show_all() wait_dialog.show_all()
thread = threading.Thread( thread = threading.Thread(target=self._background, args=(wait_dialog, record))
target=self._background, args=(wait_dialog, record)
)
thread.start() thread.start()
def _background(self, dialog: "GenericDialog", record: str) -> None: def _background(self, dialog: "GenericDialog", record: str) -> None:
@ -3542,9 +3466,7 @@ class ModDialog(GenericDialog):
self.view.set_model(modlist_store) self.view.set_model(modlist_store)
GLib.idle_add(_load) GLib.idle_add(_load)
def _parse_modlist_rows( def _parse_modlist_rows(self, data: subprocess.CompletedProcess) -> bool | int:
self, data: subprocess.CompletedProcess
) -> bool | int:
lines = data.stdout.splitlines() lines = data.stdout.splitlines()
hits = len(lines) hits = len(lines)
reader = csv.reader(lines, delimiter=delimiter) reader = csv.reader(lines, delimiter=delimiter)
@ -3574,9 +3496,7 @@ class ModDialog(GenericDialog):
class LinkDialog(GenericDialog): class LinkDialog(GenericDialog):
def __init__( def __init__(self, text: str, link: str | None, command: RowType, uid: str = ""):
self, text: str, link: str | None, command: RowType, uid: str = ""
):
super().__init__(text, Popup.NOTIFY) super().__init__(text, Popup.NOTIFY)
text = textwrap.dedent(text) text = textwrap.dedent(text)
@ -3794,7 +3714,12 @@ class Options(Gtk.Box):
client_store = Gtk.ListStore(str, str) client_store = Gtk.ListStore(str, str)
client_store.append(["steam", "steam"]) client_store.append(["steam", "steam"])
client_store.append(["flatpak", "flatpak run com.valvesoftware.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) renderer_text = Gtk.CellRendererText(ellipsize=Pango.EllipsizeMode.END)
self.client_combo = Gtk.ComboBox.new_with_model(client_store) self.client_combo = Gtk.ComboBox.new_with_model(client_store)
@ -3818,8 +3743,7 @@ class Options(Gtk.Box):
self.force_button.set_sensitive(False) self.force_button.set_sensitive(False)
msg = ( msg = (
"Manual: prompt to subscribe to mods in Steam. " "Manual: prompt to subscribe to mods in Steam. " "Auto: unmanned downloads."
"Auto: unmanned downloads."
) )
eb = InfoEventBox(msg) eb = InfoEventBox(msg)
@ -3878,9 +3802,7 @@ class Options(Gtk.Box):
self.make_frame(mods_grid, "Mods"), self.make_frame(mods_grid, "Mods"),
self.make_frame(version_grid, "Version"), self.make_frame(version_grid, "Version"),
]: ]:
grid.attach( grid.attach(frame, col, row, self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT)
frame, col, row, self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT
)
row += 1 row += 1
self.scrollable = Gtk.ScrolledWindow(vexpand=True) self.scrollable = Gtk.ScrolledWindow(vexpand=True)
@ -3926,9 +3848,7 @@ class Options(Gtk.Box):
return box return box
def _on_field_activated( def _on_field_activated(self, entry: Gtk.Entry, context: Preferences) -> None:
self, entry: Gtk.Entry, context: Preferences
) -> None:
text = entry.get_text() text = entry.get_text()
button = entry.sibling button = entry.sibling
if not self._is_valid_text(text, context): if not self._is_valid_text(text, context):
@ -3949,9 +3869,7 @@ class Options(Gtk.Box):
for record in rows: for record in rows:
col = 1 col = 1
for el in record: for el in record:
grid.attach( grid.attach(el, col, row, self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT)
el, col, row, self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT
)
col += 1 col += 1
row += 1 row += 1
return grid return grid
@ -4004,9 +3922,7 @@ class Options(Gtk.Box):
return return
process_toggle(RowType.TGL_BRANCH) process_toggle(RowType.TGL_BRANCH)
def _on_radio_toggled( def _on_radio_toggled(self, button: Gtk.RadioButton, context: Preferences) -> None:
self, button: Gtk.RadioButton, context: Preferences
) -> None:
if App.treeview.subpage is not RowType.OPTIONS: if App.treeview.subpage is not RowType.OPTIONS:
return return
match context: match context:
@ -4150,18 +4066,14 @@ class Options(Gtk.Box):
field[1].get_children()[1].set_sensitive(False) field[1].get_children()[1].set_sensitive(False)
try: try:
pe_file_path = PeFile.get_pefile_path( pe_file_path = PeFile.get_pefile_path(default_steam_path, APPID_DAYZ)
default_steam_path, APPID_DAYZ
)
vers = PeFile.get_dayz_version(pe_file_path) vers = PeFile.get_dayz_version(pe_file_path)
dayz_version = PeFile.dayz_version_to_str(vers) dayz_version = PeFile.dayz_version_to_str(vers)
except Exception: except Exception:
dayz_version = "-" dayz_version = "-"
try: try:
exp_file_path = PeFile.get_pefile_path( exp_file_path = PeFile.get_pefile_path(default_steam_path, APPID_DAYZ_EXP)
default_steam_path, APPID_DAYZ_EXP
)
vers = PeFile.get_dayz_version(exp_file_path) vers = PeFile.get_dayz_version(exp_file_path)
dayz_exp_version = PeFile.dayz_version_to_str(vers) dayz_exp_version = PeFile.dayz_version_to_str(vers)
except Exception: except Exception:
@ -4270,9 +4182,7 @@ class KeybindingsDialog(Gtk.Box):
if not sep: if not sep:
grid.attach(desc, col, row, w, h) grid.attach(desc, col, row, w, h)
else: else:
grid.attach_next_to( grid.attach_next_to(desc, sep, Gtk.PositionType.BOTTOM, w, h)
desc, sep, Gtk.PositionType.BOTTOM, w, h
)
row += 1 row += 1
sep = None sep = None
grid.attach_next_to(frame, desc, Gtk.PositionType.RIGHT, w, h) grid.attach_next_to(frame, desc, Gtk.PositionType.RIGHT, w, h)
@ -4280,9 +4190,7 @@ class KeybindingsDialog(Gtk.Box):
l_spacer = Gtk.Label(label="") l_spacer = Gtk.Label(label="")
r_spacer = Gtk.Label(label="") r_spacer = Gtk.Label(label="")
grid.attach(l_spacer, col, row + 1, w, h) grid.attach(l_spacer, col, row + 1, w, h)
grid.attach_next_to( grid.attach_next_to(r_spacer, l_spacer, Gtk.PositionType.RIGHT, w, h)
r_spacer, l_spacer, Gtk.PositionType.RIGHT, w, h
)
row += 1 row += 1
return grid return grid
@ -4291,9 +4199,7 @@ class KeybindingsDialog(Gtk.Box):
col = 0 col = 0
w = 1 w = 1
h = 1 h = 1
sidebar = Gtk.Grid( sidebar = Gtk.Grid(row_homogeneous=True, orientation=Gtk.Orientation.VERTICAL)
row_homogeneous=True, orientation=Gtk.Orientation.VERTICAL
)
for cat in categories: for cat in categories:
label = Gtk.Label(label=cat) label = Gtk.Label(label=cat)
add_class(label, "left-label") add_class(label, "left-label")
@ -4313,9 +4219,7 @@ class KeybindingsDialog(Gtk.Box):
column = 1 column = 1
w = 1 w = 1
h = 1 h = 1
sidebar = self.build_sidebar( sidebar = self.build_sidebar(["Servers", "Navigation", "Vim-style keys"])
["Servers", "Navigation", "Vim-style keys"]
)
separator = Gtk.Separator() separator = Gtk.Separator()
keys_box = self.build_keys(items) keys_box = self.build_keys(items)
@ -4533,9 +4437,7 @@ class Statusbar(Gtk.Statusbar):
players_pretty = pluralize("players", players) players_pretty = pluralize("players", players)
hits_pretty = pluralize("matches", hits) hits_pretty = pluralize("matches", hits)
formatted = ( formatted = f"Found {hits:n} {hits_pretty} with {players:n} {players_pretty}"
f"Found {hits:n} {hits_pretty} with {players:n} {players_pretty}"
)
suffix = "| Distance: calculating..." suffix = "| Distance: calculating..."
if players == 0: if players == 0:
@ -4587,9 +4489,7 @@ class Grid(Gtk.Grid):
self.breadcrumbs = Gtk.Label(label="Main menu", halign=Gtk.Align.START) self.breadcrumbs = Gtk.Label(label="Main menu", halign=Gtk.Align.START)
self.attach(self.notebook, 0, 0, 3, 1) self.attach(self.notebook, 0, 0, 3, 1)
self.attach_next_to( self.attach_next_to(self.breadcrumbs, self.notebook, Gtk.PositionType.TOP, 3, 1)
self.breadcrumbs, self.notebook, Gtk.PositionType.TOP, 3, 1
)
self.attach_next_to( self.attach_next_to(
self.statusbar, self.notebook, Gtk.PositionType.BOTTOM, 3, 1 self.statusbar, self.notebook, Gtk.PositionType.BOTTOM, 3, 1
) )
@ -4651,11 +4551,23 @@ class App(Gtk.Application):
) )
self.win.add_accel_group(accel) self.win.add_accel_group(accel)
GLib.unix_signal_add( self.setup_signals()
GLib.PRIORITY_DEFAULT, signal.SIGINT, self._catch_sigint
)
Gtk.main() 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: def _catch_sigint(self) -> None:
self.win.halt_proc_and_quit() self.win.halt_proc_and_quit()
@ -4740,9 +4652,7 @@ class ModSelectionPanel(Gtk.Box):
def toggle_select_stale_button(self, state: bool) -> None: def toggle_select_stale_button(self, state: bool) -> None:
if state: if state:
button = Gtk.Button( button = Gtk.Button(label="Select stale", margin_start=10, margin_end=10)
label="Select stale", margin_start=10, margin_end=10
)
text = "Bulk selects all currently highlighted mods" text = "Bulk selects all currently highlighted mods"
button.set_tooltip_text(text) button.set_tooltip_text(text)
button.connect("clicked", self._on_button_clicked) button.connect("clicked", self._on_button_clicked)
@ -4862,9 +4772,7 @@ class FilterPanel(Gtk.Box):
self.keyword_entry = Gtk.Entry() self.keyword_entry = Gtk.Entry()
self.keyword_entry.set_placeholder_text("Filter by keyword") self.keyword_entry.set_placeholder_text("Filter by keyword")
self.keyword_entry.connect("activate", self._on_keyword_enter) self.keyword_entry.connect("activate", self._on_keyword_enter)
self.keyword_entry.connect( self.keyword_entry.connect("key-press-event", self._on_keyword_keypress)
"key-press-event", self._on_keyword_keypress
)
completion = Gtk.EntryCompletion(inline_completion=True) completion = Gtk.EntryCompletion(inline_completion=True)
completion.set_text_column(0) completion.set_text_column(0)
@ -4932,9 +4840,7 @@ class FilterPanel(Gtk.Box):
state = self.default_filters[label] state = self.default_filters[label]
check.set_active(state) check.set_active(state)
def _on_map_entry_keypress( def _on_map_entry_keypress(self, entry: Gtk.Entry, event: Gdk.EventKey) -> None:
self, entry: Gtk.Entry, event: Gdk.EventKey
) -> None:
match event.keyval: match event.keyval:
case Gdk.KEY_Return: case Gdk.KEY_Return:
text = entry.get_text() text = entry.get_text()
@ -4977,9 +4883,7 @@ class FilterPanel(Gtk.Box):
App.treeview.grab_focus() App.treeview.grab_focus()
return False return False
def _on_keyword_keypress( def _on_keyword_keypress(self, entry: Gtk.Entry, event: Gdk.EventKey) -> bool:
self, entry: Gtk.Entry, event: Gdk.EventKey
) -> bool:
match event.keyval: match event.keyval:
case Gdk.KEY_Up: case Gdk.KEY_Up:
return True return True
@ -4990,9 +4894,7 @@ class FilterPanel(Gtk.Box):
return True return True
return False return False
def _on_combo_keypress( def _on_combo_keypress(self, combo: Gtk.ComboBox, event: Gdk.EventKey) -> bool:
self, combo: Gtk.ComboBox, event: Gdk.EventKey
) -> bool:
match event.keyval: match event.keyval:
case Gdk.KEY_Down: case Gdk.KEY_Down:
self.maps_combo.popup() self.maps_combo.popup()

BIN
images/preview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB