def parse_webenginecore() -> Optional[Versions]: """Parse the QtWebEngineCore library file.""" if version.is_flatpak(): # Flatpak has Qt in /usr/lib/x86_64-linux-gnu, but QtWebEngine in /app/lib. library_path = pathlib.Path("/app/lib") else: library_path = pathlib.Path(QLibraryInfo.location(QLibraryInfo.LibrariesPath)) library_name = sorted(library_path.glob('libQt5WebEngineCore.so*')) if not library_name: log.misc.debug(f"No QtWebEngine .so found in {library_path}") return None else: lib_file = library_name[-1] log.misc.debug(f"QtWebEngine .so found at {lib_file}") try: with lib_file.open('rb') as f: versions = _parse_from_file(f) log.misc.debug(f"Got versions from ELF: {versions}") return versions except ParseError as e: log.misc.debug(f"Failed to parse ELF: {e}", exc_info=True) return None
def _on_error(self, error: QProcess.ProcessError) -> None: """Show a message if there was an error while spawning.""" if error == QProcess.Crashed and not utils.is_windows: # Already handled via ExitStatus in _on_finished return what = f"{self.what} {self.cmd!r}" error_descriptions = { QProcess.FailedToStart: f"{what.capitalize()} failed to start", QProcess.Crashed: f"{what.capitalize()} crashed", QProcess.Timedout: f"{what.capitalize()} timed out", QProcess.WriteError: f"Write error for {what}", QProcess.ReadError: f"Read error for {what}", } error_string = self._proc.errorString() msg = ': '.join([error_descriptions[error], error_string]) # We can't get some kind of error code from Qt... # https://bugreports.qt.io/browse/QTBUG-44769 # However, it looks like those strings aren't actually translated? known_errors = ['No such file or directory', 'Permission denied'] if (': ' in error_string and # pragma: no branch error_string.split(': ', maxsplit=1)[1] in known_errors): msg += f'\nHint: Make sure {self.cmd!r} exists and is executable' if version.is_flatpak(): msg += ' inside the Flatpak container' message.error(msg)
def _init_data(args: Optional[argparse.Namespace]) -> None: """Initialize the location for data.""" typ = QStandardPaths.AppDataLocation path = _from_args(typ, args) if path is None: if utils.is_windows: app_data_path = _writable_location(typ) # same location as config path = os.path.join(app_data_path, 'data') elif sys.platform.startswith('haiku'): # HaikuOS returns an empty value for AppDataLocation config_path = _writable_location(QStandardPaths.ConfigLocation) path = os.path.join(config_path, 'data') else: path = _writable_location(typ) _create(path) _locations[_Location.data] = path # system_data _locations.pop(_Location.system_data, None) # Remove old state if utils.is_linux: prefix = '/app' if version.is_flatpak() else '/usr' path = f'{prefix}/share/{APPNAME}' if os.path.exists(path): _locations[_Location.system_data] = path
def _on_error(self, error: QProcess.ProcessError) -> None: """Show a message if there was an error while spawning.""" if error == QProcess.Crashed and not utils.is_windows: # Already handled via ExitStatus in _on_finished return what = f"{self.what} {self.cmd!r}" error_descriptions = { QProcess.FailedToStart: f"{what.capitalize()} failed to start", QProcess.Crashed: f"{what.capitalize()} crashed", QProcess.Timedout: f"{what.capitalize()} timed out", QProcess.WriteError: f"Write error for {what}", QProcess.ReadError: f"Read error for {what}", } error_string = self._proc.errorString() msg = ': '.join([error_descriptions[error], error_string]) # We can't get some kind of error code from Qt... # https://bugreports.qt.io/browse/QTBUG-44769 # but we pre-resolve the executable in Python, which also checks if it's # runnable. if self.resolved_cmd is None: # pragma: no branch msg += f'\nHint: Make sure {self.cmd!r} exists and is executable' if version.is_flatpak(): msg += ' inside the Flatpak container' message.error(msg)
def open_file(filename: str, cmdline: str = None) -> None: """Open the given file. If cmdline is not given, downloads.open_dispatcher is used. If open_dispatcher is unset, the system's default application is used. Args: filename: The filename to open. cmdline: The command to use as string. A `{}` is expanded to the filename. None means to use the system's default application or `downloads.open_dispatcher` if set. If no `{}` is found, the filename is appended to the cmdline. """ # Import late to avoid circular imports: # - usertypes -> utils -> guiprocess -> message -> usertypes # - usertypes -> utils -> config -> configdata -> configtypes -> # cmdutils -> command -> message -> usertypes from qutebrowser.config import config from qutebrowser.misc import guiprocess from qutebrowser.utils import version, message # the default program to open downloads with - will be empty string # if we want to use the default override = config.val.downloads.open_dispatcher if version.is_flatpak(): if cmdline: message.error("Cannot spawn download dispatcher from sandbox") return if override: message.warning("Ignoring download dispatcher from config in " "sandbox environment") override = None # precedence order: cmdline > downloads.open_dispatcher > openUrl if cmdline is None and not override: log.misc.debug("Opening {} with the system application" .format(filename)) url = QUrl.fromLocalFile(filename) QDesktopServices.openUrl(url) return if cmdline is None and override: cmdline = override assert cmdline is not None cmd, *args = shlex.split(cmdline) args = [arg.replace('{}', filename) for arg in args] if '{}' not in cmdline: args.append(filename) log.misc.debug("Opening {} with {}" .format(filename, [cmd] + args)) proc = guiprocess.GUIProcess(what='open-file') proc.start_detached(cmd, args)
def _webengine_locales_path() -> pathlib.Path: """Get the path of the QtWebEngine locales.""" if version.is_flatpak(): # TranslationsPath is /usr/translations on Flatpak, i.e. the path for qtbase, # not QtWebEngine. base = pathlib.Path('/app/translations') else: base = pathlib.Path( QLibraryInfo.location(QLibraryInfo.TranslationsPath)) return base / 'qtwebengine_locales'
def _init_runtime(args: Optional[argparse.Namespace]) -> None: """Initialize location for runtime data.""" if utils.is_mac or utils.is_windows: # RuntimeLocation is a weird path on macOS and Windows. typ = QStandardPaths.TempLocation else: typ = QStandardPaths.RuntimeLocation path = _from_args(typ, args) if path is None: try: path = _writable_location(typ) except EmptyValueError: # Fall back to TempLocation when RuntimeLocation is misconfigured if typ == QStandardPaths.TempLocation: raise path = _writable_location( # pragma: no cover QStandardPaths.TempLocation) # This is generic, but per-user. # _writable_location makes sure we have a qutebrowser-specific subdir. # # For TempLocation: # "The returned value might be application-specific, shared among # other applications for this user, or even system-wide." # # Unfortunately this path could get too long for sockets (which have a # maximum length of 104 chars), so we don't add the username here... if version.is_flatpak(): # We need a path like # /run/user/1000/app/org.qutebrowser.qutebrowser rather than # /run/user/1000/qutebrowser on Flatpak, since that's bind-mounted # in a way that it is accessible by any other qutebrowser # instances. *parts, app_name = os.path.split(path) assert app_name == APPNAME, app_name flatpak_id = version.flatpak_id() assert flatpak_id is not None path = os.path.join(*parts, 'app', flatpak_id) _create(path) _locations[_Location.runtime] = path
def _apply_platform_markers(config, item): """Apply a skip marker to a given item.""" markers = [ ('posix', pytest.mark.skipif, not utils.is_posix, "Requires a POSIX os"), ('windows', pytest.mark.skipif, not utils.is_windows, "Requires Windows"), ('linux', pytest.mark.skipif, not utils.is_linux, "Requires Linux"), ('mac', pytest.mark.skipif, not utils.is_mac, "Requires macOS"), ('not_mac', pytest.mark.skipif, utils.is_mac, "Skipped on macOS"), ('not_frozen', pytest.mark.skipif, getattr(sys, 'frozen', False), "Can't be run when frozen"), ('not_flatpak', pytest.mark.skipif, version.is_flatpak(), "Can't be run with Flatpak"), ('frozen', pytest.mark.skipif, not getattr(sys, 'frozen', False), "Can only run when frozen"), ('ci', pytest.mark.skipif, not testutils.ON_CI, "Only runs on CI."), ('no_ci', pytest.mark.skipif, testutils.ON_CI, "Skipped on CI."), ('unicode_locale', pytest.mark.skipif, sys.getfilesystemencoding() == 'ascii', "Skipped because of ASCII locale"), ] for searched_marker, new_marker_kind, condition, default_reason in markers: marker = item.get_closest_marker(searched_marker) if not marker or not condition: continue if 'reason' in marker.kwargs: reason = '{}: {}'.format(default_reason, marker.kwargs['reason']) del marker.kwargs['reason'] else: reason = default_reason + '.' new_marker = new_marker_kind(condition, *marker.args, reason=reason, **marker.kwargs) item.add_marker(new_marker)
def parse_webenginecore() -> Optional[Versions]: """Parse the QtWebEngineCore library file.""" if version.is_flatpak(): # Flatpak has Qt in /usr/lib/x86_64-linux-gnu, but QtWebEngine in /app/lib. library_path = pathlib.Path("/app/lib") else: library_path = pathlib.Path( QLibraryInfo.location(QLibraryInfo.LibrariesPath)) # PyQt bundles those files with a .5 suffix lib_file = library_path / 'libQt5WebEngineCore.so.5' if not lib_file.exists(): return None try: with lib_file.open('rb') as f: versions = _parse_from_file(f) log.misc.debug(f"Got versions from ELF: {versions}") return versions except ParseError as e: log.misc.debug(f"Failed to parse ELF: {e}", exc_info=True) return None
def test_is_flatpak(monkeypatch, distribution, expected): monkeypatch.setattr(version, "distribution", lambda: distribution) assert version.is_flatpak() == expected
class TestSocketName: WINDOWS_TESTS = [ (None, 'qutebrowser-testusername'), ('/x', 'qutebrowser-testusername-{}'.format(md5('/x'))), ] @pytest.fixture(autouse=True) def patch_user(self, monkeypatch): monkeypatch.setattr(ipc.getpass, 'getuser', lambda: 'testusername') @pytest.mark.parametrize('basedir, expected', WINDOWS_TESTS) @pytest.mark.windows def test_windows(self, basedir, expected): socketname = ipc._get_socketname(basedir) assert socketname == expected @pytest.mark.parametrize('basedir, expected', WINDOWS_TESTS) def test_windows_on_posix(self, basedir, expected): socketname = ipc._get_socketname_windows(basedir) assert socketname == expected def test_windows_broken_getpass(self, monkeypatch): def _fake_username(): raise ImportError monkeypatch.setattr(ipc.getpass, 'getuser', _fake_username) with pytest.raises(ipc.Error, match='USERNAME'): ipc._get_socketname_windows(basedir=None) @pytest.mark.mac @pytest.mark.parametrize('basedir, expected', [ (None, 'i-{}'.format(md5('testusername'))), ('/x', 'i-{}'.format(md5('testusername-/x'))), ]) def test_mac(self, basedir, expected): socketname = ipc._get_socketname(basedir) parts = socketname.split(os.sep) assert parts[-2] == 'qutebrowser' assert parts[-1] == expected @pytest.mark.linux @pytest.mark.not_flatpak @pytest.mark.parametrize('basedir, expected', [ (None, 'ipc-{}'.format(md5('testusername'))), ('/x', 'ipc-{}'.format(md5('testusername-/x'))), ]) def test_linux(self, basedir, fake_runtime_dir, expected): socketname = ipc._get_socketname(basedir) expected_path = str(fake_runtime_dir / 'qutebrowser' / expected) assert socketname == expected_path # We can't use the fake_flatpak fixture here, because it conflicts with # fake_runtime_dir... @pytest.mark.linux @pytest.mark.parametrize('basedir, expected', [ (None, 'ipc-{}'.format(md5('testusername'))), ('/x', 'ipc-{}'.format(md5('testusername-/x'))), ]) @pytest.mark.parametrize('has_flatpak_id', [True, False]) @pytest.mark.skipif(not version.is_flatpak(), reason="Needs Flatpak") def test_flatpak(self, monkeypatch, fake_runtime_dir, basedir, expected, has_flatpak_id): if not has_flatpak_id: # Simulate an older Flatpak version monkeypatch.delenv('FLATPAK_ID', raising=False) socketname = ipc._get_socketname(basedir) expected_path = str(fake_runtime_dir / 'app' / 'org.qutebrowser.qutebrowser' / expected) assert socketname == expected_path def test_other_unix(self): """Fake test for POSIX systems which aren't Linux/macOS. We probably would adjust the code first to make it work on that platform. """ if utils.is_windows: pass elif utils.is_mac: pass elif utils.is_linux: pass else: raise Exception("Unexpected platform!")
def fake_flatpak(monkeypatch): app_id = 'org.qutebrowser.qutebrowser' monkeypatch.setenv('FLATPAK_ID', app_id) assert version.is_flatpak()