diff --git a/src/axidev_osk/windows/builder.py b/src/axidev_osk/windows/builder.py index eefaf85..da2e23c 100644 --- a/src/axidev_osk/windows/builder.py +++ b/src/axidev_osk/windows/builder.py @@ -4,9 +4,10 @@ from PySide6.QtCore import QSize from PySide6.QtGui import QCloseEvent, QShowEvent -from PySide6.QtWidgets import QMainWindow, QVBoxLayout, QWidget +from PySide6.QtWidgets import QGraphicsOpacityEffect, QMainWindow, QVBoxLayout, QWidget from ..config.models import WindowConfig +from ..platform.overlay import OverlayBackend from ..runtime.context import Context from ..runtime.events import WindowCloseRequested from .chrome import install_overlay_chrome @@ -42,10 +43,10 @@ def __init__(self, config: WindowConfig, context: Context, parent: QWidget | Non self._config = config self._context = context self._quit_controller_managed = False + self._layer_shell_opacity_effect: QGraphicsOpacityEffect | None = None self.setProperty("componentType", "window") self.setProperty("componentId", config.id) self.setWindowTitle(config.title) - self.setWindowOpacity(config.opacity) if config.overlay.always_on_top: self._overlay = configure_always_on_top_window(self, config=config.overlay.config) else: @@ -63,8 +64,21 @@ def __init__(self, config: WindowConfig, context: Context, parent: QWidget | Non on_resize=self._overlay.resize_by, ) self.setCentralWidget(central) + if self._overlay.backend == OverlayBackend.WAYLAND_LAYER_SHELL: + self._layer_shell_opacity_effect = QGraphicsOpacityEffect(central) + central.setGraphicsEffect(self._layer_shell_opacity_effect) + self.setWindowOpacity(config.opacity) self.apply_startup_size(minimum_size=config.surface.minimum_size) + def setWindowOpacity(self, opacity: float) -> None: # noqa: N802 + """Apply opacity through rendered pixels when layer-shell cannot do it.""" + + if self._layer_shell_opacity_effect is None: + super().setWindowOpacity(opacity) + return + super().setWindowOpacity(1.0) + self._layer_shell_opacity_effect.setOpacity(opacity) + @property def window_id(self) -> str: """Return this window's deterministic runtime ID.""" diff --git a/tests/test_window_builder.py b/tests/test_window_builder.py index d9690cf..ac9be58 100644 --- a/tests/test_window_builder.py +++ b/tests/test_window_builder.py @@ -4,17 +4,18 @@ from unittest.mock import patch from PySide6.QtCore import Qt -from PySide6.QtWidgets import QApplication, QLabel, QPushButton +from PySide6.QtWidgets import QApplication, QGraphicsOpacityEffect, QLabel, QPushButton from axidev_osk.components import register_components from axidev_osk.components.grid.keyboard import KeyboardWidget from axidev_osk.config.defaults import build_default_app_config +from axidev_osk.platform.overlay import OverlayBackend from axidev_osk.runtime.registries import ComponentRegistry, SurfaceRegistry from axidev_osk.runtime.testing import make_test_context from axidev_osk.windows.builder import build_window from axidev_osk.windows.chrome import OverlayResizeHandle, OverlayTitleBar -from axidev_osk.windows.surface import register_surfaces from axidev_osk.windows.overlay.always_on_top import OverlayPlacement +from axidev_osk.windows.surface import register_surfaces class FakeKeyboardBackend: @@ -49,7 +50,13 @@ def sync_latched_key(self, spec, latched: bool, press_handle=None): class FakeOverlayController: - def __init__(self, *, uses_custom_chrome: bool = True) -> None: + def __init__( + self, + *, + backend: OverlayBackend = OverlayBackend.WINDOWS_NATIVE, + uses_custom_chrome: bool = True, + ) -> None: + self.backend = backend self.uses_custom_chrome = uses_custom_chrome def prepare_show(self) -> bool: @@ -207,6 +214,27 @@ def test_keyboard_window_uses_configured_normal_opacity(self) -> None: self.addCleanup(window.close) self.assertAlmostEqual(window.windowOpacity(), 0.85, delta=0.005) + def test_layer_shell_window_renders_configured_opacity_in_content(self) -> None: + _app() + overlay = FakeOverlayController(backend=OverlayBackend.WAYLAND_LAYER_SHELL) + + with patch( + "axidev_osk.windows.builder.configure_always_on_top_window", + return_value=overlay, + ): + window = _build_keyboard_window(FakeKeyboardBackend(ready=True)) + + self.addCleanup(window.close) + effect = window.centralWidget().graphicsEffect() + self.assertIsInstance(effect, QGraphicsOpacityEffect) + self.assertAlmostEqual(window.windowOpacity(), 1.0, delta=0.005) + self.assertAlmostEqual(effect.opacity(), 0.85, delta=0.005) + + window.setWindowOpacity(0.01) + + self.assertAlmostEqual(window.windowOpacity(), 1.0, delta=0.005) + self.assertAlmostEqual(effect.opacity(), 0.01, delta=0.005) + def test_runtime_window_and_components_expose_dynamic_identity_properties(self) -> None: _app() overlay = FakeOverlayController()