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 ./__pycache__ -r)
|
||||||
-(rm ./app_storage -r)
|
-(rm ./app_storage -r)
|
||||||
-(rm ./share/pkg/* -r)
|
-(rm ./share/pkg/* -r)
|
||||||
-(rm ./share/mirrors/* -r)
|
-(rm ./share/mirrors/* -rf)
|
||||||
-(rm ./bin -r)
|
-(rm ./bin -r)
|
||||||
|
|
||||||
cleanlibs:
|
cleanlibs:
|
||||||
@ -66,13 +66,14 @@ fetchshare:
|
|||||||
cp ../../dist_archive/lxmf-*-py3-none-any.whl ./share/pkg/
|
cp ../../dist_archive/lxmf-*-py3-none-any.whl ./share/pkg/
|
||||||
cp ../../dist_archive/nomadnet-*-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/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/
|
cp ../../dist_archive/RNode_Firmware_*_Source.zip ./share/pkg/
|
||||||
zip --junk-paths ./share/pkg/example_plugins.zip ../docs/example_plugins/*.py
|
zip --junk-paths ./share/pkg/example_plugins.zip ../docs/example_plugins/*.py
|
||||||
cp -r ../../dist_archive/reticulum.network ./share/mirrors/
|
cp -r ../../dist_archive/reticulum.network ./share/mirrors/
|
||||||
cp -r ../../dist_archive/unsigned.io ./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.pdf ./share/mirrors/Reticulum_Manual.pdf
|
||||||
cp ../../dist_archive/Reticulum\ Manual.epub ./share/mirrors/Reticulum_Manual.epub
|
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:
|
release:
|
||||||
. venv/bin/activate; buildozer android 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.regex = __version__ = ['"](.*)['"]
|
||||||
version.filename = %(source.dir)s/main.py
|
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
|
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 threading
|
||||||
import RNS.vendor.umsgpack as msgpack
|
import RNS.vendor.umsgpack as msgpack
|
||||||
|
|
||||||
|
WINDOW_DEFAULT_WIDTH = "494"
|
||||||
|
WINDOW_DEFAULT_HEIGHT = "800"
|
||||||
|
|
||||||
app_ui_scaling_path = None
|
app_ui_scaling_path = None
|
||||||
def apply_ui_scale():
|
def apply_ui_scale():
|
||||||
global app_ui_scaling_path
|
global app_ui_scaling_path
|
||||||
@ -171,6 +174,11 @@ if not args.daemon:
|
|||||||
local = os.path.dirname(__file__)
|
local = os.path.dirname(__file__)
|
||||||
sys.path.append(local)
|
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:
|
if args.daemon:
|
||||||
from .sideband.core import SidebandCore
|
from .sideband.core import SidebandCore
|
||||||
class DaemonElement():
|
class DaemonElement():
|
||||||
@ -558,6 +566,15 @@ class SidebandApp(MDApp):
|
|||||||
fn_italic=fb_path+"NotoSans-Italic.ttf",
|
fn_italic=fb_path+"NotoSans-Italic.ttf",
|
||||||
fn_bolditalic=fb_path+"NotoSans-BoldItalic.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):
|
def update_input_language(self):
|
||||||
language = self.sideband.config["input_language"]
|
language = self.sideband.config["input_language"]
|
||||||
if language == None:
|
if language == None:
|
||||||
@ -1406,6 +1423,8 @@ class SidebandApp(MDApp):
|
|||||||
self.close_sub_telemetry_action()
|
self.close_sub_telemetry_action()
|
||||||
elif self.root.ids.screen_manager.current == "rnstatus_screen":
|
elif self.root.ids.screen_manager.current == "rnstatus_screen":
|
||||||
self.close_sub_utilities_action()
|
self.close_sub_utilities_action()
|
||||||
|
elif self.root.ids.screen_manager.current == "logviewer_screen":
|
||||||
|
self.close_sub_utilities_action()
|
||||||
else:
|
else:
|
||||||
self.open_conversations(direction="right")
|
self.open_conversations(direction="right")
|
||||||
|
|
||||||
@ -2501,30 +2520,35 @@ class SidebandApp(MDApp):
|
|||||||
return "Could not retrieve connectivity status"
|
return "Could not retrieve connectivity status"
|
||||||
|
|
||||||
def connectivity_status(self, sender):
|
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:
|
if self.connectivity_updater != None:
|
||||||
self.connectivity_updater.cancel()
|
self.connectivity_updater.cancel()
|
||||||
|
|
||||||
yes_button.bind(on_release=dl_yes)
|
self.connectivity_updater = Clock.schedule_interval(cs_updater, 2.0)
|
||||||
dialog.open()
|
|
||||||
|
|
||||||
if self.connectivity_updater != None:
|
else:
|
||||||
self.connectivity_updater.cancel()
|
if not self.utilities_ready:
|
||||||
|
self.utilities_init()
|
||||||
self.connectivity_updater = Clock.schedule_interval(cs_updater, 2.0)
|
self.utilities_screen.rnstatus_action()
|
||||||
|
|
||||||
def ingest_lxm_action(self, sender):
|
def ingest_lxm_action(self, sender):
|
||||||
def cb(dt):
|
def cb(dt):
|
||||||
@ -3009,6 +3033,11 @@ class SidebandApp(MDApp):
|
|||||||
self.sideband.save_configuration()
|
self.sideband.save_configuration()
|
||||||
self.update_ui_theme()
|
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):
|
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.config["display_style_in_contact_list"] = self.settings_screen.ids.display_style_in_contact_list.active
|
||||||
self.sideband.save_configuration()
|
self.sideband.save_configuration()
|
||||||
@ -3019,6 +3048,10 @@ class SidebandApp(MDApp):
|
|||||||
self.sideband.save_configuration()
|
self.sideband.save_configuration()
|
||||||
self.sideband.setstate("wants.viewupdate.conversations", True)
|
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):
|
def save_advanced_stats(sender=None, event=None):
|
||||||
self.sideband.config["advanced_stats"] = self.settings_screen.ids.settings_advanced_statistics.active
|
self.sideband.config["advanced_stats"] = self.settings_screen.ids.settings_advanced_statistics.active
|
||||||
self.sideband.save_configuration()
|
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.active = self.sideband.config["eink_mode"]
|
||||||
self.settings_screen.ids.settings_eink_mode.bind(active=save_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.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)
|
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.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_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.active = self.sideband.config["lxmf_ignore_invalid_stamps"]
|
||||||
self.settings_screen.ids.settings_ignore_invalid_stamps.bind(active=save_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"):
|
def repository_action(self, sender=None, direction="left"):
|
||||||
if self.repository_ready:
|
if self.repository_ready:
|
||||||
|
self.repository_update_info()
|
||||||
self.repository_open(direction=direction)
|
self.repository_open(direction=direction)
|
||||||
else:
|
else:
|
||||||
self.loader_action(direction=direction)
|
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 += "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"
|
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 self.sideband.webshare_server != None:
|
||||||
if RNS.vendor.platformutils.is_android():
|
def getIP():
|
||||||
def getIP():
|
adrs = []
|
||||||
adrs = []
|
if RNS.vendor.platformutils.is_android():
|
||||||
try:
|
try:
|
||||||
from jnius import autoclass
|
from jnius import autoclass
|
||||||
import ipaddress
|
import ipaddress
|
||||||
@ -3700,24 +3740,30 @@ class SidebandApp(MDApp):
|
|||||||
RNS.log("Error while getting repository IP address: "+str(e), RNS.LOG_ERROR)
|
RNS.log("Error while getting repository IP address: "+str(e), RNS.LOG_ERROR)
|
||||||
return None
|
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:
|
else:
|
||||||
ipstr = ""
|
import socket
|
||||||
for ip in ips:
|
adrs.append(socket.gethostbyname(socket.gethostname()))
|
||||||
ipstr += "http://"+str(ip)+":4444/\n"
|
|
||||||
self.reposository_url = ipstr
|
|
||||||
|
|
||||||
ms = "" if len(ips) == 1 else "es"
|
return adrs
|
||||||
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)
|
|
||||||
|
|
||||||
self.repository_screen.ids.repository_enable_button.disabled = True
|
ips = getIP()
|
||||||
self.repository_screen.ids.repository_disable_button.disabled = False
|
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:
|
else:
|
||||||
self.repository_screen.ids.repository_enable_button.disabled = False
|
self.repository_screen.ids.repository_enable_button.disabled = False
|
||||||
@ -3739,39 +3785,85 @@ class SidebandApp(MDApp):
|
|||||||
def update_job(sender=None):
|
def update_job(sender=None):
|
||||||
try:
|
try:
|
||||||
import requests
|
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
|
except Exception as e:
|
||||||
apk_version = None
|
self.repository_screen.ids.repository_update.text = f"Downloading RNode firmware release info failed with the error:\n"+str(e)
|
||||||
apk_url = None
|
return
|
||||||
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
|
|
||||||
|
|
||||||
self.repository_screen.ids.repository_update.text = "Downloading: "+str(apk_url)
|
try:
|
||||||
with requests.get(apk_url, stream=True) as response:
|
for download in downloads:
|
||||||
with open("./dl_tmp", "wb") as tmp_file:
|
fw_url = download[0]
|
||||||
cs = 32*1024
|
pkgname = download[1]
|
||||||
tds = 0
|
self.repository_screen.ids.repository_update.text = "Downloading: "+str(pkgname)
|
||||||
for chunk in response.iter_content(chunk_size=cs):
|
with requests.get(fw_url, stream=True) as response:
|
||||||
tmp_file.write(chunk)
|
with open("./dl_tmp", "wb") as tmp_file:
|
||||||
tds += cs
|
cs = 32*1024
|
||||||
self.repository_screen.ids.repository_update.text = "Downloaded "+RNS.prettysize(tds)+" of "+str(pkgname)
|
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:
|
except Exception as e:
|
||||||
self.repository_screen.ids.repository_update.text = f"Downloading contents failed with the error:\n"+str(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.root.ids.screen_manager.add_widget(self.repository_screen)
|
||||||
|
|
||||||
self.repository_screen.ids.repository_scrollview.effect_cls = ScrollEffect
|
self.repository_screen.ids.repository_scrollview.effect_cls = ScrollEffect
|
||||||
|
|
||||||
self.repository_update_info()
|
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
|
self.repository_ready = True
|
||||||
|
|
||||||
def close_repository_action(self, sender=None):
|
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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<span class="logo">Guides</span>
|
<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.
|
Welcome to the <b>Guide Section</b>!<br/><br/>From here, you can browse or download various included manuals, documentation, references and guides.
|
||||||
|
|
||||||
<ul>
|
<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_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>
|
<li><a href="./mirrors/reticulum.network/index.html">Browse a local copy of the Reticulum website</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<br/>
|
||||||
<hr>
|
<hr>
|
||||||
<p><center></p>
|
<p><center></p>
|
||||||
</body></html>
|
</body></html>
|
||||||
|
@ -9,8 +9,9 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<<<<<<< HEAD
|
||||||
<span class="logo">openCom Companion Repo</span>
|
<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;">
|
<table style="margin-bottom: 1.5em;">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -9,10 +9,10 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<span class="logo">Software</span>
|
<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.
|
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 id="filelist">
|
||||||
</ul>
|
</ul><br/>
|
||||||
<hr>
|
<hr>
|
||||||
<p><center></p>
|
<p><center></p>
|
||||||
</body></html>
|
</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 copy import deepcopy
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
|
from collections import deque
|
||||||
from .res import sideband_fb_data
|
from .res import sideband_fb_data
|
||||||
from .sense import Telemeter, Commands
|
from .sense import Telemeter, Commands
|
||||||
from .plugins import SidebandCommandPlugin, SidebandServicePlugin, SidebandTelemetryPlugin
|
from .plugins import SidebandCommandPlugin, SidebandServicePlugin, SidebandTelemetryPlugin
|
||||||
@ -44,7 +45,7 @@ class PropagationNodeDetector():
|
|||||||
|
|
||||||
aspect_filter = "lxmf.propagation"
|
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:
|
try:
|
||||||
if app_data != None and len(app_data) > 0:
|
if app_data != None and len(app_data) > 0:
|
||||||
if pn_announce_data_is_valid(app_data):
|
if pn_announce_data_is_valid(app_data):
|
||||||
@ -63,8 +64,12 @@ class PropagationNodeDetector():
|
|||||||
# age = 0
|
# age = 0
|
||||||
pass
|
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")
|
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.config["lxmf_propagation_node"] == None:
|
||||||
if self.owner.active_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]]
|
DEFAULT_APPEARANCE = ["account", [0,0,0,1], [1,1,1,1]]
|
||||||
|
|
||||||
|
LOG_DEQUE_MAXLEN = 128
|
||||||
|
|
||||||
aspect_filter = "lxmf.delivery"
|
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
|
# Add the announce to the directory announce
|
||||||
# stream logger
|
# 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
|
# This reformats the new v0.5.0 announce data back to the expected format
|
||||||
# for Sidebands database and other handling functions.
|
# for Sidebands database and other handling functions.
|
||||||
dn = LXMF.display_name_from_app_data(app_data)
|
dn = LXMF.display_name_from_app_data(app_data)
|
||||||
@ -123,7 +134,7 @@ class SidebandCore():
|
|||||||
if dn != None:
|
if dn != None:
|
||||||
app_data = dn.encode("utf-8")
|
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):
|
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
|
self.is_service = is_service
|
||||||
@ -142,6 +153,7 @@ class SidebandCore():
|
|||||||
self.is_standalone = False
|
self.is_standalone = False
|
||||||
|
|
||||||
self.log_verbose = verbose
|
self.log_verbose = verbose
|
||||||
|
self.log_deque = deque(maxlen=self.LOG_DEQUE_MAXLEN)
|
||||||
self.owner_app = owner_app
|
self.owner_app = owner_app
|
||||||
self.reticulum = None
|
self.reticulum = None
|
||||||
self.webshare_server = None
|
self.webshare_server = None
|
||||||
@ -156,6 +168,7 @@ class SidebandCore():
|
|||||||
self.telemetry_send_blocked_until = 0
|
self.telemetry_send_blocked_until = 0
|
||||||
self.pending_telemetry_request = False
|
self.pending_telemetry_request = False
|
||||||
self.telemetry_request_max_history = 7*24*60*60
|
self.telemetry_request_max_history = 7*24*60*60
|
||||||
|
self.live_tracked_objects = {}
|
||||||
self.default_lxm_limit = 128*1000
|
self.default_lxm_limit = 128*1000
|
||||||
self.state_db = {}
|
self.state_db = {}
|
||||||
self.state_lock = Lock()
|
self.state_lock = Lock()
|
||||||
@ -167,6 +180,7 @@ class SidebandCore():
|
|||||||
self.owner_service = owner_service
|
self.owner_service = owner_service
|
||||||
self.allow_service_dispatch = True
|
self.allow_service_dispatch = True
|
||||||
self.version_str = ""
|
self.version_str = ""
|
||||||
|
self.config_template = rns_config
|
||||||
|
|
||||||
if config_path == None:
|
if config_path == None:
|
||||||
self.app_dir = plyer.storagepath.get_home_dir()+"/.config/occ"
|
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.log_dir = self.app_dir+"/app_storage/"
|
||||||
self.tmp_dir = self.app_dir+"/app_storage/tmp"
|
self.tmp_dir = self.app_dir+"/app_storage/tmp"
|
||||||
self.exports_dir = self.app_dir+"/exports"
|
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.first_run = True
|
||||||
self.saving_configuration = False
|
self.saving_configuration = False
|
||||||
@ -267,6 +288,29 @@ class SidebandCore():
|
|||||||
if load_config_only:
|
if load_config_only:
|
||||||
return
|
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
|
# Initialise Reticulum configuration
|
||||||
if RNS.vendor.platformutils.get_platform() == "android":
|
if RNS.vendor.platformutils.get_platform() == "android":
|
||||||
try:
|
try:
|
||||||
@ -277,11 +321,10 @@ class SidebandCore():
|
|||||||
RNS.log("Configuring Reticulum instance...")
|
RNS.log("Configuring Reticulum instance...")
|
||||||
if self.config["connect_transport"]:
|
if self.config["connect_transport"]:
|
||||||
RNS.log("Enabling Reticulum 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:
|
else:
|
||||||
RNS.log("Not enabling Reticulum Transport")
|
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 = open(self.rns_configdir+"/config", "wb")
|
||||||
config_file.write(generated_config.encode("utf-8"))
|
config_file.write(generated_config.encode("utf-8"))
|
||||||
@ -387,7 +430,7 @@ class SidebandCore():
|
|||||||
self.config["debug"] = False
|
self.config["debug"] = False
|
||||||
self.config["display_name"] = "Anonymous Peer"
|
self.config["display_name"] = "Anonymous Peer"
|
||||||
self.config["notifications_on"] = True
|
self.config["notifications_on"] = True
|
||||||
self.config["dark_ui"] = False
|
self.config["dark_ui"] = True
|
||||||
self.config["start_announce"] = True
|
self.config["start_announce"] = True
|
||||||
self.config["propagation_by_default"] = False
|
self.config["propagation_by_default"] = False
|
||||||
self.config["home_node_as_broadcast_repeater"] = False
|
self.config["home_node_as_broadcast_repeater"] = False
|
||||||
@ -403,8 +446,9 @@ class SidebandCore():
|
|||||||
self.config["last_lxmf_propagation_node"] = None
|
self.config["last_lxmf_propagation_node"] = None
|
||||||
self.config["nn_home_node"] = None
|
self.config["nn_home_node"] = None
|
||||||
self.config["print_command"] = "lp"
|
self.config["print_command"] = "lp"
|
||||||
self.config["eink_mode"] = False
|
self.config["eink_mode"] = True
|
||||||
self.config["lxm_limit_1mb"] = True
|
self.config["lxm_limit_1mb"] = True
|
||||||
|
self.config["trusted_markup_only"] = False
|
||||||
|
|
||||||
# Connectivity
|
# Connectivity
|
||||||
self.config["connect_transport"] = False
|
self.config["connect_transport"] = False
|
||||||
@ -569,7 +613,7 @@ class SidebandCore():
|
|||||||
if not "dark_ui" in self.config:
|
if not "dark_ui" in self.config:
|
||||||
self.config["dark_ui"] = True
|
self.config["dark_ui"] = True
|
||||||
if not "advanced_stats" in self.config:
|
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:
|
if not "lxmf_periodic_sync" in self.config:
|
||||||
self.config["lxmf_periodic_sync"] = False
|
self.config["lxmf_periodic_sync"] = False
|
||||||
if not "lxmf_ignore_unknown" in self.config:
|
if not "lxmf_ignore_unknown" in self.config:
|
||||||
@ -589,13 +633,17 @@ class SidebandCore():
|
|||||||
if not "print_command" in self.config:
|
if not "print_command" in self.config:
|
||||||
self.config["print_command"] = "lp"
|
self.config["print_command"] = "lp"
|
||||||
if not "eink_mode" in self.config:
|
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:
|
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:
|
if not "lxm_limit_1mb" in self.config:
|
||||||
self.config["lxm_limit_1mb"] = True
|
self.config["lxm_limit_1mb"] = True
|
||||||
if not "hq_ptt" in self.config:
|
if not "hq_ptt" in self.config:
|
||||||
self.config["hq_ptt"] = False
|
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:
|
if not "input_language" in self.config:
|
||||||
self.config["input_language"] = None
|
self.config["input_language"] = None
|
||||||
@ -604,6 +652,8 @@ class SidebandCore():
|
|||||||
if not "block_predictive_text" in self.config:
|
if not "block_predictive_text" in self.config:
|
||||||
self.config["block_predictive_text"] = False
|
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:
|
if not "connect_transport" in self.config:
|
||||||
self.config["connect_transport"] = False
|
self.config["connect_transport"] = False
|
||||||
if not "connect_rnode" in self.config:
|
if not "connect_rnode" in self.config:
|
||||||
@ -739,11 +789,11 @@ class SidebandCore():
|
|||||||
if not "telemetry_bg" in self.config:
|
if not "telemetry_bg" in self.config:
|
||||||
self.config["telemetry_bg"] = SidebandCore.DEFAULT_APPEARANCE[2]
|
self.config["telemetry_bg"] = SidebandCore.DEFAULT_APPEARANCE[2]
|
||||||
if not "telemetry_send_appearance" in self.config:
|
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:
|
if not "telemetry_display_trusted_only" in self.config:
|
||||||
self.config["telemetry_display_trusted_only"] = False
|
self.config["telemetry_display_trusted_only"] = False
|
||||||
if not "display_style_from_all" in self.config:
|
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:
|
if not "telemetry_receive_trusted_only" in self.config:
|
||||||
self.config["telemetry_receive_trusted_only"] = False
|
self.config["telemetry_receive_trusted_only"] = False
|
||||||
|
|
||||||
@ -854,9 +904,8 @@ class SidebandCore():
|
|||||||
time.sleep(0.15)
|
time.sleep(0.15)
|
||||||
try:
|
try:
|
||||||
self.saving_configuration = True
|
self.saving_configuration = True
|
||||||
config_file = open(self.config_path, "wb")
|
with open(self.config_path, "wb") as config_file:
|
||||||
config_file.write(msgpack.packb(self.config))
|
config_file.write(msgpack.packb(self.config))
|
||||||
config_file.close()
|
|
||||||
self.saving_configuration = False
|
self.saving_configuration = False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.saving_configuration = False
|
self.saving_configuration = False
|
||||||
@ -987,14 +1036,14 @@ class SidebandCore():
|
|||||||
else:
|
else:
|
||||||
plyer.notification.notify(title, content, app_icon=self.icon_32)
|
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:
|
try:
|
||||||
if app_data == None:
|
if app_data == None:
|
||||||
app_data = b""
|
app_data = b""
|
||||||
if type(app_data) != bytes:
|
if type(app_data) != bytes:
|
||||||
app_data = msgpack.packb([app_data, stamp_cost])
|
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)
|
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)
|
self.setstate("app.flags.new_announces", True)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -1316,7 +1365,7 @@ class SidebandCore():
|
|||||||
else:
|
else:
|
||||||
return False
|
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:
|
if self.allow_service_dispatch and self.is_client:
|
||||||
try:
|
try:
|
||||||
return self._service_request_latest_telemetry(from_addr)
|
return self._service_request_latest_telemetry(from_addr)
|
||||||
@ -1350,7 +1399,11 @@ class SidebandCore():
|
|||||||
if self.config["telemetry_use_propagation_only"] == True:
|
if self.config["telemetry_use_propagation_only"] == True:
|
||||||
desired_method = LXMF.LXMessage.PROPAGATED
|
desired_method = LXMF.LXMessage.PROPAGATED
|
||||||
else:
|
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
|
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: [
|
lxm_fields = { LXMF.FIELD_COMMANDS: [
|
||||||
@ -1363,7 +1416,7 @@ class SidebandCore():
|
|||||||
lxm.register_failed_callback(self.telemetry_request_finished)
|
lxm.register_failed_callback(self.telemetry_request_finished)
|
||||||
|
|
||||||
if self.message_router.get_outbound_propagation_node() != None:
|
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
|
lxm.try_propagation_on_fail = True
|
||||||
|
|
||||||
RNS.log(f"Sending telemetry request with timebase {request_timebase}", RNS.LOG_DEBUG)
|
RNS.log(f"Sending telemetry request with timebase {request_timebase}", RNS.LOG_DEBUG)
|
||||||
@ -1375,6 +1428,62 @@ class SidebandCore():
|
|||||||
else:
|
else:
|
||||||
return "not_sent"
|
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):
|
def _service_send_latest_telemetry(self, to_addr=None, stream=None, is_authorized_telemetry_request=False):
|
||||||
if not RNS.vendor.platformutils.is_android():
|
if not RNS.vendor.platformutils.is_android():
|
||||||
return False
|
return False
|
||||||
@ -1430,7 +1539,11 @@ class SidebandCore():
|
|||||||
if self.config["telemetry_use_propagation_only"] == True:
|
if self.config["telemetry_use_propagation_only"] == True:
|
||||||
desired_method = LXMF.LXMessage.PROPAGATED
|
desired_method = LXMF.LXMessage.PROPAGATED
|
||||||
else:
|
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)
|
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:
|
if lxm_fields == False and stream == None:
|
||||||
@ -1841,6 +1954,14 @@ class SidebandCore():
|
|||||||
elif "get_lxm_stamp_cost" in call:
|
elif "get_lxm_stamp_cost" in call:
|
||||||
args = call["get_lxm_stamp_cost"]
|
args = call["get_lxm_stamp_cost"]
|
||||||
connection.send(self.get_lxm_stamp_cost(args["lxm_hash"]))
|
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:
|
else:
|
||||||
connection.send(None)
|
connection.send(None)
|
||||||
|
|
||||||
@ -1934,10 +2055,10 @@ class SidebandCore():
|
|||||||
# TODO: Remove this again at some point in the future
|
# TODO: Remove this again at some point in the future
|
||||||
db = self.__db_connect()
|
db = self.__db_connect()
|
||||||
dbc = db.cursor()
|
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()
|
result = dbc.fetchall()
|
||||||
if len(result) == 0:
|
if len(result) == 0:
|
||||||
dbc.execute("ALTER TABLE lxm ADD COLUMN extra BLOB")
|
dbc.execute("ALTER TABLE announce ADD COLUMN extra BLOB")
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
def _db_initstate(self):
|
def _db_initstate(self):
|
||||||
@ -2494,8 +2615,16 @@ class SidebandCore():
|
|||||||
for entry in result:
|
for entry in result:
|
||||||
try:
|
try:
|
||||||
if not entry[2] in added_dests:
|
if not entry[2] in added_dests:
|
||||||
app_data = entry[3]
|
app_data = entry[3]
|
||||||
dest_type = entry[4]
|
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":
|
if dest_type == "lxmf.delivery":
|
||||||
announced_name = LXMF.display_name_from_app_data(app_data)
|
announced_name = LXMF.display_name_from_app_data(app_data)
|
||||||
announced_cost = self.message_router.get_outbound_stamp_cost(entry[2])
|
announced_cost = self.message_router.get_outbound_stamp_cost(entry[2])
|
||||||
@ -2503,11 +2632,12 @@ class SidebandCore():
|
|||||||
announced_name = None
|
announced_name = None
|
||||||
announced_cost = None
|
announced_cost = None
|
||||||
announce = {
|
announce = {
|
||||||
"dest": entry[2],
|
"dest" : entry[2],
|
||||||
"name": announced_name,
|
"name" : announced_name,
|
||||||
"cost": announced_cost,
|
"cost" : announced_cost,
|
||||||
"time": entry[1],
|
"time" : entry[1],
|
||||||
"type": dest_type
|
"type" : dest_type,
|
||||||
|
"extras": extras,
|
||||||
}
|
}
|
||||||
added_dests.append(entry[2])
|
added_dests.append(entry[2])
|
||||||
announces.append(announce)
|
announces.append(announce)
|
||||||
@ -2921,7 +3051,7 @@ class SidebandCore():
|
|||||||
|
|
||||||
self.__event_conversation_changed(context_dest)
|
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:
|
with self.db_lock:
|
||||||
db = self.__db_connect()
|
db = self.__db_connect()
|
||||||
dbc = db.cursor()
|
dbc = db.cursor()
|
||||||
@ -2935,14 +3065,16 @@ class SidebandCore():
|
|||||||
now = time.time()
|
now = time.time()
|
||||||
hash_material = str(time).encode("utf-8")+destination_hash+app_data+dest_type.encode("utf-8")
|
hash_material = str(time).encode("utf-8")+destination_hash+app_data+dest_type.encode("utf-8")
|
||||||
announce_hash = RNS.Identity.full_hash(hash_material)
|
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 = (
|
data = (
|
||||||
announce_hash,
|
announce_hash,
|
||||||
now,
|
now,
|
||||||
destination_hash,
|
destination_hash,
|
||||||
app_data,
|
app_data,
|
||||||
dest_type,
|
dest_type,
|
||||||
|
extras,
|
||||||
)
|
)
|
||||||
|
|
||||||
dbc.execute(query, data)
|
dbc.execute(query, data)
|
||||||
@ -3466,6 +3598,28 @@ class SidebandCore():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("An error occurred while requesting scheduled telemetry from collector: "+str(e), RNS.LOG_ERROR)
|
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):
|
def __start_jobs_deferred(self):
|
||||||
if self.is_service:
|
if self.is_service:
|
||||||
self.service_thread = threading.Thread(target=self._service_jobs, daemon=True)
|
self.service_thread = threading.Thread(target=self._service_jobs, daemon=True)
|
||||||
@ -3724,6 +3878,14 @@ class SidebandCore():
|
|||||||
if self.is_client:
|
if self.is_client:
|
||||||
self.service_rpc_set_debug(debug)
|
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):
|
def __start_jobs_immediate(self):
|
||||||
if self.log_verbose:
|
if self.log_verbose:
|
||||||
selected_level = 7 # debugging purposes
|
selected_level = 7 # debugging purposes
|
||||||
@ -3731,7 +3893,20 @@ class SidebandCore():
|
|||||||
selected_level = 2
|
selected_level = 2
|
||||||
|
|
||||||
self.setstate("init.loadingstate", "Substantiating Reticulum")
|
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:
|
if self.is_service:
|
||||||
self.__start_rpc_listener()
|
self.__start_rpc_listener()
|
||||||
@ -4615,6 +4790,7 @@ class SidebandCore():
|
|||||||
from http import server
|
from http import server
|
||||||
import socketserver
|
import socketserver
|
||||||
import json
|
import json
|
||||||
|
import ssl
|
||||||
|
|
||||||
webshare_dir = self.webshare_dir
|
webshare_dir = self.webshare_dir
|
||||||
port = 4444
|
port = 4444
|
||||||
@ -4635,7 +4811,7 @@ class SidebandCore():
|
|||||||
self.send_response(200)
|
self.send_response(200)
|
||||||
self.send_header("Content-type", "text/json")
|
self.send_header("Content-type", "text/json")
|
||||||
self.end_headers()
|
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"))
|
self.wfile.write(json_result.encode("utf-8"))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.send_response(500)
|
self.send_response(500)
|
||||||
@ -4650,6 +4826,8 @@ class SidebandCore():
|
|||||||
self.send_response(200)
|
self.send_response(200)
|
||||||
if path.lower().endswith(".apk"):
|
if path.lower().endswith(".apk"):
|
||||||
self.send_header("Content-type", "application/vnd.android.package-archive")
|
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.end_headers()
|
||||||
self.wfile.write(data)
|
self.wfile.write(data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -4659,7 +4837,36 @@ class SidebandCore():
|
|||||||
es = "Error"
|
es = "Error"
|
||||||
self.wfile.write(es.encode("utf-8"))
|
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
|
self.webshare_server = webserver
|
||||||
webserver.serve_forever()
|
webserver.serve_forever()
|
||||||
self.webshare_server = None
|
self.webshare_server = None
|
||||||
@ -4864,15 +5071,41 @@ class SidebandCore():
|
|||||||
if not self.reticulum.is_connected_to_shared_instance:
|
if not self.reticulum.is_connected_to_shared_instance:
|
||||||
RNS.Transport.detach_interfaces()
|
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]
|
[reticulum]
|
||||||
enable_transport = TRANSPORT_IS_ENABLED
|
# Don't change this line, use the UI
|
||||||
share_instance = Yes
|
# setting for selecting whether RNS
|
||||||
shared_instance_port = 37428
|
# transport is enabled or disabled
|
||||||
instance_control_port = 37429
|
enable_transport = TRANSPORT_IS_ENABLED
|
||||||
panic_on_interface_error = No
|
|
||||||
|
|
||||||
|
# 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]
|
[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 [
|
return [
|
||||||
struct.pack("!i", int(round(d["latitude"], 6)*1e6)),
|
struct.pack("!i", int(round(d["latitude"], 6)*1e6)),
|
||||||
struct.pack("!i", int(round(d["longitude"], 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["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)),
|
struct.pack("!H", int(round(d["accuracy"], 2)*1e2)),
|
||||||
d["last_update"],
|
d["last_update"],
|
||||||
]
|
]
|
||||||
@ -796,9 +796,9 @@ class Location(Sensor):
|
|||||||
return {
|
return {
|
||||||
"latitude": struct.unpack("!i", packed[0])[0]/1e6,
|
"latitude": struct.unpack("!i", packed[0])[0]/1e6,
|
||||||
"longitude": struct.unpack("!i", packed[1])[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,
|
"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,
|
"accuracy": struct.unpack("!H", packed[5])[0]/1e2,
|
||||||
"last_update": packed[6],
|
"last_update": packed[6],
|
||||||
}
|
}
|
||||||
|
@ -18,9 +18,9 @@ from kivy.lang.builder import Builder
|
|||||||
|
|
||||||
from kivy.utils import escape_markup
|
from kivy.utils import escape_markup
|
||||||
if RNS.vendor.platformutils.get_platform() == "android":
|
if RNS.vendor.platformutils.get_platform() == "android":
|
||||||
from ui.helpers import multilingual_markup
|
from ui.helpers import multilingual_markup, sig_icon_for_q
|
||||||
else:
|
else:
|
||||||
from .helpers import multilingual_markup
|
from .helpers import multilingual_markup, sig_icon_for_q
|
||||||
|
|
||||||
if RNS.vendor.platformutils.get_platform() == "android":
|
if RNS.vendor.platformutils.get_platform() == "android":
|
||||||
from ui.helpers import ts_format
|
from ui.helpers import ts_format
|
||||||
@ -92,6 +92,25 @@ class Announces():
|
|||||||
a_name = announce["name"]
|
a_name = announce["name"]
|
||||||
a_cost = announce["cost"]
|
a_cost = announce["cost"]
|
||||||
dest_type = announce["type"]
|
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 not context_dest in self.added_item_dests:
|
||||||
if self.app.sideband.is_trusted(context_dest):
|
if self.app.sideband.is_trusted(context_dest):
|
||||||
@ -99,16 +118,16 @@ class Announces():
|
|||||||
else:
|
else:
|
||||||
trust_icon = "account-question"
|
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")
|
name = multilingual_markup(escape_markup(str(name)).encode("utf-8")).decode("utf-8")
|
||||||
cost = str(cost)
|
cost = str(cost)
|
||||||
def x(sender):
|
def x(sender):
|
||||||
yes_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
|
yes_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
|
||||||
if dtype == "lxmf.delivery":
|
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":
|
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(
|
dialog = MDDialog(
|
||||||
text=ad_text,
|
text=ad_text,
|
||||||
@ -123,7 +142,8 @@ class Announces():
|
|||||||
dialog.open()
|
dialog.open()
|
||||||
return x
|
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":
|
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")
|
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"
|
disp_name = "Unknown Announce"
|
||||||
iconl = IconLeftWidget(icon="progress-question")
|
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.add_widget(iconl)
|
||||||
item.sb_uid = context_dest
|
item.sb_uid = context_dest
|
||||||
item.ts = ts
|
item.ts = ts
|
||||||
|
@ -133,6 +133,7 @@ class Conversations():
|
|||||||
unread = conv["unread"]
|
unread = conv["unread"]
|
||||||
last_activity = conv["last_activity"]
|
last_activity = conv["last_activity"]
|
||||||
trusted = conv["trust"] == 1
|
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)
|
appearance = self.app.sideband.peer_appearance(context_dest, conv=conv)
|
||||||
is_object = self.app.sideband.is_object(context_dest, conv_data=conv)
|
is_object = self.app.sideband.is_object(context_dest, conv_data=conv)
|
||||||
da = self.app.sideband.DEFAULT_APPEARANCE
|
da = self.app.sideband.DEFAULT_APPEARANCE
|
||||||
@ -141,7 +142,7 @@ class Conversations():
|
|||||||
conv_icon = self.trust_icon(conv)
|
conv_icon = self.trust_icon(conv)
|
||||||
fg = None; bg = None; ti_color = None
|
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]
|
fg = appearance[1] or da[1]; bg = appearance[2] or da[2]
|
||||||
ti_color = "Custom"
|
ti_color = "Custom"
|
||||||
else:
|
else:
|
||||||
|
@ -4,6 +4,7 @@ from kivy.uix.screenmanager import ScreenManager, Screen
|
|||||||
from kivymd.theming import ThemableBehavior
|
from kivymd.theming import ThemableBehavior
|
||||||
from kivymd.uix.list import OneLineIconListItem, MDList, IconLeftWidget, IconRightWidget
|
from kivymd.uix.list import OneLineIconListItem, MDList, IconLeftWidget, IconRightWidget
|
||||||
from kivy.properties import StringProperty
|
from kivy.properties import StringProperty
|
||||||
|
import re
|
||||||
|
|
||||||
ts_format = "%Y-%m-%d %H:%M:%S"
|
ts_format = "%Y-%m-%d %H:%M:%S"
|
||||||
file_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_dark = "600"
|
||||||
intensity_play_light = "300"
|
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):
|
class ContentNavigationDrawer(Screen):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -45,13 +59,12 @@ def strip_emojis(str_input):
|
|||||||
return output
|
return output
|
||||||
|
|
||||||
def multilingual_markup(data):
|
def multilingual_markup(data):
|
||||||
# TODO: Remove
|
|
||||||
# import time
|
|
||||||
# ts = time.time()
|
|
||||||
|
|
||||||
do = ""
|
do = ""
|
||||||
rfont = "default"
|
rfont = "default"
|
||||||
ds = data.decode("utf-8")
|
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:
|
for cp in ds:
|
||||||
match = False
|
match = False
|
||||||
switch = False
|
switch = False
|
||||||
@ -63,6 +76,10 @@ def multilingual_markup(data):
|
|||||||
switch = True
|
switch = True
|
||||||
rfont = "emoji"
|
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:
|
if not match:
|
||||||
for range_start in codepoint_map:
|
for range_start in codepoint_map:
|
||||||
range_end = codepoint_map[range_start][0]
|
range_end = codepoint_map[range_start][0]
|
||||||
@ -71,8 +88,9 @@ def multilingual_markup(data):
|
|||||||
if range_end >= ord(cp) >= range_start:
|
if range_end >= ord(cp) >= range_start:
|
||||||
match = True
|
match = True
|
||||||
if rfont != mapped_font:
|
if rfont != mapped_font:
|
||||||
rfont = mapped_font
|
if not in_persistent:
|
||||||
switch = True
|
rfont = mapped_font
|
||||||
|
switch = True
|
||||||
break
|
break
|
||||||
|
|
||||||
if (not match) and rfont != "default":
|
if (not match) and rfont != "default":
|
||||||
@ -86,15 +104,30 @@ def multilingual_markup(data):
|
|||||||
do += "[font="+str(rfont)+"]"
|
do += "[font="+str(rfont)+"]"
|
||||||
|
|
||||||
do += cp
|
do += cp
|
||||||
|
di += 1
|
||||||
|
|
||||||
if rfont != "default":
|
if rfont != "default":
|
||||||
do += "[/font]"
|
do += "[/font]"
|
||||||
|
|
||||||
# TODO: Remove
|
|
||||||
# print(do+"\n\n"+str(time.time()-ts))
|
|
||||||
|
|
||||||
return do.encode("utf-8")
|
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 = {
|
codepoint_map = {
|
||||||
0x0590: [0x05ff, "hebrew"],
|
0x0590: [0x05ff, "hebrew"],
|
||||||
0x2e3a: [0x2e3b, "chinese"],
|
0x2e3a: [0x2e3b, "chinese"],
|
||||||
@ -128,6 +161,29 @@ codepoint_map = {
|
|||||||
0xac00: [0xd7af, "korean"],
|
0xac00: [0xd7af, "korean"],
|
||||||
0xd7b0: [0xd7ff, "korean"],
|
0xd7b0: [0xd7ff, "korean"],
|
||||||
0x0900: [0x097f, "combined"], # Devanagari
|
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 = [
|
emoji_lookup = [
|
||||||
|
@ -80,13 +80,13 @@ MDNavigationLayout:
|
|||||||
on_release: root.ids.screen_manager.app.map_action(self)
|
on_release: root.ids.screen_manager.app.map_action(self)
|
||||||
|
|
||||||
|
|
||||||
OneLineIconListItem:
|
# OneLineIconListItem:
|
||||||
text: "Overview"
|
# text: "Overview"
|
||||||
on_release: root.ids.screen_manager.app.overview_action(self)
|
# on_release: root.ids.screen_manager.app.overview_action(self)
|
||||||
|
|
||||||
IconLeftWidget:
|
# IconLeftWidget:
|
||||||
icon: "view-dashboard-outline"
|
# icon: "view-dashboard-outline"
|
||||||
on_release: root.ids.screen_manager.app.overview_action(self)
|
# on_release: root.ids.screen_manager.app.overview_action(self)
|
||||||
|
|
||||||
|
|
||||||
OneLineIconListItem:
|
OneLineIconListItem:
|
||||||
@ -1287,7 +1287,7 @@ layout_settings_screen = """
|
|||||||
MDLabel:
|
MDLabel:
|
||||||
id: scaling_info
|
id: scaling_info
|
||||||
markup: True
|
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
|
size_hint_y: None
|
||||||
text_size: self.width, None
|
text_size: self.width, None
|
||||||
height: self.texture_size[1]
|
height: self.texture_size[1]
|
||||||
@ -1494,6 +1494,21 @@ MDScreen:
|
|||||||
pos_hint: {"center_y": 0.3}
|
pos_hint: {"center_y": 0.3}
|
||||||
active: False
|
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:
|
MDBoxLayout:
|
||||||
orientation: "horizontal"
|
orientation: "horizontal"
|
||||||
size_hint_y: None
|
size_hint_y: None
|
||||||
@ -1568,7 +1583,7 @@ MDScreen:
|
|||||||
height: dp(48)
|
height: dp(48)
|
||||||
|
|
||||||
MDLabel:
|
MDLabel:
|
||||||
text: "Announce Automatically"
|
text: "Announce automatically"
|
||||||
font_style: "H6"
|
font_style: "H6"
|
||||||
|
|
||||||
MDSwitch:
|
MDSwitch:
|
||||||
@ -1583,7 +1598,7 @@ MDScreen:
|
|||||||
height: dp(48)
|
height: dp(48)
|
||||||
|
|
||||||
MDLabel:
|
MDLabel:
|
||||||
text: "Try propagation on direct delivery failure"
|
text: "Try propagation automatically"
|
||||||
font_style: "H6"
|
font_style: "H6"
|
||||||
|
|
||||||
MDSwitch:
|
MDSwitch:
|
||||||
@ -1624,6 +1639,22 @@ MDScreen:
|
|||||||
disabled: False
|
disabled: False
|
||||||
active: 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:
|
MDBoxLayout:
|
||||||
orientation: "horizontal"
|
orientation: "horizontal"
|
||||||
size_hint_y: None
|
size_hint_y: None
|
||||||
@ -1752,21 +1783,21 @@ MDScreen:
|
|||||||
disabled: False
|
disabled: False
|
||||||
active: False
|
active: False
|
||||||
|
|
||||||
MDBoxLayout:
|
# MDBoxLayout:
|
||||||
orientation: "horizontal"
|
# orientation: "horizontal"
|
||||||
size_hint_y: None
|
# size_hint_y: None
|
||||||
padding: [0,0,dp(24),dp(0)]
|
# padding: [0,0,dp(24),dp(0)]
|
||||||
height: dp(48)
|
# height: dp(48)
|
||||||
|
|
||||||
MDLabel:
|
# MDLabel:
|
||||||
text: "Use Home Node as Broadcast Repeater"
|
# text: "Use Home Node as Broadcast Repeater"
|
||||||
font_style: "H6"
|
# font_style: "H6"
|
||||||
|
|
||||||
MDSwitch:
|
# MDSwitch:
|
||||||
id: settings_home_node_as_broadcast_repeater
|
# id: settings_home_node_as_broadcast_repeater
|
||||||
pos_hint: {"center_y": 0.3}
|
# pos_hint: {"center_y": 0.3}
|
||||||
active: False
|
# active: False
|
||||||
disabled: True
|
# disabled: True
|
||||||
|
|
||||||
MDBoxLayout:
|
MDBoxLayout:
|
||||||
orientation: "horizontal"
|
orientation: "horizontal"
|
||||||
|
@ -35,11 +35,13 @@ if RNS.vendor.platformutils.get_platform() == "android":
|
|||||||
from sideband.sense import Telemeter, Commands
|
from sideband.sense import Telemeter, Commands
|
||||||
from ui.helpers import ts_format, file_ts_format, mdc
|
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_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:
|
else:
|
||||||
import sbapp.plyer as plyer
|
import sbapp.plyer as plyer
|
||||||
from sbapp.sideband.sense import Telemeter, Commands
|
from sbapp.sideband.sense import Telemeter, Commands
|
||||||
from .helpers import ts_format, file_ts_format, mdc
|
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_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():
|
if RNS.vendor.platformutils.is_darwin():
|
||||||
from PIL import Image as PilImage
|
from PIL import Image as PilImage
|
||||||
@ -203,6 +205,23 @@ class Messages():
|
|||||||
self.ids.message_text.input_type = "text"
|
self.ids.message_text.input_type = "text"
|
||||||
self.ids.message_text.keyboard_suggestions = True
|
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):
|
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)
|
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:
|
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))
|
self.list.add_widget(self.load_more_button, len(self.list.children))
|
||||||
|
|
||||||
if self.app.sideband.config["dark_ui"]:
|
if self.app.sideband.config["classic_message_colors"]:
|
||||||
intensity_msgs = intensity_msgs_dark
|
if self.app.sideband.config["dark_ui"]:
|
||||||
intensity_play = intensity_play_dark
|
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:
|
else:
|
||||||
intensity_msgs = intensity_msgs_light
|
if self.app.sideband.config["dark_ui"]:
|
||||||
intensity_play = intensity_play_light
|
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:
|
for w in self.widgets:
|
||||||
m = w.m
|
m = w.m
|
||||||
@ -271,7 +302,7 @@ class Messages():
|
|||||||
delivery_syms = multilingual_markup(delivery_syms.encode("utf-8")).decode("utf-8")
|
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:
|
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"]))
|
txstr = time.strftime(ts_format, time.localtime(msg["sent"]))
|
||||||
titlestr = ""
|
titlestr = ""
|
||||||
prgstr = ""
|
prgstr = ""
|
||||||
@ -305,7 +336,7 @@ class Messages():
|
|||||||
|
|
||||||
|
|
||||||
if msg["state"] == LXMF.LXMessage.DELIVERED:
|
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"]))
|
txstr = time.strftime(ts_format, time.localtime(msg["sent"]))
|
||||||
titlestr = ""
|
titlestr = ""
|
||||||
if msg["title"]:
|
if msg["title"]:
|
||||||
@ -317,7 +348,7 @@ class Messages():
|
|||||||
m["state"] = msg["state"]
|
m["state"] = msg["state"]
|
||||||
|
|
||||||
if msg["method"] == LXMF.LXMessage.PAPER:
|
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"]))
|
txstr = time.strftime(ts_format, time.localtime(msg["sent"]))
|
||||||
titlestr = ""
|
titlestr = ""
|
||||||
if msg["title"]:
|
if msg["title"]:
|
||||||
@ -326,7 +357,7 @@ class Messages():
|
|||||||
m["state"] = msg["state"]
|
m["state"] = msg["state"]
|
||||||
|
|
||||||
if msg["method"] == LXMF.LXMessage.PROPAGATED and msg["state"] == LXMF.LXMessage.SENT:
|
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"]))
|
txstr = time.strftime(ts_format, time.localtime(msg["sent"]))
|
||||||
titlestr = ""
|
titlestr = ""
|
||||||
if msg["title"]:
|
if msg["title"]:
|
||||||
@ -338,7 +369,7 @@ class Messages():
|
|||||||
m["state"] = msg["state"]
|
m["state"] = msg["state"]
|
||||||
|
|
||||||
if msg["state"] == LXMF.LXMessage.FAILED:
|
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"]))
|
txstr = time.strftime(ts_format, time.localtime(msg["sent"]))
|
||||||
titlestr = ""
|
titlestr = ""
|
||||||
if msg["title"]:
|
if msg["title"]:
|
||||||
@ -361,14 +392,49 @@ class Messages():
|
|||||||
wid.height, wid.size_hint_y, wid.opacity, wid.disabled = 0, None, 0, True
|
wid.height, wid.size_hint_y, wid.opacity, wid.disabled = 0, None, 0, True
|
||||||
|
|
||||||
def update_widget(self):
|
def update_widget(self):
|
||||||
if self.app.sideband.config["dark_ui"]:
|
|
||||||
intensity_msgs = intensity_msgs_dark
|
if self.app.sideband.config["classic_message_colors"]:
|
||||||
intensity_play = intensity_play_dark
|
if self.app.sideband.config["dark_ui"]:
|
||||||
mt_color = [1.0, 1.0, 1.0, 0.8]
|
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:
|
else:
|
||||||
intensity_msgs = intensity_msgs_light
|
if self.app.sideband.config["dark_ui"]:
|
||||||
intensity_play = intensity_play_light
|
intensity_msgs = intensity_msgs_dark_alt
|
||||||
mt_color = [1.0, 1.0, 1.0, 0.95]
|
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
|
self.ids.message_text.font_name = self.app.input_font
|
||||||
|
|
||||||
@ -378,7 +444,7 @@ class Messages():
|
|||||||
for m in self.new_messages:
|
for m in self.new_messages:
|
||||||
if not m["hash"] in self.added_item_hashes:
|
if not m["hash"] in self.added_item_hashes:
|
||||||
try:
|
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")
|
message_input = str( escape_markup(m["content"].decode("utf-8")) ).encode("utf-8")
|
||||||
else:
|
else:
|
||||||
message_input = m["content"]
|
message_input = m["content"]
|
||||||
@ -524,31 +590,31 @@ class Messages():
|
|||||||
|
|
||||||
if m["source"] == self.app.sideband.lxmf_destination.hash:
|
if m["source"] == self.app.sideband.lxmf_destination.hash:
|
||||||
if m["state"] == LXMF.LXMessage.DELIVERED:
|
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"
|
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:
|
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"
|
heading_str = titlestr+"[b]Sent[/b] "+txstr+delivery_syms+"\n[b]State[/b] On Propagation Net"
|
||||||
|
|
||||||
elif m["method"] == LXMF.LXMessage.PAPER:
|
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"
|
heading_str = titlestr+"[b]Created[/b] "+txstr+"\n[b]State[/b] Paper Message"
|
||||||
|
|
||||||
elif m["state"] == LXMF.LXMessage.FAILED:
|
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"
|
heading_str = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] Failed"
|
||||||
|
|
||||||
elif m["state"] == LXMF.LXMessage.OUTBOUND or m["state"] == LXMF.LXMessage.SENDING:
|
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 "
|
heading_str = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] Sending "
|
||||||
|
|
||||||
else:
|
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"
|
heading_str = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] Unknown"
|
||||||
|
|
||||||
else:
|
else:
|
||||||
msg_color = mdc(color_received, intensity_msgs)
|
msg_color = mdc(c_received, intensity_msgs)
|
||||||
heading_str = titlestr
|
heading_str = titlestr
|
||||||
if phy_stats_str != "" and self.app.sideband.config["advanced_stats"]:
|
if phy_stats_str != "" and self.app.sideband.config["advanced_stats"]:
|
||||||
heading_str += phy_stats_str+"\n"
|
heading_str += phy_stats_str+"\n"
|
||||||
@ -598,9 +664,9 @@ class Messages():
|
|||||||
self.app.play_audio_field(sender.audio_field)
|
self.app.play_audio_field(sender.audio_field)
|
||||||
stored_color = sender.md_bg_color
|
stored_color = sender.md_bg_color
|
||||||
if sender.lsource == self.app.sideband.lxmf_destination.hash:
|
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:
|
else:
|
||||||
sender.md_bg_color = mdc(color_received, intensity_play)
|
sender.md_bg_color = mdc(c_received, intensity_play)
|
||||||
|
|
||||||
def cb(dt):
|
def cb(dt):
|
||||||
sender.md_bg_color = stored_color
|
sender.md_bg_color = stored_color
|
||||||
@ -653,6 +719,19 @@ class Messages():
|
|||||||
item.ids.content_text.owner = item
|
item.ids.content_text.owner = item
|
||||||
item.ids.content_text.bind(texture_size=check_textures)
|
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():
|
if not RNS.vendor.platformutils.is_android():
|
||||||
item.radius = dp(5)
|
item.radius = dp(5)
|
||||||
|
|
||||||
|
@ -148,6 +148,15 @@ class ObjectDetails():
|
|||||||
else:
|
else:
|
||||||
self.from_objects = False
|
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.coords = None
|
||||||
self.telemetry_list.data = []
|
self.telemetry_list.data = []
|
||||||
pds = multilingual_markup(escape_markup(str(self.app.sideband.peer_display_name(source_dest))).encode("utf-8")).decode("utf-8")
|
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.clear_widget()
|
||||||
self.update()
|
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):
|
def send_update(self):
|
||||||
if not self.viewing_self:
|
if not self.viewing_self:
|
||||||
result = self.app.sideband.send_latest_telemetry(to_addr=self.object_hash)
|
result = self.app.sideband.send_latest_telemetry(to_addr=self.object_hash)
|
||||||
@ -643,10 +661,9 @@ class RVDetails(MDRecycleView):
|
|||||||
alt_str = RNS.prettydistance(alt)
|
alt_str = RNS.prettydistance(alt)
|
||||||
formatted_values = f"Coordinates [b]{fcoords}[/b], altitude [b]{alt_str}[/b]"
|
formatted_values = f"Coordinates [b]{fcoords}[/b], altitude [b]{alt_str}[/b]"
|
||||||
if speed != None:
|
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]"
|
speed_formatted_values = f"Speed [b]{speed} Km/h[/b], heading [b]{heading}°[/b]"
|
||||||
else:
|
else:
|
||||||
# speed_formatted_values = f"Speed [b]0 Km/h[/b]"
|
|
||||||
speed_formatted_values = f"Object is [b]stationary[/b]"
|
speed_formatted_values = f"Object is [b]stationary[/b]"
|
||||||
else:
|
else:
|
||||||
speed_formatted_values = None
|
speed_formatted_values = None
|
||||||
@ -972,22 +989,22 @@ MDScreen:
|
|||||||
on_release: root.delegate.request_update()
|
on_release: root.delegate.request_update()
|
||||||
disabled: False
|
disabled: False
|
||||||
|
|
||||||
# MDBoxLayout:
|
MDBoxLayout:
|
||||||
# orientation: "horizontal"
|
orientation: "horizontal"
|
||||||
# spacing: dp(16)
|
spacing: dp(16)
|
||||||
# size_hint_y: None
|
size_hint_y: None
|
||||||
# height: self.minimum_height
|
height: self.minimum_height
|
||||||
# padding: [dp(24), dp(16), dp(24), dp(24)]
|
padding: [dp(24), dp(0), dp(24), dp(24)]
|
||||||
|
|
||||||
# MDRectangleFlatIconButton:
|
MDRectangleFlatIconButton:
|
||||||
# id: delete_button
|
id: track_button
|
||||||
# icon: "trash-can-outline"
|
icon: "crosshairs-gps"
|
||||||
# text: "Delete All Telemetry"
|
text: "Start Live Tracking"
|
||||||
# padding: [dp(0), dp(14), dp(0), dp(14)]
|
padding: [dp(0), dp(14), dp(0), dp(14)]
|
||||||
# icon_size: dp(24)
|
icon_size: dp(24)
|
||||||
# font_size: dp(16)
|
font_size: dp(16)
|
||||||
# size_hint: [1.0, None]
|
size_hint: [1.0, None]
|
||||||
# on_release: root.delegate.copy_telemetry(self)
|
on_release: root.delegate.live_tracking(self)
|
||||||
# disabled: False
|
disabled: False
|
||||||
|
|
||||||
"""
|
"""
|
@ -9,7 +9,10 @@ from kivy.utils import escape_markup
|
|||||||
from kivymd.uix.recycleview import MDRecycleView
|
from kivymd.uix.recycleview import MDRecycleView
|
||||||
from kivymd.uix.list import OneLineIconListItem
|
from kivymd.uix.list import OneLineIconListItem
|
||||||
from kivymd.uix.pickers import MDColorPicker
|
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.icon_definitions import md_icons
|
||||||
|
from kivymd.toast import toast
|
||||||
from kivy.properties import StringProperty, BooleanProperty
|
from kivy.properties import StringProperty, BooleanProperty
|
||||||
from kivy.effects.scroll import ScrollEffect
|
from kivy.effects.scroll import ScrollEffect
|
||||||
from kivy.clock import Clock
|
from kivy.clock import Clock
|
||||||
@ -29,6 +32,7 @@ class Utilities():
|
|||||||
self.screen = None
|
self.screen = None
|
||||||
self.rnstatus_screen = None
|
self.rnstatus_screen = None
|
||||||
self.rnstatus_instance = None
|
self.rnstatus_instance = None
|
||||||
|
self.logviewer_screen = None
|
||||||
|
|
||||||
if not self.app.root.ids.screen_manager.has_screen("utilities_screen"):
|
if not self.app.root.ids.screen_manager.has_screen("utilities_screen"):
|
||||||
self.screen = Builder.load_string(layout_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.app.root.ids.screen_manager.add_widget(self.screen)
|
||||||
|
|
||||||
self.screen.ids.telemetry_scrollview.effect_cls = ScrollEffect
|
self.screen.ids.telemetry_scrollview.effect_cls = ScrollEffect
|
||||||
info = "\nYou can use various RNS utilities from Sideband. "
|
info = "This section contains various utilities and diagnostics tools, "
|
||||||
info += ""
|
info += "that can be helpful while using Sideband and Reticulum."
|
||||||
|
|
||||||
if self.app.theme_cls.theme_style == "Dark":
|
if self.app.theme_cls.theme_style == "Dark":
|
||||||
info = "[color=#"+self.app.dark_theme_text_color+"]"+info+"[/color]"
|
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
|
### rnstatus screen
|
||||||
@ -72,24 +103,91 @@ class Utilities():
|
|||||||
|
|
||||||
import io
|
import io
|
||||||
from contextlib import redirect_stdout
|
from contextlib import redirect_stdout
|
||||||
output_marker = "===begin rnstatus output==="
|
|
||||||
output = "None"
|
output = "None"
|
||||||
with io.StringIO() as buffer, redirect_stdout(buffer):
|
with io.StringIO() as buffer, redirect_stdout(buffer):
|
||||||
print(output_marker, end="")
|
with RNS.logging_lock:
|
||||||
self.rnstatus_instance.main(rns_instance=RNS.Reticulum.get_instance())
|
self.rnstatus_instance.main(rns_instance=RNS.Reticulum.get_instance())
|
||||||
output = buffer.getvalue()
|
output = buffer.getvalue()
|
||||||
|
|
||||||
remainder = output[:output.find(output_marker)]
|
|
||||||
output = output[output.find(output_marker)+len(output_marker):]
|
|
||||||
print(remainder, end="")
|
|
||||||
|
|
||||||
def cb(dt):
|
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)
|
Clock.schedule_once(cb, 0.2)
|
||||||
|
|
||||||
if self.app.root.ids.screen_manager.current == "rnstatus_screen":
|
if self.app.root.ids.screen_manager.current == "rnstatus_screen":
|
||||||
Clock.schedule_once(self.update_rnstatus, 1)
|
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 = """
|
layout_utilities_screen = """
|
||||||
MDScreen:
|
MDScreen:
|
||||||
@ -116,14 +214,14 @@ MDScreen:
|
|||||||
orientation: "vertical"
|
orientation: "vertical"
|
||||||
size_hint_y: None
|
size_hint_y: None
|
||||||
height: self.minimum_height
|
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:
|
MDLabel:
|
||||||
text: "Utilities & Tools"
|
id: utilities_info
|
||||||
font_style: "H6"
|
|
||||||
|
|
||||||
MDLabel:
|
|
||||||
id: telemetry_info
|
|
||||||
markup: True
|
markup: True
|
||||||
text: ""
|
text: ""
|
||||||
size_hint_y: None
|
size_hint_y: None
|
||||||
@ -156,8 +254,30 @@ MDScreen:
|
|||||||
icon_size: dp(24)
|
icon_size: dp(24)
|
||||||
font_size: dp(16)
|
font_size: dp(16)
|
||||||
size_hint: [1.0, None]
|
size_hint: [1.0, None]
|
||||||
on_release: root.delegate.rnstatus_action(self)
|
on_release: root.delegate.logviewer_action(self)
|
||||||
disabled: True
|
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")]]
|
[['menu', lambda x: root.app.nav_drawer.set_state("open")]]
|
||||||
right_action_items:
|
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)],
|
['close', lambda x: root.app.close_sub_utilities_action(self)],
|
||||||
]
|
]
|
||||||
|
|
||||||
MDScrollView:
|
MDScrollView:
|
||||||
id: sensors_scrollview
|
id: rnstatus_scrollview
|
||||||
size_hint_x: 1
|
size_hint_x: 1
|
||||||
size_hint_y: None
|
size_hint_y: None
|
||||||
size: [root.width, root.height-root.ids.top_bar.height]
|
size: [root.width, root.height-root.ids.top_bar.height]
|
||||||
@ -202,4 +322,120 @@ MDScreen:
|
|||||||
size_hint_y: None
|
size_hint_y: None
|
||||||
text_size: self.width, None
|
text_size: self.width, None
|
||||||
height: self.texture_size[1]
|
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
|
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(
|
packages = setuptools.find_packages(
|
||||||
exclude=[
|
exclude=[
|
||||||
"sbapp.plyer.platforms.android",
|
"sbapp.plyer.platforms.android",
|
||||||
@ -63,6 +77,7 @@ package_data = {
|
|||||||
"kivymd/images/*",
|
"kivymd/images/*",
|
||||||
"kivymd/*",
|
"kivymd/*",
|
||||||
"mapview/icons/*",
|
"mapview/icons/*",
|
||||||
|
*glob_share(),
|
||||||
*glob_paths(".kv")
|
*glob_paths(".kv")
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -99,7 +114,7 @@ setuptools.setup(
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
install_requires=[
|
install_requires=[
|
||||||
"rns>=0.8.7",
|
"rns>=0.8.8",
|
||||||
"lxmf>=0.5.8",
|
"lxmf>=0.5.8",
|
||||||
"kivy>=2.3.0",
|
"kivy>=2.3.0",
|
||||||
"pillow>=10.2.0",
|
"pillow>=10.2.0",
|
||||||
|
Loading…
Reference in New Issue
Block a user