mirror of
https://github.com/freedomofpress/dangerzone.git
synced 2025-04-28 09:52:37 +02:00
Refactor dangerzone to move GUI code into its own module
This commit is contained in:
parent
12b3bde03d
commit
6ff68f88ea
13 changed files with 350 additions and 321 deletions
|
@ -3,11 +3,14 @@ import sys
|
||||||
|
|
||||||
dangerzone_version = "0.1.5"
|
dangerzone_version = "0.1.5"
|
||||||
|
|
||||||
# This is a hack for Windows and Mac to be able to run dangerzone-container, even though
|
# Depending on the filename, decide if we want to run:
|
||||||
# PyInstaller builds a single binary
|
# dangerzone, dangerzone-cli, or dangerzone-container
|
||||||
|
|
||||||
basename = os.path.basename(sys.argv[0])
|
basename = os.path.basename(sys.argv[0])
|
||||||
|
|
||||||
if basename == "dangerzone-container" or basename == "dangerzone-container.exe":
|
if basename == "dangerzone-container" or basename == "dangerzone-container.exe":
|
||||||
from .container import container_main as main
|
from .container import container_main as main
|
||||||
|
elif basename == "dangerzone-cli" or basename == "dangerzone-cli.exe":
|
||||||
|
from .cli import cli_main as main
|
||||||
else:
|
else:
|
||||||
# If the binary isn't "dangerzone-contatiner", then launch the GUI
|
|
||||||
from .gui import gui_main as main
|
from .gui import gui_main as main
|
||||||
|
|
8
dangerzone/cli.py
Normal file
8
dangerzone/cli.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import click
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.option("--custom-container") # Use this container instead of flmcode/dangerzone
|
||||||
|
@click.argument("filename", required=False)
|
||||||
|
def cli_main(custom_container, filename):
|
||||||
|
pass
|
|
@ -1,26 +1,13 @@
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import inspect
|
import inspect
|
||||||
import tempfile
|
|
||||||
import appdirs
|
import appdirs
|
||||||
import platform
|
import platform
|
||||||
import subprocess
|
import subprocess
|
||||||
import shlex
|
|
||||||
import pipes
|
import pipes
|
||||||
from PySide2 import QtCore, QtGui, QtWidgets
|
from PySide2 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
if platform.system() == "Darwin":
|
|
||||||
import CoreServices
|
|
||||||
import LaunchServices
|
|
||||||
import plistlib
|
|
||||||
|
|
||||||
elif platform.system() == "Linux":
|
|
||||||
import grp
|
|
||||||
import getpass
|
|
||||||
from xdg.DesktopEntry import DesktopEntry
|
|
||||||
|
|
||||||
from .settings import Settings
|
from .settings import Settings
|
||||||
from .docker_installer import is_docker_ready
|
|
||||||
|
|
||||||
|
|
||||||
class GlobalCommon(object):
|
class GlobalCommon(object):
|
||||||
|
@ -28,19 +15,7 @@ class GlobalCommon(object):
|
||||||
The GlobalCommon class is a singleton of shared functionality throughout the app
|
The GlobalCommon class is a singleton of shared functionality throughout the app
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, app):
|
def __init__(self):
|
||||||
# Qt app
|
|
||||||
self.app = app
|
|
||||||
|
|
||||||
# Name of input file
|
|
||||||
self.document_filename = None
|
|
||||||
|
|
||||||
# Name of output file
|
|
||||||
self.save_filename = None
|
|
||||||
|
|
||||||
# Preload font
|
|
||||||
self.fixed_font = QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.FixedFont)
|
|
||||||
|
|
||||||
# App data folder
|
# App data folder
|
||||||
self.appdata_path = appdirs.user_config_dir("dangerzone")
|
self.appdata_path = appdirs.user_config_dir("dangerzone")
|
||||||
|
|
||||||
|
@ -50,9 +25,6 @@ class GlobalCommon(object):
|
||||||
# dangerzone-container path
|
# dangerzone-container path
|
||||||
self.dz_container_path = self.get_dangerzone_container_path()
|
self.dz_container_path = self.get_dangerzone_container_path()
|
||||||
|
|
||||||
# Preload list of PDF viewers on computer
|
|
||||||
self.pdf_viewers = self._find_pdf_viewers()
|
|
||||||
|
|
||||||
# Languages supported by tesseract
|
# Languages supported by tesseract
|
||||||
self.ocr_languages = {
|
self.ocr_languages = {
|
||||||
"Afrikaans": "ar",
|
"Afrikaans": "ar",
|
||||||
|
@ -295,187 +267,6 @@ class GlobalCommon(object):
|
||||||
stderr=subprocess.PIPE,
|
stderr=subprocess.PIPE,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_window_icon(self):
|
|
||||||
if platform.system() == "Windows":
|
|
||||||
path = self.get_resource_path("dangerzone.ico")
|
|
||||||
else:
|
|
||||||
path = self.get_resource_path("icon.png")
|
|
||||||
return QtGui.QIcon(path)
|
|
||||||
|
|
||||||
def open_pdf_viewer(self, filename):
|
|
||||||
if self.settings.get("open_app") in self.pdf_viewers:
|
|
||||||
if platform.system() == "Darwin":
|
|
||||||
# Get the PDF reader bundle command
|
|
||||||
bundle_identifier = self.pdf_viewers[self.settings.get("open_app")]
|
|
||||||
args = ["open", "-b", bundle_identifier, filename]
|
|
||||||
|
|
||||||
# Run
|
|
||||||
print(f"Executing: {' '.join(args)}")
|
|
||||||
subprocess.run(args)
|
|
||||||
|
|
||||||
elif platform.system() == "Linux":
|
|
||||||
# Get the PDF reader command
|
|
||||||
args = shlex.split(self.pdf_viewers[self.settings.get("open_app")])
|
|
||||||
# %f, %F, %u, and %U are filenames or URLS -- so replace with the file to open
|
|
||||||
for i in range(len(args)):
|
|
||||||
if (
|
|
||||||
args[i] == "%f"
|
|
||||||
or args[i] == "%F"
|
|
||||||
or args[i] == "%u"
|
|
||||||
or args[i] == "%U"
|
|
||||||
):
|
|
||||||
args[i] = filename
|
|
||||||
|
|
||||||
# Open as a background process
|
|
||||||
print(f"Executing: {' '.join(args)}")
|
|
||||||
subprocess.Popen(args)
|
|
||||||
|
|
||||||
def _find_pdf_viewers(self):
|
|
||||||
pdf_viewers = {}
|
|
||||||
|
|
||||||
if platform.system() == "Darwin":
|
|
||||||
# Get all installed apps that can open PDFs
|
|
||||||
bundle_identifiers = LaunchServices.LSCopyAllRoleHandlersForContentType(
|
|
||||||
"com.adobe.pdf", CoreServices.kLSRolesAll
|
|
||||||
)
|
|
||||||
for bundle_identifier in bundle_identifiers:
|
|
||||||
# Get the filesystem path of the app
|
|
||||||
res = LaunchServices.LSCopyApplicationURLsForBundleIdentifier(
|
|
||||||
bundle_identifier, None
|
|
||||||
)
|
|
||||||
if res[0] is None:
|
|
||||||
continue
|
|
||||||
app_url = res[0][0]
|
|
||||||
app_path = str(app_url.path())
|
|
||||||
|
|
||||||
# Load its plist file
|
|
||||||
plist_path = os.path.join(app_path, "Contents/Info.plist")
|
|
||||||
|
|
||||||
# Skip if there's not an Info.plist
|
|
||||||
if not os.path.exists(plist_path):
|
|
||||||
continue
|
|
||||||
|
|
||||||
with open(plist_path, "rb") as f:
|
|
||||||
plist_data = f.read()
|
|
||||||
|
|
||||||
plist_dict = plistlib.loads(plist_data)
|
|
||||||
|
|
||||||
if (
|
|
||||||
plist_dict.get("CFBundleName")
|
|
||||||
and plist_dict["CFBundleName"] != "Dangerzone"
|
|
||||||
):
|
|
||||||
pdf_viewers[plist_dict["CFBundleName"]] = bundle_identifier
|
|
||||||
|
|
||||||
elif platform.system() == "Linux":
|
|
||||||
# Find all .desktop files
|
|
||||||
for search_path in [
|
|
||||||
"/usr/share/applications",
|
|
||||||
"/usr/local/share/applications",
|
|
||||||
os.path.expanduser("~/.local/share/applications"),
|
|
||||||
]:
|
|
||||||
try:
|
|
||||||
for filename in os.listdir(search_path):
|
|
||||||
full_filename = os.path.join(search_path, filename)
|
|
||||||
if os.path.splitext(filename)[1] == ".desktop":
|
|
||||||
|
|
||||||
# See which ones can open PDFs
|
|
||||||
desktop_entry = DesktopEntry(full_filename)
|
|
||||||
if (
|
|
||||||
"application/pdf" in desktop_entry.getMimeTypes()
|
|
||||||
and desktop_entry.getName() != "dangerzone"
|
|
||||||
):
|
|
||||||
pdf_viewers[
|
|
||||||
desktop_entry.getName()
|
|
||||||
] = desktop_entry.getExec()
|
|
||||||
|
|
||||||
except FileNotFoundError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return pdf_viewers
|
|
||||||
|
|
||||||
def ensure_docker_group_preference(self):
|
|
||||||
# If the user prefers typing their password
|
|
||||||
if self.settings.get("linux_prefers_typing_password") == True:
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Get the docker group
|
|
||||||
try:
|
|
||||||
groupinfo = grp.getgrnam("docker")
|
|
||||||
except:
|
|
||||||
# Ignore if group is not found
|
|
||||||
return True
|
|
||||||
|
|
||||||
# See if the user is in the group
|
|
||||||
username = getpass.getuser()
|
|
||||||
if username not in groupinfo.gr_mem:
|
|
||||||
# User is not in the docker group, ask if they prefer typing their password
|
|
||||||
message = "<b>Dangerzone requires Docker</b><br><br>In order to use Docker, your user must be in the 'docker' group or you'll need to type your password each time you run dangerzone.<br><br><b>Adding your user to the 'docker' group is more convenient but less secure</b>, and will require just typing your password once. Which do you prefer?"
|
|
||||||
return_code = Alert(
|
|
||||||
self,
|
|
||||||
message,
|
|
||||||
ok_text="I'll type my password each time",
|
|
||||||
extra_button_text="Add my user to the 'docker' group",
|
|
||||||
).launch()
|
|
||||||
if return_code == QtWidgets.QDialog.Accepted:
|
|
||||||
# Prefers typing password
|
|
||||||
self.settings.set("linux_prefers_typing_password", True)
|
|
||||||
self.settings.save()
|
|
||||||
return True
|
|
||||||
elif return_code == 2:
|
|
||||||
# Prefers being in the docker group
|
|
||||||
self.settings.set("linux_prefers_typing_password", False)
|
|
||||||
self.settings.save()
|
|
||||||
|
|
||||||
# Add user to the docker group
|
|
||||||
p = subprocess.run(
|
|
||||||
[
|
|
||||||
"/usr/bin/pkexec",
|
|
||||||
"/usr/sbin/usermod",
|
|
||||||
"-a",
|
|
||||||
"-G",
|
|
||||||
"docker",
|
|
||||||
username,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
if p.returncode == 0:
|
|
||||||
message = "Great! Now you must log out of your computer and log back in, and then you can use Dangerzone."
|
|
||||||
Alert(self, message).launch()
|
|
||||||
else:
|
|
||||||
message = "Failed to add your user to the 'docker' group, quitting."
|
|
||||||
Alert(self, message).launch()
|
|
||||||
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
# Cancel
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def ensure_docker_service_is_started(self):
|
|
||||||
if not is_docker_ready(self):
|
|
||||||
message = "<b>Dangerzone requires Docker</b><br><br>Docker should be installed, but it looks like it's not running in the background.<br><br>Click Ok to try starting the docker service. You will have to type your login password."
|
|
||||||
if Alert(self, message).launch() == QtWidgets.QDialog.Accepted:
|
|
||||||
p = subprocess.run(
|
|
||||||
[
|
|
||||||
"/usr/bin/pkexec",
|
|
||||||
self.get_resource_path("enable_docker_service.sh"),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
if p.returncode == 0:
|
|
||||||
# Make sure docker is now ready
|
|
||||||
if is_docker_ready(self):
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
message = "Restarting docker appeared to work, but the service still isn't responding, quitting."
|
|
||||||
Alert(self, message).launch()
|
|
||||||
else:
|
|
||||||
message = "Failed to start the docker service, quitting."
|
|
||||||
Alert(self, message).launch()
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get_subprocess_startupinfo(self):
|
def get_subprocess_startupinfo(self):
|
||||||
if platform.system() == "Windows":
|
if platform.system() == "Windows":
|
||||||
startupinfo = subprocess.STARTUPINFO()
|
startupinfo = subprocess.STARTUPINFO()
|
||||||
|
@ -483,71 +274,3 @@ class GlobalCommon(object):
|
||||||
return startupinfo
|
return startupinfo
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class Alert(QtWidgets.QDialog):
|
|
||||||
def __init__(self, common, message, ok_text="Ok", extra_button_text=None):
|
|
||||||
super(Alert, self).__init__()
|
|
||||||
self.common = common
|
|
||||||
|
|
||||||
self.setWindowTitle("dangerzone")
|
|
||||||
self.setWindowIcon(self.common.get_window_icon())
|
|
||||||
self.setModal(True)
|
|
||||||
|
|
||||||
flags = (
|
|
||||||
QtCore.Qt.CustomizeWindowHint
|
|
||||||
| QtCore.Qt.WindowTitleHint
|
|
||||||
| QtCore.Qt.WindowSystemMenuHint
|
|
||||||
| QtCore.Qt.WindowCloseButtonHint
|
|
||||||
| QtCore.Qt.WindowStaysOnTopHint
|
|
||||||
)
|
|
||||||
self.setWindowFlags(flags)
|
|
||||||
|
|
||||||
logo = QtWidgets.QLabel()
|
|
||||||
logo.setPixmap(
|
|
||||||
QtGui.QPixmap.fromImage(
|
|
||||||
QtGui.QImage(self.common.get_resource_path("icon.png"))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
label = QtWidgets.QLabel()
|
|
||||||
label.setText(message)
|
|
||||||
label.setWordWrap(True)
|
|
||||||
|
|
||||||
message_layout = QtWidgets.QHBoxLayout()
|
|
||||||
message_layout.addWidget(logo)
|
|
||||||
message_layout.addSpacing(10)
|
|
||||||
message_layout.addWidget(label, stretch=1)
|
|
||||||
|
|
||||||
ok_button = QtWidgets.QPushButton(ok_text)
|
|
||||||
ok_button.clicked.connect(self.clicked_ok)
|
|
||||||
if extra_button_text:
|
|
||||||
extra_button = QtWidgets.QPushButton(extra_button_text)
|
|
||||||
extra_button.clicked.connect(self.clicked_extra)
|
|
||||||
cancel_button = QtWidgets.QPushButton("Cancel")
|
|
||||||
cancel_button.clicked.connect(self.clicked_cancel)
|
|
||||||
|
|
||||||
buttons_layout = QtWidgets.QHBoxLayout()
|
|
||||||
buttons_layout.addStretch()
|
|
||||||
buttons_layout.addWidget(ok_button)
|
|
||||||
if extra_button_text:
|
|
||||||
buttons_layout.addWidget(extra_button)
|
|
||||||
buttons_layout.addWidget(cancel_button)
|
|
||||||
|
|
||||||
layout = QtWidgets.QVBoxLayout()
|
|
||||||
layout.addLayout(message_layout)
|
|
||||||
layout.addSpacing(10)
|
|
||||||
layout.addLayout(buttons_layout)
|
|
||||||
self.setLayout(layout)
|
|
||||||
|
|
||||||
def clicked_ok(self):
|
|
||||||
self.done(QtWidgets.QDialog.Accepted)
|
|
||||||
|
|
||||||
def clicked_extra(self):
|
|
||||||
self.done(2)
|
|
||||||
|
|
||||||
def clicked_cancel(self):
|
|
||||||
self.done(QtWidgets.QDialog.Rejected)
|
|
||||||
|
|
||||||
def launch(self):
|
|
||||||
return self.exec_()
|
|
||||||
|
|
|
@ -3,21 +3,19 @@ import sys
|
||||||
import signal
|
import signal
|
||||||
import platform
|
import platform
|
||||||
import click
|
import click
|
||||||
import time
|
|
||||||
import uuid
|
import uuid
|
||||||
import subprocess
|
|
||||||
from PySide2 import QtCore, QtWidgets
|
from PySide2 import QtCore, QtWidgets
|
||||||
|
|
||||||
from .global_common import GlobalCommon
|
from .common import GuiCommon
|
||||||
from .main_window import MainWindow
|
from .main_window import MainWindow
|
||||||
from .docker_installer import (
|
from .docker_installer import (
|
||||||
is_docker_installed,
|
is_docker_installed,
|
||||||
is_docker_ready,
|
is_docker_ready,
|
||||||
launch_docker_windows,
|
|
||||||
DockerInstaller,
|
DockerInstaller,
|
||||||
AuthorizationFailed,
|
AuthorizationFailed,
|
||||||
)
|
)
|
||||||
from .container import container_runtime
|
from ..global_common import GlobalCommon
|
||||||
|
from ..container import container_runtime
|
||||||
|
|
||||||
|
|
||||||
# For some reason, Dangerzone segfaults if I inherit from QApplication directly, so instead
|
# For some reason, Dangerzone segfaults if I inherit from QApplication directly, so instead
|
||||||
|
@ -59,8 +57,9 @@ def gui_main(custom_container, filename):
|
||||||
app_wrapper = ApplicationWrapper()
|
app_wrapper = ApplicationWrapper()
|
||||||
app = app_wrapper.app
|
app = app_wrapper.app
|
||||||
|
|
||||||
# GlobalCommon object
|
# Common objects
|
||||||
global_common = GlobalCommon(app)
|
global_common = GlobalCommon()
|
||||||
|
gui_common = GuiCommon(app, global_common)
|
||||||
|
|
||||||
if custom_container:
|
if custom_container:
|
||||||
# Do we have this container?
|
# Do we have this container?
|
||||||
|
@ -89,10 +88,10 @@ def gui_main(custom_container, filename):
|
||||||
|
|
||||||
# If we're using Linux and docker, see if we need to add the user to the docker group or if the user prefers typing their password
|
# If we're using Linux and docker, see if we need to add the user to the docker group or if the user prefers typing their password
|
||||||
if platform.system() == "Linux":
|
if platform.system() == "Linux":
|
||||||
if not global_common.ensure_docker_group_preference():
|
if not gui_common.ensure_docker_group_preference():
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
if not global_common.ensure_docker_service_is_started():
|
if not gui_common.ensure_docker_service_is_started():
|
||||||
click.echo("Failed to start docker service")
|
click.echo("Failed to start docker service")
|
||||||
return
|
return
|
||||||
except AuthorizationFailed:
|
except AuthorizationFailed:
|
||||||
|
@ -101,10 +100,10 @@ def gui_main(custom_container, filename):
|
||||||
|
|
||||||
# See if we need to install Docker...
|
# See if we need to install Docker...
|
||||||
if (platform.system() == "Darwin" or platform.system() == "Windows") and (
|
if (platform.system() == "Darwin" or platform.system() == "Windows") and (
|
||||||
not is_docker_installed(global_common) or not is_docker_ready(global_common)
|
not is_docker_installed() or not is_docker_ready(global_common)
|
||||||
):
|
):
|
||||||
click.echo("Docker is either not installed or not running")
|
click.echo("Docker is either not installed or not running")
|
||||||
docker_installer = DockerInstaller(global_common)
|
docker_installer = DockerInstaller(gui_common)
|
||||||
docker_installer.start()
|
docker_installer.start()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -124,7 +123,7 @@ def gui_main(custom_container, filename):
|
||||||
window = windows[list(windows.keys())[0]]
|
window = windows[list(windows.keys())[0]]
|
||||||
else:
|
else:
|
||||||
window_id = uuid.uuid4().hex
|
window_id = uuid.uuid4().hex
|
||||||
window = MainWindow(global_common, window_id)
|
window = MainWindow(global_common, gui_common, window_id)
|
||||||
window.delete_window.connect(delete_window)
|
window.delete_window.connect(delete_window)
|
||||||
windows[window_id] = window
|
windows[window_id] = window
|
||||||
|
|
295
dangerzone/gui/common.py
Normal file
295
dangerzone/gui/common.py
Normal file
|
@ -0,0 +1,295 @@
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import subprocess
|
||||||
|
import shlex
|
||||||
|
from PySide2 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
|
if platform.system() == "Darwin":
|
||||||
|
import CoreServices
|
||||||
|
import LaunchServices
|
||||||
|
import plistlib
|
||||||
|
|
||||||
|
elif platform.system() == "Linux":
|
||||||
|
import grp
|
||||||
|
import getpass
|
||||||
|
from xdg.DesktopEntry import DesktopEntry
|
||||||
|
|
||||||
|
from .docker_installer import is_docker_ready
|
||||||
|
from ..settings import Settings
|
||||||
|
|
||||||
|
|
||||||
|
class GuiCommon(object):
|
||||||
|
"""
|
||||||
|
The GuiCommon class is a singleton of shared functionality for the GUI
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, app, global_common):
|
||||||
|
# Qt app
|
||||||
|
self.app = app
|
||||||
|
|
||||||
|
# Global common singleton
|
||||||
|
self.global_common = global_common
|
||||||
|
|
||||||
|
# Preload font
|
||||||
|
self.fixed_font = QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.FixedFont)
|
||||||
|
|
||||||
|
# Preload list of PDF viewers on computer
|
||||||
|
self.pdf_viewers = self._find_pdf_viewers()
|
||||||
|
|
||||||
|
def get_window_icon(self):
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
path = self.global_common.get_resource_path("dangerzone.ico")
|
||||||
|
else:
|
||||||
|
path = self.global_common.get_resource_path("icon.png")
|
||||||
|
return QtGui.QIcon(path)
|
||||||
|
|
||||||
|
def open_pdf_viewer(self, filename):
|
||||||
|
if self.global_common.settings.get("open_app") in self.pdf_viewers:
|
||||||
|
if platform.system() == "Darwin":
|
||||||
|
# Get the PDF reader bundle command
|
||||||
|
bundle_identifier = self.pdf_viewers[self.global_common.settings.get("open_app")]
|
||||||
|
args = ["open", "-b", bundle_identifier, filename]
|
||||||
|
|
||||||
|
# Run
|
||||||
|
print(f"Executing: {' '.join(args)}")
|
||||||
|
subprocess.run(args)
|
||||||
|
|
||||||
|
elif platform.system() == "Linux":
|
||||||
|
# Get the PDF reader command
|
||||||
|
args = shlex.split(self.pdf_viewers[self.global_common.settings.get("open_app")])
|
||||||
|
# %f, %F, %u, and %U are filenames or URLS -- so replace with the file to open
|
||||||
|
for i in range(len(args)):
|
||||||
|
if (
|
||||||
|
args[i] == "%f"
|
||||||
|
or args[i] == "%F"
|
||||||
|
or args[i] == "%u"
|
||||||
|
or args[i] == "%U"
|
||||||
|
):
|
||||||
|
args[i] = filename
|
||||||
|
|
||||||
|
# Open as a background process
|
||||||
|
print(f"Executing: {' '.join(args)}")
|
||||||
|
subprocess.Popen(args)
|
||||||
|
|
||||||
|
def _find_pdf_viewers(self):
|
||||||
|
pdf_viewers = {}
|
||||||
|
|
||||||
|
if platform.system() == "Darwin":
|
||||||
|
# Get all installed apps that can open PDFs
|
||||||
|
bundle_identifiers = LaunchServices.LSCopyAllRoleHandlersForContentType(
|
||||||
|
"com.adobe.pdf", CoreServices.kLSRolesAll
|
||||||
|
)
|
||||||
|
for bundle_identifier in bundle_identifiers:
|
||||||
|
# Get the filesystem path of the app
|
||||||
|
res = LaunchServices.LSCopyApplicationURLsForBundleIdentifier(
|
||||||
|
bundle_identifier, None
|
||||||
|
)
|
||||||
|
if res[0] is None:
|
||||||
|
continue
|
||||||
|
app_url = res[0][0]
|
||||||
|
app_path = str(app_url.path())
|
||||||
|
|
||||||
|
# Load its plist file
|
||||||
|
plist_path = os.path.join(app_path, "Contents/Info.plist")
|
||||||
|
|
||||||
|
# Skip if there's not an Info.plist
|
||||||
|
if not os.path.exists(plist_path):
|
||||||
|
continue
|
||||||
|
|
||||||
|
with open(plist_path, "rb") as f:
|
||||||
|
plist_data = f.read()
|
||||||
|
|
||||||
|
plist_dict = plistlib.loads(plist_data)
|
||||||
|
|
||||||
|
if (
|
||||||
|
plist_dict.get("CFBundleName")
|
||||||
|
and plist_dict["CFBundleName"] != "Dangerzone"
|
||||||
|
):
|
||||||
|
pdf_viewers[plist_dict["CFBundleName"]] = bundle_identifier
|
||||||
|
|
||||||
|
elif platform.system() == "Linux":
|
||||||
|
# Find all .desktop files
|
||||||
|
for search_path in [
|
||||||
|
"/usr/share/applications",
|
||||||
|
"/usr/local/share/applications",
|
||||||
|
os.path.expanduser("~/.local/share/applications"),
|
||||||
|
]:
|
||||||
|
try:
|
||||||
|
for filename in os.listdir(search_path):
|
||||||
|
full_filename = os.path.join(search_path, filename)
|
||||||
|
if os.path.splitext(filename)[1] == ".desktop":
|
||||||
|
|
||||||
|
# See which ones can open PDFs
|
||||||
|
desktop_entry = DesktopEntry(full_filename)
|
||||||
|
if (
|
||||||
|
"application/pdf" in desktop_entry.getMimeTypes()
|
||||||
|
and desktop_entry.getName() != "dangerzone"
|
||||||
|
):
|
||||||
|
pdf_viewers[
|
||||||
|
desktop_entry.getName()
|
||||||
|
] = desktop_entry.getExec()
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return pdf_viewers
|
||||||
|
|
||||||
|
def ensure_docker_group_preference(self):
|
||||||
|
# If the user prefers typing their password
|
||||||
|
if self.global_common.settings.get("linux_prefers_typing_password") == True:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Get the docker group
|
||||||
|
try:
|
||||||
|
groupinfo = grp.getgrnam("docker")
|
||||||
|
except:
|
||||||
|
# Ignore if group is not found
|
||||||
|
return True
|
||||||
|
|
||||||
|
# See if the user is in the group
|
||||||
|
username = getpass.getuser()
|
||||||
|
if username not in groupinfo.gr_mem:
|
||||||
|
# User is not in the docker group, ask if they prefer typing their password
|
||||||
|
message = "<b>Dangerzone requires Docker</b><br><br>In order to use Docker, your user must be in the 'docker' group or you'll need to type your password each time you run dangerzone.<br><br><b>Adding your user to the 'docker' group is more convenient but less secure</b>, and will require just typing your password once. Which do you prefer?"
|
||||||
|
return_code = Alert(
|
||||||
|
self,
|
||||||
|
self.global_common,
|
||||||
|
message,
|
||||||
|
ok_text="I'll type my password each time",
|
||||||
|
extra_button_text="Add my user to the 'docker' group",
|
||||||
|
).launch()
|
||||||
|
if return_code == QtWidgets.QDialog.Accepted:
|
||||||
|
# Prefers typing password
|
||||||
|
self.global_common.settings.set("linux_prefers_typing_password", True)
|
||||||
|
self.global_common.settings.save()
|
||||||
|
return True
|
||||||
|
elif return_code == 2:
|
||||||
|
# Prefers being in the docker group
|
||||||
|
self.global_common.settings.set("linux_prefers_typing_password", False)
|
||||||
|
self.global_common.settings.save()
|
||||||
|
|
||||||
|
# Add user to the docker group
|
||||||
|
p = subprocess.run(
|
||||||
|
[
|
||||||
|
"/usr/bin/pkexec",
|
||||||
|
"/usr/sbin/usermod",
|
||||||
|
"-a",
|
||||||
|
"-G",
|
||||||
|
"docker",
|
||||||
|
username,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
if p.returncode == 0:
|
||||||
|
message = "Great! Now you must log out of your computer and log back in, and then you can use Dangerzone."
|
||||||
|
Alert(self, self.global_common, message).launch()
|
||||||
|
else:
|
||||||
|
message = "Failed to add your user to the 'docker' group, quitting."
|
||||||
|
Alert(self, self.global_common, message).launch()
|
||||||
|
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
# Cancel
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def ensure_docker_service_is_started(self):
|
||||||
|
if not is_docker_ready(self):
|
||||||
|
message = "<b>Dangerzone requires Docker</b><br><br>Docker should be installed, but it looks like it's not running in the background.<br><br>Click Ok to try starting the docker service. You will have to type your login password."
|
||||||
|
if (
|
||||||
|
Alert(self, self.global_common, message).launch()
|
||||||
|
== QtWidgets.QDialog.Accepted
|
||||||
|
):
|
||||||
|
p = subprocess.run(
|
||||||
|
[
|
||||||
|
"/usr/bin/pkexec",
|
||||||
|
self.global_common.get_resource_path(
|
||||||
|
"enable_docker_service.sh"
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
if p.returncode == 0:
|
||||||
|
# Make sure docker is now ready
|
||||||
|
if is_docker_ready(self):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
message = "Restarting docker appeared to work, but the service still isn't responding, quitting."
|
||||||
|
Alert(self, self.global_common, message).launch()
|
||||||
|
else:
|
||||||
|
message = "Failed to start the docker service, quitting."
|
||||||
|
Alert(self, self.global_common, message).launch()
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class Alert(QtWidgets.QDialog):
|
||||||
|
def __init__(
|
||||||
|
self, global_common, gui_common, message, ok_text="Ok", extra_button_text=None
|
||||||
|
):
|
||||||
|
super(Alert, self).__init__()
|
||||||
|
self.global_common = global_common
|
||||||
|
self.gui_common = gui_common
|
||||||
|
|
||||||
|
self.setWindowTitle("dangerzone")
|
||||||
|
self.setWindowIcon(self.gui_common.get_window_icon())
|
||||||
|
self.setModal(True)
|
||||||
|
|
||||||
|
flags = (
|
||||||
|
QtCore.Qt.CustomizeWindowHint
|
||||||
|
| QtCore.Qt.WindowTitleHint
|
||||||
|
| QtCore.Qt.WindowSystemMenuHint
|
||||||
|
| QtCore.Qt.WindowCloseButtonHint
|
||||||
|
| QtCore.Qt.WindowStaysOnTopHint
|
||||||
|
)
|
||||||
|
self.setWindowFlags(flags)
|
||||||
|
|
||||||
|
logo = QtWidgets.QLabel()
|
||||||
|
logo.setPixmap(
|
||||||
|
QtGui.QPixmap.fromImage(
|
||||||
|
QtGui.QImage(self.global_common.get_resource_path("icon.png"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
label = QtWidgets.QLabel()
|
||||||
|
label.setText(message)
|
||||||
|
label.setWordWrap(True)
|
||||||
|
|
||||||
|
message_layout = QtWidgets.QHBoxLayout()
|
||||||
|
message_layout.addWidget(logo)
|
||||||
|
message_layout.addSpacing(10)
|
||||||
|
message_layout.addWidget(label, stretch=1)
|
||||||
|
|
||||||
|
ok_button = QtWidgets.QPushButton(ok_text)
|
||||||
|
ok_button.clicked.connect(self.clicked_ok)
|
||||||
|
if extra_button_text:
|
||||||
|
extra_button = QtWidgets.QPushButton(extra_button_text)
|
||||||
|
extra_button.clicked.connect(self.clicked_extra)
|
||||||
|
cancel_button = QtWidgets.QPushButton("Cancel")
|
||||||
|
cancel_button.clicked.connect(self.clicked_cancel)
|
||||||
|
|
||||||
|
buttons_layout = QtWidgets.QHBoxLayout()
|
||||||
|
buttons_layout.addStretch()
|
||||||
|
buttons_layout.addWidget(ok_button)
|
||||||
|
if extra_button_text:
|
||||||
|
buttons_layout.addWidget(extra_button)
|
||||||
|
buttons_layout.addWidget(cancel_button)
|
||||||
|
|
||||||
|
layout = QtWidgets.QVBoxLayout()
|
||||||
|
layout.addLayout(message_layout)
|
||||||
|
layout.addSpacing(10)
|
||||||
|
layout.addLayout(buttons_layout)
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
def clicked_ok(self):
|
||||||
|
self.done(QtWidgets.QDialog.Accepted)
|
||||||
|
|
||||||
|
def clicked_extra(self):
|
||||||
|
self.done(2)
|
||||||
|
|
||||||
|
def clicked_cancel(self):
|
||||||
|
self.done(QtWidgets.QDialog.Rejected)
|
||||||
|
|
||||||
|
def launch(self):
|
||||||
|
return self.exec_()
|
|
@ -8,14 +8,14 @@ import time
|
||||||
import platform
|
import platform
|
||||||
from PySide2 import QtCore, QtGui, QtWidgets
|
from PySide2 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
from .container import container_runtime
|
from ..container import container_runtime
|
||||||
|
|
||||||
|
|
||||||
class AuthorizationFailed(Exception):
|
class AuthorizationFailed(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def is_docker_installed(global_common):
|
def is_docker_installed():
|
||||||
if platform.system() == "Darwin":
|
if platform.system() == "Darwin":
|
||||||
# Does the docker binary exist?
|
# Does the docker binary exist?
|
||||||
if os.path.isdir("/Applications/Docker.app") and os.path.exists(
|
if os.path.isdir("/Applications/Docker.app") and os.path.exists(
|
||||||
|
@ -55,12 +55,11 @@ def launch_docker_windows(global_common):
|
||||||
|
|
||||||
|
|
||||||
class DockerInstaller(QtWidgets.QDialog):
|
class DockerInstaller(QtWidgets.QDialog):
|
||||||
def __init__(self, global_common):
|
def __init__(self, gui_common):
|
||||||
super(DockerInstaller, self).__init__()
|
super(DockerInstaller, self).__init__()
|
||||||
self.global_common = global_common
|
|
||||||
|
|
||||||
self.setWindowTitle("dangerzone")
|
self.setWindowTitle("dangerzone")
|
||||||
self.setWindowIcon(self.global_common.get_window_icon())
|
self.setWindowIcon(gui_common.get_window_icon())
|
||||||
# self.setMinimumHeight(170)
|
# self.setMinimumHeight(170)
|
||||||
|
|
||||||
label = QtWidgets.QLabel()
|
label = QtWidgets.QLabel()
|
|
@ -6,20 +6,21 @@ from PySide2 import QtCore, QtGui, QtWidgets
|
||||||
from .doc_selection_widget import DocSelectionWidget
|
from .doc_selection_widget import DocSelectionWidget
|
||||||
from .settings_widget import SettingsWidget
|
from .settings_widget import SettingsWidget
|
||||||
from .tasks_widget import TasksWidget
|
from .tasks_widget import TasksWidget
|
||||||
from .common import Common
|
from ..common import Common
|
||||||
|
|
||||||
|
|
||||||
class MainWindow(QtWidgets.QMainWindow):
|
class MainWindow(QtWidgets.QMainWindow):
|
||||||
delete_window = QtCore.Signal(str)
|
delete_window = QtCore.Signal(str)
|
||||||
|
|
||||||
def __init__(self, global_common, window_id):
|
def __init__(self, global_common, gui_common, window_id):
|
||||||
super(MainWindow, self).__init__()
|
super(MainWindow, self).__init__()
|
||||||
self.global_common = global_common
|
self.global_common = global_common
|
||||||
|
self.gui_common = gui_common
|
||||||
self.window_id = window_id
|
self.window_id = window_id
|
||||||
self.common = Common()
|
self.common = Common()
|
||||||
|
|
||||||
self.setWindowTitle("dangerzone")
|
self.setWindowTitle("dangerzone")
|
||||||
self.setWindowIcon(self.global_common.get_window_icon())
|
self.setWindowIcon(self.gui_common.get_window_icon())
|
||||||
|
|
||||||
self.setMinimumWidth(600)
|
self.setMinimumWidth(600)
|
||||||
self.setMinimumHeight(400)
|
self.setMinimumHeight(400)
|
||||||
|
@ -32,7 +33,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
header_label = QtWidgets.QLabel("dangerzone")
|
header_label = QtWidgets.QLabel("dangerzone")
|
||||||
header_label.setFont(self.global_common.fixed_font)
|
header_label.setFont(self.gui_common.fixed_font)
|
||||||
header_label.setStyleSheet("QLabel { font-weight: bold; font-size: 50px; }")
|
header_label.setStyleSheet("QLabel { font-weight: bold; font-size: 50px; }")
|
||||||
header_layout = QtWidgets.QHBoxLayout()
|
header_layout = QtWidgets.QHBoxLayout()
|
||||||
header_layout.addStretch()
|
header_layout.addStretch()
|
||||||
|
@ -47,7 +48,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||||
self.doc_selection_widget.show()
|
self.doc_selection_widget.show()
|
||||||
|
|
||||||
# Settings
|
# Settings
|
||||||
self.settings_widget = SettingsWidget(self.global_common, self.common)
|
self.settings_widget = SettingsWidget(
|
||||||
|
self.global_common, self.gui_common, self.common
|
||||||
|
)
|
||||||
self.doc_selection_widget.document_selected.connect(
|
self.doc_selection_widget.document_selected.connect(
|
||||||
self.settings_widget.document_selected
|
self.settings_widget.document_selected
|
||||||
)
|
)
|
||||||
|
@ -59,7 +62,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Tasks
|
# Tasks
|
||||||
self.tasks_widget = TasksWidget(self.global_common, self.common)
|
self.tasks_widget = TasksWidget(
|
||||||
|
self.global_common, self.gui_common, self.common
|
||||||
|
)
|
||||||
self.tasks_widget.close_window.connect(self.close)
|
self.tasks_widget.close_window.connect(self.close)
|
||||||
self.doc_selection_widget.document_selected.connect(
|
self.doc_selection_widget.document_selected.connect(
|
||||||
self.tasks_widget.document_selected
|
self.tasks_widget.document_selected
|
||||||
|
@ -93,4 +98,4 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||||
self.delete_window.emit(self.window_id)
|
self.delete_window.emit(self.window_id)
|
||||||
|
|
||||||
if platform.system() != "Darwin":
|
if platform.system() != "Darwin":
|
||||||
self.global_common.app.quit()
|
self.gui_common.app.quit()
|
|
@ -1,5 +1,4 @@
|
||||||
import os
|
import os
|
||||||
import subprocess
|
|
||||||
import platform
|
import platform
|
||||||
from PySide2 import QtCore, QtGui, QtWidgets
|
from PySide2 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
|
@ -8,9 +7,10 @@ class SettingsWidget(QtWidgets.QWidget):
|
||||||
start_clicked = QtCore.Signal()
|
start_clicked = QtCore.Signal()
|
||||||
close_window = QtCore.Signal()
|
close_window = QtCore.Signal()
|
||||||
|
|
||||||
def __init__(self, global_common, common):
|
def __init__(self, global_common, gui_common, common):
|
||||||
super(SettingsWidget, self).__init__()
|
super(SettingsWidget, self).__init__()
|
||||||
self.global_common = global_common
|
self.global_common = global_common
|
||||||
|
self.gui_common = gui_common
|
||||||
self.common = common
|
self.common = common
|
||||||
|
|
||||||
# Dangerous document label
|
# Dangerous document label
|
||||||
|
@ -49,8 +49,8 @@ class SettingsWidget(QtWidgets.QWidget):
|
||||||
)
|
)
|
||||||
self.open_checkbox.clicked.connect(self.update_ui)
|
self.open_checkbox.clicked.connect(self.update_ui)
|
||||||
self.open_combobox = QtWidgets.QComboBox()
|
self.open_combobox = QtWidgets.QComboBox()
|
||||||
for k in self.global_common.pdf_viewers:
|
for k in self.gui_common.pdf_viewers:
|
||||||
self.open_combobox.addItem(k, self.global_common.pdf_viewers[k])
|
self.open_combobox.addItem(k, self.gui_common.pdf_viewers[k])
|
||||||
open_layout = QtWidgets.QHBoxLayout()
|
open_layout = QtWidgets.QHBoxLayout()
|
||||||
open_layout.addWidget(self.open_checkbox)
|
open_layout.addWidget(self.open_checkbox)
|
||||||
open_layout.addWidget(self.open_combobox)
|
open_layout.addWidget(self.open_combobox)
|
|
@ -5,15 +5,16 @@ import platform
|
||||||
import subprocess
|
import subprocess
|
||||||
from PySide2 import QtCore, QtGui, QtWidgets
|
from PySide2 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
from .tasks import PullImageTask, ConvertToPixels, ConvertToPDF
|
from ..tasks import PullImageTask, ConvertToPixels, ConvertToPDF
|
||||||
|
|
||||||
|
|
||||||
class TasksWidget(QtWidgets.QWidget):
|
class TasksWidget(QtWidgets.QWidget):
|
||||||
close_window = QtCore.Signal()
|
close_window = QtCore.Signal()
|
||||||
|
|
||||||
def __init__(self, global_common, common):
|
def __init__(self, global_common, gui_common, common):
|
||||||
super(TasksWidget, self).__init__()
|
super(TasksWidget, self).__init__()
|
||||||
self.global_common = global_common
|
self.global_common = global_common
|
||||||
|
self.gui_common = gui_common
|
||||||
self.common = common
|
self.common = common
|
||||||
|
|
||||||
# Dangerous document label
|
# Dangerous document label
|
||||||
|
@ -31,7 +32,7 @@ class TasksWidget(QtWidgets.QWidget):
|
||||||
self.task_details.setStyleSheet(
|
self.task_details.setStyleSheet(
|
||||||
"QLabel { background-color: #ffffff; font-size: 12px; padding: 10px; }"
|
"QLabel { background-color: #ffffff; font-size: 12px; padding: 10px; }"
|
||||||
)
|
)
|
||||||
self.task_details.setFont(self.global_common.fixed_font)
|
self.task_details.setFont(self.gui_common.fixed_font)
|
||||||
self.task_details.setAlignment(QtCore.Qt.AlignTop)
|
self.task_details.setAlignment(QtCore.Qt.AlignTop)
|
||||||
|
|
||||||
self.details_scrollarea = QtWidgets.QScrollArea()
|
self.details_scrollarea = QtWidgets.QScrollArea()
|
||||||
|
@ -111,7 +112,7 @@ class TasksWidget(QtWidgets.QWidget):
|
||||||
|
|
||||||
# Open
|
# Open
|
||||||
if self.global_common.settings.get("open"):
|
if self.global_common.settings.get("open"):
|
||||||
self.global_common.open_pdf_viewer(dest_filename)
|
self.gui_common.open_pdf_viewer(dest_filename)
|
||||||
|
|
||||||
# Clean up
|
# Clean up
|
||||||
self.common.pixel_dir.cleanup()
|
self.common.pixel_dir.cleanup()
|
||||||
|
@ -122,7 +123,7 @@ class TasksWidget(QtWidgets.QWidget):
|
||||||
# In macOS, just close the window
|
# In macOS, just close the window
|
||||||
self.close_window.emit()
|
self.close_window.emit()
|
||||||
else:
|
else:
|
||||||
self.global_common.app.quit()
|
self.gui_common.app.quit()
|
||||||
|
|
||||||
def scroll_to_bottom(self, minimum, maximum):
|
def scroll_to_bottom(self, minimum, maximum):
|
||||||
self.details_scrollarea.verticalScrollBar().setValue(maximum)
|
self.details_scrollarea.verticalScrollBar().setValue(maximum)
|
|
@ -7,18 +7,12 @@ class Settings:
|
||||||
def __init__(self, common):
|
def __init__(self, common):
|
||||||
self.common = common
|
self.common = common
|
||||||
self.settings_filename = os.path.join(self.common.appdata_path, "settings.json")
|
self.settings_filename = os.path.join(self.common.appdata_path, "settings.json")
|
||||||
|
|
||||||
if len(self.common.pdf_viewers) == 0:
|
|
||||||
default_pdf_viewer = None
|
|
||||||
else:
|
|
||||||
default_pdf_viewer = list(self.common.pdf_viewers)[0]
|
|
||||||
|
|
||||||
self.default_settings = {
|
self.default_settings = {
|
||||||
"save": True,
|
"save": True,
|
||||||
"ocr": True,
|
"ocr": True,
|
||||||
"ocr_language": "English",
|
"ocr_language": "English",
|
||||||
"open": True,
|
"open": True,
|
||||||
"open_app": default_pdf_viewer,
|
"open_app": None,
|
||||||
"update_container": True,
|
"update_container": True,
|
||||||
"linux_prefers_typing_password": None,
|
"linux_prefers_typing_password": None,
|
||||||
}
|
}
|
||||||
|
|
1
dev_scripts/dangerzone-cli
Symbolic link
1
dev_scripts/dangerzone-cli
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
dangerzone
|
|
@ -27,6 +27,7 @@ black = "^21.5b2"
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
dangerzone = 'dangerzone:main'
|
dangerzone = 'dangerzone:main'
|
||||||
dangerzone-container = 'dangerzone:main'
|
dangerzone-container = 'dangerzone:main'
|
||||||
|
dangerzone-cli = 'dangerzone:main'
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry>=1.1.4"]
|
requires = ["poetry>=1.1.4"]
|
||||||
|
|
Loading…
Reference in a new issue