From a4eaac92fa404048ea83bbe7ecfa6de6053255c7 Mon Sep 17 00:00:00 2001 From: Alex R Date: Wed, 1 Sep 2021 18:06:40 -0500 Subject: [PATCH] Persist pair and bring back SMS chatty notifications (#79) * add 'keep paired' checkbox button (not functional) * Getting persistant pairing working (#64) * persistent pairing * Minor ui tweak * sync persist-pair with what's in main (#76) * Add complete dependency list on Ubuntu (#55) In order to figure this out (and make sure I was correct) I used a Docker container. By design it's awkward to support dbus & bluetooth from within a container, so totally understandable if you'd prefer to not include the Dockerfile in the repo. I managed to get the app to run and print out "Bluetooth is disabled" which exercises a decent bit of the code. If this is helpful, it could be the start of some light CI/GitHub workflows to run some testing for PRs. Co-authored-by: Alex R * change app-id to com.github.alexr4535.siglo for flathub * update manifest for flathub * bump tag * Update com.github.alexr4535.siglo.json * add python3-modules.json * reference python3-requests in manifest * prepare for next release * use --system-talk-name to specify dbus name * Install the required Python packages in the Flatpak manifest (#61) * Prepare for next release * Appdata: Add missing XML tags required by flathub * Appdata: Add screenshot required by flathub * bump version for next release * fix typo * bump version for next release * bump version for next release * Appdata: fix screenshot URL required by flathub * bump version for next release * Adding custom form_factor values (#67) This should make Siglo show up in the PureOS Store on the Librem 5 * Adding X-Purism-FormFactor (#66) This should help with showing up on the main app launcher of Phosh > 0.12. For details read https://linmob.net/phosh-0-12-app-drawer/ * Fix metadata file (#68) Fixes https://github.com/alexr4535/siglo/issues/65 * Bump version for next release * Fix crash without network access (#69) This allows Siglo to start when internet access is not available, either because the user is outside service or the flatpak permission has been disabled. Co-authored-by: undef * Add requirements section and flathub install instructions * Version Bump Co-authored-by: Joe Smith Co-authored-by: Jordan Williams Co-authored-by: 1peter10 <32742559+1peter10@users.noreply.github.com> Co-authored-by: TheEvilSkeleton Co-authored-by: Undef-a <75462734+Undef-a@users.noreply.github.com> Co-authored-by: undef * Notifications service into persist-pair branch (#77) * Add complete dependency list on Ubuntu (#55) In order to figure this out (and make sure I was correct) I used a Docker container. By design it's awkward to support dbus & bluetooth from within a container, so totally understandable if you'd prefer to not include the Dockerfile in the repo. I managed to get the app to run and print out "Bluetooth is disabled" which exercises a decent bit of the code. If this is helpful, it could be the start of some light CI/GitHub workflows to run some testing for PRs. Co-authored-by: Alex R * change app-id to com.github.alexr4535.siglo for flathub * update manifest for flathub * bump tag * Update com.github.alexr4535.siglo.json * add python3-modules.json * reference python3-requests in manifest * prepare for next release * use --system-talk-name to specify dbus name * Install the required Python packages in the Flatpak manifest (#61) * Prepare for next release * Appdata: Add missing XML tags required by flathub * Appdata: Add screenshot required by flathub * bump version for next release * fix typo * bump version for next release * bump version for next release * Appdata: fix screenshot URL required by flathub * bump version for next release * Adding custom form_factor values (#67) This should make Siglo show up in the PureOS Store on the Librem 5 * Adding X-Purism-FormFactor (#66) This should help with showing up on the main app launcher of Phosh > 0.12. For details read https://linmob.net/phosh-0-12-app-drawer/ * Fix metadata file (#68) Fixes https://github.com/alexr4535/siglo/issues/65 * Bump version for next release * Add notification service This allows notifications to be sent from the standard application rather than only through the daemon. This version should be more stable as it includes the connection improvments of the main Siglo application. Co-authored-by: Joe Smith Co-authored-by: Jordan Williams Co-authored-by: 1peter10 <32742559+1peter10@users.noreply.github.com> Co-authored-by: TheEvilSkeleton Co-authored-by: undef * Revert "Notifications service into persist-pair branch (#77)" (#78) This reverts commit 3f2084c9b20b2986c3312e0998f9107f1ea8ce58. * fixup dbus monitoring for chatty notifications * Daemon: remove deprecated dbus eavesdropping * stopping the daemon disconnects the device * update systemd file * remove my old deprecated pair switch * Revert "remove my old deprecated pair switch" This reverts commit 62fc8230f6fe2a2b906dbdfdc4586edd0868ea86. * remove deprecated pair switch * remove deprecated time sync button * remove deprecated scan functions * delete deprecated scan functions * delete deprecated deploy_type stuff * fix battery and firmware version * keep paired toggle starts the daemon * make pair switch work with daemon * keep paired until siglo opens again Co-authored-by: Konstantin Quillfeldt <87606259+kq98@users.noreply.github.com> Co-authored-by: Joe Smith Co-authored-by: Jordan Williams Co-authored-by: 1peter10 <32742559+1peter10@users.noreply.github.com> Co-authored-by: TheEvilSkeleton Co-authored-by: Undef-a <75462734+Undef-a@users.noreply.github.com> Co-authored-by: undef Co-authored-by: Alex Robinson --- data/siglo.service | 5 +- src/bluetooth.py | 13 +++-- src/daemon.py | 32 +++++++---- src/siglo.in | 14 +++-- src/window.py | 134 +++++++++------------------------------------ src/window.ui | 119 +++++++++++++++++++++------------------- 6 files changed, 129 insertions(+), 188 deletions(-) diff --git a/data/siglo.service b/data/siglo.service index 05b3bd4..fde41b6 100644 --- a/data/siglo.service +++ b/data/siglo.service @@ -1,5 +1,6 @@ [Unit] Description=siglo service [Service] -ExecStart=siglo --daemon -Environment=PYTHONUNBUFFERED=1 \ No newline at end of file +ExecStart=siglo --start +ExecStop=siglo --stop +Environment=PYTHONUNBUFFERED=1 diff --git a/src/bluetooth.py b/src/bluetooth.py index 364fa1b..58d82b5 100644 --- a/src/bluetooth.py +++ b/src/bluetooth.py @@ -75,8 +75,6 @@ def get_scan_result(self): return self.scan_result def get_device_set(self): - if self.conf.get_property("paired"): - self.device_set.add(self.conf.get_property("last_paired_device")) return self.device_set def get_adapter_name(self): @@ -108,8 +106,11 @@ def scan_for_infinitime(self): class InfiniTimeDevice(gatt.Device): - def __init__(self, mac_address, manager): + def __init__(self, mac_address, manager, thread): self.conf = config() + self.mac = mac_address + self.manager = manager + self.thread = thread super().__init__(mac_address, manager) def connect(self): @@ -119,6 +120,8 @@ def connect(self): def connect_succeeded(self): super().connect_succeeded() print("[%s] Connected" % (self.mac_address)) + print("self.mac", self.mac) + self.conf.set_property("last_paired_device", self.mac) def connect_failed(self, error): super().connect_failed(error) @@ -187,8 +190,8 @@ def services_resolved(self): # Get device firmware self.battery = int(battery_level.read_value()[0]) - - self.services_done() + if self.thread: + self.services_done() def send_notification(self, alert_dict): message = alert_dict["message"] diff --git a/src/daemon.py b/src/daemon.py index c966053..8f2e014 100644 --- a/src/daemon.py +++ b/src/daemon.py @@ -11,18 +11,28 @@ class daemon: def __init__(self): self.conf = config() self.manager = InfiniTimeManager() - self.device = InfiniTimeDevice(manager=self.manager, mac_address=self.conf.get_property("last_paired_device")) - self.device.connect(sync_time=False) + self.device = InfiniTimeDevice(manager=self.manager, mac_address=self.conf.get_property("last_paired_device"), thread=False) + self.mainloop = glib.MainLoop() + + def start(self): + self.device.connect() + self.scan_for_notifications() + + def stop(self): + self.mainloop.quit() + self.device.disconnect() def scan_for_notifications(self): DBusGMainLoop(set_as_default=True) - bus = dbus.SessionBus() - bus.add_match_string_non_blocking( - "eavesdrop=true, interface='org.freedesktop.Notifications', member='Notify'" - ) - bus.add_message_filter(self.notifications) - mainloop = glib.MainLoop() - mainloop.run() + monitor_bus = dbus.SessionBus(private=True) + try: + dbus_monitor_iface = dbus.Interface(monitor_bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus'), dbus_interface='org.freedesktop.DBus.Monitoring') + dbus_monitor_iface.BecomeMonitor(["interface='org.freedesktop.Notifications', member='Notify'"], 0) + except dbus.exceptions.DBusException as e: + print(e) + return + monitor_bus.add_message_filter(self.notifications) + self.mainloop.run() def notifications(self, bus, message): alert_dict = {} @@ -30,8 +40,10 @@ def notifications(self, bus, message): if isinstance(arg, dbus.Dictionary): if arg["desktop-entry"] == "sm.puri.Chatty": alert_dict["category"] = "SMS" - alert_dict["sender"] = message.get_args_list()[3].split("New message from ")[1] + alert_dict["sender"] = message.get_args_list()[3] alert_dict["message"] = message.get_args_list()[4] alert_dict_empty = not alert_dict if len(alert_dict) > 0: + print(alert_dict) self.device.send_notification(alert_dict) + diff --git a/src/siglo.in b/src/siglo.in index e026324..f412718 100755 --- a/src/siglo.in +++ b/src/siglo.in @@ -17,13 +17,16 @@ gettext.install('siglo', localedir) def main(): p = argparse.ArgumentParser(description="app to sync InfiniTime watch") - p.add_argument('--daemon', '-d', required=False, action='store_true', help="run as a service") + p.add_argument('--start', '-d', required=False, action='store_true', help="start daemon") + p.add_argument('--stop', '-x', required=False, action='store_true', help="stop daemon") args = p.parse_args() + from siglo import daemon + d = daemon.daemon() - if args.daemon: - from siglo import daemon - d = daemon.daemon() - d.scan_for_notifications() + if args.start: + d.start() + elif args.stop: + d.stop() else: import gi @@ -36,3 +39,4 @@ def main(): if __name__ == '__main__': main() + diff --git a/src/window.py b/src/window.py index 8255460..1a125b3 100644 --- a/src/window.py +++ b/src/window.py @@ -26,7 +26,7 @@ def __init__(self, manager, mac, callback): self.device = None def run(self): - self.device = InfiniTimeDevice(manager=self.manager, mac_address=self.mac) + self.device = InfiniTimeDevice(manager=self.manager, mac_address=self.mac, thread=True) self.device.services_done = self.data_received self.device.connect() @@ -59,6 +59,7 @@ class SigloWindow(Gtk.ApplicationWindow): firmware_run = Gtk.Template.Child() firmware_file = Gtk.Template.Child() firmware_run_file = Gtk.Template.Child() + keep_paired_switch = Gtk.Template.Child() # Flasher dfu_stack = Gtk.Template.Child() @@ -77,16 +78,6 @@ def __init__(self, **kwargs): super().__init__(**kwargs) GObject.threads_init() self.full_list = get_quick_deploy_list() - if self.conf.get_property("deploy_type") == "manual": - self.auto_switch_deploy_type = True - self.deploy_type_switch.set_active(True) - else: - self.auto_switch_deploy_type = False - if self.conf.get_property("paired"): - self.auto_switch_paired = True - self.pair_switch.set_active(True) - else: - self.auto_switch_paired = False GObject.signal_new( "flash-signal", self, @@ -95,10 +86,14 @@ def __init__(self, **kwargs): (GObject.TYPE_PYOBJECT,), ) - def destroy_manager(self): - if self.manager: - self.manager.stop() - self.manager = None + def disconnect_paired_device(self): + try: + devices = self.manager.devices() + for d in devices: + if d.mac_address == self.manager.get_mac_address() and d.is_connected(): + d.disconnect() + finally: + self.conf.set_property("paired", "False") def destroy_manager(self): if self.manager: @@ -134,7 +129,7 @@ def make_watch_row(self, name, mac): grid.attach(value_mac, 2, 1, 1, 1) arrow = Gtk.Image.new_from_icon_name("go-next-symbolic", Gtk.IconSize.BUTTON) - grid.attach(arrow, 3, 0, 1, 2) + grid.attach(arrow, 4, 0, 1, 2) row.show_all() return row @@ -156,6 +151,9 @@ def do_scanning(self): if not self.manager: return + if self.conf.get_property("paired"): + self.disconnect_paired_device() + self.depopulate_listbox() self.manager.scan_result = False try: @@ -194,39 +192,6 @@ def populate_assetbox(self): for asset in get_assets_by_tag(self.tag, self.full_list): self.ota_pick_asset_combobox.append_text(asset) - def done_scanning_multi(self, info_prefix): - if self.manager: - scan_result = self.manager.get_scan_result() - self.bt_spinner.set_visible(False) - self.rescan_button.set_visible(True) - if self.manager and scan_result: - info_suffix = "\n[INFO ] Scan Succeeded" - self.populate_listbox() - else: - info_suffix += "\n[INFO ] Scan Failed" - self.scan_fail_box.set_visible(True) - self.main_info.set_text(info_prefix + info_suffix) - - def done_scanning_singleton(self, manager): - self.manager = manager - scan_result = manager.get_scan_result() - print("[INFO ] Single-Device Mode") - if scan_result: - print("[INFO ] Scan Succeeded") - print( - "[INFO ] Got watch {} on {}".format( - manager.get_mac_address(), manager.adapter_name - ) - ) - - if self.deploy_type == "quick": - self.auto_bbox_scan_pass.set_visible(True) - if self.deploy_type == "manual": - self.bbox_scan_pass.set_visible(True) - else: - print("[INFO ] Scan Failed") - self.main_stack.set_visible_child_name("nodevice") - def callback_device_connect(self, data): firmware, battery = data @@ -238,9 +203,17 @@ def on_watches_listbox_row_activated(self, widget, row): mac = row.mac self.current_mac = mac alias = row.alias - thread = ConnectionThread(self.manager, mac, self.callback_device_connect) - thread.daemon = True - thread.start() + + if self.keep_paired_switch.get_active(): + # Start daemon + subprocess.Popen(["systemctl", "--user", "start", "siglo"]) + self.conf.set_property("paired", "True") + + if self.manager is not None: + thread = ConnectionThread(self.manager, mac, self.callback_device_connect) + thread.daemon = True + thread.start() + self.watch_name.set_text(alias) self.watch_address.set_text(mac) self.main_stack.set_visible_child_name("watch") @@ -283,21 +256,6 @@ def rescan_button_clicked(self, widget): def on_bluetooth_settings_clicked(self, widget): subprocess.Popen(["gnome-control-center", "bluetooth"]) - @Gtk.Template.Callback() - def sync_time_button_clicked(self, widget): - if self.manager is not None: - print("Sync Time button clicked...") - device = InfiniTimeDevice( - manager=self.manager, mac_address=self.manager.get_mac_address() - ) - device.connect(sync_time=True) - if device.successful_connection: - self.main_info.set_text("InfiniTime Sync... Success!") - else: - self.main_info.set_text("InfiniTime Sync... Failed!") - self.scan_pass_box.set_visible(False) - self.rescan_button.set_visible(True) - @Gtk.Template.Callback() def ota_file_selected(self, widget): filename = widget.get_filename() @@ -307,18 +265,6 @@ def ota_file_selected(self, widget): self.ota_selection_box.set_visible(False) self.ota_picked_box.set_sensitive(True) - @Gtk.Template.Callback() - def ota_cancel_button_clicked(self, widget): - if self.conf.get_property("deploy_type") == "quick": - self.ota_pick_asset_combobox.remove_all() - self.ota_pick_tag_combobox.remove_all() - self.populate_tagbox() - self.ota_picked_box.set_sensitive(False) - if self.conf.get_property("deploy_type") == "manual": - self.main_info.set_text("Choose another OTA File") - self.ota_picked_box.set_visible(False) - self.ota_selection_box.set_visible(True) - @Gtk.Template.Callback() def firmware_run_file_clicked_cb(self, widget): self.dfu_stack.set_visible_child_name("ok") @@ -403,35 +349,6 @@ def deploy_type_toggled(self, widget): self.conf.set_property("deploy_type", "quick") self.rescan_button.emit("clicked") - @Gtk.Template.Callback() - def pair_switch_toggled(self, widget): - self.conf.set_property("last_paired_device", self.manager.get_mac_address()) - print(self.manager) - if self.conf.get_property("paired") and self.auto_switch_paired == True: - self.auto_switch_paired = False - else: - if not self.conf.get_property("paired"): - self.conf.set_property("paired", "True") - if self.manager is not None: - print("Pairing with", self.manager.get_mac_address()) - device = InfiniTimeDevice( - manager=self.manager, mac_address=self.manager.get_mac_address() - ) - device.connect(sync_time=True) - subprocess.call(["systemctl", "--user", "daemon-reload"]) - subprocess.call(["systemctl", "--user", "restart", "siglo"]) - else: - try: - device = InfiniTimeDevice( - manager=self.manager, mac_address=self.manager.get_mac_address() - ) - device.disconnect() - except dbus.exceptions.DBusException: - raise BluetoothDisabled - finally: - subprocess.call(["systemctl", "--user", "daemon-reload"]) - subprocess.call(["systemctl", "--user", "stop", "siglo"]) - self.conf.set_property("paired", "False") def update_progress_bar(self): self.dfu_progress_bar.set_fraction( @@ -454,7 +371,6 @@ def show_complete(self, success): else: self.main_info.set_text("OTA Update Failed") self.bt_spinner.set_visible(False) - self.sync_time_button.set_visible(True) self.dfu_progress_box.set_visible(False) self.ota_picked_box.set_visible(True) if self.conf.get_property("deploy_type") == "quick": diff --git a/src/window.ui b/src/window.ui index 1a2aff7..c3c8935 100644 --- a/src/window.ui +++ b/src/window.ui @@ -20,17 +20,6 @@ False - - - True - False - False - Sync time and recieve notifications even after app closes - Pair Device (Beta) - True - - -