I’ve just tried to create audio qube using debian-12-minimal template with "RECOMMENDED WAY” and it worked for me as well.
I’ve installed these packages:
Patch
--- qvm_start_daemon.py.orig 2024-05-03 12:56:17.535996438 +0000
+++ qvm_start_daemon.py 2024-05-03 13:04:34.394034525 +0000
@@ -322,7 +322,7 @@
class DAEMONLauncher:
"""Launch GUI/AUDIO daemon for VMs"""
- def __init__(self, app: qubesadmin.app.QubesBase, vm_names=None, kde=False):
+ def __init__(self, app: qubesadmin.app.QubesBase, services, vm_names=None, kde=False):
""" Initialize DAEMONLauncher.
:param app: :py:class:`qubesadmin.Qubes` instance
@@ -333,6 +333,7 @@
self.started_processes = {}
self.vm_names = vm_names
self.kde = kde
+ self.services = services
# cache XID values when the VM was still running -
# for cleanup purpose
@@ -491,6 +492,9 @@
:param monitor_layout: monitor layout to send; if None, fetch it from
local X server.
"""
+ if not "guivm" in self.services:
+ return
+
guid_cmd = self.common_guid_args(vm)
if self.kde:
guid_cmd.extend(self.kde_guid_args(vm))
@@ -519,6 +523,9 @@
This function is a coroutine.
"""
+ if not "guivm" in self.services:
+ return
+
want_stubdom = force
if not want_stubdom and \
vm.features.check_with_template('gui-emulated', False):
@@ -547,6 +554,9 @@
:param vm: VM for which start AUDIO daemon
"""
+ if not "audiovm" in self.services:
+ return
+
pacat_cmd = [PACAT_DAEMON_PATH, '-l', self.pacat_domid(vm), vm.name]
vm.log.info('Starting AUDIO')
@@ -562,6 +572,9 @@
one for target AppVM is running.
:param monitor_layout: monitor layout configuration
"""
+ if not "guivm" in self.services:
+ return
+
guivm = getattr(vm, 'guivm', None)
if guivm != vm.app.local_name:
vm.log.info('GUI connected to {}. Skipping.'.format(guivm))
@@ -583,6 +596,9 @@
:param vm: VM for which AUDIO daemon should be started
"""
+ if not "audiovm" in self.services:
+ return
+
audiovm = getattr(vm, 'audiovm', None)
if audiovm != vm.app.local_name:
vm.log.info('AUDIO connected to {}. Skipping.'.format(audiovm))
@@ -601,6 +617,9 @@
if not self.is_watched(vm):
return
+ if not "guivm" in self.services:
+ return
+
try:
if getattr(vm, 'guivm', None) != vm.app.local_name:
return
@@ -622,28 +641,32 @@
self.xid_cache[vm.name] = vm.xid, vm.stubdom_xid
- try:
- if getattr(vm, 'guivm', None) == vm.app.local_name and \
- vm.features.check_with_template('gui', True) and \
- kwargs.get('start_guid', 'True') == 'True':
- asyncio.ensure_future(self.start_gui_for_vm(vm))
- 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 and \
- vm.features.check_with_template('audio', True) and \
- 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))
+ if "guivm" in self.services:
+ try:
+ if getattr(vm, 'guivm', None) == vm.app.local_name and \
+ vm.features.check_with_template('gui', True) and \
+ kwargs.get('start_guid', 'True') == 'True':
+ asyncio.ensure_future(self.start_gui_for_vm(vm))
+ except qubesadmin.exc.QubesException as e:
+ vm.log.warning('Failed to start GUI for %s: %s', vm.name, str(e))
+ if "audiovm" in self.services:
+ try:
+ if getattr(vm, 'audiovm', None) == vm.app.local_name and \
+ vm.features.check_with_template('audio', True) and \
+ 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/AUDIO
daemon for domains started before this tool. """
- monitor_layout = get_monitor_layout()
self.app.domains.clear_cache()
+
+ if "guivm" in self.services:
+ monitor_layout = get_monitor_layout()
+
for vm in self.app.domains:
if vm.klass == 'AdminVM':
continue
@@ -653,17 +676,23 @@
power_state = vm.get_power_state()
if power_state == 'Running':
- asyncio.ensure_future(
- self.start_gui(vm, monitor_layout=monitor_layout))
- asyncio.ensure_future(self.start_audio(vm))
+
+ if "guivm" in self.services:
+ asyncio.ensure_future(
+ self.start_gui(vm, monitor_layout=monitor_layout))
+
+ if "audiovm" in self.services:
+ asyncio.ensure_future(self.start_audio(vm))
+
self.xid_cache[vm.name] = vm.xid, vm.stubdom_xid
elif power_state == 'Transient':
# it is still starting, we'll get 'domain-start'
# event when fully started
- if vm.virt_mode == 'hvm':
+ if vm.virt_mode == 'hvm' and "guivm" in self.services:
asyncio.ensure_future(
self.start_gui_for_stubdomain(vm))
+
def on_domain_stopped(self, vm, _event, **_kwargs):
"""Handler of 'domain-stopped' event, cleans up"""
@@ -678,8 +707,19 @@
return
if xid != -1:
self.cleanup_guid(xid)
+ self.cleanup_pacat_process(xid)
if stubdom_xid != -1:
self.cleanup_guid(stubdom_xid)
+ self.cleanup_pacat_process(stubdom_xid)
+
+ def cleanup_pacat_process(self, xid):
+ try:
+ with open(self.pacat_pidfile(xid)) as f:
+ pid = int(f.readline())
+ os.kill(pid, signal.SIGTERM)
+ print(f"Sent SIGTERM signal to pacat-simple-vchan process {pid}")
+ except OSError:
+ print(f"Failed to send SIGTERM signal for the pacat-simple-vchan with xid of {xid}")
def cleanup_guid(self, xid):
"""
@@ -743,8 +783,9 @@
only_if_service_enabled = ['guivm', 'audiovm']
enabled_services = [service for service in only_if_service_enabled if
os.path.exists('/var/run/qubes-service/%s' % service)]
- if not enabled_services and '--force' not in sys.argv and \
- not os.path.exists('/etc/qubes-release'):
+ if os.path.exists('/etc/qubes-release'):
+ enabled_services = only_if_service_enabled
+ if not enabled_services and '--force' not in sys.argv:
print(parser.format_help())
return
args = parser.parse_args(args)
@@ -758,7 +799,8 @@
launcher = DAEMONLauncher(
args.app,
vm_names=vm_names,
- kde=args.kde)
+ kde=args.kde,
+ services=enabled_services)
if args.watch:
fd = os.open(args.pidfile,
@@ -780,8 +822,14 @@
lock_f.flush()
lock_f.truncate()
loop = asyncio.get_event_loop()
- # pylint: disable=no-member
- events = qubesadmin.events.EventsDispatcher(args.app)
+
+ if "guivm" in enabled_services:
+ # pylint: disable=no-member
+ events = qubesadmin.events.EventsDispatcher(args.app)
+ else:
+ # pylint: disable=no-member
+ events = qubesadmin.events.EventsDispatcher(args.app,enable_cache=False)
+
# pylint: enable=no-member
launcher.register_events(events)
@@ -794,18 +842,20 @@
loop.add_signal_handler(signal.SIGHUP,
launcher.send_monitor_layout_all)
- conn = xcffib.connect()
- x_watcher = XWatcher(conn, args.app)
- x_fd = conn.get_file_descriptor()
- loop.add_reader(x_fd, x_watcher.event_reader,
- events_listener.cancel)
- x_watcher.update_keyboard_layout()
+ if "guivm" in enabled_services:
+ conn = xcffib.connect()
+ x_watcher = XWatcher(conn, args.app)
+ x_fd = conn.get_file_descriptor()
+ loop.add_reader(x_fd, x_watcher.event_reader,
+ events_listener.cancel)
+ x_watcher.update_keyboard_layout()
try:
loop.run_until_complete(events_listener)
except asyncio.CancelledError:
pass
- loop.remove_reader(x_fd)
+ if "guivm" in enabled_services:
+ loop.remove_reader(x_fd)
loop.stop()
loop.run_forever()
loop.close()