From 867ded34d9b62a7800ee6ee88e8c7ace19802bb3 Mon Sep 17 00:00:00 2001 From: Jordan Date: Wed, 9 Jun 2021 21:33:08 -0400 Subject: [PATCH] Add update manager capability and revamp system panel to show all moonraker update clients --- ks_includes/KlippyGtk.py | 13 +- ks_includes/printer.py | 4 +- panels/system.py | 212 +++++++++++++++++++++++++-- screen.py | 26 +++- styles/z-bolt/images/information.svg | 14 ++ styles/z-bolt/style.css | 2 + 6 files changed, 249 insertions(+), 22 deletions(-) create mode 100644 styles/z-bolt/images/information.svg diff --git a/ks_includes/KlippyGtk.py b/ks_includes/KlippyGtk.py index 9c49512d..835b5f74 100644 --- a/ks_includes/KlippyGtk.py +++ b/ks_includes/KlippyGtk.py @@ -185,23 +185,20 @@ class KlippyGtk: dialog.connect("response", callback, *args) dialog.get_style_context().add_class("dialog") - grid = Gtk.Grid() - grid.set_size_request(screen.width - 60, -1) - grid.set_vexpand(True) - grid.set_halign(Gtk.Align.CENTER) - grid.set_valign(Gtk.Align.CENTER) - grid.add(content) + box = Gtk.Box() + box.set_size_request(screen.width - 60, 0) + box.set_vexpand(True) content_area = dialog.get_content_area() content_area.set_margin_start(15) content_area.set_margin_end(15) content_area.set_margin_top(15) content_area.set_margin_bottom(15) - content_area.add(grid) + content_area.add(content) dialog.show_all() - return dialog, grid + return dialog def ToggleButtonImage(self, image_name, label, style=False, width_scale=1, height_scale=1): diff --git a/ks_includes/printer.py b/ks_includes/printer.py index 7c514ef9..997accb1 100644 --- a/ks_includes/printer.py +++ b/ks_includes/printer.py @@ -21,8 +21,9 @@ class Printer: } tools = [] - def __init__(self, printer_info, data): + def __init__(self, printer_info, data, state_execute_cb): self.state = "disconnected" + self.state_cb = state_execute_cb self.power_devices = {} def reinit(self, printer_info, data): @@ -145,6 +146,7 @@ class Printer: logging.debug("Adding callback for state: %s" % state) Gdk.threads_add_idle( GLib.PRIORITY_HIGH_IDLE, + self.state_cb, self.state_callbacks[state], prev_state ) diff --git a/panels/system.py b/panels/system.py index d67e1061..6fe2e039 100644 --- a/panels/system.py +++ b/panels/system.py @@ -4,6 +4,7 @@ import os gi.require_version("Gtk", "3.0") from gi.repository import Gtk, Gdk, GLib +from datetime import datetime from ks_includes.KlippyGcodes import KlippyGcodes from ks_includes.screen_panel import ScreenPanel @@ -20,27 +21,30 @@ class SystemPanel(ScreenPanel): restart = self._gtk.ButtonImage('refresh',"\n".join(_('Klipper Restart').split(' ')),'color1') restart.connect("clicked", self.restart_klippy) + restart.set_vexpand(False) firmrestart = self._gtk.ButtonImage('refresh',"\n".join(_('Firmware Restart').split(' ')),'color2') firmrestart.connect("clicked", self.restart_klippy, "firmware") + firmrestart.set_vexpand(False) ks_restart = self._gtk.ButtonImage('refresh',"\n".join(_('Restart Klipper Screen').split(' '))) + ks_restart.set_vexpand(False) ks_restart.connect("clicked", self.restart_ks) reboot = self._gtk.ButtonImage('refresh',_('System\nRestart'),'color3') reboot.connect("clicked", self._screen._confirm_send_action, _("Are you sure you wish to reboot the system?"), "machine.reboot") + reboot.set_vexpand(False) shutdown = self._gtk.ButtonImage('shutdown',_('System\nShutdown'),'color4') shutdown.connect("clicked", self._screen._confirm_send_action, _("Are you sure you wish to shutdown the system?"), "machine.shutdown") + shutdown.set_vexpand(False) info = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) info.set_vexpand(True) info.set_valign(Gtk.Align.CENTER) self.labels['loadavg'] = Gtk.Label("temp") - self.update_system_load() - - self.system_timeout = GLib.timeout_add(1000, self.update_system_load) + #self.system_timeout = GLib.timeout_add(1000, self.update_system_load) self.labels['klipper_version'] = Gtk.Label(_("Klipper Version") + (": %s" % self._screen.printer.get_klipper_version())) @@ -53,8 +57,47 @@ class SystemPanel(ScreenPanel): info.add(self.labels['klipper_version']) info.add(self.labels['ks_version']) + scroll = Gtk.ScrolledWindow() + scroll.set_property("overlay-scrolling", False) + scroll.set_vexpand(True) - grid.attach(info, 0, 0, 5, 2) + infogrid = Gtk.Grid() + infogrid.get_style_context().add_class("system-program-grid") + update_resp = self._screen.apiclient.send_request("machine/update/status") + self.update_status = False + + if update_resp == False: + logging.info("No update manager configured") + else: + self.update_status = update_resp['result'] + vi = update_resp['result']['version_info'] + items = sorted(list(vi)) + i = 0 + for prog in items: + self.labels[prog] = Gtk.Label("") + self.labels[prog].set_hexpand(True) + self.labels[prog].set_halign(Gtk.Align.START) + + self.labels["%s_status" % prog] = self._gtk.Button() + self.labels["%s_status" % prog].set_hexpand(False) + self.labels["%s_status" % prog].connect("clicked", self.update_program, prog) + self.labels["%s_box" % prog] = Gtk.Box() + self.labels["%s_box" % prog].set_hexpand(False) + self.labels["%s_info" % prog] = self._gtk.ButtonImage("information",None, None, .7, .7) + self.labels["%s_info" % prog].connect("clicked", self.show_commit_history, prog) + + self.labels["%s_box" % prog].pack_end(self.labels["%s_status" % prog], True, 0, 0) + logging.info("Updating program: %s " % prog) + self.update_program_info(prog) + + + infogrid.attach(self.labels["%s_box" % prog], 1, i, 1, 1) + infogrid.attach(self.labels[prog], 0, i, 1, 1) + i = i + 1 + + scroll.add(infogrid) + + grid.attach(scroll, 0, 0, 5, 2) grid.attach(restart, 0, 2, 1, 1) grid.attach(firmrestart, 1, 2, 1, 1) grid.attach(ks_restart, 2, 2, 1, 1) @@ -62,16 +105,163 @@ class SystemPanel(ScreenPanel): grid.attach(shutdown, 4, 2, 1, 1) self.content.add(grid) + self._screen.add_subscription(panel_name) - def update_system_load(self): + def activate(self): + self.get_updates() + + def destroy_widget(self, widget, response_id): + widget.destroy() + + def finish_updating(self, widget, response_id): + widget.destroy() + self._screen.set_updating(False) + self.get_updates() + + def get_updates(self): + update_resp = self._screen.apiclient.send_request("machine/update/status") + if update_resp == False: + logging.info("No update manager configured") + else: + self.update_status = update_resp['result'] + vi = update_resp['result']['version_info'] + items = sorted(list(vi)) + for prog in items: + self.update_program_info(prog) + + def process_update(self, action, data): + if action == "notify_update_response": + logging.info("Update: %s" % data) + if 'application' in data and data['application'] == self.update_prog: + self.labels['update_progress'].set_text(self.labels['update_progress'].get_text().strip() + "\n" + + data['message'] + "\n") + adjustment = self.labels['update_scroll'].get_vadjustment() + adjustment.set_value( adjustment.get_upper() - adjustment.get_page_size() ) + adjustment = self.labels['update_scroll'].show_all() + + if data['complete'] == True: + self.update_dialog.set_response_sensitive(Gtk.ResponseType.CANCEL, True) + + def show_commit_history(self, widget, program): _ = self.lang.gettext - lavg = os.getloadavg() - self.labels['loadavg'].set_text( - _("Load Average") + (": %.2f %.2f %.2f" % (lavg[0], lavg[1], lavg[2])) - ) - #TODO: Shouldn't need this - self.system_timeout = GLib.timeout_add(1000, self.update_system_load) + if self.update_status == False or program not in self.update_status['version_info']: + return + + info = self.update_status['version_info'][program] + if info['version'] == info['remote_version']: + return + + buttons = [ + {"name":_("Go Back"), "response": Gtk.ResponseType.CANCEL} + ] + + scroll = Gtk.ScrolledWindow() + scroll.set_hexpand(True) + scroll.set_vexpand(True) + + grid = Gtk.Grid() + grid.set_halign(Gtk.Align.START) + i = 0 + date = "" + for c in info['commits_behind']: + ndate = datetime.fromtimestamp(int(c['date'])).strftime("%b %d") + if date != ndate: + date = ndate + label = Gtk.Label("") + label.set_markup("%s\n" % date) + grid.attach(label, 0, i, 1, 1) + i = i + 1 + + label = Gtk.Label() + label.set_markup("%s\n%s %s %s\n" % (c['subject'], c['author'], _("Commited"),"2 days ago")) + label.set_hexpand(True) + label.set_halign(Gtk.Align.START) + grid.attach(label, 0, i, 1, 1) + i = i + 1 + + scroll.add(grid) + + dialog = self._gtk.Dialog(self._screen, buttons, scroll, self.destroy_widget) + + def update_program(self, widget, program): + if self._screen.is_updating(): + return + + _ = self.lang.gettext + + if self.update_status == False or program not in self.update_status['version_info']: + return + + info = self.update_status['version_info'][program] + logging.info("program: %s" % info) + if "package_count" in info: + if info['package_count'] == 0: + return + else: + if info['version'] == info['remote_version']: + return + + buttons = [ + {"name":_("Finish"), "response": Gtk.ResponseType.CANCEL} + ] + + scroll = Gtk.ScrolledWindow() + scroll.set_hexpand(True) + scroll.set_vexpand(True) + + self.labels['update_progress'] = Gtk.Label("%s %s%s" % (_("Starting update for"), program, _("..."))) + self.labels['update_progress'].set_halign(Gtk.Align.START) + self.labels['update_progress'].set_valign(Gtk.Align.START) + scroll.add(self.labels['update_progress']) + self.labels['update_scroll'] = scroll + + dialog = self._gtk.Dialog(self._screen, buttons, scroll, self.finish_updating) + dialog.set_response_sensitive(Gtk.ResponseType.CANCEL, False) + + self.update_prog = program + self.update_dialog = dialog + + if program in ['klipper','moonraker','system']: + logging.info("Sending machine.update.%s" % program) + self._screen._ws.send_method("machine.update.%s" % program) + else: + logging.info("Sending machine.update.client name: %s" % program) + self._screen._ws.send_method("machine.update.client", {"name": program}) + self._screen.set_updating(True) + + def update_program_info(self, p): + _ = self.lang.gettext + + logging.info("Updating program: %s " % p) + if 'version_info' not in self.update_status or p not in self.update_status['version_info']: + return + + info = self.update_status['version_info'][p] + logging.info("%s: %s" % (p, info)) + if p != "system": + version = (info['full_version_string'] if "full_version_string" in info else info['version']) + + if info['version'] == info['remote_version']: + self.labels[p].set_markup("%s\n%s" % (p, version)) + self.labels["%s_status" % p].set_label(_("Up To Date")) + self.labels["%s_status" % p].set_sensitive(False) + if self.labels["%s_info" % p] in self.labels["%s_box" % p].get_children(): + self.labels["%s_box" % p].remove(self.labels["%s_info" % p]) + else: + self.labels[p].set_markup("%s\n%s -> %s" % (p, version, info['remote_version'])) + self.labels["%s_status" % p].set_label(_("Update")) + self.labels["%s_status" % p].set_sensitive(True) + if not self.labels["%s_info" % p] in self.labels["%s_box" % p].get_children(): + self.labels["%s_box" % p].pack_start(self.labels["%s_info" % p], True, 0, 0) + else: + self.labels[p].set_markup("System") + if info['package_count'] == 0: + self.labels["%s_status" % p].set_label(_("Up To Date")) + self.labels["%s_status" % p].set_sensitive(False) + else: + self.labels["%s_status" % p].set_label(_("Update")) + self.labels["%s_status" % p].set_sensitive(True) def restart_klippy(self, widget, type=None): if type == "firmware": diff --git a/screen.py b/screen.py index 0c039fc9..75de61de 100644 --- a/screen.py +++ b/screen.py @@ -79,6 +79,8 @@ class KlipperScreen(Gtk.Window): rtl_languages = ['he_il'] subscriptions = [] shutdown = True + updating = False + update_queue = [] _ws = None def __init__(self, args, version): @@ -193,7 +195,7 @@ class KlipperScreen(Gtk.Window): 'virtual_sdcard': { 'is_active': False } - }) + }, self.state_execute) self._remove_all_panels() panels = list(self.panels) @@ -439,6 +441,9 @@ class KlipperScreen(Gtk.Window): def is_printing(self): return self.printer.get_state() == "printing" + def is_updating(self): + return self.updating + def _go_to_submenu(self, widget, name): logging.info("#### Go to submenu " + str(name)) #self._remove_current_panel(False) @@ -540,11 +545,26 @@ class KlipperScreen(Gtk.Window): if self.dpms_timeout == None and functions.dpms_loaded == True: self.dpms_timeout = GLib.timeout_add(1000, self.check_dpms_state) + def set_updating(self, updating=False): + if self.updating == True and updating == False: + if len(self.update_queue) > 0: + i = self.update_queue.pop() + self.update_queue = [] + i[0](i[1]) + + self.updating = updating + def show_printer_select(self, widget=None): logging.debug("Saving panel: %s" % self._cur_panels[0]) self.printer_select_prepanel = self._cur_panels[0] self.show_panel("printer_select","printer_select","Printer Select", 2) + def state_execute(self, callback, prev_state): + if self.is_updating(): + self.update_queue.append([callback, prev_state]) + else: + callback(prev_state) + def state_disconnected(self, prev_state): if "printer_select" in self._cur_panels: self.printer_select_callbacks = [self.state_disconnected] @@ -557,7 +577,7 @@ class KlipperScreen(Gtk.Window): for panel in list(self.panels): if panel in ["printer_select","splash_screen"]: continue - del self.panels[panel] + #del self.panels[panel] def state_error(self, prev_state): if "printer_select" in self._cur_panels: @@ -650,6 +670,8 @@ class KlipperScreen(Gtk.Window): self.files.process_update(data) elif action == "notify_metadata_update": self.files.request_metadata(data['filename']) + elif action == "notify_update_response": + logging.info("%s: %s" % (action,data)) elif action == "notify_power_changed": logging.debug("Power status changed: %s", data) self.printer.process_power_update(data) diff --git a/styles/z-bolt/images/information.svg b/styles/z-bolt/images/information.svg new file mode 100644 index 00000000..797e4d56 --- /dev/null +++ b/styles/z-bolt/images/information.svg @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/styles/z-bolt/style.css b/styles/z-bolt/style.css index 19c56096..bc4f3067 100644 --- a/styles/z-bolt/style.css +++ b/styles/z-bolt/style.css @@ -330,6 +330,8 @@ trough { opacity: 0.8; } +.system-program-grid + .warning { background-color: rgba(30, 204, 39, 0.7); }