diff --git a/dangerzone/gui/__init__.py b/dangerzone/gui/__init__.py index 664fe1c..0f51759 100644 --- a/dangerzone/gui/__init__.py +++ b/dangerzone/gui/__init__.py @@ -5,7 +5,7 @@ import platform import signal import sys import typing -from typing import Dict, List, Optional +from typing import List, Optional import click import colorama diff --git a/dangerzone/gui/main_window.py b/dangerzone/gui/main_window.py index 4c739d0..27ca0bc 100644 --- a/dangerzone/gui/main_window.py +++ b/dangerzone/gui/main_window.py @@ -10,14 +10,18 @@ from typing import List, Optional # FIXME: See https://github.com/freedomofpress/dangerzone/issues/320 for more details. if typing.TYPE_CHECKING: from PySide2 import QtCore, QtGui, QtSvg, QtWidgets - from PySide2.QtWidgets import QAction + from PySide2.QtCore import Qt + from PySide2.QtWidgets import QAction, QTextEdit else: try: from PySide6 import QtCore, QtGui, QtSvg, QtWidgets + from PySide6.QtCore import Qt from PySide6.QtGui import QAction + from PySide6.QtWidgets import QTextEdit except ImportError: from PySide2 import QtCore, QtGui, QtSvg, QtWidgets - from PySide2.QtWidgets import QAction + from PySide2.QtWidgets import QAction, QTextEdit + from PySide2.QtCore import Qt from .. import errors from ..document import SAFE_EXTENSION, Document @@ -434,10 +438,29 @@ class WaitingWidgetContainer(WaitingWidget): self.buttons = QtWidgets.QWidget() self.buttons.setLayout(buttons_layout) + # Error + self.error_text = QTextEdit() + self.error_text.setReadOnly(True) + self.error_text.setStyleSheet( + """ + QTextEdit { + font-family: Consolas, Monospace; + font-size: 12px; + background-color: #fff; + color: #000; + padding: 10px; + } + """ + ) + self.error_text.setVisible(False) + # Enable copying + self.error_text.setTextInteractionFlags(Qt.TextSelectableByMouse) + # Layout layout = QtWidgets.QVBoxLayout() layout.addStretch() layout.addWidget(self.label) + layout.addWidget(self.error_text) layout.addStretch() layout.addWidget(self.buttons) layout.addStretch() @@ -448,49 +471,84 @@ class WaitingWidgetContainer(WaitingWidget): def check_state(self) -> None: state: Optional[str] = None + error: Optional[str] = None try: if isinstance( # Sanity check self.dangerzone.isolation_provider, Container ): container_runtime = self.dangerzone.isolation_provider.get_runtime() + runtime_name = self.dangerzone.isolation_provider.get_runtime_name() except NoContainerTechException as e: log.error(str(e)) state = "not_installed" else: - # Can we run `docker image ls` without an error + # Can we run `docker/podman image ls` without an error with subprocess.Popen( [container_runtime, "image", "ls"], stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, + stderr=subprocess.PIPE, startupinfo=get_subprocess_startupinfo(), ) as p: - p.communicate() + _, stderr = p.communicate() if p.returncode != 0: - log.error("Docker is not running") + log.error(f"{runtime_name} is not running") state = "not_running" + error = stderr.decode() else: # Always try installing the container state = "install_container" # Update the state - self.state_change(state) + self.state_change(state, error) - def state_change(self, state: str) -> None: + def state_change(self, state: str, error: str | None = None) -> None: if state == "not_installed": - self.label.setText( - "Dangerzone Requires Docker Desktop

Download Docker Desktop, install it, and open it." - ) + if platform.system() == "Linux": + self.label.setText( + ( + "Dangerzone requires Podman

" + "Install it and retry." + ) + ) + else: + self.label.setText( + ( + "Dangerzone requires Docker Desktop

" + "Download Docker Desktop" + ", install it, and open it." + ) + ) self.buttons.show() elif state == "not_running": - self.label.setText( - "Dangerzone Requires Docker Desktop

Docker is installed but isn't running.

Open Docker and make sure it's running in the background." - ) + if platform.system() == "Linux": + # "not_running" here means that the `podman image ls` command failed. + message = ( + "Dangerzone requires Podman

" + "Podman is installed but cannot run properly. See errors below" + ) + if error: + self.error_text.setPlainText(error) + self.error_text.setVisible(True) + + self.label.setText(message) + + else: + self.label.setText( + ( + "Dangerzone requires Docker Desktop

" + "Docker is installed but isn't running.

" + "Open Docker and make sure it's running in the background." + ) + ) self.buttons.show() else: self.label.setText( - "Installing the Dangerzone container image.

This might take a few minutes..." + ( + "Installing the Dangerzone container image.

" + "This might take a few minutes..." + ) ) self.buttons.hide() self.install_container_t = InstallContainerThread(self.dangerzone) diff --git a/dangerzone/isolation_provider/container.py b/dangerzone/isolation_provider/container.py index 76c4aa0..7137d57 100644 --- a/dangerzone/isolation_provider/container.py +++ b/dangerzone/isolation_provider/container.py @@ -242,7 +242,7 @@ class Container(IsolationProvider): # `int`. # # See https://stackoverflow.com/a/37888668 - if not type(val) == _type: + if type(val) is not _type: raise ValueError("Status field has incorrect type") def parse_progress_trusted(self, document: Document, line: str) -> None: