Skip to content

Commit

Permalink
Add support for PyQt5
Browse files Browse the repository at this point in the history
  • Loading branch information
eloquence committed Sep 4, 2020
1 parent 215108f commit 8567d8b
Show file tree
Hide file tree
Showing 7 changed files with 226 additions and 6 deletions.
22 changes: 22 additions & 0 deletions launcher/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
The preflight updater GUI currently supports both PyQt4 and PyQt5. To
enforce the use of PyQt5, set the environment variable SDW_UPDATER_QT to 5.

## Why support PyQt4 and PyQt5?

Qubes 4.0.3 uses an end-of-life Fedora template in dom0 (fedora-25). See
rationale here:

https://www.qubes-os.org/doc/supported-versions/#note-on-dom0-and-eol

fedora-25 only includes PyQt4, which is why we have to support it for now.
PyQt4 itself is no longer maintained, and is best installed through system
packages, e.g., https://packages.debian.org/buster/python3-pyqt4

The next version of Qubes, Qubes 4.1, will include PyQt5 in dom0.

## Installing PyQt5

Activate a virtualenv and intall the requirements using the following
command:

pip install --require-hashes -r qt5-requirements.txt
1 change: 1 addition & 0 deletions launcher/qt5-requirements.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PyQt5==5.11.3
30 changes: 30 additions & 0 deletions launcher/qt5-requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#
# This file is autogenerated by pip-compile
# To update, run:
#
# pip-compile --allow-unsafe --generate-hashes --output-file=qt5-requirements.txt qt5-requirements.in
#
pyqt5-sip==4.19.19 \
--hash=sha256:304acf771b6033cb4bafc415939d227c91265d30664ed643b298d7e95f509f81 \
--hash=sha256:39d2677f4de46ed4d7aa3b612f31c74c881975efe51c6a23fbb1d9382e4cc850 \
--hash=sha256:54b99a3057e8f01b90d49cca9ca566b1ea23d8920038760f44e75b90c62b9d5f \
--hash=sha256:59f5332f86f3ccd3ac94674fe91eae6e8aca26da7c6588917cabd0fe22af106d \
--hash=sha256:72be07a21b0f379987c4ec59bc86834a9719a2f9cfb49606a4d4e34dae9aa549 \
--hash=sha256:7b3b8c015e545fa30e42205fc1115b7c6afcb6acec790ce3f330a06323730523 \
--hash=sha256:7fbb6389c20aff4c3257e89bb1787effffcaf05c32d937c00094ae45846bffd5 \
--hash=sha256:828d9911acc483672a2bae1cc1bf79f591eb3338faad1f2c798aa2f45459a318 \
--hash=sha256:a9460dac973deccc6ff2d90f18fd105cbaada147f84e5917ed79374dcb237758 \
--hash=sha256:aade50f9a1b9d20f6aabe88e8999b10db57218f5c31950160f3f7957dd64e07c \
--hash=sha256:ac9e5b282d1f0771a8310ed974afe1961ec31e9ae787d052c0e504ea46ae323a \
--hash=sha256:ba41bd21b89c6713f7077b5f7d4a1c452989190aad5704e215560a266a1ecbab \
--hash=sha256:c309dbbd6c155e961bfd6893496afa5cd184cce6f7dffd87ea68ee048b6f97e1 \
--hash=sha256:cfc21b1f80d4655ffa776c505a2576b4d148bbc52bb3e33fedbf6cfbdbc09d1b \
--hash=sha256:d7b26e0b6d81bf14c1239e6a891ac1303a7e882512d990ec330369c7269226d7 \
--hash=sha256:f8b7a3e05235ce58a38bf317f71a5cb4ab45d3b34dc57421dd8cea48e0e4023e \
# via pyqt5
pyqt5==5.11.3 \
--hash=sha256:517e4339135c4874b799af0d484bc2e8c27b54850113a68eec40a0b56534f450 \
--hash=sha256:ac1eb5a114b6e7788e8be378be41c5e54b17d5158994504e85e43b5fca006a39 \
--hash=sha256:d2309296a5a79d0a1c0e6c387c30f0398b65523a6dcc8a19cc172e46b949e00d \
--hash=sha256:e85936bae1581bcb908847d2038e5b34237a5e6acc03130099a78930770e7ead \
# via -r qt5-requirements.in
10 changes: 8 additions & 2 deletions launcher/sdw-launcher.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
from PyQt4 import QtGui
from sdw_updater_gui.UpdaterApp import UpdaterApp
from sdw_util import Util
from sdw_updater_gui import Updater
Expand All @@ -9,6 +8,13 @@
import sys
import argparse

if Util.get_qt_version() == 5:
print("Using Qt5 (experimental)")
from PyQt5.QtWidgets import QApplication
else:
from PyQt4.QtGui import QApplication


DEFAULT_INTERVAL = 28800 # 8hr default for update interval


Expand All @@ -23,7 +29,7 @@ def launch_updater():
Start the updater GUI
"""

app = QtGui.QApplication(sys.argv)
app = QApplication(sys.argv)
form = UpdaterApp()
form.show()
sys.exit(app.exec_())
Expand Down
16 changes: 12 additions & 4 deletions launcher/sdw_updater_gui/UpdaterApp.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
from PyQt4 import QtGui
from PyQt4.QtCore import QThread, pyqtSignal, pyqtSlot
from sdw_updater_gui.UpdaterAppUi import Ui_UpdaterDialog
from sdw_updater_gui import strings
from sdw_updater_gui import Updater
from sdw_updater_gui.Updater import UpdateStatus
from sdw_util import Util
import logging
import subprocess
import sys

if Util.get_qt_version() == 5:
from PyQt5.QtWidgets import QDialog
from PyQt5.QtCore import QThread, pyqtSignal, pyqtSlot
from sdw_updater_gui.UpdaterAppUiQt5 import Ui_UpdaterDialog
else:
from PyQt4.QtGui import QDialog
from PyQt4.QtCore import QThread, pyqtSignal, pyqtSlot
from sdw_updater_gui.UpdaterAppUi import Ui_UpdaterDialog


logger = logging.getLogger(__name__)


Expand All @@ -24,7 +32,7 @@ def launch_securedrop_client():
sys.exit(0)


class UpdaterApp(QtGui.QDialog, Ui_UpdaterDialog):
class UpdaterApp(QDialog, Ui_UpdaterDialog):
def __init__(self, parent=None):
super(UpdaterApp, self).__init__(parent)

Expand Down
114 changes: 114 additions & 0 deletions launcher/sdw_updater_gui/UpdaterAppUiQt5.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'sdw_updater.ui'
#
# Created by: PyQt5 UI code generator 5.10.1
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_UpdaterDialog(object):
def setupUi(self, UpdaterDialog):
UpdaterDialog.setObjectName("UpdaterDialog")
UpdaterDialog.resize(520, 300)
sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred
)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(UpdaterDialog.sizePolicy().hasHeightForWidth())
UpdaterDialog.setSizePolicy(sizePolicy)
UpdaterDialog.setMaximumSize(QtCore.QSize(600, 420))
self.gridLayout_2 = QtWidgets.QGridLayout(UpdaterDialog)
self.gridLayout_2.setObjectName("gridLayout_2")
self.gridLayout = QtWidgets.QGridLayout()
self.gridLayout.setContentsMargins(-1, 15, -1, 15)
self.gridLayout.setHorizontalSpacing(3)
self.gridLayout.setObjectName("gridLayout")
spacerItem = QtWidgets.QSpacerItem(
20, 10, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed
)
self.gridLayout.addItem(spacerItem, 1, 1, 1, 5)
self.clientOpenButton = QtWidgets.QPushButton(UpdaterDialog)
self.clientOpenButton.setStyleSheet("")
self.clientOpenButton.setAutoDefault(True)
self.clientOpenButton.setObjectName("clientOpenButton")
self.gridLayout.addWidget(self.clientOpenButton, 7, 4, 1, 1)
self.proposedActionDescription = QtWidgets.QLabel(UpdaterDialog)
sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum
)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(
self.proposedActionDescription.sizePolicy().hasHeightForWidth()
)
self.proposedActionDescription.setSizePolicy(sizePolicy)
self.proposedActionDescription.setAlignment(
QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop
)
self.proposedActionDescription.setWordWrap(True)
self.proposedActionDescription.setObjectName("proposedActionDescription")
self.gridLayout.addWidget(self.proposedActionDescription, 4, 1, 1, 5)
spacerItem1 = QtWidgets.QSpacerItem(
40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum
)
self.gridLayout.addItem(spacerItem1, 7, 1, 1, 1)
self.rebootButton = QtWidgets.QPushButton(UpdaterDialog)
self.rebootButton.setStyleSheet("")
self.rebootButton.setAutoDefault(True)
self.rebootButton.setObjectName("rebootButton")
self.gridLayout.addWidget(self.rebootButton, 7, 3, 1, 1)
self.applyUpdatesButton = QtWidgets.QPushButton(UpdaterDialog)
self.applyUpdatesButton.setStyleSheet("")
self.applyUpdatesButton.setAutoDefault(True)
self.applyUpdatesButton.setDefault(False)
self.applyUpdatesButton.setObjectName("applyUpdatesButton")
self.gridLayout.addWidget(self.applyUpdatesButton, 7, 2, 1, 1)
self.cancelButton = QtWidgets.QPushButton(UpdaterDialog)
self.cancelButton.setStyleSheet("")
self.cancelButton.setAutoDefault(True)
self.cancelButton.setObjectName("cancelButton")
self.gridLayout.addWidget(self.cancelButton, 7, 5, 1, 1)
self.progressBar = QtWidgets.QProgressBar(UpdaterDialog)
self.progressBar.setProperty("value", 0)
self.progressBar.setObjectName("progressBar")
self.gridLayout.addWidget(self.progressBar, 2, 1, 1, 5)
self.headline = QtWidgets.QLabel(UpdaterDialog)
sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed
)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.headline.sizePolicy().hasHeightForWidth())
self.headline.setSizePolicy(sizePolicy)
font = QtGui.QFont()
font.setPointSize(18)
font.setBold(True)
font.setItalic(False)
font.setWeight(75)
self.headline.setFont(font)
self.headline.setObjectName("headline")
self.gridLayout.addWidget(self.headline, 0, 1, 1, 5)
spacerItem2 = QtWidgets.QSpacerItem(
20, 10, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed
)
self.gridLayout.addItem(spacerItem2, 3, 1, 1, 5)
self.gridLayout_2.addLayout(self.gridLayout, 0, 0, 1, 1)

self.retranslateUi(UpdaterDialog)
QtCore.QMetaObject.connectSlotsByName(UpdaterDialog)

def retranslateUi(self, UpdaterDialog):
_translate = QtCore.QCoreApplication.translate
UpdaterDialog.setWindowTitle(
_translate("UpdaterDialog", "SecureDrop Workstation preflight updater")
)
self.clientOpenButton.setText(_translate("UpdaterDialog", "Continue"))
self.proposedActionDescription.setText(_translate("UpdaterDialog", "Description goes here"))
self.rebootButton.setText(_translate("UpdaterDialog", "Reboot"))
self.applyUpdatesButton.setText(_translate("UpdaterDialog", "Start Updates"))
self.cancelButton.setText(_translate("UpdaterDialog", "Cancel"))
self.headline.setText(_translate("UpdaterDialog", "Headline goes here"))
39 changes: 39 additions & 0 deletions launcher/sdw_util/Util.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,42 @@ def is_conflicting_process_running(list):
sdlog.error("Conflicting process '{}' is currently running.".format(name))
return True
return False


def get_qubes_version():
"""
Helper function for checking the Qubes version. Returns None if not on Qubes.
"""
is_qubes = False
version = None
try:
with open("/etc/os-release") as f:
for line in f:
key, value = line.rstrip().split("=")
if key == "NAME" and "qubes" in value.lower():
is_qubes = True
if key == "VERSION":
version = value
except FileNotFoundError:
return None

if not is_qubes:
return None

return version


def get_qt_version():
"""
Determine the version of Qt appropriate for the environment we're in.
"""
qubes_version = get_qubes_version()

# For now we must support both Qt4 and Qt5. We default to Qt4, because
# that's used in Qubes 4.0, the current stable version.
if qubes_version is not None and "4.1" in qubes_version:
default_version = 5
else:
default_version = 4

return int(os.getenv("SDW_UPDATER_QT", default_version))

0 comments on commit 8567d8b

Please sign in to comment.