diff --git a/dangerzone/__init__.py b/dangerzone/__init__.py
index 902d49a..3a1253b 100644
--- a/dangerzone/__init__.py
+++ b/dangerzone/__init__.py
@@ -6,7 +6,7 @@ import platform
import click
import time
-from .common import Common
+from .global_common import GlobalCommon
from .main_window import MainWindow
from .docker_installer import (
is_docker_installed,
@@ -45,46 +45,60 @@ def main(filename):
app = Application()
app.setQuitOnLastWindowClosed(False)
- # Common object
- common = Common(app)
+ # GlobalCommon object
+ global_common = GlobalCommon(app)
# If we're using Linux and docker, see if we need to add the user to the docker group
- if platform.system() == "Linux" and common.container_runtime == "/usr/bin/docker":
- if not common.ensure_user_is_in_docker_group():
+ if (
+ platform.system() == "Linux"
+ and global_common.container_runtime == "/usr/bin/docker"
+ ):
+ if not global_common.ensure_user_is_in_docker_group():
print("Failed to add user to docker group")
return
# See if we need to install Docker...
if (platform.system() == "Darwin" or platform.system() == "Windows") and (
- not is_docker_installed(common) or not is_docker_ready(common)
+ not is_docker_installed(global_common) or not is_docker_ready(global_common)
):
print("Docker is either not installed or not running")
- docker_installer = DockerInstaller(common)
+ docker_installer = DockerInstaller(global_common)
docker_installer.start()
return
- # Main window
- main_window = MainWindow(common)
+ windows = []
+
+ # Open a document in a window
+ def select_document(filename=None):
+ if len(windows) == 1 and windows[0].common.document_filename == None:
+ window = windows[0]
+ else:
+ window = MainWindow(global_common)
+ windows.append(window)
+
+ if filename:
+ # Validate filename
+ filename = os.path.abspath(os.path.expanduser(filename))
+ try:
+ open(filename, "rb")
+ except FileNotFoundError:
+ print("File not found")
+ return False
+ except PermissionError:
+ print("Permission denied")
+ return False
+ window.common.document_filename = filename
+ window.doc_selection_widget.document_selected.emit()
- def select_document(filename):
- # Validate filename
- filename = os.path.abspath(os.path.expanduser(filename))
- try:
- open(filename, "rb")
- except FileNotFoundError:
- print("File not found")
- return False
- except PermissionError:
- print("Permission denied")
- return False
- common.set_document_filename(filename)
- main_window.doc_selection_widget.document_selected.emit()
return True
- # If filename is passed as an argument, open it
- if filename is not None:
+ # Open a new window if not filename is passed
+ if filename is None:
+ select_document()
+ else:
+ # If filename is passed as an argument, open it
if not select_document(filename):
- return False
+ return True
# If we get a file open event, open it
app.document_selected.connect(select_document)
diff --git a/dangerzone/common.py b/dangerzone/common.py
index 9a2e449..9b479c7 100644
--- a/dangerzone/common.py
+++ b/dangerzone/common.py
@@ -1,35 +1,13 @@
-import sys
-import os
-import inspect
-import tempfile
-import appdirs
import platform
-import subprocess
-import shlex
-from PyQt5 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
+import tempfile
class Common(object):
"""
- The Common class is a singleton of shared functionality throughout the app
+ The Common class is a singleton of shared functionality throughout an open dangerzone window
"""
- def __init__(self, app):
- # Qt app
- self.app = app
-
+ def __init__(self):
# Temporary directory to store pixel data
# Note in macOS, temp dirs must be in /tmp (or a few other paths) for Docker to mount them
if platform.system() == "Windows":
@@ -44,411 +22,6 @@ class Common(object):
f"Temporary directories created, dangerous={self.pixel_dir.name}, safe={self.safe_dir.name}"
)
- # Name of input file
+ # Name of input and out files
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
- self.appdata_path = appdirs.user_config_dir("dangerzone")
-
- # Container runtime
- if platform.system() == "Darwin":
- self.container_runtime = "/usr/local/bin/docker"
- elif platform.system() == "Windows":
- self.container_runtime = (
- "C:\\Program Files\\Docker\\Docker\\resources\\bin\\docker.exe"
- )
- else:
- # Linux
-
- # If this is fedora-like, use podman
- if os.path.exists("/usr/bin/dnf"):
- self.container_runtime = "/usr/bin/podman"
- # Otherwise, use docker
- else:
- self.container_runtime = "/usr/bin/docker"
-
- # Preload list of PDF viewers on computer
- self.pdf_viewers = self._find_pdf_viewers()
-
- # Languages supported by tesseract
- self.ocr_languages = {
- "Afrikaans": "ar",
- "Albanian": "sqi",
- "Amharic": "amh",
- "Arabic": "ara",
- "Arabic script": "Arabic",
- "Armenian": "hye",
- "Armenian script": "Armenian",
- "Assamese": "asm",
- "Azerbaijani": "aze",
- "Azerbaijani (Cyrillic)": "aze_cyrl",
- "Basque": "eus",
- "Belarusian": "bel",
- "Bengali": "ben",
- "Bengali script": "Bengali",
- "Bosnian": "bos",
- "Breton": "bre",
- "Bulgarian": "bul",
- "Burmese": "mya",
- "Canadian Aboriginal script": "Canadian_Aboriginal",
- "Catalan": "cat",
- "Cebuano": "ceb",
- "Cherokee": "chr",
- "Cherokee script": "Cherokee",
- "Chinese - Simplified": "chi_sim",
- "Chinese - Simplified (vertical)": "chi_sim_vert",
- "Chinese - Traditional": "chi_tra",
- "Chinese - Traditional (vertical)": "chi_tra_vert",
- "Corsican": "cos",
- "Croatian": "hrv",
- "Cyrillic script": "Cyrillic",
- "Czech": "ces",
- "Danish": "dan",
- "Devanagari script": "Devanagari",
- "Divehi": "div",
- "Dutch": "nld",
- "Dzongkha": "dzo",
- "English": "eng",
- "English, Middle (1100-1500)": "enm",
- "Esperanto": "epo",
- "Estonian": "est",
- "Ethiopic script": "Ethiopic",
- "Faroese": "fao",
- "Filipino": "fil",
- "Finnish": "fin",
- "Fraktur script": "Fraktur",
- "Frankish": "frk",
- "French": "fra",
- "French, Middle (ca.1400-1600)": "frm",
- "Frisian (Western)": "fry",
- "Gaelic (Scots)": "gla",
- "Galician": "glg",
- "Georgian": "kat",
- "Georgian script": "Georgian",
- "German": "deu",
- "Greek": "ell",
- "Greek script": "Greek",
- "Gujarati": "guj",
- "Gujarati script": "Gujarati",
- "Gurmukhi script": "Gurmukhi",
- "Hangul script": "Hangul",
- "Hangul (vertical) script": "Hangul_vert",
- "Han - Simplified script": "HanS",
- "Han - Simplified (vertical) script": "HanS_vert",
- "Han - Traditional script": "HanT",
- "Han - Traditional (vertical) script": "HanT_vert",
- "Hatian": "hat",
- "Hebrew": "heb",
- "Hebrew script": "Hebrew",
- "Hindi": "hin",
- "Hungarian": "hun",
- "Icelandic": "isl",
- "Indonesian": "ind",
- "Inuktitut": "iku",
- "Irish": "gle",
- "Italian": "ita",
- "Italian - Old": "ita_old",
- "Japanese": "jpn",
- "Japanese script": "Japanese",
- "Japanese (vertical)": "jpn_vert",
- "Japanese (vertical) script": "Japanese_vert",
- "Javanese": "jav",
- "Kannada": "kan",
- "Kannada script": "Kannada",
- "Kazakh": "kaz",
- "Khmer": "khm",
- "Khmer script": "Khmer",
- "Korean": "kor",
- "Korean (vertical)": "kor_vert",
- "Kurdish (Arabic)": "kur_ara",
- "Kyrgyz": "kir",
- "Lao": "lao",
- "Lao script": "Lao",
- "Latin": "lat",
- "Latin script": "Latin",
- "Latvian": "lav",
- "Lithuanian": "lit",
- "Luxembourgish": "ltz",
- "Macedonian": "mkd",
- "Malayalam": "mal",
- "Malayalam script": "Malayalam",
- "Malay": "msa",
- "Maltese": "mlt",
- "Maori": "mri",
- "Marathi": "mar",
- "Mongolian": "mon",
- "Myanmar script": "Myanmar",
- "Nepali": "nep",
- "Norwegian": "nor",
- "Occitan (post 1500)": "oci",
- "Old Georgian": "kat_old",
- "Oriya (Odia) script": "Oriya",
- "Oriya": "ori",
- "Pashto": "pus",
- "Persian": "fas",
- "Polish": "pol",
- "Portuguese": "por",
- "Punjabi": "pan",
- "Quechua": "que",
- "Romanian": "ron",
- "Russian": "rus",
- "Sanskrit": "san",
- "script and orientation": "osd",
- "Serbian (Latin)": "srp_latn",
- "Serbian": "srp",
- "Sindhi": "snd",
- "Sinhala script": "Sinhala",
- "Sinhala": "sin",
- "Slovakian": "slk",
- "Slovenian": "slv",
- "Spanish, Castilian - Old": "spa_old",
- "Spanish": "spa",
- "Sundanese": "sun",
- "Swahili": "swa",
- "Swedish": "swe",
- "Syriac script": "Syriac",
- "Syriac": "syr",
- "Tajik": "tgk",
- "Tamil script": "Tamil",
- "Tamil": "tam",
- "Tatar": "tat",
- "Telugu script": "Telugu",
- "Telugu": "tel",
- "Thaana script": "Thaana",
- "Thai script": "Thai",
- "Thai": "tha",
- "Tibetan script": "Tibetan",
- "Tibetan Standard": "bod",
- "Tigrinya": "tir",
- "Tonga": "ton",
- "Turkish": "tur",
- "Ukrainian": "ukr",
- "Urdu": "urd",
- "Uyghur": "uig",
- "Uzbek (Cyrillic)": "uzb_cyrl",
- "Uzbek": "uzb",
- "Vietnamese script": "Vietnamese",
- "Vietnamese": "vie",
- "Welsh": "cym",
- "Yiddish": "yid",
- "Yoruba": "yor",
- }
-
- # Load settings
- self.settings = Settings(self)
-
- def set_document_filename(self, filename):
- self.document_filename = filename
-
- def get_resource_path(self, filename):
- if getattr(sys, "dangerzone_dev", False):
- # Look for resources directory relative to python file
- prefix = os.path.join(
- os.path.dirname(
- os.path.dirname(
- os.path.abspath(inspect.getfile(inspect.currentframe()))
- )
- ),
- "share",
- )
- else:
- if platform.system() == "Darwin":
- prefix = os.path.join(
- os.path.dirname(os.path.dirname(sys.executable)), "Resources/share"
- )
- elif platform.system() == "Linux":
- prefix = os.path.join(sys.prefix, "share", "dangerzone")
- else:
- # Windows
- prefix = os.path.join(os.path.dirname(sys.executable), "share")
-
- resource_path = os.path.join(prefix, filename)
- return resource_path
-
- def get_window_icon(self):
- if platform.system() == "Windows":
- path = self.get_resource_path("dangerzone.ico")
- else:
- path = self.get_resource_path("logo.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")
- with open(plist_path, "rb") as f:
- plist_data = f.read()
- plist_dict = plistlib.loads(plist_data)
-
- if 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_user_is_in_docker_group(self):
- try:
- groupinfo = grp.getgrnam("docker")
- except:
- # Ignore if group is not found
- return True
-
- username = getpass.getuser()
- if username not in groupinfo.gr_mem:
- # User is not in docker group, so prompt about adding the user to the docker group
- message = "Dangerzone requires Docker.
Click Ok to add your user to the 'docker' group. You will have to type your login password."
- if Alert(self, message).launch():
- 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
-
- return True
-
- def get_subprocess_startupinfo(self):
- if platform.system() == "Windows":
- startupinfo = subprocess.STARTUPINFO()
- startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
- return startupinfo
- else:
- return None
-
-
-class Alert(QtWidgets.QDialog):
- def __init__(self, common, message):
- 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.addWidget(label)
-
- ok_button = QtWidgets.QPushButton("Ok")
- ok_button.clicked.connect(self.accept)
- cancel_button = QtWidgets.QPushButton("Cancel")
- cancel_button.clicked.connect(self.reject)
-
- buttons_layout = QtWidgets.QHBoxLayout()
- buttons_layout.addStretch()
- buttons_layout.addWidget(ok_button)
- buttons_layout.addWidget(cancel_button)
-
- layout = QtWidgets.QVBoxLayout()
- layout.addLayout(message_layout)
- layout.addLayout(buttons_layout)
- self.setLayout(layout)
-
- def launch(self):
- return self.exec_() == QtWidgets.QDialog.Accepted
diff --git a/dangerzone/doc_selection_widget.py b/dangerzone/doc_selection_widget.py
index 7559673..2c79191 100644
--- a/dangerzone/doc_selection_widget.py
+++ b/dangerzone/doc_selection_widget.py
@@ -39,5 +39,5 @@ class DocSelectionWidget(QtWidgets.QWidget):
)
if filename[0] != "":
filename = filename[0]
- self.common.set_document_filename(filename)
+ self.common.document_filename = filename
self.document_selected.emit()
diff --git a/dangerzone/docker_installer.py b/dangerzone/docker_installer.py
index f2401cd..641656e 100644
--- a/dangerzone/docker_installer.py
+++ b/dangerzone/docker_installer.py
@@ -9,49 +9,49 @@ import platform
from PyQt5 import QtCore, QtGui, QtWidgets
-def is_docker_installed(common):
+def is_docker_installed(global_common):
if platform.system() == "Darwin":
# Does the docker binary exist?
if os.path.isdir("/Applications/Docker.app") and os.path.exists(
- common.container_runtime
+ global_common.container_runtime
):
# Is it executable?
- st = os.stat(common.container_runtime)
+ st = os.stat(global_common.container_runtime)
return bool(st.st_mode & stat.S_IXOTH)
if platform.system() == "Windows":
- return os.path.exists(common.container_runtime)
+ return os.path.exists(global_common.container_runtime)
return False
-def is_docker_ready(common):
+def is_docker_ready(global_common):
# Run `docker ps` without an error
try:
subprocess.run(
- [common.container_runtime, "ps"],
+ [global_common.container_runtime, "ps"],
check=True,
- startupinfo=common.get_subprocess_startupinfo(),
+ startupinfo=global_common.get_subprocess_startupinfo(),
)
return True
except subprocess.CalledProcessError:
return False
-def launch_docker_windows(common):
+def launch_docker_windows(global_common):
docker_desktop_path = "C:\\Program Files\\Docker\\Docker\\Docker Desktop.exe"
subprocess.Popen(
- [docker_desktop_path], startupinfo=common.get_subprocess_startupinfo()
+ [docker_desktop_path], startupinfo=global_common.get_subprocess_startupinfo()
)
class DockerInstaller(QtWidgets.QDialog):
- def __init__(self, common):
+ def __init__(self, global_common):
super(DockerInstaller, self).__init__()
- self.common = common
+ self.global_common = global_common
self.setWindowTitle("dangerzone")
- self.setWindowIcon(self.common.get_window_icon())
+ self.setWindowIcon(self.global_common.get_window_icon())
self.setMinimumHeight(170)
label = QtWidgets.QLabel()
diff --git a/dangerzone/global_common.py b/dangerzone/global_common.py
new file mode 100644
index 0000000..9b9a2a6
--- /dev/null
+++ b/dangerzone/global_common.py
@@ -0,0 +1,451 @@
+import sys
+import os
+import inspect
+import tempfile
+import appdirs
+import platform
+import subprocess
+import shlex
+from PyQt5 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
+
+
+class GlobalCommon(object):
+ """
+ The GlobalCommon class is a singleton of shared functionality throughout the app
+ """
+
+ def __init__(self, app):
+ # Qt app
+ self.app = app
+
+ # Temporary directory to store pixel data
+ # Note in macOS, temp dirs must be in /tmp (or a few other paths) for Docker to mount them
+ if platform.system() == "Windows":
+ self.pixel_dir = tempfile.TemporaryDirectory(prefix="dangerzone-pixel-")
+ self.safe_dir = tempfile.TemporaryDirectory(prefix="dangerzone-safe-")
+ else:
+ self.pixel_dir = tempfile.TemporaryDirectory(
+ prefix="/tmp/dangerzone-pixel-"
+ )
+ self.safe_dir = tempfile.TemporaryDirectory(prefix="/tmp/dangerzone-safe-")
+ print(
+ f"Temporary directories created, dangerous={self.pixel_dir.name}, safe={self.safe_dir.name}"
+ )
+
+ # 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
+ self.appdata_path = appdirs.user_config_dir("dangerzone")
+
+ # Container runtime
+ if platform.system() == "Darwin":
+ self.container_runtime = "/usr/local/bin/docker"
+ elif platform.system() == "Windows":
+ self.container_runtime = (
+ "C:\\Program Files\\Docker\\Docker\\resources\\bin\\docker.exe"
+ )
+ else:
+ # Linux
+
+ # If this is fedora-like, use podman
+ if os.path.exists("/usr/bin/dnf"):
+ self.container_runtime = "/usr/bin/podman"
+ # Otherwise, use docker
+ else:
+ self.container_runtime = "/usr/bin/docker"
+
+ # Preload list of PDF viewers on computer
+ self.pdf_viewers = self._find_pdf_viewers()
+
+ # Languages supported by tesseract
+ self.ocr_languages = {
+ "Afrikaans": "ar",
+ "Albanian": "sqi",
+ "Amharic": "amh",
+ "Arabic": "ara",
+ "Arabic script": "Arabic",
+ "Armenian": "hye",
+ "Armenian script": "Armenian",
+ "Assamese": "asm",
+ "Azerbaijani": "aze",
+ "Azerbaijani (Cyrillic)": "aze_cyrl",
+ "Basque": "eus",
+ "Belarusian": "bel",
+ "Bengali": "ben",
+ "Bengali script": "Bengali",
+ "Bosnian": "bos",
+ "Breton": "bre",
+ "Bulgarian": "bul",
+ "Burmese": "mya",
+ "Canadian Aboriginal script": "Canadian_Aboriginal",
+ "Catalan": "cat",
+ "Cebuano": "ceb",
+ "Cherokee": "chr",
+ "Cherokee script": "Cherokee",
+ "Chinese - Simplified": "chi_sim",
+ "Chinese - Simplified (vertical)": "chi_sim_vert",
+ "Chinese - Traditional": "chi_tra",
+ "Chinese - Traditional (vertical)": "chi_tra_vert",
+ "Corsican": "cos",
+ "Croatian": "hrv",
+ "Cyrillic script": "Cyrillic",
+ "Czech": "ces",
+ "Danish": "dan",
+ "Devanagari script": "Devanagari",
+ "Divehi": "div",
+ "Dutch": "nld",
+ "Dzongkha": "dzo",
+ "English": "eng",
+ "English, Middle (1100-1500)": "enm",
+ "Esperanto": "epo",
+ "Estonian": "est",
+ "Ethiopic script": "Ethiopic",
+ "Faroese": "fao",
+ "Filipino": "fil",
+ "Finnish": "fin",
+ "Fraktur script": "Fraktur",
+ "Frankish": "frk",
+ "French": "fra",
+ "French, Middle (ca.1400-1600)": "frm",
+ "Frisian (Western)": "fry",
+ "Gaelic (Scots)": "gla",
+ "Galician": "glg",
+ "Georgian": "kat",
+ "Georgian script": "Georgian",
+ "German": "deu",
+ "Greek": "ell",
+ "Greek script": "Greek",
+ "Gujarati": "guj",
+ "Gujarati script": "Gujarati",
+ "Gurmukhi script": "Gurmukhi",
+ "Hangul script": "Hangul",
+ "Hangul (vertical) script": "Hangul_vert",
+ "Han - Simplified script": "HanS",
+ "Han - Simplified (vertical) script": "HanS_vert",
+ "Han - Traditional script": "HanT",
+ "Han - Traditional (vertical) script": "HanT_vert",
+ "Hatian": "hat",
+ "Hebrew": "heb",
+ "Hebrew script": "Hebrew",
+ "Hindi": "hin",
+ "Hungarian": "hun",
+ "Icelandic": "isl",
+ "Indonesian": "ind",
+ "Inuktitut": "iku",
+ "Irish": "gle",
+ "Italian": "ita",
+ "Italian - Old": "ita_old",
+ "Japanese": "jpn",
+ "Japanese script": "Japanese",
+ "Japanese (vertical)": "jpn_vert",
+ "Japanese (vertical) script": "Japanese_vert",
+ "Javanese": "jav",
+ "Kannada": "kan",
+ "Kannada script": "Kannada",
+ "Kazakh": "kaz",
+ "Khmer": "khm",
+ "Khmer script": "Khmer",
+ "Korean": "kor",
+ "Korean (vertical)": "kor_vert",
+ "Kurdish (Arabic)": "kur_ara",
+ "Kyrgyz": "kir",
+ "Lao": "lao",
+ "Lao script": "Lao",
+ "Latin": "lat",
+ "Latin script": "Latin",
+ "Latvian": "lav",
+ "Lithuanian": "lit",
+ "Luxembourgish": "ltz",
+ "Macedonian": "mkd",
+ "Malayalam": "mal",
+ "Malayalam script": "Malayalam",
+ "Malay": "msa",
+ "Maltese": "mlt",
+ "Maori": "mri",
+ "Marathi": "mar",
+ "Mongolian": "mon",
+ "Myanmar script": "Myanmar",
+ "Nepali": "nep",
+ "Norwegian": "nor",
+ "Occitan (post 1500)": "oci",
+ "Old Georgian": "kat_old",
+ "Oriya (Odia) script": "Oriya",
+ "Oriya": "ori",
+ "Pashto": "pus",
+ "Persian": "fas",
+ "Polish": "pol",
+ "Portuguese": "por",
+ "Punjabi": "pan",
+ "Quechua": "que",
+ "Romanian": "ron",
+ "Russian": "rus",
+ "Sanskrit": "san",
+ "script and orientation": "osd",
+ "Serbian (Latin)": "srp_latn",
+ "Serbian": "srp",
+ "Sindhi": "snd",
+ "Sinhala script": "Sinhala",
+ "Sinhala": "sin",
+ "Slovakian": "slk",
+ "Slovenian": "slv",
+ "Spanish, Castilian - Old": "spa_old",
+ "Spanish": "spa",
+ "Sundanese": "sun",
+ "Swahili": "swa",
+ "Swedish": "swe",
+ "Syriac script": "Syriac",
+ "Syriac": "syr",
+ "Tajik": "tgk",
+ "Tamil script": "Tamil",
+ "Tamil": "tam",
+ "Tatar": "tat",
+ "Telugu script": "Telugu",
+ "Telugu": "tel",
+ "Thaana script": "Thaana",
+ "Thai script": "Thai",
+ "Thai": "tha",
+ "Tibetan script": "Tibetan",
+ "Tibetan Standard": "bod",
+ "Tigrinya": "tir",
+ "Tonga": "ton",
+ "Turkish": "tur",
+ "Ukrainian": "ukr",
+ "Urdu": "urd",
+ "Uyghur": "uig",
+ "Uzbek (Cyrillic)": "uzb_cyrl",
+ "Uzbek": "uzb",
+ "Vietnamese script": "Vietnamese",
+ "Vietnamese": "vie",
+ "Welsh": "cym",
+ "Yiddish": "yid",
+ "Yoruba": "yor",
+ }
+
+ # Load settings
+ self.settings = Settings(self)
+
+ def get_resource_path(self, filename):
+ if getattr(sys, "dangerzone_dev", False):
+ # Look for resources directory relative to python file
+ prefix = os.path.join(
+ os.path.dirname(
+ os.path.dirname(
+ os.path.abspath(inspect.getfile(inspect.currentframe()))
+ )
+ ),
+ "share",
+ )
+ else:
+ if platform.system() == "Darwin":
+ prefix = os.path.join(
+ os.path.dirname(os.path.dirname(sys.executable)), "Resources/share"
+ )
+ elif platform.system() == "Linux":
+ prefix = os.path.join(sys.prefix, "share", "dangerzone")
+ else:
+ # Windows
+ prefix = os.path.join(os.path.dirname(sys.executable), "share")
+
+ resource_path = os.path.join(prefix, filename)
+ return resource_path
+
+ def get_window_icon(self):
+ if platform.system() == "Windows":
+ path = self.get_resource_path("dangerzone.ico")
+ else:
+ path = self.get_resource_path("logo.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")
+ with open(plist_path, "rb") as f:
+ plist_data = f.read()
+ plist_dict = plistlib.loads(plist_data)
+
+ if 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_user_is_in_docker_group(self):
+ try:
+ groupinfo = grp.getgrnam("docker")
+ except:
+ # Ignore if group is not found
+ return True
+
+ username = getpass.getuser()
+ if username not in groupinfo.gr_mem:
+ # User is not in docker group, so prompt about adding the user to the docker group
+ message = "Dangerzone requires Docker.
Click Ok to add your user to the 'docker' group. You will have to type your login password."
+ if Alert(self, message).launch():
+ 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
+
+ return True
+
+ def get_subprocess_startupinfo(self):
+ if platform.system() == "Windows":
+ startupinfo = subprocess.STARTUPINFO()
+ startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
+ return startupinfo
+ else:
+ return None
+
+
+class Alert(QtWidgets.QDialog):
+ def __init__(self, common, message):
+ 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.addWidget(label)
+
+ ok_button = QtWidgets.QPushButton("Ok")
+ ok_button.clicked.connect(self.accept)
+ cancel_button = QtWidgets.QPushButton("Cancel")
+ cancel_button.clicked.connect(self.reject)
+
+ buttons_layout = QtWidgets.QHBoxLayout()
+ buttons_layout.addStretch()
+ buttons_layout.addWidget(ok_button)
+ buttons_layout.addWidget(cancel_button)
+
+ layout = QtWidgets.QVBoxLayout()
+ layout.addLayout(message_layout)
+ layout.addLayout(buttons_layout)
+ self.setLayout(layout)
+
+ def launch(self):
+ return self.exec_() == QtWidgets.QDialog.Accepted
diff --git a/dangerzone/main_window.py b/dangerzone/main_window.py
index 4d6e231..5becd69 100644
--- a/dangerzone/main_window.py
+++ b/dangerzone/main_window.py
@@ -1,19 +1,22 @@
import shutil
import os
+import platform
from PyQt5 import QtCore, QtGui, QtWidgets
from .doc_selection_widget import DocSelectionWidget
from .settings_widget import SettingsWidget
from .tasks_widget import TasksWidget
+from .common import Common
class MainWindow(QtWidgets.QMainWindow):
- def __init__(self, common):
+ def __init__(self, global_common):
super(MainWindow, self).__init__()
- self.common = common
+ self.global_common = global_common
+ self.common = Common()
self.setWindowTitle("dangerzone")
- self.setWindowIcon(self.common.get_window_icon())
+ self.setWindowIcon(self.global_common.get_window_icon())
self.setMinimumWidth(600)
self.setMinimumHeight(400)
@@ -22,11 +25,11 @@ class MainWindow(QtWidgets.QMainWindow):
logo = QtWidgets.QLabel()
logo.setPixmap(
QtGui.QPixmap.fromImage(
- QtGui.QImage(self.common.get_resource_path("icon.png"))
+ QtGui.QImage(self.global_common.get_resource_path("icon.png"))
)
)
header_label = QtWidgets.QLabel("dangerzone")
- header_label.setFont(self.common.fixed_font)
+ header_label.setFont(self.global_common.fixed_font)
header_label.setStyleSheet("QLabel { font-weight: bold; font-size: 50px; }")
header_layout = QtWidgets.QHBoxLayout()
header_layout.addStretch()
@@ -41,7 +44,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.doc_selection_widget.show()
# Settings
- self.settings_widget = SettingsWidget(self.common)
+ self.settings_widget = SettingsWidget(self.global_common, self.common)
self.doc_selection_widget.document_selected.connect(
self.settings_widget.document_selected
)
@@ -49,7 +52,8 @@ class MainWindow(QtWidgets.QMainWindow):
self.settings_widget.hide()
# Tasks
- self.tasks_widget = TasksWidget(self.common)
+ self.tasks_widget = TasksWidget(self.global_common, self.common)
+ self.tasks_widget.close_window.connect(self.close)
self.doc_selection_widget.document_selected.connect(
self.tasks_widget.document_selected
)
@@ -79,4 +83,5 @@ class MainWindow(QtWidgets.QMainWindow):
def closeEvent(self, e):
e.accept()
- self.common.app.quit()
+ if platform.system() != "Darwin":
+ self.global_common.app.quit()
diff --git a/dangerzone/settings_widget.py b/dangerzone/settings_widget.py
index 18f5c00..9c4bb13 100644
--- a/dangerzone/settings_widget.py
+++ b/dangerzone/settings_widget.py
@@ -7,8 +7,9 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class SettingsWidget(QtWidgets.QWidget):
start_clicked = QtCore.pyqtSignal()
- def __init__(self, common):
+ def __init__(self, global_common, common):
super(SettingsWidget, self).__init__()
+ self.global_common = global_common
self.common = common
# Dangerous document label
@@ -47,9 +48,9 @@ class SettingsWidget(QtWidgets.QWidget):
)
self.open_checkbox.clicked.connect(self.update_ui)
self.open_combobox = QtWidgets.QComboBox()
- for k in self.common.pdf_viewers:
+ for k in self.global_common.pdf_viewers:
self.open_combobox.addItem(
- k, QtCore.QVariant(self.common.pdf_viewers[k])
+ k, QtCore.QVariant(self.global_common.pdf_viewers[k])
)
open_layout = QtWidgets.QHBoxLayout()
open_layout.addWidget(self.open_checkbox)
@@ -59,8 +60,10 @@ class SettingsWidget(QtWidgets.QWidget):
# OCR document
self.ocr_checkbox = QtWidgets.QCheckBox("OCR document, language")
self.ocr_combobox = QtWidgets.QComboBox()
- for k in self.common.ocr_languages:
- self.ocr_combobox.addItem(k, QtCore.QVariant(self.common.ocr_languages[k]))
+ for k in self.global_common.ocr_languages:
+ self.ocr_combobox.addItem(
+ k, QtCore.QVariant(self.global_common.ocr_languages[k])
+ )
ocr_layout = QtWidgets.QHBoxLayout()
ocr_layout.addWidget(self.ocr_checkbox)
ocr_layout.addWidget(self.ocr_combobox)
@@ -98,39 +101,43 @@ class SettingsWidget(QtWidgets.QWidget):
self.setLayout(layout)
# Load values from settings
- if self.common.settings.get("save"):
+ if self.global_common.settings.get("save"):
self.save_checkbox.setCheckState(QtCore.Qt.Checked)
else:
self.save_checkbox.setCheckState(QtCore.Qt.Unchecked)
- if self.common.settings.get("ocr"):
+ if self.global_common.settings.get("ocr"):
self.ocr_checkbox.setCheckState(QtCore.Qt.Checked)
else:
self.ocr_checkbox.setCheckState(QtCore.Qt.Unchecked)
- index = self.ocr_combobox.findText(self.common.settings.get("ocr_language"))
+ index = self.ocr_combobox.findText(
+ self.global_common.settings.get("ocr_language")
+ )
if index != -1:
self.ocr_combobox.setCurrentIndex(index)
if platform.system() != "Windows":
- if self.common.settings.get("open"):
+ if self.global_common.settings.get("open"):
self.open_checkbox.setCheckState(QtCore.Qt.Checked)
else:
self.open_checkbox.setCheckState(QtCore.Qt.Unchecked)
- index = self.open_combobox.findText(self.common.settings.get("open_app"))
+ index = self.open_combobox.findText(
+ self.global_common.settings.get("open_app")
+ )
if index != -1:
self.open_combobox.setCurrentIndex(index)
- if self.common.settings.get("update_container"):
+ if self.global_common.settings.get("update_container"):
self.update_checkbox.setCheckState(QtCore.Qt.Checked)
else:
self.update_checkbox.setCheckState(QtCore.Qt.Unchecked)
# Is update containers required?
output = subprocess.check_output(
- [self.common.container_runtime, "image", "ls", "dangerzone"],
- startupinfo=self.common.get_subprocess_startupinfo(),
+ [self.global_common.container_runtime, "image", "ls", "dangerzone"],
+ startupinfo=self.global_common.get_subprocess_startupinfo(),
)
if b"dangerzone" not in output:
self.update_checkbox.setCheckState(QtCore.Qt.Checked)
@@ -158,10 +165,9 @@ class SettingsWidget(QtWidgets.QWidget):
)
# Update the save location
- self.common.save_filename = (
- f"{os.path.splitext(self.common.document_filename)[0]}-safe.pdf"
- )
- self.save_lineedit.setText(os.path.basename(self.common.save_filename))
+ save_filename = f"{os.path.splitext(self.common.document_filename)[0]}-safe.pdf"
+ self.common.save_filename = save_filename
+ self.save_lineedit.setText(os.path.basename(save_filename))
def save_browse_button_clicked(self):
filename = QtWidgets.QFileDialog.getSaveFileName(
@@ -176,22 +182,24 @@ class SettingsWidget(QtWidgets.QWidget):
def start_button_clicked(self):
# Update settings
- self.common.settings.set(
+ self.global_common.settings.set(
"save", self.save_checkbox.checkState() == QtCore.Qt.Checked
)
- self.common.settings.set(
+ self.global_common.settings.set(
"ocr", self.ocr_checkbox.checkState() == QtCore.Qt.Checked
)
- self.common.settings.set("ocr_language", self.ocr_combobox.currentText())
+ self.global_common.settings.set("ocr_language", self.ocr_combobox.currentText())
if platform.system() != "Windows":
- self.common.settings.set(
+ self.global_common.settings.set(
"open", self.open_checkbox.checkState() == QtCore.Qt.Checked
)
- self.common.settings.set("open_app", self.open_combobox.currentText())
- self.common.settings.set(
+ self.global_common.settings.set(
+ "open_app", self.open_combobox.currentText()
+ )
+ self.global_common.settings.set(
"update_container", self.update_checkbox.checkState() == QtCore.Qt.Checked
)
- self.common.settings.save()
+ self.global_common.settings.save()
# Start!
self.start_clicked.emit()
diff --git a/dangerzone/tasks.py b/dangerzone/tasks.py
index 11a093b..01a968f 100644
--- a/dangerzone/tasks.py
+++ b/dangerzone/tasks.py
@@ -16,7 +16,7 @@ class TaskBase(QtCore.QThread):
super(TaskBase, self).__init__()
def exec_container(self, args, watch="stdout"):
- args = [self.common.container_runtime] + args
+ args = [self.global_common.container_runtime] + args
args_str = " ".join(pipes.quote(s) for s in args)
print()
@@ -31,7 +31,7 @@ class TaskBase(QtCore.QThread):
stderr=subprocess.PIPE,
bufsize=1,
universal_newlines=True,
- startupinfo=self.common.get_subprocess_startupinfo(),
+ startupinfo=self.global_common.get_subprocess_startupinfo(),
) as p:
if watch == "stdout":
pipe = p.stdout
@@ -53,14 +53,15 @@ class TaskBase(QtCore.QThread):
class PullImageTask(TaskBase):
- def __init__(self, common):
+ def __init__(self, global_common, common):
super(PullImageTask, self).__init__()
+ self.global_common = global_common
self.common = common
def run(self):
self.update_label.emit("Pulling container image")
self.update_details.emit("")
- args = ["pull", "ubuntu:20.04"]
+ args = ["pull", "debian:buster"]
returncode, _ = self.exec_container(args, watch="stderr")
if returncode != 0:
@@ -71,12 +72,13 @@ class PullImageTask(TaskBase):
class BuildContainerTask(TaskBase):
- def __init__(self, common):
+ def __init__(self, global_common, common):
super(BuildContainerTask, self).__init__()
+ self.global_common = global_common
self.common = common
def run(self):
- container_path = self.common.get_resource_path("container")
+ container_path = self.global_common.get_resource_path("container")
self.update_label.emit("Building container (this might take a long time)")
self.update_details.emit("")
args = ["build", "-t", "dangerzone", container_path]
@@ -90,8 +92,9 @@ class BuildContainerTask(TaskBase):
class ConvertToPixels(TaskBase):
- def __init__(self, common):
+ def __init__(self, global_common, common):
super(ConvertToPixels, self).__init__()
+ self.global_common = global_common
self.common = common
self.max_image_width = 10000
@@ -119,7 +122,11 @@ class ConvertToPixels(TaskBase):
# Did we hit an error?
for line in output.split("\n"):
- if "failed:" in line or "The document format is not supported" in line:
+ if (
+ "failed:" in line
+ or "The document format is not supported" in line
+ or "Error" in line
+ ):
self.task_failed.emit(output)
return
@@ -183,8 +190,9 @@ class ConvertToPixels(TaskBase):
class ConvertToPDF(TaskBase):
- def __init__(self, common):
+ def __init__(self, global_common, common):
super(ConvertToPDF, self).__init__()
+ self.global_common = global_common
self.common = common
def run(self):
@@ -192,13 +200,13 @@ class ConvertToPDF(TaskBase):
# Build environment variables list
envs = []
- if self.common.settings.get("ocr"):
+ if self.global_common.settings.get("ocr"):
envs += ["-e", "OCR=1"]
else:
envs += ["-e", "OCR=0"]
envs += [
"-e",
- f"OCR_LANGUAGE={self.common.ocr_languages[self.common.settings.get('ocr_language')]}",
+ f"OCR_LANGUAGE={self.global_common.ocr_languages[self.global_common.settings.get('ocr_language')]}",
]
args = (
diff --git a/dangerzone/tasks_widget.py b/dangerzone/tasks_widget.py
index 92ce455..b70363b 100644
--- a/dangerzone/tasks_widget.py
+++ b/dangerzone/tasks_widget.py
@@ -9,8 +9,11 @@ from .tasks import PullImageTask, BuildContainerTask, ConvertToPixels, ConvertTo
class TasksWidget(QtWidgets.QWidget):
- def __init__(self, common):
+ close_window = QtCore.pyqtSignal()
+
+ def __init__(self, global_common, common):
super(TasksWidget, self).__init__()
+ self.global_common = global_common
self.common = common
# Dangerous document label
@@ -28,7 +31,7 @@ class TasksWidget(QtWidgets.QWidget):
self.task_details.setStyleSheet(
"QLabel { background-color: #ffffff; font-size: 12px; padding: 10px; }"
)
- self.task_details.setFont(self.common.fixed_font)
+ self.task_details.setFont(self.global_common.fixed_font)
self.task_details.setAlignment(QtCore.Qt.AlignTop)
self.details_scrollarea = QtWidgets.QScrollArea()
@@ -55,7 +58,7 @@ class TasksWidget(QtWidgets.QWidget):
)
def start(self):
- if self.common.settings.get("update_container"):
+ if self.global_common.settings.get("update_container"):
self.tasks += [PullImageTask, BuildContainerTask]
self.tasks += [ConvertToPixels, ConvertToPDF]
self.next_task()
@@ -67,7 +70,7 @@ class TasksWidget(QtWidgets.QWidget):
self.task_details.setText("")
- self.current_task = self.tasks.pop(0)(self.common)
+ self.current_task = self.tasks.pop(0)(self.global_common, self.common)
self.current_task.update_label.connect(self.update_label)
self.current_task.update_details.connect(self.update_details)
self.current_task.task_finished.connect(self.next_task)
@@ -91,7 +94,7 @@ class TasksWidget(QtWidgets.QWidget):
def all_done(self):
# Save safe PDF
source_filename = f"{self.common.safe_dir.name}/safe-output-compressed.pdf"
- if self.common.settings.get("save"):
+ if self.global_common.settings.get("save"):
dest_filename = self.common.save_filename
else:
# If not saving, then save it to a temp file instead
@@ -107,15 +110,19 @@ class TasksWidget(QtWidgets.QWidget):
)
# Open
- if self.common.settings.get("open"):
- self.common.open_pdf_viewer(dest_filename)
+ if self.global_common.settings.get("open"):
+ self.global_common.open_pdf_viewer(dest_filename)
# Clean up
self.common.pixel_dir.cleanup()
self.common.safe_dir.cleanup()
# Quit
- self.common.app.quit()
+ if platform.system() == "Darwin":
+ # In macOS, just close the window
+ self.close_window.emit()
+ else:
+ self.global_common.app.quit()
def scroll_to_bottom(self, minimum, maximum):
self.details_scrollarea.verticalScrollBar().setValue(maximum)
diff --git a/share/container b/share/container
index 48fbf72..9de7f4c 160000
--- a/share/container
+++ b/share/container
@@ -1 +1 @@
-Subproject commit 48fbf72a24a3c7f5e3f36baf00d4fd137ae7ab22
+Subproject commit 9de7f4cd81e71df0dcea4c4f9a76bee6a27cb234