diff --git a/tests/testdriver.xml b/tests/testdriver.xml index 2f9eee71..ddaf8231 100644 --- a/tests/testdriver.xml +++ b/tests/testdriver.xml @@ -167,7 +167,7 @@ Foo bar baz & yeah boii < > yeahfoo - + @@ -206,7 +206,7 @@ Foo bar baz & yeah boii < > yeahfoo - +
diff --git a/tests/uitests/choosecd.py b/tests/uitests/choosecd.py deleted file mode 100644 index acdd17d1..00000000 --- a/tests/uitests/choosecd.py +++ /dev/null @@ -1,95 +0,0 @@ -# This work is licensed under the GNU GPLv2 or later. -# See the COPYING file in the top-level directory. - -from tests.uitests import utils as uiutils - - -class ChooseCD(uiutils.UITestCase): - """ - UI tests for the choosecd dialog - """ - - ################### - # Private helpers # - ################### - - ############## - # Test cases # - ############## - - def testChooseCD(self): - win = self._open_details_window(shutdown=True) - hw = win.find("hw-list") - tab = win.find("disk-tab") - - # Floppy + physical - hw.find("Floppy 1", "table cell").click() - tab.find("Disconnect", "push button").click() - tab.find("Connect", "push button").click() - cm = self.app.root.find("Choose Media", "dialog") - cm.find("OK", "push button").click() - self.assertTrue("/dev/fdb" in tab.find("disk-source-path").text) - - # Floppy + image - hw.find("Floppy 2", "table cell").click() - tab.find("Disconnect", "push button").click() - tab.find("Connect", "push button").click() - cm = self.app.root.find("Choose Media", "dialog") - cm.find("Image Location", "radio button").click() - cm.find("Location:", "text").text = "/dev/default-pool/bochs-vol" - cm.find("OK", "push button").click() - self.assertTrue("bochs-vol" in tab.find("disk-source-path").text) - - # CDROM + physical - hw.find("IDE CDROM 1", "table cell").click() - tab.find("Connect", "push button").click() - cm = self.app.root.find("Choose Media", "dialog") - cm.find("Physical Device", "radio button").click() - cm.find("physical-device-combo").click() - cm.find_fuzzy("/dev/sr1", "menu item").click() - cm.find("OK", "push button").click() - self.assertTrue("/dev/sr1" in tab.find("disk-source-path").text) - - # CDROM + image - hw.find("SCSI CDROM 1", "table cell").click() - tab.find("Connect", "push button").click() - cm = self.app.root.find("Choose Media", "dialog") - cm.find("Image Location", "radio button").click() - cm.find("Browse...", "push button").click() - browsewin = self.app.root.find( - "Choose Storage Volume", "frame") - browsewin.find_fuzzy("default-pool", "table cell").click() - browsewin.find_fuzzy("backingl1.img", "table cell").click() - browsewin.find("Choose Volume", "push button").click() - cm.find("OK", "push button").click() - alert = self.app.root.find("vmm dialog", "alert") - alert.find_fuzzy("already in use by", "label") - alert.find("Yes", "push button").click() - self.assertTrue(lambda: not cm.showing) - self.assertTrue("backing" in tab.find("disk-source-path").text) - tab.find("Disconnect", "push button").click() - self.assertTrue("-" in tab.find("disk-source-path").text) - - def testChooseCDHotplug(self): - """ - Test in the case of a running VM - """ - win = self._open_details_window() - hw = win.find("hw-list") - tab = win.find("disk-tab") - - # CDROM + physical - hw.find("IDE CDROM 1", "table cell").click() - tab.find("Connect", "push button").click() - cm = self.app.root.find("Choose Media", "dialog") - cm.find("OK", "push button").click() - alert = self.app.root.find("vmm dialog", "alert") - alert.find_fuzzy("changes will take effect", "label") - alert.find("OK", "push button").click() - self.assertTrue("-" in tab.find("disk-source-path").text) - - # Shutdown the VM, verify change shows up - win.find("Shut Down", "push button").click() - run = win.find("Run", "push button") - uiutils.check_in_loop(lambda: run.sensitive) - self.assertTrue("/dev/sr0" in tab.find("disk-source-path").text) diff --git a/tests/uitests/console.py b/tests/uitests/console.py index 8baec997..7b836dac 100644 --- a/tests/uitests/console.py +++ b/tests/uitests/console.py @@ -195,15 +195,17 @@ class Console(uiutils.UITestCase): # Change CDROM win.find("IDE CDROM 1", "table cell").click() tab = win.find("disk-tab", None) + entry = win.find("media-entry") + appl = win.find("config-apply") uiutils.check_in_loop(lambda: tab.showing) - tab.find("Connect", "push button").click() - cm = self.app.root.find("Choose Media", "dialog") - cm.find("Image Location", "radio button").click() - cm.find("Location:", "text").text = fname - cm.find("OK", "push button").click() - self.assertTrue(tab.find("disk-source-path").text == fname) - tab.find("Disconnect", "push button").click() - self.assertTrue("-" in tab.find("disk-source-path").text) + entry.text = fname + appl.click() + uiutils.check_in_loop(lambda: not appl.sensitive) + self.assertTrue(entry.text == fname) + entry.click_secondary_icon() + appl.click() + uiutils.check_in_loop(lambda: not appl.sensitive) + self.assertTrue(not entry.text) @_vm_wrapper("uitests-hotplug") diff --git a/tests/uitests/mediachange.py b/tests/uitests/mediachange.py new file mode 100644 index 00000000..6e20b632 --- /dev/null +++ b/tests/uitests/mediachange.py @@ -0,0 +1,104 @@ +# This work is licensed under the GNU GPLv2 or later. +# See the COPYING file in the top-level directory. + +from tests.uitests import utils as uiutils + + +class MediaChange(uiutils.UITestCase): + """ + UI tests for details storage media change + """ + + ############## + # Test cases # + ############## + + def testMediaChange(self): + win = self._open_details_window(shutdown=True) + hw = win.find("hw-list") + tab = win.find("disk-tab") + combo = win.find("media-combo") + entry = win.find("media-entry") + appl = win.find("config-apply") + + # Floppy + physical + hw.find("Floppy 1", "table cell").click() + combo.click_combo_entry() + combo.find(r"Floppy_install_label \(/dev/fdb\)") + self.assertTrue(entry.text == "No media detected (/dev/fda)") + entry.click() + entry.click_secondary_icon() + self.assertTrue(not entry.text) + appl.click() + uiutils.check_in_loop(lambda: not appl.sensitive) + self.assertTrue(not entry.text) + appl.click() + + # Enter /dev/fdb, after apply it should change to pretty label + entry.text = "/dev/fdb" + appl.click() + uiutils.check_in_loop(lambda: not appl.sensitive) + self.assertTrue(entry.text == "Floppy_install_label (/dev/fdb)") + + # Specify manual path + path = "/tmp/aaaaaaaaaaaaaaaaaaaaaaa.img" + entry.text = path + appl.click() + uiutils.check_in_loop(lambda: not appl.sensitive) + self.assertTrue(entry.text == path) + + # Go to Floppy 2, make sure previous path is in recent list + hw.find("Floppy 2", "table cell").click() + combo.click_combo_entry() + combo.find(path) + entry.click() + + # Browse for image + hw.find("IDE CDROM 1", "table cell").click() + combo.click_combo_entry() + combo.find(r"Fedora12_media \(/dev/sr0\)") + entry.click() + tab.find("Browse", "push button").click() + browsewin = self.app.root.find( + "Choose Storage Volume", "frame") + browsewin.find_fuzzy("default-pool", "table cell").click() + browsewin.find_fuzzy("backingl1.img", "table cell").click() + browsewin.find("Choose Volume", "push button").click() + appl.click() + # Check 'already in use' dialog + alert = self.app.root.find("vmm dialog", "alert") + alert.find_fuzzy("already in use by", "label") + alert.find("Yes", "push button").click() + uiutils.check_in_loop(lambda: not appl.sensitive) + self.assertTrue("backing" in entry.text) + entry.text = "" + appl.click() + uiutils.check_in_loop(lambda: not appl.sensitive) + self.assertTrue(not entry.text) + + + def testMediaHotplug(self): + """ + Test in the case of a running VM + """ + win = self._open_details_window() + hw = win.find("hw-list") + entry = win.find("media-entry") + appl = win.find("config-apply") + + # CDROM + physical + hw.find("IDE CDROM 1", "table cell").click() + self.assertTrue(not entry.text) + entry.text = "/dev/sr0" + appl.click() + alert = self.app.root.find("vmm dialog", "alert") + alert.find_fuzzy("changes will take effect", "label") + alert.find("OK", "push button").click() + uiutils.check_in_loop(lambda: not appl.sensitive) + self.assertTrue(not entry.text) + + # Shutdown the VM, verify change shows up + win.find("Shut Down", "push button").click() + run = win.find("Run", "push button") + uiutils.check_in_loop(lambda: run.sensitive) + self.assertTrue(entry.text == "Fedora12_media (/dev/sr0)") diff --git a/tests/uitests/newvm.py b/tests/uitests/newvm.py index b09d8b12..b4c1f07f 100644 --- a/tests/uitests/newvm.py +++ b/tests/uitests/newvm.py @@ -100,8 +100,13 @@ class NewVM(uiutils.UITestCase): newvm.find_fuzzy("Local install media", "radio").click() newvm.find_fuzzy("Forward", "button").click() + # check prepopulated cdrom media + combo = newvm.find("media-combo") + combo.click_combo_entry() + combo.find(r"No media detected \(/dev/sr1\)") + combo.find(r"Fedora12_media \(/dev/sr0\)").click() + # Select a fake iso - newvm.find_fuzzy("Use ISO", "radio").click() newvm.find_fuzzy("install-iso-browse", "button").click() browser = self.app.root.find_fuzzy("Choose Storage", "frame") browser.find_fuzzy("default-pool", "table cell").click() diff --git a/tests/uitests/utils.py b/tests/uitests/utils.py index b1d8cd98..d3080c94 100644 --- a/tests/uitests/utils.py +++ b/tests/uitests/utils.py @@ -234,6 +234,15 @@ class VMMDogtailNode(dogtail.tree.Node): self.position[1] > 0 and self.position[1] + self.size[1] < screen.get_height()) + def click_secondary_icon(self): + """ + Helper for clicking the secondary icon of a text entry + """ + button = 1 + clickX = self.position[0] + self.size[0] - 10 + clickY = self.position[1] + (self.size[1] / 2) + dogtail.rawinput.click(clickX, clickY, button) + def click_combo_entry(self): """ Helper for clicking the arrow of a combo entry, to expose the menu. diff --git a/ui/choosecd.ui b/ui/choosecd.ui deleted file mode 100644 index ae97e1b6..00000000 --- a/ui/choosecd.ui +++ /dev/null @@ -1,251 +0,0 @@ - - - - - - True - True - 6 - Choose Media - False - center-on-parent - dialog - center - - - - True - False - vertical - 6 - - - True - False - end - - - gtk-cancel - True - True - True - False - True - - - - False - False - 0 - - - - - gtk-ok - True - True - True - False - True - - - - False - False - 1 - - - - - False - True - end - 0 - - - - - True - False - 3 - 0 - none - - - True - False - 6 - 10 - - - True - False - 6 - 6 - - - True - False - True - 6 - - - True - True - - - - True - True - 0 - - - - - _Browse... - True - True - True - True - - - - False - False - 1 - - - - - 1 - 1 - - - - - P_hysical Device - True - True - False - True - True - True - - - - 0 - 2 - 2 - - - - - True - False - start - 0 - - - _Image Location - True - True - False - True - True - physical-media - - - - - - 0 - 0 - 2 - - - - - True - False - 21 - - - True - False - start - _Location: - True - iso-path - - - - - 0 - 1 - - - - - True - False - 21 - - - True - False - start - _Device Media: - True - - - - - 0 - 3 - - - - - True - False - False - False - - - - - - 1 - 3 - - - - - - - - - True - False - <b>Choose Source Device or File</b> - True - - - - - False - True - 1 - - - - - - Cancel - OK - - - - - - diff --git a/ui/create.ui b/ui/create.ui index 552f9d74..736a5eca 100644 --- a/ui/create.ui +++ b/ui/create.ui @@ -776,67 +776,19 @@ bar True False vertical - 4 + 6 - + True False - vertical - 4 - - - Use CD_ROM or DVD - True - True - False - True - True - True - - - - False - True - 0 - - - - - True - False - 20 - - - - - - False - False - 1 - - - - - False - False - 0 - - - - - Use _ISO image: - True - True - False + start + Choose _ISO or CDROM install media: True - True - True - install-cdrom-radio False True - 1 + 0 @@ -845,36 +797,23 @@ bar False 5 - + True False - 20 - - True - False - True - - - True - - - - - + True True - -1 + 0 Bro_wse... True - False True True True @@ -895,7 +834,7 @@ bar False True - 2 + 1 diff --git a/ui/details.ui b/ui/details.ui index 4767dfdd..1a132c79 100644 --- a/ui/details.ui +++ b/ui/details.ui @@ -3315,11 +3315,11 @@ - + True False end - Source path: + Source _path: True @@ -3344,17 +3344,18 @@ True False + True 6 - + True False start - path + pathlabel True start - + disk-source-path @@ -3366,19 +3367,42 @@ - - gtk-connect + True - True - True - True - True - Connect or disconnect media - True - + False + 6 + + + True + False + + + + + + False + True + 0 + + + + + _Browse + True + True + True + True + + + + False + True + 1 + + - False + True True 1 diff --git a/virtManager/choosecd.py b/virtManager/choosecd.py deleted file mode 100644 index ec4bbbe3..00000000 --- a/virtManager/choosecd.py +++ /dev/null @@ -1,145 +0,0 @@ -# Copyright (C) 2006, 2013, 2014 Red Hat, Inc. -# Copyright (C) 2006 Hugh O. Brock -# -# This work is licensed under the GNU GPLv2 or later. -# See the COPYING file in the top-level directory. - -import logging - -from virtinst import DeviceDisk - -from .baseclass import vmmGObjectUI -from .mediacombo import vmmMediaCombo -from .storagebrowse import vmmStorageBrowser -from .addstorage import vmmAddStorage - - -class vmmChooseCD(vmmGObjectUI): - __gsignals__ = { - "cdrom-chosen": (vmmGObjectUI.RUN_FIRST, None, [object, str]) - } - - def __init__(self, vm, disk): - vmmGObjectUI.__init__(self, "choosecd.ui", "vmm-choose-cd") - - self.vm = vm - self.conn = self.vm.conn - self.storage_browser = None - - # This is also overwritten from details.py when targeting a new disk - self.disk = disk - self.media_type = disk.device - - self.mediacombo = vmmMediaCombo(self.conn, self.builder, self.topwin, - self.media_type) - self.widget("media-combo-align").add(self.mediacombo.top_box) - - self.builder.connect_signals({ - "on_vmm_choose_cd_delete_event": self.close, - - "on_media_toggled": self.media_toggled, - "on_fv_iso_location_browse_clicked": self.browse_fv_iso_location, - - "on_ok_clicked": self.ok, - "on_cancel_clicked": self.close, - }) - - self.reset_state() - - def close(self, ignore1=None, ignore2=None): - logging.debug("Closing media chooser") - self.topwin.hide() - if self.storage_browser: - self.storage_browser.close() - - return 1 - - def show(self, parent): - logging.debug("Showing media chooser") - self.reset_state() - self.topwin.set_transient_for(parent) - self.topwin.present() - self.conn.schedule_priority_tick(pollnodedev=True) - - def _cleanup(self): - self.vm = None - self.conn = None - self.disk = None - - if self.storage_browser: - self.storage_browser.cleanup() - self.storage_browser = None - if self.mediacombo: - self.mediacombo.cleanup() - self.mediacombo = None - - def _init_ui(self): - if self.media_type == vmmMediaCombo.MEDIA_FLOPPY: - self.widget("physical-media").set_label(_("Floppy D_rive")) - self.widget("iso-image").set_label(_("Floppy _Image")) - - def reset_state(self): - self.mediacombo.reset_state() - - enable_phys = not self.vm.xmlobj.stable_defaults() - self.widget("physical-media").set_sensitive(enable_phys) - self.widget("physical-media").set_tooltip_text("" if enable_phys else - _("Physical CDROM passthrough not supported with this hypervisor")) - - use_cdrom = (self.mediacombo.has_media()) and enable_phys - - self.widget("physical-media").set_active(use_cdrom) - self.widget("iso-image").set_active(not use_cdrom) - - def ok(self, ignore1=None, ignore2=None): - if self.widget("iso-image").get_active(): - path = self.widget("iso-path").get_text() - else: - path = self.mediacombo.get_path() - if path == "" or path is None: - return self.err.val_err(_("Invalid Media Path"), - _("A media path must be specified.")) - - names = DeviceDisk.path_in_use_by(self.disk.conn, path) - if names: - res = self.err.yes_no( - _('Disk "%s" is already in use by other guests %s') % - (path, names), - _("Do you really want to use the disk?")) - if not res: - return False - - vmmAddStorage.check_path_search(self, self.conn, path) - - try: - self.disk.path = path - except Exception as e: - return self.err.val_err(_("Invalid Media Path"), e) - - self.close() - self.emit("cdrom-chosen", self.disk, path) - - def media_toggled(self, ignore1=None, ignore2=None): - is_phys = bool(self.widget("physical-media").get_active()) - self.mediacombo.combo.set_sensitive(is_phys) - self.widget("iso-path").set_sensitive(not is_phys) - self.widget("iso-file-chooser").set_sensitive(not is_phys) - - def browse_fv_iso_location(self, ignore1=None, ignore2=None): - self._browse_file() - - def set_storage_path(self, src_ignore, path): - self.widget("iso-path").set_text(path) - - def _browse_file(self): - if self.storage_browser is None: - self.storage_browser = vmmStorageBrowser(self.conn) - self.storage_browser.set_finish_cb(self.set_storage_path) - - if self.media_type == vmmMediaCombo.MEDIA_FLOPPY: - self.storage_browser.set_browse_reason( - self.config.CONFIG_DIR_FLOPPY_MEDIA) - else: - self.storage_browser.set_browse_reason( - self.config.CONFIG_DIR_ISO_MEDIA) - self.storage_browser.show(self.topwin) diff --git a/virtManager/config.py b/virtManager/config.py index a2152e4b..24079774 100644 --- a/virtManager/config.py +++ b/virtManager/config.py @@ -542,17 +542,20 @@ class vmmConfig(object): def add_container_url(self, url): self._url_add_helper("/urls/containers", url) + def get_container_urls(self): + return self.conf.get("/urls/containers") or [] + def add_media_url(self, url): self._url_add_helper("/urls/urls", url) + def get_media_urls(self): + return self.conf.get("/urls/urls") or [] + def add_iso_path(self, path): self._url_add_helper("/urls/isos", path) - - def get_container_urls(self): - return self.conf.get("/urls/containers") - def get_media_urls(self): - return self.conf.get("/urls/urls") def get_iso_paths(self): - return self.conf.get("/urls/isos") + return self.conf.get("/urls/isos") or [] + def on_iso_paths_changed(self, cb): + return self.conf.notify_add("/urls/isos", cb) # Whether to ask about fixing path permissions diff --git a/virtManager/create.py b/virtManager/create.py index 9bd734e8..fe8ed15c 100644 --- a/virtManager/create.py +++ b/virtManager/create.py @@ -134,7 +134,6 @@ class vmmCreate(vmmGObjectUI): self._storage_browser = None self._netlist = None - self._mediacombo = None self._addstorage = vmmAddStorage(self.conn, self.builder, self.topwin) self.widget("storage-align").add(self._addstorage.top_box) @@ -142,6 +141,13 @@ class vmmCreate(vmmGObjectUI): self._browse_file(widget) self._addstorage.connect("browse-clicked", _browse_file_cb) + self._mediacombo = vmmMediaCombo(self.conn, self.builder, self.topwin) + self._mediacombo.connect("changed", self._iso_changed_cb) + self._mediacombo.connect("activate", self._iso_activated_cb) + self._mediacombo.set_mnemonic_label( + self.widget("install-iso-label")) + self.widget("install-iso-align").add(self._mediacombo.top_box) + self.builder.connect_signals({ "on_vmm_newcreate_delete_event": self._close_requested, @@ -159,9 +165,6 @@ class vmmCreate(vmmGObjectUI): "on_machine_changed": self._machine_changed, "on_vz_virt_type_changed": self._vz_virt_type_changed, - "on_install_cdrom_radio_toggled": self._local_media_toggled, - "on_install_iso_entry_changed": self._iso_changed, - "on_install_iso_entry_activate": self._iso_activated, "on_install_iso_browse_clicked": self._browse_iso, "on_install_url_entry_changed": self._url_changed, "on_install_url_entry_activate": self._url_activated, @@ -281,9 +284,6 @@ class vmmCreate(vmmGObjectUI): lst.set_model(model) lst.set_entry_text_column(0) - # ISO media list - set_model_list("install-iso-combo") - # Lists for the install urls set_model_list("install-url-combo") @@ -363,9 +363,8 @@ class vmmCreate(vmmGObjectUI): for url in urls: media_model.append([url]) - self.widget("install-iso-entry").set_text("") - iso_model = self.widget("install-iso-combo").get_model() - _populate_media_model(iso_model, self.config.get_iso_paths()) + # Install local + self._mediacombo.reset_state() # Install URL self.widget("install-urlopts-entry").set_text("") @@ -527,6 +526,7 @@ class vmmCreate(vmmGObjectUI): self._capsinfo = None self.conn.invalidate_caps() self._change_caps() + is_local = not self.conn.is_remote() if not self._capsinfo.guest.has_install_options(): error = _("No hypervisor options were found for this " @@ -584,39 +584,10 @@ class vmmCreate(vmmGObjectUI): self.widget("vz-virt-type-exe").set_active( not has_hvm_guests and has_exe_guests) - # Install local - iso_option = self.widget("install-iso-radio") - cdrom_option = self.widget("install-cdrom-radio") - - if self._mediacombo: - self.widget("install-cdrom-align").remove( - self._mediacombo.top_box) - self._mediacombo.cleanup() - self._mediacombo = None - - self._mediacombo = vmmMediaCombo(self.conn, self.builder, self.topwin, - vmmMediaCombo.MEDIA_CDROM) - - self._mediacombo.combo.connect("changed", self._cdrom_changed) + # ISO media + # Dependent on connection so we need to do this here + self._mediacombo.set_conn(self.conn) self._mediacombo.reset_state() - self.widget("install-cdrom-align").add( - self._mediacombo.top_box) - - # Don't select physical CDROM if no valid media is present - cdrom_option.set_active(self._mediacombo.has_media()) - iso_option.set_active(not self._mediacombo.has_media()) - - enable_phys = not self._stable_defaults() - cdrom_option.set_sensitive(enable_phys) - cdrom_option.set_tooltip_text("" if enable_phys else - _("Physical CDROM passthrough not supported with this hypervisor")) - - # Only allow ISO option for remote VM - is_local = not self.conn.is_remote() - if not is_local or not enable_phys: - iso_option.set_active(True) - - self._local_media_toggled(cdrom_option) # Allow container bootstrap only for local connection and # only if virt-bootstrap is installed. Otherwise, show message. @@ -1058,13 +1029,7 @@ class vmmCreate(vmmGObjectUI): INSTALL_PAGE_VZ_TEMPLATE] def _get_config_local_media(self, store_media=False): - if self.widget("install-cdrom-radio").get_active(): - return self._mediacombo.get_path() - else: - ret = self.widget("install-iso-entry").get_text() - if ret and store_media: - self.config.add_iso_path(ret) - return ret + return self._mediacombo.get_path(store_media=store_media) def _get_config_detectable_media(self): instpage = self._get_config_install_page() @@ -1207,12 +1172,10 @@ class vmmCreate(vmmGObjectUI): self._detectable_media_widget_changed(src) def _url_activated(self, src): self._detectable_media_widget_changed(src, checkfocus=False) - def _iso_changed(self, src): - self._detectable_media_widget_changed(src) - def _iso_activated(self, src): - self._detectable_media_widget_changed(src, checkfocus=False) - def _cdrom_changed(self, src): - self._detectable_media_widget_changed(src) + def _iso_changed_cb(self, mediacombo, entry): + self._detectable_media_widget_changed(entry) + def _iso_activated_cb(self, mediacombo, entry): + self._detectable_media_widget_changed(entry, checkfocus=False) def _detect_os_toggled_cb(self, src): if not src.is_visible(): @@ -1225,17 +1188,6 @@ class vmmCreate(vmmGObjectUI): self._os_already_detected_for_media = False self._start_detect_os_if_needed() - def _local_media_toggled(self, src): - usecdrom = src.get_active() - self.widget("install-cdrom-align").set_sensitive(usecdrom) - self.widget("install-iso-combo").set_sensitive(not usecdrom) - self.widget("install-iso-browse").set_sensitive(not usecdrom) - - if usecdrom: - self._cdrom_changed(self._mediacombo.combo) - else: - self._iso_changed(self.widget("install-iso-entry")) - def _browse_oscontainer(self, ignore): self._browse_file("install-oscontainer-fs", is_dir=True) def _browse_app(self, ignore): @@ -1244,7 +1196,7 @@ class vmmCreate(vmmGObjectUI): self._browse_file("install-import-entry") def _browse_iso(self, ignore): def set_path(ignore, path): - self.widget("install-iso-entry").set_text(path) + self._mediacombo.set_path(path) self._browse_file(None, cb=set_path, is_media=True) def _browse_kernel(self, ignore): self._browse_file("kernel") diff --git a/virtManager/details.py b/virtManager/details.py index 7fe14077..cc054e43 100644 --- a/virtManager/details.py +++ b/virtManager/details.py @@ -18,12 +18,13 @@ from virtinst import util from . import vmmenu from . import uiutil from .addhardware import vmmAddHardware +from .addstorage import vmmAddStorage from .baseclass import vmmGObjectUI -from .choosecd import vmmChooseCD from .engine import vmmEngine from .fsdetails import vmmFSDetails from .gfxdetails import vmmGraphicsDetails from .graphwidgets import Sparkline +from .mediacombo import vmmMediaCombo from .netlist import vmmNetworkList from .oslist import vmmOSList from .snapshots import vmmSnapshotPage @@ -64,6 +65,7 @@ from .storagebrowse import vmmStorageBrowser EDIT_DISK_SERIAL, EDIT_DISK_FORMAT, EDIT_DISK_SGIO, + EDIT_DISK_PATH, EDIT_SOUND_MODEL, @@ -98,7 +100,7 @@ from .storagebrowse import vmmStorageBrowser EDIT_FS, - EDIT_HOSTDEV_ROMBAR) = range(1, 54) + EDIT_HOSTDEV_ROMBAR) = range(1, 55) # Columns in hw list model @@ -395,8 +397,8 @@ class vmmDetails(vmmGObjectUI): self.active_edits = [] self.addhw = None - self.media_choosers = {"cdrom": None, "floppy": None} self.storage_browser = None + self._mediacombo = None self.ignoreDetails = False @@ -405,6 +407,14 @@ class vmmDetails(vmmGObjectUI): self.snapshots = vmmSnapshotPage(self.vm, self.builder, self.topwin) self.widget("snapshot-placeholder").add(self.snapshots.top_box) + self._mediacombo = vmmMediaCombo(self.conn, self.builder, self.topwin) + self.widget("disk-source-align").add(self._mediacombo.top_box) + self._mediacombo.set_mnemonic_label( + self.widget("disk-source-mnemonic")) + self._mediacombo.connect("changed", + lambda *x: self.enable_apply(x, EDIT_DISK_PATH)) + self._mediacombo.show_clear_icon() + self.fsDetails = vmmFSDetails(self.vm, self.builder, self.topwin) self.widget("fs-alignment").add(self.fsDetails.top_box) self.fsDetails.connect("changed", @@ -537,7 +547,8 @@ class vmmDetails(vmmGObjectUI): "on_boot_init_path_changed": lambda *x: self.enable_apply(x, EDIT_INIT), "on_boot_init_args_changed": lambda *x: self.enable_apply(x, EDIT_INIT), - "on_disk_cdrom_connect_clicked": self.toggle_storage_media, + + "on_disk_source_browse_clicked": self._disk_source_browse_clicked_cb, "on_disk_readonly_changed": lambda *x: self.enable_apply(x, EDIT_DISK_RO), "on_disk_shareable_changed": lambda *x: self.enable_apply(x, EDIT_DISK_SHARE), "on_disk_removable_changed": lambda *x: self.enable_apply(x, EDIT_DISK_REMOVABLE), @@ -631,16 +642,12 @@ class vmmDetails(vmmGObjectUI): if self.addhw: self.addhw.cleanup() self.addhw = None - if self.storage_browser: self.storage_browser.cleanup() self.storage_browser = None - for key in self.media_choosers: - if self.media_choosers[key]: - self.media_choosers[key].cleanup() - self.media_choosers = {} - + self._mediacombo.cleanup() + self._mediacombo = None self.console.cleanup() self.console = None self.snapshots.cleanup() @@ -1596,11 +1603,11 @@ class vmmDetails(vmmGObjectUI): # Details/Hardware listeners # ############################## - def _browse_file(self, callback, is_media=False): - if is_media: - reason = self.config.CONFIG_DIR_ISO_MEDIA - else: + def _browse_file(self, callback, is_media=False, reason=None): + if not reason: reason = self.config.CONFIG_DIR_IMAGE + if is_media: + reason = self.config.CONFIG_DIR_ISO_MEDIA if self.storage_browser is None: self.storage_browser = vmmStorageBrowser(self.conn) @@ -1819,50 +1826,22 @@ class vmmDetails(vmmGObjectUI): boot_list.get_selection().emit("changed") self.enable_apply(EDIT_BOOTORDER) + + # Disk callbacks def disk_format_changed(self, ignore): self.widget("disk-format-warn").show() self.enable_apply(EDIT_DISK_FORMAT) - - # CDROM Eject/Connect - def _change_storage_media(self, devobj, newpath): - kwargs = {"path": newpath} - return vmmAddHardware.change_config_helper(self.vm.define_disk, - kwargs, self.vm, self.err, devobj=devobj) - - def _eject_media(self, disk): - try: - self._change_storage_media(disk, None) - except Exception as e: - self.err.show_err((_("Error disconnecting media: %s") % e)) - - def _insert_media(self, disk): - try: - devtype = disk.device - - def change_cdrom_wrapper(src_ignore, devobj, newpath): - return self._change_storage_media(devobj, newpath) - - # Launch 'Choose CD' dialog - if self.media_choosers[devtype] is None: - ret = vmmChooseCD(self.vm, disk) - - ret.connect("cdrom-chosen", change_cdrom_wrapper) - self.media_choosers[devtype] = ret - - dialog = self.media_choosers[devtype] - dialog.disk = disk - - dialog.show(self.topwin) - except Exception as e: - self.err.show_err((_("Error launching media dialog: %s") % e)) - return - - def toggle_storage_media(self, src_ignore): + def _disk_source_browse_clicked_cb(self, src): disk = self.get_hw_row()[HW_LIST_COL_DEVICE] - if disk.path: - return self._eject_media(disk) - return self._insert_media(disk) + if disk.is_floppy(): + reason = self.config.CONFIG_DIR_FLOPPY_MEDIA + else: + reason = self.config.CONFIG_DIR_ISO_MEDIA + + def cb(ignore, path): + self._mediacombo.set_path(path) + self._browse_file(cb, reason=reason) # Net IP refresh @@ -2121,6 +2100,21 @@ class vmmDetails(vmmGObjectUI): def config_disk_apply(self, devobj): kwargs = {} + if self.edited(EDIT_DISK_PATH): + path = self._mediacombo.get_path() + + names = virtinst.DeviceDisk.path_in_use_by(devobj.conn, path) + if names: + res = self.err.yes_no( + _('Disk "%s" is already in use by other guests %s') % + (path, names), + _("Do you really want to use the disk?")) + if not res: + return False + + vmmAddStorage.check_path_search(self, self.conn, path) + kwargs["path"] = path or None + if self.edited(EDIT_DISK_RO): kwargs["readonly"] = self.widget("disk-readonly").get_active() @@ -2676,8 +2670,6 @@ class vmmDetails(vmmGObjectUI): if vol: size = vol.get_pretty_capacity() - is_cdrom = (devtype == virtinst.DeviceDisk.DEVICE_CDROM) - is_floppy = (devtype == virtinst.DeviceDisk.DEVICE_FLOPPY) is_usb = (bus == "usb") can_set_removable = (is_usb and (self.conn.is_qemu() or @@ -2689,11 +2681,10 @@ class vmmDetails(vmmGObjectUI): pretty_name = _label_for_device(disk) - self.widget("disk-source-path").set_text(path or "-") self.widget("disk-target-type").set_text(pretty_name) self.widget("disk-readonly").set_active(ro) - self.widget("disk-readonly").set_sensitive(not is_cdrom) + self.widget("disk-readonly").set_sensitive(not disk.is_cdrom()) self.widget("disk-shareable").set_active(share) self.widget("disk-removable").set_active(removable) uiutil.set_grid_row_visible(self.widget("disk-removable"), @@ -2720,16 +2711,14 @@ class vmmDetails(vmmGObjectUI): uiutil.set_list_selection(self.widget("disk-bus"), bus) self.widget("disk-serial").set_text(serial or "") - button = self.widget("disk-cdrom-connect") - if is_cdrom or is_floppy: - if not path: - # source device not connected - button.set_label(Gtk.STOCK_CONNECT) - else: - button.set_label(Gtk.STOCK_DISCONNECT) - button.show() - else: - button.hide() + is_removable = disk.is_cdrom() or disk.is_floppy() + self.widget("disk-source-box").set_visible(is_removable) + self.widget("disk-source-label").set_visible(not is_removable) + + self.widget("disk-source-label").set_text(path or "-") + if is_removable: + self._mediacombo.reset_state(is_floppy=disk.is_floppy()) + self._mediacombo.set_path(path or "") def refresh_network_page(self, net): vmmAddHardware.populate_network_model_combo( diff --git a/virtManager/mediacombo.py b/virtManager/mediacombo.py index a3493cb7..45c03432 100644 --- a/virtManager/mediacombo.py +++ b/virtManager/mediacombo.py @@ -3,35 +3,45 @@ # This work is licensed under the GNU GPLv2 or later. # See the COPYING file in the top-level directory. -import logging - from gi.repository import Gtk from . import uiutil -from .baseclass import vmmGObjectUI +from .baseclass import vmmGObject, vmmGObjectUI class vmmMediaCombo(vmmGObjectUI): - MEDIA_FLOPPY = "floppy" - MEDIA_CDROM = "cdrom" + __gsignals__ = { + "changed": (vmmGObject.RUN_FIRST, None, [object]), + "activate": (vmmGObject.RUN_FIRST, None, [object]), + } - OPTICAL_FIELDS = 4 - (OPTICAL_DEV_PATH, - OPTICAL_LABEL, - OPTICAL_HAS_MEDIA, - OPTICAL_DEV_KEY) = range(OPTICAL_FIELDS) + MEDIA_TYPE_FLOPPY = "floppy" + MEDIA_TYPE_CDROM = "cdrom" - def __init__(self, conn, builder, topwin, media_type): + MEDIA_FIELDS_NUM = 4 + (MEDIA_FIELD_PATH, + MEDIA_FIELD_LABEL, + MEDIA_FIELD_HAS_MEDIA, + MEDIA_FIELD_KEY) = range(MEDIA_FIELDS_NUM) + + def __init__(self, conn, builder, topwin): vmmGObjectUI.__init__(self, None, None, builder=builder, topwin=topwin) self.conn = conn - self.media_type = media_type self.top_box = None - self.combo = None - self._warn_icon = None + self._combo = None self._populated = False self._init_ui() + self._iso_rows = [] + self._cdrom_rows = [] + self._floppy_rows = [] + self._rows_inited = False + + self.add_gsettings_handle( + self.config.on_iso_paths_changed(self._iso_paths_changed_cb)) + + def _cleanup(self): self.conn = None self.top_box.destroy() @@ -46,117 +56,138 @@ class vmmMediaCombo(vmmGObjectUI): self.top_box = Gtk.Box() self.top_box.set_spacing(6) self.top_box.set_orientation(Gtk.Orientation.HORIZONTAL) - self._warn_icon = Gtk.Image() - self._warn_icon.set_from_stock( - Gtk.STOCK_DIALOG_WARNING, Gtk.IconSize.MENU) - self.combo = Gtk.ComboBox() - self.top_box.add(self.combo) - self.top_box.add(self._warn_icon) + self._combo = Gtk.ComboBox(has_entry=True) + self._combo.set_entry_text_column(self.MEDIA_FIELD_LABEL) + self._combo.get_accessible().set_name("media-combo") + def separator_cb(_model, _iter): + return _model[_iter][self.MEDIA_FIELD_PATH] is None + self._combo.set_row_separator_func(separator_cb) + + self._entry = self._combo.get_child() + self._entry.set_placeholder_text(_("No media selected")) + self._entry.set_hexpand(True) + self._entry.get_accessible().set_name("media-entry") + self._entry.connect("changed", self._on_entry_changed_cb) + self._entry.connect("activate", self._on_entry_activated_cb) + self._entry.connect("icon-press", self._on_entry_icon_press_cb) + + self._browse = Gtk.Button() + + self.top_box.add(self._combo) self.top_box.show_all() - # [Device path, pretty label, has_media?, device key] - fields = [] - fields.insert(self.OPTICAL_DEV_PATH, str) - fields.insert(self.OPTICAL_LABEL, str) - fields.insert(self.OPTICAL_HAS_MEDIA, bool) - fields.insert(self.OPTICAL_DEV_KEY, str) - self.combo.set_model(Gtk.ListStore(*fields)) + # [path, label, has_media?, device key] + store = Gtk.ListStore(str, str, bool, str) + self._combo.set_model(store) - text = Gtk.CellRendererText() - self.combo.pack_start(text, True) - self.combo.add_attribute(text, 'text', self.OPTICAL_LABEL) - self.combo.get_accessible().set_name("physical-device-combo") - - error = None - if not self.conn.is_nodedev_capable(): - error = _("Libvirt version does not support media listing.") - self._warn_icon.set_tooltip_text(error) - self._warn_icon.set_visible(bool(error)) - - - def _set_mediadev_default(self): - model = self.combo.get_model() - if len(model) != 0: - return - - row = [None] * self.OPTICAL_FIELDS - row[self.OPTICAL_DEV_PATH] = None - row[self.OPTICAL_LABEL] = _("No device present") - row[self.OPTICAL_HAS_MEDIA] = False - row[self.OPTICAL_DEV_KEY] = None - model.append(row) - - def _pretty_label(self, nodedev): - media_label = nodedev.xmlobj.media_label - if not nodedev.xmlobj.media_available: - media_label = _("No media detected") - elif not nodedev.xmlobj.media_label: - media_label = _("Media Unknown") - - return "%s (%s)" % (media_label, nodedev.xmlobj.block) - - def _mediadev_set_default_selection(self): - # Set the first active cdrom device as selected, otherwise none - widget = self.combo - model = widget.get_model() - idx = 0 - active = widget.get_active() - - if active != -1: - # already a selection, don't change it - return - - for row in model: - if row[self.OPTICAL_HAS_MEDIA] is True: - widget.set_active(idx) - return - idx += 1 - - widget.set_active(0) - - def _populate_media(self): - if self._populated: - return - - widget = self.combo - model = widget.get_model() - model.clear() + def _make_row(self, path, label, has_media, key): + row = [None] * self.MEDIA_FIELDS_NUM + row[self.MEDIA_FIELD_PATH] = path + row[self.MEDIA_FIELD_LABEL] = label + row[self.MEDIA_FIELD_HAS_MEDIA] = has_media + row[self.MEDIA_FIELD_KEY] = key + return row + def _make_nodedev_rows(self, media_type): + rows = [] for nodedev in self.conn.filter_nodedevs(devtype="storage"): if not (nodedev.xmlobj.device_type == "storage" and nodedev.xmlobj.drive_type in ["cdrom", "floppy"]): continue - if nodedev.xmlobj.drive_type != self.media_type: + if nodedev.xmlobj.drive_type != media_type: continue - row = [None] * self.OPTICAL_FIELDS - row[self.OPTICAL_DEV_PATH] = nodedev.xmlobj.block - row[self.OPTICAL_LABEL] = self._pretty_label(nodedev) - row[self.OPTICAL_HAS_MEDIA] = nodedev.xmlobj.media_available - row[self.OPTICAL_DEV_KEY] = nodedev.xmlobj.name - model.append(row) + media_label = nodedev.xmlobj.media_label + if not nodedev.xmlobj.media_available: + media_label = _("No media detected") + elif not nodedev.xmlobj.media_label: + media_label = _("Media Unknown") + label = "%s (%s)" % (media_label, nodedev.xmlobj.block) - self._set_mediadev_default() + row = self._make_row(nodedev.xmlobj.block, label, + nodedev.xmlobj.media_available, + nodedev.xmlobj.name) + rows.append(row) + return rows - widget.set_active(-1) - self._mediadev_set_default_selection() - self._populated = True + def _make_iso_rows(self): + rows = [] + for path in self.config.get_iso_paths(): + row = self._make_row(path, path, True, path) + rows.append(row) + return rows + + def _init_rows(self): + self._cdrom_rows = self._make_nodedev_rows("cdrom") + self._floppy_rows = self._make_nodedev_rows("floppy") + self._iso_rows = self._make_iso_rows() + self._rows_inited = True + + + ################ + # UI callbacks # + ################ + + def _on_entry_changed_cb(self, src): + self.emit("changed", self._entry) + + def _on_entry_activated_cb(self, src): + self.emit("activate", self._entry) + + def _on_entry_icon_press_cb(self, src, icon_pos, event): + self._entry.set_text("") + + def _iso_paths_changed_cb(self): + self._iso_rows = self._make_iso_rows() ############## # Public API # ############## - def reset_state(self): - try: - self._populate_media() - except Exception: - logging.debug("Error populating mediadev combo", exc_info=True) + def set_conn(self, conn): + if conn == self.conn: + return + self.conn = conn + self._init_rows() - def get_path(self): - return uiutil.get_list_selection( - self.combo, column=self.OPTICAL_DEV_PATH) + def reset_state(self, is_floppy=False): + if not self._rows_inited: + self._init_rows() - def has_media(self): - return uiutil.get_list_selection( - self.combo, column=self.OPTICAL_HAS_MEDIA) or False + model = self._combo.get_model() + model.clear() + + for row in self._iso_rows: + model.append(row) + + nodedev_rows = self._cdrom_rows + if is_floppy: + nodedev_rows = self._floppy_rows + + if len(model) and nodedev_rows: + model.append(self._make_row(None, None, False, None)) + for row in nodedev_rows: + model.append(row) + + self._combo.set_active(-1) + + def get_path(self, store_media=True): + ret = uiutil.get_list_selection( + self._combo, column=self.MEDIA_FIELD_PATH) + if store_media and not ret.startswith("/dev"): + self.config.add_iso_path(ret) + return ret + + def set_path(self, path): + uiutil.set_list_selection( + self._combo, path, column=self.MEDIA_FIELD_PATH) + self._entry.set_position(-1) + + def set_mnemonic_label(self, label): + label.set_mnemonic_widget(self._entry) + + def show_clear_icon(self): + pos = Gtk.EntryIconPosition.SECONDARY + self._entry.set_icon_from_icon_name(pos, "edit-clear-symbolic") + self._entry.set_icon_activatable(pos, True)