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

Merge pull request #163 from aclist/release/5.5.0-stable

Release/5.5.0 stable
This commit is contained in:
aclist 2024-11-11 09:22:06 +09:00 committed by GitHub
commit 3bdc0057a2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 150 additions and 36 deletions

View file

@ -1,5 +1,19 @@
# Changelog
## [5.5.0] 2024-11-10
### Added
- Support servers running DLC content (fixes Frostline servers)
- Expose a toggle setting for whether to launch the application in fullscreen
- Text autocompletion in maps search field (partial search)
- Add disk space warning to popup dialog when downloading mods
### Fixed
- Servers in saved servers list would populate context menu with same option when right-clicking in server browser
- Enable adding/removing servers to/from My Saved Servers when in Recent Servers context
- Prevent maps combobox from duplicating contents
- Restore keyboard input to keyword entry field
### Changed
- Abort fallback query method if DLC is required
## [5.4.2] 2024-10-05
### Fixed
- Sanitize third-party API IDs to remove UGC collisions

View file

@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -o pipefail
version=5.4.2
version=5.5.0
#CONSTANTS
aid=221100
@ -193,6 +193,9 @@ debug="$debug"
#Toggle stable/testing branch
branch="$branch"
#Start in fullscreen
fullscreen="$fullscreen"
#Steam API key
steam_api="$steam_api"
@ -525,14 +528,14 @@ fetch_a2s(){
logger INFO "Updated A2S helper to sha '$sha'"
}
fetch_dzq(){
local sum="232f42b98a3b50a0dd6e73fee55521b2"
local sum="9caed1445c45832f4af87736ba3f9637"
local file="$helpers_path/a2s/dayzquery.py"
if [[ -f $file ]] && [[ $(get_hash "$file") == $sum ]]; then
logger INFO "DZQ is current"
return 0
fi
local sha=ccc4f71b48610a1885706c9d92638dbd8ca012a5
local author="yepoleb"
local sha=3088bbfb147b77bc7b6a9425581b439889ff3f7f
local author="aclist"
local repo="dayzquery"
local url="https://raw.githubusercontent.com/$author/$repo/$sha/dayzquery.py"
curl -Ls "$url" > "$file"
@ -566,10 +569,10 @@ fetch_helpers_by_sum(){
[[ -f "$config_file" ]] && source "$config_file"
declare -A sums
sums=(
["ui.py"]="9cac4d3b87ef292e7d30b25ca86cc438"
["ui.py"]="dd7aa34df1d374739127cca3033a3f67"
["query_v2.py"]="55d339ba02512ac69de288eb3be41067"
["vdf2json.py"]="2f49f6f5d3af919bebaab2e9c220f397"
["funcs"]="71d3a941209792a41f381f011e78def8"
["funcs"]="d8ae2662fbc3c62bdb5a51dec1935705"
["lan"]="c62e84ddd1457b71a85ad21da662b9af"
)
local author="aclist"

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash
set -o pipefail
version=5.4.2
version=5.5.0
#CONSTANTS
aid=221100
@ -280,6 +280,14 @@ initialize_remote_servers(){
res=$(get_remote_servers)
parse_server_json "$res" >> "$file"
}
is_dlc(){
local dlc
local ip="$1"
local gport="$2"
local res="$(curl -Ls "https://api.steampowered.com/IGameServersService/GetServerList/v1/?filter=\gameaddr\\${ip}:${gport}\appid\221100&key=$steam_api")"
dlc=$(<<< "$res" jq '.response.servers[].gametype|contains("isDLC")')
printf "%s\n" "$dlc"
}
a2s(){
local ip="$1"
local qport="$2"
@ -402,6 +410,7 @@ query_config(){
"name"
"fav_label"
"preferred_client"
"fullscreen"
)
if [[ -n $key ]]; then
if [[ -n ${!key} ]]; then
@ -706,6 +715,9 @@ debug="$debug"
#Toggle stable/testing branch
branch="$branch"
#Start in fullscreen
fullscreen="$fullscreen"
#Steam API key
steam_api="$steam_api"
@ -799,6 +811,14 @@ toggle(){
else
preferred_client="steam"
fi
;;
Toggle[[:space:]]DZGUI[[:space:]]fullscreen[[:space:]]boot)
if [[ $fullscreen == "true" ]]; then
fullscreen="false"
else
fullscreen="true"
fi
;;
esac
update_config
return 90
@ -1081,25 +1101,39 @@ try_fallback(){
local qport="$2"
local mode="$3"
if [[ $mode != "rules" ]] && [[ $mode != "names" ]]; then
logger WARN "Fallback query API called with an invalid mode: '$mode'"
return 1
fi
[[ -z $api_key ]] && return 1
logger INFO "Using fallback query API on '$ip:$qport' with mode: '$mode'"
local res=$(curl -s "$bm_api" -H "Authorization: Bearer "$api_key"" \
-G -d "filter[game]=$game" \
-d "filter[search]=%22${ip}:${qport}%22")
[[ -z $res ]] && return 1
local len=$(<<< "$res" jq '.data|length')
[[ $len -eq 0 ]] && return 1
# cull defunct entries
local record=$(<<< "$res" jq -r '
.data[].attributes
|select(.status != "removed").details')
|select(.status != "removed")')
# reverse lookup gport
local dlc
local gport=$(<<< "$record" jq -r '.port')
dlc=$(is_dlc "$ip" "$gport")
if [[ $dlc == "true" ]]; then
logger FAIL "Fallback query API called on DLC-enabled server"
return 1
fi
case "$mode" in
"rules")
<<< "$record" jq '.modIds[]'
<<< "$record" jq '.details.modIds[]'
;;
"names")
<<< "$record" jq '
[.modNames, .modIds] as [$n, $i]
.details|[.modNames, .modIds] as [$n, $i]
| {names: $n, ids: $i}'
;;
esac
@ -1403,7 +1437,7 @@ manual_mod_install(){
if [[ $mode == "auto" ]] || [[ $mode == "force" ]]; then
$steam_cmd "steam://url/CommunityFilePage/${stage_mods[$i]}+workshop_download_item $aid ${stage_mods[$i]}"
echo "# Opening workshop page for ${stage_mods[$i]}"
echo "# Opening workshop page for ${stage_mods[$i]}. If you see no progress, you may be out of disk space."
else
$steam_cmd "steam://url/CommunityFilePage/${stage_mods[$i]}"
echo "# Opening workshop page for ${stage_mods[$i]}. If you see no progress after subscribing, try unsubscribing and resubscribing again until the download commences."

View file

@ -19,7 +19,7 @@ gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib, Gdk, GObject, Pango
from enum import Enum
# 5.4.1
# 5.5.0
app_name = "DZGUI"
start_time = 0
@ -105,6 +105,7 @@ options = [
("Toggle release branch",),
("Toggle mod install mode",),
("Toggle Steam/Flatpak",),
("Toggle DZGUI fullscreen boot",),
("Change player name",),
("Change Steam API key",),
("Change Battlemetrics API key",),
@ -153,6 +154,7 @@ status_tooltip = {
"Toggle release branch": "Switch between stable and testing branches",
"Toggle mod install mode": "Switch between manual and auto mod installation",
"Toggle Steam/Flatpak": "Switch the preferred client to use for launching DayZ",
"Toggle DZGUI fullscreen boot": "Whether to start DZGUI as a maximized window (desktop only)",
"Change player name": "Update your in-game name (required by some servers)",
"Change Steam API key": "Can be used if you revoked an old API key",
"Change Battlemetrics API key": "Can be used if you revoked an old API key",
@ -348,7 +350,12 @@ def process_tree_option(input, treeview):
logger.info("Parsing tree option '%s' for the context '%s'" %(command, context))
transient_parent = treeview.get_outer_window()
toggle_contexts = ["Toggle mod install mode", "Toggle release branch", "Toggle Steam/Flatpak"]
toggle_contexts = [
"Toggle mod install mode",
"Toggle release branch",
"Toggle Steam/Flatpak",
"Toggle DZGUI fullscreen boot"
]
def call_on_thread(bool, subproc, msg, args):
def _background(subproc, args, dialog):
@ -374,7 +381,6 @@ def process_tree_option(input, treeview):
msg = out[-1]
process_shell_return_code(transient_parent, msg, rc, input)
match context:
case "Help":
if command == "View changelog":
@ -465,7 +471,8 @@ class OuterWindow(Gtk.Window):
if is_game_mode is True:
self.fullscreen()
else:
self.maximize()
if query_config(None, "fullscreen")[0] == "true":
self.maximize()
# Hide FilterPanel on main menu
self.show_all()
@ -759,7 +766,7 @@ class TreeView(Gtk.TreeView):
mod_context_items = ["Open in Steam Workshop", "Delete mod"]
subcontext_items = {"Server browser": ["Add to my servers", "Copy IP to clipboard", "Show server-side mods", "Refresh player count"],
"My saved servers": ["Remove from my servers", "Copy IP to clipboard", "Show server-side mods", "Refresh player count"],
"Recent servers": ["Remove from history", "Copy IP to clipboard", "Show server-side mods", "Refresh player count"],
"Recent servers": ["Add to my servers", "Remove from history", "Copy IP to clipboard", "Show server-side mods", "Refresh player count"],
}
# submenu hierarchy https://stackoverflow.com/questions/52847909/how-to-add-a-sub-menu-to-a-gtk-menu
if context == "Mod":
@ -772,11 +779,12 @@ class TreeView(Gtk.TreeView):
return
for item in items:
if subcontext == "Server browser" and item == "Add to my servers":
record = "%s:%s" %(self.get_column_at_index(7), self.get_column_at_index(8))
proc = call_out(widget, "is_in_favs", record)
if proc.returncode == 0:
item = "Remove from my servers"
if subcontext == "Server browser" or "Recent servers":
if item == "Add to my servers":
record = "%s:%s" %(self.get_column_at_index(7), self.get_column_at_index(8))
proc = call_out(widget, "is_in_favs", record)
if proc.returncode == 0:
item = "Remove from my servers"
item = Gtk.MenuItem(label=item)
item.connect("activate", self._on_menu_click)
self.menu.append(item)
@ -895,8 +903,7 @@ class TreeView(Gtk.TreeView):
case Gdk.KEY_m:
if self.get_first_col() == "Mod":
return
grid.right_panel.filters_vbox.maps_combo.grab_focus()
grid.right_panel.filters_vbox.maps_combo.popup()
grid.right_panel.filters_vbox.maps_entry.grab_focus()
case _:
return False
elif keyname.isnumeric() and int(keyname) > 0:
@ -1238,7 +1245,8 @@ def format_metadata(row_sel):
"auto_install": config_vals[2],
"name": config_vals[3],
"fav_label": config_vals[4],
"preferred_client": config_vals[5]
"preferred_client": config_vals[5],
"fullscreen": config_vals[6]
}
match row_sel:
case "Quick-connect to favorite server" | "Change favorite server":
@ -1258,6 +1266,10 @@ def format_metadata(row_sel):
val = "branch"
case "Toggle Steam/Flatpak":
val = "preferred_client"
case "Toggle DZGUI fullscreen boot":
default = "false"
alt = "true"
val = "fullscreen"
case _:
return prefix
@ -1566,7 +1578,7 @@ def KeysDialog(parent, text, mode):
Enter/Space/Double click: connect to server
Right-click on row/Ctrl-l: displays additional context menus
Ctrl-f: jump to keyword field
Ctrl-m: jump to maps dropdown
Ctrl-m: jump to maps field
Ctrl-d: toggle dry run (debug) mode
Ctrl-r: refresh player count for active row
1-9: toggle filter ON/OFF
@ -1639,7 +1651,7 @@ class ModDialog(GenericDialog):
def _load():
dialog.destroy()
if data.returncode == 1:
spawn_dialog(parent, "Server has no mods installed", "NOTIFY")
spawn_dialog(parent, "Server has no mods installed or is unsupported in this mode", "NOTIFY")
return
self.show_all()
self.set_markup("Modlist (%s mods)" %(mod_count))
@ -1858,15 +1870,27 @@ class FilterPanel(Gtk.Box):
self.keyword_entry.set_placeholder_text("Filter by keyword")
self.keyword_entry.connect("activate", self._on_keyword_enter)
self.keyword_entry.connect("key-press-event", self._on_esc_pressed)
completion = Gtk.EntryCompletion(inline_completion=True)
completion.set_text_column(0)
completion.set_minimum_key_length(1)
completion.connect("match_selected", self._on_completer_match)
renderer_text = Gtk.CellRendererText(ellipsize=Pango.EllipsizeMode.END)
self.maps_combo = Gtk.ComboBox.new_with_model(map_store)
self.maps_combo = Gtk.ComboBox.new_with_model_and_entry(map_store)
self.maps_combo.set_entry_text_column(0)
# instantiate maps completer entry
self.maps_entry = self.maps_combo.get_child()
self.maps_entry.set_completion(completion)
self.maps_entry.set_placeholder_text("Filter by map")
self.maps_entry.connect("changed", self._on_map_completion, True)
self.maps_entry.connect("key-press-event", self._on_map_entry_keypress)
self.maps_combo.pack_start(renderer_text, True)
self.maps_combo.add_attribute(renderer_text, "text", 0)
self.maps_combo.connect("changed", self._on_map_changed)
self.maps_combo.connect("key-press-event", self._on_esc_pressed)
self.pack_start(self.filters_label, False, False, True)
self.pack_start(self.keyword_entry, False, False, True)
self.pack_start(self.maps_combo, False, False, True)
@ -1874,6 +1898,37 @@ class FilterPanel(Gtk.Box):
for i, check in enumerate(checks[0:]):
self.pack_start(checks[i], False, False, True)
def _on_map_entry_keypress(self, entry, event):
match event.keyval:
case Gdk.KEY_Return:
text = entry.get_text()
if text is None:
return
# if entry is exact match for value in liststore,
# trigger map change function
for i in enumerate(map_store):
if text == i[1][0]:
self.maps_combo.set_active(i[0])
self._on_map_changed(self.maps_combo)
case Gdk.KEY_Escape:
GLib.idle_add(self.restore_focus_to_treeview)
# TODO: this is a workaround for widget.grab_remove()
# set cursor position to SOL when unfocusing
text = self.maps_entry.get_text()
self.maps_entry.set_position(len(text))
case _:
return
def _on_completer_match(self, completion, model, iter):
self.maps_combo.set_active_iter(iter)
def _on_map_completion(self, entry, editable):
text = entry.get_text()
completion = entry.get_completion()
if len(text) >= completion.get_minimum_key_length():
completion.set_model(map_store)
self._on_map_changed(self.maps_combo)
def grab_keyword_focus(self):
self.keyword_entry.grab_focus()
@ -1884,9 +1939,11 @@ class FilterPanel(Gtk.Box):
return False
def _on_esc_pressed(self, entry, event):
keyname = Gdk.keyval_name(event.keyval)
if keyname == "Escape":
GLib.idle_add(self.restore_focus_to_treeview)
match event.keyval:
case Gdk.KEY_Escape:
GLib.idle_add(self.restore_focus_to_treeview)
case _:
return False
def get_outer_grid(self):
panel = self.get_parent()
@ -1952,13 +2009,19 @@ class FilterPanel(Gtk.Box):
tree_iter = combo.get_active_iter()
if tree_iter is not None:
selected_map.clear()
# take no action if completer query is same as current map sel
old_sel = selected_map[0].split("Map=")[1]
model = combo.get_model()
selection = model[tree_iter][0]
selected_map.append("Map=" + selection)
logger.info("User selected map '%s'" %(selection))
filter_servers(transient_parent, self, treeview, context)
if selection == old_sel:
return
selected_map.clear()
if selection is not None:
selected_map.append("Map=" + selection)
logger.info("User selected map '%s'" %(selection))
filter_servers(transient_parent, self, treeview, context)
self.maps_entry.set_text(selection)
def main():