diff --git a/archlinux/PKGBUILD.in b/archlinux/PKGBUILD.in index 87b942c..d0473e2 100644 --- a/archlinux/PKGBUILD.in +++ b/archlinux/PKGBUILD.in @@ -7,7 +7,7 @@ pkgdesc="The Qubes service for proxying input devices" arch=("x86_64") url="http://qubes-os.org/" license=('GPL') -depends=(sh usbutils qubes-vm-core) +depends=(sh usbutils qubes-vm-core python-pyinotify) _pkgnvr="${pkgname}-${pkgver}-${pkgrel}" source=("${_pkgnvr}.tar.gz") sha256sums=(SKIP) diff --git a/debian/control b/debian/control index 863e431..c8fcdee 100644 --- a/debian/control +++ b/debian/control @@ -9,6 +9,7 @@ Homepage: http://www.qubes-os.org Package: qubes-input-proxy-sender Architecture: any Depends: ${shlibs:Depends}, qubes-core-agent(>=3.0.25) +Recommends: python3-pyinotify Description: Provides Simple input events proxy This package is intended to run in a qube with physical input device attached (for example USB VM). It sends events to a listener in dom0 which passes diff --git a/qubes-rpc/qubes-input-trigger b/qubes-rpc/qubes-input-trigger index 3e128e9..5687941 100755 --- a/qubes-rpc/qubes-input-trigger +++ b/qubes-rpc/qubes-input-trigger @@ -7,7 +7,66 @@ import argparse import subprocess import os import sys +import time from stat import * +try: + import pyinotify + has_inotify = True +except ImportError: + has_inotify = False + + +GUI_STATUS_FILE_PATH = "/run/qubes/gui-agent.status" + + +def wait_for_gui_session(): + """Wait for user session in GUI domain to initialize. This is relevant for + potential qrexec policy prompt ('ask' action) - before GUI session is + started, policy prompt cannot be displayed and the call would be + denied.""" + + if not os.path.exists(GUI_STATUS_FILE_PATH): + print(f"{GUI_STATUS_FILE_PATH} does not exist, not waiting for GUI", file=sys.stderr) + return + + if has_inotify: + wm = pyinotify.WatchManager() + + class EventHandler(pyinotify.ProcessEvent): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.connected = False + + def check_status_file(self): + with open(GUI_STATUS_FILE_PATH) as f: + status = f.read() + if status.strip() == "connected": + self.connected = True + + def process_IN_CLOSE_WRITE(self, event): + self.check_status_file() + + handler = EventHandler() + notifier = pyinotify.Notifier(wm, handler) + + mask = pyinotify.IN_CLOSE_WRITE # watched events + wdd = wm.add_watch(GUI_STATUS_FILE_PATH, mask) + + handler.check_status_file() + notifier.process_events() + while not handler.connected and notifier.check_events(): + notifier.read_events() + notifier.process_events() + else: + connected = False + while True: + with open(GUI_STATUS_FILE_PATH) as f: + status = f.read() + if status.strip() == "connected": + break + + time.sleep(1) + def get_args(): @@ -25,6 +84,12 @@ def get_args(): "--event", required=False ) + parser.add_argument( + "--wait-for-gui", + required=False, + action="store_true", + help="Wait for GUI daemon to connect, for user to see qrexec prompt" + ) parser.add_argument( "--dom0", required=False, @@ -166,6 +231,9 @@ def main(): if args.event and not args.action: print("Please provide action to perform: add/remove") + if args.wait_for_gui: + wait_for_gui_session() + if args.all: handle_all_events(args.dom0) else: diff --git a/qubes-rpc/qubes-input-trigger.desktop b/qubes-rpc/qubes-input-trigger.desktop index 4106fd0..c1b69d9 100644 --- a/qubes-rpc/qubes-input-trigger.desktop +++ b/qubes-rpc/qubes-input-trigger.desktop @@ -2,4 +2,4 @@ Version=1.0 Type=Application Name=Retry Qubes RPC prompts for input devices -Exec=/usr/bin/qubes-input-trigger --all +Exec=/usr/bin/qubes-input-trigger --all --wait-for-gui diff --git a/rpm_spec/input-proxy.spec.in b/rpm_spec/input-proxy.spec.in index d22b5d7..c3b3828 100644 --- a/rpm_spec/input-proxy.spec.in +++ b/rpm_spec/input-proxy.spec.in @@ -32,6 +32,7 @@ This package is receiver part. Summary: Simple input device proxy (sender) %{?systemd_requires} BuildRequires: systemd +Recommends: python3-inotify %description sender Simple input device proxy, which pass events from /dev/input/eventN device, to