From c969e7909d57aff1efea38538bc472dcf99b4d9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20I=C3=B1iguez=20Goia?= Date: Thu, 19 Nov 2020 01:00:58 +0100 Subject: [PATCH] ui: fallback to Qt built-in icons if no valid icon theme configured There're several situations where the icons of the app don't show up: - icon theme not configured. - icon theme configured but lacks standard icons defined by the standard (freedesktop). - icon theme configured but Qt doesn't load it. If we fall into any of these cases, use the Qt built-in icons . More information on this issue: #53 * removed non-used imports. --- ui/opensnitch/dialogs/preferences.py | 24 +++++++------- ui/opensnitch/dialogs/processdetails.py | 33 ++++++++++++------- ui/opensnitch/dialogs/prompt.py | 16 ++++----- ui/opensnitch/dialogs/ruleseditor.py | 38 ++++++++++++---------- ui/opensnitch/dialogs/stats.py | 17 ++++++++++ ui/opensnitch/service.py | 43 +------------------------ 6 files changed, 81 insertions(+), 90 deletions(-) diff --git a/ui/opensnitch/dialogs/preferences.py b/ui/opensnitch/dialogs/preferences.py index 1a4092bf..0194a3da 100644 --- a/ui/opensnitch/dialogs/preferences.py +++ b/ui/opensnitch/dialogs/preferences.py @@ -1,10 +1,7 @@ -import threading -import logging import sys import time import os import json -from datetime import datetime from PyQt5 import QtCore, QtGui, uic, QtWidgets @@ -20,7 +17,7 @@ class PreferencesDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): CFG_DEFAULT_DURATION = "global/default_duration" CFG_DEFAULT_TARGET = "global/default_target" CFG_DEFAULT_TIMEOUT = "global/default_timeout" - + LOG_TAG = "[Preferences] " _notification_callback = QtCore.pyqtSignal(ui_pb2.NotificationReply) @@ -39,9 +36,14 @@ class PreferencesDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): self.applyButton.clicked.connect(self._cb_apply_button_clicked) self.cancelButton.clicked.connect(self._cb_cancel_button_clicked) + if QtGui.QIcon.hasThemeIcon("emblem-default") == False: + self.applyButton.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_DialogApplyButton"))) + self.cancelButton.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_DialogCloseButton"))) + self.acceptButton.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_DialogSaveButton"))) + def showEvent(self, event): super(PreferencesDialog, self).showEvent(event) - + try: self._reset_status_message() self._hide_status_label() @@ -57,7 +59,7 @@ class PreferencesDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): print(self.LOG_TAG + "exception loading nodes", e) self._load_settings() - + # connect the signals after loading settings, to avoid firing # the signals self.comboNodes.currentIndexChanged.connect(self._cb_node_combo_changed) @@ -69,7 +71,7 @@ class PreferencesDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): self.comboNodeAddress.currentIndexChanged.connect(self._cb_node_needs_update) self.checkInterceptUnknown.clicked.connect(self._cb_node_needs_update) self.checkApplyToNodes.clicked.connect(self._cb_node_needs_update) - + # True when any node option changes self._node_needs_update = False @@ -129,7 +131,7 @@ class PreferencesDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): self._cfg.setSettings(self.CFG_DEFAULT_DURATION, self.comboUIDuration.currentText()) self._cfg.setSettings(self.CFG_DEFAULT_TARGET, self.comboUITarget.currentIndex()) self._cfg.setSettings(self.CFG_DEFAULT_TIMEOUT, self.spinUITimeout.value()) - + elif self.tabWidget.currentIndex() == 1: self._show_status_label() @@ -155,7 +157,7 @@ class PreferencesDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): except Exception as e: print(self.LOG_TAG + "exception saving config: ", e) self._set_status_error("Exception saving config: %s" % str(e)) - + self._node_needs_update = False def _save_node_config(self, notifObject, addr): @@ -239,7 +241,7 @@ class PreferencesDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): def _cb_apply_button_clicked(self): self._save_settings() - + def _cb_cancel_button_clicked(self): self.reject() @@ -247,4 +249,4 @@ class PreferencesDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): self._load_node_settings() def _cb_node_needs_update(self): - self._node_needs_update = True + self._node_needs_update = True diff --git a/ui/opensnitch/dialogs/processdetails.py b/ui/opensnitch/dialogs/processdetails.py index 0524dddf..1cf309c8 100644 --- a/ui/opensnitch/dialogs/processdetails.py +++ b/ui/opensnitch/dialogs/processdetails.py @@ -2,9 +2,7 @@ import os import sys import json -from PyQt5 import Qt, QtCore, QtGui, uic, QtWidgets -from PyQt5.QtSql import QSqlDatabase, QSqlDatabase, QSqlQueryModel, QSqlQuery, QSqlTableModel -from PyQt5.QtGui import QColor +from PyQt5 import QtCore, QtGui, uic, QtWidgets import ui_pb2 from nodes import Nodes @@ -12,9 +10,9 @@ from desktop_parser import LinuxDesktopParser DIALOG_UI_PATH = "%s/../res/process_details.ui" % os.path.dirname(sys.modules[__name__].__file__) class ProcessDetailsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): - + LOG_TAG = "[ProcessDetails]: " - + _notification_callback = QtCore.pyqtSignal(ui_pb2.NotificationReply) TAB_STATUS = 0 @@ -56,17 +54,17 @@ class ProcessDetailsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]) QtWidgets.QDialog.__init__(self, parent, QtCore.Qt.WindowStaysOnTopHint) self.setWindowFlags(QtCore.Qt.Window) self.setupUi(self) - + self._app_name = None self._app_icon = None self._apps_parser = LinuxDesktopParser() self._nodes = Nodes.instance() self._notification_callback.connect(self._cb_notification_callback) - + self._nid = None self._pid = "" self._notifications_sent = {} - + self.cmdClose.clicked.connect(self._cb_close_clicked) self.cmdAction.clicked.connect(self._cb_action_clicked) self.comboPids.currentIndexChanged.connect(self._cb_combo_pids_changed) @@ -78,6 +76,14 @@ class ProcessDetailsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]) self.TABS[self.TAB_STACK]['text'] = self.textStack self.TABS[self.TAB_ENVS]['text'] = self.textEnv + self.iconStart = QtGui.QIcon.fromTheme("media-playback-start") + self.iconPause = QtGui.QIcon.fromTheme("media-playback-pause") + + if QtGui.QIcon.hasThemeIcon("window-close") == False: + self.cmdClose.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_DialogCloseButton"))) + self.iconStart = self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_MediaPlay")) + self.iconPause = self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_MediaPause")) + @QtCore.pyqtSlot(ui_pb2.NotificationReply) def _cb_notification_callback(self, reply): if reply.id in self._notifications_sent: @@ -114,7 +120,7 @@ class ProcessDetailsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]) def _cb_close_clicked(self): self._close() - + def _cb_combo_pids_changed(self, idx): if idx == -1: return @@ -127,9 +133,10 @@ class ProcessDetailsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]) def _cb_action_clicked(self): if not self.cmdAction.isChecked(): self._stop_monitoring() + self.cmdAction.setIcon(self.iconStart) else: + self.cmdAction.setIcon(self.iconPause) self._start_monitoring() - self.cmdAction.setChecked(self.cmdAction.isChecked()) def _show_message(self, message): msgBox = QtWidgets.QMessageBox() @@ -153,7 +160,7 @@ class ProcessDetailsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]) self.labelCwd.setText("") for tidx in range(0, len(self.TABS)): self.TABS[tidx]['text'].setPlainText("") - + def _close(self): self._stop_monitoring() self.comboPids.clear() @@ -188,6 +195,7 @@ class ProcessDetailsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]) if self._pid == "": return + self.cmdAction.setIcon(self.iconPause) self.cmdAction.setChecked(True) noti = ui_pb2.Notification(clientName="", serverName="", type=ui_pb2.MONITOR_PROCESS, data=self._pid, rules=[]) self._nid = self._nodes.send_notification(self._pids[self._pid], noti, self._notification_callback) @@ -199,6 +207,7 @@ class ProcessDetailsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]) if self._pid == "": return + self.cmdAction.setIcon(self.iconStart) self.cmdAction.setChecked(False) noti = ui_pb2.Notification(clientName="", serverName="", type=ui_pb2.STOP_MONITOR_PROCESS, data=str(self._pid), rules=[]) self._nid = self._nodes.send_notification(self._pids[self._pid], noti, self._notification_callback) @@ -250,7 +259,7 @@ class ProcessDetailsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]) return self._app_name, self._app_icon, _ = self._apps_parser.get_info_by_path(proc_path, "terminal") - + icon = QtGui.QIcon().fromTheme(self._app_icon) pixmap = icon.pixmap(icon.actualSize(QtCore.QSize(48, 48))) self.labelProcIcon.setPixmap(pixmap) diff --git a/ui/opensnitch/dialogs/prompt.py b/ui/opensnitch/dialogs/prompt.py index 398f9de6..1677e26b 100644 --- a/ui/opensnitch/dialogs/prompt.py +++ b/ui/opensnitch/dialogs/prompt.py @@ -1,12 +1,9 @@ import threading -import logging import sys import time import os import pwd import json -import re -from datetime import datetime from PyQt5 import QtCore, QtGui, uic, QtWidgets @@ -75,6 +72,10 @@ class PromptDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): self._ischeckAdvanceded = False self.checkAdvanced.toggled.connect(self._checkbox_toggled) + if QtGui.QIcon.hasThemeIcon("emblem-default") == False: + self.applyButton.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_DialogApplyButton"))) + self.denyButton.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_DialogCancelButton"))) + def showEvent(self, event): super(PromptDialog, self).showEvent(event) self.resize(540, 300) @@ -114,7 +115,7 @@ class PromptDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): self._tick_thread.start() # wait for user choice or timeout self._done.wait() - + return self._rule, self._timeout_triggered def _timeout_worker(self): @@ -133,7 +134,7 @@ class PromptDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): self._tick -= 1 self._tick_trigger.emit() time.sleep(1) - + if not self._done.is_set(): self._timeout_trigger.emit() @@ -286,7 +287,7 @@ class PromptDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): if not event.key() == QtCore.Qt.Key_Escape: super(PromptDialog, self).keyPressEvent(event) - # prevent a click on the window's x + # prevent a click on the window's x # from quitting the whole application def closeEvent(self, e): self._send_rule() @@ -407,7 +408,6 @@ class PromptDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): self.checkAdvanced.toggle() self._idcheckAdvanceded = False - # signal that the user took a decision and + # signal that the user took a decision and # a new rule is available self._done.set() - diff --git a/ui/opensnitch/dialogs/ruleseditor.py b/ui/opensnitch/dialogs/ruleseditor.py index 1ce5b364..b2a360ed 100644 --- a/ui/opensnitch/dialogs/ruleseditor.py +++ b/ui/opensnitch/dialogs/ruleseditor.py @@ -1,5 +1,5 @@ -from PyQt5 import Qt, QtCore, QtGui, uic, QtWidgets +from PyQt5 import QtCore, QtGui, uic, QtWidgets from slugify import slugify from datetime import datetime import re @@ -43,6 +43,10 @@ class RulesEditorDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): self.dstIPCheck.toggled.connect(self._cb_dstip_check_toggled) self.dstHostCheck.toggled.connect(self._cb_dsthost_check_toggled) + if QtGui.QIcon.hasThemeIcon("emblem-default") == False: + self.actionAllowRadio.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_DialogApplyButton"))) + self.actionDenyRadio.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_DialogCancelButton"))) + if _rule != None: self._load_rule(rule=_rule) @@ -140,19 +144,19 @@ class RulesEditorDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): self.procCheck.setChecked(False) self.procLine.setText("") - + self.cmdlineCheck.setChecked(False) self.cmdlineLine.setText("") - + self.uidCheck.setChecked(False) self.uidLine.setText("") - + self.dstPortCheck.setChecked(False) self.dstPortLine.setText("") - + self.dstIPCheck.setChecked(False) self.dstIPLine.setText("") - + self.dstHostCheck.setChecked(False) self.dstHostLine.setText("") @@ -168,7 +172,7 @@ class RulesEditorDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): self.actionAllowRadio.setChecked(True) self.durationCombo.setCurrentText(self.rule.duration) - + if self.rule.operator.type != "list": self._load_rule_operator(self.rule.operator) else: @@ -180,7 +184,7 @@ class RulesEditorDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): op = ui_pb2.Operator(type=r['type'], operand=r['operand'], data=r['data'], sensitive=_sensitive) self._load_rule_operator(op) - + def _load_rule_operator(self, operator): self.sensitiveCheck.setChecked(operator.sensitive) if operator.operand == "protocol": @@ -266,7 +270,7 @@ class RulesEditorDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): self._notifications_sent[nid] = notif except Exception as e: print(self.LOG_TAG, "add_rule() exception: ", e) - + def _delete_rule(self): try: if self._old_rule_name != None: @@ -286,8 +290,8 @@ class RulesEditorDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): self._old_rule_name = None except Exception as e: print(self.LOG_TAG, "delete_rule() exception: ", e) - - + + def _save_rule(self): """ Create a new rule based on the fields selected. @@ -304,7 +308,7 @@ class RulesEditorDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): self.rule.precedence = self.precedenceCheck.isChecked() self.rule.action = "deny" if self.actionDenyRadio.isChecked() else "allow" self.rule.duration = self.durationCombo.currentText() - + # FIXME: there should be a sensitive checkbox per operand self.rule.operator.sensitive = self.sensitiveCheck.isChecked() rule_data = [] @@ -361,7 +365,7 @@ class RulesEditorDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): rule_data[len(rule_data)-1]['type'] = "regexp" if self._is_valid_regex(self.cmdlineLine.text()) == False: return False, "Command line regexp error" - + if self.dstPortCheck.isChecked(): if self.dstPortLine.text() == "": return False, "Dest port can not be empty" @@ -379,7 +383,7 @@ class RulesEditorDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): rule_data[len(rule_data)-1]['type'] = "regexp" if self._is_valid_regex(self.dstPortLine.text()) == False: return False, "Dst port regexp error" - + if self.dstHostCheck.isChecked(): if self.dstHostLine.text() == "": return False, "Dest host can not be empty" @@ -397,7 +401,7 @@ class RulesEditorDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): rule_data[len(rule_data)-1]['type'] = "regexp" if self._is_valid_regex(self.dstHostLine.text()) == False: return False, "Dst host regexp error" - + if self.dstIPCheck.isChecked(): if self.dstIPLine.text() == "": return False, "Dest IP can not be empty" @@ -415,7 +419,7 @@ class RulesEditorDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): rule_data[len(rule_data)-1]['type'] = "regexp" if self._is_valid_regex(self.dstIPLine.text()) == False: return False, "Dst IP regexp error" - + if self.uidCheck.isChecked(): if self.uidLine.text() == "": return False, "User ID can not be empty" @@ -464,7 +468,7 @@ class RulesEditorDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): self.rule.operator.data = "" if records.value(10) == None else str(records.value(10)) self._old_rule_name = records.value(2) - + self._load_rule(addr=_addr, rule=self.rule) self.show() diff --git a/ui/opensnitch/dialogs/stats.py b/ui/opensnitch/dialogs/stats.py index aab19352..c0826a3a 100644 --- a/ui/opensnitch/dialogs/stats.py +++ b/ui/opensnitch/dialogs/stats.py @@ -373,6 +373,9 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): 'users.csv' ) + if QtGui.QIcon.hasThemeIcon("document-new") == False: + self._configure_buttons_icons() + def showEvent(self, event): super(StatsDialog, self).showEvent(event) self._shown_trigger.emit() @@ -384,6 +387,20 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): self.setWindowTitle(window_title) self._refresh_active_table() + def _configure_buttons_icons(self): + self.newRuleButton.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_FileIcon"))) + self.delRuleButton.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_TrashIcon"))) + self.editRuleButton.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_FileDialogDetailedView"))) + self.saveButton.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_DialogSaveButton"))) + self.prefsButton.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_FileDialogDetailedView"))) + self.startButton.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_MediaPlay"))) + self.cmdProcDetails.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_FileDialogContentsView"))) + self.TABLES[self.TAB_MAIN]['cmdCleanStats'].setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_DialogResetButton"))) + for idx in range(1,8): + self.TABLES[idx]['cmd'].setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_ArrowLeft"))) + if "cmdCleanStats" in self.TABLES[idx]: + self.TABLES[idx]['cmdCleanStats'].setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_DialogResetButton"))) + def _load_settings(self): dialog_geometry = self._cfg.getSettings("statsDialog/geometry") dialog_last_tab = self._cfg.getSettings("statsDialog/last_tab") diff --git a/ui/opensnitch/service.py b/ui/opensnitch/service.py index a3188711..cbe28b19 100644 --- a/ui/opensnitch/service.py +++ b/ui/opensnitch/service.py @@ -1,9 +1,7 @@ -from PyQt5 import QtWidgets, QtGui, QtCore, Qt -from PyQt5.QtSql import QSqlDatabase, QSqlDatabase, QSqlQueryModel, QSqlQuery +from PyQt5 import QtWidgets, QtGui, QtCore from datetime import datetime, timedelta from threading import Thread, Lock, Event -from queue import Queue import time import os import socket @@ -73,35 +71,6 @@ class UIService(ui_pb2_grpc.UIServicer, QtWidgets.QGraphicsObject): 'users':{} } - if QtGui.QIcon.hasThemeIcon("document-new") == False: - hasFallback = "fallbackThemeName" in dir(QtGui.QIcon) - if hasFallback: - fTheme = QtGui.QIcon.fallbackThemeName() - if fTheme != "": - QtGui.QIcon.setThemeName(fTheme) - else: - self._set_alternative_theme() - else: - self._set_alternative_theme() - - - def _set_alternative_theme(self): - themes = os.listdir("/usr/share/icons") - try: - themes.remove("HighContrast") - themes.remove("hicolor") - themes.remove("locolor") - except Exception: - pass - - for theme in themes: - QtGui.QIcon.setThemeName(theme) - if QtGui.QIcon.hasThemeIcon("document-new"): - return - - if QtGui.QIcon.themeName() == "" or QtGui.QIcon.hasThemeIcon("document-new") == False: - self._show_theme_empty_dialog() - # https://gist.github.com/pklaus/289646 def _setup_interfaces(self): max_possible = 128 # arbitrary. raise if needed. @@ -186,16 +155,6 @@ class UIService(ui_pb2_grpc.UIServicer, QtWidgets.QGraphicsObject): self._tray.setIcon(self.white_icon) self._stats_dialog.show() - def _show_theme_empty_dialog(self): - self._msg.setIcon(QtWidgets.QMessageBox.Warning) - self._msg.setWindowTitle("OpenSnitch") - self._msg.setText("Your Desktop Environment doesn't have an icon theme configured, " + \ - "or it lacks of some icons (document-new, document-save)." + \ - "\n\nPlease, use gnome-tweaks or other tool to set an icon theme." - ) - self._msg.setStandardButtons(QtWidgets.QMessageBox.Ok) - self._msg.show() - @QtCore.pyqtSlot() def _on_status_change(self): self._stats_dialog.daemon_connected = self._connected