diff --git a/KlippyGtk.py b/KlippyGtk.py index 82d4df46..b5e5366f 100644 --- a/KlippyGtk.py +++ b/KlippyGtk.py @@ -46,6 +46,23 @@ class KlippyGtk: return Gtk.Image.new_from_pixbuf(pixbuf) + @staticmethod + def ImageFromFile(filename, style=False, width=None, height=None): + if height != -1 or width != -1: + pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(filename, width, height, True) + else: + pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename) + + return Gtk.Image.new_from_pixbuf(pixbuf) + + @staticmethod + def PixbufFromFile(filename, style=False, width=None, height=None): + if height != -1 or width != -1: + pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(filename, width, height, True) + else: + pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename) + + return pixbuf @staticmethod def ProgressBar(style=False): @@ -117,7 +134,7 @@ class KlippyGtk: label = Gtk.Label() label.set_line_wrap(True) - label.set_size_request(250, -1) + label.set_size_request(800, -1) label.set_markup(text) label.get_style_context().add_class("text") table = Gtk.Table(1, 1, False) diff --git a/KlippyWebsocket.py b/KlippyWebsocket.py index b28aa88c..ac669233 100644 --- a/KlippyWebsocket.py +++ b/KlippyWebsocket.py @@ -106,7 +106,6 @@ class KlippyWebsocket(threading.Thread): "params": params, "id": self._req_id } - print(json.dumps(data)) self.ws.send(json.dumps(data)) def on_open(self, ws): diff --git a/files.py b/files.py index 88923262..77c61555 100644 --- a/files.py +++ b/files.py @@ -1,6 +1,8 @@ import logging import asyncio import json +import os +import base64 import gi gi.require_version("Gtk", "3.0") @@ -17,12 +19,15 @@ class KlippyFiles: def __init__(self, screen): self._screen = screen self.timeout = GLib.timeout_add(2000, self.ret_files) - #GLib.idle_add(self.ret_files) + + if not os.path.exists('/tmp/.KS-thumbnails'): + os.makedirs('/tmp/.KS-thumbnails') + GLib.idle_add(self.ret_files) def _callback(self, result, method, params): if method == "server.files.list": - if isinstance(result['result'],list): + if "result" in result and isinstance(result['result'],list): newfiles = [] deletedfiles = self.filelist.copy() for item in result['result']: @@ -36,6 +41,7 @@ class KlippyFiles: "size": item['size'], "modified": item['modified'] } + self.update_metadata(item['filename']) if len(self.callbacks) > 0 and (len(newfiles) > 0 or len(deletedfiles) > 0): logger.debug("Running callbacks...") @@ -53,14 +59,27 @@ class KlippyFiles: #await asyncio.gather(files) #files = [GLib.idle_add(self.ret_file_data, file) for file in self.files] - elif method == "get_file_metadata": - print("Got metadata for %s" % (result['result']['filename'])) - #print(json.dumps(result, indent=4)) + elif method == "server.files.metadata": + if "error" in result.keys(): + logger.debug("Error in getting metadata for %s" %(params['filename'])) + GLib.timeout_add(2000, self._screen._ws.klippy.get_file_metadata, params['filename'], self._callback) + return + + logger.debug("Got metadata for %s" % (result['result']['filename'])) + for x in result['result']: + self.files[params['filename']][x] = result['result'][x] + if "thumbnails" in self.files[params['filename']]: + self.files[params['filename']]['thumbnails'].sort(key=lambda x: x['size'], reverse=True) + + for thumbnail in self.files[params['filename']]['thumbnails']: + f = open("/tmp/.KS-thumbnails/%s-%s" % (params['filename'], thumbnail['size']), "wb") + f.write(base64.b64decode(thumbnail['data'])) + f.close() + for cb in self.callbacks: + cb([], [], [params['filename']]) def add_file_callback(self, callback): self.callbacks.append(callback) - print("Callbacks...") - print(self.callbacks) def ret_files(self): self._screen._ws.klippy.get_file_list(self._callback) @@ -81,3 +100,6 @@ class KlippyFiles: def add_file(self, file_name, size, modified, old_file_name = None): print(file_name) + + def update_metadata(self, filename): + self._screen._ws.klippy.get_file_metadata(filename, self._callback) diff --git a/panels/print.py b/panels/print.py index a8ee6b38..c50e1606 100644 --- a/panels/print.py +++ b/panels/print.py @@ -14,6 +14,8 @@ logger = logging.getLogger("KlipperScreen.PrintPanel") class PrintPanel(ScreenPanel): def initialize(self, panel_name): + self.labels['files'] = {} + scroll = Gtk.ScrolledWindow() scroll.set_property("overlay-scrolling", False) scroll.set_vexpand(True) @@ -32,7 +34,7 @@ class PrintPanel(ScreenPanel): bar.set_margin_end(5) refresh = KlippyGtk.ButtonImage('refresh', None, None, 60, 60) refresh.connect("clicked", self.reload_files) - bar.add(refresh) + #bar.add(refresh) back = KlippyGtk.ButtonImage('back', None, None, 60, 60) back.connect("clicked", self._screen._menu_go_back) @@ -40,7 +42,6 @@ class PrintPanel(ScreenPanel): box.pack_end(bar, False, False, 0) - self.labels['filelist'] = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.labels['filelist'].set_vexpand(True) @@ -62,7 +63,6 @@ class PrintPanel(ScreenPanel): return def add_file(self, filename): - fileinfo = self._screen.files.get_file_info(filename) if fileinfo == None: return @@ -72,17 +72,15 @@ class PrintPanel(ScreenPanel): name = Gtk.Label() - n = 50 - name.set_markup("%s" % ("\n".join([filename[i:i+n] for i in range(0, len(filename), n)]))) + #n = 50 + #name.set_markup("%s" % ("\n".join([filename[i:i+n] for i in range(0, len(filename), n)]))) + name.set_markup("%s" % (filename)) name.set_hexpand(True) name.set_halign(Gtk.Align.START) info = Gtk.Label("Uploaded: blah - Size: blah") info.set_halign(Gtk.Align.START) - info.set_markup("Uploaded: %s - Size: %s" % ( - datetime.fromtimestamp(fileinfo['modified']).strftime("%Y-%m-%d %H:%M"), - humanize.naturalsize(fileinfo['size']) - )) + info.set_markup(self.get_file_info_str(filename)) labels = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) labels.add(name) labels.add(info) @@ -102,7 +100,13 @@ class PrintPanel(ScreenPanel): file.set_margin_bottom(1) file.set_hexpand(True) file.set_vexpand(False) - file.add(KlippyGtk.Image("file", False, 35, 35)) + + icon = KlippyGtk.Image("file", False, 100, 100) + pixbuf = self.get_file_image(filename) + if pixbuf != None: + icon.set_from_pixbuf(pixbuf) + + file.add(icon) file.add(labels) file.add(actions) frame.add(file) @@ -111,10 +115,75 @@ class PrintPanel(ScreenPanel): files = sorted(self.files) pos = files.index(filename) + self.labels['files'][filename] = { + "icon": icon, + "info": info, + "name": name + } + self.labels['filelist'].insert_row(pos) self.labels['filelist'].attach(self.files[filename], 0, pos, 1, 1) self.labels['filelist'].show_all() + def get_file_image(self, filename, width=100, height=100): + fileinfo = self._screen.files.get_file_info(filename) + if fileinfo == None: + return None + + if "thumbnails" in fileinfo and len(fileinfo["thumbnails"]) > 0: + thumbnail = fileinfo['thumbnails'][0] + return KlippyGtk.PixbufFromFile("/tmp/.KS-thumbnails/%s-%s" % (fileinfo['filename'], thumbnail['size']), + None, width, height) + return None + + + def get_file_info_str(self, filename): + fileinfo = self._screen.files.get_file_info(filename) + if fileinfo == None: + return + + return "Uploaded: %s - Size: %s - Print Time: %s" % ( + datetime.fromtimestamp(fileinfo['modified']).strftime("%Y-%m-%d %H:%M"), + humanize.naturalsize(fileinfo['size']), + self.get_print_time(filename) + ) + + def get_print_time (self, filename): + fileinfo = self._screen.files.get_file_info(filename) + if fileinfo == None: + return + + if "estimated_time" in fileinfo: + print_time = fileinfo['estimated_time'] + print_str = "" + + # Figure out how many days + print_val = int(print_time/86400) + if print_val > 0: + print_str = "%sd " % print_val + + # Take remainder from days and divide by hours + print_val = int((print_time%86400)/3600) + if print_val > 0: + print_str = "%s%sh " % (print_str, print_val) + + print_val = int(((print_time%86400)%3600)/60) + print_str = "%s%sm" % (print_str, print_val) + return print_str + return "Unavailable" + + def update_file(self, filename): + if filename not in self.labels['files']: + return + + print("Updating file %s" % filename) + self.labels['files'][filename]['info'].set_markup(self.get_file_info_str(filename)) + + # Update icon + pixbuf = self.get_file_image(filename) + if pixbuf != None: + self.labels['files'][filename]['icon'].set_from_pixbuf(pixbuf) + def delete_file(self, filename): files = sorted(self.files) pos = files.index(filename) @@ -131,31 +200,59 @@ class PrintPanel(ScreenPanel): #TODO: Change priority on this GLib.idle_add(self.add_file, file) - def _callback(self, newfiles, deletedfiles): + def _callback(self, newfiles, deletedfiles, updatedfiles=[]): logger.debug("newfiles: %s", newfiles) for file in newfiles: self.add_file(file) logger.debug("deletedfiles: %s", deletedfiles) for file in deletedfiles: self.delete_file(file) + logger.debug("updatefiles: %s", updatedfiles) + for file in updatedfiles: + self.update_file(file) def confirm_print(self, widget, filename): - dialog = KlippyGtk.ConfirmDialog( - self._screen, - "Are you sure you want to print %s?" % (filename), - [ - { - "name": "Print", - "response": Gtk.ResponseType.OK - }, - { - "name": "Cancel", - "response": Gtk.ResponseType.CANCEL - } - ], - self.confirm_print_response, - filename - ) + dialog = Gtk.Dialog() + #TODO: Factor other resolutions in + dialog.set_default_size(984, 580) + dialog.set_resizable(False) + dialog.set_transient_for(self._screen) + dialog.set_modal(True) + + dialog.add_button(button_text="Print", response_id=Gtk.ResponseType.OK) + dialog.add_button(button_text="Cancel", response_id=Gtk.ResponseType.CANCEL) + + dialog.connect("response", self.confirm_print_response, filename) + dialog.get_style_context().add_class("dialog") + + 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) + + label = Gtk.Label() + label.set_line_wrap(True) + label.set_size_request(800, -1) + label.set_markup("Are you sure you want to print %s?\n\n" % (filename)) + label.get_style_context().add_class("text") + + grid = Gtk.Grid() + grid.add(label) + + pixbuf = self.get_file_image(filename, 400, 350) + if pixbuf != None: + image = Gtk.Image.new_from_pixbuf(pixbuf) + + grid.attach_next_to(image, label, Gtk.PositionType.BOTTOM, 1, 3) + + #table.attach(label, 0, 1, 0, 1, Gtk.AttachOptions.SHRINK | Gtk.AttachOptions.FILL) + grid.set_vexpand(True) + grid.set_halign(Gtk.Align.CENTER) + grid.set_valign(Gtk.Align.CENTER) + content_area.add(grid) + + dialog.show_all() def confirm_print_response(self, widget, response_id, filename): widget.destroy() diff --git a/printer.py b/printer.py index 1ffdf472..499b9664 100644 --- a/printer.py +++ b/printer.py @@ -42,6 +42,20 @@ class Printer: for y in data[x]: self.data[x][y] = data[x][y] + if "heater_bed" in data: + d = data["heater_bed"] + if "target" in d: + self.set_dev_stat("heater_bed", "target", d["target"]) + if "temperature" in d: + self.set_dev_stat("heater_bed", "temperature", d["temperature"]) + for x in self.get_tools(): + if x in data: + d = data[x] + if "target" in d: + self.set_dev_stat(x, "target", d["target"]) + if "temperature" in d: + self.set_dev_stat(x, "temperature", d["temperature"]) + def get_config_section_list(self): return list(self.config) diff --git a/screen.py b/screen.py index f5703721..e97d2ce5 100644 --- a/screen.py +++ b/screen.py @@ -313,52 +313,35 @@ class KlipperScreen(Gtk.Window): return def _websocket_callback(self, action, data): - #print(json.dumps(data, indent=2)) + #print(json.dumps([action, data], indent=2)) - self.printer.process_update(data) - - if "webhooks" in data: - print(data) - - - if "webhooks" in data and "state" in data['webhooks']: - if data['webhooks']['state'] == "shutdown": - logger.info("### Going to disconnected state") - self.printer_initializing("Klipper has shutdown") - elif data['webhooks']['state'] == "ready": - logger.info("### Going to ready state") - self.printer_ready() - else: - active = self.printer.get_stat('virtual_sdcard','is_active') - paused = self.printer.get_stat('pause_resume','is_paused') - if "job_status" in self._cur_panels: - if active == False and paused == False: + if action == "notify_status_update": + self.printer.process_update(data) + if "webhooks" in data and "klippy_state" in data['webhooks']: + if data['webhooks']['klippy_state'] == "shutdown": + logger.info("### Going to disconnected state") + self.printer_initializing("Klipper has shutdown") + elif data['webhooks']['state'] == "ready": + logger.info("### Going to ready state") self.printer_ready() else: - if active == True or paused == True: - self.printer_printing() - + active = self.printer.get_stat('virtual_sdcard','is_active') + paused = self.printer.get_stat('pause_resume','is_paused') + if "job_status" in self._cur_panels: + if active == False and paused == False: + self.printer_ready() + else: + if active == True or paused == True: + self.printer_printing() if action == "notify_filelist_changed": - logger.DEBUG("Filelist changed: %s", json.dumps(data,indent=2)) + logger.debug("Filelist changed: %s", json.dumps(data,indent=2)) #self.files.add_file() - elif action == "notify_status_update": - if "heater_bed" in data: - d = data["heater_bed"] - if "target" in d: - self.printer.set_dev_stat("heater_bed", "target", d["target"]) - if "temperature" in d: - self.printer.set_dev_stat("heater_bed", "temperature", d["temperature"]) - for x in self.printer.get_tools(): - if x in data: - d = data[x] - if "target" in d: - self.printer.set_dev_stat(x, "target", d["target"]) - if "temperature" in d: - self.printer.set_dev_stat(x, "temperature", d["temperature"]) + elif action == "notify_metadata_update": + self.files.update_metadata(data['filename']) - for sub in self.subscriptions: - self.panels[sub].process_update(data) + for sub in self.subscriptions: + self.panels[sub].process_update(data) def _send_action(self, widget, method, params): @@ -389,11 +372,13 @@ class KlipperScreen(Gtk.Window): #TODO: Check that we get good data data = json.loads(r.content) self.printer_config = data['result']['status']['configfile']['config'] - logger.debug("Printer config: %s" % json.dumps(self.printer_config, indent=2)) - self.printer = Printer(data['result']['status']) + #logger.debug("Printer config: %s" % json.dumps(self.printer_config, indent=2)) - logger.debug("Config sections: %s", self.printer.get_config_section_list()) - logger.debug("Bed_screws: %s", self.printer.get_config_section("bed_screws")) + # Reinitialize printer, in case the printer was shut down and anything has changed. + self.printer.__init__(data['result']['status']) + + #logger.debug("Config sections: %s", self.printer.get_config_section_list()) + #logger.debug("Bed_screws: %s", self.printer.get_config_section("bed_screws")) self.show_panel('main_panel', "MainPanel", 2, items=self._config['mainmenu'], extrudercount=self.printer.get_extruder_count())