Changed feature keyboard_layout to a property
Purpose: make it easier to implement more robust keyboard layout tools and propagation. references QubesOS/qubes-issues#1396 references QubesOS/qubes-issues#4294
This commit is contained in:
parent
b889bf98f4
commit
06e2d14a97
@ -12,7 +12,6 @@ services:
|
|||||||
install:
|
install:
|
||||||
- sudo apt-get -y install python3-gi gir1.2-gtk-3.0
|
- sudo apt-get -y install python3-gi gir1.2-gtk-3.0
|
||||||
- pip3 install --quiet -r ci/requirements.txt
|
- pip3 install --quiet -r ci/requirements.txt
|
||||||
- git clone https://github.com/"${TRAVIS_REPO_SLUG%%/*}"/qubes-builder ~/qubes-builder
|
|
||||||
- git clone https://github.com/"${TRAVIS_REPO_SLUG%%/*}"/qubes-core-qrexec ~/qubes-core-qrexec
|
- git clone https://github.com/"${TRAVIS_REPO_SLUG%%/*}"/qubes-core-qrexec ~/qubes-core-qrexec
|
||||||
script:
|
script:
|
||||||
- PYTHONPATH=test-packages:~/qubes-core-qrexec pylint qubes
|
- PYTHONPATH=test-packages:~/qubes-core-qrexec pylint qubes
|
||||||
|
@ -21,8 +21,6 @@
|
|||||||
# License along with this library; if not, see <https://www.gnu.org/licenses/>.
|
# License along with this library; if not, see <https://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
import qubes.config
|
import qubes.config
|
||||||
import qubes.ext
|
import qubes.ext
|
||||||
import qubes.exc
|
import qubes.exc
|
||||||
@ -89,15 +87,13 @@ class GUI(qubes.ext.Extension):
|
|||||||
|
|
||||||
# Add GuiVM Xen ID for gui-daemon
|
# Add GuiVM Xen ID for gui-daemon
|
||||||
if getattr(vm, 'guivm', None):
|
if getattr(vm, 'guivm', None):
|
||||||
if vm != vm.guivm and vm.guivm.is_running():
|
if vm != vm.guivm:
|
||||||
|
vm.untrusted_qdb.write('/keyboard-layout', vm.keyboard_layout)
|
||||||
|
|
||||||
|
if vm.guivm.is_running():
|
||||||
vm.untrusted_qdb.write('/qubes-gui-domain-xid',
|
vm.untrusted_qdb.write('/qubes-gui-domain-xid',
|
||||||
str(vm.guivm.xid))
|
str(vm.guivm.xid))
|
||||||
|
|
||||||
# Add keyboard layout from that of GuiVM
|
|
||||||
kbd_layout = vm.guivm.features.get('keyboard-layout', None)
|
|
||||||
if kbd_layout:
|
|
||||||
vm.untrusted_qdb.write('/keyboard-layout', kbd_layout)
|
|
||||||
|
|
||||||
# Set GuiVM prefix
|
# Set GuiVM prefix
|
||||||
guivm_windows_prefix = vm.features.get('guivm-windows-prefix', 'GuiVM')
|
guivm_windows_prefix = vm.features.get('guivm-windows-prefix', 'GuiVM')
|
||||||
if vm.features.get('service.guivm-gui-agent', None):
|
if vm.features.get('service.guivm-gui-agent', None):
|
||||||
@ -121,22 +117,21 @@ class GUI(qubes.ext.Extension):
|
|||||||
attached_vm.untrusted_qdb.write('/qubes-gui-domain-xid',
|
attached_vm.untrusted_qdb.write('/qubes-gui-domain-xid',
|
||||||
str(vm.xid))
|
str(vm.xid))
|
||||||
|
|
||||||
@qubes.ext.handler('domain-feature-pre-set:keyboard-layout')
|
@qubes.ext.handler('property-reset:keyboard_layout')
|
||||||
def on_feature_pre_set(self, subject, event, feature, value, oldvalue=None):
|
def on_keyboard_reset(self, vm, event, name, oldvalue=None):
|
||||||
untrusted_xkb_layout = value.split('+')
|
if not vm.is_running():
|
||||||
if len(untrusted_xkb_layout) != 3:
|
return
|
||||||
raise qubes.exc.QubesValueError("Invalid number of parameters")
|
kbd_layout = vm.keyboard_layout
|
||||||
|
|
||||||
untrusted_layout = untrusted_xkb_layout[0]
|
vm.untrusted_qdb.write('/keyboard-layout', kbd_layout)
|
||||||
untrusted_variant = untrusted_xkb_layout[1]
|
|
||||||
untrusted_options = untrusted_xkb_layout[2]
|
|
||||||
|
|
||||||
re_variant = r'^[a-zA-Z0-9-_]*$'
|
@qubes.ext.handler('property-set:keyboard_layout')
|
||||||
re_options = r'^[a-zA-Z0-9-_:,]*$'
|
def on_keyboard_set(self, vm, event, name, newvalue, oldvalue=None):
|
||||||
|
for domain in vm.app.domains:
|
||||||
|
if getattr(domain, 'guivm', None) == vm and \
|
||||||
|
domain.property_is_default('keyboard_layout'):
|
||||||
|
domain.fire_event('property-reset:keyboard_layout',
|
||||||
|
name='keyboard_layout', oldvalue=oldvalue)
|
||||||
|
|
||||||
if not untrusted_layout.isalpha():
|
if vm.is_running():
|
||||||
raise qubes.exc.QubesValueError("Invalid layout provided")
|
vm.untrusted_qdb.write('/keyboard-layout', newvalue)
|
||||||
if not re.match(re_variant, untrusted_variant):
|
|
||||||
raise qubes.exc.QubesValueError("Invalid variant provided")
|
|
||||||
if not re.match(re_options, untrusted_options):
|
|
||||||
raise qubes.exc.QubesValueError("Invalid options provided")
|
|
||||||
|
@ -99,6 +99,7 @@ class TestApp(qubes.tests.TestEmitter):
|
|||||||
self.default_pool_kernel = 'linux-kernel'
|
self.default_pool_kernel = 'linux-kernel'
|
||||||
self.default_qrexec_timeout = 60
|
self.default_qrexec_timeout = 60
|
||||||
self.default_netvm = None
|
self.default_netvm = None
|
||||||
|
self.default_guivm = None
|
||||||
self.domains = TestVMsCollection()
|
self.domains = TestVMsCollection()
|
||||||
#: jinja2 environment for libvirt XML templates
|
#: jinja2 environment for libvirt XML templates
|
||||||
self.env = jinja2.Environment(
|
self.env = jinja2.Environment(
|
||||||
|
@ -1877,7 +1877,8 @@ class TC_90_QubesVM(QubesVMTestsMixin, qubes.tests.QubesTestCase):
|
|||||||
name='appvm', qid=3)
|
name='appvm', qid=3)
|
||||||
vm.netvm = None
|
vm.netvm = None
|
||||||
vm.guivm = guivm
|
vm.guivm = guivm
|
||||||
guivm.features['keyboard-layout'] = 'fr++'
|
vm.is_running = lambda: True
|
||||||
|
guivm.keyboard_layout = 'fr++'
|
||||||
guivm.is_running = lambda: True
|
guivm.is_running = lambda: True
|
||||||
vm.events_enabled = True
|
vm.events_enabled = True
|
||||||
test_qubesdb = TestQubesDB()
|
test_qubesdb = TestQubesDB()
|
||||||
@ -1925,6 +1926,7 @@ class TC_90_QubesVM(QubesVMTestsMixin, qubes.tests.QubesTestCase):
|
|||||||
name='appvm', qid=3)
|
name='appvm', qid=3)
|
||||||
vm.netvm = None
|
vm.netvm = None
|
||||||
vm.audiovm = audiovm
|
vm.audiovm = audiovm
|
||||||
|
vm.is_running = lambda: True
|
||||||
audiovm.is_running = lambda: True
|
audiovm.is_running = lambda: True
|
||||||
vm.events_enabled = True
|
vm.events_enabled = True
|
||||||
test_qubesdb = TestQubesDB()
|
test_qubesdb = TestQubesDB()
|
||||||
@ -1968,8 +1970,87 @@ class TC_90_QubesVM(QubesVMTestsMixin, qubes.tests.QubesTestCase):
|
|||||||
name='sys-gui', qid=2, provides_network=False)
|
name='sys-gui', qid=2, provides_network=False)
|
||||||
guivm.is_running = lambda: True
|
guivm.is_running = lambda: True
|
||||||
guivm.events_enabled = True
|
guivm.events_enabled = True
|
||||||
with self.assertRaises(qubes.exc.QubesValueError):
|
with self.assertRaises(qubes.exc.QubesPropertyValueError):
|
||||||
guivm.features['keyboard-layout'] = 'fr123++'
|
guivm.keyboard_layout = 'fr123++'
|
||||||
|
|
||||||
|
with self.assertRaises(qubes.exc.QubesPropertyValueError):
|
||||||
|
guivm.keyboard_layout = 'fr+???+'
|
||||||
|
|
||||||
|
with self.assertRaises(qubes.exc.QubesPropertyValueError):
|
||||||
|
guivm.keyboard_layout = 'fr++variant?'
|
||||||
|
|
||||||
|
with self.assertRaises(qubes.exc.QubesPropertyValueError):
|
||||||
|
guivm.keyboard_layout = 'fr'
|
||||||
|
|
||||||
|
@unittest.mock.patch('qubes.utils.get_timezone')
|
||||||
|
@unittest.mock.patch('qubes.utils.urandom')
|
||||||
|
@unittest.mock.patch('qubes.vm.qubesvm.QubesVM.untrusted_qdb')
|
||||||
|
def test_625_qdb_keyboard_layout_change(self, mock_qubesdb, mock_urandom,
|
||||||
|
mock_timezone):
|
||||||
|
mock_urandom.return_value = b'A' * 64
|
||||||
|
mock_timezone.return_value = 'UTC'
|
||||||
|
template = self.get_vm(
|
||||||
|
cls=qubes.vm.templatevm.TemplateVM, name='template')
|
||||||
|
template.netvm = None
|
||||||
|
guivm = self.get_vm(cls=qubes.vm.appvm.AppVM, template=template,
|
||||||
|
name='sys-gui', qid=2, provides_network=False)
|
||||||
|
vm = self.get_vm(cls=qubes.vm.appvm.AppVM, template=template,
|
||||||
|
name='appvm', qid=3)
|
||||||
|
vm.netvm = None
|
||||||
|
vm.guivm = guivm
|
||||||
|
vm.is_running = lambda: True
|
||||||
|
guivm.keyboard_layout = 'fr++'
|
||||||
|
guivm.is_running = lambda: True
|
||||||
|
vm.events_enabled = True
|
||||||
|
test_qubesdb = TestQubesDB()
|
||||||
|
mock_qubesdb.write.side_effect = test_qubesdb.write
|
||||||
|
mock_qubesdb.rm.side_effect = test_qubesdb.rm
|
||||||
|
vm.create_qdb_entries()
|
||||||
|
self.maxDiff = None
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
'/name': 'test-inst-appvm',
|
||||||
|
'/type': 'AppVM',
|
||||||
|
'/default-user': 'user',
|
||||||
|
'/keyboard-layout': 'fr++',
|
||||||
|
'/qubes-vm-type': 'AppVM',
|
||||||
|
'/qubes-gui-domain-xid': '{}'.format(guivm.xid),
|
||||||
|
'/qubes-debug-mode': '0',
|
||||||
|
'/qubes-base-template': 'test-inst-template',
|
||||||
|
'/qubes-timezone': 'UTC',
|
||||||
|
'/qubes-random-seed': base64.b64encode(b'A' * 64),
|
||||||
|
'/qubes-vm-persistence': 'rw-only',
|
||||||
|
'/qubes-vm-updateable': 'False',
|
||||||
|
'/qubes-block-devices': '',
|
||||||
|
'/qubes-usb-devices': '',
|
||||||
|
'/qubes-iptables': 'reload',
|
||||||
|
'/qubes-iptables-error': '',
|
||||||
|
'/qubes-iptables-header': unittest.mock.ANY,
|
||||||
|
'/qubes-service/qubes-update-check': '0',
|
||||||
|
'/qubes-service/meminfo-writer': '1',
|
||||||
|
'/connected-ips': '',
|
||||||
|
'/connected-ips6': '',
|
||||||
|
}
|
||||||
|
|
||||||
|
with self.subTest('default'):
|
||||||
|
self.assertEqual(test_qubesdb.data, expected)
|
||||||
|
|
||||||
|
with self.subTest('value_change'):
|
||||||
|
vm.keyboard_layout = 'de++'
|
||||||
|
expected['/keyboard-layout'] = 'de++'
|
||||||
|
self.assertEqual(test_qubesdb.data, expected)
|
||||||
|
|
||||||
|
with self.subTest('value_revert'):
|
||||||
|
vm.keyboard_layout = qubes.property.DEFAULT
|
||||||
|
expected['/keyboard-layout'] = 'fr++'
|
||||||
|
self.assertEqual(test_qubesdb.data, expected)
|
||||||
|
|
||||||
|
with self.subTest('no_default'):
|
||||||
|
guivm.keyboard_layout = qubes.property.DEFAULT
|
||||||
|
vm.keyboard_layout = qubes.property.DEFAULT
|
||||||
|
expected['/keyboard-layout'] = 'us++'
|
||||||
|
self.assertEqual(test_qubesdb.data, expected)
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def coroutine_mock(self, mock, *args, **kwargs):
|
def coroutine_mock(self, mock, *args, **kwargs):
|
||||||
|
@ -28,6 +28,7 @@ import libvirt
|
|||||||
import qubes
|
import qubes
|
||||||
import qubes.exc
|
import qubes.exc
|
||||||
import qubes.vm
|
import qubes.vm
|
||||||
|
from qubes.vm.qubesvm import _setter_kbd_layout
|
||||||
|
|
||||||
|
|
||||||
class AdminVM(qubes.vm.BaseVM):
|
class AdminVM(qubes.vm.BaseVM):
|
||||||
@ -61,6 +62,14 @@ class AdminVM(qubes.vm.BaseVM):
|
|||||||
setter=qubes.property.forbidden,
|
setter=qubes.property.forbidden,
|
||||||
doc='True if this machine may be updated on its own.')
|
doc='True if this machine may be updated on its own.')
|
||||||
|
|
||||||
|
# for changes in keyboard_layout, see also the same property in QubesVM
|
||||||
|
keyboard_layout = qubes.property(
|
||||||
|
'keyboard_layout',
|
||||||
|
type=str,
|
||||||
|
setter=_setter_kbd_layout,
|
||||||
|
default='us++',
|
||||||
|
doc='Keyboard layout for this VM')
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import base64
|
import base64
|
||||||
import grp
|
import grp
|
||||||
|
import re
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
import shutil
|
import shutil
|
||||||
@ -115,6 +116,32 @@ def _setter_virt_mode(self, prop, value):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def _setter_kbd_layout(self, prop, value):
|
||||||
|
untrusted_xkb_layout = value.split('+')
|
||||||
|
if len(untrusted_xkb_layout) != 3:
|
||||||
|
raise qubes.exc.QubesPropertyValueError(
|
||||||
|
self, prop, value, "invalid number of keyboard layout parameters")
|
||||||
|
|
||||||
|
untrusted_layout = untrusted_xkb_layout[0]
|
||||||
|
untrusted_variant = untrusted_xkb_layout[1]
|
||||||
|
untrusted_options = untrusted_xkb_layout[2]
|
||||||
|
|
||||||
|
re_variant = r'^[a-zA-Z0-9-_]*$'
|
||||||
|
re_options = r'^[a-zA-Z0-9-_:,]*$'
|
||||||
|
|
||||||
|
if not untrusted_layout.isalpha():
|
||||||
|
raise qubes.exc.QubesPropertyValueError(
|
||||||
|
self, prop, value, "Invalid keyboard layout provided")
|
||||||
|
if not re.match(re_variant, untrusted_variant):
|
||||||
|
raise qubes.exc.QubesPropertyValueError(
|
||||||
|
self, prop, value, "Invalid layout variant provided")
|
||||||
|
if not re.match(re_options, untrusted_options):
|
||||||
|
raise qubes.exc.QubesPropertyValueError(
|
||||||
|
self, prop, value, "Invalid layout options provided")
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
def _default_virt_mode(self):
|
def _default_virt_mode(self):
|
||||||
if self.devices['pci'].persistent():
|
if self.devices['pci'].persistent():
|
||||||
return 'hvm'
|
return 'hvm'
|
||||||
@ -690,6 +717,14 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
|
|||||||
setter=qubes.property.forbidden,
|
setter=qubes.property.forbidden,
|
||||||
doc='True if this machine may be updated on its own.')
|
doc='True if this machine may be updated on its own.')
|
||||||
|
|
||||||
|
# for changes in keyboard_layout, see also the same property in AdminVM
|
||||||
|
keyboard_layout = qubes.property(
|
||||||
|
'keyboard_layout',
|
||||||
|
default=(lambda self: getattr(self.guivm, 'keyboard_layout', 'us++')),
|
||||||
|
type=str,
|
||||||
|
setter=_setter_kbd_layout,
|
||||||
|
doc='Keyboard layout for this VM')
|
||||||
|
|
||||||
#
|
#
|
||||||
# static, class-wide properties
|
# static, class-wide properties
|
||||||
#
|
#
|
||||||
|
Loading…
Reference in New Issue
Block a user