fix: dispatch Wayland workspace events at runtime
This commit is contained in:
@@ -15,6 +15,8 @@ import os
|
|||||||
from collections.abc import Mapping, Sequence
|
from collections.abc import Mapping, Sequence
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from gi.repository import GLib
|
||||||
|
|
||||||
from .compositor_state_types import (
|
from .compositor_state_types import (
|
||||||
DESKTOP_TRANSITION_FINISHED,
|
DESKTOP_TRANSITION_FINISHED,
|
||||||
DESKTOP_TRANSITION_STARTED,
|
DESKTOP_TRANSITION_STARTED,
|
||||||
@@ -62,6 +64,7 @@ class WaylandSharedProtocolsBackend:
|
|||||||
self._workspaceStates: dict[Any, dict[str, Any]] = {}
|
self._workspaceStates: dict[Any, dict[str, Any]] = {}
|
||||||
self._batchDirty = False
|
self._batchDirty = False
|
||||||
self._transitionPending = False
|
self._transitionPending = False
|
||||||
|
self._dispatchSourceId = 0
|
||||||
|
|
||||||
def is_available(self, session_type: str | None = None) -> bool:
|
def is_available(self, session_type: str | None = None) -> bool:
|
||||||
effectiveSessionType = (session_type or get_session_type()).strip().lower()
|
effectiveSessionType = (session_type or get_session_type()).strip().lower()
|
||||||
@@ -94,6 +97,7 @@ class WaylandSharedProtocolsBackend:
|
|||||||
self._bind_listener(self._registry, "global", self._handle_registry_global)
|
self._bind_listener(self._registry, "global", self._handle_registry_global)
|
||||||
self._bind_listener(self._registry, "global_remove", self._handle_registry_global_remove)
|
self._bind_listener(self._registry, "global_remove", self._handle_registry_global_remove)
|
||||||
self._roundtrip()
|
self._roundtrip()
|
||||||
|
self._install_dispatch_watch()
|
||||||
except Exception:
|
except Exception:
|
||||||
self.deactivate()
|
self.deactivate()
|
||||||
|
|
||||||
@@ -101,6 +105,7 @@ class WaylandSharedProtocolsBackend:
|
|||||||
self._workspaceStates = {}
|
self._workspaceStates = {}
|
||||||
self._batchDirty = False
|
self._batchDirty = False
|
||||||
self._transitionPending = False
|
self._transitionPending = False
|
||||||
|
self._remove_dispatch_watch()
|
||||||
self._safe_close_proxy(self._workspaceManager)
|
self._safe_close_proxy(self._workspaceManager)
|
||||||
self._safe_close_proxy(self._registry)
|
self._safe_close_proxy(self._registry)
|
||||||
self._workspaceManager = None
|
self._workspaceManager = None
|
||||||
@@ -154,6 +159,67 @@ class WaylandSharedProtocolsBackend:
|
|||||||
method()
|
method()
|
||||||
break
|
break
|
||||||
|
|
||||||
|
def _install_dispatch_watch(self) -> None:
|
||||||
|
if self._display is None or self._dispatchSourceId:
|
||||||
|
return
|
||||||
|
|
||||||
|
getFd = getattr(self._display, "get_fd", None)
|
||||||
|
if not callable(getFd):
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
displayFd = int(getFd())
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
|
||||||
|
if displayFd < 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._dispatchSourceId = GLib.io_add_watch(
|
||||||
|
displayFd,
|
||||||
|
GLib.PRIORITY_DEFAULT,
|
||||||
|
GLib.IO_IN | GLib.IO_ERR | GLib.IO_HUP,
|
||||||
|
self._dispatch_display_events,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _remove_dispatch_watch(self) -> None:
|
||||||
|
if not self._dispatchSourceId:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
GLib.source_remove(self._dispatchSourceId)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self._dispatchSourceId = 0
|
||||||
|
|
||||||
|
def _dispatch_display_events(self, _fd: int, condition: Any) -> bool:
|
||||||
|
if condition & (GLib.IO_ERR | GLib.IO_HUP):
|
||||||
|
self.deactivate()
|
||||||
|
return False
|
||||||
|
|
||||||
|
dispatch = getattr(self._display, "dispatch", None)
|
||||||
|
if not callable(dispatch):
|
||||||
|
self.deactivate()
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
dispatched = dispatch(block=False)
|
||||||
|
if not dispatched:
|
||||||
|
break
|
||||||
|
except TypeError:
|
||||||
|
try:
|
||||||
|
dispatch()
|
||||||
|
except Exception:
|
||||||
|
self.deactivate()
|
||||||
|
return False
|
||||||
|
except Exception:
|
||||||
|
self.deactivate()
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def _safe_close_proxy(self, proxy: Any) -> None:
|
def _safe_close_proxy(self, proxy: Any) -> None:
|
||||||
if proxy is None:
|
if proxy is None:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -74,6 +74,42 @@ class CompositorStateAdapterRegressionTests(unittest.TestCase):
|
|||||||
self.assertFalse(backend.is_available("x11"))
|
self.assertFalse(backend.is_available("x11"))
|
||||||
self.assertFalse(backend.is_available("wayland"))
|
self.assertFalse(backend.is_available("wayland"))
|
||||||
|
|
||||||
|
def test_wayland_backend_installs_dispatch_watch_and_processes_runtime_events(self) -> None:
|
||||||
|
fakeDisplay = mock.Mock()
|
||||||
|
fakeRegistry = mock.Mock()
|
||||||
|
fakeDisplay.connect = mock.Mock()
|
||||||
|
fakeDisplay.get_registry = mock.Mock(return_value=fakeRegistry)
|
||||||
|
fakeDisplay.get_fd = mock.Mock(return_value=17)
|
||||||
|
fakeDisplay.roundtrip = mock.Mock()
|
||||||
|
fakeDisplay.dispatch = mock.Mock(side_effect=[1, 0])
|
||||||
|
fakeProtocols = mock.Mock()
|
||||||
|
fakeProtocols.INTERFACE_NAME = "ext_workspace_manager_v1"
|
||||||
|
fakeProtocols.INTERFACE_VERSION = 1
|
||||||
|
fakeProtocols.ACTIVE_STATE_VALUE = 1
|
||||||
|
fakeProtocols.has_runtime_support.return_value = True
|
||||||
|
fakeProtocols.get_display_class.return_value = mock.Mock(return_value=fakeDisplay)
|
||||||
|
emitSignal = mock.Mock()
|
||||||
|
backend = compositor_state_wayland.WaylandSharedProtocolsBackend(
|
||||||
|
environment={"WAYLAND_DISPLAY": "wayland-0"},
|
||||||
|
protocols=fakeProtocols,
|
||||||
|
)
|
||||||
|
|
||||||
|
with (
|
||||||
|
mock.patch.object(compositor_state_wayland, "get_session_type", return_value="wayland"),
|
||||||
|
mock.patch.object(compositor_state_wayland.GLib, "io_add_watch", return_value=41) as ioAddWatch,
|
||||||
|
mock.patch.object(compositor_state_wayland.GLib, "source_remove") as sourceRemove,
|
||||||
|
):
|
||||||
|
backend.activate(emitSignal)
|
||||||
|
|
||||||
|
ioAddWatch.assert_called_once()
|
||||||
|
watchCallback = ioAddWatch.call_args.args[3]
|
||||||
|
self.assertTrue(watchCallback(17, compositor_state_wayland.GLib.IO_IN))
|
||||||
|
self.assertEqual(fakeDisplay.dispatch.call_count, 2)
|
||||||
|
|
||||||
|
backend.deactivate()
|
||||||
|
|
||||||
|
sourceRemove.assert_called_once_with(41)
|
||||||
|
|
||||||
def test_local_ext_workspace_wrapper_supports_base_pywayland_without_distro_protocol_module(self) -> None:
|
def test_local_ext_workspace_wrapper_supports_base_pywayland_without_distro_protocol_module(self) -> None:
|
||||||
fakeClientModule = types.ModuleType("pywayland.client")
|
fakeClientModule = types.ModuleType("pywayland.client")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user