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.
This commit is contained in:
Gustavo Iñiguez Goia 2020-11-19 01:00:58 +01:00
parent df3e7c3ef7
commit c969e7909d
6 changed files with 81 additions and 90 deletions

View file

@ -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

View file

@ -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)

View file

@ -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()

View file

@ -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()

View file

@ -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")

View file

@ -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