From c6e284ce03d6ed51a933a3469b04d6b257090f2b Mon Sep 17 00:00:00 2001 From: "Hugh O. Brock" Date: Fri, 21 Sep 2007 16:28:31 -0400 Subject: [PATCH] Add button to connect and disconnect cdrom source device for full virt guests. --- src/virtManager/choosecd.py | 130 ++++++++++ src/virtManager/create.py | 83 +------ src/virtManager/details.py | 30 ++- src/virtManager/domain.py | 61 +++++ src/virtManager/opticalhelper.py | 106 ++++++++ src/vmm-choose-cd.glade | 404 +++++++++++++++++++++++++++++++ src/vmm-details.glade | 23 +- 7 files changed, 757 insertions(+), 80 deletions(-) create mode 100644 src/virtManager/choosecd.py create mode 100644 src/virtManager/opticalhelper.py create mode 100644 src/vmm-choose-cd.glade diff --git a/src/virtManager/choosecd.py b/src/virtManager/choosecd.py new file mode 100644 index 000000000..b187a645f --- /dev/null +++ b/src/virtManager/choosecd.py @@ -0,0 +1,130 @@ +# +# Copyright (C) 2006 Red Hat, Inc. +# Copyright (C) 2006 Hugh O. Brock +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# +import gtk.glade +import gobject +import logging +from virtManager.opticalhelper import vmmOpticalDriveHelper + +class vmmChooseCD(gobject.GObject): + __gsignals__ = {"cdrom-chosen": (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + (str, str, str)), # type, source, target +} + def __init__(self, config, target): + self.__gobject_init__() + self.window = gtk.glade.XML(config.get_glade_dir() + "/vmm-choose-cd.glade", "vmm-choose-cd", domain="virt-manager") + self.config = config + self.window.get_widget("vmm-choose-cd").hide() + self.target = target + + self.window.signal_autoconnect({ + "on_media_toggled": self.media_toggled, + "on_fv_iso_location_browse_clicked": self.browse_fv_iso_location, + "on_cd_path_changed": self.change_cd_path, + "on_ok_clicked": self.ok, + "on_vmm_choose_cd_delete_event": self.cancel, + "on_cancel_clicked": self.cancel, + }) + + self.window.get_widget("physical-media").set_active(True) + + # set up the list for the cd-path widget + cd_list = self.window.get_widget("cd-path") + # Fields are raw device path, volume label, flag indicating + # whether volume is present or not, and HAL path + cd_model = gtk.ListStore(str, str, bool, str) + cd_list.set_model(cd_model) + text = gtk.CellRendererText() + cd_list.pack_start(text, True) + cd_list.add_attribute(text, 'text', 1) + cd_list.add_attribute(text, 'sensitive', 2) + try: + self.optical_helper = vmmOpticalDriveHelper(self.window.get_widget("cd-path")) + self.optical_helper.populate_opt_media() + self.window.get_widget("physical-media").set_sensitive(True) + except Exception, e: + logging.error("Unable to create optical-helper widget: '%s'", e) + self.window.get_widget("physical-media").set_sensitive(False) + + def set_target(self, target): + self.target=target + + def close(self,ignore1=None,ignore2=None): + self.window.get_widget("vmm-choose-cd").hide() + return 1 + + def cancel(self,ignore1=None,ignore2=None): + self.close() + + def show(self): + win = self.window.get_widget("vmm-choose-cd") + win.show() + + def ok(self,ignore1=None, ignore2=None): + if self.window.get_widget("iso-image").get_active(): + self.emit("cdrom-chosen", "file", self.window.get_widget("iso-path").get_text(), self.target) + else: + cd = self.window.get_widget("cd-path") + model = cd.get_model() + self.emit("cdrom-chosen", "block", model.get_value(cd.get_active_iter(), 0), self.target) + self.close() + + def media_toggled(self, ignore1=None, ignore2=None): + if self.window.get_widget("physical-media").get_active(): + self.window.get_widget("cd-path").set_sensitive(True) + self.window.get_widget("iso-path").set_sensitive(False) + self.window.get_widget("iso-file-chooser").set_sensitive(False) + else: + self.window.get_widget("cd-path").set_sensitive(False) + self.window.get_widget("iso-path").set_sensitive(True) + self.window.get_widget("iso-file-chooser").set_sensitive(True) + + def change_cd_path(self, ignore1=None, ignore2=None): + pass + + def browse_fv_iso_location(self, ignore1=None, ignore2=None): + file = self._browse_file(_("Locate ISO Image"), type="iso") + if file != None: + self.window.get_widget("iso-path").set_text(file) + + def _browse_file(self, dialog_name, folder=None, type=None): + # user wants to browse for an ISO + fcdialog = gtk.FileChooserDialog(dialog_name, + self.window.get_widget("vmm-choose-cd"), + gtk.FILE_CHOOSER_ACTION_OPEN, + (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, + gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT), + None) + if type != None: + f = gtk.FileFilter() + f.add_pattern("*." + type) + fcdialog.set_filter(f) + if folder != None: + fcdialog.set_current_folder(folder) + response = fcdialog.run() + fcdialog.hide() + if(response == gtk.RESPONSE_ACCEPT): + filename = fcdialog.get_filename() + fcdialog.destroy() + return filename + else: + fcdialog.destroy() + return None + +gobject.type_register(vmmChooseCD) diff --git a/src/virtManager/create.py b/src/virtManager/create.py index 63f986d95..b36c5b984 100644 --- a/src/virtManager/create.py +++ b/src/virtManager/create.py @@ -37,6 +37,7 @@ import traceback from virtManager.asyncjob import vmmAsyncJob from virtManager.error import vmmErrorDialog from virtManager.createmeter import vmmCreateMeter +from virtManager.opticalhelper import vmmOpticalDriveHelper VM_PARA_VIRT = 1 VM_FULLY_VIRT = 2 @@ -136,17 +137,12 @@ class vmmCreate(gobject.GObject): cd_list.add_attribute(text, 'text', 1) cd_list.add_attribute(text, 'sensitive', 2) try: - # Get a connection to the SYSTEM bus - self.bus = dbus.SystemBus() - # Get a handle to the HAL service - hal_object = self.bus.get_object('org.freedesktop.Hal', '/org/freedesktop/Hal/Manager') - self.hal_iface = dbus.Interface(hal_object, 'org.freedesktop.Hal.Manager') - self.populate_opt_media(cd_model) + self.optical_helper = vmmOpticalDriveHelper(self.window.get_widget("cd-path")) + self.optical_helper.populate_opt_media() + self.window.get_widget("media-physical").set_sensitive(True) except Exception, e: - logging.error("Unable to connect to HAL to list cdrom volumes: '%s'", e) + logging.error("Unable to create optical-helper widget: '%s'", e) self.window.get_widget("media-physical").set_sensitive(False) - self.bus = None - self.hal_iface = None if os.getuid() != 0: self.window.get_widget("media-physical").set_sensitive(False) @@ -1048,75 +1044,6 @@ class vmmCreate(gobject.GObject): message_box.destroy() return res - def populate_opt_media(self, model): - # get a list of optical devices with data discs in, for FV installs - vollabel = {} - volpath = {} - # Track device add/removes so we can detect newly inserted CD media - self.hal_iface.connect_to_signal("DeviceAdded", self._device_added) - self.hal_iface.connect_to_signal("DeviceRemoved", self._device_removed) - - # Find info about all current present media - for d in self.hal_iface.FindDeviceByCapability("volume"): - vol = self.bus.get_object("org.freedesktop.Hal", d) - if vol.GetPropertyBoolean("volume.is_disc") and \ - vol.GetPropertyBoolean("volume.disc.has_data"): - devnode = vol.GetProperty("block.device") - label = vol.GetProperty("volume.label") - if label == None or len(label) == 0: - label = devnode - vollabel[devnode] = label - volpath[devnode] = d - - - for d in self.hal_iface.FindDeviceByCapability("storage.cdrom"): - dev = self.bus.get_object("org.freedesktop.Hal", d) - devnode = dev.GetProperty("block.device") - if vollabel.has_key(devnode): - model.append([devnode, vollabel[devnode], True, volpath[devnode]]) - else: - model.append([devnode, _("No media present"), False, None]) - - def _device_added(self, path): - vol = self.bus.get_object("org.freedesktop.Hal", path) - if vol.QueryCapability("volume"): - if vol.GetPropertyBoolean("volume.is_disc") and \ - vol.GetPropertyBoolean("volume.disc.has_data"): - devnode = vol.GetProperty("block.device") - label = vol.GetProperty("volume.label") - if label == None or len(label) == 0: - label = devnode - - cdlist = self.window.get_widget("cd-path") - model = cdlist.get_model() - - # Search for the row with matching device node and - # fill in info about inserted media - for row in model: - if row[0] == devnode: - row[1] = label - row[2] = True - row[3] = path - - def _device_removed(self, path): - vol = self.bus.get_object("org.freedesktop.Hal", path) - cdlist = self.window.get_widget("cd-path") - model = cdlist.get_model() - - active = cdlist.get_active() - idx = 0 - # Search for the row containing matching HAL volume path - # and update (clear) it, de-activating it if its currently - # selected - for row in model: - if row[3] == path: - row[1] = _("No media present") - row[2] = False - row[3] = None - if idx == active: - cdlist.set_active(-1) - idx = idx + 1 - def populate_url_model(self, model, urls): model.clear() for url in urls: diff --git a/src/virtManager/details.py b/src/virtManager/details.py index 7ac244e16..a8d428aaf 100644 --- a/src/virtManager/details.py +++ b/src/virtManager/details.py @@ -27,6 +27,7 @@ import traceback from virtManager.error import vmmErrorDialog from virtManager.addhardware import vmmAddHardware +from virtManager.choosecd import vmmChooseCD import virtinst import urlgrabber.progress as progress @@ -86,7 +87,8 @@ class vmmDetails(gobject.GObject): self.window.get_widget("hw-panel").set_show_tabs(False) self.addhw = None - + self.choose_cd = None + self.cpu_usage_graph = sparkline.Sparkline() self.window.get_widget("graph-table").attach(self.cpu_usage_graph, 1, 2, 0, 1) @@ -121,6 +123,7 @@ class vmmDetails(gobject.GObject): "on_config_memory_apply_clicked": self.config_memory_apply, "on_details_help_activate": self.show_help, + "on_config_cdrom_connect_clicked": self.toggle_cdrom, "on_config_disk_remove_clicked": self.remove_disk, "on_config_network_remove_clicked": self.remove_network, "on_add_hardware_button_clicked": self.add_hardware, @@ -412,6 +415,16 @@ class vmmDetails(gobject.GObject): self.window.get_widget("disk-source-path").set_text(diskinfo[1]) self.window.get_widget("disk-target-type").set_text(diskinfo[2]) self.window.get_widget("disk-target-device").set_text(diskinfo[3]) + button = self.window.get_widget("config-cdrom-connect") + if diskinfo[2] == "cdrom": + if diskinfo[1] == "-": + # source device not connected + button.set_label(gtk.STOCK_CONNECT) + else: + button.set_label(gtk.STOCK_DISCONNECT) + button.show() + else: + button.hide() def refresh_network_page(self): vmlist = self.window.get_widget("hw-list") @@ -595,5 +608,20 @@ class vmmDetails(gobject.GObject): self.addhw.show() + def toggle_cdrom(self, src): + if src.get_label() == gtk.STOCK_DISCONNECT: + #disconnect the cdrom + self.vm.disconnect_cdrom_device(self.window.get_widget("disk-target-device").get_text()) + else: + # connect a new cdrom + if self.choose_cd is None: + self.choose_cd = vmmChooseCD(self.config, self.window.get_widget("disk-target-device").get_text()) + self.choose_cd.connect("cdrom-chosen", self.connect_cdrom) + else: + self.choose_cd.set_target(self.window.get_widget("disk-target-device").get_text()) + self.choose_cd.show() + + def connect_cdrom(self, src, type, source, target): + self.vm.connect_cdrom_device(type, source, target) gobject.type_register(vmmDetails) diff --git a/src/virtManager/domain.py b/src/virtManager/domain.py index a97232cea..128bdf7a7 100644 --- a/src/virtManager/domain.py +++ b/src/virtManager/domain.py @@ -514,6 +514,67 @@ class vmmDomain(gobject.GObject): def add_disk_device(self, xml): self.vm.attachDevice(xml) + def connect_cdrom_device(self, type, source, target): + xml = self.get_xml() + doc = None + try: + doc = libxml2.parseDoc(xml) + except: + return [] + ctx = doc.xpathNewContext() + try: + disk_fragment = ctx.xpathEval("/domain/devices/disk[@device='cdrom' and target/@dev='%s']" % target) + if len(disk_fragment) == 0: + raise RuntimeError("Attmpted to connect cdrom device to %s, but %s does not exist" % (target,target)) + if len(disk_fragment) > 1: + raise RuntimeError("Found multiple cdrom devices named %s. This domain's XML is malformed." % target) + disk_fragment[0].setProp("type", type) + elem = disk_fragment[0].newChild(None, "source", None) + if type == "file": + elem.setProp("file", source) + else: + elem.setProp("dev", source) + result = disk_fragment[0].serialize() + logging.debug("connect_cdrom_device produced the following XML: %s" % result) + finally: + if ctx != None: + ctx.xpathFreeContext() + if doc != None: + doc.freeDoc() + self.add_disk_device(result) + + def disconnect_cdrom_device(self, target): + xml = self.get_xml() + doc = None + try: + doc = libxml2.parseDoc(xml) + except: + return [] + ctx = doc.xpathNewContext() + try: + disk_fragment = ctx.xpathEval("/domain/devices/disk[@device='cdrom' and target/@dev='%s']" % target) + if len(disk_fragment) == 0: + raise RuntimeError("Attmpted to disconnect cdrom device from %s, but %s does not exist" % (target,target)) + if len(disk_fragment) > 1: + raise RuntimeError("Found multiple cdrom devices named %s. This domain's XML is malformed." % target) + sourcenode = None + for child in disk_fragment[0].children: + if child.name == "source": + sourcenode = child + break + else: + continue + sourcenode.unlinkNode() + sourcenode.freeNode() + result = disk_fragment[0].serialize() + logging.debug("disconnect_cdrom_device produced the following XML: %s" % result) + finally: + if ctx != None: + ctx.xpathFreeContext() + if doc != None: + doc.freeDoc() + self.add_disk_device(result) + def get_network_devices(self): xml = self.get_xml() doc = None diff --git a/src/virtManager/opticalhelper.py b/src/virtManager/opticalhelper.py new file mode 100644 index 000000000..36bc3be46 --- /dev/null +++ b/src/virtManager/opticalhelper.py @@ -0,0 +1,106 @@ +# +# Copyright (C) 2007 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +import gobject +import dbus + +class vmmOpticalDriveHelper(gobject.GObject): + __gsignals__ = {} + + def __init__(self, widget): + self.__gobject_init__() + self.widget = widget + self.model = self.widget.get_model() + try: + # Get a connection to the SYSTEM bus + self.bus = dbus.SystemBus() + # Get a handle to the HAL service + hal_object = self.bus.get_object('org.freedesktop.Hal', '/org/freedesktop/Hal/Manager') + self.hal_iface = dbus.Interface(hal_object, 'org.freedesktop.Hal.Manager') + self.populate_opt_media() + except Exception, e: + logging.error("Unable to connect to HAL to list cdrom volumes: '%s'", e) + self.bus = None + self.hal_iface = None + raise + + def populate_opt_media(self): + # get a list of optical devices with data discs in, for FV installs + vollabel = {} + volpath = {} + # Track device add/removes so we can detect newly inserted CD media + self.hal_iface.connect_to_signal("DeviceAdded", self._device_added) + self.hal_iface.connect_to_signal("DeviceRemoved", self._device_removed) + + # Find info about all current present media + for d in self.hal_iface.FindDeviceByCapability("volume"): + vol = self.bus.get_object("org.freedesktop.Hal", d) + if vol.GetPropertyBoolean("volume.is_disc") and \ + vol.GetPropertyBoolean("volume.disc.has_data"): + devnode = vol.GetProperty("block.device") + label = vol.GetProperty("volume.label") + if label == None or len(label) == 0: + label = devnode + vollabel[devnode] = label + volpath[devnode] = d + + for d in self.hal_iface.FindDeviceByCapability("storage.cdrom"): + dev = self.bus.get_object("org.freedesktop.Hal", d) + devnode = dev.GetProperty("block.device") + if vollabel.has_key(devnode): + self.model.append([devnode, vollabel[devnode], True, volpath[devnode]]) + else: + self.model.append([devnode, _("No media present"), False, None]) + + def _device_added(self, path): + vol = self.bus.get_object("org.freedesktop.Hal", path) + if vol.QueryCapability("volume"): + if vol.GetPropertyBoolean("volume.is_disc") and \ + vol.GetPropertyBoolean("volume.disc.has_data"): + devnode = vol.GetProperty("block.device") + label = vol.GetProperty("volume.label") + if label == None or len(label) == 0: + label = devnode + + # Search for the row with matching device node and + # fill in info about inserted media + for row in self.model: + if row[0] == devnode: + row[1] = label + row[2] = True + row[3] = path + + def _device_removed(self, path): + vol = self.bus.get_object("org.freedesktop.Hal", path) + + active = self.widget.get_active() + idx = 0 + # Search for the row containing matching HAL volume path + # and update (clear) it, de-activating it if its currently + # selected + for row in self.model: + if row[3] == path: + row[1] = _("No media present") + row[2] = False + row[3] = None + if idx == active: + self.widget.set_active(-1) + idx = idx + 1 + + +gobject.type_register(vmmOpticalDriveHelper) diff --git a/src/vmm-choose-cd.glade b/src/vmm-choose-cd.glade new file mode 100644 index 000000000..136993f4f --- /dev/null +++ b/src/vmm-choose-cd.glade @@ -0,0 +1,404 @@ + + + + + + + True + True + True + Choose A CD Source Device + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_CENTER_ON_PARENT + False + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_DIALOG + GDK_GRAVITY_CENTER + True + False + False + + + + + True + False + 0 + + + + True + GTK_BUTTONBOX_END + + + + True + True + True + gtk-ok + True + GTK_RELIEF_NORMAL + True + -5 + + + + + + + True + True + True + gtk-cancel + True + GTK_RELIEF_NORMAL + True + -6 + + + + + + 0 + False + True + GTK_PACK_END + + + + + + True + False + 0 + + + + 3 + True + 0 + 0.5 + GTK_SHADOW_ETCHED_IN + + + + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 0 + + + + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 25 + 0 + + + + True + 4 + 3 + False + 2 + 0 + + + + True + 0.5 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + + + + + + + 0 + 1 + 1 + 2 + fill + fill + + + + + + True + 0.5 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + + + + True + True + _ISO Image Location: + True + GTK_RELIEF_NORMAL + True + False + False + True + + + + + + 0 + 2 + 0 + 1 + fill + fill + + + + + + True + 0.5 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + + + + True + True + _CD-ROM or DVD: + True + GTK_RELIEF_NORMAL + True + True + False + True + iso-image + + + + + + 0 + 3 + 2 + 3 + fill + fill + + + + + + True + False + 0 + + + + True + True + True + True + 0 + + True + + False + + + 0 + True + True + + + + + + True + True + _Browse... + True + GTK_RELIEF_NORMAL + True + + + + 0 + False + False + + + + + 2 + 3 + 1 + 2 + fill + fill + + + + + + True + ISO _Location: + True + False + GTK_JUSTIFY_LEFT + False + False + 1 + 0.5 + 0 + 0 + iso-path + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 1 + 2 + 1 + 2 + 3 + fill + + + + + + + True + False + 0 + + + + True + _Path to install media: + True + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 5 + 0 + cd-path + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + True + False + True + + + + 0 + True + True + + + + + 1 + 3 + 3 + 4 + fill + + + + + + + + + + + + True + <b>CD Source Device Or File</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + True + True + + + + + 0 + True + True + + + + + + + diff --git a/src/vmm-details.glade b/src/vmm-details.glade index dc8b71ecd..8a048f4e8 100644 --- a/src/vmm-details.glade +++ b/src/vmm-details.glade @@ -1924,7 +1924,7 @@ 3 True 4 - 2 + 3 False 3 3 @@ -2152,6 +2152,27 @@ + + + + True + True + True + gtk-connect + True + GTK_RELIEF_NORMAL + True + + + + 2 + 3 + 1 + 2 + fill + + +