From 597f8a28a2537a2642ca7cdbb8413b1032c0d21a Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Thu, 12 Sep 2024 10:06:24 +0200 Subject: [PATCH 01/22] Improved announce details display --- sbapp/sideband/core.py | 18 ++++++++++++------ sbapp/ui/announces.py | 12 +++++++----- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/sbapp/sideband/core.py b/sbapp/sideband/core.py index 3f1200a..9496df6 100644 --- a/sbapp/sideband/core.py +++ b/sbapp/sideband/core.py @@ -110,11 +110,12 @@ class SidebandCore(): # This reformats the new v0.5.0 announce data back to the expected format # for Sidebands database and other handling functions. dn = LXMF.display_name_from_app_data(app_data) + sc = LXMF.stamp_cost_from_app_data(app_data) app_data = b"" if dn != None: app_data = dn.encode("utf-8") - self.log_announce(destination_hash, app_data, dest_type=SidebandCore.aspect_filter) + self.log_announce(destination_hash, app_data, dest_type=SidebandCore.aspect_filter, stamp_cost=sc) 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 @@ -891,11 +892,12 @@ class SidebandCore(): else: plyer.notification.notify(title, content, app_icon=self.icon_32) - def log_announce(self, dest, app_data, dest_type): + def log_announce(self, dest, app_data, dest_type, stamp_cost=None): try: if app_data == None: app_data = b"" - RNS.log("Received "+str(dest_type)+" announce for "+RNS.prettyhexrep(dest)+" with data: "+app_data.decode("utf-8"), RNS.LOG_DEBUG) + app_data = msgpack.packb([app_data, stamp_cost]) + RNS.log("Received "+str(dest_type)+" announce for "+RNS.prettyhexrep(dest)+" with data: "+str(app_data), RNS.LOG_DEBUG) self._db_save_announce(dest, app_data, dest_type) self.setstate("app.flags.new_announces", True) @@ -1090,9 +1092,11 @@ class SidebandCore(): else: app_data = RNS.Identity.recall_app_data(context_dest) if app_data != None: - return LXMF.display_name_from_app_data(app_data)+" "+RNS.prettyhexrep(context_dest) + name_str = LXMF.display_name_from_app_data(app_data) + addr_str = RNS.prettyhexrep(context_dest) + return name_str+" "+addr_str else: - return RNS.prettyhexrep(context_dest) + return "Anonymous Peer "+RNS.prettyhexrep(context_dest) except Exception as e: @@ -2268,9 +2272,11 @@ class SidebandCore(): for entry in result: try: if not entry[2] in added_dests: + app_data = entry[3] announce = { "dest": entry[2], - "data": entry[3].decode("utf-8"), + "name": LXMF.display_name_from_app_data(app_data), + "cost": LXMF.stamp_cost_from_app_data(app_data), "time": entry[1], "type": entry[4] } diff --git a/sbapp/ui/announces.py b/sbapp/ui/announces.py index bd620d5..5e58dbb 100644 --- a/sbapp/ui/announces.py +++ b/sbapp/ui/announces.py @@ -89,7 +89,8 @@ class Announces(): for announce in self.announces: context_dest = announce["dest"] ts = announce["time"] - a_data = announce["data"] + a_name = announce["name"] + a_cost = announce["cost"] dest_type = announce["type"] if not context_dest in self.added_item_dests: @@ -98,15 +99,16 @@ class Announces(): else: trust_icon = "account-question" - def gen_info(ts, dest, name, dtype): + def gen_info(ts, dest, name, cost, dtype): name = multilingual_markup(escape_markup(str(name)).encode("utf-8")).decode("utf-8") + cost = str(cost) def x(sender): yes_button = MDRectangleFlatButton(text="OK",font_size=dp(18)) if dtype == "lxmf.delivery": - ad_text = "[size=22dp]LXMF Peer[/size]\n\nReceived: "+ts+"\nAnnounced Name: "+name+"\nAddress: "+RNS.prettyhexrep(dest) + 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 if dtype == "lxmf.propagation": - ad_text = "[size=22dp]LXMF Propagation Node[/size]\n\nReceived: "+ts+"\nAddress: "+RNS.prettyhexrep(dest) + ad_text = "[size=22dp]LXMF Propagation Node[/size]\n\n[b]Received[/b] "+ts+"\n[b]Address[/b] "+RNS.prettyhexrep(dest) dialog = MDDialog( text=ad_text, @@ -135,7 +137,7 @@ class Announces(): disp_name = "Unknown Announce" iconl = IconLeftWidget(icon="progress-question") - item = TwoLineAvatarIconListItem(text=time_string, secondary_text=disp_name, on_release=gen_info(time_string, context_dest, a_data, dest_type)) + item = TwoLineAvatarIconListItem(text=time_string, secondary_text=disp_name, on_release=gen_info(time_string, context_dest, a_name, a_cost, dest_type)) item.add_widget(iconl) item.sb_uid = context_dest item.ts = ts From a33158570a67d02ee5e8cfc4e998da1c76ced9ec Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Thu, 12 Sep 2024 18:26:53 +0200 Subject: [PATCH 02/22] Updated Android build code --- sbapp/buildozer.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sbapp/buildozer.spec b/sbapp/buildozer.spec index db09da8..0d4bc10 100644 --- a/sbapp/buildozer.spec +++ b/sbapp/buildozer.spec @@ -10,7 +10,7 @@ source.exclude_patterns = app_storage/*,venv/*,Makefile,./Makefil*,requirements, version.regex = __version__ = ['"](.*)['"] version.filename = %(source.dir)s/main.py -android.numeric_version = 20240911 +android.numeric_version = 20240912 requirements = kivy==2.3.0,libbz2,pillow==10.2.0,qrcode==7.3.1,usb4a,usbserial4a,libwebp,libogg,libopus,opusfile,numpy,cryptography,ffpyplayer,codec2,pycodec2,sh,pynacl From 9c66538ec1246d25b2249f50f589f5847cf021a1 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Thu, 12 Sep 2024 20:17:44 +0200 Subject: [PATCH 03/22] Dispatch messages via service on Android --- sbapp/sideband/core.py | 186 ++++++++++++++++++++++++++++++----------- 1 file changed, 135 insertions(+), 51 deletions(-) diff --git a/sbapp/sideband/core.py b/sbapp/sideband/core.py index 9496df6..b54af71 100644 --- a/sbapp/sideband/core.py +++ b/sbapp/sideband/core.py @@ -155,6 +155,7 @@ class SidebandCore(): self.service_stopped = False self.service_context = service_context self.owner_service = owner_service + self.allow_service_dispatch = True self.version_str = "" if config_path == None: @@ -1620,8 +1621,27 @@ class SidebandCore(): connection.send(True) elif "get_plugins_info" in call: connection.send(self._get_plugins_info()) + elif "send_message" in call: + args = call["send_message"] + RNS.log(str(call)) + RNS.log(str(args)) + RNS.log("Handling RPC send") + send_result = self.send_message( + args["content"], + args["destination_hash"], + args["propagation"], + skip_fields=args["skip_fields"], + no_display=args["no_display"], + attachment=args["attachment"], + image=args["image"], + audio=args["audio"]) + RNS.log("RPC send complete") + connection.send(send_result) + elif "get_lxm_progress" in call: + args = call["get_lxm_progress"] + connection.send(self.get_lxm_progress(args["lxm_hash"])) else: - return None + connection.send(None) except Exception as e: RNS.log("Error on client RPC connection: "+str(e), RNS.LOG_ERROR) @@ -3808,9 +3828,34 @@ class SidebandCore(): RNS.log("Error while creating paper message: "+str(e), RNS.LOG_ERROR) return False + def _service_get_lxm_progress(self, lxm_hash): + if not RNS.vendor.platformutils.is_android(): + return False + else: + if self.is_client: + try: + if self.rpc_connection == None: + self.rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key) + + self.rpc_connection.send({"get_lxm_progress": {"lxm_hash": lxm_hash}}) + response = self.rpc_connection.recv() + return response + + except Exception as e: + RNS.log("Error while getting LXM progress over RPC: "+str(e), RNS.LOG_DEBUG) + RNS.trace_exception(e) + return False + else: + return False + + def get_lxm_progress(self, lxm_hash): try: - return self.message_router.get_outbound_progress(lxm_hash) + prg = self.message_router.get_outbound_progress(lxm_hash) + if not prg and self.is_client: + prg = self._service_get_lxm_progress(lxm_hash) + + return prg except Exception as e: RNS.log("An error occurred while getting message transfer progress: "+str(e), RNS.LOG_ERROR) return None @@ -3822,56 +3867,95 @@ class SidebandCore(): RNS.log("An error occurred while getting message transfer stamp cost: "+str(e), RNS.LOG_ERROR) return None - def send_message(self, content, destination_hash, propagation, skip_fields=False, no_display=False, attachment = None, image = None, audio = None): - try: - if content == "": - raise ValueError("Message content cannot be empty") - - dest_identity = RNS.Identity.recall(destination_hash) - dest = RNS.Destination(dest_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "lxmf", "delivery") - source = self.lxmf_destination - - if propagation: - desired_method = LXMF.LXMessage.PROPAGATED - else: - desired_method = LXMF.LXMessage.DIRECT - - if skip_fields: - fields = {} - else: - fields = self.get_message_fields(destination_hash) - - if attachment != None: - fields[LXMF.FIELD_FILE_ATTACHMENTS] = [attachment] - if image != None: - fields[LXMF.FIELD_IMAGE] = image - if audio != None: - fields[LXMF.FIELD_AUDIO] = audio - - lxm = LXMF.LXMessage(dest, source, content, title="", desired_method=desired_method, fields = fields, include_ticket=self.is_trusted(destination_hash)) - - if not no_display: - lxm.register_delivery_callback(self.message_notification) - lxm.register_failed_callback(self.message_notification) - else: - lxm.register_delivery_callback(self.message_notification_no_display) - lxm.register_failed_callback(self.message_notification_no_display) - - if self.message_router.get_outbound_propagation_node() != None: - if self.config["lxmf_try_propagation_on_fail"]: - lxm.try_propagation_on_fail = True - - self.message_router.handle_outbound(lxm) - - if not no_display: - self.lxm_ingest(lxm, originator=True) - - return True - - except Exception as e: - RNS.log("Error while sending message: "+str(e), RNS.LOG_ERROR) - RNS.trace_exception(e) + def _service_send_message(self, content, destination_hash, propagation, skip_fields=False, no_display=False, attachment = None, image = None, audio = None): + if not RNS.vendor.platformutils.is_android(): return False + else: + if self.is_client: + try: + if self.rpc_connection == None: + self.rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key) + + self.rpc_connection.send({"send_message": { + "content": content, + "destination_hash": destination_hash, + "propagation": propagation, + "skip_fields": skip_fields, + "no_display": no_display, + "attachment": attachment, + "image": image, + "audio": audio} + }) + response = self.rpc_connection.recv() + return response + + except Exception as e: + RNS.log("Error while sending message over RPC: "+str(e), RNS.LOG_DEBUG) + RNS.trace_exception(e) + return False + else: + return False + + def send_message(self, content, destination_hash, propagation, skip_fields=False, no_display=False, attachment = None, image = None, audio = None): + if self.allow_service_dispatch and self.is_client: + try: + return self._service_send_message(content, destination_hash, propagation, skip_fields, no_display, attachment, image, audio) + + except Exception as e: + RNS.log("Error while sending message: "+str(e), RNS.LOG_ERROR) + RNS.trace_exception(e) + return False + + else: + try: + if content == "": + raise ValueError("Message content cannot be empty") + + dest_identity = RNS.Identity.recall(destination_hash) + dest = RNS.Destination(dest_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "lxmf", "delivery") + source = self.lxmf_destination + + if propagation: + desired_method = LXMF.LXMessage.PROPAGATED + else: + desired_method = LXMF.LXMessage.DIRECT + + if skip_fields: + fields = {} + else: + fields = self.get_message_fields(destination_hash) + + if attachment != None: + fields[LXMF.FIELD_FILE_ATTACHMENTS] = [attachment] + if image != None: + fields[LXMF.FIELD_IMAGE] = image + if audio != None: + fields[LXMF.FIELD_AUDIO] = audio + + lxm = LXMF.LXMessage(dest, source, content, title="", desired_method=desired_method, fields = fields, include_ticket=self.is_trusted(destination_hash)) + + if not no_display: + lxm.register_delivery_callback(self.message_notification) + lxm.register_failed_callback(self.message_notification) + else: + lxm.register_delivery_callback(self.message_notification_no_display) + lxm.register_failed_callback(self.message_notification_no_display) + + if self.message_router.get_outbound_propagation_node() != None: + if self.config["lxmf_try_propagation_on_fail"]: + lxm.try_propagation_on_fail = True + + self.message_router.handle_outbound(lxm) + + if not no_display: + self.lxm_ingest(lxm, originator=True) + + return True + + except Exception as e: + RNS.log("Error while sending message: "+str(e), RNS.LOG_ERROR) + RNS.trace_exception(e) + return False def send_command(self, content, destination_hash, propagation): try: From a13607646c25403c722182eb15cfdcb59ce09d59 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Thu, 12 Sep 2024 21:37:36 +0200 Subject: [PATCH 04/22] Dispatch commands via service on Android --- sbapp/sideband/core.py | 125 ++++++++++++++++++++++++++--------------- 1 file changed, 81 insertions(+), 44 deletions(-) diff --git a/sbapp/sideband/core.py b/sbapp/sideband/core.py index b54af71..97b2333 100644 --- a/sbapp/sideband/core.py +++ b/sbapp/sideband/core.py @@ -1623,9 +1623,6 @@ class SidebandCore(): connection.send(self._get_plugins_info()) elif "send_message" in call: args = call["send_message"] - RNS.log(str(call)) - RNS.log(str(args)) - RNS.log("Handling RPC send") send_result = self.send_message( args["content"], args["destination_hash"], @@ -1635,7 +1632,13 @@ class SidebandCore(): attachment=args["attachment"], image=args["image"], audio=args["audio"]) - RNS.log("RPC send complete") + connection.send(send_result) + elif "send_command" in call: + args = call["send_command"] + send_result = self.send_command( + args["content"], + args["destination_hash"], + args["propagation"]) connection.send(send_result) elif "get_lxm_progress" in call: args = call["get_lxm_progress"] @@ -3896,6 +3899,30 @@ class SidebandCore(): else: return False + def _service_send_command(self, content, destination_hash, propagation): + if not RNS.vendor.platformutils.is_android(): + return False + else: + if self.is_client: + try: + if self.rpc_connection == None: + self.rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key) + + self.rpc_connection.send({"send_command": { + "content": content, + "destination_hash": destination_hash, + "propagation": propagation} + }) + response = self.rpc_connection.recv() + return response + + except Exception as e: + RNS.log("Error while sending command over RPC: "+str(e), RNS.LOG_DEBUG) + RNS.trace_exception(e) + return False + else: + return False + def send_message(self, content, destination_hash, propagation, skip_fields=False, no_display=False, attachment = None, image = None, audio = None): if self.allow_service_dispatch and self.is_client: try: @@ -3958,51 +3985,61 @@ class SidebandCore(): return False def send_command(self, content, destination_hash, propagation): - try: - if content == "": + if self.allow_service_dispatch and self.is_client: + try: + return self._service_send_command(content, destination_hash, propagation) + + except Exception as e: + RNS.log("Error while sending message: "+str(e), RNS.LOG_ERROR) + RNS.trace_exception(e) return False - commands = [] - if content.startswith("echo "): - echo_content = content.replace("echo ", "").encode("utf-8") - if len(echo_content) > 0: - commands.append({Commands.ECHO: echo_content}) - elif content.startswith("sig"): - commands.append({Commands.SIGNAL_REPORT: True}) - elif content.startswith("ping"): - commands.append({Commands.PING: True}) - else: - commands.append({Commands.PLUGIN_COMMAND: content}) + else: + try: + if content == "": + return False - if len(commands) == 0: + commands = [] + if content.startswith("echo "): + echo_content = content.replace("echo ", "").encode("utf-8") + if len(echo_content) > 0: + commands.append({Commands.ECHO: echo_content}) + elif content.startswith("sig"): + commands.append({Commands.SIGNAL_REPORT: True}) + elif content.startswith("ping"): + commands.append({Commands.PING: True}) + else: + commands.append({Commands.PLUGIN_COMMAND: content}) + + if len(commands) == 0: + return False + + dest_identity = RNS.Identity.recall(destination_hash) + dest = RNS.Destination(dest_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "lxmf", "delivery") + source = self.lxmf_destination + + if propagation: + desired_method = LXMF.LXMessage.PROPAGATED + else: + desired_method = LXMF.LXMessage.DIRECT + + lxm = LXMF.LXMessage(dest, source, "", title="", desired_method=desired_method, fields = {LXMF.FIELD_COMMANDS: commands}, include_ticket=self.is_trusted(destination_hash)) + lxm.register_delivery_callback(self.message_notification) + lxm.register_failed_callback(self.message_notification) + + if self.message_router.get_outbound_propagation_node() != None: + if self.config["lxmf_try_propagation_on_fail"]: + lxm.try_propagation_on_fail = True + + self.message_router.handle_outbound(lxm) + self.lxm_ingest(lxm, originator=True) + + return True + + except Exception as e: + RNS.log("Error while sending message: "+str(e), RNS.LOG_ERROR) return False - dest_identity = RNS.Identity.recall(destination_hash) - dest = RNS.Destination(dest_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "lxmf", "delivery") - source = self.lxmf_destination - - if propagation: - desired_method = LXMF.LXMessage.PROPAGATED - else: - desired_method = LXMF.LXMessage.DIRECT - - lxm = LXMF.LXMessage(dest, source, "", title="", desired_method=desired_method, fields = {LXMF.FIELD_COMMANDS: commands}, include_ticket=self.is_trusted(destination_hash)) - lxm.register_delivery_callback(self.message_notification) - lxm.register_failed_callback(self.message_notification) - - if self.message_router.get_outbound_propagation_node() != None: - if self.config["lxmf_try_propagation_on_fail"]: - lxm.try_propagation_on_fail = True - - self.message_router.handle_outbound(lxm) - self.lxm_ingest(lxm, originator=True) - - return True - - except Exception as e: - RNS.log("Error while sending message: "+str(e), RNS.LOG_ERROR) - return False - def new_conversation(self, dest_str, name = "", trusted = False): if len(dest_str) != RNS.Reticulum.TRUNCATED_HASHLENGTH//8*2: return False From e68926806cebd25b8a4e709fe25f7cd3d8e105b2 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Fri, 13 Sep 2024 12:48:17 +0200 Subject: [PATCH 05/22] Show exit screen on window close --- sbapp/main.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sbapp/main.py b/sbapp/main.py index a9ff541..143e45e 100644 --- a/sbapp/main.py +++ b/sbapp/main.py @@ -303,6 +303,7 @@ class SidebandApp(MDApp): self.final_load_completed = False self.service_last_available = 0 + self.closing_app = False self.attach_path = None self.attach_type = None @@ -988,6 +989,11 @@ class SidebandApp(MDApp): ok_button.bind(on_release=dl_ok) dialog.open() + def close_requested(self, *args): + if not self.closing_app: + self.quit_action(None) + return True + def on_start(self): self.last_exit_event = time.time() self.root.ids.screen_manager.transition = self.slide_transition @@ -997,6 +1003,7 @@ class SidebandApp(MDApp): EventLoop.window.bind(on_keyboard=self.keyboard_event) EventLoop.window.bind(on_key_down=self.keydown_event) EventLoop.window.bind(on_key_up=self.keyup_event) + Window.bind(on_request_close=self.close_requested) if __variant__ != "": variant_str = " "+__variant__ @@ -1241,6 +1248,7 @@ class SidebandApp(MDApp): self.root.ids.nav_drawer.set_state("closed") def quit_action(self, sender): + self.closing_app = True self.root.ids.nav_drawer.set_state("closed") self.sideband.should_persist_data() From 3a952334ad0ddc19146266ea8e13448b2a22321c Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Fri, 13 Sep 2024 13:05:07 +0200 Subject: [PATCH 06/22] Updated Android build code --- sbapp/buildozer.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sbapp/buildozer.spec b/sbapp/buildozer.spec index 0d4bc10..53c7c82 100644 --- a/sbapp/buildozer.spec +++ b/sbapp/buildozer.spec @@ -10,7 +10,7 @@ source.exclude_patterns = app_storage/*,venv/*,Makefile,./Makefil*,requirements, version.regex = __version__ = ['"](.*)['"] version.filename = %(source.dir)s/main.py -android.numeric_version = 20240912 +android.numeric_version = 20240913 requirements = kivy==2.3.0,libbz2,pillow==10.2.0,qrcode==7.3.1,usb4a,usbserial4a,libwebp,libogg,libopus,opusfile,numpy,cryptography,ffpyplayer,codec2,pycodec2,sh,pynacl From 6038bc541cd82ffce36176e9f8668efb863b7e7c Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Sat, 14 Sep 2024 19:55:25 +0200 Subject: [PATCH 07/22] Get link establishment rates via service on Android --- sbapp/sideband/core.py | 42 ++++++++++++++++++++++++++++++++++++++- sbapp/ui/objectdetails.py | 12 ++++------- 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/sbapp/sideband/core.py b/sbapp/sideband/core.py index 97b2333..aa40c04 100644 --- a/sbapp/sideband/core.py +++ b/sbapp/sideband/core.py @@ -1578,7 +1578,45 @@ class SidebandCore(): RNS.log(ed, RNS.LOG_DEBUG) return ed - + def _get_destination_establishment_rate(self, destination_hash): + try: + mr = self.message_router + oh = destination_hash + ol = None + if oh in mr.direct_links: + ol = mr.direct_links[oh] + elif oh in mr.backchannel_links: + ol = mr.backchannel_links[oh] + + if ol != None: + ler = ol.get_establishment_rate() + if ler: + return ler + + return None + + except Exception as e: + RNS.trace_exception(e) + return None + + def get_destination_establishment_rate(self, destination_hash): + if not RNS.vendor.platformutils.is_android(): + return self._get_destination_establishment_rate(destination_hash) + else: + if self.is_service: + return self._get_destination_establishment_rate(destination_hash) + else: + try: + if self.rpc_connection == None: + self.rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key) + self.rpc_connection.send({"get_destination_establishment_rate": destination_hash}) + response = self.rpc_connection.recv() + return response + except Exception as e: + ed = "Error while getting destination link etablishment rate over RPC: "+str(e) + RNS.log(ed, RNS.LOG_DEBUG) + return None + def __start_rpc_listener(self): try: RNS.log("Starting RPC listener", RNS.LOG_DEBUG) @@ -1621,6 +1659,8 @@ class SidebandCore(): connection.send(True) elif "get_plugins_info" in call: connection.send(self._get_plugins_info()) + elif "get_destination_establishment_rate" in call: + connection.send(self._get_destination_establishment_rate(call["get_destination_establishment_rate"])) elif "send_message" in call: args = call["send_message"] send_result = self.send_message( diff --git a/sbapp/ui/objectdetails.py b/sbapp/ui/objectdetails.py index 6acb51f..38d0074 100644 --- a/sbapp/ui/objectdetails.py +++ b/sbapp/ui/objectdetails.py @@ -796,14 +796,10 @@ class RVDetails(MDRecycleView): self.entries.append({"icon": "routes", "text": f"Current path on [b]{nhi}[/b]", "on_release": pass_job}) try: - mr = self.delegate.app.sideband.message_router - oh = self.delegate.object_hash - if oh in mr.direct_links: - ol = mr.direct_links[oh] - ler = ol.get_establishment_rate() - if ler: - lers = RNS.prettyspeed(ler, "b") - self.entries.append({"icon": "lock-check-outline", "text": f"Direct link established, LER is [b]{lers}[/b]", "on_release": pass_job}) + ler = self.delegate.app.sideband.get_destination_establishment_rate(self.delegate.object_hash) + if ler: + lers = RNS.prettyspeed(ler, "b") + self.entries.append({"icon": "lock-check-outline", "text": f"Direct link established, LER is [b]{lers}[/b]", "on_release": pass_job}) except Exception as e: RNS.trace_exception(e) From 548663655bba0e384ee2d21ee1ea479c85204925 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Mon, 16 Sep 2024 09:59:21 +0200 Subject: [PATCH 08/22] Perform telemetry requests and updates via service on Android. Update service heartbeat monitoring. --- sbapp/sideband/core.py | 310 ++++++++++++++++++++++++++--------------- 1 file changed, 196 insertions(+), 114 deletions(-) diff --git a/sbapp/sideband/core.py b/sbapp/sideband/core.py index aa40c04..8c7d11e 100644 --- a/sbapp/sideband/core.py +++ b/sbapp/sideband/core.py @@ -1205,130 +1205,192 @@ class SidebandCore(): else: self.setstate(f"telemetry.{RNS.hexrep(message.destination_hash, delimit=False)}.request_sending", False) + def _service_request_latest_telemetry(self, from_addr=None): + if not RNS.vendor.platformutils.is_android(): + return False + else: + if self.is_client: + try: + if self.rpc_connection == None: + self.rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key) + + self.rpc_connection.send({"request_latest_telemetry": {"from_addr": from_addr}}) + response = self.rpc_connection.recv() + return response + + except Exception as e: + RNS.log("Error while requesting latest telemetry over RPC: "+str(e), RNS.LOG_DEBUG) + RNS.trace_exception(e) + return False + else: + return False def request_latest_telemetry(self, from_addr=None): - if from_addr == None or from_addr == self.lxmf_destination.hash: - return "no_address" - else: - if self.getstate(f"telemetry.{RNS.hexrep(from_addr, delimit=False)}.request_sending") == True: - RNS.log("Not sending new telemetry request, since an earlier transfer is already in progress", RNS.LOG_DEBUG) - return "in_progress" + if self.allow_service_dispatch and self.is_client: + try: + return self._service_request_latest_telemetry(from_addr) - if from_addr != None: - dest_identity = RNS.Identity.recall(from_addr) - - if dest_identity == None: - RNS.log("The identity for "+RNS.prettyhexrep(from_addr)+" could not be recalled. Requesting identity from network...", RNS.LOG_DEBUG) - RNS.Transport.request_path(from_addr) - return "destination_unknown" - - else: - now = time.time() - dest = RNS.Destination(dest_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "lxmf", "delivery") - source = self.lxmf_destination - - if self.config["telemetry_use_propagation_only"] == True: - desired_method = LXMF.LXMessage.PROPAGATED - else: - desired_method = LXMF.LXMessage.DIRECT - - request_timebase = self.getpersistent(f"telemetry.{RNS.hexrep(from_addr, delimit=False)}.timebase") or now - self.telemetry_request_max_history - lxm_fields = { LXMF.FIELD_COMMANDS: [ - {Commands.TELEMETRY_REQUEST: request_timebase}, - ]} - - lxm = LXMF.LXMessage(dest, source, "", desired_method=desired_method, fields = lxm_fields, include_ticket=True) - lxm.request_timebase = request_timebase - lxm.register_delivery_callback(self.telemetry_request_finished) - lxm.register_failed_callback(self.telemetry_request_finished) - - if self.message_router.get_outbound_propagation_node() != None: - if self.config["telemetry_try_propagation_on_fail"]: - lxm.try_propagation_on_fail = True - - RNS.log(f"Sending telemetry request with timebase {request_timebase}", RNS.LOG_DEBUG) - self.setpersistent(f"telemetry.{RNS.hexrep(from_addr, delimit=False)}.last_request_attempt", time.time()) - self.setstate(f"telemetry.{RNS.hexrep(from_addr, delimit=False)}.request_sending", True) - self.message_router.handle_outbound(lxm) - return "sent" - - else: + except Exception as e: + RNS.log("Error requesting latest telemetry: "+str(e), RNS.LOG_ERROR) + RNS.trace_exception(e) return "not_sent" + else: + if from_addr == None or from_addr == self.lxmf_destination.hash: + return "no_address" + else: + if self.getstate(f"telemetry.{RNS.hexrep(from_addr, delimit=False)}.request_sending") == True: + RNS.log("Not sending new telemetry request, since an earlier transfer is already in progress", RNS.LOG_DEBUG) + return "in_progress" - def send_latest_telemetry(self, to_addr=None, stream=None, is_authorized_telemetry_request=False): - if to_addr == None or to_addr == self.lxmf_destination.hash: - return "no_address" - else: - if to_addr == self.config["telemetry_collector"]: - is_authorized_telemetry_request = True + if from_addr != None: + dest_identity = RNS.Identity.recall(from_addr) + + if dest_identity == None: + RNS.log("The identity for "+RNS.prettyhexrep(from_addr)+" could not be recalled. Requesting identity from network...", RNS.LOG_DEBUG) + RNS.Transport.request_path(from_addr) + return "destination_unknown" - if self.getstate(f"telemetry.{RNS.hexrep(to_addr, delimit=False)}.update_sending") == True: - RNS.log("Not sending new telemetry update, since an earlier transfer is already in progress", RNS.LOG_DEBUG) - return "in_progress" + else: + now = time.time() + dest = RNS.Destination(dest_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "lxmf", "delivery") + source = self.lxmf_destination + + if self.config["telemetry_use_propagation_only"] == True: + desired_method = LXMF.LXMessage.PROPAGATED + else: + desired_method = LXMF.LXMessage.DIRECT - if (self.latest_packed_telemetry != None and self.latest_telemetry != None) or stream != None: - dest_identity = RNS.Identity.recall(to_addr) - - if dest_identity == None: - RNS.log("The identity for "+RNS.prettyhexrep(to_addr)+" could not be recalled. Requesting identity from network...", RNS.LOG_DEBUG) - RNS.Transport.request_path(to_addr) - return "destination_unknown" + 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: [ + {Commands.TELEMETRY_REQUEST: request_timebase}, + ]} + + lxm = LXMF.LXMessage(dest, source, "", desired_method=desired_method, fields = lxm_fields, include_ticket=True) + lxm.request_timebase = request_timebase + lxm.register_delivery_callback(self.telemetry_request_finished) + lxm.register_failed_callback(self.telemetry_request_finished) + + if self.message_router.get_outbound_propagation_node() != None: + if self.config["telemetry_try_propagation_on_fail"]: + lxm.try_propagation_on_fail = True + + RNS.log(f"Sending telemetry request with timebase {request_timebase}", RNS.LOG_DEBUG) + self.setpersistent(f"telemetry.{RNS.hexrep(from_addr, delimit=False)}.last_request_attempt", time.time()) + self.setstate(f"telemetry.{RNS.hexrep(from_addr, delimit=False)}.request_sending", True) + self.message_router.handle_outbound(lxm) + return "sent" else: - dest = RNS.Destination(dest_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "lxmf", "delivery") - source = self.lxmf_destination - - if self.config["telemetry_use_propagation_only"] == True: - desired_method = LXMF.LXMessage.PROPAGATED - else: - desired_method = LXMF.LXMessage.DIRECT + return "not_sent" - 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: - return "already_sent" - - if stream != None and len(stream) > 0: - if lxm_fields == False: - lxm_fields = {} - lxm_fields[LXMF.FIELD_TELEMETRY_STREAM] = stream - - if lxm_fields != None and lxm_fields != False and (LXMF.FIELD_TELEMETRY in lxm_fields or LXMF.FIELD_TELEMETRY_STREAM in lxm_fields): - if LXMF.FIELD_TELEMETRY in lxm_fields: - telemeter = Telemeter.from_packed(lxm_fields[LXMF.FIELD_TELEMETRY]) - telemetry_timebase = telemeter.read_all()["time"]["utc"] - elif LXMF.FIELD_TELEMETRY_STREAM in lxm_fields: - telemetry_timebase = 0 - for te in lxm_fields[LXMF.FIELD_TELEMETRY_STREAM]: - ts = te[1] - telemetry_timebase = max(telemetry_timebase, ts) - - if telemetry_timebase > (self.getpersistent(f"telemetry.{RNS.hexrep(to_addr, delimit=False)}.last_send_success_timebase") or 0): - lxm = LXMF.LXMessage(dest, source, "", desired_method=desired_method, fields = lxm_fields, include_ticket=self.is_trusted(to_addr)) - lxm.telemetry_timebase = telemetry_timebase - lxm.register_delivery_callback(self.outbound_telemetry_finished) - lxm.register_failed_callback(self.outbound_telemetry_finished) - - if self.message_router.get_outbound_propagation_node() != None: - if self.config["telemetry_try_propagation_on_fail"]: - lxm.try_propagation_on_fail = True - - RNS.log(f"Sending telemetry update with timebase {telemetry_timebase}", RNS.LOG_DEBUG) - - self.setpersistent(f"telemetry.{RNS.hexrep(to_addr, delimit=False)}.last_send_attempt", time.time()) - self.setstate(f"telemetry.{RNS.hexrep(to_addr, delimit=False)}.update_sending", True) - self.message_router.handle_outbound(lxm) - return "sent" - - else: - RNS.log(f"Telemetry update with timebase {telemetry_timebase} was already successfully sent", RNS.LOG_DEBUG) - return "already_sent" - else: - return "nothing_to_send" + def _service_send_latest_telemetry(self, to_addr=None, stream=None, is_authorized_telemetry_request=False): + if not RNS.vendor.platformutils.is_android(): + return False + else: + if self.is_client: + try: + if self.rpc_connection == None: + self.rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key) + self.rpc_connection.send({"send_latest_telemetry": { + "to_addr": to_addr, + "stream": stream, + "is_authorized_telemetry_request": is_authorized_telemetry_request} + }) + response = self.rpc_connection.recv() + return response + + except Exception as e: + RNS.log("Error while sending latest telemetry over RPC: "+str(e), RNS.LOG_DEBUG) + RNS.trace_exception(e) + return False else: - RNS.log("A telemetry update was requested, but there was nothing to send.", RNS.LOG_WARNING) - return "nothing_to_send" + return False + + def send_latest_telemetry(self, to_addr=None, stream=None, is_authorized_telemetry_request=False): + if self.allow_service_dispatch and self.is_client: + try: + return self._service_send_latest_telemetry(to_addr, stream, is_authorized_telemetry_request) + + except Exception as e: + RNS.log("Error requesting latest telemetry: "+str(e), RNS.LOG_ERROR) + RNS.trace_exception(e) + return "not_sent" + + else: + if to_addr == None or to_addr == self.lxmf_destination.hash: + return "no_address" + else: + if to_addr == self.config["telemetry_collector"]: + is_authorized_telemetry_request = True + + if self.getstate(f"telemetry.{RNS.hexrep(to_addr, delimit=False)}.update_sending") == True: + RNS.log("Not sending new telemetry update, since an earlier transfer is already in progress", RNS.LOG_DEBUG) + return "in_progress" + + if (self.latest_packed_telemetry != None and self.latest_telemetry != None) or stream != None: + dest_identity = RNS.Identity.recall(to_addr) + + if dest_identity == None: + RNS.log("The identity for "+RNS.prettyhexrep(to_addr)+" could not be recalled. Requesting identity from network...", RNS.LOG_DEBUG) + RNS.Transport.request_path(to_addr) + return "destination_unknown" + + else: + dest = RNS.Destination(dest_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "lxmf", "delivery") + source = self.lxmf_destination + + if self.config["telemetry_use_propagation_only"] == True: + desired_method = LXMF.LXMessage.PROPAGATED + else: + desired_method = LXMF.LXMessage.DIRECT + + lxm_fields = self.get_message_fields(to_addr, is_authorized_telemetry_request=is_authorized_telemetry_request, signal_already_sent=True) + if lxm_fields == False and stream == None: + return "already_sent" + + if stream != None and len(stream) > 0: + if lxm_fields == False: + lxm_fields = {} + lxm_fields[LXMF.FIELD_TELEMETRY_STREAM] = stream + + if lxm_fields != None and lxm_fields != False and (LXMF.FIELD_TELEMETRY in lxm_fields or LXMF.FIELD_TELEMETRY_STREAM in lxm_fields): + if LXMF.FIELD_TELEMETRY in lxm_fields: + telemeter = Telemeter.from_packed(lxm_fields[LXMF.FIELD_TELEMETRY]) + telemetry_timebase = telemeter.read_all()["time"]["utc"] + elif LXMF.FIELD_TELEMETRY_STREAM in lxm_fields: + telemetry_timebase = 0 + for te in lxm_fields[LXMF.FIELD_TELEMETRY_STREAM]: + ts = te[1] + telemetry_timebase = max(telemetry_timebase, ts) + + if telemetry_timebase > (self.getpersistent(f"telemetry.{RNS.hexrep(to_addr, delimit=False)}.last_send_success_timebase") or 0): + lxm = LXMF.LXMessage(dest, source, "", desired_method=desired_method, fields = lxm_fields, include_ticket=self.is_trusted(to_addr)) + lxm.telemetry_timebase = telemetry_timebase + lxm.register_delivery_callback(self.outbound_telemetry_finished) + lxm.register_failed_callback(self.outbound_telemetry_finished) + + if self.message_router.get_outbound_propagation_node() != None: + if self.config["telemetry_try_propagation_on_fail"]: + lxm.try_propagation_on_fail = True + + RNS.log(f"Sending telemetry update with timebase {telemetry_timebase}", RNS.LOG_DEBUG) + + self.setpersistent(f"telemetry.{RNS.hexrep(to_addr, delimit=False)}.last_send_attempt", time.time()) + self.setstate(f"telemetry.{RNS.hexrep(to_addr, delimit=False)}.update_sending", True) + self.message_router.handle_outbound(lxm) + return "sent" + + else: + RNS.log(f"Telemetry update with timebase {telemetry_timebase} was already successfully sent", RNS.LOG_DEBUG) + return "already_sent" + else: + return "nothing_to_send" + + else: + RNS.log("A telemetry update was requested, but there was nothing to send.", RNS.LOG_WARNING) + return "nothing_to_send" def list_telemetry(self, context_dest = None, after = None, before = None, limit = None): @@ -1400,6 +1462,7 @@ class SidebandCore(): return [] def service_available(self): + heartbeat_stale_time = 7.5 now = time.time() service_heartbeat = self.getstate("service.heartbeat") if not service_heartbeat: @@ -1407,9 +1470,16 @@ class SidebandCore(): return False else: try: - if now - service_heartbeat > 4.0: - RNS.log("Stale service heartbeat at "+str(now), RNS.LOG_DEBUG) - return False + if now - service_heartbeat > heartbeat_stale_time: + RNS.log("Stale service heartbeat at "+str(now)+", retrying...", RNS.LOG_DEBUG) + now = time.time() + service_heartbeat = self.getstate("service.heartbeat") + if now - service_heartbeat > heartbeat_stale_time: + RNS.log("Service heartbeat did not recover after retry", RNS.LOG_DEBUG) + return False + else: + RNS.log("Service heartbeat recovered at"+str(time), RNS.LOG_DEBUG) + return True else: return True except Exception as e: @@ -1680,6 +1750,18 @@ class SidebandCore(): args["destination_hash"], args["propagation"]) connection.send(send_result) + elif "request_latest_telemetry" in call: + args = call["request_latest_telemetry"] + send_result = self.request_latest_telemetry(args["from_addr"]) + connection.send(send_result) + elif "send_latest_telemetry" in call: + args = call["send_latest_telemetry"] + send_result = self.send_latest_telemetry( + to_addr=args["to_addr"], + stream=args["stream"], + is_authorized_telemetry_request=args["is_authorized_telemetry_request"] + ) + connection.send(send_result) elif "get_lxm_progress" in call: args = call["get_lxm_progress"] connection.send(self.get_lxm_progress(args["lxm_hash"])) From f8b3e3760b831745cf10d97aeae64e9c00a136a9 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Mon, 16 Sep 2024 18:06:49 +0200 Subject: [PATCH 09/22] Handle undecodable message content --- sbapp/ui/messages.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/sbapp/ui/messages.py b/sbapp/ui/messages.py index 7c370cb..bfc0954 100644 --- a/sbapp/ui/messages.py +++ b/sbapp/ui/messages.py @@ -357,10 +357,14 @@ class Messages(): for m in self.new_messages: if not m["hash"] in self.added_item_hashes: - if not self.is_trusted: - message_input = str( escape_markup(m["content"].decode("utf-8")) ).encode("utf-8") - else: - message_input = m["content"] + try: + if not self.is_trusted: + message_input = str( escape_markup(m["content"].decode("utf-8")) ).encode("utf-8") + else: + message_input = m["content"] + except Exception as e: + RNS.log(f"Message content could not be decoded: {e}", RNS.LOG_DEBUG) + message_input = b"" if message_input.strip() == b"": if not ("lxm" in m and m["lxm"] != None and m["lxm"].fields != None and LXMF.FIELD_COMMANDS in m["lxm"].fields): @@ -966,7 +970,7 @@ class Messages(): "viewclass": "OneLineListItem", "text": "Copy message text", "height": dp(40), - "on_release": gen_copy(m["content"].decode("utf-8"), item) + "on_release": gen_copy(message_input.decode("utf-8"), item) }, { "text": "Delete", @@ -1000,7 +1004,7 @@ class Messages(): "viewclass": "OneLineListItem", "text": "Copy message text", "height": dp(40), - "on_release": gen_copy(m["content"].decode("utf-8"), item) + "on_release": gen_copy(message_input.decode("utf-8"), item) }, { "text": "Delete", @@ -1018,7 +1022,7 @@ class Messages(): "viewclass": "OneLineListItem", "text": "Copy", "height": dp(40), - "on_release": gen_copy(m["content"].decode("utf-8"), item) + "on_release": gen_copy(message_input.decode("utf-8"), item) }, { "text": "Delete", @@ -1035,7 +1039,7 @@ class Messages(): "viewclass": "OneLineListItem", "text": "Copy", "height": dp(40), - "on_release": gen_copy(m["content"].decode("utf-8"), item) + "on_release": gen_copy(message_input.decode("utf-8"), item) }, { "viewclass": "OneLineListItem", @@ -1058,7 +1062,7 @@ class Messages(): "viewclass": "OneLineListItem", "text": "Copy", "height": dp(40), - "on_release": gen_copy(m["content"].decode("utf-8"), item) + "on_release": gen_copy(message_input.decode("utf-8"), item) }, { "text": "Delete", From 2ecdf1c8532a0dc33f56a751d12b3b59a14e4fda Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Mon, 16 Sep 2024 18:59:33 +0200 Subject: [PATCH 10/22] Send messages opportunistically if ratchets are available --- sbapp/sideband/core.py | 6 +++++- sbapp/ui/messages.py | 5 ++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/sbapp/sideband/core.py b/sbapp/sideband/core.py index 8c7d11e..fd83777 100644 --- a/sbapp/sideband/core.py +++ b/sbapp/sideband/core.py @@ -4067,7 +4067,11 @@ class SidebandCore(): if propagation: desired_method = LXMF.LXMessage.PROPAGATED else: - desired_method = LXMF.LXMessage.DIRECT + if not self.message_router.delivery_link_available(destination_hash) and RNS.Identity.current_ratchet_id(destination_hash) != None: + RNS.log(f"Have ratchet for {RNS.prettyhexrep(destination_hash)}, requesting opportunistic delivery of message", RNS.LOG_DEBUG) + desired_method = LXMF.LXMessage.OPPORTUNISTIC + else: + desired_method = LXMF.LXMessage.DIRECT if skip_fields: fields = {} diff --git a/sbapp/ui/messages.py b/sbapp/ui/messages.py index bfc0954..70a0d2a 100644 --- a/sbapp/ui/messages.py +++ b/sbapp/ui/messages.py @@ -102,7 +102,6 @@ class Messages(): self.list.remove_widget(self.load_more_button) def message_details_dialog(self, lxm_hash): - RNS.log(f"Opening dialog for {RNS.prettyhexrep(lxm_hash)}", RNS.LOG_DEBUG) ss = int(dp(16)) ms = int(dp(14)) @@ -247,10 +246,10 @@ class Messages(): else: w.line_color = (1.0, 1.0, 1.0, 0.5) - if m["state"] == LXMF.LXMessage.SENDING or m["state"] == LXMF.LXMessage.OUTBOUND: + if m["state"] == LXMF.LXMessage.SENDING or m["state"] == LXMF.LXMessage.OUTBOUND or m["state"] == LXMF.LXMessage.SENT: msg = self.app.sideband.message(m["hash"]) - if msg["state"] == LXMF.LXMessage.OUTBOUND or msg["state"] == LXMF.LXMessage.SENDING: + 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) txstr = time.strftime(ts_format, time.localtime(msg["sent"])) titlestr = "" From 92d7ba24cc125c48aa1e885dadef1d2d72f5c3fa Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Mon, 16 Sep 2024 19:51:48 +0200 Subject: [PATCH 11/22] Added delivery method icons to message view --- sbapp/ui/messages.py | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/sbapp/ui/messages.py b/sbapp/ui/messages.py index 70a0d2a..fb79658 100644 --- a/sbapp/ui/messages.py +++ b/sbapp/ui/messages.py @@ -249,6 +249,17 @@ class Messages(): if m["state"] == LXMF.LXMessage.SENDING or m["state"] == LXMF.LXMessage.OUTBOUND or m["state"] == LXMF.LXMessage.SENT: msg = self.app.sideband.message(m["hash"]) + delivery_syms = "" + # if msg["extras"] != None and "ratchet_id" in m["extras"]: + # delivery_syms += " ⚙️" + if msg["method"] == LXMF.LXMessage.OPPORTUNISTIC: + delivery_syms += " 📨" + if msg["method"] == LXMF.LXMessage.DIRECT: + delivery_syms += " 🔗" + if msg["method"] == LXMF.LXMessage.PROPAGATED: + delivery_syms += " 📦" + delivery_syms = multilingual_markup(delivery_syms.encode("utf-8")).decode("utf-8") + if msg["state"] == LXMF.LXMessage.OUTBOUND or msg["state"] == LXMF.LXMessage.SENDING or msg["state"] == LXMF.LXMessage.SENT: w.md_bg_color = msg_color = mdc(color_unknown, intensity_msgs) txstr = time.strftime(ts_format, time.localtime(msg["sent"])) @@ -289,7 +300,7 @@ class Messages(): titlestr = "" if msg["title"]: titlestr = "[b]Title[/b] "+msg["title"].decode("utf-8")+"\n" - w.heading = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] Delivered" + w.heading = titlestr+"[b]Sent[/b] "+txstr+delivery_syms+"\n[b]State[/b] Delivered" if w.has_audio: alstr = RNS.prettysize(w.audio_size) w.heading += f"\n[b]Audio Message[/b] ({alstr})" @@ -310,7 +321,7 @@ class Messages(): titlestr = "" if msg["title"]: titlestr = "[b]Title[/b] "+msg["title"].decode("utf-8")+"\n" - w.heading = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] On Propagation Net" + w.heading = titlestr+"[b]Sent[/b] "+txstr+delivery_syms+"\n[b]State[/b] On Propagation Net" if w.has_audio: alstr = RNS.prettysize(w.audio_size) w.heading += f"\n[b]Audio Message[/b] ({alstr})" @@ -388,6 +399,17 @@ class Messages(): stamp_valid = False stamp_value = None + delivery_syms = "" + # if m["extras"] != None and "ratchet_id" in m["extras"]: + # delivery_syms += " ⚙️" + if m["method"] == LXMF.LXMessage.OPPORTUNISTIC: + delivery_syms += " 📨" + if m["method"] == LXMF.LXMessage.DIRECT: + delivery_syms += " 🔗" + if m["method"] == LXMF.LXMessage.PROPAGATED: + delivery_syms += " 📦" + delivery_syms = multilingual_markup(delivery_syms.encode("utf-8")).decode("utf-8") + if "lxm" in m and m["lxm"] != None and m["lxm"].signature_validated: signature_valid = True @@ -493,11 +515,11 @@ class Messages(): if m["source"] == self.app.sideband.lxmf_destination.hash: if m["state"] == LXMF.LXMessage.DELIVERED: msg_color = mdc(color_delivered, intensity_msgs) - heading_str = titlestr+"[b]Sent[/b] "+txstr+"\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: msg_color = mdc(color_propagated, intensity_msgs) - heading_str = titlestr+"[b]Sent[/b] "+txstr+"\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: msg_color = mdc(color_paper, intensity_msgs) @@ -524,7 +546,7 @@ class Messages(): # if stamp_valid: # txstr += f" [b]Stamp[/b] value is {stamp_value} " - heading_str += "[b]Sent[/b] "+txstr + heading_str += "[b]Sent[/b] "+txstr+delivery_syms heading_str += "\n[b]Received[/b] "+rxstr if rcvd_d_str != "": From 4fddc45dbca0aebf715047308c707740b90ac345 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Mon, 16 Sep 2024 20:13:42 +0200 Subject: [PATCH 12/22] Updated versions --- sbapp/buildozer.spec | 2 +- sbapp/main.py | 2 +- setup.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sbapp/buildozer.spec b/sbapp/buildozer.spec index 53c7c82..be9541a 100644 --- a/sbapp/buildozer.spec +++ b/sbapp/buildozer.spec @@ -10,7 +10,7 @@ source.exclude_patterns = app_storage/*,venv/*,Makefile,./Makefil*,requirements, version.regex = __version__ = ['"](.*)['"] version.filename = %(source.dir)s/main.py -android.numeric_version = 20240913 +android.numeric_version = 20240916 requirements = kivy==2.3.0,libbz2,pillow==10.2.0,qrcode==7.3.1,usb4a,usbserial4a,libwebp,libogg,libopus,opusfile,numpy,cryptography,ffpyplayer,codec2,pycodec2,sh,pynacl diff --git a/sbapp/main.py b/sbapp/main.py index 143e45e..17aac37 100644 --- a/sbapp/main.py +++ b/sbapp/main.py @@ -1,6 +1,6 @@ __debug_build__ = False __disable_shaders__ = False -__version__ = "0.9.5" +__version__ = "0.9.6" __variant__ = "beta" import sys diff --git a/setup.py b/setup.py index 518fd2d..4a2988c 100644 --- a/setup.py +++ b/setup.py @@ -96,8 +96,8 @@ setuptools.setup( ] }, install_requires=[ - "rns>=0.7.7", - "lxmf>=0.5.1", + "rns>=0.7.8", + "lxmf>=0.5.2", "kivy>=2.3.0", "pillow>=10.2.0", "qrcode", From 69ab9105909c507d0aa86b6ab336f0286b7c7449 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Mon, 16 Sep 2024 20:25:32 +0200 Subject: [PATCH 13/22] Send commands opportunistically if ratchets are available --- sbapp/sideband/core.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sbapp/sideband/core.py b/sbapp/sideband/core.py index fd83777..bbea819 100644 --- a/sbapp/sideband/core.py +++ b/sbapp/sideband/core.py @@ -4147,7 +4147,11 @@ class SidebandCore(): if propagation: desired_method = LXMF.LXMessage.PROPAGATED else: - desired_method = LXMF.LXMessage.DIRECT + if not self.message_router.delivery_link_available(destination_hash) and RNS.Identity.current_ratchet_id(destination_hash) != None: + RNS.log(f"Have ratchet for {RNS.prettyhexrep(destination_hash)}, requesting opportunistic delivery of command", RNS.LOG_DEBUG) + desired_method = LXMF.LXMessage.OPPORTUNISTIC + else: + desired_method = LXMF.LXMessage.DIRECT lxm = LXMF.LXMessage(dest, source, "", title="", desired_method=desired_method, fields = {LXMF.FIELD_COMMANDS: commands}, include_ticket=self.is_trusted(destination_hash)) lxm.register_delivery_callback(self.message_notification) From 074bb4aa4e12bac798812edaa66f6ea1f8b8d025 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Tue, 17 Sep 2024 14:24:56 +0200 Subject: [PATCH 14/22] Check message db return is not none --- sbapp/ui/messages.py | 167 ++++++++++++++++++++++--------------------- 1 file changed, 84 insertions(+), 83 deletions(-) diff --git a/sbapp/ui/messages.py b/sbapp/ui/messages.py index fb79658..8798723 100644 --- a/sbapp/ui/messages.py +++ b/sbapp/ui/messages.py @@ -249,96 +249,97 @@ class Messages(): if m["state"] == LXMF.LXMessage.SENDING or m["state"] == LXMF.LXMessage.OUTBOUND or m["state"] == LXMF.LXMessage.SENT: msg = self.app.sideband.message(m["hash"]) - delivery_syms = "" - # if msg["extras"] != None and "ratchet_id" in m["extras"]: - # delivery_syms += " ⚙️" - if msg["method"] == LXMF.LXMessage.OPPORTUNISTIC: - delivery_syms += " 📨" - if msg["method"] == LXMF.LXMessage.DIRECT: - delivery_syms += " 🔗" - if msg["method"] == LXMF.LXMessage.PROPAGATED: - delivery_syms += " 📦" - delivery_syms = multilingual_markup(delivery_syms.encode("utf-8")).decode("utf-8") + if msg != None: + delivery_syms = "" + # if msg["extras"] != None and "ratchet_id" in m["extras"]: + # delivery_syms += " ⚙️" + if msg["method"] == LXMF.LXMessage.OPPORTUNISTIC: + delivery_syms += " 📨" + if msg["method"] == LXMF.LXMessage.DIRECT: + delivery_syms += " 🔗" + if msg["method"] == LXMF.LXMessage.PROPAGATED: + delivery_syms += " 📦" + delivery_syms = multilingual_markup(delivery_syms.encode("utf-8")).decode("utf-8") - if msg["state"] == LXMF.LXMessage.OUTBOUND or msg["state"] == LXMF.LXMessage.SENDING or msg["state"] == LXMF.LXMessage.SENT: - w.md_bg_color = msg_color = mdc(color_unknown, intensity_msgs) - txstr = time.strftime(ts_format, time.localtime(msg["sent"])) - titlestr = "" - prgstr = "" - sphrase = "Sending" - prg = self.app.sideband.get_lxm_progress(msg["hash"]) - if prg != None: - prgstr = ", "+str(round(prg*100, 1))+"% done" - if prg <= 0.00: - stamp_cost = self.app.sideband.get_lxm_stamp_cost(msg["hash"]) - if stamp_cost: - sphrase = f"Generating stamp with cost {stamp_cost}" - prgstr = "" - else: + 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) + txstr = time.strftime(ts_format, time.localtime(msg["sent"])) + titlestr = "" + prgstr = "" + sphrase = "Sending" + prg = self.app.sideband.get_lxm_progress(msg["hash"]) + if prg != None: + prgstr = ", "+str(round(prg*100, 1))+"% done" + if prg <= 0.00: + stamp_cost = self.app.sideband.get_lxm_stamp_cost(msg["hash"]) + if stamp_cost: + sphrase = f"Generating stamp with cost {stamp_cost}" + prgstr = "" + else: + sphrase = "Waiting for path" + elif prg <= 0.01: sphrase = "Waiting for path" - elif prg <= 0.01: - sphrase = "Waiting for path" - elif prg <= 0.03: - sphrase = "Establishing link" - elif prg <= 0.05: - sphrase = "Link established" - elif prg >= 0.05: - sphrase = "Sending" - - if msg["title"]: - titlestr = "[b]Title[/b] "+msg["title"].decode("utf-8")+"\n" - w.heading = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] "+sphrase+prgstr+" " - if w.has_audio: - alstr = RNS.prettysize(w.audio_size) - w.heading += f"\n[b]Audio Message[/b] ({alstr})" - m["state"] = msg["state"] + elif prg <= 0.03: + sphrase = "Establishing link" + elif prg <= 0.05: + sphrase = "Link established" + elif prg >= 0.05: + sphrase = "Sending" + + if msg["title"]: + titlestr = "[b]Title[/b] "+msg["title"].decode("utf-8")+"\n" + w.heading = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] "+sphrase+prgstr+" " + if w.has_audio: + alstr = RNS.prettysize(w.audio_size) + w.heading += f"\n[b]Audio Message[/b] ({alstr})" + m["state"] = msg["state"] - if msg["state"] == LXMF.LXMessage.DELIVERED: - w.md_bg_color = msg_color = mdc(color_delivered, intensity_msgs) - txstr = time.strftime(ts_format, time.localtime(msg["sent"])) - titlestr = "" - if msg["title"]: - titlestr = "[b]Title[/b] "+msg["title"].decode("utf-8")+"\n" - w.heading = titlestr+"[b]Sent[/b] "+txstr+delivery_syms+"\n[b]State[/b] Delivered" - if w.has_audio: - alstr = RNS.prettysize(w.audio_size) - w.heading += f"\n[b]Audio Message[/b] ({alstr})" - m["state"] = msg["state"] + if msg["state"] == LXMF.LXMessage.DELIVERED: + w.md_bg_color = msg_color = mdc(color_delivered, intensity_msgs) + txstr = time.strftime(ts_format, time.localtime(msg["sent"])) + titlestr = "" + if msg["title"]: + titlestr = "[b]Title[/b] "+msg["title"].decode("utf-8")+"\n" + w.heading = titlestr+"[b]Sent[/b] "+txstr+delivery_syms+"\n[b]State[/b] Delivered" + if w.has_audio: + alstr = RNS.prettysize(w.audio_size) + w.heading += f"\n[b]Audio Message[/b] ({alstr})" + m["state"] = msg["state"] - if msg["method"] == LXMF.LXMessage.PAPER: - w.md_bg_color = msg_color = mdc(color_paper, intensity_msgs) - txstr = time.strftime(ts_format, time.localtime(msg["sent"])) - titlestr = "" - if msg["title"]: - titlestr = "[b]Title[/b] "+msg["title"].decode("utf-8")+"\n" - w.heading = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] Paper Message" - m["state"] = msg["state"] + if msg["method"] == LXMF.LXMessage.PAPER: + w.md_bg_color = msg_color = mdc(color_paper, intensity_msgs) + txstr = time.strftime(ts_format, time.localtime(msg["sent"])) + titlestr = "" + if msg["title"]: + titlestr = "[b]Title[/b] "+msg["title"].decode("utf-8")+"\n" + w.heading = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] Paper Message" + m["state"] = msg["state"] - if msg["method"] == LXMF.LXMessage.PROPAGATED and msg["state"] == LXMF.LXMessage.SENT: - w.md_bg_color = msg_color = mdc(color_propagated, intensity_msgs) - txstr = time.strftime(ts_format, time.localtime(msg["sent"])) - titlestr = "" - if msg["title"]: - titlestr = "[b]Title[/b] "+msg["title"].decode("utf-8")+"\n" - w.heading = titlestr+"[b]Sent[/b] "+txstr+delivery_syms+"\n[b]State[/b] On Propagation Net" - if w.has_audio: - alstr = RNS.prettysize(w.audio_size) - w.heading += f"\n[b]Audio Message[/b] ({alstr})" - m["state"] = msg["state"] + if msg["method"] == LXMF.LXMessage.PROPAGATED and msg["state"] == LXMF.LXMessage.SENT: + w.md_bg_color = msg_color = mdc(color_propagated, intensity_msgs) + txstr = time.strftime(ts_format, time.localtime(msg["sent"])) + titlestr = "" + if msg["title"]: + titlestr = "[b]Title[/b] "+msg["title"].decode("utf-8")+"\n" + w.heading = titlestr+"[b]Sent[/b] "+txstr+delivery_syms+"\n[b]State[/b] On Propagation Net" + if w.has_audio: + alstr = RNS.prettysize(w.audio_size) + w.heading += f"\n[b]Audio Message[/b] ({alstr})" + m["state"] = msg["state"] - if msg["state"] == LXMF.LXMessage.FAILED: - w.md_bg_color = msg_color = mdc(color_failed, intensity_msgs) - txstr = time.strftime(ts_format, time.localtime(msg["sent"])) - titlestr = "" - if msg["title"]: - titlestr = "[b]Title[/b] "+msg["title"].decode("utf-8")+"\n" - w.heading = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] Failed" - m["state"] = msg["state"] - if w.has_audio: - alstr = RNS.prettysize(w.audio_size) - w.heading += f"\n[b]Audio Message[/b] ({alstr})" - w.dmenu.items.append(w.dmenu.retry_item) + if msg["state"] == LXMF.LXMessage.FAILED: + w.md_bg_color = msg_color = mdc(color_failed, intensity_msgs) + txstr = time.strftime(ts_format, time.localtime(msg["sent"])) + titlestr = "" + if msg["title"]: + titlestr = "[b]Title[/b] "+msg["title"].decode("utf-8")+"\n" + w.heading = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] Failed" + m["state"] = msg["state"] + if w.has_audio: + alstr = RNS.prettysize(w.audio_size) + w.heading += f"\n[b]Audio Message[/b] ({alstr})" + w.dmenu.items.append(w.dmenu.retry_item) def hide_widget(self, wid, dohide=True): From 72e21a4735242e242700736508b43fa421656167 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Wed, 18 Sep 2024 10:06:49 +0200 Subject: [PATCH 15/22] Skip saving config on spurious scroll events --- sbapp/main.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sbapp/main.py b/sbapp/main.py index 17aac37..21522e0 100644 --- a/sbapp/main.py +++ b/sbapp/main.py @@ -2768,8 +2768,9 @@ class SidebandApp(MDApp): pre = self.settings_screen.ids.settings_lxmf_sync_periodic.text self.settings_screen.ids.settings_lxmf_sync_periodic.text = "Auto sync every "+interval_text if save: - self.sideband.config["lxmf_sync_interval"] = interval - self.sideband.save_configuration() + if (event == None or not hasattr(event, "button") or not event.button) or not "scroll" in event.button: + self.sideband.config["lxmf_sync_interval"] = interval + self.sideband.save_configuration() def stamp_cost_change(sender=None, event=None, save=True): slider_val = int(self.settings_screen.ids.settings_lxmf_require_stamps_cost.value) @@ -2782,7 +2783,8 @@ class SidebandApp(MDApp): if slider_val < 1: slider_val = 1 self.sideband.config["lxmf_inbound_stamp_cost"] = slider_val - self.sideband.save_configuration() + if (event == None or not hasattr(event, "button") or not event.button) or not "scroll" in event.button: + self.sideband.save_configuration() self.settings_screen.ids.settings_lxmf_address.text = RNS.hexrep(self.sideband.lxmf_destination.hash, delimit=False) self.settings_screen.ids.settings_identity_hash.text = RNS.hexrep(self.sideband.lxmf_destination.identity.hash, delimit=False) From 08ea89be1d2de9edbd6e432dab18e7835738baf0 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Wed, 18 Sep 2024 10:07:00 +0200 Subject: [PATCH 16/22] Updated announce intervals --- sbapp/sideband/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sbapp/sideband/core.py b/sbapp/sideband/core.py index bbea819..dd5f0c6 100644 --- a/sbapp/sideband/core.py +++ b/sbapp/sideband/core.py @@ -96,9 +96,9 @@ class SidebandCore(): TELEMETRY_INTERVAL = 60 SERVICE_TELEMETRY_INTERVAL = 300 - IF_CHANGE_ANNOUNCE_MIN_INTERVAL = 6 # In seconds + IF_CHANGE_ANNOUNCE_MIN_INTERVAL = 3.5 # In seconds AUTO_ANNOUNCE_RANDOM_MIN = 90 # In minutes - AUTO_ANNOUNCE_RANDOM_MAX = 480 # In minutes + AUTO_ANNOUNCE_RANDOM_MAX = 300 # In minutes DEFAULT_APPEARANCE = ["account", [0,0,0,1], [1,1,1,1]] From 6cd22aa4ef6531758e9ecc5ece86771fd640821f Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Wed, 18 Sep 2024 10:38:03 +0200 Subject: [PATCH 17/22] Adjusted settings screen --- sbapp/main.py | 2 +- sbapp/ui/layouts.py | 52 ++++++++++++++++++++++++++++++++++++++------- 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/sbapp/main.py b/sbapp/main.py index 21522e0..2cb08ea 100644 --- a/sbapp/main.py +++ b/sbapp/main.py @@ -5682,7 +5682,7 @@ Thank you very much for using Free Communications Systems. self.root.ids.screen_manager.transition = self.slide_transition self.root.ids.screen_manager.transition.direction = direction - info = "The [b]Local Broadcasts[/b] feature will allow you to send and listen for local broadcast transmissions on connected radio, LoRa and WiFi interfaces.\n\n[b]Local Broadcasts[/b] makes it easy to establish public information exchange with anyone in direct radio range, or even with large areas far away using the [i]Remote Broadcast Repeater[/i] feature.\n\nThese features are not yet implemented in Sideband.\n\nWant it faster? Go to [u][ref=link]https://unsigned.io/donate[/ref][/u] to support the project." + info = "The [b]Local Broadcasts[/b] feature will allow you to send and listen for local broadcast transmissions on all connected interfaces.\n\n[b]Local Broadcasts[/b] makes it easy to establish public information exchange with anyone in direct radio range, or even with large areas far away using the [i]Remote Broadcast Repeater[/i] feature.\n\nThese features are not yet implemented in Sideband.\n\nWant it faster? Go to [u][ref=link]https://unsigned.io/donate[/ref][/u] to support the project." if self.theme_cls.theme_style == "Dark": info = "[color=#"+dark_theme_text_color+"]"+info+"[/color]" self.broadcasts_screen.ids.broadcasts_info.text = info diff --git a/sbapp/ui/layouts.py b/sbapp/ui/layouts.py index 22a6263..66acdd0 100644 --- a/sbapp/ui/layouts.py +++ b/sbapp/ui/layouts.py @@ -616,7 +616,7 @@ MDScreen: MDLabel: text: "Shared Instance Access\\n" id: connectivity_shared_access_label - font_style: "H6" + font_style: "H5" MDLabel: id: connectivity_shared_access @@ -1176,7 +1176,7 @@ MDScreen: padding: [0,dp(0),dp(0),dp(0)] text: "Plugin Settings" id: plugins_active_heading - font_style: "H6" + font_style: "H5" size_hint_y: None height: self.texture_size[1] @@ -1286,7 +1286,7 @@ MDScreen: MDLabel: text: "User Options" - font_style: "H6" + font_style: "H5" size_hint_y: None height: self.texture_size[1] @@ -1326,8 +1326,17 @@ MDScreen: height: dp(64) MDLabel: - text: "Address & Identity" + text: "•" font_style: "H6" + text_size: self.size + halign: "center" + size_hint_y: None + height: self.texture_size[1] + padding: [0, dp(2), 0, dp(22)] + + MDLabel: + text: "Address & Identity" + font_style: "H5" size_hint_y: None height: self.texture_size[1] @@ -1361,8 +1370,17 @@ MDScreen: height: dp(64) MDLabel: - text: "Appearance" + text: "•" font_style: "H6" + text_size: self.size + halign: "center" + size_hint_y: None + height: self.texture_size[1] + padding: [0, dp(2), 0, dp(22)] + + MDLabel: + text: "Appearance" + font_style: "H5" size_hint_y: None height: self.texture_size[1] @@ -1464,8 +1482,17 @@ MDScreen: active: False MDLabel: - text: "\\nBehaviour" + text: "•" font_style: "H6" + text_size: self.size + halign: "center" + size_hint_y: None + height: self.texture_size[1] + padding: [0, dp(22), 0, dp(2)] + + MDLabel: + text: "\\nBehaviour" + font_style: "H5" size_hint_y: None height: self.texture_size[1] @@ -1498,7 +1525,7 @@ MDScreen: height: dp(48) MDLabel: - text: "Try Propagation Node on direct delivery failure" + text: "Try propagation on direct delivery failure" font_style: "H6" MDSwitch: @@ -1684,8 +1711,17 @@ MDScreen: active: False MDLabel: - text: "Input Options & Localisation" + text: "•" font_style: "H6" + text_size: self.size + halign: "center" + size_hint_y: None + height: self.texture_size[1] + padding: [0, dp(0), 0, dp(30)] + + MDLabel: + text: "Input Options & Localisation" + font_style: "H5" size_hint_y: None height: self.texture_size[1] From 1bbbbd849f1cf1fd0bdd39d4d23381628f4a3e88 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Wed, 18 Sep 2024 12:36:20 +0200 Subject: [PATCH 18/22] Updated readme --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c1ff3e6..c8fd925 100644 --- a/README.md +++ b/README.md @@ -45,12 +45,12 @@ After the application is installed on your Android device, it is also possible t ## On Linux -On all Linux-based operating systems, Sideband is available as a `pipx`/`pip` package. This installation method **includes desktop integration**, so that Sideband will show up in your applications menu and launchers. +On all Linux-based operating systems, Sideband is available as a `pipx`/`pip` package. This installation method **includes desktop integration**, so that Sideband will show up in your applications menu and launchers. Below are install steps for the most common recent Linux distros. For Debian 11, see the end of this section. You will first need to install a few dependencies for audio messaging and Codec2 support to work: ```bash -# For Debian, Ubuntu and derivatives +# For Debian (12+), Ubuntu (22.04+) and derivatives sudo apt install pipx python3-pyaudio python3-dev build-essential libopusfile0 portaudio19-dev codec2 xclip xsel # For Manjaro and derivatives @@ -112,6 +112,14 @@ pip install sbapp --no-dependencies # In the above case, you will still need to # manually install the RNS and LXMF dependencies: pip install rns lxmf + +# Install Sideband on Debian 11 and derivatives: +sudo apt install python3-pip python3-pyaudio python3-dev build-essential libopusfile0 portaudio19-dev codec2 xclip xsel +pip install sbapp + +# On Debian 11, run Sideband manually via the +# terminal once to install desktop integration: +python3 -m sbapp.main ``` ## On Raspberry Pi From 241f5d9c01675f72ddb60803659e5e212d4c1419 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Wed, 18 Sep 2024 12:36:37 +0200 Subject: [PATCH 19/22] Cleanup --- sbapp/main.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sbapp/main.py b/sbapp/main.py index 2cb08ea..3fe32ea 100644 --- a/sbapp/main.py +++ b/sbapp/main.py @@ -3001,35 +3001,35 @@ class SidebandApp(MDApp): self.widget_hide(self.connectivity_screen.ids.connectivity_transport_fields) def con_collapse_local(collapse=True): - # self.widget_hide(self.root.ids.connectivity_local_fields, collapse) + # self.widget_hide(self.connectivity_screen.ids.connectivity_local_fields, collapse) pass def con_collapse_tcp(collapse=True): - # self.widget_hide(self.root.ids.connectivity_tcp_fields, collapse) + # self.widget_hide(self.connectivity_screen.ids.connectivity_tcp_fields, collapse) pass def con_collapse_i2p(collapse=True): - # self.widget_hide(self.root.ids.connectivity_i2p_fields, collapse) + # self.widget_hide(self.connectivity_screen.ids.connectivity_i2p_fields, collapse) pass def con_collapse_bluetooth(collapse=True): - # self.widget_hide(self.root.ids.connectivity_bluetooth_fields, collapse) + # self.widget_hide(self.connectivity_screen.ids.connectivity_bluetooth_fields, collapse) pass def con_collapse_rnode(collapse=True): - # self.widget_hide(self.root.ids.connectivity_rnode_fields, collapse) + # self.widget_hide(self.connectivity_screen.ids.connectivity_rnode_fields, collapse) pass def con_collapse_modem(collapse=True): - # self.widget_hide(self.root.ids.connectivity_modem_fields, collapse) + # self.widget_hide(self.connectivity_screen.ids.connectivity_modem_fields, collapse) pass def con_collapse_serial(collapse=True): - # self.widget_hide(self.root.ids.connectivity_serial_fields, collapse) + # self.widget_hide(self.connectivity_screen.ids.connectivity_serial_fields, collapse) pass def con_collapse_transport(collapse=True): - # self.widget_hide(self.root.ids.connectivity_transport_fields, collapse) + # self.widget_hide(self.connectivity_screen.ids.connectivity_transport_fields, collapse) pass def save_connectivity(sender=None, event=None): From dc44071a1a705addd809a7a30b61ca353d49f813 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Thu, 19 Sep 2024 00:44:37 +0200 Subject: [PATCH 20/22] Updated Android build code --- sbapp/buildozer.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sbapp/buildozer.spec b/sbapp/buildozer.spec index be9541a..2a31cf6 100644 --- a/sbapp/buildozer.spec +++ b/sbapp/buildozer.spec @@ -10,7 +10,7 @@ source.exclude_patterns = app_storage/*,venv/*,Makefile,./Makefil*,requirements, version.regex = __version__ = ['"](.*)['"] version.filename = %(source.dir)s/main.py -android.numeric_version = 20240916 +android.numeric_version = 20240919 requirements = kivy==2.3.0,libbz2,pillow==10.2.0,qrcode==7.3.1,usb4a,usbserial4a,libwebp,libogg,libopus,opusfile,numpy,cryptography,ffpyplayer,codec2,pycodec2,sh,pynacl From 7f28192228e3b624122f0d6bba7afa485c5c9dde Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Thu, 19 Sep 2024 23:27:17 +0200 Subject: [PATCH 21/22] Updated versions --- sbapp/buildozer.spec | 2 +- sbapp/main.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sbapp/buildozer.spec b/sbapp/buildozer.spec index 2a31cf6..13a7cdf 100644 --- a/sbapp/buildozer.spec +++ b/sbapp/buildozer.spec @@ -10,7 +10,7 @@ source.exclude_patterns = app_storage/*,venv/*,Makefile,./Makefil*,requirements, version.regex = __version__ = ['"](.*)['"] version.filename = %(source.dir)s/main.py -android.numeric_version = 20240919 +android.numeric_version = 20240920 requirements = kivy==2.3.0,libbz2,pillow==10.2.0,qrcode==7.3.1,usb4a,usbserial4a,libwebp,libogg,libopus,opusfile,numpy,cryptography,ffpyplayer,codec2,pycodec2,sh,pynacl diff --git a/sbapp/main.py b/sbapp/main.py index 3fe32ea..456d15b 100644 --- a/sbapp/main.py +++ b/sbapp/main.py @@ -1,6 +1,6 @@ __debug_build__ = False __disable_shaders__ = False -__version__ = "0.9.6" +__version__ = "0.9.7" __variant__ = "beta" import sys diff --git a/setup.py b/setup.py index 4a2988c..84c5efa 100644 --- a/setup.py +++ b/setup.py @@ -97,7 +97,7 @@ setuptools.setup( }, install_requires=[ "rns>=0.7.8", - "lxmf>=0.5.2", + "lxmf>=0.5.3", "kivy>=2.3.0", "pillow>=10.2.0", "qrcode", From cf8f704319fbfe2db0db5c5857c560875945fd88 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Thu, 19 Sep 2024 23:55:20 +0200 Subject: [PATCH 22/22] Updated readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c8fd925..c1d8288 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Sideband provides many useful and interesting functions, such as: - Geospatial awareness calculations. - Exchanging messages through **encrypted QR-codes on paper**, or through messages embedded directly in **lxm://** links. - Using **Android devices as impromptu Reticulum routers** (*Transport Instances*), for setting up or extending networks easily. -- Remote **command execution and response engine**, with built-in commands, such as `ping`, `signal` reports and `echo`, and **full plug-ing expandability**. +- Remote **command execution and response engine**, with built-in commands, such as `ping`, `signal` reports and `echo`, and **full plugin expandability**. - Remote **telemetry querying**, with strong, secure and cryptographically robust authentication and control. - **Plugin system** that allows you to easily **create your own commands**, services and telemetry sources.