Skip to content

Commit

Permalink
Merge pull request #610 from freedomofpress/607-qt5
Browse files Browse the repository at this point in the history
Add support for PyQt5
  • Loading branch information
emkll authored Sep 11, 2020
2 parents aff216b + 235ba47 commit 7d2c00e
Show file tree
Hide file tree
Showing 14 changed files with 372 additions and 11 deletions.
2 changes: 1 addition & 1 deletion launcher/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.PHONY: update-pip-requirements
update-pip-requirements: ## Updates all Python requirements files via pip-compile.
pip-compile --allow-unsafe --generate-hashes --output-file=test-requirements.txt test-requirements.in
pip-compile --allow-unsafe --generate-hashes --output-file=dev-requirements.txt dev-requirements.in

.PHONY: bandit
bandit:
Expand Down
24 changes: 24 additions & 0 deletions launcher/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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.
The next version of Qubes, Qubes 4.1, will include PyQt5 in dom0.

## Installing PyQt4

PyQt4 is no longer maintained, and is best installed through system
packages, e.g., https://packages.debian.org/buster/python3-pyqt4

## Installing PyQt5

The recommended version of PyQt5 is included in the developer requirements
for this project, which you can install via:

pip install --require-hashes -r dev-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ bandit
black
pip-tools
pip
PyQt5==5.11.3
pytest
pytest-cov
26 changes: 25 additions & 1 deletion launcher/test-requirements.txt → launcher/dev-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# This file is autogenerated by pip-compile
# To update, run:
#
# pip-compile --allow-unsafe --generate-hashes --output-file=test-requirements.txt test-requirements.in
# pip-compile --allow-unsafe --generate-hashes --output-file=dev-requirements.txt dev-requirements.in
#
appdirs==1.4.3 \
--hash=sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92 \
Expand Down Expand Up @@ -188,6 +188,30 @@ zipp==0.6.0 \
--hash=sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335 \
# via importlib-metadata

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 \

# The following packages are considered to be unsafe in a requirements file:
pip==19.3.1 \
--hash=sha256:21207d76c1031e517668898a6b46a9fb1501c7a4710ef5dfd6a40ad9e6757ea7 \
Expand Down
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
10 changes: 7 additions & 3 deletions launcher/sdw-notify.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@
from sdw_notify import Notify
from sdw_updater_gui import Updater
from sdw_util import Util
from PyQt4 import QtGui
from PyQt4.QtGui import QMessageBox

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


def main():
Expand Down Expand Up @@ -50,7 +54,7 @@ def show_update_warning():
Show a graphical warning reminding the user to check for security updates
using the preflight updater.
"""
app = QtGui.QApplication([]) # noqa: F841
app = QApplication([]) # noqa: F841

QMessageBox.warning(
None,
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"))
55 changes: 55 additions & 0 deletions launcher/sdw_util/Util.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
# Folder where logs are stored
LOG_DIRECTORY = os.path.join(BASE_DIRECTORY, "logs")

# File that contains Qubes version information (overridden by tests)
OS_RELEASE_FILE = "/etc/os-release"

# Shared error string
LOCK_ERROR = "Error obtaining lock on '{}'. Process may already be running."

Expand Down Expand Up @@ -108,3 +111,55 @@ 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(OS_RELEASE_FILE) as f:
for line in f:
try:
key, value = line.rstrip().split("=")
except ValueError:
continue

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

version_str = os.getenv("SDW_UPDATER_QT", default_version)
try:
version = int(version_str)
except ValueError:
version = None

if version in [4, 5]:
return version
else:
raise ValueError("Qt version not supported: {}".format(version_str))
4 changes: 4 additions & 0 deletions launcher/tests/fixtures/bad-os-release-file
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# No line
VERSION=
[we're doing toml now]
RELEASES = [ ["gamma", "delta"], [1, 2] ]
7 changes: 7 additions & 0 deletions launcher/tests/fixtures/os-release-qubes-4.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
NAME=Qubes
VERSION="4.0 (R4.0)"
ID=qubes
VERSION_ID=4.0
PRETTY_NAME="Qubes 4.0 (R4.0)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:ITL:qubes:4.0"
7 changes: 7 additions & 0 deletions launcher/tests/fixtures/os-release-qubes-4.1
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
NAME=Qubes
VERSION="4.1 (R4.1)"
ID=qubes
VERSION_ID=4.0
PRETTY_NAME="Qubes 4.1 (R4.1)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:ITL:qubes:4.1"
12 changes: 12 additions & 0 deletions launcher/tests/fixtures/os-release-ubuntu
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
NAME="Ubuntu"
VERSION="18.04.5 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.5 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic
Loading

0 comments on commit 7d2c00e

Please sign in to comment.