Skip to content

Commit

Permalink
#4382 make posix dbus events re-usable
Browse files Browse the repository at this point in the history
  • Loading branch information
totaam committed Oct 11, 2024
1 parent e1a4126 commit 696e093
Show file tree
Hide file tree
Showing 3 changed files with 224 additions and 123 deletions.
56 changes: 56 additions & 0 deletions xpra/platform/events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env python3
# This file is part of Xpra.
# Copyright (C) 2010 Nathaniel Smith <[email protected]>
# Copyright (C) 2012-2024 Antoine Martin <[email protected]>
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
# later version. See the file COPYING for details.

import sys
from collections.abc import Callable

from xpra.platform import platform_import


EVENTS: tuple[str] = ("suspend", "resume", )


def add_handler(event: str, handler: Callable) -> None:
raise NotImplementedError


def remove_handler(event: str, handler: Callable) -> None:
raise NotImplementedError


platform_import(globals(), "events", True, "add_handler", "remove_handler")


def main(argv: list[str]) -> int:
from xpra.os_util import gi_import
from xpra.platform import program_context, init
from xpra.log import Logger, enable_color, consume_verbose_argv
log = Logger("events")
with program_context("Event-Listener"):
enable_color()
consume_verbose_argv(argv, "all")
init()

def handler(event: str, args):
log.info(f"{event!r} {args}")

for event in EVENTS:
add_handler(event, handler)

GLib = gi_import("GLib")
loop = GLib.MainLoop()
try:
loop.run()
except KeyboardInterrupt:
pass
for event in EVENTS:
remove_handler(event, handler)
return 0


if __name__ == "__main__":
sys.exit(main(sys.argv))
154 changes: 154 additions & 0 deletions xpra/platform/posix/events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# This file is part of Xpra.
# Copyright (C) 2010 Nathaniel Smith <[email protected]>
# Copyright (C) 2011-2024 Antoine Martin <[email protected]>
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
# later version. See the file COPYING for details.

from collections.abc import Callable

from xpra.os_util import gi_import
from xpra.util.env import envbool, first_time
from xpra.log import Logger

GLib = gi_import("GLib")

log = Logger("posix", "dbus", "events")


DBUS_SCREENSAVER = envbool("XPRA_DBUS_SCREENSAVER", True)
DBUS_LOGIN1 = envbool("XPRA_DBUS_LOGIN1", True)
DBUS_UPOWER = envbool("XPRA_DBUS_UPOWER", True)

bus_signal_match: dict[tuple[str, Callable], Callable[[], None]] = {}


def load_dbus() -> bool:
try:
import xpra.dbus
assert xpra.dbus
except ImportError:
log("setup_dbus_signals()", exc_info=True)
if first_time("dbus"):
log.info("no dbus support")
return False

try:
from xpra.dbus.common import init_system_bus, init_session_bus
log(f"loaded init functions: {init_system_bus}, {init_session_bus}")
except ImportError as e:
log("system_bus()", exc_info=True)
log.error("Error: the xpra dbus bindings are missing,")
log.error(" cannot setup event listeners:")
log.estr(e)
return False

try:
import dbus
assert dbus
return True
except ImportError as e:
log("system_bus()", exc_info=True)
log.warn("Warning: cannot setup dbus signals")
log.warn(f" {e}")
return False


def get_system_bus():
if not load_dbus():
return None
from xpra.dbus.common import init_system_bus
return init_system_bus()


def get_session_bus():
if not load_dbus():
return None
from xpra.dbus.common import init_session_bus
return init_session_bus()


UPOWER_BUS_NAME = "org.freedesktop.UPower"
UPOWER_IFACE_NAME = UPOWER_BUS_NAME

LOGIN1_BUS_NAME = "org.freedesktop.login1"
LOGIN1_IFACE_NAME = f"{LOGIN1_BUS_NAME}.Manager"

SCREENSAVER_BUS_NAME = "org.gnome.ScreenSaver"
SCREENSAVER_IFACE_NAME = SCREENSAVER_BUS_NAME


def add_bus_handler(get_bus: Callable, callback: Callable, signal: str, iface: str, bus_name: str) -> Callable | None:
# ie: add_bus_handler(system_bus, "Sleeping", UPOWER_IFACE_NAME, UPOWER_BUS_NAME)
try:
bus = get_bus()
except Exception:
log.warn(f"Warning: no bus for {signal!r}")
return None
if not bus:
return None
log(f"{bus}.add_signal_receiver({callback}, {signal!r}, {iface!r}, {bus_name!r})")
try:
match = bus.add_signal_receiver(callback, signal, iface, bus_name)
except (ValueError, RuntimeError) as e:
log(f"failed to add bus handler for {signal!r}: {e}")
return None
if not match:
return None

def cleanup() -> None:
bus._clean_up_signal_match(match)
return cleanup


def add_handler(event: str, handler: Callable) -> None:
log(f"add_handler({event!r}, {handler})")

def forward(*args) -> None:
log(f"dbus event: {event!r}, calling {handler}")
try:
handler(args)
except Exception as e:
log.error(f"Error handling {event!r} event")
log.error(f" using {handler}:")
log.estr(e)

def add(*args):
cleanup = add_bus_handler(*args)
if cleanup:
bus_signal_match[(event, handler)] = cleanup

# (deprecated - may not be available) UPower events:
if DBUS_UPOWER:
if event == "suspend":
add(get_system_bus, forward, "Sleeping", UPOWER_IFACE_NAME, UPOWER_BUS_NAME)
elif event == "resume":
add(get_system_bus, forward, "Resuming", UPOWER_IFACE_NAME, UPOWER_BUS_NAME)
else:
log(f"unsupported posix event: {event!r}")
return

if DBUS_LOGIN1:
# same event via login1:
if event in ("suspend", "resume"):
def prepare_for_sleep(suspend) -> None:
if suspend and event == "suspend":
forward(suspend)
elif not suspend and event == "resume":
forward(suspend)
add(get_system_bus, prepare_for_sleep, "PrepareForSleep", LOGIN1_IFACE_NAME, LOGIN1_BUS_NAME)

if DBUS_SCREENSAVER:
def active_changed(active) -> None:
log("ActiveChanged(%s)", active)
if active and event == "suspend":
forward(active)
elif not active and event == "resume":
forward(active)
add(get_session_bus, active_changed, "ActiveChanged", SCREENSAVER_IFACE_NAME, SCREENSAVER_BUS_NAME)


def remove_handler(event: str, handler: Callable) -> None:
remove = bus_signal_match.get((event, handler))
log(f"remove_handler({event!r}, {handler}) calling {remove}")
if remove:
remove()
137 changes: 14 additions & 123 deletions xpra/platform/posix/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from xpra.util.system import is_Wayland, is_X11
from xpra.util.str_fn import csv, bytestostr
from xpra.util.env import envint, envbool, first_time, get_saved_env, get_saved_env_var
from xpra.platform.posix.events import add_handler, remove_handler
from xpra.log import Logger

GLib = gi_import("GLib")
Expand Down Expand Up @@ -70,7 +71,6 @@ def X11XI2Bindings():
XSETTINGS_DPI = envbool("XPRA_XSETTINGS_DPI", True)
USE_NATIVE_TRAY = envbool("XPRA_USE_NATIVE_TRAY", True)
XINPUT_WHEEL_DIV = envint("XPRA_XINPUT_WHEEL_DIV", 15)
DBUS_SCREENSAVER = envbool("XPRA_DBUS_SCREENSAVER", False)


def gl_check() -> str:
Expand Down Expand Up @@ -645,12 +645,6 @@ def __init__(self, client, _opts):
self.client = client
self._xsettings_watcher = None
self._root_props_watcher = None
self.system_bus = None
self.session_bus = None
self.upower_resuming_match = None
self.upower_sleeping_match = None
self.login1_match = None
self.screensaver_match = None
self.x11_filter = None
if client.xsettings_enabled:
self.setup_xprops()
Expand Down Expand Up @@ -690,126 +684,23 @@ def cleanup(self) -> None:
if self._root_props_watcher:
self._root_props_watcher.cleanup()
self._root_props_watcher = None
if self.system_bus:
bus = self.system_bus
log("cleanup() system bus=%s, matches: %s",
bus, (self.upower_resuming_match, self.upower_sleeping_match, self.login1_match))
self.system_bus = None
if self.upower_resuming_match:
bus._clean_up_signal_match(self.upower_resuming_match)
self.upower_resuming_match = None
if self.upower_sleeping_match:
bus._clean_up_signal_match(self.upower_sleeping_match)
self.upower_sleeping_match = None
if self.login1_match:
bus._clean_up_signal_match(self.login1_match)
self.login1_match = None
if self.session_bus and self.screensaver_match:
self.session_bus._clean_up_signal_match(self.screensaver_match)
self.screensaver_match = None

def resuming_callback(self, *args) -> None:
eventlog("resuming_callback%s", args)
self.client.resume()

def sleeping_callback(self, *args) -> None:
eventlog("sleeping_callback%s", args)
self.client.suspend()
remove_handler("suspend", self.suspend_callback)
remove_handler("resume", self.resume_callback)

def setup_dbus_signals(self) -> None:
try:
import xpra.dbus
assert xpra.dbus
except ImportError:
dbuslog("setup_dbus_signals()", exc_info=True)
dbuslog.info("dbus support is not installed")
dbuslog.info(" no support for power events")
return
try:
from xpra.dbus.common import init_system_bus, init_session_bus
except ImportError as e:
dbuslog("setup_dbus_signals()", exc_info=True)
dbuslog.error("Error: dbus bindings are missing,")
dbuslog.error(" cannot setup event listeners:")
dbuslog.estr(e)
return

try:
import dbus
assert dbus
except ImportError as e:
dbuslog("setup_dbus_signals()", exc_info=True)
dbuslog.warn("Warning: cannot setup dbus signals")
dbuslog.warn(f" {e}")
return

try:
bus = init_system_bus()
self.system_bus = bus
dbuslog("setup_dbus_signals() system bus=%s", bus)
except Exception as e:
dbuslog("setup_dbus_signals()", exc_info=True)
dbuslog.error("Error setting up dbus signals:")
dbuslog.estr(e)
return

# the UPower signals:
try:
bus_name = 'org.freedesktop.UPower'
dbuslog("bus has owner(%s)=%s", bus_name, bus.name_has_owner(bus_name))
iface_name = 'org.freedesktop.UPower'
self.upower_resuming_match = bus.add_signal_receiver(self.resuming_callback,
"Resuming", iface_name, bus_name)
self.upower_sleeping_match = bus.add_signal_receiver(self.sleeping_callback,
"Sleeping", iface_name, bus_name)
dbuslog("listening for 'Resuming' and 'Sleeping' signals on %s", iface_name)
except Exception as e:
dbuslog("failed to setup UPower event listener: %s", e)

# the "logind" signals:
try:
bus_name = 'org.freedesktop.login1'
dbuslog("bus has owner(%s)=%s", bus_name, bus.name_has_owner(bus_name))

def sleep_event_handler(suspend):
if suspend:
self.sleeping_callback()
else:
self.resuming_callback()

iface_name = 'org.freedesktop.login1.Manager'
self.login1_match = bus.add_signal_receiver(sleep_event_handler, 'PrepareForSleep', iface_name, bus_name)
dbuslog("listening for 'PrepareForSleep' signal on %s", iface_name)
except Exception as e:
dbuslog("failed to setup login1 event listener: %s", e)

if DBUS_SCREENSAVER:
try:
session_bus = init_session_bus()
self.session_bus = session_bus
dbuslog("setup_dbus_signals() session bus=%s", session_bus)
except Exception as e:
dbuslog("setup_dbus_signals()", exc_info=True)
dbuslog.error("Error setting up dbus signals:")
dbuslog.estr(e)
else:
# screensaver signals:
try:
bus_name = "org.gnome.ScreenSaver"
iface_name = bus_name
self.screensaver_match = bus.add_signal_receiver(self.ActiveChanged,
"ActiveChanged", iface_name, bus_name)
dbuslog("listening for 'ActiveChanged' signal on %s", iface_name)
except Exception as e:
dbuslog.warn("Warning: failed to setup screensaver event listener: %s", e)

def ActiveChanged(self, active) -> None:
log("ActiveChanged(%s)", active)
if active:
def suspend_callback(self, *args) -> None:
eventlog("suspend_callback%s", args)
if self.client:
self.client.suspend()
else:

def resume_callback(self, *args) -> None:
eventlog("resume_callback%s", args)
if self.client:
self.client.resume()

def setup_dbus_signals(self) -> None:
add_handler("suspend", self.suspend_callback)
add_handler("resume", self.resume_callback)

def setup_xprops(self) -> None:
# wait for handshake to complete:
if x11_bindings() and self.client:
Expand Down

0 comments on commit 696e093

Please sign in to comment.