diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..1d70b52
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,14 @@
+include:
+ - project: 'QubesOS/qubes-continuous-integration'
+ file: '/r4.1/gitlab-base.yml'
+ - project: 'QubesOS/qubes-continuous-integration'
+ file: '/r4.1/gitlab-dom0.yml'
+ - project: 'QubesOS/qubes-continuous-integration'
+ file: '/r4.1/gitlab-vm.yml'
+
+checks:pylint:
+ stage: checks
+ before_script:
+ - pip3 install --quiet -r ci/requirements.txt
+ script:
+ - PYTHONPATH="test-packages:$PYTHONPATH" python3 -m pylint qubesmanager
diff --git a/ci/pylintrc b/.pylintrc
similarity index 100%
rename from ci/pylintrc
rename to .pylintrc
diff --git a/debian/control b/debian/control
index ebfaee6..78f87df 100644
--- a/debian/control
+++ b/debian/control
@@ -7,7 +7,7 @@ Build-Depends:
dh-python,
python3-all,
python3-setuptools,
- qt5-default,
+ qtbase5-dev,
qttools5-dev-tools,
pyqt5-dev-tools
Standards-Version: 4.3.0
diff --git a/debian/rules b/debian/rules
index f2b3a6b..465c69f 100755
--- a/debian/rules
+++ b/debian/rules
@@ -2,6 +2,7 @@
include /usr/share/dpkg/default.mk
export DESTDIR=$(shell pwd)/debian/tmp
+export QT_SELECT=qt5
%:
dh $@ --with python3 --buildsystem=pybuild
diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py
index 2550435..64bd2dd 100644
--- a/qubesmanager/qube_manager.py
+++ b/qubesmanager/qube_manager.py
@@ -37,7 +37,7 @@ from PyQt5.QtCore import (Qt, QAbstractTableModel, QObject, pyqtSlot, QEvent,
# pylint: disable=import-error
from PyQt5.QtWidgets import (QLineEdit, QStyledItemDelegate, QToolTip,
QMenu, QInputDialog, QMainWindow, QProgressDialog, QStyleOptionViewItem,
- QAbstractItemView, QMessageBox, QAction)
+ QMessageBox)
# pylint: disable=import-error
from PyQt5.QtGui import (QIcon, QPixmap, QRegExpValidator, QFont, QColor)
@@ -630,8 +630,11 @@ class RunCommandThread(common_threads.QubesThread):
except (ChildProcessError, exc.QubesException) as ex:
self.msg = (self.tr("Error while running command!"), str(ex))
-
class QubesProxyModel(QSortFilterProxyModel):
+ def __init__(self, window):
+ super().__init__()
+ self.window = window
+
def lessThan(self, left, right):
if left.data(self.sortRole()) != right.data(self.sortRole()):
return super().lessThan(left, right)
@@ -641,6 +644,31 @@ class QubesProxyModel(QSortFilterProxyModel):
return left_vm.name.lower() < right_vm.name.lower()
+ # pylint: disable=too-many-return-statements
+ def filterAcceptsRow(self, sourceRow, sourceParent):
+ if self.window.show_all.isChecked():
+ return super().filterAcceptsRow(sourceRow, sourceParent)
+
+ index = self.sourceModel().index(sourceRow, 0, sourceParent)
+ vm = self.sourceModel().data(index, Qt.UserRole)
+
+ if self.window.show_running.isChecked() and \
+ vm.state['power'] == 'Running':
+ return super().filterAcceptsRow(sourceRow, sourceParent)
+ if self.window.show_halted.isChecked() and \
+ vm.state['power'] == 'Halted':
+ return super().filterAcceptsRow(sourceRow, sourceParent)
+ if self.window.show_network.isChecked() and \
+ getattr(vm.vm, 'provides_network', False):
+ return super().filterAcceptsRow(sourceRow, sourceParent)
+ if self.window.show_templates.isChecked() and vm.klass == 'TemplateVM':
+ return super().filterAcceptsRow(sourceRow, sourceParent)
+ if self.window.show_standalone.isChecked() \
+ and vm.klass == 'StandaloneVM':
+ return super().filterAcceptsRow(sourceRow, sourceParent)
+
+ return False
+
class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
# suppress saving settings while initializing widgets
@@ -659,7 +687,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
self.searchbox.setValidator(QRegExpValidator(
QRegExp("[a-zA-Z0-9_-]*", Qt.CaseInsensitive), None))
self.searchbox.textChanged.connect(self.do_search)
- self.searchContainer.addWidget(self.searchbox)
+ self.searchContainer.insertWidget(1, self.searchbox)
self.settings_windows = {}
@@ -668,33 +696,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
self.init_template_menu()
self.init_network_menu()
- self.context_menu = QMenu(self)
-
- self.context_menu.addAction(self.action_settings)
- self.context_menu.addMenu(self.template_menu)
- self.context_menu.addMenu(self.network_menu)
- self.context_menu.addAction(self.action_editfwrules)
- self.context_menu.addAction(self.action_appmenus)
- self.context_menu.addAction(self.action_set_keyboard_layout)
- self.context_menu.addSeparator()
-
- self.context_menu.addAction(self.action_updatevm)
- self.context_menu.addAction(self.action_run_command_in_vm)
- self.context_menu.addAction(self.action_open_console)
- self.context_menu.addAction(self.action_resumevm)
- self.context_menu.addAction(self.action_startvm_tools_install)
- self.context_menu.addAction(self.action_pausevm)
- self.context_menu.addAction(self.action_shutdownvm)
- self.context_menu.addAction(self.action_restartvm)
- self.context_menu.addAction(self.action_killvm)
- self.context_menu.addSeparator()
-
- self.context_menu.addAction(self.action_clonevm)
- self.context_menu.addAction(self.action_removevm)
- self.context_menu.addSeparator()
-
- self.context_menu.addMenu(self.logs_menu)
- self.context_menu.addSeparator()
+ self.__init_context_menu()
self.tools_context_menu = QMenu(self)
self.tools_context_menu.addAction(self.action_toolbar)
@@ -706,6 +708,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
lambda pos: self.open_tools_context_menu(self.toolbar, pos))
self.action_menubar.toggled.connect(self.showhide_menubar)
self.action_toolbar.toggled.connect(self.showhide_toolbar)
+ self.action_compact_view.toggled.connect(self.set_compactview)
self.logs_menu.triggered.connect(self.show_log)
self.table.resizeColumnsToContents()
@@ -717,7 +720,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
self.fill_cache()
self.qubes_model = QubesTableModel(self.qubes_cache)
- self.proxy = QubesProxyModel()
+ self.proxy = QubesProxyModel(self)
self.proxy.setSourceModel(self.qubes_model)
self.proxy.setSortRole(Qt.UserRole + 1)
self.proxy.setSortCaseSensitivity(Qt.CaseInsensitive)
@@ -727,10 +730,16 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
self.proxy.layoutChanged.connect(self.update_template_menu)
self.proxy.layoutChanged.connect(self.update_network_menu)
+ self.show_running.stateChanged.connect(self.invalidate)
+ self.show_halted.stateChanged.connect(self.invalidate)
+ self.show_network.stateChanged.connect(self.invalidate)
+ self.show_templates.stateChanged.connect(self.invalidate)
+ self.show_standalone.stateChanged.connect(self.invalidate)
+ self.show_all.stateChanged.connect(self.invalidate)
+
self.table.setModel(self.proxy)
self.table.setItemDelegateForColumn(3, StateIconDelegate())
self.table.resizeColumnsToContents()
- self.table.setSelectionMode(QAbstractItemView.ExtendedSelection)
selection_model = self.table.selectionModel()
selection_model.selectionChanged.connect(self.table_selection_changed)
@@ -747,6 +756,8 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
self.menu_view.addSeparator()
self.menu_view.addAction(self.action_toolbar)
self.menu_view.addAction(self.action_menubar)
+ self.menu_view.addSeparator()
+ self.menu_view.addAction(self.action_compact_view)
try:
self.load_manager_settings()
@@ -850,12 +861,54 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
self.tr("Change Network Error"),
self.tr((str(ex))))
+ def __init_context_menu(self):
+ self.context_menu = QMenu(self)
+ self.context_menu.addAction(self.action_settings)
+ self.context_menu.addAction(self.template_menu.menuAction())
+ self.context_menu.addAction(self.network_menu.menuAction())
+ self.context_menu.addAction(self.action_editfwrules)
+ self.context_menu.addAction(self.action_appmenus)
+ self.context_menu.addAction(self.action_set_keyboard_layout)
+ self.context_menu.addSeparator()
+ self.context_menu.addAction(self.action_updatevm)
+ self.context_menu.addAction(self.action_run_command_in_vm)
+ self.context_menu.addAction(self.action_open_console)
+ self.context_menu.addAction(self.action_resumevm)
+ self.context_menu.addAction(self.action_startvm_tools_install)
+ self.context_menu.addAction(self.action_pausevm)
+ self.context_menu.addAction(self.action_shutdownvm)
+ self.context_menu.addAction(self.action_restartvm)
+ self.context_menu.addAction(self.action_killvm)
+ self.context_menu.addSeparator()
+ self.context_menu.addAction(self.action_clonevm)
+ self.context_menu.addAction(self.action_removevm)
+ self.context_menu.addSeparator()
+ self.context_menu.addMenu(self.logs_menu)
+ self.context_menu.addSeparator()
+
+ def save_showing(self):
+ self.manager_settings.setValue('show/running',
+ self.show_running.isChecked())
+ self.manager_settings.setValue('show/halted',
+ self.show_halted.isChecked())
+ self.manager_settings.setValue('show/network',
+ self.show_network.isChecked())
+ self.manager_settings.setValue('show/templates',
+ self.show_templates.isChecked())
+ self.manager_settings.setValue('show/standalone',
+ self.show_standalone.isChecked())
+ self.manager_settings.setValue('show/all', self.show_all.isChecked())
+
def save_sorting(self):
self.manager_settings.setValue('view/sort_column',
self.proxy.sortColumn())
self.manager_settings.setValue('view/sort_order',
self.proxy.sortOrder())
+ def invalidate(self):
+ self.proxy.invalidate()
+ self.table.resizeColumnsToContents()
+
def fill_cache(self):
progress = QProgressDialog(
self.tr(
@@ -1024,6 +1077,23 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
if not self.manager_settings.value("view/toolbar_visible",
defaultValue=True):
self.action_toolbar.setChecked(False)
+ if self.manager_settings.value("view/compactview",
+ defaultValue="false") != "false":
+ self.action_compact_view.setChecked(True)
+
+ # Restore show checkboxes
+ self.show_running.setChecked(self.manager_settings.value(
+ 'show/running', "true") == "true")
+ self.show_halted.setChecked(self.manager_settings.value(
+ 'show/halted', "true") == "true")
+ self.show_network.setChecked(self.manager_settings.value(
+ 'show/network', "true") == "true")
+ self.show_templates.setChecked(self.manager_settings.value(
+ 'show/templates', "true") == "true")
+ self.show_standalone.setChecked(self.manager_settings.value(
+ 'show/standalone', "true") == "true")
+ self.show_all.setChecked(self.manager_settings.value(
+ 'show/all', "true") == "true")
# load last window size
self.resize(self.manager_settings.value("window_size",
@@ -1389,6 +1459,9 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
"\nError: {}".format(str(ex))))
return
+ def closeEvent(self, _):
+ self.save_showing()
+
# noinspection PyArgumentList
@pyqtSlot(name='on_action_settings_triggered')
def action_settings_triggered(self):
@@ -1515,6 +1588,14 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
def action_exit_triggered(self):
self.close()
+ def set_compactview(self, checked):
+ if checked:
+ self.toolbar.setToolButtonStyle(Qt.ToolButtonIconOnly)
+ else:
+ self.toolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
+ if self.settings_loaded:
+ self.manager_settings.setValue('view/compactview', checked)
+
def showhide_menubar(self, checked):
self.menubar.setVisible(checked)
if not checked:
diff --git a/qubesmanager/utils.py b/qubesmanager/utils.py
index 7fb10ea..6b7d828 100644
--- a/qubesmanager/utils.py
+++ b/qubesmanager/utils.py
@@ -459,7 +459,7 @@ def format_dependencies_list(dependencies):
def loop_shutdown():
- pending = asyncio.Task.all_tasks()
+ pending = asyncio.all_tasks()
for task in pending:
with suppress(asyncio.CancelledError):
task.cancel()
diff --git a/ui/qubemanager.ui b/ui/qubemanager.ui
index 204d07f..4162ee4 100644
--- a/ui/qubemanager.ui
+++ b/ui/qubemanager.ui
@@ -52,23 +52,6 @@
QLayout::SetDefaultConstraint
- -
-
-
- 6
-
-
- 6
-
-
-
-
-
- Search:
-
-
-
-
-
-
@@ -108,7 +91,7 @@
true
- QAbstractItemView::SingleSelection
+ QAbstractItemView::ExtendedSelection
QAbstractItemView::SelectRows
@@ -236,6 +219,103 @@ Template
+ -
+
+
+ 6
+
+
+ 6
+
+
-
+
+
+ Search:
+
+
+
+ -
+
+
+ Show:
+
+
+
+ -
+
+
+ Running
+
+
+ true
+
+
+
+ -
+
+
+ Halted
+
+
+ true
+
+
+
+ -
+
+
+ Network
+
+
+ true
+
+
+
+ -
+
+
+ Templates
+
+
+ true
+
+
+
+ -
+
+
+ Standalone
+
+
+ true
+
+
+
+ -
+
+
+ All
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+