From 57c7c4c4a58ab9801e9e418d284b72d9985a8846 Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Mon, 6 Feb 2023 14:01:07 -0600 Subject: [PATCH 01/31] Change network icon based on "signal level" --- Under Construction/Network.app/Network | 55 ++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index 83b728e0..d86212a6 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -14,12 +14,13 @@ from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QMenu, QAction, QHBoxLayout, QGroupBox, QSlider, QWidget, \ QActionGroup, QDesktopWidget, QMessageBox, QInputDialog, QLineEdit -from PyQt5.QtGui import QIcon, QPixmap, QCursor +from PyQt5.QtGui import QIcon, QPixmap, QCursor, QColor from PyQt5.QtCore import Qt, QProcess, QMetaObject, QCoreApplication, QEvent, QObject, QTimer, QPoint, QTimer from subprocess import Popen, check_output import sys, os, re, subprocess import re + class NetworkMenu(QObject): def __init__(self): @@ -87,6 +88,7 @@ class NetworkMenu(QObject): p.waitForFinished() def updateStatus(self): + bssid = "" p = QProcess() p.setProgram("wpa_cli") p.setArguments(["status"]) @@ -100,6 +102,10 @@ class NetworkMenu(QObject): # Update the icon in the menu for element in self.status_lines: + if element.startswith("bssid="): + bssid = element.split("=")[1] + continue + if element.startswith("wpa_state="): status = element.split("=")[1] break @@ -107,10 +113,45 @@ class NetworkMenu(QObject): if status == "SCANNING": self.tray.setIcon(QIcon.fromTheme("network-wireless-acquiring-symbolic")) elif status == "COMPLETED": - self.tray.setIcon(QIcon.fromTheme("network-wireless-symbolic")) + p.setArguments(["bss", bssid]) + p.start() + p.waitForFinished() + lines = str(p.readAllStandardOutput(), 'utf-8').strip().split("\n") + + signal_strength_dbm = 0 + signal_noise_dbm = 0 + for line in lines: + if line.startswith("noise="): + signal_noise_dbm = int(line.split("=")[1]) + continue + if line.startswith("level="): + signal_strength_dbm = int(line.split("=")[1]) + break + + # https://github.com/maxatome/wifimgr/blob/ca9951f0c08a72ad3f36c98a970414219d5b9b03/src/wifimgr.c#L848 + signal_level = (signal_strength_dbm - signal_noise_dbm) * 4 + + print('Signal strength: %s, signal noise: %s, signal level: %s' % (signal_strength_dbm, + signal_noise_dbm, signal_level)) + + # https://github.com/maxatome/wifimgr/blob/ca9951f0c08a72ad3f36c98a970414219d5b9b03/src/wifimgr-gtk.c#L1312 + if signal_level > 80: + icon_name = "network-wireless-100" + elif signal_level > 60: + icon_name = "network-wireless-75" + elif signal_level > 40: + icon_name = "network-wireless-50" + elif signal_level > 20: + icon_name = "network-wireless-25" + elif signal_level > 0: + icon_name = "network-wireless-00" + else: + icon_name = "network-wireless-00" + + self.tray.setIcon(QIcon.fromTheme(icon_name)) # TODO: Set different icons based on signal_level else: - self.tray.setIcon(QIcon.fromTheme("network-wireless-offline-symbolic")) + self.tray.setIcon(QIcon.fromTheme("network-wireless-disconnected")) except: pass @@ -261,14 +302,14 @@ n wpa_cli doesn't seem to be working. Information for debuggers: """ + o.strip()) return - isUsed = 0 + is_used = 0 for line in l[2:]: # Ignore 'selected interface' message and column labels - parts = line.split() + parts = line.split('\t') if parts[1] in [ssid, bssid]: - isUsed = 1 + is_used = 1 n = parts[0] - if isUsed: # TODO: We know we've already connected -- check why we are re-connecting + if is_used: # TODO: We know we've already connected -- check why we are re-connecting if "PSK" in flags: p.setArguments(["set_network", str(n), "psk", '"' + password + '"']) print(p.program() + " " + " ".join(p.arguments())) From 2e05a1c3b4f1e5325306ea372724ad697e6ed949 Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Mon, 6 Feb 2023 15:16:26 -0600 Subject: [PATCH 02/31] [WIP] Try to select network if it is saved, ask for password on failure * We need to prevent another dialog asking for password being displayed, or check authentication failures in other way --- Under Construction/Network.app/Network | 76 ++++++++++++++++---------- 1 file changed, 48 insertions(+), 28 deletions(-) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index d86212a6..e136feeb 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -111,8 +111,13 @@ class NetworkMenu(QObject): break try: if status == "SCANNING": + flags = getattr(self.wirelessGroup.checkedAction(), "flags") + if self.new_network_selected and "PSK" in flags: + self.reset_network_password() + self.tray.setIcon(QIcon.fromTheme("network-wireless-acquiring-symbolic")) elif status == "COMPLETED": + self.new_network_selected = False p.setArguments(["bss", bssid]) p.start() p.waitForFinished() @@ -273,15 +278,7 @@ class NetworkMenu(QObject): bssid = getattr(self.wirelessGroup.checkedAction(), "bssid") flags = getattr(self.wirelessGroup.checkedAction(), "flags") - if "PSK" in flags: - password, ok = QInputDialog.getText(None, "Wireless network password", "Please enter\ - the password for the %s network:" % ssid if ssid else bssid, - QLineEdit.Password) # TODO: Make OK only clickable when we have >= 8 characters - if not ok: - print("User did not click OK in password dialog") - return # Don't try to connect to a network if it has been cancelled. - - self.reconnect() + # self.reconnect() # Get a byte string with wpa_cli's output and decode it p = QProcess() @@ -292,9 +289,9 @@ class NetworkMenu(QObject): print(p.program() + " " + " ".join(p.arguments())) p.start() p.waitForFinished() - output = str(p.readAllStandardOutput(), 'utf-8') - l = output.strip().splitlines() # Split that output into lines, ignoring the - if len(l) < 2: # useless ones. + # Split that output into lines, ignoring the useless ones + output = str(p.readAllStandardOutput(), 'utf-8').strip().splitlines() + if len(output) < 2: # If this is called, something is wrong with wpa_cli. self.showError("Could not connect to the network", """For some reaso\ n wpa_cli doesn't seem to be working. @@ -303,23 +300,19 @@ Information for debuggers: """ + o.strip()) return is_used = 0 - for line in l[2:]: # Ignore 'selected interface' message and column labels + for line in output[2:]: # Ignore 'selected interface' message and column labels parts = line.split('\t') if parts[1] in [ssid, bssid]: is_used = 1 - n = parts[0] + self.network_id = parts[0] + continue if is_used: # TODO: We know we've already connected -- check why we are re-connecting - if "PSK" in flags: - p.setArguments(["set_network", str(n), "psk", '"' + password + '"']) - print(p.program() + " " + " ".join(p.arguments())) - p.start() - p.waitForFinished() - if "OK" in str(p.readAllStandardOutput(), 'utf-8'): - return # good - else: - self.showError("Could not connect to the network. Please check the password.") - return # bad + p.setArguments(["select_network", str(self.network_id)]) + p.start() + p.waitForFinished() + self.new_network_selected = True + else: p.setArguments(["add_network"]) print(p.program() + " " + " ".join(p.arguments())) @@ -337,7 +330,7 @@ Information for debuggers: We don't know why this happened.") print("returned non-zero exit code") return # bad - p.setArguments(["set_network", str(n), "ssid" if ssid else "bssid", '"' + (ssid if ssid else bssid) + '"']) + p.setArguments(["set_network", str(self.network_id), "ssid" if ssid else "bssid", '"' + (ssid if ssid else bssid) + '"']) print(p.program() + " " + " ".join(p.arguments())) p.start() p.waitForFinished() @@ -347,9 +340,9 @@ We don't know why this happened.") """Information for debuggers: """ + out) return # bad if "PSK" in flags: - p.setArguments(["set_network", str(n), "psk", '"' + password + '"']) + p.setArguments(["set_network", str(self.network_id), "psk", '"' + password + '"']) else: - p.setArguments(["set_network", str(n), "key_mgmt", "NONE"]) + p.setArguments(["set_network", str(self.network_id), "key_mgmt", "NONE"]) print(p.program() + " " + " ".join(p.arguments())) p.start() p.waitForFinished() @@ -358,7 +351,7 @@ We don't know why this happened.") self.showError("Could not connect to the network. Please check the password.", """Information for debuggers: """ + out) return # not good - p.setArguments(["enable_network", str(n)]) + p.setArguments(["enable_network", str(self.network_id)]) print(p.program() + " " + " ".join(p.arguments())) p.start() p.waitForFinished() @@ -406,6 +399,33 @@ We don't know why this happened.") msg.setDetailedText("Please see https://github.com/helloSystem/Utilities if you would like to contribute.\n\n" + detailed_text) msg.exec() + def reset_network_password(self): + ssid = getattr(self.wirelessGroup.checkedAction(), "ssid") + bssid = getattr(self.wirelessGroup.checkedAction(), "bssid") + password, ok = QInputDialog.getText(None, "Wireless network password", + "Please enter the password for the %s network:" % ssid if ssid else bssid, + QLineEdit.Password) # TODO: Make OK only clickable when we have >= 8 characters + if not ok: + self.new_network_selected = False + print("User did not click OK in password dialog") + return # Don't try to connect to a network if it has been cancelled. + + p = QProcess() + p.setProgram("wpa_cli") + p.setArguments(["set_network", str(self.network_id), "psk", '"' + password + '"']) + print(p.program() + " " + " ".join(p.arguments())) + p.start() + p.waitForFinished() + self.reconnect() + + if "OK" in str(p.readAllStandardOutput(), 'utf-8'): + self.new_network_selected = False + return # good + else: + self.showError("Could not connect to the network. Please check the password.") + return # bad + + if __name__ == "__main__": # Simple singleton: From 5051b163f55172a5182dbba4b70b2ccb553e4d7f Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Tue, 7 Feb 2023 11:00:18 -0600 Subject: [PATCH 03/31] Set checked icon on currently connected network --- Under Construction/Network.app/Network | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index e136feeb..be88f0b0 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -96,7 +96,7 @@ class NetworkMenu(QObject): p.start() p.waitForFinished() - self.status_lines = str(p.readAllStandardOutput(), 'utf-8').strip().split("\n") + self.status_lines = str(p.readAllStandardOutput(), 'utf-8').strip().splitlines() print(self.status_lines) @@ -176,7 +176,7 @@ class NetworkMenu(QObject): lines = str(p.readAllStandardOutput(), 'utf-8').strip().split("\n") - self.wirelessGroup = QActionGroup(self.menu) # Only one of the actions added to this group can be active + self.wirelessGroup = QActionGroup(self.menu) # Only one of the actions added to this group can be active action = QAction("Wireless") action.setDisabled(True) @@ -209,11 +209,11 @@ class NetworkMenu(QObject): # if 'flags' in vars(): action.__setattr__("flags", flags) action.triggered.connect(self.switchNetwork) # lambda could be used to pass an argument but the argument passed is taken at the time when this executes, which is not what we want + action.setCheckable(True) action.setText(label) + action.setIcon(QIcon.fromTheme("network-wireless-symbolic")) + if "ssid=" + ssid in self.status_lines: - action.setIcon(QIcon.fromTheme("network-wireless-symbolic")) - action.setCheckable(True) - if "default" in line: action.setChecked(True) if ssid not in ssids_added_to_menu: self.actions.append(action) From 9c06eea407c782c4150e7f0f38364142e18e14c2 Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Tue, 7 Feb 2023 14:23:37 -0600 Subject: [PATCH 04/31] Create separate functions for signal level related operations --- Under Construction/Network.app/Network | 98 ++++++++++++++------------ 1 file changed, 53 insertions(+), 45 deletions(-) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index be88f0b0..5eb7607e 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -18,7 +18,46 @@ from PyQt5.QtGui import QIcon, QPixmap, QCursor, QColor from PyQt5.QtCore import Qt, QProcess, QMetaObject, QCoreApplication, QEvent, QObject, QTimer, QPoint, QTimer from subprocess import Popen, check_output import sys, os, re, subprocess -import re + + +def get_icon_for_signal_level(signal_level: int) -> str: + # https://github.com/maxatome/wifimgr/blob/ca9951f0c08a72ad3f36c98a970414219d5b9b03/src/wifimgr-gtk.c#L1312 + if signal_level > 80: + icon_name = "network-wireless-100" + elif signal_level > 60: + icon_name = "network-wireless-75" + elif signal_level > 40: + icon_name = "network-wireless-50" + elif signal_level > 20: + icon_name = "network-wireless-25" + elif signal_level > 0: + icon_name = "network-wireless-00" + else: + icon_name = "network-wireless-00" + + return icon_name + + +def get_network_signal_level(bssid: str) -> int: + p = QProcess() + p.setProgram("wpa_cli") + p.setArguments(["bss", bssid]) + p.start() + p.waitForFinished() + lines = str(p.readAllStandardOutput(), 'utf-8').strip().split("\n") + + signal_strength_dbm = 0 + signal_noise_dbm = 0 + for line in lines: + if line.startswith("noise="): + signal_noise_dbm = int(line.split("=")[1]) + continue + if line.startswith("level="): + signal_strength_dbm = int(line.split("=")[1]) + break + + # https://github.com/maxatome/wifimgr/blob/ca9951f0c08a72ad3f36c98a970414219d5b9b03/src/wifimgr.c#L848 + return (signal_strength_dbm - signal_noise_dbm) * 4 class NetworkMenu(QObject): @@ -71,7 +110,7 @@ class NetworkMenu(QObject): # self.menu.move(QCursor.pos()) # self.menu.show() # When called like this, it appears as a context menu over the menu bar, which is not desired # self.menu.activateWindow() # Needed on some systems to make the menu go away when clicked anywhere else but in the menu? - self.menu.popup(QCursor.pos()) # When called like this, it appears almost at the correct location but with the wrong font size, + self.menu.popup(QCursor.pos()) # When called like this, it appears almost at the correct location but with the wrong font size, # as if it was a context menu rather than a real menu; probably because somehow its parent now is not the global menu bar main window? @@ -118,43 +157,10 @@ class NetworkMenu(QObject): self.tray.setIcon(QIcon.fromTheme("network-wireless-acquiring-symbolic")) elif status == "COMPLETED": self.new_network_selected = False - p.setArguments(["bss", bssid]) - p.start() - p.waitForFinished() - lines = str(p.readAllStandardOutput(), 'utf-8').strip().split("\n") - - signal_strength_dbm = 0 - signal_noise_dbm = 0 - for line in lines: - if line.startswith("noise="): - signal_noise_dbm = int(line.split("=")[1]) - continue - if line.startswith("level="): - signal_strength_dbm = int(line.split("=")[1]) - break - - # https://github.com/maxatome/wifimgr/blob/ca9951f0c08a72ad3f36c98a970414219d5b9b03/src/wifimgr.c#L848 - signal_level = (signal_strength_dbm - signal_noise_dbm) * 4 - - print('Signal strength: %s, signal noise: %s, signal level: %s' % (signal_strength_dbm, - signal_noise_dbm, signal_level)) - - # https://github.com/maxatome/wifimgr/blob/ca9951f0c08a72ad3f36c98a970414219d5b9b03/src/wifimgr-gtk.c#L1312 - if signal_level > 80: - icon_name = "network-wireless-100" - elif signal_level > 60: - icon_name = "network-wireless-75" - elif signal_level > 40: - icon_name = "network-wireless-50" - elif signal_level > 20: - icon_name = "network-wireless-25" - elif signal_level > 0: - icon_name = "network-wireless-00" - else: - icon_name = "network-wireless-00" - - self.tray.setIcon(QIcon.fromTheme(icon_name)) - # TODO: Set different icons based on signal_level + + signal_level = get_network_signal_level(bssid) + print('Signal level: %s' % signal_level) + self.tray.setIcon(QIcon.fromTheme(get_icon_for_signal_level(signal_level))) else: self.tray.setIcon(QIcon.fromTheme("network-wireless-disconnected")) except: @@ -190,15 +196,15 @@ class NetworkMenu(QObject): continue print(line) # Parse out information for each network - regex = r"([^\ ]+)\t([^\ ]+)\t([^\ ]+)\t([^\ ]+)\t(.*)$" + regex = r"([^\ ]+)\t[^\ ]+\t[^\ ]+\t([^\ ]+)\t(.*)$" matches = re.findall(regex, line) if not matches: continue print(len(matches[0])) bssid = matches[0][0] - signal_level = int(matches[0][2]) - flags = matches[0][3] - label = matches[0][4] + flags = matches[0][1] + label = matches[0][2] + signal_level = get_network_signal_level(bssid) ssid = label if label == "" or label.startswith("\\x00"): @@ -215,6 +221,8 @@ class NetworkMenu(QObject): if "ssid=" + ssid in self.status_lines: action.setChecked(True) + + # TODO: Show networks with same SSID but different BSSID if ssid not in ssids_added_to_menu: self.actions.append(action) self.wirelessGroup.addAction(action) @@ -229,12 +237,12 @@ class NetworkMenu(QObject): self.actions.append(action) self.menu.addAction(action) - action = QAction("Create Hotspot...") # TODO: To be implemented + action = QAction("Create Hotspot...") # TODO: To be implemented action.setDisabled(True) self.actions.append(action) self.menu.addAction(action) - action = QAction("Disconnect") # TODO: To be implemented + action = QAction("Disconnect") if "wpa_state=COMPLETED" in self.status_lines: action.setDisabled(False) else: From e642b82ce739eecc060ff57380911517d3b48e6d Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Wed, 8 Feb 2023 14:22:27 -0600 Subject: [PATCH 05/31] [WIP] Set lock icon and signal level to network menu items * For some reason the default CE_MenuItem style is lost, the default font can be restored using a new style and passing "panda" (QtPlugin key) to it, but the default highlight color isn't restored. --- Under Construction/Network.app/Network | 102 +++++++++++++++++++++++-- 1 file changed, 95 insertions(+), 7 deletions(-) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index 5eb7607e..3be71546 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -12,10 +12,10 @@ # https://docs.freebsd.org/en/books/handbook/advanced-networking/#network-wireless -from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QMenu, QAction, QHBoxLayout, QGroupBox, QSlider, QWidget, \ - QActionGroup, QDesktopWidget, QMessageBox, QInputDialog, QLineEdit -from PyQt5.QtGui import QIcon, QPixmap, QCursor, QColor -from PyQt5.QtCore import Qt, QProcess, QMetaObject, QCoreApplication, QEvent, QObject, QTimer, QPoint, QTimer +from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QMenu, QAction, QHBoxLayout, QWidget, QActionGroup,\ + QMessageBox, QInputDialog, QLineEdit, QStyle, QStyleOptionMenuItem, QLabel, QWidgetAction, QProxyStyle +from PyQt5.QtGui import QIcon, QPixmap, QCursor, QPainter, QMouseEvent, QPaintEvent +from PyQt5.QtCore import Qt, QProcess, QObject, QTimer, QRect, QSize from subprocess import Popen, check_output import sys, os, re, subprocess @@ -98,6 +98,7 @@ class NetworkMenu(QObject): def eventFilter(self, obj, event): print("eventFilter function running") # FIXME: Why is this never called when the icon is right-clicked? # We need to refresh the contents of the right-click menu somehow when the user right-clicks... + return super().eventFilter(obj, event) def show_menu(self, reason): self.updateMenu() @@ -165,6 +166,14 @@ class NetworkMenu(QObject): self.tray.setIcon(QIcon.fromTheme("network-wireless-disconnected")) except: pass + + if not self.menu.isHidden(): + for action in self.menu.actions(): + if hasattr(action, "bssid"): + bssid = getattr(action, "bssid") + flags = getattr(action, "flags") + action.defaultWidget().set_signal_level(get_network_signal_level(bssid)) + action.defaultWidget().set_password_protected("PSK" in flags) def updateMenu(self): @@ -208,8 +217,8 @@ class NetworkMenu(QObject): ssid = label if label == "" or label.startswith("\\x00"): - label = bssid # For networks with hidden ssid (network name) - action = QAction(line) + label = bssid # For networks with hidden ssid (network name) + action = QWidgetAction(self.menu) action.__setattr__("ssid", ssid) action.__setattr__("bssid", bssid) # if 'flags' in vars(): @@ -217,7 +226,12 @@ class NetworkMenu(QObject): action.triggered.connect(self.switchNetwork) # lambda could be used to pass an argument but the argument passed is taken at the time when this executes, which is not what we want action.setCheckable(True) action.setText(label) - action.setIcon(QIcon.fromTheme("network-wireless-symbolic")) + # TODO: Update these on when the popup is present + #action.setIcon(QIcon.fromTheme(get_icon_for_signal_level(signal_level))) + + #item = NetworkItem(self.menu, action, label, "ssid=" + ssid in self.status_lines, "PSK" in flags) + item = NetworkItem(self.menu, action, "PSK" in flags) + action.setDefaultWidget(item) if "ssid=" + ssid in self.status_lines: action.setChecked(True) @@ -434,6 +448,80 @@ We don't know why this happened.") return # bad +# https://forum.qt.io/post/367830 +class NetworkItem(QWidget): + + #def __init__(self, parent: QWidget, action: QAction, text: str, checked:bool, + # password_protected: bool): + def __init__(self, parent: QWidget, action: QAction, password_protected: bool): + super().__init__(parent) + + self.proxyStyle = QProxyStyle("panda") + + self.action = action + #self.text = text + #self.checked = checked + + self.layout = QHBoxLayout() + self.layout.setAlignment(Qt.AlignTrailing) + self.layout.setContentsMargins(0, 0, 10, 0) + + self.lock_label = QLabel() + self.lock_label.setPixmap(QIcon.fromTheme("locked").pixmap(QSize(14, 14))) + self.lock_label.setVisible(password_protected) + self.layout.addWidget(self.lock_label) + self.signal_label = QLabel() + self.signal_label.setPixmap(QIcon.fromTheme("network-wireless-100").pixmap(QSize(14, 14))) + self.layout.addWidget(self.signal_label) + + self.setLayout(self.layout) + self.setMouseTracking(True) + + def set_password_protected(self, password_protected: bool): + self.lock_label.setVisible(password_protected) + + def set_signal_level(self, signal_level: int): + self.signal_label.setPixmap(QIcon.fromTheme(get_icon_for_signal_level(signal_level)).pixmap(QSize(14, 14))) + + def minimumSizeHint(self): + opt = QStyleOptionMenuItem() + opt.initFrom(self) + opt.menuHasCheckableItems = True + contentSize = self.fontMetrics().size(Qt.TextSingleLine | Qt.TextShowMnemonic, self.action.text()) + return self.style().sizeFromContents(QStyle.CT_MenuItem, opt, contentSize, self)\ + + QSize(self.layout.sizeHint().width(), 0) + + def paintEvent(self, e: QPaintEvent): + p = QPainter(self) + p.setFont(self.parent().font()) + opt = QStyleOptionMenuItem() + QMenu.initStyleOption(self.parent(), opt, self.action) + #opt.initFrom(self.parent()) + #opt.text = self.text + #opt.menuHasCheckableItems = True + #opt.checked = self.checked + opt.checkType = QStyleOptionMenuItem.NonExclusive + #opt.menuItemType = QStyleOptionMenuItem.Normal + #opt.font = self.action.font().resolve(self.parent().font()) + #opt.fontMetrics = QFontMetrics(opt.font) + #opt.palette = self.parent().palette() + + #if self.rect().contains(self.mapFromGlobal(self.cursor().pos())): + # opt.state |= QStyle.State_Selected + + self.proxyStyle.drawControl(QStyle.CE_MenuItem, opt, p, self) + + def mouseReleaseEvent(self, e: QMouseEvent): + super().mouseReleaseEvent(e) + checkboxRect = QRect(0, 0, 25, self.height()) # the value 25 seems to be hardcoded in the style + if self.isEnabled(): + if checkboxRect.contains(self.mapFromGlobal(self.cursor().pos())): + self.checked = not self.checked + # emit toggled(checked) + #else: + # emit activated(); + + if __name__ == "__main__": # Simple singleton: From 569c36279ddbec42b3aa7e7994030d39ef567675 Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Thu, 9 Feb 2023 12:37:30 -0600 Subject: [PATCH 06/31] Try to avoid some exceptions, still wondering how to check authentication errors in a good way --- Under Construction/Network.app/Network | 73 +++++++++++++------------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index 3be71546..532311ca 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -44,7 +44,7 @@ def get_network_signal_level(bssid: str) -> int: p.setArguments(["bss", bssid]) p.start() p.waitForFinished() - lines = str(p.readAllStandardOutput(), 'utf-8').strip().split("\n") + lines = str(p.readAllStandardOutput(), 'utf-8').strip().splitlines() signal_strength_dbm = 0 signal_noise_dbm = 0 @@ -69,12 +69,11 @@ class NetworkMenu(QObject): # self.showTODO("It can show wireless networks but not connect to them. Do you know how to fix this?") self.showTODO() - # icon = QIcon(os.path.dirname(os.path.abspath(__file__)) + "/Resources/wireless.svg") # Does not seem to work - # TODO: Change the icon depending on signal strength; see /usr/local/share/icons/elementary-xfce/status/symbolic/network-wireless-* self.tray = QSystemTrayIcon() self.tray.setVisible(True) self.menu = QMenu() + self.wirelessGroup = QActionGroup(self.menu) # Only one of the actions added to this group can be active self.tray.setContextMenu(self.menu) self.tray.activated.connect(self.show_menu) @@ -84,6 +83,7 @@ class NetworkMenu(QObject): # Sneaky PyQt quirk! A reference to the actions must be kept around or the actions will be destroyed self.actions = [] self.sliderWindow = None + self.new_network_selected = False self.updateStatus() self.timer = QTimer() @@ -95,7 +95,7 @@ class NetworkMenu(QObject): self.tray.installEventFilter(self) # FIXME: This never seems to get called, why? self.installEventFilter(self) # FIXME: This never seems to get called, why? - def eventFilter(self, obj, event): + def eventFilter(self, obj, event) -> bool: print("eventFilter function running") # FIXME: Why is this never called when the icon is right-clicked? # We need to refresh the contents of the right-click menu somehow when the user right-clicks... return super().eventFilter(obj, event) @@ -129,6 +129,7 @@ class NetworkMenu(QObject): def updateStatus(self): bssid = "" + status = "" p = QProcess() p.setProgram("wpa_cli") p.setArguments(["status"]) @@ -149,23 +150,22 @@ class NetworkMenu(QObject): if element.startswith("wpa_state="): status = element.split("=")[1] break - try: - if status == "SCANNING": + + if status == "SCANNING": + if self.wirelessGroup.checkedAction() is not None: flags = getattr(self.wirelessGroup.checkedAction(), "flags") if self.new_network_selected and "PSK" in flags: self.reset_network_password() - self.tray.setIcon(QIcon.fromTheme("network-wireless-acquiring-symbolic")) - elif status == "COMPLETED": - self.new_network_selected = False + self.tray.setIcon(QIcon.fromTheme("network-wireless-acquiring-symbolic")) + elif status == "COMPLETED": + self.new_network_selected = False - signal_level = get_network_signal_level(bssid) - print('Signal level: %s' % signal_level) - self.tray.setIcon(QIcon.fromTheme(get_icon_for_signal_level(signal_level))) - else: - self.tray.setIcon(QIcon.fromTheme("network-wireless-disconnected")) - except: - pass + signal_level = get_network_signal_level(bssid) + print('Signal level: %s' % signal_level) + self.tray.setIcon(QIcon.fromTheme(get_icon_for_signal_level(signal_level))) + else: + self.tray.setIcon(QIcon.fromTheme("network-wireless-disconnected")) if not self.menu.isHidden(): for action in self.menu.actions(): @@ -173,6 +173,7 @@ class NetworkMenu(QObject): bssid = getattr(action, "bssid") flags = getattr(action, "flags") action.defaultWidget().set_signal_level(get_network_signal_level(bssid)) + # TODO: Those flags are not updating action.defaultWidget().set_password_protected("PSK" in flags) def updateMenu(self): @@ -189,9 +190,7 @@ class NetworkMenu(QObject): p.start() p.waitForFinished() - lines = str(p.readAllStandardOutput(), 'utf-8').strip().split("\n") - - self.wirelessGroup = QActionGroup(self.menu) # Only one of the actions added to this group can be active + lines = str(p.readAllStandardOutput(), 'utf-8').strip().splitlines() action = QAction("Wireless") action.setDisabled(True) @@ -226,8 +225,6 @@ class NetworkMenu(QObject): action.triggered.connect(self.switchNetwork) # lambda could be used to pass an argument but the argument passed is taken at the time when this executes, which is not what we want action.setCheckable(True) action.setText(label) - # TODO: Update these on when the popup is present - #action.setIcon(QIcon.fromTheme(get_icon_for_signal_level(signal_level))) #item = NetworkItem(self.menu, action, label, "ssid=" + ssid in self.status_lines, "PSK" in flags) item = NetworkItem(self.menu, action, "PSK" in flags) @@ -346,7 +343,7 @@ Information for debuggers: print("'%s' is not a number." % e) return # bad try: - n = int(e) + self.network_id = int(e) except: self.showError("Could not connect to the network.", "\ We don't know why this happened.") @@ -362,6 +359,10 @@ We don't know why this happened.") """Information for debuggers: """ + out) return # bad if "PSK" in flags: + password, ok = self.request_network_password(ssid, bssid) + if not ok: + return + p.setArguments(["set_network", str(self.network_id), "psk", '"' + password + '"']) else: p.setArguments(["set_network", str(self.network_id), "key_mgmt", "NONE"]) @@ -421,15 +422,23 @@ We don't know why this happened.") msg.setDetailedText("Please see https://github.com/helloSystem/Utilities if you would like to contribute.\n\n" + detailed_text) msg.exec() - def reset_network_password(self): - ssid = getattr(self.wirelessGroup.checkedAction(), "ssid") - bssid = getattr(self.wirelessGroup.checkedAction(), "bssid") + def request_network_password(self, ssid: str, bssid: str) -> tuple[str, bool]: password, ok = QInputDialog.getText(None, "Wireless network password", "Please enter the password for the %s network:" % ssid if ssid else bssid, QLineEdit.Password) # TODO: Make OK only clickable when we have >= 8 characters if not ok: self.new_network_selected = False print("User did not click OK in password dialog") + + return password, ok + + def reset_network_password(self): + ssid = getattr(self.wirelessGroup.checkedAction(), "ssid") + bssid = getattr(self.wirelessGroup.checkedAction(), "bssid") + password, ok = self.request_network_password(ssid, bssid) + + if not ok: + self.new_network_selected = False return # Don't try to connect to a network if it has been cancelled. p = QProcess() @@ -441,7 +450,7 @@ We don't know why this happened.") self.reconnect() if "OK" in str(p.readAllStandardOutput(), 'utf-8'): - self.new_network_selected = False + # self.new_network_selected = False return # good else: self.showError("Could not connect to the network. Please check the password.") @@ -467,7 +476,7 @@ class NetworkItem(QWidget): self.layout.setContentsMargins(0, 0, 10, 0) self.lock_label = QLabel() - self.lock_label.setPixmap(QIcon.fromTheme("locked").pixmap(QSize(14, 14))) + self.lock_label.setPixmap(QIcon.fromTheme("network-wireless-encrypted-symbolic").pixmap(QSize(14, 14))) self.lock_label.setVisible(password_protected) self.layout.addWidget(self.lock_label) self.signal_label = QLabel() @@ -511,16 +520,6 @@ class NetworkItem(QWidget): self.proxyStyle.drawControl(QStyle.CE_MenuItem, opt, p, self) - def mouseReleaseEvent(self, e: QMouseEvent): - super().mouseReleaseEvent(e) - checkboxRect = QRect(0, 0, 25, self.height()) # the value 25 seems to be hardcoded in the style - if self.isEnabled(): - if checkboxRect.contains(self.mapFromGlobal(self.cursor().pos())): - self.checked = not self.checked - # emit toggled(checked) - #else: - # emit activated(); - if __name__ == "__main__": From f3fc0fffccea4455a1d3aa670f1149fcff9cbdb0 Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Thu, 9 Feb 2023 13:39:42 -0600 Subject: [PATCH 07/31] Synchronize configurations, clarify "new_network_selected" flag variable --- Under Construction/Network.app/Network | 37 +++++++++++++++----------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index 532311ca..2c2277ba 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -83,7 +83,7 @@ class NetworkMenu(QObject): # Sneaky PyQt quirk! A reference to the actions must be kept around or the actions will be destroyed self.actions = [] self.sliderWindow = None - self.new_network_selected = False + self.check_network_password = False self.updateStatus() self.timer = QTimer() @@ -154,12 +154,13 @@ class NetworkMenu(QObject): if status == "SCANNING": if self.wirelessGroup.checkedAction() is not None: flags = getattr(self.wirelessGroup.checkedAction(), "flags") - if self.new_network_selected and "PSK" in flags: - self.reset_network_password() + if self.check_network_password: + self.set_network_password() + self.reconnect() self.tray.setIcon(QIcon.fromTheme("network-wireless-acquiring-symbolic")) elif status == "COMPLETED": - self.new_network_selected = False + self.check_network_password = False signal_level = get_network_signal_level(bssid) print('Signal level: %s' % signal_level) @@ -291,6 +292,7 @@ class NetworkMenu(QObject): self.tray.setIcon(QIcon.fromTheme("network-wireless-offline-symbolic")) def switchNetwork(self, line): + self.check_network_password = False self.updateStatus() ssid = getattr(self.wirelessGroup.checkedAction(), "ssid") @@ -312,26 +314,35 @@ class NetworkMenu(QObject): output = str(p.readAllStandardOutput(), 'utf-8').strip().splitlines() if len(output) < 2: # If this is called, something is wrong with wpa_cli. - self.showError("Could not connect to the network", """For some reaso\ -n wpa_cli doesn't seem to be working. + self.showError("Could not connect to the network", """For some reason wpa_cli doesn't seem to be working. Information for debuggers: -""" + o.strip()) +""" + output.strip()) return is_used = 0 for line in output[2:]: # Ignore 'selected interface' message and column labels parts = line.split('\t') if parts[1] in [ssid, bssid]: - is_used = 1 + is_used = True self.network_id = parts[0] continue if is_used: # TODO: We know we've already connected -- check why we are re-connecting + if "PSK" in flags: + self.check_network_password = True + else: + # Synchronize current network configuration with configuration file + p.setArguments(["set_network", str(self.network_id), "key_mgmt", "NONE"]) + p.start() + p.waitForFinished() + + p.setArguments(["save_config"]) + p.start() + p.waitForFinished() + p.setArguments(["select_network", str(self.network_id)]) p.start() p.waitForFinished() - self.new_network_selected = True - else: p.setArguments(["add_network"]) print(p.program() + " " + " ".join(p.arguments())) @@ -359,11 +370,7 @@ We don't know why this happened.") """Information for debuggers: """ + out) return # bad if "PSK" in flags: - password, ok = self.request_network_password(ssid, bssid) - if not ok: - return - - p.setArguments(["set_network", str(self.network_id), "psk", '"' + password + '"']) + self.set_network_password() else: p.setArguments(["set_network", str(self.network_id), "key_mgmt", "NONE"]) print(p.program() + " " + " ".join(p.arguments())) From 58ff2135c07b20d0f0182962a75669ffdc423d90 Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Fri, 10 Feb 2023 08:30:04 -0600 Subject: [PATCH 08/31] Create PasswordRequestDialog --- Under Construction/Network.app/Network | 110 +++++++++++++++++++++---- 1 file changed, 92 insertions(+), 18 deletions(-) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index 2c2277ba..4ccf61a6 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -12,10 +12,11 @@ # https://docs.freebsd.org/en/books/handbook/advanced-networking/#network-wireless -from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QMenu, QAction, QHBoxLayout, QWidget, QActionGroup,\ - QMessageBox, QInputDialog, QLineEdit, QStyle, QStyleOptionMenuItem, QLabel, QWidgetAction, QProxyStyle -from PyQt5.QtGui import QIcon, QPixmap, QCursor, QPainter, QMouseEvent, QPaintEvent -from PyQt5.QtCore import Qt, QProcess, QObject, QTimer, QRect, QSize +from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QMenu, QAction, QHBoxLayout, QWidget, QActionGroup, \ + QMessageBox, QLineEdit, QStyle, QStyleOptionMenuItem, QLabel, QWidgetAction, QProxyStyle, QDialog, \ + QGridLayout, QLayout, QCheckBox, QPushButton, QSizePolicy +from PyQt5.QtGui import QIcon, QPixmap, QCursor, QPainter, QPaintEvent +from PyQt5.QtCore import Qt, QProcess, QObject, QTimer, QSize from subprocess import Popen, check_output import sys, os, re, subprocess @@ -319,7 +320,7 @@ class NetworkMenu(QObject): Information for debuggers: """ + output.strip()) return - is_used = 0 + is_used = False for line in output[2:]: # Ignore 'selected interface' message and column labels parts = line.split('\t') if parts[1] in [ssid, bssid]: @@ -430,38 +431,39 @@ We don't know why this happened.") msg.exec() def request_network_password(self, ssid: str, bssid: str) -> tuple[str, bool]: - password, ok = QInputDialog.getText(None, "Wireless network password", - "Please enter the password for the %s network:" % ssid if ssid else bssid, - QLineEdit.Password) # TODO: Make OK only clickable when we have >= 8 characters + dialog = PasswordRequestDialog("

Please enter the password for the \"%s\" network:

" % ssid if ssid else bssid) + dialog.setWindowTitle("Wireless network password") + + ok, password = dialog.get_password() if not ok: - self.new_network_selected = False print("User did not click OK in password dialog") return password, ok - def reset_network_password(self): + def set_network_password(self): ssid = getattr(self.wirelessGroup.checkedAction(), "ssid") bssid = getattr(self.wirelessGroup.checkedAction(), "bssid") + flags = getattr(self.wirelessGroup.checkedAction(), "flags") password, ok = self.request_network_password(ssid, bssid) if not ok: - self.new_network_selected = False + self.check_network_password = False return # Don't try to connect to a network if it has been cancelled. p = QProcess() p.setProgram("wpa_cli") + p.setArguments(["set_network", str(self.network_id), "key_mgmt", flags.split(" ")[0]]) + p.start() + p.waitForFinished() + p.setArguments(["set_network", str(self.network_id), "psk", '"' + password + '"']) print(p.program() + " " + " ".join(p.arguments())) p.start() p.waitForFinished() - self.reconnect() - if "OK" in str(p.readAllStandardOutput(), 'utf-8'): - # self.new_network_selected = False - return # good - else: - self.showError("Could not connect to the network. Please check the password.") - return # bad + # Not really a way to tell if authentication failure occurred + if "FAIL" in str(p.readAllStandardOutput(), 'utf-8'): + print("Failure while setting network password") # https://forum.qt.io/post/367830 @@ -528,6 +530,78 @@ class NetworkItem(QWidget): self.proxyStyle.drawControl(QStyle.CE_MenuItem, opt, p, self) +class PasswordRequestDialog(QDialog): + def __init__(self, text: str): + super().__init__() + + self.layout = QGridLayout() + self.layout.setHorizontalSpacing(15) + self.layout.setSizeConstraint(QLayout.SetFixedSize) + + self.icon_label = QLabel() + self.icon_label.setPixmap(QPixmap(os.path.dirname(__file__) + "/Resources/Network.png").scaledToWidth(64, Qt.SmoothTransformation)) + + self.text_label = QLabel() + self.text_label.setMaximumWidth(300) + self.text_label.setContentsMargins(0, 5, 0, 0) + self.text_label.setText(text) + self.text_label.setWordWrap(True) + + self.password_label = QLabel() + self.password_label.setText("Password:") + + self.password_line_edit = QLineEdit() + self.password_line_edit.setEchoMode(QLineEdit.Password) + self.password_line_edit.textChanged.connect(self.password_changed) + self.password_line_edit.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + + self.show_password_checkbox = QCheckBox() + self.show_password_checkbox.setChecked(True) + self.show_password_checkbox.setText("Show password") + self.show_password_checkbox.stateChanged.connect(self.show_password_changed) + + self.buttons_layout = QHBoxLayout() + self.buttons_layout.setContentsMargins(0, 10, 0, 0) + self.buttons_layout.setSpacing(10) + self.buttons_layout.setSizeConstraint(QLayout.SetFixedSize) + + self.cancel_button = QPushButton() + self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + self.cancel_button.setText("Cancel") + self.cancel_button.clicked.connect(self.reject) + + self.connect_button = QPushButton() + self.connect_button.setEnabled(False) + self.connect_button.setText("Connect") + self.connect_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + self.connect_button.clicked.connect(self.accept) + + self.buttons_layout.addWidget(self.cancel_button) + self.buttons_layout.addWidget(self.connect_button) + + self.layout.addWidget(self.icon_label, 0, 0, Qt.AlignTop) + self.layout.addWidget(self.text_label, 0, 1, 1, 2, Qt.AlignTop) + self.layout.addWidget(self.password_label, 1, 1, Qt.AlignTop) + self.layout.addWidget(self.password_line_edit, 1, 2, Qt.AlignTop) + self.layout.addWidget(self.show_password_checkbox, 2, 2, Qt.AlignTop) + self.layout.addLayout(self.buttons_layout, 3, 2, Qt.AlignTrailing) + + self.setLayout(self.layout) + self.setModal(True) + self.setSizeGripEnabled(False) + + def get_password(self) -> tuple[bool, str]: + self.exec() + + return self.result(), self.password_label.text() + + def password_changed(self, password: str): + self.connect_button.setEnabled(len(password) > 8) + + def show_password_changed(self, state: int): + self.password_line_edit.setEchoMode(QLineEdit.Password if state else QLineEdit.Normal) + + if __name__ == "__main__": # Simple singleton: From 452cc3cf6fd0c0c2f897f95f51092f45abd40e25 Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Fri, 10 Feb 2023 08:41:45 -0600 Subject: [PATCH 09/31] Fix some mistakes --- Under Construction/Network.app/Network | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index 4ccf61a6..c358d718 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -452,9 +452,9 @@ We don't know why this happened.") p = QProcess() p.setProgram("wpa_cli") - p.setArguments(["set_network", str(self.network_id), "key_mgmt", flags.split(" ")[0]]) - p.start() - p.waitForFinished() + #p.setArguments(["set_network", str(self.network_id), "key_mgmt", flags.split(" ")[0]]) + #p.start() + #p.waitForFinished() p.setArguments(["set_network", str(self.network_id), "psk", '"' + password + '"']) print(p.program() + " " + " ".join(p.arguments())) @@ -556,7 +556,6 @@ class PasswordRequestDialog(QDialog): self.password_line_edit.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.show_password_checkbox = QCheckBox() - self.show_password_checkbox.setChecked(True) self.show_password_checkbox.setText("Show password") self.show_password_checkbox.stateChanged.connect(self.show_password_changed) @@ -593,13 +592,13 @@ class PasswordRequestDialog(QDialog): def get_password(self) -> tuple[bool, str]: self.exec() - return self.result(), self.password_label.text() + return self.result(), self.password_line_edit.text() def password_changed(self, password: str): self.connect_button.setEnabled(len(password) > 8) def show_password_changed(self, state: int): - self.password_line_edit.setEchoMode(QLineEdit.Password if state else QLineEdit.Normal) + self.password_line_edit.setEchoMode(QLineEdit.Normal if state else QLineEdit.Password) if __name__ == "__main__": From 3f900f159179a7c64b0a388441da8e0f96b834b0 Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Fri, 10 Feb 2023 12:30:12 -0600 Subject: [PATCH 10/31] Handle authentication failures in a more proper way --- Under Construction/Network.app/Network | 27 +++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index c358d718..7f771e32 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -92,6 +92,12 @@ class NetworkMenu(QObject): self.timer.timeout.connect(self.updateStatus) self.timer.start() + self.read_log_process = QProcess(self) + self.read_log_process.setProgram("tail") + self.read_log_process.setArguments(["-f", "/var/log/messages", "|", "grep", "wpa_supplicant"]) + self.read_log_process.start() + self.read_log_process.readyReadStandardOutput.connect(self.read_wpa_supplicant_log) + self.refreshMenu() # Initially populate the menu self.tray.installEventFilter(self) # FIXME: This never seems to get called, why? self.installEventFilter(self) # FIXME: This never seems to get called, why? @@ -153,16 +159,9 @@ class NetworkMenu(QObject): break if status == "SCANNING": - if self.wirelessGroup.checkedAction() is not None: - flags = getattr(self.wirelessGroup.checkedAction(), "flags") - if self.check_network_password: - self.set_network_password() - self.reconnect() - self.tray.setIcon(QIcon.fromTheme("network-wireless-acquiring-symbolic")) elif status == "COMPLETED": self.check_network_password = False - signal_level = get_network_signal_level(bssid) print('Signal level: %s' % signal_level) self.tray.setIcon(QIcon.fromTheme(get_icon_for_signal_level(signal_level))) @@ -465,6 +464,15 @@ We don't know why this happened.") if "FAIL" in str(p.readAllStandardOutput(), 'utf-8'): print("Failure while setting network password") + def read_wpa_supplicant_log(self): + output = str(self.read_log_process.readAllStandardOutput(), 'UTF-8') + + if self.check_network_password and self.wirelessGroup.checkedAction() and\ + ("ssid=\"%s\"" % getattr(self.wirelessGroup.checkedAction(), "ssid")) and "reason=WRONG_KEY" in output: + self.disconnect() # Prevent wpa_supplicant from trying to connect and fail again + self.set_network_password() + self.reconnect() + # https://forum.qt.io/post/367830 class NetworkItem(QWidget): @@ -600,6 +608,10 @@ class PasswordRequestDialog(QDialog): def show_password_changed(self, state: int): self.password_line_edit.setEchoMode(QLineEdit.Normal if state else QLineEdit.Password) +def signal_handler(signal_number: int): + if signal_number == signal.SIGTERM: + # Needed for read_log_process to stop + app.quit() if __name__ == "__main__": @@ -625,6 +637,7 @@ if __name__ == "__main__": p.start() p.waitForFinished() + signal.signal(signal.SIGTERM, signal_handler) app = QApplication(sys.argv) app.setQuitOnLastWindowClosed(False) NM = NetworkMenu() From 42ec2e6f612c65305a7057d74b1d9c2b5cbe4a1c Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Fri, 10 Feb 2023 12:31:08 -0600 Subject: [PATCH 11/31] Add missing import from last commit --- Under Construction/Network.app/Network | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index 7f771e32..bbee6072 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -18,8 +18,7 @@ from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QMenu, QAction, QHBox from PyQt5.QtGui import QIcon, QPixmap, QCursor, QPainter, QPaintEvent from PyQt5.QtCore import Qt, QProcess, QObject, QTimer, QSize from subprocess import Popen, check_output -import sys, os, re, subprocess - +import sys, os, re, subprocess, signal def get_icon_for_signal_level(signal_level: int) -> str: # https://github.com/maxatome/wifimgr/blob/ca9951f0c08a72ad3f36c98a970414219d5b9b03/src/wifimgr-gtk.c#L1312 From 7db83d1f57c42ed208b89fedd01f8f08f19cbba9 Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Fri, 10 Feb 2023 14:38:35 -0600 Subject: [PATCH 12/31] Update network "password protected" status when menu is open --- Under Construction/Network.app/Network | 46 ++++++++++++++++---------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index bbee6072..c22003e0 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -18,6 +18,7 @@ from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QMenu, QAction, QHBox from PyQt5.QtGui import QIcon, QPixmap, QCursor, QPainter, QPaintEvent from PyQt5.QtCore import Qt, QProcess, QObject, QTimer, QSize from subprocess import Popen, check_output +from collections import namedtuple import sys, os, re, subprocess, signal def get_icon_for_signal_level(signal_level: int) -> str: @@ -38,7 +39,11 @@ def get_icon_for_signal_level(signal_level: int) -> str: return icon_name -def get_network_signal_level(bssid: str) -> int: +# Only need this for now +NetworkInfo = namedtuple("NetworkInfo", ["flags", "signal_level"]) + + +def get_network_info(bssid: str) -> NetworkInfo: p = QProcess() p.setProgram("wpa_cli") p.setArguments(["bss", bssid]) @@ -48,16 +53,19 @@ def get_network_signal_level(bssid: str) -> int: signal_strength_dbm = 0 signal_noise_dbm = 0 + flags = "" for line in lines: if line.startswith("noise="): signal_noise_dbm = int(line.split("=")[1]) continue if line.startswith("level="): signal_strength_dbm = int(line.split("=")[1]) - break + continue + if line.startswith("flags="): + flags = line.split("=")[1] # https://github.com/maxatome/wifimgr/blob/ca9951f0c08a72ad3f36c98a970414219d5b9b03/src/wifimgr.c#L848 - return (signal_strength_dbm - signal_noise_dbm) * 4 + return NetworkInfo(flags, (signal_strength_dbm - signal_noise_dbm) * 4) class NetworkMenu(QObject): @@ -102,7 +110,7 @@ class NetworkMenu(QObject): self.installEventFilter(self) # FIXME: This never seems to get called, why? def eventFilter(self, obj, event) -> bool: - print("eventFilter function running") # FIXME: Why is this never called when the icon is right-clicked? + print("eventFilter function running") # FIXME: Why is this never called when the icon is right-clicked? # We need to refresh the contents of the right-click menu somehow when the user right-clicks... return super().eventFilter(obj, event) @@ -161,7 +169,7 @@ class NetworkMenu(QObject): self.tray.setIcon(QIcon.fromTheme("network-wireless-acquiring-symbolic")) elif status == "COMPLETED": self.check_network_password = False - signal_level = get_network_signal_level(bssid) + signal_level = get_network_info(bssid).signal_level print('Signal level: %s' % signal_level) self.tray.setIcon(QIcon.fromTheme(get_icon_for_signal_level(signal_level))) else: @@ -172,9 +180,10 @@ class NetworkMenu(QObject): if hasattr(action, "bssid"): bssid = getattr(action, "bssid") flags = getattr(action, "flags") - action.defaultWidget().set_signal_level(get_network_signal_level(bssid)) + network_info = get_network_info(bssid) + action.defaultWidget().set_signal_level(network_info.signal_level) # TODO: Those flags are not updating - action.defaultWidget().set_password_protected("PSK" in flags) + action.defaultWidget().set_password_protected("PSK" in network_info.flags) def updateMenu(self): @@ -212,7 +221,7 @@ class NetworkMenu(QObject): bssid = matches[0][0] flags = matches[0][1] label = matches[0][2] - signal_level = get_network_signal_level(bssid) + signal_level = get_network_info(bssid).signal_level ssid = label if label == "" or label.startswith("\\x00"): @@ -226,8 +235,8 @@ class NetworkMenu(QObject): action.setCheckable(True) action.setText(label) - #item = NetworkItem(self.menu, action, label, "ssid=" + ssid in self.status_lines, "PSK" in flags) - item = NetworkItem(self.menu, action, "PSK" in flags) + item = NetworkItem(self.menu, action) + item.set_password_protected("PSK" in flags) action.setDefaultWidget(item) if "ssid=" + ssid in self.status_lines: @@ -369,7 +378,8 @@ We don't know why this happened.") """Information for debuggers: """ + out) return # bad if "PSK" in flags: - self.set_network_password() + # TODO: Don't add network if user presses "Cancel" on password dialog + self.set_network_password("

Please enter the password for the \"%s\" network:

" % ssid if ssid else bssid) else: p.setArguments(["set_network", str(self.network_id), "key_mgmt", "NONE"]) print(p.program() + " " + " ".join(p.arguments())) @@ -428,8 +438,8 @@ We don't know why this happened.") msg.setDetailedText("Please see https://github.com/helloSystem/Utilities if you would like to contribute.\n\n" + detailed_text) msg.exec() - def request_network_password(self, ssid: str, bssid: str) -> tuple[str, bool]: - dialog = PasswordRequestDialog("

Please enter the password for the \"%s\" network:

" % ssid if ssid else bssid) + def request_network_password(self, dialog_text: str) -> tuple[str, bool]: + dialog = PasswordRequestDialog(dialog_text) dialog.setWindowTitle("Wireless network password") ok, password = dialog.get_password() @@ -438,11 +448,11 @@ We don't know why this happened.") return password, ok - def set_network_password(self): + def set_network_password(self, dialog_text: str): ssid = getattr(self.wirelessGroup.checkedAction(), "ssid") bssid = getattr(self.wirelessGroup.checkedAction(), "bssid") flags = getattr(self.wirelessGroup.checkedAction(), "flags") - password, ok = self.request_network_password(ssid, bssid) + password, ok = self.request_network_password(dialog_text) if not ok: self.check_network_password = False @@ -468,8 +478,10 @@ We don't know why this happened.") if self.check_network_password and self.wirelessGroup.checkedAction() and\ ("ssid=\"%s\"" % getattr(self.wirelessGroup.checkedAction(), "ssid")) and "reason=WRONG_KEY" in output: + ssid = getattr(self.wirelessGroup.checkedAction(), "ssid") + bssid = getattr(self.wirelessGroup.checkedAction(), "bssid") self.disconnect() # Prevent wpa_supplicant from trying to connect and fail again - self.set_network_password() + self.set_network_password("

Please enter the password for the \"%s\" network:


Incorrect password. Please try again." % ssid if ssid else bssid) self.reconnect() @@ -478,7 +490,7 @@ class NetworkItem(QWidget): #def __init__(self, parent: QWidget, action: QAction, text: str, checked:bool, # password_protected: bool): - def __init__(self, parent: QWidget, action: QAction, password_protected: bool): + def __init__(self, parent: QWidget, action: QAction): super().__init__(parent) self.proxyStyle = QProxyStyle("panda") From 9c2b3684acd5732a673dab3b7ba9653158dc55d0 Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Sat, 11 Feb 2023 09:53:46 -0600 Subject: [PATCH 13/31] Handle network with same SSID but different authentication method * Identify currently connected network by BSSID * Remove added network if user clicks "Cancel" on password request dialog --- Under Construction/Network.app/Network | 120 ++++++++++++------------- 1 file changed, 58 insertions(+), 62 deletions(-) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index c22003e0..f4fc9672 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -21,6 +21,13 @@ from subprocess import Popen, check_output from collections import namedtuple import sys, os, re, subprocess, signal + +def signal_handler(signal_number: int): + if signal_number == signal.SIGTERM: + # Needed for read_log_process to stop + app.quit() + + def get_icon_for_signal_level(signal_level: int) -> str: # https://github.com/maxatome/wifimgr/blob/ca9951f0c08a72ad3f36c98a970414219d5b9b03/src/wifimgr-gtk.c#L1312 if signal_level > 80: @@ -68,6 +75,17 @@ def get_network_info(bssid: str) -> NetworkInfo: return NetworkInfo(flags, (signal_strength_dbm - signal_noise_dbm) * 4) +def request_network_password(dialog_text: str) -> tuple[str, bool]: + dialog = PasswordRequestDialog(dialog_text) + dialog.setWindowTitle("Wireless network password") + + ok, password = dialog.get_password() + if not ok: + print("User did not click OK in password dialog") + + return password, ok + + class NetworkMenu(QObject): def __init__(self): @@ -179,12 +197,10 @@ class NetworkMenu(QObject): for action in self.menu.actions(): if hasattr(action, "bssid"): bssid = getattr(action, "bssid") - flags = getattr(action, "flags") network_info = get_network_info(bssid) action.defaultWidget().set_signal_level(network_info.signal_level) - # TODO: Those flags are not updating action.defaultWidget().set_password_protected("PSK" in network_info.flags) - + def updateMenu(self): # Find out whether we are connected to one of the networks @@ -237,9 +253,10 @@ class NetworkMenu(QObject): item = NetworkItem(self.menu, action) item.set_password_protected("PSK" in flags) + item.set_signal_level(signal_level) action.setDefaultWidget(item) - if "ssid=" + ssid in self.status_lines: + if "bssid=" + bssid in self.status_lines: action.setChecked(True) # TODO: Show networks with same SSID but different BSSID @@ -300,6 +317,7 @@ class NetworkMenu(QObject): self.tray.setIcon(QIcon.fromTheme("network-wireless-offline-symbolic")) def switchNetwork(self, line): + # TODO: Support networks protected with EAP self.check_network_password = False self.updateStatus() @@ -328,25 +346,24 @@ Information for debuggers: """ + output.strip()) return is_used = False - for line in output[2:]: # Ignore 'selected interface' message and column labels + for line in output[2:]: # Ignore 'selected interface' message and column labels parts = line.split('\t') if parts[1] in [ssid, bssid]: - is_used = True - self.network_id = parts[0] - continue + p.setArguments(["get_network", parts[0], "key_mgmt"]) + p.start() + p.waitForFinished() + key_mgmt = str(p.readAllStandardOutput(), 'utf-8').strip().splitlines()[1] + + # Accept if the same authentication method is used, treat as different network if not. + # Not handling IEEE8021X + if ("WPA" in flags) == ("WPA" in key_mgmt): + is_used = True + self.network_id = parts[0] + break if is_used: # TODO: We know we've already connected -- check why we are re-connecting if "PSK" in flags: self.check_network_password = True - else: - # Synchronize current network configuration with configuration file - p.setArguments(["set_network", str(self.network_id), "key_mgmt", "NONE"]) - p.start() - p.waitForFinished() - - p.setArguments(["save_config"]) - p.start() - p.waitForFinished() p.setArguments(["select_network", str(self.network_id)]) p.start() @@ -367,7 +384,7 @@ Information for debuggers: self.showError("Could not connect to the network.", "\ We don't know why this happened.") print("returned non-zero exit code") - return # bad + return # bad p.setArguments(["set_network", str(self.network_id), "ssid" if ssid else "bssid", '"' + (ssid if ssid else bssid) + '"']) print(p.program() + " " + " ".join(p.arguments())) p.start() @@ -378,8 +395,15 @@ We don't know why this happened.") """Information for debuggers: """ + out) return # bad if "PSK" in flags: - # TODO: Don't add network if user presses "Cancel" on password dialog - self.set_network_password("

Please enter the password for the \"%s\" network:

" % ssid if ssid else bssid) + password, ok = request_network_password( + "

Please enter the password for the \"%s\" network:

" % ssid if ssid else bssid) + if ok: + p.setArguments(["set_network", str(self.network_id), "psk", "\"%s\"" % password]) + else: + p.setArguments(["remove_network", str(self.network_id)]) + p.start() + p.waitForFinished() + return else: p.setArguments(["set_network", str(self.network_id), "key_mgmt", "NONE"]) print(p.program() + " " + " ".join(p.arguments())) @@ -438,41 +462,6 @@ We don't know why this happened.") msg.setDetailedText("Please see https://github.com/helloSystem/Utilities if you would like to contribute.\n\n" + detailed_text) msg.exec() - def request_network_password(self, dialog_text: str) -> tuple[str, bool]: - dialog = PasswordRequestDialog(dialog_text) - dialog.setWindowTitle("Wireless network password") - - ok, password = dialog.get_password() - if not ok: - print("User did not click OK in password dialog") - - return password, ok - - def set_network_password(self, dialog_text: str): - ssid = getattr(self.wirelessGroup.checkedAction(), "ssid") - bssid = getattr(self.wirelessGroup.checkedAction(), "bssid") - flags = getattr(self.wirelessGroup.checkedAction(), "flags") - password, ok = self.request_network_password(dialog_text) - - if not ok: - self.check_network_password = False - return # Don't try to connect to a network if it has been cancelled. - - p = QProcess() - p.setProgram("wpa_cli") - #p.setArguments(["set_network", str(self.network_id), "key_mgmt", flags.split(" ")[0]]) - #p.start() - #p.waitForFinished() - - p.setArguments(["set_network", str(self.network_id), "psk", '"' + password + '"']) - print(p.program() + " " + " ".join(p.arguments())) - p.start() - p.waitForFinished() - - # Not really a way to tell if authentication failure occurred - if "FAIL" in str(p.readAllStandardOutput(), 'utf-8'): - print("Failure while setting network password") - def read_wpa_supplicant_log(self): output = str(self.read_log_process.readAllStandardOutput(), 'UTF-8') @@ -481,7 +470,20 @@ We don't know why this happened.") ssid = getattr(self.wirelessGroup.checkedAction(), "ssid") bssid = getattr(self.wirelessGroup.checkedAction(), "bssid") self.disconnect() # Prevent wpa_supplicant from trying to connect and fail again - self.set_network_password("

Please enter the password for the \"%s\" network:


Incorrect password. Please try again." % ssid if ssid else bssid) + + password, ok = request_network_password("

Please enter the password for the \"%s\" network:


Incorrect password. Please try again." % ssid if ssid else bssid) + + if not ok: + self.check_network_password = False + return # Don't try to connect to a network if it has been cancelled. + + p = QProcess() + p.setProgram("wpa_cli") + p.setArguments(["set_network", str(self.network_id), "psk", '"' + password + '"']) + print(p.program() + " " + " ".join(p.arguments())) + p.start() + p.waitForFinished() + self.reconnect() @@ -505,7 +507,6 @@ class NetworkItem(QWidget): self.lock_label = QLabel() self.lock_label.setPixmap(QIcon.fromTheme("network-wireless-encrypted-symbolic").pixmap(QSize(14, 14))) - self.lock_label.setVisible(password_protected) self.layout.addWidget(self.lock_label) self.signal_label = QLabel() self.signal_label.setPixmap(QIcon.fromTheme("network-wireless-100").pixmap(QSize(14, 14))) @@ -619,11 +620,6 @@ class PasswordRequestDialog(QDialog): def show_password_changed(self, state: int): self.password_line_edit.setEchoMode(QLineEdit.Normal if state else QLineEdit.Password) -def signal_handler(signal_number: int): - if signal_number == signal.SIGTERM: - # Needed for read_log_process to stop - app.quit() - if __name__ == "__main__": # Simple singleton: From 9bf7b7b6a201e68ae11f4cd5326cd4898fdb7f2e Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Mon, 13 Feb 2023 08:10:20 -0600 Subject: [PATCH 14/31] Use QFileSystemWatcher instead of "tail" --- Under Construction/Network.app/Network | 46 ++++++++++++-------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index f4fc9672..5193fd7e 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -16,16 +16,10 @@ from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QMenu, QAction, QHBox QMessageBox, QLineEdit, QStyle, QStyleOptionMenuItem, QLabel, QWidgetAction, QProxyStyle, QDialog, \ QGridLayout, QLayout, QCheckBox, QPushButton, QSizePolicy from PyQt5.QtGui import QIcon, QPixmap, QCursor, QPainter, QPaintEvent -from PyQt5.QtCore import Qt, QProcess, QObject, QTimer, QSize +from PyQt5.QtCore import Qt, QProcess, QObject, QTimer, QSize, QFileSystemWatcher from subprocess import Popen, check_output from collections import namedtuple -import sys, os, re, subprocess, signal - - -def signal_handler(signal_number: int): - if signal_number == signal.SIGTERM: - # Needed for read_log_process to stop - app.quit() +import sys, os, re, subprocess def get_icon_for_signal_level(signal_level: int) -> str: @@ -64,11 +58,9 @@ def get_network_info(bssid: str) -> NetworkInfo: for line in lines: if line.startswith("noise="): signal_noise_dbm = int(line.split("=")[1]) - continue - if line.startswith("level="): + elif line.startswith("level="): signal_strength_dbm = int(line.split("=")[1]) - continue - if line.startswith("flags="): + elif line.startswith("flags="): flags = line.split("=")[1] # https://github.com/maxatome/wifimgr/blob/ca9951f0c08a72ad3f36c98a970414219d5b9b03/src/wifimgr.c#L848 @@ -113,17 +105,14 @@ class NetworkMenu(QObject): self.updateStatus() self.timer = QTimer() - self.timer.setInterval(3000) # Every 3 seconds + self.timer.setInterval(3000) # Every 3 seconds self.timer.timeout.connect(self.updateStatus) self.timer.start() - self.read_log_process = QProcess(self) - self.read_log_process.setProgram("tail") - self.read_log_process.setArguments(["-f", "/var/log/messages", "|", "grep", "wpa_supplicant"]) - self.read_log_process.start() - self.read_log_process.readyReadStandardOutput.connect(self.read_wpa_supplicant_log) + log_file_watcher = QFileSystemWatcher(["/var/log/messages"], self) + log_file_watcher.fileChanged.connect(self.read_log_file) - self.refreshMenu() # Initially populate the menu + self.refreshMenu() # Initially populate the menu self.tray.installEventFilter(self) # FIXME: This never seems to get called, why? self.installEventFilter(self) # FIXME: This never seems to get called, why? @@ -177,9 +166,7 @@ class NetworkMenu(QObject): for element in self.status_lines: if element.startswith("bssid="): bssid = element.split("=")[1] - continue - - if element.startswith("wpa_state="): + elif element.startswith("wpa_state="): status = element.split("=")[1] break @@ -462,8 +449,18 @@ We don't know why this happened.") msg.setDetailedText("Please see https://github.com/helloSystem/Utilities if you would like to contribute.\n\n" + detailed_text) msg.exec() - def read_wpa_supplicant_log(self): - output = str(self.read_log_process.readAllStandardOutput(), 'UTF-8') + def read_log_file(self, path): + output = "" + + # Read last line of file + with open(path, 'rb') as file: + try: + file.seek(-2, os.SEEK_END) + while file.read(1) != b'\n': + file.seek(-2, os.SEEK_CUR) + except OSError: + file.seek(0) + output = file.readline().decode() if self.check_network_password and self.wirelessGroup.checkedAction() and\ ("ssid=\"%s\"" % getattr(self.wirelessGroup.checkedAction(), "ssid")) and "reason=WRONG_KEY" in output: @@ -644,7 +641,6 @@ if __name__ == "__main__": p.start() p.waitForFinished() - signal.signal(signal.SIGTERM, signal_handler) app = QApplication(sys.argv) app.setQuitOnLastWindowClosed(False) NM = NetworkMenu() From c92258279e0fb7834c52d4958dbcbefae295c927 Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Tue, 14 Feb 2023 13:20:17 -0600 Subject: [PATCH 15/31] Change monochrome icon colors with function from helloSystem/Menu@c13548d --- Under Construction/Network.app/Network | 55 ++++++++++++++++++++------ 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index 5193fd7e..91160b3d 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -12,17 +12,18 @@ # https://docs.freebsd.org/en/books/handbook/advanced-networking/#network-wireless +from ctypes import cast, POINTER, c_uint32 +from PyQt5.sip import voidptr from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QMenu, QAction, QHBoxLayout, QWidget, QActionGroup, \ QMessageBox, QLineEdit, QStyle, QStyleOptionMenuItem, QLabel, QWidgetAction, QProxyStyle, QDialog, \ QGridLayout, QLayout, QCheckBox, QPushButton, QSizePolicy -from PyQt5.QtGui import QIcon, QPixmap, QCursor, QPainter, QPaintEvent +from PyQt5.QtGui import QIcon, QPixmap, QCursor, QPainter, QPaintEvent, QImage, qGray, qAlpha, qRgba from PyQt5.QtCore import Qt, QProcess, QObject, QTimer, QSize, QFileSystemWatcher -from subprocess import Popen, check_output from collections import namedtuple -import sys, os, re, subprocess +import sys, os, re -def get_icon_for_signal_level(signal_level: int) -> str: +def get_icon_for_signal_level(signal_level: int) -> QIcon: # https://github.com/maxatome/wifimgr/blob/ca9951f0c08a72ad3f36c98a970414219d5b9b03/src/wifimgr-gtk.c#L1312 if signal_level > 80: icon_name = "network-wireless-100" @@ -37,7 +38,32 @@ def get_icon_for_signal_level(signal_level: int) -> str: else: icon_name = "network-wireless-00" - return icon_name + icon = QIcon.fromTheme(icon_name) + pixmap: QPixmap = icon.pixmap(QSize(16, 16)) + image = pixmap.toImage() + image = QPixmap.fromImage(set_monochrome_image_color(image)) + + return QIcon(image) + + +# https://github.com/helloSystem/Menu/blob/c13548d3866c3896d728d2388bd0d4cd636a3a91/plugin-statusnotifier/statusnotifierbutton.cpp#L108 +def set_monochrome_image_color(src_image: QImage, rgb_value: int=-1) -> QImage: + image: QImage = src_image.convertToFormat(QImage.Format_ARGB32 if src_image.hasAlphaChannel() else QImage.Format_RGB32) + pointer: voidptr = image.bits() + pixel_array = cast(pointer.__int__(), POINTER(c_uint32)) + pixel_count = image.width() * image.height() + + for i in range(pixel_count): + pixel = pixel_array.__getitem__(i) + + value = rgb_value + if rgb_value == -1: + value = qGray(pixel) + + value = qRgba(value, value, value, qAlpha(pixel)) + pixel_array.__setitem__(i, value) + + return image # Only need this for now @@ -176,7 +202,7 @@ class NetworkMenu(QObject): self.check_network_password = False signal_level = get_network_info(bssid).signal_level print('Signal level: %s' % signal_level) - self.tray.setIcon(QIcon.fromTheme(get_icon_for_signal_level(signal_level))) + self.tray.setIcon(get_icon_for_signal_level(signal_level)) else: self.tray.setIcon(QIcon.fromTheme("network-wireless-disconnected")) @@ -487,16 +513,12 @@ We don't know why this happened.") # https://forum.qt.io/post/367830 class NetworkItem(QWidget): - #def __init__(self, parent: QWidget, action: QAction, text: str, checked:bool, # password_protected: bool): def __init__(self, parent: QWidget, action: QAction): super().__init__(parent) self.proxyStyle = QProxyStyle("panda") - self.action = action - #self.text = text - #self.checked = checked self.layout = QHBoxLayout() self.layout.setAlignment(Qt.AlignTrailing) @@ -506,7 +528,7 @@ class NetworkItem(QWidget): self.lock_label.setPixmap(QIcon.fromTheme("network-wireless-encrypted-symbolic").pixmap(QSize(14, 14))) self.layout.addWidget(self.lock_label) self.signal_label = QLabel() - self.signal_label.setPixmap(QIcon.fromTheme("network-wireless-100").pixmap(QSize(14, 14))) + self.signal_label.setPixmap(QIcon.fromTheme("network-wireless-100").pixmap(QSize(16, 16))) self.layout.addWidget(self.signal_label) self.setLayout(self.layout) @@ -516,7 +538,7 @@ class NetworkItem(QWidget): self.lock_label.setVisible(password_protected) def set_signal_level(self, signal_level: int): - self.signal_label.setPixmap(QIcon.fromTheme(get_icon_for_signal_level(signal_level)).pixmap(QSize(14, 14))) + self.signal_label.setPixmap(get_icon_for_signal_level(signal_level).pixmap(QSize(16, 16))) def minimumSizeHint(self): opt = QStyleOptionMenuItem() @@ -543,6 +565,15 @@ class NetworkItem(QWidget): #if self.rect().contains(self.mapFromGlobal(self.cursor().pos())): # opt.state |= QStyle.State_Selected + if opt.state & QStyle.State_Selected and opt.state & QStyle.State_Enabled: + rgb_color = 255 + else: + rgb_color = 0 + + self.lock_label.setPixmap(QPixmap.fromImage(set_monochrome_image_color(self.lock_label.pixmap().toImage(), + rgb_color))) + self.signal_label.setPixmap(QPixmap.fromImage(set_monochrome_image_color(self.signal_label.pixmap().toImage(), + rgb_color))) self.proxyStyle.drawControl(QStyle.CE_MenuItem, opt, p, self) From b559620d36eadadc99e896ff921c19b419a8f209 Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Wed, 15 Feb 2023 08:49:52 -0600 Subject: [PATCH 16/31] Use original icon set, limit max length for Wi-Fi passwords, save config when reentering password --- Under Construction/Network.app/Network | 65 ++++++++++++++------------ 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index 91160b3d..5951c3c1 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -23,27 +23,33 @@ from collections import namedtuple import sys, os, re -def get_icon_for_signal_level(signal_level: int) -> QIcon: - # https://github.com/maxatome/wifimgr/blob/ca9951f0c08a72ad3f36c98a970414219d5b9b03/src/wifimgr-gtk.c#L1312 - if signal_level > 80: - icon_name = "network-wireless-100" - elif signal_level > 60: - icon_name = "network-wireless-75" - elif signal_level > 40: - icon_name = "network-wireless-50" - elif signal_level > 20: - icon_name = "network-wireless-25" - elif signal_level > 0: - icon_name = "network-wireless-00" +def get_pixmap_for_status(status: str, signal_level: int) -> QPixmap: + if status == "SCANNING" or status == "ACQUIRING": + icon_name = "network-wireless-acquiring-symbolic" + elif status == "COMPLETED": + # https://github.com/maxatome/wifimgr/blob/ca9951f0c08a72ad3f36c98a970414219d5b9b03/src/wifimgr-gtk.c#L1312 + if signal_level > 80: + icon_name = "network-wireless-signal-excellent-symbolic" + elif signal_level > 60: + icon_name = "network-wireless-signal-good-symbolic" + elif signal_level > 40: + icon_name = "network-wireless-signal-ok-symbolic" + elif signal_level > 20: + icon_name = "network-wireless-signal-weak-symbolic" + elif signal_level > 0: + icon_name = "network-wireless-signal-none-symbolic" + else: + icon_name = "network-wireless-signal-none-symbolic" else: - icon_name = "network-wireless-00" + icon_name = "network-wireless-offline-symbolic" icon = QIcon.fromTheme(icon_name) pixmap: QPixmap = icon.pixmap(QSize(16, 16)) image = pixmap.toImage() - image = QPixmap.fromImage(set_monochrome_image_color(image)) + # Use 100 on this icon set for a gray color that looks similar to the rest of the icons on the system tray + pixmap = QPixmap.fromImage(set_monochrome_image_color(image, 100)) - return QIcon(image) + return pixmap # https://github.com/helloSystem/Menu/blob/c13548d3866c3896d728d2388bd0d4cd636a3a91/plugin-statusnotifier/statusnotifierbutton.cpp#L108 @@ -161,7 +167,6 @@ class NetworkMenu(QObject): self.menu.popup(QCursor.pos()) # When called like this, it appears almost at the correct location but with the wrong font size, # as if it was a context menu rather than a real menu; probably because somehow its parent now is not the global menu bar main window? - def refreshMenu(self): self.actions = [] # Get the networks from wpa_cli @@ -196,21 +201,15 @@ class NetworkMenu(QObject): status = element.split("=")[1] break - if status == "SCANNING": - self.tray.setIcon(QIcon.fromTheme("network-wireless-acquiring-symbolic")) - elif status == "COMPLETED": + if status == "COMPLETED": self.check_network_password = False - signal_level = get_network_info(bssid).signal_level - print('Signal level: %s' % signal_level) - self.tray.setIcon(get_icon_for_signal_level(signal_level)) - else: - self.tray.setIcon(QIcon.fromTheme("network-wireless-disconnected")) + + network_info = get_network_info(bssid) + self.tray.setIcon(QIcon(get_pixmap_for_status(status, network_info.signal_level))) if not self.menu.isHidden(): for action in self.menu.actions(): if hasattr(action, "bssid"): - bssid = getattr(action, "bssid") - network_info = get_network_info(bssid) action.defaultWidget().set_signal_level(network_info.signal_level) action.defaultWidget().set_password_protected("PSK" in network_info.flags) @@ -507,6 +506,10 @@ We don't know why this happened.") p.start() p.waitForFinished() + p.setArguments(["save_config"]) + p.start() + p.waitForFinished() + self.reconnect() @@ -528,7 +531,7 @@ class NetworkItem(QWidget): self.lock_label.setPixmap(QIcon.fromTheme("network-wireless-encrypted-symbolic").pixmap(QSize(14, 14))) self.layout.addWidget(self.lock_label) self.signal_label = QLabel() - self.signal_label.setPixmap(QIcon.fromTheme("network-wireless-100").pixmap(QSize(16, 16))) + self.signal_label.setPixmap(QIcon.fromTheme("network-wireless-symbolic").pixmap(QSize(16, 16))) self.layout.addWidget(self.signal_label) self.setLayout(self.layout) @@ -538,7 +541,7 @@ class NetworkItem(QWidget): self.lock_label.setVisible(password_protected) def set_signal_level(self, signal_level: int): - self.signal_label.setPixmap(get_icon_for_signal_level(signal_level).pixmap(QSize(16, 16))) + self.signal_label.setPixmap(get_pixmap_for_status("COMPLETED", signal_level)) def minimumSizeHint(self): opt = QStyleOptionMenuItem() @@ -643,7 +646,11 @@ class PasswordRequestDialog(QDialog): return self.result(), self.password_line_edit.text() def password_changed(self, password: str): - self.connect_button.setEnabled(len(password) > 8) + password_length = len(password) + self.connect_button.setEnabled(password_length > 8) + + if password_length > 63: + self.password_line_edit.setText(password[:63]) def show_password_changed(self, state: int): self.password_line_edit.setEchoMode(QLineEdit.Normal if state else QLineEdit.Password) From d9a809f9d2d04a352d0e2baa7d621f81765b6214 Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Wed, 15 Feb 2023 09:10:55 -0600 Subject: [PATCH 17/31] Replace nonexistent state, display icon for "INTERFACE_DISABLED" state --- Under Construction/Network.app/Network | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index 5951c3c1..32d2057d 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -24,7 +24,8 @@ import sys, os, re def get_pixmap_for_status(status: str, signal_level: int) -> QPixmap: - if status == "SCANNING" or status == "ACQUIRING": + # https://w1.fi/wpa_supplicant/devel/defs_8h.html#a4aeb27c1e4abd046df3064ea9756f0bca6304c99164cf51fa8baecf8b124c6117 + if status == "SCANNING" or status == "ASSOCIATING": icon_name = "network-wireless-acquiring-symbolic" elif status == "COMPLETED": # https://github.com/maxatome/wifimgr/blob/ca9951f0c08a72ad3f36c98a970414219d5b9b03/src/wifimgr-gtk.c#L1312 @@ -40,6 +41,8 @@ def get_pixmap_for_status(status: str, signal_level: int) -> QPixmap: icon_name = "network-wireless-signal-none-symbolic" else: icon_name = "network-wireless-signal-none-symbolic" + elif status == "INTERFACE_DISABLED": + icon_name = "network-wireless-no-route-symbolic" else: icon_name = "network-wireless-offline-symbolic" From 9951c580565898adbba98da204f172ba028fd630 Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Thu, 16 Feb 2023 09:40:45 -0600 Subject: [PATCH 18/31] Show status for wired networks, fix status for networks in popup menu --- Under Construction/Network.app/Network | 68 +++++++++++++++++++++++--- 1 file changed, 62 insertions(+), 6 deletions(-) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index 32d2057d..f206959b 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -22,8 +22,23 @@ from PyQt5.QtCore import Qt, QProcess, QObject, QTimer, QSize, QFileSystemWatche from collections import namedtuple import sys, os, re +WIRED_CONNECTED = 0 +WIRED_DISCONNECTED = 1 +WIRED_OFFLINE = 2 -def get_pixmap_for_status(status: str, signal_level: int) -> QPixmap: + +def get_pixmap_for_wired_status(status: int) -> QPixmap: + if status == WIRED_CONNECTED: + icon_name = "network-wired-symbolic" + elif status == WIRED_DISCONNECTED: + icon_name = "network-wired-disconnected-symbolic" # Same as "network-wired-offline-symbolic" + else: + icon_name = "network-wired-no-route-symbolic" + + return get_tray_icon_with_color(icon_name) + + +def get_pixmap_for_wpa_status(status: str, signal_level: int) -> QPixmap: # https://w1.fi/wpa_supplicant/devel/defs_8h.html#a4aeb27c1e4abd046df3064ea9756f0bca6304c99164cf51fa8baecf8b124c6117 if status == "SCANNING" or status == "ASSOCIATING": icon_name = "network-wireless-acquiring-symbolic" @@ -46,13 +61,15 @@ def get_pixmap_for_status(status: str, signal_level: int) -> QPixmap: else: icon_name = "network-wireless-offline-symbolic" + return get_tray_icon_with_color(icon_name) + + +def get_tray_icon_with_color(icon_name: str) -> QPixmap: icon = QIcon.fromTheme(icon_name) pixmap: QPixmap = icon.pixmap(QSize(16, 16)) image = pixmap.toImage() # Use 100 on this icon set for a gray color that looks similar to the rest of the icons on the system tray - pixmap = QPixmap.fromImage(set_monochrome_image_color(image, 100)) - - return pixmap + return QPixmap.fromImage(set_monochrome_image_color(image, 100)) # https://github.com/helloSystem/Menu/blob/c13548d3866c3896d728d2388bd0d4cd636a3a91/plugin-statusnotifier/statusnotifierbutton.cpp#L108 @@ -186,6 +203,40 @@ class NetworkMenu(QObject): bssid = "" status = "" p = QProcess() + + p.setProgram("ifconfig") + p.setArguments(["-l", "ether"]) + p.start() + p.waitForFinished() + interfaces = str(p.readAllStandardOutput(), 'UTF-8').strip().split(' ') + + wlan_present = False + wired_status = -1 + if len(interfaces) == 0: + self.tray.setIcon(QIcon(get_pixmap_for_wired_status(WIRED_OFFLINE))) + + for i in interfaces: + # wpa_cli takes care of wireless interfaces + if "wlan" in i: + wlan_present = True + continue + + p.setArguments([i, "inet"]) + p.start() + p.waitForFinished() + output = str(p.readAllStandardOutput(), 'UTF-8') + + # If it doesn't have an IPv4 address then "inet" is not present, but "inet6" is always there, does that + # mean that it always has an IPv6 address? Is it the MAC address? + if "inet" in output: + wired_status = WIRED_CONNECTED + else: + wired_status = WIRED_DISCONNECTED + + if not wlan_present: + self.tray.setIcon(QIcon(get_pixmap_for_wired_status(wired_status))) + return + p.setProgram("wpa_cli") p.setArguments(["status"]) print(p.program() + " " + " ".join(p.arguments())) @@ -207,12 +258,17 @@ class NetworkMenu(QObject): if status == "COMPLETED": self.check_network_password = False - network_info = get_network_info(bssid) - self.tray.setIcon(QIcon(get_pixmap_for_status(status, network_info.signal_level))) + if wired_status == WIRED_CONNECTED: + icon_pixmap = get_pixmap_for_wired_status(wired_status) + else: + icon_pixmap = get_pixmap_for_wpa_status(status, get_network_info(bssid).signal_level) + + self.tray.setIcon(QIcon(icon_pixmap)) if not self.menu.isHidden(): for action in self.menu.actions(): if hasattr(action, "bssid"): + network_info = get_network_info(getattr(action, "bssid")) action.defaultWidget().set_signal_level(network_info.signal_level) action.defaultWidget().set_password_protected("PSK" in network_info.flags) From 4be0f41261a88479d043077ae8f2feef7fe380cf Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Thu, 16 Feb 2023 10:29:03 -0600 Subject: [PATCH 19/31] Remove some duplicate code, fix "get_pixmap_for_wpa_status" function call --- Under Construction/Network.app/Network | 146 ++++++++++--------------- 1 file changed, 56 insertions(+), 90 deletions(-) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index f206959b..84d05c13 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -22,6 +22,19 @@ from PyQt5.QtCore import Qt, QProcess, QObject, QTimer, QSize, QFileSystemWatche from collections import namedtuple import sys, os, re + +def run_command_and_get_output(command: str, args, process: QProcess = None) -> str: + if process is None: + process = QProcess() + + process.setProgram(command) + process.setArguments(args) + print(process.program() + " " + " ".join(process.arguments())) + process.start() + process.waitForFinished() + return str(process.readAllStandardOutput(), 'UTF-8') + + WIRED_CONNECTED = 0 WIRED_DISCONNECTED = 1 WIRED_OFFLINE = 2 @@ -97,12 +110,7 @@ NetworkInfo = namedtuple("NetworkInfo", ["flags", "signal_level"]) def get_network_info(bssid: str) -> NetworkInfo: - p = QProcess() - p.setProgram("wpa_cli") - p.setArguments(["bss", bssid]) - p.start() - p.waitForFinished() - lines = str(p.readAllStandardOutput(), 'utf-8').strip().splitlines() + lines = run_command_and_get_output("wpa_cli", ["bss", bssid]).strip().splitlines() signal_strength_dbm = 0 signal_noise_dbm = 0 @@ -203,12 +211,7 @@ class NetworkMenu(QObject): bssid = "" status = "" p = QProcess() - - p.setProgram("ifconfig") - p.setArguments(["-l", "ether"]) - p.start() - p.waitForFinished() - interfaces = str(p.readAllStandardOutput(), 'UTF-8').strip().split(' ') + interfaces = run_command_and_get_output("ifconfig", ["-l", "ether"], p).strip().split(' ') wlan_present = False wired_status = -1 @@ -221,10 +224,7 @@ class NetworkMenu(QObject): wlan_present = True continue - p.setArguments([i, "inet"]) - p.start() - p.waitForFinished() - output = str(p.readAllStandardOutput(), 'UTF-8') + output = run_command_and_get_output("ifconfig", [i, "inet"], p) # If it doesn't have an IPv4 address then "inet" is not present, but "inet6" is always there, does that # mean that it always has an IPv6 address? Is it the MAC address? @@ -237,14 +237,7 @@ class NetworkMenu(QObject): self.tray.setIcon(QIcon(get_pixmap_for_wired_status(wired_status))) return - p.setProgram("wpa_cli") - p.setArguments(["status"]) - print(p.program() + " " + " ".join(p.arguments())) - p.start() - p.waitForFinished() - - self.status_lines = str(p.readAllStandardOutput(), 'utf-8').strip().splitlines() - + self.status_lines = run_command_and_get_output("wpa_cli", ["status"]).strip().splitlines() print(self.status_lines) # Update the icon in the menu @@ -279,14 +272,7 @@ class NetworkMenu(QObject): self.menu.clear() # Second, show - p = QProcess() - p.setProgram("wpa_cli") - p.setArguments(["scan_results"]) - print(p.program() + " " + " ".join(p.arguments())) - p.start() - p.waitForFinished() - - lines = str(p.readAllStandardOutput(), 'utf-8').strip().splitlines() + lines = run_command_and_get_output("wpa_cli", ["scan_results"]).strip().splitlines() action = QAction("Wireless") action.setDisabled(True) @@ -367,25 +353,11 @@ class NetworkMenu(QObject): self.menu.addAction(action) def reconnect(self): - p = QProcess() - p.setProgram("wpa_cli") - p.setArguments(["reconnect"]) - print(p.program() + " " + " ".join(p.arguments())) - p.start() - p.waitForFinished() - output = str(p.readAllStandardOutput(), 'utf-8') - print(output) + print(run_command_and_get_output("wpa_cli", ["reconnect"])) def disconnect(self): - p = QProcess() - p.setProgram("wpa_cli") - p.setArguments(["disconnect"]) - print(p.program() + " " + " ".join(p.arguments())) - p.start() - p.waitForFinished() - output = str(p.readAllStandardOutput(), 'utf-8') - print(output) - self.tray.setIcon(QIcon.fromTheme("network-wireless-offline-symbolic")) + print(run_command_and_get_output("wpa_cli", ["disconnect"])) + # self.tray.setIcon(QIcon.fromTheme("network-wireless-offline-symbolic")) def switchNetwork(self, line): # TODO: Support networks protected with EAP @@ -400,16 +372,10 @@ class NetworkMenu(QObject): # Get a byte string with wpa_cli's output and decode it p = QProcess() - p.setProgram("wpa_cli") - - # First, scan - p.setArguments(["list_networks"]) - print(p.program() + " " + " ".join(p.arguments())) - p.start() - p.waitForFinished() # Split that output into lines, ignoring the useless ones - output = str(p.readAllStandardOutput(), 'utf-8').strip().splitlines() - if len(output) < 2: + output = run_command_and_get_output("wpa_cli", ["list_networks"], p) + network_list = output.strip().splitlines() + if len(network_list) < 2: # If this is called, something is wrong with wpa_cli. self.showError("Could not connect to the network", """For some reason wpa_cli doesn't seem to be working. @@ -417,13 +383,11 @@ Information for debuggers: """ + output.strip()) return is_used = False - for line in output[2:]: # Ignore 'selected interface' message and column labels + for line in network_list[2:]: # Ignore 'selected interface' message and column labels parts = line.split('\t') if parts[1] in [ssid, bssid]: - p.setArguments(["get_network", parts[0], "key_mgmt"]) - p.start() - p.waitForFinished() - key_mgmt = str(p.readAllStandardOutput(), 'utf-8').strip().splitlines()[1] + key_mgmt = run_command_and_get_output("wpa_cli", ["get_network", parts[0], "key_mgmt"], + p).strip().splitlines()[1] # Accept if the same authentication method is used, treat as different network if not. # Not handling IEEE8021X @@ -440,30 +404,34 @@ Information for debuggers: p.start() p.waitForFinished() else: - p.setArguments(["add_network"]) - print(p.program() + " " + " ".join(p.arguments())) - p.start() - p.waitForFinished() - e = str(p.readAllStandardOutput(), 'utf-8').strip().splitlines()[-1] - if not e.isnumeric(): - self.showError("Could not connect to the network.", "'%s' is not a number." % e) - print("'%s' is not a number." % e) + output = run_command_and_get_output("wpa_cli", ["add_network"], p).strip().splitlines()[-1] + if not output.isnumeric(): + self.showError("Could not connect to the network.", "'%s' is not a number." % output) + print("'%s' is not a number." % output) return # bad try: - self.network_id = int(e) + self.network_id = int(output) except: self.showError("Could not connect to the network.", "\ We don't know why this happened.") print("returned non-zero exit code") return # bad - p.setArguments(["set_network", str(self.network_id), "ssid" if ssid else "bssid", '"' + (ssid if ssid else bssid) + '"']) - print(p.program() + " " + " ".join(p.arguments())) - p.start() - p.waitForFinished() - out = str(p.readAllStandardOutput(), 'utf-8') - if "OK" not in out: + + if ssid: + identifier = "ssid" + identifier_str = ssid + else: + identifier = "bssid" + identifier_str = bssid + + output = run_command_and_get_output( + "wpa_cli", + ["set_network", str(self.network_id), identifier, "\"%s\"" % identifier_str], + p) + + if "OK" not in output: self.showError("Could not connect to the network.", - """Information for debuggers: """ + out) + """Information for debuggers: """ + output) return # bad if "PSK" in flags: password, ok = request_network_password( @@ -477,20 +445,18 @@ We don't know why this happened.") return else: p.setArguments(["set_network", str(self.network_id), "key_mgmt", "NONE"]) + print(p.program() + " " + " ".join(p.arguments())) p.start() p.waitForFinished() - out = str(p.readAllStandardOutput(), 'utf-8') - if "OK" not in out: + output = str(p.readAllStandardOutput(), 'utf-8') + if "OK" not in output: self.showError("Could not connect to the network. Please check the password.", - """Information for debuggers: """ + out) + """Information for debuggers: """ + output) return # not good - p.setArguments(["enable_network", str(self.network_id)]) - print(p.program() + " " + " ".join(p.arguments())) - p.start() - p.waitForFinished() - lastout = str(p.readAllStandardOutput(), 'utf-8') - if "OK" in lastout: + + output = run_command_and_get_output("wpa_cli", ["enable_network", str(self.network_id)], p) + if "OK" in output: p.setArguments(["save_config"]) print(p.program() + " " + " ".join(p.arguments())) p.start() @@ -498,7 +464,7 @@ We don't know why this happened.") return # good else: self.showError("Could not connect to the network. Please check the password.", - """Information for debuggers: """ + lastout) + """Information for debuggers: """ + output) return # not good def _showAbout(self): @@ -600,7 +566,7 @@ class NetworkItem(QWidget): self.lock_label.setVisible(password_protected) def set_signal_level(self, signal_level: int): - self.signal_label.setPixmap(get_pixmap_for_status("COMPLETED", signal_level)) + self.signal_label.setPixmap(get_pixmap_for_wpa_status("COMPLETED", signal_level)) def minimumSizeHint(self): opt = QStyleOptionMenuItem() From 0766822ba5245b536adfa3efd7f7c6d7dd2b9e7b Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Thu, 16 Feb 2023 13:21:57 -0600 Subject: [PATCH 20/31] Show wired network interfaces in menu --- Under Construction/Network.app/Network | 31 +++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index 84d05c13..236d9c10 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -211,7 +211,7 @@ class NetworkMenu(QObject): bssid = "" status = "" p = QProcess() - interfaces = run_command_and_get_output("ifconfig", ["-l", "ether"], p).strip().split(' ') + interfaces = run_command_and_get_output("ifconfig", ["-ul", "ether"], p).strip().split(' ') wlan_present = False wired_status = -1 @@ -224,7 +224,11 @@ class NetworkMenu(QObject): wlan_present = True continue - output = run_command_and_get_output("ifconfig", [i, "inet"], p) + output = run_command_and_get_output("ifconfig", ["-v", i], p) + # Could use the number before the "<" and: http://fxr.watson.org/fxr/source/net/if.h#L142 + # But this also works + interface_flags = output[output.find("<"):output.find(">")] + has_active_status = "status: active" in output # status row is not present for "ue" interfaces # If it doesn't have an IPv4 address then "inet" is not present, but "inet6" is always there, does that # mean that it always has an IPv6 address? Is it the MAC address? @@ -272,12 +276,33 @@ class NetworkMenu(QObject): self.menu.clear() # Second, show - lines = run_command_and_get_output("wpa_cli", ["scan_results"]).strip().splitlines() + p = QProcess() + # Get interfaces marked as "UP" but exclude those containing "wlan" + interfaces = [i for i in run_command_and_get_output("ifconfig", ["-ul", "ether"], p).strip().split(' ') if "wlan" not in i] + + if interfaces: + action = QAction("Wired") + action.setDisabled(True) + self.actions.append(action) + self.menu.addAction(action) + + for i in interfaces: + name = "" + if "ue" in i: + name = "USB Ethernet" + elif "eth": # Is this correct? But doesn't seem like the best thing to do... How many of these identifiers are there + name = "Ethernet" + + action = QAction(name) + self.actions.append(action) + self.menu.addAction(action) action = QAction("Wireless") action.setDisabled(True) self.actions.append(action) self.menu.addAction(action) + + lines = run_command_and_get_output("wpa_cli", ["scan_results"]).strip().splitlines() if len(lines) > 1: ssids_added_to_menu = [] From 59440e1ed27f3f7f3ccbc8bd54186dd008eedb8b Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Fri, 17 Feb 2023 09:05:59 -0600 Subject: [PATCH 21/31] Show status icon for wired interfaces in menu, improve status detection method --- Under Construction/Network.app/Network | 49 +++++++++++++++++--------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index 236d9c10..c1646897 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -40,6 +40,20 @@ WIRED_DISCONNECTED = 1 WIRED_OFFLINE = 2 +def get_wired_status(interface_name: str) -> int: + output = run_command_and_get_output("ifconfig", ["-v", interface_name]) + has_active_status = "status: active" in output # "status" row is not present for "ue" interfaces + + # If it doesn't have an IPv4 address then "inet" is not present, but "inet6" is always there, does that + # mean that it always has an IPv6 address? Is it the MAC address? + if "ue" in interface_name and "inet" in output or has_active_status: + wired_status = WIRED_CONNECTED + else: + wired_status = WIRED_DISCONNECTED + + return wired_status + + def get_pixmap_for_wired_status(status: int) -> QPixmap: if status == WIRED_CONNECTED: icon_name = "network-wired-symbolic" @@ -224,24 +238,13 @@ class NetworkMenu(QObject): wlan_present = True continue - output = run_command_and_get_output("ifconfig", ["-v", i], p) - # Could use the number before the "<" and: http://fxr.watson.org/fxr/source/net/if.h#L142 - # But this also works - interface_flags = output[output.find("<"):output.find(">")] - has_active_status = "status: active" in output # status row is not present for "ue" interfaces - - # If it doesn't have an IPv4 address then "inet" is not present, but "inet6" is always there, does that - # mean that it always has an IPv6 address? Is it the MAC address? - if "inet" in output: - wired_status = WIRED_CONNECTED - else: - wired_status = WIRED_DISCONNECTED + wired_status = get_wired_status(i) if not wlan_present: self.tray.setIcon(QIcon(get_pixmap_for_wired_status(wired_status))) return - self.status_lines = run_command_and_get_output("wpa_cli", ["status"]).strip().splitlines() + self.status_lines = run_command_and_get_output("wpa_cli", ["status"], p).strip().splitlines() print(self.status_lines) # Update the icon in the menu @@ -290,10 +293,18 @@ class NetworkMenu(QObject): name = "" if "ue" in i: name = "USB Ethernet" - elif "eth": # Is this correct? But doesn't seem like the best thing to do... How many of these identifiers are there - name = "Ethernet" + else: + name = "Ethernet (%s)" % name + + action = QWidgetAction(self.menu) + action.setCheckable(False) + action.setText(name) + + wired_status = get_wired_status(i) + item = NetworkItem(self.menu, action) + item.set_wired_status(wired_status) + action.setDefaultWidget(item) - action = QAction(name) self.actions.append(action) self.menu.addAction(action) @@ -302,7 +313,7 @@ class NetworkMenu(QObject): self.actions.append(action) self.menu.addAction(action) - lines = run_command_and_get_output("wpa_cli", ["scan_results"]).strip().splitlines() + lines = run_command_and_get_output("wpa_cli", ["scan_results"], p).strip().splitlines() if len(lines) > 1: ssids_added_to_menu = [] @@ -579,6 +590,7 @@ class NetworkItem(QWidget): self.lock_label = QLabel() self.lock_label.setPixmap(QIcon.fromTheme("network-wireless-encrypted-symbolic").pixmap(QSize(14, 14))) + self.lock_label.setVisible(False) self.layout.addWidget(self.lock_label) self.signal_label = QLabel() self.signal_label.setPixmap(QIcon.fromTheme("network-wireless-symbolic").pixmap(QSize(16, 16))) @@ -593,6 +605,9 @@ class NetworkItem(QWidget): def set_signal_level(self, signal_level: int): self.signal_label.setPixmap(get_pixmap_for_wpa_status("COMPLETED", signal_level)) + def set_wired_status(self, status: int): + self.signal_label.setPixmap(get_pixmap_for_wired_status(status)) + def minimumSizeHint(self): opt = QStyleOptionMenuItem() opt.initFrom(self) From 2dcdb366732a30ad017dfc71e8cd975014cec3b6 Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Fri, 17 Feb 2023 09:15:43 -0600 Subject: [PATCH 22/31] Save config when a saved network is selected --- Under Construction/Network.app/Network | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index c1646897..b636e8e1 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -439,6 +439,10 @@ Information for debuggers: p.setArguments(["select_network", str(self.network_id)]) p.start() p.waitForFinished() + + p.setArguments(["save_config"]) + p.start() + p.waitForFinished() else: output = run_command_and_get_output("wpa_cli", ["add_network"], p).strip().splitlines()[-1] if not output.isnumeric(): From cc12f187f28737e060247555b5261c47fd7aecb1 Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Sat, 18 Feb 2023 07:32:19 -0600 Subject: [PATCH 23/31] Scan before getting scan results in "updateMenu" --- Under Construction/Network.app/Network | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index b636e8e1..b876a88a 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -276,6 +276,7 @@ class NetworkMenu(QObject): # Find out whether we are connected to one of the networks self.updateStatus() + self.refreshMenu() self.menu.clear() # Second, show @@ -581,7 +582,6 @@ We don't know why this happened.") # https://forum.qt.io/post/367830 class NetworkItem(QWidget): - # password_protected: bool): def __init__(self, parent: QWidget, action: QAction): super().__init__(parent) @@ -596,9 +596,9 @@ class NetworkItem(QWidget): self.lock_label.setPixmap(QIcon.fromTheme("network-wireless-encrypted-symbolic").pixmap(QSize(14, 14))) self.lock_label.setVisible(False) self.layout.addWidget(self.lock_label) - self.signal_label = QLabel() - self.signal_label.setPixmap(QIcon.fromTheme("network-wireless-symbolic").pixmap(QSize(16, 16))) - self.layout.addWidget(self.signal_label) + self.status_label = QLabel() + self.status_label.setPixmap(QIcon.fromTheme("network-wireless-symbolic").pixmap(QSize(16, 16))) + self.layout.addWidget(self.status_label) self.setLayout(self.layout) self.setMouseTracking(True) @@ -607,10 +607,10 @@ class NetworkItem(QWidget): self.lock_label.setVisible(password_protected) def set_signal_level(self, signal_level: int): - self.signal_label.setPixmap(get_pixmap_for_wpa_status("COMPLETED", signal_level)) + self.status_label.setPixmap(get_pixmap_for_wpa_status("COMPLETED", signal_level)) def set_wired_status(self, status: int): - self.signal_label.setPixmap(get_pixmap_for_wired_status(status)) + self.status_label.setPixmap(get_pixmap_for_wired_status(status)) def minimumSizeHint(self): opt = QStyleOptionMenuItem() @@ -644,7 +644,7 @@ class NetworkItem(QWidget): self.lock_label.setPixmap(QPixmap.fromImage(set_monochrome_image_color(self.lock_label.pixmap().toImage(), rgb_color))) - self.signal_label.setPixmap(QPixmap.fromImage(set_monochrome_image_color(self.signal_label.pixmap().toImage(), + self.status_label.setPixmap(QPixmap.fromImage(set_monochrome_image_color(self.status_label.pixmap().toImage(), rgb_color))) self.proxyStyle.drawControl(QStyle.CE_MenuItem, opt, p, self) From 74732d9be293a151bb46cea16894726411aba2bd Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Sat, 18 Feb 2023 12:43:28 -0600 Subject: [PATCH 24/31] Fix string replacement --- Under Construction/Network.app/Network | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index b876a88a..c79bbf81 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -295,7 +295,7 @@ class NetworkMenu(QObject): if "ue" in i: name = "USB Ethernet" else: - name = "Ethernet (%s)" % name + name = "Ethernet (%s)" % i action = QWidgetAction(self.menu) action.setCheckable(False) From 296148df954b9ab2f5f51ea42949d19047987bdc Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Mon, 20 Feb 2023 08:02:09 -0600 Subject: [PATCH 25/31] Set "status_line" field on init to avoid exceptions, add check for "lagg" interface --- Under Construction/Network.app/Network | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index c79bbf81..b48b529e 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -176,6 +176,7 @@ class NetworkMenu(QObject): self.actions = [] self.sliderWindow = None self.check_network_password = False + self.status_lines = "" self.updateStatus() self.timer = QTimer() @@ -284,16 +285,17 @@ class NetworkMenu(QObject): # Get interfaces marked as "UP" but exclude those containing "wlan" interfaces = [i for i in run_command_and_get_output("ifconfig", ["-ul", "ether"], p).strip().split(' ') if "wlan" not in i] - if interfaces: + if interfaces and interfaces[0] != '': action = QAction("Wired") action.setDisabled(True) self.actions.append(action) self.menu.addAction(action) for i in interfaces: - name = "" if "ue" in i: name = "USB Ethernet" + elif "lagg" in i: + name = "Link aggregation (%s)" % i else: name = "Ethernet (%s)" % i @@ -318,9 +320,7 @@ class NetworkMenu(QObject): if len(lines) > 1: ssids_added_to_menu = [] - for line in lines: - if line.startswith("Selected") or line.startswith("bssid"): - continue + for line in lines[2:]: print(line) # Parse out information for each network regex = r"([^\ ]+)\t[^\ ]+\t[^\ ]+\t([^\ ]+)\t(.*)$" @@ -330,10 +330,10 @@ class NetworkMenu(QObject): print(len(matches[0])) bssid = matches[0][0] flags = matches[0][1] - label = matches[0][2] + ssid = matches[0][2] signal_level = get_network_info(bssid).signal_level - ssid = label + label = ssid if label == "" or label.startswith("\\x00"): label = bssid # For networks with hidden ssid (network name) action = QWidgetAction(self.menu) @@ -353,7 +353,7 @@ class NetworkMenu(QObject): if "bssid=" + bssid in self.status_lines: action.setChecked(True) - # TODO: Show networks with same SSID but different BSSID + # TODO: Show networks with same SSID if ssid not in ssids_added_to_menu: self.actions.append(action) self.wirelessGroup.addAction(action) @@ -363,7 +363,6 @@ class NetworkMenu(QObject): self.menu.addSeparator() action = QAction("Rescan Networks") - # action.setDisabled(True) action.triggered.connect(self.refreshMenu) self.actions.append(action) self.menu.addAction(action) @@ -374,10 +373,7 @@ class NetworkMenu(QObject): self.menu.addAction(action) action = QAction("Disconnect") - if "wpa_state=COMPLETED" in self.status_lines: - action.setDisabled(False) - else: - action.setDisabled(True) + action.setEnabled("wpa_state=COMPLETED" in self.status_lines) action.triggered.connect(self.disconnect) self.actions.append(action) self.menu.addAction(action) From c1328b0315390f423cf047d47a444300d433cfb7 Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Mon, 20 Feb 2023 12:40:35 -0600 Subject: [PATCH 26/31] [WIP] Switch to wired network when available and while connected to Wi-Fi, improve log file reading mechanism --- Under Construction/Network.app/Network | 78 ++++++++++++++++---------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index b48b529e..8b95515c 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -23,10 +23,7 @@ from collections import namedtuple import sys, os, re -def run_command_and_get_output(command: str, args, process: QProcess = None) -> str: - if process is None: - process = QProcess() - +def run_command_and_get_output(command: str, args, process = QProcess()) -> str: process.setProgram(command) process.setArguments(args) print(process.program() + " " + " ".join(process.arguments())) @@ -158,7 +155,6 @@ class NetworkMenu(QObject): super().__init__() - # self.showTODO("It can show wireless networks but not connect to them. Do you know how to fix this?") self.showTODO() self.tray = QSystemTrayIcon() @@ -187,6 +183,9 @@ class NetworkMenu(QObject): log_file_watcher = QFileSystemWatcher(["/var/log/messages"], self) log_file_watcher.fileChanged.connect(self.read_log_file) + with open("/var/log/messages", 'rb') as file: + self.log_file_position = file.seek(0, os.SEEK_END) + self.refreshMenu() # Initially populate the menu self.tray.installEventFilter(self) # FIXME: This never seems to get called, why? self.installEventFilter(self) # FIXME: This never seems to get called, why? @@ -318,7 +317,7 @@ class NetworkMenu(QObject): lines = run_command_and_get_output("wpa_cli", ["scan_results"], p).strip().splitlines() - if len(lines) > 1: + if len(lines) > 2: ssids_added_to_menu = [] for line in lines[2:]: print(line) @@ -539,40 +538,57 @@ We don't know why this happened.") def read_log_file(self, path): output = "" - # Read last line of file + # Read lines added to file with open(path, 'rb') as file: try: - file.seek(-2, os.SEEK_END) - while file.read(1) != b'\n': - file.seek(-2, os.SEEK_CUR) + file.seek(self.log_file_position, os.SEEK_CUR) except OSError: - file.seek(0) - output = file.readline().decode() + return - if self.check_network_password and self.wirelessGroup.checkedAction() and\ - ("ssid=\"%s\"" % getattr(self.wirelessGroup.checkedAction(), "ssid")) and "reason=WRONG_KEY" in output: - ssid = getattr(self.wirelessGroup.checkedAction(), "ssid") - bssid = getattr(self.wirelessGroup.checkedAction(), "bssid") - self.disconnect() # Prevent wpa_supplicant from trying to connect and fail again + lines = file.readlines() + self.log_file_position = file.tell() - password, ok = request_network_password("

Please enter the password for the \"%s\" network:


Incorrect password. Please try again." % ssid if ssid else bssid) + for line in lines: + output = line.decode() + print(output) + if self.check_network_password and self.wirelessGroup.checkedAction() and\ + ("ssid=\"%s\"" % getattr(self.wirelessGroup.checkedAction(), "ssid")) and "reason=WRONG_KEY" in output: + ssid = getattr(self.wirelessGroup.checkedAction(), "ssid") + bssid = getattr(self.wirelessGroup.checkedAction(), "bssid") + self.disconnect() # Prevent wpa_supplicant from trying to connect and fail again - if not ok: - self.check_network_password = False - return # Don't try to connect to a network if it has been cancelled. + password, ok = request_network_password("

Please enter the password for the \"%s\" network:


Incorrect password. Please try again." % ssid if ssid else bssid) - p = QProcess() - p.setProgram("wpa_cli") - p.setArguments(["set_network", str(self.network_id), "psk", '"' + password + '"']) - print(p.program() + " " + " ".join(p.arguments())) - p.start() - p.waitForFinished() + if not ok: + self.check_network_password = False + return # Don't try to connect to a network if it has been cancelled. - p.setArguments(["save_config"]) - p.start() - p.waitForFinished() + p = QProcess() + p.setProgram("wpa_cli") + p.setArguments(["set_network", str(self.network_id), "psk", '"' + password + '"']) + print(p.program() + " " + " ".join(p.arguments())) + p.start() + p.waitForFinished() + + p.setArguments(["save_config"]) + p.start() + p.waitForFinished() - self.reconnect() + self.reconnect() + elif "dhclient" in output: + if "New Routers" in output: + gateway = output[output.rfind(":") + 1:].strip() + print(run_command_and_get_output("ifconfig", ["wlan0", "down"])) + print(run_command_and_get_output("route", ["add", "default", gateway])) + elif "Interface" and "no longer appears valid." in output: + interface = output[output.find("Interface ") + 10:] + lines = run_command_and_get_output("/sbin/resolvconf", ["-l", interface]).strip().splitlines() + + if lines: + gateway = lines[1].split(' ')[1] + print(run_command_and_get_output("route", ["delete", "default", gateway])) + + print(run_command_and_get_output("ifconfig", ["wlan0", "up"])) # https://forum.qt.io/post/367830 From 15e88d735d0d70fddf2be45957f3d76e63dd35b8 Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Tue, 21 Feb 2023 10:08:25 -0600 Subject: [PATCH 27/31] Exclude unplugged wired interfaces from lists --- Under Construction/Network.app/Network | 38 ++++++++++++++++++++------ 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index 8b95515c..bdcf981e 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -39,11 +39,8 @@ WIRED_OFFLINE = 2 def get_wired_status(interface_name: str) -> int: output = run_command_and_get_output("ifconfig", ["-v", interface_name]) - has_active_status = "status: active" in output # "status" row is not present for "ue" interfaces - # If it doesn't have an IPv4 address then "inet" is not present, but "inet6" is always there, does that - # mean that it always has an IPv6 address? Is it the MAC address? - if "ue" in interface_name and "inet" in output or has_active_status: + if "inet" in output: wired_status = WIRED_CONNECTED else: wired_status = WIRED_DISCONNECTED @@ -96,6 +93,15 @@ def get_tray_icon_with_color(icon_name: str) -> QPixmap: return QPixmap.fromImage(set_monochrome_image_color(image, 100)) +def is_wired_interface_plugged_in(interface_name: str) -> bool: + output = run_command_and_get_output("ifconfig", ["-v", interface_name]) + status_index = output.find("status:") + + # Some interfaces (like the ones with the "urndis" driver) don't display status, media and parent interface + # rows. Is there a way to relate those two interfaces to provide a more reliable check? + return (status_index == -1 and "ue" in interface_name) or "status: active" in output + + # https://github.com/helloSystem/Menu/blob/c13548d3866c3896d728d2388bd0d4cd636a3a91/plugin-statusnotifier/statusnotifierbutton.cpp#L108 def set_monochrome_image_color(src_image: QImage, rgb_value: int=-1) -> QImage: image: QImage = src_image.convertToFormat(QImage.Format_ARGB32 if src_image.hasAlphaChannel() else QImage.Format_RGB32) @@ -237,6 +243,8 @@ class NetworkMenu(QObject): if "wlan" in i: wlan_present = True continue + if not is_wired_interface_plugged_in(i): + continue wired_status = get_wired_status(i) @@ -282,7 +290,8 @@ class NetworkMenu(QObject): # Second, show p = QProcess() # Get interfaces marked as "UP" but exclude those containing "wlan" - interfaces = [i for i in run_command_and_get_output("ifconfig", ["-ul", "ether"], p).strip().split(' ') if "wlan" not in i] + interfaces = [i for i in run_command_and_get_output("ifconfig", ["-ul", "ether"], p).strip().split(' ') + if "wlan" not in i and is_wired_interface_plugged_in(i)] if interfaces and interfaces[0] != '': action = QAction("Wired") @@ -290,16 +299,26 @@ class NetworkMenu(QObject): self.actions.append(action) self.menu.addAction(action) + route_info = run_command_and_get_output("route", ["-n", "get", "default"]) + interface_index = route_info.find("interface: ") + default_route_interface = "" + + if interface_index != -1: + default_route_interface = route_info[interface_index + 11:route_info.find("\n", interface_index)] + for i in interfaces: if "ue" in i: - name = "USB Ethernet" + name = "USB Ethernet (%s)" elif "lagg" in i: - name = "Link aggregation (%s)" % i + name = "Link aggregation (%s)" else: - name = "Ethernet (%s)" % i + name = "Ethernet (%s)" + + name = name % i action = QWidgetAction(self.menu) - action.setCheckable(False) + action.setCheckable(True) + action.setChecked(i == default_route_interface) action.setText(name) wired_status = get_wired_status(i) @@ -736,6 +755,7 @@ class PasswordRequestDialog(QDialog): def show_password_changed(self, state: int): self.password_line_edit.setEchoMode(QLineEdit.Normal if state else QLineEdit.Password) + if __name__ == "__main__": # Simple singleton: From d51cf374e35aa62c0a49361ba2b5d0eaf55582ec Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Wed, 22 Feb 2023 08:36:14 -0600 Subject: [PATCH 28/31] Show "internet" icon for the default route interface or selected Wi-Fi network, don't disable wireless interface while switching to wired --- Under Construction/Network.app/Network | 88 +++++++++++++++++++------- 1 file changed, 64 insertions(+), 24 deletions(-) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index bdcf981e..d429a575 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -32,6 +32,17 @@ def run_command_and_get_output(command: str, args, process = QProcess()) -> str: return str(process.readAllStandardOutput(), 'UTF-8') +def get_default_route_interface() -> str: + route_info = run_command_and_get_output("route", ["-n", "get", "default"]) + interface_index = route_info.find("interface: ") + default_route_interface = "" + + if interface_index != -1: + default_route_interface = route_info[interface_index + 11:route_info.find("\n", interface_index)] + + return default_route_interface + + WIRED_CONNECTED = 0 WIRED_DISCONNECTED = 1 WIRED_OFFLINE = 2 @@ -155,6 +166,16 @@ def request_network_password(dialog_text: str) -> tuple[str, bool]: return password, ok +def get_gateway_for_interface(interface_name) -> str: + lines = run_command_and_get_output("/sbin/resolvconf", ["-l", interface_name]).strip().splitlines() + gateway = "" + + if lines: + gateway = lines[1].split(' ')[1] + + return gateway + + class NetworkMenu(QObject): def __init__(self): @@ -292,20 +313,15 @@ class NetworkMenu(QObject): # Get interfaces marked as "UP" but exclude those containing "wlan" interfaces = [i for i in run_command_and_get_output("ifconfig", ["-ul", "ether"], p).strip().split(' ') if "wlan" not in i and is_wired_interface_plugged_in(i)] + wired_interfaces_available = len(interfaces) > 0 and interfaces[0] != '' + default_route_interface = get_default_route_interface() - if interfaces and interfaces[0] != '': + if wired_interfaces_available: action = QAction("Wired") action.setDisabled(True) self.actions.append(action) self.menu.addAction(action) - route_info = run_command_and_get_output("route", ["-n", "get", "default"]) - interface_index = route_info.find("interface: ") - default_route_interface = "" - - if interface_index != -1: - default_route_interface = route_info[interface_index + 11:route_info.find("\n", interface_index)] - for i in interfaces: if "ue" in i: name = "USB Ethernet (%s)" @@ -317,12 +333,12 @@ class NetworkMenu(QObject): name = name % i action = QWidgetAction(self.menu) - action.setCheckable(True) - action.setChecked(i == default_route_interface) + action.setCheckable(False) action.setText(name) wired_status = get_wired_status(i) item = NetworkItem(self.menu, action) + item.set_default_route_interface(i == default_route_interface) item.set_wired_status(wired_status) action.setDefaultWidget(item) @@ -370,6 +386,8 @@ class NetworkMenu(QObject): if "bssid=" + bssid in self.status_lines: action.setChecked(True) + # Only display the "internet" icon when there are wired interfaces + item.set_default_route_interface(wired_interfaces_available and "wlan" in default_route_interface) # TODO: Show networks with same SSID if ssid not in ssids_added_to_menu: @@ -420,9 +438,15 @@ class NetworkMenu(QObject): flags = getattr(self.wirelessGroup.checkedAction(), "flags") # self.reconnect() - - # Get a byte string with wpa_cli's output and decode it p = QProcess() + + #default_route_interface = get_default_route_interface() + + #if "wlan" not in default_route_interface: + # gateway = get_gateway_for_interface("wlan0") + # print(run_command_and_get_output("route", ["change", "default", gateway, "-ifp", "wlan0"])) + + # Get a byte string with wpa_cli's output and decode it # Split that output into lines, ignoring the useless ones output = run_command_and_get_output("wpa_cli", ["list_networks"], p) network_list = output.strip().splitlines() @@ -576,7 +600,9 @@ We don't know why this happened.") bssid = getattr(self.wirelessGroup.checkedAction(), "bssid") self.disconnect() # Prevent wpa_supplicant from trying to connect and fail again - password, ok = request_network_password("

Please enter the password for the \"%s\" network:


Incorrect password. Please try again." % ssid if ssid else bssid) + password, ok = request_network_password( + "

Please enter the password for the \"%s\" network:

" + "
Incorrect password. Please try again." % ssid if ssid else bssid) if not ok: self.check_network_password = False @@ -594,20 +620,25 @@ We don't know why this happened.") p.waitForFinished() self.reconnect() - elif "dhclient" in output: - if "New Routers" in output: + elif "dhclient" in output and "wlan" not in output: + default_route_interface = get_default_route_interface() + + if "New Routers" in output and "wlan" in default_route_interface: + # print(run_command_and_get_output("ifconfig", ["wlan0", "down"])) + # self.disconnect() + interface_name = output[output.rfind("(") + 1:output.rfind(")")] gateway = output[output.rfind(":") + 1:].strip() - print(run_command_and_get_output("ifconfig", ["wlan0", "down"])) - print(run_command_and_get_output("route", ["add", "default", gateway])) - elif "Interface" and "no longer appears valid." in output: - interface = output[output.find("Interface ") + 10:] - lines = run_command_and_get_output("/sbin/resolvconf", ["-l", interface]).strip().splitlines() + print(run_command_and_get_output("route", ["change", "default", gateway, "-ifp", interface_name])) + elif "no longer appears valid." in output: + # TODO: Switch to another wired interface if available + # interface = output[output.find("Interface ") + 10:output.find("no longer")] + gateway = get_gateway_for_interface("wlan0") - if lines: - gateway = lines[1].split(' ')[1] - print(run_command_and_get_output("route", ["delete", "default", gateway])) + if gateway: + print(run_command_and_get_output("route", ["change", "default", gateway, "-ifp", "wlan0"])) + # self.reconnect() - print(run_command_and_get_output("ifconfig", ["wlan0", "up"])) + # print(run_command_and_get_output("ifconfig", ["wlan0", "up"])) # https://forum.qt.io/post/367830 @@ -623,6 +654,10 @@ class NetworkItem(QWidget): self.layout.setAlignment(Qt.AlignTrailing) self.layout.setContentsMargins(0, 0, 10, 0) + self.default_route_interface_label = QLabel() + self.default_route_interface_label.setPixmap(QIcon.fromTheme("web-browser-symbolic").pixmap(QSize(14, 14))) + self.default_route_interface_label.setVisible(False) + self.layout.addWidget(self.default_route_interface_label) self.lock_label = QLabel() self.lock_label.setPixmap(QIcon.fromTheme("network-wireless-encrypted-symbolic").pixmap(QSize(14, 14))) self.lock_label.setVisible(False) @@ -634,6 +669,9 @@ class NetworkItem(QWidget): self.setLayout(self.layout) self.setMouseTracking(True) + def set_default_route_interface(self, default: bool): + self.default_route_interface_label.setVisible(default) + def set_password_protected(self, password_protected: bool): self.lock_label.setVisible(password_protected) @@ -673,6 +711,8 @@ class NetworkItem(QWidget): else: rgb_color = 0 + self.default_route_interface_label.setPixmap(QPixmap.fromImage(set_monochrome_image_color( + self.default_route_interface_label.pixmap().toImage(), rgb_color))) self.lock_label.setPixmap(QPixmap.fromImage(set_monochrome_image_color(self.lock_label.pixmap().toImage(), rgb_color))) self.status_label.setPixmap(QPixmap.fromImage(set_monochrome_image_color(self.status_label.pixmap().toImage(), From 89e8e9b59a8fa3a56c0a871c0773d56b265679a3 Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Wed, 22 Feb 2023 09:13:44 -0600 Subject: [PATCH 29/31] Use netifaces library --- Under Construction/Network.app/Network | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index d429a575..164b6f22 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -49,9 +49,9 @@ WIRED_OFFLINE = 2 def get_wired_status(interface_name: str) -> int: - output = run_command_and_get_output("ifconfig", ["-v", interface_name]) + addresses: dict = netifaces.ifaddresses(interface_name) - if "inet" in output: + if netifaces.AF_INET in addresses: wired_status = WIRED_CONNECTED else: wired_status = WIRED_DISCONNECTED From 980bb0d797c861d7f9354df3332d82ae8909439b Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Wed, 22 Feb 2023 10:00:47 -0600 Subject: [PATCH 30/31] Add missing import, update "wired status" when menu is shown --- Under Construction/Network.app/Network | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index 164b6f22..e507dc87 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -20,7 +20,7 @@ from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QMenu, QAction, QHBox from PyQt5.QtGui import QIcon, QPixmap, QCursor, QPainter, QPaintEvent, QImage, qGray, qAlpha, qRgba from PyQt5.QtCore import Qt, QProcess, QObject, QTimer, QSize, QFileSystemWatcher from collections import namedtuple -import sys, os, re +import sys, os, re, netifaces def run_command_and_get_output(command: str, args, process = QProcess()) -> str: @@ -300,6 +300,9 @@ class NetworkMenu(QObject): network_info = get_network_info(getattr(action, "bssid")) action.defaultWidget().set_signal_level(network_info.signal_level) action.defaultWidget().set_password_protected("PSK" in network_info.flags) + elif hasattr(action, "wired_interface"): + wired_status = get_wired_status(getattr(action, "wired_interface")) + action.defaultWidget().set_wired_status(wired_status) def updateMenu(self): @@ -333,6 +336,7 @@ class NetworkMenu(QObject): name = name % i action = QWidgetAction(self.menu) + action.__setattr__("wired_interface", i) action.setCheckable(False) action.setText(name) From 805392da0975d8f3dfc359d2c107f362941c0d06 Mon Sep 17 00:00:00 2001 From: CocoCR300 <55771921+CocoCR300@users.noreply.github.com> Date: Wed, 22 Feb 2023 10:56:05 -0600 Subject: [PATCH 31/31] Add a real check for USB Tethered interfaces --- Under Construction/Network.app/Network | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Under Construction/Network.app/Network b/Under Construction/Network.app/Network index e507dc87..d89cc15c 100755 --- a/Under Construction/Network.app/Network +++ b/Under Construction/Network.app/Network @@ -104,17 +104,27 @@ def get_tray_icon_with_color(icon_name: str) -> QPixmap: return QPixmap.fromImage(set_monochrome_image_color(image, 100)) +def is_usb_tethered_interface(interface_name: str) -> bool: + if "ue" in interface_name: + number = interface_name[-1] + output = run_command_and_get_output("sysctl", ["net.ue.%s.%%parent" % number]) + parent = output[output.rfind(":") + 2:].strip() + return parent[0:-1] in ["urndis", "cdce", "ipheth"] + + return False + + def is_wired_interface_plugged_in(interface_name: str) -> bool: output = run_command_and_get_output("ifconfig", ["-v", interface_name]) - status_index = output.find("status:") # Some interfaces (like the ones with the "urndis" driver) don't display status, media and parent interface - # rows. Is there a way to relate those two interfaces to provide a more reliable check? - return (status_index == -1 and "ue" in interface_name) or "status: active" in output + # rows (I assume this is the same for any USB Tethered interface), but unlike USB Ethernet adapters, + # if they are present that means that they are really "plugged in". + return "status: active" in output or is_usb_tethered_interface(interface_name) # https://github.com/helloSystem/Menu/blob/c13548d3866c3896d728d2388bd0d4cd636a3a91/plugin-statusnotifier/statusnotifierbutton.cpp#L108 -def set_monochrome_image_color(src_image: QImage, rgb_value: int=-1) -> QImage: +def set_monochrome_image_color(src_image: QImage, rgb_value: int = -1) -> QImage: image: QImage = src_image.convertToFormat(QImage.Format_ARGB32 if src_image.hasAlphaChannel() else QImage.Format_RGB32) pointer: voidptr = image.bits() pixel_array = cast(pointer.__int__(), POINTER(c_uint32))