1
0
Fork 0
mirror of https://github.com/aclist/dztui.git synced 2024-12-27 21:02:36 +01:00

Merge pull request #147 from aclist/release/5.3.3

Release/5.3.3 (stable)
This commit is contained in:
aclist 2024-09-01 13:13:54 +09:00 committed by GitHub
commit 32c467fc6d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 315 additions and 30 deletions

View file

@ -1,5 +1,17 @@
# Changelog
## [5.3.3] 2024-08-27
### Added
- Scan local area network for DayZ servers
- Freedesktop application icons for system taskbar, tray, and other dialogs
- Emit CPU model name when exporting system debug log
### Fixed
- Detect Steam Deck OLED APU variant during initial setup
- Errors being printed to the console when Exit button was explicitly clicked
- Test if DayZ library location was moved internally on Steam by user
- Encapsulate player names correctly to support whitespace
- Report WM_CLASS name to the window manager
## [5.3.2] 2024-07-02
### Fixed
- Server list would sometimes be missing some expected servers due to truncation being caused when a server erroneously set an incomplete gametype

View file

@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -o pipefail
version=5.3.2
version=5.3.3
#CONSTANTS
aid=221100
@ -13,7 +13,7 @@ sd_res="--width=1280 --height=800"
steamsafe_zenity="/usr/bin/zenity"
zenity_flags=("--width=500" "--title=DZGUI")
declare -A deps
deps=([awk]="5.1.1" [curl]="7.80.0" [jq]="1.6" [tr]="9.0" [$steamsafe_zenity]="3.42.1")
deps=([awk]="5.1.1" [curl]="7.80.0" [jq]="1.6" [tr]="9.0" [$steamsafe_zenity]="3.44.1")
#CONFIG
config_path="$HOME/.config/dztui"
@ -65,6 +65,7 @@ km_helper_url="$releases_url/latlon"
geo_file_url="$releases_url/ips.csv.gz"
set_im_module(){
#TODO: drop pending SteamOS changes
pgrep -a gamescope | grep -q "generate-drm-mode"
if [[ $? -eq 0 ]]; then
GTK_IM_MODULE=""
@ -377,18 +378,20 @@ test_display_mode(){
fi
}
check_architecture(){
local cpu=$(< /proc/cpuinfo grep "AMD Custom APU 0405")
if [[ -n "$cpu" ]]; then
if [[ $(test_display_mode) == "gm" ]]; then
is_steam_deck=2
else
is_steam_deck=1
fi
logger INFO "Setting architecture to 'Steam Deck'"
else
local cpu=$(< /proc/cpuinfo awk -F": " '/AMD Custom APU [0-9]{4}$/ {print $2; exit}')
read -a APU_MODEL <<< "$cpu"
if [[ ${APU_MODEL[3]} != "0932" ]] && [[ ${APU_MODEL[3]} != "0405" ]]; then
is_steam_deck=0
logger INFO "Setting architecture to 'desktop'"
return
fi
if [[ $(test_display_mode) == "gm" ]]; then
is_steam_deck=2
else
is_steam_deck=1
fi
logger INFO "Setting architecture to 'Steam Deck'"
}
check_map_count(){
[[ $is_steam_deck -gt 0 ]] && return 0
@ -531,14 +534,39 @@ fetch_dzq(){
curl -Ls "$url" > "$file"
logger INFO "Updated DZQ to sha '$sha'"
}
fetch_icons(){
res=(
"16"
"24"
"32"
"48"
"64"
"96"
"128"
"256"
)
url="$stable_url/images/icons"
for i in "${res[@]}"; do
size="${i}x${i}"
dir="$HOME/.local/share/icons/hicolor/$size/apps"
icon="$dir/$app_name.png"
[[ -f $icon ]] && return
if [[ ! -d $dir ]]; then
mkdir -p "$dir"
fi
logger INFO "Updating $size Freedesktop icon"
curl -Ls "${url}/${i}.png" > "$icon"
done
}
fetch_helpers_by_sum(){
[[ -f "$config_file" ]] && source "$config_file"
declare -A sums
sums=(
["ui.py"]="f14772424461ec438579dec567db0634"
["query_v2.py"]="1822bd1769ce7d7cb0d686a60f9fa197"
["ui.py"]="13bb5456aa9d95efdf2335e437f4c6fb"
["query_v2.py"]="55d339ba02512ac69de288eb3be41067"
["vdf2json.py"]="2f49f6f5d3af919bebaab2e9c220f397"
["funcs"]="718c3060b88041f5b99a1ce52b12642f"
["funcs"]="44eca80b207057423c2d298cc7cf1e29"
["lan"]="c62e84ddd1457b71a85ad21da662b9af"
)
local author="aclist"
local repo="dztui"
@ -576,6 +604,7 @@ fetch_helpers_by_sum(){
logger INFO "Updated '$full_path' to sum '$sum'"
fi
[[ $file == "funcs" ]] && chmod +x "$full_path"
[[ $file == "lan" ]] && chmod +x "$full_path"
done
return 0
}
@ -600,6 +629,7 @@ fetch_helpers(){
fetch_geo_file
fetch_helpers_by_sum
[[ ! -f $share_path/icon.png ]] && freedesktop_dirs
fetch_icons
}
raise_error_and_quit(){
local msg="$1"
@ -778,7 +808,7 @@ varcheck(){
source "$config_file"
local workshop_dir="$steam_path/steamapps/workshop/content/$aid"
local game_dir="$steam_path/steamapps/common/DayZ"
if [[ ! -d $steam_path ]] || [[ ! -d $game_dir ]]; then
if [[ ! -d $steam_path ]] || [[ ! -d $game_dir ]] || [[ ! $(find $game_dir -type f) ]]; then
logger WARN "DayZ path resolved to '$game_dir'"
logger WARN "Workshop path resolved to '$workshop_dir'"
qdialog "$msg2" "Yes" "Exit"
@ -894,7 +924,7 @@ uninstall(){
}
main(){
local zenv=$(zenity --version 2>/dev/null)
[[ -z $zenv ]] && { echo "Requires zenity <= 3.44.1"; exit 1; }
[[ -z $zenv ]] && { echo "Requires zenity >= ${deps[$steamsafe_zenity]}"; exit 1; }
if [[ $1 == "--uninstall" ]] || [[ $1 == "-u" ]]; then
uninstall &&
exit 0

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash
set -o pipefail
version=5.2.3
version=5.3.3
#CONSTANTS
aid=221100
@ -44,6 +44,7 @@ _cache_launch="$cache_dir/$prefix.launch_mods"
_cache_address="$cache_dir/$prefix.launch_address"
_cache_coords="$cache_path/$prefix.coords"
_cache_cooldown="$cache_path/$prefix.cooldown"
_cache_lan="$cache_path/$prefix.lan"
#XDG
freedesktop_path="$HOME/.local/share/applications"
@ -55,6 +56,7 @@ km_helper="$helpers_path/latlon"
sums_path="$helpers_path/sums.md5"
query_helper="$helpers_path/query_v2.py"
func_helper="$helpers_path/funcs"
lan_helper="$helpers_path/lan"
#STEAM PATHS
workshop_path="$steam_path/steamapps/workshop"
@ -125,8 +127,19 @@ declare -A funcs=(
["force_update"]="force_update"
["Handshake"]="final_handshake"
["get_player_count"]="get_player_count"
["lan_scan"]="lan_scan"
)
lan_scan(){
local port="$1"
local res
res=$("$lan_helper" "$port")
if [[ -z $res ]]; then
printf "\n"
else
printf "%s\n" "$res"
fi
}
get_player_count(){
shift
local res
@ -589,6 +602,16 @@ dump_servers(){
_iterate "$file" "${iters[@]}"
fi
;;
*Scan[[:space:]]LAN[[:space:]]servers*)
local port=$(<<< "$subcontext" awk -F: '{print $2}')
local file="$_cache_lan"
if [[ ! $subcontext =~ Name ]]; then
[[ -f $file ]] && rm $file
local lan=$(lan_scan $port)
readarray -t iters <<< "$lan"
_iterate "$file" "${iters[@]}"
fi
;;
esac
shift
logger INFO "Server context is '$subcontext', reading from file '$file'"
@ -955,6 +978,7 @@ generate_log(){
cat <<-DOC > $system_log
Distro: $(< /etc/os-release grep -w NAME | awk -F\" '{print $2}')
Kernel: $(uname -mrs)
CPU: $(< /proc/cpuinfo awk -F": " '/model name/ {print $2; exit}')
Version: $version
Branch: $branch
Mode: $(if [[ -z $debug ]]; then echo normal; else echo debug; fi)
@ -988,8 +1012,6 @@ query_defunct(){
-d "$(payload)" 'https://api.steampowered.com/ISteamRemoteStorage/GetPublishedFileDetails/v1/?format=json'
}
local result=$(post | jq -r '.[].publishedfiledetails[] | select(.result==1) | "\(.file_size) \(.publishedfileid)"')
local result2=$(post | jq -r '')
echo "$result2" > $HOME/json
<<< "$result" awk '{print $2}'
}
encode(){
@ -1287,7 +1309,7 @@ launch(){
update_symlinks
if [[ $debug -eq 1 ]]; then
local launch_options="$steam_cmd -applaunch $aid -connect=$ip:$gameport -nolauncher -nosplash -name=$name -skipintro \"-mod=$concat\""
local launch_options="$steam_cmd -applaunch $aid -connect=$ip:$gameport -nolauncher -nosplash -name=$name -skipintro -mod=$concat"
printf "Debug mode: these options would have been used to launch the game: $launch_options"
return 0
fi
@ -1307,11 +1329,19 @@ final_handshake(){
return 1
fi
logger INFO "Kicking off Steam launch"
$steam_cmd -applaunch $aid -connect=$saved_address -nolauncher -nosplash -skipintro -name=$name \"-mod=$saved_mods\" &
local params=()
params+=("-connect=$saved_address")
params+=("-nolauncher")
params+=("-nosplash")
params+=("-skipintro")
params+=("-name=$name")
params+=("-mod=$saved_mods")
$steam_cmd -applaunch $aid "${params[@]}" &
until [[ $(is_dayz_running) -eq 1 ]]; do
sleep 0.1s
done
logger INFO "Caught DayZ process"
printf "\n"
return 6
}
manual_mod_install(){

46
helpers/lan Executable file
View file

@ -0,0 +1,46 @@
#!/usr/bin/env bash
query_name(){
local ip="$1"
local port="$2"
local api="$HOME/.local/share/dzgui/helpers/query_v2.py"
python3 "$api" "$ip" "$port" test
}
scan(){
local ip="$1"
local port="$2"
local res=$(query_name "$ip" "$port")
[[ -z $res ]] && return 1
printf "%s\n" "${ip}:XXX:${port}"
}
get_netmask(){
ip r \
| awk '/default/ {print $3}' \
| uniq \
| awk -F. 'OFS="."{print $1,$2,$3}'
}
iter(){
_testping(){
ping -c1 -i 0.1 -w 1 "$1" 2>/dev/null 1>&2
[[ $? -eq 0 ]] && echo "$1"
}
export -f _testping
local mask=$(get_netmask)
# GNU parallel is not available OOTB on Steam Deck
for i in $(seq 1 255); do
echo "${mask}.${i}"
done | xargs -I {} -P 200 bash -c '_testping "{}"'
}
export -f scan
export -f query_name
DZG_LAN_PORT="$1"
readarray -t ips < <(iter)
for i in "${ips[@]}"; do
scan "$i" $DZG_LAN_PORT
done

View file

@ -5,6 +5,14 @@ import json
from a2s import dayzquery
sys.path.append('a2s')
def test_local(ip, qport):
try:
info = a2s.info((ip, int(qport)), 0.5)
name = info.server_name
print(name)
except:
sys.exit(1)
def get_info(ip, qport):
try:
info = a2s.info((ip, int(qport)))
@ -73,3 +81,5 @@ match mode:
get_rules(ip, qport)
case "names":
get_names(ip, qport)
case "test":
test_local(ip, qport)

View file

@ -17,8 +17,9 @@ import time
locale.setlocale(locale.LC_ALL, '')
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib, Gdk, GObject, Pango
from enum import Enum
# 5.2.3
# 5.3.3
app_name = "DZGUI"
start_time = 0
@ -91,7 +92,8 @@ connect = [
("Quick-connect to favorite server",),
("Recent servers",),
("Connect by IP",),
("Connect by ID",)
("Connect by ID",),
("Scan LAN servers",)
]
manage = [
("Add server by IP",),
@ -143,6 +145,7 @@ status_tooltip = {
"Recent servers": "Shows the last 10 servers you connected to (includes attempts)",
"Connect by IP": "Connect to a server by IP",
"Connect by ID": "Connect to a server by Battlemetrics ID",
"Scan LAN servers": "Search for servers on your local network",
"Add server by IP": "Add a server by IP",
"Add server by ID": "Add a server by Battlemetrics ID",
"Change favorite server": "Update your quick-connect server",
@ -570,10 +573,16 @@ class ButtonBox(Gtk.Box):
renderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn(column_title, renderer, text=i)
treeview.append_column(column)
self._populate(context)
treeview.set_model(row_store)
treeview.grab_focus()
def _populate(self, array_context):
def _populate(self, context):
match context:
case 'Manage': array_context = manage
case 'Main menu': array_context = connect
case 'Options': array_context = options
case 'Help': array_context = help
row_store.clear()
status = array_context[0][0]
treeview = self.get_treeview()
@ -593,6 +602,7 @@ class ButtonBox(Gtk.Box):
if context == "Exit":
logger.info("Normal user exit")
Gtk.main_quit()
return
cols = treeview.get_columns()
if len(cols) > 1:
@ -606,11 +616,7 @@ class ButtonBox(Gtk.Box):
for col in cols:
col.set_title(context)
match context:
case 'Manage': self._populate(manage)
case 'Main menu': self._populate(connect)
case 'Options': self._populate(options)
case 'Help': self._populate(help)
self._populate(context)
toggle_signal(treeview, treeview.selected_row, '_on_tree_selection_changed', True)
@ -1069,6 +1075,17 @@ class TreeView(Gtk.TreeView):
toggle_signal(self.get_outer_grid().right_panel.filters_vbox, check, '_on_check_toggle', True)
toggle_signal(self, self, '_on_keypress', True)
if mode == "Scan LAN servers":
lan_dialog = LanButtonDialog(self.get_outer_window())
port = lan_dialog.get_selected_port()
if port is None:
grid = self.get_outer_grid()
right_panel = grid.right_panel
vbox = right_panel.button_vbox
vbox._update_single_column("Main menu")
return
mode = mode + ":" + port
wait_dialog = GenericDialog(transient_parent, "Fetching server metadata", "WAIT")
wait_dialog.show_all()
thread = threading.Thread(target=self._background, args=(wait_dialog, mode))
@ -1170,7 +1187,7 @@ class TreeView(Gtk.TreeView):
right_panel = outer.grid.right_panel
filters_vbox = right_panel.filters_vbox
valid_contexts = ["Server browser", "My saved servers", "Recent servers"]
valid_contexts = ["Server browser", "My saved servers", "Recent servers", "Scan LAN servers"]
if chosen_row in valid_contexts:
# server contexts share the same model type
for check in checks:
@ -1304,6 +1321,10 @@ class AppHeaderBar(Gtk.HeaderBar):
self.set_decoration_layout("menu:minimize,maximize,close")
self.set_show_close_button(True)
class Port(Enum):
# Contains enums for LanButtonDialog ports
DEFAULT = 1
CUSTOM = 2
class GenericDialog(Gtk.MessageDialog):
def __init__(self, parent, text, mode):
@ -1361,6 +1382,140 @@ class GenericDialog(Gtk.MessageDialog):
self.format_secondary_text(text)
class LanButtonDialog(Gtk.Window):
def __init__(self, parent):
super().__init__()
self.buttonBox = Gtk.Box()
header_label = "Scan LAN servers"
buttons = [
( "Use default query port (27016)", Port.DEFAULT ),
( "Enter custom query port", Port.CUSTOM ),
]
self.buttonBox.set_orientation(Gtk.Orientation.VERTICAL)
self.buttonBox.active_button = None
for i in enumerate(buttons):
string = i[1][0]
enum = i[1][1]
button = Gtk.RadioButton(label=string)
button.port = enum
button.connect("toggled", self._on_button_toggled)
if i[0] == 0:
self.buttonBox.active_button = button
else:
button.join_group(self.buttonBox.active_button)
self.buttonBox.add(button)
self.entry = Gtk.Entry()
self.buttonBox.add(self.entry)
self.entry.set_no_show_all(True)
self.label = Gtk.Label()
self.label.set_text("Invalid port")
self.label.set_no_show_all(True)
self.buttonBox.add(self.label)
self.dialog = LanDialog(parent, header_label, self.buttonBox, self.entry, self.label)
self.dialog.run()
self.dialog.destroy()
def get_selected_port(self):
return self.dialog.p
def _on_button_toggled(self, button):
if button.get_active():
self.buttonBox.active_button = button
match button.port:
case Port.DEFAULT:
self.entry.set_visible(False)
case Port.CUSTOM:
self.entry.set_visible(True)
self.entry.grab_focus()
def get_active_button():
return self.buttonBox.active_button
class LanDialog(Gtk.MessageDialog):
# Custom dialog class that performs integer validation and blocks input if invalid port
# Returns None if user cancels the dialog
def __init__(self, parent, text, child, entry, label):
super().__init__(transient_for=parent,
flags=0,
message_type=Gtk.MessageType.INFO,
buttons=Gtk.ButtonsType.OK_CANCEL,
text=text,
secondary_text="Select the query port",
title=app_name,
modal=True,
)
self.outer = self.get_content_area()
self.outer.pack_start(child, False, False, 0)
self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
self.set_size_request(500, 0)
self.outer.set_margin_start(30)
self.outer.set_margin_end(30)
self.outer.show_all()
self.connect("response", self._on_dialog_response, child, entry)
self.connect("key-press-event", self._on_keypress, entry)
self.connect("key-release-event", self._on_key_release, entry, label)
self.child = child
self.p = None
def _on_key_release(self, dialog, event, entry, label):
label.set_visible(False)
if entry.is_visible() == False or entry.get_text() == "":
return
if self._is_invalid(entry.get_text()):
label.set_visible(True)
else:
label.set_visible(False)
def _on_keypress(self, a, event, entry):
if event.keyval == Gdk.KEY_Return:
self.response(Gtk.ResponseType.OK)
if event.keyval == Gdk.KEY_Up:
entry.set_text("")
self.child.get_children()[0].grab_focus()
def _on_dialog_response(self, dialog, resp, child, entry):
match resp:
case Gtk.ResponseType.CANCEL:
return
case Gtk.ResponseType.DELETE_EVENT:
return
string = entry.get_text()
port = child.active_button.port
match port:
case Port.DEFAULT:
self.p = "27016"
case Port.CUSTOM:
if self._is_invalid(string):
self.stop_emission_by_name("response")
else:
self.p = string
def _is_invalid(self, string):
if string.isdigit() == False \
or int(string) == 0 \
or int(string[0]) == 0 \
or int(string) > 65535:
return True
return False
def ChangelogDialog(parent, text, mode):
dialog = GenericDialog(parent, text, mode)
@ -1657,7 +1812,9 @@ class App(Gtk.Application):
is_steam_deck = False
is_game_mode = False
GLib.set_prgname(app_name)
self.win = OuterWindow(is_steam_deck, is_game_mode)
self.win.set_icon_name("dzgui")
accel = Gtk.AccelGroup()
accel.connect(Gdk.KEY_q, Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE, self._halt_window_subprocess)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 497 KiB

After

Width:  |  Height:  |  Size: 459 KiB

BIN
images/icons/128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
images/icons/16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

BIN
images/icons/24.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
images/icons/256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

BIN
images/icons/32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
images/icons/48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

BIN
images/icons/64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

BIN
images/icons/96.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB