Skip to content

Commit

Permalink
Add a lightdm session for sys-gui
Browse files Browse the repository at this point in the history
Starts just sys-gui VM and qvm-start-daemon in a special mode.

See QubesOS/qubes-issues#5662.
  • Loading branch information
pwmarcz committed Jul 21, 2020
1 parent 60d28dc commit cbdf94c
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 8 deletions.
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ install:
install -d $(DESTDIR)/etc/xdg/autostart
install -m 0644 etc/qvm-start-daemon.desktop $(DESTDIR)/etc/xdg/autostart/
install -m 0644 etc/qvm-start-daemon-kde.desktop $(DESTDIR)/etc/xdg/autostart/
install -d $(DESTDIR)/usr/share/xsessions/
install -m 0644 xsessions/sys-gui.desktop $(DESTDIR)/usr/share/xsessions/
install -d $(DESTDIR)/usr/bin
ln -sf qvm-start-daemon $(DESTDIR)/usr/bin/qvm-start-gui
install -m 0755 scripts/qubes-gui-session $(DESTDIR)/usr/bin/

clean:
rm -rf test-packages/__pycache__ qubesadmin/__pycache__
Expand Down
6 changes: 5 additions & 1 deletion doc/manpages/qvm-start-daemon.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ Options

.. option:: --watch

Keep watching for further domains startups, must be used with --all
Keep watching for further domains startups

.. option:: --exit

Exit after all watched domains have exited

.. option:: --force-stubdomain

Expand Down
4 changes: 4 additions & 0 deletions etc/sys-gui.desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[Desktop Entry]
Name=GUI Domain (sys-gui)
Exec=qubes-gui-session sys-gui
Type=Application
75 changes: 68 additions & 7 deletions qubesadmin/tools/qvm_start_daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import re
import functools
import sys
import logging

import xcffib
import xcffib.xproto # pylint: disable=unused-import

Expand All @@ -46,6 +48,8 @@
except ImportError:
pass

logger = logging.getLogger('qvm-start-daemon')

GUI_DAEMON_PATH = '/usr/bin/qubes-guid'
PACAT_DAEMON_PATH = '/usr/bin/pacat-simple-vchan'
QUBES_ICON_DIR = '/usr/share/icons/hicolor/128x128/devices'
Expand Down Expand Up @@ -322,14 +326,24 @@ def get_monitor_layout():
class DAEMONLauncher:
"""Launch GUI/AUDIO daemon for VMs"""

def __init__(self, app: qubesadmin.app.QubesBase):
def __init__(self,
app: qubesadmin.app.QubesBase,
vms,
exit_after_vms,
kde):
""" Initialize DAEMONLauncher.
:param app: :py:class:`qubesadmin.Qubes` instance
:param vms: VMs to watch for, or None if watching for all
:param exit_after_vms: exit when all VMs are finished
"""
self.app = app
self.started_processes = {}
self.kde = False
self.vms = vms
self.exit_after_vms = exit_after_vms
self.kde = kde

self.started_vms = set()
self.cancel = None

@asyncio.coroutine
def send_monitor_layout(self, vm, layout=None, startup=False):
Expand Down Expand Up @@ -596,6 +610,9 @@ def start_audio(self, vm):

def on_domain_spawn(self, vm, _event, **kwargs):
"""Handler of 'domain-spawn' event, starts GUI daemon for stubdomain"""
if not self.is_watched(vm):
return

try:
if getattr(vm, 'guivm', None) != vm.app.local_name:
return
Expand All @@ -610,6 +627,11 @@ def on_domain_spawn(self, vm, _event, **kwargs):
def on_domain_start(self, vm, _event, **kwargs):
"""Handler of 'domain-start' event, starts GUI/AUDIO daemon for
actual VM """
if not self.is_watched(vm):
return

self.started_vms.add(vm)

try:
if getattr(vm, 'guivm', None) == vm.app.local_name and \
vm.features.check_with_template('gui', True) and \
Expand All @@ -636,24 +658,42 @@ def on_connection_established(self, _subject, _event, **_kwargs):
if vm.klass == 'AdminVM':
continue

if not self.is_watched(vm):
continue

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))
self.started_vms.add(vm)
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 self.exit_after_vms and not self.started_vms:
logger.info('No VMs started yet, exiting')
self.cancel()

def on_domain_stopped(self, vm, _event, **_kwargs):
"""Handler of 'domain-stopped' event, cleans up"""
if not self.is_watched(vm):
return

self.cleanup_guid(vm.xid)
if vm.virt_mode == 'hvm':
self.cleanup_guid(vm.stubdom_xid)

if vm not in self.started_vms:
logger.warning('VM not in started_vms: %s', vm)
self.started_vms.remove(vm)
if self.exit_after_vms and not self.started_vms:
logger.info('All VMs stopped, exiting')
self.cancel()

def cleanup_guid(self, xid):
"""
Clean up after qubes-guid. Removes the auto-generated configuration
Expand All @@ -672,6 +712,20 @@ def register_events(self, events):
self.on_connection_established)
events.add_handler('domain-stopped', self.on_domain_stopped)

def set_cancel(self, cancel):
"""Set a 'cancel' callback in case of early exit"""
self.cancel = cancel

def is_watched(self, vm):
'''
Should we watch this VM for changes
'''

if self.vms is None:
return True
return vm in self.vms


if 'XDG_RUNTIME_DIR' in os.environ:
pidfile_path = os.path.join(os.environ['XDG_RUNTIME_DIR'],
'qvm-start-daemon.pid')
Expand All @@ -683,7 +737,9 @@ def register_events(self, events):
description='start GUI for qube(s)', vmname_nargs='*')
parser.add_argument('--watch', action='store_true',
help='Keep watching for further domains'
' startups, must be used with --all')
' startups')
parser.add_argument('--exit', action='store_true',
help='Exit when all domains exit')
parser.add_argument('--force-stubdomain', action='store_true',
help='Start GUI to stubdomain-emulated VGA,'
' even if gui-agent is running in the VM')
Expand All @@ -710,11 +766,15 @@ def main(args=None):
print(parser.format_help())
return
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 = DAEMONLauncher(args.app)

launcher = DAEMONLauncher(
args.app,
vms=None if args.all_domains else args.domains,
exit_after_vms=args.exit,
kde=args.kde,
)
if args.kde:
launcher.kde = True
if args.watch:
Expand All @@ -728,6 +788,7 @@ def main(args=None):
launcher.register_events(events)

events_listener = asyncio.ensure_future(events.listen_for_events())
launcher.set_cancel(events_listener.cancel)

for signame in ('SIGINT', 'SIGTERM'):
loop.add_signal_handler(getattr(signal, signame),
Expand Down
1 change: 1 addition & 0 deletions rpm_spec/qubes-core-admin-client.spec.in
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ make -C doc DESTDIR=$RPM_BUILD_ROOT \
%{_bindir}/qvm-*
%{_mandir}/man1/qvm-*.1*
%{_mandir}/man1/qubes*.1*
%{_datadir}/xsessions/sys-gui.desktop

%files -n python%{python3_pkgversion}-qubesadmin
%{python3_sitelib}/qubesadmin-*egg-info
Expand Down
17 changes: 17 additions & 0 deletions scripts/qubes-gui-session
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/bash

print_usage() {
cat >&2 <<USAGE
Usage: $0 vmname
Starts given VM and runs its associated GUI daemon. Used as X session for the
GUI domain.
USAGE
}

if [ $# -lt 1 ] ; then
print_usage
exit 1
fi

qvm-start sys-gui
exec qvm-start-daemon --watch --exit "$1"
4 changes: 4 additions & 0 deletions xsessions/sys-gui.desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[Desktop Entry]
Name=GUI Domain (sys-gui)
Exec=qubes-gui-session
Type=Application

0 comments on commit cbdf94c

Please sign in to comment.