mirror of
https://github.com/aclist/dztui.git
synced 2026-11-06 18:10:58 +01:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c594c944bc |
No files matched your search
@@ -5,10 +5,6 @@ labels: ["bug"]
|
|||||||
assignees:
|
assignees:
|
||||||
- aclist
|
- aclist
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
### WARNING: When posting log/config data, redact any sensitive information like API keys.
|
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
id: kb
|
id: kb
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
@@ -5,10 +5,6 @@ labels: ["troubleshooting"]
|
|||||||
assignees:
|
assignees:
|
||||||
- aclist
|
- aclist
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
### WARNING: When posting log/config data, redact any sensitive information like API keys.
|
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
id: device
|
id: device
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ jobs:
|
|||||||
mirror-to-codeberg:
|
mirror-to-codeberg:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: pixta-dev/repository-mirroring-action@v1
|
- uses: pixta-dev/repository-mirroring-action@v1
|
||||||
|
|||||||
@@ -1,4 +1 @@
|
|||||||
tags
|
tags
|
||||||
__pycache__
|
|
||||||
pre-commit
|
|
||||||
tests/
|
|
||||||
+9
-113
@@ -1,125 +1,21 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [6.0.6-beta.1] 2026-05-17
|
## [6.0.0-beta.8] XXXX-XX-XX
|
||||||
## 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-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
|
||||||
- More descriptive help dialogs when initial dependencies are missing
|
- Flag to disable branch toggle for distro-packaged releases
|
||||||
|
- Test whether script was sourced inline
|
||||||
## [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
|
- Commandline usage help text
|
||||||
|
|
||||||
## Fixed
|
## Fixed
|
||||||
- ESC key destroying wait dialogs while a thread is pending
|
- ESC key destroying wait dialogs while thread is pending
|
||||||
- Tooltip signals being erroneously drawn on main menu
|
- Tooltip signals being processed on main menu
|
||||||
- Leaky variable name in dialog titles
|
- Leaky variable name in dialog title
|
||||||
- Prevent extraneous signals from propagating when column width is adjusted
|
- 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
|
||||||
|
|
||||||
## Changed
|
## Changed
|
||||||
- Optimize time complexity of pre-boot checks (GaryBlackbourne)
|
- Optimize time cost of setup checks
|
||||||
- Moved location of notes file
|
- Location of notes file
|
||||||
- Rewrote distance calculation module (GaryBlackbourne)
|
|
||||||
|
|
||||||
## [6.0.0-beta.7] 2025-09-20
|
## [6.0.0-beta.7] 2025-09-20
|
||||||
## Added
|
## Added
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
# Introduction
|
|
||||||
|
|
||||||
Thank you for your interest in DZGUI!
|
|
||||||
|
|
||||||
This guide goes over development conventions and best practices for contributors.
|
|
||||||
If you are a developer, you can skip to the end.
|
|
||||||
|
|
||||||
# Requesting help
|
|
||||||
|
|
||||||
If you encounter a problem with DZGUI, you can submit tickets on the GitHub
|
|
||||||
[issue tracker](https://github.com/aclist/dztui/issues) under the
|
|
||||||
`troubleshooting` tag.
|
|
||||||
|
|
||||||
# How can I help the project?
|
|
||||||
|
|
||||||
There are several ways to help this project.
|
|
||||||
1. Report bugs that you find
|
|
||||||
2. Request features that you would like
|
|
||||||
3. Contribute features and fixes to the codebase
|
|
||||||
4. Contribute documentation to the project
|
|
||||||
|
|
||||||
Before making any contribution, please read the `CODE_OF_CONDUCT.md` and act
|
|
||||||
accordingly.
|
|
||||||
|
|
||||||
## Submitting a ticket
|
|
||||||
|
|
||||||
Navigate to the GitHub [issue tracker](https://github.com/aclist/dztui/issues).
|
|
||||||
From there, follow the onscreen prompts. You will be asked questions such as:
|
|
||||||
|
|
||||||
- What version are you using?
|
|
||||||
- What distribution are you using?
|
|
||||||
- What is the issue that you found?
|
|
||||||
- How can we reproduce the issue?
|
|
||||||
|
|
||||||
You can also attach screenshots, logs, or other data that can help us.
|
|
||||||
|
|
||||||
## Requesting a feature
|
|
||||||
|
|
||||||
You can also request features via the same issue tracker. It is good practice to
|
|
||||||
first search for your idea to see if a similar one has already been posted.
|
|
||||||
If not, open a ticket where you describe your feature and its possible benefits.
|
|
||||||
|
|
||||||
Please note that this is a community project, so it takes time for us to develop
|
|
||||||
features. Putting in a feature request does not mean that it will be implemented,
|
|
||||||
but we will do our best to support as many cool ideas as possible.
|
|
||||||
|
|
||||||
## Contributing code or documentation
|
|
||||||
|
|
||||||
If you would like to take ownership or assist with an issue on the issue tracker, or
|
|
||||||
contribute a new change, please follow the guidelines below.
|
|
||||||
|
|
||||||
Fork this repository and check out the code up to `prerelease/<latest-version>`.
|
|
||||||
If there is no pending prerelease PR, you can base your changes off of
|
|
||||||
the `testing` branch. You may later be asked to retarget your PR to the prerelease
|
|
||||||
branch once a new one is made. This is because out-of-band PRs are consolidated into
|
|
||||||
the next release before merging.
|
|
||||||
|
|
||||||
The following naming conventions apply for PRs:
|
|
||||||
- fix/<your-fix> - patch/hotfix branches
|
|
||||||
- feat/<your-feature> - feature branches
|
|
||||||
- doc/<your-doc-branch> - documentation branches
|
|
||||||
- infra/<your-infra-branch> - infrastructure branches
|
|
||||||
|
|
||||||
Implement your changes and test them locally. If they work you may
|
|
||||||
open a merge request, and then we review your changes. If everything is OK, it
|
|
||||||
will be merged to `prerelease/<latest-version>`, then `testing`, and after a live
|
|
||||||
testing phase, to `stable` as well.
|
|
||||||
|
|
||||||
It is recommended to follow
|
|
||||||
[Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/), as this
|
|
||||||
integrates well with tooling and helps the project review your code.
|
|
||||||
@@ -11,15 +11,6 @@ This overcomes certain limitations in the Linux client and helps prepare the gam
|
|||||||
- Concatenate launch options to pass to Steam
|
- Concatenate launch options to pass to Steam
|
||||||
- Connect to mod-enabled LAN servers
|
- 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
|
## Setup and usage
|
||||||
|
|
||||||
Refer to the documentation for installation and setup instructions:
|
Refer to the documentation for installation and setup instructions:
|
||||||
@@ -27,6 +18,7 @@ Refer to the documentation for installation and setup instructions:
|
|||||||
- [GitHub](https://aclist.github.io/dzgui/index.html)
|
- [GitHub](https://aclist.github.io/dzgui/index.html)
|
||||||
- [Mirror (Codeberg)](https://aclist.codeberg.page)
|
- [Mirror (Codeberg)](https://aclist.codeberg.page)
|
||||||
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Attribution
|
## Attribution
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
|
|
||||||
src_path="$(readlink -e "$0")"
|
version=6.0.0.beta-8
|
||||||
version=6.0.6-beta.1
|
|
||||||
|
|
||||||
#CONSTANTS
|
#CONSTANTS
|
||||||
aid=221100
|
aid=221100
|
||||||
@@ -40,7 +39,6 @@ cols_file="$state_path/$prefix.cols.json"
|
|||||||
|
|
||||||
#CACHE FILES
|
#CACHE FILES
|
||||||
coords_file="$cache_path/$prefix.coords"
|
coords_file="$cache_path/$prefix.coords"
|
||||||
src_path_file="$cache_path/$prefix.src"
|
|
||||||
|
|
||||||
#legacy paths
|
#legacy paths
|
||||||
hist_file="$config_path/history"
|
hist_file="$config_path/history"
|
||||||
@@ -117,7 +115,6 @@ setup_state_files(){
|
|||||||
done
|
done
|
||||||
logger INFO "Wiped cache files"
|
logger INFO "Wiped cache files"
|
||||||
fi
|
fi
|
||||||
echo "$src_path" > "$src_path_file"
|
|
||||||
}
|
}
|
||||||
print_config_vals(){
|
print_config_vals(){
|
||||||
local keys=(
|
local keys=(
|
||||||
@@ -139,10 +136,10 @@ print_config_vals(){
|
|||||||
|
|
||||||
}
|
}
|
||||||
test_gobject(){
|
test_gobject(){
|
||||||
python3.13 -c "import gi"
|
python3 -c "import gi"
|
||||||
if [[ ! $? -eq 0 ]]; then
|
if [[ ! $? -eq 0 ]]; then
|
||||||
logger CRITICAL "Missing PyGObject"
|
logger CRITICAL "Missing PyGObject"
|
||||||
quit_with_help_dialog "Requires PyGObject (python-gobject)"
|
fdialog "Requires PyGObject (python-gobject)"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
logger INFO "Found PyGObject in Python env"
|
logger INFO "Found PyGObject in Python env"
|
||||||
@@ -221,14 +218,16 @@ default_steam_path="$default_steam_path"
|
|||||||
|
|
||||||
#Preferred Steam launch command (for Flatpak support)
|
#Preferred Steam launch command (for Flatpak support)
|
||||||
preferred_client="$preferred_client"
|
preferred_client="$preferred_client"
|
||||||
|
|
||||||
|
#DZGUI source path
|
||||||
|
src_path="$src_path"
|
||||||
END
|
END
|
||||||
}
|
}
|
||||||
depcheck(){
|
depcheck(){
|
||||||
for dep in "${!deps[@]}"; do
|
for dep in "${!deps[@]}"; do
|
||||||
if ! command -v "$dep" &> /dev/null; then
|
if ! command -v "$dep" &> /dev/null; then
|
||||||
local msg="Requires $dep >= ${deps[$dep]}"
|
local msg="Requires $dep >= ${deps[$dep]}"
|
||||||
echo "$msg"
|
raise_error_and_quit "$msg"
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
local jqmsg="jq must be compiled with support for oniguruma"
|
local jqmsg="jq must be compiled with support for oniguruma"
|
||||||
@@ -237,31 +236,14 @@ depcheck(){
|
|||||||
[[ $? -ne 0 ]] && raise_error_and_quit "$jqmsg"
|
[[ $? -ne 0 ]] && raise_error_and_quit "$jqmsg"
|
||||||
logger INFO "Initial dependencies satisfied"
|
logger INFO "Initial dependencies satisfied"
|
||||||
}
|
}
|
||||||
open_url(){
|
|
||||||
url="$1"
|
|
||||||
if [[ -n "$BROWSER" ]]; then
|
|
||||||
logger INFO "Opening '$url' in '$BROWSER'"
|
|
||||||
"$BROWSER" "$url"
|
|
||||||
else
|
|
||||||
logger INFO "Opening '$url' with xdg-open"
|
|
||||||
xdg-open "$url"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
quit_with_help_dialog(){
|
|
||||||
local sel
|
|
||||||
msg="$1"
|
|
||||||
help_button="Open help page"
|
|
||||||
url="https://aclist.github.io/dzgui/installation.html"
|
|
||||||
logger CRITICAL "$msg"
|
|
||||||
sel=$(zenity --info --extra-button="$help_button" --text="$msg")
|
|
||||||
[[ $sel == "$help_button" ]] && open_url "$url" &
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
check_pyver(){
|
check_pyver(){
|
||||||
if [[ ! $(python3.13 --version) ]]; then
|
local pyver=$(python3 --version | awk '{print $2}')
|
||||||
local msg="Requires Python 3.13"
|
local minor=$(<<< $pyver awk -F. '{print $2}')
|
||||||
quit_with_help_dialog "$msg"
|
if [[ -z $pyver ]] || [[ ${pyver:0:1} -lt 3 ]] || [[ $minor -lt 10 ]]; then
|
||||||
|
local msg="Requires Python >=3.10"
|
||||||
|
raise_error_and_quit "$msg"
|
||||||
fi
|
fi
|
||||||
|
logger INFO "Found Python version: $pyver"
|
||||||
}
|
}
|
||||||
watcher_deps(){
|
watcher_deps(){
|
||||||
if [[ ! $(command -v wmctrl) ]] && [[ ! $(command -v xdotool) ]]; then
|
if [[ ! $(command -v wmctrl) ]] && [[ ! $(command -v xdotool) ]]; then
|
||||||
@@ -335,8 +317,6 @@ check_unmerged(){
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
check_version(){
|
check_version(){
|
||||||
echo "# Checking version"
|
|
||||||
[[ -n $reference_branch ]] && return
|
|
||||||
local version_url=$(format_version_url)
|
local version_url=$(format_version_url)
|
||||||
local upstream=$(curl -Ls "$version_url" | awk -F= '/^version=/ {print $2}')
|
local upstream=$(curl -Ls "$version_url" | awk -F= '/^version=/ {print $2}')
|
||||||
local res=$(get_response_code "$version_url")
|
local res=$(get_response_code "$version_url")
|
||||||
@@ -400,11 +380,10 @@ prompt_dl(){
|
|||||||
dl_changelog(){
|
dl_changelog(){
|
||||||
local mdbranch
|
local mdbranch
|
||||||
local md
|
local md
|
||||||
source "$config_file"
|
|
||||||
[[ $branch == "stable" ]] && mdbranch="dzgui"
|
[[ $branch == "stable" ]] && mdbranch="dzgui"
|
||||||
[[ $branch == "testing" ]] && mdbranch="testing"
|
[[ $branch == "testing" ]] && mdbranch="testing"
|
||||||
local changelog="$url_prefix/${mdbranch}/CHANGELOG.md"
|
local md="$url_prefix/${mdbranch}/$file"
|
||||||
curl -Ls "$changelog" > "$state_path/CHANGELOG.md"
|
curl -Ls "$md" > "$state_path/CHANGELOG.md"
|
||||||
}
|
}
|
||||||
test_display_mode(){
|
test_display_mode(){
|
||||||
pgrep -a gamescope | grep -q "generate-drm-mode"
|
pgrep -a gamescope | grep -q "generate-drm-mode"
|
||||||
@@ -415,7 +394,6 @@ test_display_mode(){
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
check_architecture(){
|
check_architecture(){
|
||||||
echo "# Checking system architecture"
|
|
||||||
local cpu=$(< /proc/cpuinfo awk -F": " '/AMD Custom APU [0-9]{4}$/ {print $2; exit}')
|
local cpu=$(< /proc/cpuinfo awk -F": " '/AMD Custom APU [0-9]{4}$/ {print $2; exit}')
|
||||||
read -a APU_MODEL <<< "$cpu"
|
read -a APU_MODEL <<< "$cpu"
|
||||||
if [[ ${APU_MODEL[3]} != "0932" ]] && [[ ${APU_MODEL[3]} != "0405" ]]; then
|
if [[ ${APU_MODEL[3]} != "0932" ]] && [[ ${APU_MODEL[3]} != "0405" ]]; then
|
||||||
@@ -432,7 +410,6 @@ check_architecture(){
|
|||||||
logger INFO "Setting architecture to 'Steam Deck'"
|
logger INFO "Setting architecture to 'Steam Deck'"
|
||||||
}
|
}
|
||||||
check_map_count(){
|
check_map_count(){
|
||||||
echo "# Checking map count"
|
|
||||||
[[ $is_steam_deck -gt 0 ]] && return 0
|
[[ $is_steam_deck -gt 0 ]] && return 0
|
||||||
local map_count_file="/proc/sys/vm/max_map_count"
|
local map_count_file="/proc/sys/vm/max_map_count"
|
||||||
local min_count=1048576
|
local min_count=1048576
|
||||||
@@ -482,7 +459,6 @@ tdialog(){
|
|||||||
$steamsafe_zenity --info --text="$1" "${zenity_flags[@]}"
|
$steamsafe_zenity --info --text="$1" "${zenity_flags[@]}"
|
||||||
}
|
}
|
||||||
steam_deps(){
|
steam_deps(){
|
||||||
echo "# Checking Steam"
|
|
||||||
local flatpak
|
local flatpak
|
||||||
local steam
|
local steam
|
||||||
[[ $(command -v flatpak) ]] && flatpak=$(flatpak list | grep valvesoftware.Steam)
|
[[ $(command -v flatpak) ]] && flatpak=$(flatpak list | grep valvesoftware.Steam)
|
||||||
@@ -491,15 +467,33 @@ steam_deps(){
|
|||||||
local msg="Found neither Steam nor Flatpak Steam"
|
local msg="Found neither Steam nor Flatpak Steam"
|
||||||
raise_error_and_quit "$msg"
|
raise_error_and_quit "$msg"
|
||||||
exit 1
|
exit 1
|
||||||
|
elif [[ -n "$steam" ]] && [[ -n "$flatpak" ]]; then
|
||||||
|
[[ -n $preferred_client ]] && return 0
|
||||||
|
if [[ -z $preferred_client ]]; then
|
||||||
|
preferred_client="steam"
|
||||||
fi
|
fi
|
||||||
|
elif [[ -n "$steam" ]]; then
|
||||||
|
preferred_client="steam"
|
||||||
|
else
|
||||||
|
preferred_client="flatpak"
|
||||||
|
fi
|
||||||
|
update_config
|
||||||
|
logger INFO "Preferred client set to '$preferred_client'"
|
||||||
|
}
|
||||||
|
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(){
|
stale_symlinks(){
|
||||||
echo "# Cleaning stale symlinks"
|
|
||||||
local game_dir="$steam_path/steamapps/common/DayZ"
|
local game_dir="$steam_path/steamapps/common/DayZ"
|
||||||
readarray -t links < <(find "$game_dir" -xtype l)
|
for l in $(find "$game_dir" -xtype l); do
|
||||||
for link in "${links[@]}"; do
|
logger DEBUG "Updating stale symlink '$l'"
|
||||||
logger DEBUG "Updating stale symlink '$link'"
|
unlink "$l"
|
||||||
unlink "$link"
|
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -508,7 +502,7 @@ check_availability() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
local url=$1
|
local url=$1
|
||||||
local timeout_sec="1"
|
local timeout_sec="3"
|
||||||
if [[ $2 ]]; then
|
if [[ $2 ]]; then
|
||||||
timeout_sec=$2
|
timeout_sec=$2
|
||||||
fi
|
fi
|
||||||
@@ -519,7 +513,6 @@ check_availability() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
local_latlon(){
|
local_latlon(){
|
||||||
echo "# Checking coordinates"
|
|
||||||
if [[ -z $(command -v dig) ]]; then
|
if [[ -z $(command -v dig) ]]; then
|
||||||
local url_ipecho="https://ipecho.net/plain"
|
local url_ipecho="https://ipecho.net/plain"
|
||||||
if ! check_availability "ipecho.net"; then
|
if ! check_availability "ipecho.net"; then
|
||||||
@@ -531,9 +524,6 @@ local_latlon(){
|
|||||||
# TODO : implement checking remote
|
# TODO : implement checking remote
|
||||||
local local_ip=$(dig -4 +short myip.opendns.com @resolver1.opendns.com)
|
local local_ip=$(dig -4 +short myip.opendns.com @resolver1.opendns.com)
|
||||||
fi
|
fi
|
||||||
if calc_local_coords "$local_ip"; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
local url_ip_api="http://ip-api.com/json/$local_ip"
|
local url_ip_api="http://ip-api.com/json/$local_ip"
|
||||||
if ! check_availability "ip-api.com"; then
|
if ! check_availability "ip-api.com"; then
|
||||||
logger WARN "Failed to get local coordinates, ip-api.com service may be down."
|
logger WARN "Failed to get local coordinates, ip-api.com service may be down."
|
||||||
@@ -548,7 +538,6 @@ local_latlon(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
lock(){
|
lock(){
|
||||||
echo "# Setting up lock file"
|
|
||||||
[[ ! -f $lock_file ]] && touch $lock_file
|
[[ ! -f $lock_file ]] && touch $lock_file
|
||||||
local pid=$(cat $lock_file)
|
local pid=$(cat $lock_file)
|
||||||
ps -p $pid -o pid= >/dev/null 2>&1
|
ps -p $pid -o pid= >/dev/null 2>&1
|
||||||
@@ -567,12 +556,9 @@ get_hash(){
|
|||||||
md5sum "$1" | awk '{print $1}'
|
md5sum "$1" | awk '{print $1}'
|
||||||
}
|
}
|
||||||
fetch_a2s(){
|
fetch_a2s(){
|
||||||
sum="d26da2fe03df2b95436f823f0496887b"
|
# this file is currently monolithic
|
||||||
if [[ -d $helpers_path/a2s ]] && [[ $(get_hash "$helpers_path/a2s/info.py") == $sum ]]; then
|
[[ -d $helpers_path/a2s ]] && { logger INFO "A2S helper is current"; return 0; }
|
||||||
logger INFO "A2S helper is current"
|
local sha=c7590ffa9a6d0c6912e17ceeab15b832a1090640
|
||||||
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"
|
||||||
@@ -586,13 +572,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="0d316badbb8d943c5f078e73862cac3e"
|
local sum="0a334e1e144e76e560419d155435c91e"
|
||||||
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="07483b88ed096327ebca752f5e177011b604d7fe"
|
local sha=a22a9f428cbe075d7dda62f78000296955eea92a
|
||||||
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"
|
||||||
@@ -629,12 +615,12 @@ 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"]="1cd26343cd68a5382e3377a9757497fa"
|
["funcs"]="6ae3ead7034dc8e7543472bddee75c63"
|
||||||
["query_v2.py"]="55d339ba02512ac69de288eb3be41067"
|
["query_v2.py"]="55d339ba02512ac69de288eb3be41067"
|
||||||
["servers.py"]="ed442c3aecf33f777d59dcf53650d263"
|
["servers.py"]="ed442c3aecf33f777d59dcf53650d263"
|
||||||
["ui.py"]="cf52a3d5883afe02a013d8673df494fa"
|
["ui.py"]="8d05bbd7b8e15a97fae568e9a3dbf4d6"
|
||||||
["vdf2json.py"]="2f49f6f5d3af919bebaab2e9c220f397"
|
["vdf2json.py"]="2f49f6f5d3af919bebaab2e9c220f397"
|
||||||
["pefile.py"]="b452974a84bff1d821872fcebf59e380"
|
["pefile.py"]="21531f2c0d9dfa5f110cf6779f9d22c0"
|
||||||
)
|
)
|
||||||
local author="aclist"
|
local author="aclist"
|
||||||
local repo="dztui"
|
local repo="dztui"
|
||||||
@@ -653,10 +639,6 @@ fetch_helpers_by_sum(){
|
|||||||
realbranch="dzgui"
|
realbranch="dzgui"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n $reference_branch ]]; then
|
|
||||||
realbranch="$reference_branch"
|
|
||||||
fi
|
|
||||||
|
|
||||||
for i in "${!sums[@]}"; do
|
for i in "${!sums[@]}"; do
|
||||||
file="$i"
|
file="$i"
|
||||||
sum="${sums[$i]}"
|
sum="${sums[$i]}"
|
||||||
@@ -694,12 +676,15 @@ fetch_km_helper(){
|
|||||||
}
|
}
|
||||||
get_response_code(){
|
get_response_code(){
|
||||||
local url="$1"
|
local url="$1"
|
||||||
curl --connect-timeout 3 -Ls -I -o /dev/null -w "%{http_code}" "$url"
|
curl -Ls -I -o /dev/null -w "%{http_code}" "$url"
|
||||||
|
}
|
||||||
|
raise_error_and_quit(){
|
||||||
|
echo "$1"
|
||||||
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch_ip_db(){
|
fetch_ip_db(){
|
||||||
parse_dl_url(){
|
parse_dl_url(){
|
||||||
curl --connect-timeout 3 -Ls "$url" \
|
curl -Ls "$url" \
|
||||||
| grep "csv.gz" \
|
| grep "csv.gz" \
|
||||||
| awk -F"['']" '{print $2}'
|
| awk -F"['']" '{print $2}'
|
||||||
}
|
}
|
||||||
@@ -715,9 +700,6 @@ fetch_ip_db(){
|
|||||||
fetch(){
|
fetch(){
|
||||||
logger INFO "Triggering fetch routine"
|
logger INFO "Triggering fetch routine"
|
||||||
local url="$1"
|
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"
|
curl -Ls "$url" > "$base_file"
|
||||||
if [[ $? -ne 0 ]]; then
|
if [[ $? -ne 0 ]]; then
|
||||||
@@ -737,7 +719,7 @@ fetch_ip_db(){
|
|||||||
logger WARN "Abnormal exit while parsing IPs"
|
logger WARN "Abnormal exit while parsing IPs"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
rm "${state_path}/${this_month}.csv"
|
rm "${state_path}/${month}.csv"
|
||||||
|
|
||||||
readarray -t records < <(cat $ip_file | awk -F, 'NR==1 {print $1} END {print $1}')
|
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
|
if [[ ${records[0]} != "0.0.0.0" ]] && [[ ${records[1]} != "224.0.0.0" ]]; then
|
||||||
@@ -752,13 +734,16 @@ fetch_ip_db(){
|
|||||||
rm "$ip_file"
|
rm "$ip_file"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "$this_month" > "$month_file"
|
echo "$month" > "$month_file"
|
||||||
logger INFO "Wrote '$this_month' to stub '$month_file'"
|
logger INFO "Wrote '$month' to stub '$month_file'"
|
||||||
logger INFO "Updated '$ip_file'"
|
logger INFO "Updated '$ip_file'"
|
||||||
}
|
}
|
||||||
|
|
||||||
check_remote(){
|
local url="https://db-ip.com/db/download/ip-to-city-lite"
|
||||||
local url="$1"
|
local month_file="${state_path}/.month"
|
||||||
|
local ip_file="${state_path}/ips.csv"
|
||||||
|
|
||||||
|
# test main url
|
||||||
local res=$(get_response_code "$url")
|
local res=$(get_response_code "$url")
|
||||||
if [[ $res -ne 200 ]]; then
|
if [[ $res -ne 200 ]]; then
|
||||||
logger WARN "Failed to retrieve remote resource: '$url' ($res)"
|
logger WARN "Failed to retrieve remote resource: '$url' ($res)"
|
||||||
@@ -774,35 +759,30 @@ fetch_ip_db(){
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
logger INFO "Resolved download URL: '$dl_url'"
|
logger INFO "Resolved download URL: '$dl_url'"
|
||||||
fetch "$dl_url"
|
|
||||||
}
|
|
||||||
|
|
||||||
local url="https://db-ip.com/db/download/ip-to-city-lite"
|
local month=$(parse_dl_url_date "$dl_url")
|
||||||
month_file="${state_path}/.month"
|
local base_file="${state_path}/${month}.csv.gz"
|
||||||
this_month=$(date +%Y-%m)
|
local extracted_file="${state_path}/${month}.csv"
|
||||||
|
|
||||||
# no stub file
|
# no stub file
|
||||||
if [[ ! -f $month_file ]]; then
|
if [[ ! -f $month_file ]]; then
|
||||||
logger WARN "No stub file '$month_file' present"
|
logger WARN "No stub file '$month_file' present"
|
||||||
check_remote "$url"
|
fetch "$dl_url"
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
# if stub is same date, abort
|
|
||||||
local last_month=$(< "$month_file")
|
|
||||||
if [[ $last_month == "$this_month" ]]; then
|
|
||||||
logger INFO "Local stub '$last_month' is identical to remote, skipping"
|
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# local needs update
|
# local needs update
|
||||||
logger WARN "Local stub '$last_month' does not match '$this_month'"
|
local last_month=$(< "$month_file")
|
||||||
check_remote "$url"
|
if [[ $last_month != "$month" ]]; then
|
||||||
|
logger WARN "Local stub '$last_month' does not match remote stub '$month'"
|
||||||
|
fetch "$dl_url"
|
||||||
return
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# if stub is same date, abort
|
||||||
|
logger INFO "Local stub '$last_month' is identical to remote, skipping"
|
||||||
}
|
}
|
||||||
fetch_helpers(){
|
fetch_helpers(){
|
||||||
echo "# Checking helper files"
|
|
||||||
fetch_a2s
|
fetch_a2s
|
||||||
fetch_dzq
|
fetch_dzq
|
||||||
fetch_km_helper
|
fetch_km_helper
|
||||||
@@ -901,26 +881,12 @@ file_picker(){
|
|||||||
}
|
}
|
||||||
find_library_folder(){
|
find_library_folder(){
|
||||||
local search_path="$1"
|
local search_path="$1"
|
||||||
readarray -t paths < <(python3.13 "$helpers_path/vdf2json.py" -i "$1/steamapps/libraryfolders.vdf" \
|
steam_path="$(python3 "$helpers_path/vdf2json.py" -i "$1/steamapps/libraryfolders.vdf" \
|
||||||
| jq -r '.libraryfolders[]|select(.apps|has("221100")).path')
|
| jq -r '.libraryfolders[]|select(.apps|has("221100")).path')"
|
||||||
if [[ ! $? -eq 0 ]]; then
|
if [[ ! $? -eq 0 ]] || [[ -z $steam_path ]]; then
|
||||||
logger WARN "Failed to parse Steam path using '$search_path'"
|
logger WARN "Failed to parse Steam path using '$search_path'"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
if [[ ${#paths[@]} -eq 0 ]]; then
|
|
||||||
logger WARN "No valid VDF paths in '$search_path'"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
if [[ ${#paths[@]} -gt 1 ]]; then
|
|
||||||
for path in "${paths[@]}"; do
|
|
||||||
if [[ -d "$path" ]]; then
|
|
||||||
steam_path="$path"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
else
|
|
||||||
steam_path="${paths[0]}"
|
|
||||||
fi
|
|
||||||
logger INFO "Steam path resolved to: $steam_path"
|
logger INFO "Steam path resolved to: $steam_path"
|
||||||
}
|
}
|
||||||
create_config(){
|
create_config(){
|
||||||
@@ -929,7 +895,6 @@ create_config(){
|
|||||||
unset default_steam_path
|
unset default_steam_path
|
||||||
unset steam_path
|
unset steam_path
|
||||||
|
|
||||||
preferred_client="steam"
|
|
||||||
while true; do
|
while true; do
|
||||||
local player_input="$($steamsafe_zenity \
|
local player_input="$($steamsafe_zenity \
|
||||||
--forms \
|
--forms \
|
||||||
@@ -990,7 +955,6 @@ create_config(){
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
varcheck(){
|
varcheck(){
|
||||||
echo "# Checking config file"
|
|
||||||
local msg="Config file '$config_file' missing. Start first-time setup now?"
|
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?"
|
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
|
if [[ ! -f $config_file ]]; then
|
||||||
@@ -1004,7 +968,7 @@ varcheck(){
|
|||||||
source "$config_file"
|
source "$config_file"
|
||||||
local workshop_dir="$steam_path/steamapps/workshop/content/$aid"
|
local workshop_dir="$steam_path/steamapps/workshop/content/$aid"
|
||||||
local game_dir="$steam_path/steamapps/common/DayZ"
|
local game_dir="$steam_path/steamapps/common/DayZ"
|
||||||
if [[ ! -d $steam_path ]] || [[ ! -d $game_dir ]] || [[ ! $(find "$game_dir" -type f) ]]; then
|
if [[ ! -d $steam_path ]] || [[ ! -d $game_dir ]] || [[ ! $(find $game_dir -type f) ]]; then
|
||||||
logger WARN "DayZ path resolved to '$game_dir'"
|
logger WARN "DayZ path resolved to '$game_dir'"
|
||||||
logger WARN "Workshop path resolved to '$workshop_dir'"
|
logger WARN "Workshop path resolved to '$workshop_dir'"
|
||||||
qdialog "$msg2" "Yes" "Exit"
|
qdialog "$msg2" "Yes" "Exit"
|
||||||
@@ -1015,16 +979,18 @@ varcheck(){
|
|||||||
create_config
|
create_config
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
if [[ $src_path != $(realpath "$0") ]]; then
|
||||||
|
src_path=$(realpath "$0")
|
||||||
|
update_config
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
is_dzg_downloading(){
|
is_dzg_downloading(){
|
||||||
echo "# Checking DayZ"
|
|
||||||
if [[ -d $steam_path ]] && [[ -d $steam_path/downloading/$aid ]]; then
|
if [[ -d $steam_path ]] && [[ -d $steam_path/downloading/$aid ]]; then
|
||||||
logger WARN "DayZ may be scheduling updates"
|
logger WARN "DayZ may be scheduling updates"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
is_steam_running(){
|
is_steam_running(){
|
||||||
echo "# Checking Steam"
|
|
||||||
local res=$(ps aux | grep "steamwebhelper" | grep -v grep)
|
local res=$(ps aux | grep "steamwebhelper" | grep -v grep)
|
||||||
if [[ -z $res ]]; then
|
if [[ -z $res ]]; then
|
||||||
logger WARN "Steam may not be running"
|
logger WARN "Steam may not be running"
|
||||||
@@ -1037,7 +1003,6 @@ get_response_code(){
|
|||||||
curl -Ls -I -o /dev/null -w "%{http_code}" "$url"
|
curl -Ls -I -o /dev/null -w "%{http_code}" "$url"
|
||||||
}
|
}
|
||||||
test_connection(){
|
test_connection(){
|
||||||
echo "# Testing connection"
|
|
||||||
declare -A hr
|
declare -A hr
|
||||||
local res1
|
local res1
|
||||||
local res2
|
local res2
|
||||||
@@ -1071,7 +1036,6 @@ legacy_cols(){
|
|||||||
mv $cols_file.new $cols_file
|
mv $cols_file.new $cols_file
|
||||||
}
|
}
|
||||||
stale_mod_signatures(){
|
stale_mod_signatures(){
|
||||||
echo "# Checking stale mods"
|
|
||||||
[[ ! -f "$versions_file" ]] && return
|
[[ ! -f "$versions_file" ]] && return
|
||||||
local workshop_dir="$steam_path/steamapps/workshop/content/$aid"
|
local workshop_dir="$steam_path/steamapps/workshop/content/$aid"
|
||||||
if [[ -d $workshop_dir ]]; then
|
if [[ -d $workshop_dir ]]; then
|
||||||
@@ -1085,48 +1049,18 @@ stale_mod_signatures(){
|
|||||||
|
|
||||||
}
|
}
|
||||||
create_new_links(){
|
create_new_links(){
|
||||||
echo "# Setting up symlinks"
|
|
||||||
"$func_helper" "update_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(){
|
initial_setup(){
|
||||||
|
setup_dirs
|
||||||
|
setup_state_files
|
||||||
|
depcheck
|
||||||
|
check_pyver
|
||||||
|
test_gobject
|
||||||
|
watcher_deps
|
||||||
check_architecture
|
check_architecture
|
||||||
test_connection
|
test_connection
|
||||||
fetch_helpers
|
fetch_helpers > >(pdialog "Checking helper files")
|
||||||
varcheck
|
varcheck
|
||||||
source "$config_file"
|
source "$config_file"
|
||||||
lock
|
lock
|
||||||
@@ -1135,12 +1069,14 @@ initial_setup(){
|
|||||||
check_version
|
check_version
|
||||||
check_map_count
|
check_map_count
|
||||||
steam_deps
|
steam_deps
|
||||||
|
migrate_files
|
||||||
stale_symlinks
|
stale_symlinks
|
||||||
stale_mod_signatures
|
stale_mod_signatures
|
||||||
create_new_links
|
create_new_links
|
||||||
local_latlon
|
local_latlon
|
||||||
is_steam_running
|
is_steam_running
|
||||||
is_dzg_downloading
|
is_dzg_downloading
|
||||||
|
print_config_vals
|
||||||
}
|
}
|
||||||
uninstall(){
|
uninstall(){
|
||||||
_full(){
|
_full(){
|
||||||
@@ -1242,18 +1178,11 @@ main(){
|
|||||||
|
|
||||||
set_im_module
|
set_im_module
|
||||||
|
|
||||||
printf "Checking dependencies...\n"
|
printf "Initializing setup...\n"
|
||||||
setup_dirs
|
initial_setup
|
||||||
setup_state_files
|
|
||||||
depcheck
|
|
||||||
check_pyver
|
|
||||||
test_gobject
|
|
||||||
watcher_deps
|
|
||||||
|
|
||||||
initial_setup > >(pdialog "Initializing setup")
|
|
||||||
|
|
||||||
printf "All OK. Kicking off UI...\n"
|
printf "All OK. Kicking off UI...\n"
|
||||||
python3.13 "$ui_helper" "--init-ui" "$version" "$is_steam_deck"
|
python3 "$ui_helper" "--init-ui" "$version" "$is_steam_deck"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+152
-143
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
version="6.0.5-beta.2"
|
version="6.0.0-beta.8"
|
||||||
|
|
||||||
#CONSTANTS
|
#CONSTANTS
|
||||||
aid=221100
|
aid=221100
|
||||||
@@ -51,7 +51,6 @@ _cache_binary="$cache_dir/$prefix.binary"
|
|||||||
_cache_coords="$cache_path/$prefix.coords"
|
_cache_coords="$cache_path/$prefix.coords"
|
||||||
_cache_cooldown="$cache_path/$prefix.cooldown"
|
_cache_cooldown="$cache_path/$prefix.cooldown"
|
||||||
_cache_lan="$cache_path/$prefix.lan"
|
_cache_lan="$cache_path/$prefix.lan"
|
||||||
_cache_src_path="$cache_path/$prefix.src"
|
|
||||||
|
|
||||||
#XDG
|
#XDG
|
||||||
freedesktop_path="$HOME/.local/share/applications"
|
freedesktop_path="$HOME/.local/share/applications"
|
||||||
@@ -88,14 +87,17 @@ steam_api_url="https://steamcommunity.com/dev/apikey"
|
|||||||
battlemetrics_api_url="https://www.battlemetrics.com/developers"
|
battlemetrics_api_url="https://www.battlemetrics.com/developers"
|
||||||
bm_api="https://api.battlemetrics.com/servers"
|
bm_api="https://api.battlemetrics.com/servers"
|
||||||
|
|
||||||
steam_cmd="$preferred_client"
|
if [[ $preferred_client == "steam" ]]; then
|
||||||
|
steam_cmd="steam"
|
||||||
|
else
|
||||||
|
steam_cmd="flatpak run com.valvesoftware.Steam"
|
||||||
|
fi
|
||||||
|
|
||||||
declare -A funcs=(
|
declare -A funcs=(
|
||||||
["Highlight stale"]="find_stale_mods"
|
["Highlight stale"]="find_stale_mods"
|
||||||
["Change player name"]="update_config_val"
|
["Change player name"]="update_config_val"
|
||||||
["Change Steam API key"]="update_config_val"
|
["Change Steam API key"]="update_config_val"
|
||||||
["Change Battlemetrics API key"]="update_config_val"
|
["Change Battlemetrics API key"]="update_config_val"
|
||||||
["Change client"]="update_config_val"
|
|
||||||
["Change favorite server"]="add_record"
|
["Change favorite server"]="add_record"
|
||||||
["Add server by IP"]="add_record"
|
["Add server by IP"]="add_record"
|
||||||
["Add server by ID"]="add_record"
|
["Add server by ID"]="add_record"
|
||||||
@@ -286,7 +288,7 @@ a2s(){
|
|||||||
local mode="$3"
|
local mode="$3"
|
||||||
logger INFO "Querying '$ip:$qport' with mode '$mode'"
|
logger INFO "Querying '$ip:$qport' with mode '$mode'"
|
||||||
local res
|
local res
|
||||||
res=$(python3.13 "$query_helper" "$ip" "$qport" "$mode")
|
res=$(python3 "$query_helper" "$ip" "$qport" "$mode")
|
||||||
if [[ $? -eq 1 ]]; then
|
if [[ $? -eq 1 ]]; then
|
||||||
res=$(try_fallback "$ip" "$qport" "$mode")
|
res=$(try_fallback "$ip" "$qport" "$mode")
|
||||||
if [[ $? -eq 1 ]]; then
|
if [[ $? -eq 1 ]]; then
|
||||||
@@ -309,7 +311,7 @@ is_in_favs(){
|
|||||||
find_id(){
|
find_id(){
|
||||||
local file="$default_steam_path/config/loginusers.vdf"
|
local file="$default_steam_path/config/loginusers.vdf"
|
||||||
[[ ! -f $file ]] && return 1
|
[[ ! -f $file ]] && return 1
|
||||||
local res=$(python3.13 "$helpers_path/vdf2json.py" \
|
local res=$(python3 "$helpers_path/vdf2json.py" \
|
||||||
-i "$file" \
|
-i "$file" \
|
||||||
| jq -r '.users
|
| jq -r '.users
|
||||||
|to_entries[]
|
|to_entries[]
|
||||||
@@ -338,7 +340,7 @@ list_mods(){
|
|||||||
name=$(awk -F\" '/name/ {print $2}' "${dir}/meta.cpp")
|
name=$(awk -F\" '/name/ {print $2}' "${dir}/meta.cpp")
|
||||||
base_dir=$(basename $(readlink -f $game_dir/$symlink))
|
base_dir=$(basename $(readlink -f $game_dir/$symlink))
|
||||||
size=$(du -s "$(readlink -f "$game_dir/$symlink")" | awk '{print $1}')
|
size=$(du -s "$(readlink -f "$game_dir/$symlink")" | awk '{print $1}')
|
||||||
size=$(python3.13 -c "n=($size/1024) +.005; print(round(n,4))")
|
size=$(python3 -c "n=($size/1024) +.005; print(round(n,4))")
|
||||||
LC_NUMERIC=C printf "%s$sep%s$sep%s$sep%3.3f\n" "$name" "$symlink" "$base_dir" "$size"
|
LC_NUMERIC=C printf "%s$sep%s$sep%s$sep%3.3f\n" "$name" "$symlink" "$base_dir" "$size"
|
||||||
done | sort -k1)
|
done | sort -k1)
|
||||||
# user may have manually pruned mods out-of-band
|
# user may have manually pruned mods out-of-band
|
||||||
@@ -397,7 +399,6 @@ get_dist(){
|
|||||||
logger INFO "Distance: $dist km"
|
logger INFO "Distance: $dist km"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
query_config(){
|
query_config(){
|
||||||
[[ -n $2 ]] && local key=$2
|
[[ -n $2 ]] && local key=$2
|
||||||
keys=(
|
keys=(
|
||||||
@@ -423,7 +424,6 @@ query_config(){
|
|||||||
echo "${!i}"
|
echo "${!i}"
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
align_versions_file(){
|
align_versions_file(){
|
||||||
shift
|
shift
|
||||||
local mod="$1"
|
local mod="$1"
|
||||||
@@ -589,6 +589,9 @@ default_steam_path="$default_steam_path"
|
|||||||
|
|
||||||
#Preferred Steam launch command (for Flatpak support)
|
#Preferred Steam launch command (for Flatpak support)
|
||||||
preferred_client="$preferred_client"
|
preferred_client="$preferred_client"
|
||||||
|
|
||||||
|
#DZGUI source path
|
||||||
|
src_path="$src_path"
|
||||||
END
|
END
|
||||||
}
|
}
|
||||||
format_version_url(){
|
format_version_url(){
|
||||||
@@ -642,7 +645,6 @@ download_new_version(){
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
local version_url="$(format_version_url)"
|
local version_url="$(format_version_url)"
|
||||||
local src_path="$(< "$_cache_src_path")"
|
|
||||||
mv "$src_path" "$src_path.old"
|
mv "$src_path" "$src_path.old"
|
||||||
curl -Ls "$version_url" > "$src_path"
|
curl -Ls "$version_url" > "$src_path"
|
||||||
rc=$?
|
rc=$?
|
||||||
@@ -690,7 +692,6 @@ 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)
|
||||||
@@ -700,6 +701,13 @@ toggle(){
|
|||||||
debug=""
|
debug=""
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
|
Toggle[[:space:]]Steam/Flatpak)
|
||||||
|
if [[ $preferred_client == "steam" ]]; then
|
||||||
|
preferred_client="flatpak"
|
||||||
|
else
|
||||||
|
preferred_client="steam"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
Toggle[[:space:]]DZGUI[[:space:]]fullscreen[[:space:]]boot)
|
Toggle[[:space:]]DZGUI[[:space:]]fullscreen[[:space:]]boot)
|
||||||
if [[ $fullscreen == "true" ]]; then
|
if [[ $fullscreen == "true" ]]; then
|
||||||
fullscreen="false"
|
fullscreen="false"
|
||||||
@@ -798,9 +806,6 @@ update_config_val(){
|
|||||||
local context="$1"
|
local context="$1"
|
||||||
local value="$2"
|
local value="$2"
|
||||||
case $1 in
|
case $1 in
|
||||||
"Change client")
|
|
||||||
key="preferred_client"
|
|
||||||
;;
|
|
||||||
"Change player name")
|
"Change player name")
|
||||||
key="name"
|
key="name"
|
||||||
if [[ -z "${value// }" ]]; then
|
if [[ -z "${value// }" ]]; then
|
||||||
@@ -826,6 +831,7 @@ update_config_val(){
|
|||||||
declare -n nr=$key
|
declare -n nr=$key
|
||||||
nr="$value"
|
nr="$value"
|
||||||
update_config
|
update_config
|
||||||
|
echo "Updated the key '$key' to '$value'"
|
||||||
return 80
|
return 80
|
||||||
}
|
}
|
||||||
show_log(){
|
show_log(){
|
||||||
@@ -930,19 +936,13 @@ query_defunct(){
|
|||||||
<<< "$result" awk '{print $2}'
|
<<< "$result" awk '{print $2}'
|
||||||
}
|
}
|
||||||
encode(){
|
encode(){
|
||||||
echo -n "$1" | md5sum | cut -c -8
|
echo "$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"
|
||||||
|
|
||||||
@@ -1033,10 +1033,7 @@ update_symlinks(){
|
|||||||
if [[ ! -d "$workshop_dir" ]]; then
|
if [[ ! -d "$workshop_dir" ]]; then
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
if [[ ! -d "$game_dir" ]]; then
|
legacy_symlinks
|
||||||
return
|
|
||||||
fi
|
|
||||||
unlink_all
|
|
||||||
symlinks
|
symlinks
|
||||||
}
|
}
|
||||||
try_fallback(){
|
try_fallback(){
|
||||||
@@ -1087,42 +1084,41 @@ try_connect(){
|
|||||||
local appid="$2"
|
local appid="$2"
|
||||||
local binary="$3"
|
local binary="$3"
|
||||||
|
|
||||||
IFS=":" read -ra address <<< "$record"
|
local ip=$(<<< $record awk -F: '{print $1}')
|
||||||
local ip
|
local gameport=$(<<< $record awk -F: '{print $2}')
|
||||||
local gameport
|
local qport=$(<<< $record awk -F: '{print $3}')
|
||||||
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 sanitized_mods
|
local diff=$(compare "$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
|
||||||
"") install_mods "$sanitized_mods" "manual" ;;
|
"") manual_mod_install "$ip" "$gameport" "$diff" "$sanitized_mods" "" "$appid";;
|
||||||
1|2) install_mods "$sanitized_mods" "auto" ;;
|
1|2) manual_mod_install "$ip" "$gameport" "$diff" "$sanitized_mods" "auto" "$appid" ;;
|
||||||
esac
|
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
|
else
|
||||||
printf "User aborted download process, or some mods may have failed to download. Try connecting again to resync."
|
launch "$ip" "$gameport" "$sanitized_mods" "$appid"
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
check_architecture(){
|
check_architecture(){
|
||||||
@@ -1138,15 +1134,13 @@ 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 mods
|
local update=$(check_timestamps)
|
||||||
mods=$(ls -1 "$workshop_dir")
|
manual_mod_install "null" "null" "$update" "null" "force" "null"
|
||||||
install_mods "$mods" "force"
|
|
||||||
echo "Finished requesting mod updates."
|
echo "Finished requesting mod updates."
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
get_timestamps(){
|
get_local_stamps(){
|
||||||
readarray -t modlist < <(printf "%s\n" "$@")
|
readarray -t modlist < <(printf "%s\n" "$@")
|
||||||
local max="${#modlist[@]}"
|
local max="${#modlist[@]}"
|
||||||
_concat(){
|
_concat(){
|
||||||
@@ -1171,6 +1165,67 @@ 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
|
||||||
@@ -1212,7 +1267,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: %s" "$launch_options"
|
printf "Debug mode: these options would have been used to launch the game: $launch_options"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
echo "$concat" > "$_cache_launch"
|
echo "$concat" > "$_cache_launch"
|
||||||
@@ -1248,121 +1303,75 @@ final_handshake(){
|
|||||||
printf "\n"
|
printf "\n"
|
||||||
return 6
|
return 6
|
||||||
}
|
}
|
||||||
watcher(){
|
manual_mod_install(){
|
||||||
local -n to_download="$1"
|
local ip="$1"
|
||||||
local -n signatures="$2"
|
local gameport="$2"
|
||||||
local mode="$3"
|
local diff="$3"
|
||||||
|
local sanitized_mods="$4"
|
||||||
|
local mode="$5"
|
||||||
|
local appid="$6"
|
||||||
|
local ex="$state_path/dzg.watcher"
|
||||||
|
|
||||||
declare -i i=0
|
readarray -t stage_mods <<< "$diff"
|
||||||
local remote_time
|
[[ -f $ex ]] && rm $ex
|
||||||
local length
|
|
||||||
length=${#to_download[@]}
|
_watcher(){
|
||||||
for mod in "${!to_download[@]}"; do
|
for((i=0;i<${#stage_mods[@]};i++)); do
|
||||||
remote_time=${to_download[$mod]}
|
|
||||||
[[ -f $ex ]] && return 1
|
[[ -f $ex ]] && return 1
|
||||||
case $mode in
|
|
||||||
"auto" | "force")
|
if [[ $mode == "auto" ]] || [[ $mode == "force" ]]; then
|
||||||
$steam_cmd "steam://url/CommunityFilePage/${mod}+workshop_download_item ${aid} ${mod}"
|
$steam_cmd "steam://url/CommunityFilePage/${stage_mods[$i]}+workshop_download_item $aid ${stage_mods[$i]}"
|
||||||
echo "# Opening workshop page for ${mod}. If you see no progress, you may be out of disk space."
|
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]}"
|
||||||
$steam_cmd "steam://url/CommunityFilePage/${mod}"
|
echo "# Opening workshop page for ${stage_mods[$i]}. If you see no progress after subscribing, try unsubscribing and resubscribing again until the download commences."
|
||||||
echo "# Opening workshop page for ${mod}. If you see no progress after subscribing, try unsubscribing and resubscribing again until the download commences."
|
fi
|
||||||
;;
|
|
||||||
esac
|
|
||||||
sleep 1s
|
sleep 1s
|
||||||
foreground
|
foreground
|
||||||
|
|
||||||
until [[ -d $downloads_dir/${mod} ]]; do
|
until [[ -d $downloads_dir/${stage_mods[$i]} ]]; do
|
||||||
[[ -f $ex ]] && return 1
|
[[ -f $ex ]] && return 1
|
||||||
sleep 0.1s
|
sleep 0.1s
|
||||||
# NOTE: fallback for instantaneous downloads
|
if [[ -d $workshop_dir/${stage_mods[$i]} ]]; then
|
||||||
if [[ $mode == "auto" || $mode == "force" ]]; then
|
|
||||||
if [[ -d $workshop_dir/${mod} ]]; then
|
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
fi
|
|
||||||
done
|
done
|
||||||
|
|
||||||
foreground
|
foreground
|
||||||
case $mode in
|
if [[ $mode == "auto" ]] || [[ $mode == "force" ]]; then
|
||||||
"auto" | "force")
|
echo "# Steam is downloading ${stage_mods[$i]} (mod $((i+1)) of ${#stage_mods[@]}). You do not need to manually Subscribe."
|
||||||
echo "# Steam is downloading ${mod} (mod $((i+1)) of ${length}). You do not need to manually Subscribe."
|
else
|
||||||
;;
|
echo "# Steam is downloading ${stage_mods[$i]} (mod $((i+1)) of ${#stage_mods[@]})"
|
||||||
*)
|
fi
|
||||||
echo "# Steam is downloading ${mod} (mod $((i+1)) of ${length})"
|
until [[ -d $workshop_dir/${stage_mods[$i]} ]]; do
|
||||||
;;
|
|
||||||
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
|
||||||
if [[ ! -v signatures["${mod}"] ]]; then
|
echo "# ${stage_mods[$i]} moved to mods dir"
|
||||||
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"
|
||||||
}
|
}
|
||||||
install_mods(){
|
_watcher > >($steamsafe_zenity --pulsate --progress --auto-close --title="DZG Watcher" --width=500 2>/dev/null; rc=$?; [[ $rc -eq 1 ]] && touch $ex)
|
||||||
local sanitized_mods="$1"
|
|
||||||
local mode="$2"
|
|
||||||
local ex="$state_path/dzg.watcher"
|
|
||||||
|
|
||||||
[[ -f $ex ]] && rm "$ex"
|
if [[ $mode == "force" ]]; then
|
||||||
|
rm "$versions_file"
|
||||||
|
check_timestamps
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ -f "$versions_file" ]]; then
|
local diff=$(compare "$sanitized_mods")
|
||||||
readarray -t local_mods < "$versions_file"
|
if [[ -z $diff ]]; then
|
||||||
|
if [[ $mode == "auto" ]]; then
|
||||||
|
rm "$versions_file"
|
||||||
|
check_timestamps
|
||||||
|
fi
|
||||||
|
launch "$ip" "$gameport" "$sanitized_mods" "$appid"
|
||||||
else
|
else
|
||||||
local_mods=()
|
printf "User aborted download process, or some mods may have failed to download. Try connecting again to resync."
|
||||||
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"
|
||||||
|
|||||||
+1
-1
@@ -295,7 +295,7 @@ def get_dayz_version(file: Path) -> DayZVersion | Exception:
|
|||||||
try:
|
try:
|
||||||
version = get_version(file)
|
version = get_version(file)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise e
|
return e
|
||||||
patch = str(version.build) + str(version.revision)
|
patch = str(version.build) + str(version.revision)
|
||||||
dz_vers = DayZVersion(version.major, version.minor, int(patch))
|
dz_vers = DayZVersion(version.major, version.minor, int(patch))
|
||||||
return dz_vers
|
return dz_vers
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
// Earth's radius in meters.
|
// Earths radius in meters
|
||||||
#define RADIUS ((12756L / 2.0L) * 1000L)
|
#define RADIUS ((12756L / 2.0L) * 1000L)
|
||||||
|
|
||||||
// PI natural constant
|
// PI natural constant
|
||||||
@@ -68,9 +68,6 @@ int main(int argc, const char* argv[]) {
|
|||||||
.longitude = radian_from(atof(argv[4])),
|
.longitude = radian_from(atof(argv[4])),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns the distance in meters.
|
|
||||||
// To pretty-print the result in a consumer of this module, it is
|
|
||||||
// recommended to divide the result by 1,000 and round it.
|
|
||||||
printf("%.1f\n", great_circle_distance(a, b));
|
printf("%.1f\n", great_circle_distance(a, b));
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
+289
-197
@@ -18,8 +18,7 @@ from enum import Enum
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from concurrent.futures import wait
|
from concurrent.futures import wait
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
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 servers as Servers # noqa E402
|
||||||
import pefile as PeFile # noqa E402
|
import pefile as PeFile # noqa E402
|
||||||
@@ -32,16 +31,12 @@ from pefile import (
|
|||||||
)
|
)
|
||||||
from pefile import VersionMatch
|
from pefile import VersionMatch
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from servers import Prereqs
|
|
||||||
|
|
||||||
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")
|
||||||
gi.require_version("GLibUnix", "2.0")
|
from gi.repository import Gtk, GLib, Gdk, GObject, Pango # noqa E402
|
||||||
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)
|
||||||
@@ -828,6 +823,7 @@ def query_history() -> list | None:
|
|||||||
rows = [row.rstrip("\n") for row in f]
|
rows = [row.rstrip("\n") for row in f]
|
||||||
except OSError:
|
except OSError:
|
||||||
rows = None
|
rows = None
|
||||||
|
finally:
|
||||||
return rows
|
return rows
|
||||||
|
|
||||||
|
|
||||||
@@ -854,7 +850,9 @@ 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(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(
|
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,
|
||||||
@@ -868,19 +866,20 @@ def spawn_dialog(msg: str, mode: Popup) -> bool:
|
|||||||
dialog = GenericDialog(msg, mode)
|
dialog = GenericDialog(msg, mode)
|
||||||
response = dialog.run()
|
response = dialog.run()
|
||||||
dialog.destroy()
|
dialog.destroy()
|
||||||
clean_msg = msg.replace("\n", " ")
|
|
||||||
|
|
||||||
match response:
|
match response:
|
||||||
case Gtk.ResponseType.OK:
|
case Gtk.ResponseType.OK:
|
||||||
logger.info(f"User confirmed dialog with message '{clean_msg}'")
|
logger.info(f"User confirmed dialog with message '{msg}'")
|
||||||
return False
|
return False
|
||||||
case Gtk.ResponseType.CANCEL | Gtk.ResponseType.DELETE_EVENT:
|
case Gtk.ResponseType.CANCEL | Gtk.ResponseType.DELETE_EVENT:
|
||||||
logger.info(f"User aborted dialog with message '{clean_msg}'")
|
logger.info(f"User aborted dialog with message '{msg}'")
|
||||||
return True
|
return True
|
||||||
return False
|
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(
|
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}'"
|
||||||
@@ -907,7 +906,9 @@ def process_shell_return_code(msg: str, code: int, original_input: RowType) -> N
|
|||||||
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("Invalid Battlemetrics API key, reverting", Popup.NOTIFY)
|
spawn_dialog(
|
||||||
|
"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()
|
||||||
@@ -973,7 +974,9 @@ 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(target=_background, args=(subproc, args, wait_dialog))
|
thread = threading.Thread(
|
||||||
|
target=_background, args=(subproc, args, wait_dialog)
|
||||||
|
)
|
||||||
thread.start()
|
thread.start()
|
||||||
else:
|
else:
|
||||||
"""
|
"""
|
||||||
@@ -998,18 +1001,22 @@ 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(show_wait_dialog, cmd_string, wait_msg, record, choice=choice)
|
call_on_thread(
|
||||||
|
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(show_wait_dialog, cmd_string, wait_msg, "", choice=choice)
|
call_on_thread(
|
||||||
|
show_wait_dialog, cmd_string, wait_msg, "", choice=choice
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if command == RowType.CHANGELOG:
|
if command == RowType.CHANGELOG:
|
||||||
App.grid.notebook.open_changelog()
|
App.grid.notebook.set_page_by_enum(NotebookPage.CHANGELOG)
|
||||||
return
|
return
|
||||||
|
|
||||||
if command == RowType.QUICK_CONNECT:
|
if command == RowType.QUICK_CONNECT:
|
||||||
@@ -1019,7 +1026,13 @@ def process_tree_option(choice: RowType) -> None:
|
|||||||
return
|
return
|
||||||
|
|
||||||
record = str_to_record(record)
|
record = str_to_record(record)
|
||||||
prepare_connection(command, record)
|
thread_new_with_dialog(
|
||||||
|
App.treeview.prepare_connection,
|
||||||
|
parse_shell_output,
|
||||||
|
"Querying server",
|
||||||
|
command,
|
||||||
|
[record],
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
match command.dict["type"]:
|
match command.dict["type"]:
|
||||||
@@ -1047,50 +1060,48 @@ def parse_shell_output(proc: subprocess.CompletedProcess, row: RowType):
|
|||||||
process_shell_return_code(msg, proc.returncode, row)
|
process_shell_return_code(msg, proc.returncode, row)
|
||||||
|
|
||||||
|
|
||||||
def prepare_connection(rowtype: RowType, record: Record) -> None:
|
def thread_new_with_dialog(
|
||||||
def background(rowtype: RowType, record: Record) -> None:
|
func: Callable,
|
||||||
def cleanup() -> None:
|
callback: Callable | None,
|
||||||
App.treeview.wait_dialog.destroy()
|
msg: str,
|
||||||
# NOTE: When using RowType.CONN_BY_IP, the gameport needs to be interpolated
|
row: RowType | None,
|
||||||
record.gameport = prereqs.gameport
|
args: list,
|
||||||
addr = record_to_str(record)
|
) -> None:
|
||||||
|
"""
|
||||||
|
Pop a GenericDialog transient to App.treeview and
|
||||||
|
call a function on a thread, with optional callback.
|
||||||
|
Chiefly used for connection-related subprocesses.
|
||||||
|
|
||||||
if proceed is False:
|
After completion, the dialog is destroyed in the main event loop
|
||||||
spawn_dialog(msg, Popup.NOTIFY)
|
and additional exception handling occurs.
|
||||||
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)
|
|
||||||
|
|
||||||
proceed, msg, pefile_path, prereqs = App.treeview.get_prereqs(record)
|
This is intended as a bridge between legacy shell methods and the UI.
|
||||||
GLib.idle_add(cleanup)
|
A more abstracted version of call_on_thread() for when extra threaded
|
||||||
|
processing occurs before calls to shell subprocesses.
|
||||||
|
"""
|
||||||
|
|
||||||
msg = "Checking prerequisites"
|
def background(*args):
|
||||||
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():
|
def cleanup():
|
||||||
App.treeview.dialog_hide()
|
App.treeview.dialog_hide()
|
||||||
parse_shell_output(proc, row)
|
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)
|
||||||
|
|
||||||
|
exception = None
|
||||||
|
proc = None
|
||||||
try:
|
try:
|
||||||
proc = call_out("try_connect", addr, appid, path)
|
proc = func(*args)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.critical(e)
|
exception = e
|
||||||
GLib.idle_add(cleanup)
|
GLib.idle_add(cleanup)
|
||||||
return
|
return
|
||||||
GLib.idle_add(cleanup)
|
GLib.idle_add(cleanup)
|
||||||
|
|
||||||
msg = "Querying server"
|
|
||||||
App.treeview.dialog_show(msg)
|
App.treeview.dialog_show(msg)
|
||||||
thread = threading.Thread(target=background, args=(addr, appid, path))
|
thread = threading.Thread(target=background, args=(args))
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
|
|
||||||
@@ -1130,32 +1141,27 @@ def record_to_str(record: Record) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def connect_by_ip(enum: RowType, response: str) -> None:
|
def connect_by_ip(enum: RowType, response: str) -> None:
|
||||||
try:
|
def _prep(response: str) -> None:
|
||||||
record = Servers.validate_ip(response)
|
record = Servers.validate_ip(response)
|
||||||
except Exception as e:
|
proc = App.treeview.prepare_connection(record)
|
||||||
spawn_dialog(str(e), Popup.NOTIFY)
|
return proc
|
||||||
|
|
||||||
|
thread_new_with_dialog(
|
||||||
|
_prep, parse_shell_output, "Querying IP", enum, [response]
|
||||||
|
)
|
||||||
return
|
return
|
||||||
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:
|
|
||||||
App.treeview.dialog_hide()
|
|
||||||
prepare_connection(enum, record)
|
|
||||||
|
|
||||||
try:
|
|
||||||
record = Servers.query_bm_api(key, response)
|
record = Servers.query_bm_api(key, response)
|
||||||
except Exception as e:
|
proc = App.treeview.prepare_connection(record)
|
||||||
spawn_dialog(str(e), Popup.NOTIFY)
|
return proc
|
||||||
App.treeview.dialog_hide()
|
|
||||||
return
|
|
||||||
GLib.idle_add(cleanup)
|
|
||||||
|
|
||||||
msg = "Validating ID"
|
thread_new_with_dialog(
|
||||||
App.treeview.dialog_show(msg)
|
_prep, parse_shell_output, "Querying API", enum, [key, response]
|
||||||
thread = threading.Thread(target=_prep, args=(key, response))
|
)
|
||||||
thread.start()
|
return
|
||||||
|
|
||||||
|
|
||||||
def process_user_input(enum: RowType) -> None:
|
def process_user_input(enum: RowType) -> None:
|
||||||
@@ -1166,7 +1172,9 @@ 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("No Battlemetrics API key is set; see Options", Popup.NOTIFY)
|
spawn_dialog(
|
||||||
|
"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)
|
||||||
@@ -1187,7 +1195,9 @@ def process_user_input(enum: RowType) -> None:
|
|||||||
|
|
||||||
show_wait_dialog = True
|
show_wait_dialog = True
|
||||||
wait_msg = "Working"
|
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
|
return
|
||||||
|
|
||||||
|
|
||||||
@@ -1275,7 +1285,9 @@ 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(self, window: "OuterWindow", event: Gdk.EventKey) -> None:
|
def _on_delete_event(
|
||||||
|
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:
|
||||||
@@ -1342,7 +1354,8 @@ 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" "actually connecting to a server"
|
"Used to perform a dry run without\n"
|
||||||
|
"actually connecting to a server"
|
||||||
)
|
)
|
||||||
ping_tooltip = (
|
ping_tooltip = (
|
||||||
"Refresh the ping for visible servers.\n"
|
"Refresh the ping for visible servers.\n"
|
||||||
@@ -1411,7 +1424,8 @@ 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) for i, row in enumerate(rows)
|
executor.submit(Servers.ping, i, row)
|
||||||
|
for i, row in enumerate(rows)
|
||||||
]
|
]
|
||||||
wait(futures)
|
wait(futures)
|
||||||
for future in futures:
|
for future in futures:
|
||||||
@@ -1472,8 +1486,6 @@ class ButtonBox(Gtk.Box):
|
|||||||
context = button.type
|
context = button.type
|
||||||
logger.info(f"User clicked '{context}'")
|
logger.info(f"User clicked '{context}'")
|
||||||
|
|
||||||
App.grid.right_panel.filters_vbox.set_visible(False)
|
|
||||||
|
|
||||||
if context == ButtonType.EXIT:
|
if context == ButtonType.EXIT:
|
||||||
logger.info("Normal user exit")
|
logger.info("Normal user exit")
|
||||||
save_res_and_quit()
|
save_res_and_quit()
|
||||||
@@ -1787,7 +1799,9 @@ class ModelManagerSingleton:
|
|||||||
|
|
||||||
|
|
||||||
class TreeView(Gtk.TreeView):
|
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):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -1906,7 +1920,9 @@ 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(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()
|
box = button.get_parent()
|
||||||
dialog = box.get_parent()
|
dialog = box.get_parent()
|
||||||
try:
|
try:
|
||||||
@@ -1998,7 +2014,9 @@ class TreeView(Gtk.TreeView):
|
|||||||
model.remove(it)
|
model.remove(it)
|
||||||
|
|
||||||
block_signals()
|
block_signals()
|
||||||
thread = threading.Thread(target=ModelManager.resync_model, args=(addr, qport))
|
thread = threading.Thread(
|
||||||
|
target=ModelManager.resync_model, args=(addr, qport)
|
||||||
|
)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
def remove_from_history(self) -> None:
|
def remove_from_history(self) -> None:
|
||||||
@@ -2022,12 +2040,16 @@ 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"calls {func}"
|
f"User clicked context menu '{menu_item.get_label()}', "
|
||||||
|
f"calls {func}"
|
||||||
)
|
)
|
||||||
logger.info(msg)
|
logger.info(msg)
|
||||||
func(self)
|
func(self)
|
||||||
else:
|
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 = (
|
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}'"
|
||||||
@@ -2068,7 +2090,9 @@ class TreeView(Gtk.TreeView):
|
|||||||
return True
|
return True
|
||||||
return False
|
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:
|
if event.type is Gdk.EventType.BUTTON_RELEASE and event.button != 3:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2152,7 +2176,9 @@ 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(widget, Gdk.Gravity.CENTER, Gdk.Gravity.WEST)
|
self.menu.popup_at_widget(
|
||||||
|
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)
|
||||||
@@ -2166,7 +2192,9 @@ class TreeView(Gtk.TreeView):
|
|||||||
return None
|
return None
|
||||||
call_out("start_cooldown", "", "")
|
call_out("start_cooldown", "", "")
|
||||||
|
|
||||||
thread = threading.Thread(target=self._background_player_count, args=())
|
thread = threading.Thread(
|
||||||
|
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:
|
||||||
@@ -2181,7 +2209,9 @@ 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(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():
|
if self.current_proc and self.current_proc.is_alive():
|
||||||
self.current_proc.terminate()
|
self.current_proc.terminate()
|
||||||
@@ -2239,7 +2269,9 @@ class TreeView(Gtk.TreeView):
|
|||||||
self.set_cursor(path)
|
self.set_cursor(path)
|
||||||
return None
|
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)
|
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:
|
||||||
@@ -2294,7 +2326,9 @@ class TreeView(Gtk.TreeView):
|
|||||||
return False
|
return False
|
||||||
return None
|
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
|
Suppresses spamming on keydown
|
||||||
"""
|
"""
|
||||||
@@ -2409,7 +2443,10 @@ class TreeView(Gtk.TreeView):
|
|||||||
params = Servers.params
|
params = Servers.params
|
||||||
serv = []
|
serv = []
|
||||||
with ThreadPoolExecutor() as executor:
|
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)
|
wait(futures)
|
||||||
for future in futures:
|
for future in futures:
|
||||||
res = future.result()
|
res = future.result()
|
||||||
@@ -2431,7 +2468,10 @@ 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 = [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)
|
wait(futures)
|
||||||
servers = []
|
servers = []
|
||||||
for future in futures:
|
for future in futures:
|
||||||
@@ -2450,8 +2490,6 @@ class TreeView(Gtk.TreeView):
|
|||||||
def _dump_servers(self, ips: list) -> list | None:
|
def _dump_servers(self, ips: list) -> list | None:
|
||||||
if len(ips) == 0:
|
if len(ips) == 0:
|
||||||
return []
|
return []
|
||||||
# NOTE: block malformed records
|
|
||||||
ips = [ip for ip in ips if len(ip.split(":")) == 3 and ip.split(":")[2] != ""]
|
|
||||||
with ThreadPoolExecutor() as executor:
|
with ThreadPoolExecutor() as executor:
|
||||||
futures = [
|
futures = [
|
||||||
executor.submit(
|
executor.submit(
|
||||||
@@ -2556,7 +2594,9 @@ 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(target=ModelManager.filter, args=(mode, *args))
|
thread = threading.Thread(
|
||||||
|
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:
|
||||||
@@ -2715,9 +2755,13 @@ 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("Fetching server metadata", Popup.WAIT)
|
self.wait_dialog = GenericDialog(
|
||||||
|
"Fetching server metadata", Popup.WAIT
|
||||||
|
)
|
||||||
self.wait_dialog.show_all()
|
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()
|
thread.start()
|
||||||
|
|
||||||
def _format_float(
|
def _format_float(
|
||||||
@@ -2896,7 +2940,9 @@ class TreeView(Gtk.TreeView):
|
|||||||
def get_view(self):
|
def get_view(self):
|
||||||
return self.view
|
return self.view
|
||||||
|
|
||||||
def get_prereqs(self, record: Record) -> tuple[bool, str, str | None, "Prereqs"]:
|
def prepare_connection(
|
||||||
|
self, record: Record
|
||||||
|
) -> subprocess.CompletedProcess | None:
|
||||||
"""
|
"""
|
||||||
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
|
||||||
"""
|
"""
|
||||||
@@ -2904,39 +2950,50 @@ class TreeView(Gtk.TreeView):
|
|||||||
if prereqs.appid is None:
|
if prereqs.appid is None:
|
||||||
logger.warning(f"Query to '{record.ip}:{record.qport}' timed out")
|
logger.warning(f"Query to '{record.ip}:{record.qport}' timed out")
|
||||||
msg = "Timed out when querying server, check IP or try again later"
|
msg = "Timed out when querying server, check IP or try again later"
|
||||||
return (False, msg, None, prereqs)
|
spawn_dialog(msg, Popup.NOTIFY)
|
||||||
|
return None
|
||||||
|
|
||||||
build = "DayZ" if prereqs.appid == APPID_DAYZ else "DayZ Experimental"
|
build = "DayZ" if prereqs.appid == APPID_DAYZ else "DayZ Experimental"
|
||||||
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("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."
|
msg = "Local Steam installation is not set, possibly malformed config file."
|
||||||
return (False, msg, None, prereqs)
|
spawn_dialog(msg, Popup.NOTIFY)
|
||||||
|
return None
|
||||||
|
|
||||||
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(f"'{prereqs.appid}' not found in user's libraryfolders")
|
logger.critical(
|
||||||
|
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. "
|
||||||
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, "
|
||||||
"restart Steam to allow these changes to synchronize, then try again."
|
"restart Steam to allow these changes to synchronize, then try again."
|
||||||
)
|
)
|
||||||
return (False, msg, None, prereqs)
|
spawn_dialog(msg, Popup.NOTIFY)
|
||||||
|
return None
|
||||||
except AppMovedError:
|
except AppMovedError:
|
||||||
logger.critical(f"Library folder synch error for '{prereqs.appid}'")
|
logger.critical(
|
||||||
|
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, "
|
||||||
"restart Steam to allow these changes to synchronize, then try again."
|
"restart Steam to allow these changes to synchronize, then try again."
|
||||||
)
|
)
|
||||||
return (False, msg, None, prereqs)
|
spawn_dialog(msg, Popup.NOTIFY)
|
||||||
|
return None
|
||||||
except (VDFLoadError, PeFileError, Exception) as e:
|
except (VDFLoadError, PeFileError, Exception) as e:
|
||||||
logger.critical(e)
|
logger.critical(e)
|
||||||
msg = "Steam settings or DayZ installation may be corrupted. Try restarting Steam."
|
msg = "Steam settings or DayZ installation may be corrupted. Try restarting Steam."
|
||||||
return (False, msg, None, prereqs)
|
spawn_dialog(msg, Popup.NOTIFY)
|
||||||
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
local_vers = PeFile.get_dayz_version(pefile_path)
|
local_vers = PeFile.get_dayz_version(pefile_path)
|
||||||
@@ -2948,7 +3005,7 @@ class TreeView(Gtk.TreeView):
|
|||||||
local_vers = None
|
local_vers = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
remote_vers = PeFile.dayz_version_from_str(prereqs)
|
remote_vers = PeFile.dayz_version_from_str(prereqs.version)
|
||||||
except Exception:
|
except Exception:
|
||||||
remote_vers = None
|
remote_vers = None
|
||||||
|
|
||||||
@@ -2961,15 +3018,18 @@ class TreeView(Gtk.TreeView):
|
|||||||
f"This server is running a newer build ({prereqs.version}) of {build} than "
|
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?"
|
f"your local version. You may be unable to connect. Proceed anyway?"
|
||||||
)
|
)
|
||||||
return (True, msg, pefile_path, prereqs)
|
res = spawn_dialog(msg, Popup.CONFIRM)
|
||||||
|
if res is True:
|
||||||
|
return None
|
||||||
case VersionMatch.LOCAL_NEWER:
|
case VersionMatch.LOCAL_NEWER:
|
||||||
msg = (
|
msg = (
|
||||||
f"This server is running an out-of-date build ({prereqs.version}) of {build}. "
|
f"This server is running an out-of-date build ({prereqs.version}) of {build}. "
|
||||||
"You may be unable to connect. Proceed anyway?"
|
"You may be unable to connect. Proceed anyway?"
|
||||||
)
|
)
|
||||||
return (True, msg, pefile_path, prereqs)
|
res = spawn_dialog(msg, Popup.CONFIRM)
|
||||||
|
if res is True:
|
||||||
|
return None
|
||||||
case VersionMatch.SAME_VERSION:
|
case VersionMatch.SAME_VERSION:
|
||||||
return (True, "", pefile_path, prereqs)
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if prereqs.password is True:
|
if prereqs.password is True:
|
||||||
@@ -2977,9 +3037,19 @@ class TreeView(Gtk.TreeView):
|
|||||||
"This server is password-protected and you will be "
|
"This server is password-protected and you will be "
|
||||||
"prompted when connecting. Do you want to proceed?"
|
"prompted when connecting. Do you want to proceed?"
|
||||||
)
|
)
|
||||||
return (True, msg, pefile_path, prereqs)
|
res = spawn_dialog(msg, Popup.CONFIRM)
|
||||||
|
if res is True:
|
||||||
|
return None
|
||||||
|
|
||||||
return (True, "", 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
|
||||||
|
|
||||||
@signal_emission
|
@signal_emission
|
||||||
@update_window_labels
|
@update_window_labels
|
||||||
@@ -3049,7 +3119,13 @@ class TreeView(Gtk.TreeView):
|
|||||||
record = self.get_record()
|
record = self.get_record()
|
||||||
if record is None:
|
if record is None:
|
||||||
return
|
return
|
||||||
prepare_connection(None, record)
|
thread_new_with_dialog(
|
||||||
|
self.prepare_connection,
|
||||||
|
parse_shell_output,
|
||||||
|
"Querying server",
|
||||||
|
None,
|
||||||
|
[record],
|
||||||
|
)
|
||||||
case _: # any other non-server option from the main menu
|
case _: # any other non-server option from the main menu
|
||||||
process_tree_option(output)
|
process_tree_option(output)
|
||||||
|
|
||||||
@@ -3213,7 +3289,9 @@ 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("deleted-text", self._on_text_deleted)
|
self.entry.get_property("buffer").connect(
|
||||||
|
"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):
|
||||||
@@ -3329,7 +3407,9 @@ 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_title, renderer, text=i, weight=2)
|
column = Gtk.TreeViewColumn(
|
||||||
|
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)
|
||||||
@@ -3345,8 +3425,12 @@ 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(orientation=Gtk.Orientation.VERTICAL, halign=Gtk.Align.CENTER)
|
box = Gtk.Box(
|
||||||
self.description = Gtk.Label(justify=Gtk.Justification.CENTER, wrap=True)
|
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 = 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:
|
||||||
@@ -3384,7 +3468,9 @@ class DetailsDialog(GenericDialog):
|
|||||||
self.run()
|
self.run()
|
||||||
self.destroy()
|
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)
|
response = Servers.details(self.ip, self.qport)
|
||||||
if response.success:
|
if response.success:
|
||||||
for row in response.data:
|
for row in response.data:
|
||||||
@@ -3441,7 +3527,9 @@ 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(target=self._background, args=(wait_dialog, record))
|
thread = threading.Thread(
|
||||||
|
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:
|
||||||
@@ -3466,7 +3554,9 @@ 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(self, data: subprocess.CompletedProcess) -> bool | int:
|
def _parse_modlist_rows(
|
||||||
|
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)
|
||||||
@@ -3496,7 +3586,9 @@ class ModDialog(GenericDialog):
|
|||||||
|
|
||||||
|
|
||||||
class LinkDialog(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)
|
super().__init__(text, Popup.NOTIFY)
|
||||||
|
|
||||||
text = textwrap.dedent(text)
|
text = textwrap.dedent(text)
|
||||||
@@ -3709,27 +3801,12 @@ class Options(Gtk.Box):
|
|||||||
"Always fullscreen",
|
"Always fullscreen",
|
||||||
Preferences.WINDOW,
|
Preferences.WINDOW,
|
||||||
)
|
)
|
||||||
|
self.steam_toggle = self.make_binary_radio(
|
||||||
# TODO: gray out options if not available on system
|
"Steam", "Flatpak (experimental)", Preferences.CLIENT
|
||||||
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",
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
renderer_text = Gtk.CellRendererText(ellipsize=Pango.EllipsizeMode.END)
|
|
||||||
self.client_combo = Gtk.ComboBox.new_with_model(client_store)
|
|
||||||
self.client_combo.set_halign(Gtk.Align.START)
|
|
||||||
self.client_combo.pack_start(renderer_text, True)
|
|
||||||
self.client_combo.connect("changed", self._on_client_changed)
|
|
||||||
self.client_combo.add_attribute(renderer_text, "text", 0)
|
|
||||||
|
|
||||||
pref_rows = [
|
pref_rows = [
|
||||||
[LeftLabel("Steam client"), self.client_combo],
|
[LeftLabel("Steam client"), self.steam_toggle],
|
||||||
[LeftLabel("Window size at boot"), self.fullscreen_toggle],
|
[LeftLabel("Window size at boot"), self.fullscreen_toggle],
|
||||||
[LeftLabel("Player name"), self.player_box],
|
[LeftLabel("Player name"), self.player_box],
|
||||||
]
|
]
|
||||||
@@ -3743,7 +3820,8 @@ 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. " "Auto: unmanned downloads."
|
"Manual: prompt to subscribe to mods in Steam. "
|
||||||
|
"Auto: unmanned downloads."
|
||||||
)
|
)
|
||||||
eb = InfoEventBox(msg)
|
eb = InfoEventBox(msg)
|
||||||
|
|
||||||
@@ -3802,7 +3880,9 @@ 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(frame, col, row, self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT)
|
grid.attach(
|
||||||
|
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)
|
||||||
@@ -3848,7 +3928,9 @@ class Options(Gtk.Box):
|
|||||||
|
|
||||||
return 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()
|
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):
|
||||||
@@ -3869,7 +3951,9 @@ 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(el, col, row, self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT)
|
grid.attach(
|
||||||
|
el, col, row, self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT
|
||||||
|
)
|
||||||
col += 1
|
col += 1
|
||||||
row += 1
|
row += 1
|
||||||
return grid
|
return grid
|
||||||
@@ -3907,25 +3991,20 @@ class Options(Gtk.Box):
|
|||||||
show_wait_dialog = True
|
show_wait_dialog = True
|
||||||
call_on_thread(show_wait_dialog, cmd, wait_msg, "")
|
call_on_thread(show_wait_dialog, cmd, wait_msg, "")
|
||||||
|
|
||||||
def _on_client_changed(self, combo: Gtk.ComboBox) -> None:
|
|
||||||
# prevent triggering on initial init
|
|
||||||
if App.treeview.subpage is not RowType.OPTIONS:
|
|
||||||
return
|
|
||||||
ind = combo.get_active()
|
|
||||||
mod = combo.get_model()
|
|
||||||
client = mod[ind][1]
|
|
||||||
call_bash_func("Change client", client)
|
|
||||||
|
|
||||||
def _on_branch_changed(self, combo: Gtk.ComboBoxText) -> None:
|
def _on_branch_changed(self, combo: Gtk.ComboBoxText) -> None:
|
||||||
# prevent triggering on initial init
|
# prevent triggering on initial init
|
||||||
if App.treeview.subpage is not RowType.OPTIONS:
|
if App.treeview.subpage is not RowType.OPTIONS:
|
||||||
return
|
return
|
||||||
process_toggle(RowType.TGL_BRANCH)
|
process_toggle(RowType.TGL_BRANCH)
|
||||||
|
|
||||||
def _on_radio_toggled(self, button: Gtk.RadioButton, context: Preferences) -> None:
|
def _on_radio_toggled(
|
||||||
if App.treeview.subpage is not RowType.OPTIONS:
|
self, button: Gtk.RadioButton, context: Preferences
|
||||||
|
) -> None:
|
||||||
|
if App.treeview.subpage is None:
|
||||||
return
|
return
|
||||||
match context:
|
match context:
|
||||||
|
case Preferences.CLIENT:
|
||||||
|
toggle = RowType.TGL_STEAM
|
||||||
case Preferences.INSTALL:
|
case Preferences.INSTALL:
|
||||||
toggle = RowType.TGL_INSTALL
|
toggle = RowType.TGL_INSTALL
|
||||||
state = button.get_group()[0].get_active()
|
state = button.get_group()[0].get_active()
|
||||||
@@ -4047,9 +4126,11 @@ class Options(Gtk.Box):
|
|||||||
radio = 0
|
radio = 0
|
||||||
self.mod_install_toggle.get_children()[radio].set_active(True)
|
self.mod_install_toggle.get_children()[radio].set_active(True)
|
||||||
|
|
||||||
for ind, row in enumerate(self.client_combo.get_model()):
|
if client == "flatpak":
|
||||||
if row[1] == client:
|
radio = 1
|
||||||
self.client_combo.set_active(ind)
|
else:
|
||||||
|
radio = 0
|
||||||
|
self.steam_toggle.get_children()[radio].set_active(True)
|
||||||
|
|
||||||
if fullscreen == "true":
|
if fullscreen == "true":
|
||||||
radio = 1
|
radio = 1
|
||||||
@@ -4066,14 +4147,18 @@ 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(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)
|
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(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)
|
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:
|
||||||
@@ -4182,7 +4267,9 @@ 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(desc, sep, Gtk.PositionType.BOTTOM, w, h)
|
grid.attach_next_to(
|
||||||
|
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)
|
||||||
@@ -4190,7 +4277,9 @@ 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(r_spacer, l_spacer, Gtk.PositionType.RIGHT, w, h)
|
grid.attach_next_to(
|
||||||
|
r_spacer, l_spacer, Gtk.PositionType.RIGHT, w, h
|
||||||
|
)
|
||||||
row += 1
|
row += 1
|
||||||
return grid
|
return grid
|
||||||
|
|
||||||
@@ -4199,7 +4288,9 @@ class KeybindingsDialog(Gtk.Box):
|
|||||||
col = 0
|
col = 0
|
||||||
w = 1
|
w = 1
|
||||||
h = 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:
|
for cat in categories:
|
||||||
label = Gtk.Label(label=cat)
|
label = Gtk.Label(label=cat)
|
||||||
add_class(label, "left-label")
|
add_class(label, "left-label")
|
||||||
@@ -4219,7 +4310,9 @@ class KeybindingsDialog(Gtk.Box):
|
|||||||
column = 1
|
column = 1
|
||||||
w = 1
|
w = 1
|
||||||
h = 1
|
h = 1
|
||||||
sidebar = self.build_sidebar(["Servers", "Navigation", "Vim-style keys"])
|
sidebar = self.build_sidebar(
|
||||||
|
["Servers", "Navigation", "Vim-style keys"]
|
||||||
|
)
|
||||||
separator = Gtk.Separator()
|
separator = Gtk.Separator()
|
||||||
keys_box = self.build_keys(items)
|
keys_box = self.build_keys(items)
|
||||||
|
|
||||||
@@ -4232,20 +4325,22 @@ class KeybindingsDialog(Gtk.Box):
|
|||||||
class Changelog(Gtk.Box):
|
class Changelog(Gtk.Box):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self.changelog_label = Gtk.Label()
|
|
||||||
self.add(self.changelog_label)
|
|
||||||
|
|
||||||
def open_changelog(self, path: Path) -> None:
|
|
||||||
try:
|
try:
|
||||||
changelog = path.read_text()
|
with open(changelog_path, "r") as f:
|
||||||
|
changelog = f.read()
|
||||||
|
changelog_label = Gtk.Label()
|
||||||
|
changelog = self.format_pango(changelog)
|
||||||
|
changelog_label.set_markup(changelog)
|
||||||
|
self.add(changelog_label)
|
||||||
|
except FileNotFoundError:
|
||||||
|
msg = f"Failed to find CHANGELOG.md at {changelog_path}"
|
||||||
|
logger.critical(msg)
|
||||||
|
spawn_dialog(msg, Popup.WARN)
|
||||||
|
return
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
spawn_dialog(f"Something went wrong: {e}", Popup.NOTIFY)
|
spawn_dialog(f"Something went wrong: {e}", Popup.WARN)
|
||||||
logger.critical(e)
|
logger.critical(e)
|
||||||
return Exception
|
return
|
||||||
formatted = self.format_pango(changelog)
|
|
||||||
self.changelog_label.set_markup(formatted)
|
|
||||||
App.grid.notebook.set_page_by_enum(NotebookPage.CHANGELOG)
|
|
||||||
|
|
||||||
def format_pango(self, text: str) -> str:
|
def format_pango(self, text: str) -> str:
|
||||||
medium = '<span size="medium"><b>'
|
medium = '<span size="medium"><b>'
|
||||||
@@ -4262,8 +4357,7 @@ class Notebook(Gtk.Notebook):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(show_tabs=False, show_border=False)
|
super().__init__(show_tabs=False, show_border=False)
|
||||||
|
|
||||||
self.changelog = Changelog()
|
self.clog = ScrollableNote(Changelog())
|
||||||
self.clog = ScrollableNote(self.changelog)
|
|
||||||
self.clog.type = RowType.CHANGELOG
|
self.clog.type = RowType.CHANGELOG
|
||||||
self.clog.show_all()
|
self.clog.show_all()
|
||||||
self.append_page(self.clog)
|
self.append_page(self.clog)
|
||||||
@@ -4282,10 +4376,6 @@ class Notebook(Gtk.Notebook):
|
|||||||
self.connect("switch-page", self._on_page_changed)
|
self.connect("switch-page", self._on_page_changed)
|
||||||
self.connect("key-press-event", self._on_keypress)
|
self.connect("key-press-event", self._on_keypress)
|
||||||
|
|
||||||
def open_changelog(self) -> None:
|
|
||||||
path = Path(changelog_path)
|
|
||||||
self.changelog.open_changelog(path)
|
|
||||||
|
|
||||||
def _set_adjustment(self, adjustment: VAdjustment) -> None:
|
def _set_adjustment(self, adjustment: VAdjustment) -> None:
|
||||||
INCREMENT = 50
|
INCREMENT = 50
|
||||||
page = self.get_page()
|
page = self.get_page()
|
||||||
@@ -4437,7 +4527,9 @@ 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 = 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..."
|
suffix = "| Distance: calculating..."
|
||||||
|
|
||||||
if players == 0:
|
if players == 0:
|
||||||
@@ -4489,7 +4581,9 @@ 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.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.attach_next_to(
|
||||||
self.statusbar, self.notebook, Gtk.PositionType.BOTTOM, 3, 1
|
self.statusbar, self.notebook, Gtk.PositionType.BOTTOM, 3, 1
|
||||||
)
|
)
|
||||||
@@ -4551,23 +4645,11 @@ class App(Gtk.Application):
|
|||||||
)
|
)
|
||||||
self.win.add_accel_group(accel)
|
self.win.add_accel_group(accel)
|
||||||
|
|
||||||
self.setup_signals()
|
GLib.unix_signal_add(
|
||||||
|
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()
|
||||||
|
|
||||||
@@ -4652,7 +4734,9 @@ 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(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"
|
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)
|
||||||
@@ -4772,7 +4856,9 @@ 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("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 = Gtk.EntryCompletion(inline_completion=True)
|
||||||
completion.set_text_column(0)
|
completion.set_text_column(0)
|
||||||
@@ -4840,7 +4926,9 @@ 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(self, entry: Gtk.Entry, event: Gdk.EventKey) -> None:
|
def _on_map_entry_keypress(
|
||||||
|
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()
|
||||||
@@ -4883,7 +4971,9 @@ class FilterPanel(Gtk.Box):
|
|||||||
App.treeview.grab_focus()
|
App.treeview.grab_focus()
|
||||||
return False
|
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:
|
match event.keyval:
|
||||||
case Gdk.KEY_Up:
|
case Gdk.KEY_Up:
|
||||||
return True
|
return True
|
||||||
@@ -4894,7 +4984,9 @@ class FilterPanel(Gtk.Box):
|
|||||||
return True
|
return True
|
||||||
return False
|
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:
|
match event.keyval:
|
||||||
case Gdk.KEY_Down:
|
case Gdk.KEY_Down:
|
||||||
self.maps_combo.popup()
|
self.maps_combo.popup()
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 238 KiB |
Reference in new issue
Block a user