From 9e992c83fd475255c38e8eae000282ba25108f46 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Sat, 7 Dec 2024 22:27:39 +0100 Subject: [PATCH] Added log viewer --- sbapp/sideband/core.py | 14 +++++- sbapp/ui/layouts.py | 2 +- sbapp/ui/utilities.py | 102 ++++++++++++++++++++++++++++++++++++----- 3 files changed, 104 insertions(+), 14 deletions(-) diff --git a/sbapp/sideband/core.py b/sbapp/sideband/core.py index 48ce9ee..8d3df28 100644 --- a/sbapp/sideband/core.py +++ b/sbapp/sideband/core.py @@ -16,6 +16,7 @@ import multiprocessing.connection from copy import deepcopy from threading import Lock +from collections import deque from .res import sideband_fb_data from .sense import Telemeter, Commands from .plugins import SidebandCommandPlugin, SidebandServicePlugin, SidebandTelemetryPlugin @@ -110,6 +111,8 @@ class SidebandCore(): DEFAULT_APPEARANCE = ["account", [0,0,0,1], [1,1,1,1]] + LOG_DEQUE_MAXLEN = 256 + aspect_filter = "lxmf.delivery" def received_announce(self, destination_hash, announced_identity, app_data): # Add the announce to the directory announce @@ -142,6 +145,7 @@ class SidebandCore(): self.is_standalone = False self.log_verbose = verbose + self.log_deque = deque(maxlen=self.LOG_DEQUE_MAXLEN) self.owner_app = owner_app self.reticulum = None self.webshare_server = None @@ -3630,6 +3634,14 @@ class SidebandCore(): if self.is_client: self.service_rpc_set_debug(debug) + def _log_handler(self, message): + self.log_deque.append(message) + print(message) + + # TODO: Get service log on Android + def get_log(self): + return "\n".join(self.log_deque) + def __start_jobs_immediate(self): if self.log_verbose: selected_level = 6 @@ -3637,7 +3649,7 @@ class SidebandCore(): selected_level = 2 self.setstate("init.loadingstate", "Substantiating Reticulum") - self.reticulum = RNS.Reticulum(configdir=self.rns_configdir, loglevel=selected_level) + self.reticulum = RNS.Reticulum(configdir=self.rns_configdir, loglevel=selected_level, logdest=self._log_handler) if self.is_service: self.__start_rpc_listener() diff --git a/sbapp/ui/layouts.py b/sbapp/ui/layouts.py index a4aefc0..dcd463f 100644 --- a/sbapp/ui/layouts.py +++ b/sbapp/ui/layouts.py @@ -1287,7 +1287,7 @@ layout_settings_screen = """ MDLabel: id: scaling_info markup: True - text: "You can scale the entire Sideband UI by specifying a scaling factor in the field below. After setting it, restart sideband for the scaling to take effect.\\n\\nSet to 0.0 to disable scaling adjustments." + text: "You can scale the entire Sideband UI by specifying a scaling factor in the field below. After setting it, restart sideband for the scaling to take effect.\\n\\nSet to 0.0 to disable scaling adjustments.\\n\\n[b]Please note![/b] On some devices, the default scaling factor will be higher than 1.0, and setting a smaller value will result in miniscule UI elements." size_hint_y: None text_size: self.width, None height: self.texture_size[1] diff --git a/sbapp/ui/utilities.py b/sbapp/ui/utilities.py index c192ed6..29e6f93 100644 --- a/sbapp/ui/utilities.py +++ b/sbapp/ui/utilities.py @@ -10,6 +10,7 @@ from kivymd.uix.recycleview import MDRecycleView from kivymd.uix.list import OneLineIconListItem from kivymd.uix.pickers import MDColorPicker from kivymd.icon_definitions import md_icons +from kivymd.toast import toast from kivy.properties import StringProperty, BooleanProperty from kivy.effects.scroll import ScrollEffect from kivy.clock import Clock @@ -29,6 +30,7 @@ class Utilities(): self.screen = None self.rnstatus_screen = None self.rnstatus_instance = None + self.logviewer_screen = None if not self.app.root.ids.screen_manager.has_screen("utilities_screen"): self.screen = Builder.load_string(layout_utilities_screen) @@ -72,16 +74,11 @@ class Utilities(): import io from contextlib import redirect_stdout - output_marker = "===begin rnstatus output===" output = "None" with io.StringIO() as buffer, redirect_stdout(buffer): - print(output_marker, end="") - self.rnstatus_instance.main(rns_instance=RNS.Reticulum.get_instance()) - output = buffer.getvalue() - - remainder = output[:output.find(output_marker)] - output = output[output.find(output_marker)+len(output_marker):] - print(remainder, end="") + with RNS.logging_lock: + self.rnstatus_instance.main(rns_instance=RNS.Reticulum.get_instance()) + output = buffer.getvalue() def cb(dt): self.rnstatus_screen.ids.rnstatus_output.text = f"[font=RobotoMono-Regular]{output}[/font]" @@ -90,6 +87,44 @@ class Utilities(): if self.app.root.ids.screen_manager.current == "rnstatus_screen": Clock.schedule_once(self.update_rnstatus, 1) + ### Log viewer screen + ###################################### + + def logviewer_action(self, sender=None): + if not self.app.root.ids.screen_manager.has_screen("logviewer_screen"): + self.logviewer_screen = Builder.load_string(layout_logviewer_screen) + self.logviewer_screen.app = self.app + self.logviewer_screen.delegate = self + self.app.root.ids.screen_manager.add_widget(self.logviewer_screen) + + self.app.root.ids.screen_manager.transition.direction = "left" + self.app.root.ids.screen_manager.current = "logviewer_screen" + self.app.sideband.setstate("app.displaying", self.app.root.ids.screen_manager.current) + + self.update_logviewer() + + def update_logviewer(self, sender=None): + threading.Thread(target=self.update_logviewer_job, daemon=True).start() + + def update_logviewer_job(self, sender=None): + try: + output = self.app.sideband.get_log() + except Exception as e: + output = f"An error occurred while retrieving log entries:\n{e}" + + self.logviewer_screen.log_contents = output + def cb(dt): + self.logviewer_screen.ids.logviewer_output.text = f"[font=RobotoMono-Regular]{output}[/font]" + Clock.schedule_once(cb, 0.2) + + if self.app.root.ids.screen_manager.current == "logviewer_screen": + Clock.schedule_once(self.update_logviewer, 1) + + def logviewer_copy(self, sender=None): + Clipboard.copy(self.logviewer_screen.log_contents) + if True or RNS.vendor.platformutils.is_android(): + toast("Log copied to clipboard") + layout_utilities_screen = """ MDScreen: @@ -156,8 +191,8 @@ MDScreen: icon_size: dp(24) font_size: dp(16) size_hint: [1.0, None] - on_release: root.delegate.rnstatus_action(self) - disabled: True + on_release: root.delegate.logviewer_action(self) + disabled: False """ @@ -177,12 +212,12 @@ MDScreen: [['menu', lambda x: root.app.nav_drawer.set_state("open")]] right_action_items: [ - ['refresh', lambda x: root.delegate.update_rnstatus()], + # ['refresh', lambda x: root.delegate.update_rnstatus()], ['close', lambda x: root.app.close_sub_utilities_action(self)], ] MDScrollView: - id: sensors_scrollview + id: rnstatus_scrollview size_hint_x: 1 size_hint_y: None size: [root.width, root.height-root.ids.top_bar.height] @@ -202,4 +237,47 @@ MDScreen: size_hint_y: None text_size: self.width, None height: self.texture_size[1] +""" + +layout_logviewer_screen = """ +MDScreen: + name: "logviewer_screen" + + BoxLayout: + orientation: "vertical" + + MDTopAppBar: + id: top_bar + title: "Log Viewer" + anchor_title: "left" + elevation: 0 + left_action_items: + [['menu', lambda x: root.app.nav_drawer.set_state("open")]] + right_action_items: + [ + ['content-copy', lambda x: root.delegate.logviewer_copy()], + ['close', lambda x: root.app.close_sub_utilities_action(self)], + ] + + MDScrollView: + id: logviewer_scrollview + size_hint_x: 1 + size_hint_y: None + size: [root.width, root.height-root.ids.top_bar.height] + do_scroll_x: False + do_scroll_y: True + + MDGridLayout: + cols: 1 + padding: [dp(28), dp(14), dp(28), dp(28)] + size_hint_y: None + height: self.minimum_height + + MDLabel: + id: logviewer_output + markup: True + text: "" + size_hint_y: None + text_size: self.width, None + height: self.texture_size[1] """ \ No newline at end of file