mirror of
https://github.com/aclist/dztui.git
synced 2024-12-29 13:52:03 +01:00
feat: scan LAN servers
This commit is contained in:
parent
c6a4b419dd
commit
e6244370dd
6 changed files with 249 additions and 13 deletions
|
@ -1,5 +1,9 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [5.3.3-beta.3] 2024-08-04
|
||||||
|
### Added
|
||||||
|
- Scan local area network for DayZ servers
|
||||||
|
|
||||||
## [5.3.3-beta.2] 2024-08-03
|
## [5.3.3-beta.2] 2024-08-03
|
||||||
### Fixed
|
### Fixed
|
||||||
- Clerical hotfix for previous player names fix
|
- Clerical hotfix for previous player names fix
|
||||||
|
|
9
dzgui.sh
9
dzgui.sh
|
@ -1,7 +1,7 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
|
|
||||||
version=5.3.3-beta.2
|
version=5.3.3-beta.3
|
||||||
|
|
||||||
#CONSTANTS
|
#CONSTANTS
|
||||||
aid=221100
|
aid=221100
|
||||||
|
@ -535,10 +535,11 @@ fetch_helpers_by_sum(){
|
||||||
[[ -f "$config_file" ]] && source "$config_file"
|
[[ -f "$config_file" ]] && source "$config_file"
|
||||||
declare -A sums
|
declare -A sums
|
||||||
sums=(
|
sums=(
|
||||||
["ui.py"]="f14772424461ec438579dec567db0634"
|
["ui.py"]="9755d63904fb63dc48eff3397a31de17"
|
||||||
["query_v2.py"]="1822bd1769ce7d7cb0d686a60f9fa197"
|
["query_v2.py"]="55d339ba02512ac69de288eb3be41067"
|
||||||
["vdf2json.py"]="2f49f6f5d3af919bebaab2e9c220f397"
|
["vdf2json.py"]="2f49f6f5d3af919bebaab2e9c220f397"
|
||||||
["funcs"]="28728839c031ae99002860a546352455"
|
["funcs"]="2ebe0f7072f7a9459007b1d9d09e2f4f"
|
||||||
|
["lan"]="c62e84ddd1457b71a85ad21da662b9af"
|
||||||
)
|
)
|
||||||
local author="aclist"
|
local author="aclist"
|
||||||
local repo="dztui"
|
local repo="dztui"
|
||||||
|
|
|
@ -44,6 +44,7 @@ _cache_launch="$cache_dir/$prefix.launch_mods"
|
||||||
_cache_address="$cache_dir/$prefix.launch_address"
|
_cache_address="$cache_dir/$prefix.launch_address"
|
||||||
_cache_coords="$cache_path/$prefix.coords"
|
_cache_coords="$cache_path/$prefix.coords"
|
||||||
_cache_cooldown="$cache_path/$prefix.cooldown"
|
_cache_cooldown="$cache_path/$prefix.cooldown"
|
||||||
|
_cache_lan="$cache_path/$prefix.lan"
|
||||||
|
|
||||||
#XDG
|
#XDG
|
||||||
freedesktop_path="$HOME/.local/share/applications"
|
freedesktop_path="$HOME/.local/share/applications"
|
||||||
|
@ -55,6 +56,7 @@ km_helper="$helpers_path/latlon"
|
||||||
sums_path="$helpers_path/sums.md5"
|
sums_path="$helpers_path/sums.md5"
|
||||||
query_helper="$helpers_path/query_v2.py"
|
query_helper="$helpers_path/query_v2.py"
|
||||||
func_helper="$helpers_path/funcs"
|
func_helper="$helpers_path/funcs"
|
||||||
|
lan_helper="$helpers_path/lan"
|
||||||
|
|
||||||
#STEAM PATHS
|
#STEAM PATHS
|
||||||
workshop_path="$steam_path/steamapps/workshop"
|
workshop_path="$steam_path/steamapps/workshop"
|
||||||
|
@ -125,8 +127,19 @@ declare -A funcs=(
|
||||||
["force_update"]="force_update"
|
["force_update"]="force_update"
|
||||||
["Handshake"]="final_handshake"
|
["Handshake"]="final_handshake"
|
||||||
["get_player_count"]="get_player_count"
|
["get_player_count"]="get_player_count"
|
||||||
|
["lan_scan"]="lan_scan"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
lan_scan(){
|
||||||
|
local port="$1"
|
||||||
|
local res
|
||||||
|
res=$("$lan_helper" "$port")
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
printf "\n"
|
||||||
|
else
|
||||||
|
printf "%s\n" "$res"
|
||||||
|
fi
|
||||||
|
}
|
||||||
get_player_count(){
|
get_player_count(){
|
||||||
shift
|
shift
|
||||||
local res
|
local res
|
||||||
|
@ -589,6 +602,16 @@ dump_servers(){
|
||||||
_iterate "$file" "${iters[@]}"
|
_iterate "$file" "${iters[@]}"
|
||||||
fi
|
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
|
esac
|
||||||
shift
|
shift
|
||||||
logger INFO "Server context is '$subcontext', reading from file '$file'"
|
logger INFO "Server context is '$subcontext', reading from file '$file'"
|
||||||
|
@ -988,8 +1011,6 @@ query_defunct(){
|
||||||
-d "$(payload)" 'https://api.steampowered.com/ISteamRemoteStorage/GetPublishedFileDetails/v1/?format=json'
|
-d "$(payload)" 'https://api.steampowered.com/ISteamRemoteStorage/GetPublishedFileDetails/v1/?format=json'
|
||||||
}
|
}
|
||||||
local result=$(post | jq -r '.[].publishedfiledetails[] | select(.result==1) | "\(.file_size) \(.publishedfileid)"')
|
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}'
|
<<< "$result" awk '{print $2}'
|
||||||
}
|
}
|
||||||
encode(){
|
encode(){
|
||||||
|
|
46
helpers/lan
Executable file
46
helpers/lan
Executable 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
|
|
@ -5,6 +5,14 @@ import json
|
||||||
from a2s import dayzquery
|
from a2s import dayzquery
|
||||||
sys.path.append('a2s')
|
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):
|
def get_info(ip, qport):
|
||||||
try:
|
try:
|
||||||
info = a2s.info((ip, int(qport)))
|
info = a2s.info((ip, int(qport)))
|
||||||
|
@ -73,3 +81,5 @@ match mode:
|
||||||
get_rules(ip, qport)
|
get_rules(ip, qport)
|
||||||
case "names":
|
case "names":
|
||||||
get_names(ip, qport)
|
get_names(ip, qport)
|
||||||
|
case "test":
|
||||||
|
test_local(ip, qport)
|
||||||
|
|
168
helpers/ui.py
168
helpers/ui.py
|
@ -17,6 +17,7 @@ import time
|
||||||
locale.setlocale(locale.LC_ALL, '')
|
locale.setlocale(locale.LC_ALL, '')
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gtk, GLib, Gdk, GObject, Pango
|
from gi.repository import Gtk, GLib, Gdk, GObject, Pango
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
# 5.2.3
|
# 5.2.3
|
||||||
app_name = "DZGUI"
|
app_name = "DZGUI"
|
||||||
|
@ -91,7 +92,8 @@ connect = [
|
||||||
("Quick-connect to favorite server",),
|
("Quick-connect to favorite server",),
|
||||||
("Recent servers",),
|
("Recent servers",),
|
||||||
("Connect by IP",),
|
("Connect by IP",),
|
||||||
("Connect by ID",)
|
("Connect by ID",),
|
||||||
|
("Scan LAN servers",)
|
||||||
]
|
]
|
||||||
manage = [
|
manage = [
|
||||||
("Add server by IP",),
|
("Add server by IP",),
|
||||||
|
@ -143,6 +145,7 @@ status_tooltip = {
|
||||||
"Recent servers": "Shows the last 10 servers you connected to (includes attempts)",
|
"Recent servers": "Shows the last 10 servers you connected to (includes attempts)",
|
||||||
"Connect by IP": "Connect to a server by IP",
|
"Connect by IP": "Connect to a server by IP",
|
||||||
"Connect by ID": "Connect to a server by Battlemetrics ID",
|
"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 IP": "Add a server by IP",
|
||||||
"Add server by ID": "Add a server by Battlemetrics ID",
|
"Add server by ID": "Add a server by Battlemetrics ID",
|
||||||
"Change favorite server": "Update your quick-connect server",
|
"Change favorite server": "Update your quick-connect server",
|
||||||
|
@ -570,10 +573,16 @@ class ButtonBox(Gtk.Box):
|
||||||
renderer = Gtk.CellRendererText()
|
renderer = Gtk.CellRendererText()
|
||||||
column = Gtk.TreeViewColumn(column_title, renderer, text=i)
|
column = Gtk.TreeViewColumn(column_title, renderer, text=i)
|
||||||
treeview.append_column(column)
|
treeview.append_column(column)
|
||||||
|
self._populate(context)
|
||||||
treeview.set_model(row_store)
|
treeview.set_model(row_store)
|
||||||
treeview.grab_focus()
|
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()
|
row_store.clear()
|
||||||
status = array_context[0][0]
|
status = array_context[0][0]
|
||||||
treeview = self.get_treeview()
|
treeview = self.get_treeview()
|
||||||
|
@ -606,11 +615,7 @@ class ButtonBox(Gtk.Box):
|
||||||
for col in cols:
|
for col in cols:
|
||||||
col.set_title(context)
|
col.set_title(context)
|
||||||
|
|
||||||
match context:
|
self._populate(context)
|
||||||
case 'Manage': self._populate(manage)
|
|
||||||
case 'Main menu': self._populate(connect)
|
|
||||||
case 'Options': self._populate(options)
|
|
||||||
case 'Help': self._populate(help)
|
|
||||||
|
|
||||||
toggle_signal(treeview, treeview.selected_row, '_on_tree_selection_changed', True)
|
toggle_signal(treeview, treeview.selected_row, '_on_tree_selection_changed', True)
|
||||||
|
|
||||||
|
@ -1069,6 +1074,17 @@ class TreeView(Gtk.TreeView):
|
||||||
toggle_signal(self.get_outer_grid().right_panel.filters_vbox, check, '_on_check_toggle', True)
|
toggle_signal(self.get_outer_grid().right_panel.filters_vbox, check, '_on_check_toggle', True)
|
||||||
toggle_signal(self, self, '_on_keypress', 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 = GenericDialog(transient_parent, "Fetching server metadata", "WAIT")
|
||||||
wait_dialog.show_all()
|
wait_dialog.show_all()
|
||||||
thread = threading.Thread(target=self._background, args=(wait_dialog, mode))
|
thread = threading.Thread(target=self._background, args=(wait_dialog, mode))
|
||||||
|
@ -1304,6 +1320,10 @@ class AppHeaderBar(Gtk.HeaderBar):
|
||||||
self.set_decoration_layout("menu:minimize,maximize,close")
|
self.set_decoration_layout("menu:minimize,maximize,close")
|
||||||
self.set_show_close_button(True)
|
self.set_show_close_button(True)
|
||||||
|
|
||||||
|
class Port(Enum):
|
||||||
|
# Contains enums for LanButtonDialog ports
|
||||||
|
DEFAULT = 1
|
||||||
|
CUSTOM = 2
|
||||||
|
|
||||||
class GenericDialog(Gtk.MessageDialog):
|
class GenericDialog(Gtk.MessageDialog):
|
||||||
def __init__(self, parent, text, mode):
|
def __init__(self, parent, text, mode):
|
||||||
|
@ -1361,6 +1381,140 @@ class GenericDialog(Gtk.MessageDialog):
|
||||||
self.format_secondary_text(text)
|
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):
|
def ChangelogDialog(parent, text, mode):
|
||||||
|
|
||||||
dialog = GenericDialog(parent, text, mode)
|
dialog = GenericDialog(parent, text, mode)
|
||||||
|
|
Loading…
Reference in a new issue