Esempio n. 1
0
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
Esempio n. 2
0
    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)
Esempio n. 3
0
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
Esempio n. 4
0
    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)
Esempio n. 5
0
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)
Esempio n. 6
0
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'
Esempio n. 7
0
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
Esempio n. 8
0
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)
Esempio n. 9
0
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
Esempio n. 10
0
def test_is_flatpak(monkeypatch, distribution, expected):
    monkeypatch.setattr(version, "distribution", lambda: distribution)
    assert version.is_flatpak() == expected
Esempio n. 11
0
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!")
Esempio n. 12
0
def fake_flatpak(monkeypatch):
    app_id = 'org.qutebrowser.qutebrowser'
    monkeypatch.setenv('FLATPAK_ID', app_id)
    assert version.is_flatpak()