Map marker drawing

This commit is contained in:
Mark Qvist 2023-10-20 23:38:28 +02:00
parent 8dad802f53
commit 179ebe7cec
5 changed files with 341 additions and 33 deletions

View File

@ -1,4 +1,4 @@
__debug_build__ = False __debug_build__ = True
__disable_shaders__ = False __disable_shaders__ = False
__version__ = "0.6.3" __version__ = "0.6.3"
__variant__ = "beta" __variant__ = "beta"
@ -44,6 +44,8 @@ from kivy.uix.screenmanager import FadeTransition, NoTransition
from kivymd.uix.list import OneLineIconListItem from kivymd.uix.list import OneLineIconListItem
from kivy.properties import StringProperty from kivy.properties import StringProperty
from kivymd.uix.pickers import MDColorPicker from kivymd.uix.pickers import MDColorPicker
from sideband.sense import Telemeter
from mapview import MapMarker
if RNS.vendor.platformutils.get_platform() == "android": if RNS.vendor.platformutils.get_platform() == "android":
from sideband.core import SidebandCore from sideband.core import SidebandCore
@ -132,6 +134,8 @@ class SidebandApp(MDApp):
self.notification_icon = self.sideband.asset_dir+"/notification_icon.png" self.notification_icon = self.sideband.asset_dir+"/notification_icon.png"
self.connectivity_updater = None self.connectivity_updater = None
self.last_map_update = 0
self.last_telemetry_received = 0
################################################# #################################################
@ -601,6 +605,16 @@ class SidebandApp(MDApp):
if self.announces_view != None: if self.announces_view != None:
self.announces_view.update() self.announces_view.update()
elif self.root.ids.screen_manager.current == "map_screen":
if hasattr(self.root.ids.map_layout, "map") and self.root.ids.map_layout.map != None:
self.sideband.config["map_lat"] = self.root.ids.map_layout.map.lat
self.sideband.config["map_lon"] = self.root.ids.map_layout.map.lon
self.sideband.config["map_zoom"] = self.root.ids.map_layout.map.zoom
self.last_telemetry_received = self.sideband.getstate("app.flags.last_telemetry", allow_cache=True) or 0
if self.last_telemetry_received > self.last_map_update:
self.map_update_markers()
if self.sideband.getstate("app.flags.new_conversations", allow_cache=True): if self.sideband.getstate("app.flags.new_conversations", allow_cache=True):
if self.conversations_view != None: if self.conversations_view != None:
self.conversations_view.update() self.conversations_view.update()
@ -695,6 +709,10 @@ class SidebandApp(MDApp):
self.message_send_action() self.message_send_action()
if len(modifiers) > 0 and modifiers[0] == 'ctrl' and (text == "l"): if len(modifiers) > 0 and modifiers[0] == 'ctrl' and (text == "l"):
self.announces_action(self) self.announces_action(self)
if len(modifiers) > 0 and modifiers[0] == 'ctrl' and (text == "m"):
self.map_action(self)
if len(modifiers) > 0 and modifiers[0] == 'ctrl' and (text == "t"):
self.telemetry_action(self)
if len(modifiers) > 0 and modifiers[0] == 'ctrl' and (text == "r"): if len(modifiers) > 0 and modifiers[0] == 'ctrl' and (text == "r"):
self.conversations_action(self) self.conversations_action(self)
if len(modifiers) > 0 and modifiers[0] == 'ctrl' and (text == "g"): if len(modifiers) > 0 and modifiers[0] == 'ctrl' and (text == "g"):
@ -897,7 +915,6 @@ class SidebandApp(MDApp):
on_release=self.messages_view.close_send_error_dialog on_release=self.messages_view.close_send_error_dialog
) )
], ],
# elevation=0,
) )
self.messages_view.send_error_dialog.open() self.messages_view.send_error_dialog.open()
@ -1198,7 +1215,7 @@ class SidebandApp(MDApp):
self.root.ids.information_scrollview.effect_cls = ScrollEffect self.root.ids.information_scrollview.effect_cls = ScrollEffect
self.root.ids.information_logo.icon = self.sideband.asset_dir+"/rns_256.png" self.root.ids.information_logo.icon = self.sideband.asset_dir+"/rns_256.png"
info = "This is Sideband v"+__version__+" "+__variant__+", on RNS v"+RNS.__version__+" and LXMF v"+LXMF.__version__+".\n\nHumbly build using the following open components:\n\n - [b]Reticulum[/b] (MIT License)\n - [b]LXMF[/b] (MIT License)\n - [b]KivyMD[/b] (MIT License)\n - [b]Kivy[/b] (MIT License)\n - [b]Python[/b] (PSF License)"+"\n\nGo to [u][ref=link]https://unsigned.io/donate[/ref][/u] to support the project.\n\nThe Sideband app is Copyright (c) 2022 Mark Qvist / unsigned.io\n\nPermission is granted to freely share and distribute binary copies of Sideband v"+__version__+" "+__variant__+", so long as no payment or compensation is charged for said distribution or sharing.\n\nIf you were charged or paid anything for this copy of Sideband, please report it to [b]license@unsigned.io[/b].\n\nTHIS IS EXPERIMENTAL SOFTWARE - USE AT YOUR OWN RISK AND RESPONSIBILITY" info = "This is Sideband v"+__version__+" "+__variant__+", on RNS v"+RNS.__version__+" and LXMF v"+LXMF.__version__+".\n\nHumbly build using the following open components:\n\n - [b]Reticulum[/b] (MIT License)\n - [b]LXMF[/b] (MIT License)\n - [b]KivyMD[/b] (MIT License)\n - [b]Kivy[/b] (MIT License)\n - [b]Python[/b] (PSF License)"+"\n\nGo to [u][ref=link]https://unsigned.io/donate[/ref][/u] to support the project.\n\nThe Sideband app is Copyright (c) 2022 Mark Qvist / unsigned.io\n\nPermission is granted to freely share and distribute binary copies of Sideband v"+__version__+" "+__variant__+", so long as no payment or compensation is charged for said distribution or sharing.\n\nIf you were charged or paid anything for this copy of Sideband, please report it to [b]license@unsigned.io[/b].\n\nTHIS IS EXPERIMENTAL SOFTWARE - SIDEBAND COMES WITH ABSOLUTELY NO WARRANTY - USE AT YOUR OWN RISK AND RESPONSIBILITY"
self.root.ids.information_info.text = info self.root.ids.information_info.text = info
self.root.ids.information_info.bind(on_ref_press=link_exec) self.root.ids.information_info.bind(on_ref_press=link_exec)
self.root.ids.screen_manager.transition.direction = "left" self.root.ids.screen_manager.transition.direction = "left"
@ -2832,6 +2849,9 @@ class SidebandApp(MDApp):
self.root.ids.telemetry_send_to_trusted.active = self.sideband.config["telemetry_send_to_trusted"] self.root.ids.telemetry_send_to_trusted.active = self.sideband.config["telemetry_send_to_trusted"]
self.root.ids.telemetry_send_to_trusted.bind(active=self.telemetry_save) self.root.ids.telemetry_send_to_trusted.bind(active=self.telemetry_save)
self.root.ids.telemetry_send_appearance.active = self.sideband.config["telemetry_send_appearance"]
self.root.ids.telemetry_send_appearance.bind(active=self.telemetry_save)
self.root.ids.telemetry_s_location.active = self.sideband.config["telemetry_s_location"] self.root.ids.telemetry_s_location.active = self.sideband.config["telemetry_s_location"]
self.root.ids.telemetry_s_location.bind(active=self.telemetry_location_toggle) self.root.ids.telemetry_s_location.bind(active=self.telemetry_location_toggle)
@ -2909,6 +2929,7 @@ class SidebandApp(MDApp):
self.sideband.config["telemetry_enabled"] = self.root.ids.telemetry_enabled.active self.sideband.config["telemetry_enabled"] = self.root.ids.telemetry_enabled.active
self.sideband.config["telemetry_send_to_collector"] = self.root.ids.telemetry_send_to_collector.active self.sideband.config["telemetry_send_to_collector"] = self.root.ids.telemetry_send_to_collector.active
self.sideband.config["telemetry_send_to_trusted"] = self.root.ids.telemetry_send_to_trusted.active self.sideband.config["telemetry_send_to_trusted"] = self.root.ids.telemetry_send_to_trusted.active
self.sideband.config["telemetry_send_appearance"] = self.root.ids.telemetry_send_appearance.active
self.sideband.config["telemetry_s_location"] = self.root.ids.telemetry_s_location.active self.sideband.config["telemetry_s_location"] = self.root.ids.telemetry_s_location.active
self.sideband.config["telemetry_s_orientation"] = self.root.ids.telemetry_s_orientation.active self.sideband.config["telemetry_s_orientation"] = self.root.ids.telemetry_s_orientation.active
@ -3034,7 +3055,7 @@ class SidebandApp(MDApp):
def map_action(self, sender=None): def map_action(self, sender=None):
if not hasattr(self.root.ids.map_layout, "map") or self.root.ids.map_layout.map == None: if not hasattr(self.root.ids.map_layout, "map") or self.root.ids.map_layout.map == None:
from mapview import MapView from mapview import MapView
mapview = MapView(zoom=11, lat=50.6394, lon=3.057) mapview = MapView(zoom=self.sideband.config["map_zoom"], lat=self.sideband.config["map_lat"], lon=self.sideband.config["map_lon"])
mapview.snap_to_zoom = False mapview.snap_to_zoom = False
mapview.double_tap_zoom = False mapview.double_tap_zoom = False
self.root.ids.map_layout.map = mapview self.root.ids.map_layout.map = mapview
@ -3045,6 +3066,73 @@ class SidebandApp(MDApp):
self.root.ids.nav_drawer.set_state("closed") self.root.ids.nav_drawer.set_state("closed")
self.sideband.setstate("app.displaying", self.root.ids.screen_manager.current) self.sideband.setstate("app.displaying", self.root.ids.screen_manager.current)
if not hasattr(self, "map_markers") or self.map_markers == None:
self.map_markers = {}
def am_job(dt):
self.map_update_markers()
Clock.schedule_once(am_job, 0.6)
def map_update_markers(self, sender=None):
# TODO: Remove
# time_s = time.time()
# RNS.log("Update map markers", RNS.LOG_WARNING)
earliest = time.time() - self.sideband.config["map_history_limit"]
telemetry_entries = self.sideband.list_telemetry(after=earliest)
changes = False
for telemetry_source in telemetry_entries:
skip = False
if telemetry_source in self.map_markers:
marker = self.map_markers[telemetry_source]
newest_timestamp = telemetry_entries[telemetry_source][0][0]
if newest_timestamp <= marker.latest_timestamp:
skip = True
latest_viewable = None
if not skip:
for telemetry_entry in telemetry_entries[telemetry_source]:
telemetry_timestamp = telemetry_entry[0]
telemetry_data = telemetry_entry[1]
t = Telemeter.from_packed(telemetry_data)
if t != None:
telemetry = t.read_all()
# TODO: Remove
# RNS.log(str(telemetry)+" "+str(t), RNS.LOG_WARNING)
if "location" in telemetry and telemetry["location"]["latitude"] != None and telemetry["location"]["longtitude"] != None:
latest_viewable = telemetry
break
if latest_viewable != None:
l = latest_viewable["location"]
if not telemetry_source in self.map_markers:
marker = MapMarker(lat=l["latitude"], lon=l["longtitude"])
marker.source_dest = telemetry_source
marker.latest_timestamp = latest_viewable["time"]["utc"]
self.map_markers[telemetry_source] = marker
self.root.ids.map_layout.map.add_widget(marker)
changes = True
else:
marker = self.map_markers[telemetry_source]
marker.latest_timestamp = latest_viewable["time"]["utc"]
marker.lat = l["latitude"]
marker.lon = l["longtitude"]
changes = True
self.last_map_update = time.time()
if changes:
mv = self.root.ids.map_layout.map
mv.trigger_update(True)
# TODO: Remove
# RNS.log("Updated map markers in "+RNS.prettytime(time.time()-time_s), RNS.LOG_WARNING)
def map_add_marker(self, marker):
marker = MapMarker(lat=0.0, lon=0.0)
self.root.ids.map_layout.map.add_widget(marker)
### Guide screen ### Guide screen
###################################### ######################################
def close_guide_action(self, sender=None): def close_guide_action(self, sender=None):
@ -3090,6 +3178,8 @@ If you use Reticulum and LXMF on hardware that does not carry any identifiers ti
- Ctrl-D or Ctrl-S Send message - Ctrl-D or Ctrl-S Send message
- Ctrl-R Show Conversations - Ctrl-R Show Conversations
- Ctrl-L Show Announce Stream - Ctrl-L Show Announce Stream
- Ctrl-M Show Situation Map
- Ctrl-T Show Telemetry Setup
- Ctrl-N New conversation - Ctrl-N New conversation
- Ctrl-G Show guide""" - Ctrl-G Show guide"""

View File

@ -18,6 +18,7 @@ from mapview.constants import CACHE_DIR
# if "MAPVIEW_DEBUG_DOWNLOADER" in environ: # if "MAPVIEW_DEBUG_DOWNLOADER" in environ:
# Logger.setLevel(LOG_LEVELS['debug']) # Logger.setLevel(LOG_LEVELS['debug'])
Logger.setLevel(LOG_LEVELS['error'])
# user agent is needed because since may 2019 OSM gives me a 429 or 403 server error # user agent is needed because since may 2019 OSM gives me a 429 or 403 server error
# I tried it with a simpler one (just Mozilla/5.0) this also gets rejected # I tried it with a simpler one (just Mozilla/5.0) this also gets rejected
@ -56,21 +57,21 @@ class Downloader:
self._futures.append(future) self._futures.append(future)
def download_tile(self, tile): def download_tile(self, tile):
Logger.debug( # Logger.debug(
"Downloader: queue(tile) zoom={} x={} y={}".format( # "Downloader: queue(tile) zoom={} x={} y={}".format(
tile.zoom, tile.tile_x, tile.tile_y # tile.zoom, tile.tile_x, tile.tile_y
) # )
) # )
future = self.executor.submit(self._load_tile, tile) future = self.executor.submit(self._load_tile, tile)
self._futures.append(future) self._futures.append(future)
def download(self, url, callback, **kwargs): def download(self, url, callback, **kwargs):
Logger.debug("Downloader: queue(url) {}".format(url)) # Logger.debug("Downloader: queue(url) {}".format(url))
future = self.executor.submit(self._download_url, url, callback, kwargs) future = self.executor.submit(self._download_url, url, callback, kwargs)
self._futures.append(future) self._futures.append(future)
def _download_url(self, url, callback, kwargs): def _download_url(self, url, callback, kwargs):
Logger.debug("Downloader: download(url) {}".format(url)) # Logger.debug("Downloader: download(url) {}".format(url))
response = requests.get(url, **kwargs) response = requests.get(url, **kwargs)
response.raise_for_status() response.raise_for_status()
return callback, (url, response) return callback, (url, response)
@ -80,20 +81,20 @@ class Downloader:
return return
cache_fn = tile.cache_fn cache_fn = tile.cache_fn
if exists(cache_fn): if exists(cache_fn):
Logger.debug("Downloader: use cache {}".format(cache_fn)) # Logger.debug("Downloader: use cache {}".format(cache_fn))
return tile.set_source, (cache_fn,) return tile.set_source, (cache_fn,)
tile_y = tile.map_source.get_row_count(tile.zoom) - tile.tile_y - 1 tile_y = tile.map_source.get_row_count(tile.zoom) - tile.tile_y - 1
uri = tile.map_source.url.format( uri = tile.map_source.url.format(
z=tile.zoom, x=tile.tile_x, y=tile_y, s=choice(tile.map_source.subdomains) z=tile.zoom, x=tile.tile_x, y=tile_y, s=choice(tile.map_source.subdomains)
) )
Logger.debug("Downloader: download(tile) {}".format(uri)) # Logger.debug("Downloader: download(tile) {}".format(uri))
response = requests.get(uri, headers={'User-agent': USER_AGENT}, timeout=5) response = requests.get(uri, headers={'User-agent': USER_AGENT}, timeout=5)
try: try:
response.raise_for_status() response.raise_for_status()
data = response.content data = response.content
with open(cache_fn, "wb") as fd: with open(cache_fn, "wb") as fd:
fd.write(data) fd.write(data)
Logger.debug("Downloaded {} bytes: {}".format(len(data), uri)) # Logger.debug("Downloaded {} bytes: {}".format(len(data), uri))
return tile.set_source, (cache_fn,) return tile.set_source, (cache_fn,)
except Exception as e: except Exception as e:
print("Downloader error: {!r}".format(e)) print("Downloader error: {!r}".format(e))

View File

@ -4,6 +4,7 @@ import threading
import plyer import plyer
import os.path import os.path
import time import time
import struct
import sqlite3 import sqlite3
import random import random
@ -313,6 +314,7 @@ class SidebandCore():
else: else:
self._db_initstate() self._db_initstate()
self._db_initpersistent() self._db_initpersistent()
self._db_inittelemetry()
self.__save_config() self.__save_config()
@ -457,6 +459,8 @@ class SidebandCore():
self.config["telemetry_fg"] = [0,0,0,1] self.config["telemetry_fg"] = [0,0,0,1]
if not "telemetry_bg" in self.config: if not "telemetry_bg" in self.config:
self.config["telemetry_bg"] = [1,1,1,1] self.config["telemetry_bg"] = [1,1,1,1]
if not "telemetry_send_appearance" in self.config:
self.config["telemetry_send_appearance"] = False
if not "telemetry_s_location" in self.config: if not "telemetry_s_location" in self.config:
self.config["telemetry_s_location"] = False self.config["telemetry_s_location"] = False
@ -483,12 +487,22 @@ class SidebandCore():
if not "telemetry_s_proximity" in self.config: if not "telemetry_s_proximity" in self.config:
self.config["telemetry_s_proximity"] = False self.config["telemetry_s_proximity"] = False
if not "map_history_limit" in self.config:
self.config["map_history_limit"] = 7*24*60*60
if not "map_lat" in self.config:
self.config["map_lat"] = 0.0
if not "map_lon" in self.config:
self.config["map_lon"] = 0.0
if not "map_zoom" in self.config:
self.config["map_zoom"] = 3
# Make sure we have a database # Make sure we have a database
if not os.path.isfile(self.db_path): if not os.path.isfile(self.db_path):
self.__db_init() self.__db_init()
else: else:
self._db_initstate() self._db_initstate()
self._db_initpersistent() self._db_initpersistent()
self._db_inittelemetry()
self.__db_indices() self.__db_indices()
def __reload_config(self): def __reload_config(self):
@ -709,6 +723,9 @@ class SidebandCore():
else: else:
return None return None
def list_telemetry(self, context_dest = None, after = None, before = None, limit = None):
return self._db_telemetry(context_dest = context_dest, after = after, before = before, limit = limit)
def list_messages(self, context_dest, after = None, before = None, limit = None): def list_messages(self, context_dest, after = None, before = None, limit = None):
result = self._db_messages(context_dest, after, before, limit) result = self._db_messages(context_dest, after, before, limit)
if result != None: if result != None:
@ -909,6 +926,13 @@ class SidebandCore():
dbc.execute("CREATE TABLE IF NOT EXISTS persistent (property BLOB PRIMARY KEY, value BLOB)") dbc.execute("CREATE TABLE IF NOT EXISTS persistent (property BLOB PRIMARY KEY, value BLOB)")
db.commit() db.commit()
def _db_inittelemetry(self):
db = self.__db_connect()
dbc = db.cursor()
dbc.execute("CREATE TABLE IF NOT EXISTS telemetry (id INTEGER PRIMARY KEY, dest_context BLOB, ts INTEGER, data BLOB)")
db.commit()
def _db_getpersistent(self, prop): def _db_getpersistent(self, prop):
try: try:
db = self.__db_connect() db = self.__db_connect()
@ -998,6 +1022,110 @@ class SidebandCore():
result = dbc.fetchall() result = dbc.fetchall()
db.commit() db.commit()
def _db_telemetry(self, context_dest = None, after = None, before = None, limit = None):
db = self.__db_connect()
dbc = db.cursor()
# TODO: Implement limit
order_part = " order by ts DESC"
if context_dest == None:
if after != None and before == None:
query = "select * from telemetry where ts>:after_ts"+order_part
dbc.execute(query, {"after_ts": after})
elif after == None and before != None:
query = "select * from telemetry where ts<:before_ts"+order_part
dbc.execute(query, {"before_ts": before})
elif after != None and before != None:
query = "select * from telemetry where ts<:before_ts and ts>:after_ts"+order_part
dbc.execute(query, {"before_ts": before, "after_ts": after})
else:
query = query = "select * from telemetry"
dbc.execute(query, {})
else:
if after != None and before == None:
query = "select * from telemetry where dest_context=:context_dest and ts>:after_ts"+order_part
dbc.execute(query, {"context_dest": context_dest, "after_ts": after})
elif after == None and before != None:
query = "select * from telemetry where dest_context=:context_dest and ts<:before_ts"+order_part
dbc.execute(query, {"context_dest": context_dest, "before_ts": before})
elif after != None and before != None:
query = "select * from telemetry where dest_context=:context_dest and ts<:before_ts and ts>:after_ts"+order_part
dbc.execute(query, {"context_dest": context_dest, "before_ts": before, "after_ts": after})
else:
query = query = "select * from telemetry where dest_context=:context_dest"+order_part
dbc.execute(query, {"context_dest": context_dest})
result = dbc.fetchall()
if len(result) < 1:
return None
else:
results = {}
for entry in result:
telemetry_source = entry[1]
telemetry_timestamp = entry[2]
telemetry_data = entry[3]
if not telemetry_source in results:
results[telemetry_source] = []
results[telemetry_source].append([telemetry_timestamp, telemetry_data])
return results
def _db_save_telemetry(self, context_dest, telemetry):
# TODO: Remove
# RNS.log("Saving telemetry for "+RNS.prettyhexrep(context_dest), RNS.LOG_WARNING)
try:
remote_telemeter = Telemeter.from_packed(telemetry)
telemetry_timestamp = remote_telemeter.read_all()["time"]["utc"]
db = self.__db_connect()
dbc = db.cursor()
query = "select * from telemetry where dest_context=:ctx and ts=:tts"
dbc.execute(query, {"ctx": context_dest, "tts": telemetry_timestamp})
result = dbc.fetchall()
if len(result) != 0:
# TODO: Remove
# RNS.log("Telemetry entry already exists, ignoring", RNS.LOG_WARNING)
return
query = "INSERT INTO telemetry (dest_context, ts, data) values (?, ?, ?)"
data = (context_dest, telemetry_timestamp, telemetry)
dbc.execute(query, data)
db.commit()
self.setstate("app.flags.last_telemetry", time.time())
except Exception as e:
RNS.log("An error occurred while saving telemetry to database: "+str(e), RNS.LOG_ERROR)
self.db = None
def _db_update_appearance(self, context_dest, timestamp, appearance):
# TODO: Remove
# RNS.log("Updating appearance for "+RNS.prettyhexrep(context_dest), RNS.LOG_WARNING)
conv = self._db_conversation(context_dest)
data_dict = conv["data"]
if data_dict == None:
data_dict = {}
data_dict["appearance"] = appearance
packed_dict = msgpack.packb(data_dict)
db = self.__db_connect()
dbc = db.cursor()
query = "UPDATE conv set data = ? where dest_context = ?"
data = (packed_dict, context_dest)
dbc.execute(query, data)
result = dbc.fetchall()
db.commit()
def _db_conversation_set_telemetry(self, context_dest, send_telemetry=False): def _db_conversation_set_telemetry(self, context_dest, send_telemetry=False):
conv = self._db_conversation(context_dest) conv = self._db_conversation(context_dest)
data_dict = conv["data"] data_dict = conv["data"]
@ -1346,6 +1474,13 @@ class SidebandCore():
db.commit() db.commit()
if lxm.fields != None:
if LXMF.FIELD_ICON_APPEARANCE in lxm.fields:
self._db_update_appearance(context_dest, lxm.timestamp, lxm.fields[LXMF.FIELD_ICON_APPEARANCE])
if LXMF.FIELD_TELEMETRY in lxm.fields:
self._db_save_telemetry(context_dest, lxm.fields[LXMF.FIELD_TELEMETRY])
self.__event_conversation_changed(context_dest) self.__event_conversation_changed(context_dest)
def _db_save_announce(self, destination_hash, app_data, dest_type="lxmf.delivery"): def _db_save_announce(self, destination_hash, app_data, dest_type="lxmf.delivery"):
@ -1442,8 +1577,6 @@ class SidebandCore():
def get_packed_telemetry(self): def get_packed_telemetry(self):
self.update_telemeter_config() self.update_telemeter_config()
packed = self.telemeter.packed() packed = self.telemeter.packed()
# TODO: Remove
RNS.log("Packed telemetry: "+str(packed), RNS.LOG_WARNING)
return packed return packed
def is_known(self, dest_hash): def is_known(self, dest_hash):
@ -2096,6 +2229,34 @@ class SidebandCore():
else: else:
self.lxm_ingest(message, originator=True) self.lxm_ingest(message, originator=True)
def get_message_fields(self, context_dest):
fields = None
send_telemetry = self.should_send_telemetry(context_dest)
send_appearance = self.config["telemetry_send_appearance"] or send_telemetry
if send_telemetry or send_appearance:
fields = {}
if send_appearance:
# TODO: REMOVE
# RNS.log("Sending appearance", RNS.LOG_WARNING)
def fth(c):
r = c[0]; g = c[1]; b = c[2]
r = min(max(0, r), 1); g = min(max(0, g), 1); b = min(max(0, b), 1)
d = 1.0/255.0
return struct.pack("!BBB", int(r/d), int(g/d), int(b/d))
icon = self.config["telemetry_icon"]
fg = fth(self.config["telemetry_fg"][:-1])
bg = fth(self.config["telemetry_bg"][:-1])
fields[LXMF.FIELD_ICON_APPEARANCE] = [icon, fg, bg]
if send_telemetry:
# TODO: REMOVE
# RNS.log("Sending telemetry", RNS.LOG_WARNING)
fields[LXMF.FIELD_TELEMETRY] = self.latest_packed_telemetry
return fields
def paper_message(self, content, destination_hash): def paper_message(self, content, destination_hash):
try: try:
if content == "": if content == "":
@ -2106,7 +2267,7 @@ class SidebandCore():
source = self.lxmf_destination source = self.lxmf_destination
desired_method = LXMF.LXMessage.PAPER desired_method = LXMF.LXMessage.PAPER
lxm = LXMF.LXMessage(dest, source, content, title="", desired_method=desired_method) lxm = LXMF.LXMessage(dest, source, content, title="", desired_method=desired_method, fields = self.get_message_fields(destination_hash))
self.lxm_ingest(lxm, originator=True) self.lxm_ingest(lxm, originator=True)
@ -2130,7 +2291,7 @@ class SidebandCore():
else: else:
desired_method = LXMF.LXMessage.DIRECT desired_method = LXMF.LXMessage.DIRECT
lxm = LXMF.LXMessage(dest, source, content, title="", desired_method=desired_method) lxm = LXMF.LXMessage(dest, source, content, title="", desired_method=desired_method, fields = self.get_message_fields(destination_hash))
lxm.register_delivery_callback(self.message_notification) lxm.register_delivery_callback(self.message_notification)
lxm.register_failed_callback(self.message_notification) lxm.register_failed_callback(self.message_notification)
@ -2195,6 +2356,9 @@ class SidebandCore():
self.setstate("lxm_uri_ingest.result", response) self.setstate("lxm_uri_ingest.result", response)
def lxm_ingest(self, message, originator = False): def lxm_ingest(self, message, originator = False):
# TODO: Remove
RNS.log("MESSAGE FIELDS: "+str(message.fields), RNS.LOG_WARNING)
should_notify = False should_notify = False
is_trusted = False is_trusted = False
unread_reason_tx = False unread_reason_tx = False

View File

@ -964,6 +964,22 @@ MDNavigationLayout:
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: "Always send custom display style"
font_style: "H6"
MDSwitch:
id: telemetry_send_appearance
pos_hint: {"center_y": 0.3}
active: False
MDLabel: MDLabel:
markup: True markup: True
text: "\\n\\n" text: "\\n\\n"

View File

@ -22,9 +22,11 @@ import subprocess
import shlex import shlex
if RNS.vendor.platformutils.get_platform() == "android": if RNS.vendor.platformutils.get_platform() == "android":
from sideband.sense import Telemeter
from ui.helpers import ts_format, file_ts_format, mdc from ui.helpers import ts_format, file_ts_format, mdc
from ui.helpers import color_received, color_delivered, color_propagated, color_paper, color_failed, color_unknown, intensity_msgs_dark, intensity_msgs_light from ui.helpers import color_received, color_delivered, color_propagated, color_paper, color_failed, color_unknown, intensity_msgs_dark, intensity_msgs_light
else: else:
from sbapp.sideband.sense import Telemeter
from .helpers import ts_format, file_ts_format, mdc from .helpers import ts_format, file_ts_format, mdc
from .helpers import color_received, color_delivered, color_propagated, color_paper, color_failed, color_unknown, intensity_msgs_dark, intensity_msgs_light from .helpers import color_received, color_delivered, color_propagated, color_paper, color_failed, color_unknown, intensity_msgs_dark, intensity_msgs_light
@ -260,6 +262,18 @@ class Messages():
return x return x
def gen_copy_telemetry(packed_telemetry, item):
def x():
try:
telemeter = Telemeter.from_packed(packed_telemetry)
Clipboard.copy(str(telemeter.read_all()))
item.dmenu.dismiss()
except Exception as e:
RNS.log("An error occurred while decoding telemetry. The contained exception was: "+str(e), RNS.LOG_ERROR)
Clipboard.copy("Could not decode telemetry")
return x
def gen_copy_lxm_uri(lxm, item): def gen_copy_lxm_uri(lxm, item):
def x(): def x():
Clipboard.copy(lxm.as_uri()) Clipboard.copy(lxm.as_uri())
@ -447,21 +461,44 @@ class Messages():
} }
] ]
else: else:
dm_items = [ if "lxm" in m and m["lxm"] and m["lxm"].fields != None and LXMF.FIELD_TELEMETRY in m["lxm"].fields:
{ packed_telemetry = m["lxm"].fields[LXMF.FIELD_TELEMETRY]
"viewclass": "OneLineListItem", dm_items = [
"text": "Copy", {
"height": dp(40), "viewclass": "OneLineListItem",
"on_release": gen_copy(m["content"].decode("utf-8"), item) "text": "Copy",
}, "height": dp(40),
{ "on_release": gen_copy(m["content"].decode("utf-8"), item)
"text": "Delete", },
"viewclass": "OneLineListItem", {
"height": dp(40), "viewclass": "OneLineListItem",
"on_release": gen_del(m["hash"], item) "text": "Copy telemetry",
} "height": dp(40),
"on_release": gen_copy_telemetry(packed_telemetry, item)
] },
{
"text": "Delete",
"viewclass": "OneLineListItem",
"height": dp(40),
"on_release": gen_del(m["hash"], item)
}
]
else:
dm_items = [
{
"viewclass": "OneLineListItem",
"text": "Copy",
"height": dp(40),
"on_release": gen_copy(m["content"].decode("utf-8"), item)
},
{
"text": "Delete",
"viewclass": "OneLineListItem",
"height": dp(40),
"on_release": gen_del(m["hash"], item)
}
]
item.dmenu = MDDropdownMenu( item.dmenu = MDDropdownMenu(
caller=item.ids.msg_submenu, caller=item.ids.msg_submenu,