mirror of
https://github.com/aclist/dztui.git
synced 2026-10-25 20:22:37 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f5559121d | ||
|
|
019ea82004 | ||
|
|
df291c867c | ||
|
|
9db3ac1f80 | ||
|
|
bdb7da9440 | ||
|
|
cc623914da | ||
|
|
fa9c58f1e2 | ||
|
|
d7ba49ea97 | ||
|
|
bab2379fad | ||
|
|
98b4f37e54 | ||
|
|
8ca8dbb6ff | ||
|
|
7d6e089c03 | ||
|
|
3c75b9362b | ||
|
|
dc24ef6e85 | ||
|
|
f76985c79d | ||
|
|
3d3cd19892 | ||
|
|
c14fb10d23 | ||
|
|
a8417d27d7 |
No files matched your search
@@ -8,7 +8,7 @@ jobs:
|
|||||||
mirror-to-codeberg:
|
mirror-to-codeberg:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: pixta-dev/repository-mirroring-action@v1
|
- uses: pixta-dev/repository-mirroring-action@v1
|
||||||
|
|||||||
@@ -1 +1,4 @@
|
|||||||
tags
|
tags
|
||||||
|
__pycache__
|
||||||
|
pre-commit
|
||||||
|
tests/
|
||||||
+27
-3
@@ -1,5 +1,29 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [6.0.5] 2026-05-17
|
||||||
|
## Changed
|
||||||
|
- Update symlink hash logic to align with DZGUI 7 beta
|
||||||
|
- Update A2S helper to latest commit
|
||||||
|
|
||||||
|
## [6.0.4] 2026-04-13
|
||||||
|
## Fixed
|
||||||
|
- Update A2S module checksums to support servers with missing description field
|
||||||
|
|
||||||
|
## [6.0.3] 2026-04-12
|
||||||
|
## Fixed
|
||||||
|
- Use GLibUnix namespace where available when setting up SIGINT callback
|
||||||
|
|
||||||
|
## [6.0.2] 2026-01-26
|
||||||
|
## Fixed
|
||||||
|
- Explicitly use Python 3.13 when calling subprocesses
|
||||||
|
- Raise error correctly from module
|
||||||
|
- 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] 2026-01-16
|
## [6.0.1] 2026-01-16
|
||||||
## Fixed
|
## Fixed
|
||||||
- Explicitly use Python 3.13 when calling subprocesses
|
- Explicitly use Python 3.13 when calling subprocesses
|
||||||
@@ -377,7 +401,7 @@ As part of this change, version 4.0.0 introduces the ability to connect to or ad
|
|||||||
|
|
||||||
If you encounter any problems with this new release or with the migration of configs, please do not hesitate to submit a bug report.
|
If you encounter any problems with this new release or with the migration of configs, please do not hesitate to submit a bug report.
|
||||||
|
|
||||||
Attention Fedora 38 users: problems with upstream GNOME packages causing crashes have been reported to GNOME development and a fix has been issued. You have the choice of compiling the zenity package
|
Attention Fedora 38 users: problems with upstream GNOME packages causing crashes have been reported to GNOME development and a fix has been issued. You have the choice of compiling the zenity package
|
||||||
from source or waiting until the latest version is merged into Fedora's package manager.
|
from source or waiting until the latest version is merged into Fedora's package manager.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@@ -402,7 +426,7 @@ from source or waiting until the latest version is merged into Fedora's package
|
|||||||
- Store complete IP:Port instead of server IDs
|
- Store complete IP:Port instead of server IDs
|
||||||
- Make Battlemetrics API key optional: this is only used for the 'Connect by ID' and 'Add server by ID' methods and is not required. If you prefer, you can simply connect/add by IP.
|
- Make Battlemetrics API key optional: this is only used for the 'Connect by ID' and 'Add server by ID' methods and is not required. If you prefer, you can simply connect/add by IP.
|
||||||
- Prevent the application from launching in Game Mode on Steam Deck: Steam Deck's kiosk mode has problems sending keyboard input to third party applications. To prevent unintended usage, DZGUI now warns the user to launch the app in Desktop Mode if they attempt to use it from Game Mode. Adding DZGUI as a Non-Steam Game does work on desktop PCs, but is not recommended due to the way Steam handles subshells. For best results, launch DZGUI directly via the script/applications menu (PC) or via the desktop icon (Steam Deck).
|
- Prevent the application from launching in Game Mode on Steam Deck: Steam Deck's kiosk mode has problems sending keyboard input to third party applications. To prevent unintended usage, DZGUI now warns the user to launch the app in Desktop Mode if they attempt to use it from Game Mode. Adding DZGUI as a Non-Steam Game does work on desktop PCs, but is not recommended due to the way Steam handles subshells. For best results, launch DZGUI directly via the script/applications menu (PC) or via the desktop icon (Steam Deck).
|
||||||
- Omit null servers from list: servers that time out or send an empty response are now omitted entirely from the My Servers list, as they will not return meaningful metadata unless they are online.
|
- Omit null servers from list: servers that time out or send an empty response are now omitted entirely from the My Servers list, as they will not return meaningful metadata unless they are online.
|
||||||
The My Servers list thus shows online and accessible servers
|
The My Servers list thus shows online and accessible servers
|
||||||
|
|
||||||
## [3.3.0] 2023-05-16
|
## [3.3.0] 2023-05-16
|
||||||
@@ -421,7 +445,7 @@ from source or waiting until the latest version is merged into Fedora's package
|
|||||||
- First-time setup: sudo escalation when checking system map count for the first time
|
- First-time setup: sudo escalation when checking system map count for the first time
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Steam Deck: non-ASCII delimiter causing setup menu to despawn on some devices
|
- Steam Deck: non-ASCII delimiter causing setup menu to despawn on some devices
|
||||||
- Don't add items in My Servers multiple times to array when the list of favorites is paginated
|
- Don't add items in My Servers multiple times to array when the list of favorites is paginated
|
||||||
- Trigger progress dialogs sooner and in sequence to reduce appearance of visual lag
|
- Trigger progress dialogs sooner and in sequence to reduce appearance of visual lag
|
||||||
- First-time setup: break out of dialogs correctly when user backs out
|
- First-time setup: break out of dialogs correctly when user backs out
|
||||||
|
|||||||
+3
-3
@@ -8,7 +8,7 @@ If you are a developer, you can skip to the end.
|
|||||||
# Requesting help
|
# Requesting help
|
||||||
|
|
||||||
If you encounter a problem with DZGUI, you can submit tickets on the GitHub
|
If you encounter a problem with DZGUI, you can submit tickets on the GitHub
|
||||||
(issue tracker)[https://github.com/aclist/dzgui-devel/issues] under the
|
[issue tracker](https://github.com/aclist/dztui/issues) under the
|
||||||
`troubleshooting` tag.
|
`troubleshooting` tag.
|
||||||
|
|
||||||
# How can I help the project?
|
# How can I help the project?
|
||||||
@@ -24,7 +24,7 @@ accordingly.
|
|||||||
|
|
||||||
## Submitting a ticket
|
## Submitting a ticket
|
||||||
|
|
||||||
Navigate to the GitHub (issue tracker)[https://github.com/aclist/dzgui-devel/issues].
|
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:
|
From there, follow the onscreen prompts. You will be asked questions such as:
|
||||||
|
|
||||||
- What version are you using?
|
- What version are you using?
|
||||||
@@ -67,5 +67,5 @@ will be merged to `prerelease/<latest-version>`, then `testing`, and after a liv
|
|||||||
testing phase, to `stable` as well.
|
testing phase, to `stable` as well.
|
||||||
|
|
||||||
It is recommended to follow
|
It is recommended to follow
|
||||||
(Conventional Commits)[https://www.conventionalcommits.org/en/v1.0.0/], as this
|
[Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/), as this
|
||||||
integrates well with tooling and helps the project review your code.
|
integrates well with tooling and helps the project review your code.
|
||||||
@@ -11,6 +11,15 @@ 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:
|
||||||
@@ -18,7 +27,6 @@ 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
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
set -o pipefail
|
set -o pipefail
|
||||||
|
|
||||||
src_path="$(readlink -e "$0")"
|
src_path="$(readlink -e "$0")"
|
||||||
version=6.0.1
|
version=6.0.5
|
||||||
|
|
||||||
#CONSTANTS
|
#CONSTANTS
|
||||||
aid=221100
|
aid=221100
|
||||||
@@ -142,7 +142,7 @@ test_gobject(){
|
|||||||
python3.13 -c "import gi"
|
python3.13 -c "import gi"
|
||||||
if [[ ! $? -eq 0 ]]; then
|
if [[ ! $? -eq 0 ]]; then
|
||||||
logger CRITICAL "Missing PyGObject"
|
logger CRITICAL "Missing PyGObject"
|
||||||
quit_with_pdialog "Requires PyGObject (python-gobject)"
|
quit_with_help_dialog "Requires PyGObject (python-gobject)"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
logger INFO "Found PyGObject in Python env"
|
logger INFO "Found PyGObject in Python env"
|
||||||
@@ -247,7 +247,7 @@ open_url(){
|
|||||||
xdg-open "$url"
|
xdg-open "$url"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
quit_with_pdialog(){
|
quit_with_help_dialog(){
|
||||||
local sel
|
local sel
|
||||||
msg="$1"
|
msg="$1"
|
||||||
help_button="Open help page"
|
help_button="Open help page"
|
||||||
@@ -260,7 +260,7 @@ quit_with_pdialog(){
|
|||||||
check_pyver(){
|
check_pyver(){
|
||||||
if [[ ! $(python3.13 --version) ]]; then
|
if [[ ! $(python3.13 --version) ]]; then
|
||||||
local msg="Requires Python 3.13"
|
local msg="Requires Python 3.13"
|
||||||
quit_with_pdialog "$msg"
|
quit_with_help_dialog "$msg"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
watcher_deps(){
|
watcher_deps(){
|
||||||
@@ -335,6 +335,7 @@ check_unmerged(){
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
check_version(){
|
check_version(){
|
||||||
|
echo "# Checking version"
|
||||||
[[ -n $reference_branch ]] && return
|
[[ -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}')
|
||||||
@@ -385,7 +386,7 @@ prompt_dl(){
|
|||||||
Your branch: $branch
|
Your branch: $branch
|
||||||
Your version: $version
|
Your version: $version
|
||||||
Upstream version: $upstream
|
Upstream version: $upstream
|
||||||
|
|
||||||
Version updates introduce important bug fixes and are encouraged. Attempt to download the latest version?
|
Version updates introduce important bug fixes and are encouraged. Attempt to download the latest version?
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
@@ -414,6 +415,7 @@ 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
|
||||||
@@ -430,6 +432,7 @@ 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
|
||||||
@@ -479,6 +482,7 @@ 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)
|
||||||
@@ -489,16 +493,8 @@ steam_deps(){
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
migrate_files(){
|
|
||||||
if [[ ! -f $config_path/dztuirc.oldapi ]]; then
|
|
||||||
cp $config_file $config_path/dztuirc.oldapi
|
|
||||||
logger INFO "Migrated old API file"
|
|
||||||
fi
|
|
||||||
[[ ! -f $hist_file ]] && return
|
|
||||||
rm $hist_file
|
|
||||||
logger INFO "Wiped old history file"
|
|
||||||
}
|
|
||||||
stale_symlinks(){
|
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)
|
readarray -t links < <(find "$game_dir" -xtype l)
|
||||||
for link in "${links[@]}"; do
|
for link in "${links[@]}"; do
|
||||||
@@ -512,7 +508,7 @@ check_availability() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
local url=$1
|
local url=$1
|
||||||
local timeout_sec="3"
|
local timeout_sec="2"
|
||||||
if [[ $2 ]]; then
|
if [[ $2 ]]; then
|
||||||
timeout_sec=$2
|
timeout_sec=$2
|
||||||
fi
|
fi
|
||||||
@@ -523,6 +519,7 @@ 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
|
||||||
@@ -534,6 +531,9 @@ 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,6 +548,7 @@ 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
|
||||||
@@ -566,9 +567,12 @@ get_hash(){
|
|||||||
md5sum "$1" | awk '{print $1}'
|
md5sum "$1" | awk '{print $1}'
|
||||||
}
|
}
|
||||||
fetch_a2s(){
|
fetch_a2s(){
|
||||||
# this file is currently monolithic
|
sum="d26da2fe03df2b95436f823f0496887b"
|
||||||
[[ -d $helpers_path/a2s ]] && { logger INFO "A2S helper is current"; return 0; }
|
if [[ -d $helpers_path/a2s ]] && [[ $(get_hash "$helpers_path/a2s/info.py") == $sum ]]; then
|
||||||
local sha=c7590ffa9a6d0c6912e17ceeab15b832a1090640
|
logger INFO "A2S helper is current"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
local sha=b40eb24cdbb06ebd08272f224257fe5a81610e86
|
||||||
local author="yepoleb"
|
local author="yepoleb"
|
||||||
local repo="python-a2s"
|
local repo="python-a2s"
|
||||||
local url="https://github.com/$author/$repo/tarball/$sha"
|
local url="https://github.com/$author/$repo/tarball/$sha"
|
||||||
@@ -582,13 +586,13 @@ fetch_a2s(){
|
|||||||
logger INFO "Updated A2S helper to sha '$sha'"
|
logger INFO "Updated A2S helper to sha '$sha'"
|
||||||
}
|
}
|
||||||
fetch_dzq(){
|
fetch_dzq(){
|
||||||
local sum="0a334e1e144e76e560419d155435c91e"
|
local sum="0d316badbb8d943c5f078e73862cac3e"
|
||||||
local file="$helpers_path/a2s/dayzquery.py"
|
local file="$helpers_path/a2s/dayzquery.py"
|
||||||
if [[ -f $file ]] && [[ $(get_hash "$file") == $sum ]]; then
|
if [[ -f $file ]] && [[ $(get_hash "$file") == $sum ]]; then
|
||||||
logger INFO "DZQ is current"
|
logger INFO "DZQ is current"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
local sha=a22a9f428cbe075d7dda62f78000296955eea92a
|
local sha="07483b88ed096327ebca752f5e177011b604d7fe"
|
||||||
local author="yepoleb"
|
local author="yepoleb"
|
||||||
local repo="dayzquery"
|
local repo="dayzquery"
|
||||||
local url="https://raw.githubusercontent.com/$author/$repo/$sha/dayzquery.py"
|
local url="https://raw.githubusercontent.com/$author/$repo/$sha/dayzquery.py"
|
||||||
@@ -625,12 +629,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"]="5e35a9812c03fc64d75843a23d024f0d"
|
["funcs"]="cff21e5ecfbbf72951f9f772b46024c9"
|
||||||
["query_v2.py"]="55d339ba02512ac69de288eb3be41067"
|
["query_v2.py"]="55d339ba02512ac69de288eb3be41067"
|
||||||
["servers.py"]="ed442c3aecf33f777d59dcf53650d263"
|
["servers.py"]="ed442c3aecf33f777d59dcf53650d263"
|
||||||
["ui.py"]="3d67e5e8e85a23dde1fd0e85a9be62a9"
|
["ui.py"]="cf52a3d5883afe02a013d8673df494fa"
|
||||||
["vdf2json.py"]="2f49f6f5d3af919bebaab2e9c220f397"
|
["vdf2json.py"]="2f49f6f5d3af919bebaab2e9c220f397"
|
||||||
["pefile.py"]="21531f2c0d9dfa5f110cf6779f9d22c0"
|
["pefile.py"]="b452974a84bff1d821872fcebf59e380"
|
||||||
)
|
)
|
||||||
local author="aclist"
|
local author="aclist"
|
||||||
local repo="dztui"
|
local repo="dztui"
|
||||||
@@ -690,12 +694,12 @@ fetch_km_helper(){
|
|||||||
}
|
}
|
||||||
get_response_code(){
|
get_response_code(){
|
||||||
local url="$1"
|
local url="$1"
|
||||||
curl -Ls -I -o /dev/null -w "%{http_code}" "$url"
|
curl --connect-timeout 3 -Ls -I -o /dev/null -w "%{http_code}" "$url"
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch_ip_db(){
|
fetch_ip_db(){
|
||||||
parse_dl_url(){
|
parse_dl_url(){
|
||||||
curl -Ls "$url" \
|
curl --connect-timeout 3 -Ls "$url" \
|
||||||
| grep "csv.gz" \
|
| grep "csv.gz" \
|
||||||
| awk -F"['']" '{print $2}'
|
| awk -F"['']" '{print $2}'
|
||||||
}
|
}
|
||||||
@@ -711,6 +715,9 @@ 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
|
||||||
@@ -730,7 +737,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}/${month}.csv"
|
rm "${state_path}/${this_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
|
||||||
@@ -745,55 +752,57 @@ fetch_ip_db(){
|
|||||||
rm "$ip_file"
|
rm "$ip_file"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "$month" > "$month_file"
|
echo "$this_month" > "$month_file"
|
||||||
logger INFO "Wrote '$month' to stub '$month_file'"
|
logger INFO "Wrote '$this_month' to stub '$month_file'"
|
||||||
logger INFO "Updated '$ip_file'"
|
logger INFO "Updated '$ip_file'"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
check_remote(){
|
||||||
|
local url="$1"
|
||||||
|
local res=$(get_response_code "$url")
|
||||||
|
if [[ $res -ne 200 ]]; then
|
||||||
|
logger WARN "Failed to retrieve remote resource: '$url' ($res)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
logger INFO "Resolved remote URL: '$url'"
|
||||||
|
|
||||||
|
# test dl url
|
||||||
|
local dl_url="$(parse_dl_url)"
|
||||||
|
local res=$(get_response_code "$dl_url")
|
||||||
|
if [[ $res -ne 200 ]]; then
|
||||||
|
logger WARN "Remote resource unavailable: '$dl_url' ($res)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
logger INFO "Resolved download URL: '$dl_url'"
|
||||||
|
fetch "$dl_url"
|
||||||
|
}
|
||||||
|
|
||||||
local url="https://db-ip.com/db/download/ip-to-city-lite"
|
local url="https://db-ip.com/db/download/ip-to-city-lite"
|
||||||
local month_file="${state_path}/.month"
|
month_file="${state_path}/.month"
|
||||||
local ip_file="${state_path}/ips.csv"
|
this_month=$(date +%Y-%m)
|
||||||
|
|
||||||
# test main url
|
|
||||||
local res=$(get_response_code "$url")
|
|
||||||
if [[ $res -ne 200 ]]; then
|
|
||||||
logger WARN "Failed to retrieve remote resource: '$url' ($res)"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
logger INFO "Resolved remote URL: '$url'"
|
|
||||||
|
|
||||||
# test dl url
|
|
||||||
local dl_url="$(parse_dl_url)"
|
|
||||||
local res=$(get_response_code "$dl_url")
|
|
||||||
if [[ $res -ne 200 ]]; then
|
|
||||||
logger WARN "Remote resource unavailable: '$dl_url' ($res)"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
logger INFO "Resolved download URL: '$dl_url'"
|
|
||||||
|
|
||||||
local month=$(parse_dl_url_date "$dl_url")
|
|
||||||
local base_file="${state_path}/${month}.csv.gz"
|
|
||||||
local extracted_file="${state_path}/${month}.csv"
|
|
||||||
|
|
||||||
# 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"
|
||||||
fetch "$dl_url"
|
check_remote "$url"
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
# local needs update
|
|
||||||
local last_month=$(< "$month_file")
|
|
||||||
if [[ $last_month != "$month" ]]; then
|
|
||||||
logger WARN "Local stub '$last_month' does not match remote stub '$month'"
|
|
||||||
fetch "$dl_url"
|
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# if stub is same date, abort
|
# if stub is same date, abort
|
||||||
logger INFO "Local stub '$last_month' is identical to remote, skipping"
|
local last_month=$(< "$month_file")
|
||||||
|
if [[ $last_month == "$this_month" ]]; then
|
||||||
|
logger INFO "Local stub '$last_month' is identical to remote, skipping"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# local needs update
|
||||||
|
logger WARN "Local stub '$last_month' does not match '$this_month'"
|
||||||
|
check_remote "$url"
|
||||||
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
fetch_helpers(){
|
fetch_helpers(){
|
||||||
|
echo "# Checking helper files"
|
||||||
fetch_a2s
|
fetch_a2s
|
||||||
fetch_dzq
|
fetch_dzq
|
||||||
fetch_km_helper
|
fetch_km_helper
|
||||||
@@ -981,6 +990,7 @@ 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
|
||||||
@@ -1007,12 +1017,14 @@ varcheck(){
|
|||||||
fi
|
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"
|
||||||
@@ -1025,6 +1037,7 @@ 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
|
||||||
@@ -1058,6 +1071,7 @@ 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
|
||||||
@@ -1071,18 +1085,48 @@ 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 > >(pdialog "Checking helper files")
|
fetch_helpers
|
||||||
varcheck
|
varcheck
|
||||||
source "$config_file"
|
source "$config_file"
|
||||||
lock
|
lock
|
||||||
@@ -1091,14 +1135,12 @@ 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(){
|
||||||
@@ -1200,8 +1242,15 @@ main(){
|
|||||||
|
|
||||||
set_im_module
|
set_im_module
|
||||||
|
|
||||||
printf "Initializing setup...\n"
|
printf "Checking dependencies...\n"
|
||||||
initial_setup
|
setup_dirs
|
||||||
|
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.13 "$ui_helper" "--init-ui" "$version" "$is_steam_deck"
|
||||||
|
|||||||
+12
-3
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
version="6.0.1"
|
version="6.0.5"
|
||||||
|
|
||||||
#CONSTANTS
|
#CONSTANTS
|
||||||
aid=221100
|
aid=221100
|
||||||
@@ -929,13 +929,19 @@ query_defunct(){
|
|||||||
<<< "$result" awk '{print $2}'
|
<<< "$result" awk '{print $2}'
|
||||||
}
|
}
|
||||||
encode(){
|
encode(){
|
||||||
echo "$1" | md5sum | cut -c -8
|
echo -n "$1" | md5sum | cut -c -8
|
||||||
}
|
}
|
||||||
compare(){
|
compare(){
|
||||||
local modlist="$@"
|
local modlist="$@"
|
||||||
diff=$(comm -23 <(<<< "$modlist" sort -u) <(installed_mods | sort))
|
diff=$(comm -23 <(<<< "$modlist" sort -u) <(installed_mods | sort))
|
||||||
echo "$diff"
|
echo "$diff"
|
||||||
}
|
}
|
||||||
|
unlink_all(){
|
||||||
|
readarray -t links < <(find "$game_dir" -maxdepth 1 -mindepth 1 -type l)
|
||||||
|
for link in "${links[@]}"; do
|
||||||
|
unlink "$link"
|
||||||
|
done
|
||||||
|
}
|
||||||
legacy_symlinks(){
|
legacy_symlinks(){
|
||||||
logger INFO "Removing legacy symlinks"
|
logger INFO "Removing legacy symlinks"
|
||||||
|
|
||||||
@@ -1026,7 +1032,10 @@ update_symlinks(){
|
|||||||
if [[ ! -d "$workshop_dir" ]]; then
|
if [[ ! -d "$workshop_dir" ]]; then
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
legacy_symlinks
|
if [[ ! -d "$game_dir" ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
unlink_all
|
||||||
symlinks
|
symlinks
|
||||||
}
|
}
|
||||||
try_fallback(){
|
try_fallback(){
|
||||||
|
|||||||
+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:
|
||||||
return e
|
raise 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
|
||||||
|
|||||||
+151
-264
@@ -19,7 +19,7 @@ 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 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,12 +32,16 @@ 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")
|
||||||
from gi.repository import Gtk, GLib, Gdk, GObject, Pango # noqa E402
|
gi.require_version("GLibUnix", "2.0")
|
||||||
|
from gi.repository import Gtk, GLib, GLibUnix, Gdk, GObject, Pango # noqa E402
|
||||||
|
|
||||||
# https://bugzilla.gnome.org/show_bug.cgi?id=708676
|
# https://bugzilla.gnome.org/show_bug.cgi?id=708676
|
||||||
warnings.filterwarnings("ignore", ".*g_value_get_int", Warning)
|
warnings.filterwarnings("ignore", ".*g_value_get_int", Warning)
|
||||||
@@ -850,9 +854,7 @@ def call_out(command: str, *args: str) -> subprocess.CompletedProcess:
|
|||||||
arg_ar = []
|
arg_ar = []
|
||||||
for i in args:
|
for i in args:
|
||||||
arg_ar.append(i)
|
arg_ar.append(i)
|
||||||
logger.info(
|
logger.info(f"Context '{name}' calling subprocess '{command}' with args '{arg_ar}'")
|
||||||
f"Context '{name}' calling subprocess '{command}' with args '{arg_ar}'"
|
|
||||||
)
|
|
||||||
proc = subprocess.run(
|
proc = subprocess.run(
|
||||||
["/usr/bin/env", "bash", funcs, command] + arg_ar,
|
["/usr/bin/env", "bash", funcs, command] + arg_ar,
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
@@ -878,9 +880,7 @@ def spawn_dialog(msg: str, mode: Popup) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def process_shell_return_code(
|
def process_shell_return_code(msg: str, code: int, original_input: RowType) -> None:
|
||||||
msg: str, code: int, original_input: RowType
|
|
||||||
) -> None:
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Processing return code '{code}' for the input "
|
f"Processing return code '{code}' for the input "
|
||||||
f"'{original_input}', returned message '{msg}'"
|
f"'{original_input}', returned message '{msg}'"
|
||||||
@@ -907,9 +907,7 @@ def process_shell_return_code(
|
|||||||
spawn_dialog("Invalid Steam API key, reverting", Popup.NOTIFY)
|
spawn_dialog("Invalid Steam API key, reverting", Popup.NOTIFY)
|
||||||
App.notebook.settings.revert(Preferences.STEAM)
|
App.notebook.settings.revert(Preferences.STEAM)
|
||||||
case 79: # failed settings update (bm)
|
case 79: # failed settings update (bm)
|
||||||
spawn_dialog(
|
spawn_dialog("Invalid Battlemetrics API key, reverting", Popup.NOTIFY)
|
||||||
"Invalid Battlemetrics API key, reverting", Popup.NOTIFY
|
|
||||||
)
|
|
||||||
App.notebook.settings.revert(Preferences.BM)
|
App.notebook.settings.revert(Preferences.BM)
|
||||||
case 80: # pop toast after successful settings change
|
case 80: # pop toast after successful settings change
|
||||||
config_vals.clear()
|
config_vals.clear()
|
||||||
@@ -975,9 +973,7 @@ def call_on_thread(
|
|||||||
if state:
|
if state:
|
||||||
wait_dialog = GenericDialog(msg, Popup.WAIT)
|
wait_dialog = GenericDialog(msg, Popup.WAIT)
|
||||||
wait_dialog.show_all()
|
wait_dialog.show_all()
|
||||||
thread = threading.Thread(
|
thread = threading.Thread(target=_background, args=(subproc, args, wait_dialog))
|
||||||
target=_background, args=(subproc, args, wait_dialog)
|
|
||||||
)
|
|
||||||
thread.start()
|
thread.start()
|
||||||
else:
|
else:
|
||||||
"""
|
"""
|
||||||
@@ -1002,18 +998,14 @@ def process_tree_option(choice: RowType) -> None:
|
|||||||
record = App.treeview.get_record_string()
|
record = App.treeview.get_record_string()
|
||||||
wait_msg = command.dict["wait_msg"]
|
wait_msg = command.dict["wait_msg"]
|
||||||
show_wait_dialog = True
|
show_wait_dialog = True
|
||||||
call_on_thread(
|
call_on_thread(show_wait_dialog, cmd_string, wait_msg, record, choice=choice)
|
||||||
show_wait_dialog, cmd_string, wait_msg, record, choice=choice
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# modlist highlight stale action
|
# modlist highlight stale action
|
||||||
if context == WindowContext.TABLE_MODS and command == RowType.HIGHLIGHT:
|
if context == WindowContext.TABLE_MODS and command == RowType.HIGHLIGHT:
|
||||||
wait_msg = command.dict["wait_msg"]
|
wait_msg = command.dict["wait_msg"]
|
||||||
show_wait_dialog = True
|
show_wait_dialog = True
|
||||||
call_on_thread(
|
call_on_thread(show_wait_dialog, cmd_string, wait_msg, "", choice=choice)
|
||||||
show_wait_dialog, cmd_string, wait_msg, "", choice=choice
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if command == RowType.CHANGELOG:
|
if command == RowType.CHANGELOG:
|
||||||
@@ -1027,13 +1019,7 @@ def process_tree_option(choice: RowType) -> None:
|
|||||||
return
|
return
|
||||||
|
|
||||||
record = str_to_record(record)
|
record = str_to_record(record)
|
||||||
thread_new_with_dialog(
|
prepare_connection(command, record)
|
||||||
App.treeview.prepare_connection,
|
|
||||||
parse_shell_output,
|
|
||||||
"Querying server",
|
|
||||||
command,
|
|
||||||
[record],
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
match command.dict["type"]:
|
match command.dict["type"]:
|
||||||
@@ -1061,48 +1047,50 @@ def parse_shell_output(proc: subprocess.CompletedProcess, row: RowType):
|
|||||||
process_shell_return_code(msg, proc.returncode, row)
|
process_shell_return_code(msg, proc.returncode, row)
|
||||||
|
|
||||||
|
|
||||||
def thread_new_with_dialog(
|
def prepare_connection(rowtype: RowType, record: Record) -> None:
|
||||||
func: Callable,
|
def background(rowtype: RowType, record: Record) -> None:
|
||||||
callback: Callable | None,
|
def cleanup() -> None:
|
||||||
msg: str,
|
App.treeview.wait_dialog.destroy()
|
||||||
row: RowType | None,
|
# NOTE: When using RowType.CONN_BY_IP, the gameport needs to be interpolated
|
||||||
args: list,
|
record.gameport = prereqs.gameport
|
||||||
) -> None:
|
addr = record_to_str(record)
|
||||||
"""
|
|
||||||
Pop a GenericDialog transient to App.treeview and
|
|
||||||
call a function on a thread, with optional callback.
|
|
||||||
Chiefly used for connection-related subprocesses.
|
|
||||||
|
|
||||||
After completion, the dialog is destroyed in the main event loop
|
if proceed is False:
|
||||||
and additional exception handling occurs.
|
spawn_dialog(msg, Popup.NOTIFY)
|
||||||
|
return
|
||||||
|
if msg != "":
|
||||||
|
res = spawn_dialog(msg, Popup.CONFIRM)
|
||||||
|
if res is False:
|
||||||
|
try_connect(addr, str(prereqs.appid), str(pefile_path), rowtype)
|
||||||
|
else:
|
||||||
|
try_connect(addr, str(prereqs.appid), str(pefile_path), rowtype)
|
||||||
|
|
||||||
This is intended as a bridge between legacy shell methods and the UI.
|
proceed, msg, pefile_path, prereqs = App.treeview.get_prereqs(record)
|
||||||
A more abstracted version of call_on_thread() for when extra threaded
|
GLib.idle_add(cleanup)
|
||||||
processing occurs before calls to shell subprocesses.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def background(*args):
|
msg = "Checking prerequisites"
|
||||||
|
App.treeview.dialog_show(msg)
|
||||||
|
thread = threading.Thread(target=background, args=(rowtype, record))
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
|
||||||
|
def try_connect(addr: str, appid: str, path: str, row: RowType) -> None:
|
||||||
|
def background(addr, appid, path):
|
||||||
def cleanup():
|
def cleanup():
|
||||||
App.treeview.dialog_hide()
|
App.treeview.dialog_hide()
|
||||||
if exception is not None:
|
parse_shell_output(proc, row)
|
||||||
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 = func(*args)
|
proc = call_out("try_connect", addr, appid, path)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
exception = e
|
logger.critical(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=(args))
|
thread = threading.Thread(target=background, args=(addr, appid, path))
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
|
|
||||||
@@ -1142,27 +1130,32 @@ 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:
|
||||||
def _prep(response: str) -> None:
|
try:
|
||||||
record = Servers.validate_ip(response)
|
record = Servers.validate_ip(response)
|
||||||
proc = App.treeview.prepare_connection(record)
|
except Exception as e:
|
||||||
return proc
|
spawn_dialog(str(e), Popup.NOTIFY)
|
||||||
|
return
|
||||||
thread_new_with_dialog(
|
prepare_connection(enum, record)
|
||||||
_prep, parse_shell_output, "Querying IP", enum, [response]
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
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:
|
||||||
record = Servers.query_bm_api(key, response)
|
def cleanup() -> None:
|
||||||
proc = App.treeview.prepare_connection(record)
|
App.treeview.dialog_hide()
|
||||||
return proc
|
prepare_connection(enum, record)
|
||||||
|
|
||||||
thread_new_with_dialog(
|
try:
|
||||||
_prep, parse_shell_output, "Querying API", enum, [key, response]
|
record = Servers.query_bm_api(key, response)
|
||||||
)
|
except Exception as e:
|
||||||
return
|
spawn_dialog(str(e), Popup.NOTIFY)
|
||||||
|
App.treeview.dialog_hide()
|
||||||
|
return
|
||||||
|
GLib.idle_add(cleanup)
|
||||||
|
|
||||||
|
msg = "Validating ID"
|
||||||
|
App.treeview.dialog_show(msg)
|
||||||
|
thread = threading.Thread(target=_prep, args=(key, response))
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
|
||||||
def process_user_input(enum: RowType) -> None:
|
def process_user_input(enum: RowType) -> None:
|
||||||
@@ -1173,9 +1166,7 @@ def process_user_input(enum: RowType) -> None:
|
|||||||
if enum == RowType.CONN_BY_ID:
|
if enum == RowType.CONN_BY_ID:
|
||||||
key = query_config("api_key")[0]
|
key = query_config("api_key")[0]
|
||||||
if len(key) == 0:
|
if len(key) == 0:
|
||||||
spawn_dialog(
|
spawn_dialog("No Battlemetrics API key is set; see Options", Popup.NOTIFY)
|
||||||
"No Battlemetrics API key is set; see Options", Popup.NOTIFY
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
user_entry = EntryDialog(prompt, Popup.ENTRY, link_label)
|
user_entry = EntryDialog(prompt, Popup.ENTRY, link_label)
|
||||||
@@ -1196,9 +1187,7 @@ def process_user_input(enum: RowType) -> None:
|
|||||||
|
|
||||||
show_wait_dialog = True
|
show_wait_dialog = True
|
||||||
wait_msg = "Working"
|
wait_msg = "Working"
|
||||||
call_on_thread(
|
call_on_thread(show_wait_dialog, cmd_string, wait_msg, response, choice=enum)
|
||||||
show_wait_dialog, cmd_string, wait_msg, response, choice=enum
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
@@ -1286,9 +1275,7 @@ class OuterWindow(Gtk.Window):
|
|||||||
logger.info(f"Using default window size {w},{h}")
|
logger.info(f"Using default window size {w},{h}")
|
||||||
self.set_default_size(w, h)
|
self.set_default_size(w, h)
|
||||||
|
|
||||||
def _on_delete_event(
|
def _on_delete_event(self, window: "OuterWindow", event: Gdk.EventKey) -> None:
|
||||||
self, window: "OuterWindow", event: Gdk.EventKey
|
|
||||||
) -> None:
|
|
||||||
self.halt_proc_and_quit()
|
self.halt_proc_and_quit()
|
||||||
|
|
||||||
def halt_proc_and_quit(self) -> None:
|
def halt_proc_and_quit(self) -> None:
|
||||||
@@ -1355,8 +1342,7 @@ class RightPanel(Gtk.Box):
|
|||||||
self.pack_start(self.filters_vbox, False, False, 0)
|
self.pack_start(self.filters_vbox, False, False, 0)
|
||||||
|
|
||||||
debug_tooltip = (
|
debug_tooltip = (
|
||||||
"Used to perform a dry run without\n"
|
"Used to perform a dry run without\n" "actually connecting to a server"
|
||||||
"actually connecting to a server"
|
|
||||||
)
|
)
|
||||||
ping_tooltip = (
|
ping_tooltip = (
|
||||||
"Refresh the ping for visible servers.\n"
|
"Refresh the ping for visible servers.\n"
|
||||||
@@ -1425,8 +1411,7 @@ class RightPanel(Gtk.Box):
|
|||||||
rows = ModelManager.get_filtered()
|
rows = ModelManager.get_filtered()
|
||||||
with ThreadPoolExecutor(100) as executor:
|
with ThreadPoolExecutor(100) as executor:
|
||||||
futures = [
|
futures = [
|
||||||
executor.submit(Servers.ping, i, row)
|
executor.submit(Servers.ping, i, row) for i, row in enumerate(rows)
|
||||||
for i, row in enumerate(rows)
|
|
||||||
]
|
]
|
||||||
wait(futures)
|
wait(futures)
|
||||||
for future in futures:
|
for future in futures:
|
||||||
@@ -1802,9 +1787,7 @@ class ModelManagerSingleton:
|
|||||||
|
|
||||||
|
|
||||||
class TreeView(Gtk.TreeView):
|
class TreeView(Gtk.TreeView):
|
||||||
__gsignals__ = {
|
__gsignals__ = {"on_distcalc_started": (GObject.SignalFlags.RUN_FIRST, None, ())}
|
||||||
"on_distcalc_started": (GObject.SignalFlags.RUN_FIRST, None, ())
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -1923,9 +1906,7 @@ class TreeView(Gtk.TreeView):
|
|||||||
if self.current_proc and self.current_proc.is_alive():
|
if self.current_proc and self.current_proc.is_alive():
|
||||||
self.current_proc.terminate()
|
self.current_proc.terminate()
|
||||||
|
|
||||||
def _delete_note(
|
def _delete_note(self, button: Gtk.Button, user_entry: Gtk.Box, addr: str) -> None:
|
||||||
self, button: Gtk.Button, user_entry: Gtk.Box, addr: str
|
|
||||||
) -> None:
|
|
||||||
box = button.get_parent()
|
box = button.get_parent()
|
||||||
dialog = box.get_parent()
|
dialog = box.get_parent()
|
||||||
try:
|
try:
|
||||||
@@ -2017,9 +1998,7 @@ class TreeView(Gtk.TreeView):
|
|||||||
model.remove(it)
|
model.remove(it)
|
||||||
|
|
||||||
block_signals()
|
block_signals()
|
||||||
thread = threading.Thread(
|
thread = threading.Thread(target=ModelManager.resync_model, args=(addr, qport))
|
||||||
target=ModelManager.resync_model, args=(addr, qport)
|
|
||||||
)
|
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
def remove_from_history(self) -> None:
|
def remove_from_history(self) -> None:
|
||||||
@@ -2043,16 +2022,12 @@ class TreeView(Gtk.TreeView):
|
|||||||
if hasattr(TreeView, menu_item.action):
|
if hasattr(TreeView, menu_item.action):
|
||||||
func = getattr(TreeView, menu_item.action)
|
func = getattr(TreeView, menu_item.action)
|
||||||
msg = (
|
msg = (
|
||||||
f"User clicked context menu '{menu_item.get_label()}', "
|
f"User clicked context menu '{menu_item.get_label()}', " f"calls {func}"
|
||||||
f"calls {func}"
|
|
||||||
)
|
)
|
||||||
logger.info(msg)
|
logger.info(msg)
|
||||||
func(self)
|
func(self)
|
||||||
else:
|
else:
|
||||||
msg = (
|
msg = f"Context menu function for '{menu_item.action}' " f"does not exist"
|
||||||
f"Context menu function for '{menu_item.action}' "
|
|
||||||
f"does not exist"
|
|
||||||
)
|
|
||||||
u_msg = (
|
u_msg = (
|
||||||
f"Something went wrong when accessing the method "
|
f"Something went wrong when accessing the method "
|
||||||
f"'{menu_item.action}'"
|
f"'{menu_item.action}'"
|
||||||
@@ -2093,9 +2068,7 @@ class TreeView(Gtk.TreeView):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _on_button_release(
|
def _on_button_release(self, widget: Gtk.Widget, event: Gdk.EventButton) -> None:
|
||||||
self, widget: Gtk.Widget, event: Gdk.EventButton
|
|
||||||
) -> None:
|
|
||||||
if event.type is Gdk.EventType.BUTTON_RELEASE and event.button != 3:
|
if event.type is Gdk.EventType.BUTTON_RELEASE and event.button != 3:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2179,9 +2152,7 @@ class TreeView(Gtk.TreeView):
|
|||||||
if event.type is Gdk.EventType.KEY_PRESS and event.keyval is Gdk.KEY_l:
|
if event.type is Gdk.EventType.KEY_PRESS and event.keyval is Gdk.KEY_l:
|
||||||
if self.is_selection_empty():
|
if self.is_selection_empty():
|
||||||
return
|
return
|
||||||
self.menu.popup_at_widget(
|
self.menu.popup_at_widget(widget, Gdk.Gravity.CENTER, Gdk.Gravity.WEST)
|
||||||
widget, Gdk.Gravity.CENTER, Gdk.Gravity.WEST
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
self.menu.popup_at_pointer(event)
|
self.menu.popup_at_pointer(event)
|
||||||
self.menu.select_first(False)
|
self.menu.select_first(False)
|
||||||
@@ -2195,9 +2166,7 @@ class TreeView(Gtk.TreeView):
|
|||||||
return None
|
return None
|
||||||
call_out("start_cooldown", "", "")
|
call_out("start_cooldown", "", "")
|
||||||
|
|
||||||
thread = threading.Thread(
|
thread = threading.Thread(target=self._background_player_count, args=())
|
||||||
target=self._background_player_count, args=()
|
|
||||||
)
|
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
def get_current_iter(self) -> Gtk.TreeIter | None:
|
def get_current_iter(self) -> Gtk.TreeIter | None:
|
||||||
@@ -2212,9 +2181,7 @@ class TreeView(Gtk.TreeView):
|
|||||||
|
|
||||||
context = App.treeview.get_subpage_label()
|
context = App.treeview.get_subpage_label()
|
||||||
row_sel = self.get_value_at_index(0)
|
row_sel = self.get_value_at_index(0)
|
||||||
logger.info(
|
logger.info(f"Tree selection for context '{context}' changed to '{row_sel}'")
|
||||||
f"Tree selection for context '{context}' changed to '{row_sel}'"
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.current_proc and self.current_proc.is_alive():
|
if self.current_proc and self.current_proc.is_alive():
|
||||||
self.current_proc.terminate()
|
self.current_proc.terminate()
|
||||||
@@ -2272,9 +2239,7 @@ class TreeView(Gtk.TreeView):
|
|||||||
self.set_cursor(path)
|
self.set_cursor(path)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _on_keypress(
|
def _on_keypress(self, treeview: Gtk.TreeView, event: Gdk.EventKey) -> bool | None:
|
||||||
self, treeview: Gtk.TreeView, event: Gdk.EventKey
|
|
||||||
) -> bool | None:
|
|
||||||
keyname = Gdk.keyval_name(event.keyval)
|
keyname = Gdk.keyval_name(event.keyval)
|
||||||
grid = App.grid
|
grid = App.grid
|
||||||
if event.state is Gdk.ModifierType.CONTROL_MASK:
|
if event.state is Gdk.ModifierType.CONTROL_MASK:
|
||||||
@@ -2329,9 +2294,7 @@ class TreeView(Gtk.TreeView):
|
|||||||
return False
|
return False
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _on_key_release(
|
def _on_key_release(self, treeview: Gtk.TreeView, event: Gdk.EventKey) -> None:
|
||||||
self, treeview: Gtk.TreeView, event: Gdk.EventKey
|
|
||||||
) -> None:
|
|
||||||
"""
|
"""
|
||||||
Suppresses spamming on keydown
|
Suppresses spamming on keydown
|
||||||
"""
|
"""
|
||||||
@@ -2446,10 +2409,7 @@ class TreeView(Gtk.TreeView):
|
|||||||
params = Servers.params
|
params = Servers.params
|
||||||
serv = []
|
serv = []
|
||||||
with ThreadPoolExecutor() as executor:
|
with ThreadPoolExecutor() as executor:
|
||||||
futures = [
|
futures = [executor.submit(job, key, APPID_DAYZ, param) for param in params]
|
||||||
executor.submit(job, key, APPID_DAYZ, param)
|
|
||||||
for param in params
|
|
||||||
]
|
|
||||||
wait(futures)
|
wait(futures)
|
||||||
for future in futures:
|
for future in futures:
|
||||||
res = future.result()
|
res = future.result()
|
||||||
@@ -2471,10 +2431,7 @@ class TreeView(Gtk.TreeView):
|
|||||||
|
|
||||||
def _dump_lan(self, port: int) -> list | None:
|
def _dump_lan(self, port: int) -> list | None:
|
||||||
with ThreadPoolExecutor() as executor:
|
with ThreadPoolExecutor() as executor:
|
||||||
futures = [
|
futures = [executor.submit(Servers.test_ip, i, port) for i in range(1, 256)]
|
||||||
executor.submit(Servers.test_ip, i, port)
|
|
||||||
for i in range(1, 256)
|
|
||||||
]
|
|
||||||
wait(futures)
|
wait(futures)
|
||||||
servers = []
|
servers = []
|
||||||
for future in futures:
|
for future in futures:
|
||||||
@@ -2493,6 +2450,8 @@ 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(
|
||||||
@@ -2597,9 +2556,7 @@ class TreeView(Gtk.TreeView):
|
|||||||
ModelManager.set_store(App.treeview.get_model())
|
ModelManager.set_store(App.treeview.get_model())
|
||||||
App.treeview.set_model(None)
|
App.treeview.set_model(None)
|
||||||
|
|
||||||
thread = threading.Thread(
|
thread = threading.Thread(target=ModelManager.filter, args=(mode, *args))
|
||||||
target=ModelManager.filter, args=(mode, *args)
|
|
||||||
)
|
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
def _background_quad(self, dialog: "GenericDialog", mode: RowType) -> None:
|
def _background_quad(self, dialog: "GenericDialog", mode: RowType) -> None:
|
||||||
@@ -2758,13 +2715,9 @@ class TreeView(Gtk.TreeView):
|
|||||||
self.initialize_columns()
|
self.initialize_columns()
|
||||||
self.set_selection_mode(Gtk.SelectionMode.SINGLE)
|
self.set_selection_mode(Gtk.SelectionMode.SINGLE)
|
||||||
|
|
||||||
self.wait_dialog = GenericDialog(
|
self.wait_dialog = GenericDialog("Fetching server metadata", Popup.WAIT)
|
||||||
"Fetching server metadata", Popup.WAIT
|
|
||||||
)
|
|
||||||
self.wait_dialog.show_all()
|
self.wait_dialog.show_all()
|
||||||
thread = threading.Thread(
|
thread = threading.Thread(target=self._query_servers, args=(mode, port))
|
||||||
target=self._query_servers, args=(mode, port)
|
|
||||||
)
|
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
def _format_float(
|
def _format_float(
|
||||||
@@ -2943,9 +2896,7 @@ class TreeView(Gtk.TreeView):
|
|||||||
def get_view(self):
|
def get_view(self):
|
||||||
return self.view
|
return self.view
|
||||||
|
|
||||||
def prepare_connection(
|
def get_prereqs(self, record: Record) -> tuple[bool, str, str | None, "Prereqs"]:
|
||||||
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
|
||||||
"""
|
"""
|
||||||
@@ -2953,50 +2904,39 @@ 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"
|
||||||
spawn_dialog(msg, Popup.NOTIFY)
|
return (False, msg, None, prereqs)
|
||||||
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(
|
logger.critical("Config file has no value set for 'default_steam_path'")
|
||||||
"Config file has no value set for 'default_steam_path'"
|
|
||||||
)
|
|
||||||
msg = "Local Steam installation is not set, possibly malformed config file."
|
msg = "Local Steam installation is not set, possibly malformed config file."
|
||||||
spawn_dialog(msg, Popup.NOTIFY)
|
return (False, msg, None, prereqs)
|
||||||
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(
|
logger.critical(f"'{prereqs.appid}' not found in user's libraryfolders")
|
||||||
f"'{prereqs.appid}' not found in user's libraryfolders"
|
|
||||||
)
|
|
||||||
msg = (
|
msg = (
|
||||||
f"This server is running {build}. You can install "
|
f"This server is running {build}. You can install "
|
||||||
f"{build} by searching for it in your Steam library. "
|
f"{build} by searching for it in your Steam library. "
|
||||||
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."
|
||||||
)
|
)
|
||||||
spawn_dialog(msg, Popup.NOTIFY)
|
return (False, msg, None, prereqs)
|
||||||
return None
|
|
||||||
except AppMovedError:
|
except AppMovedError:
|
||||||
logger.critical(
|
logger.critical(f"Library folder synch error for '{prereqs.appid}'")
|
||||||
f"Library folder synch error for '{prereqs.appid}'"
|
|
||||||
)
|
|
||||||
msg = (
|
msg = (
|
||||||
f"Steam is reporting that {build} is installed at a non-existent location. "
|
f"Steam is reporting that {build} is installed at a non-existent location. "
|
||||||
f"If you recently installed {build} or moved it to a different drive, "
|
f"If you recently installed {build} or moved it to a different drive, "
|
||||||
"restart Steam to allow these changes to synchronize, then try again."
|
"restart Steam to allow these changes to synchronize, then try again."
|
||||||
)
|
)
|
||||||
spawn_dialog(msg, Popup.NOTIFY)
|
return (False, msg, None, prereqs)
|
||||||
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."
|
||||||
spawn_dialog(msg, Popup.NOTIFY)
|
return (False, msg, None, prereqs)
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
local_vers = PeFile.get_dayz_version(pefile_path)
|
local_vers = PeFile.get_dayz_version(pefile_path)
|
||||||
@@ -3008,7 +2948,7 @@ class TreeView(Gtk.TreeView):
|
|||||||
local_vers = None
|
local_vers = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
remote_vers = PeFile.dayz_version_from_str(prereqs.version)
|
remote_vers = PeFile.dayz_version_from_str(prereqs)
|
||||||
except Exception:
|
except Exception:
|
||||||
remote_vers = None
|
remote_vers = None
|
||||||
|
|
||||||
@@ -3021,18 +2961,15 @@ class TreeView(Gtk.TreeView):
|
|||||||
f"This server is running a newer build ({prereqs.version}) of {build} than "
|
f"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?"
|
||||||
)
|
)
|
||||||
res = spawn_dialog(msg, Popup.CONFIRM)
|
return (True, msg, pefile_path, prereqs)
|
||||||
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?"
|
||||||
)
|
)
|
||||||
res = spawn_dialog(msg, Popup.CONFIRM)
|
return (True, msg, pefile_path, prereqs)
|
||||||
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:
|
||||||
@@ -3040,19 +2977,9 @@ 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?"
|
||||||
)
|
)
|
||||||
res = spawn_dialog(msg, Popup.CONFIRM)
|
return (True, msg, pefile_path, prereqs)
|
||||||
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
|
||||||
@@ -3122,13 +3049,7 @@ class TreeView(Gtk.TreeView):
|
|||||||
record = self.get_record()
|
record = self.get_record()
|
||||||
if record is None:
|
if record is None:
|
||||||
return
|
return
|
||||||
thread_new_with_dialog(
|
prepare_connection(None, record)
|
||||||
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 +3134,7 @@ class GenericDialog(Gtk.MessageDialog):
|
|||||||
self.outer.set_margin_end(30)
|
self.outer.set_margin_end(30)
|
||||||
|
|
||||||
def _on_dialog_delete(
|
def _on_dialog_delete(
|
||||||
self, response_id: Gtk.ResponseType, event: Gdk.Event
|
self, response_id: Gtk.ResponseType, event: Gdk.Event
|
||||||
) -> Literal[True]:
|
) -> Literal[True]:
|
||||||
"""
|
"""
|
||||||
Prevent manual dialog destruction
|
Prevent manual dialog destruction
|
||||||
@@ -3292,9 +3213,7 @@ class LanDialog(Gtk.MessageDialog):
|
|||||||
self.connect("delete-event", self.restore_context)
|
self.connect("delete-event", self.restore_context)
|
||||||
|
|
||||||
self.entry.connect("insert-text", self._on_text_typed)
|
self.entry.connect("insert-text", self._on_text_typed)
|
||||||
self.entry.get_property("buffer").connect(
|
self.entry.get_property("buffer").connect("deleted-text", self._on_text_deleted)
|
||||||
"deleted-text", self._on_text_deleted
|
|
||||||
)
|
|
||||||
|
|
||||||
def _validate(self, text: str) -> None:
|
def _validate(self, text: str) -> None:
|
||||||
if self._is_invalid(text):
|
if self._is_invalid(text):
|
||||||
@@ -3410,9 +3329,7 @@ class DetailsDialog(GenericDialog):
|
|||||||
for i, column_title in enumerate(["Item", "Details"]):
|
for i, column_title in enumerate(["Item", "Details"]):
|
||||||
renderer = Gtk.CellRendererText(xalign=0)
|
renderer = Gtk.CellRendererText(xalign=0)
|
||||||
if i == 0:
|
if i == 0:
|
||||||
column = Gtk.TreeViewColumn(
|
column = Gtk.TreeViewColumn(column_title, renderer, text=i, weight=2)
|
||||||
column_title, renderer, text=i, weight=2
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
column = Gtk.TreeViewColumn(column_title, renderer, text=i)
|
column = Gtk.TreeViewColumn(column_title, renderer, text=i)
|
||||||
column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
|
column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
|
||||||
@@ -3428,12 +3345,8 @@ class DetailsDialog(GenericDialog):
|
|||||||
scrollable_message = Gtk.ScrolledWindow()
|
scrollable_message = Gtk.ScrolledWindow()
|
||||||
desc = Gtk.Label(label="Server message", valign=Gtk.Align.START)
|
desc = Gtk.Label(label="Server message", valign=Gtk.Align.START)
|
||||||
add_class(desc, "details-heading")
|
add_class(desc, "details-heading")
|
||||||
box = Gtk.Box(
|
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, halign=Gtk.Align.CENTER)
|
||||||
orientation=Gtk.Orientation.VERTICAL, halign=Gtk.Align.CENTER
|
self.description = Gtk.Label(justify=Gtk.Justification.CENTER, wrap=True)
|
||||||
)
|
|
||||||
self.description = Gtk.Label(
|
|
||||||
justify=Gtk.Justification.CENTER, wrap=True
|
|
||||||
)
|
|
||||||
sep = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
|
sep = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
|
||||||
sep.set_margin_bottom(10)
|
sep.set_margin_bottom(10)
|
||||||
for el in desc, sep, self.description:
|
for el in desc, sep, self.description:
|
||||||
@@ -3471,9 +3384,7 @@ class DetailsDialog(GenericDialog):
|
|||||||
self.run()
|
self.run()
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
def _background(
|
def _background(self, dialog: "GenericDialog", ip: str, qport: int) -> None:
|
||||||
self, dialog: "GenericDialog", ip: str, qport: int
|
|
||||||
) -> None:
|
|
||||||
response = Servers.details(self.ip, self.qport)
|
response = Servers.details(self.ip, self.qport)
|
||||||
if response.success:
|
if response.success:
|
||||||
for row in response.data:
|
for row in response.data:
|
||||||
@@ -3530,9 +3441,7 @@ class ModDialog(GenericDialog):
|
|||||||
|
|
||||||
wait_dialog = GenericDialog("Fetching modlist", Popup.WAIT)
|
wait_dialog = GenericDialog("Fetching modlist", Popup.WAIT)
|
||||||
wait_dialog.show_all()
|
wait_dialog.show_all()
|
||||||
thread = threading.Thread(
|
thread = threading.Thread(target=self._background, args=(wait_dialog, record))
|
||||||
target=self._background, args=(wait_dialog, record)
|
|
||||||
)
|
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
def _background(self, dialog: "GenericDialog", record: str) -> None:
|
def _background(self, dialog: "GenericDialog", record: str) -> None:
|
||||||
@@ -3557,9 +3466,7 @@ class ModDialog(GenericDialog):
|
|||||||
self.view.set_model(modlist_store)
|
self.view.set_model(modlist_store)
|
||||||
GLib.idle_add(_load)
|
GLib.idle_add(_load)
|
||||||
|
|
||||||
def _parse_modlist_rows(
|
def _parse_modlist_rows(self, data: subprocess.CompletedProcess) -> bool | int:
|
||||||
self, data: subprocess.CompletedProcess
|
|
||||||
) -> bool | int:
|
|
||||||
lines = data.stdout.splitlines()
|
lines = data.stdout.splitlines()
|
||||||
hits = len(lines)
|
hits = len(lines)
|
||||||
reader = csv.reader(lines, delimiter=delimiter)
|
reader = csv.reader(lines, delimiter=delimiter)
|
||||||
@@ -3589,9 +3496,7 @@ class ModDialog(GenericDialog):
|
|||||||
|
|
||||||
|
|
||||||
class LinkDialog(GenericDialog):
|
class LinkDialog(GenericDialog):
|
||||||
def __init__(
|
def __init__(self, text: str, link: str | None, command: RowType, uid: str = ""):
|
||||||
self, text: str, link: str | None, command: RowType, uid: str = ""
|
|
||||||
):
|
|
||||||
super().__init__(text, Popup.NOTIFY)
|
super().__init__(text, Popup.NOTIFY)
|
||||||
|
|
||||||
text = textwrap.dedent(text)
|
text = textwrap.dedent(text)
|
||||||
@@ -3809,7 +3714,12 @@ class Options(Gtk.Box):
|
|||||||
client_store = Gtk.ListStore(str, str)
|
client_store = Gtk.ListStore(str, str)
|
||||||
client_store.append(["steam", "steam"])
|
client_store.append(["steam", "steam"])
|
||||||
client_store.append(["flatpak", "flatpak run com.valvesoftware.Steam"])
|
client_store.append(["flatpak", "flatpak run com.valvesoftware.Steam"])
|
||||||
client_store.append(["flatpak (container)", "flatpak-spawn --host flatpak run com.valvesoftware.Steam"])
|
client_store.append(
|
||||||
|
[
|
||||||
|
"flatpak (container)",
|
||||||
|
"flatpak-spawn --host flatpak run com.valvesoftware.Steam",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
renderer_text = Gtk.CellRendererText(ellipsize=Pango.EllipsizeMode.END)
|
renderer_text = Gtk.CellRendererText(ellipsize=Pango.EllipsizeMode.END)
|
||||||
self.client_combo = Gtk.ComboBox.new_with_model(client_store)
|
self.client_combo = Gtk.ComboBox.new_with_model(client_store)
|
||||||
@@ -3833,8 +3743,7 @@ class Options(Gtk.Box):
|
|||||||
self.force_button.set_sensitive(False)
|
self.force_button.set_sensitive(False)
|
||||||
|
|
||||||
msg = (
|
msg = (
|
||||||
"Manual: prompt to subscribe to mods in Steam. "
|
"Manual: prompt to subscribe to mods in Steam. " "Auto: unmanned downloads."
|
||||||
"Auto: unmanned downloads."
|
|
||||||
)
|
)
|
||||||
eb = InfoEventBox(msg)
|
eb = InfoEventBox(msg)
|
||||||
|
|
||||||
@@ -3893,9 +3802,7 @@ class Options(Gtk.Box):
|
|||||||
self.make_frame(mods_grid, "Mods"),
|
self.make_frame(mods_grid, "Mods"),
|
||||||
self.make_frame(version_grid, "Version"),
|
self.make_frame(version_grid, "Version"),
|
||||||
]:
|
]:
|
||||||
grid.attach(
|
grid.attach(frame, col, row, self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT)
|
||||||
frame, col, row, self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT
|
|
||||||
)
|
|
||||||
row += 1
|
row += 1
|
||||||
|
|
||||||
self.scrollable = Gtk.ScrolledWindow(vexpand=True)
|
self.scrollable = Gtk.ScrolledWindow(vexpand=True)
|
||||||
@@ -3941,9 +3848,7 @@ class Options(Gtk.Box):
|
|||||||
|
|
||||||
return box
|
return box
|
||||||
|
|
||||||
def _on_field_activated(
|
def _on_field_activated(self, entry: Gtk.Entry, context: Preferences) -> None:
|
||||||
self, entry: Gtk.Entry, context: Preferences
|
|
||||||
) -> None:
|
|
||||||
text = entry.get_text()
|
text = entry.get_text()
|
||||||
button = entry.sibling
|
button = entry.sibling
|
||||||
if not self._is_valid_text(text, context):
|
if not self._is_valid_text(text, context):
|
||||||
@@ -3964,9 +3869,7 @@ class Options(Gtk.Box):
|
|||||||
for record in rows:
|
for record in rows:
|
||||||
col = 1
|
col = 1
|
||||||
for el in record:
|
for el in record:
|
||||||
grid.attach(
|
grid.attach(el, col, row, self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT)
|
||||||
el, col, row, self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT
|
|
||||||
)
|
|
||||||
col += 1
|
col += 1
|
||||||
row += 1
|
row += 1
|
||||||
return grid
|
return grid
|
||||||
@@ -4019,9 +3922,7 @@ class Options(Gtk.Box):
|
|||||||
return
|
return
|
||||||
process_toggle(RowType.TGL_BRANCH)
|
process_toggle(RowType.TGL_BRANCH)
|
||||||
|
|
||||||
def _on_radio_toggled(
|
def _on_radio_toggled(self, button: Gtk.RadioButton, context: Preferences) -> None:
|
||||||
self, button: Gtk.RadioButton, context: Preferences
|
|
||||||
) -> None:
|
|
||||||
if App.treeview.subpage is not RowType.OPTIONS:
|
if App.treeview.subpage is not RowType.OPTIONS:
|
||||||
return
|
return
|
||||||
match context:
|
match context:
|
||||||
@@ -4165,18 +4066,14 @@ class Options(Gtk.Box):
|
|||||||
field[1].get_children()[1].set_sensitive(False)
|
field[1].get_children()[1].set_sensitive(False)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
pe_file_path = PeFile.get_pefile_path(
|
pe_file_path = PeFile.get_pefile_path(default_steam_path, APPID_DAYZ)
|
||||||
default_steam_path, APPID_DAYZ
|
|
||||||
)
|
|
||||||
vers = PeFile.get_dayz_version(pe_file_path)
|
vers = PeFile.get_dayz_version(pe_file_path)
|
||||||
dayz_version = PeFile.dayz_version_to_str(vers)
|
dayz_version = PeFile.dayz_version_to_str(vers)
|
||||||
except Exception:
|
except Exception:
|
||||||
dayz_version = "-"
|
dayz_version = "-"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
exp_file_path = PeFile.get_pefile_path(
|
exp_file_path = PeFile.get_pefile_path(default_steam_path, APPID_DAYZ_EXP)
|
||||||
default_steam_path, APPID_DAYZ_EXP
|
|
||||||
)
|
|
||||||
vers = PeFile.get_dayz_version(exp_file_path)
|
vers = PeFile.get_dayz_version(exp_file_path)
|
||||||
dayz_exp_version = PeFile.dayz_version_to_str(vers)
|
dayz_exp_version = PeFile.dayz_version_to_str(vers)
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -4285,9 +4182,7 @@ class KeybindingsDialog(Gtk.Box):
|
|||||||
if not sep:
|
if not sep:
|
||||||
grid.attach(desc, col, row, w, h)
|
grid.attach(desc, col, row, w, h)
|
||||||
else:
|
else:
|
||||||
grid.attach_next_to(
|
grid.attach_next_to(desc, sep, Gtk.PositionType.BOTTOM, w, h)
|
||||||
desc, sep, Gtk.PositionType.BOTTOM, w, h
|
|
||||||
)
|
|
||||||
row += 1
|
row += 1
|
||||||
sep = None
|
sep = None
|
||||||
grid.attach_next_to(frame, desc, Gtk.PositionType.RIGHT, w, h)
|
grid.attach_next_to(frame, desc, Gtk.PositionType.RIGHT, w, h)
|
||||||
@@ -4295,9 +4190,7 @@ class KeybindingsDialog(Gtk.Box):
|
|||||||
l_spacer = Gtk.Label(label="")
|
l_spacer = Gtk.Label(label="")
|
||||||
r_spacer = Gtk.Label(label="")
|
r_spacer = Gtk.Label(label="")
|
||||||
grid.attach(l_spacer, col, row + 1, w, h)
|
grid.attach(l_spacer, col, row + 1, w, h)
|
||||||
grid.attach_next_to(
|
grid.attach_next_to(r_spacer, l_spacer, Gtk.PositionType.RIGHT, w, h)
|
||||||
r_spacer, l_spacer, Gtk.PositionType.RIGHT, w, h
|
|
||||||
)
|
|
||||||
row += 1
|
row += 1
|
||||||
return grid
|
return grid
|
||||||
|
|
||||||
@@ -4306,9 +4199,7 @@ class KeybindingsDialog(Gtk.Box):
|
|||||||
col = 0
|
col = 0
|
||||||
w = 1
|
w = 1
|
||||||
h = 1
|
h = 1
|
||||||
sidebar = Gtk.Grid(
|
sidebar = Gtk.Grid(row_homogeneous=True, orientation=Gtk.Orientation.VERTICAL)
|
||||||
row_homogeneous=True, orientation=Gtk.Orientation.VERTICAL
|
|
||||||
)
|
|
||||||
for cat in categories:
|
for cat in categories:
|
||||||
label = Gtk.Label(label=cat)
|
label = Gtk.Label(label=cat)
|
||||||
add_class(label, "left-label")
|
add_class(label, "left-label")
|
||||||
@@ -4328,9 +4219,7 @@ class KeybindingsDialog(Gtk.Box):
|
|||||||
column = 1
|
column = 1
|
||||||
w = 1
|
w = 1
|
||||||
h = 1
|
h = 1
|
||||||
sidebar = self.build_sidebar(
|
sidebar = self.build_sidebar(["Servers", "Navigation", "Vim-style keys"])
|
||||||
["Servers", "Navigation", "Vim-style keys"]
|
|
||||||
)
|
|
||||||
separator = Gtk.Separator()
|
separator = Gtk.Separator()
|
||||||
keys_box = self.build_keys(items)
|
keys_box = self.build_keys(items)
|
||||||
|
|
||||||
@@ -4548,9 +4437,7 @@ class Statusbar(Gtk.Statusbar):
|
|||||||
|
|
||||||
players_pretty = pluralize("players", players)
|
players_pretty = pluralize("players", players)
|
||||||
hits_pretty = pluralize("matches", hits)
|
hits_pretty = pluralize("matches", hits)
|
||||||
formatted = (
|
formatted = f"Found {hits:n} {hits_pretty} with {players:n} {players_pretty}"
|
||||||
f"Found {hits:n} {hits_pretty} with {players:n} {players_pretty}"
|
|
||||||
)
|
|
||||||
suffix = "| Distance: calculating..."
|
suffix = "| Distance: calculating..."
|
||||||
|
|
||||||
if players == 0:
|
if players == 0:
|
||||||
@@ -4602,9 +4489,7 @@ class Grid(Gtk.Grid):
|
|||||||
self.breadcrumbs = Gtk.Label(label="Main menu", halign=Gtk.Align.START)
|
self.breadcrumbs = Gtk.Label(label="Main menu", halign=Gtk.Align.START)
|
||||||
|
|
||||||
self.attach(self.notebook, 0, 0, 3, 1)
|
self.attach(self.notebook, 0, 0, 3, 1)
|
||||||
self.attach_next_to(
|
self.attach_next_to(self.breadcrumbs, self.notebook, Gtk.PositionType.TOP, 3, 1)
|
||||||
self.breadcrumbs, self.notebook, Gtk.PositionType.TOP, 3, 1
|
|
||||||
)
|
|
||||||
self.attach_next_to(
|
self.attach_next_to(
|
||||||
self.statusbar, self.notebook, Gtk.PositionType.BOTTOM, 3, 1
|
self.statusbar, self.notebook, Gtk.PositionType.BOTTOM, 3, 1
|
||||||
)
|
)
|
||||||
@@ -4666,11 +4551,23 @@ class App(Gtk.Application):
|
|||||||
)
|
)
|
||||||
self.win.add_accel_group(accel)
|
self.win.add_accel_group(accel)
|
||||||
|
|
||||||
GLib.unix_signal_add(
|
self.setup_signals()
|
||||||
GLib.PRIORITY_DEFAULT, signal.SIGINT, self._catch_sigint
|
|
||||||
)
|
|
||||||
Gtk.main()
|
Gtk.main()
|
||||||
|
|
||||||
|
def setup_signals(self) -> None:
|
||||||
|
SIGNAL_ADD = "signal_add"
|
||||||
|
SIGNAL_ADD_FULL = "signal_add_full"
|
||||||
|
try:
|
||||||
|
if SIGNAL_ADD in dir(GLibUnix):
|
||||||
|
func = GLibUnix.signal_add
|
||||||
|
elif SIGNAL_ADD_FULL in dir(GLibUnix):
|
||||||
|
func = GLibUnix.signal_add_full
|
||||||
|
else:
|
||||||
|
func = GLib.unix_signal_add
|
||||||
|
func(GLib.PRIORITY_DEFAULT, signal.SIGINT, self._catch_sigint)
|
||||||
|
except Exception as e:
|
||||||
|
logger.critical(e)
|
||||||
|
|
||||||
def _catch_sigint(self) -> None:
|
def _catch_sigint(self) -> None:
|
||||||
self.win.halt_proc_and_quit()
|
self.win.halt_proc_and_quit()
|
||||||
|
|
||||||
@@ -4755,9 +4652,7 @@ class ModSelectionPanel(Gtk.Box):
|
|||||||
|
|
||||||
def toggle_select_stale_button(self, state: bool) -> None:
|
def toggle_select_stale_button(self, state: bool) -> None:
|
||||||
if state:
|
if state:
|
||||||
button = Gtk.Button(
|
button = Gtk.Button(label="Select stale", margin_start=10, margin_end=10)
|
||||||
label="Select stale", margin_start=10, margin_end=10
|
|
||||||
)
|
|
||||||
text = "Bulk selects all currently highlighted mods"
|
text = "Bulk selects all currently highlighted mods"
|
||||||
button.set_tooltip_text(text)
|
button.set_tooltip_text(text)
|
||||||
button.connect("clicked", self._on_button_clicked)
|
button.connect("clicked", self._on_button_clicked)
|
||||||
@@ -4877,9 +4772,7 @@ class FilterPanel(Gtk.Box):
|
|||||||
self.keyword_entry = Gtk.Entry()
|
self.keyword_entry = Gtk.Entry()
|
||||||
self.keyword_entry.set_placeholder_text("Filter by keyword")
|
self.keyword_entry.set_placeholder_text("Filter by keyword")
|
||||||
self.keyword_entry.connect("activate", self._on_keyword_enter)
|
self.keyword_entry.connect("activate", self._on_keyword_enter)
|
||||||
self.keyword_entry.connect(
|
self.keyword_entry.connect("key-press-event", self._on_keyword_keypress)
|
||||||
"key-press-event", self._on_keyword_keypress
|
|
||||||
)
|
|
||||||
|
|
||||||
completion = Gtk.EntryCompletion(inline_completion=True)
|
completion = Gtk.EntryCompletion(inline_completion=True)
|
||||||
completion.set_text_column(0)
|
completion.set_text_column(0)
|
||||||
@@ -4947,9 +4840,7 @@ class FilterPanel(Gtk.Box):
|
|||||||
state = self.default_filters[label]
|
state = self.default_filters[label]
|
||||||
check.set_active(state)
|
check.set_active(state)
|
||||||
|
|
||||||
def _on_map_entry_keypress(
|
def _on_map_entry_keypress(self, entry: Gtk.Entry, event: Gdk.EventKey) -> None:
|
||||||
self, entry: Gtk.Entry, event: Gdk.EventKey
|
|
||||||
) -> None:
|
|
||||||
match event.keyval:
|
match event.keyval:
|
||||||
case Gdk.KEY_Return:
|
case Gdk.KEY_Return:
|
||||||
text = entry.get_text()
|
text = entry.get_text()
|
||||||
@@ -4992,9 +4883,7 @@ class FilterPanel(Gtk.Box):
|
|||||||
App.treeview.grab_focus()
|
App.treeview.grab_focus()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _on_keyword_keypress(
|
def _on_keyword_keypress(self, entry: Gtk.Entry, event: Gdk.EventKey) -> bool:
|
||||||
self, entry: Gtk.Entry, event: Gdk.EventKey
|
|
||||||
) -> bool:
|
|
||||||
match event.keyval:
|
match event.keyval:
|
||||||
case Gdk.KEY_Up:
|
case Gdk.KEY_Up:
|
||||||
return True
|
return True
|
||||||
@@ -5005,9 +4894,7 @@ class FilterPanel(Gtk.Box):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _on_combo_keypress(
|
def _on_combo_keypress(self, combo: Gtk.ComboBox, event: Gdk.EventKey) -> bool:
|
||||||
self, combo: Gtk.ComboBox, event: Gdk.EventKey
|
|
||||||
) -> bool:
|
|
||||||
match event.keyval:
|
match event.keyval:
|
||||||
case Gdk.KEY_Down:
|
case Gdk.KEY_Down:
|
||||||
self.maps_combo.popup()
|
self.maps_combo.popup()
|
||||||
|
|||||||
Reference in new issue
Block a user