Order list of PDF viewers and return default application first (Linux).

This commit is contained in:
Ro 2024-06-10 11:45:31 -04:00 committed by Alexis Métaireau
parent f03bc71855
commit 54ab9ce98f
No known key found for this signature in database
GPG key ID: C65C7A89A8FFC56E
2 changed files with 131 additions and 5 deletions

View file

@ -4,6 +4,7 @@ import platform
import shlex import shlex
import subprocess import subprocess
import typing import typing
from collections import OrderedDict
from pathlib import Path from pathlib import Path
from typing import Dict, Optional from typing import Dict, Optional
@ -49,7 +50,7 @@ class DangerzoneGui(DangerzoneCore):
# Preload font # Preload font
self.fixed_font = QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.FixedFont) self.fixed_font = QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.FixedFont)
# Preload list of PDF viewers on computer # Preload ordered list of PDF viewers on computer, starting with default
self.pdf_viewers = self._find_pdf_viewers() self.pdf_viewers = self._find_pdf_viewers()
# Are we done waiting (for Docker Desktop to be installed, or for container to install) # Are we done waiting (for Docker Desktop to be installed, or for container to install)
@ -93,9 +94,22 @@ class DangerzoneGui(DangerzoneCore):
log.info(Fore.YELLOW + "> " + Fore.CYAN + args_str) log.info(Fore.YELLOW + "> " + Fore.CYAN + args_str)
subprocess.Popen(args) subprocess.Popen(args)
def _find_pdf_viewers(self) -> Dict[str, str]: def _find_pdf_viewers(self) -> OrderedDict[str, str]:
pdf_viewers: Dict[str, str] = {} pdf_viewers: OrderedDict[str, str] = OrderedDict()
if platform.system() == "Linux": if platform.system() == "Linux":
# Opportunistically query for default pdf handler
default_pdf_viewer = None
try:
default_pdf_viewer = subprocess.check_output(
["xdg-mime", "query", "default", "application/pdf"]
).decode()
except (FileNotFoundError, subprocess.CalledProcessError) as e:
# Log it and continue
log.info(
"xdg-mime query failed, default PDF handler could not be found."
)
log.debug(f"xdg-mime query failed: {e}")
# Find all .desktop files # Find all .desktop files
for search_path in [ for search_path in [
"/usr/share/applications", "/usr/share/applications",
@ -108,14 +122,26 @@ class DangerzoneGui(DangerzoneCore):
if os.path.splitext(filename)[1] == ".desktop": if os.path.splitext(filename)[1] == ".desktop":
# See which ones can open PDFs # See which ones can open PDFs
desktop_entry = DesktopEntry(full_filename) desktop_entry = DesktopEntry(full_filename)
desktop_entry_name = desktop_entry.getName()
if ( if (
"application/pdf" in desktop_entry.getMimeTypes() "application/pdf" in desktop_entry.getMimeTypes()
and "dangerzone" not in desktop_entry.getName().lower() and "dangerzone" not in desktop_entry_name.lower()
): ):
pdf_viewers[desktop_entry.getName()] = ( pdf_viewers[desktop_entry_name] = (
desktop_entry.getExec() desktop_entry.getExec()
) )
# Put the default entry first
if filename == default_pdf_viewer:
try:
pdf_viewers.move_to_end(
desktop_entry_name, last=False
)
except KeyError as e:
# Should be unreachable
log.error(
f"Problem reordering applications: {e}"
)
except FileNotFoundError: except FileNotFoundError:
pass pass

100
tests/gui/test_logic.py Normal file
View file

@ -0,0 +1,100 @@
import platform
import subprocess
from unittest import mock
import pytest
from dangerzone.gui.logic import DangerzoneGui
if platform.system() == "Linux":
from xdg.DesktopEntry import DesktopEntry
@pytest.mark.skipif(platform.system() != "Linux", reason="Linux-only test")
def test_order_mime_handers() -> None:
"""
Given a default mime handler returned by xdg-mime,
ensure it is the first item available in the list
of compatible applications.
"""
mock_app = mock.MagicMock()
dummy = mock.MagicMock()
mock_desktop = mock.MagicMock(spec=DesktopEntry)
mock_desktop.getMimeTypes.return_value = "application/pdf"
mock_desktop.getExec.side_effect = [
"/usr/bin/madeup-evince",
"/usr/local/bin/madeup-mupdf",
"/usr/local/bin/madeup-libredraw",
]
mock_desktop.getName.side_effect = [
"Evince",
"MuPDF",
"LibreOffice",
]
with mock.patch(
"subprocess.check_output", return_value=b"libreoffice-draw.desktop"
) as mock_default_mime_hander, mock.patch(
"os.listdir",
side_effect=[
["org.gnome.Evince.desktop"],
["org.pwmt.zathura-pdf-mupdf.desktop"],
["libreoffice-draw.desktop"],
],
) as mock_list, mock.patch(
"dangerzone.gui.logic.DesktopEntry", return_value=mock_desktop
):
dz = DangerzoneGui(mock_app, dummy)
mock_default_mime_hander.assert_called_once_with(
["xdg-mime", "query", "default", "application/pdf"]
)
mock_list.assert_called()
assert len(dz.pdf_viewers) == 3
assert dz.pdf_viewers.popitem(last=False)[0] == "LibreOffice"
@pytest.mark.skipif(platform.system() != "Linux", reason="Linux-only test")
def test_mime_handers_succeeds_no_default_found() -> None:
"""
Given a failure to return default mime handler,
ensure compatible applications are still returned.
"""
mock_app = mock.MagicMock()
dummy = mock.MagicMock()
mock_desktop = mock.MagicMock(spec=DesktopEntry)
mock_desktop.getMimeTypes.return_value = "application/pdf"
mock_desktop.getExec.side_effect = [
"/usr/bin/madeup-evince",
"/usr/local/bin/madeup-mupdf",
"/usr/local/bin/madeup-libredraw",
]
mock_desktop.getName.side_effect = [
"Evince",
"MuPDF",
"LibreOffice",
]
with mock.patch(
"subprocess.check_output",
side_effect=subprocess.CalledProcessError(1, "Oh no, xdg-mime error!)"),
) as mock_default_mime_hander, mock.patch(
"os.listdir",
side_effect=[
["org.gnome.Evince.desktop"],
["org.pwmt.zathura-pdf-mupdf.desktop"],
["libreoffice-draw.desktop"],
],
) as mock_list, mock.patch(
"dangerzone.gui.logic.DesktopEntry", return_value=mock_desktop
):
dz = DangerzoneGui(mock_app, dummy)
mock_default_mime_hander.assert_called_once_with(
["xdg-mime", "query", "default", "application/pdf"]
)
mock_list.assert_called()
assert len(dz.pdf_viewers) == 3
assert dz.pdf_viewers.popitem(last=False)[0] == "Evince"