From 7e3b919c6247d833df26ca88d9bb48a6311bee1a Mon Sep 17 00:00:00 2001 From: jordanruthe <31575189+jordanruthe@users.noreply.github.com> Date: Thu, 13 May 2021 20:53:58 -0400 Subject: [PATCH] Wifi manager (#148) Updates to include ability to change wifi networks. --- ks_includes/functions.py | 25 ++ ks_includes/wifi.py | 550 +++++++++++++++++++------ panels/network.py | 369 ++++++++++++++--- screen.py | 9 +- scripts/KlipperScreen-requirements.txt | 1 + styles/z-bolt/images/delete.svg | 8 + styles/z-bolt/images/settings.svg | 101 ++--- 7 files changed, 808 insertions(+), 255 deletions(-) create mode 100644 styles/z-bolt/images/delete.svg diff --git a/ks_includes/functions.py b/ks_includes/functions.py index bf2c0873..4ab9d060 100644 --- a/ks_includes/functions.py +++ b/ks_includes/functions.py @@ -49,6 +49,31 @@ try: except: pass +def get_network_interfaces(): + stream = os.popen("ip addr | grep ^'[0-9]' | cut -d ' ' -f 2 | grep -o '[a-zA-Z0-9\.]*'") + return [i for i in stream.read().strip().split('\n') if not i.startswith('lo')] + +def get_wireless_interfaces(): + p = subprocess.Popen(["which","iwconfig"], stdout=subprocess.PIPE) + + while p.poll() is None: + time.sleep(.1) + if p.poll() != 0: + return None + + try: + p = subprocess.Popen(["iwconfig"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + result = p.stdout.read().decode('ascii').split('\n') + except: + logging.info("Error with running iwconfig command") + return None + interfaces = [] + for line in result: + match = re.search('^(\S+)\s+.*$', line) + if match: + interfaces.append(match.group(1)) + + return interfaces def get_software_version(): prog = ('git', '-C', os.path.dirname(__file__), 'describe', '--always', diff --git a/ks_includes/wifi.py b/ks_includes/wifi.py index dddd75b0..17916ab2 100644 --- a/ks_includes/wifi.py +++ b/ks_includes/wifi.py @@ -2,82 +2,197 @@ import os, signal import json import logging import re +import socket import subprocess import threading +import time from contextlib import suppress from threading import Thread -RESCAN_INTERVAL = 120 +from subprocess import PIPE, Popen, STDOUT +from queue import Queue, Empty -class WifiManager(Thread): - iw_regexes = [ - re.compile(r"^ESSID:\"(?P.*)\"$"), - re.compile(r"^Protocol:(?P.+)$"), - re.compile(r"^Mode:(?P.+)$"), - re.compile(r"^Frequency:(?P[\d.]+) (?P.+) \(Channel (?P\d+)\)$"), - re.compile(r"^Encryption key:(?P.+)$"), - re.compile(r"^Quality=(?P\d+)/(?P\d+)\s+Signal level=(?P.+) d.+$"), - re.compile(r"^Signal level=(?P\d+)/(?P\d+).*$") - ] +import gi +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk, Gdk, GLib + +RESCAN_INTERVAL = 180 +KS_SOCKET_FILE = "/tmp/.KS_wpa_supplicant" + +class WifiManager(): networks_in_supplicant = [] - wpa = { - "wpa": re.compile(r"IE:\ WPA\ Version\ 1$"), - "wpa2": re.compile(r"IE:\ IEEE\ 802\.11i/WPA2\ Version\ 1$") - } connected = False _stop_loop = False + thread = None - def __init__(self, *args, **kwargs): + def __init__(self, interface, *args, **kwargs): super().__init__(*args, **kwargs) self.loop = None self._poll_task = None self._scanning = False + self._callbacks = { + "connected": [], + "connecting_status": [], + "scan_results": [] + } + self._stop_loop = False + self.connected = False + self.connected_ssid = None + self.connecting_info = [] + self.event = threading.Event() + self.initialized = False + self.interface = interface self.networks = {} + self.supplicant_networks = {} + self.queue = Queue() + self.tasks = [] + self.timeout = None + self.scan_time = 0 + + if os.path.exists(KS_SOCKET_FILE): + os.remove(KS_SOCKET_FILE) + + try: + self.soc = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) + self.soc.bind(KS_SOCKET_FILE) + self.soc.connect("/var/run/wpa_supplicant/%s" % interface) + except: + logging.info("Error connecting to wifi socket: %s" % interface) + return + + self.wpa_thread = WpaSocket(self, self.queue, self.callback) + self.wpa_thread.start() + self.initialized = True + + self.wpa_cli("ATTACH", False) + self.wpa_cli("SCAN", False) + GLib.idle_add(self.read_wpa_supplicant) + self.timeout = GLib.timeout_add_seconds(RESCAN_INTERVAL, self.rescan) + + def add_callback(self, name, callback): + if name in self._callbacks and callback not in self._callbacks[name]: + self._callbacks[name].append(callback) + + def add_network(self, ssid, psk): + for id in list(self.supplicant_networks): + if self.supplicant_networks[id]['ssid'] == ssid: + #Modify network + return + + # TODO: Add wpa_cli error checking + network_id = self.wpa_cli("ADD_NETWORK") + commands = [ + 'ENABLE_NETWORK %s' % (network_id), + 'SET_NETWORK %s ssid "%s"' % (network_id, ssid.replace('"','\"')), + 'SET_NETWORK %s psk "%s"' % (network_id, psk.replace('"','\"')) + ] + self.wpa_cli_batch(commands) + self.read_wpa_supplicant() - - def run(self): - event = threading.Event() - logging.debug("Setting up wifi event loop") - while self._stop_loop == False: - try: - self.scan() - event.wait(RESCAN_INTERVAL) - except: - logging.exception("Poll wifi error") - - def stop(self): - self.loop.call_soon_threadsafe(self.loop.stop) - - def stop_loop(self): - self._stop_loop = True - - def get_current_wifi(self, interface="wlan0"): - p = subprocess.Popen(["iwconfig",interface], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - content = p.stdout.read().decode('utf-8').split('\n') - - essid = None - mac = None - for line in content: - match = re.match(r'^.*ESSID:"(.*)"$', line.strip()) - if match: - essid = match.group(1) - continue - match = re.match(r'^.*Access\s+Point:\s+([0-9A-Fa-f:]+)$', line.strip()) - if match: - mac = match.group(1) + id = None + for i in list(self.supplicant_networks): + if self.supplicant_networks[i]['ssid'] == ssid: + id = i break - if essid is None or mac is None: - self.connected = False - return None - self.connected = True - return [essid, mac] + if id == None: + logging.info("Error adding network") + return False - def get_network_info(self, essid=None, mac=None): - if essid is not None and essid in self.networks: - return self.networks[essid] - if mac is not None and essid is None: + self.save_wpa_conf() + return True + + def callback(self, type, msg): + if type in self._callbacks: + for cb in self._callbacks[type]: + Gdk.threads_add_idle( + GLib.PRIORITY_DEFAULT_IDLE, + cb, + msg) + + def connect(self, ssid): + id = None + for netid, net in self.supplicant_networks.items(): + if net['ssid'] == ssid: + id = netid + break + + if id == None: + logging.info("Wifi network is not defined in wpa_supplicant") + return False + + logging.info("Attempting to connect to wifi: %s" % id) + self.connecting_info = ["Attempting to connect to %s" % ssid] + self.wpa_cli("SELECT_NETWORK %s" % id) + self.save_wpa_conf() + + def delete_network(self, ssid): + id = None + for i in list(self.supplicant_networks): + if self.supplicant_networks[i]['ssid'] == ssid: + id = i + break + + if id == None: + logging.debug("Unable to find network in wpa_supplicant") + return + self.wpa_cli("REMOVE_NETWORK %s" % id) + + for id in list(self.supplicant_networks): + if self.supplicant_networks[id]['ssid'] == ssid: + del self.supplicant_networks[id] + break + + self.save_wpa_conf() + + def get_connected_ssid(self): + return self.connected_ssid + + def get_current_wifi(self, interface="wlan0"): + logging.info("Getting current wifi information") + status = self.wpa_cli("STATUS").split('\n') + vars = {} + for line in status: + arr = line.split('=') + vars[arr[0]] = "=".join(arr[1:]) + + prev_ssid = self.connected_ssid + if "ssid" in vars and "bssid" in vars: + self.connected = True + self.connected_ssid = vars['ssid'] + for ssid, val in self.networks.items(): + if ssid == vars['ssid']: + self.networks[ssid]['connected'] = True + else: + self.networks[ssid]['connected'] = False + if prev_ssid != self.connected_ssid: + for cb in self._callbacks['connected']: + Gdk.threads_add_idle( + GLib.PRIORITY_DEFAULT_IDLE, + cb, self.connected_ssid, prev_ssid) + return [vars['ssid'], vars['bssid']] + else: + logging.info("Resetting connected_ssid") + self.connected = False + self.connected_ssid = None + for ssid, val in self.networks.items(): + self.networks[ssid]['connected'] = False + if prev_ssid != self.connected_ssid: + for cb in self._callbacks['connected']: + Gdk.threads_add_idle( + GLib.PRIORITY_DEFAULT_IDLE, + cb, self.connected_ssid, prev_ssid) + return None + + def get_current_wifi_idle_add(self): + self.get_current_wifi() + return False + + def get_network_info(self, ssid=None, mac=None): + if ssid is not None and ssid in self.networks: + return self.networks[ssid] + if mac is not None and ssid is None: for net in self.networks: if mac == net['mac']: return net @@ -86,77 +201,272 @@ class WifiManager(Thread): def get_networks(self): return list(self.networks) + def get_supplicant_networks(self): + return self.supplicant_networks + def is_connected(self): return self.connected - def parse(self, data): - aps = [] - lines = data.split('\n') - - for line in lines: - line = line.strip() - match = re.match(r'^Cell\s+([0-9]+)\s+-\s+Address:\s+(?P[0-9A-Fa-f:]+)$', line) - if match: - aps.append({"mac":match.group(2)}) - continue - if len(aps) < 1: - continue - for w, wreg in self.wpa.items(): - t = wreg.search(line) - if t is not None: - aps[-1].update({'encryption': w}) - for exp in self.iw_regexes: - result = exp.search(line) - if result is not None: - if "encryption" in result.groupdict(): - if result.groupdict()['encryption'] == 'on' : - aps[-1].update({'encryption': 'wep'}) - else: - aps[-1].update({'encryption': 'off'}) - else: - aps[-1].update(result.groupdict()) - return aps + def is_initialized(self): + return self.initialized def read_wpa_supplicant(self): - wpaconf = "/etc/wpa_supplicant/wpa_supplicant.conf" - if not os.path.exists(wpaconf): - return None - - regexes = [ - re.compile(r'^ssid\s*=\s*"(?P.*)"$'), - re.compile(r'^psk\s*=\s*"(?P.*)"$') - ] - networks = [] - p = subprocess.Popen(["sudo","cat",wpaconf], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - contents = p.stdout.read().decode('utf-8').split('\n') - - for line in contents: - if re.match(r'^network\s*=\s*{$', line.strip()): - networks.append({}) - continue - if len(networks) < 1: - continue - for exp in regexes: - result = exp.search(line.strip()) - if result is not None: - networks[-1].update(result.groupdict()) - + results = self.wpa_cli("LIST_NETWORKS").split('\n') + results.pop(0) + self.supplicant_networks = {} self.networks_in_supplicant = [] - for network in networks: - self.networks_in_supplicant.append(network) + for net in [n.split('\t') for n in results]: + self.supplicant_networks[net[0]] = { + "ssid": net[1], + "bssid": net[2], + "flags": net[3] if len(net) == 4 else "" + } + self.networks_in_supplicant.append(self.supplicant_networks[net[0]]) + + def remove_callback(self, name, callback): + if name in self._callbacks and callback in self._callbacks[name]: + self._callbacks[name].remove(callback) + + def rescan(self): + self.wpa_cli("SCAN", False) + return True + + def save_wpa_conf(self): + logging.info("Saving WPA config") + self.wpa_cli("SAVE_CONFIG") + + def scan_results(self, interface='wlan0'): + new_networks = [] + deleted_networks = list(self.networks) + + logging.info("Trying to get scan results") + results = self.wpa_cli("SCAN_RESULTS").split('\n') + results.pop(0) + + aps = [] + for res in results: + match = re.match("^([a-f0-9:]+)\s+([0-9]+)\s+([\-0-9]+)\s+(\S+)\s+(.+)?", res) + if match: + net = { + "mac": match.group(1), + "channel": WifiChannels.lookup(match.group(2))[1], + "connected": False, + "configured": False, + "frequency": match.group(2), + "flags": match.group(4), + "signal_level_dBm": match.group(3), + "ssid": match.group(5) + } + + if "WPA2" in net['flags']: + net['encryption'] = "WPA2" + elif "WPA" in net['flags']: + net['encryption'] = "WPA" + elif "WEP" in net['flags']: + net['encryption'] = "WEP" + else: + net['encryption'] = "off" + + aps.append(net) - def scan(self, interface='wlan0'): - p = subprocess.Popen(["sudo","iwlist",interface,"scan"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - aps = self.parse(p.stdout.read().decode('utf-8')) cur_info = self.get_current_wifi() - - self.networks = {} for ap in aps: - self.networks[ap['essid']] = ap - if cur_info is not None and cur_info[0] == ap['essid'] and cur_info[1] == ap['mac']: - self.networks[ap['essid']]['connected'] = True - for net in self.networks_in_supplicant: - if ap['essid'] == net['ssid'] and "psk" in net: - ap['psk'] = net['psk'] - break + self.networks[ap['ssid']] = ap + if cur_info is not None and cur_info[0] == ap['ssid'] and cur_info[1].lower() == ap['mac'].lower(): + self.networks[ap['ssid']]['connected'] = True + + for net in list(self.networks): + if net in deleted_networks: + deleted_networks.remove(net) + else: + new_networks.append(net) + if len(new_networks) > 0 or len(deleted_networks) > 0: + for cb in self._callbacks['scan_results']: + Gdk.threads_add_idle( + GLib.PRIORITY_DEFAULT_IDLE, + cb, new_networks, deleted_networks) + + def wpa_cli(self, command, wait=True): + if wait == False: + self.wpa_thread.skip_command() + self.soc.send(command.encode()) + if wait == True: + resp = self.queue.get() + return resp + + def wpa_cli_batch(self, commands): + for cmd in commands: + self.wpa_cli(cmd) + + +class WpaSocket(Thread): + def __init__ (self, wm, queue, callback): + super().__init__() + self.queue = queue + self.callback = callback + self.soc = wm.soc + self._stop_loop = False + self.skip_commands = 0 + self.wm = wm + + def run(self): + event = threading.Event() + logging.debug("Setting up wifi event loop") + while self._stop_loop == False: + try: + msg = self.soc.recv(4096).decode().strip() + except: + # TODO: Socket error + continue + if msg.startswith("<"): + if "CTRL-EVENT-SCAN-RESULTS" in msg: + logging.info("Adding scan_results to callbacks") + Gdk.threads_add_idle(GLib.PRIORITY_DEFAULT_IDLE, self.wm.scan_results) + elif "CTRL-EVENT-DISCONNECTED" in msg: + self.callback("connecting_status", msg) + match = re.match('<3>CTRL-EVENT-DISCONNECTED bssid=(\S+) reason=3 locally_generated=1', msg) + if match: + for net in self.wm.networks: + if self.wm.networks[net]['mac'] == match.group(1): + self.wm.networks[net]['connected'] = False + break + elif "Trying to associate" in msg: + self.callback("connecting_status", msg) + elif "CTRL-EVENT-REGDOM-CHANGE" in msg: + self.callback("connecting_status", msg) + elif "CTRL-EVENT-CONNECTED" in msg: + Gdk.threads_add_idle(GLib.PRIORITY_DEFAULT_IDLE, self.wm.get_current_wifi_idle_add) + self.callback("connecting_status", msg) + else: + if self.skip_commands > 0: + self.skip_commands = self.skip_commands - 1 + else: + self.queue.put(msg) + logging.info("Wifi event loop ended") + + def skip_command(self): + self.skip_commands = self.skip_commands + 1 + + def stop(self): + self._stop_loop = True + + +class WifiChannels: + @staticmethod + def lookup(freq): + if freq == "2412": + return ("2.4","1") + if freq == "2417": + return ("2.4","2") + if freq == "2422": + return ("2.4","3") + if freq == "2427": + return ("2.4","4") + if freq == "2432": + return ("2.4","5") + if freq == "2437": + return ("2.4","6") + if freq == "2442": + return ("2.4","7") + if freq == "2447": + return ("2.4","8") + if freq == "2452": + return ("2.4","9") + if freq == "2457": + return ("2.4","10") + if freq == "2462": + return ("2.4","11") + if freq == "2467": + return ("2.4","12") + if freq == "2472": + return ("2.4","13") + if freq == "2484": + return ("2.4","14") + if freq == "5035": + return ("5","7") + if freq == "5040": + return ("5","8") + if freq == "5045": + return ("5","9") + if freq == "5055": + return ("5","11") + if freq == "5060": + return ("5","12") + if freq == "5080": + return ("5","16") + if freq == "5170": + return ("5","34") + if freq == "5180": + return ("5","36") + if freq == "5190": + return ("5","38") + if freq == "5200": + return ("5","40") + if freq == "5210": + return ("5","42") + if freq == "5220": + return ("5","44") + if freq == "5230": + return ("5","46") + if freq == "5240": + return ("5","48") + if freq == "5260": + return ("5","52") + if freq == "5280": + return ("5","56") + if freq == "5300": + return ("5","60") + if freq == "5320": + return ("5","64") + if freq == "5500": + return ("5","100") + if freq == "5520": + return ("5","104") + if freq == "5540": + return ("5","108") + if freq == "5560": + return ("5","112") + if freq == "5580": + return ("5","116") + if freq == "5600": + return ("5","120") + if freq == "5620": + return ("5","124") + if freq == "5640": + return ("5","128") + if freq == "5660": + return ("5","132") + if freq == "5680": + return ("5","136") + if freq == "5700": + return ("5","140") + if freq == "5720": + return ("5","144") + if freq == "5745": + return ("5","149") + if freq == "5765": + return ("5","153") + if freq == "5785": + return ("5","157") + if freq == "5805": + return ("5","161") + if freq == "5825": + return ("5","165") + if freq == "4915": + return ("5","183") + if freq == "4920": + return ("5","184") + if freq == "4925": + return ("5","185") + if freq == "4935": + return ("5","187") + if freq == "4940": + return ("5","188") + if freq == "4945": + return ("5","189") + if freq == "4960": + return ("5","192") + if freq == "4980": + return ("5","196") + return None; diff --git a/panels/network.py b/panels/network.py index e89a97fd..5a92bf0e 100644 --- a/panels/network.py +++ b/panels/network.py @@ -1,5 +1,7 @@ import gi +import json import logging +import netifaces import os import re @@ -14,7 +16,6 @@ def create_panel(*args): class NetworkPanel(ScreenPanel): networks = {} network_list = [] - interface = "wlan0" def initialize(self, menu): _ = self.lang.gettext @@ -25,8 +26,20 @@ class NetworkPanel(ScreenPanel): stream = os.popen('hostname -A') hostname = stream.read() # Get IP Address - stream = os.popen('hostname -I') - ip = stream.read() + 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('lo') + self.interfaces = ints[0] + + 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 = "0.0.0.0" self.labels['networks'] = {} @@ -48,41 +61,65 @@ class NetworkPanel(ScreenPanel): box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) box.set_vexpand(True) - box.pack_start(sbox, False, False, 0) - box.pack_start(scroll, True, True, 0) self.labels['networklist'] = Gtk.Grid() self.files = {} - GLib.idle_add(self.load_networks) + if self._screen.wifi != None and self._screen.wifi.is_initialized(): + box.pack_start(sbox, False, False, 0) + box.pack_start(scroll, True, True, 0) - scroll.add(self.labels['networklist']) + GLib.idle_add(self.load_networks) + scroll.add(self.labels['networklist']) - #self.labels['networkinfo'] = Gtk.Label( - # _("Network Info") + "\n\n%s%s" % (hostname, ip) - #) - #self.labels['networkinfo'].get_style_context().add_class('temperature_entry') - #grid.attach(self.labels['networkinfo'], 1, 0, 1, 1) + self._screen.wifi.add_callback("connected", self.connected_callback) + self._screen.wifi.add_callback("scan_results", self.scan_callback) + self.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() + self.timeout = GLib.timeout_add_seconds(5, self.update_single_network_info) self.content.add(box) + self.labels['main_box'] = box def load_networks(self): networks = self._screen.wifi.get_networks() + + conn_ssid = self._screen.wifi.get_connected_ssid() + if conn_ssid in networks: + networks.remove(conn_ssid) + self.add_network(conn_ssid, False) + for net in networks: self.add_network(net, False) + self.update_all_networks() self.content.show_all() - def add_network(self, essid, show=True): + def add_network(self, ssid, show=True): _ = self.lang.gettext - netinfo = self._screen.wifi.get_network_info(essid) + if ssid == None: + return + ssid = ssid.strip() + + if ssid in list(self.networks): + logging.info("SSID already listed") + return + + netinfo = self._screen.wifi.get_network_info(ssid) if netinfo == None: + logging.debug("Couldn't get netinfo") return - - # For now, only add connected network - if "connected" not in netinfo: - return + + configured_networks = self._screen.wifi.get_supplicant_networks() + network_id = -1 + for net in list(configured_networks): + if configured_networks[net]['ssid'] == ssid: + network_id = net frame = Gtk.Frame() frame.set_property("shadow-type",Gtk.ShadowType.NONE) @@ -90,42 +127,15 @@ class NetworkPanel(ScreenPanel): name = Gtk.Label() - name.set_markup("%s" % (essid)) + name.set_markup("%s" % (ssid)) name.set_hexpand(True) name.set_halign(Gtk.Align.START) name.set_line_wrap(True) name.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR) - stream = os.popen('ip add show dev %s' % self.interface) - content = stream.read() - ipv4_re = re.compile(r'inet ([0-9\.]+)/[0-9]+', re.MULTILINE) - ipv6_re = re.compile(r'inet6 ([a-fA-F0-9:\.]+)/[0-9+]', re.MULTILINE) - match = ipv4_re.search(content) - ipv4 = "" - if match: - ipv4 = "%s: %s " % (_("IPv4"), match.group(1)) - match = ipv6_re.search(content) - ipv6 = "" - if match: - ipv6 = "%s: %s " % (_("IPv6"), match.group(1)) - - - stream = os.popen('hostname -f') - hostname = stream.read().strip() - - connected = "" - if "connected" in netinfo: - connected = "%s\n%s: %s\n%s%s\n" % (_("Connected"),_("Hostname"),hostname, ipv4, ipv6) - elif "psk" in netinfo: - connected = "Password saved." - freq = "2.4 GHz" if netinfo['frequency'][0:1] == "2" else "5 Ghz" info = Gtk.Label() - info.set_markup("%s%s %s %s %s %s%s" % ( connected, - "" if netinfo['encryption'] == "off" else netinfo['encryption'].upper(), - freq, _("Channel"), netinfo['channel'], netinfo['signal_level_dBm'], _("dBm") - )) info.set_halign(Gtk.Align.START) - #info.set_markup(self.get_file_info_str(essid)) + #info.set_markup(self.get_file_info_str(ssid)) labels = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) labels.add(name) labels.add(info) @@ -133,37 +143,270 @@ class NetworkPanel(ScreenPanel): labels.set_valign(Gtk.Align.CENTER) labels.set_halign(Gtk.Align.START) - actions = self._gtk.ButtonImage("print",None,"color3") - #actions.connect("clicked", self.confirm_print, essid) - actions.set_hexpand(False) - actions.set_halign(Gtk.Align.END) + 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","","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) - if not "connected" in netinfo: - network.add(actions) - self.networks[essid] = frame + buttons = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) + if network_id != -1: + buttons.pack_end(delete, False, False, 0) + if netinfo['connected'] == False: + buttons.pack_end(connect, False, False, 0) + + network.add(buttons) + + self.networks[ssid] = frame frame.add(network) reverse = False - nets = sorted(self.networks, reverse=reverse) - pos = nets.index(essid) - if "connected" in netinfo: - pos = 0 - elif self._screen.wifi.is_connected(): - pos += 1 - self.labels['networks'][essid] = { + pos = 0 + if netinfo['connected'] == True: + pos = 0 + else: + connected_ssid = self._screen.wifi.get_connected_ssid() + nets = list(self.networks) + if connected_ssid != None: + if connected_ssid in nets: + nets.remove(connected_ssid) + nets = sorted(nets, reverse=reverse) + pos = nets.index(ssid) + if connected_ssid != None: + pos += 1 + + 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[essid], 0, pos, 1, 1) + self.labels['networklist'].attach(self.networks[ssid], 0, pos, 1, 1) if show == True: - self.labels['networklist'].show_all() + self.labels['networklist'].show() + + def add_new_network(self, widget, ssid, connect=False): + networks = self._screen.wifi.get_networks() + psk = self.labels['network_psk'].get_text() + result = self._screen.wifi.add_network(ssid, psk) + + self.close_add_network(widget, ssid) + + if connect == True: + if result == True: + self.connect_network(widget, ssid, False) + else: + self._screen.show_popup_message("Error adding network %s" % ssid) + + def check_missing_networks(self): + networks = self._screen.wifi.get_networks() + for net in list(self.networks): + if net in networks: + networks.remove(net) + + for net in networks: + self.add_network(net) + self.labels['networklist'].show_all() + + def close_add_network(self, widget, ssid): + for child in self.content.get_children(): + self.content.remove(child) + self.content.add(self.labels['main_box']) + self.content.show() + + def close_dialog(self, widget, response_id): + widget.destroy() + + def connected_callback(self, ssid, prev_ssid): + logging.info("Now connected to a new network") + if ssid != None: + self.remove_network(ssid) + if prev_ssid != None: + self.remove_network(prev_ssid) + + self.check_missing_networks() + + def connect_network(self, widget, ssid, showadd=True): + _ = self.lang.gettext + + snets = self._screen.wifi.get_supplicant_networks() + isdef = False + for id, net in snets.items(): + if net['ssid'] == ssid: + isdef = True + break + + if isdef == False: + if showadd == True: + self.show_add_network(widget, ssid) + return + self.prev_network = self._screen.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.set_size_request(800,400) + self.labels['connecting_info'] = Gtk.Label(_("Starting WiFi Re-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']) + dialog = self._gtk.Dialog(self._screen, buttons, scroll, self.close_dialog) + self._screen.show_all() + + if ssid in self.networks: + self.remove_network(ssid) + if self.prev_network in self.networks: + self.remove_network(self.prev_network) + #GLib.timeout_add(500, self.add_network, self.prev_network) + + self._screen.wifi.add_callback("connecting_status", self.connecting_status_callback) + self._screen.wifi.connect(ssid) + + def connecting_status_callback(self, msg): + self.labels['connecting_info'].set_text(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 self.networks: + return + + i = 0 + while self.labels['networklist'].get_child_at(0, i) != None: + 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 + i = i+1 + return + + def remove_network_wid(self, widget, ssid): + self.remove_network(ssid) + + def remove_wifi_network(self, widget, ssid): + self._screen.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): + _ = self.lang.gettext + for child in self.content.get_children(): + self.content.remove(child) + + if "add_network" in self.labels: + del self.labels['add_network'] + + self.labels['add_network'] = Gtk.VBox() + self.labels['add_network'].set_valign(Gtk.Align.START) + + box = Gtk.Box(spacing=5) + box.set_size_request(self._gtk.get_content_width(), self._gtk.get_content_height() - + self._screen.keyboard_height - 20) + box.set_hexpand(True) + box.set_vexpand(False) + self.labels['add_network'].add(box) + + l = self._gtk.Label("%s %s:" % (_("PSK for"), ssid)) + l.set_hexpand(False) + entry = Gtk.Entry() + entry.set_hexpand(True) + + save = self._gtk.ButtonImage("sd",_("Save"),"color3") + save.set_hexpand(False) + save.connect("clicked", self.add_new_network, ssid, True) + + + self.labels['network_psk'] = entry + box.pack_start(l, False, False, 5) + box.pack_start(entry, True, True, 5) + box.pack_start(save, False, False, 5) + + self.show_create = True + self.labels['network_psk'].set_text('') + self.content.add(self.labels['add_network']) + self.content.show() + self._screen.show_keyboard() + self.labels['network_psk'].grab_focus_without_selecting() + + def update_all_networks(self): + for network in list(self.networks): + self.update_network_info(network) + return True + + def update_network_info(self, ssid): + _ = self.lang.gettext + + if ssid not in self.networks or ssid not in self.labels['networks']: + return + netinfo = self._screen.wifi.get_network_info(ssid) + if netinfo == None: + logging.debug("Couldn't get netinfo for update") + return + + connected = "" + if netinfo['connected'] == True: + 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 = "%s: %s " % (_("IPv4"), ifadd[netifaces.AF_INET][0]['addr']) + if netifaces.AF_INET6 in ifadd and len(ifadd[netifaces.AF_INET6]) > 0: + ipv6 = ipv6 = "%s: %s " % (_("IPv6"), ifadd[netifaces.AF_INET6][0]['addr'].split('%')[0]) + connected = "%s\n%s: %s\n%s%s\n" % (_("Connected"),_("Hostname"),hostname, ipv4, ipv6) + elif "psk" in netinfo: + connected = "Password saved." + freq = "2.4 GHz" if netinfo['frequency'][0:1] == "2" else "5 Ghz" + + self.labels['networks'][ssid]['info'].set_markup("%s%s %s %s %s %s%s" % ( connected, + "" if netinfo['encryption'] == "off" else netinfo['encryption'].upper(), + freq, _("Channel"), netinfo['channel'], netinfo['signal_level_dBm'], _("dBm") + )) + self.labels['networks'][ssid]['info'].show_all() + + def update_single_network_info(self): + _ = self.lang.gettext + + 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 = "%s: %s " % (_("IPv4"), ifadd[netifaces.AF_INET][0]['addr']) + if netifaces.AF_INET6 in ifadd and len(ifadd[netifaces.AF_INET6]) > 0: + ipv6 = ipv6 = "%s: %s " % (_("IPv6"), ifadd[netifaces.AF_INET6][0]['addr'].split('%')[0]) + connected = "%s\n\n%s\n%s: %s\n%s\n%s\n" % (self.interface, _("Connected"),_("Hostname"), + hostname, ipv4, ipv6) + + self.labels['networkinfo'].set_markup(connected) + self.labels['networkinfo'].show_all() diff --git a/screen.py b/screen.py index 0e49d448..9047cf72 100644 --- a/screen.py +++ b/screen.py @@ -7,6 +7,7 @@ import time import threading import json +import netifaces import requests import websocket import importlib @@ -88,8 +89,12 @@ class KlipperScreen(Gtk.Window): self.lang = gettext.translation('KlipperScreen', localedir='ks_includes/locales', fallback=True) self._config = KlipperScreenConfig(configfile, self.lang, self) - self.wifi = WifiManager() - self.wifi.start() + 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')) diff --git a/scripts/KlipperScreen-requirements.txt b/scripts/KlipperScreen-requirements.txt index f6006dd6..45c41928 100644 --- a/scripts/KlipperScreen-requirements.txt +++ b/scripts/KlipperScreen-requirements.txt @@ -1,6 +1,7 @@ humanize==3.5.0 jinja2==2.11.3 matplotlib==3.4.1 +netifaces==0.10.9 requests==2.25.1 vext==0.7.6 websocket-client==0.59.0 diff --git a/styles/z-bolt/images/delete.svg b/styles/z-bolt/images/delete.svg new file mode 100644 index 00000000..46a61cde --- /dev/null +++ b/styles/z-bolt/images/delete.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/styles/z-bolt/images/settings.svg b/styles/z-bolt/images/settings.svg index 73b7fe3f..ef6247f0 100644 --- a/styles/z-bolt/images/settings.svg +++ b/styles/z-bolt/images/settings.svg @@ -1,71 +1,32 @@ - - - - - - image/svg+xml - - folder - - - - - - - folder - Created with Sketch. - - + + + + + + + + +