def __init__(self, args, version): self.dpms_timeout = None self.version = version configfile = os.path.normpath(os.path.expanduser(args.configfile)) self.lang = gettext.translation('KlipperScreen', localedir='ks_includes/locales', fallback=True) self._config = KlipperScreenConfig(configfile, self.lang, self) self.wifi = WifiManager() self.wifi.start() logging.debug("OS Language: %s" % os.getenv('LANG')) self.lang_ltr = True for lang in self.rtl_languages: if os.getenv('LANG').lower().startswith(lang): self.lang_ltr = False Gtk.Widget.set_default_direction(Gtk.TextDirection.RTL) logging.debug("Enabling RTL mode") break _ = self.lang.gettext Gtk.Window.__init__(self) self.width = self._config.get_main_config().getint("width", Gdk.Screen.get_width(Gdk.Screen.get_default())) self.height = self._config.get_main_config().getint("height", Gdk.Screen.get_height(Gdk.Screen.get_default())) self.set_default_size(self.width, self.height) self.set_resizable(False) logging.info("Screen resolution: %sx%s" % (self.width, self.height)) self.gtk = KlippyGtk(self.width, self.height) self.init_style() self.printer_initializing(_("Initializing")) self.set_screenblanking_timeout(self._config.get_main_config_option('screen_blanking')) # Move mouse to 0,0 os.system("/usr/bin/xdotool mousemove 0 0") # Change cursor to blank if self._config.get_main_config().getboolean("show_cursor", fallback=False): self.get_window().set_cursor(Gdk.Cursor(Gdk.CursorType.ARROW)) else: self.get_window().set_cursor(Gdk.Cursor(Gdk.CursorType.BLANK_CURSOR)) printers = self._config.get_printers() logging.debug("Printers: %s" % printers) if len(printers) == 1: pname = list(self._config.get_printers()[0])[0] self.connect_printer(pname) else: self.show_panel("printer_select","printer_select","Printer Select", 2)
def __init__(self, screen, title, back=True): super().__init__(screen, title, back) self.show_add = False self.networks = {} self.interface = None self.prev_network = None self.update_timeout = None self.network_interfaces = netifaces.interfaces() self.wireless_interfaces = [ iface for iface in self.network_interfaces if iface.startswith('w') ] self.wifi = None if len(self.wireless_interfaces) > 0: logging.info( f"Found wireless interfaces: {self.wireless_interfaces}") self.wifi = WifiManager(self.wireless_interfaces[0])
def __init__(self, args, version): self.dpms_timeout = None self.version = version configfile = os.path.normpath(os.path.expanduser(args.configfile)) self._config = KlipperScreenConfig(configfile, self) self.lang = self._config.get_lang() self.network_interfaces = netifaces.interfaces() self.wireless_interfaces = [ int for int in self.network_interfaces if int.startswith('w') ] self.wifi = None if len(self.wireless_interfaces) > 0: logging.info("Found wireless interfaces: %s" % self.wireless_interfaces) self.wifi = WifiManager(self.wireless_interfaces[0]) logging.debug("OS Language: %s" % os.getenv('LANG')) self.lang_ltr = True for lang in self.rtl_languages: if os.getenv('LANG').lower().startswith(lang): self.lang_ltr = False Gtk.Widget.set_default_direction(Gtk.TextDirection.RTL) logging.debug("Enabling RTL mode") break _ = self.lang.gettext Gtk.Window.__init__(self) self.width = self._config.get_main_config().getint( "width", Gdk.Screen.get_width(Gdk.Screen.get_default())) self.height = self._config.get_main_config().getint( "height", Gdk.Screen.get_height(Gdk.Screen.get_default())) self.set_default_size(self.width, self.height) self.set_resizable(False) logging.info("Screen resolution: %sx%s" % (self.width, self.height)) self.theme = self._config.get_main_config_option('theme') self.gtk = KlippyGtk(self, self.width, self.height, self.theme) self.keyboard_height = self.gtk.get_keyboard_height() self.init_style() self.base_panel = BasePanel(self, "Base Panel", False) self.add(self.base_panel.get()) self.show_all() self.base_panel.activate() self.printer_initializing(_("Initializing")) self.set_screenblanking_timeout( self._config.get_main_config_option('screen_blanking')) # Move mouse to 0,0 os.system("/usr/bin/xdotool mousemove 0 0") # Change cursor to blank if self._config.get_main_config().getboolean("show_cursor", fallback=False): self.get_window().set_cursor(Gdk.Cursor(Gdk.CursorType.ARROW)) else: self.get_window().set_cursor( Gdk.Cursor(Gdk.CursorType.BLANK_CURSOR)) printers = self._config.get_printers() logging.debug("Printers: %s" % printers) if len(printers) == 1: pname = list(self._config.get_printers()[0])[0] self.connect_printer(pname) else: self.show_panel("printer_select", "printer_select", "Printer Select", 2)
class KlipperScreen(Gtk.Window): """ Class for creating a screen for Klipper via HDMI """ _cur_panels = [] bed_temp_label = None connecting = False connected_printer = None currentPanel = None files = None filename = "" keyboard = None keyboard_height = 200 last_update = {} load_panel = {} number_tools = 1 panels = {} popup_message = None printer = None printer_select_callbacks = [] printer_select_prepanel = None rtl_languages = ['he_il'] subscriptions = [] shutdown = True _ws = None def __init__(self, args, version): self.dpms_timeout = None self.version = version configfile = os.path.normpath(os.path.expanduser(args.configfile)) self.lang = gettext.translation('KlipperScreen', localedir='ks_includes/locales', fallback=True) self._config = KlipperScreenConfig(configfile, self.lang, self) self.wifi = WifiManager() self.wifi.start() logging.debug("OS Language: %s" % os.getenv('LANG')) self.lang_ltr = True for lang in self.rtl_languages: if os.getenv('LANG').lower().startswith(lang): self.lang_ltr = False Gtk.Widget.set_default_direction(Gtk.TextDirection.RTL) logging.debug("Enabling RTL mode") break _ = self.lang.gettext Gtk.Window.__init__(self) self.width = self._config.get_main_config().getint("width", Gdk.Screen.get_width(Gdk.Screen.get_default())) self.height = self._config.get_main_config().getint("height", Gdk.Screen.get_height(Gdk.Screen.get_default())) self.set_default_size(self.width, self.height) self.set_resizable(False) logging.info("Screen resolution: %sx%s" % (self.width, self.height)) self.gtk = KlippyGtk(self.width, self.height) self.init_style() self.printer_initializing(_("Initializing")) self.set_screenblanking_timeout(self._config.get_main_config_option('screen_blanking')) # Move mouse to 0,0 os.system("/usr/bin/xdotool mousemove 0 0") # Change cursor to blank if self._config.get_main_config().getboolean("show_cursor", fallback=False): self.get_window().set_cursor(Gdk.Cursor(Gdk.CursorType.ARROW)) else: self.get_window().set_cursor(Gdk.Cursor(Gdk.CursorType.BLANK_CURSOR)) printers = self._config.get_printers() logging.debug("Printers: %s" % printers) if len(printers) == 1: pname = list(self._config.get_printers()[0])[0] self.connect_printer(pname) else: self.show_panel("printer_select","printer_select","Printer Select", 2) def connect_printer_widget(self, widget, name): self.connect_printer(name) def connect_printer(self, name): _ = self.lang.gettext if self.connected_printer == name: if self.printer_select_prepanel != None: self.show_panel(self.printer_select_prepanel, "","", 2) self.printer_select_prepanel = None while len(self.printer_select_callbacks) > 0: i = self.printer_select_callbacks.pop(0) i() return self.printer_select_callbacks = [] self.printer_select_prepanel = None if self.files is not None: self.files.stop() self.files = None for printer in self._config.get_printers(): pname = list(printer)[0] if pname != name: continue data = printer[pname] break if self._ws is not None: self._ws.close() self.connecting = True logging.info("Connecting to printer: %s" % name) self.apiclient = KlippyRest(data["moonraker_host"], data["moonraker_port"], data["moonraker_api_key"]) self.printer = Printer({ "software_version": "Unknown" }, { 'configfile': { 'config': {} }, 'print_stats': { 'state': 'disconnected' }, 'virtual_sdcard': { 'is_active': False } }) self._remove_all_panels() panels = list(self.panels) if len(self.subscriptions) > 0: self.subscriptions = [] for panel in panels: del self.panels[panel] self.printer_initializing(_("Connecting to %s") % name) self.printer.set_callbacks({ "disconnected": self.state_disconnected, "error": self.state_error, "paused": self.state_paused, "printing": self.state_printing, "ready": self.state_ready, "startup": self.state_startup, "shutdown": self.state_shutdown }) powerdevs = self.apiclient.send_request("machine/device_power/devices") logging.debug("Found power devices: %s" % powerdevs) if powerdevs != False: self.printer.configure_power_devices(powerdevs['result']) self.panels['splash_screen'].show_restart_buttons() self._ws = KlippyWebsocket(self, { "on_connect": self.init_printer, "on_message": self._websocket_callback, "on_close": self.printer_initializing }, data["moonraker_host"], data["moonraker_port"] ) self._ws.initial_connect() self.connecting = False self.files = KlippyFiles(self) self.files.start() self.connected_printer = name logging.debug("Connected to printer: %s" % name) def ws_subscribe(self): requested_updates = { "objects": { "bed_mesh": ["profile_name","mesh_max","mesh_min","probed_matrix"], "configfile": ["config"], "fan": ["speed"], "gcode_move": ["extrude_factor","gcode_position","homing_origin","speed_factor"], "idle_timeout": ["state"], "pause_resume": ["is_paused"], "print_stats": ["print_duration","total_duration","filament_used","filename","state","message"], "toolhead": ["homed_axes","estimated_print_time","print_time","position","extruder"], "virtual_sdcard": ["file_position","is_active","progress"], "webhooks": ["state","state_message"] } } for extruder in self.printer.get_tools(): requested_updates['objects'][extruder] = ["target","temperature","pressure_advance","smooth_time"] for h in self.printer.get_heaters(): requested_updates['objects'][h] = ["target","temperature"] self._ws.klippy.object_subscription(requested_updates) def _load_panel(self, panel, *args): if not panel in self.load_panel: logging.debug("Loading panel: %s" % panel) panel_path = os.path.join(os.path.dirname(__file__), 'panels', "%s.py" % panel) logging.info("Panel path: %s" % panel_path) if not os.path.exists(panel_path): msg = f"Panel {panel} does not exist" logging.info(msg) raise Exception(msg) module = importlib.import_module("panels.%s" % panel) if not hasattr(module, "create_panel"): msg = f"Cannot locate create_panel function for {panel}" logging.info(msg) raise Exception(msg) self.load_panel[panel] = getattr(module, "create_panel") try: return self.load_panel[panel](*args) except Exception: msg = f"Unable to create panel {panel}" logging.exception(msg) raise Exception(msg) def show_panel(self, panel_name, type, title, remove=None, pop=True, **kwargs): if panel_name not in self.panels: self.panels[panel_name] = self._load_panel(type, self, title) try: if kwargs != {}: self.panels[panel_name].initialize(panel_name, **kwargs) else: self.panels[panel_name].initialize(panel_name) except: del self.panels[panel_name] logging.exception("Unable to load panel %s" % type) self.show_error_modal("Unable to load panel %s" % type) return if hasattr(self.panels[panel_name],"process_update"): self.panels[panel_name].process_update("notify_status_update", self.printer.get_data()) try: if remove == 2: self._remove_all_panels() elif remove == 1: self._remove_current_panel(pop) logging.debug("Attaching panel %s" % panel_name) self.add(self.panels[panel_name].get()) self.show_all() if hasattr(self.panels[panel_name],"process_update"): self.panels[panel_name].process_update("notify_status_update", self.printer.get_updates()) if hasattr(self.panels[panel_name],"activate"): self.panels[panel_name].activate() self.show_all() except: logging.exception("Error attaching panel") self._cur_panels.append(panel_name) logging.debug("Current panel hierarchy: %s", str(self._cur_panels)) def show_popup_message(self, message): if self.popup_message != None: self.close_popup_message() box = Gtk.Box() box.get_style_context().add_class("message_popup") box.set_size_request(self.width, self.gtk.get_header_size()) label = Gtk.Label() if "must home axis first" in message.lower(): message = "Must home all axis first." label.set_text(message) close = Gtk.Button.new_with_label("X") close.set_can_focus(False) close.props.relief = Gtk.ReliefStyle.NONE close.connect("clicked", self.close_popup_message) box.pack_start(label, True, True, 0) box.pack_end(close, False, False, 0) box.set_halign(Gtk.Align.CENTER) cur_panel = self.panels[self._cur_panels[-1]] for i in ['back','estop','home']: if i in cur_panel.control: cur_panel.control[i].set_sensitive(False) cur_panel.get().put(box, 0,0) self.show_all() self.popup_message = box GLib.timeout_add(10000, self.close_popup_message) return False def close_popup_message(self, widget=None): if self.popup_message == None: return cur_panel = self.panels[self._cur_panels[-1]] for i in ['back','estop','home']: if i in cur_panel.control: cur_panel.control[i].set_sensitive(True) cur_panel.get().remove(self.popup_message) self.popup_message = None self.show_all() def show_error_modal(self, err): _ = self.lang.gettext logging.exception("Showing error modal: %s", err) buttons = [ {"name":_("Go Back"),"response": Gtk.ResponseType.CANCEL} ] label = Gtk.Label() label.set_markup(("%s \n\n" % err) + _("Check /tmp/KlipperScreen.log for more information.\nPlease submit an issue on GitHub for help.")) label.set_hexpand(True) label.set_halign(Gtk.Align.CENTER) label.set_line_wrap(True) label.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR) dialog = self.gtk.Dialog(self, buttons, label, self.error_modal_response) def error_modal_response(self, widget, response_id): widget.destroy() def init_style(self): style_provider = Gtk.CssProvider() css = open(klipperscreendir + "/styles/style.css") css_data = css.read() css.close() css_data = css_data.replace("KS_FONT_SIZE",str(self.gtk.get_font_size())) style_provider = Gtk.CssProvider() style_provider.load_from_data(css_data.encode()) Gtk.StyleContext.add_provider_for_screen( Gdk.Screen.get_default(), style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION ) def is_printing(self): return self.printer.get_state() == "printing" def _go_to_submenu(self, widget, name): logging.info("#### Go to submenu " + str(name)) #self._remove_current_panel(False) # Find current menu item panels = list(self._cur_panels) if "main_panel" in self._cur_panels: menu = "__main" elif "splash_screen" in self._cur_panels: menu = "__splashscreen" else: menu = "__print" logging.info("#### Menu " + str(menu)) disname = self._config.get_menu_name(menu, name) menuitems = self._config.get_menu_items(menu, name) if len(menuitems) == 0: logging.info("No items in menu, returning.") return self.show_panel(self._cur_panels[-1] + '_' + name, "menu", disname, 1, False, display_name=disname, items=menuitems) def _remove_all_panels(self): while len(self._cur_panels) > 0: self._remove_current_panel(True, False) self.show_all() def _remove_current_panel(self, pop=True, show=True): if len(self._cur_panels) > 0: self.remove(self.panels[self._cur_panels[-1]].get()) if pop == True: self._cur_panels.pop() if len(self._cur_panels) > 0: self.add(self.panels[self._cur_panels[-1]].get()) if hasattr(self.panels[self._cur_panels[-1]], "process_update"): self.panels[self._cur_panels[-1]].process_update("notify_status_update", self.printer.get_updates()) if show == True: self.show_all() def _menu_go_back (self, widget=None): logging.info("#### Menu go back") self.remove_keyboard() self._remove_current_panel() def _menu_go_home(self): logging.info("#### Menu go home") self.remove_keyboard() while len(self._cur_panels) > 1: self._remove_current_panel() def add_subscription (self, panel_name): add = True for sub in self.subscriptions: if sub == panel_name: return self.subscriptions.append(panel_name) def remove_subscription (self, panel_name): for i in range(len(self.subscriptions)): if self.subscriptions[i] == panel_name: self.subscriptions.pop(i) return def check_dpms_state(self): state = functions.get_DPMS_state() if state == functions.DPMS_State.Off and "screensaver" not in self._cur_panels: logging.info("### Creating screensaver panel") self.show_panel("screensaver", "screensaver", "Screen Saver", 1, False) elif state == functions.DPMS_State.On and "screensaver" in self._cur_panels: logging.info("### Remove screensaver panel") self._menu_go_back() return True def set_screenblanking_timeout(self, time): # Disable screen blanking os.system("xset -display :0 s off") os.system("xset -display :0 s noblank") if functions.dpms_loaded == False: logging.info("DPMS functions not loaded. Unable to protect on button click when DPMS is enabled.") logging.debug("Changing power save to: %s" % time) if time == "off": if self.dpms_timeout != None: GLib.source_remove(self.dpms_timeout) self.dpms_timeout = None os.system("xset -display :0 -dpms") return time = int(time) if time < 0: return os.system("xset -display :0 dpms 0 %s 0" % time) if self.dpms_timeout == None and functions.dpms_loaded == True: self.dpms_timeout = GLib.timeout_add(1000, self.check_dpms_state) 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_disconnected(self): if "printer_select" in self._cur_panels: self.printer_select_callbacks = [self.state_disconnected] return _ = self.lang.gettext logging.debug("### Going to disconnected") self.printer_initializing(_("Klipper has disconnected")) def state_error(self): if "printer_select" in self._cur_panels: self.printer_select_callbacks = [self.state_error] return _ = self.lang.gettext msg = self.printer.get_stat("webhooks","state_message") if "FIRMWARE_RESTART" in msg: self.printer_initializing( _("Klipper has encountered an error.\nIssue a FIRMWARE_RESTART to attempt fixing the issue.") ) elif "micro-controller" in msg: self.printer_initializing( _("Klipper has encountered an error with the micro-controller.\nPlease recompile and flash.") ) else: self.printer_initializing( _("Klipper has encountered an error.") ) def state_paused(self): if "job_status" not in self._cur_panels: self.printer_printing() def state_printing(self): if "printer_select" in self._cur_panels: self.printer_select_callbacks = [self.state_printing] return if "job_status" not in self._cur_panels: self.printer_printing() def state_ready(self): if "printer_select" in self._cur_panels: self.printer_select_callbacks = [self.state_ready] return # Do not return to main menu if completing a job, timeouts/user input will return if "job_status" in self._cur_panels or "main_menu" in self._cur_panels: return self.printer_ready() def state_startup(self): if "printer_select" in self._cur_panels: self.printer_select_callbacks = [self.state_startup] return _ = self.lang.gettext self.printer_initializing(_("Klipper is attempting to start")) def state_shutdown(self): if "printer_select" in self._cur_panels: self.printer_select_callbacks = [self.state_shutdown] return _ = self.lang.gettext self.printer_initializing(_("Klipper has shutdown")) def _websocket_callback(self, action, data): _ = self.lang.gettext if self.connecting == True: return if action == "notify_klippy_disconnected": logging.debug("Received notify_klippy_disconnected") self.printer.change_state("disconnected") return elif action == "notify_klippy_ready": self.printer.change_state("ready") elif action == "notify_status_update" and self.printer.get_state() != "shutdown": self.printer.process_update(data) elif action == "notify_filelist_changed": logging.debug("Filelist changed: %s", json.dumps(data,indent=2)) #self.files.add_file() elif action == "notify_metadata_update": self.files.request_metadata(data['filename']) elif action == "notify_power_changed": logging.debug("Power status changed: %s", data) self.printer.process_power_update(data) elif self.printer.get_state() not in ["error","shutdown"] and action == "notify_gcode_response": if "Klipper state: Shutdown" in data: logging.debug("Shutdown in gcode response, changing state to shutdown") self.printer.change_state("shutdown") if not (data.startswith("B:") and re.search(r'B:[0-9\.]+\s/[0-9\.]+\sT[0-9]+:[0-9\.]+', data)): if data.startswith("!! "): self.show_popup_message(data[3:]) logging.debug(json.dumps([action, data], indent=2)) if self._cur_panels[-1] in self.subscriptions: self.panels[self._cur_panels[-1]].process_update(action, data) def _confirm_send_action(self, widget, text, method, params={}): _ = self.lang.gettext buttons = [ {"name":_("Continue"), "response": Gtk.ResponseType.OK}, {"name":_("Cancel"),"response": Gtk.ResponseType.CANCEL} ] try: env = Environment(extensions=["jinja2.ext.i18n"]) env.install_gettext_translations(self.lang) j2_temp = env.from_string(text) text = j2_temp.render() except: logging.debug("Error parsing jinja for confirm_send_action") label = Gtk.Label() label.set_markup(text) label.set_hexpand(True) label.set_halign(Gtk.Align.CENTER) label.set_line_wrap(True) label.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR) dialog = self.gtk.Dialog(self, buttons, label, self._confirm_send_action_response, method, params) def _confirm_send_action_response(self, widget, response_id, method, params): if response_id == Gtk.ResponseType.OK: self._send_action(widget, method, params) widget.destroy() def _send_action(self, widget, method, params): self._ws.send_method(method, params) def printer_initializing(self, text=None): self.shutdown = True self.close_popup_message() self.show_panel('splash_screen',"splash_screen", "Splash Screen", 2) if text != None: self.panels['splash_screen'].update_text(text) self.panels['splash_screen'].show_restart_buttons() def init_printer(self): _ = self.lang.gettext printer_info = self.apiclient.get_printer_info() data = self.apiclient.send_request("printer/objects/query?" + "&".join(PRINTER_BASE_STATUS_OBJECTS)) powerdevs = self.apiclient.send_request("machine/device_power/devices") data = data['result']['status'] # Reinitialize printer, in case the printer was shut down and anything has changed. self.printer.reinit(printer_info['result'], data) self.ws_subscribe() if powerdevs != False: self.printer.configure_power_devices(powerdevs['result']) def printer_ready(self): _ = self.lang.gettext self.close_popup_message() # Force update to printer webhooks state in case the update is missed due to websocket subscribe not yet sent self.printer.process_update({"webhooks":{"state":"ready","state_message": "Printer is ready"}}) self.show_panel('main_panel', "main_menu", _("Home"), 2, items=self._config.get_menu_items("__main"), extrudercount=self.printer.get_extruder_count()) self.ws_subscribe() if "job_status" in self.panels: self.remove_subscription("job_status") del self.panels["job_status"] def printer_printing(self): self.close_popup_message() self.show_panel('job_status',"job_status", "Print Status", 2) def show_keyboard(self, widget=None): if self.keyboard is not None: return env = os.environ.copy() env["MB_KBD_CONFIG"] = "/home/pi/.matchbox/keyboard.xml" env["MB_KBD_CONFIG"] = "ks_includes/locales/keyboard.xml" p = subprocess.Popen(["matchbox-keyboard", "--xid"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) #p = subprocess.Popen(["onboard", "--xid"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) xid = int(p.stdout.readline()) logging.debug("XID %s" % xid) logging.debug("PID %s" % p.pid) keyboard = Gtk.Socket() #keyboard.connect("plug-added", self.plug_added) action_bar_width = self.gtk.get_action_bar_width() box = Gtk.VBox() box.set_size_request(self.width - action_bar_width, self.keyboard_height) box.add(keyboard) cur_panel = self.panels[self._cur_panels[-1]] #for i in ['back','estop','home']: # if i in cur_panel.control: # cur_panel.control[i].set_sensitive(False) cur_panel.get().put(box, action_bar_width, self.height - 200) self.show_all() keyboard.add_id(xid) keyboard.show() self.keyboard = { "box": box, "panel": cur_panel.get(), "process": p, "socket": keyboard } def remove_keyboard(self, widget=None): if self.keyboard is None: return self.keyboard['panel'].remove(self.keyboard['box']) os.kill(self.keyboard['process'].pid, signal.SIGTERM) self.keyboard = None
class NetworkPanel(ScreenPanel): initialized = False def __init__(self, screen, title, back=True): super().__init__(screen, title, back) self.show_add = False self.networks = {} self.interface = None self.prev_network = None self.update_timeout = None self.network_interfaces = netifaces.interfaces() self.wireless_interfaces = [ iface for iface in self.network_interfaces if iface.startswith('w') ] self.wifi = None if len(self.wireless_interfaces) > 0: logging.info( f"Found wireless interfaces: {self.wireless_interfaces}") self.wifi = WifiManager(self.wireless_interfaces[0]) def initialize(self, menu): grid = self._gtk.HomogeneousGrid() grid.set_hexpand(True) # Get IP Address gws = netifaces.gateways() if "default" in gws and netifaces.AF_INET in gws["default"]: self.interface = gws["default"][netifaces.AF_INET][1] else: ints = netifaces.interfaces() if 'lo' in ints: ints.pop(ints.index('lo')) if len(ints) > 0: self.interface = ints[0] else: self.interface = 'lo' res = netifaces.ifaddresses(self.interface) if netifaces.AF_INET in res and len(res[netifaces.AF_INET]) > 0: ip = res[netifaces.AF_INET][0]['addr'] else: ip = None self.labels['networks'] = {} self.labels['interface'] = Gtk.Label() self.labels['interface'].set_text(" %s: %s " % (_("Interface"), self.interface)) self.labels['interface'].set_hexpand(True) self.labels['ip'] = Gtk.Label() self.labels['ip'].set_hexpand(True) reload_networks = self._gtk.ButtonImage("refresh", None, "color1", .66) reload_networks.connect("clicked", self.reload_networks) reload_networks.set_hexpand(False) sbox = Gtk.Box() sbox.set_hexpand(True) sbox.set_vexpand(False) sbox.add(self.labels['interface']) if ip is not None: self.labels['ip'].set_text(f"IP: {ip} ") sbox.add(self.labels['ip']) sbox.add(reload_networks) scroll = self._gtk.ScrolledWindow() box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) box.set_vexpand(True) self.labels['networklist'] = Gtk.Grid() if self.wifi is not None and self.wifi.is_initialized(): box.pack_start(sbox, False, False, 5) box.pack_start(scroll, True, True, 0) GLib.idle_add(self.load_networks) scroll.add(self.labels['networklist']) self.wifi.add_callback("connected", self.connected_callback) self.wifi.add_callback("scan_results", self.scan_callback) if self.update_timeout is None: self.update_timeout = GLib.timeout_add_seconds( 5, self.update_all_networks) else: self.labels['networkinfo'] = Gtk.Label("") self.labels['networkinfo'].get_style_context().add_class( 'temperature_entry') box.pack_start(self.labels['networkinfo'], False, False, 0) self.update_single_network_info() if self.update_timeout is None: self.update_timeout = GLib.timeout_add_seconds( 5, self.update_single_network_info) self.content.add(box) self.labels['main_box'] = box self.initialized = True def load_networks(self): networks = self.wifi.get_networks() if not networks: return for net in networks: self.add_network(net, False) self.update_all_networks() self.content.show_all() def add_network(self, ssid, show=True): if ssid is None: logging.info("SSID is None") return ssid = ssid.strip() if ssid in list(self.networks): logging.info("SSID already listed") return configured_networks = self.wifi.get_supplicant_networks() network_id = -1 for net in list(configured_networks): if configured_networks[net]['ssid'] == ssid: network_id = net display_name = _("Hidden") if ssid.startswith("\x00") else f"{ssid}" netinfo = self.wifi.get_network_info(ssid) connected_ssid = self.wifi.get_connected_ssid() if netinfo is None: logging.debug("Couldn't get netinfo") if connected_ssid == ssid: netinfo = {'connected': True} else: netinfo = {'connected': False} if connected_ssid == ssid: display_name += " (" + _("Connected") + ")" name = Gtk.Label("") name.set_markup(f"<big><b>{display_name}</b></big>") name.set_hexpand(True) name.set_halign(Gtk.Align.START) name.set_line_wrap(True) name.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR) info = Gtk.Label() info.set_halign(Gtk.Align.START) labels = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) labels.add(name) labels.add(info) labels.set_vexpand(True) labels.set_valign(Gtk.Align.CENTER) labels.set_halign(Gtk.Align.START) connect = self._gtk.ButtonImage("load", None, "color3") connect.connect("clicked", self.connect_network, ssid) connect.set_hexpand(False) connect.set_halign(Gtk.Align.END) delete = self._gtk.ButtonImage("delete", None, "color3") delete.connect("clicked", self.remove_wifi_network, ssid) delete.set_size_request(60, 0) delete.set_hexpand(False) delete.set_halign(Gtk.Align.END) network = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) network.set_hexpand(True) network.set_vexpand(False) network.add(labels) buttons = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) if network_id != -1 or netinfo['connected']: buttons.pack_end(delete, False, False, 0) else: buttons.pack_end(connect, False, False, 0) network.add(buttons) frame = Gtk.Frame() frame.get_style_context().add_class("frame-item") self.networks[ssid] = frame frame.add(network) nets = sorted(list(self.networks), reverse=False) if connected_ssid in nets: nets.remove(connected_ssid) nets.insert(0, connected_ssid) if nets.index(ssid) is not None: pos = nets.index(ssid) else: logging.info("Error: SSID not in nets") return self.labels['networks'][ssid] = { "connect": connect, "delete": delete, "info": info, "name": name, "row": network } self.labels['networklist'].insert_row(pos) self.labels['networklist'].attach(self.networks[ssid], 0, pos, 1, 1) if show: self.labels['networklist'].show() def add_new_network(self, widget, ssid, connect=False): self._screen.remove_keyboard() psk = self.labels['network_psk'].get_text() result = self.wifi.add_network(ssid, psk) self.close_add_network() if connect: if result: self.connect_network(widget, ssid, False) else: self._screen.show_popup_message(f"Error adding network {ssid}") def back(self): if self.show_add: self.close_add_network() return True return False def check_missing_networks(self): networks = self.wifi.get_networks() for net in list(self.networks): if net in networks: networks.remove(net) for net in networks: self.add_network(net, False) self.labels['networklist'].show_all() def close_add_network(self): if not self.show_add: return for child in self.content.get_children(): self.content.remove(child) self.content.add(self.labels['main_box']) self.content.show() for i in ['add_network', 'network_psk']: if i in self.labels: del self.labels[i] self.show_add = False @staticmethod def close_dialog(widget, response_id): widget.destroy() def connected_callback(self, ssid, prev_ssid): logging.info("Now connected to a new network") if ssid is not None: self.remove_network(ssid) if prev_ssid is not None: self.remove_network(prev_ssid) self.check_missing_networks() def connect_network(self, widget, ssid, showadd=True): snets = self.wifi.get_supplicant_networks() isdef = False for netid, net in snets.items(): if net['ssid'] == ssid: isdef = True break if not isdef: if showadd: self.show_add_network(widget, ssid) return self.prev_network = self.wifi.get_connected_ssid() buttons = [{"name": _("Close"), "response": Gtk.ResponseType.CANCEL}] scroll = Gtk.ScrolledWindow() scroll.set_property("overlay-scrolling", False) scroll.set_hexpand(True) scroll.set_vexpand(True) scroll.add_events(Gdk.EventMask.TOUCH_MASK) scroll.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self.labels['connecting_info'] = Gtk.Label( _("Starting WiFi Association")) self.labels['connecting_info'].set_halign(Gtk.Align.START) self.labels['connecting_info'].set_valign(Gtk.Align.START) scroll.add(self.labels['connecting_info']) self._gtk.Dialog(self._screen, buttons, scroll, self.close_dialog) self._screen.show_all() if ssid in list(self.networks): self.remove_network(ssid) if self.prev_network in list(self.networks): self.remove_network(self.prev_network) self.wifi.add_callback("connecting_status", self.connecting_status_callback) self.wifi.connect(ssid) def connecting_status_callback(self, msg): self.labels['connecting_info'].set_text( f"{self.labels['connecting_info'].get_text()}\n{msg}") self.labels['connecting_info'].show_all() def remove_network(self, ssid, show=True): if ssid not in list(self.networks): return logging.info(self.labels['networklist']) for i in range(len(self.labels['networklist'])): if self.networks[ssid] == self.labels['networklist'].get_child_at( 0, i): self.labels['networklist'].remove_row(i) self.labels['networklist'].show() del self.networks[ssid] del self.labels['networks'][ssid] return def remove_wifi_network(self, widget, ssid): self.wifi.delete_network(ssid) self.remove_network(ssid) self.check_missing_networks() def scan_callback(self, new_networks, old_networks): for net in old_networks: self.remove_network(net, False) for net in new_networks: self.add_network(net, False) self.content.show_all() def show_add_network(self, widget, ssid): if self.show_add: return for child in self.content.get_children(): self.content.remove(child) if "add_network" in self.labels: del self.labels['add_network'] label = self._gtk.Label(_("PSK for") + f' ssid') label.set_hexpand(False) self.labels['network_psk'] = Gtk.Entry() self.labels['network_psk'].set_text('') self.labels['network_psk'].set_hexpand(True) self.labels['network_psk'].connect("activate", self.add_new_network, ssid, True) self.labels['network_psk'].connect("focus-in-event", self._show_keyboard) self.labels['network_psk'].grab_focus_without_selecting() save = self._gtk.ButtonImage("sd", _("Save"), "color3") save.set_hexpand(False) save.connect("clicked", self.add_new_network, ssid, True) box = Gtk.Box() box.pack_start(self.labels['network_psk'], True, True, 5) box.pack_start(save, False, False, 5) self.labels['add_network'] = Gtk.Box( orientation=Gtk.Orientation.VERTICAL, spacing=5) self.labels['add_network'].set_valign(Gtk.Align.CENTER) self.labels['add_network'].set_hexpand(True) self.labels['add_network'].set_vexpand(True) self.labels['add_network'].pack_start(label, True, True, 5) self.labels['add_network'].pack_start(box, True, True, 5) self.content.add(self.labels['add_network']) self._screen.show_keyboard() self.content.show_all() self.show_add = True def _show_keyboard(self, widget=None, event=None): self._screen.show_keyboard(entry=self.labels['network_psk']) def update_all_networks(self): for network in list(self.networks): self.update_network_info(network) return True def update_network_info(self, ssid): info = freq = encr = chan = lvl = ipv4 = ipv6 = "" if ssid not in list( self.networks) or ssid not in self.labels['networks']: logging.info(f"Unknown SSID {ssid}") return netinfo = self.wifi.get_network_info(ssid) if netinfo is None: netinfo = {} if "connected" in netinfo: connected = netinfo['connected'] else: connected = False if connected or self.wifi.get_connected_ssid() == ssid: stream = os.popen('hostname -f') hostname = stream.read().strip() ifadd = netifaces.ifaddresses(self.interface) if netifaces.AF_INET in ifadd and len( ifadd[netifaces.AF_INET]) > 0: ipv4 = f"<b>IPv4:</b> {ifadd[netifaces.AF_INET][0]['addr']} " if netifaces.AF_INET6 in ifadd and len( ifadd[netifaces.AF_INET6]) > 0: ipv6 = f"<b>IPv6:</b> {ifadd[netifaces.AF_INET6][0]['addr'].split('%')[0]} " info = f'<b>' + _( "Hostname") + f':</b> {hostname}\n{ipv4}\n{ipv6}\n' elif "psk" in netinfo: info = _("Password saved") if "encryption" in netinfo: if netinfo['encryption'] != "off": encr = netinfo['encryption'].upper() if "frequency" in netinfo: freq = "2.4 GHz" if netinfo['frequency'][0:1] == "2" else "5 Ghz" if "channel" in netinfo: chan = _("Channel") + f' {netinfo["channel"]}' if "signal_level_dBm" in netinfo: lvl = f'{netinfo["signal_level_dBm"]} ' + _("dBm") self.labels['networks'][ssid]['info'].set_markup( f"{info} <small>{encr} {freq} {chan} {lvl}</small>") self.labels['networks'][ssid]['info'].show_all() def update_single_network_info(self): stream = os.popen('hostname -f') hostname = stream.read().strip() ifadd = netifaces.ifaddresses(self.interface) ipv4 = "" ipv6 = "" if netifaces.AF_INET in ifadd and len(ifadd[netifaces.AF_INET]) > 0: ipv4 = f"<b>IPv4:</b> {ifadd[netifaces.AF_INET][0]['addr']} " if netifaces.AF_INET6 in ifadd and len(ifadd[netifaces.AF_INET6]) > 0: ipv6 = f"<b>IPv6:</b> {ifadd[netifaces.AF_INET6][0]['addr'].split('%')[0]} " connected = (f'<b>{self.interface}</b>\n\n' f'<small><b>' + _("Connected") + f'</b></small>\n' f'<b>' + _("Hostname") + f':</b> {hostname}\n' f'{ipv4}\n' f'{ipv6}\n') self.labels['networkinfo'].set_markup(connected) self.labels['networkinfo'].show_all() def reload_networks(self, widget=None): self.networks = {} self.labels['networklist'].remove_column(0) self.wifi.rescan() if self.wifi is not None and self.wifi.is_initialized(): GLib.idle_add(self.load_networks) def activate(self): if self.initialized: self.reload_networks() if self.update_timeout is None: if self.wifi is not None and self.wifi.is_initialized(): self.update_timeout = GLib.timeout_add_seconds( 5, self.update_all_networks) else: self.update_timeout = GLib.timeout_add_seconds( 5, self.update_single_network_info) def deactivate(self): if self.update_timeout is not None: GLib.source_remove(self.update_timeout) self.update_timeout = None