summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Bruhin <me@the-compiler.org>2020-05-29 20:53:21 +0200
committerFlorian Bruhin <me@the-compiler.org>2020-05-29 20:53:21 +0200
commit6aeb3d81859a53b3ac779b881d6ab0e8035939fa (patch)
treeb2c9da3138ed43825744bf1fbed268bf14632112
parent6356e80ecc3c94c1f2f4098105d3bdf4089f3c06 (diff)
downloadqutebrowser-6aeb3d81859a53b3ac779b881d6ab0e8035939fa.tar.gz
qutebrowser-6aeb3d81859a53b3ac779b881d6ab0e8035939fa.zip
Refactor how we get OpenGL info
This allows us to get the version string in addition to the vendor. We also show that version string in the version info output. See #5313
-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