diff --git a/qubes/ext/core_features.py b/qubes/ext/core_features.py index b24dc9e7..4df3da9f 100644 --- a/qubes/ext/core_features.py +++ b/qubes/ext/core_features.py @@ -62,3 +62,25 @@ class CoreFeatures(qubes.ext.Extension): # if this is the first time qrexec was advertised, now can finish # template setup yield from vm.fire_event_async('template-postinstall') + + # pylint: disable=no-self-use + def set_servicevm_feature(self, subject): + if getattr(subject, 'provides_network', False): + subject.features['servicevm'] = 1 + elif 'servicevm' in subject.features: + del subject.features['servicevm'] + + @qubes.ext.handler('property-set:provides_network') + def on_property_set(self, subject, event, name, newvalue, oldvalue=None): + # pylint: disable=unused-argument + self.set_servicevm_feature(subject) + + @qubes.ext.handler('property-del:provides_network') + def on_property_del(self, subject, event, name): + # pylint: disable=unused-argument + self.set_servicevm_feature(subject) + + @qubes.ext.handler('domain-load') + def on_domain_load(self, subject, event): + # pylint: disable=unused-argument + self.set_servicevm_feature(subject) diff --git a/qubes/ext/qubesmanager.py b/qubes/ext/qubesmanager.py deleted file mode 100644 index 9db9b337..00000000 --- a/qubes/ext/qubesmanager.py +++ /dev/null @@ -1,70 +0,0 @@ -# -# The Qubes OS Project, https://www.qubes-os.org/ -# -# Copyright (C) 2014-2015 Joanna Rutkowska -# Copyright (C) 2014 Marek Marczykowski-Górecki -# -# Copyright (C) 2015 Wojtek Porczyk -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, see . -# - -'''Qubes Manager hooks. - -.. warning:: API defined here is not declared stable. -''' - -import dbus -import qubes.ext - - -class QubesManager(qubes.ext.Extension): - def __init__(self, *args, **kwargs): - super(QubesManager, self).__init__(*args, **kwargs) - try: - self._system_bus = dbus.SystemBus() - except dbus.exceptions.DBusException: - # we can't access Qubes() object here to check for offline mode, - # so lets assume it is this case... - self._system_bus = None - - # pylint: disable=no-self-use,unused-argument,too-few-public-methods - - @qubes.ext.handler('status:error') - def on_status_error(self, vm, event, status, message): - if self._system_bus is None: - return - try: - qubes_manager = self._system_bus.get_object( - 'org.qubesos.QubesManager', - '/org/qubesos/QubesManager') - qubes_manager.notify_error(vm.name, message, - dbus_interface='org.qubesos.QubesManager') - except dbus.DBusException: - # ignore the case when no qubes-manager is running - pass - - @qubes.ext.handler('status:no-error') - def on_status_no_error(self, vm, event, status, message): - if self._system_bus is None: - return - try: - qubes_manager = self._system_bus.get_object( - 'org.qubesos.QubesManager', - '/org/qubesos/QubesManager') - qubes_manager.clear_error_exact(vm.name, message, - dbus_interface='org.qubesos.QubesManager') - except dbus.DBusException: - # ignore the case when no qubes-manager is running - pass diff --git a/qubes/tests/ext.py b/qubes/tests/ext.py index 8767dfef..fde1f6c2 100644 --- a/qubes/tests/ext.py +++ b/qubes/tests/ext.py @@ -34,8 +34,11 @@ class TC_00_CoreFeatures(qubes.tests.QubesTestCase): self.features = {} self.vm.configure_mock(**{ 'features.get.side_effect': self.features.get, + 'features.items.side_effect': self.features.items, + 'features.__iter__.side_effect': self.features.__iter__, 'features.__contains__.side_effect': self.features.__contains__, 'features.__setitem__.side_effect': self.features.__setitem__, + 'features.__delitem__.side_effect': self.features.__delitem__, }) def test_010_notify_tools(self): @@ -181,6 +184,16 @@ class TC_00_CoreFeatures(qubes.tests.QubesTestCase): ('features.__contains__', ('gui',), {}), ]) + def test_100_servicevm_feature(self): + self.vm.provides_network = True + self.ext.set_servicevm_feature(self.vm) + self.assertEqual(self.features['servicevm'], 1) + + self.vm.provides_network = False + self.ext.set_servicevm_feature(self.vm) + self.assertNotIn('servicevm', self.features) + + class TC_10_WindowsFeatures(qubes.tests.QubesTestCase): def setUp(self): super().setUp() diff --git a/qubes/tests/vm/__init__.py b/qubes/tests/vm/__init__.py index ffb1f7c1..9850a459 100644 --- a/qubes/tests/vm/__init__.py +++ b/qubes/tests/vm/__init__.py @@ -65,7 +65,10 @@ class TestPool(object): return TestVolume(self) class TestApp(qubes.tests.TestEmitter): - labels = {1: qubes.Label(1, '0xcc0000', 'red')} + labels = {1: qubes.Label(1, '0xcc0000', 'red'), + 2: qubes.Label(2, '0x00cc00', 'green'), + 3: qubes.Label(3, '0x0000cc', 'blue'), + 4: qubes.Label(4, '0xcccccc', 'black')} check_updates_vm = False def get_label(self, label): diff --git a/qubes/tests/vm/qubesvm.py b/qubes/tests/vm/qubesvm.py index d488b7ba..3d3c5234 100644 --- a/qubes/tests/vm/qubesvm.py +++ b/qubes/tests/vm/qubesvm.py @@ -47,7 +47,10 @@ import qubes.tests import qubes.tests.vm class TestApp(object): - labels = {1: qubes.Label(1, '0xcc0000', 'red')} + labels = {1: qubes.Label(1, '0xcc0000', 'red'), + 2: qubes.Label(2, '0x00cc00', 'green'), + 3: qubes.Label(3, '0x0000cc', 'blue'), + 4: qubes.Label(4, '0xcccccc', 'black')} def __init__(self): self.domains = {} @@ -394,24 +397,46 @@ class TC_90_QubesVM(QubesVMTestsMixin, qubes.tests.QubesTestCase): with self.assertRaises(AttributeError): vm.uuid = uuid.uuid4() - @unittest.skip('TODO: how to not fail on making an icon symlink here?') - def test_130_label(self): + @unittest.mock.patch("os.symlink") + def test_130_label(self, _): vm = self.get_vm() self.assertPropertyDefaultValue(vm, 'label') self.assertPropertyValue(vm, 'label', self.app.labels[1], - self.app.labels[1], 'label-1') + self.app.labels[1], 'red') del vm.label self.assertPropertyDefaultValue(vm, 'label') self.assertPropertyValue(vm, 'label', 'red', - self.app.labels[1], 'label-1') + self.app.labels[1], 'red') self.assertPropertyValue(vm, 'label', 'label-1', - self.app.labels[1], 'label-1') + self.app.labels[1], 'red') def test_131_label_invalid(self): vm = self.get_vm() self.assertPropertyInvalidValue(vm, 'label', 'invalid') self.assertPropertyInvalidValue(vm, 'label', 123) + @unittest.mock.patch("os.symlink") + def test_135_icon(self, _): + vm = self.get_vm(cls=qubes.vm.appvm.AppVM) + vm.label = "red" + self.assertEqual(vm.icon, "appvm-red") + + templatevm = self.get_vm(cls=qubes.vm.templatevm.TemplateVM) + templatevm.label = "blue" + self.assertEqual(templatevm.icon, "templatevm-blue") + + vm.template_for_dispvms = True + dispvm = self.get_vm(cls=qubes.vm.dispvm.DispVM, template=vm, + dispid=10) + dispvm.label = "black" + self.assertEqual(dispvm.icon, "dispvm-black") + + vm = self.get_vm() + vm.label = "green" + vm.features["servicevm"] = 1 + self.assertEqual(vm.icon, "servicevm-green") + + def test_160_memory(self): vm = self.get_vm() self.assertPropertyDefaultValue(vm, 'memory', 400) diff --git a/qubes/vm/qubesvm.py b/qubes/vm/qubesvm.py index c7f40726..e0b42da9 100644 --- a/qubes/vm/qubesvm.py +++ b/qubes/vm/qubesvm.py @@ -2101,6 +2101,20 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): return None + @qubes.stateless_property + def icon(self): + """freedesktop icon name, suitable for use in + :py:meth:`PyQt4.QtGui.QIcon.fromTheme`""" + # pylint: disable=comparison-with-callable + raw_icon_name = self.label.name + if self.klass == 'TemplateVM': + return 'templatevm-' + raw_icon_name + if self.klass == 'DispVM': + return 'dispvm-' + raw_icon_name + if self.features.get('servicevm', False): + return 'servicevm-' + raw_icon_name + return 'appvm-' + raw_icon_name + @property def kernelopts_common(self): """Kernel options which should be used in addition to *kernelopts* diff --git a/rpm_spec/core-dom0.spec.in b/rpm_spec/core-dom0.spec.in index c392255f..64dbe2a5 100644 --- a/rpm_spec/core-dom0.spec.in +++ b/rpm_spec/core-dom0.spec.in @@ -277,7 +277,6 @@ fi %{python3_sitelib}/qubes/ext/gui.py %{python3_sitelib}/qubes/ext/audio.py %{python3_sitelib}/qubes/ext/pci.py -%{python3_sitelib}/qubes/ext/qubesmanager.py %{python3_sitelib}/qubes/ext/r3compatibility.py %{python3_sitelib}/qubes/ext/services.py %{python3_sitelib}/qubes/ext/windows.py diff --git a/setup.py b/setup.py index ddbc132e..1a606caf 100644 --- a/setup.py +++ b/setup.py @@ -62,7 +62,6 @@ if __name__ == '__main__': 'qubes.ext': [ 'qubes.ext.admin = qubes.ext.admin:AdminExtension', 'qubes.ext.core_features = qubes.ext.core_features:CoreFeatures', - 'qubes.ext.qubesmanager = qubes.ext.qubesmanager:QubesManager', 'qubes.ext.gui = qubes.ext.gui:GUI', 'qubes.ext.audio = qubes.ext.audio:AUDIO', 'qubes.ext.r3compatibility = qubes.ext.r3compatibility:R3Compatibility',