Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a lightdm session for sys-gui #152

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
11 changes: 10 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 All @@ -72,6 +76,11 @@ Options

Notify running instance in --watch mode about changed monitor layout

.. option:: --gui-allow-fullscreen

Override ``allow_fullscreen`` GUI option to true. Intended for use with the
GUI domain.

Authors
-------

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
86 changes: 77 additions & 9 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,27 @@ 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=None,
exit_after_vms=False,
kde=False,
gui_allow_fullscreen=False):
""" 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
:param gui_allow_fullscreen: override allow_fullscreen to true
"""
self.app = app
self.started_processes = {}
self.kde = False
self.vms = vms
self.exit_after_vms = exit_after_vms
self.kde = kde
self.gui_allow_fullscreen = gui_allow_fullscreen

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

@asyncio.coroutine
def send_monitor_layout(self, vm, layout=None, startup=False):
Expand Down Expand Up @@ -440,6 +457,8 @@ def common_guid_args(self, vm):

guivm = self.app.domains[vm.guivm]
options = retrieve_gui_daemon_options(vm, guivm)
if self.gui_allow_fullscreen:
options['allow_fullscreen'] = True
config = serialize_gui_daemon_options(options)
config_path = self.guid_config_file(vm.xid)
self.write_guid_config(config_path, config)
Expand Down Expand Up @@ -596,6 +615,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 +632,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.xid)

try:
if getattr(vm, 'guivm', None) == vm.app.local_name and \
vm.features.check_with_template('gui', True) and \
Expand All @@ -636,24 +663,43 @@ 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.xid)
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.xid in self.started_vms:
self.started_vms.remove(vm.xid)
else:
logger.warning('VM not in started_vms: %s (%s)', vm, vm.xid)
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 +718,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 +743,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 @@ -698,6 +760,8 @@ def register_events(self, events):
parser.add_argument('--force', action='store_true', default=False,
help='Force running daemon without enabled services'
' \'guivm-gui-agent\' or \'audiovm-audio-agent\'')
parser.add_argument('--gui-allow-fullscreen', action='store_true',
help='Override allow_fullscreen GUI option to true')


def main(args=None):
Expand All @@ -710,13 +774,16 @@ 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)
if args.kde:
launcher.kde = True

launcher = DAEMONLauncher(
args.app,
vms=None if args.all_domains else args.domains,
exit_after_vms=args.exit,
kde=args.kde,
gui_allow_fullscreen=args.gui_allow_fullscreen,
)
if args.watch:
if not have_events:
parser.error('--watch option require Python >= 3.5')
Expand All @@ -728,6 +795,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 --gui-allow-fullscreen "$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