Merge remote-tracking branch 'origin/pr/118'
* origin/pr/118: qvm-start-daemon: adjust pacat pid file path qvm-start-daemon: check if layout is parsed qvm-start-daemon: allow multiple options in keyboard layout qvm-start-daemon: improve parsing args for setting keyboard layout qvm-start-daemon: set keyboard-layout only for the first set layout gui: set keyboard layout when starting daemon daemon: start it for dom0 unconditionnaly qvm-start-daemon: ensure separate task between GUI/AUDIO qvm-start-daemon: allow starting only if service enabled Fix and improvements from Marek's comments Change qvm-start-gui to qvm-start-daemon for handling audio too Support for AudioVM
This commit is contained in:
commit
4971faa462
5
Makefile
5
Makefile
@ -11,8 +11,9 @@ build:
|
|||||||
install:
|
install:
|
||||||
$(PYTHON) setup.py install -O1 $(PYTHON_PREFIX_ARG) --root $(DESTDIR)
|
$(PYTHON) setup.py install -O1 $(PYTHON_PREFIX_ARG) --root $(DESTDIR)
|
||||||
install -d $(DESTDIR)/etc/xdg/autostart
|
install -d $(DESTDIR)/etc/xdg/autostart
|
||||||
install -m 0644 etc/qvm-start-gui.desktop $(DESTDIR)/etc/xdg/autostart/
|
install -m 0644 etc/qvm-start-daemon.desktop $(DESTDIR)/etc/xdg/autostart/
|
||||||
install -D scripts/qvm-console $(DESTDIR)/usr/bin/qvm-console
|
install -d $(DESTDIR)/usr/bin
|
||||||
|
ln -sf qvm-start-daemon $(DESTDIR)/usr/bin/qvm-start-gui
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf test-packages/__pycache__ qubesadmin/__pycache__
|
rm -rf test-packages/__pycache__ qubesadmin/__pycache__
|
||||||
|
@ -358,8 +358,8 @@ man_pages = [
|
|||||||
u'Manage (Qubes-specific) services started in VM', _man_pages_author, 1),
|
u'Manage (Qubes-specific) services started in VM', _man_pages_author, 1),
|
||||||
('manpages/qvm-shutdown', 'qvm-shutdown',
|
('manpages/qvm-shutdown', 'qvm-shutdown',
|
||||||
u'Gracefully shut down a qube', _man_pages_author, 1),
|
u'Gracefully shut down a qube', _man_pages_author, 1),
|
||||||
('manpages/qvm-start-gui', 'qvm-start-gui',
|
('manpages/qvm-start-daemon', 'qvm-start-daemon',
|
||||||
u'Start GUI daemon for qubes', _man_pages_author, 1),
|
u'Start GUI/AUDIO daemon for qubes', _man_pages_author, 1),
|
||||||
('manpages/qvm-start', 'qvm-start',
|
('manpages/qvm-start', 'qvm-start',
|
||||||
u'Start a specified qube', _man_pages_author, 1),
|
u'Start a specified qube', _man_pages_author, 1),
|
||||||
('manpages/qvm-tags', 'qvm-tags',
|
('manpages/qvm-tags', 'qvm-tags',
|
||||||
|
@ -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
|
: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
|
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,
|
: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.
|
command.
|
||||||
|
|
||||||
This feature is applicable only when qube's `virt_mode` is set to `hvm`.
|
This feature is applicable only when qube's `virt_mode` is set to `hvm`.
|
||||||
|
72
doc/manpages/qvm-start-daemon.rst
Normal file
72
doc/manpages/qvm-start-daemon.rst
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
.. program:: qvm-start-daemon
|
||||||
|
|
||||||
|
:program:`qvm-start-daemon` -- start GUI/AUDIO for qube(s)
|
||||||
|
=========================================================
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
`qvm-start-gui` has been renamed to `qvm-start-daemon` as it handles now
|
||||||
|
`gui` and `audio`.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
This page was autogenerated from command-line parser. It shouldn't be 1:1
|
||||||
|
conversion, because it would add little value. Please revise it and add
|
||||||
|
more descriptive help, which normally won't fit in standard ``--help``
|
||||||
|
option.
|
||||||
|
|
||||||
|
After rewrite, please remove this admonition.
|
||||||
|
|
||||||
|
Synopsis
|
||||||
|
--------
|
||||||
|
|
||||||
|
:command:`qvm-start-daemon` [-h] [--verbose] [--quiet] [--all] [--exclude *EXCLUDE*] [--watch] [--force-stubdomain] [--pidfile *PIDFILE*] [--notify-monitory-layout] [*VMNAME* [*VMNAME* ...]]
|
||||||
|
|
||||||
|
Options
|
||||||
|
-------
|
||||||
|
|
||||||
|
.. option:: --help, -h
|
||||||
|
|
||||||
|
show this help message and exit
|
||||||
|
|
||||||
|
.. option:: --verbose, -v
|
||||||
|
|
||||||
|
increase verbosity
|
||||||
|
|
||||||
|
.. option:: --quiet, -q
|
||||||
|
|
||||||
|
decrease verbosity
|
||||||
|
|
||||||
|
.. option:: --all
|
||||||
|
|
||||||
|
perform the action on all qubes
|
||||||
|
|
||||||
|
.. option:: --exclude
|
||||||
|
|
||||||
|
exclude the qube from --all
|
||||||
|
|
||||||
|
.. option:: --watch
|
||||||
|
|
||||||
|
Keep watching for further domains startups, must be used with --all
|
||||||
|
|
||||||
|
.. option:: --force-stubdomain
|
||||||
|
|
||||||
|
Start GUI to stubdomain-emulated VGA, even if gui-agent is running in the VM
|
||||||
|
|
||||||
|
.. option:: --pidfile
|
||||||
|
|
||||||
|
Pidfile path to create in --watch mode
|
||||||
|
|
||||||
|
.. option:: --notify-monitor-layout
|
||||||
|
|
||||||
|
Notify running instance in --watch mode about changed monitor layout
|
||||||
|
|
||||||
|
Authors
|
||||||
|
-------
|
||||||
|
|
||||||
|
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
|
||||||
|
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
|
||||||
|
| Marek Marczykowski <marmarek at invisiblethingslab dot com>
|
||||||
|
| Wojtek Porczyk <woju at invisiblethingslab dot com>
|
||||||
|
|
||||||
|
.. vim: ts=3 sw=3 et tw=80
|
@ -1,67 +0,0 @@
|
|||||||
.. program:: qvm-start-gui
|
|
||||||
|
|
||||||
:program:`qvm-start-gui` -- start GUI for qube(s)
|
|
||||||
=========================================================
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
This page was autogenerated from command-line parser. It shouldn't be 1:1
|
|
||||||
conversion, because it would add little value. Please revise it and add
|
|
||||||
more descriptive help, which normally won't fit in standard ``--help``
|
|
||||||
option.
|
|
||||||
|
|
||||||
After rewrite, please remove this admonition.
|
|
||||||
|
|
||||||
Synopsis
|
|
||||||
--------
|
|
||||||
|
|
||||||
:command:`qvm-start-gui` [-h] [--verbose] [--quiet] [--all] [--exclude *EXCLUDE*] [--watch] [--force-stubdomain] [--pidfile *PIDFILE*] [--notify-monitory-layout] [*VMNAME* [*VMNAME* ...]]
|
|
||||||
|
|
||||||
Options
|
|
||||||
-------
|
|
||||||
|
|
||||||
.. option:: --help, -h
|
|
||||||
|
|
||||||
show this help message and exit
|
|
||||||
|
|
||||||
.. option:: --verbose, -v
|
|
||||||
|
|
||||||
increase verbosity
|
|
||||||
|
|
||||||
.. option:: --quiet, -q
|
|
||||||
|
|
||||||
decrease verbosity
|
|
||||||
|
|
||||||
.. option:: --all
|
|
||||||
|
|
||||||
perform the action on all qubes
|
|
||||||
|
|
||||||
.. option:: --exclude
|
|
||||||
|
|
||||||
exclude the qube from --all
|
|
||||||
|
|
||||||
.. option:: --watch
|
|
||||||
|
|
||||||
Keep watching for further domains startups, must be used with --all
|
|
||||||
|
|
||||||
.. option:: --force-stubdomain
|
|
||||||
|
|
||||||
Start GUI to stubdomain-emulated VGA, even if gui-agent is running in the VM
|
|
||||||
|
|
||||||
.. option:: --pidfile
|
|
||||||
|
|
||||||
Pidfile path to create in --watch mode
|
|
||||||
|
|
||||||
.. option:: --notify-monitor-layout
|
|
||||||
|
|
||||||
Notify running instance in --watch mode about changed monitor layout
|
|
||||||
|
|
||||||
Authors
|
|
||||||
-------
|
|
||||||
|
|
||||||
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
|
|
||||||
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
|
|
||||||
| Marek Marczykowski <marmarek at invisiblethingslab dot com>
|
|
||||||
| Wojtek Porczyk <woju at invisiblethingslab dot com>
|
|
||||||
|
|
||||||
.. vim: ts=3 sw=3 et tw=80
|
|
1
doc/manpages/qvm-start-gui.rst
Symbolic link
1
doc/manpages/qvm-start-gui.rst
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
qvm-start-daemon.rst
|
@ -164,10 +164,10 @@ qubesadmin\.tests\.tools\.qvm\_start module
|
|||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
: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:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
@ -164,10 +164,10 @@ qubesadmin\.tools\.qvm\_start module
|
|||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
: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:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
7
etc/qvm-start-daemon.desktop
Normal file
7
etc/qvm-start-daemon.desktop
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[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
|
@ -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
|
|
@ -26,14 +26,15 @@ import unittest.mock
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
import qubesadmin.tests
|
import qubesadmin.tests
|
||||||
import qubesadmin.tools.qvm_start_gui
|
import qubesadmin.tools.qvm_start_daemon
|
||||||
import qubesadmin.vm
|
import qubesadmin.vm
|
||||||
|
|
||||||
|
|
||||||
class TC_00_qvm_start_gui(qubesadmin.tests.QubesTestCase):
|
class TC_00_qvm_start_gui(qubesadmin.tests.QubesTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TC_00_qvm_start_gui, self).setUp()
|
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')
|
@unittest.mock.patch('subprocess.check_output')
|
||||||
def test_000_kde_args(self, proc_mock):
|
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)
|
VGA1 disconnected (normal left inverted right x axis y axis)
|
||||||
VIRTUAL1 disconnected (normal left inverted right x axis y axis)
|
VIRTUAL1 disconnected (normal left inverted right x axis y axis)
|
||||||
'''.splitlines()
|
'''.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'])
|
['1920 1200 0 0\n'])
|
||||||
|
|
||||||
@unittest.mock.patch('subprocess.Popen')
|
@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)
|
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)
|
VGA1 connected 1280x1024+1600+0 (normal left inverted right x axis y axis)
|
||||||
'''.splitlines()
|
'''.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'])
|
['1600 900 0 0\n', '1280 1024 1600 0\n'])
|
||||||
|
|
||||||
@unittest.mock.patch('subprocess.Popen')
|
@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*+
|
1920x1200 60.00*+
|
||||||
'''.splitlines()
|
'''.splitlines()
|
||||||
dpi = 150
|
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(
|
['2560 1920 0 0 {} {}\n'.format(
|
||||||
int(2560 / dpi * 254 / 10),
|
int(2560 / dpi * 254 / 10),
|
||||||
int(1920 / 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*+
|
1920x1200 60.00*+
|
||||||
'''.splitlines()
|
'''.splitlines()
|
||||||
dpi = 200
|
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(
|
['2560 1920 0 0 {} {}\n'.format(
|
||||||
int(2560 / dpi * 254 / 10),
|
int(2560 / dpi * 254 / 10),
|
||||||
int(1920 / 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*+
|
1920x1200 60.00*+
|
||||||
'''.splitlines()
|
'''.splitlines()
|
||||||
dpi = 300
|
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(
|
['2560 1920 0 0 {} {}\n'.format(
|
||||||
int(2560 / dpi * 254 / 10),
|
int(2560 / dpi * 254 / 10),
|
||||||
int(1920 / 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()
|
patch_send_monitor_layout.start()
|
||||||
monitor_layout = ['1920 1080 0 0\n']
|
monitor_layout = ['1920 1080 0 0\n']
|
||||||
mock_get_monior_layout = unittest.mock.patch(
|
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
|
mock_get_monior_layout.return_value = monitor_layout
|
||||||
|
|
||||||
self.launcher.send_monitor_layout_all()
|
self.launcher.send_monitor_layout_all()
|
@ -36,8 +36,9 @@ class TestVMUsage(qubesadmin.tests.QubesTestCase):
|
|||||||
b'sys-firewall class=AppVM state=Running\n'
|
b'sys-firewall class=AppVM state=Running\n'
|
||||||
|
|
||||||
self.global_properties = ['default_dispvm', 'default_netvm',
|
self.global_properties = ['default_dispvm', 'default_netvm',
|
||||||
'default_guivm', 'default_template',
|
'default_guivm', 'default_audiovm',
|
||||||
'clockvm', 'updatevm', 'management_dispvm']
|
'default_template', 'clockvm', 'updatevm',
|
||||||
|
'management_dispvm']
|
||||||
|
|
||||||
for prop in self.global_properties:
|
for prop in self.global_properties:
|
||||||
self.app.expected_calls[
|
self.app.expected_calls[
|
||||||
@ -47,8 +48,8 @@ class TestVMUsage(qubesadmin.tests.QubesTestCase):
|
|||||||
self.vms = ['vm1', 'vm2', 'sys-net', 'sys-firewall',
|
self.vms = ['vm1', 'vm2', 'sys-net', 'sys-firewall',
|
||||||
'template1', 'template2']
|
'template1', 'template2']
|
||||||
|
|
||||||
self.vm_properties = ['template', 'netvm', 'guivm', 'default_dispvm',
|
self.vm_properties = ['template', 'netvm', 'guivm', 'audiovm',
|
||||||
'management_dispvm']
|
'default_dispvm', 'management_dispvm']
|
||||||
|
|
||||||
for vm in self.vms:
|
for vm in self.vms:
|
||||||
for prop in self.vm_properties:
|
for prop in self.vm_properties:
|
||||||
|
@ -118,7 +118,7 @@ def process_actions(parser, args, target):
|
|||||||
if args.value is not None:
|
if args.value is not None:
|
||||||
if str(args.value).lower() == "none":
|
if str(args.value).lower() == "none":
|
||||||
if args.property in ["default_dispvm", "netvm", "template",
|
if args.property in ["default_dispvm", "netvm", "template",
|
||||||
"guivm"]:
|
"guivm", "audiovm"]:
|
||||||
args.value = ''
|
args.value = ''
|
||||||
try:
|
try:
|
||||||
setattr(target, args.property, args.value)
|
setattr(target, args.property, args.value)
|
||||||
|
@ -18,15 +18,15 @@
|
|||||||
# You should have received a copy of the GNU Lesser General Public License along
|
# You should have received a copy of the GNU Lesser General Public License along
|
||||||
# with this program; if not, see <http://www.gnu.org/licenses/>.
|
# with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
""" GUI daemon launcher tool"""
|
""" GUI/AUDIO daemon launcher tool"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
import subprocess
|
import subprocess
|
||||||
import asyncio
|
import asyncio
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
|
import sys
|
||||||
import xcffib
|
import xcffib
|
||||||
import xcffib.xproto # pylint: disable=unused-import
|
import xcffib.xproto # pylint: disable=unused-import
|
||||||
|
|
||||||
@ -46,6 +46,7 @@ except ImportError:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
GUI_DAEMON_PATH = '/usr/bin/qubes-guid'
|
GUI_DAEMON_PATH = '/usr/bin/qubes-guid'
|
||||||
|
PACAT_DAEMON_PATH = '/usr/bin/pacat-simple-vchan'
|
||||||
QUBES_ICON_DIR = '/usr/share/icons/hicolor/128x128/devices'
|
QUBES_ICON_DIR = '/usr/share/icons/hicolor/128x128/devices'
|
||||||
|
|
||||||
# "LVDS connected 1024x768+0+0 (normal left inverted right) 304mm x 228mm"
|
# "LVDS connected 1024x768+0+0 (normal left inverted right) 304mm x 228mm"
|
||||||
@ -113,159 +114,42 @@ def get_monitor_layout():
|
|||||||
return outputs
|
return outputs
|
||||||
|
|
||||||
|
|
||||||
class GUILauncher(object):
|
def set_keyboard_layout(vm):
|
||||||
"""Launch GUI daemon for VMs"""
|
"""Set layout configuration into features for Gui admin extension"""
|
||||||
|
try:
|
||||||
|
# Examples of 'xprop -root _XKB_RULES_NAMES' output values:
|
||||||
|
# "evdev", "pc105", "fr", "oss", ""
|
||||||
|
# "evdev", "pc105", "pl,us", ",", "grp:win_switch,compose:caps"
|
||||||
|
|
||||||
|
# We use the first layout provided
|
||||||
|
xkb_re = r'_XKB_RULES_NAMES\(STRING\) = ' \
|
||||||
|
r'\"(.*)\", \"(.*)\", \"(.*)\", \"(.*)\", \"(.*)\"\n'
|
||||||
|
xkb_rules_names = subprocess.check_output(
|
||||||
|
['xprop', '-root', '_XKB_RULES_NAMES']).decode()
|
||||||
|
xkb_parsed = re.match(xkb_re, xkb_rules_names)
|
||||||
|
if xkb_parsed:
|
||||||
|
xkb_layout = [x.split(',')[0] for x in xkb_parsed.groups()[2:4]]
|
||||||
|
# We keep all options
|
||||||
|
xkb_layout.append(xkb_parsed.group(5))
|
||||||
|
keyboard_layout = '+'.join(xkb_layout)
|
||||||
|
vm.features['keyboard-layout'] = keyboard_layout
|
||||||
|
else:
|
||||||
|
vm.log.warning('Failed to parse layout for %s', vm)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
vm.log.warning('Failed to set layout for %s: %s', vm, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
class DAEMONLauncher:
|
||||||
|
"""Launch GUI/AUDIO daemon for VMs"""
|
||||||
|
|
||||||
def __init__(self, app: qubesadmin.app.QubesBase):
|
def __init__(self, app: qubesadmin.app.QubesBase):
|
||||||
""" Initialize GUILauncher.
|
""" Initialize DAEMONLauncher.
|
||||||
|
|
||||||
:param app: :py:class:`qubesadmin.Qubes` instance
|
:param app: :py:class:`qubesadmin.Qubes` instance
|
||||||
"""
|
"""
|
||||||
self.app = app
|
self.app = app
|
||||||
self.started_processes = {}
|
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
|
@asyncio.coroutine
|
||||||
def send_monitor_layout(self, vm, layout=None, startup=False):
|
def send_monitor_layout(self, vm, layout=None, startup=False):
|
||||||
"""Send monitor layout to a given VM
|
"""Send monitor layout to a given VM
|
||||||
@ -326,6 +210,186 @@ class GUILauncher(object):
|
|||||||
asyncio.ensure_future(self.send_monitor_layout(vm,
|
asyncio.ensure_future(self.send_monitor_layout(vm,
|
||||||
monitor_layout))
|
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.{}'.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):
|
def on_domain_spawn(self, vm, _event, **kwargs):
|
||||||
"""Handler of 'domain-spawn' event, starts GUI daemon for stubdomain"""
|
"""Handler of 'domain-spawn' event, starts GUI daemon for stubdomain"""
|
||||||
try:
|
try:
|
||||||
@ -340,19 +404,26 @@ class GUILauncher(object):
|
|||||||
vm.log.warning('Failed to start GUI for %s: %s', vm.name, str(e))
|
vm.log.warning('Failed to start GUI for %s: %s', vm.name, str(e))
|
||||||
|
|
||||||
def on_domain_start(self, vm, _event, **kwargs):
|
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:
|
try:
|
||||||
if getattr(vm, 'guivm', None) != vm.app.local_name:
|
if getattr(vm, 'guivm', None) == vm.app.local_name and \
|
||||||
return
|
vm.features.check_with_template('gui', True) and \
|
||||||
if not vm.features.check_with_template('gui', True):
|
kwargs.get('start_guid', 'True') == 'True':
|
||||||
return
|
|
||||||
if kwargs.get('start_guid', 'True') == 'True':
|
|
||||||
asyncio.ensure_future(self.start_gui_for_vm(vm))
|
asyncio.ensure_future(self.start_gui_for_vm(vm))
|
||||||
except qubesadmin.exc.QubesException as e:
|
except qubesadmin.exc.QubesException as e:
|
||||||
vm.log.warning('Failed to start GUI for %s: %s', vm.name, str(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))
|
||||||
|
|
||||||
def on_connection_established(self, _subject, _event, **_kwargs):
|
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. """
|
daemon for domains started before this tool. """
|
||||||
|
|
||||||
monitor_layout = get_monitor_layout()
|
monitor_layout = get_monitor_layout()
|
||||||
@ -360,19 +431,18 @@ class GUILauncher(object):
|
|||||||
for vm in self.app.domains:
|
for vm in self.app.domains:
|
||||||
if vm.klass == 'AdminVM':
|
if vm.klass == 'AdminVM':
|
||||||
continue
|
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()
|
power_state = vm.get_power_state()
|
||||||
if power_state == 'Running':
|
if power_state == 'Running':
|
||||||
asyncio.ensure_future(
|
asyncio.ensure_future(
|
||||||
self.start_gui(vm, monitor_layout=monitor_layout))
|
self.start_gui(vm, monitor_layout=monitor_layout))
|
||||||
|
asyncio.ensure_future(self.start_audio(vm))
|
||||||
elif power_state == 'Transient':
|
elif power_state == 'Transient':
|
||||||
# it is still starting, we'll get 'domain-start' event when
|
# it is still starting, we'll get 'domain-start'
|
||||||
# fully started
|
# event when fully started
|
||||||
if vm.virt_mode == 'hvm':
|
if vm.virt_mode == 'hvm':
|
||||||
asyncio.ensure_future(self.start_gui_for_stubdomain(vm))
|
asyncio.ensure_future(
|
||||||
|
self.start_gui_for_stubdomain(vm))
|
||||||
|
|
||||||
def register_events(self, events):
|
def register_events(self, events):
|
||||||
"""Register domain startup events in app.events dispatcher"""
|
"""Register domain startup events in app.events dispatcher"""
|
||||||
@ -394,10 +464,10 @@ def x_reader(conn, callback):
|
|||||||
|
|
||||||
if 'XDG_RUNTIME_DIR' in os.environ:
|
if 'XDG_RUNTIME_DIR' in os.environ:
|
||||||
pidfile_path = os.path.join(os.environ['XDG_RUNTIME_DIR'],
|
pidfile_path = os.path.join(os.environ['XDG_RUNTIME_DIR'],
|
||||||
'qvm-start-gui.pid')
|
'qvm-start-daemon.pid')
|
||||||
else:
|
else:
|
||||||
pidfile_path = os.path.join(os.environ.get('HOME', '/'),
|
pidfile_path = os.path.join(os.environ.get('HOME', '/'),
|
||||||
'.qvm-start-gui.pid')
|
'.qvm-start-daemon.pid')
|
||||||
|
|
||||||
parser = qubesadmin.tools.QubesArgumentParser(
|
parser = qubesadmin.tools.QubesArgumentParser(
|
||||||
description='start GUI for qube(s)', vmname_nargs='*')
|
description='start GUI for qube(s)', vmname_nargs='*')
|
||||||
@ -412,16 +482,35 @@ parser.add_argument('--pidfile', action='store', default=pidfile_path,
|
|||||||
parser.add_argument('--notify-monitor-layout', action='store_true',
|
parser.add_argument('--notify-monitor-layout', action='store_true',
|
||||||
help='Notify running instance in --watch mode'
|
help='Notify running instance in --watch mode'
|
||||||
' about changed monitor layout')
|
' about changed monitor layout')
|
||||||
|
parser.add_argument('--set-keyboard-layout', action='store_true',
|
||||||
|
help='Set keyboard layout values into GuiVM features.'
|
||||||
|
'This option is implied by --watch')
|
||||||
|
# Add it for the help only
|
||||||
|
parser.add_argument('--force', action='store_true', default=False,
|
||||||
|
help='Force running daemon without enabled services'
|
||||||
|
' \'guivm-gui-agent\' or \'audiovm-audio-agent\'')
|
||||||
|
|
||||||
|
|
||||||
def main(args=None):
|
def main(args=None):
|
||||||
""" Main function of qvm-start-gui tool"""
|
""" Main function of qvm-start-daemon tool"""
|
||||||
|
only_if_service_enabled = ['guivm-gui-agent', 'audiovm-audio-agent']
|
||||||
|
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'):
|
||||||
|
print(parser.format_help())
|
||||||
|
return
|
||||||
args = parser.parse_args(args)
|
args = parser.parse_args(args)
|
||||||
if args.watch and not args.all_domains:
|
if args.watch and not args.all_domains:
|
||||||
parser.error('--watch option must be used with --all')
|
parser.error('--watch option must be used with --all')
|
||||||
if args.watch and args.notify_monitor_layout:
|
if args.watch and args.notify_monitor_layout:
|
||||||
parser.error('--watch cannot be used with --notify-monitor-layout')
|
parser.error('--watch cannot be used with --notify-monitor-layout')
|
||||||
launcher = GUILauncher(args.app)
|
if args.watch and 'guivm-gui-agent' in enabled_services:
|
||||||
|
args.set_keyboard_layout = True
|
||||||
|
if args.set_keyboard_layout or os.path.exists('/etc/qubes-release'):
|
||||||
|
guivm = args.app.domains[args.app.local_name]
|
||||||
|
set_keyboard_layout(guivm)
|
||||||
|
launcher = DAEMONLauncher(args.app)
|
||||||
if args.watch:
|
if args.watch:
|
||||||
if not have_events:
|
if not have_events:
|
||||||
parser.error('--watch option require Python >= 3.5')
|
parser.error('--watch option require Python >= 3.5')
|
||||||
@ -468,6 +557,8 @@ def main(args=None):
|
|||||||
if vm.is_running():
|
if vm.is_running():
|
||||||
tasks.append(asyncio.ensure_future(launcher.start_gui(
|
tasks.append(asyncio.ensure_future(launcher.start_gui(
|
||||||
vm, force_stubdom=args.force_stubdomain)))
|
vm, force_stubdom=args.force_stubdomain)))
|
||||||
|
tasks.append(asyncio.ensure_future(launcher.start_audio(
|
||||||
|
vm)))
|
||||||
if tasks:
|
if tasks:
|
||||||
loop.run_until_complete(asyncio.wait(tasks))
|
loop.run_until_complete(asyncio.wait(tasks))
|
||||||
loop.stop()
|
loop.stop()
|
@ -128,14 +128,14 @@ def vm_dependencies(app, reference_vm):
|
|||||||
result = []
|
result = []
|
||||||
|
|
||||||
global_properties = ['default_dispvm', 'default_netvm', 'default_guivm',
|
global_properties = ['default_dispvm', 'default_netvm', 'default_guivm',
|
||||||
'default_template', 'clockvm', 'updatevm',
|
'default_audiovm', 'default_template', 'clockvm',
|
||||||
'management_dispvm']
|
'updatevm', 'management_dispvm']
|
||||||
|
|
||||||
for prop in global_properties:
|
for prop in global_properties:
|
||||||
if reference_vm == getattr(app, prop, None):
|
if reference_vm == getattr(app, prop, None):
|
||||||
result.append((None, prop))
|
result.append((None, prop))
|
||||||
|
|
||||||
vm_properties = ['template', 'netvm', 'guivm',
|
vm_properties = ['template', 'netvm', 'guivm', 'audiovm',
|
||||||
'default_dispvm', 'management_dispvm']
|
'default_dispvm', 'management_dispvm']
|
||||||
|
|
||||||
for vm in app.domains:
|
for vm in app.domains:
|
||||||
|
@ -51,7 +51,7 @@ make -C doc DESTDIR=$RPM_BUILD_ROOT \
|
|||||||
%files
|
%files
|
||||||
%defattr(-,root,root,-)
|
%defattr(-,root,root,-)
|
||||||
%doc LICENSE
|
%doc LICENSE
|
||||||
%config /etc/xdg/autostart/qvm-start-gui.desktop
|
%config /etc/xdg/autostart/qvm-start-daemon.desktop
|
||||||
%{_bindir}/qubes-*
|
%{_bindir}/qubes-*
|
||||||
%{_bindir}/qvm-*
|
%{_bindir}/qvm-*
|
||||||
%{_mandir}/man1/qvm-*.1*
|
%{_mandir}/man1/qvm-*.1*
|
||||||
|
Loading…
Reference in New Issue
Block a user