Merge remote-tracking branch 'upstream/main'

This commit is contained in:
jacob.eva 2024-10-23 16:38:19 +01:00
commit d639ffcf9a
No known key found for this signature in database
GPG Key ID: 0B92E083BBCCAA1E
10 changed files with 526 additions and 312 deletions

View File

@ -10,9 +10,9 @@ 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 = 20240920 android.numeric_version = 20241013
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,android,able_recipe 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
#android.gradle_dependencies = com.android.support:support-compat:28.0.0 #android.gradle_dependencies = com.android.support:support-compat:28.0.0
#android.enable_androidx = True #android.enable_androidx = True
@ -29,7 +29,8 @@ android.presplash_color = #00000000
orientation = portrait orientation = portrait
fullscreen = 0 fullscreen = 0
android.permissions = INTERNET,POST_NOTIFICATIONS,WAKE_LOCK,FOREGROUND_SERVICE,CHANGE_WIFI_MULTICAST_STATE,BLUETOOTH, BLUETOOTH_ADMIN, BLUETOOTH_SCAN, BLUETOOTH_CONNECT, BLUETOOTH_ADVERTISE,ACCESS_NETWORK_STATE,ACCESS_FINE_LOCATION,ACCESS_COARSE_LOCATION,MANAGE_EXTERNAL_STORAGE,ACCESS_BACKGROUND_LOCATION,RECORD_AUDIO #android.permissions = INTERNET,POST_NOTIFICATIONS,WAKE_LOCK,FOREGROUND_SERVICE,CHANGE_WIFI_MULTICAST_STATE,BLUETOOTH, BLUETOOTH_ADMIN, BLUETOOTH_SCAN, BLUETOOTH_CONNECT, BLUETOOTH_ADVERTISE,ACCESS_NETWORK_STATE,ACCESS_FINE_LOCATION,ACCESS_COARSE_LOCATION,MANAGE_EXTERNAL_STORAGE,ACCESS_BACKGROUND_LOCATION,RECORD_AUDIO
android.permissions = INTERNET,POST_NOTIFICATIONS,WAKE_LOCK,FOREGROUND_SERVICE,CHANGE_WIFI_MULTICAST_STATE,BLUETOOTH_SCAN,BLUETOOTH_ADVERTISE,BLUETOOTH_CONNECT,ACCESS_NETWORK_STATE,ACCESS_FINE_LOCATION,ACCESS_COARSE_LOCATION,MANAGE_EXTERNAL_STORAGE,ACCESS_BACKGROUND_LOCATION,RECORD_AUDIO,REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,FOREGROUND_SERVICE_CONNECTED_DEVICE
android.api = 31 android.api = 31
android.minapi = 24 android.minapi = 24

View File

@ -649,7 +649,8 @@ class MDFileManager(MDRelativeLayout):
return dirs, files return dirs, files
except OSError: except OSError as e:
print("Filemanager OSError: "+str(e))
return None, None return None, None
def close(self) -> None: def close(self) -> None:

View File

@ -1,7 +1,7 @@
__debug_build__ = False __debug_build__ = False
__disable_shaders__ = False __disable_shaders__ = False
__version__ = "0.9.7" __version__ = "1.1.3"
__variant__ = "beta" __variant__ = ""
import sys import sys
import argparse import argparse
@ -300,6 +300,7 @@ class SidebandApp(MDApp):
self.hardware_rnode_ready = False self.hardware_rnode_ready = False
self.hardware_modem_ready = False self.hardware_modem_ready = False
self.hardware_serial_ready = False self.hardware_serial_ready = False
self.hw_error_dialog = None
self.final_load_completed = False self.final_load_completed = False
self.service_last_available = 0 self.service_last_available = 0
@ -406,6 +407,20 @@ class SidebandApp(MDApp):
else: else:
self.open_conversations() self.open_conversations()
if RNS.vendor.platformutils.is_android():
if self.sideband.getstate("android.power_restricted", allow_cache=False):
RNS.log("Android power restrictions detected, background connectivity will not work. Asking for permissions.", RNS.LOG_DEBUG)
def pm_job(dt):
Settings = autoclass("android.provider.Settings")
Intent = autoclass("android.content.Intent")
Uri = autoclass("android.net.Uri")
requestIntent = Intent()
requestIntent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
requestIntent.setData(Uri.parse("package:io.unsigned.sideband"))
mActivity.startActivity(requestIntent)
Clock.schedule_once(pm_job, 1.5)
if not self.root.ids.screen_manager.has_screen("messages_screen"): if not self.root.ids.screen_manager.has_screen("messages_screen"):
self.messages_screen = Builder.load_string(messages_screen_kv) self.messages_screen = Builder.load_string(messages_screen_kv)
self.messages_screen.app = self self.messages_screen.app = self
@ -418,22 +433,25 @@ class SidebandApp(MDApp):
def check_errors(dt): def check_errors(dt):
if self.sideband.getpersistent("startup.errors.rnode") != None: if self.sideband.getpersistent("startup.errors.rnode") != None:
description = self.sideband.getpersistent("startup.errors.rnode")["description"] if self.hw_error_dialog == None or (self.hw_error_dialog != None and not self.hw_error_dialog.is_open):
self.sideband.setpersistent("startup.errors.rnode", None) description = self.sideband.getpersistent("startup.errors.rnode")["description"]
yes_button = MDRectangleFlatButton( self.sideband.setpersistent("startup.errors.rnode", None)
text="OK", yes_button = MDRectangleFlatButton(
font_size=dp(18), text="OK",
) font_size=dp(18),
self.hw_error_dialog = MDDialog( )
title="Hardware Error", self.hw_error_dialog = MDDialog(
text="When starting a connected RNode, Reticulum reported the following error:\n\n[i]"+str(description)+"[/i]", title="Hardware Error",
buttons=[ yes_button ], text="When starting a connected RNode, Reticulum reported the following error:\n\n[i]"+str(description)+"[/i]",
# elevation=0, buttons=[ yes_button ],
) # elevation=0,
def dl_yes(s): )
self.hw_error_dialog.dismiss() def dl_yes(s):
yes_button.bind(on_release=dl_yes) self.hw_error_dialog.is_open = False
self.hw_error_dialog.open() self.hw_error_dialog.dismiss()
yes_button.bind(on_release=dl_yes)
self.hw_error_dialog.open()
self.hw_error_dialog.is_open = True
Clock.schedule_once(check_errors, 1.5) Clock.schedule_once(check_errors, 1.5)
@ -708,7 +726,18 @@ class SidebandApp(MDApp):
if check_permission(bt_permission_name): if check_permission(bt_permission_name):
RNS.log("Have bluetooth connect permissions", RNS.LOG_DEBUG) RNS.log("Have bluetooth connect permissions", RNS.LOG_DEBUG)
self.sideband.setpersistent("permissions.bluetooth", True)
if android_api_version > 30:
if check_permission("android.permission.BLUETOOTH_SCAN"):
RNS.log("Have bluetooth scan permissions", RNS.LOG_DEBUG)
self.sideband.setpersistent("permissions.bluetooth", True)
else:
RNS.log("Do not have bluetooth scan permissions")
self.sideband.setpersistent("permissions.bluetooth", False)
else:
self.sideband.setpersistent("permissions.bluetooth", True)
else: else:
RNS.log("Do not have bluetooth connect permissions") RNS.log("Do not have bluetooth connect permissions")
self.sideband.setpersistent("permissions.bluetooth", False) self.sideband.setpersistent("permissions.bluetooth", False)
@ -799,18 +828,36 @@ class SidebandApp(MDApp):
def request_bluetooth_permissions(self): def request_bluetooth_permissions(self):
if RNS.vendor.platformutils.get_platform() == "android": if RNS.vendor.platformutils.get_platform() == "android":
if not check_permission("android.permission.BLUETOOTH_CONNECT"): if not check_permission("android.permission.BLUETOOTH_CONNECT") or not check_permission("android.permission.BLUETOOTH_SCAN"):
RNS.log("Requesting bluetooth permission", RNS.LOG_DEBUG) RNS.log("Requesting Bluetooth permissions", RNS.LOG_DEBUG)
request_permissions(["android.permission.BLUETOOTH_CONNECT"]) request_permissions(["android.permission.BLUETOOTH_CONNECT", "android.permission.BLUETOOTH_SCAN"])
self.check_bluetooth_permissions() self.check_bluetooth_permissions()
def on_new_intent(self, intent): def on_new_intent(self, intent):
RNS.log("Received intent", RNS.LOG_DEBUG)
intent_action = intent.getAction() intent_action = intent.getAction()
action = None action = None
data = None data = None
RNS.log(f"Received intent: {intent_action}", RNS.LOG_DEBUG)
if intent_action == "android.intent.action.MAIN":
JString = autoclass('java.lang.String')
Intent = autoclass("android.content.Intent")
try:
extras = intent.getExtras()
if extras:
data = extras.getString("intent_action", "undefined")
if data.startswith("conversation."):
conv_hexhash = bytes.fromhex(data.replace("conversation.", ""))
def cb(dt):
self.open_conversation(conv_hexhash)
Clock.schedule_once(cb, 0.2)
except Exception as e:
RNS.log(f"Error while getting intent action data: {e}", RNS.LOG_ERROR)
RNS.trace_exception(e)
if intent_action == "android.intent.action.WEB_SEARCH": if intent_action == "android.intent.action.WEB_SEARCH":
SearchManager = autoclass('android.app.SearchManager') SearchManager = autoclass('android.app.SearchManager')
data = intent.getStringExtra(SearchManager.QUERY) data = intent.getStringExtra(SearchManager.QUERY)
@ -890,6 +937,29 @@ class SidebandApp(MDApp):
else: else:
self.service_last_available = time.time() self.service_last_available = time.time()
if RNS.vendor.platformutils.is_android():
rnode_errors = self.sideband.getpersistent("runtime.errors.rnode")
if rnode_errors != None:
if self.hw_error_dialog == None or (self.hw_error_dialog != None and not self.hw_error_dialog.is_open):
description = rnode_errors["description"]
self.sideband.setpersistent("runtime.errors.rnode", None)
yes_button = MDRectangleFlatButton(
text="OK",
font_size=dp(18),
)
self.hw_error_dialog = MDDialog(
title="Hardware Error",
text="While communicating with an RNode, Reticulum reported the following error:\n\n[i]"+str(description)+"[/i]",
buttons=[ yes_button ],
# elevation=0,
)
def dl_yes(s):
self.hw_error_dialog.dismiss()
self.hw_error_dialog.is_open = False
yes_button.bind(on_release=dl_yes)
self.hw_error_dialog.open()
self.hw_error_dialog.is_open = True
if self.root.ids.screen_manager.current == "messages_screen": if self.root.ids.screen_manager.current == "messages_screen":
self.messages_view.update() self.messages_view.update()
@ -1645,8 +1715,6 @@ class SidebandApp(MDApp):
ate_dialog.open() ate_dialog.open()
else: else:
self.sideband.config["map_storage_path"] = None
self.sideband.save_configuration()
if RNS.vendor.platformutils.get_platform() == "android": if RNS.vendor.platformutils.get_platform() == "android":
toast("No file access, check permissions!") toast("No file access, check permissions!")
else: else:
@ -1779,7 +1847,11 @@ class SidebandApp(MDApp):
return return
self.sideband.ui_started_recording() self.sideband.ui_started_recording()
self.audio_msg_mode = LXMF.AM_CODEC2_2400 if self.sideband.config["hq_ptt"]:
self.audio_msg_mode = LXMF.AM_OPUS_OGG
else:
self.audio_msg_mode = LXMF.AM_CODEC2_2400
self.message_attach_action(attach_type="audio", nodialog=True) self.message_attach_action(attach_type="audio", nodialog=True)
if self.rec_dialog == None: if self.rec_dialog == None:
self.message_init_rec_dialog() self.message_init_rec_dialog()
@ -2719,6 +2791,14 @@ class SidebandApp(MDApp):
self.sideband.save_configuration() self.sideband.save_configuration()
self.sideband._reticulum_log_debug(self.sideband.config["debug"]) self.sideband._reticulum_log_debug(self.sideband.config["debug"])
def save_block_predictive_text(sender=None, event=None):
self.sideband.config["block_predictive_text"] = self.settings_screen.ids.settings_block_predictive_text.active
self.sideband.save_configuration()
def save_hq_ptt(sender=None, event=None):
self.sideband.config["hq_ptt"] = self.settings_screen.ids.settings_hq_ptt.active
self.sideband.save_configuration()
def save_print_command(sender=None, event=None): def save_print_command(sender=None, event=None):
if not sender.focus: if not sender.focus:
in_cmd = self.settings_screen.ids.settings_print_command.text in_cmd = self.settings_screen.ids.settings_print_command.text
@ -2882,9 +2962,15 @@ class SidebandApp(MDApp):
self.settings_screen.ids.settings_lxm_limit_1mb.active = self.sideband.config["lxm_limit_1mb"] self.settings_screen.ids.settings_lxm_limit_1mb.active = self.sideband.config["lxm_limit_1mb"]
self.settings_screen.ids.settings_lxm_limit_1mb.bind(active=save_lxm_limit_1mb) self.settings_screen.ids.settings_lxm_limit_1mb.bind(active=save_lxm_limit_1mb)
self.settings_screen.ids.settings_hq_ptt.active = self.sideband.config["hq_ptt"]
self.settings_screen.ids.settings_hq_ptt.bind(active=save_hq_ptt)
self.settings_screen.ids.settings_debug.active = self.sideband.config["debug"] self.settings_screen.ids.settings_debug.active = self.sideband.config["debug"]
self.settings_screen.ids.settings_debug.bind(active=save_debug) self.settings_screen.ids.settings_debug.bind(active=save_debug)
self.settings_screen.ids.settings_block_predictive_text.active = self.sideband.config["block_predictive_text"]
self.settings_screen.ids.settings_block_predictive_text.bind(active=save_block_predictive_text)
self.settings_screen.ids.settings_lang_default.active = False self.settings_screen.ids.settings_lang_default.active = False
self.settings_screen.ids.settings_lang_chinese.active = False self.settings_screen.ids.settings_lang_chinese.active = False
self.settings_screen.ids.settings_lang_japanese.active = False self.settings_screen.ids.settings_lang_japanese.active = False
@ -4010,6 +4096,15 @@ class SidebandApp(MDApp):
self.sideband.save_configuration() self.sideband.save_configuration()
def hardware_rnode_ble_toggle_action(self, sender=None, event=None):
if sender.active:
self.sideband.config["hw_rnode_ble"] = True
self.request_bluetooth_permissions()
else:
self.sideband.config["hw_rnode_ble"] = False
self.sideband.save_configuration()
def hardware_rnode_framebuffer_toggle_action(self, sender=None, event=None): def hardware_rnode_framebuffer_toggle_action(self, sender=None, event=None):
if sender.active: if sender.active:
self.sideband.config["hw_rnode_enable_framebuffer"] = True self.sideband.config["hw_rnode_enable_framebuffer"] = True
@ -4115,6 +4210,7 @@ class SidebandApp(MDApp):
t_btd = "" t_btd = ""
self.hardware_rnode_screen.ids.hardware_rnode_bluetooth.active = self.sideband.config["hw_rnode_bluetooth"] self.hardware_rnode_screen.ids.hardware_rnode_bluetooth.active = self.sideband.config["hw_rnode_bluetooth"]
self.hardware_rnode_screen.ids.hardware_rnode_ble.active = self.sideband.config["hw_rnode_ble"]
self.hardware_rnode_screen.ids.hardware_rnode_framebuffer.active = self.sideband.config["hw_rnode_enable_framebuffer"] self.hardware_rnode_screen.ids.hardware_rnode_framebuffer.active = self.sideband.config["hw_rnode_enable_framebuffer"]
self.hardware_rnode_screen.ids.hardware_rnode_advanced_cfg.active = self.sideband.config["hw_rnode_advanced_cfg"] self.hardware_rnode_screen.ids.hardware_rnode_advanced_cfg.active = self.sideband.config["hw_rnode_advanced_cfg"]
@ -4181,6 +4277,7 @@ class SidebandApp(MDApp):
self.hardware_rnode_screen.ids.hardware_rnode_beaconinterval.bind(on_text_validate=save_connectivity) self.hardware_rnode_screen.ids.hardware_rnode_beaconinterval.bind(on_text_validate=save_connectivity)
self.hardware_rnode_screen.ids.hardware_rnode_beacondata.bind(on_text_validate=save_connectivity) self.hardware_rnode_screen.ids.hardware_rnode_beacondata.bind(on_text_validate=save_connectivity)
self.hardware_rnode_screen.ids.hardware_rnode_bluetooth.bind(active=self.hardware_rnode_bt_toggle_action) self.hardware_rnode_screen.ids.hardware_rnode_bluetooth.bind(active=self.hardware_rnode_bt_toggle_action)
self.hardware_rnode_screen.ids.hardware_rnode_ble.bind(active=self.hardware_rnode_ble_toggle_action)
self.hardware_rnode_screen.ids.hardware_rnode_framebuffer.bind(active=self.hardware_rnode_framebuffer_toggle_action) self.hardware_rnode_screen.ids.hardware_rnode_framebuffer.bind(active=self.hardware_rnode_framebuffer_toggle_action)
self.hardware_rnode_screen.ids.hardware_rnode_advanced_cfg.bind(active=self.hardware_rnode_advanced_cfg_toggle_action) self.hardware_rnode_screen.ids.hardware_rnode_advanced_cfg.bind(active=self.hardware_rnode_advanced_cfg_toggle_action)
@ -6060,37 +6157,35 @@ This short guide will give you a basic introduction to the concepts that underpi
This also means that openCom Companion operates differently than what you might be used to. It does not need a connection to a server on the Internet to function, and you do not have an account anywhere.""" This also means that openCom Companion operates differently than what you might be used to. It does not need a connection to a server on the Internet to function, and you do not have an account anywhere."""
guide_text3 = """ guide_text3 = """
[size=18dp][b]Operating Principles[/b][/size][size=5dp]\n \n[/size]When openCom Companion is started on your device for the first time, it randomly generates a set of cryptographic keys. These keys are then used to create an LXMF address for your use. Any other endpoint in [i]any[/i] Reticulum network will be able to send data to this address, as long as there is [i]some sort of physical connection[/i] between your device and the remote endpoint. You can also move around to other Reticulum networks with this address, even ones that were never connected to the network the address was created on, or that didn't exist when the address was created. The address is yours to keep and control for as long (or short) a time you need it, and you can always delete it and create a new one.""" [size=18dp][b]Operating Principles[/b][/size][size=5dp]\n \n[/size]When openCom Companion is started on your device for the first time, it randomly generates a 512-bit Reticulum Identity Key. This cryptographic key is then used to create an LXMF address for your use, and in turn to secure any communication to your address. Any other endpoint in [i]any[/i] Reticulum network will be able to send data to your address, as long as there is [i]some sort of physical connection[/i] between your device and the remote endpoint. You can also move around to other Reticulum networks with this address, even ones that were never connected to the network the address was created on, or that didn't exist when the address was created.\n\nYour LXMF address is yours to keep and control for as long (or short) a time you need it, and you can always delete it and create a new one. You identity keys and corresponding addresses are never registered on or controlled by any external servers or services, and will never leave your device, unless you manually export them for backup."""
guide_text4 = """ guide_text4 = """
[size=18dp][b]Becoming Reachable[/b][/size][size=5dp]\n \n[/size]To establish reachability for any Reticulum address on a network, an [i]announce[/i] must be sent. openCom Companion does not do this automatically by default, but can be configured to do so every time the program starts. To send an announce manually, press the [i]Announce[/i] button in the [i]Conversations[/i] section of the program. When you send an announce, you make your LXMF address reachable for real-time messaging to the entire network you are connected to. Even in very large networks, you can expect global reachability for your address to be established in under a minute. [size=18dp][b]Becoming Reachable[/b][/size][size=5dp]\n \n[/size]To establish reachability for any Reticulum destination on a network, an [i]announce[/i] must be sent. By default, openCom Companion will announce automatically when necessary, but if you want to stay silent, automatic announces can be disabled in [b]Preferences[/b].\n\nTo send an announce manually, press the [i]Announce[/i] button in the [i]Conversations[/i] section of the program. When you send an announce, you make your LXMF address reachable for real-time messaging to the entire network you are connected to. Even in very large networks, you can expect global reachability for your address to be established in under a minute."""
If you don't move to other places in the network, and keep connected through the same hubs or gateways, it is generally not necessary to send an announce more often than once every week. If you change your entry point to the network, you may want to send an announce, or you may just want to stay quiet.""" guide_text10 = """
[size=18dp][b]Getting Connected[/b][/size][size=5dp]\n \n[/size]If you already have Reticulum connectivity set up on the device you are running Sideband on, no further configuration should be necessary, and Sideband will simply use the available Reticulum connectivity.\n\nIf you are running Sideband on a computer, you can configure interfaces in the Reticulum configuration file ([b]~/.reticulum/config[/b] by default). If you are running Sideband on an Android device, you can configure various interface types in the [b]Connectivity[/b] section. By default, only an [i]AutoInterface[/i] is enabled, which will connect you automatically with any other local devices on the same WiFi and/or Ethernet networks. This may or may not include Reticulum Transport Nodes, which can route your traffic to wider networks.\n\nYou can enable any or all of the other available interface types to gain wider connectivity. For more specific information on interface types, configuration options, and how to effectively build your own Reticulum networks, see the [b]Reticulum Manual[b]."""
guide_text5 = """ guide_text5 = """
[size=18dp][b]Relax & Disconnect[/b][/size][size=5dp]\n \n[/size]If you are not connected to the network, it is still possible for other people to message you, as long as one or more [i]Propagation Nodes[/i] exist on the network. These nodes pick up and hold encrypted in-transit messages for offline users. Messages are always encrypted before leaving the originators device, and nobody else than the intended recipient can decrypt messages in transit. [size=18dp][b]Relax & Disconnect[/b][/size][size=5dp]\n \n[/size]If you are not connected to the network, it is still possible for other people to message you, as long as one or more [i]Propagation Nodes[/i] exist on the network. These nodes pick up and hold encrypted in-transit messages for offline users. Messages are always encrypted before leaving the originators device, and nobody else than the intended recipient can decrypt messages in transit.
The Propagation Nodes also distribute copies of messages between each other, such that even the failure of almost every node in the network will still allow users to sync their waiting messages. If all Propagation Nodes disappear or are destroyed, users can still communicate directly. Reticulum and LXMF will degrade gracefully all the way down to single users communicating directly via long-range data radios. Anyone can start up new propagation nodes and integrate them into existing networks without permission or coordination. Even a small and cheap device like a Rasperry Pi can handle messages for millions of users. LXMF networks are designed to be quite resilient, as long as there are people using them.""" The Propagation Nodes also distribute copies of messages between each other, such that even the failure of almost every node in the network will still allow users to sync their waiting messages. If all Propagation Nodes disappear or are destroyed, users can still communicate directly.\n\nReticulum and LXMF will degrade gracefully all the way down to single users communicating directly via long-range data radios. Anyone can start up new propagation nodes and integrate them into existing networks without permission or coordination. Even a small and cheap device like a Rasperry Pi can handle messages for millions of users. LXMF networks are designed to be quite resilient, as long as there are people using them."""
guide_text6 = """ guide_text6 = """
[size=18dp][b]Packets Find A Way[/b][/size][size=5dp]\n \n[/size]Connections in Reticulum networks can be wired or wireless, span many intermediary hops, run over fast links or ultra-low bandwidth radio, tunnel over the Invisible Internet (I2P), private networks, satellite connections, serial lines or anything else that Reticulum can carry data over. In most cases it will not be possible to know what path data takes in a Reticulum network, and no transmitted packets carries any identifying characteristics, apart from a destination address. There is no source addresses in Reticulum. As long as you do not reveal any connecting details between your person and your LXMF address, you can remain anonymous. Sending messages to others does not reveal [i]your[/i] address to anyone else than the intended recipient.""" [size=18dp][b]Packets Find A Way[/b][/size][size=5dp]\n \n[/size]Connections in Reticulum networks can be wired or wireless, span many intermediary hops, run over fast links or ultra-low bandwidth radio, tunnel over the Invisible Internet (I2P), private networks, satellite connections, serial lines or anything else that Reticulum can carry data over.\n\nIn most cases it will not be possible to know what path packets takes in a Reticulum network, and apart from a destination hash, no transmitted packets carries any identifying characteristics. In Reticulum, [i]there is no source addresses[/i].\n\nAs long as you do not reveal any connecting details between your person and your LXMF address, you can remain anonymous. Sending messages to others does not reveal [i]your[/i] address to anyone else than the intended recipient."""
guide_text7 = """ guide_text7 = """
[size=18dp][b]Be Yourself, Be Unknown, Stay Free[/b][/size][size=5dp]\n \n[/size]Even with the above characteristics in mind, you [b]must remember[/b] that LXMF and Reticulum is not a technology that can guarantee anonymising connections that are already de-anonymised! If you use openCom Companion to connect to TCP Reticulum hubs over the clear Internet, from a network that can be tied to your personal identity, an adversary may learn that you are generating LXMF traffic. If you want to avoid this, it is recommended to use I2P to connect to Reticulum hubs on the Internet. Or only connecting from within pure Reticulum networks, that take one or more hops to reach connections that span the Internet. This is a complex topic, with many more nuances than can be covered here. You are encouraged to ask on the various Reticulum discussion forums if you are in doubt. [size=18dp][b]Be Yourself, Be Unknown, Stay Free[/b][/size][size=5dp]\n \n[/size]Even with the above characteristics in mind, you [b]must remember[/b] that LXMF and Reticulum is not a technology that can guarantee anonymising connections that are already de-anonymised! If you use openCom Companion to connect to TCP Reticulum hubs over the clear Internet, from a network that can be tied to your personal identity, an adversary may learn that you are generating LXMF traffic. If you want to avoid this, it is recommended to use I2P to connect to Reticulum hubs on the Internet. Or only connecting from within pure Reticulum networks, that take one or more hops to reach connections that span the Internet. This is a complex topic, with many more nuances than can be covered here. You are encouraged to ask on the various Reticulum discussion forums if you are in doubt. If you use Reticulum and LXMF on hardware that does not carry any identifiers tied to you, it is possible to establish a completely free and identification-less communication system with Reticulum and LXMF clients."""
If you use Reticulum and LXMF on hardware that does not carry any identifiers tied to you, it is possible to establish a completely free and anonymous communication system with Reticulum and LXMF clients."""
guide_text8 = """""" guide_text8 = """"""
guide_text9 = """ guide_text9 = """
[size=18dp][b]Please Support The Upstream Project[/b][/size][size=5dp]\n \n[/size]It took Mark Qvist more than seven years to design and built the entire ecosystem of software and hardware that supports openCom Companion and the openCom line of RNodes. If this project is valuable to you, please go to [u][ref=link]https://unsigned.io/donate[/ref][/u] to support his project with a donation. Every donation directly makes the entire Reticulum project possible. [size=18dp][b]Please Support The Upstream Project[/b][/size][size=5dp]\n \n[/size]It took Mark Qvist more than seven years to design and built the entire ecosystem of software and hardware that supports openCom Companion and the openCom line of RNodes. If this project is valuable to you, please go to [u][ref=link]https://unsigned.io/donate[/ref][/u] to support his project with a donation. Every donation directly makes the entire Reticulum project possible. Thank you very much for using Free Communications Systems.
Thank you very much for using Free Communications Systems.
""" """
info1 = guide_text1 info1 = guide_text1
info2 = guide_text8 info2 = guide_text8
info3 = guide_text2 info3 = guide_text2
info4 = guide_text3 info4 = guide_text3
info10 = guide_text10
info5 = guide_text4 info5 = guide_text4
info6 = guide_text5 info6 = guide_text5
info7 = guide_text6 info7 = guide_text6
@ -6107,6 +6202,7 @@ Thank you very much for using Free Communications Systems.
info7 = "[color=#"+dark_theme_text_color+"]"+info7+"[/color]" info7 = "[color=#"+dark_theme_text_color+"]"+info7+"[/color]"
info8 = "[color=#"+dark_theme_text_color+"]"+info8+"[/color]" info8 = "[color=#"+dark_theme_text_color+"]"+info8+"[/color]"
info9 = "[color=#"+dark_theme_text_color+"]"+info9+"[/color]" info9 = "[color=#"+dark_theme_text_color+"]"+info9+"[/color]"
info10 = "[color=#"+dark_theme_text_color+"]"+info10+"[/color]"
self.guide_screen.ids.guide_info1.text = info1 self.guide_screen.ids.guide_info1.text = info1
self.guide_screen.ids.guide_info2.text = info2 self.guide_screen.ids.guide_info2.text = info2
self.guide_screen.ids.guide_info3.text = info3 self.guide_screen.ids.guide_info3.text = info3
@ -6116,6 +6212,7 @@ Thank you very much for using Free Communications Systems.
self.guide_screen.ids.guide_info7.text = info7 self.guide_screen.ids.guide_info7.text = info7
self.guide_screen.ids.guide_info8.text = info8 self.guide_screen.ids.guide_info8.text = info8
self.guide_screen.ids.guide_info9.text = info9 self.guide_screen.ids.guide_info9.text = info9
self.guide_screen.ids.guide_info10.text = info10
self.guide_screen.ids.guide_info9.bind(on_ref_press=link_exec) self.guide_screen.ids.guide_info9.bind(on_ref_press=link_exec)
self.guide_screen.ids.guide_scrollview.effect_cls = ScrollEffect self.guide_screen.ids.guide_scrollview.effect_cls = ScrollEffect

View File

@ -54,6 +54,7 @@
{{ args.extra_manifest_application_arguments }} {{ args.extra_manifest_application_arguments }}
android:theme="{{args.android_apptheme}}{% if not args.window %}.Fullscreen{% endif %}" android:theme="{{args.android_apptheme}}{% if not args.window %}.Fullscreen{% endif %}"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:requestLegacyExternalStorage="true"
> >
{% for l in args.android_used_libs %} {% for l in args.android_used_libs %}
<uses-library android:name="{{ l }}" /> <uses-library android:name="{{ l }}" />

View File

@ -144,11 +144,13 @@ public class PythonService extends Service implements Runnable {
Notification.Builder builder = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID); Notification.Builder builder = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID);
builder.setContentTitle("openCom Companion Active"); builder.setContentTitle("openCom Companion Active");
// builder.setContentText("Reticulum Active"); // builder.setContentText("Reticulum Active");
// builder.setContentTitle("Reticulum available");
// builder.setContentText("Reticulum available");
builder.setContentIntent(pIntent); builder.setContentIntent(pIntent);
// builder.setOngoing(true); // builder.setOngoing(true);
String files_path = context.getFilesDir().getPath(); String files_path = context.getFilesDir().getPath();
Bitmap icon_bitmap = BitmapFactory.decodeFile(files_path+"/app/assets/notification_icon.png"); Bitmap icon_bitmap = BitmapFactory.decodeFile(files_path+"/app/assets/notification_icon_black.png");
Icon service_icon = Icon.createWithBitmap(icon_bitmap); Icon service_icon = Icon.createWithBitmap(icon_bitmap);
// builder.setSmallIcon(context.getApplicationInfo().icon); // builder.setSmallIcon(context.getApplicationInfo().icon);
builder.setSmallIcon(service_icon); builder.setSmallIcon(service_icon);

View File

@ -38,10 +38,12 @@ if RNS.vendor.platformutils.get_platform() == "android":
AndroidString = autoclass('java.lang.String') AndroidString = autoclass('java.lang.String')
NotificationManager = autoclass('android.app.NotificationManager') NotificationManager = autoclass('android.app.NotificationManager')
Context = autoclass('android.content.Context') Context = autoclass('android.content.Context')
JString = autoclass('java.lang.String')
if android_api_version >= 26: if android_api_version >= 26:
NotificationBuilder = autoclass('android.app.Notification$Builder') NotificationBuilder = autoclass('android.app.Notification$Builder')
NotificationChannel = autoclass('android.app.NotificationChannel') NotificationChannel = autoclass('android.app.NotificationChannel')
RingtoneManager = autoclass('android.media.RingtoneManager')
from usb4a import usb from usb4a import usb
from usbserial4a import serial4a from usbserial4a import serial4a
@ -89,33 +91,39 @@ class SidebandService():
self.notification_channel = NotificationChannel(channel_id, channel_name, NotificationManager.IMPORTANCE_DEFAULT) self.notification_channel = NotificationChannel(channel_id, channel_name, NotificationManager.IMPORTANCE_DEFAULT)
self.notification_channel.enableVibration(True) self.notification_channel.enableVibration(True)
self.notification_channel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION), None)
self.notification_channel.setShowBadge(True) self.notification_channel.setShowBadge(True)
self.notification_service.createNotificationChannel(self.notification_channel) self.notification_service.createNotificationChannel(self.notification_channel)
notification = NotificationBuilder(self.app_context, channel_id) notification = NotificationBuilder(self.app_context, channel_id)
notification.setContentTitle(title) notification.setContentTitle(title)
notification.setContentText(AndroidString(content)) notification.setContentText(AndroidString(content))
notification.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
# if group != None: # if group != None:
# notification.setGroup(group_id) # notification.setGroup(group_id)
if not self.notification_small_icon: if not self.notification_small_icon:
path = self.sideband.notification_icon # path = self.sideband.notification_icon
path = self.sideband.notif_icon_black
bitmap = BitmapFactory.decodeFile(path) bitmap = BitmapFactory.decodeFile(path)
self.notification_small_icon = Icon.createWithBitmap(bitmap) self.notification_small_icon = Icon.createWithBitmap(bitmap)
notification.setSmallIcon(self.notification_small_icon) notification.setSmallIcon(self.notification_small_icon)
# notification.setLargeIcon(self.notification_small_icon)
# large_icon_path = self.sideband.icon # large_icon_path = self.sideband.icon
# bitmap_icon = BitmapFactory.decodeFile(large_icon_path) # bitmap_icon = BitmapFactory.decodeFile(large_icon_path)
# notification.setLargeIcon(bitmap_icon) # notification.setLargeIcon(bitmap_icon)
if not self.notification_intent: notification_intent = Intent(self.app_context, python_act)
notification_intent = Intent(self.app_context, python_act) notification_intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
notification_intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) notification_intent.setAction(Intent.ACTION_MAIN)
notification_intent.setAction(Intent.ACTION_MAIN) notification_intent.addCategory(Intent.CATEGORY_LAUNCHER)
notification_intent.addCategory(Intent.CATEGORY_LAUNCHER) if context_id != None:
self.notification_intent = PendingIntent.getActivity(self.app_context, 0, notification_intent, 0) cstr = f"conversation.{context_id}"
notification_intent.putExtra(JString("intent_action"), JString(cstr))
self.notification_intent = PendingIntent.getActivity(self.app_context, 0, notification_intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT)
notification.setContentIntent(self.notification_intent) notification.setContentIntent(self.notification_intent)
notification.setAutoCancel(True) notification.setAutoCancel(True)
@ -149,6 +157,18 @@ class SidebandService():
return True return True
def update_power_restrictions(self):
package_name = "io.unsigned.sideband"
if RNS.vendor.platformutils.is_android():
if android_api_version >= 28:
if self.power_manager != None:
if self.power_manager.isIgnoringBatteryOptimizations(package_name):
self.power_restricted = False
else:
self.power_restricted = True
self.sideband.setstate("android.power_restricted", self.power_restricted)
def android_location_callback(self, **kwargs): def android_location_callback(self, **kwargs):
self._raw_gps = kwargs self._raw_gps = kwargs
self._last_gps_update = time.time() self._last_gps_update = time.time()
@ -200,6 +220,7 @@ class SidebandService():
self.app_context = None self.app_context = None
self.wifi_manager = None self.wifi_manager = None
self.power_manager = None self.power_manager = None
self.power_restricted = False
self.usb_devices = [] self.usb_devices = []
self.usb_device_filter = SidebandService.usb_device_filter self.usb_device_filter = SidebandService.usb_device_filter
@ -235,6 +256,7 @@ class SidebandService():
self.sideband.start() self.sideband.start()
self.update_connectivity_type() self.update_connectivity_type()
self.update_power_restrictions()
if RNS.vendor.platformutils.is_android(): if RNS.vendor.platformutils.is_android():
RNS.log("Discovered USB devices: "+str(self.usb_devices), RNS.LOG_EXTREME) RNS.log("Discovered USB devices: "+str(self.usb_devices), RNS.LOG_EXTREME)
@ -354,7 +376,13 @@ class SidebandService():
else: else:
rs = "Interface Down" rs = "Interface Down"
stat += "[b]openCom device[/b]\n{rs}\n\n".format(rs=rs) bs = ""
bat_state = self.sideband.interface_rnode.get_battery_state_string()
bat_percent = self.sideband.interface_rnode.get_battery_percent()
if bat_state != "unknown":
bs = f"\nBattery at {bat_percent}%"
stat += f"[b]openCom device[/b]\n{rs}{bs}\n\n"
if self.sideband.interface_modem != None: if self.sideband.interface_modem != None:
if self.sideband.interface_modem.online: if self.sideband.interface_modem.online:

View File

@ -151,7 +151,9 @@ class SidebandCore():
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()
self.message_router = None
self.rpc_connection = None self.rpc_connection = None
self.rpc_lock = Lock()
self.service_stopped = False self.service_stopped = False
self.service_context = service_context self.service_context = service_context
self.owner_service = owner_service self.owner_service = owner_service
@ -198,6 +200,7 @@ class SidebandCore():
self.icon_32 = self.asset_dir+"/icon_32.png" self.icon_32 = self.asset_dir+"/icon_32.png"
self.icon_macos = self.asset_dir+"/icon_macos.png" self.icon_macos = self.asset_dir+"/icon_macos.png"
self.notification_icon = self.asset_dir+"/notification_icon.png" self.notification_icon = self.asset_dir+"/notification_icon.png"
self.notif_icon_black = self.asset_dir+"/notification_icon_black.png"
os.environ["TELEMETER_GEOID_PATH"] = os.path.join(self.asset_dir, "geoids") os.environ["TELEMETER_GEOID_PATH"] = os.path.join(self.asset_dir, "geoids")
@ -218,6 +221,7 @@ class SidebandCore():
self.last_lxmf_announce = 0 self.last_lxmf_announce = 0
self.last_if_change_announce = 0 self.last_if_change_announce = 0
self.interface_local_adding = False self.interface_local_adding = False
self.interface_rnode_adding = False
self.next_auto_announce = time.time() + 60*(random.random()*(SidebandCore.AUTO_ANNOUNCE_RANDOM_MAX-SidebandCore.AUTO_ANNOUNCE_RANDOM_MIN)+SidebandCore.AUTO_ANNOUNCE_RANDOM_MIN) self.next_auto_announce = time.time() + 60*(random.random()*(SidebandCore.AUTO_ANNOUNCE_RANDOM_MAX-SidebandCore.AUTO_ANNOUNCE_RANDOM_MIN)+SidebandCore.AUTO_ANNOUNCE_RANDOM_MIN)
try: try:
@ -482,6 +486,7 @@ class SidebandCore():
self.config["hw_rnode_beacondata"] = None self.config["hw_rnode_beacondata"] = None
self.config["hw_rnode_bt_device"] = None self.config["hw_rnode_bt_device"] = None
self.config["hw_rnode_bluetooth"] = False self.config["hw_rnode_bluetooth"] = False
self.config["hw_rnode_ble"] = False
self.config["hw_modem_baudrate"] = 57600 self.config["hw_modem_baudrate"] = 57600
self.config["hw_modem_databits"] = 8 self.config["hw_modem_databits"] = 8
self.config["hw_modem_stopbits"] = 1 self.config["hw_modem_stopbits"] = 1
@ -575,11 +580,15 @@ class SidebandCore():
self.config["display_style_in_contact_list"] = False self.config["display_style_in_contact_list"] = False
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:
self.config["hq_ptt"] = 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
if not "allow_predictive_text" in self.config: if not "allow_predictive_text" in self.config:
self.config["allow_predictive_text"] = False self.config["allow_predictive_text"] = False
if not "block_predictive_text" in self.config:
self.config["block_predictive_text"] = False
if not "connect_transport" in self.config: if not "connect_transport" in self.config:
self.config["connect_transport"] = False self.config["connect_transport"] = False
@ -655,6 +664,8 @@ class SidebandCore():
self.config["hw_rnode_beacondata"] = None self.config["hw_rnode_beacondata"] = None
if not "hw_rnode_bluetooth" in self.config: if not "hw_rnode_bluetooth" in self.config:
self.config["hw_rnode_bluetooth"] = False self.config["hw_rnode_bluetooth"] = False
if not "hw_rnode_ble" in self.config:
self.config["hw_rnode_ble"] = False
if not "hw_rnode_enable_framebuffer" in self.config: if not "hw_rnode_enable_framebuffer" in self.config:
self.config["hw_rnode_enable_framebuffer"] = False self.config["hw_rnode_enable_framebuffer"] = False
if not "hw_rnode_bt_device" in self.config: if not "hw_rnode_bt_device" in self.config:
@ -820,6 +831,7 @@ class SidebandCore():
self.update_ignore_invalid_stamps() self.update_ignore_invalid_stamps()
except Exception as e: except Exception as e:
RNS.log("Error while reloading configuration: "+str(e), RNS.LOG_ERROR) RNS.log("Error while reloading configuration: "+str(e), RNS.LOG_ERROR)
RNS.trace_exception(e)
def __save_config(self): def __save_config(self):
RNS.log("Saving openCom Companion configuration...", RNS.LOG_DEBUG) RNS.log("Saving openCom Companion configuration...", RNS.LOG_DEBUG)
@ -925,12 +937,13 @@ class SidebandCore():
RNS.log("No active propagation node configured") RNS.log("No active propagation node configured")
else: else:
try: try:
self.active_propagation_node = dest if self.message_router:
self.config["last_lxmf_propagation_node"] = dest self.active_propagation_node = dest
self.message_router.set_outbound_propagation_node(dest) self.config["last_lxmf_propagation_node"] = dest
self.message_router.set_outbound_propagation_node(dest)
RNS.log("Active propagation node set to: "+RNS.prettyhexrep(dest)) RNS.log("Active propagation node set to: "+RNS.prettyhexrep(dest))
self.__save_config() self.__save_config()
except Exception as e: except Exception as e:
RNS.log("Error while setting LXMF propagation node: "+str(e), RNS.LOG_ERROR) RNS.log("Error while setting LXMF propagation node: "+str(e), RNS.LOG_ERROR)
@ -1263,7 +1276,8 @@ class SidebandCore():
self.message_router.handle_outbound(message) self.message_router.handle_outbound(message)
else: else:
if message.state == LXMF.LXMessage.DELIVERED: if message.state == LXMF.LXMessage.DELIVERED:
self.setpersistent(f"telemetry.{RNS.hexrep(message.destination_hash, delimit=False)}.last_request_success_timebase", message.request_timebase) delivery_timebase = int(time.time())
self.setpersistent(f"telemetry.{RNS.hexrep(message.destination_hash, delimit=False)}.last_request_success_timebase", delivery_timebase)
self.setstate(f"telemetry.{RNS.hexrep(message.destination_hash, delimit=False)}.request_sending", False) self.setstate(f"telemetry.{RNS.hexrep(message.destination_hash, delimit=False)}.request_sending", False)
if message.destination_hash == self.config["telemetry_collector"]: if message.destination_hash == self.config["telemetry_collector"]:
self.pending_telemetry_request = False self.pending_telemetry_request = False
@ -1278,12 +1292,7 @@ class SidebandCore():
else: else:
if self.is_client: if self.is_client:
try: try:
if self.rpc_connection == None: return self.service_rpc_request({"request_latest_telemetry": {"from_addr": from_addr}})
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: except Exception as e:
RNS.log("Error while requesting latest telemetry over RPC: "+str(e), RNS.LOG_DEBUG) RNS.log("Error while requesting latest telemetry over RPC: "+str(e), RNS.LOG_DEBUG)
@ -1357,16 +1366,11 @@ class SidebandCore():
else: else:
if self.is_client: if self.is_client:
try: try:
if self.rpc_connection == None: return self.service_rpc_request({"send_latest_telemetry": {
self.rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key)
self.rpc_connection.send({"send_latest_telemetry": {
"to_addr": to_addr, "to_addr": to_addr,
"stream": stream, "stream": stream,
"is_authorized_telemetry_request": is_authorized_telemetry_request} "is_authorized_telemetry_request": is_authorized_telemetry_request}
}) })
response = self.rpc_connection.recv()
return response
except Exception as e: except Exception as e:
RNS.log("Error while sending latest telemetry over RPC: "+str(e), RNS.LOG_DEBUG) RNS.log("Error while sending latest telemetry over RPC: "+str(e), RNS.LOG_DEBUG)
@ -1545,7 +1549,7 @@ class SidebandCore():
RNS.log("Service heartbeat did not recover after retry", RNS.LOG_DEBUG) RNS.log("Service heartbeat did not recover after retry", RNS.LOG_DEBUG)
return False return False
else: else:
RNS.log("Service heartbeat recovered at"+str(time), RNS.LOG_DEBUG) RNS.log("Service heartbeat recovered at"+str(now), RNS.LOG_DEBUG)
return True return True
else: else:
return True return True
@ -1575,11 +1579,7 @@ class SidebandCore():
return True return True
else: else:
def set(): def set():
if self.rpc_connection == None: return self.service_rpc_request({"setstate": (prop, val)})
self.rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key)
self.rpc_connection.send({"setstate": (prop, val)})
response = self.rpc_connection.recv()
return response
try: try:
set() set()
@ -1601,11 +1601,7 @@ class SidebandCore():
return True return True
else: else:
try: try:
if self.rpc_connection == None: return self.service_rpc_request({"latest_telemetry": (latest_telemetry, latest_packed_telemetry)})
self.rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key)
self.rpc_connection.send({"latest_telemetry": (latest_telemetry, latest_packed_telemetry)})
response = self.rpc_connection.recv()
return response
except Exception as e: except Exception as e:
RNS.log("Error while setting telemetry over RPC: "+str(e), RNS.LOG_DEBUG) RNS.log("Error while setting telemetry over RPC: "+str(e), RNS.LOG_DEBUG)
return False return False
@ -1622,11 +1618,7 @@ class SidebandCore():
return True return True
else: else:
try: try:
if self.rpc_connection == None: return self.service_rpc_request({"set_debug": debug})
self.rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key)
self.rpc_connection.send({"set_debug": debug})
response = self.rpc_connection.recv()
return response
except Exception as e: except Exception as e:
RNS.log("Error while setting log level over RPC: "+str(e), RNS.LOG_DEBUG) RNS.log("Error while setting log level over RPC: "+str(e), RNS.LOG_DEBUG)
return False return False
@ -1640,15 +1632,26 @@ class SidebandCore():
return True return True
else: else:
try: try:
if self.rpc_connection == None: return self.service_rpc_request({"set_ui_recording": recording})
self.rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key)
self.rpc_connection.send({"set_ui_recording": recording})
response = self.rpc_connection.recv()
return response
except Exception as e: except Exception as e:
RNS.log("Error while setting UI recording status over RPC: "+str(e), RNS.LOG_DEBUG) RNS.log("Error while setting UI recording status over RPC: "+str(e), RNS.LOG_DEBUG)
return False return False
def service_rpc_request(self, request):
# RNS.log("Running service RPC call: "+str(request), RNS.LOG_DEBUG)
try:
with self.rpc_lock:
if self.rpc_connection == None:
self.rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key)
self.rpc_connection.send(request)
response = self.rpc_connection.recv()
return response
except Exception as e:
if not type(e) == ConnectionRefusedError:
RNS.log(f"An error occurred while executing the service RPC request: {request}", RNS.LOG_ERROR)
RNS.log(f"The contained exception was: {e}", RNS.LOG_ERROR)
def getstate(self, prop, allow_cache=False): def getstate(self, prop, allow_cache=False):
with self.state_lock: with self.state_lock:
if not self.service_stopped: if not self.service_stopped:
@ -1666,11 +1669,7 @@ class SidebandCore():
return None return None
else: else:
try: try:
if self.rpc_connection == None: return self.service_rpc_request({"getstate": prop})
self.rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key)
self.rpc_connection.send({"getstate": prop})
response = self.rpc_connection.recv()
return response
except Exception as e: except Exception as e:
RNS.log("Error while retrieving state "+str(prop)+" over RPC: "+str(e), RNS.LOG_DEBUG) RNS.log("Error while retrieving state "+str(prop)+" over RPC: "+str(e), RNS.LOG_DEBUG)
@ -1705,11 +1704,7 @@ class SidebandCore():
return self._get_plugins_info() return self._get_plugins_info()
else: else:
try: try:
if self.rpc_connection == None: return self.service_rpc_request({"get_plugins_info": True})
self.rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key)
self.rpc_connection.send({"get_plugins_info": True})
response = self.rpc_connection.recv()
return response
except Exception as e: except Exception as e:
ed = "Error while getting plugins info over RPC: "+str(e) ed = "Error while getting plugins info over RPC: "+str(e)
RNS.log(ed, RNS.LOG_DEBUG) RNS.log(ed, RNS.LOG_DEBUG)
@ -1744,11 +1739,7 @@ class SidebandCore():
return self._get_destination_establishment_rate(destination_hash) return self._get_destination_establishment_rate(destination_hash)
else: else:
try: try:
if self.rpc_connection == None: return self.service_rpc_request({"get_destination_establishment_rate": destination_hash})
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: except Exception as e:
ed = "Error while getting destination link etablishment rate over RPC: "+str(e) ed = "Error while getting destination link etablishment rate over RPC: "+str(e)
RNS.log(ed, RNS.LOG_DEBUG) RNS.log(ed, RNS.LOG_DEBUG)
@ -1832,11 +1823,15 @@ class SidebandCore():
elif "get_lxm_progress" in call: elif "get_lxm_progress" in call:
args = call["get_lxm_progress"] args = call["get_lxm_progress"]
connection.send(self.get_lxm_progress(args["lxm_hash"])) connection.send(self.get_lxm_progress(args["lxm_hash"]))
elif "get_lxm_stamp_cost" in call:
args = call["get_lxm_stamp_cost"]
connection.send(self.get_lxm_stamp_cost(args["lxm_hash"]))
else: else:
connection.send(None) connection.send(None)
except Exception as e: except Exception as e:
RNS.log("Error on client RPC connection: "+str(e), RNS.LOG_ERROR) RNS.log("Error on client RPC connection: "+str(e), RNS.LOG_ERROR)
RNS.trace_exception(e)
try: try:
connection.close() connection.close()
except: except:
@ -3185,6 +3180,25 @@ class SidebandCore():
self.lxmf_announce(attached_interface=self.interface_local) self.lxmf_announce(attached_interface=self.interface_local)
threading.Thread(target=job, daemon=True).start() threading.Thread(target=job, daemon=True).start()
if hasattr(self, "interface_rnode") and self.interface_rnode != None:
if len(self.interface_rnode.hw_errors) > 0:
self.setpersistent("runtime.errors.rnode", self.interface_rnode.hw_errors[0])
self.interface_rnode.hw_errors = []
# if not self.interface_rnode_adding:
# RNS.log("Hardware error on RNodeInterface, scheduling re-init", RNS.LOG_DEBUG)
# if self.interface_rnode in RNS.Transport.interfaces:
# RNS.Transport.interfaces.remove(self.interface_rnode)
# del self.interface_rnode
# self.interface_rnode = None
# self.interface_rnode_adding = True
# def job():
# self.__add_rnodeinterface(delay=5)
# if self.config["start_announce"] == True:
# time.sleep(12)
# self.lxmf_announce(attached_interface=self.interface_rnode)
# threading.Thread(target=job, daemon=True).start()
if (now - last_multicast_lock_check > 120): if (now - last_multicast_lock_check > 120):
RNS.log("Checking multicast and wake locks", RNS.LOG_DEBUG) RNS.log("Checking multicast and wake locks", RNS.LOG_DEBUG)
self.owner_service.take_locks() self.owner_service.take_locks()
@ -3503,6 +3517,167 @@ class SidebandCore():
self.interface_local = None self.interface_local = None
self.interface_local_adding = False self.interface_local_adding = False
def __add_rnodeinterface(self, delay=None):
self.interface_rnode_adding = True
if delay:
time.sleep(delay)
try:
RNS.log("Adding RNode Interface...", RNS.LOG_DEBUG)
target_device = None
if len(self.owner_app.usb_devices) > 0:
# TODO: Add more intelligent selection here
target_device = self.owner_app.usb_devices[0]
# if target_device or self.config["hw_rnode_bluetooth"]:
if target_device != None:
target_port = target_device["port"]
else:
target_port = None
bt_device_name = None
ble_dispatcher = None
rnode_allow_bluetooth = False
if self.getpersistent("permissions.bluetooth"):
if self.config["hw_rnode_bluetooth"]:
RNS.log("Allowing RNode bluetooth", RNS.LOG_DEBUG)
rnode_allow_bluetooth = True
ble_dispatcher = RNS.Interfaces.Android.RNodeMultiInterface.AndroidBLEDispatcher()
if self.config["hw_rnode_bt_device"] != None:
bt_device_name = self.config["hw_rnode_bt_device"]
else:
RNS.log("Disallowing RNode bluetooth since config is disabled", RNS.LOG_DEBUG)
rnode_allow_bluetooth = False
else:
RNS.log("Disallowing RNode bluetooth due to missing permission", RNS.LOG_DEBUG)
rnode_allow_bluetooth = False
if self.config["connect_rnode_ifac_netname"] == "":
ifac_netname = None
else:
ifac_netname = self.config["connect_rnode_ifac_netname"]
if self.config["connect_rnode_ifac_passphrase"] == "":
ifac_netkey = None
else:
ifac_netkey = self.config["connect_rnode_ifac_passphrase"]
if self.config["hw_rnode_atl_short"] == "":
atl_short = None
else:
atl_short = self.config["hw_rnode_atl_short"]
if self.config["hw_rnode_atl_long"] == "":
atl_long = None
else:
atl_long = self.config["hw_rnode_atl_long"]
if self.config["hw_rnode_secondary_modem"]:
if self.config["hw_rnode_sec_atl_short"] == "":
sec_atl_short = None
else:
sec_atl_short = self.config["hw_rnode_sec_atl_short"]
if self.config["hw_rnode_sec_atl_long"] == "":
sec_atl_long = None
else:
sec_atl_long = self.config["hw_rnode_sec_atl_long"]
subint_config = [[0]*11 for i in range(2)]
# Primary modem
subint_config[0][0] = "Primary modem" # Name of interface
subint_config[0][1] = 0 # Virtual port
subint_config[0][2] = self.config["hw_rnode_frequency"]
subint_config[0][3] = self.config["hw_rnode_bandwidth"]
subint_config[0][4] = self.config["hw_rnode_tx_power"]
subint_config[0][5] = self.config["hw_rnode_spreading_factor"]
subint_config[0][6] = self.config["hw_rnode_coding_rate"]
subint_config[0][7] = False # flow control hardcoded to false for now
subint_config[0][8] = atl_short
subint_config[0][9] = atl_long
subint_config[0][10] = True # outgoing
# Secondary modem
subint_config[1][0] = "Secondary modem" # Name of interface
subint_config[1][1] = 1 # Virtual port
subint_config[1][2] = self.config["hw_rnode_sec_frequency"]
subint_config[1][3] = self.config["hw_rnode_sec_bandwidth"]
subint_config[1][4] = self.config["hw_rnode_sec_tx_power"]
subint_config[1][5] = self.config["hw_rnode_sec_spreading_factor"]
subint_config[1][6] = self.config["hw_rnode_coding_rate"]
subint_config[1][7] = False # flow control hardcoded to false for now
subint_config[0][8] = sec_atl_short
subint_config[0][9] = sec_atl_long
subint_config[1][10] = True # outgoing
rnodeinterface = RNS.Interfaces.Android.RNodeMultiInterface.RNodeMultiInterface(
RNS.Transport,
"RNodeInterface",
target_port,
subint_config,
ble_dispatcher = ble_dispatcher,
allow_bluetooth = rnode_allow_bluetooth,
target_device_name = bt_device_name,
)
rnodeinterface.start()
else:
rnodeinterface = RNS.Interfaces.Android.RNodeInterface.RNodeInterface(
RNS.Transport,
"RNodeInterface",
target_port,
frequency = self.config["hw_rnode_frequency"],
bandwidth = self.config["hw_rnode_bandwidth"],
txpower = self.config["hw_rnode_tx_power"],
sf = self.config["hw_rnode_spreading_factor"],
cr = self.config["hw_rnode_coding_rate"],
flow_control = None,
id_interval = self.config["hw_rnode_beaconinterval"],
id_callsign = self.config["hw_rnode_beacondata"],
allow_bluetooth = rnode_allow_bluetooth,
target_device_name = bt_device_name,
st_alock = atl_short,
lt_alock = atl_long,
)
rnodeinterface.OUT = True
if RNS.Reticulum.transport_enabled():
if_mode = Interface.Interface.MODE_FULL
if self.config["connect_ifmode_rnode"] == "gateway":
if_mode = Interface.Interface.MODE_GATEWAY
elif self.config["connect_ifmode_rnode"] == "access point":
if_mode = Interface.Interface.MODE_ACCESS_POINT
elif self.config["connect_ifmode_rnode"] == "roaming":
if_mode = Interface.Interface.MODE_ROAMING
elif self.config["connect_ifmode_rnode"] == "boundary":
if_mode = Interface.Interface.MODE_BOUNDARY
else:
if_mode = None
self.reticulum._add_interface(rnodeinterface, mode = if_mode, ifac_netname = ifac_netname, ifac_netkey = ifac_netkey)
self.interface_rnode = rnodeinterface
if rnodeinterface != None:
if len(rnodeinterface.hw_errors) > 0:
self.setpersistent("startup.errors.rnode", rnodeinterface.hw_errors[0])
if self.config["hw_rnode_enable_framebuffer"] == True:
if self.interface_rnode.online:
self.interface_rnode.display_image(sideband_fb_data)
self.interface_rnode.enable_external_framebuffer()
else:
self.interface_rnode.last_imagedata = sideband_fb_data
else:
if self.interface_rnode.online:
self.interface_rnode.disable_external_framebuffer()
except Exception as e:
RNS.log("Error while adding RNode Interface. The contained exception was: "+str(e))
self.interface_rnode = None
def _reticulum_log_debug(self, debug=False): def _reticulum_log_debug(self, debug=False):
self.log_verbose = debug self.log_verbose = debug
if self.log_verbose: if self.log_verbose:
@ -3663,161 +3838,7 @@ class SidebandCore():
if self.config["connect_rnode"]: if self.config["connect_rnode"]:
self.setstate("init.loadingstate", "Starting RNode") self.setstate("init.loadingstate", "Starting RNode")
try: self.__add_rnodeinterface()
RNS.log("Adding RNode Interface...", RNS.LOG_DEBUG)
target_device = None
if len(self.owner_app.usb_devices) > 0:
# TODO: Add more intelligent selection here
target_device = self.owner_app.usb_devices[0]
# if target_device or self.config["hw_rnode_bluetooth"]:
if target_device != None:
target_port = target_device["port"]
else:
target_port = None
bt_device_name = None
ble_dispatcher = None
rnode_allow_bluetooth = False
if self.getpersistent("permissions.bluetooth"):
if self.config["hw_rnode_bluetooth"]:
RNS.log("Allowing RNode bluetooth", RNS.LOG_DEBUG)
rnode_allow_bluetooth = True
ble_dispatcher = RNS.Interfaces.Android.RNodeMultiInterface.AndroidBLEDispatcher()
if self.config["hw_rnode_bt_device"] != None:
bt_device_name = self.config["hw_rnode_bt_device"]
else:
RNS.log("Disallowing RNode bluetooth since config is disabled", RNS.LOG_DEBUG)
rnode_allow_bluetooth = False
else:
RNS.log("Disallowing RNode bluetooth due to missing permission", RNS.LOG_DEBUG)
rnode_allow_bluetooth = False
if self.config["connect_rnode_ifac_netname"] == "":
ifac_netname = None
else:
ifac_netname = self.config["connect_rnode_ifac_netname"]
if self.config["connect_rnode_ifac_passphrase"] == "":
ifac_netkey = None
else:
ifac_netkey = self.config["connect_rnode_ifac_passphrase"]
if self.config["hw_rnode_atl_short"] == "":
atl_short = None
else:
atl_short = self.config["hw_rnode_atl_short"]
if self.config["hw_rnode_atl_long"] == "":
atl_long = None
else:
atl_long = self.config["hw_rnode_atl_long"]
if self.config["hw_rnode_secondary_modem"]:
if self.config["hw_rnode_sec_atl_short"] == "":
sec_atl_short = None
else:
sec_atl_short = self.config["hw_rnode_sec_atl_short"]
if self.config["hw_rnode_sec_atl_long"] == "":
sec_atl_long = None
else:
sec_atl_long = self.config["hw_rnode_sec_atl_long"]
subint_config = [[0]*11 for i in range(2)]
# Primary modem
subint_config[0][0] = "Primary modem" # Name of interface
subint_config[0][1] = 0 # Virtual port
subint_config[0][2] = self.config["hw_rnode_frequency"]
subint_config[0][3] = self.config["hw_rnode_bandwidth"]
subint_config[0][4] = self.config["hw_rnode_tx_power"]
subint_config[0][5] = self.config["hw_rnode_spreading_factor"]
subint_config[0][6] = self.config["hw_rnode_coding_rate"]
subint_config[0][7] = False # flow control hardcoded to false for now
subint_config[0][8] = atl_short
subint_config[0][9] = atl_long
subint_config[0][10] = True # outgoing
# Secondary modem
subint_config[1][0] = "Secondary modem" # Name of interface
subint_config[1][1] = 1 # Virtual port
subint_config[1][2] = self.config["hw_rnode_sec_frequency"]
subint_config[1][3] = self.config["hw_rnode_sec_bandwidth"]
subint_config[1][4] = self.config["hw_rnode_sec_tx_power"]
subint_config[1][5] = self.config["hw_rnode_sec_spreading_factor"]
subint_config[1][6] = self.config["hw_rnode_coding_rate"]
subint_config[1][7] = False # flow control hardcoded to false for now
subint_config[0][8] = sec_atl_short
subint_config[0][9] = sec_atl_long
subint_config[1][10] = True # outgoing
rnodeinterface = RNS.Interfaces.Android.RNodeMultiInterface.RNodeMultiInterface(
RNS.Transport,
"RNodeInterface",
target_port,
subint_config,
ble_dispatcher = ble_dispatcher,
allow_bluetooth = rnode_allow_bluetooth,
target_device_name = bt_device_name,
)
rnodeinterface.start()
else:
rnodeinterface = RNS.Interfaces.Android.RNodeInterface.RNodeInterface(
RNS.Transport,
"RNodeInterface",
target_port,
frequency = self.config["hw_rnode_frequency"],
bandwidth = self.config["hw_rnode_bandwidth"],
txpower = self.config["hw_rnode_tx_power"],
sf = self.config["hw_rnode_spreading_factor"],
cr = self.config["hw_rnode_coding_rate"],
flow_control = None,
id_interval = self.config["hw_rnode_beaconinterval"],
id_callsign = self.config["hw_rnode_beacondata"],
allow_bluetooth = rnode_allow_bluetooth,
target_device_name = bt_device_name,
st_alock = atl_short,
lt_alock = atl_long,
)
rnodeinterface.OUT = True
if RNS.Reticulum.transport_enabled():
if_mode = Interface.Interface.MODE_FULL
if self.config["connect_ifmode_rnode"] == "gateway":
if_mode = Interface.Interface.MODE_GATEWAY
elif self.config["connect_ifmode_rnode"] == "access point":
if_mode = Interface.Interface.MODE_ACCESS_POINT
elif self.config["connect_ifmode_rnode"] == "roaming":
if_mode = Interface.Interface.MODE_ROAMING
elif self.config["connect_ifmode_rnode"] == "boundary":
if_mode = Interface.Interface.MODE_BOUNDARY
else:
if_mode = None
self.reticulum._add_interface(rnodeinterface, mode = if_mode, ifac_netname = ifac_netname, ifac_netkey = ifac_netkey)
self.interface_rnode = rnodeinterface
if rnodeinterface != None:
if len(rnodeinterface.hw_errors) > 0:
self.setpersistent("startup.errors.rnode", rnodeinterface.hw_errors[0])
if self.config["hw_rnode_enable_framebuffer"] == True:
if self.interface_rnode.online:
self.interface_rnode.display_image(sideband_fb_data)
self.interface_rnode.enable_external_framebuffer()
else:
self.interface_rnode.last_imagedata = sideband_fb_data
else:
if self.interface_rnode.online:
self.interface_rnode.disable_external_framebuffer()
except Exception as e:
RNS.log("Error while adding RNode Interface. The contained exception was: "+str(e))
self.interface_rnode = None
elif self.config["connect_serial"]: elif self.config["connect_serial"]:
self.setstate("init.loadingstate", "Starting Serial Interface") self.setstate("init.loadingstate", "Starting Serial Interface")
@ -4079,12 +4100,7 @@ class SidebandCore():
else: else:
if self.is_client: if self.is_client:
try: try:
if self.rpc_connection == None: return self.service_rpc_request({"get_lxm_progress": {"lxm_hash": lxm_hash}})
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: except Exception as e:
RNS.log("Error while getting LXM progress over RPC: "+str(e), RNS.LOG_DEBUG) RNS.log("Error while getting LXM progress over RPC: "+str(e), RNS.LOG_DEBUG)
@ -4105,12 +4121,37 @@ class SidebandCore():
RNS.log("An error occurred while getting message transfer progress: "+str(e), RNS.LOG_ERROR) RNS.log("An error occurred while getting message transfer progress: "+str(e), RNS.LOG_ERROR)
return None return None
def _service_get_lxm_stamp_cost(self, lxm_hash):
if not RNS.vendor.platformutils.is_android():
return False
else:
if self.is_client:
try:
return self.service_rpc_request({"get_lxm_stamp_cost": { "lxm_hash": lxm_hash } })
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 get_lxm_stamp_cost(self, lxm_hash): def get_lxm_stamp_cost(self, lxm_hash):
try: if self.allow_service_dispatch and self.is_client:
return self.message_router.get_outbound_lxm_stamp_cost(lxm_hash) try:
except Exception as e: return self._service_get_lxm_stamp_cost(lxm_hash)
RNS.log("An error occurred while getting message transfer stamp cost: "+str(e), RNS.LOG_ERROR)
return None except Exception as e:
RNS.log("Error while getting message transfer stamp cost: "+str(e), RNS.LOG_ERROR)
RNS.trace_exception(e)
return False
else:
try:
return self.message_router.get_outbound_lxm_stamp_cost(lxm_hash)
except Exception as e:
RNS.log("An error occurred while getting message transfer stamp cost: "+str(e), RNS.LOG_ERROR)
return None
def _service_send_message(self, content, destination_hash, propagation, skip_fields=False, no_display=False, attachment = None, image = None, audio = None): 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(): if not RNS.vendor.platformutils.is_android():
@ -4118,10 +4159,7 @@ class SidebandCore():
else: else:
if self.is_client: if self.is_client:
try: try:
if self.rpc_connection == None: return self.service_rpc_request({"send_message": {
self.rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key)
self.rpc_connection.send({"send_message": {
"content": content, "content": content,
"destination_hash": destination_hash, "destination_hash": destination_hash,
"propagation": propagation, "propagation": propagation,
@ -4131,8 +4169,6 @@ class SidebandCore():
"image": image, "image": image,
"audio": audio} "audio": audio}
}) })
response = self.rpc_connection.recv()
return response
except Exception as e: except Exception as e:
RNS.log("Error while sending message over RPC: "+str(e), RNS.LOG_DEBUG) RNS.log("Error while sending message over RPC: "+str(e), RNS.LOG_DEBUG)
@ -4147,16 +4183,11 @@ class SidebandCore():
else: else:
if self.is_client: if self.is_client:
try: try:
if self.rpc_connection == None: return self.service_rpc_request({"send_command": {
self.rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key)
self.rpc_connection.send({"send_command": {
"content": content, "content": content,
"destination_hash": destination_hash, "destination_hash": destination_hash,
"propagation": propagation} "propagation": propagation}
}) })
response = self.rpc_connection.recv()
return response
except Exception as e: except Exception as e:
RNS.log("Error while sending command over RPC: "+str(e), RNS.LOG_DEBUG) RNS.log("Error while sending command over RPC: "+str(e), RNS.LOG_DEBUG)
@ -4431,6 +4462,7 @@ class SidebandCore():
self.notify(title=self.peer_display_name(context_dest), content=notification_content, group="LXM", context_id=RNS.hexrep(context_dest, delimit=False)) self.notify(title=self.peer_display_name(context_dest), content=notification_content, group="LXM", context_id=RNS.hexrep(context_dest, delimit=False))
except Exception as e: except Exception as e:
RNS.log("Could not post notification for received message: "+str(e), RNS.LOG_ERROR) RNS.log("Could not post notification for received message: "+str(e), RNS.LOG_ERROR)
RNS.trace_exception(e)
def ptt_playback(self, message): def ptt_playback(self, message):
ptt_timeout = 60 ptt_timeout = 60
@ -4529,7 +4561,7 @@ class SidebandCore():
thread.start() thread.start()
self.setstate("core.started", True) self.setstate("core.started", True)
RNS.log("openCom Companion Core "+str(self)+" version "+str(self.version_str)+" started") RNS.log("openCom Companion Core "+str(self)+" "+str(self.version_str)+" started")
def stop_webshare(self): def stop_webshare(self):
if self.webshare_server != None: if self.webshare_server != None:

View File

@ -89,13 +89,13 @@ MDNavigationLayout:
on_release: root.ids.screen_manager.app.announces_action(self) on_release: root.ids.screen_manager.app.announces_action(self)
OneLineIconListItem: # OneLineIconListItem:
text: "Local Broadcasts" # text: "Local Broadcasts"
on_release: root.ids.screen_manager.app.broadcasts_action(self) # on_release: root.ids.screen_manager.app.broadcasts_action(self)
IconLeftWidget: # IconLeftWidget:
icon: "radio-tower" # icon: "radio-tower"
on_release: root.ids.screen_manager.app.broadcasts_action(self) # on_release: root.ids.screen_manager.app.broadcasts_action(self)
OneLineIconListItem: OneLineIconListItem:
@ -807,6 +807,14 @@ MDScreen:
text_size: self.width, None text_size: self.width, None
height: self.texture_size[1] height: self.texture_size[1]
MDLabel:
id: guide_info10
markup: True
text: ""
size_hint_y: None
text_size: self.width, None
height: self.texture_size[1]
MDLabel: MDLabel:
id: guide_info5 id: guide_info5
markup: True markup: True
@ -1678,6 +1686,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: "Use high-quality voice for PTT"
font_style: "H6"
MDSwitch:
id: settings_hq_ptt
pos_hint: {"center_y": 0.3}
disabled: False
active: False
MDBoxLayout: MDBoxLayout:
orientation: "horizontal" orientation: "horizontal"
size_hint_y: None size_hint_y: None
@ -1822,20 +1846,20 @@ MDScreen:
pos_hint: {"center_y": 0.3} pos_hint: {"center_y": 0.3}
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: "Allow Predictive Text" text: "Block Predictive Text"
# font_style: "H6" font_style: "H6"
# MDSwitch: MDSwitch:
# id: settings_allow_predictive_text id: settings_block_predictive_text
# pos_hint: {"center_y": 0.3} pos_hint: {"center_y": 0.3}
# active: False active: False
# MDBoxLayout: # MDBoxLayout:
# orientation: "vertical" # orientation: "vertical"
@ -1844,7 +1868,7 @@ MDScreen:
# padding: [0, dp(24), 0, dp(24)] # padding: [0, dp(24), 0, dp(24)]
# MDRectangleFlatIconButton: # MDRectangleFlatIconButton:
# id: hardware_rnode_button # id: input_language_button
# icon: "translate" # icon: "translate"
# text: "Input Languages" # text: "Input Languages"
# padding: [dp(0), dp(14), dp(0), dp(14)] # padding: [dp(0), dp(14), dp(0), dp(14)]
@ -2579,6 +2603,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: "Device requires BLE"
font_style: "H6"
MDSwitch:
id: hardware_rnode_ble
pos_hint: {"center_y": 0.3}
active: False
MDLabel: MDLabel:
id: hardware_rnode_info id: hardware_rnode_info
markup: True markup: True

View File

@ -194,6 +194,15 @@ class Messages():
self.details_dialog.open() self.details_dialog.open()
def update(self, limit=8): def update(self, limit=8):
if self.app.sideband.config["block_predictive_text"]:
if self.ids.message_text.input_type != "null":
self.ids.message_text.input_type = "null"
self.ids.message_text.keyboard_suggestions = False
else:
if self.ids.message_text.input_type != "text":
self.ids.message_text.input_type = "text"
self.ids.message_text.keyboard_suggestions = True
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)
@ -1229,6 +1238,7 @@ MDScreen:
MDTextField: MDTextField:
id: message_text id: message_text
input_type: "text"
keyboard_suggestions: True keyboard_suggestions: True
multiline: True multiline: True
hint_text: "Write message" hint_text: "Write message"

View File

@ -30,7 +30,7 @@ def get_variant() -> str:
version = re.findall(version_regex, version_file_data, re.M)[0] version = re.findall(version_regex, version_file_data, re.M)[0]
return version return version
except IndexError: except IndexError:
raise ValueError(f"Unable to find version string in {version_file}.") return None
__version__ = get_version() __version__ = get_version()
__variant__ = get_variant() __variant__ = get_variant()
@ -67,7 +67,10 @@ package_data = {
] ]
} }
print("Packaging openCom Companion "+__version__+" "+__variant__) variant_str = ""
if __variant__:
variant_str = " "+__variant__
print("Packaging openCom Companion "+__version__+variant_str)
setuptools.setup( setuptools.setup(
name="occ", name="occ",
@ -96,8 +99,8 @@ setuptools.setup(
] ]
}, },
install_requires=[ install_requires=[
"rns>=0.7.8", "rns>=0.8.4",
"lxmf>=0.5.3", "lxmf>=0.5.7",
"kivy>=2.3.0", "kivy>=2.3.0",
"pillow>=10.2.0", "pillow>=10.2.0",
"qrcode", "qrcode",