summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--qutebrowser/misc/backendproblem.py16
-rw-r--r--qutebrowser/utils/utils.py15
-rw-r--r--qutebrowser/utils/version.py70
-rw-r--r--tests/conftest.py6
-rw-r--r--tests/unit/utils/test_version.py82
5 files changed, 159 insertions, 30 deletions
diff --git a/qutebrowser/misc/backendproblem.py b/qutebrowser/misc/backendproblem.py
index 2ed273547..73044cd99 100644
--- a/qutebrowser/misc/backendproblem.py
+++ b/qutebrowser/misc/backendproblem.py
@@ -23,8 +23,6 @@ import os
import sys
import functools
import html
-import ctypes
-import ctypes.util
import enum
import shutil
import typing
@@ -201,19 +199,10 @@ class _BackendProblemChecker:
def _nvidia_shader_workaround(self) -> None:
"""Work around QOpenGLShaderProgram issues.
- NOTE: This needs to be called before _handle_nouveau_graphics, or some
- setups will segfault in version.opengl_vendor().
-
See https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826
"""
self._assert_backend(usertypes.Backend.QtWebEngine)
-
- if os.environ.get('QUTE_SKIP_LIBGL_WORKAROUND'):
- return
-
- libgl = ctypes.util.find_library("GL")
- if libgl is not None:
- ctypes.CDLL(libgl, mode=ctypes.RTLD_GLOBAL)
+ utils.libgl_workaround()
def _handle_nouveau_graphics(self) -> None:
"""Force software rendering when using the Nouveau driver.
@@ -231,7 +220,8 @@ class _BackendProblemChecker:
if qtutils.version_check('5.10', compiled=False):
return
- if version.opengl_vendor() != 'nouveau':
+ opengl_info = version.opengl_info()
+ if opengl_info is None or opengl_info.vendor != 'nouveau':
return
if (os.environ.get('LIBGL_ALWAYS_SOFTWARE') == '1' or
diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py
index 368cb0ab6..39d46add8 100644
--- a/qutebrowser/utils/utils.py
+++ b/qutebrowser/utils/utils.py
@@ -36,6 +36,8 @@ import shlex
import glob
import mimetypes
import typing
+import ctypes
+import ctypes.util
from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QColor, QClipboard, QDesktopServices
@@ -776,3 +778,16 @@ def ceil_log(number: int, base: int) -> int:
result += 1
accum *= base
return result
+
+
+def libgl_workaround():
+ """Work around QOpenGLShaderProgram issues, especially for Nvidia.
+
+ See https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826
+ """
+ if os.environ.get('QUTE_SKIP_LIBGL_WORKAROUND'):
+ return
+
+ libgl = ctypes.util.find_library("GL")
+ if libgl is not None:
+ ctypes.CDLL(libgl, mode=ctypes.RTLD_GLOBAL)
diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py
index 1ad8b22cf..8dc4ec593 100644
--- a/qutebrowser/utils/version.py
+++ b/qutebrowser/utils/version.py
@@ -31,6 +31,7 @@ import enum
import datetime
import getpass
import typing
+import functools
import attr
import pkg_resources
@@ -442,8 +443,8 @@ def version() -> str:
if qapp:
style = qapp.style()
lines.append('Style: {}'.format(style.metaObject().className()))
- platform_name = qapp.platformName()
- lines.append('Platform plugin: {}'.format(platform_name))
+ lines.append('Platform plugin: {}'.format(qapp.platformName()))
+ lines.append('OpenGL: {}'.format(opengl_info()))
importpath = os.path.dirname(os.path.abspath(qutebrowser.__file__))
@@ -487,7 +488,55 @@ def version() -> str:
return '\n'.join(lines)
-def opengl_vendor() -> typing.Optional[str]: # pragma: no cover
+@attr.s
+class OpenGLInfo:
+
+ """Information about the OpenGL setup in use."""
+
+ # If we're using OpenGL ES. If so, no further information is available.
+ gles = attr.ib(False) # type: bool
+
+ # The name of the vendor. Examples:
+ # - nouveau
+ # - "Intel Open Source Technology Center", "Intel", "Intel Inc."
+ vendor = attr.ib(None) # type: typing.Optional[str]
+
+ # The OpenGL version as a string. See tests for examples.
+ version_str = attr.ib(None) # type: typing.Optional[str]
+
+ # The parsed version as a (major, minor) tuple of ints
+ version = attr.ib(None) # type: typing.Optional[typing.Tuple[int, ...]]
+
+ # The vendor specific information following the version number
+ vendor_specific = attr.ib(None) # type: typing.Optional[str]
+
+ def __str__(self) -> str:
+ if self.gles:
+ return 'OpenGL ES'
+ return '{}, {}'.format(self.vendor, self.version_str)
+
+ @classmethod
+ def parse(cls, *, vendor: str, version: str) -> 'OpenGLInfo':
+ if ' ' not in version:
+ log.misc.warning("Failed to parse OpenGL version (missing space): "
+ "{}".format(version))
+ return cls(vendor=vendor, version_str=version)
+
+ num_str, vendor_specific = version.split(' ', maxsplit=1)
+
+ try:
+ parsed_version = tuple(int(i) for i in num_str.split('.'))
+ except ValueError:
+ log.misc.warning("Failed to parse OpenGL version (parsing int): "
+ "{}".format(version))
+ return cls(vendor=vendor, version_str=version)
+
+ return cls(vendor=vendor, version_str=version,
+ version=parsed_version, vendor_specific=vendor_specific)
+
+
+@functools.lru_cache(maxsize=1)
+def opengl_info() -> typing.Optional[OpenGLInfo]: # pragma: no cover
"""Get the OpenGL vendor used.
This returns a string such as 'nouveau' or
@@ -496,10 +545,14 @@ def opengl_vendor() -> typing.Optional[str]: # pragma: no cover
"""
assert QApplication.instance()
- override = os.environ.get('QUTE_FAKE_OPENGL_VENDOR')
+ # Some setups can segfault in here if we don't do this.
+ utils.libgl_workaround()
+
+ override = os.environ.get('QUTE_FAKE_OPENGL')
if override is not None:
log.init.debug("Using override {}".format(override))
- return override
+ vendor, version = override.split(', ', maxsplit=1)
+ return OpenGLInfo.parse(vendor=vendor, version=version)
old_context = typing.cast(typing.Optional[QOpenGLContext],
QOpenGLContext.currentContext())
@@ -522,7 +575,7 @@ def opengl_vendor() -> typing.Optional[str]: # pragma: no cover
try:
if ctx.isOpenGLES():
# Can't use versionFunctions there
- return None
+ return OpenGLInfo(gles=True)
vp = QOpenGLVersionProfile()
vp.setVersion(2, 0)
@@ -537,7 +590,10 @@ def opengl_vendor() -> typing.Optional[str]: # pragma: no cover
log.init.debug("Getting version functions failed!")
return None
- return vf.glGetString(vf.GL_VENDOR)
+ vendor = vf.glGetString(vf.GL_VENDOR)
+ version = vf.glGetString(vf.GL_VERSION)
+
+ return OpenGLInfo.parse(vendor=vendor, version=version)
finally:
ctx.doneCurrent()
if old_context and old_surface:
diff --git a/tests/conftest.py b/tests/conftest.py
index c6b6c2efc..e698bde74 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -25,8 +25,6 @@ import os
import sys
import warnings
import pathlib
-import ctypes
-import ctypes.util
import pytest
import hypothesis
@@ -258,9 +256,7 @@ def set_backend(monkeypatch, request):
@pytest.fixture(autouse=True, scope='session')
def apply_libgl_workaround():
"""Make sure we load libGL early so QtWebEngine tests run properly."""
- libgl = ctypes.util.find_library("GL")
- if libgl is not None:
- ctypes.CDLL(libgl, mode=ctypes.RTLD_GLOBAL)
+ utils.libgl_workaround()
@pytest.fixture(autouse=True)
diff --git a/tests/unit/utils/test_version.py b/tests/unit/utils/test_version.py
index 0a3c5e4aa..903585b62 100644
--- a/tests/unit/utils/test_version.py
+++ b/tests/unit/utils/test_version.py
@@ -35,6 +35,8 @@ import datetime
import attr
import pkg_resources
import pytest
+import hypothesis
+import hypothesis.strategies
import qutebrowser
from qutebrowser.config import config
@@ -956,11 +958,15 @@ def test_version_output(params, stubs, monkeypatch, config_stub):
'config.instance.yaml_loaded': params.autoconfig_loaded,
}
+ version.opengl_info.cache_clear()
+ monkeypatch.setenv('QUTE_FAKE_OPENGL', 'VENDOR, 1.0 VERSION')
+
substitutions = {
'git_commit': '\nGit commit: GIT COMMIT' if params.git_commit else '',
'style': '\nStyle: STYLE' if params.qapp else '',
'platform_plugin': ('\nPlatform plugin: PLATFORM' if params.qapp
else ''),
+ 'opengl': '\nOpenGL: VENDOR, 1.0 VERSION' if params.qapp else '',
'qt': 'QT VERSION',
'frozen': str(params.frozen),
'import_path': import_path,
@@ -1026,7 +1032,7 @@ def test_version_output(params, stubs, monkeypatch, config_stub):
pdf.js: PDFJS VERSION
sqlite: SQLITE VERSION
QtNetwork SSL: {ssl}
- {style}{platform_plugin}
+ {style}{platform_plugin}{opengl}
Platform: PLATFORM, ARCHITECTURE{linuxdist}
Frozen: {frozen}
Imported from {import_path}
@@ -1045,10 +1051,76 @@ def test_version_output(params, stubs, monkeypatch, config_stub):
assert version.version() == expected
-def test_opengl_vendor(qapp):
- """Simply call version.opengl_vendor() and see if it doesn't crash."""
- pytest.importorskip("PyQt5.QtOpenGL")
- return version.opengl_vendor()
+class TestOpenGLInfo:
+
+ @pytest.fixture(autouse=True)
+ def cache_clear(self):
+ """Clear the lru_cache between tests."""
+ version.opengl_info.cache_clear()
+
+ def test_func(self, qapp):
+ """Simply call version.opengl_info() and see if it doesn't crash."""
+ pytest.importorskip("PyQt5.QtOpenGL")
+ version.opengl_info()
+
+ def test_func_fake(self, qapp, monkeypatch):
+ monkeypatch.setenv('QUTE_FAKE_OPENGL', 'Outtel Inc., 3.0 Messiah 20.0')
+ info = version.opengl_info()
+ assert info.vendor == 'Outtel Inc.'
+ assert info.version_str == '3.0 Messiah 20.0'
+ assert info.version == (3, 0)
+ assert info.vendor_specific == 'Messiah 20.0'
+
+ @pytest.mark.parametrize('version_str, reason', [
+ ('blah', 'missing space'),
+ ('2,x blah', 'parsing int'),
+ ])
+ def test_parse_invalid(self, caplog, version_str, reason):
+ with caplog.at_level(logging.WARNING):
+ info = version.OpenGLInfo.parse(vendor="vendor",
+ version=version_str)
+
+ assert info.version is None
+ assert info.vendor_specific is None
+ assert info.vendor == 'vendor'
+ assert info.version_str == version_str
+
+ msg = "Failed to parse OpenGL version ({}): {}".format(
+ reason, version_str)
+ assert caplog.messages == [msg]
+
+ @hypothesis.given(vendor=hypothesis.strategies.text(),
+ version_str=hypothesis.strategies.text())
+ def test_parse_hypothesis(self, caplog, vendor, version_str):
+ with caplog.at_level(logging.WARNING):
+ info = version.OpenGLInfo.parse(vendor=vendor, version=version_str)
+
+ assert info.vendor == vendor
+ assert info.version_str == version_str
+ assert vendor in str(info)
+ assert version_str in str(info)
+
+ if info.version is not None:
+ reconstructed = ' '.join(['.'.join(str(part)
+ for part in info.version),
+ info.vendor_specific])
+ assert reconstructed == info.version_str
+
+ @pytest.mark.parametrize('version_str, expected', [
+ ("2.1 INTEL-10.36.26", (2, 1)),
+ ("4.6 (Compatibility Profile) Mesa 20.0.7", (4, 6)),
+ ("3.0 Mesa 20.0.7", (3, 0)),
+ ("3.0 Mesa 20.0.6", (3, 0)),
+ # Not from the wild, but can happen according to standards
+ ("3.0.2 Mesa 20.0.6", (3, 0, 2)),
+ ])
+ def test_version(self, version_str, expected):
+ info = version.OpenGLInfo.parse(vendor='vendor', version=version_str)
+ assert info.version == expected
+
+ def test_str_gles(self):
+ info = version.OpenGLInfo(gles=True)
+ assert str(info) == 'OpenGL ES'
@pytest.fixture