From a343c3d68b7e050d251a40c17ca74df41441a949 Mon Sep 17 00:00:00 2001 From: ZFordDev Date: Fri, 28 Aug 2026 03:55:32 +1000 Subject: [PATCH 1/3] fix: disable hardware acceleration on legacy Windows hardware Detect a low-spec profile (<=4 logical cores and <=8 GiB RAM) and disable hardware acceleration before QApplication construction. This sidesteps the GPU/DWM recomposition loop that causes the translucent HUD to flicker and steal focus on older Windows machines. Closes #19. --- CHANGELOG.md | 4 +++- src/app.py | 6 ++++++ src/core/compat.py | 54 ++++++++++++++++++++++++++++++++++++++++++++++ tests/test_core.py | 25 +++++++++++++++++++++ 4 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 src/core/compat.py diff --git a/CHANGELOG.md b/CHANGELOG.md index ebc44a4..88c2c21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,12 +6,14 @@ > **Release Cadence Update:** Planned releases are shifting from weekly to fortnightly (targeting Wednesdays) as we transition toward a monthly release cycle. Critical fixes will still be released immediately as emergency patches. ### Plan -- Aggressive window refresh loop and focus-stealing on Windows [#19] ### Added ### Fixed +- Aggressive window refresh loop and focus-stealing on Windows [#19] +- Legacy hardware (≤4 cores and ≤8 GiB RAM) now falls back to software rendering, sidestepping the GPU/DWM recomposition loop behind the flicker + ### Docs ## v1.0.1 (2026-08-22) diff --git a/src/app.py b/src/app.py index 2ebe91c..3df0a02 100644 --- a/src/app.py +++ b/src/app.py @@ -2,13 +2,19 @@ import sys +from PyQt6.QtCore import Qt from PyQt6.QtWidgets import QApplication +from src.core.compat import is_low_spec from src.ui.hud import GlassHUD from src.ui.tray import TrayManager def main() -> int: + # Must be set before QApplication is constructed; on legacy hardware the + # GPU/DWM recomposition of the translucent HUD causes refresh/focus loops. + if sys.platform == "win32" and is_low_spec(): + QApplication.setAttribute(Qt.ApplicationAttribute.AA_DisableHardwareAcceleration) app = QApplication(sys.argv) app.setApplicationName("Glint") app.setOrganizationName("ZFordDev") diff --git a/src/core/compat.py b/src/core/compat.py new file mode 100644 index 0000000..03a0bf2 --- /dev/null +++ b/src/core/compat.py @@ -0,0 +1,54 @@ +"""Hardware capability detection for rendering fallbacks. + +Certain subsets of legacy Windows hardware trigger a DWM recomposition +loop when the HUD's translucent, painter-rendered surface is refreshed on +the GUI thread. Rather than forcing hardware acceleration off everywhere, +we only degrade the renderer when the machine clearly falls below a +generation threshold. +""" + +from __future__ import annotations + +import logging + +import psutil + +logger = logging.getLogger(__name__) + +# A legacy profile: few logical cores plus modest physical memory. These +# thresholds are intentionally conservative so modern machines keep +# hardware-accelerated rendering and only clearly dated hardware falls back. +LOW_CORE_THRESHOLD = 4 +LOW_RAM_THRESHOLD_BYTES = 8 * 1024**3 # 8 GiB + + +def _logical_cores() -> int | None: + try: + count = psutil.cpu_count(logical=True) + return count if count else None + except (OSError, RuntimeError): + return None + + +def _total_memory() -> int | None: + try: + return int(psutil.virtual_memory().total) + except (OSError, RuntimeError): + return None + + +def is_low_spec() -> bool: + """Return True when the host is likely GPU-limited legacy hardware.""" + cores = _logical_cores() + memory = _total_memory() + low_cores = cores is not None and cores <= LOW_CORE_THRESHOLD + low_memory = memory is not None and memory <= LOW_RAM_THRESHOLD_BYTES + if low_cores and low_memory: + logger.debug( + "Low-spec profile detected (cores=%s, ram=%s GiB); disabling hardware acceleration", + cores, + None if memory is None else round(memory / 1024**3, 1), + ) + return True + logger.debug("Rendering profile: cores=%s, ram=%s", cores, memory) + return False diff --git a/tests/test_core.py b/tests/test_core.py index 899e33f..c37ed39 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -165,3 +165,28 @@ def test_get_all_skips_heavy_probes_after_stop(): def test_bundled_default_theme_loads(): assert load_theme()["colors"]["text"] + + +def test_low_spec_falls_back_when_cores_and_ram_are_legacy(monkeypatch): + from src.core import compat + + monkeypatch.setattr(compat, "_logical_cores", lambda: 4) + monkeypatch.setattr(compat, "_total_memory", lambda: 6 * 1024**3) + assert compat.is_low_spec() is True + + +def test_low_spec_requires_both_undersized_metrics(monkeypatch): + from src.core import compat + + # Plenty of RAM but few cores -> not legacy enough to degrade rendering. + monkeypatch.setattr(compat, "_logical_cores", lambda: 2) + monkeypatch.setattr(compat, "_total_memory", lambda: 32 * 1024**3) + assert compat.is_low_spec() is False + + +def test_low_spec_tolerates_missing_probes(monkeypatch): + from src.core import compat + + monkeypatch.setattr(compat, "_logical_cores", lambda: None) + monkeypatch.setattr(compat, "_total_memory", lambda: None) + assert compat.is_low_spec() is False From 01413da30b4e2b2d0ad8988b6dd1fdaf28cb8550 Mon Sep 17 00:00:00 2001 From: ZFordDev Date: Fri, 28 Aug 2026 04:03:16 +1000 Subject: [PATCH 2/3] fix: use AA_UseSoftwareOpenGL and enable fallback on all platforms Qt 6 removed AA_DisableHardwareAcceleration; the fallback used a nonexistent attribute and would have crashed low-spec hosts at startup. Switch to AA_UseSoftwareOpenGL and apply the low-spec software-rendering fallback on every platform for consistent behavior independent of the compositor or graphics stack. Factor the decision into a testable helper. --- CHANGELOG.md | 3 ++- src/app.py | 19 +++++++++++++++---- tests/test_ui.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88c2c21..d7c717f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,8 @@ ### Fixed - Aggressive window refresh loop and focus-stealing on Windows [#19] -- Legacy hardware (≤4 cores and ≤8 GiB RAM) now falls back to software rendering, sidestepping the GPU/DWM recomposition loop behind the flicker +- Legacy hardware (≤4 cores and ≤8 GiB RAM) falls back to software rendering on every platform, sidestepping the GPU/DWM recomposition loop behind the flicker +- Software-rendering fallback uses Qt's `AA_UseSoftwareOpenGL` (Qt 6 removed `AA_DisableHardwareAcceleration`) ### Docs diff --git a/src/app.py b/src/app.py index 3df0a02..67baa79 100644 --- a/src/app.py +++ b/src/app.py @@ -10,11 +10,22 @@ from src.ui.tray import TrayManager +def _maybe_disable_hardware_acceleration() -> None: + """Degrade the renderer on low-spec hosts before QApplication exists. + + Must be called before ``QApplication`` is constructed, because on legacy + hardware the GPU/DWM recomposition of the translucent HUD causes + refresh/focus loops. Applied on every platform so behavior is consistent + regardless of the compositor or graphics stack in use. + """ + if is_low_spec(): + # Qt 6 removed AA_DisableHardwareAcceleration; AA_UseSoftwareOpenGL is + # its replacement and routes rendering through the software rasterizer. + QApplication.setAttribute(Qt.ApplicationAttribute.AA_UseSoftwareOpenGL) + + def main() -> int: - # Must be set before QApplication is constructed; on legacy hardware the - # GPU/DWM recomposition of the translucent HUD causes refresh/focus loops. - if sys.platform == "win32" and is_low_spec(): - QApplication.setAttribute(Qt.ApplicationAttribute.AA_DisableHardwareAcceleration) + _maybe_disable_hardware_acceleration() app = QApplication(sys.argv) app.setApplicationName("Glint") app.setOrganizationName("ZFordDev") diff --git a/tests/test_ui.py b/tests/test_ui.py index cb18a4e..8b17783 100644 --- a/tests/test_ui.py +++ b/tests/test_ui.py @@ -77,3 +77,31 @@ def test_autostart_content_quotes_executable_paths(monkeypatch): monkeypatch.setattr("src.ui.tray.platform.system", lambda: "Darwin") assert f"{spaced}" in TrayManager._startup_content() + + +def test_hardware_acceleration_disabled_when_low_spec(monkeypatch): + import src.app as app_module + + calls = [] + monkeypatch.setattr(app_module, "is_low_spec", lambda: True) + monkeypatch.setattr( + app_module.QApplication, + "setAttribute", + staticmethod(lambda attribute: calls.append(attribute)), + ) + app_module._maybe_disable_hardware_acceleration() + assert calls == [app_module.Qt.ApplicationAttribute.AA_UseSoftwareOpenGL] + + +def test_hardware_acceleration_kept_when_not_low_spec(monkeypatch): + import src.app as app_module + + calls = [] + monkeypatch.setattr(app_module, "is_low_spec", lambda: False) + monkeypatch.setattr( + app_module.QApplication, + "setAttribute", + staticmethod(lambda attribute: calls.append(attribute)), + ) + app_module._maybe_disable_hardware_acceleration() + assert calls == [] From 4f709a04e921886731b7fe416df68a5111c8687b Mon Sep 17 00:00:00 2001 From: ZFordDev Date: Fri, 28 Aug 2026 04:05:34 +1000 Subject: [PATCH 3/3] version bump --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index abc582b..65f545d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "glint-monitor" -version = "1.0.1" +version = "1.0.2" description = "A Modern Painter-Rendered Desktop System Monitor" readme = "README.md" requires-python = ">=3.10"