From 4527dec27af6daa922fdac6b6266fe852a1b0575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Wed, 25 Sep 2024 13:22:53 +0200 Subject: [PATCH] Update qt to PyQt6 Updated all files to Qt version 6. Based on the template manager redesign. fixes QubesOS/qubes-issues#9476 --- .pylintrc | 2 +- Makefile | 13 +- debian/install | 2 +- qubesmanager.pro | 2 +- qubesmanager/.gitignore | 2 +- qubesmanager/about.py | 10 +- qubesmanager/appmenu_select.py | 2 +- qubesmanager/backup.py | 36 +++-- qubesmanager/backup_utils.py | 6 +- qubesmanager/bootfromdevice.py | 9 +- qubesmanager/clipboard.py | 4 +- qubesmanager/clone_vm.py | 15 +- qubesmanager/common_threads.py | 5 +- qubesmanager/create_new_vm.py | 17 +- qubesmanager/device_list.py | 6 +- qubesmanager/firewall.py | 36 +++-- qubesmanager/informationnotes.py | 5 +- qubesmanager/log_dialog.py | 12 +- qubesmanager/multiselectwidget.py | 10 +- qubesmanager/qube_manager.py | 189 +++++++++++++---------- qubesmanager/qvm_template_gui.py | 133 ++++++++-------- qubesmanager/restore.py | 34 ++-- qubesmanager/settings.py | 62 ++++---- qubesmanager/template_manager.py | 17 +- qubesmanager/tests/__init__.py | 2 +- qubesmanager/tests/test_backup.py | 26 ++-- qubesmanager/tests/test_backup_utils.py | 2 +- qubesmanager/tests/test_clone_vm.py | 26 ++-- qubesmanager/tests/test_create_new_vm.py | 14 +- qubesmanager/tests/test_qube_manager.py | 127 ++++++++------- qubesmanager/tests/test_vm_settings.py | 22 +-- qubesmanager/utils.py | 14 +- rpm_spec/qmgr.spec.in | 16 +- ui/backupdlg.ui | 11 +- ui/clonevmdlg.ui | 3 +- ui/newappvmdlg.ui | 3 +- ui/qubemanager.ui | 10 +- ui/qvmtemplate.ui | 6 +- ui/restoredlg.ui | 11 +- 39 files changed, 512 insertions(+), 410 deletions(-) diff --git a/.pylintrc b/.pylintrc index 7f34f895..146927aa 100644 --- a/.pylintrc +++ b/.pylintrc @@ -11,7 +11,7 @@ ignore=tests, ui_newfwruledlg.py, ui_restoredlg.py, ui_settingsdlg.py, - resources_rc.py + resources.py extension-pkg-whitelist=PyQt5 [MESSAGES CONTROL] diff --git a/Makefile b/Makefile index 164a11e0..ef345088 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,8 @@ VERSION := $(shell cat version) PYTHON ?= python3 -LRELEASE_QT5 ?= $(if $(wildcard /etc/debian_version),lrelease,lrelease-qt5) +LRELEASE_QT6 ?= $(if $(wildcard /etc/debian_version),lrelease,lrelease-qt6) +RCC ?= /usr/lib64/qt6/libexec/rcc SETUPTOOLS_OPTS = SETUPTOOLS_OPTS += $(if $(wildcard /etc/debian_version),--install-layout=deb,) @@ -11,17 +12,17 @@ export QT_HASH_SEED=0 export PYTHONHASHSEED=0 qubesmanager/ui_%.py: ui/%.ui - pyuic5 --from-imports -o $@ $< + pyuic6 -o $@ $< touch --reference=$< $@ ui: $(patsubst ui/%.ui,qubesmanager/ui_%.py,$(wildcard ui/*.ui)) res: - pyrcc5 -o qubesmanager/resources_rc.py resources.qrc - touch --reference=resources.qrc qubesmanager/resources_rc.py + $(RCC) -g python resources.qrc | sed '0,/PySide6/s//PyQt6/' > qubesmanager/resources.py + touch --reference=resources.qrc qubesmanager/resources.py translations: - $(LRELEASE_QT5) qubesmanager.pro + $(LRELEASE_QT6) qubesmanager.pro python: $(PYTHON) ./setup.py build @@ -30,7 +31,7 @@ python_install: $(PYTHON) ./setup.py install -O1 --skip-build --root $(DESTDIR) $(SETUPTOOLS_OPTS) update_ts: res - pylupdate5 qubesmanager.pro + pylupdate6 qubesmanager.pro install: mkdir -p $(DESTDIR)/usr/libexec/qubes-manager/ diff --git a/debian/install b/debian/install index f6609aa5..03adb67e 100644 --- a/debian/install +++ b/debian/install @@ -34,7 +34,7 @@ /usr/lib/*/dist-packages/qubesmanager/qvm_template_gui.py /usr/lib/*/dist-packages/qubesmanager/clone_vm.py -/usr/lib/*/dist-packages/qubesmanager/resources_rc.py +/usr/lib/*/dist-packages/qubesmanager/resources.py /usr/lib/*/dist-packages/qubesmanager/ui_backupdlg.py /usr/lib/*/dist-packages/qubesmanager/ui_bootfromdevice.py diff --git a/qubesmanager.pro b/qubesmanager.pro index ac6cc7f1..cde88c38 100644 --- a/qubesmanager.pro +++ b/qubesmanager.pro @@ -26,7 +26,7 @@ SOURCES = \ qubesmanager/log_dialog.py \ qubesmanager/multiselectwidget.py \ qubesmanager/qube_manager.py \ - qubesmanager/resources_rc.py \ + qubesmanager/resources.py \ qubesmanager/restore.py \ qubesmanager/settings.py \ qubesmanager/template_manager.py \ diff --git a/qubesmanager/.gitignore b/qubesmanager/.gitignore index d1c5005d..2bc4541d 100644 --- a/qubesmanager/.gitignore +++ b/qubesmanager/.gitignore @@ -1,4 +1,4 @@ qrc_resources.py -resources_rc.py +resources.py ui_*.py *.py[co] diff --git a/qubesmanager/about.py b/qubesmanager/about.py index 063ab344..cee134ab 100644 --- a/qubesmanager/about.py +++ b/qubesmanager/about.py @@ -20,12 +20,16 @@ # with this program; if not, see . # # -from PyQt5.QtWidgets import QDialog # pylint: disable=import-error -from PyQt5.QtGui import QIcon # pylint: disable=import-error +from PyQt6.QtWidgets import QDialog # pylint: disable=import-error +from PyQt6.QtGui import QIcon # pylint: disable=import-error from qubesmanager.informationnotes import InformationNotesDialog from . import ui_about # pylint: disable=no-name-in-module +# this is needed for icons to actually work +# pylint: disable=unused-import +from . import resources + # pylint: disable=too-few-public-methods class AboutDialog(ui_about.Ui_AboutDialog, QDialog): @@ -58,4 +62,4 @@ def __init__(self, parent=None): def on_information_notes_clicked(self): information_notes_dialog = InformationNotesDialog(self) - information_notes_dialog.exec_() + information_notes_dialog.exec() diff --git a/qubesmanager/appmenu_select.py b/qubesmanager/appmenu_select.py index 965e65b3..178ba92e 100755 --- a/qubesmanager/appmenu_select.py +++ b/qubesmanager/appmenu_select.py @@ -20,7 +20,7 @@ # import subprocess -from PyQt5 import QtWidgets, QtCore # pylint: disable=import-error +from PyQt6 import QtWidgets, QtCore # pylint: disable=import-error from qubesadmin import exc # TODO description in tooltip diff --git a/qubesmanager/backup.py b/qubesmanager/backup.py index d35c24be..2ceb6c19 100644 --- a/qubesmanager/backup.py +++ b/qubesmanager/backup.py @@ -24,18 +24,22 @@ from qubesadmin import exc from qubesadmin import utils as admin_utils -from PyQt5 import QtCore, QtWidgets, QtGui, Qt # pylint: disable=import-error -from . import ui_backupdlg # pylint: disable=no-name-in-module -from . import multiselectwidget +from PyQt6 import QtCore, QtWidgets, QtGui # pylint: disable=import-error +from qubesmanager import ui_backupdlg # pylint: disable=no-name-in-module +from qubesmanager import multiselectwidget -from . import backup_utils -from . import utils +from qubesmanager import backup_utils +from qubesmanager import utils import grp import pwd import os import shutil +# this is needed for icons to actually work +# pylint: disable=unused-import +from . import resources + # pylint: disable=too-few-public-methods class BackupThread(QtCore.QThread): def __init__(self, vm): @@ -81,8 +85,8 @@ def __init__(self, qt_app, qubes_app, dispatcher, parent=None): self.setupUi(self) self.setWindowFlags(self.windowFlags() | - Qt.Qt.WindowMaximizeButtonHint | - Qt.Qt.WindowMinimizeButtonHint) + QtCore.Qt.WindowType.WindowMaximizeButtonHint | + QtCore.Qt.WindowType.WindowMinimizeButtonHint) self.progress_status.text = self.tr("Backup in progress...") self.dir_line_edit.setReadOnly(False) @@ -146,10 +150,12 @@ def __init__(self, qt_app, qubes_app, dispatcher, parent=None): def show_hide_password(self): if self.show_passwd_button.isChecked(): - self.passphrase_line_edit.setEchoMode(QtWidgets.QLineEdit.Password) + self.passphrase_line_edit.setEchoMode( + QtWidgets.QLineEdit.EchoMode.Password) self.show_passwd_button.setIcon(QtGui.QIcon(':/eye-off.svg')) else: - self.passphrase_line_edit.setEchoMode(QtWidgets.QLineEdit.Normal) + self.passphrase_line_edit.setEchoMode( + QtWidgets.QLineEdit.EchoMode.Normal) self.show_passwd_button.setIcon(QtGui.QIcon(':/eye.svg')) def save_profile_changed(self): @@ -376,7 +382,7 @@ def current_page_changed(self, page_id): # pylint: disable=unused-argument self.save_settings(use_temp=False, save_passphrase=save_passphrase) - self.button(self.FinishButton).setDisabled(True) + self.button(self.WizardButton.FinishButton).setDisabled(True) self.showFileDialog.setEnabled( self.appvm_combobox.currentIndex() != 0) self.showFileDialog.setChecked(self.showFileDialog.isEnabled() @@ -399,8 +405,8 @@ def backup_finished(self): self, self.tr("Backup error"), self.tr("ERROR: {}").format( self.thread.msg)) - self.button(self.CancelButton).setEnabled(False) - self.button(self.FinishButton).setEnabled(True) + self.button(self.WizardButton.CancelButton).setEnabled(False) + self.button(self.WizardButton.FinishButton).setEnabled(True) self.cleanup_temporary_files() else: @@ -415,8 +421,8 @@ def backup_finished(self): "the file selection dialog.")) backup_utils.select_path_button_clicked(self, False, True) - self.button(self.CancelButton).setEnabled(False) - self.button(self.FinishButton).setEnabled(True) + self.button(self.WizardButton.CancelButton).setEnabled(False) + self.button(self.WizardButton.FinishButton).setEnabled(True) self.showFileDialog.setEnabled(False) self.cleanup_temporary_files() @@ -426,7 +432,7 @@ def backup_finished(self): def reject(self): if (self.currentPage() is self.commit_page) and \ - self.button(self.CancelButton).isEnabled(): + self.button(self.WizardButton.CancelButton).isEnabled(): try: self.qubes_app.qubesd_call( 'dom0', 'admin.backup.Cancel', diff --git a/qubesmanager/backup_utils.py b/qubesmanager/backup_utils.py index 1e41d1d7..853f8316 100644 --- a/qubesmanager/backup_utils.py +++ b/qubesmanager/backup_utils.py @@ -21,12 +21,16 @@ import re import socket -from PyQt5 import QtWidgets # pylint: disable=import-error +from PyQt6 import QtWidgets # pylint: disable=import-error import subprocess from . import utils import yaml +# this is needed for icons to actually work +# pylint: disable=unused-import +from . import resources + path_re = re.compile(r"[a-zA-Z0-9/:.,_+=() -]*") path_max_len = 512 diff --git a/qubesmanager/bootfromdevice.py b/qubesmanager/bootfromdevice.py index 6fc60454..b1f0011e 100644 --- a/qubesmanager/bootfromdevice.py +++ b/qubesmanager/bootfromdevice.py @@ -21,11 +21,14 @@ import subprocess from . import utils from . import ui_bootfromdevice # pylint: disable=no-name-in-module -from PyQt5 import QtWidgets, QtGui, Qt # pylint: disable=import-error +from PyQt6 import QtWidgets, QtGui, QtCore # pylint: disable=import-error from qubesadmin import tools from qubesadmin import exc from qubesadmin.tools import qvm_start +# this is needed for icons to actually work +# pylint: disable=unused-import +from . import resources class VMBootFromDeviceWindow(ui_bootfromdevice.Ui_BootDialog, QtWidgets.QDialog): @@ -42,8 +45,8 @@ def __init__(self, vm, qapp, qubesapp=None, parent=None, new_vm=False): self.setWindowTitle( self.tr("Boot {vm} from device").format(vm=self.vm)) self.setWindowFlags(self.windowFlags() | - Qt.Qt.WindowMaximizeButtonHint | - Qt.Qt.WindowMinimizeButtonHint) + QtCore.Qt.WindowType.WindowMaximizeButtonHint | + QtCore.Qt.WindowType.WindowMinimizeButtonHint) self.buttonBox.accepted.connect(self.save_and_apply) self.buttonBox.rejected.connect(self.reject) diff --git a/qubesmanager/clipboard.py b/qubesmanager/clipboard.py index 8bf8f1eb..8c81671e 100644 --- a/qubesmanager/clipboard.py +++ b/qubesmanager/clipboard.py @@ -26,8 +26,8 @@ from math import log # pylint: disable=import-error -from PyQt5.QtWidgets import QApplication, QMessageBox -from PyQt5.QtCore import QCoreApplication +from PyQt6.QtWidgets import QApplication, QMessageBox +from PyQt6.QtCore import QCoreApplication APPVIEWER_LOCK = "/var/run/qubes/appviewer.lock" CLIPBOARD_CONTENTS = "/var/run/qubes/qubes-clipboard.bin" diff --git a/qubesmanager/clone_vm.py b/qubesmanager/clone_vm.py index 05fba0e7..3b3d4a39 100644 --- a/qubesmanager/clone_vm.py +++ b/qubesmanager/clone_vm.py @@ -24,7 +24,7 @@ import sys import subprocess -from PyQt5 import QtCore, QtWidgets, QtGui # pylint: disable=import-error +from PyQt6 import QtCore, QtWidgets, QtGui # pylint: disable=import-error import qubesadmin import qubesadmin.tools @@ -35,6 +35,10 @@ from .ui_clonevmdlg import Ui_CloneVMDlg # pylint: disable=import-error +# this is needed for icons to actually work +# pylint: disable=unused-import +from . import resources + class CloneVMDlg(QtWidgets.QDialog, Ui_CloneVMDlg): def __init__(self, qtapp, app, parent=None, src_vm=None): @@ -73,8 +77,11 @@ def __init__(self, qtapp, app, parent=None, src_vm=None): self.set_clone_name() - self.name.setValidator(QtGui.QRegExpValidator( - QtCore.QRegExp("[a-zA-Z0-9_-]*", QtCore.Qt.CaseInsensitive), None)) + self.name.setValidator(QtGui.QRegularExpressionValidator( + QtCore.QRegularExpression( + "[a-zA-Z0-9_-]*", + QtCore.QRegularExpression.PatternOption.CaseInsensitiveOption), + None)) self.name.selectAll() self.name.setFocus() @@ -183,4 +190,4 @@ def main(args=None): "appname", 'Clone qube')) dialog = CloneVMDlg(qtapp, args.app, src_vm=src_vm) - dialog.exec_() + dialog.exec() diff --git a/qubesmanager/common_threads.py b/qubesmanager/common_threads.py index 5f94a06e..239bb0ab 100644 --- a/qubesmanager/common_threads.py +++ b/qubesmanager/common_threads.py @@ -20,7 +20,7 @@ # -from PyQt5 import QtCore, QtWidgets # pylint: disable=import-error +from PyQt6 import QtCore, QtWidgets # pylint: disable=import-error from contextlib import contextmanager from qubesadmin import exc @@ -29,7 +29,8 @@ @contextmanager def busy_cursor(): try: - QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.BusyCursor) + QtWidgets.QApplication.setOverrideCursor( + QtCore.Qt.CursorShape.BusyCursor) yield finally: QtWidgets.QApplication.restoreOverrideCursor() diff --git a/qubesmanager/create_new_vm.py b/qubesmanager/create_new_vm.py index e8038146..3e33f70c 100644 --- a/qubesmanager/create_new_vm.py +++ b/qubesmanager/create_new_vm.py @@ -25,7 +25,7 @@ import sys import subprocess -from PyQt5 import QtCore, QtWidgets, QtGui # pylint: disable=import-error +from PyQt6 import QtCore, QtWidgets, QtGui # pylint: disable=import-error import qubesadmin import qubesadmin.tools @@ -36,6 +36,10 @@ from .ui_newappvmdlg import Ui_NewVMDlg # pylint: disable=import-error +# this is needed for icons to actually work +# pylint: disable=unused-import +from . import resources + # pylint: disable=too-few-public-methods class CreateVMThread(QtCore.QThread): @@ -145,8 +149,11 @@ def __init__(self, qtapp, app, parent=None): self.storage_pool.clear() self.storage_pool.addItem("(default)", qubesadmin.DEFAULT) - self.name.setValidator(QtGui.QRegExpValidator( - QtCore.QRegExp("[a-zA-Z0-9_-]*", QtCore.Qt.CaseInsensitive), None)) + self.name.setValidator(QtGui.QRegularExpressionValidator( + QtCore.QRegularExpression( + "[a-zA-Z0-9_-]*", + QtCore.QRegularExpression.PatternOption.CaseInsensitiveOption), + None)) self.name.selectAll() self.name.setFocus() @@ -182,7 +189,7 @@ def accept(self): if self.install_system.isChecked(): self.boot_dialog = bootfromdevice.VMBootFromDeviceWindow( name, self.qtapp, self.app, self, True) - if not self.boot_dialog.exec_(): + if not self.boot_dialog.exec(): return if name in self.app.domains: @@ -336,4 +343,4 @@ def main(args=None): "appname", 'Create qube')) dialog = NewVmDlg(qtapp, args.app) - dialog.exec_() + dialog.exec() diff --git a/qubesmanager/device_list.py b/qubesmanager/device_list.py index c32fd249..5a58aa7d 100644 --- a/qubesmanager/device_list.py +++ b/qubesmanager/device_list.py @@ -18,7 +18,11 @@ # from . import ui_devicelist # pylint: disable=no-name-in-module -from PyQt5 import QtWidgets # pylint: disable=import-error +from PyQt6 import QtWidgets # pylint: disable=import-error + +# this is needed for icons to actually work +# pylint: disable=unused-import +from . import resources class PCIDeviceListWindow(ui_devicelist.Ui_Dialog, QtWidgets.QDialog): diff --git a/qubesmanager/firewall.py b/qubesmanager/firewall.py index 4564504d..df09b39c 100644 --- a/qubesmanager/firewall.py +++ b/qubesmanager/firewall.py @@ -21,10 +21,13 @@ import datetime import re -from PyQt5 import QtCore, QtGui, QtWidgets # pylint: disable=import-error +from PyQt6 import QtCore, QtGui, QtWidgets # pylint: disable=import-error import qubesadmin.firewall from . import ui_newfwruledlg # pylint: disable=no-name-in-module +# this is needed for icons to actually work +# pylint: disable=unused-import +from . import resources class FirewallModifiedOutsideError(ValueError): @@ -39,13 +42,17 @@ def __init__(self, parent=None): self.set_ok_state(False) self.addressComboBox.editTextChanged.connect( self.address_editing_finished) - self.serviceComboBox.setValidator(QtGui.QRegExpValidator( - QtCore.QRegExp("[a-z][a-z0-9-]+|[0-9]+(-[0-9]+)?", - QtCore.Qt.CaseInsensitive), None)) + self.serviceComboBox.setValidator(QtGui.QRegularExpressionValidator( + QtCore.QRegularExpression( + "[a-z][a-z0-9-]+|[0-9]+(-[0-9]+)?", + QtCore.QRegularExpression.PatternOption.CaseInsensitiveOption), + None)) self.serviceComboBox.setEnabled(False) - self.serviceComboBox.setInsertPolicy(QtWidgets.QComboBox.InsertAtBottom) + self.serviceComboBox.setInsertPolicy( + QtWidgets.QComboBox.InsertPolicy.InsertAtBottom) self.populate_combos() - self.serviceComboBox.setInsertPolicy(QtWidgets.QComboBox.InsertAtTop) + self.serviceComboBox.setInsertPolicy( + QtWidgets.QComboBox.InsertPolicy.InsertAtTop) self.model = None @@ -142,7 +149,8 @@ def address_editing_finished(self): self.set_ok_state(True) def set_ok_state(self, ok_state): - ok_button = self.buttonBox.button(QtWidgets.QDialogButtonBox.Ok) + ok_button = self.buttonBox.button( + QtWidgets.QDialogButtonBox.StandardButton.Ok) if ok_button is not None: ok_button.setEnabled(ok_state) @@ -190,7 +198,7 @@ def __init__(self, parent=None): self.__children = None # list of rules in the FW def sort(self, idx, order): - rev = order == QtCore.Qt.AscendingOrder + rev = order == QtCore.Qt.SortOrder.AscendingOrder self.children.sort(key=lambda x: self.get_column_string(idx, x), reverse=rev) @@ -246,7 +254,6 @@ def get_firewall_conf(self, vm): allow_dns = False allow_icmp = False - common_action = None reversed_rules = reversed(vm.firewall.rules) last_rule = next(reversed_rules, None) @@ -413,16 +420,17 @@ def hasChildren(self, index=QtCore.QModelIndex()): parent_item = index.internalPointer() return parent_item is None - def data(self, index, role=QtCore.Qt.DisplayRole): - if index.isValid() and role == QtCore.Qt.DisplayRole: + def data(self, index, role=QtCore.Qt.ItemDataRole.DisplayRole): + if index.isValid() and role == QtCore.Qt.ItemDataRole.DisplayRole: return self.get_column_string(index.column(), self.children[index.row()]) # pylint: disable=invalid-name - def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole): + def headerData(self, section, orientation, + role=QtCore.Qt.ItemDataRole.DisplayRole): if section < len(self.__column_names) \ - and orientation == QtCore.Qt.Horizontal \ - and role == QtCore.Qt.DisplayRole: + and orientation == QtCore.Qt.Orientation.Horizontal \ + and role == QtCore.Qt.ItemDataRole.DisplayRole: return self.__column_names[section] @property diff --git a/qubesmanager/informationnotes.py b/qubesmanager/informationnotes.py index 755be5b7..0e2edf7d 100644 --- a/qubesmanager/informationnotes.py +++ b/qubesmanager/informationnotes.py @@ -20,11 +20,14 @@ # with this program; if not, see . # # -from PyQt5.QtWidgets import QDialog # pylint: disable=import-error +from PyQt6.QtWidgets import QDialog # pylint: disable=import-error from . import ui_informationnotes # pylint: disable=no-name-in-module import subprocess +# this is needed for icons to actually work +# pylint: disable=unused-import +from . import resources class InformationNotesDialog(ui_informationnotes.Ui_InformationNotesDialog, QDialog): diff --git a/qubesmanager/log_dialog.py b/qubesmanager/log_dialog.py index d5ac0031..353ff25c 100644 --- a/qubesmanager/log_dialog.py +++ b/qubesmanager/log_dialog.py @@ -22,11 +22,15 @@ import sys import os from functools import partial -from PyQt5 import QtWidgets, Qt # pylint: disable=import-error +from PyQt6 import QtWidgets, QtCore # pylint: disable=import-error from qubesadmin import Qubes from . import ui_logdlg # pylint: disable=no-name-in-module from . import clipboard +# this is needed for icons to actually work +# pylint: disable=unused-import +from . import resources + # Display only this size of log LOG_DISPLAY_SIZE = 1024*1024 @@ -43,8 +47,8 @@ def __init__(self, app, logfiles, parent=None): self.setupUi(self) self.setWindowFlags(self.windowFlags() | - Qt.Qt.WindowMaximizeButtonHint | - Qt.Qt.WindowMinimizeButtonHint) + QtCore.Qt.WindowType.WindowMaximizeButtonHint | + QtCore.Qt.WindowType.WindowMinimizeButtonHint) self.copy_to_qubes_clipboard.clicked.connect( self.copy_to_clipboard_triggered) @@ -89,7 +93,7 @@ def main(): log_window = LogDialog(qubes_app, sys.argv[1:]) log_window.show() - qt_app.exec_() + qt_app.exec() qt_app.exit() diff --git a/qubesmanager/multiselectwidget.py b/qubesmanager/multiselectwidget.py index 53455514..9ddde325 100644 --- a/qubesmanager/multiselectwidget.py +++ b/qubesmanager/multiselectwidget.py @@ -1,6 +1,10 @@ -from PyQt5 import QtCore, QtWidgets # pylint: disable=import-error +from PyQt6 import QtCore, QtWidgets # pylint: disable=import-error from . import ui_multiselectwidget # pylint: disable=no-name-in-module +# this is needed for icons to actually work +# pylint: disable=unused-import +from . import resources + class MultiSelectWidget( ui_multiselectwidget.Ui_MultiSelectWidget, QtWidgets.QWidget): @@ -17,9 +21,9 @@ def __init__(self, parent=None): self.remove_selected_button.clicked.connect(self.remove_selected) self.remove_all_button.clicked.connect(self.remove_all) self.available_list.setSelectionMode( - QtWidgets.QAbstractItemView.ExtendedSelection) + QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection) self.selected_list.setSelectionMode( - QtWidgets.QAbstractItemView.ExtendedSelection) + QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection) self.available_list.itemDoubleClicked.connect(self.add_selected) self.selected_list.itemDoubleClicked.connect(self.remove_selected) diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py index 59c426e8..72699a02 100644 --- a/qubesmanager/qube_manager.py +++ b/qubesmanager/qube_manager.py @@ -33,17 +33,18 @@ from qubesadmin.tools import qvm_start # pylint: disable=import-error -from PyQt5.QtCore import (Qt, QAbstractTableModel, QObject, pyqtSlot, QEvent, - QSettings, QRegExp, QSortFilterProxyModel, QSize, QPoint, QTimer) +from PyQt6.QtCore import (Qt, QAbstractTableModel, QObject, pyqtSlot, QEvent, + QSettings, QRegularExpression, QSortFilterProxyModel, + QSize, QPoint, QTimer) # pylint: disable=import-error -from PyQt5.QtWidgets import (QLineEdit, QStyledItemDelegate, QToolTip, - QMenu, QInputDialog, QMainWindow, QProgressDialog, QStyleOptionViewItem, - QMessageBox, QShortcut) +from PyQt6.QtWidgets import (QLineEdit, QStyledItemDelegate, QToolTip, + QMenu, QInputDialog, QMainWindow, QProgressDialog, + QStyleOptionViewItem, QMessageBox) # pylint: disable=import-error -from PyQt5.QtGui import (QIcon, QPixmap, QRegExpValidator, QFont, QColor, - QKeySequence) +from PyQt6.QtGui import (QIcon, QPixmap, QRegularExpressionValidator, QFont, + QColor, QShortcut, QKeySequence) from qubesmanager.about import AboutDialog @@ -57,6 +58,9 @@ from qubesmanager import common_threads from qubesmanager import clone_vm +# this is needed for icons to actually work +# pylint: disable=unused-import +from . import resources def spawn_in_background(cmd: str | list[str]) -> None: if isinstance(cmd, str): @@ -120,12 +124,12 @@ def __init__(self): def sizeHint(self, option, index): hint = super().sizeHint(option, index) option = QStyleOptionViewItem(option) - option.features |= option.HasDecoration + option.features |= option.ViewItemFeature.HasDecoration widget = option.widget style = widget.style() - iconRect = style.subElementRect(style.SE_ItemViewItemDecoration, - option, widget) - width = iconRect.width() * 3 # Nº of possible icons + iconRect = style.subElementRect( + style.SubElement.SE_ItemViewItemDecoration, option, widget) + width = iconRect.width() * 3 # Nº of possible icons hint.setWidth(width) return hint @@ -137,13 +141,14 @@ def paint(self, qp, option, index): style = widget.style() # paint the base item (borders, gradients, selection colors, etc) - style.drawControl(style.CE_ItemViewItem, option, qp, widget) + style.drawControl(style.ControlElement.CE_ItemViewItem, + option, qp, widget) # "lie" about the decoration, to get a valid icon rectangle (even if we # don't have any "real" icon set for the item) - option.features |= option.HasDecoration - iconRect = style.subElementRect(style.SE_ItemViewItemDecoration, - option, widget) + option.features |= option.ViewItemFeature.HasDecoration + iconRect = style.subElementRect( + style.SubElement.SE_ItemViewItemDecoration, option, widget) iconSize = iconRect.size() margin = iconRect.left() - option.rect.left() @@ -166,16 +171,16 @@ def paint(self, qp, option, index): qp.restore() def helpEvent(self, event, view, option, index): - if event.type() != QEvent.ToolTip: + if event.type() != QEvent.Type.ToolTip: return super().helpEvent(event, view, option, index) option = QStyleOptionViewItem(option) widget = option.widget style = widget.style() - option.features |= option.HasDecoration + option.features |= option.ViewItemFeature.HasDecoration - iconRect = style.subElementRect(style.SE_ItemViewItemDecoration, - option, widget) + iconRect = style.subElementRect( + style.SubElement.SE_ItemViewItemDecoration, option, widget) iconRect.setTop(option.rect.y()) iconRect.setHeight(option.rect.height()) @@ -419,7 +424,7 @@ def data(self, index, role): col_name = self.columns_indices[col] vm = self.qubes_cache.get_vm(row) - if role == Qt.DisplayRole: + if role == Qt.ItemDataRole.DisplayRole: if col in [0, 1]: return None if col_name == "Name": @@ -446,7 +451,7 @@ def data(self, index, role): return "Yes" if vm.dvm_template else "" if col_name == "Virt Mode": return vm.virt_mode - if role == Qt.DecorationRole: + if role == Qt.ItemDataRole.DecorationRole: if col_name == "Type": try: return self.klass_pixmap[vm.klass] @@ -467,24 +472,25 @@ def data(self, index, role): icon = QIcon.fromTheme(vm.icon) self.label_pixmap[vm.icon] = icon.pixmap(icon_size) return self.label_pixmap[vm.icon] - if role == Qt.CheckStateRole: + if role == Qt.ItemDataRole.CheckStateRole: if col_name == "Backup": - return Qt.Checked if vm.inc_backup else Qt.Unchecked - if role == Qt.FontRole: + return Qt.CheckState.Checked if vm.inc_backup else ( + Qt.CheckState.Unchecked) + if role == Qt.ItemDataRole.FontRole: if col_name == "Template": if vm.template is None: font = QFont() font.setItalic(True) return font - if role == Qt.ForegroundRole: + if role == Qt.ItemDataRole.ForegroundRole: if col_name == "Template": if vm.template is None: return QColor("gray") # Used for get VM Object - if role == Qt.UserRole: + if role == Qt.ItemDataRole.UserRole: return vm # Used for sorting - if role == Qt.UserRole + 1: + if role == Qt.ItemDataRole.UserRole + 1: if vm.klass == 'AdminVM': return "" if col_name == "Type": @@ -514,36 +520,37 @@ def data(self, index, role): if col_name == "Backup": # sort True before False, hence the not return not vm.inc_backup - return self.data(index, Qt.DisplayRole) + return self.data(index, Qt.ItemDataRole.DisplayRole) # pylint: disable=invalid-name def headerData(self, col, orientation, role): if col < 2: return None - if orientation == Qt.Horizontal and role == Qt.DisplayRole: + if (orientation == Qt.Orientation.Horizontal and role == + Qt.ItemDataRole.DisplayRole): return self.columns_indices[col] return None - def setData(self, index, value, role=Qt.EditRole): + def setData(self, index, value, role=Qt.ItemDataRole.EditRole): if not index.isValid(): return False - if role == Qt.CheckStateRole: + if role == Qt.ItemDataRole.CheckStateRole: col_name = self.columns_indices[index.column()] if col_name == "Backup": vm = self.qubes_cache.get_vm(index.row()) - vm.vm.include_in_backups = value == Qt.Checked - vm.inc_backup = value == Qt.Checked + vm.vm.include_in_backups = value == Qt.CheckState.Checked + vm.inc_backup = value == Qt.CheckState.Checked return True return False def flags(self, index): if not index.isValid(): - return Qt.NoItemFlags + return Qt.ItemFlag.NoItemFlags def_flags = QAbstractTableModel.flags(self, index) if self.columns_indices[index.column()] == "Backup": - return def_flags | Qt.ItemIsUserCheckable + return def_flags | Qt.ItemFlag.ItemIsUserCheckable return def_flags vm_restart_check_timeout = 1000 # in msec @@ -587,27 +594,28 @@ def check_if_vm_has_shutdown(self): if self.timeout_reached(): msgbox = QMessageBox(self.caller) - msgbox.setIcon(QMessageBox.Question) + msgbox.setIcon(QMessageBox.Icon.Question) msgbox.setWindowTitle(self.tr("Qube Shutdown")) msgbox.setText(self.tr( "The Qube '{0}' hasn't shutdown within the last " "{1} seconds, do you want to kill it?
").format( vm.name, self.shutdown_timeout)) kill_button = msgbox.addButton( - self.tr("Kill it!"), QMessageBox.YesRole) + self.tr("Kill it!"), QMessageBox.ButtonRole.YesRole) wait_button = msgbox.addButton( self.tr("Wait another {0} seconds...").format( self.shutdown_timeout), - QMessageBox.NoRole) - ignore_button = msgbox.addButton(self.tr("Don't ask again"), - QMessageBox.RejectRole) + QMessageBox.ButtonRole.NoRole) + ignore_button = msgbox.addButton( + self.tr("Don't ask again"), + QMessageBox.ButtonRole.RejectRole) msgbox.setDefaultButton(wait_button) msgbox.setEscapeButton(ignore_button) msgbox.setWindowFlags( - msgbox.windowFlags() | Qt.CustomizeWindowHint) + msgbox.windowFlags() | Qt.WindowType.CustomizeWindowHint) msgbox.setWindowFlags( - msgbox.windowFlags() & ~Qt.WindowCloseButtonHint) - msgbox.exec_() + msgbox.windowFlags() & ~Qt.WindowType.WindowCloseButtonHint) + msgbox.exec() msgbox.deleteLater() if msgbox.clickedButton() is kill_button: @@ -680,15 +688,15 @@ def lessThan(self, left, right): if left.data(self.sortRole()) != right.data(self.sortRole()): return super().lessThan(left, right) - left_vm = left.data(Qt.UserRole) - right_vm = right.data(Qt.UserRole) + left_vm = left.data(Qt.ItemDataRole.UserRole) + right_vm = right.data(Qt.ItemDataRole.UserRole) return left_vm.name.lower() < right_vm.name.lower() # pylint: disable=too-many-return-statements def filterAcceptsRow(self, sourceRow, sourceParent): index = self.sourceModel().index(sourceRow, 0, sourceParent) - vm = self.sourceModel().data(index, Qt.UserRole) + vm = self.sourceModel().data(index, Qt.ItemDataRole.UserRole) # if hide internal is true, ignore all other filters if not self.window.show_internal_action.isChecked() and vm.internal: @@ -729,8 +737,11 @@ def __init__(self, qt_app, qubes_app, dispatcher, _parent=None): self.qt_app = qt_app self.searchbox = SearchBox() - self.searchbox.setValidator(QRegExpValidator( - QRegExp("[a-zA-Z0-9_-]*", Qt.CaseInsensitive), None)) + self.searchbox.setValidator(QRegularExpressionValidator( + QRegularExpression( + "[a-zA-Z0-9_-]*", + QRegularExpression.PatternOption.CaseInsensitiveOption), + None)) self.searchbox.textChanged.connect(self.do_search) self.searchContainer.insertWidget(1, self.searchbox) @@ -801,10 +812,10 @@ def __init__(self, qt_app, qubes_app, dispatcher, _parent=None): self.proxy = QubesProxyModel(self) self.proxy.setSourceModel(self.qubes_model) - self.proxy.setSortRole(Qt.UserRole + 1) - self.proxy.setSortCaseSensitivity(Qt.CaseInsensitive) + self.proxy.setSortRole(Qt.ItemDataRole.UserRole + 1) + self.proxy.setSortCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive) self.proxy.setFilterKeyColumn(2) - self.proxy.setFilterCaseSensitivity(Qt.CaseInsensitive) + self.proxy.setFilterCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive) self.proxy.layoutChanged.connect(self.save_sorting) self.proxy.layoutChanged.connect(self.update_template_menu) self.proxy.layoutChanged.connect(self.update_network_menu) @@ -815,7 +826,7 @@ def __init__(self, qt_app, qubes_app, dispatcher, _parent=None): selection_model = self.table.selectionModel() selection_model.selectionChanged.connect(self.table_selection_changed) - self.table.setContextMenuPolicy(Qt.CustomContextMenu) + self.table.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.table.customContextMenuRequested.connect(self.open_context_menu) try: @@ -890,9 +901,9 @@ def change_template(self, template): self.tr("Do you want to change '{0}'
" "to Template '{1}'?").format( ', '.join(vm.name for vm in selected_vms), template), - QMessageBox.Yes | QMessageBox.Cancel) + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.Cancel) - if reply == QMessageBox.Yes: + if reply == QMessageBox.StandardButton.Yes: errors = [] for info in selected_vms: try: @@ -911,9 +922,9 @@ def change_network(self, netvm_name): self.tr("Do you want to change '{0}'
" "to Network '{1}'?").format( ', '.join(vm.name for vm in selected_vms), netvm_name), - QMessageBox.Yes | QMessageBox.Cancel) + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.Cancel) - if reply != QMessageBox.Yes: + if reply != QMessageBox.StandardButton.Yes: return if netvm_name: @@ -930,9 +941,10 @@ def change_network(self, netvm_name): self.tr("
Can not change netvm to a halted Qube.
" "Do you want to start the Qube '{0}'?").format( netvm_name), - QMessageBox.Yes | QMessageBox.Cancel) + QMessageBox.StandardButton.Yes | + QMessageBox.StandardButton.Cancel) - if reply == QMessageBox.Yes: + if reply == QMessageBox.StandardButton.Yes: self.start_vm(netvm, True) else: return @@ -1008,7 +1020,7 @@ def fill_cache(self): len(self.qubes_app.domains.keys())) progress.setWindowTitle(self.tr("Qube Manager")) progress.setMinimumDuration(1000) - progress.setWindowModality(Qt.WindowModal) + progress.setWindowModality(Qt.WindowModality.WindowModal) progress.setCancelButton(None) row_no = 0 @@ -1051,7 +1063,7 @@ def setup_application(self): self.qt_app.setWindowIcon(QIcon.fromTheme("qubes-manager")) def keyPressEvent(self, event): # pylint: disable=invalid-name - if event.key() == Qt.Key_Escape: + if event.key() == Qt.Key.Key_Escape: self.searchbox.clear() super().keyPressEvent(event) @@ -1175,11 +1187,16 @@ def load_manager_settings(self): # Restore sorting sort_column = int(self.manager_settings.value("view/sort_column", defaultValue=2)) - order = Qt.SortOrder(self.manager_settings.value("view/sort_order", - defaultValue=Qt.AscendingOrder)) + + order = self.manager_settings.value("view/sort_order", + defaultValue=Qt.SortOrder.AscendingOrder) + if isinstance(order, str): + # convoluted in order to maintain backwards compat + order = int(order) + order = Qt.SortOrder(order) if not sort_column: # Default sort by name - self.table.sortByColumn(2, Qt.AscendingOrder) + self.table.sortByColumn(2, Qt.SortOrder.AscendingOrder) else: self.table.sortByColumn(sort_column, order) @@ -1190,7 +1207,7 @@ def load_manager_settings(self): self.action_toolbar.setChecked(False) self.toolbar.setVisible(False) if self.manager_settings.value("view/compactview", - defaultValue="false") != "false": + defaultValue="false") != "false": self.action_compact_view.setChecked(True) # Restore show checkboxes @@ -1231,7 +1248,7 @@ def get_selected_vms(self): for index in indexes: if index.column() != 0: continue - vms.append(index.data(Qt.UserRole)) + vms.append(index.data(Qt.ItemDataRole.UserRole)) return vms @@ -1350,7 +1367,7 @@ def action_createvm_triggered(self): with common_threads.busy_cursor(): create_window = create_new_vm.NewVmDlg( self.qt_app, self.qubes_app, self) - create_window.exec_() + create_window.exec() # noinspection PyArgumentList @pyqtSlot(name='on_action_removevm_triggered') @@ -1415,7 +1432,7 @@ def action_clonevm_triggered(self): with common_threads.busy_cursor(): clone_window = clone_vm.CloneVMDlg( self.qt_app, self.qubes_app, src_vm=vm) - clone_window.exec_() + clone_window.exec() # noinspection PyArgumentList @pyqtSlot(name='on_action_resumevm_triggered') @@ -1484,9 +1501,10 @@ def action_shutdownvm_triggered(self): "?
This will shutdown all the running" " applications within this Qube.").format( vm.name), - QMessageBox.Yes | QMessageBox.Cancel) + QMessageBox.StandardButton.Yes | + QMessageBox.StandardButton.Cancel) - if reply == QMessageBox.Yes: + if reply == QMessageBox.StandardButton.Yes: self.shutdown_vm(vm) def get_connected_vms(self, vm, connected_vms): @@ -1510,9 +1528,10 @@ def shutdown_vm(self, vm, force=False, check_time=vm_restart_check_timeout, "
Do you want to shutdown: " "'{1}'?").format(vm.name, ", ".join([x.name for x in connected_vms])), - QMessageBox.Yes | QMessageBox.Cancel) + QMessageBox.StandardButton.Yes | + QMessageBox.StandardButton.Cancel) - if reply != QMessageBox.Yes: + if reply != QMessageBox.StandardButton.Yes: return False force = True @@ -1545,9 +1564,10 @@ def action_restartvm_triggered(self): self.tr("Are you sure you want to restart the Qube '{0}'" "?
This will shutdown all the running applica" "tions within this Qube.").format(vm.name), - QMessageBox.Yes | QMessageBox.Cancel) + QMessageBox.StandardButton.Yes | + QMessageBox.StandardButton.Cancel) - if reply == QMessageBox.Yes: + if reply == QMessageBox.StandardButton.Yes: # in case the user shut down the VM in the meantime try: if manager_utils.is_running(vm, False): @@ -1585,10 +1605,11 @@ def action_killvm_triggered(self): reply = QMessageBox.question( self, self.tr("Qube Kill Confirmation"), info, - QMessageBox.Yes | QMessageBox.Cancel, - QMessageBox.Cancel) + QMessageBox.StandardButton.Yes | + QMessageBox.StandardButton.Cancel, + QMessageBox.StandardButton.Cancel) - if reply == QMessageBox.Yes: + if reply == QMessageBox.StandardButton.Yes: try: vm.kill() except exc.QubesException as ex: @@ -1714,7 +1735,7 @@ def action_restore_triggered(self): with common_threads.busy_cursor(): restore_window = restore.RestoreVMsWindow(self.qt_app, self.qubes_app, self) - restore_window.exec_() + restore_window.exec() # noinspection PyArgumentList @pyqtSlot(name='on_action_backup_triggered') @@ -1731,9 +1752,11 @@ def action_exit_triggered(self): def set_compactview(self, checked): if checked: - self.toolbar.setToolButtonStyle(Qt.ToolButtonIconOnly) + self.toolbar.setToolButtonStyle( + Qt.ToolButtonStyle.ToolButtonIconOnly) else: - self.toolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) + self.toolbar.setToolButtonStyle( + Qt.ToolButtonStyle.ToolButtonTextUnderIcon) if self.settings_loaded: self.manager_settings.setValue('view/compactview', checked) @@ -1764,7 +1787,7 @@ def showhide_column(self, col_num, show): @pyqtSlot(name='on_action_about_qubes_triggered') def action_about_qubes_triggered(self): about = AboutDialog(self) - about.exec_() + about.exec() def createPopupMenu(self): # pylint: disable=invalid-name menu = QMenu() @@ -1773,11 +1796,11 @@ def createPopupMenu(self): # pylint: disable=invalid-name return menu def open_tools_context_menu(self, widget, point): - self.tools_context_menu.exec_(widget.mapToGlobal(point)) + self.tools_context_menu.exec(widget.mapToGlobal(point)) @pyqtSlot('const QPoint&') def open_context_menu(self, point): - self.context_menu.exec_(self.table.mapToGlobal( + self.context_menu.exec(self.table.mapToGlobal( point + QPoint(10, 0))) def show_log(self): @@ -1801,7 +1824,7 @@ def show_log(self): if len(logfiles) > 0: log_dlg = log_dialog.LogDialog(self.qt_app, logfiles) - log_dlg.exec_() + log_dlg.exec() else: QMessageBox.warning( self, diff --git a/qubesmanager/qvm_template_gui.py b/qubesmanager/qvm_template_gui.py index d7e17cab..ece88c85 100644 --- a/qubesmanager/qvm_template_gui.py +++ b/qubesmanager/qvm_template_gui.py @@ -35,22 +35,27 @@ import typing import shlex -import PyQt5 # pylint: disable=import-error -import PyQt5.QtWidgets # pylint: disable=import-error -import PyQt5.QtCore # pylint: disable=import-error -import PyQt5.QtGui # pylint: disable=import-error +import PyQt6 # pylint: disable=import-error +import PyQt6.QtWidgets # pylint: disable=import-error +import PyQt6.QtCore # pylint: disable=import-error +import PyQt6.QtGui # pylint: disable=import-error from . import ui_templateinstallconfirmdlg # pylint: disable=no-name-in-module from . import ui_templateinstallprogressdlg # pylint: disable=no-name-in-module from . import ui_templatemanager # pylint: disable=no-name-in-module from . import utils from qui.utils import EOL_DATES, SUFFIXES # pylint: disable=import-error + +# this is needed for icons to actually work +# pylint: disable=unused-import +from . import resources + BASE_CMD = ['qvm-template', '--yes'] # singleton for "no date" ZERO_DATE = datetime.fromtimestamp(0, UTC) -tr = functools.partial(PyQt5.QtCore.QCoreApplication.translate, "Template GUI") +tr = functools.partial(PyQt6.QtCore.QCoreApplication.translate, "Template GUI") HELP_TEXT = tr(""" This tool can be used to manage templates on your system. \ @@ -71,18 +76,6 @@ """) -# Todo: -# - tests -# - packaging - -# tests: -# - run with data, see if there are things listed -# - check button visibility? - -# later bullcrap -# - fix qvm-template -# - update eol table - class TreeItem(abc.ABC): COL_NAMES = [ 'Name', @@ -252,21 +245,21 @@ def get_upgradable(self) -> bool: def status(self, role): # pylint: disable=too-many-return-statements if self.obsolete(): - if role == PyQt5.QtCore.Qt.ToolTipRole: + if role == PyQt6.QtCore.Qt.ItemDataRole.ToolTipRole: return tr("This template is obsolete and no longer receives " "updates") - if role == PyQt5.QtCore.Qt.DecorationRole: + if role == PyQt6.QtCore.Qt.ItemDataRole.DecorationRole: return ":/obsolete.svg" if self.template_status == 'extra': - if role == PyQt5.QtCore.Qt.ToolTipRole: + if role == PyQt6.QtCore.Qt.ItemDataRole.ToolTipRole: return tr("This template is a local template, not installed " "from a repository") - if role == PyQt5.QtCore.Qt.DecorationRole: + if role == PyQt6.QtCore.Qt.ItemDataRole.DecorationRole: return ':/checkmark-with-plus.svg' if self.template_status in ['installed', 'upgradable']: - if role == PyQt5.QtCore.Qt.ToolTipRole: + if role == PyQt6.QtCore.Qt.ItemDataRole.ToolTipRole: return tr("This template is installed") - if role == PyQt5.QtCore.Qt.DecorationRole: + if role == PyQt6.QtCore.Qt.ItemDataRole.DecorationRole: return ':/checkmark.svg' return None @@ -318,7 +311,7 @@ class DescriptiveItem(TreeItem): def __init__(self, name): self._name = name self._children: typing.List[TreeItem] = [] - self._parent = PyQt5.QtCore.QModelIndex() + self._parent = PyQt6.QtCore.QModelIndex() @property def name(self) -> str: @@ -337,23 +330,23 @@ def parent(self) -> TreeItem: return self._parent -class TemplateModel(PyQt5.QtCore.QAbstractItemModel): +class TemplateModel(PyQt6.QtCore.QAbstractItemModel): def __init__(self, qubes_app): super().__init__() self.qubes_app = qubes_app self.children = [] - def index(self, row, column, parent=PyQt5.QtCore.QModelIndex()): + def index(self, row, column, parent=PyQt6.QtCore.QModelIndex()): if not self.hasIndex(row, column, parent): - return PyQt5.QtCore.QModelIndex() + return PyQt6.QtCore.QModelIndex() if not parent.isValid(): child_item = self.children[row] else: child_item = parent.internalPointer().children[row] return self.createIndex(row, column, child_item) - def parent(self, child_index: PyQt5.QtCore.QModelIndex): - node = PyQt5.QtCore.QModelIndex() + def parent(self, child_index: PyQt6.QtCore.QModelIndex): + node = PyQt6.QtCore.QModelIndex() if child_index.isValid(): own_object = child_index.internalPointer() if own_object is not None: @@ -366,21 +359,21 @@ def parent(self, child_index: PyQt5.QtCore.QModelIndex): node = self.createIndex(row, 0, parent) return node - def rowCount(self, parent=PyQt5.QtCore.QModelIndex()): + def rowCount(self, parent=PyQt6.QtCore.QModelIndex()): if parent.internalPointer(): return len(parent.internalPointer().children) return len(self.children) - def columnCount(self, _parent=PyQt5.QtCore.QModelIndex()): + def columnCount(self, _parent=PyQt6.QtCore.QModelIndex()): return len(Template.COL_NAMES) - def data(self, index, role=PyQt5.QtCore.Qt.DisplayRole): + def data(self, index, role=PyQt6.QtCore.Qt.ItemDataRole.DisplayRole): # pylint: disable=too-many-return-statements if index.isValid(): data = index.internalPointer() - if role == PyQt5.QtCore.Qt.ItemDataRole: + if role == PyQt6.QtCore.Qt.ItemDataRole: return data.description - if role == PyQt5.QtCore.Qt.DisplayRole: + if role == PyQt6.QtCore.Qt.ItemDataRole.DisplayRole: if index.column() == 0: return data.name if index.column() == 1: @@ -390,7 +383,7 @@ def data(self, index, role=PyQt5.QtCore.Qt.DisplayRole): if index.column() == 3: return data.repository() return data.name - if role == PyQt5.QtCore.Qt.ToolTipRole: + if role == PyQt6.QtCore.Qt.ItemDataRole.ToolTipRole: if index.column() == 0: return "Template name" if index.column() == 1: @@ -399,29 +392,29 @@ def data(self, index, role=PyQt5.QtCore.Qt.DisplayRole): return "Template version" if index.column() == 3: return "Repository" - if role == PyQt5.QtCore.Qt.TextAlignmentRole: + if role == PyQt6.QtCore.Qt.ItemDataRole.TextAlignmentRole: if isinstance(data, int): - return PyQt5.QtCore.Qt.AlignRight - return PyQt5.QtCore.Qt.AlignLeft - if role == PyQt5.QtCore.Qt.DecorationRole: + return PyQt6.QtCore.Qt.AlignmentFlag.AlignRight + return PyQt6.QtCore.Qt.AlignmentFlag.AlignLeft + if role == PyQt6.QtCore.Qt.ItemDataRole.DecorationRole: if index.column() == 1: icon_name = data.status(role) if icon_name: - return PyQt5.QtGui.QIcon(icon_name) - if role == PyQt5.QtCore.Qt.UserRole: + return PyQt6.QtGui.QIcon(icon_name) + if role == PyQt6.QtCore.Qt.ItemDataRole.UserRole: return data return None def headerData(self, section, orientation, - role=PyQt5.QtCore.Qt.DisplayRole): + role=PyQt6.QtCore.Qt.ItemDataRole.DisplayRole): if section < len(Template.COL_NAMES) \ - and orientation == PyQt5.QtCore.Qt.Horizontal \ - and role == PyQt5.QtCore.Qt.DisplayRole: + and orientation == PyQt6.QtCore.Qt.Orientation.Horizontal \ + and role == PyQt6.QtCore.Qt.ItemDataRole.DisplayRole: return Template.COL_NAMES[section] return None - def removeRows(self, row, count, _parent=PyQt5.QtCore.QModelIndex()): - self.beginRemoveRows(PyQt5.QtCore.QModelIndex(), row, row + count) + def removeRows(self, row, count, _parent=PyQt6.QtCore.QModelIndex()): + self.beginRemoveRows(PyQt6.QtCore.QModelIndex(), row, row + count) del self.children[row:row+count] self.endRemoveRows() self.dataChanged.emit(*self.row_index(row, row + count)) @@ -448,7 +441,7 @@ async def refresh(self, refresh=True): return False, stderr # remove old rows rows_to_remove = len(self.children) - self.beginRemoveRows(PyQt5.QtCore.QModelIndex(), 0, + self.beginRemoveRows(PyQt6.QtCore.QModelIndex(), 0, rows_to_remove) self.children = [] self.endRemoveRows() @@ -509,7 +502,7 @@ async def refresh(self, refresh=True): # Convert back to list tpls = {k.title(): list(v.values()) for k, v in tpls.items()} - self.beginInsertRows(PyQt5.QtCore.QModelIndex(), 0, len(tpls) - 1) + self.beginInsertRows(PyQt6.QtCore.QModelIndex(), 0, len(tpls) - 1) for template_type, template_list in tpls.items(): if not template_list: continue @@ -529,10 +522,10 @@ async def refresh(self, refresh=True): class TemplateInstallConfirmDialog( ui_templateinstallconfirmdlg.Ui_TemplateInstallConfirmDlg, - PyQt5.QtWidgets.QDialog): + PyQt6.QtWidgets.QDialog): # pylint: disable=too-few-public-methods def __init__(self, question: str, operation_name: str, - palette: PyQt5.QtGui.QPalette, enable_warn: bool = False): + palette: PyQt6.QtGui.QPalette, enable_warn: bool = False): super().__init__() self.setupUi(self) @@ -541,19 +534,19 @@ def __init__(self, question: str, operation_name: str, ok_button = self.button_box.addButton( operation_name, - PyQt5.QtWidgets.QDialogButtonBox.ButtonRole.AcceptRole) + PyQt6.QtWidgets.QDialogButtonBox.ButtonRole.AcceptRole) ok_button.setPalette(palette) self.button_box.addButton( "Cancel", - PyQt5.QtWidgets.QDialogButtonBox.ButtonRole.RejectRole) + PyQt6.QtWidgets.QDialogButtonBox.ButtonRole.RejectRole) class TemplateInstallProgressDialog( ui_templateinstallprogressdlg.Ui_TemplateInstallProgressDlg, - PyQt5.QtWidgets.QDialog): + PyQt6.QtWidgets.QDialog): def __init__(self, command: typing.List[str], - palette: PyQt5.QtGui.QPalette, + palette: PyQt6.QtGui.QPalette, window_title: typing.Optional[str] = None): """ :param command: a list of strings containing the command to be used @@ -567,14 +560,14 @@ def __init__(self, command: typing.List[str], self.cancel_button = self.button_box.addButton( "Abort", - PyQt5.QtWidgets.QDialogButtonBox.ButtonRole.RejectRole) + PyQt6.QtWidgets.QDialogButtonBox.ButtonRole.RejectRole) def add_ok_button(self, error: bool = False): """Replace the "Cancel" button with OK or "Close" button""" self.button_box.removeButton(self.cancel_button) - ok_button: PyQt5.QtWidgets.QPushButton = self.button_box.addButton( + ok_button: PyQt6.QtWidgets.QPushButton = self.button_box.addButton( "Close" if error else "OK", - PyQt5.QtWidgets.QDialogButtonBox.ButtonRole.AcceptRole) + PyQt6.QtWidgets.QDialogButtonBox.ButtonRole.AcceptRole) if not error: ok_button.setPalette(self.qubes_palette) @@ -626,16 +619,16 @@ async def coro(): class QvmTemplateWindow( ui_templatemanager.Ui_MainWindow, - PyQt5.QtWidgets.QMainWindow): + PyQt6.QtWidgets.QMainWindow): def __init__(self, qt_app, qubes_app, dispatcher, _parent=None): super().__init__() self.setupUi(self) self.template_tree.header().setSectionResizeMode( - PyQt5.QtWidgets.QHeaderView.ResizeToContents) + PyQt6.QtWidgets.QHeaderView.ResizeMode.ResizeToContents) self.qubes_app = qubes_app - self.qt_app: PyQt5.QtWidgets.QApplication = qt_app - self.qt_app.setWindowIcon(PyQt5.QtGui.QIcon.fromTheme("qubes-manager")) + self.qt_app: PyQt6.QtWidgets.QApplication = qt_app + self.qt_app.setWindowIcon(PyQt6.QtGui.QIcon.fromTheme("qubes-manager")) self.dispatcher = dispatcher self.template_model = TemplateModel(self.qubes_app) @@ -671,10 +664,10 @@ def initialize_styles(self): qubes_style_buttons = [self.upgrade_button, self.install_button, self.reinstall_button, self.uninstall_button] palette = self.qt_app.palette() - palette.setColor(PyQt5.QtGui.QPalette.Button, PyQt5.QtGui.QColor( - "#4180c9")) - palette.setColor(PyQt5.QtGui.QPalette.ButtonText, PyQt5.QtGui.QColor( - "#ffffff")) + palette.setColor(PyQt6.QtGui.QPalette.ColorRole.Button, + PyQt6.QtGui.QColor("#4180c9")) + palette.setColor(PyQt6.QtGui.QPalette.ColorRole.ButtonText, + PyQt6.QtGui.QColor("#ffffff")) for button in qubes_style_buttons: button.setPalette(palette) @@ -718,10 +711,10 @@ def _get_selected_item(self) -> typing.Optional[TreeItem]: # and the selection model is single-row selected_item = selected_indexes[0] item = self.template_model.data(selected_item, - PyQt5.QtCore.Qt.UserRole) + PyQt6.QtCore.Qt.ItemDataRole.UserRole) return item - def template_selected(self, _selected: PyQt5.QtCore.QItemSelection): + def template_selected(self, _selected: PyQt6.QtCore.QItemSelection): item = self._get_selected_item() if not item: self._show_help() @@ -746,12 +739,12 @@ def _do_action(self, command: typing.List[str], operation_name: str, confirm = TemplateInstallConfirmDialog(question, operation_name, self.qubes_palette, enable_warn) - if confirm.exec_(): + if confirm.exec(): progress = TemplateInstallProgressDialog(command, self.qubes_palette, window_title) progress.install() - progress.exec_() + progress.exec() self.refresh() def do_uninstall(self): @@ -799,7 +792,7 @@ def refresh(self, refresh=True): async def coro(): ok, stderr = await self.template_model.refresh(refresh) if not ok: - PyQt5.QtWidgets.QMessageBox.warning( + PyQt6.QtWidgets.QMessageBox.warning( self, self.tr('Failed to fetch template list!'), self.tr('Failed to fetch template list: \n') + stderr diff --git a/qubesmanager/restore.py b/qubesmanager/restore.py index 90269a85..ffc5e5e5 100644 --- a/qubesmanager/restore.py +++ b/qubesmanager/restore.py @@ -20,8 +20,8 @@ # # -from PyQt5 import QtCore, QtWidgets, QtGui, Qt # pylint: disable=import-error import argparse +from PyQt6 import QtCore, QtWidgets, QtGui # pylint: disable=import-error import os import os.path import logging @@ -37,9 +37,12 @@ from qubesadmin import exc from qubesadmin.backup import restore +# this is needed for icons to actually work +# pylint: disable=unused-import +from . import resources # pylint: disable=too-few-public-methods -def parse_args() -> None: +def parse_args(): parser = argparse.ArgumentParser() parser.add_argument( '--log', action='store', default='INFO', @@ -88,8 +91,8 @@ def __init__(self, qt_app, qubes_app, parent=None): self.qt_app = qt_app self.qubes_app = qubes_app self.setWindowFlags(self.windowFlags() | - Qt.Qt.WindowMaximizeButtonHint | - Qt.Qt.WindowMinimizeButtonHint) + QtCore.Qt.WindowType.WindowMaximizeButtonHint | + QtCore.Qt.WindowType.WindowMinimizeButtonHint) self.vms_to_restore = None self.func_output = [] @@ -111,7 +114,10 @@ def __init__(self, qt_app, qubes_app, parent=None): self.dom0_restored_label.setVisible(False) self.select_vms_widget = multiselectwidget.MultiSelectWidget(self) - self.select_vms_layout.insertWidget(1, self.select_vms_widget) + + # widget must be inserted at position 0, not 1, to avoid segfault + # https://bugreports.qt.io/browse/QTBUG-130275 + self.select_vms_layout.insertWidget(0, self.select_vms_widget) self.currentIdChanged.connect(self.current_page_changed) self.dir_line_edit.textChanged.connect(self.backup_location_changed) @@ -128,14 +134,16 @@ def __init__(self, qt_app, qubes_app, parent=None): self.passwd_show_button.pressed.connect(self.show_hide_password) self.passphrase_line_edit.returnPressed.connect( - self.button(QtWidgets.QWizard.NextButton).click) + self.button(QtWidgets.QWizard.WizardButton.NextButton).click) def show_hide_password(self): if self.passwd_show_button.isChecked(): - self.passphrase_line_edit.setEchoMode(QtWidgets.QLineEdit.Password) + self.passphrase_line_edit.setEchoMode( + QtWidgets.QLineEdit.EchoMode.Password) self.passwd_show_button.setIcon(QtGui.QIcon(':/eye-off.svg')) else: - self.passphrase_line_edit.setEchoMode(QtWidgets.QLineEdit.Normal) + self.passphrase_line_edit.setEchoMode( + QtWidgets.QLineEdit.EchoMode.Normal) self.passwd_show_button.setIcon(QtGui.QIcon(':/eye.svg')) def setup_application(self): @@ -155,7 +163,6 @@ def cleanupPage(self, p_int): # pylint: disable=invalid-name def __fill_vms_list__(self): if self.vms_to_restore is not None: return - self.select_vms_widget.selected_list.clear() self.select_vms_widget.available_list.clear() @@ -201,7 +208,6 @@ def append_output(self, text): def current_page_changed(self, page_id): # pylint: disable=unused-argument if self.currentPage() is self.select_vms_page: self.__fill_vms_list__() - elif self.currentPage() is self.confirm_page: # pylint: disable=assignment-from-no-return self.vms_to_restore = self.backup_restore.get_restore_info() @@ -227,7 +233,7 @@ def current_page_changed(self, page_id): # pylint: disable=unused-argument self.confirm_page.completeChanged.emit() elif self.currentPage() is self.commit_page: - self.button(self.FinishButton).setDisabled(True) + self.button(self.WizardButton.FinishButton).setDisabled(True) self.showFileDialog.setEnabled(True) self.showFileDialog.setChecked(self.showFileDialog.isEnabled() and str(self.dir_line_edit.text()) @@ -264,8 +270,8 @@ def thread_finished(self): "the file selection dialog."))) backup_utils.select_path_button_clicked(self, False, True) - self.button(self.FinishButton).setEnabled(True) - self.button(self.CancelButton).setEnabled(False) + self.button(self.WizardButton.FinishButton).setEnabled(True) + self.button(self.WizardButton.CancelButton).setEnabled(False) self.showFileDialog.setEnabled(False) def update_log(self): @@ -295,7 +301,7 @@ def reject(self): self.backup_restore.canceled = True self.append_output('{0}'.format( self.tr("Aborting the operation..."))) - self.button(self.CancelButton).setDisabled(True) + self.button(self.WizardButton.CancelButton).setDisabled(True) else: self.done(0) diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index 358d25ec..0e824fc6 100644 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -43,10 +43,14 @@ from .appmenu_select import AppmenuSelectManager from . import firewall -from PyQt5 import QtCore, QtWidgets, QtGui, Qt # pylint: disable=import-error +from PyQt6 import QtCore, QtWidgets, QtGui # pylint: disable=import-error from . import ui_settingsdlg # pylint: disable=no-name-in-module +# this is needed for icons to actually work +# pylint: disable=unused-import +from . import resources + SERVICE_PREFIX = "service." SUPPORTED_SERVICE_PREFIX = "supported-service." @@ -56,6 +60,7 @@ INTERNAL_SERVICE_FEATURES = [IDLE_SERVICE] INTERNAL_SUPPORTED_FEATURES = [IDLE_SUPPORTED_SERVICE] + # pylint: disable=too-few-public-methods class RenameVMThread(common_threads.QubesThread): def __init__(self, vm, new_vm_name, dependencies): @@ -152,14 +157,15 @@ def __init__(self, vm, init_page="basic", qapp=None, qubesapp=None, self.setupUi(self) self.setWindowTitle(self.tr("Settings: {vm}").format(vm=self.vm.name)) self.setWindowFlags(self.windowFlags() | - Qt.Qt.WindowMaximizeButtonHint | - Qt.Qt.WindowMinimizeButtonHint) + QtCore.Qt.WindowType.WindowMaximizeButtonHint | + QtCore.Qt.WindowType.WindowMinimizeButtonHint) if init_page in self.tabs_indices: idx = self.tabs_indices[init_page] assert idx in range(self.tabWidget.count()) self.tabWidget.setCurrentIndex(idx) - self.buttonBox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect( + self.buttonBox.button( + QtWidgets.QDialogButtonBox.StandardButton.Apply).clicked.connect( self.apply) self.tabWidget.currentChanged.connect(self.current_tab_changed) @@ -237,15 +243,15 @@ def __init__(self, vm, init_page="basic", qapp=None, qubesapp=None, self.app_list.available_list.setDragEnabled(True) self.app_list.available_list.setAcceptDrops(True) self.app_list.available_list.setDragDropMode( - QtWidgets.QListWidget.DragDrop) + QtWidgets.QListWidget.DragDropMode.DragDrop) self.app_list.available_list.setDefaultDropAction( - QtCore.Qt.MoveAction) + QtCore.Qt.DropAction.MoveAction) self.app_list.selected_list.setDragEnabled(True) self.app_list.selected_list.setAcceptDrops(True) self.app_list.selected_list.setDragDropMode( - QtWidgets.QListWidget.DragDrop) + QtWidgets.QListWidget.DragDropMode.DragDrop) self.app_list.selected_list.setDefaultDropAction( - QtCore.Qt.MoveAction) + QtCore.Qt.DropAction.MoveAction) # template change if self.template_name.isEnabled(): @@ -282,8 +288,8 @@ def clear_threads(self): raise RuntimeError(self.tr('No finished thread found')) def keyPressEvent(self, event): # pylint: disable=invalid-name - if event.key() == QtCore.Qt.Key_Enter \ - or event.key() == QtCore.Qt.Key_Return: + if event.key() == QtCore.Qt.Key.Key_Enter \ + or event.key() == QtCore.Qt.Key.Key_Return: return super().keyPressEvent(event) @@ -397,9 +403,11 @@ def current_tab_changed(self, idx): def __init_basic_tab__(self): self.vmname.setText(self.vm.name) self.vmname.setValidator( - QtGui.QRegExpValidator( - QtCore.QRegExp("[a-zA-Z0-9_-]*", - QtCore.Qt.CaseInsensitive), None)) + QtGui.QRegularExpressionValidator( + QtCore.QRegularExpression( + "[a-zA-Z0-9_-]*", + QtCore.QRegularExpression. + PatternOption.CaseInsensitiveOption), None)) self.vmname.setEnabled(False) self.rename_vm_button.setEnabled(not self.vm.is_running()) self.delete_vm_button.setEnabled(not self.vm.is_running()) @@ -590,10 +598,10 @@ def __apply_basic_tab__(self): "to a halted Qube.
" "Do you want to start the Qube" " '{0}'?").format(netvm.name), - QtWidgets.QMessageBox.Yes | - QtWidgets.QMessageBox.Cancel) + QtWidgets.QMessageBox.StandardButton.Yes | + QtWidgets.QMessageBox.StandardButton.Cancel) - if reply == QtWidgets.QMessageBox.Yes: + if reply == QtWidgets.QMessageBox.StandardButton.Yes: netvm.start() self.vm.netvm = self.netVM.currentData() else: @@ -816,7 +824,7 @@ def clone_vm(self): with common_threads.busy_cursor(): clone_window = clone_vm.CloneVMDlg( self.qapp, self.qubesapp, src_vm=self.vm) - clone_window.exec_() + clone_window.exec() ######### advanced tab @@ -1078,7 +1086,7 @@ def __apply_advanced_tab__(self): def include_in_balancing_changed(self, state): if self.dev_list.selected_list.count() > 0: - if state == ui_settingsdlg.QtCore.Qt.Checked: + if state == ui_settingsdlg.QtCore.Qt.CheckState.Checked: self.dmm_warning_adv.show() self.dmm_warning_dev.show() else: @@ -1091,7 +1099,7 @@ def include_in_balancing_changed(self, state): def boot_from_cdrom_button_pressed(self): boot_dialog = bootfromdevice.VMBootFromDeviceWindow( self.vm.name, self.qapp, self.qubesapp, self) - if boot_dialog.exec_(): + if boot_dialog.exec(): self.save_and_apply() qvm_start.main( ['--cdrom', boot_dialog.cdrom_location, self.vm.name]) @@ -1382,9 +1390,10 @@ def __init_services_tab__(self): continue service = feature[len(SERVICE_PREFIX):] item = QtWidgets.QListWidgetItem(service) - item.setCheckState(ui_settingsdlg.QtCore.Qt.Checked - if self.vm.features[feature] - else ui_settingsdlg.QtCore.Qt.Unchecked) + item.setCheckState( + ui_settingsdlg.QtCore.Qt.CheckState.Checked + if self.vm.features[feature] + else ui_settingsdlg.QtCore.Qt.CheckState.Unchecked) self.services_list.addItem(item) self.new_srv_dict[service] = self.vm.features[feature] except qubesadmin.exc.QubesDaemonAccessError: @@ -1436,7 +1445,7 @@ def __add_service__(self): self.tr('Service already on the list!')) return item = QtWidgets.QListWidgetItem(srv) - item.setCheckState(ui_settingsdlg.QtCore.Qt.Checked) + item.setCheckState(ui_settingsdlg.QtCore.Qt.CheckState.Checked) self.services_list.addItem(item) self.new_srv_dict[srv] = True @@ -1460,7 +1469,8 @@ def __apply_services_tab__(self): for i in range(self.services_list.count()): item = self.services_list.item(i) self.new_srv_dict[str(item.text())] = \ - item.checkState() == ui_settingsdlg.QtCore.Qt.Checked + (item.checkState() == + ui_settingsdlg.QtCore.Qt.CheckState.Checked) for service, v in self.new_srv_dict.items(): feature = SERVICE_PREFIX + service @@ -1484,9 +1494,9 @@ def set_fw_model(self, model): self.fw_model = model self.rulesTreeView.setModel(model) self.rulesTreeView.header().setSectionResizeMode( - QtWidgets.QHeaderView.ResizeToContents) + QtWidgets.QHeaderView.ResizeMode.ResizeToContents) self.rulesTreeView.header().setSectionResizeMode( - 0, QtWidgets.QHeaderView.Stretch) + 0, QtWidgets.QHeaderView.ResizeMode.Stretch) self.set_allow(model.allow) if model.temp_full_access_expire_time: self.temp_full_access.setChecked(True) diff --git a/qubesmanager/template_manager.py b/qubesmanager/template_manager.py index 70e6aec9..747a86b1 100644 --- a/qubesmanager/template_manager.py +++ b/qubesmanager/template_manager.py @@ -22,12 +22,16 @@ from qubesadmin import exc -from PyQt5 import QtWidgets, QtGui, QtCore # pylint: disable=import-error +from PyQt6 import QtWidgets, QtGui, QtCore # pylint: disable=import-error from . import ui_templatemanager # pylint: disable=no-name-in-module from . import utils from . import common_threads +# this is needed for icons to actually work +# pylint: disable=unused-import +from . import resources + column_names = ['State', 'Qube', 'Current template', 'New template'] @@ -52,15 +56,18 @@ def __init__(self, qt_app, qubes_app, dispatcher, parent=None): self.prepare_lists() self.initialize_table_events() - self.buttonBox.button(QtWidgets.QDialogButtonBox.Ok).clicked.connect( + self.buttonBox.button( + QtWidgets.QDialogButtonBox.StandardButton.Ok).clicked.connect( self.apply) self.buttonBox.button( QtWidgets.QDialogButtonBox.StandardButton.Ok).setText('Apply') self.buttonBox.button( - QtWidgets.QDialogButtonBox.Cancel).clicked.connect(self.cancel) + QtWidgets.QDialogButtonBox.StandardButton.Cancel).clicked.connect( + self.cancel) self.buttonBox.button( QtWidgets.QDialogButtonBox.StandardButton.Cancel).setText('Close') - self.buttonBox.button(QtWidgets.QDialogButtonBox.Reset).clicked.connect( + self.buttonBox.button( + QtWidgets.QDialogButtonBox.StandardButton.Reset).clicked.connect( self.reset) self.change_all_combobox.currentIndexChanged.connect( @@ -168,7 +175,7 @@ def sorting_changed(self, index, _order): if index == column_names.index('New template') or \ index == column_names.index('State'): self.vm_list.horizontalHeader().setSortIndicator( - -1, QtCore.Qt.AscendingOrder) + -1, QtCore.Qt.SortOrder.AscendingOrder) def clear_selection(self): for row in self.rows_in_table.values(): diff --git a/qubesmanager/tests/__init__.py b/qubesmanager/tests/__init__.py index 5590f56c..059e7833 100644 --- a/qubesmanager/tests/__init__.py +++ b/qubesmanager/tests/__init__.py @@ -2,7 +2,7 @@ import sys import qasync -from PyQt5 import QtWidgets +from PyQt6 import QtWidgets qtapp = None loop = None diff --git a/qubesmanager/tests/test_backup.py b/qubesmanager/tests/test_backup.py index e6475c29..15a88833 100644 --- a/qubesmanager/tests/test_backup.py +++ b/qubesmanager/tests/test_backup.py @@ -23,7 +23,7 @@ import unittest import unittest.mock -from PyQt5 import QtTest, QtCore, QtWidgets +from PyQt6 import QtTest, QtCore, QtWidgets from qubesadmin import Qubes, events, utils, exc from qubesmanager import backup from qubesmanager.tests import init_qtapp @@ -161,7 +161,7 @@ def test_06_passphrase_verification(self): # required to check if next button is correctly enabled self.dialog.dir_line_edit.setText("/home") - next_button = self.dialog.button(self.dialog.NextButton) + next_button = self.dialog.button(self.dialog.WizardButton.NextButton) # check if next remains inactive for various incorrect # passphrase/incorrect combinations @@ -394,7 +394,7 @@ def test_21_loading_settings_error(self, mock_load): self.assertIn('incorrect_vm', self.dialog.warning_running_label.text()) @unittest.mock.patch('qubesmanager.backup_utils.load_backup_profile') - @unittest.mock.patch('PyQt5.QtWidgets.QMessageBox.information') + @unittest.mock.patch('PyQt6.QtWidgets.QMessageBox.information') def test_22_loading_settings_exc(self, mock_info, mock_load): mock_load.side_effect = exc.QubesException('Error') @@ -431,7 +431,7 @@ def test_23_cancel_confirm(self, *_args): mock_remove.assert_called_once_with( '/etc/qubes/backup/qubes-manager-backup-tmp.conf') - @unittest.mock.patch('PyQt5.QtWidgets.QMessageBox.warning') + @unittest.mock.patch('PyQt6.QtWidgets.QMessageBox.warning') @unittest.mock.patch('qubesmanager.backup_utils.write_backup_profile') @unittest.mock.patch('qubesadmin.Qubes.qubesd_call', return_value=b'backup output') @@ -456,7 +456,7 @@ def test_24_cancel_in_progress(self, mock_call, *_args): mock_remove.assert_called_once_with( '/etc/qubes/backup/qubes-manager-backup-tmp.conf') - @unittest.mock.patch('PyQt5.QtWidgets.QMessageBox.warning') + @unittest.mock.patch('PyQt6.QtWidgets.QMessageBox.warning') @unittest.mock.patch('os.system') @unittest.mock.patch('os.remove') @unittest.mock.patch('qubesmanager.backup_utils.write_backup_profile') @@ -496,7 +496,7 @@ def test_25_successful_backup(self, _a, _b, mock_remove, self.assertEqual(mock_warning.call_count, 0, "Backup succeeded but received warning") - @unittest.mock.patch('PyQt5.QtWidgets.QMessageBox.warning') + @unittest.mock.patch('PyQt6.QtWidgets.QMessageBox.warning') @unittest.mock.patch('os.system') @unittest.mock.patch('os.remove') @unittest.mock.patch('qubesmanager.backup_utils.write_backup_profile') @@ -535,7 +535,7 @@ def test_26_success_backup_poweroff( self.assertEqual(mock_warning.call_count, 0, "Backup succeeded but received warning") - @unittest.mock.patch('PyQt5.QtWidgets.QMessageBox.warning') + @unittest.mock.patch('PyQt6.QtWidgets.QMessageBox.warning') @unittest.mock.patch('os.system') @unittest.mock.patch('os.remove') @unittest.mock.patch('qubesmanager.backup_utils.write_backup_profile') @@ -573,7 +573,7 @@ def test_27_failed_backup( "Attempted shutdown at failed backup") self.assertEqual(mock_warn.call_count, 1) - @unittest.mock.patch('PyQt5.QtWidgets.QMessageBox.warning') + @unittest.mock.patch('PyQt6.QtWidgets.QMessageBox.warning') @unittest.mock.patch('os.system') @unittest.mock.patch('os.remove') @unittest.mock.patch('qubesmanager.backup_utils.write_backup_profile') @@ -621,12 +621,14 @@ def _select_location(self, vm_name): widget.setCurrentIndex(widget.currentIndex() + 1) def _click_next(self): - next_widget = self.dialog.button(QtWidgets.QWizard.NextButton) - QtTest.QTest.mouseClick(next_widget, QtCore.Qt.LeftButton) + next_widget = self.dialog.button( + QtWidgets.QWizard.WizardButton.NextButton) + QtTest.QTest.mouseClick(next_widget, QtCore.Qt.MouseButton.LeftButton) def _click_cancel(self): - cancel_widget = self.dialog.button(QtWidgets.QWizard.CancelButton) - QtTest.QTest.mouseClick(cancel_widget, QtCore.Qt.LeftButton) + cancel_widget = self.dialog.button( + QtWidgets.QWizard.WizardButton.CancelButton) + QtTest.QTest.mouseClick(cancel_widget, QtCore.Qt.MouseButton.LeftButton) def _select_vm(self, name_starts_with): for i in range(self.dialog.select_vms_widget.available_list.count()): diff --git a/qubesmanager/tests/test_backup_utils.py b/qubesmanager/tests/test_backup_utils.py index 615b9f41..cc9653e9 100644 --- a/qubesmanager/tests/test_backup_utils.py +++ b/qubesmanager/tests/test_backup_utils.py @@ -21,7 +21,7 @@ # import logging.handlers import unittest.mock -from PyQt5 import QtWidgets +from PyQt6 import QtWidgets from qubesadmin import Qubes from qubesmanager import backup_utils diff --git a/qubesmanager/tests/test_clone_vm.py b/qubesmanager/tests/test_clone_vm.py index 9cfe53ea..8094377d 100644 --- a/qubesmanager/tests/test_clone_vm.py +++ b/qubesmanager/tests/test_clone_vm.py @@ -23,7 +23,7 @@ import unittest import unittest.mock -from PyQt5 import QtTest, QtCore +from PyQt6 import QtTest, QtCore from qubesadmin import Qubes from qubesmanager.tests import init_qtapp from qubesmanager import clone_vm @@ -46,17 +46,17 @@ def setUp(self): # mock the progress dialog to speed testing up self.patcher_progress = unittest.mock.patch( - 'PyQt5.QtWidgets.QProgressDialog') + 'PyQt6.QtWidgets.QProgressDialog') self.mock_progress = self.patcher_progress.start() self.addCleanup(self.patcher_progress.stop) # mock the message dialog to not hang on success self.patcher_warning = unittest.mock.patch( - 'PyQt5.QtWidgets.QMessageBox.warning') + 'PyQt6.QtWidgets.QMessageBox.warning') self.mock_warning = self.patcher_warning.start() self.addCleanup(self.patcher_warning.stop) self.patcher_information = unittest.mock.patch( - 'PyQt5.QtWidgets.QMessageBox.information') + 'PyQt6.QtWidgets.QMessageBox.information') self.mock_information = self.patcher_information.start() self.addCleanup(self.patcher_information.stop) @@ -203,15 +203,15 @@ def test_09_pool_nondefault(self): def __click_ok(self): okwidget = self.dialog.buttonBox.button( - self.dialog.buttonBox.Ok) + self.dialog.buttonBox.StandardButton.Ok) - QtTest.QTest.mouseClick(okwidget, QtCore.Qt.LeftButton) + QtTest.QTest.mouseClick(okwidget, QtCore.Qt.MouseButton.LeftButton) def __click_cancel(self): cancelwidget = self.dialog.buttonBox.button( - self.dialog.buttonBox.Cancel) + self.dialog.buttonBox.StandardButton.Cancel) - QtTest.QTest.mouseClick(cancelwidget, QtCore.Qt.LeftButton) + QtTest.QTest.mouseClick(cancelwidget, QtCore.Qt.MouseButton.LeftButton) class CloneVMTestSrcVM(unittest.TestCase): @@ -229,17 +229,17 @@ def setUp(self): # mock the progress dialog to speed testing up self.patcher_progress = unittest.mock.patch( - 'PyQt5.QtWidgets.QProgressDialog') + 'PyQt6.QtWidgets.QProgressDialog') self.mock_progress = self.patcher_progress.start() self.addCleanup(self.patcher_progress.stop) # mock the message dialog to not hang on success self.patcher_warning = unittest.mock.patch( - 'PyQt5.QtWidgets.QMessageBox.warning') + 'PyQt6.QtWidgets.QMessageBox.warning') self.mock_warning = self.patcher_warning.start() self.addCleanup(self.patcher_warning.stop) self.patcher_information = unittest.mock.patch( - 'PyQt5.QtWidgets.QMessageBox.information') + 'PyQt6.QtWidgets.QMessageBox.information') self.mock_information = self.patcher_information.start() self.addCleanup(self.patcher_information.stop) @@ -271,9 +271,9 @@ def test_01_simple_clone(self): def __click_ok(self): okwidget = self.dialog.buttonBox.button( - self.dialog.buttonBox.Ok) + self.dialog.buttonBox.StandardButton.Ok) - QtTest.QTest.mouseClick(okwidget, QtCore.Qt.LeftButton) + QtTest.QTest.mouseClick(okwidget, QtCore.Qt.MouseButton.LeftButton) if __name__ == "__main__": diff --git a/qubesmanager/tests/test_create_new_vm.py b/qubesmanager/tests/test_create_new_vm.py index 19456d2e..24925bc2 100644 --- a/qubesmanager/tests/test_create_new_vm.py +++ b/qubesmanager/tests/test_create_new_vm.py @@ -23,7 +23,7 @@ import unittest import unittest.mock -from PyQt5 import QtTest, QtCore +from PyQt6 import QtTest, QtCore from qubesadmin import Qubes from qubesmanager.tests import init_qtapp from qubesmanager import create_new_vm @@ -44,7 +44,7 @@ def setUp(self): # mock the progress dialog to speed testing up self.patcher_progress = unittest.mock.patch( - 'PyQt5.QtWidgets.QProgressDialog') + 'PyQt6.QtWidgets.QProgressDialog') self.mock_progress = self.patcher_progress.start() self.addCleanup(self.patcher_progress.stop) @@ -204,7 +204,7 @@ def test_10_standalone_empty(self, mock_qvm_start, mock_bootwindow): self.mock_thread().msg = None self.dialog.create_finished() - mock_bootwindow.return_value.exec_.assert_called_once_with() + mock_bootwindow.return_value.exec.assert_called_once_with() mock_qvm_start.main.assert_called_once_with( ['--cdrom', 'CDROM_LOCATION', 'test-vm']) @@ -270,15 +270,15 @@ def test_12_setting_change(self): def __click_ok(self): okwidget = self.dialog.buttonBox.button( - self.dialog.buttonBox.Ok) + self.dialog.buttonBox.StandardButton.Ok) - QtTest.QTest.mouseClick(okwidget, QtCore.Qt.LeftButton) + QtTest.QTest.mouseClick(okwidget, QtCore.Qt.MouseButton.LeftButton) def __click_cancel(self): cancelwidget = self.dialog.buttonBox.button( - self.dialog.buttonBox.Cancel) + self.dialog.buttonBox.StandardButton.Cancel) - QtTest.QTest.mouseClick(cancelwidget, QtCore.Qt.LeftButton) + QtTest.QTest.mouseClick(cancelwidget, QtCore.Qt.MouseButton.LeftButton) # class CreatteVMThreadTest(unittest.TestCase): diff --git a/qubesmanager/tests/test_qube_manager.py b/qubesmanager/tests/test_qube_manager.py index 3e36b1f0..a768a6e7 100644 --- a/qubesmanager/tests/test_qube_manager.py +++ b/qubesmanager/tests/test_qube_manager.py @@ -30,9 +30,9 @@ import datetime import time -from PyQt5 import QtTest, QtCore, QtWidgets -from PyQt5.QtCore import (Qt, QSize) -from PyQt5.QtGui import (QIcon) +from PyQt6 import QtTest, QtCore, QtWidgets +from PyQt6.QtCore import (Qt, QSize) +from PyQt6.QtGui import (QIcon) from qubesadmin import Qubes, events, exc import qubesmanager.qube_manager as qube_manager @@ -78,7 +78,7 @@ def setUp(self): self.qtapp, self.loop = init_qtapp() self.mock_qprogress = unittest.mock.patch( - 'PyQt5.QtWidgets.QProgressDialog') + 'PyQt6.QtWidgets.QProgressDialog') self.mock_qprogress.start() self.addCleanup(self.mock_qprogress.stop) @@ -188,7 +188,9 @@ def test_007_incl_in_backups_listed(self): for row in range(self.dialog.table.model().rowCount()): vm = self._get_table_vm(row) - incl_backups_item = self._get_table_item(row, "Backup", Qt.CheckStateRole) == Qt.Checked + incl_backups_item = self._get_table_item( + row, "Backup", + Qt.ItemDataRole.CheckStateRole) == Qt.CheckState.Checked incl_backups_value = getattr(vm, 'include_in_backups', False) self.assertEqual( @@ -243,7 +245,8 @@ def test_011_is_label_correct(self): icon = QIcon.fromTheme(getattr(vm, 'icon', 'appvm-black')) icon = icon.pixmap(icon_size) - label_pixmap = self._get_table_item(row, "Label", Qt.DecorationRole) + label_pixmap = self._get_table_item( + row, "Label", Qt.ItemDataRole.DecorationRole) self.assertEqual(label_pixmap.toImage(), icon.toImage()) @@ -268,9 +271,9 @@ def test_013_incorrect_settings_file(self): mock_settings.side_effect = ( lambda x, *args, **kwargs: settings_result_dict.get(x)) - with unittest.mock.patch('PyQt5.QtCore.QSettings.value', + with unittest.mock.patch('PyQt6.QtCore.QSettings.value', mock_settings),\ - unittest.mock.patch('PyQt5.QtWidgets.QMessageBox.warning')\ + unittest.mock.patch('PyQt6.QtWidgets.QMessageBox.warning')\ as mock_warning: self.dialog = qube_manager.VmManagerWindow( self.qtapp, self.qapp, self.dispatcher) @@ -278,11 +281,11 @@ def test_013_incorrect_settings_file(self): def test_100_sorting(self): col = self.dialog.qubes_model.columns_indices.index("Template") - self.dialog.table.sortByColumn(col, QtCore.Qt.AscendingOrder) + self.dialog.table.sortByColumn(col, QtCore.Qt.SortOrder.AscendingOrder) self.__check_sorting("Template") col = self.dialog.qubes_model.columns_indices.index("Name") - self.dialog.table.sortByColumn(col, QtCore.Qt.AscendingOrder) + self.dialog.table.sortByColumn(col, QtCore.Qt.SortOrder.AscendingOrder) self.__check_sorting("Name") @unittest.mock.patch('qubesmanager.qube_manager.QSettings.setValue') @@ -302,7 +305,7 @@ def test_200_vm_open_settings(self, mock_window): widget = self.dialog.toolbar.widgetForAction( self.dialog.action_settings) QtTest.QTest.mouseClick(widget, - QtCore.Qt.LeftButton) + QtCore.Qt.MouseButton.LeftButton) mock_window.assert_called_once_with( selected_vm, "basic", self.qtapp, self.qapp, self.dialog) @@ -323,7 +326,7 @@ def test_202_vm_open_firewall(self, mock_window): widget = self.dialog.toolbar.widgetForAction( self.dialog.action_editfwrules) QtTest.QTest.mouseClick(widget, - QtCore.Qt.LeftButton) + QtCore.Qt.MouseButton.LeftButton) mock_window.assert_called_once_with( selected_vm, "firewall", self.qtapp, self.qapp, self.dialog) @@ -334,11 +337,11 @@ def test_203_vm_open_apps(self, mock_window): widget = self.dialog.toolbar.widgetForAction( self.dialog.action_appmenus) QtTest.QTest.mouseClick(widget, - QtCore.Qt.LeftButton) + QtCore.Qt.MouseButton.LeftButton) mock_window.assert_called_once_with( selected_vm, "applications", self.qtapp, self.qapp, self.dialog) - @unittest.mock.patch('PyQt5.QtWidgets.QMessageBox.warning') + @unittest.mock.patch('PyQt6.QtWidgets.QMessageBox.warning') def test_204_vm_keyboard(self, mock_message): selected_vm = self._select_non_admin_vm(running=True) self.assertIsNotNone(selected_vm, "No valid non-admin VM found") @@ -348,12 +351,12 @@ def test_204_vm_keyboard(self, mock_message): self.dialog.action_set_keyboard_layout) with unittest.mock.patch.object(selected_vm, 'run') as mock_run: QtTest.QTest.mouseClick(widget, - QtCore.Qt.LeftButton) + QtCore.Qt.MouseButton.LeftButton) mock_run.assert_called_once_with("qubes-change-keyboard-layout") self.assertEqual(mock_message.call_count, 0, "VM does not support new layout change") - @unittest.mock.patch('PyQt5.QtWidgets.QMessageBox.warning') + @unittest.mock.patch('PyQt6.QtWidgets.QMessageBox.warning') def test_205_vm_keyboard_not_running(self, mock_message): selected_vm = self._select_non_admin_vm(running=False) self.assertIsNotNone(selected_vm, "No valid non-admin VM found") @@ -361,7 +364,7 @@ def test_205_vm_keyboard_not_running(self, mock_message): self.dialog.action_set_keyboard_layout) with unittest.mock.patch.object(selected_vm, 'run') as mock_run: QtTest.QTest.mouseClick(widget, - QtCore.Qt.LeftButton) + QtCore.Qt.MouseButton.LeftButton) self.assertEqual(mock_run.call_count, 0, "Keyboard change called on a halted VM") @@ -379,11 +382,11 @@ def test_208_update_vm_admin(self): with unittest.mock.patch('qubesmanager.qube_manager.UpdateVMsThread') \ as mock_update: QtTest.QTest.mouseClick(widget, - QtCore.Qt.LeftButton) + QtCore.Qt.MouseButton.LeftButton) mock_update.assert_called_once_with([selected_vm.name]) mock_update().start.assert_called_once_with() - @unittest.mock.patch("PyQt5.QtWidgets.QInputDialog.getText", + @unittest.mock.patch("PyQt6.QtWidgets.QInputDialog.getText", return_value=("command to run", True)) def test_209_run_command_in_vm(self, _): selected_vm = self._select_non_admin_vm() @@ -404,7 +407,7 @@ def test_210_run_command_in_adminvm(self): self.assertFalse(self.dialog.action_run_command_in_vm.isEnabled(), "Should not be able to run commands for dom0") - @unittest.mock.patch("PyQt5.QtWidgets.QMessageBox.warning") + @unittest.mock.patch("PyQt6.QtWidgets.QMessageBox.warning") def test_211_pausevm(self, mock_warn): selected_vm = self._select_non_admin_vm(running=True) @@ -442,9 +445,9 @@ def test_213_resume_running_vm(self): self._select_non_admin_vm(running=True) self.assertFalse(self.dialog.action_resumevm.isEnabled()) - @unittest.mock.patch("PyQt5.QtWidgets.QMessageBox.question", - return_value=QtWidgets.QMessageBox.Yes) - @unittest.mock.patch('PyQt5.QtCore.QTimer.singleShot') + @unittest.mock.patch("PyQt6.QtWidgets.QMessageBox.question", + return_value=QtWidgets.QMessageBox.StandardButton.Yes) + @unittest.mock.patch('PyQt6.QtCore.QTimer.singleShot') @unittest.mock.patch('qubesmanager.qube_manager.VmShutdownMonitor') def test_214_shutdownvm(self, mock_monitor, mock_timer, _): selected_vm = self._select_non_admin_vm(running=True) @@ -492,8 +495,8 @@ def test_218_remove_vm_dependencies(self, mock_dependencies, mock_msgbox): mock_msgbox().show.assert_called_with() - @unittest.mock.patch('PyQt5.QtWidgets.QMessageBox.warning') - @unittest.mock.patch("PyQt5.QtWidgets.QInputDialog.getText") + @unittest.mock.patch('PyQt6.QtWidgets.QMessageBox.warning') + @unittest.mock.patch("PyQt6.QtWidgets.QInputDialog.getText") @unittest.mock.patch('qubesadmin.utils.vm_dependencies') def test_219_remove_vm_no_depencies( self, mock_dependencies, mock_input, mock_warning): @@ -527,10 +530,10 @@ def test_220_restartvm_halted_vm(self): self._select_non_admin_vm(running=False) self.assertFalse(self.dialog.action_restartvm.isEnabled()) - @unittest.mock.patch('PyQt5.QtCore.QTimer.singleShot') + @unittest.mock.patch('PyQt6.QtCore.QTimer.singleShot') @unittest.mock.patch('qubesmanager.qube_manager.VmShutdownMonitor') - @unittest.mock.patch("PyQt5.QtWidgets.QMessageBox.question", - return_value=QtWidgets.QMessageBox.Yes) + @unittest.mock.patch("PyQt6.QtWidgets.QMessageBox.question", + return_value=QtWidgets.QMessageBox.StandardButton.Yes) def test_221_restartvm_running_vm(self, _msgbox, mock_monitor, _qtimer): selected_vm = self._select_non_admin_vm(running=True) @@ -545,8 +548,8 @@ def test_221_restartvm_running_vm(self, _msgbox, mock_monitor, _qtimer): selected_vm, 1000, True, unittest.mock.ANY) @unittest.mock.patch('qubesmanager.qube_manager.StartVMThread') - @unittest.mock.patch("PyQt5.QtWidgets.QMessageBox.question", - return_value=QtWidgets.QMessageBox.Yes) + @unittest.mock.patch("PyQt6.QtWidgets.QMessageBox.question", + return_value=QtWidgets.QMessageBox.StandardButton.Yes) def test_222_restartvm_shutdown_meantime(self, _, mock_thread): selected_vm = self._select_non_admin_vm(running=True) @@ -572,8 +575,8 @@ def test_223_updatevm_running(self, mock_thread): self.dialog.clear_threads) mock_thread().start.assert_called_once_with() - @unittest.mock.patch("PyQt5.QtWidgets.QMessageBox.question", - return_value=QtWidgets.QMessageBox.Yes) + @unittest.mock.patch("PyQt6.QtWidgets.QMessageBox.question", + return_value=QtWidgets.QMessageBox.StandardButton.Yes) def test_224_killvm(self, _): selected_vm = self._select_non_admin_vm(running=True) action = self.dialog.action_killvm @@ -582,8 +585,8 @@ def test_224_killvm(self, _): action.trigger() mock_kill.assert_called_once_with() - @unittest.mock.patch("PyQt5.QtWidgets.QMessageBox.question", - return_value=QtWidgets.QMessageBox.Cancel) + @unittest.mock.patch("PyQt6.QtWidgets.QMessageBox.question", + return_value=QtWidgets.QMessageBox.StandardButton.Cancel) def test_225_killvm_cancel(self, _): selected_vm = self._select_non_admin_vm(running=True) action = self.dialog.action_killvm @@ -661,10 +664,12 @@ def test_233_search_action(self): # input text self.dialog.searchbox.setText("sys") # click outside the widget - QtTest.QTest.mouseClick(self.dialog.table, QtCore.Qt.LeftButton) + QtTest.QTest.mouseClick(self.dialog.table, + QtCore.Qt.MouseButton.LeftButton) # click the widget, check if it is correctly activated and the whole # text was selected - QtTest.QTest.mouseClick(self.dialog.searchbox, QtCore.Qt.LeftButton) + QtTest.QTest.mouseClick(self.dialog.searchbox, + QtCore.Qt.MouseButton.LeftButton) self.assertTrue(self.dialog.searchbox.hasFocus()) self.assertEqual(self.dialog.searchbox.selectedText(), "sys") @@ -686,7 +691,7 @@ def test_234_searchbox(self): "Incorrect number of vms shown for cleared search box") def test_235_hide_show_toolbars(self): - with unittest.mock.patch('PyQt5.QtCore.QSettings.setValue')\ + with unittest.mock.patch('PyQt6.QtCore.QSettings.setValue')\ as mock_setvalue: self.dialog.action_menubar.trigger() mock_setvalue.assert_called_with('view/menubar_visible', False) @@ -703,7 +708,7 @@ def test_236_clear_searchbox(self): self.assertEqual(self.dialog.searchbox.text(), "text") - QtTest.QTest.keyPress(self.dialog, QtCore.Qt.Key_Escape) + QtTest.QTest.keyPress(self.dialog, QtCore.Qt.Key.Key_Escape) self.assertEqual(self.dialog.searchbox.text(), "", "Escape failed to clear searchbox") @@ -714,10 +719,10 @@ def test_236_clear_searchbox(self): self.assertEqual(expected_number, actual_number, "Incorrect number of vms shown for cleared search box") - @unittest.mock.patch('PyQt5.QtWidgets.QMessageBox.question') + @unittest.mock.patch('PyQt6.QtWidgets.QMessageBox.question') @listen_for_events def test_240_network_menu_single(self, mock_question): - mock_question.return_value = QtWidgets.QMessageBox.Yes + mock_question.return_value = QtWidgets.QMessageBox.StandardButton.Yes target_vm_name = 'work' self._run_command_and_process_events( @@ -777,10 +782,10 @@ def test_240_network_menu_single(self, mock_question): mock_question.assert_called() self.assertTrue(selected_vm.property_is_default('netvm')) - @unittest.mock.patch('PyQt5.QtWidgets.QMessageBox.question') + @unittest.mock.patch('PyQt6.QtWidgets.QMessageBox.question') @listen_for_events def test_241_network_menu_multiple(self, mock_question): - mock_question.return_value = QtWidgets.QMessageBox.Yes + mock_question.return_value = QtWidgets.QMessageBox.StandardButton.Yes target_vm_names = ['work', 'personal', 'vault'] work = self.qapp.domains['work'] personal = self.qapp.domains['personal'] @@ -828,11 +833,11 @@ def test_241_network_menu_multiple(self, mock_question): self.assertEqual(str(vault.netvm), 'sys-net') mock_question.reset_mock() - @unittest.mock.patch('PyQt5.QtWidgets.QMessageBox.question') + @unittest.mock.patch('PyQt6.QtWidgets.QMessageBox.question') @listen_for_events @skip_if_running('work') def test_250_template_menu_single(self, mock_question): - mock_question.return_value = QtWidgets.QMessageBox.Yes + mock_question.return_value = QtWidgets.QMessageBox.StandardButton.Yes target_vm_name = 'work' selected_vm = self.qapp.domains[target_vm_name] current_template = selected_vm.template @@ -876,11 +881,11 @@ def test_250_template_menu_single(self, mock_question): self.assertEqual(str(selected_vm.template), str(new_template)) mock_question.reset_mock() - @unittest.mock.patch('PyQt5.QtWidgets.QMessageBox.question') + @unittest.mock.patch('PyQt6.QtWidgets.QMessageBox.question') @listen_for_events @skip_if_running('work', 'personal', 'untrusted') def test_251_template_menu_multiple(self, mock_question): - mock_question.return_value = QtWidgets.QMessageBox.Yes + mock_question.return_value = QtWidgets.QMessageBox.StandardButton.Yes target_vm_names = ['work', 'personal', 'untrusted'] work = self.qapp.domains['work'] personal = self.qapp.domains['personal'] @@ -946,8 +951,8 @@ def test_251_template_menu_multiple(self, mock_question): self.assertEqual(str(untrusted.template), str(new_template)) - @unittest.mock.patch('PyQt5.QtWidgets.QMessageBox.information') - @unittest.mock.patch('PyQt5.QtWidgets.QMessageBox.warning') + @unittest.mock.patch('PyQt6.QtWidgets.QMessageBox.information') + @unittest.mock.patch('PyQt6.QtWidgets.QMessageBox.warning') def test_300_clear_threads(self, mock_warning, mock_info): mock_thread_finished_ok = unittest.mock.Mock( spec=['isFinished', 'msg', 'msg_is_success'], @@ -1103,14 +1108,16 @@ def test_405_prop_change_label(self): target_vm_name = "work" vm_row = self._find_vm_row(target_vm_name) - current_label = self._get_table_item(vm_row, "Label", Qt.DecorationRole) + current_label = self._get_table_item(vm_row, "Label", + Qt.ItemDataRole.DecorationRole) self.addCleanup( subprocess.call, ["qvm-prefs", target_vm_name, "label", "blue"]) self._run_command_and_process_events( ["qvm-prefs", target_vm_name, "label", "red"], timeout=20) - new_label = self._get_table_item(vm_row, "Label", Qt.DecorationRole) + new_label = self._get_table_item(vm_row, "Label", + Qt.ItemDataRole.DecorationRole) self.assertNotEqual(current_label.toImage(), new_label.toImage(), "Label icon did not change") @@ -1467,7 +1474,7 @@ def _select_admin_vm(self): if template == 'AdminVM': index = self.dialog.table.model().index(row, 0) self.dialog.table.setCurrentIndex(index) - return index.data(Qt.UserRole).vm + return index.data(Qt.ItemDataRole.UserRole).vm return None def _select_non_admin_vm(self, running=None): @@ -1498,7 +1505,8 @@ def _select_templatevm(self, running=None, different_than=()): def _select_vms(self, vms: list): self.dialog.table.selectionModel().clear() - mode = QtCore.QItemSelectionModel.Select | QtCore.QItemSelectionModel.Rows + mode = (QtCore.QItemSelectionModel.SelectionFlag.Select | + QtCore.QItemSelectionModel.SelectionFlag.Rows) for row in range(self.dialog.table.model().rowCount()): vm = self._get_table_vm(row) if str(vm) in vms: @@ -1531,13 +1539,14 @@ def __check_sorting(self, column_name): def _get_table_vminfo(self, row): model = self.dialog.table.model() - return model.index(row, 0).data(Qt.UserRole) + return model.index(row, 0).data(Qt.ItemDataRole.UserRole) def _get_table_vm(self, row): model = self.dialog.table.model() - return model.index(row, 0).data(Qt.UserRole).vm + return model.index(row, 0).data(Qt.ItemDataRole.UserRole).vm - def _get_table_item(self, row, column_name, role=Qt.DisplayRole): + def _get_table_item(self, row, column_name, + role=Qt.ItemDataRole.DisplayRole): model = self.dialog.table.model() column = self.dialog.qubes_model.columns_indices.index(column_name) return model.index(row, column).data(role) @@ -1619,7 +1628,7 @@ def test_23_update_vm_thread_error(self, mock_call): class VMShutdownMonitorTest(unittest.TestCase): @unittest.mock.patch('qubesmanager.qube_manager.QMessageBox') - @unittest.mock.patch('PyQt5.QtCore.QTimer') + @unittest.mock.patch('PyQt6.QtCore.QTimer') def test_01_vm_shutdown_correct(self, mock_timer, mock_question): mock_vm = unittest.mock.Mock() mock_vm.is_running.return_value = False @@ -1634,7 +1643,7 @@ def test_01_vm_shutdown_correct(self, mock_timer, mock_question): monitor.restart_vm_if_needed.assert_called_once_with() @unittest.mock.patch('qubesmanager.qube_manager.QMessageBox') - @unittest.mock.patch('PyQt5.QtCore.QTimer.singleShot') + @unittest.mock.patch('PyQt6.QtCore.QTimer.singleShot') def test_02_vm_not_shutdown_wait(self, mock_timer, mock_question): mock_question().clickedButton.return_value = 1 mock_question().addButton.return_value = 0 @@ -1652,7 +1661,7 @@ def test_02_vm_not_shutdown_wait(self, mock_timer, mock_question): self.assertEqual(mock_timer.call_count, 1) @unittest.mock.patch('qubesmanager.qube_manager.QMessageBox') - @unittest.mock.patch('PyQt5.QtCore.QTimer.singleShot') + @unittest.mock.patch('PyQt6.QtCore.QTimer.singleShot') def test_03_vm_kill(self, mock_timer, mock_question): mock_question().clickedButton.return_value = 1 mock_question().addButton.return_value = 1 @@ -1673,7 +1682,7 @@ def test_03_vm_kill(self, mock_timer, mock_question): monitor.restart_vm_if_needed.assert_called_once_with() @unittest.mock.patch('qubesmanager.qube_manager.QMessageBox') - @unittest.mock.patch('PyQt5.QtCore.QTimer.singleShot') + @unittest.mock.patch('PyQt6.QtCore.QTimer.singleShot') def test_04_check_later(self, mock_timer, mock_question): mock_vm = unittest.mock.Mock() mock_vm.is_running.return_value = True diff --git a/qubesmanager/tests/test_vm_settings.py b/qubesmanager/tests/test_vm_settings.py index 35b20282..193c3390 100644 --- a/qubesmanager/tests/test_vm_settings.py +++ b/qubesmanager/tests/test_vm_settings.py @@ -23,7 +23,7 @@ import unittest import unittest.mock -from PyQt5 import QtTest, QtCore +from PyQt6 import QtTest, QtCore from qubesadmin import Qubes import qubesmanager.settings as vm_settings from qubesmanager.tests import init_qtapp @@ -35,7 +35,7 @@ def setUp(self): self.qtapp, self.loop = init_qtapp() self.mock_qprogress = unittest.mock.patch( - 'PyQt5.QtWidgets.QProgressDialog') + 'PyQt6.QtWidgets.QProgressDialog') self.mock_qprogress.start() self.addCleanup(self.mock_qprogress.stop) @@ -295,8 +295,8 @@ def test_10_increase_private_storage(self): # TODO are dependencies correctly processed - @unittest.mock.patch('PyQt5.QtWidgets.QProgressDialog') - @unittest.mock.patch('PyQt5.QtWidgets.QInputDialog.getText') + @unittest.mock.patch('PyQt6.QtWidgets.QProgressDialog') + @unittest.mock.patch('PyQt6.QtWidgets.QInputDialog.getText') @unittest.mock.patch('qubesmanager.settings.RenameVMThread') def test_11_rename_vm(self, mock_thread, mock_input, _): self.vm = self.qapp.add_new_vm("AppVM", "test-vm", "blue") @@ -325,9 +325,9 @@ def test_12_clone_vm(self, mock_clone): src_vm=self.vm) - @unittest.mock.patch('PyQt5.QtWidgets.QMessageBox.warning') - @unittest.mock.patch('PyQt5.QtWidgets.QProgressDialog') - @unittest.mock.patch('PyQt5.QtWidgets.QInputDialog.getText') + @unittest.mock.patch('PyQt6.QtWidgets.QMessageBox.warning') + @unittest.mock.patch('PyQt6.QtWidgets.QProgressDialog') + @unittest.mock.patch('PyQt6.QtWidgets.QInputDialog.getText') @unittest.mock.patch('qubesmanager.common_threads.RemoveVMThread') def test_13_remove_vm(self, mock_thread, mock_input, _, mock_warning): self.vm = self.qapp.add_new_vm("AppVM", "test-vm", "blue") @@ -522,7 +522,7 @@ def test_27_boot_cdrom(self, mock_qvm_start, mock_bootwindow): mock_bootwindow.return_value.cdrom_location = 'CDROM_LOCATION' self.dialog.boot_from_device_button.click() - mock_bootwindow.return_value.exec_.assert_called_once_with() + mock_bootwindow.return_value.exec.assert_called_once_with() mock_qvm_start.main.assert_called_once_with( ['--cdrom', 'CDROM_LOCATION', 'test-vm']) @@ -554,15 +554,15 @@ def test_28_advanced_debug_true(self): def _click_ok(self): okwidget = self.dialog.buttonBox.button( - self.dialog.buttonBox.Ok) + self.dialog.buttonBox.StandardButton.Ok) - QtTest.QTest.mouseClick(okwidget, QtCore.Qt.LeftButton) + QtTest.QTest.mouseClick(okwidget, QtCore.Qt.MouseButton.LeftButton) def _click_cancel(self): cancelwidget = self.dialog.buttonBox.button( self.dialog.buttonBox.Cancel) - QtTest.QTest.mouseClick(cancelwidget, QtCore.Qt.LeftButton) + QtTest.QTest.mouseClick(cancelwidget, QtCore.Qt.MouseButton.LeftButton) def _set_noncurrent(self, widget): if widget.count() < 2: diff --git a/qubesmanager/utils.py b/qubesmanager/utils.py index 45621017..6d6c5e18 100644 --- a/qubesmanager/utils.py +++ b/qubesmanager/utils.py @@ -36,7 +36,7 @@ import pathlib import shutil -from PyQt5 import QtWidgets, QtCore, QtGui # pylint: disable=import-error +from PyQt6 import QtWidgets, QtCore, QtGui # pylint: disable=import-error # important usage note: which initialize_widget should I use? @@ -103,8 +103,8 @@ def __init__(self, *args, **kwargs): self.pattern = r'(\d+\.?\d?) ?(GiB|MiB)' self.regex = re.compile(self.pattern) - self.validator = QtGui.QRegExpValidator(QtCore.QRegExp( - self.pattern), self) + self.validator = QtGui.QRegularExpressionValidator( + QtCore.QRegularExpression(self.pattern), self) def textFromValue(self, v: int) -> str: if v > 1024: @@ -131,7 +131,7 @@ class QubeManagerToolBar(QtWidgets.QToolBar): # pylint: disable=too-few-public-m def __init__(self, parent=None): super().__init__(parent) def event(self, e): - if e.type() == QtCore.QEvent.Leave: + if e.type() == QtCore.QEvent.Type.Leave: return True return super().event(e) @@ -516,7 +516,7 @@ def handle_exception(exc_type, exc_value, exc_traceback): msg_box = QtWidgets.QMessageBox() msg_box.setDetailedText(strace) - msg_box.setIcon(QtWidgets.QMessageBox.Critical) + msg_box.setIcon(QtWidgets.QMessageBox.Icon.Critical) msg_box.setWindowTitle(QtCore.QCoreApplication.translate( "ManagerUtils", "Houston, we have a problem...")) msg_box.setText(QtCore.QCoreApplication.translate( @@ -525,7 +525,7 @@ def handle_exception(exc_type, exc_value, exc_traceback): "{0}
at line {1}
of file " "{2}.

").format(error, line, filename)) - msg_box.exec_() + msg_box.exec() def run_asynchronous(window_class): @@ -591,7 +591,7 @@ def run_synchronous(window_class): window.show() - qt_app.exec_() + qt_app.exec() qt_app.exit() return window diff --git a/rpm_spec/qmgr.spec.in b/rpm_spec/qmgr.spec.in index 3bf80a04..4ddfbcbd 100644 --- a/rpm_spec/qmgr.spec.in +++ b/rpm_spec/qmgr.spec.in @@ -8,11 +8,7 @@ Vendor: Invisible Things Lab License: GPL URL: https://www.qubes-os.org Requires: python%{python3_pkgversion} -%if 0%{?fedora} >= 34 -Requires: python%{python3_pkgversion}-qt5 -%else -Requires: python%{python3_pkgversion}-PyQt5 -%endif +Requires: python%{python3_pkgversion}-pyqt6 Requires: python%{python3_pkgversion}-inotify Requires: python%{python3_pkgversion}-qubesadmin >= 4.3.0 Requires: python%{python3_pkgversion}-qasync @@ -21,16 +17,16 @@ Requires: qubes-artwork Requires: pmount Requires: cryptsetup Requires: wmctrl -BuildRequires: python%{python3_pkgversion}-PyQt5-devel +BuildRequires: python%{python3_pkgversion}-PyQt6-devel BuildRequires: python%{python3_pkgversion}-devel BuildRequires: python%{python3_pkgversion}-setuptools BuildRequires: make %if 0%{?fedora} >= 33 || 0%{?rhel} >= 7 -BuildRequires: qt5-qtbase-devel +BuildRequires: qt6-qtbase-devel %else -BuildRequires: qt5-devel +BuildRequires: qt6-devel %endif -BuildRequires: qt5-linguist +BuildRequires: qt6-linguist AutoReq: 0 Source0: %{name}-%{version}.tar.gz @@ -97,7 +93,7 @@ rm -rf $RPM_BUILD_ROOT %{python3_sitelib}/qubesmanager/template_manager.py %{python3_sitelib}/qubesmanager/qvm_template_gui.py -%{python3_sitelib}/qubesmanager/resources_rc.py +%{python3_sitelib}/qubesmanager/resources.py %{python3_sitelib}/qubesmanager/ui_backupdlg.py %{python3_sitelib}/qubesmanager/ui_bootfromdevice.py diff --git a/ui/backupdlg.ui b/ui/backupdlg.ui index d8d737fc..4b8402e8 100644 --- a/ui/backupdlg.ui +++ b/ui/backupdlg.ui @@ -378,11 +378,14 @@ - -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p></body></html> +hr { height: 1px; border-width: 0; } +li.unchecked::marker { content: "\2610"; } +li.checked::marker { content: "\2612"; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> diff --git a/ui/clonevmdlg.ui b/ui/clonevmdlg.ui index 6a9d64b3..38266b4c 100644 --- a/ui/clonevmdlg.ui +++ b/ui/clonevmdlg.ui @@ -14,8 +14,7 @@ Clone qube - - .. + diff --git a/ui/newappvmdlg.ui b/ui/newappvmdlg.ui index fc2c8b6f..6b2cd679 100644 --- a/ui/newappvmdlg.ui +++ b/ui/newappvmdlg.ui @@ -14,8 +14,7 @@ Create new qube - - .. + diff --git a/ui/qubemanager.ui b/ui/qubemanager.ui index 9585b0e9..6abed076 100644 --- a/ui/qubemanager.ui +++ b/ui/qubemanager.ui @@ -23,8 +23,7 @@ Qube Manager - - .. + @@ -390,7 +389,7 @@ template Network - + :/netvm.png:/netvm.png @@ -806,8 +805,7 @@ template - - .. + &Qubes OS @@ -994,7 +992,7 @@ template - + :/log.png:/log.png diff --git a/ui/qvmtemplate.ui b/ui/qvmtemplate.ui index 79554482..cc48c59d 100644 --- a/ui/qvmtemplate.ui +++ b/ui/qvmtemplate.ui @@ -92,8 +92,7 @@ - - .. + Refresh @@ -101,8 +100,7 @@ - - .. + Apply diff --git a/ui/restoredlg.ui b/ui/restoredlg.ui index bba923d7..03dcf3c6 100644 --- a/ui/restoredlg.ui +++ b/ui/restoredlg.ui @@ -225,11 +225,14 @@ - -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p></body></html> +hr { height: 1px; border-width: 0; } +li.unchecked::marker { content: "\2610"; } +li.checked::marker { content: "\2612"; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html>