# The test's stand-in for the person: screenshots through the portal, clicks and keys through # Mutter's own remote desktop interface. op.py shot out.png | click x y | key keysym import sys, time, shutil, urllib.parse from gi.repository import Gio, GLib bus = Gio.bus_get_sync(Gio.BusType.SESSION) def call(dest, path, iface, method, args=None, sig=None): return bus.call_sync(dest, path, iface, method, args, None, 0, -1, None) def shot(out): token = 'op%d' % int(time.time() * 1000) sender = bus.get_unique_name()[1:].replace('.', '_') req = '/org/freedesktop/portal/desktop/request/%s/%s' % (sender, token) loop = GLib.MainLoop(); res = {} def on(conn, snd, path, iface, sig, params, *a): res['r'] = params.unpack(); loop.quit() bus.signal_subscribe(None, 'org.freedesktop.portal.Request', 'Response', req, None, 0, on) call('org.freedesktop.portal.Desktop', '/org/freedesktop/portal/desktop', 'org.freedesktop.portal.Screenshot', 'Screenshot', GLib.Variant('(sa{sv})', ('', {'handle_token': GLib.Variant('s', token), 'interactive': GLib.Variant('b', False)}))) GLib.timeout_add_seconds(10, loop.quit); loop.run() uri = res['r'][1]['uri'] shutil.move(urllib.parse.unquote(uri[7:]), out) def session(): path = call('org.gnome.Mutter.RemoteDesktop', '/org/gnome/Mutter/RemoteDesktop', 'org.gnome.Mutter.RemoteDesktop', 'CreateSession').unpack()[0] call('org.gnome.Mutter.RemoteDesktop', path, 'org.gnome.Mutter.RemoteDesktop.Session', 'Start') return lambda m, a: call('org.gnome.Mutter.RemoteDesktop', path, 'org.gnome.Mutter.RemoteDesktop.Session', m, a) def eye(out): # Mutter's own screen cast (the operator's view, no portal), one frame through GStreamer. import subprocess sp = call('org.gnome.Mutter.ScreenCast', '/org/gnome/Mutter/ScreenCast', 'org.gnome.Mutter.ScreenCast', 'CreateSession', GLib.Variant('(a{sv})', ({},))).unpack()[0] st = call('org.gnome.Mutter.ScreenCast', sp, 'org.gnome.Mutter.ScreenCast.Session', 'RecordMonitor', GLib.Variant('(sa{sv})', ('Meta-0', {}))).unpack()[0] loop = GLib.MainLoop(); res = {} def on(conn, snd, path, iface, sig, params, *a): res['node'] = params.unpack()[0]; loop.quit() bus.signal_subscribe(None, 'org.gnome.Mutter.ScreenCast.Stream', 'PipeWireStreamAdded', st, None, 0, on) call('org.gnome.Mutter.ScreenCast', sp, 'org.gnome.Mutter.ScreenCast.Session', 'Start') GLib.timeout_add_seconds(5, loop.quit); loop.run() subprocess.run(['gst-launch-1.0', '-q', 'pipewiresrc', 'path=%d' % res['node'], 'num-buffers=3', '!', 'videoconvert', '!', 'pngenc', 'snapshot=true', '!', 'filesink', 'location=' + out], timeout=20) call('org.gnome.Mutter.ScreenCast', sp, 'org.gnome.Mutter.ScreenCast.Session', 'Stop') cmd = sys.argv[1] if cmd == 'eye': eye(sys.argv[2]) if cmd == 'shot': shot(sys.argv[2]) elif cmd == 'click': # Absolute moves need a screen cast stream tied to the remote desktop session. rdp = call('org.gnome.Mutter.RemoteDesktop', '/org/gnome/Mutter/RemoteDesktop', 'org.gnome.Mutter.RemoteDesktop', 'CreateSession').unpack()[0] sid = call('org.gnome.Mutter.RemoteDesktop', rdp, 'org.freedesktop.DBus.Properties', 'Get', GLib.Variant('(ss)', ('org.gnome.Mutter.RemoteDesktop.Session', 'SessionId'))).unpack()[0] sp = call('org.gnome.Mutter.ScreenCast', '/org/gnome/Mutter/ScreenCast', 'org.gnome.Mutter.ScreenCast', 'CreateSession', GLib.Variant('(a{sv})', ({'remote-desktop-session-id': GLib.Variant('s', sid)},))).unpack()[0] st = call('org.gnome.Mutter.ScreenCast', sp, 'org.gnome.Mutter.ScreenCast.Session', 'RecordMonitor', GLib.Variant('(sa{sv})', ('Meta-0', {}))).unpack()[0] loop = GLib.MainLoop() bus.signal_subscribe(None, 'org.gnome.Mutter.ScreenCast.Stream', 'PipeWireStreamAdded', st, None, 0, lambda *a: loop.quit()) call('org.gnome.Mutter.RemoteDesktop', rdp, 'org.gnome.Mutter.RemoteDesktop.Session', 'Start') GLib.timeout_add_seconds(5, loop.quit); loop.run() import subprocess gst = subprocess.Popen(['gst-launch-1.0', '-q', 'pipewiresrc', 'path=%d' % 0, '!', 'fakesink'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) s = lambda m, a: call('org.gnome.Mutter.RemoteDesktop', rdp, 'org.gnome.Mutter.RemoteDesktop.Session', m, a) time.sleep(0.5) s('NotifyPointerMotionAbsolute', GLib.Variant('(sdd)', (st, float(sys.argv[2]), float(sys.argv[3])))); time.sleep(0.2) s('NotifyPointerButton', GLib.Variant('(ib)', (0x110, True))); time.sleep(0.05) s('NotifyPointerButton', GLib.Variant('(ib)', (0x110, False))); time.sleep(0.3) elif cmd == 'key': s = session(); time.sleep(0.3) for k in sys.argv[2:]: s('NotifyKeyboardKeysym', GLib.Variant('(ub)', (int(k, 16), True))); time.sleep(0.05) s('NotifyKeyboardKeysym', GLib.Variant('(ub)', (int(k, 16), False))); time.sleep(0.1)