mirror of
https://github.com/liberatedsystems/openCom-Companion.git
synced 2024-12-27 20:48:39 +01:00
Merge remote-tracking branch 'upstream/main'
This commit is contained in:
commit
55f6574297
@ -7,7 +7,7 @@ clean:
|
||||
-(rm ./__pycache__ -r)
|
||||
-(rm ./app_storage -r)
|
||||
-(rm ./share/pkg/* -r)
|
||||
-(rm ./share/mirrors/* -r)
|
||||
-(rm ./share/mirrors/* -rf)
|
||||
-(rm ./bin -r)
|
||||
|
||||
cleanlibs:
|
||||
@ -66,13 +66,14 @@ fetchshare:
|
||||
cp ../../dist_archive/lxmf-*-py3-none-any.whl ./share/pkg/
|
||||
cp ../../dist_archive/nomadnet-*-py3-none-any.whl ./share/pkg/
|
||||
cp ../../dist_archive/rnsh-*-py3-none-any.whl ./share/pkg/
|
||||
# cp ../../dist_archive/sbapp-*-py3-none-any.whl ./share/pkg/
|
||||
cp ../../dist_archive/RNode_Firmware_*_Source.zip ./share/pkg/
|
||||
zip --junk-paths ./share/pkg/example_plugins.zip ../docs/example_plugins/*.py
|
||||
cp -r ../../dist_archive/reticulum.network ./share/mirrors/
|
||||
cp -r ../../dist_archive/unsigned.io ./share/mirrors/
|
||||
cp ../../dist_archive/Reticulum\ Manual.pdf ./share/mirrors/Reticulum_Manual.pdf
|
||||
cp ../../dist_archive/Reticulum\ Manual.epub ./share/mirrors/Reticulum_Manual.epub
|
||||
cp -r ../../rnode-flasher ./share/mirrors/
|
||||
-(rm ./share/mirrors/rnode-flasher/.git -rf)
|
||||
|
||||
release:
|
||||
. venv/bin/activate; buildozer android release
|
||||
|
BIN
sbapp/assets/fonts/BigBlueTerm437NerdFont-Regular.ttf
Normal file
BIN
sbapp/assets/fonts/BigBlueTerm437NerdFont-Regular.ttf
Normal file
Binary file not shown.
BIN
sbapp/assets/fonts/RobotoMonoNerdFont-Regular.ttf
Normal file
BIN
sbapp/assets/fonts/RobotoMonoNerdFont-Regular.ttf
Normal file
Binary file not shown.
@ -10,7 +10,7 @@ source.exclude_patterns = app_storage/*,venv/*,Makefile,./Makefil*,requirements,
|
||||
|
||||
version.regex = __version__ = ['"](.*)['"]
|
||||
version.filename = %(source.dir)s/main.py
|
||||
android.numeric_version = 20241020
|
||||
android.numeric_version = 20241213
|
||||
|
||||
requirements = kivy==2.3.0,libbz2,pillow==10.2.0,qrcode==7.3.1,usb4a,usbserial4a,able_recipe,libwebp,libogg,libopus,opusfile,numpy,cryptography,ffpyplayer,codec2,pycodec2,sh,pynacl,typing-extensions
|
||||
|
||||
|
234
sbapp/main.py
234
sbapp/main.py
@ -24,6 +24,9 @@ import base64
|
||||
import threading
|
||||
import RNS.vendor.umsgpack as msgpack
|
||||
|
||||
WINDOW_DEFAULT_WIDTH = "494"
|
||||
WINDOW_DEFAULT_HEIGHT = "800"
|
||||
|
||||
app_ui_scaling_path = None
|
||||
def apply_ui_scale():
|
||||
global app_ui_scaling_path
|
||||
@ -171,6 +174,11 @@ if not args.daemon:
|
||||
local = os.path.dirname(__file__)
|
||||
sys.path.append(local)
|
||||
|
||||
if not RNS.vendor.platformutils.is_android():
|
||||
from kivy.config import Config
|
||||
Config.set("graphics", "width", WINDOW_DEFAULT_WIDTH)
|
||||
Config.set("graphics", "height", WINDOW_DEFAULT_HEIGHT)
|
||||
|
||||
if args.daemon:
|
||||
from .sideband.core import SidebandCore
|
||||
class DaemonElement():
|
||||
@ -558,6 +566,15 @@ class SidebandApp(MDApp):
|
||||
fn_italic=fb_path+"NotoSans-Italic.ttf",
|
||||
fn_bolditalic=fb_path+"NotoSans-BoldItalic.ttf")
|
||||
|
||||
LabelBase.register(name="mono",
|
||||
fn_regular=fb_path+"RobotoMonoNerdFont-Regular.ttf")
|
||||
|
||||
LabelBase.register(name="term",
|
||||
fn_regular=fb_path+"BigBlueTerm437NerdFont-Regular.ttf")
|
||||
|
||||
LabelBase.register(name="nf",
|
||||
fn_regular=fb_path+"RobotoMonoNerdFont-Regular.ttf")
|
||||
|
||||
def update_input_language(self):
|
||||
language = self.sideband.config["input_language"]
|
||||
if language == None:
|
||||
@ -1406,6 +1423,8 @@ class SidebandApp(MDApp):
|
||||
self.close_sub_telemetry_action()
|
||||
elif self.root.ids.screen_manager.current == "rnstatus_screen":
|
||||
self.close_sub_utilities_action()
|
||||
elif self.root.ids.screen_manager.current == "logviewer_screen":
|
||||
self.close_sub_utilities_action()
|
||||
else:
|
||||
self.open_conversations(direction="right")
|
||||
|
||||
@ -2501,30 +2520,35 @@ class SidebandApp(MDApp):
|
||||
return "Could not retrieve connectivity status"
|
||||
|
||||
def connectivity_status(self, sender):
|
||||
hs = dp(22)
|
||||
if RNS.vendor.platformutils.is_android():
|
||||
hs = dp(22)
|
||||
yes_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
|
||||
dialog = MDDialog(
|
||||
title="Connectivity Status",
|
||||
text=str(self.get_connectivity_text()),
|
||||
buttons=[ yes_button ],
|
||||
# elevation=0,
|
||||
)
|
||||
def cs_updater(dt):
|
||||
dialog.text = str(self.get_connectivity_text())
|
||||
def dl_yes(s):
|
||||
self.connectivity_updater.cancel()
|
||||
dialog.dismiss()
|
||||
if self.connectivity_updater != None:
|
||||
self.connectivity_updater.cancel()
|
||||
|
||||
yes_button.bind(on_release=dl_yes)
|
||||
dialog.open()
|
||||
|
||||
yes_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
|
||||
dialog = MDDialog(
|
||||
title="Connectivity Status",
|
||||
text=str(self.get_connectivity_text()),
|
||||
buttons=[ yes_button ],
|
||||
# elevation=0,
|
||||
)
|
||||
def cs_updater(dt):
|
||||
dialog.text = str(self.get_connectivity_text())
|
||||
def dl_yes(s):
|
||||
self.connectivity_updater.cancel()
|
||||
dialog.dismiss()
|
||||
if self.connectivity_updater != None:
|
||||
self.connectivity_updater.cancel()
|
||||
|
||||
yes_button.bind(on_release=dl_yes)
|
||||
dialog.open()
|
||||
self.connectivity_updater = Clock.schedule_interval(cs_updater, 2.0)
|
||||
|
||||
if self.connectivity_updater != None:
|
||||
self.connectivity_updater.cancel()
|
||||
|
||||
self.connectivity_updater = Clock.schedule_interval(cs_updater, 2.0)
|
||||
else:
|
||||
if not self.utilities_ready:
|
||||
self.utilities_init()
|
||||
self.utilities_screen.rnstatus_action()
|
||||
|
||||
def ingest_lxm_action(self, sender):
|
||||
def cb(dt):
|
||||
@ -3009,6 +3033,11 @@ class SidebandApp(MDApp):
|
||||
self.sideband.save_configuration()
|
||||
self.update_ui_theme()
|
||||
|
||||
def save_classic_message_colors(sender=None, event=None):
|
||||
self.sideband.config["classic_message_colors"] = self.settings_screen.ids.settings_classic_message_colors.active
|
||||
self.sideband.save_configuration()
|
||||
self.update_ui_theme()
|
||||
|
||||
def save_display_style_in_contact_list(sender=None, event=None):
|
||||
self.sideband.config["display_style_in_contact_list"] = self.settings_screen.ids.display_style_in_contact_list.active
|
||||
self.sideband.save_configuration()
|
||||
@ -3019,6 +3048,10 @@ class SidebandApp(MDApp):
|
||||
self.sideband.save_configuration()
|
||||
self.sideband.setstate("wants.viewupdate.conversations", True)
|
||||
|
||||
def save_trusted_markup_only(sender=None, event=None):
|
||||
self.sideband.config["trusted_markup_only"] = self.settings_screen.ids.settings_trusted_markup_only.active
|
||||
self.sideband.save_configuration()
|
||||
|
||||
def save_advanced_stats(sender=None, event=None):
|
||||
self.sideband.config["advanced_stats"] = self.settings_screen.ids.settings_advanced_statistics.active
|
||||
self.sideband.save_configuration()
|
||||
@ -3170,6 +3203,9 @@ class SidebandApp(MDApp):
|
||||
self.settings_screen.ids.settings_eink_mode.active = self.sideband.config["eink_mode"]
|
||||
self.settings_screen.ids.settings_eink_mode.bind(active=save_eink_mode)
|
||||
|
||||
self.settings_screen.ids.settings_classic_message_colors.active = self.sideband.config["classic_message_colors"]
|
||||
self.settings_screen.ids.settings_classic_message_colors.bind(active=save_classic_message_colors)
|
||||
|
||||
self.settings_screen.ids.display_style_in_contact_list.active = self.sideband.config["display_style_in_contact_list"]
|
||||
self.settings_screen.ids.display_style_in_contact_list.bind(active=save_display_style_in_contact_list)
|
||||
|
||||
@ -3191,6 +3227,9 @@ class SidebandApp(MDApp):
|
||||
self.settings_screen.ids.settings_lxmf_ignore_unknown.active = self.sideband.config["lxmf_ignore_unknown"]
|
||||
self.settings_screen.ids.settings_lxmf_ignore_unknown.bind(active=save_lxmf_ignore_unknown)
|
||||
|
||||
self.settings_screen.ids.settings_trusted_markup_only.active = self.sideband.config["trusted_markup_only"]
|
||||
self.settings_screen.ids.settings_trusted_markup_only.bind(active=save_trusted_markup_only)
|
||||
|
||||
self.settings_screen.ids.settings_ignore_invalid_stamps.active = self.sideband.config["lxmf_ignore_invalid_stamps"]
|
||||
self.settings_screen.ids.settings_ignore_invalid_stamps.bind(active=save_lxmf_ignore_invalid_stamps)
|
||||
|
||||
@ -3639,6 +3678,7 @@ class SidebandApp(MDApp):
|
||||
######################################
|
||||
def repository_action(self, sender=None, direction="left"):
|
||||
if self.repository_ready:
|
||||
self.repository_update_info()
|
||||
self.repository_open(direction=direction)
|
||||
else:
|
||||
self.loader_action(direction=direction)
|
||||
@ -3675,9 +3715,9 @@ class SidebandApp(MDApp):
|
||||
info += "If you want to share the openCom Companion application itself via the repository server, you must first download it into the local repository, using the \"Update Content\" button below.\n\n"
|
||||
info += "To make the repository available on your local network, simply start it below, and it will become browsable on a local IP address for anyone connected to the same WiFi or wired network.\n\n"
|
||||
if self.sideband.webshare_server != None:
|
||||
if RNS.vendor.platformutils.is_android():
|
||||
def getIP():
|
||||
adrs = []
|
||||
def getIP():
|
||||
adrs = []
|
||||
if RNS.vendor.platformutils.is_android():
|
||||
try:
|
||||
from jnius import autoclass
|
||||
import ipaddress
|
||||
@ -3700,24 +3740,30 @@ class SidebandApp(MDApp):
|
||||
RNS.log("Error while getting repository IP address: "+str(e), RNS.LOG_ERROR)
|
||||
return None
|
||||
|
||||
return adrs
|
||||
|
||||
ips = getIP()
|
||||
if ips == None or len(ips) == 0:
|
||||
info += "The repository server is running, but the local device IP address could not be determined.\n\nYou can access the repository by pointing a browser to: http://DEVICE_IP:4444/"
|
||||
self.reposository_url = None
|
||||
else:
|
||||
ipstr = ""
|
||||
for ip in ips:
|
||||
ipstr += "http://"+str(ip)+":4444/\n"
|
||||
self.reposository_url = ipstr
|
||||
import socket
|
||||
adrs.append(socket.gethostbyname(socket.gethostname()))
|
||||
|
||||
ms = "" if len(ips) == 1 else "es"
|
||||
info += "The repository server is running at the following address"+ms+":\n [u][ref=link]"+ipstr+"[/ref][u]"
|
||||
self.repository_screen.ids.repository_info.bind(on_ref_press=self.repository_link_action)
|
||||
return adrs
|
||||
|
||||
self.repository_screen.ids.repository_enable_button.disabled = True
|
||||
self.repository_screen.ids.repository_disable_button.disabled = False
|
||||
ips = getIP()
|
||||
if ips == None or len(ips) == 0:
|
||||
info += "The repository server is running, but the local device IP address could not be determined.\n\nYou can access the repository by pointing a browser to: https://DEVICE_IP:4444/"
|
||||
self.reposository_url = None
|
||||
else:
|
||||
ipstr = ""
|
||||
for ip in ips:
|
||||
ipstr += "https://"+str(ip)+":4444/\n"
|
||||
self.reposository_url = ipstr
|
||||
|
||||
ms = "" if len(ips) == 1 else "es"
|
||||
info += "The repository server is running at the following address"+ms+":\n [u][ref=link]"+ipstr+"[/ref][u]"
|
||||
self.repository_screen.ids.repository_info.bind(on_ref_press=self.repository_link_action)
|
||||
|
||||
def cb(dt):
|
||||
self.repository_screen.ids.repository_enable_button.disabled = True
|
||||
self.repository_screen.ids.repository_disable_button.disabled = False
|
||||
Clock.schedule_once(cb, 0.1)
|
||||
|
||||
else:
|
||||
self.repository_screen.ids.repository_enable_button.disabled = False
|
||||
@ -3739,39 +3785,85 @@ class SidebandApp(MDApp):
|
||||
def update_job(sender=None):
|
||||
try:
|
||||
import requests
|
||||
### RNode Firmwares ###########
|
||||
if True:
|
||||
downloads = []
|
||||
try:
|
||||
release_url = "https://api.github.com/repos/markqvist/rnode_firmware/releases"
|
||||
with requests.get(release_url) as response:
|
||||
releases = response.json()
|
||||
release = releases[0]
|
||||
assets = release["assets"]
|
||||
for asset in assets:
|
||||
if asset["name"].lower().startswith("rnode_firmware"):
|
||||
fw_url = asset["browser_download_url"]
|
||||
pkgname = asset["name"]
|
||||
fw_version = release["tag_name"]
|
||||
RNS.log(f"Found version {fw_version} artefact {pkgname} at {fw_url}", RNS.LOG_DEBUG)
|
||||
downloads.append([fw_url, pkgname, fw_version])
|
||||
|
||||
# Get release info
|
||||
apk_version = None
|
||||
apk_url = None
|
||||
pkgname = None
|
||||
try:
|
||||
release_url = "https://api.github.com/repos/markqvist/sideband/releases"
|
||||
with requests.get(release_url) as response:
|
||||
releases = response.json()
|
||||
release = releases[0]
|
||||
assets = release["assets"]
|
||||
for asset in assets:
|
||||
if asset["name"].lower().endswith(".apk"):
|
||||
apk_url = asset["browser_download_url"]
|
||||
pkgname = asset["name"]
|
||||
apk_version = release["tag_name"]
|
||||
RNS.log(f"Found version {apk_version} artefact {pkgname} at {apk_url}")
|
||||
except Exception as e:
|
||||
self.repository_screen.ids.repository_update.text = f"Downloading release info failed with the error:\n"+str(e)
|
||||
return
|
||||
except Exception as e:
|
||||
self.repository_screen.ids.repository_update.text = f"Downloading RNode firmware release info failed with the error:\n"+str(e)
|
||||
return
|
||||
|
||||
self.repository_screen.ids.repository_update.text = "Downloading: "+str(apk_url)
|
||||
with requests.get(apk_url, stream=True) as response:
|
||||
with open("./dl_tmp", "wb") as tmp_file:
|
||||
cs = 32*1024
|
||||
tds = 0
|
||||
for chunk in response.iter_content(chunk_size=cs):
|
||||
tmp_file.write(chunk)
|
||||
tds += cs
|
||||
self.repository_screen.ids.repository_update.text = "Downloaded "+RNS.prettysize(tds)+" of "+str(pkgname)
|
||||
try:
|
||||
for download in downloads:
|
||||
fw_url = download[0]
|
||||
pkgname = download[1]
|
||||
self.repository_screen.ids.repository_update.text = "Downloading: "+str(pkgname)
|
||||
with requests.get(fw_url, stream=True) as response:
|
||||
with open("./dl_tmp", "wb") as tmp_file:
|
||||
cs = 32*1024
|
||||
tds = 0
|
||||
for chunk in response.iter_content(chunk_size=cs):
|
||||
tmp_file.write(chunk)
|
||||
tds += cs
|
||||
self.repository_screen.ids.repository_update.text = "Downloaded "+RNS.prettysize(tds)+" of "+str(pkgname)
|
||||
|
||||
os.rename("./dl_tmp", f"{self.sideband.webshare_dir}/pkg/{pkgname}")
|
||||
self.repository_screen.ids.repository_update.text = f"Added {pkgname} to the repository!"
|
||||
|
||||
except Exception as e:
|
||||
self.repository_screen.ids.repository_update.text = f"Downloading RNode firmware failed with the error:\n"+str(e)
|
||||
return
|
||||
|
||||
### Sideband APK File #########
|
||||
if True:
|
||||
# Get release info
|
||||
apk_version = None
|
||||
apk_url = None
|
||||
pkgname = None
|
||||
try:
|
||||
release_url = "https://api.github.com/repos/markqvist/sideband/releases"
|
||||
with requests.get(release_url) as response:
|
||||
releases = response.json()
|
||||
release = releases[0]
|
||||
assets = release["assets"]
|
||||
for asset in assets:
|
||||
if asset["name"].lower().endswith(".apk"):
|
||||
apk_url = asset["browser_download_url"]
|
||||
pkgname = asset["name"]
|
||||
apk_version = release["tag_name"]
|
||||
RNS.log(f"Found version {apk_version} artefact {pkgname} at {apk_url}", RNS.LOG_DEBUG)
|
||||
except Exception as e:
|
||||
self.repository_screen.ids.repository_update.text = f"Downloading Sideband APK release info failed with the error:\n"+str(e)
|
||||
return
|
||||
|
||||
self.repository_screen.ids.repository_update.text = "Downloading: "+str(pkgname)
|
||||
with requests.get(apk_url, stream=True) as response:
|
||||
with open("./dl_tmp", "wb") as tmp_file:
|
||||
cs = 32*1024
|
||||
tds = 0
|
||||
for chunk in response.iter_content(chunk_size=cs):
|
||||
tmp_file.write(chunk)
|
||||
tds += cs
|
||||
self.repository_screen.ids.repository_update.text = "Downloaded "+RNS.prettysize(tds)+" of "+str(pkgname)
|
||||
|
||||
os.rename("./dl_tmp", f"{self.sideband.webshare_dir}/pkg/{pkgname}")
|
||||
self.repository_screen.ids.repository_update.text = f"Added {pkgname} to the repository!"
|
||||
|
||||
self.repository_screen.ids.repository_update.text = f"Repository contents updated successfully!"
|
||||
|
||||
os.rename("./dl_tmp", f"./share/pkg/{pkgname}")
|
||||
self.repository_screen.ids.repository_update.text = f"Added {pkgname} to the repository!"
|
||||
except Exception as e:
|
||||
self.repository_screen.ids.repository_update.text = f"Downloading contents failed with the error:\n"+str(e)
|
||||
|
||||
@ -3788,15 +3880,7 @@ class SidebandApp(MDApp):
|
||||
self.root.ids.screen_manager.add_widget(self.repository_screen)
|
||||
|
||||
self.repository_screen.ids.repository_scrollview.effect_cls = ScrollEffect
|
||||
|
||||
self.repository_update_info()
|
||||
|
||||
if not RNS.vendor.platformutils.is_android():
|
||||
self.widget_hide(self.repository_screen.ids.repository_enable_button)
|
||||
self.widget_hide(self.repository_screen.ids.repository_disable_button)
|
||||
self.widget_hide(self.repository_screen.ids.repository_download_button)
|
||||
self.repository_screen.ids.repository_info.text = "\nThe [b]Repository Webserver[/b] feature is currently only available on mobile devices."
|
||||
|
||||
self.repository_ready = True
|
||||
|
||||
def close_repository_action(self, sender=None):
|
||||
|
33
sbapp/share/flasher.html
Normal file
33
sbapp/share/flasher.html
Normal file
@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="css/water.css">
|
||||
<link rel="shortcut icon" type="image/x-icon" href="gfx/icon.png">
|
||||
<meta charset="utf-8"/>
|
||||
<title>Sideband Repository</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
</head>
|
||||
<body>
|
||||
<span class="logo">RNode Flasher</span>
|
||||
<p><center><span class="menu"><a href="index.html">Start</a> | <a href="pkgs.html">Software</a> | <a href="flasher.html">RNode Flasher</a> | <a href="guides.html">Guides</a></span></center></p><hr>
|
||||
<br/>Sideband includes a copy of the web-based RNode Flasher developed by <a href="https://github.com/liamcottle/rnode-flasher">Liam Cottle</a>. You can use this flasher to install and provision the RNode firmware on any compatible boards.<br/>
|
||||
<br/>
|
||||
<b>Please note!</b> Your browser must support Web-USB for this to work.<br/>
|
||||
<br/>
|
||||
To use the flasher, you will need firmware packages for the boards you want use. You can obtain these in different ways:
|
||||
<ul>
|
||||
<li>Sideband can automatically download the latest release of these, and include them directly in this repository.
|
||||
<ul>
|
||||
<li>To do so, go to the <b>Repository</b> menu item, and select <b>Update Contents</b>.</li>
|
||||
<li>After that, they will be available on the <a href="pkgs.html">Software</a> page of this repository.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>You can download them manually from <a href="https://github.com/markqvist/RNode_Firmware/releases/latest">the latest release page on GitHub</a>.</li>
|
||||
<li>You can compile them yourself from the RNode Firmware source code package <a href="pkgs.html">included in this repository</a>.</li>
|
||||
</ul>
|
||||
<br/>
|
||||
<center><a href="/mirrors/rnode-flasher/index.html"><button type="button" id="task-replicate">Launch Web Flasher</button></a></center>
|
||||
<br/>
|
||||
<hr>
|
||||
<p><center></p>
|
||||
</body></html>
|
@ -9,7 +9,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<span class="logo">Guides</span>
|
||||
<p><center><span class="menu"><a href="index.html">Start</a> | <a href="pkgs.html">Software</a> | <a href="guides.html">Guides</a></span></center></p><hr>
|
||||
<p><center><span class="menu"><a href="index.html">Start</a> | <a href="pkgs.html">Software</a> | <a href="flasher.html">RNode Flasher</a> | <a href="guides.html">Guides</a></span></center></p><hr><br/>
|
||||
Welcome to the <b>Guide Section</b>!<br/><br/>From here, you can browse or download various included manuals, documentation, references and guides.
|
||||
|
||||
<ul>
|
||||
@ -19,6 +19,7 @@ Welcome to the <b>Guide Section</b>!<br/><br/>From here, you can browse or downl
|
||||
<li><a href="./mirrors/Reticulum_Manual.epub">Download the Reticulum Manual in EPUB format</a></li>
|
||||
<li><a href="./mirrors/reticulum.network/index.html">Browse a local copy of the Reticulum website</a></li>
|
||||
</ul>
|
||||
<br/>
|
||||
<hr>
|
||||
<p><center></p>
|
||||
</body></html>
|
||||
|
@ -9,8 +9,9 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
</head>
|
||||
<body>
|
||||
<<<<<<< HEAD
|
||||
<span class="logo">openCom Companion Repo</span>
|
||||
<p><center><span class="menu"><a href="index.html">Start</a> | <a href="pkgs.html">Software</a> | <a href="guides.html">Guides</a></span></center></p><hr><h2>Hello!</h2>
|
||||
<p><center><span class="menu"><a href="index.html">Start</a> | <a href="pkgs.html">Software</a> | <a href="flasher.html">RNode Flasher</a> |<a href="guides.html">Guides</a></span></center></p><hr><h2>Hello!</h2>
|
||||
<table style="margin-bottom: 1.5em;">
|
||||
<tbody>
|
||||
<tr>
|
||||
|
@ -9,10 +9,10 @@
|
||||
</head>
|
||||
<body>
|
||||
<span class="logo">Software</span>
|
||||
<p><center><span class="menu"><a href="index.html">Start</a> | <a href="pkgs.html">Software</a> | <a href="guides.html">Guides</a></span></center></p><hr>
|
||||
<p><center><span class="menu"><a href="index.html">Start</a> | <a href="pkgs.html">Software</a> | <a href="flasher.html">RNode Flasher</a> | <a href="guides.html">Guides</a></span></center></p><hr><br/>
|
||||
Welcome to the <b>Software Library</b>!<br/><br/>From here, you can download installable Python Wheel packages of Reticulum and various other auxillary programs and utilities.
|
||||
<ul id="filelist">
|
||||
</ul>
|
||||
</ul><br/>
|
||||
<hr>
|
||||
<p><center></p>
|
||||
</body></html>
|
||||
|
101
sbapp/sideband/certgen.py
Normal file
101
sbapp/sideband/certgen.py
Normal file
@ -0,0 +1,101 @@
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2024 Mark Qvist / unsigned.io.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
KEY_PASSPHRASE = None
|
||||
LOADED_KEY = None
|
||||
|
||||
import os
|
||||
import RNS
|
||||
import datetime
|
||||
from cryptography import x509
|
||||
from cryptography.x509.oid import NameOID
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
||||
|
||||
from cryptography import __version__ as cryptography_version_str
|
||||
try:
|
||||
cryptography_major_version = int(cryptography_version_str.split(".")[0])
|
||||
except:
|
||||
RNS.log(f"Could not determine PyCA/cryptography version: {e}", RNS.LOG_ERROR)
|
||||
RNS.log(f"Assuming recent version with automatic backend selection", RNS.LOG_ERROR)
|
||||
|
||||
def get_key(key_path, force_reload=False):
|
||||
KEY_PATH = key_path
|
||||
key = None
|
||||
if LOADED_KEY != None and not force_reload:
|
||||
return LOADED_KEY
|
||||
elif os.path.isfile(KEY_PATH):
|
||||
with open(KEY_PATH, "rb") as f:
|
||||
key = load_pem_private_key(f.read(), KEY_PASSPHRASE)
|
||||
else:
|
||||
if cryptography_major_version > 3:
|
||||
key = ec.generate_private_key(curve=ec.SECP256R1())
|
||||
else:
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
key = ec.generate_private_key(curve=ec.SECP256R1(), backend=default_backend())
|
||||
|
||||
if KEY_PASSPHRASE == None:
|
||||
key_encryption = serialization.NoEncryption()
|
||||
else:
|
||||
key_encryption = serialization.BestAvailableEncryption(KEY_PASSPHRASE)
|
||||
|
||||
with open(KEY_PATH, "wb") as f:
|
||||
f.write(key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.PKCS8,
|
||||
encryption_algorithm=key_encryption))
|
||||
|
||||
return key
|
||||
|
||||
def gen_cert(cert_path, key):
|
||||
CERT_PATH = cert_path
|
||||
cert_attrs = [x509.NameAttribute(NameOID.COUNTRY_NAME, "NA"),
|
||||
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "None"),
|
||||
x509.NameAttribute(NameOID.LOCALITY_NAME, "Earth"),
|
||||
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Sideband"),
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, "Sideband Repository")]
|
||||
|
||||
issuer = x509.Name(cert_attrs)
|
||||
subject = issuer
|
||||
|
||||
cb = x509.CertificateBuilder()
|
||||
cb = cb.subject_name(subject)
|
||||
cb = cb.issuer_name(issuer)
|
||||
cb = cb.public_key(key.public_key())
|
||||
cb = cb.serial_number(x509.random_serial_number())
|
||||
cb = cb.not_valid_before(datetime.datetime.now(datetime.timezone.utc)+datetime.timedelta(days=-14))
|
||||
cb = cb.not_valid_after(datetime.datetime.now(datetime.timezone.utc)+datetime.timedelta(days=3652))
|
||||
cb = cb.add_extension(x509.SubjectAlternativeName([x509.DNSName("localhost")]), critical=False)
|
||||
if cryptography_major_version > 3:
|
||||
cert = cb.sign(key, hashes.SHA256())
|
||||
else:
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
cert = cb.sign(key, hashes.SHA256(), backend=default_backend())
|
||||
|
||||
with open(CERT_PATH, "wb") as f:
|
||||
f.write(cert.public_bytes(serialization.Encoding.PEM))
|
||||
|
||||
def ensure_certificate(key_path, cert_path):
|
||||
gen_cert(cert_path, get_key(key_path))
|
||||
return cert_path
|
@ -16,6 +16,7 @@ import multiprocessing.connection
|
||||
|
||||
from copy import deepcopy
|
||||
from threading import Lock
|
||||
from collections import deque
|
||||
from .res import sideband_fb_data
|
||||
from .sense import Telemeter, Commands
|
||||
from .plugins import SidebandCommandPlugin, SidebandServicePlugin, SidebandTelemetryPlugin
|
||||
@ -44,7 +45,7 @@ class PropagationNodeDetector():
|
||||
|
||||
aspect_filter = "lxmf.propagation"
|
||||
|
||||
def received_announce(self, destination_hash, announced_identity, app_data):
|
||||
def received_announce(self, destination_hash, announced_identity, app_data, announce_packet_hash):
|
||||
try:
|
||||
if app_data != None and len(app_data) > 0:
|
||||
if pn_announce_data_is_valid(app_data):
|
||||
@ -63,8 +64,12 @@ class PropagationNodeDetector():
|
||||
# age = 0
|
||||
pass
|
||||
|
||||
link_stats = {"rssi": self.owner_app.sideband.reticulum.get_packet_rssi(announce_packet_hash),
|
||||
"snr": self.owner_app.sideband.reticulum.get_packet_snr(announce_packet_hash),
|
||||
"q": self.owner_app.sideband.reticulum.get_packet_q(announce_packet_hash)}
|
||||
|
||||
RNS.log("Detected active propagation node "+RNS.prettyhexrep(destination_hash)+" emission "+str(age)+" seconds ago, "+str(hops)+" hops away")
|
||||
self.owner.log_announce(destination_hash, app_data, dest_type=PropagationNodeDetector.aspect_filter)
|
||||
self.owner.log_announce(destination_hash, app_data, dest_type=PropagationNodeDetector.aspect_filter, link_stats=link_stats)
|
||||
|
||||
if self.owner.config["lxmf_propagation_node"] == None:
|
||||
if self.owner.active_propagation_node == None:
|
||||
@ -110,11 +115,17 @@ class SidebandCore():
|
||||
|
||||
DEFAULT_APPEARANCE = ["account", [0,0,0,1], [1,1,1,1]]
|
||||
|
||||
LOG_DEQUE_MAXLEN = 128
|
||||
|
||||
aspect_filter = "lxmf.delivery"
|
||||
def received_announce(self, destination_hash, announced_identity, app_data):
|
||||
def received_announce(self, destination_hash, announced_identity, app_data, announce_packet_hash):
|
||||
# Add the announce to the directory announce
|
||||
# stream logger
|
||||
|
||||
link_stats = {"rssi": self.reticulum.get_packet_rssi(announce_packet_hash),
|
||||
"snr": self.reticulum.get_packet_snr(announce_packet_hash),
|
||||
"q": self.reticulum.get_packet_q(announce_packet_hash)}
|
||||
|
||||
# This reformats the new v0.5.0 announce data back to the expected format
|
||||
# for Sidebands database and other handling functions.
|
||||
dn = LXMF.display_name_from_app_data(app_data)
|
||||
@ -123,7 +134,7 @@ class SidebandCore():
|
||||
if dn != None:
|
||||
app_data = dn.encode("utf-8")
|
||||
|
||||
self.log_announce(destination_hash, app_data, dest_type=SidebandCore.aspect_filter, stamp_cost=sc)
|
||||
self.log_announce(destination_hash, app_data, dest_type=SidebandCore.aspect_filter, stamp_cost=sc, link_stats=link_stats)
|
||||
|
||||
def __init__(self, owner_app, config_path = None, is_service=False, is_client=False, android_app_dir=None, verbose=False, owner_service=None, service_context=None, is_daemon=False, load_config_only=False):
|
||||
self.is_service = is_service
|
||||
@ -142,6 +153,7 @@ class SidebandCore():
|
||||
self.is_standalone = False
|
||||
|
||||
self.log_verbose = verbose
|
||||
self.log_deque = deque(maxlen=self.LOG_DEQUE_MAXLEN)
|
||||
self.owner_app = owner_app
|
||||
self.reticulum = None
|
||||
self.webshare_server = None
|
||||
@ -156,6 +168,7 @@ class SidebandCore():
|
||||
self.telemetry_send_blocked_until = 0
|
||||
self.pending_telemetry_request = False
|
||||
self.telemetry_request_max_history = 7*24*60*60
|
||||
self.live_tracked_objects = {}
|
||||
self.default_lxm_limit = 128*1000
|
||||
self.state_db = {}
|
||||
self.state_lock = Lock()
|
||||
@ -167,6 +180,7 @@ class SidebandCore():
|
||||
self.owner_service = owner_service
|
||||
self.allow_service_dispatch = True
|
||||
self.version_str = ""
|
||||
self.config_template = rns_config
|
||||
|
||||
if config_path == None:
|
||||
self.app_dir = plyer.storagepath.get_home_dir()+"/.config/occ"
|
||||
@ -228,7 +242,14 @@ class SidebandCore():
|
||||
self.log_dir = self.app_dir+"/app_storage/"
|
||||
self.tmp_dir = self.app_dir+"/app_storage/tmp"
|
||||
self.exports_dir = self.app_dir+"/exports"
|
||||
self.webshare_dir = "./share/"
|
||||
if RNS.vendor.platformutils.is_android():
|
||||
self.webshare_dir = "./share/"
|
||||
else:
|
||||
sideband_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
self.webshare_dir = os.path.abspath(os.path.join(sideband_dir, "..", "share"))
|
||||
|
||||
self.webshare_ssl_key_path = self.app_dir+"/app_storage/ssl_key.pem"
|
||||
self.webshare_ssl_cert_path = self.app_dir+"/app_storage/ssl_cert.pem"
|
||||
|
||||
self.first_run = True
|
||||
self.saving_configuration = False
|
||||
@ -267,6 +288,29 @@ class SidebandCore():
|
||||
if load_config_only:
|
||||
return
|
||||
|
||||
if RNS.vendor.platformutils.is_android():
|
||||
if self.config["config_template"] != None:
|
||||
try:
|
||||
if not os.path.isfile(self.rns_configdir+"/config_template_invalid"):
|
||||
if self.is_service:
|
||||
with open(self.rns_configdir+"/config_template_invalid", "w") as invalidation_file:
|
||||
invalidation_file.write("\n")
|
||||
|
||||
ct = self.config["config_template"]
|
||||
RNS.log(f"Loading modified RNS config template", RNS.LOG_WARNING)
|
||||
self.config_template = ct
|
||||
|
||||
else:
|
||||
RNS.log("Custom configuration template invalid, using default configuration template", RNS.LOG_WARNING)
|
||||
self.config_template = rns_config
|
||||
if self.is_service:
|
||||
self.setstate("hardware_operation.error", "At the previous start, Sideband could not initialise Reticulum. Custom configuration template loading has been temporarily disabled. Please check and fix any errors in your configuration template.")
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"An error occurred while setting RNS configuration template: {e}", RNS.LOG_ERROR)
|
||||
RNS.log(f"Using default configuration template", RNS.LOG_ERROR)
|
||||
self.config_template = rns_config
|
||||
|
||||
# Initialise Reticulum configuration
|
||||
if RNS.vendor.platformutils.get_platform() == "android":
|
||||
try:
|
||||
@ -277,11 +321,10 @@ class SidebandCore():
|
||||
RNS.log("Configuring Reticulum instance...")
|
||||
if self.config["connect_transport"]:
|
||||
RNS.log("Enabling Reticulum Transport")
|
||||
generated_config = rns_config.replace("TRANSPORT_IS_ENABLED", "Yes")
|
||||
generated_config = self.config_template.replace("TRANSPORT_IS_ENABLED", "Yes")
|
||||
else:
|
||||
RNS.log("Not enabling Reticulum Transport")
|
||||
generated_config = rns_config.replace("TRANSPORT_IS_ENABLED", "No")
|
||||
|
||||
generated_config = self.config_template.replace("TRANSPORT_IS_ENABLED", "No")
|
||||
|
||||
config_file = open(self.rns_configdir+"/config", "wb")
|
||||
config_file.write(generated_config.encode("utf-8"))
|
||||
@ -387,7 +430,7 @@ class SidebandCore():
|
||||
self.config["debug"] = False
|
||||
self.config["display_name"] = "Anonymous Peer"
|
||||
self.config["notifications_on"] = True
|
||||
self.config["dark_ui"] = False
|
||||
self.config["dark_ui"] = True
|
||||
self.config["start_announce"] = True
|
||||
self.config["propagation_by_default"] = False
|
||||
self.config["home_node_as_broadcast_repeater"] = False
|
||||
@ -403,8 +446,9 @@ class SidebandCore():
|
||||
self.config["last_lxmf_propagation_node"] = None
|
||||
self.config["nn_home_node"] = None
|
||||
self.config["print_command"] = "lp"
|
||||
self.config["eink_mode"] = False
|
||||
self.config["eink_mode"] = True
|
||||
self.config["lxm_limit_1mb"] = True
|
||||
self.config["trusted_markup_only"] = False
|
||||
|
||||
# Connectivity
|
||||
self.config["connect_transport"] = False
|
||||
@ -569,7 +613,7 @@ class SidebandCore():
|
||||
if not "dark_ui" in self.config:
|
||||
self.config["dark_ui"] = True
|
||||
if not "advanced_stats" in self.config:
|
||||
self.config["advanced_stats"] = False
|
||||
self.config["advanced_stats"] = True
|
||||
if not "lxmf_periodic_sync" in self.config:
|
||||
self.config["lxmf_periodic_sync"] = False
|
||||
if not "lxmf_ignore_unknown" in self.config:
|
||||
@ -589,13 +633,17 @@ class SidebandCore():
|
||||
if not "print_command" in self.config:
|
||||
self.config["print_command"] = "lp"
|
||||
if not "eink_mode" in self.config:
|
||||
self.config["eink_mode"] = False
|
||||
self.config["eink_mode"] = True
|
||||
if not "classic_message_colors" in self.config:
|
||||
self.config["classic_message_colors"] = False
|
||||
if not "display_style_in_contact_list" in self.config:
|
||||
self.config["display_style_in_contact_list"] = False
|
||||
self.config["display_style_in_contact_list"] = True
|
||||
if not "lxm_limit_1mb" in self.config:
|
||||
self.config["lxm_limit_1mb"] = True
|
||||
if not "hq_ptt" in self.config:
|
||||
self.config["hq_ptt"] = False
|
||||
if not "trusted_markup_only" in self.config:
|
||||
self.config["trusted_markup_only"] = False
|
||||
|
||||
if not "input_language" in self.config:
|
||||
self.config["input_language"] = None
|
||||
@ -604,6 +652,8 @@ class SidebandCore():
|
||||
if not "block_predictive_text" in self.config:
|
||||
self.config["block_predictive_text"] = False
|
||||
|
||||
if not "config_template" in self.config:
|
||||
self.config["config_template"] = None
|
||||
if not "connect_transport" in self.config:
|
||||
self.config["connect_transport"] = False
|
||||
if not "connect_rnode" in self.config:
|
||||
@ -739,11 +789,11 @@ class SidebandCore():
|
||||
if not "telemetry_bg" in self.config:
|
||||
self.config["telemetry_bg"] = SidebandCore.DEFAULT_APPEARANCE[2]
|
||||
if not "telemetry_send_appearance" in self.config:
|
||||
self.config["telemetry_send_appearance"] = False
|
||||
self.config["telemetry_send_appearance"] = True
|
||||
if not "telemetry_display_trusted_only" in self.config:
|
||||
self.config["telemetry_display_trusted_only"] = False
|
||||
if not "display_style_from_all" in self.config:
|
||||
self.config["display_style_from_all"] = False
|
||||
self.config["display_style_from_all"] = True
|
||||
if not "telemetry_receive_trusted_only" in self.config:
|
||||
self.config["telemetry_receive_trusted_only"] = False
|
||||
|
||||
@ -854,9 +904,8 @@ class SidebandCore():
|
||||
time.sleep(0.15)
|
||||
try:
|
||||
self.saving_configuration = True
|
||||
config_file = open(self.config_path, "wb")
|
||||
config_file.write(msgpack.packb(self.config))
|
||||
config_file.close()
|
||||
with open(self.config_path, "wb") as config_file:
|
||||
config_file.write(msgpack.packb(self.config))
|
||||
self.saving_configuration = False
|
||||
except Exception as e:
|
||||
self.saving_configuration = False
|
||||
@ -987,14 +1036,14 @@ class SidebandCore():
|
||||
else:
|
||||
plyer.notification.notify(title, content, app_icon=self.icon_32)
|
||||
|
||||
def log_announce(self, dest, app_data, dest_type, stamp_cost=None):
|
||||
def log_announce(self, dest, app_data, dest_type, stamp_cost=None, link_stats=None):
|
||||
try:
|
||||
if app_data == None:
|
||||
app_data = b""
|
||||
if type(app_data) != bytes:
|
||||
app_data = msgpack.packb([app_data, stamp_cost])
|
||||
RNS.log("Received "+str(dest_type)+" announce for "+RNS.prettyhexrep(dest)+" with data: "+str(app_data), RNS.LOG_DEBUG)
|
||||
self._db_save_announce(dest, app_data, dest_type)
|
||||
self._db_save_announce(dest, app_data, dest_type, link_stats)
|
||||
self.setstate("app.flags.new_announces", True)
|
||||
|
||||
except Exception as e:
|
||||
@ -1316,7 +1365,7 @@ class SidebandCore():
|
||||
else:
|
||||
return False
|
||||
|
||||
def request_latest_telemetry(self, from_addr=None):
|
||||
def request_latest_telemetry(self, from_addr=None, is_livetrack=False):
|
||||
if self.allow_service_dispatch and self.is_client:
|
||||
try:
|
||||
return self._service_request_latest_telemetry(from_addr)
|
||||
@ -1350,7 +1399,11 @@ class SidebandCore():
|
||||
if self.config["telemetry_use_propagation_only"] == True:
|
||||
desired_method = LXMF.LXMessage.PROPAGATED
|
||||
else:
|
||||
desired_method = LXMF.LXMessage.DIRECT
|
||||
if not self.message_router.delivery_link_available(from_addr) and RNS.Identity.current_ratchet_id(from_addr) != None:
|
||||
RNS.log(f"Have ratchet for {RNS.prettyhexrep(from_addr)}, requesting opportunistic delivery of telemetry request", RNS.LOG_DEBUG)
|
||||
desired_method = LXMF.LXMessage.OPPORTUNISTIC
|
||||
else:
|
||||
desired_method = LXMF.LXMessage.DIRECT
|
||||
|
||||
request_timebase = self.getpersistent(f"telemetry.{RNS.hexrep(from_addr, delimit=False)}.timebase") or now - self.telemetry_request_max_history
|
||||
lxm_fields = { LXMF.FIELD_COMMANDS: [
|
||||
@ -1363,7 +1416,7 @@ class SidebandCore():
|
||||
lxm.register_failed_callback(self.telemetry_request_finished)
|
||||
|
||||
if self.message_router.get_outbound_propagation_node() != None:
|
||||
if self.config["telemetry_try_propagation_on_fail"]:
|
||||
if self.config["telemetry_try_propagation_on_fail"] and not is_livetrack:
|
||||
lxm.try_propagation_on_fail = True
|
||||
|
||||
RNS.log(f"Sending telemetry request with timebase {request_timebase}", RNS.LOG_DEBUG)
|
||||
@ -1375,6 +1428,62 @@ class SidebandCore():
|
||||
else:
|
||||
return "not_sent"
|
||||
|
||||
def _is_tracking(self, object_addr):
|
||||
return object_addr in self.live_tracked_objects
|
||||
|
||||
def is_tracking(self, object_addr, allow_cache=False):
|
||||
if not RNS.vendor.platformutils.is_android():
|
||||
return self._is_tracking(object_addr)
|
||||
else:
|
||||
if self.is_service:
|
||||
return self._is_tracking(object_addr)
|
||||
else:
|
||||
try:
|
||||
return self.service_rpc_request({"is_tracking": object_addr})
|
||||
except Exception as e:
|
||||
ed = "Error while getting tracking state over RPC: "+str(e)
|
||||
RNS.log(ed, RNS.LOG_DEBUG)
|
||||
return ed
|
||||
|
||||
def _start_tracking(self, object_addr, interval, duration):
|
||||
RNS.log("Starting tracking of "+RNS.prettyhexrep(object_addr), RNS.LOG_DEBUG)
|
||||
self.live_tracked_objects[object_addr] = [interval, 0, time.time()+duration]
|
||||
|
||||
def start_tracking(self, object_addr, interval, duration, allow_cache=False):
|
||||
if not RNS.vendor.platformutils.is_android():
|
||||
return self._start_tracking(object_addr, interval, duration)
|
||||
else:
|
||||
if self.is_service:
|
||||
return self._start_tracking(object_addr, interval, duration)
|
||||
else:
|
||||
try:
|
||||
args = {"object_addr": object_addr, "interval": interval, "duration": duration}
|
||||
return self.service_rpc_request({"start_tracking": args})
|
||||
except Exception as e:
|
||||
ed = "Error while starting tracking over RPC: "+str(e)
|
||||
RNS.log(ed, RNS.LOG_DEBUG)
|
||||
return ed
|
||||
|
||||
def _stop_tracking(self, object_addr):
|
||||
RNS.log("Stopping tracking of "+RNS.prettyhexrep(object_addr), RNS.LOG_DEBUG)
|
||||
if object_addr in self.live_tracked_objects:
|
||||
self.live_tracked_objects.pop(object_addr)
|
||||
|
||||
def stop_tracking(self, object_addr, allow_cache=False):
|
||||
if not RNS.vendor.platformutils.is_android():
|
||||
return self._stop_tracking(object_addr)
|
||||
else:
|
||||
if self.is_service:
|
||||
return self._stop_tracking(object_addr)
|
||||
else:
|
||||
try:
|
||||
args = {"object_addr": object_addr}
|
||||
return self.service_rpc_request({"stop_tracking": args})
|
||||
except Exception as e:
|
||||
ed = "Error while stopping tracking over RPC: "+str(e)
|
||||
RNS.log(ed, RNS.LOG_DEBUG)
|
||||
return ed
|
||||
|
||||
def _service_send_latest_telemetry(self, to_addr=None, stream=None, is_authorized_telemetry_request=False):
|
||||
if not RNS.vendor.platformutils.is_android():
|
||||
return False
|
||||
@ -1430,7 +1539,11 @@ class SidebandCore():
|
||||
if self.config["telemetry_use_propagation_only"] == True:
|
||||
desired_method = LXMF.LXMessage.PROPAGATED
|
||||
else:
|
||||
desired_method = LXMF.LXMessage.DIRECT
|
||||
if not self.message_router.delivery_link_available(to_addr) and RNS.Identity.current_ratchet_id(to_addr) != None:
|
||||
RNS.log(f"Have ratchet for {RNS.prettyhexrep(to_addr)}, requesting opportunistic delivery of telemetry", RNS.LOG_DEBUG)
|
||||
desired_method = LXMF.LXMessage.OPPORTUNISTIC
|
||||
else:
|
||||
desired_method = LXMF.LXMessage.DIRECT
|
||||
|
||||
lxm_fields = self.get_message_fields(to_addr, is_authorized_telemetry_request=is_authorized_telemetry_request, signal_already_sent=True)
|
||||
if lxm_fields == False and stream == None:
|
||||
@ -1841,6 +1954,14 @@ class SidebandCore():
|
||||
elif "get_lxm_stamp_cost" in call:
|
||||
args = call["get_lxm_stamp_cost"]
|
||||
connection.send(self.get_lxm_stamp_cost(args["lxm_hash"]))
|
||||
elif "is_tracking" in call:
|
||||
connection.send(self.is_tracking(call["is_tracking"]))
|
||||
elif "start_tracking" in call:
|
||||
args = call["start_tracking"]
|
||||
connection.send(self.start_tracking(object_addr=args["object_addr"], interval=args["interval"], duration=args["duration"]))
|
||||
elif "stop_tracking" in call:
|
||||
args = call["stop_tracking"]
|
||||
connection.send(self.stop_tracking(object_addr=args["object_addr"]))
|
||||
else:
|
||||
connection.send(None)
|
||||
|
||||
@ -1934,10 +2055,10 @@ class SidebandCore():
|
||||
# TODO: Remove this again at some point in the future
|
||||
db = self.__db_connect()
|
||||
dbc = db.cursor()
|
||||
dbc.execute("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'lxm' AND sql LIKE '%extra%'")
|
||||
dbc.execute("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'announce' AND sql LIKE '%extra%'")
|
||||
result = dbc.fetchall()
|
||||
if len(result) == 0:
|
||||
dbc.execute("ALTER TABLE lxm ADD COLUMN extra BLOB")
|
||||
dbc.execute("ALTER TABLE announce ADD COLUMN extra BLOB")
|
||||
db.commit()
|
||||
|
||||
def _db_initstate(self):
|
||||
@ -2494,8 +2615,16 @@ class SidebandCore():
|
||||
for entry in result:
|
||||
try:
|
||||
if not entry[2] in added_dests:
|
||||
app_data = entry[3]
|
||||
app_data = entry[3]
|
||||
dest_type = entry[4]
|
||||
if entry[5] != None:
|
||||
try:
|
||||
extras = msgpack.unpackb(entry[5])
|
||||
except Exception as e:
|
||||
RNS.log(f"Error while unpacking extras from announce: {e}", RNS.LOG_ERROR)
|
||||
extras = None
|
||||
else:
|
||||
extras = None
|
||||
if dest_type == "lxmf.delivery":
|
||||
announced_name = LXMF.display_name_from_app_data(app_data)
|
||||
announced_cost = self.message_router.get_outbound_stamp_cost(entry[2])
|
||||
@ -2503,11 +2632,12 @@ class SidebandCore():
|
||||
announced_name = None
|
||||
announced_cost = None
|
||||
announce = {
|
||||
"dest": entry[2],
|
||||
"name": announced_name,
|
||||
"cost": announced_cost,
|
||||
"time": entry[1],
|
||||
"type": dest_type
|
||||
"dest" : entry[2],
|
||||
"name" : announced_name,
|
||||
"cost" : announced_cost,
|
||||
"time" : entry[1],
|
||||
"type" : dest_type,
|
||||
"extras": extras,
|
||||
}
|
||||
added_dests.append(entry[2])
|
||||
announces.append(announce)
|
||||
@ -2921,7 +3051,7 @@ class SidebandCore():
|
||||
|
||||
self.__event_conversation_changed(context_dest)
|
||||
|
||||
def _db_save_announce(self, destination_hash, app_data, dest_type="lxmf.delivery"):
|
||||
def _db_save_announce(self, destination_hash, app_data, dest_type="lxmf.delivery", link_stats = None):
|
||||
with self.db_lock:
|
||||
db = self.__db_connect()
|
||||
dbc = db.cursor()
|
||||
@ -2935,14 +3065,16 @@ class SidebandCore():
|
||||
now = time.time()
|
||||
hash_material = str(time).encode("utf-8")+destination_hash+app_data+dest_type.encode("utf-8")
|
||||
announce_hash = RNS.Identity.full_hash(hash_material)
|
||||
extras = msgpack.packb({"link_stats": link_stats})
|
||||
|
||||
query = "INSERT INTO announce (id, received, source, data, dest_type) values (?, ?, ?, ?, ?)"
|
||||
query = "INSERT INTO announce (id, received, source, data, dest_type, extra) values (?, ?, ?, ?, ?, ?)"
|
||||
data = (
|
||||
announce_hash,
|
||||
now,
|
||||
destination_hash,
|
||||
app_data,
|
||||
dest_type,
|
||||
extras,
|
||||
)
|
||||
|
||||
dbc.execute(query, data)
|
||||
@ -3466,6 +3598,28 @@ class SidebandCore():
|
||||
except Exception as e:
|
||||
RNS.log("An error occurred while requesting scheduled telemetry from collector: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
stale_entries = []
|
||||
if len(self.live_tracked_objects) > 0:
|
||||
now = time.time()
|
||||
for object_hash in self.live_tracked_objects:
|
||||
tracking_entry = self.live_tracked_objects[object_hash]
|
||||
tracking_int = tracking_entry[0]
|
||||
tracking_last = tracking_entry[1]
|
||||
tracking_end = tracking_entry[2]
|
||||
|
||||
if now < tracking_end:
|
||||
if now > tracking_last+tracking_int:
|
||||
RNS.log("Next live tracking request time reached for "+str(RNS.prettyhexrep(object_hash)))
|
||||
self.request_latest_telemetry(from_addr=object_hash, is_livetrack=True)
|
||||
tracking_entry[1] = time.time()
|
||||
else:
|
||||
stale_entries.append(object_hash)
|
||||
|
||||
for object_hash in stale_entries:
|
||||
RNS.log("Terminating live tracking for "+RNS.prettyhexrep(object_hash)+", tracking duration reached", RNS.LOG_DEBUG)
|
||||
self.live_tracked_objects.pop(object_hash)
|
||||
|
||||
|
||||
def __start_jobs_deferred(self):
|
||||
if self.is_service:
|
||||
self.service_thread = threading.Thread(target=self._service_jobs, daemon=True)
|
||||
@ -3724,6 +3878,14 @@ class SidebandCore():
|
||||
if self.is_client:
|
||||
self.service_rpc_set_debug(debug)
|
||||
|
||||
def _log_handler(self, message):
|
||||
self.log_deque.append(message)
|
||||
print(message)
|
||||
|
||||
# TODO: Get service log on Android
|
||||
def get_log(self):
|
||||
return "\n".join(self.log_deque)
|
||||
|
||||
def __start_jobs_immediate(self):
|
||||
if self.log_verbose:
|
||||
selected_level = 7 # debugging purposes
|
||||
@ -3731,7 +3893,20 @@ class SidebandCore():
|
||||
selected_level = 2
|
||||
|
||||
self.setstate("init.loadingstate", "Substantiating Reticulum")
|
||||
self.reticulum = RNS.Reticulum(configdir=self.rns_configdir, loglevel=selected_level)
|
||||
|
||||
try:
|
||||
self.reticulum = RNS.Reticulum(configdir=self.rns_configdir, loglevel=selected_level, logdest=self._log_handler)
|
||||
if RNS.vendor.platformutils.is_android():
|
||||
if self.is_service:
|
||||
if os.path.isfile(self.rns_configdir+"/config_template_invalid"):
|
||||
os.unlink(self.rns_configdir+"/config_template_invalid")
|
||||
else:
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"Error while instantiating Reticulum: {e}", RNS.LOG_ERROR)
|
||||
RNS.log(f"Local configuration template changes will be ignored on next start", RNS.LOG_ERROR)
|
||||
exit(255)
|
||||
|
||||
if self.is_service:
|
||||
self.__start_rpc_listener()
|
||||
@ -4615,6 +4790,7 @@ class SidebandCore():
|
||||
from http import server
|
||||
import socketserver
|
||||
import json
|
||||
import ssl
|
||||
|
||||
webshare_dir = self.webshare_dir
|
||||
port = 4444
|
||||
@ -4635,7 +4811,7 @@ class SidebandCore():
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "text/json")
|
||||
self.end_headers()
|
||||
json_result = json.dumps(os.listdir(serve_root+"/pkg"))
|
||||
json_result = json.dumps(sorted(os.listdir(serve_root+"/pkg")))
|
||||
self.wfile.write(json_result.encode("utf-8"))
|
||||
except Exception as e:
|
||||
self.send_response(500)
|
||||
@ -4650,6 +4826,8 @@ class SidebandCore():
|
||||
self.send_response(200)
|
||||
if path.lower().endswith(".apk"):
|
||||
self.send_header("Content-type", "application/vnd.android.package-archive")
|
||||
elif path.lower().endswith(".js"):
|
||||
self.send_header("Content-type", "text/javascript")
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
except Exception as e:
|
||||
@ -4659,7 +4837,36 @@ class SidebandCore():
|
||||
es = "Error"
|
||||
self.wfile.write(es.encode("utf-8"))
|
||||
|
||||
with socketserver.TCPServer(("", port), RequestHandler) as webserver:
|
||||
#######################################################
|
||||
# Override BaseHTTPRequestHandler method to squelch
|
||||
# excessive exception logging when client signals
|
||||
# invalid certificate to the server. This will always
|
||||
# happen from some clients when using a self-signed
|
||||
# certificate, so we don't care.
|
||||
if not hasattr(server.BaseHTTPRequestHandler, "handle_orig"):
|
||||
server.BaseHTTPRequestHandler.handle_orig = server.BaseHTTPRequestHandler.handle
|
||||
def handle(self):
|
||||
try:
|
||||
self.handle_orig()
|
||||
except ssl.SSLError:
|
||||
pass
|
||||
except Exception as e:
|
||||
RNS.log("HTTP server exception: "+str(e), RNS.LOG_ERROR)
|
||||
server.BaseHTTPRequestHandler.handle = handle
|
||||
#######################################################
|
||||
|
||||
socketserver.TCPServer.allow_reuse_address = True
|
||||
class ThreadedHTTPServer(socketserver.ThreadingMixIn, server.HTTPServer):
|
||||
daemon_threads = True
|
||||
|
||||
with ThreadedHTTPServer(("", port), RequestHandler) as webserver:
|
||||
from sideband.certgen import ensure_certificate
|
||||
|
||||
ensure_certificate(self.webshare_ssl_key_path, self.webshare_ssl_cert_path)
|
||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
ssl_context.load_cert_chain(certfile=self.webshare_ssl_cert_path, keyfile=self.webshare_ssl_key_path)
|
||||
webserver.socket = ssl_context.wrap_socket(webserver.socket, do_handshake_on_connect=False, server_side=True)
|
||||
|
||||
self.webshare_server = webserver
|
||||
webserver.serve_forever()
|
||||
self.webshare_server = None
|
||||
@ -4864,15 +5071,41 @@ class SidebandCore():
|
||||
if not self.reticulum.is_connected_to_shared_instance:
|
||||
RNS.Transport.detach_interfaces()
|
||||
|
||||
rns_config = """
|
||||
rns_config = """# This template is used to generate a
|
||||
# running configuration for Sideband's
|
||||
# internal RNS instance. Incorrect changes
|
||||
# or addition here may cause Sideband to
|
||||
# fail starting up or working properly.
|
||||
#
|
||||
# If Sideband detects that Reticulum
|
||||
# aborts at startup, due to an error in
|
||||
# configuration, any template changes
|
||||
# will be reset to this default.
|
||||
|
||||
[reticulum]
|
||||
enable_transport = TRANSPORT_IS_ENABLED
|
||||
share_instance = Yes
|
||||
shared_instance_port = 37428
|
||||
instance_control_port = 37429
|
||||
panic_on_interface_error = No
|
||||
# Don't change this line, use the UI
|
||||
# setting for selecting whether RNS
|
||||
# transport is enabled or disabled
|
||||
enable_transport = TRANSPORT_IS_ENABLED
|
||||
|
||||
# Changing this setting will cause
|
||||
# Sideband to not work.
|
||||
share_instance = Yes
|
||||
|
||||
# Changing these options should only
|
||||
# be done if you know what you're doing.
|
||||
shared_instance_port = 37428
|
||||
instance_control_port = 37429
|
||||
panic_on_interface_error = No
|
||||
|
||||
# Logging is controlled by settings
|
||||
# in the UI, so this section is mostly
|
||||
# not relevant in Sideband.
|
||||
[logging]
|
||||
loglevel = 3
|
||||
loglevel = 3
|
||||
|
||||
# No additional interfaces are currently
|
||||
# defined, but you can use this section
|
||||
# to do so.
|
||||
[interfaces]
|
||||
"""
|
||||
|
@ -778,9 +778,9 @@ class Location(Sensor):
|
||||
return [
|
||||
struct.pack("!i", int(round(d["latitude"], 6)*1e6)),
|
||||
struct.pack("!i", int(round(d["longitude"], 6)*1e6)),
|
||||
struct.pack("!I", int(round(d["altitude"], 2)*1e2)),
|
||||
struct.pack("!i", int(round(d["altitude"], 2)*1e2)),
|
||||
struct.pack("!I", int(round(d["speed"], 2)*1e2)),
|
||||
struct.pack("!I", int(round(d["bearing"], 2)*1e2)),
|
||||
struct.pack("!i", int(round(d["bearing"], 2)*1e2)),
|
||||
struct.pack("!H", int(round(d["accuracy"], 2)*1e2)),
|
||||
d["last_update"],
|
||||
]
|
||||
@ -796,9 +796,9 @@ class Location(Sensor):
|
||||
return {
|
||||
"latitude": struct.unpack("!i", packed[0])[0]/1e6,
|
||||
"longitude": struct.unpack("!i", packed[1])[0]/1e6,
|
||||
"altitude": struct.unpack("!I", packed[2])[0]/1e2,
|
||||
"altitude": struct.unpack("!i", packed[2])[0]/1e2,
|
||||
"speed": struct.unpack("!I", packed[3])[0]/1e2,
|
||||
"bearing": struct.unpack("!I", packed[4])[0]/1e2,
|
||||
"bearing": struct.unpack("!i", packed[4])[0]/1e2,
|
||||
"accuracy": struct.unpack("!H", packed[5])[0]/1e2,
|
||||
"last_update": packed[6],
|
||||
}
|
||||
|
@ -18,9 +18,9 @@ from kivy.lang.builder import Builder
|
||||
|
||||
from kivy.utils import escape_markup
|
||||
if RNS.vendor.platformutils.get_platform() == "android":
|
||||
from ui.helpers import multilingual_markup
|
||||
from ui.helpers import multilingual_markup, sig_icon_for_q
|
||||
else:
|
||||
from .helpers import multilingual_markup
|
||||
from .helpers import multilingual_markup, sig_icon_for_q
|
||||
|
||||
if RNS.vendor.platformutils.get_platform() == "android":
|
||||
from ui.helpers import ts_format
|
||||
@ -92,6 +92,25 @@ class Announces():
|
||||
a_name = announce["name"]
|
||||
a_cost = announce["cost"]
|
||||
dest_type = announce["type"]
|
||||
a_rssi = None
|
||||
a_snr = None
|
||||
a_q = None
|
||||
|
||||
link_extras_str = ""
|
||||
link_extras_full = ""
|
||||
if "extras" in announce and announce["extras"] != None:
|
||||
extras = announce["extras"]
|
||||
if "link_stats" in extras:
|
||||
link_stats = extras["link_stats"]
|
||||
if "rssi" in link_stats and "snr" in link_stats and "q" in link_stats:
|
||||
a_rssi = link_stats["rssi"]
|
||||
a_snr = link_stats["snr"]
|
||||
a_q = link_stats["q"]
|
||||
if a_rssi != None and a_snr != None and a_q != None:
|
||||
link_extras_str = f" ([b]RSSI[/b] {a_rssi} [b]SNR[/b] {a_snr})"
|
||||
link_extras_full = f"\n[b]Link Quality[/b] {a_q}%[/b]\n[b]RSSI[/b] {a_rssi}\n[b]SNR[/b] {a_snr}"
|
||||
|
||||
sig_icon = multilingual_markup(sig_icon_for_q(a_q).encode("utf-8")).decode("utf-8")
|
||||
|
||||
if not context_dest in self.added_item_dests:
|
||||
if self.app.sideband.is_trusted(context_dest):
|
||||
@ -99,16 +118,16 @@ class Announces():
|
||||
else:
|
||||
trust_icon = "account-question"
|
||||
|
||||
def gen_info(ts, dest, name, cost, dtype):
|
||||
def gen_info(ts, dest, name, cost, dtype, link_extras):
|
||||
name = multilingual_markup(escape_markup(str(name)).encode("utf-8")).decode("utf-8")
|
||||
cost = str(cost)
|
||||
def x(sender):
|
||||
yes_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
|
||||
if dtype == "lxmf.delivery":
|
||||
ad_text = "[size=22dp]LXMF Peer[/size]\n\n[b]Received[/b] "+ts+"\n[b]Address[/b] "+RNS.prettyhexrep(dest)+"\n[b]Name[/b] "+name+"\n[b]Stamp Cost[/b] "+cost
|
||||
ad_text = "[size=22dp]LXMF Peer[/size]\n\n[b]Received[/b] "+ts+"\n[b]Address[/b] "+RNS.prettyhexrep(dest)+"\n[b]Name[/b] "+name+"\n[b]Stamp Cost[/b] "+cost+link_extras
|
||||
|
||||
if dtype == "lxmf.propagation":
|
||||
ad_text = "[size=22dp]LXMF Propagation Node[/size]\n\n[b]Received[/b] "+ts+"\n[b]Address[/b] "+RNS.prettyhexrep(dest)
|
||||
ad_text = "[size=22dp]LXMF Propagation Node[/size]\n\n[b]Received[/b] "+ts+"\n[b]Address[/b] "+RNS.prettyhexrep(dest)+link_extras
|
||||
|
||||
dialog = MDDialog(
|
||||
text=ad_text,
|
||||
@ -123,7 +142,8 @@ class Announces():
|
||||
dialog.open()
|
||||
return x
|
||||
|
||||
time_string = time.strftime(ts_format, time.localtime(ts))
|
||||
time_string = sig_icon + " " + time.strftime(ts_format, time.localtime(ts)) + link_extras_str
|
||||
time_string_plain = time.strftime(ts_format, time.localtime(ts))
|
||||
|
||||
if dest_type == "lxmf.delivery":
|
||||
disp_name = multilingual_markup(escape_markup(str(self.app.sideband.peer_display_name(context_dest))).encode("utf-8")).decode("utf-8")
|
||||
@ -137,7 +157,7 @@ class Announces():
|
||||
disp_name = "Unknown Announce"
|
||||
iconl = IconLeftWidget(icon="progress-question")
|
||||
|
||||
item = TwoLineAvatarIconListItem(text=time_string, secondary_text=disp_name, on_release=gen_info(time_string, context_dest, a_name, a_cost, dest_type))
|
||||
item = TwoLineAvatarIconListItem(text=time_string, secondary_text=disp_name, on_release=gen_info(time_string_plain, context_dest, a_name, a_cost, dest_type, link_extras_full))
|
||||
item.add_widget(iconl)
|
||||
item.sb_uid = context_dest
|
||||
item.ts = ts
|
||||
|
@ -133,6 +133,7 @@ class Conversations():
|
||||
unread = conv["unread"]
|
||||
last_activity = conv["last_activity"]
|
||||
trusted = conv["trust"] == 1
|
||||
appearance_from_all = self.app.sideband.config["display_style_from_all"]
|
||||
appearance = self.app.sideband.peer_appearance(context_dest, conv=conv)
|
||||
is_object = self.app.sideband.is_object(context_dest, conv_data=conv)
|
||||
da = self.app.sideband.DEFAULT_APPEARANCE
|
||||
@ -141,7 +142,7 @@ class Conversations():
|
||||
conv_icon = self.trust_icon(conv)
|
||||
fg = None; bg = None; ti_color = None
|
||||
|
||||
if trusted and self.app.sideband.config["display_style_in_contact_list"] and appearance != None and appearance != da:
|
||||
if (trusted or appearance_from_all) and self.app.sideband.config["display_style_in_contact_list"] and appearance != None and appearance != da:
|
||||
fg = appearance[1] or da[1]; bg = appearance[2] or da[2]
|
||||
ti_color = "Custom"
|
||||
else:
|
||||
|
@ -4,6 +4,7 @@ from kivy.uix.screenmanager import ScreenManager, Screen
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivymd.uix.list import OneLineIconListItem, MDList, IconLeftWidget, IconRightWidget
|
||||
from kivy.properties import StringProperty
|
||||
import re
|
||||
|
||||
ts_format = "%Y-%m-%d %H:%M:%S"
|
||||
file_ts_format = "%Y_%m_%d_%H_%M_%S"
|
||||
@ -25,6 +26,19 @@ intensity_msgs_light = "500"
|
||||
intensity_play_dark = "600"
|
||||
intensity_play_light = "300"
|
||||
|
||||
|
||||
intensity_msgs_dark_alt = "800"
|
||||
intensity_msgs_light_alt = "400"
|
||||
intensity_delivered_alt_dark = "800"
|
||||
color_received_alt = "BlueGray"
|
||||
color_received_alt_light = "BlueGray"
|
||||
color_delivered_alt = "Indigo"
|
||||
color_propagated_alt = "DeepPurple"
|
||||
color_paper_alt = "DeepPurple"
|
||||
color_playing_alt = "Amber"
|
||||
color_failed_alt = "Red"
|
||||
color_unknown_alt = "Gray"
|
||||
|
||||
class ContentNavigationDrawer(Screen):
|
||||
pass
|
||||
|
||||
@ -45,13 +59,12 @@ def strip_emojis(str_input):
|
||||
return output
|
||||
|
||||
def multilingual_markup(data):
|
||||
# TODO: Remove
|
||||
# import time
|
||||
# ts = time.time()
|
||||
|
||||
do = ""
|
||||
rfont = "default"
|
||||
ds = data.decode("utf-8")
|
||||
di = 0
|
||||
persistent_regions = [(m.start(), m.end()) for m in re.finditer("(?s)\[font=(?:nf|term)\].*?\[/font\]", ds)]
|
||||
|
||||
for cp in ds:
|
||||
match = False
|
||||
switch = False
|
||||
@ -63,6 +76,10 @@ def multilingual_markup(data):
|
||||
switch = True
|
||||
rfont = "emoji"
|
||||
|
||||
in_persistent = False
|
||||
if any(x[0] < di and x[1] > di for x in persistent_regions):
|
||||
in_persistent = True
|
||||
|
||||
if not match:
|
||||
for range_start in codepoint_map:
|
||||
range_end = codepoint_map[range_start][0]
|
||||
@ -71,8 +88,9 @@ def multilingual_markup(data):
|
||||
if range_end >= ord(cp) >= range_start:
|
||||
match = True
|
||||
if rfont != mapped_font:
|
||||
rfont = mapped_font
|
||||
switch = True
|
||||
if not in_persistent:
|
||||
rfont = mapped_font
|
||||
switch = True
|
||||
break
|
||||
|
||||
if (not match) and rfont != "default":
|
||||
@ -86,15 +104,30 @@ def multilingual_markup(data):
|
||||
do += "[font="+str(rfont)+"]"
|
||||
|
||||
do += cp
|
||||
di += 1
|
||||
|
||||
if rfont != "default":
|
||||
do += "[/font]"
|
||||
|
||||
# TODO: Remove
|
||||
# print(do+"\n\n"+str(time.time()-ts))
|
||||
|
||||
return do.encode("utf-8")
|
||||
|
||||
def sig_icon_for_q(q):
|
||||
if q == None:
|
||||
return ""
|
||||
elif q > 90:
|
||||
return ""
|
||||
elif q > 70:
|
||||
return ""
|
||||
elif q > 50:
|
||||
return ""
|
||||
elif q > 30:
|
||||
return ""
|
||||
elif q > 10:
|
||||
return ""
|
||||
|
||||
persistent_fonts = ["nf", "term"]
|
||||
nf_mapped = "nf"
|
||||
|
||||
codepoint_map = {
|
||||
0x0590: [0x05ff, "hebrew"],
|
||||
0x2e3a: [0x2e3b, "chinese"],
|
||||
@ -128,6 +161,29 @@ codepoint_map = {
|
||||
0xac00: [0xd7af, "korean"],
|
||||
0xd7b0: [0xd7ff, "korean"],
|
||||
0x0900: [0x097f, "combined"], # Devanagari
|
||||
0xe5fa: [0xe6b7, nf_mapped], # Seti-UI + Custom
|
||||
0xe700: [0xe8ef, nf_mapped], # Devicons
|
||||
0xed00: [0xf2ff, nf_mapped], # Font Awesome
|
||||
0xe200: [0xe2a9, nf_mapped], # Font Awesome Extension
|
||||
0xf0001: [0xf1af0, nf_mapped], # Material Design Icons
|
||||
0xe300: [0xe3e3, nf_mapped], # Weather
|
||||
0xf400: [0xf533, nf_mapped], # Octicons
|
||||
0x2665: [0x2665, nf_mapped], # Octicons
|
||||
0x26a1: [0x26a1, nf_mapped], # Octicons
|
||||
0xe0a0: [0xe0a2, nf_mapped], # Powerline Symbols
|
||||
0xe0b0: [0xe0b3, nf_mapped], # Powerline Symbols
|
||||
0xe0a3: [0xe0a3, nf_mapped], # Powerline Extra Symbols
|
||||
0xe0b4: [0xe0c8, nf_mapped], # Powerline Extra Symbols
|
||||
0xe0ca: [0xe0ca, nf_mapped], # Powerline Extra Symbols
|
||||
0xe0cc: [0xe0d7, nf_mapped], # Powerline Extra Symbols
|
||||
0x23fb: [0x23fe, nf_mapped], # IEC Power Symbols
|
||||
0x2b58: [0x2b58, nf_mapped], # IEC Power Symbols
|
||||
0xf300: [0xf381, nf_mapped], # Font logos
|
||||
0xe000: [0xe00a, nf_mapped], # Pomicons
|
||||
0xea60: [0xec1e, nf_mapped], # Codicons
|
||||
0x276c: [0x2771, nf_mapped], # Heavy Angle Brackets
|
||||
0x2500: [0x259f, nf_mapped], # Box Drawing
|
||||
0xee00: [0xee0b, nf_mapped], # Progress
|
||||
}
|
||||
|
||||
emoji_lookup = [
|
||||
|
@ -80,13 +80,13 @@ MDNavigationLayout:
|
||||
on_release: root.ids.screen_manager.app.map_action(self)
|
||||
|
||||
|
||||
OneLineIconListItem:
|
||||
text: "Overview"
|
||||
on_release: root.ids.screen_manager.app.overview_action(self)
|
||||
# OneLineIconListItem:
|
||||
# text: "Overview"
|
||||
# on_release: root.ids.screen_manager.app.overview_action(self)
|
||||
|
||||
IconLeftWidget:
|
||||
icon: "view-dashboard-outline"
|
||||
on_release: root.ids.screen_manager.app.overview_action(self)
|
||||
# IconLeftWidget:
|
||||
# icon: "view-dashboard-outline"
|
||||
# on_release: root.ids.screen_manager.app.overview_action(self)
|
||||
|
||||
|
||||
OneLineIconListItem:
|
||||
@ -1287,7 +1287,7 @@ layout_settings_screen = """
|
||||
MDLabel:
|
||||
id: scaling_info
|
||||
markup: True
|
||||
text: "You can scale the entire Sideband UI by specifying a scaling factor in the field below. After setting it, restart sideband for the scaling to take effect.\\n\\nSet to 0.0 to disable scaling adjustments."
|
||||
text: "You can scale the entire Sideband UI by specifying a scaling factor in the field below. After setting it, restart sideband for the scaling to take effect.\\n\\nSet to 0.0 to disable scaling adjustments.\\n\\n[b]Please note![/b] On some devices, the default scaling factor will be higher than 1.0, and setting a smaller value will result in miniscule UI elements."
|
||||
size_hint_y: None
|
||||
text_size: self.width, None
|
||||
height: self.texture_size[1]
|
||||
@ -1494,6 +1494,21 @@ MDScreen:
|
||||
pos_hint: {"center_y": 0.3}
|
||||
active: False
|
||||
|
||||
MDBoxLayout:
|
||||
orientation: "horizontal"
|
||||
size_hint_y: None
|
||||
padding: [0,0,dp(24),dp(0)]
|
||||
height: dp(48)
|
||||
|
||||
MDLabel:
|
||||
text: "Classic message colors"
|
||||
font_style: "H6"
|
||||
|
||||
MDSwitch:
|
||||
id: settings_classic_message_colors
|
||||
pos_hint: {"center_y": 0.3}
|
||||
active: False
|
||||
|
||||
MDBoxLayout:
|
||||
orientation: "horizontal"
|
||||
size_hint_y: None
|
||||
@ -1568,7 +1583,7 @@ MDScreen:
|
||||
height: dp(48)
|
||||
|
||||
MDLabel:
|
||||
text: "Announce Automatically"
|
||||
text: "Announce automatically"
|
||||
font_style: "H6"
|
||||
|
||||
MDSwitch:
|
||||
@ -1583,7 +1598,7 @@ MDScreen:
|
||||
height: dp(48)
|
||||
|
||||
MDLabel:
|
||||
text: "Try propagation on direct delivery failure"
|
||||
text: "Try propagation automatically"
|
||||
font_style: "H6"
|
||||
|
||||
MDSwitch:
|
||||
@ -1624,6 +1639,22 @@ MDScreen:
|
||||
disabled: False
|
||||
active: False
|
||||
|
||||
MDBoxLayout:
|
||||
orientation: "horizontal"
|
||||
size_hint_y: None
|
||||
padding: [0,0,dp(24),dp(0)]
|
||||
height: dp(48)
|
||||
|
||||
MDLabel:
|
||||
text: "Only render markup from trusted"
|
||||
font_style: "H6"
|
||||
|
||||
MDSwitch:
|
||||
id: settings_trusted_markup_only
|
||||
pos_hint: {"center_y": 0.3}
|
||||
disabled: False
|
||||
active: False
|
||||
|
||||
MDBoxLayout:
|
||||
orientation: "horizontal"
|
||||
size_hint_y: None
|
||||
@ -1752,21 +1783,21 @@ MDScreen:
|
||||
disabled: False
|
||||
active: False
|
||||
|
||||
MDBoxLayout:
|
||||
orientation: "horizontal"
|
||||
size_hint_y: None
|
||||
padding: [0,0,dp(24),dp(0)]
|
||||
height: dp(48)
|
||||
# MDBoxLayout:
|
||||
# orientation: "horizontal"
|
||||
# size_hint_y: None
|
||||
# padding: [0,0,dp(24),dp(0)]
|
||||
# height: dp(48)
|
||||
|
||||
MDLabel:
|
||||
text: "Use Home Node as Broadcast Repeater"
|
||||
font_style: "H6"
|
||||
# MDLabel:
|
||||
# text: "Use Home Node as Broadcast Repeater"
|
||||
# font_style: "H6"
|
||||
|
||||
MDSwitch:
|
||||
id: settings_home_node_as_broadcast_repeater
|
||||
pos_hint: {"center_y": 0.3}
|
||||
active: False
|
||||
disabled: True
|
||||
# MDSwitch:
|
||||
# id: settings_home_node_as_broadcast_repeater
|
||||
# pos_hint: {"center_y": 0.3}
|
||||
# active: False
|
||||
# disabled: True
|
||||
|
||||
MDBoxLayout:
|
||||
orientation: "horizontal"
|
||||
|
@ -35,11 +35,13 @@ if RNS.vendor.platformutils.get_platform() == "android":
|
||||
from sideband.sense import Telemeter, Commands
|
||||
from ui.helpers import ts_format, file_ts_format, mdc
|
||||
from ui.helpers import color_playing, color_received, color_delivered, color_propagated, color_paper, color_failed, color_unknown, intensity_msgs_dark, intensity_msgs_light, intensity_play_dark, intensity_play_light
|
||||
from ui.helpers import color_received_alt, color_received_alt_light, color_delivered_alt, color_propagated_alt, color_paper_alt, color_failed_alt, color_unknown_alt, color_playing_alt, intensity_msgs_dark_alt, intensity_msgs_light_alt, intensity_delivered_alt_dark
|
||||
else:
|
||||
import sbapp.plyer as plyer
|
||||
from sbapp.sideband.sense import Telemeter, Commands
|
||||
from .helpers import ts_format, file_ts_format, mdc
|
||||
from .helpers import color_playing, color_received, color_delivered, color_propagated, color_paper, color_failed, color_unknown, intensity_msgs_dark, intensity_msgs_light, intensity_play_dark, intensity_play_light
|
||||
from .helpers import color_received_alt, color_received_alt_light, color_delivered_alt, color_propagated_alt, color_paper_alt, color_failed_alt, color_unknown_alt, color_playing_alt, intensity_msgs_dark_alt, intensity_msgs_light_alt, intensity_delivered_alt_dark
|
||||
|
||||
if RNS.vendor.platformutils.is_darwin():
|
||||
from PIL import Image as PilImage
|
||||
@ -203,6 +205,23 @@ class Messages():
|
||||
self.ids.message_text.input_type = "text"
|
||||
self.ids.message_text.keyboard_suggestions = True
|
||||
|
||||
if not self.app.sideband.config["classic_message_colors"]:
|
||||
c_delivered = color_delivered_alt
|
||||
c_received = color_received_alt
|
||||
c_propagated = color_propagated_alt
|
||||
c_playing = color_playing_alt
|
||||
c_paper = color_paper_alt
|
||||
c_unknown = color_unknown_alt
|
||||
c_failed = color_failed_alt
|
||||
else:
|
||||
c_delivered = color_delivered
|
||||
c_received = color_received
|
||||
c_propagated = color_propagated
|
||||
c_playing = color_playing
|
||||
c_paper = color_paper
|
||||
c_unknown = color_unknown
|
||||
c_failed = color_failed
|
||||
|
||||
for new_message in self.app.sideband.list_messages(self.context_dest, after=self.latest_message_timestamp,limit=limit):
|
||||
self.new_messages.append(new_message)
|
||||
|
||||
@ -241,12 +260,24 @@ class Messages():
|
||||
if (len(self.added_item_hashes) < self.db_message_count) and not self.load_more_button in self.list.children:
|
||||
self.list.add_widget(self.load_more_button, len(self.list.children))
|
||||
|
||||
if self.app.sideband.config["dark_ui"]:
|
||||
intensity_msgs = intensity_msgs_dark
|
||||
intensity_play = intensity_play_dark
|
||||
if self.app.sideband.config["classic_message_colors"]:
|
||||
if self.app.sideband.config["dark_ui"]:
|
||||
intensity_msgs = intensity_msgs_dark
|
||||
intensity_play = intensity_play_dark
|
||||
intensity_delivered = intensity_msgs
|
||||
else:
|
||||
intensity_msgs = intensity_msgs_light
|
||||
intensity_play = intensity_play_light
|
||||
intensity_delivered = intensity_msgs
|
||||
else:
|
||||
intensity_msgs = intensity_msgs_light
|
||||
intensity_play = intensity_play_light
|
||||
if self.app.sideband.config["dark_ui"]:
|
||||
intensity_msgs = intensity_msgs_dark_alt
|
||||
intensity_play = intensity_play_dark
|
||||
intensity_delivered = intensity_delivered_alt_dark
|
||||
else:
|
||||
intensity_msgs = intensity_msgs_light_alt
|
||||
intensity_play = intensity_play_light
|
||||
intensity_delivered = intensity_msgs
|
||||
|
||||
for w in self.widgets:
|
||||
m = w.m
|
||||
@ -271,7 +302,7 @@ class Messages():
|
||||
delivery_syms = multilingual_markup(delivery_syms.encode("utf-8")).decode("utf-8")
|
||||
|
||||
if msg["state"] == LXMF.LXMessage.OUTBOUND or msg["state"] == LXMF.LXMessage.SENDING or msg["state"] == LXMF.LXMessage.SENT:
|
||||
w.md_bg_color = msg_color = mdc(color_unknown, intensity_msgs)
|
||||
w.md_bg_color = msg_color = mdc(c_unknown, intensity_msgs)
|
||||
txstr = time.strftime(ts_format, time.localtime(msg["sent"]))
|
||||
titlestr = ""
|
||||
prgstr = ""
|
||||
@ -305,7 +336,7 @@ class Messages():
|
||||
|
||||
|
||||
if msg["state"] == LXMF.LXMessage.DELIVERED:
|
||||
w.md_bg_color = msg_color = mdc(color_delivered, intensity_msgs)
|
||||
w.md_bg_color = msg_color = mdc(c_delivered, intensity_delivered)
|
||||
txstr = time.strftime(ts_format, time.localtime(msg["sent"]))
|
||||
titlestr = ""
|
||||
if msg["title"]:
|
||||
@ -317,7 +348,7 @@ class Messages():
|
||||
m["state"] = msg["state"]
|
||||
|
||||
if msg["method"] == LXMF.LXMessage.PAPER:
|
||||
w.md_bg_color = msg_color = mdc(color_paper, intensity_msgs)
|
||||
w.md_bg_color = msg_color = mdc(c_paper, intensity_msgs)
|
||||
txstr = time.strftime(ts_format, time.localtime(msg["sent"]))
|
||||
titlestr = ""
|
||||
if msg["title"]:
|
||||
@ -326,7 +357,7 @@ class Messages():
|
||||
m["state"] = msg["state"]
|
||||
|
||||
if msg["method"] == LXMF.LXMessage.PROPAGATED and msg["state"] == LXMF.LXMessage.SENT:
|
||||
w.md_bg_color = msg_color = mdc(color_propagated, intensity_msgs)
|
||||
w.md_bg_color = msg_color = mdc(c_propagated, intensity_msgs)
|
||||
txstr = time.strftime(ts_format, time.localtime(msg["sent"]))
|
||||
titlestr = ""
|
||||
if msg["title"]:
|
||||
@ -338,7 +369,7 @@ class Messages():
|
||||
m["state"] = msg["state"]
|
||||
|
||||
if msg["state"] == LXMF.LXMessage.FAILED:
|
||||
w.md_bg_color = msg_color = mdc(color_failed, intensity_msgs)
|
||||
w.md_bg_color = msg_color = mdc(c_failed, intensity_msgs)
|
||||
txstr = time.strftime(ts_format, time.localtime(msg["sent"]))
|
||||
titlestr = ""
|
||||
if msg["title"]:
|
||||
@ -361,14 +392,49 @@ class Messages():
|
||||
wid.height, wid.size_hint_y, wid.opacity, wid.disabled = 0, None, 0, True
|
||||
|
||||
def update_widget(self):
|
||||
if self.app.sideband.config["dark_ui"]:
|
||||
intensity_msgs = intensity_msgs_dark
|
||||
intensity_play = intensity_play_dark
|
||||
mt_color = [1.0, 1.0, 1.0, 0.8]
|
||||
|
||||
if self.app.sideband.config["classic_message_colors"]:
|
||||
if self.app.sideband.config["dark_ui"]:
|
||||
intensity_msgs = intensity_msgs_dark
|
||||
intensity_play = intensity_play_dark
|
||||
intensity_delivered = intensity_msgs
|
||||
mt_color = [1.0, 1.0, 1.0, 0.8]
|
||||
else:
|
||||
intensity_msgs = intensity_msgs_light
|
||||
intensity_play = intensity_play_light
|
||||
intensity_delivered = intensity_msgs
|
||||
mt_color = [1.0, 1.0, 1.0, 0.95]
|
||||
else:
|
||||
intensity_msgs = intensity_msgs_light
|
||||
intensity_play = intensity_play_light
|
||||
mt_color = [1.0, 1.0, 1.0, 0.95]
|
||||
if self.app.sideband.config["dark_ui"]:
|
||||
intensity_msgs = intensity_msgs_dark_alt
|
||||
intensity_play = intensity_play_dark
|
||||
intensity_delivered = intensity_delivered_alt_dark
|
||||
mt_color = [1.0, 1.0, 1.0, 0.8]
|
||||
else:
|
||||
intensity_msgs = intensity_msgs_light_alt
|
||||
intensity_play = intensity_play_light
|
||||
intensity_delivered = intensity_msgs
|
||||
mt_color = [1.0, 1.0, 1.0, 0.95]
|
||||
|
||||
if not self.app.sideband.config["classic_message_colors"]:
|
||||
if self.app.sideband.config["dark_ui"]:
|
||||
c_received = color_received_alt
|
||||
else:
|
||||
c_received = color_received_alt_light
|
||||
c_delivered = color_delivered_alt
|
||||
c_propagated = color_propagated_alt
|
||||
c_playing = color_playing_alt
|
||||
c_paper = color_paper_alt
|
||||
c_unknown = color_unknown_alt
|
||||
c_failed = color_failed_alt
|
||||
else:
|
||||
c_delivered = color_delivered
|
||||
c_received = color_received
|
||||
c_propagated = color_propagated
|
||||
c_playing = color_playing
|
||||
c_paper = color_paper
|
||||
c_unknown = color_unknown
|
||||
c_failed = color_failed
|
||||
|
||||
self.ids.message_text.font_name = self.app.input_font
|
||||
|
||||
@ -378,7 +444,7 @@ class Messages():
|
||||
for m in self.new_messages:
|
||||
if not m["hash"] in self.added_item_hashes:
|
||||
try:
|
||||
if not self.is_trusted:
|
||||
if self.app.sideband.config["trusted_markup_only"] and not self.is_trusted:
|
||||
message_input = str( escape_markup(m["content"].decode("utf-8")) ).encode("utf-8")
|
||||
else:
|
||||
message_input = m["content"]
|
||||
@ -524,31 +590,31 @@ class Messages():
|
||||
|
||||
if m["source"] == self.app.sideband.lxmf_destination.hash:
|
||||
if m["state"] == LXMF.LXMessage.DELIVERED:
|
||||
msg_color = mdc(color_delivered, intensity_msgs)
|
||||
msg_color = mdc(c_delivered, intensity_delivered)
|
||||
heading_str = titlestr+"[b]Sent[/b] "+txstr+delivery_syms+"\n[b]State[/b] Delivered"
|
||||
|
||||
elif m["method"] == LXMF.LXMessage.PROPAGATED and m["state"] == LXMF.LXMessage.SENT:
|
||||
msg_color = mdc(color_propagated, intensity_msgs)
|
||||
msg_color = mdc(c_propagated, intensity_msgs)
|
||||
heading_str = titlestr+"[b]Sent[/b] "+txstr+delivery_syms+"\n[b]State[/b] On Propagation Net"
|
||||
|
||||
elif m["method"] == LXMF.LXMessage.PAPER:
|
||||
msg_color = mdc(color_paper, intensity_msgs)
|
||||
msg_color = mdc(c_paper, intensity_msgs)
|
||||
heading_str = titlestr+"[b]Created[/b] "+txstr+"\n[b]State[/b] Paper Message"
|
||||
|
||||
elif m["state"] == LXMF.LXMessage.FAILED:
|
||||
msg_color = mdc(color_failed, intensity_msgs)
|
||||
msg_color = mdc(c_failed, intensity_msgs)
|
||||
heading_str = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] Failed"
|
||||
|
||||
elif m["state"] == LXMF.LXMessage.OUTBOUND or m["state"] == LXMF.LXMessage.SENDING:
|
||||
msg_color = mdc(color_unknown, intensity_msgs)
|
||||
msg_color = mdc(c_unknown, intensity_msgs)
|
||||
heading_str = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] Sending "
|
||||
|
||||
else:
|
||||
msg_color = mdc(color_unknown, intensity_msgs)
|
||||
msg_color = mdc(c_unknown, intensity_msgs)
|
||||
heading_str = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] Unknown"
|
||||
|
||||
else:
|
||||
msg_color = mdc(color_received, intensity_msgs)
|
||||
msg_color = mdc(c_received, intensity_msgs)
|
||||
heading_str = titlestr
|
||||
if phy_stats_str != "" and self.app.sideband.config["advanced_stats"]:
|
||||
heading_str += phy_stats_str+"\n"
|
||||
@ -598,9 +664,9 @@ class Messages():
|
||||
self.app.play_audio_field(sender.audio_field)
|
||||
stored_color = sender.md_bg_color
|
||||
if sender.lsource == self.app.sideband.lxmf_destination.hash:
|
||||
sender.md_bg_color = mdc(color_delivered, intensity_play)
|
||||
sender.md_bg_color = mdc(c_delivered, intensity_play)
|
||||
else:
|
||||
sender.md_bg_color = mdc(color_received, intensity_play)
|
||||
sender.md_bg_color = mdc(c_received, intensity_play)
|
||||
|
||||
def cb(dt):
|
||||
sender.md_bg_color = stored_color
|
||||
@ -653,6 +719,19 @@ class Messages():
|
||||
item.ids.content_text.owner = item
|
||||
item.ids.content_text.bind(texture_size=check_textures)
|
||||
|
||||
def cbf(w):
|
||||
def x(dt):
|
||||
if w.texture_size[0] == 0 and w.texture_size[1] == 0:
|
||||
w.markup = False
|
||||
escaped_content = escape_markup(w.text)
|
||||
def deferred(dt):
|
||||
w.text = "[i]This message could not be rendered correctly, likely due to an error in its markup. Falling back to plain-text rendering.[/i]\n\n"+escaped_content
|
||||
w.markup = True
|
||||
Clock.schedule_once(deferred, 0.1)
|
||||
return x
|
||||
|
||||
Clock.schedule_once(cbf(item.ids.content_text), 0.25)
|
||||
|
||||
if not RNS.vendor.platformutils.is_android():
|
||||
item.radius = dp(5)
|
||||
|
||||
|
@ -148,6 +148,15 @@ class ObjectDetails():
|
||||
else:
|
||||
self.from_objects = False
|
||||
|
||||
if self.viewing_self:
|
||||
self.screen.ids.track_button.disabled = True
|
||||
else:
|
||||
self.screen.ids.track_button.disabled = False
|
||||
if self.app.sideband.is_tracking(source_dest):
|
||||
self.screen.ids.track_button.text = "Stop Live Tracking"
|
||||
else:
|
||||
self.screen.ids.track_button.text = "Start Live Tracking"
|
||||
|
||||
self.coords = None
|
||||
self.telemetry_list.data = []
|
||||
pds = multilingual_markup(escape_markup(str(self.app.sideband.peer_display_name(source_dest))).encode("utf-8")).decode("utf-8")
|
||||
@ -218,6 +227,15 @@ class ObjectDetails():
|
||||
self.clear_widget()
|
||||
self.update()
|
||||
|
||||
def live_tracking(self, sender):
|
||||
if not self.viewing_self:
|
||||
if not self.app.sideband.is_tracking(self.object_hash):
|
||||
self.app.sideband.start_tracking(self.object_hash, interval=59, duration=7*24*60*60)
|
||||
self.screen.ids.track_button.text = "Stop Live Tracking"
|
||||
else:
|
||||
self.app.sideband.stop_tracking(self.object_hash)
|
||||
self.screen.ids.track_button.text = "Start Live Tracking"
|
||||
|
||||
def send_update(self):
|
||||
if not self.viewing_self:
|
||||
result = self.app.sideband.send_latest_telemetry(to_addr=self.object_hash)
|
||||
@ -643,10 +661,9 @@ class RVDetails(MDRecycleView):
|
||||
alt_str = RNS.prettydistance(alt)
|
||||
formatted_values = f"Coordinates [b]{fcoords}[/b], altitude [b]{alt_str}[/b]"
|
||||
if speed != None:
|
||||
if speed > 0.02:
|
||||
if speed > 0.1:
|
||||
speed_formatted_values = f"Speed [b]{speed} Km/h[/b], heading [b]{heading}°[/b]"
|
||||
else:
|
||||
# speed_formatted_values = f"Speed [b]0 Km/h[/b]"
|
||||
speed_formatted_values = f"Object is [b]stationary[/b]"
|
||||
else:
|
||||
speed_formatted_values = None
|
||||
@ -972,22 +989,22 @@ MDScreen:
|
||||
on_release: root.delegate.request_update()
|
||||
disabled: False
|
||||
|
||||
# MDBoxLayout:
|
||||
# orientation: "horizontal"
|
||||
# spacing: dp(16)
|
||||
# size_hint_y: None
|
||||
# height: self.minimum_height
|
||||
# padding: [dp(24), dp(16), dp(24), dp(24)]
|
||||
MDBoxLayout:
|
||||
orientation: "horizontal"
|
||||
spacing: dp(16)
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
padding: [dp(24), dp(0), dp(24), dp(24)]
|
||||
|
||||
# MDRectangleFlatIconButton:
|
||||
# id: delete_button
|
||||
# icon: "trash-can-outline"
|
||||
# text: "Delete All Telemetry"
|
||||
# padding: [dp(0), dp(14), dp(0), dp(14)]
|
||||
# icon_size: dp(24)
|
||||
# font_size: dp(16)
|
||||
# size_hint: [1.0, None]
|
||||
# on_release: root.delegate.copy_telemetry(self)
|
||||
# disabled: False
|
||||
MDRectangleFlatIconButton:
|
||||
id: track_button
|
||||
icon: "crosshairs-gps"
|
||||
text: "Start Live Tracking"
|
||||
padding: [dp(0), dp(14), dp(0), dp(14)]
|
||||
icon_size: dp(24)
|
||||
font_size: dp(16)
|
||||
size_hint: [1.0, None]
|
||||
on_release: root.delegate.live_tracking(self)
|
||||
disabled: False
|
||||
|
||||
"""
|
@ -9,7 +9,10 @@ from kivy.utils import escape_markup
|
||||
from kivymd.uix.recycleview import MDRecycleView
|
||||
from kivymd.uix.list import OneLineIconListItem
|
||||
from kivymd.uix.pickers import MDColorPicker
|
||||
from kivymd.uix.button import MDRectangleFlatButton
|
||||
from kivymd.uix.dialog import MDDialog
|
||||
from kivymd.icon_definitions import md_icons
|
||||
from kivymd.toast import toast
|
||||
from kivy.properties import StringProperty, BooleanProperty
|
||||
from kivy.effects.scroll import ScrollEffect
|
||||
from kivy.clock import Clock
|
||||
@ -29,6 +32,7 @@ class Utilities():
|
||||
self.screen = None
|
||||
self.rnstatus_screen = None
|
||||
self.rnstatus_instance = None
|
||||
self.logviewer_screen = None
|
||||
|
||||
if not self.app.root.ids.screen_manager.has_screen("utilities_screen"):
|
||||
self.screen = Builder.load_string(layout_utilities_screen)
|
||||
@ -37,13 +41,40 @@ class Utilities():
|
||||
self.app.root.ids.screen_manager.add_widget(self.screen)
|
||||
|
||||
self.screen.ids.telemetry_scrollview.effect_cls = ScrollEffect
|
||||
info = "\nYou can use various RNS utilities from Sideband. "
|
||||
info += ""
|
||||
info = "This section contains various utilities and diagnostics tools, "
|
||||
info += "that can be helpful while using Sideband and Reticulum."
|
||||
|
||||
if self.app.theme_cls.theme_style == "Dark":
|
||||
info = "[color=#"+self.app.dark_theme_text_color+"]"+info+"[/color]"
|
||||
|
||||
self.screen.ids.telemetry_info.text = info
|
||||
self.screen.ids.utilities_info.text = info
|
||||
|
||||
|
||||
### RNode Flasher
|
||||
######################################
|
||||
|
||||
def flasher_action(self, sender=None):
|
||||
yes_button = MDRectangleFlatButton(text="Launch",font_size=dp(18), theme_text_color="Custom", line_color=self.app.color_accept, text_color=self.app.color_accept)
|
||||
no_button = MDRectangleFlatButton(text="Back",font_size=dp(18))
|
||||
dialog = MDDialog(
|
||||
title="RNode Flasher",
|
||||
text="You can use the included web-based RNode flasher, by starting Sideband's built-in repository server, and accessing the RNode Flasher page.",
|
||||
buttons=[ no_button, yes_button ],
|
||||
# elevation=0,
|
||||
)
|
||||
def dl_yes(s):
|
||||
dialog.dismiss()
|
||||
self.app.sideband.start_webshare()
|
||||
def cb(dt):
|
||||
self.app.repository_action()
|
||||
Clock.schedule_once(cb, 0.6)
|
||||
|
||||
def dl_no(s):
|
||||
dialog.dismiss()
|
||||
|
||||
yes_button.bind(on_release=dl_yes)
|
||||
no_button.bind(on_release=dl_no)
|
||||
dialog.open()
|
||||
|
||||
|
||||
### rnstatus screen
|
||||
@ -72,24 +103,91 @@ class Utilities():
|
||||
|
||||
import io
|
||||
from contextlib import redirect_stdout
|
||||
output_marker = "===begin rnstatus output==="
|
||||
output = "None"
|
||||
with io.StringIO() as buffer, redirect_stdout(buffer):
|
||||
print(output_marker, end="")
|
||||
self.rnstatus_instance.main(rns_instance=RNS.Reticulum.get_instance())
|
||||
output = buffer.getvalue()
|
||||
|
||||
remainder = output[:output.find(output_marker)]
|
||||
output = output[output.find(output_marker)+len(output_marker):]
|
||||
print(remainder, end="")
|
||||
with RNS.logging_lock:
|
||||
self.rnstatus_instance.main(rns_instance=RNS.Reticulum.get_instance())
|
||||
output = buffer.getvalue()
|
||||
|
||||
def cb(dt):
|
||||
self.rnstatus_screen.ids.rnstatus_output.text = f"[font=RobotoMono-Regular]{output}[/font]"
|
||||
self.rnstatus_screen.ids.rnstatus_output.text = f"[font=RobotoMono-Regular][size={int(dp(12))}]{output}[/size][/font]"
|
||||
Clock.schedule_once(cb, 0.2)
|
||||
|
||||
if self.app.root.ids.screen_manager.current == "rnstatus_screen":
|
||||
Clock.schedule_once(self.update_rnstatus, 1)
|
||||
|
||||
### Advanced Configuration screen
|
||||
######################################
|
||||
|
||||
def advanced_action(self, sender=None):
|
||||
if not self.app.root.ids.screen_manager.has_screen("advanced_screen"):
|
||||
self.advanced_screen = Builder.load_string(layout_advanced_screen)
|
||||
self.advanced_screen.app = self.app
|
||||
self.advanced_screen.delegate = self
|
||||
self.app.root.ids.screen_manager.add_widget(self.advanced_screen)
|
||||
|
||||
self.app.root.ids.screen_manager.transition.direction = "left"
|
||||
self.app.root.ids.screen_manager.current = "advanced_screen"
|
||||
self.app.sideband.setstate("app.displaying", self.app.root.ids.screen_manager.current)
|
||||
|
||||
self.update_advanced()
|
||||
|
||||
def update_advanced(self, sender=None):
|
||||
if RNS.vendor.platformutils.is_android():
|
||||
ct = self.app.sideband.config["config_template"]
|
||||
self.advanced_screen.ids.config_template.text = f"[font=RobotoMono-Regular][size={int(dp(12))}]{ct}[/size][/font]"
|
||||
else:
|
||||
self.advanced_screen.ids.config_template.text = f"[font=RobotoMono-Regular][size={int(dp(12))}]On this platform, Reticulum configuration is managed by the system. You can change the configuration by editing the file located at:\n\n{self.app.sideband.reticulum.configpath}[/size][/font]"
|
||||
|
||||
def copy_config(self, sender=None):
|
||||
if RNS.vendor.platformutils.is_android():
|
||||
Clipboard.copy(self.app.sideband.config_template)
|
||||
|
||||
def paste_config(self, sender=None):
|
||||
if RNS.vendor.platformutils.is_android():
|
||||
self.app.sideband.config_template = Clipboard.paste()
|
||||
self.app.sideband.config["config_template"] = self.app.sideband.config_template
|
||||
self.app.sideband.save_configuration()
|
||||
self.update_advanced()
|
||||
|
||||
### Log viewer screen
|
||||
######################################
|
||||
|
||||
def logviewer_action(self, sender=None):
|
||||
if not self.app.root.ids.screen_manager.has_screen("logviewer_screen"):
|
||||
self.logviewer_screen = Builder.load_string(layout_logviewer_screen)
|
||||
self.logviewer_screen.app = self.app
|
||||
self.logviewer_screen.delegate = self
|
||||
self.app.root.ids.screen_manager.add_widget(self.logviewer_screen)
|
||||
|
||||
self.app.root.ids.screen_manager.transition.direction = "left"
|
||||
self.app.root.ids.screen_manager.current = "logviewer_screen"
|
||||
self.app.sideband.setstate("app.displaying", self.app.root.ids.screen_manager.current)
|
||||
|
||||
self.update_logviewer()
|
||||
|
||||
def update_logviewer(self, sender=None):
|
||||
threading.Thread(target=self.update_logviewer_job, daemon=True).start()
|
||||
|
||||
def update_logviewer_job(self, sender=None):
|
||||
try:
|
||||
output = self.app.sideband.get_log()
|
||||
except Exception as e:
|
||||
output = f"An error occurred while retrieving log entries:\n{e}"
|
||||
|
||||
self.logviewer_screen.log_contents = output
|
||||
def cb(dt):
|
||||
self.logviewer_screen.ids.logviewer_output.text = f"[font=RobotoMono-Regular][size={int(dp(12))}]{output}[/size][/font]"
|
||||
Clock.schedule_once(cb, 0.2)
|
||||
|
||||
if self.app.root.ids.screen_manager.current == "logviewer_screen":
|
||||
Clock.schedule_once(self.update_logviewer, 1)
|
||||
|
||||
def logviewer_copy(self, sender=None):
|
||||
Clipboard.copy(self.logviewer_screen.log_contents)
|
||||
if True or RNS.vendor.platformutils.is_android():
|
||||
toast("Log copied to clipboard")
|
||||
|
||||
|
||||
layout_utilities_screen = """
|
||||
MDScreen:
|
||||
@ -116,14 +214,14 @@ MDScreen:
|
||||
orientation: "vertical"
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
padding: [dp(28), dp(48), dp(28), dp(16)]
|
||||
padding: [dp(28), dp(32), dp(28), dp(16)]
|
||||
|
||||
# MDLabel:
|
||||
# text: "Utilities & Tools"
|
||||
# font_style: "H6"
|
||||
|
||||
MDLabel:
|
||||
text: "Utilities & Tools"
|
||||
font_style: "H6"
|
||||
|
||||
MDLabel:
|
||||
id: telemetry_info
|
||||
id: utilities_info
|
||||
markup: True
|
||||
text: ""
|
||||
size_hint_y: None
|
||||
@ -156,8 +254,30 @@ MDScreen:
|
||||
icon_size: dp(24)
|
||||
font_size: dp(16)
|
||||
size_hint: [1.0, None]
|
||||
on_release: root.delegate.rnstatus_action(self)
|
||||
disabled: True
|
||||
on_release: root.delegate.logviewer_action(self)
|
||||
disabled: False
|
||||
|
||||
MDRectangleFlatIconButton:
|
||||
id: flasher_button
|
||||
icon: "radio-handheld"
|
||||
text: "RNode Flasher"
|
||||
padding: [dp(0), dp(14), dp(0), dp(14)]
|
||||
icon_size: dp(24)
|
||||
font_size: dp(16)
|
||||
size_hint: [1.0, None]
|
||||
on_release: root.delegate.flasher_action(self)
|
||||
disabled: False
|
||||
|
||||
MDRectangleFlatIconButton:
|
||||
id: advanced_button
|
||||
icon: "network-pos"
|
||||
text: "Advanced RNS Configuration"
|
||||
padding: [dp(0), dp(14), dp(0), dp(14)]
|
||||
icon_size: dp(24)
|
||||
font_size: dp(16)
|
||||
size_hint: [1.0, None]
|
||||
on_release: root.delegate.advanced_action(self)
|
||||
disabled: False
|
||||
|
||||
"""
|
||||
|
||||
@ -177,12 +297,12 @@ MDScreen:
|
||||
[['menu', lambda x: root.app.nav_drawer.set_state("open")]]
|
||||
right_action_items:
|
||||
[
|
||||
['refresh', lambda x: root.delegate.update_rnstatus()],
|
||||
# ['refresh', lambda x: root.delegate.update_rnstatus()],
|
||||
['close', lambda x: root.app.close_sub_utilities_action(self)],
|
||||
]
|
||||
|
||||
MDScrollView:
|
||||
id: sensors_scrollview
|
||||
id: rnstatus_scrollview
|
||||
size_hint_x: 1
|
||||
size_hint_y: None
|
||||
size: [root.width, root.height-root.ids.top_bar.height]
|
||||
@ -202,4 +322,120 @@ MDScreen:
|
||||
size_hint_y: None
|
||||
text_size: self.width, None
|
||||
height: self.texture_size[1]
|
||||
"""
|
||||
"""
|
||||
|
||||
layout_logviewer_screen = """
|
||||
MDScreen:
|
||||
name: "logviewer_screen"
|
||||
|
||||
BoxLayout:
|
||||
orientation: "vertical"
|
||||
|
||||
MDTopAppBar:
|
||||
id: top_bar
|
||||
title: "Log Viewer"
|
||||
anchor_title: "left"
|
||||
elevation: 0
|
||||
left_action_items:
|
||||
[['menu', lambda x: root.app.nav_drawer.set_state("open")]]
|
||||
right_action_items:
|
||||
[
|
||||
['content-copy', lambda x: root.delegate.logviewer_copy()],
|
||||
['close', lambda x: root.app.close_sub_utilities_action(self)],
|
||||
]
|
||||
|
||||
MDScrollView:
|
||||
id: logviewer_scrollview
|
||||
size_hint_x: 1
|
||||
size_hint_y: None
|
||||
size: [root.width, root.height-root.ids.top_bar.height]
|
||||
do_scroll_x: False
|
||||
do_scroll_y: True
|
||||
|
||||
MDGridLayout:
|
||||
cols: 1
|
||||
padding: [dp(28), dp(14), dp(28), dp(28)]
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
|
||||
MDLabel:
|
||||
id: logviewer_output
|
||||
markup: True
|
||||
text: ""
|
||||
size_hint_y: None
|
||||
text_size: self.width, None
|
||||
height: self.texture_size[1]
|
||||
"""
|
||||
|
||||
layout_advanced_screen = """
|
||||
MDScreen:
|
||||
name: "advanced_screen"
|
||||
|
||||
BoxLayout:
|
||||
orientation: "vertical"
|
||||
|
||||
MDTopAppBar:
|
||||
id: top_bar
|
||||
title: "RNS Configuration"
|
||||
anchor_title: "left"
|
||||
elevation: 0
|
||||
left_action_items:
|
||||
[['menu', lambda x: root.app.nav_drawer.set_state("open")]]
|
||||
right_action_items:
|
||||
[
|
||||
# ['refresh', lambda x: root.delegate.update_rnstatus()],
|
||||
['close', lambda x: root.app.close_sub_utilities_action(self)],
|
||||
]
|
||||
|
||||
MDScrollView:
|
||||
id: advanced_scrollview
|
||||
size_hint_x: 1
|
||||
size_hint_y: None
|
||||
size: [root.width, root.height-root.ids.top_bar.height]
|
||||
do_scroll_x: False
|
||||
do_scroll_y: True
|
||||
|
||||
MDGridLayout:
|
||||
cols: 1
|
||||
padding: [dp(28), dp(14), dp(28), dp(28)]
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
|
||||
MDBoxLayout:
|
||||
orientation: "horizontal"
|
||||
spacing: dp(24)
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
padding: [dp(0), dp(14), dp(0), dp(24)]
|
||||
|
||||
MDRectangleFlatIconButton:
|
||||
id: telemetry_button
|
||||
icon: "content-copy"
|
||||
text: "Copy Configuration"
|
||||
padding: [dp(0), dp(14), dp(0), dp(14)]
|
||||
icon_size: dp(24)
|
||||
font_size: dp(16)
|
||||
size_hint: [1.0, None]
|
||||
on_release: root.delegate.copy_config(self)
|
||||
disabled: False
|
||||
|
||||
MDRectangleFlatIconButton:
|
||||
id: coordinates_button
|
||||
icon: "download"
|
||||
text: "Paste Configuration"
|
||||
padding: [dp(0), dp(14), dp(0), dp(14)]
|
||||
icon_size: dp(24)
|
||||
font_size: dp(16)
|
||||
size_hint: [1.0, None]
|
||||
on_release: root.delegate.paste_config(self)
|
||||
disabled: False
|
||||
|
||||
|
||||
MDLabel:
|
||||
id: config_template
|
||||
markup: True
|
||||
text: ""
|
||||
size_hint_y: None
|
||||
text_size: self.width, None
|
||||
height: self.texture_size[1]
|
||||
"""
|
||||
|
17
setup.py
17
setup.py
@ -47,6 +47,20 @@ def glob_paths(pattern):
|
||||
|
||||
return out_files
|
||||
|
||||
def glob_share():
|
||||
out_files = []
|
||||
src_path = os.path.join(os.path.dirname(__file__), "sbapp/share")
|
||||
print(src_path)
|
||||
|
||||
for root, dirs, files in os.walk(src_path):
|
||||
for file in files:
|
||||
filepath = os.path.join(str(Path(*Path(root).parts[1:])), file)
|
||||
|
||||
if not "mirrors/unsigned.io" in str(filepath):
|
||||
out_files.append(filepath.split(f"sbapp{os.sep}")[1])
|
||||
|
||||
return out_files
|
||||
|
||||
packages = setuptools.find_packages(
|
||||
exclude=[
|
||||
"sbapp.plyer.platforms.android",
|
||||
@ -63,6 +77,7 @@ package_data = {
|
||||
"kivymd/images/*",
|
||||
"kivymd/*",
|
||||
"mapview/icons/*",
|
||||
*glob_share(),
|
||||
*glob_paths(".kv")
|
||||
]
|
||||
}
|
||||
@ -99,7 +114,7 @@ setuptools.setup(
|
||||
]
|
||||
},
|
||||
install_requires=[
|
||||
"rns>=0.8.7",
|
||||
"rns>=0.8.8",
|
||||
"lxmf>=0.5.8",
|
||||
"kivy>=2.3.0",
|
||||
"pillow>=10.2.0",
|
||||
|
Loading…
Reference in New Issue
Block a user