From 0e049e682ad1da96e4924c15517aba4acde54961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Pierret=20=28fepitre=29?= Date: Sun, 17 Nov 2019 23:54:05 +0100 Subject: [PATCH] Change qvm-start-gui to qvm-start-daemon for handling audio too --- Makefile | 5 +- doc/conf.py | 4 +- doc/manpages/qvm-features.rst | 2 +- ...qvm-start-gui.rst => qvm-start-daemon.rst} | 6 +- doc/qubesadmin.tests.tools.rst | 4 +- doc/qubesadmin.tools.rst | 4 +- etc/qvm-start-daemon.desktop | 8 + etc/qvm-start-gui.desktop | 8 - .../{qvm_start_gui.py => qvm_start_daemon.py} | 17 +- .../{qvm_start_gui.py => qvm_start_daemon.py} | 387 ++++++++++-------- rpm_spec/qubes-core-admin-client.spec.in | 2 +- 11 files changed, 252 insertions(+), 195 deletions(-) rename doc/manpages/{qvm-start-gui.rst => qvm-start-daemon.rst} (82%) create mode 100644 etc/qvm-start-daemon.desktop delete mode 100644 etc/qvm-start-gui.desktop rename qubesadmin/tests/tools/{qvm_start_gui.py => qvm_start_daemon.py} (98%) rename qubesadmin/tools/{qvm_start_gui.py => qvm_start_daemon.py} (83%) diff --git a/Makefile b/Makefile index b14470e..812a530 100644 --- a/Makefile +++ b/Makefile @@ -11,8 +11,9 @@ build: install: $(PYTHON) setup.py install -O1 $(PYTHON_PREFIX_ARG) --root $(DESTDIR) install -d $(DESTDIR)/etc/xdg/autostart - install -m 0644 etc/qvm-start-gui.desktop $(DESTDIR)/etc/xdg/autostart/ - install -D scripts/qvm-console $(DESTDIR)/usr/bin/qvm-console + install -m 0644 etc/qvm-start-daemon.desktop $(DESTDIR)/etc/xdg/autostart/ + install -d $(DESTDIR)/usr/bin + ln -sf /usr/bin/qubes-start-daemon $(DESTDIR)/usr/bin/qubes-start-gui clean: rm -rf test-packages/__pycache__ qubesadmin/__pycache__ diff --git a/doc/conf.py b/doc/conf.py index 5b20c01..85041a7 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -358,8 +358,8 @@ man_pages = [ u'Manage (Qubes-specific) services started in VM', _man_pages_author, 1), ('manpages/qvm-shutdown', 'qvm-shutdown', u'Gracefully shut down a qube', _man_pages_author, 1), - ('manpages/qvm-start-gui', 'qvm-start-gui', - u'Start GUI daemon for qubes', _man_pages_author, 1), + ('manpages/qvm-start-daemon', 'qvm-start-daemon', + u'Start GUI/AUDIO daemon for qubes', _man_pages_author, 1), ('manpages/qvm-start', 'qvm-start', u'Start a specified qube', _man_pages_author, 1), ('manpages/qvm-tags', 'qvm-tags', diff --git a/doc/manpages/qvm-features.rst b/doc/manpages/qvm-features.rst index ad337e9..dcac541 100644 --- a/doc/manpages/qvm-features.rst +++ b/doc/manpages/qvm-features.rst @@ -73,7 +73,7 @@ Qube provides GUI through emulated VGA. Setting this feature to :py:obj:`True` enables emulated VGA output. Note that when gui-agent connects to actual VM, emulated VGA output is closed (unless `debug` property is set to :py:obj:`True`). It's possible to open emulated VGA output for a running qube, -regardless of this feature, using `qvm-start-gui --force-stubdomain QUBE_NAME` +regardless of this feature, using `qvm-start-daemon --force-stubdomain QUBE_NAME` command. This feature is applicable only when qube's `virt_mode` is set to `hvm`. diff --git a/doc/manpages/qvm-start-gui.rst b/doc/manpages/qvm-start-daemon.rst similarity index 82% rename from doc/manpages/qvm-start-gui.rst rename to doc/manpages/qvm-start-daemon.rst index 9e9d35f..1a13868 100644 --- a/doc/manpages/qvm-start-gui.rst +++ b/doc/manpages/qvm-start-daemon.rst @@ -1,6 +1,6 @@ -.. program:: qvm-start-gui +.. program:: qvm-start-daemon -:program:`qvm-start-gui` -- start GUI for qube(s) +:program:`qvm-start-daemon` -- start GUI/AUDIO for qube(s) ========================================================= .. warning:: @@ -15,7 +15,7 @@ Synopsis -------- -:command:`qvm-start-gui` [-h] [--verbose] [--quiet] [--all] [--exclude *EXCLUDE*] [--watch] [--force-stubdomain] [--pidfile *PIDFILE*] [--notify-monitory-layout] [*VMNAME* [*VMNAME* ...]] +:command:`qvm-start-daemon` [-h] [--verbose] [--quiet] [--all] [--exclude *EXCLUDE*] [--watch] [--force-stubdomain] [--pidfile *PIDFILE*] [--notify-monitory-layout] [*VMNAME* [*VMNAME* ...]] Options ------- diff --git a/doc/qubesadmin.tests.tools.rst b/doc/qubesadmin.tests.tools.rst index eb88707..ef07850 100644 --- a/doc/qubesadmin.tests.tools.rst +++ b/doc/qubesadmin.tests.tools.rst @@ -164,10 +164,10 @@ qubesadmin\.tests\.tools\.qvm\_start module :undoc-members: :show-inheritance: -qubesadmin\.tests\.tools\.qvm\_start\_gui module +qubesadmin\.tests\.tools\.qvm\_start\_daemon module ------------------------------------------------ -.. automodule:: qubesadmin.tests.tools.qvm_start_gui +.. automodule:: qubesadmin.tests.tools.qvm_start_daemon :members: :undoc-members: :show-inheritance: diff --git a/doc/qubesadmin.tools.rst b/doc/qubesadmin.tools.rst index 24de3fd..078e7f0 100644 --- a/doc/qubesadmin.tools.rst +++ b/doc/qubesadmin.tools.rst @@ -164,10 +164,10 @@ qubesadmin\.tools\.qvm\_start module :undoc-members: :show-inheritance: -qubesadmin\.tools\.qvm\_start\_gui module +qubesadmin\.tools\.qvm\_start\_daemon module ----------------------------------------- -.. automodule:: qubesadmin.tools.qvm_start_gui +.. automodule:: qubesadmin.tools.qvm_start_daemon :members: :undoc-members: :show-inheritance: diff --git a/etc/qvm-start-daemon.desktop b/etc/qvm-start-daemon.desktop new file mode 100644 index 0000000..e4b2340 --- /dev/null +++ b/etc/qvm-start-daemon.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Name=Qubes Guid/Pacat +Comment=Starts GUI/AUDIO daemon for Qubes VMs +Icon=qubes +Exec=qvm-start-daemon --all --watch +Terminal=false +Type=Application +NotShowIn=X-QUBES diff --git a/etc/qvm-start-gui.desktop b/etc/qvm-start-gui.desktop deleted file mode 100644 index d394341..0000000 --- a/etc/qvm-start-gui.desktop +++ /dev/null @@ -1,8 +0,0 @@ -[Desktop Entry] -Name=Qubes Guid -Comment=Starts Dom0 GUI daemon for Qubes VMs -Icon=qubes -Exec=qvm-start-gui --all --watch -Terminal=false -Type=Application -NotShowIn=X-QUBES diff --git a/qubesadmin/tests/tools/qvm_start_gui.py b/qubesadmin/tests/tools/qvm_start_daemon.py similarity index 98% rename from qubesadmin/tests/tools/qvm_start_gui.py rename to qubesadmin/tests/tools/qvm_start_daemon.py index 32527d8..7f655a9 100644 --- a/qubesadmin/tests/tools/qvm_start_gui.py +++ b/qubesadmin/tests/tools/qvm_start_daemon.py @@ -26,14 +26,15 @@ import unittest.mock import asyncio import qubesadmin.tests -import qubesadmin.tools.qvm_start_gui +import qubesadmin.tools.qvm_start_daemon import qubesadmin.vm class TC_00_qvm_start_gui(qubesadmin.tests.QubesTestCase): def setUp(self): super(TC_00_qvm_start_gui, self).setUp() - self.launcher = qubesadmin.tools.qvm_start_gui.GUILauncher(self.app) + self.launcher = \ + qubesadmin.tools.qvm_start_daemon.DAEMONLauncher(self.app) @unittest.mock.patch('subprocess.check_output') def test_000_kde_args(self, proc_mock): @@ -449,7 +450,7 @@ HDMI2 disconnected (normal left inverted right x axis y axis) VGA1 disconnected (normal left inverted right x axis y axis) VIRTUAL1 disconnected (normal left inverted right x axis y axis) '''.splitlines() - self.assertEqual(qubesadmin.tools.qvm_start_gui.get_monitor_layout(), + self.assertEqual(qubesadmin.tools.qvm_start_daemon.get_monitor_layout(), ['1920 1200 0 0\n']) @unittest.mock.patch('subprocess.Popen') @@ -458,7 +459,7 @@ VIRTUAL1 disconnected (normal left inverted right x axis y axis) LVDS1 connected 1600x900+0+0 (normal left inverted right x axis y axis) VGA1 connected 1280x1024+1600+0 (normal left inverted right x axis y axis) '''.splitlines() - self.assertEqual(qubesadmin.tools.qvm_start_gui.get_monitor_layout(), + self.assertEqual(qubesadmin.tools.qvm_start_daemon.get_monitor_layout(), ['1600 900 0 0\n', '1280 1024 1600 0\n']) @unittest.mock.patch('subprocess.Popen') @@ -468,7 +469,7 @@ HDMI1 connected 2560x1920+0+0 (normal left inverted right x axis y axis) 372mm x 1920x1200 60.00*+ '''.splitlines() dpi = 150 - self.assertEqual(qubesadmin.tools.qvm_start_gui.get_monitor_layout(), + self.assertEqual(qubesadmin.tools.qvm_start_daemon.get_monitor_layout(), ['2560 1920 0 0 {} {}\n'.format( int(2560 / dpi * 254 / 10), int(1920 / dpi * 254 / 10))]) @@ -480,7 +481,7 @@ HDMI1 connected 2560x1920+0+0 (normal left inverted right x axis y axis) 310mm x 1920x1200 60.00*+ '''.splitlines() dpi = 200 - self.assertEqual(qubesadmin.tools.qvm_start_gui.get_monitor_layout(), + self.assertEqual(qubesadmin.tools.qvm_start_daemon.get_monitor_layout(), ['2560 1920 0 0 {} {}\n'.format( int(2560 / dpi * 254 / 10), int(1920 / dpi * 254 / 10))]) @@ -492,7 +493,7 @@ HDMI1 connected 2560x1920+0+0 (normal left inverted right x axis y axis) 206mm x 1920x1200 60.00*+ '''.splitlines() dpi = 300 - self.assertEqual(qubesadmin.tools.qvm_start_gui.get_monitor_layout(), + self.assertEqual(qubesadmin.tools.qvm_start_daemon.get_monitor_layout(), ['2560 1920 0 0 {} {}\n'.format( int(2560 / dpi * 254 / 10), int(1920 / dpi * 254 / 10))]) @@ -695,7 +696,7 @@ HDMI1 connected 2560x1920+0+0 (normal left inverted right x axis y axis) 206mm x patch_send_monitor_layout.start() monitor_layout = ['1920 1080 0 0\n'] mock_get_monior_layout = unittest.mock.patch( - 'qubesadmin.tools.qvm_start_gui.get_monitor_layout').start() + 'qubesadmin.tools.qvm_start_daemon.get_monitor_layout').start() mock_get_monior_layout.return_value = monitor_layout self.launcher.send_monitor_layout_all() diff --git a/qubesadmin/tools/qvm_start_gui.py b/qubesadmin/tools/qvm_start_daemon.py similarity index 83% rename from qubesadmin/tools/qvm_start_gui.py rename to qubesadmin/tools/qvm_start_daemon.py index 1cac518..95b7fd9 100644 --- a/qubesadmin/tools/qvm_start_gui.py +++ b/qubesadmin/tools/qvm_start_daemon.py @@ -18,14 +18,13 @@ # You should have received a copy of the GNU Lesser General Public License along # with this program; if not, see . -""" GUI daemon launcher tool""" +""" GUI/AUDIO daemon launcher tool""" import os import signal import subprocess import asyncio import re - import functools import xcffib import xcffib.xproto # pylint: disable=unused-import @@ -46,6 +45,7 @@ except ImportError: pass GUI_DAEMON_PATH = '/usr/bin/qubes-guid' +PACAT_DAEMON_PATH = '/usr/bin/pacat-simple-vchan' QUBES_ICON_DIR = '/usr/share/icons/hicolor/128x128/devices' # "LVDS connected 1024x768+0+0 (normal left inverted right) 304mm x 228mm" @@ -113,159 +113,17 @@ def get_monitor_layout(): return outputs -class GUILauncher(object): - """Launch GUI daemon for VMs""" +class DAEMONLauncher: + """Launch GUI/AUDIO daemon for VMs""" def __init__(self, app: qubesadmin.app.QubesBase): - """ Initialize GUILauncher. + """ Initialize DAEMONLauncher. :param app: :py:class:`qubesadmin.Qubes` instance """ self.app = app self.started_processes = {} - @staticmethod - def kde_guid_args(vm): - """Return KDE-specific arguments for gui-daemon, if applicable""" - - guid_cmd = [] - # Avoid using environment variables for checking the current session, - # because this script may be called with cleared env (like with sudo). - if subprocess.check_output( - ['xprop', '-root', '-notype', 'KWIN_RUNNING']) == \ - b'KWIN_RUNNING = 0x1\n': - # native decoration plugins is used, so adjust window properties - # accordingly - guid_cmd += ['-T'] # prefix window titles with VM name - # get owner of X11 session - session_owner = None - for line in subprocess.check_output(['xhost']).splitlines(): - if line == b'SI:localuser:root': - pass - elif line.startswith(b'SI:localuser:'): - session_owner = line.split(b':')[2].decode() - if session_owner is not None: - data_dir = os.path.expanduser( - '~{}/.local/share'.format(session_owner)) - else: - # fallback to current user - data_dir = os.path.expanduser('~/.local/share') - - guid_cmd += ['-p', - '_KDE_NET_WM_COLOR_SCHEME=s:{}'.format( - os.path.join(data_dir, - 'qubes-kde', - vm.label.name + '.colors'))] - return guid_cmd - - def common_guid_args(self, vm): - """Common qubes-guid arguments for PV(H), HVM and Stubdomain""" - - guid_cmd = [GUI_DAEMON_PATH, - '-N', vm.name, - '-c', vm.label.color, - '-i', os.path.join(QUBES_ICON_DIR, vm.label.icon) + '.png', - '-l', str(vm.label.index)] - - if vm.debug: - guid_cmd += ['-v', '-v'] - # elif not verbose: - else: - guid_cmd += ['-q'] - - if vm.features.check_with_template('rpc-clipboard', False): - guid_cmd.extend(['-Q']) - - guid_cmd += self.kde_guid_args(vm) - return guid_cmd - - @staticmethod - def guid_pidfile(xid): - """Helper function to construct a pidfile path""" - return '/var/run/qubes/guid-running.{}'.format(xid) - - @asyncio.coroutine - def start_gui_for_vm(self, vm, monitor_layout=None): - """Start GUI daemon (qubes-guid) connected directly to a VM - - This function is a coroutine. - - :param vm: VM for which start GUI daemon - :param monitor_layout: monitor layout to send; if None, fetch it from - local X server. - """ - guid_cmd = self.common_guid_args(vm) - guid_cmd.extend(['-d', str(vm.xid)]) - - if vm.virt_mode == 'hvm': - guid_cmd.extend(['-n']) - - stubdom_guid_pidfile = self.guid_pidfile(vm.stubdom_xid) - if not vm.debug and os.path.exists(stubdom_guid_pidfile): - # Terminate stubdom guid once "real" gui agent connects - with open(stubdom_guid_pidfile, 'r') as pidfile: - stubdom_guid_pid = pidfile.read().strip() - guid_cmd += ['-K', stubdom_guid_pid] - - vm.log.info('Starting GUI') - - yield from asyncio.create_subprocess_exec(*guid_cmd) - - yield from self.send_monitor_layout(vm, layout=monitor_layout, - startup=True) - - @asyncio.coroutine - def start_gui_for_stubdomain(self, vm, force=False): - """Start GUI daemon (qubes-guid) connected to a stubdomain - - This function is a coroutine. - """ - want_stubdom = force - if not want_stubdom and \ - vm.features.check_with_template('gui-emulated', False): - want_stubdom = True - # if no 'gui' or 'gui-emulated' feature set at all, use emulated GUI - if not want_stubdom and \ - vm.features.check_with_template('gui', None) is None and \ - vm.features.check_with_template('gui-emulated', None) is None: - want_stubdom = True - if not want_stubdom and vm.debug: - want_stubdom = True - if not want_stubdom: - return - if os.path.exists(self.guid_pidfile(vm.stubdom_xid)): - return - vm.log.info('Starting GUI (stubdomain)') - guid_cmd = self.common_guid_args(vm) - guid_cmd.extend(['-d', str(vm.stubdom_xid), '-t', str(vm.xid)]) - - yield from asyncio.create_subprocess_exec(*guid_cmd) - - @asyncio.coroutine - def start_gui(self, vm, force_stubdom=False, monitor_layout=None): - """Start GUI daemon regardless of start event. - - This function is a coroutine. - - :param vm: VM for which GUI daemon should be started - :param force_stubdom: Force GUI daemon for stubdomain, even if the - one for target AppVM is running. - :param monitor_layout: monitor layout configuration - """ - guivm = getattr(vm, 'guivm', None) - if guivm != vm.app.local_name: - vm.log.info('GUI connected to {}. Skipping.'.format(guivm)) - return - - if vm.virt_mode == 'hvm': - yield from self.start_gui_for_stubdomain(vm, force=force_stubdom) - - if not vm.features.check_with_template('gui', True): - return - - if not os.path.exists(self.guid_pidfile(vm.xid)): - yield from self.start_gui_for_vm(vm, monitor_layout=monitor_layout) - @asyncio.coroutine def send_monitor_layout(self, vm, layout=None, startup=False): """Send monitor layout to a given VM @@ -326,6 +184,186 @@ class GUILauncher(object): asyncio.ensure_future(self.send_monitor_layout(vm, monitor_layout)) + @staticmethod + def kde_guid_args(vm): + """Return KDE-specific arguments for gui-daemon, if applicable""" + + guid_cmd = [] + # Avoid using environment variables for checking the current session, + # because this script may be called with cleared env (like with sudo). + if subprocess.check_output( + ['xprop', '-root', '-notype', 'KWIN_RUNNING']) == \ + b'KWIN_RUNNING = 0x1\n': + # native decoration plugins is used, so adjust window properties + # accordingly + guid_cmd += ['-T'] # prefix window titles with VM name + # get owner of X11 session + session_owner = None + for line in subprocess.check_output(['xhost']).splitlines(): + if line == b'SI:localuser:root': + pass + elif line.startswith(b'SI:localuser:'): + session_owner = line.split(b':')[2].decode() + if session_owner is not None: + data_dir = os.path.expanduser( + '~{}/.local/share'.format(session_owner)) + else: + # fallback to current user + data_dir = os.path.expanduser('~/.local/share') + + guid_cmd += ['-p', + '_KDE_NET_WM_COLOR_SCHEME=s:{}'.format( + os.path.join(data_dir, + 'qubes-kde', + vm.label.name + '.colors'))] + return guid_cmd + + def common_guid_args(self, vm): + """Common qubes-guid arguments for PV(H), HVM and Stubdomain""" + + guid_cmd = [GUI_DAEMON_PATH, + '-N', vm.name, + '-c', vm.label.color, + '-i', os.path.join(QUBES_ICON_DIR, vm.label.icon) + '.png', + '-l', str(vm.label.index)] + + if vm.debug: + guid_cmd += ['-v', '-v'] + # elif not verbose: + else: + guid_cmd += ['-q'] + + if vm.features.check_with_template('rpc-clipboard', False): + guid_cmd.extend(['-Q']) + + guid_cmd += self.kde_guid_args(vm) + return guid_cmd + + @staticmethod + def guid_pidfile(xid): + """Helper function to construct a GUI pidfile path""" + return '/var/run/qubes/guid-running.{}'.format(xid) + + @staticmethod + def pacat_pidfile(xid): + """Helper function to construct an AUDIO pidfile path""" + return '/var/run/qubes/pacat-running.{}'.format(xid) + + @asyncio.coroutine + def start_gui_for_vm(self, vm, monitor_layout=None): + """Start GUI daemon (qubes-guid) connected directly to a VM + + This function is a coroutine. + + :param vm: VM for which start GUI daemon + :param monitor_layout: monitor layout to send; if None, fetch it from + local X server. + """ + guid_cmd = self.common_guid_args(vm) + guid_cmd.extend(['-d', str(vm.xid)]) + + if vm.virt_mode == 'hvm': + guid_cmd.extend(['-n']) + + stubdom_guid_pidfile = self.guid_pidfile(vm.stubdom_xid) + if not vm.debug and os.path.exists(stubdom_guid_pidfile): + # Terminate stubdom guid once "real" gui agent connects + with open(stubdom_guid_pidfile, 'r') as pidfile: + stubdom_guid_pid = pidfile.read().strip() + guid_cmd += ['-K', stubdom_guid_pid] + + vm.log.info('Starting GUI') + + yield from asyncio.create_subprocess_exec(*guid_cmd) + + yield from self.send_monitor_layout(vm, layout=monitor_layout, + startup=True) + + @asyncio.coroutine + def start_gui_for_stubdomain(self, vm, force=False): + """Start GUI daemon (qubes-guid) connected to a stubdomain + + This function is a coroutine. + """ + want_stubdom = force + if not want_stubdom and \ + vm.features.check_with_template('gui-emulated', False): + want_stubdom = True + # if no 'gui' or 'gui-emulated' feature set at all, use emulated GUI + if not want_stubdom and \ + vm.features.check_with_template('gui', None) is None and \ + vm.features.check_with_template('gui-emulated', None) is None: + want_stubdom = True + if not want_stubdom and vm.debug: + want_stubdom = True + if not want_stubdom: + return + if os.path.exists(self.guid_pidfile(vm.stubdom_xid)): + return + vm.log.info('Starting GUI (stubdomain)') + guid_cmd = self.common_guid_args(vm) + guid_cmd.extend(['-d', str(vm.stubdom_xid), '-t', str(vm.xid)]) + + yield from asyncio.create_subprocess_exec(*guid_cmd) + + @asyncio.coroutine + def start_audio_for_vm(self, vm): + """Start AUDIO daemon (pacat-simple-vchan) connected directly to a VM + + This function is a coroutine. + + :param vm: VM for which start AUDIO daemon + """ + # pylint: disable=no-self-use + pacat_cmd = [PACAT_DAEMON_PATH, vm.xid, vm.name] + vm.log.info('Starting AUDIO') + + yield from asyncio.create_subprocess_exec(*pacat_cmd) + + @asyncio.coroutine + def start_gui(self, vm, force_stubdom=False, monitor_layout=None): + """Start GUI daemon regardless of start event. + + This function is a coroutine. + + :param vm: VM for which GUI daemon should be started + :param force_stubdom: Force GUI daemon for stubdomain, even if the + one for target AppVM is running. + :param monitor_layout: monitor layout configuration + """ + guivm = getattr(vm, 'guivm', None) + if guivm != vm.app.local_name: + vm.log.info('GUI connected to {}. Skipping.'.format(guivm)) + return + + if vm.virt_mode == 'hvm': + yield from self.start_gui_for_stubdomain(vm, force=force_stubdom) + + if not vm.features.check_with_template('gui', True): + return + + if not os.path.exists(self.guid_pidfile(vm.xid)): + yield from self.start_gui_for_vm(vm, monitor_layout=monitor_layout) + + @asyncio.coroutine + def start_audio(self, vm): + """Start AUDIO daemon regardless of start event. + + This function is a coroutine. + + :param vm: VM for which AUDIO daemon should be started + """ + audiovm = getattr(vm, 'audiovm', None) + if audiovm != vm.app.local_name: + vm.log.info('AUDIO connected to {}. Skipping.'.format(audiovm)) + return + + if not vm.features.check_with_template('audio', True): + return + + if not os.path.exists(self.pacat_pidfile(vm.xid)): + yield from self.start_audio_for_vm(vm) + def on_domain_spawn(self, vm, _event, **kwargs): """Handler of 'domain-spawn' event, starts GUI daemon for stubdomain""" try: @@ -340,7 +378,8 @@ class GUILauncher(object): vm.log.warning('Failed to start GUI for %s: %s', vm.name, str(e)) def on_domain_start(self, vm, _event, **kwargs): - """Handler of 'domain-start' event, starts GUI daemon for actual VM""" + """Handler of 'domain-start' event, starts GUI/AUDIO daemon for + actual VM """ try: if getattr(vm, 'guivm', None) != vm.app.local_name: return @@ -351,8 +390,18 @@ class GUILauncher(object): except qubesadmin.exc.QubesException as e: vm.log.warning('Failed to start GUI for %s: %s', vm.name, str(e)) + try: + if getattr(vm, 'audiovm', None) != vm.app.local_name: + return + if not vm.features.check_with_template('audio', True): + return + if kwargs.get('start_audio', 'True') == 'True': + asyncio.ensure_future(self.start_audio_for_vm(vm)) + except qubesadmin.exc.QubesException as e: + vm.log.warning('Failed to start AUDIO for %s: %s', vm.name, str(e)) + def on_connection_established(self, _subject, _event, **_kwargs): - """Handler of 'connection-established' event, used to launch GUI + """Handler of 'connection-established' event, used to launch GUI/AUDIO daemon for domains started before this tool. """ monitor_layout = get_monitor_layout() @@ -360,19 +409,23 @@ class GUILauncher(object): for vm in self.app.domains: if vm.klass == 'AdminVM': continue - if getattr(vm, 'guivm', None) != vm.app.local_name: - continue - if not vm.features.check_with_template('gui', True): - continue - power_state = vm.get_power_state() - if power_state == 'Running': - asyncio.ensure_future( - self.start_gui(vm, monitor_layout=monitor_layout)) - elif power_state == 'Transient': - # it is still starting, we'll get 'domain-start' event when - # fully started - if vm.virt_mode == 'hvm': - asyncio.ensure_future(self.start_gui_for_stubdomain(vm)) + if getattr(vm, 'guivm', None) == vm.app.local_name: + if vm.features.check_with_template('gui', True): + power_state = vm.get_power_state() + if power_state == 'Running': + asyncio.ensure_future( + self.start_gui(vm, monitor_layout=monitor_layout)) + elif power_state == 'Transient': + # it is still starting, we'll get 'domain-start' + # event when fully started + if vm.virt_mode == 'hvm': + asyncio.ensure_future( + self.start_gui_for_stubdomain(vm)) + if getattr(vm, 'audiovm', None) == vm.app.local_name: + if vm.features.check_with_template('audio', True): + power_state = vm.get_power_state() + if power_state == 'Running': + asyncio.ensure_future(self.start_audio(vm)) def register_events(self, events): """Register domain startup events in app.events dispatcher""" @@ -394,10 +447,10 @@ def x_reader(conn, callback): if 'XDG_RUNTIME_DIR' in os.environ: pidfile_path = os.path.join(os.environ['XDG_RUNTIME_DIR'], - 'qvm-start-gui.pid') + 'qvm-start-daemon.pid') else: pidfile_path = os.path.join(os.environ.get('HOME', '/'), - '.qvm-start-gui.pid') + '.qvm-start-daemon.pid') parser = qubesadmin.tools.QubesArgumentParser( description='start GUI for qube(s)', vmname_nargs='*') @@ -415,13 +468,13 @@ parser.add_argument('--notify-monitor-layout', action='store_true', def main(args=None): - """ Main function of qvm-start-gui tool""" + """ Main function of qvm-start-daemon tool""" args = parser.parse_args(args) if args.watch and not args.all_domains: parser.error('--watch option must be used with --all') if args.watch and args.notify_monitor_layout: parser.error('--watch cannot be used with --notify-monitor-layout') - launcher = GUILauncher(args.app) + launcher = DAEMONLauncher(args.app) if args.watch: if not have_events: parser.error('--watch option require Python >= 3.5') @@ -468,6 +521,8 @@ def main(args=None): if vm.is_running(): tasks.append(asyncio.ensure_future(launcher.start_gui( vm, force_stubdom=args.force_stubdomain))) + tasks.append(asyncio.ensure_future(launcher.start_audio( + vm))) if tasks: loop.run_until_complete(asyncio.wait(tasks)) loop.stop() diff --git a/rpm_spec/qubes-core-admin-client.spec.in b/rpm_spec/qubes-core-admin-client.spec.in index 2d7aa45..f7f79f0 100644 --- a/rpm_spec/qubes-core-admin-client.spec.in +++ b/rpm_spec/qubes-core-admin-client.spec.in @@ -51,7 +51,7 @@ make -C doc DESTDIR=$RPM_BUILD_ROOT \ %files %defattr(-,root,root,-) %doc LICENSE -%config /etc/xdg/autostart/qvm-start-gui.desktop +%config /etc/xdg/autostart/qvm-start-daemon.desktop %{_bindir}/qubes-* %{_bindir}/qvm-* %{_mandir}/man1/qvm-*.1*