Skip to content

Commit

Permalink
replace pyinotify with Gio monitor
Browse files Browse the repository at this point in the history
  • Loading branch information
totaam committed Sep 6, 2024
1 parent b998419 commit 6b3403d
Show file tree
Hide file tree
Showing 8 changed files with 59 additions and 114 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
python3-dbus python3-cryptography \
python3-netifaces \
openssh-client sshpass python3-paramiko \
python3-setproctitle python3-xdg python3-pyinotify \
python3-setproctitle python3-xdg \
libpam-dev xserver-xorg-dev xutils-dev xserver-xorg-video-dummy xvfb keyboard-configuration \
python3-kerberos python3-gssapi python3-aioquic \
python3-gst-1.0 \
Expand Down
2 changes: 1 addition & 1 deletion docs/Build/Debian.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ apt-get install openssh-client sshpass python3-paramiko

python libraries:
```shell
apt-get install python3-setproctitle python3-xdg python3-pyinotify python3-opencv
apt-get install python3-setproctitle python3-xdg
```
X11:
```shell
Expand Down
3 changes: 0 additions & 3 deletions fs/share/config/mypy/config
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ ignore_missing_imports = True
[mypy-uinput.*]
ignore_missing_imports = True

[mypy-pyinotify.*]
ignore_missing_imports = True

[mypy-OpenGL.*]
ignore_missing_imports = True

Expand Down
1 change: 0 additions & 1 deletion packaging/debian/xpra/control
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,6 @@ Recommends: xpra-codecs (= ${binary:Version})
,python3-setproctitle
# start menu and start-session GUI:
,python3-xdg
,python3-pyinotify
# to be able to resize large svg icons:
,gir1.2-rsvg-2.0
# printer forwarding:
Expand Down
1 change: 0 additions & 1 deletion packaging/rpm/xpra.spec
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,6 @@ Recommends: xpra-filesystem >= 5
Recommends: lsb_release
Recommends: %{python3}-pillow
Recommends: %{python3}-cryptography
Recommends: %{python3}-inotify
Recommends: %{python3}-netifaces
Recommends: %{python3}-dbus
Recommends: %{python3}-dns
Expand Down
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ cli = [
# printing, etc:
"cups",
"psutil",
"inotify",
"cpuinfo",
"setproctitle",
"pyxdg",
Expand Down
88 changes: 36 additions & 52 deletions xpra/platform/posix/webcam.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
# pylint: disable-msg=E1101

import os
import glob
from typing import Any
from collections.abc import Callable

from xpra.os_util import gi_import
from xpra.util.env import envbool
from xpra.util.system import is_DEB
from xpra.log import Logger
Expand Down Expand Up @@ -126,73 +128,55 @@ def get_all_video_devices(capture_only=True) -> dict[int, dict[str, Any]]:
return devices


_watch_manager = None
_notifier = None
device_timetamps = {}
device_monitor = None


def _video_device_file_filter(event) -> bool:
# return True to stop processing of event (to "stop chaining")
return not event.pathname.startswith("/dev/video")
def update_device_timestamps() -> None:
for dev in glob.glob("/dev/video*"):
try:
device_timetamps[dev] = os.path.getmtime(dev)
except OSError:
pass


def add_video_device_change_callback(callback: Callable) -> None:
# pylint: disable=import-outside-toplevel
Gio = gi_import("Gio")
from xpra.platform.webcam import _video_device_change_callbacks, _fire_video_device_change
global _watch_manager, _notifier
try:
import pyinotify
except ImportError as e:
log.error("Error: cannot watch for video device changes without pyinotify:")
log.estr(e)
return
log(f"add_video_device_change_callback({callback}) pyinotify={pyinotify}")

if not _watch_manager:
class EventHandler(pyinotify.ProcessEvent):
def process_IN_CREATE(self, event):
_fire_video_device_change(True, event.pathname)

def process_IN_DELETE(self, event):
_fire_video_device_change(False, event.pathname)

_watch_manager = pyinotify.WatchManager()
mask = pyinotify.IN_DELETE | pyinotify.IN_CREATE # @UndefinedVariable
handler = EventHandler(pevent=_video_device_file_filter)
_notifier = pyinotify.ThreadedNotifier(_watch_manager, handler)
_notifier.daemon = True
wdd = _watch_manager.add_watch('/dev', mask)
log(f"add_video_device_change_callback({callback})")
global device_timetamps, device_monitor

def dev_directory_changed(*args):
old = dict(device_timetamps)
update_device_timestamps()
if set(old.keys()) != set(device_timetamps.keys()):
_fire_video_device_change()
return
for dev, ts in old.items():
if device_timetamps.get(dev, -1) != ts:
_fire_video_device_change()
return

if not device_monitor:
update_device_timestamps()
gfile = Gio.File.new_for_path("/dev")
device_monitor = gfile.monitor_directory(Gio.FileMonitorFlags.NONE, None)
device_monitor.connect("changed", dev_directory_changed)
log("watching for video device changes in /dev")
log(f"notifier={_notifier}, watch={wdd}")
_notifier.start()
_video_device_change_callbacks.append(callback)
# for running standalone:
# notifier.loop()


def remove_video_device_change_callback(callback: Callable) -> None:
# pylint: disable=import-outside-toplevel
from xpra.platform.webcam import _video_device_change_callbacks
global _watch_manager, _notifier
if not _watch_manager:
log.error("Error: cannot remove video device change callback, no watch manager!")
return
log(f"remove_video_device_change_callback({callback})")
global device_monitor, device_timetamps
if callback not in _video_device_change_callbacks:
log.error("Error: video device change callback not found, cannot remove it!")
return
log(f"remove_video_device_change_callback({callback})")
_video_device_change_callbacks.remove(callback)
if not _video_device_change_callbacks:
log("last video device change callback removed, closing the watch manager")
# we can close it:
if _notifier:
try:
_notifier.stop()
except Exception:
pass
_notifier = None
if _watch_manager:
try:
_watch_manager.close()
except Exception:
pass
_watch_manager = None
if not _video_device_change_callbacks and device_monitor:
device_monitor.cancel()
device_monitor = None
device_timetamps = {}
75 changes: 21 additions & 54 deletions xpra/server/menu_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@
from collections.abc import Callable

from xpra.common import DEFAULT_XDG_DATA_DIRS
from xpra.os_util import OSX, POSIX, WIN32, gi_import
from xpra.os_util import OSX, POSIX, gi_import
from xpra.util.env import envint, envbool, osexpand
from xpra.util.thread import start_thread
from xpra.server.background_worker import add_work_item
from xpra.log import Logger

GLib = gi_import("GLib")
Gio = gi_import("Gio")

log = Logger("menu")

Expand Down Expand Up @@ -48,13 +49,12 @@ def get_menu_provider():

class MenuProvider:
__slots__ = (
"watch_manager", "watch_notifier", "xdg_menu_reload_timer",
"dir_watchers", "xdg_menu_reload_timer",
"on_reload", "menu_data", "desktop_sessions", "load_lock",
)

def __init__(self):
self.watch_manager = None
self.watch_notifier = None
self.dir_watchers: dict[str, Any] = {}
self.xdg_menu_reload_timer = 0
self.on_reload: list[Callable] = []
self.menu_data: dict[str, Any] | None = None
Expand All @@ -71,7 +71,7 @@ def setup(self) -> None:
def cleanup(self) -> None:
self.on_reload = []
self.cancel_xdg_menu_reload()
self.cancel_pynotify_watch()
self.cancel_dir_watchers()

def setup_menu_watcher(self) -> None:
try:
Expand All @@ -82,62 +82,29 @@ def setup_menu_watcher(self) -> None:
log.estr(e)

def do_setup_menu_watcher(self) -> None:
if self.watch_manager or OSX or WIN32:
# already setup
return
try:
# pylint: disable=import-outside-toplevel
import pyinotify
except ImportError as e:
log("setup_menu_watcher() cannot import pyinotify", exc_info=True)
log.warn("Warning: cannot watch for application menu changes without pyinotify:")
log.warn(" %s", e)
return
self.watch_manager = pyinotify.WatchManager()

def menu_data_updated(create: bool, pathname: str):
log("menu_data_updated(%s, %s)", create, pathname)
def directory_changed(*args):
log(f"directory_changed{args}")
self.schedule_xdg_menu_reload()

class EventHandler(pyinotify.ProcessEvent):

def process_IN_CREATE(self, event):
menu_data_updated(True, event.pathname)

def process_IN_DELETE(self, event):
menu_data_updated(False, event.pathname)

mask = pyinotify.IN_DELETE | pyinotify.IN_CREATE # @UndefinedVariable pylint: disable=no-member
handler = EventHandler()
self.watch_notifier = pyinotify.ThreadedNotifier(self.watch_manager, handler)
self.watch_notifier.daemon = True
data_dirs = os.environ.get("XDG_DATA_DIRS", DEFAULT_XDG_DATA_DIRS).split(":")
watched = []
for data_dir in data_dirs:
menu_dir = os.path.join(osexpand(data_dir), "applications")
if not os.path.exists(menu_dir) or menu_dir in watched:
if not os.path.exists(menu_dir) or menu_dir in self.dir_watchers:
continue
wdd = self.watch_manager.add_watch(menu_dir, mask)
watched.append(menu_dir)
log("watch_notifier=%s, watch=%s", self.watch_notifier, wdd)
self.watch_notifier.start()
if watched:
gfile = Gio.File.new_for_path(menu_dir)
monitor = gfile.monitor_directory(Gio.FileMonitorFlags.NONE, None)
monitor.connect("changed", directory_changed)
self.dir_watchers[menu_dir] = monitor
if self.dir_watchers:
log.info("watching for applications menu changes in:")
for wd in watched:
log.info(" '%s'", wd)

def cancel_pynotify_watch(self) -> None:
wn = self.watch_notifier
if wn:
self.watch_notifier = None
wn.stop()
wm = self.watch_manager
if wm:
self.watch_manager = None
try:
wm.close()
except OSError:
log("error closing watch manager %s", wm, exc_info=True)
for wd in self.dir_watchers.keys():
log.info(f" {wd!r}")

def cancel_dir_watchers(self) -> None:
dw = self.dir_watchers
self.dir_watchers = {}
for monitor in dw.values():
monitor.cancel()

def load_menu_data(self, force_reload: bool = False) -> None:
# start loading in a thread,
Expand Down

0 comments on commit 6b3403d

Please sign in to comment.