1# Copyright (C) 2006-2008, 2013, 2014 Red Hat, Inc.
2# Copyright (C) 2006 Daniel P. Berrange <berrange@redhat.com>
3#
4# This work is licensed under the GNU GPLv2 or later.
5# See the COPYING file in the top-level directory.
6
7import re
8
9from gi.repository import Gtk
10
11import libvirt
12
13import virtinst
14from virtinst import log
15
16from ..lib import uiutil
17from ..addhardware import vmmAddHardware
18from ..baseclass import vmmGObjectUI
19from ..device.addstorage import vmmAddStorage
20from ..device.fsdetails import vmmFSDetails
21from ..device.gfxdetails import vmmGraphicsDetails
22from ..device.mediacombo import vmmMediaCombo
23from ..device.netlist import vmmNetworkList
24from ..device.vsockdetails import vmmVsockDetails
25from ..lib.graphwidgets import Sparkline
26from ..oslist import vmmOSList
27from ..storagebrowse import vmmStorageBrowser
28from ..xmleditor import vmmXMLEditor
29from ..delete import vmmDeleteStorage
30
31
32# Parameters that can be edited in the details window
33(EDIT_XML,
34
35 EDIT_NAME,
36 EDIT_TITLE,
37 EDIT_MACHTYPE,
38 EDIT_FIRMWARE,
39 EDIT_DESC,
40
41 EDIT_OS_NAME,
42
43 EDIT_VCPUS,
44 EDIT_CPU,
45 EDIT_TOPOLOGY,
46
47 EDIT_MEM,
48
49 EDIT_AUTOSTART,
50 EDIT_BOOTORDER,
51 EDIT_BOOTMENU,
52 EDIT_KERNEL,
53 EDIT_INIT,
54
55 EDIT_DISK_BUS,
56 EDIT_DISK_PATH,
57 EDIT_DISK,
58
59 EDIT_SOUND_MODEL,
60
61 EDIT_SMARTCARD_MODE,
62
63 EDIT_NET_MODEL,
64 EDIT_NET_SOURCE,
65 EDIT_NET_MAC,
66 EDIT_NET_LINKSTATE,
67
68 EDIT_GFX,
69
70 EDIT_VIDEO_MODEL,
71 EDIT_VIDEO_3D,
72
73 EDIT_WATCHDOG_MODEL,
74 EDIT_WATCHDOG_ACTION,
75
76 EDIT_CONTROLLER_MODEL,
77
78 EDIT_TPM_TYPE,
79 EDIT_TPM_MODEL,
80
81 EDIT_VSOCK_AUTO,
82 EDIT_VSOCK_CID,
83
84 EDIT_FS,
85
86 EDIT_HOSTDEV_ROMBAR) = range(1, 38)
87
88
89# Columns in hw list model
90(HW_LIST_COL_LABEL,
91 HW_LIST_COL_ICON_NAME,
92 HW_LIST_COL_TYPE,
93 HW_LIST_COL_DEVICE,
94 HW_LIST_COL_KEY) = range(5)
95
96# Types for the hw list model: numbers specify what order they will be listed
97(HW_LIST_TYPE_GENERAL,
98 HW_LIST_TYPE_OS,
99 HW_LIST_TYPE_STATS,
100 HW_LIST_TYPE_CPU,
101 HW_LIST_TYPE_MEMORY,
102 HW_LIST_TYPE_BOOT,
103 HW_LIST_TYPE_DISK,
104 HW_LIST_TYPE_NIC,
105 HW_LIST_TYPE_INPUT,
106 HW_LIST_TYPE_GRAPHICS,
107 HW_LIST_TYPE_SOUND,
108 HW_LIST_TYPE_CHAR,
109 HW_LIST_TYPE_HOSTDEV,
110 HW_LIST_TYPE_VIDEO,
111 HW_LIST_TYPE_WATCHDOG,
112 HW_LIST_TYPE_CONTROLLER,
113 HW_LIST_TYPE_FILESYSTEM,
114 HW_LIST_TYPE_SMARTCARD,
115 HW_LIST_TYPE_REDIRDEV,
116 HW_LIST_TYPE_TPM,
117 HW_LIST_TYPE_RNG,
118 HW_LIST_TYPE_PANIC,
119 HW_LIST_TYPE_VSOCK) = range(23)
120
121remove_pages = [HW_LIST_TYPE_NIC, HW_LIST_TYPE_INPUT,
122                HW_LIST_TYPE_GRAPHICS, HW_LIST_TYPE_SOUND, HW_LIST_TYPE_CHAR,
123                HW_LIST_TYPE_HOSTDEV, HW_LIST_TYPE_DISK, HW_LIST_TYPE_VIDEO,
124                HW_LIST_TYPE_WATCHDOG, HW_LIST_TYPE_CONTROLLER,
125                HW_LIST_TYPE_FILESYSTEM, HW_LIST_TYPE_SMARTCARD,
126                HW_LIST_TYPE_REDIRDEV, HW_LIST_TYPE_TPM,
127                HW_LIST_TYPE_RNG, HW_LIST_TYPE_PANIC, HW_LIST_TYPE_VSOCK]
128
129# Boot device columns
130(BOOT_KEY,
131 BOOT_LABEL,
132 BOOT_ICON,
133 BOOT_ACTIVE,
134 BOOT_CAN_SELECT) = range(5)
135
136
137def _calculate_disk_bus_index(disklist):
138    # Iterate through all disks and calculate what number they are
139    # This sets disk.disk_bus_index which is not a standard property
140    idx_mapping = {}
141    ret = []
142    for dev in disklist:
143        devtype = dev.device
144        bus = dev.bus
145        key = devtype + (bus or "")
146
147        if key not in idx_mapping:
148            idx_mapping[key] = 1
149
150        disk_bus_index = idx_mapping[key]
151        idx_mapping[key] += 1
152        ret.append((dev, disk_bus_index))
153
154    return ret
155
156
157def _label_for_device(dev, disk_bus_index):
158    devtype = dev.DEVICE_TYPE
159
160    if devtype == "disk":
161        if dev.device == "floppy":
162            return _("Floppy %(index)d") % {"index": disk_bus_index}
163
164        busstr = ""
165        if dev.bus:
166            busstr = vmmAddHardware.disk_pretty_bus(dev.bus)
167        if dev.device == "cdrom":
168            return _("%(bus)s CDROM %(index)d") % {
169                "bus": busstr,
170                "index": disk_bus_index,
171            }
172        elif dev.device == "disk":
173            return _("%(bus)s Disk %(index)d") % {
174                "bus": busstr,
175                "index": disk_bus_index,
176            }
177        return _("%(bus)s %(device)s %(index)d") % {
178            "bus": busstr,
179            "device": dev.device.capitalize(),
180            "index": disk_bus_index,
181        }
182
183    if devtype == "interface":
184        mac = dev.macaddr[-9:] or ""
185        return _("NIC %(mac)s") % {"mac": mac}
186
187    if devtype == "input":
188        if dev.type == "tablet":
189            return _("Tablet")
190        elif dev.type == "mouse":
191            return _("Mouse")
192        elif dev.type == "keyboard":
193            return _("Keyboard")
194        return _("Input")  # pragma: no cover
195
196    if devtype == "serial":
197        port = dev.target_port or 0
198        return _("Serial %(num)d") % {"num": port + 1}
199
200    if devtype == "parallel":
201        port = dev.target_port or 0
202        return _("Parallel %(num)d") % {"num": port + 1}
203
204    if devtype == "console":
205        port = dev.target_port or 0
206        return _("Console %(num)d") % {"num": port + 1}
207
208    if devtype == "channel":
209        name = vmmAddHardware.char_pretty_channel_name(dev.target_name)
210        if name:
211            return _("Channel %(name)s") % {"name": name}
212        pretty_type = vmmAddHardware.char_pretty_type(dev.type)
213        return _("Channel %(type)s") % {"type": pretty_type}
214
215    if devtype == "graphics":
216        pretty = vmmGraphicsDetails.graphics_pretty_type_simple(dev.type)
217        return _("Display %s") % pretty
218    if devtype == "redirdev":
219        return _("%(bus)s Redirector %(index)d") % {
220            "bus": vmmAddHardware.disk_pretty_bus(dev.bus),
221            "index": dev.get_xml_idx() + 1,
222        }
223    if devtype == "hostdev":
224        return vmmAddHardware.hostdev_pretty_name(dev)
225    if devtype == "sound":
226        return _("Sound %s") % dev.model
227    if devtype == "video":
228        return _("Video %s") % vmmAddHardware.video_pretty_model(dev.model)
229    if devtype == "filesystem":
230        return _("Filesystem %(path)s") % {"path": dev.target[:8]}
231    if devtype == "controller":
232        idx = dev.index
233        if idx is not None:
234            return _("Controller %(controller)s %(index)s") % {
235                "controller": vmmAddHardware.controller_pretty_desc(dev),
236                "index": idx,
237            }
238        return _("Controller %(controller)s") % {
239            "controller": vmmAddHardware.controller_pretty_desc(dev),
240        }
241    if devtype == "rng":
242        if dev.device:
243            return _("RNG %(device)s") % {"device": dev.device}
244        return _("RNG")
245    if devtype == "tpm":
246        if dev.device_path:
247            return _("TPM %(device)s") % {"device": dev.device_path}
248        return _("TPM v%(version)s") % {"version": dev.version}
249
250    devmap = {
251        "panic": _("Panic Notifier"),
252        "smartcard": _("Smartcard"),
253        "vsock": _("VirtIO VSOCK"),
254        "watchdog": _("Watchdog"),
255    }
256    return devmap[devtype]
257
258
259def _icon_for_device(dev):
260    devtype = dev.DEVICE_TYPE
261
262    if devtype == "disk":
263        if dev.device == "cdrom":
264            return "media-optical"
265        elif dev.device == "floppy":
266            return "media-floppy"
267        return "drive-harddisk"
268
269    if devtype == "input":
270        if dev.type == "keyboard":
271            return "input-keyboard"
272        if dev.type == "tablet":
273            return "input-tablet"
274        return "input-mouse"
275
276    if devtype == "redirdev":
277        return "device_usb"
278
279    if devtype == "hostdev":
280        if dev.type == "usb":
281            return "device_usb"
282        return "device_pci"
283
284    typemap = {
285        "interface": "network-idle",
286        "graphics": "video-display",
287        "serial": "device_serial",
288        "parallel": "device_serial",
289        "console": "device_serial",
290        "channel": "device_serial",
291        "video": "video-display",
292        "watchdog": "device_pci",
293        "sound": "audio-card",
294        "rng": "system-run",
295        "tpm": "device_cpu",
296        "smartcard": "device_serial",
297        "filesystem": "folder",
298        "controller": "device_pci",
299        "panic": "system-run",
300        "vsock": "network-idle",
301    }
302    return typemap[devtype]
303
304
305def _chipset_label_from_machine(machine):
306    if machine and "q35" in machine:
307        return "Q35"
308    return "i440FX"
309
310
311def _get_performance_icon_name():
312    # This icon isn't in standard adwaita-icon-theme, so
313    # fallback to system-run if it is missing
314    icon = "utilities-system-monitor"
315    if not Gtk.IconTheme.get_default().has_icon(icon):
316        icon = "system-run"  # pragma: no cover
317    return icon
318
319
320def _unindent_device_xml(xml):
321    """
322    The device parsed from a domain will have no indent
323    for the first line, but then <domain> expected indent
324    from the remaining lines. Try to unindent the remaining
325    lines so it looks nice in the XML editor.
326    """
327    lines = xml.splitlines()
328    if not xml.startswith("<") or len(lines) < 2:
329        return xml
330
331    ret = ""
332    unindent = 0
333    for c in lines[1]:
334        if c != " ":
335            break
336        unindent += 1
337
338    unindent = max(0, unindent - 2)
339    ret = lines[0] + "\n"
340    for line in lines[1:]:
341        if re.match(r"^%s *<.*$" % (unindent * " "), line):
342            line = line[unindent:]
343        ret += line + "\n"
344    return ret
345
346
347class vmmDetails(vmmGObjectUI):
348    def __init__(self, vm, builder, topwin, is_customize_dialog):
349        vmmGObjectUI.__init__(self, "details.ui",
350                              None, builder=builder, topwin=topwin)
351
352        self.vm = vm
353        self._active_edits = []
354        self.top_box = self.widget("details-top-box")
355
356        self.addhw = None
357        self.storage_browser = None
358        self._mediacombo = None
359        self.is_customize_dialog = is_customize_dialog
360
361        def _e(edittype):
362            def signal_cb(*args):
363                self._enable_apply(edittype)
364            return signal_cb
365
366        self._mediacombo = vmmMediaCombo(self.conn, self.builder, self.topwin)
367        self.widget("disk-source-align").add(self._mediacombo.top_box)
368        self._mediacombo.set_mnemonic_label(
369                self.widget("disk-source-mnemonic"))
370        self._mediacombo.connect("changed", _e(EDIT_DISK_PATH))
371        self._mediacombo.show_clear_icon()
372
373        self.fsDetails = vmmFSDetails(self.vm, self.builder, self.topwin)
374        self.widget("fs-alignment").add(self.fsDetails.top_box)
375        self.fsDetails.connect("changed", _e(EDIT_FS))
376
377        self.gfxdetails = vmmGraphicsDetails(
378            self.vm, self.builder, self.topwin)
379        self.widget("graphics-align").add(self.gfxdetails.top_box)
380        self.gfxdetails.connect("changed", _e(EDIT_GFX))
381
382        self.netlist = vmmNetworkList(self.conn, self.builder, self.topwin)
383        self.widget("network-source-label-align").add(self.netlist.top_label)
384        self.widget("network-source-ui-align").add(self.netlist.top_box)
385        self.netlist.connect("changed", _e(EDIT_NET_SOURCE))
386
387        self.vsockdetails = vmmVsockDetails(self.vm, self.builder, self.topwin)
388        self.widget("vsock-align").add(self.vsockdetails.top_box)
389        self.vsockdetails.connect("changed-auto-cid", _e(EDIT_VSOCK_AUTO))
390        self.vsockdetails.connect("changed-cid", _e(EDIT_VSOCK_CID))
391
392        self._addstorage = vmmAddStorage(self.conn, self.builder, self.topwin)
393        self.widget("storage-advanced-align").add(
394                self._addstorage.advanced_top_box)
395        self._addstorage.connect("changed", _e(EDIT_DISK))
396
397        self._xmleditor = vmmXMLEditor(self.builder, self.topwin,
398                self.widget("hw-panel-align"),
399                self.widget("hw-panel"))
400        self._xmleditor.connect("changed", _e(EDIT_XML))
401        self._xmleditor.connect("xml-requested",
402                self._xmleditor_xml_requested_cb)
403        self._xmleditor.connect("xml-reset",
404                self._xmleditor_xml_reset_cb)
405
406        self._oldhwkey = None
407        self._popupmenu = None
408        self._popupmenuitems = None
409        self._os_list = None
410        self._init_menus()
411        self._init_details()
412
413        self._graph_cpu = None
414        self._graph_memory = None
415        self._graph_disk = None
416        self._graph_network = None
417        self._init_graphs()
418
419        self.vm.connect("inspection-changed", self._vm_inspection_changed_cb)
420
421        self.builder.connect_signals({
422            "on_hw_list_changed": self._hw_changed_cb,
423
424            "on_overview_name_changed": _e(EDIT_NAME),
425            "on_overview_title_changed": _e(EDIT_TITLE),
426            "on_machine_type_changed": _e(EDIT_MACHTYPE),
427            "on_overview_firmware_changed": _e(EDIT_FIRMWARE),
428            "on_overview_chipset_changed": _e(EDIT_MACHTYPE),
429
430            "on_details_inspection_refresh_clicked": self._inspection_refresh_clicked_cb,
431
432            "on_cpu_vcpus_changed": self._config_vcpus_changed_cb,
433            "on_cpu_model_changed": _e(EDIT_CPU),
434            "on_cpu_copy_host_clicked": self._cpu_copy_host_clicked_cb,
435            "on_cpu_secure_toggled": _e(EDIT_CPU),
436            "on_cpu_cores_changed": self._cpu_topology_changed_cb,
437            "on_cpu_sockets_changed": self._cpu_topology_changed_cb,
438            "on_cpu_threads_changed": self._cpu_topology_changed_cb,
439            "on_cpu_topology_enable_toggled": self._cpu_topology_enable_cb,
440            "on_mem_maxmem_changed": _e(EDIT_MEM),
441            "on_mem_memory_changed": self._curmem_changed_cb,
442
443            "on_boot_list_changed": self._boot_list_changed_cb,
444            "on_boot_moveup_clicked": self._boot_moveup_clicked_cb,
445            "on_boot_movedown_clicked": self._boot_movedown_clicked_cb,
446            "on_boot_autostart_changed": _e(EDIT_AUTOSTART),
447            "on_boot_menu_changed": _e(EDIT_BOOTMENU),
448            "on_boot_kernel_enable_toggled": self._boot_kernel_toggled_cb,
449            "on_boot_kernel_changed": _e(EDIT_KERNEL),
450            "on_boot_initrd_changed": _e(EDIT_KERNEL),
451            "on_boot_dtb_changed": _e(EDIT_KERNEL),
452            "on_boot_kernel_args_changed": _e(EDIT_KERNEL),
453            "on_boot_kernel_browse_clicked": self._browse_kernel_clicked_cb,
454            "on_boot_initrd_browse_clicked": self._browse_initrd_clicked_cb,
455            "on_boot_dtb_browse_clicked": self._browse_dtb_clicked_cb,
456            "on_boot_init_path_changed": _e(EDIT_INIT),
457            "on_boot_init_args_changed": _e(EDIT_INIT),
458
459
460            "on_disk_source_browse_clicked": self._disk_source_browse_clicked_cb,
461            "on_disk_bus_combo_changed": _e(EDIT_DISK_BUS),
462
463            "on_network_model_combo_changed": _e(EDIT_NET_MODEL),
464            "on_network_mac_entry_changed": _e(EDIT_NET_MAC),
465            "on_network_link_state_checkbox_toggled": _e(EDIT_NET_LINKSTATE),
466            "on_network_refresh_ip_clicked": self._refresh_ip_clicked_cb,
467
468            "on_sound_model_combo_changed": _e(EDIT_SOUND_MODEL),
469
470            "on_video_model_combo_changed": self._video_model_changed_cb,
471            "on_video_3d_toggled": self._video_3d_toggled_cb,
472
473            "on_watchdog_model_combo_changed": _e(EDIT_WATCHDOG_MODEL),
474            "on_watchdog_action_combo_changed": _e(EDIT_WATCHDOG_ACTION),
475
476            "on_smartcard_mode_combo_changed": _e(EDIT_SMARTCARD_MODE),
477
478            "on_hostdev_rombar_toggled": _e(EDIT_HOSTDEV_ROMBAR),
479            "on_controller_model_combo_changed": _e(EDIT_CONTROLLER_MODEL),
480            "on_tpm_model_combo_changed": _e(EDIT_TPM_MODEL),
481
482            "on_config_apply_clicked": self._config_apply_clicked_cb,
483            "on_config_cancel_clicked": self._config_cancel_clicked_cb,
484            "on_config_remove_clicked": self._config_remove_clicked_cb,
485            "on_add_hardware_button_clicked": self._addhw_clicked_cb,
486
487            "on_hw_list_button_press_event": self._popup_addhw_menu_cb,
488        })
489
490        self._init_hw_list()
491        self._refresh_page()
492
493
494    @property
495    def conn(self):
496        return self.vm.conn
497
498    def _cleanup(self):
499        self._oldhwkey = None
500
501        if self.addhw:
502            self.addhw.cleanup()
503            self.addhw = None
504        if self.storage_browser:
505            self.storage_browser.cleanup()
506            self.storage_browser = None
507
508        self._mediacombo.cleanup()
509        self._mediacombo = None
510
511        self.conn.disconnect_by_obj(self)
512        self.vm = None
513        self._popupmenu = None
514        self._popupmenuitems = None
515
516        self.gfxdetails.cleanup()
517        self.gfxdetails = None
518        self.fsDetails.cleanup()
519        self.fsDetails = None
520        self.netlist.cleanup()
521        self.netlist = None
522        self.vsockdetails.cleanup()
523        self.vsockdetails = None
524        self._xmleditor.cleanup()
525        self._xmleditor = None
526        self._addstorage.cleanup()
527        self._addstorage = None
528        self._os_list.cleanup()
529        self._os_list = None
530
531
532    ##########################
533    # Initialization helpers #
534    ##########################
535
536    def _init_menus(self):
537        # Add HW popup menu
538        self._popupmenu = Gtk.Menu()
539
540        addHW = Gtk.ImageMenuItem.new_with_label(_("_Add Hardware"))
541        addHW.set_use_underline(True)
542        addHWImg = Gtk.Image()
543        addHWImg.set_from_stock(Gtk.STOCK_ADD, Gtk.IconSize.MENU)
544        addHW.set_image(addHWImg)
545        addHW.show()
546        def _addhw_clicked_cb(*args, **kwargs):
547            self._show_addhw()
548        addHW.connect("activate", _addhw_clicked_cb)
549
550        rmHW = Gtk.ImageMenuItem.new_with_label(_("_Remove Hardware"))
551        rmHW.set_use_underline(True)
552        rmHWImg = Gtk.Image()
553        rmHWImg.set_from_stock(Gtk.STOCK_REMOVE, Gtk.IconSize.MENU)
554        rmHW.set_image(rmHWImg)
555        rmHW.show()
556        def _remove_clicked_cb(*args, **kwargs):
557            self._config_remove()
558        rmHW.connect("activate", _remove_clicked_cb)
559
560        self._popupmenuitems = {"add": addHW, "remove": rmHW}
561        for i in list(self._popupmenuitems.values()):
562            self._popupmenu.add(i)
563
564        self.widget("hw-panel").set_show_tabs(False)
565
566
567    def _init_graphs(self):
568        def _make_graph():
569            g = Sparkline()
570            g.set_property("reversed", True)
571            g.show()
572            return g
573
574        self._graph_cpu = _make_graph()
575        self.widget("overview-cpu-usage-align").add(self._graph_cpu)
576
577        self._graph_memory = _make_graph()
578        self.widget("overview-memory-usage-align").add(self._graph_memory)
579
580        self._graph_disk = _make_graph()
581        self._graph_disk.set_property("filled", False)
582        self._graph_disk.set_property("num_sets", 2)
583        self._graph_disk.set_property("rgb", [x / 255.0 for x in
584                                        [0x82, 0x00, 0x3B, 0x29, 0x5C, 0x45]])
585        self.widget("overview-disk-usage-align").add(self._graph_disk)
586
587        self._graph_network = _make_graph()
588        self._graph_network.set_property("filled", False)
589        self._graph_network.set_property("num_sets", 2)
590        self._graph_network.set_property("rgb", [x / 255.0 for x in
591                                                    [0x82, 0x00, 0x3B,
592                                                     0x29, 0x5C, 0x45]])
593        self.widget("overview-network-traffic-align").add(
594            self._graph_network)
595
596    def _init_details(self):
597        # Hardware list
598        # [ label, icon name, hw type, dev xmlobj, unique key (dev or title)]
599        hw_list_model = Gtk.ListStore(str, str, int, object, object)
600        self.widget("hw-list").set_model(hw_list_model)
601
602        hwCol = Gtk.TreeViewColumn(_("Hardware"))
603        hwCol.set_spacing(6)
604        hwCol.set_min_width(165)
605        hw_txt = Gtk.CellRendererText()
606        hw_img = Gtk.CellRendererPixbuf()
607        hw_img.set_property("stock-size", Gtk.IconSize.LARGE_TOOLBAR)
608        hwCol.pack_start(hw_img, False)
609        hwCol.pack_start(hw_txt, True)
610        hwCol.add_attribute(hw_txt, 'text', HW_LIST_COL_LABEL)
611        hwCol.add_attribute(hw_img, 'icon-name', HW_LIST_COL_ICON_NAME)
612        self.widget("hw-list").append_column(hwCol)
613
614        # Description text view
615        desc = self.widget("overview-description")
616        buf = Gtk.TextBuffer()
617        def _buf_changed_cb(*args):
618            self._enable_apply(EDIT_DESC)
619        buf.connect("changed", _buf_changed_cb)
620        desc.set_buffer(buf)
621
622        arch = self.vm.get_arch()
623        caps = self.vm.conn.caps
624
625        # Machine type
626        machtype_combo = self.widget("machine-type")
627        machtype_model = Gtk.ListStore(str)
628        machtype_combo.set_model(machtype_model)
629        uiutil.init_combo_text_column(machtype_combo, 0)
630        machtype_model.set_sort_column_id(0, Gtk.SortType.ASCENDING)
631
632        machines = []
633        try:
634            capsinfo = caps.guest_lookup(
635                os_type=self.vm.get_abi_type(),
636                arch=self.vm.get_arch(),
637                typ=self.vm.get_hv_type(),
638                machine=self.vm.get_machtype())
639
640            machines = capsinfo.machines[:]
641        except Exception:
642            log.exception("Error determining machine list")
643
644        show_machine = (arch not in ["i686", "x86_64"])
645        uiutil.set_grid_row_visible(self.widget("machine-type-title"),
646            show_machine)
647
648        if show_machine:
649            for machine in machines:
650                machtype_model.append([machine])
651
652        self.widget("machine-type").set_visible(self.is_customize_dialog)
653        self.widget("machine-type-label").set_visible(
654            not self.is_customize_dialog)
655
656        # Firmware
657        combo = self.widget("overview-firmware")
658        # [label, path, is_sensitive]
659        model = Gtk.ListStore(str, str, bool)
660        combo.set_model(model)
661        text = Gtk.CellRendererText()
662        combo.pack_start(text, True)
663        combo.add_attribute(text, "text", 0)
664        combo.add_attribute(text, "sensitive", 2)
665
666        domcaps = self.vm.get_domain_capabilities()
667        uefipaths = [v.value for v in domcaps.os.loader.values]
668
669        warn_icon = self.widget("overview-firmware-warn")
670        hv_supports_uefi = domcaps.supports_uefi_xml()
671        if not hv_supports_uefi:
672            warn_icon.set_tooltip_text(
673                _("Libvirt or hypervisor does not support UEFI."))
674        elif not uefipaths:
675            warn_icon.set_tooltip_text(  # pragma: no cover
676                _("Libvirt did not detect any UEFI/OVMF firmware image "
677                  "installed on the host."))
678
679        model.append([domcaps.label_for_firmware_path(None), None, True])
680        if not uefipaths:
681            model.append([_("UEFI not found"), None, False])
682        else:
683            for path in uefipaths:
684                model.append([domcaps.label_for_firmware_path(path),
685                    path, True])
686
687        combo.set_active(0)
688
689        self.widget("overview-firmware-warn").set_visible(
690            not (uefipaths and hv_supports_uefi) and self.is_customize_dialog)
691        self.widget("overview-firmware").set_visible(self.is_customize_dialog)
692        self.widget("overview-firmware-label").set_visible(
693            not self.is_customize_dialog)
694        show_firmware = ((self.conn.is_qemu() or
695                          self.conn.is_test() or
696                          self.conn.is_xen()) and
697                         domcaps.arch_can_uefi())
698        uiutil.set_grid_row_visible(
699            self.widget("overview-firmware-title"), show_firmware)
700
701        # Chipset
702        combo = self.widget("overview-chipset")
703        model = Gtk.ListStore(str, str)
704        combo.set_model(model)
705        model.append([_chipset_label_from_machine("pc"), "pc"])
706        if "q35" in machines:
707            model.append([_chipset_label_from_machine("q35"), "q35"])
708        combo.set_active(0)
709
710        self.widget("overview-chipset").set_visible(self.is_customize_dialog)
711        self.widget("overview-chipset-label").set_visible(
712            not self.is_customize_dialog)
713        show_chipset = ((self.conn.is_qemu() or self.conn.is_test()) and
714                        arch in ["i686", "x86_64"])
715        uiutil.set_grid_row_visible(
716            self.widget("overview-chipset-title"), show_chipset)
717
718        # OS/Inspection page
719        self._os_list = vmmOSList()
720        self.widget("details-os-align").add(self._os_list.search_entry)
721        self.widget("details-os-label").set_mnemonic_widget(
722                self._os_list.search_entry)
723        self._os_list.connect("os-selected", self._os_list_name_selected_cb)
724
725        apps_list = self.widget("inspection-apps")
726        apps_model = Gtk.ListStore(str, str, str)
727        apps_list.set_model(apps_model)
728
729        name_col = Gtk.TreeViewColumn(_("Name"))
730        version_col = Gtk.TreeViewColumn(_("Version"))
731        summary_col = Gtk.TreeViewColumn()
732
733        apps_list.append_column(name_col)
734        apps_list.append_column(version_col)
735        apps_list.append_column(summary_col)
736
737        name_text = Gtk.CellRendererText()
738        name_col.pack_start(name_text, True)
739        name_col.add_attribute(name_text, 'text', 0)
740        name_col.set_sort_column_id(0)
741
742        version_text = Gtk.CellRendererText()
743        version_col.pack_start(version_text, True)
744        version_col.add_attribute(version_text, 'text', 1)
745        version_col.set_sort_column_id(1)
746
747        summary_text = Gtk.CellRendererText()
748        summary_col.pack_start(summary_text, True)
749        summary_col.add_attribute(summary_text, 'text', 2)
750        summary_col.set_sort_column_id(2)
751
752
753        # Boot device list
754        boot_list = self.widget("boot-list")
755        # [XML boot type, display name, icon name, enabled, can select]
756        boot_list_model = Gtk.ListStore(str, str, str, bool, bool)
757        boot_list.set_model(boot_list_model)
758
759        chkCol = Gtk.TreeViewColumn()
760        txtCol = Gtk.TreeViewColumn()
761
762        boot_list.append_column(chkCol)
763        boot_list.append_column(txtCol)
764
765        chk = Gtk.CellRendererToggle()
766        chk.connect("toggled", self._config_boot_toggled_cb)
767        chkCol.pack_start(chk, False)
768        chkCol.add_attribute(chk, 'active', BOOT_ACTIVE)
769        chkCol.add_attribute(chk, 'visible', BOOT_CAN_SELECT)
770
771        icon = Gtk.CellRendererPixbuf()
772        txtCol.pack_start(icon, False)
773        txtCol.add_attribute(icon, 'icon-name', BOOT_ICON)
774
775        text = Gtk.CellRendererText()
776        txtCol.pack_start(text, True)
777        txtCol.add_attribute(text, 'text', BOOT_LABEL)
778        txtCol.add_attribute(text, 'sensitive', BOOT_ACTIVE)
779
780        # CPU model combo
781        cpu_model = self.widget("cpu-model")
782
783        def sep_func(model, it, ignore):
784            return model[it][3]
785
786        # [label, sortkey, idstring, is sep]
787        model = Gtk.ListStore(str, str, str, bool)
788        cpu_model.set_model(model)
789        cpu_model.set_entry_text_column(0)
790        cpu_model.set_row_separator_func(sep_func, None)
791        model.set_sort_column_id(1, Gtk.SortType.ASCENDING)
792        model.append([_("Application Default"), "01",
793            virtinst.DomainCpu.SPECIAL_MODE_APP_DEFAULT, False])
794        model.append([_("Hypervisor Default"), "02",
795            virtinst.DomainCpu.SPECIAL_MODE_HV_DEFAULT, False])
796        model.append([_("Clear CPU configuration"), "03",
797            virtinst.DomainCpu.SPECIAL_MODE_CLEAR, False])
798        model.append(["host-model", "04",
799            virtinst.DomainCpu.SPECIAL_MODE_HOST_MODEL, False])
800        model.append(["host-passthrough", "05",
801            virtinst.DomainCpu.SPECIAL_MODE_HOST_PASSTHROUGH, False])
802        model.append([None, None, None, True])
803        for name in domcaps.get_cpu_models():
804            model.append([name, name, name, False])
805
806        # Disk bus combo
807        disk_bus = self.widget("disk-bus")
808        vmmAddHardware.build_disk_bus_combo(self.vm, disk_bus)
809        self.widget("disk-bus-label").set_visible(
810            not self.is_customize_dialog)
811        self.widget("disk-bus").set_visible(self.is_customize_dialog)
812        if not self.is_customize_dialog:
813            # Remove the mnemonic
814            self.widget("disk-bus-labeller").set_text(_("Disk bus:"))
815
816        # Network model
817        net_model = self.widget("network-model")
818        vmmAddHardware.build_network_model_combo(self.vm, net_model)
819
820        # Network mac
821        self.widget("network-mac-label").set_visible(
822            not self.is_customize_dialog)
823        self.widget("network-mac-entry").set_visible(self.is_customize_dialog)
824
825        # Sound model
826        sound_dev = self.widget("sound-model")
827        vmmAddHardware.build_sound_combo(self.vm, sound_dev)
828
829        # Video model combo
830        video_dev = self.widget("video-model")
831        vmmAddHardware.build_video_combo(self.vm, video_dev)
832
833        # Watchdog model combo
834        combo = self.widget("watchdog-model")
835        vmmAddHardware.build_watchdogmodel_combo(self.vm, combo)
836
837        # Watchdog action combo
838        combo = self.widget("watchdog-action")
839        vmmAddHardware.build_watchdogaction_combo(self.vm, combo)
840
841        # Smartcard mode
842        sc_mode = self.widget("smartcard-mode")
843        vmmAddHardware.build_smartcard_mode_combo(self.vm, sc_mode)
844
845        # TPM model
846        tpm_model = self.widget("tpm-model")
847        vmmAddHardware.build_tpm_model_combo(self.vm, tpm_model, None)
848
849        # Controller model
850        combo = self.widget("controller-model")
851        model = Gtk.ListStore(str, str)
852        combo.set_model(model)
853        uiutil.init_combo_text_column(combo, 1)
854        combo.set_active(-1)
855
856        combo = self.widget("controller-device-list")
857        model = Gtk.ListStore(str)
858        combo.set_model(model)
859        combo.set_headers_visible(False)
860        col = Gtk.TreeViewColumn()
861        text = Gtk.CellRendererText()
862        col.pack_start(text, True)
863        col.add_attribute(text, 'text', 0)
864        combo.append_column(col)
865
866
867    ##########################
868    # Window state listeners #
869    ##########################
870
871    def _popup_addhw_menu_cb(self, widget, event):
872        if event.button != 3:
873            return
874
875        # force select the list entry before showing popup_menu
876        path_tuple = widget.get_path_at_pos(int(event.x), int(event.y))
877        if path_tuple is None:
878            return False  # pragma: no cover
879        path = path_tuple[0]
880        _iter = widget.get_model().get_iter(path)
881        widget.get_selection().select_iter(_iter)
882
883        rmdev = self._popupmenuitems["remove"]
884        rmdev.set_visible(self.widget("config-remove").get_visible())
885        rmdev.set_sensitive(self.widget("config-remove").get_sensitive())
886
887        self._popupmenu.popup_at_pointer(event)
888
889    def _set_hw_selection(self, page, _disable_apply=True):
890        if _disable_apply:
891            self._disable_apply()
892        uiutil.set_list_selection_by_number(self.widget("hw-list"), page)
893
894    def _get_hw_row(self):
895        return uiutil.get_list_selected_row(self.widget("hw-list"))
896
897    def _get_hw_row_for_device(self, dev):
898        for row in self.widget("hw-list").get_model():
899            if row[HW_LIST_COL_DEVICE] is dev:
900                return row
901
902    def _get_hw_row_label_for_device(self, dev):
903        row = self._get_hw_row_for_device(dev)
904        return row and row[HW_LIST_COL_LABEL] or ""
905
906    def _has_unapplied_changes(self, row):
907        """
908        This is a bit confusing.
909
910        * If there are now changes pending, we return False
911        * If there are changes pending, we prompt the user whether
912          they want to apply them. If they say no, return False
913        * If the applying the changes succeeds, return False
914        * Return True if applying the changes failed. In this
915          case the caller should attempt to abort the action they
916          are trying to perform, if possible
917        """
918        if not row:
919            return False
920
921        if not self.widget("config-apply").get_sensitive():
922            return False
923
924        if not self.err.confirm_unapplied_changes():
925            return False
926
927        return not self._config_apply(row=row)
928
929    def _hw_changed_cb(self, src):
930        """
931        When user changes the hw-list selection
932        """
933        newrow = self._get_hw_row()
934        model = self.widget("hw-list").get_model()
935
936        if not newrow or newrow[HW_LIST_COL_KEY] == self._oldhwkey:
937            return
938
939        oldhwrow = None
940        for row in model:
941            if row[HW_LIST_COL_KEY] == self._oldhwkey:
942                oldhwrow = row
943                break
944
945        if self._has_unapplied_changes(oldhwrow):
946            # Unapplied changes, and syncing them failed
947            pageidx = 0
948            for idx, row in enumerate(model):
949                if row[HW_LIST_COL_KEY] == self._oldhwkey:
950                    pageidx = idx
951                    break
952            self._set_hw_selection(pageidx, _disable_apply=False)
953        else:
954            self._oldhwkey = newrow[HW_LIST_COL_KEY]
955            self._refresh_page()
956
957    def _disable_device_remove(self, tooltip):
958        self.widget("config-remove").set_sensitive(False)
959        self.widget("config-remove").set_tooltip_text(tooltip)
960
961
962    #######################
963    # vmwindow Public API #
964    #######################
965
966    def _refresh_vm_state(self):
967        active = self.vm.is_active()
968        self.widget("overview-name").set_editable(not active)
969
970        reason = self.vm.run_status_reason()
971        if reason:
972            status = "%s (%s)" % (self.vm.run_status(), reason)
973        else:
974            status = self.vm.run_status()
975        self.widget("overview-status-text").set_text(status)
976        self.widget("overview-status-icon").set_from_icon_name(
977                            self.vm.run_status_icon_name(),
978                            Gtk.IconSize.BUTTON)
979
980    def vmwindow_resources_refreshed(self):
981        row = self._get_hw_row()
982        if row and row[HW_LIST_COL_TYPE] == HW_LIST_TYPE_STATS:
983            self._refresh_stats_page()
984
985    def vmwindow_refresh_vm_state(self, is_current_page):
986        if not is_current_page:
987            self._disable_apply()
988            return
989
990        self._refresh_vm_state()
991        self._repopulate_hw_list()
992
993        if self.widget("config-apply").get_sensitive():
994            # Apply button sensitive means user is making changes, don't
995            # erase them
996            return
997
998        self._refresh_page()
999
1000    def vmwindow_activate_performance_page(self):
1001        index = 0
1002        model = self.widget("hw-list").get_model()
1003        for idx, row in enumerate(model):
1004            if row[HW_LIST_COL_TYPE] == HW_LIST_TYPE_STATS:
1005                index = idx
1006                break
1007        self._set_hw_selection(index)
1008
1009    def vmwindow_has_unapplied_changes(self):
1010        return self._has_unapplied_changes(self._get_hw_row())
1011
1012    def vmwindow_close(self):
1013        self._disable_apply()
1014
1015
1016    ##############################
1017    # Add/remove device handling #
1018    ##############################
1019
1020    def _show_addhw(self):
1021        try:
1022            if self.addhw is None:
1023                self.addhw = vmmAddHardware(self.vm)
1024
1025            self.addhw.show(self.topwin)
1026        except Exception as e:  # pragma: no cover
1027            self.err.show_err((_("Error launching hardware dialog: %s") %
1028                               str(e)))
1029
1030    def _remove_non_disk(self, devobj):
1031        if not self.err.chkbox_helper(self.config.get_confirm_removedev,
1032                self.config.set_confirm_removedev,
1033                text1=(_("Are you sure you want to remove this device?"))):
1034            return
1035
1036        success = vmmDeleteStorage.remove_devobj_internal(
1037                self.vm, self.err, devobj)
1038        if not success:
1039            return
1040
1041        # This call here means when the vm config changes and triggers
1042        # refresh event, the UI page will be updated, rather than leaving
1043        # it untouched because it thinks changes are in progress
1044        self._disable_apply()
1045
1046    def _remove_disk(self, disk):
1047        dialog = vmmDeleteStorage(disk)
1048        dialog.show(self.topwin, self.vm)
1049
1050    def _config_remove(self):
1051        devobj = self._get_hw_row()[HW_LIST_COL_DEVICE]
1052        if devobj.DEVICE_TYPE == "disk":
1053            self._remove_disk(devobj)
1054        else:
1055            self._remove_non_disk(devobj)
1056
1057
1058    ############################
1059    # Details/Hardware getters #
1060    ############################
1061
1062    def _get_config_boot_order(self):
1063        boot_model = self.widget("boot-list").get_model()
1064        devs = []
1065
1066        for row in boot_model:
1067            if row[BOOT_ACTIVE]:
1068                devs.append(row[BOOT_KEY])
1069
1070        return devs
1071
1072    def _get_config_boot_selection(self):
1073        return uiutil.get_list_selected_row(self.widget("boot-list"))
1074
1075
1076    def _get_config_cpu_model(self):
1077        cpu_list = self.widget("cpu-model")
1078        text = cpu_list.get_child().get_text()
1079
1080        if self.widget("cpu-copy-host").get_active():
1081            return virtinst.DomainCpu.SPECIAL_MODE_HOST_MODEL
1082
1083        key = None
1084        for row in cpu_list.get_model():
1085            if text == row[0]:
1086                key = row[2]
1087                break
1088        if not key:
1089            return text
1090
1091        if key == virtinst.DomainCpu.SPECIAL_MODE_APP_DEFAULT:
1092            return self.config.get_default_cpu_setting()
1093        return key
1094
1095    def _get_config_vcpus(self):
1096        return uiutil.spin_get_helper(self.widget("cpu-vcpus"))
1097
1098    def _get_text(self, widgetname, strip=True, checksens=False):
1099        """
1100        Helper for reading widget text with a few options
1101        """
1102        widget = self.widget(widgetname)
1103        if (checksens and
1104            (not widget.is_sensitive() or not widget.is_visible())):
1105            return ""
1106
1107        ret = widget.get_text()
1108        if strip:
1109            ret = ret.strip()
1110        return ret
1111
1112
1113    ##############################
1114    # Details/Hardware listeners #
1115    ##############################
1116
1117    def _browse_file(self, callback, reason=None):
1118        if not reason:
1119            reason = self.config.CONFIG_DIR_IMAGE
1120
1121        if self.storage_browser is None:
1122            self.storage_browser = vmmStorageBrowser(self.conn)
1123
1124        self.storage_browser.set_finish_cb(callback)
1125        self.storage_browser.set_browse_reason(reason)
1126        self.storage_browser.show(self.topwin)
1127
1128    def _inspection_refresh_clicked_cb(self, src):
1129        from ..lib.inspection import vmmInspection
1130        inspection = vmmInspection.get_instance()
1131        if inspection:
1132            inspection.vm_refresh(self.vm)
1133
1134    def _os_list_name_selected_cb(self, src, osobj):
1135        self._enable_apply(EDIT_OS_NAME)
1136
1137    def _curmem_changed_cb(self, src):
1138        self._enable_apply(EDIT_MEM)
1139        maxadj = self.widget("mem-maxmem")
1140        mem = uiutil.spin_get_helper(self.widget("mem-memory"))
1141
1142        if maxadj.get_value() < mem:
1143            maxadj.set_value(mem)
1144
1145        ignore, upper = maxadj.get_range()
1146        maxadj.set_range(mem, upper)
1147
1148    def _config_vcpus_changed_cb(self, src):
1149        self._enable_apply(EDIT_VCPUS)
1150
1151        conn = self.vm.conn
1152        host_active_count = conn.host_active_processor_count()
1153        cur = self._get_config_vcpus()
1154
1155        # Warn about overcommit
1156        warn = bool(cur > host_active_count)
1157        self.widget("cpu-vcpus-warn-box").set_visible(warn)
1158
1159    def _cpu_copy_host_clicked_cb(self, src):
1160        uiutil.set_grid_row_visible(
1161            self.widget("cpu-model"), not src.get_active())
1162        uiutil.set_grid_row_visible(
1163            self.widget("cpu-secure"), not src.get_active())
1164        self._enable_apply(EDIT_CPU)
1165
1166    def _sync_cpu_topology_ui(self):
1167        manual_top = self.widget("cpu-topology-table").is_sensitive()
1168        self.widget("cpu-vcpus").set_sensitive(not manual_top)
1169
1170        if manual_top:
1171            cores = uiutil.spin_get_helper(self.widget("cpu-cores")) or 1
1172            sockets = uiutil.spin_get_helper(self.widget("cpu-sockets")) or 1
1173            threads = uiutil.spin_get_helper(self.widget("cpu-threads")) or 1
1174            total = cores * sockets * threads
1175            if uiutil.spin_get_helper(self.widget("cpu-vcpus")) > total:
1176                self.widget("cpu-vcpus").set_value(total)
1177            self.widget("cpu-vcpus").set_value(total)
1178        else:
1179            vcpus = uiutil.spin_get_helper(self.widget("cpu-vcpus"))
1180            self.widget("cpu-sockets").set_value(vcpus or 1)
1181            self.widget("cpu-cores").set_value(1)
1182            self.widget("cpu-threads").set_value(1)
1183
1184        self._enable_apply(EDIT_TOPOLOGY)
1185
1186    def _cpu_topology_enable_cb(self, src):
1187        do_enable = src.get_active()
1188        self.widget("cpu-topology-table").set_sensitive(do_enable)
1189        self._sync_cpu_topology_ui()
1190
1191    def _cpu_topology_changed_cb(self, src):
1192        self._sync_cpu_topology_ui()
1193
1194    def _video_model_changed_cb(self, src):
1195        model = uiutil.get_list_selection(self.widget("video-model"))
1196        uiutil.set_grid_row_visible(
1197            self.widget("video-3d"), model == "virtio")
1198        self._enable_apply(EDIT_VIDEO_MODEL)
1199
1200    def _video_3d_toggled_cb(self, src):
1201        self.widget("video-3d").set_inconsistent(False)
1202        self._enable_apply(EDIT_VIDEO_3D)
1203
1204    def _config_bootdev_selected(self):
1205        boot_row = self._get_config_boot_selection()
1206        boot_selection = boot_row and boot_row[BOOT_KEY]
1207        boot_devs = self._get_config_boot_order()
1208        up_widget = self.widget("boot-moveup")
1209        down_widget = self.widget("boot-movedown")
1210
1211        down_widget.set_sensitive(bool(boot_devs and
1212                                       boot_selection and
1213                                       boot_selection in boot_devs and
1214                                       boot_selection != boot_devs[-1]))
1215        up_widget.set_sensitive(bool(boot_devs and boot_selection and
1216                                     boot_selection in boot_devs and
1217                                     boot_selection != boot_devs[0]))
1218
1219    def _config_boot_toggled_cb(self, src, index):
1220        model = self.widget("boot-list").get_model()
1221        row = model[index]
1222
1223        row[BOOT_ACTIVE] = not row[BOOT_ACTIVE]
1224        self._config_bootdev_selected()
1225        self._enable_apply(EDIT_BOOTORDER)
1226
1227    def _config_boot_move(self, move_up):
1228        row = self._get_config_boot_selection()
1229        if not row:
1230            return  # pragma: no cover
1231
1232        row_key = row[BOOT_KEY]
1233        boot_order = self._get_config_boot_order()
1234        key_idx = boot_order.index(row_key)
1235        if move_up:
1236            new_idx = key_idx - 1
1237        else:
1238            new_idx = key_idx + 1
1239
1240        if new_idx < 0 or new_idx >= len(boot_order):
1241            # Somehow we went out of bounds
1242            return  # pragma: no cover
1243
1244        boot_list = self.widget("boot-list")
1245        model = boot_list.get_model()
1246        prev_row = None
1247        for row in model:
1248            # pylint: disable=unsubscriptable-object
1249            if prev_row and prev_row[BOOT_KEY] == row_key:
1250                model.swap(prev_row.iter, row.iter)
1251                break
1252
1253            if row[BOOT_KEY] == row_key and prev_row and move_up:
1254                model.swap(prev_row.iter, row.iter)
1255                break
1256
1257            prev_row = row
1258
1259        boot_list.get_selection().emit("changed")
1260        self._enable_apply(EDIT_BOOTORDER)
1261
1262    def _disk_source_browse_clicked_cb(self, src):
1263        disk = self._get_hw_row()[HW_LIST_COL_DEVICE]
1264        if disk.is_floppy():
1265            reason = self.config.CONFIG_DIR_FLOPPY_MEDIA
1266        else:
1267            reason = self.config.CONFIG_DIR_ISO_MEDIA
1268
1269        def cb(ignore, path):
1270            self._mediacombo.set_path(path)
1271        self._browse_file(cb, reason=reason)
1272
1273    def _set_network_ip_details(self, net):
1274        ipv4, ipv6 = self.vm.get_ips(net)
1275        label = ipv4 or ""
1276        if ipv6:
1277            if label:
1278                label += "\n"
1279            label += ipv6
1280        self.widget("network-ip").set_text(label or _("Unknown"))
1281
1282    def _refresh_ip(self):
1283        net = self._get_hw_row()[HW_LIST_COL_DEVICE]
1284        self.vm.refresh_ips(net)
1285        self._set_network_ip_details(net)
1286
1287
1288    ##################################################
1289    # Details/Hardware config changes (apply button) #
1290    ##################################################
1291
1292    def _disable_apply(self):
1293        self._active_edits = []
1294        self.widget("config-apply").set_sensitive(False)
1295        self.widget("config-cancel").set_sensitive(False)
1296        self._xmleditor.details_changed = False
1297
1298    def _enable_apply(self, edittype):
1299        self.widget("config-apply").set_sensitive(True)
1300        self.widget("config-cancel").set_sensitive(True)
1301        if edittype not in self._active_edits:
1302            self._active_edits.append(edittype)
1303        if edittype != EDIT_XML:
1304            self._xmleditor.details_changed = True
1305
1306    def _config_cancel(self, ignore=None):
1307        # Remove current changes and deactivate 'apply' button
1308        self._refresh_page()
1309
1310    def _config_apply(self, row=None):
1311        pagetype = None
1312        dev = None
1313
1314        if not row:
1315            row = self._get_hw_row()
1316        if row:
1317            pagetype = row[HW_LIST_COL_TYPE]
1318            dev = row[HW_LIST_COL_DEVICE]
1319
1320        success = False
1321        try:
1322            if self._edited(EDIT_XML):
1323                if dev:
1324                    success = self._apply_xmleditor_device(dev)
1325                else:
1326                    success = self._apply_xmleditor_domain()
1327            elif pagetype is HW_LIST_TYPE_GENERAL:
1328                success = self._apply_overview()
1329            elif pagetype is HW_LIST_TYPE_OS:
1330                success = self._apply_os()
1331            elif pagetype is HW_LIST_TYPE_CPU:
1332                success = self._apply_vcpus()
1333            elif pagetype is HW_LIST_TYPE_MEMORY:
1334                success = self._apply_memory()
1335            elif pagetype is HW_LIST_TYPE_BOOT:
1336                success = self._apply_boot_options()
1337            elif pagetype is HW_LIST_TYPE_DISK:
1338                success = self._apply_disk(dev)
1339            elif pagetype is HW_LIST_TYPE_NIC:
1340                success = self._apply_network(dev)
1341            elif pagetype is HW_LIST_TYPE_GRAPHICS:
1342                success = self._apply_graphics(dev)
1343            elif pagetype is HW_LIST_TYPE_SOUND:
1344                success = self._apply_sound(dev)
1345            elif pagetype is HW_LIST_TYPE_VIDEO:
1346                success = self._apply_video(dev)
1347            elif pagetype is HW_LIST_TYPE_WATCHDOG:
1348                success = self._apply_watchdog(dev)
1349            elif pagetype is HW_LIST_TYPE_SMARTCARD:
1350                success = self._apply_smartcard(dev)
1351            elif pagetype is HW_LIST_TYPE_CONTROLLER:
1352                success = self._apply_controller(dev)
1353            elif pagetype is HW_LIST_TYPE_FILESYSTEM:
1354                success = self._apply_filesystem(dev)
1355            elif pagetype is HW_LIST_TYPE_HOSTDEV:
1356                success = self._apply_hostdev(dev)
1357            elif pagetype is HW_LIST_TYPE_TPM:
1358                success = self._apply_tpm(dev)
1359            elif pagetype is HW_LIST_TYPE_VSOCK:
1360                success = self._apply_vsock(dev)
1361        except Exception as e:
1362            self.err.show_err(_("Error applying changes: %s") % e)
1363
1364        if success is not False:
1365            self._disable_apply()
1366            success = True
1367        return success
1368
1369    def _edited(self, pagetype):
1370        return pagetype in self._active_edits
1371
1372    def _change_config(self, cb, cb_kwargs, hotplug_args=None, devobj=None):
1373        return vmmAddHardware.change_config_helper(
1374                cb, cb_kwargs, self.vm, self.err,
1375                hotplug_args=hotplug_args, devobj=devobj)
1376
1377    def _apply_xmleditor_domain(self):
1378        newxml = self._xmleditor.get_xml()
1379        def change_cb():
1380            return self.vm.define_xml(newxml)
1381        return self._change_config(change_cb, {})
1382
1383    def _apply_xmleditor_device(self, devobj):
1384        newxml = self._xmleditor.get_xml()
1385        def change_cb():
1386            return self.vm.replace_device_xml(devobj, newxml)
1387        # By not passing devobj to change_config_helper we are
1388        # explicitly opting out of attempting device hotplug
1389        return self._change_config(change_cb, {})
1390
1391    def _apply_overview(self):
1392        kwargs = {}
1393        hotplug_args = {}
1394
1395        if self._edited(EDIT_TITLE):
1396            kwargs["title"] = self.widget("overview-title").get_text()
1397            hotplug_args["title"] = kwargs["title"]
1398
1399        if self._edited(EDIT_FIRMWARE):
1400            kwargs["loader"] = uiutil.get_list_selection(
1401                self.widget("overview-firmware"), column=1)
1402
1403        if self._edited(EDIT_MACHTYPE):
1404            if self.widget("overview-chipset").is_visible():
1405                kwargs["machine"] = uiutil.get_list_selection(
1406                    self.widget("overview-chipset"), column=1)
1407            else:
1408                kwargs["machine"] = uiutil.get_list_selection(
1409                    self.widget("machine-type"))
1410
1411        if self._edited(EDIT_DESC):
1412            desc_widget = self.widget("overview-description")
1413            kwargs["description"] = (
1414                desc_widget.get_buffer().get_property("text") or "")
1415            hotplug_args["description"] = kwargs["description"]
1416
1417        # This needs to be last
1418        if self._edited(EDIT_NAME):
1419            # Renaming is pretty convoluted, so do it here synchronously
1420            self.vm.rename_domain(self.widget("overview-name").get_text())
1421
1422            if not kwargs and not hotplug_args:
1423                # Saves some useless redefine attempts
1424                return
1425
1426        return self._change_config(
1427                self.vm.define_overview, kwargs,
1428                hotplug_args=hotplug_args)
1429
1430    def _apply_os(self):
1431        kwargs = {}
1432
1433        if self._edited(EDIT_OS_NAME):
1434            osobj = self._os_list.get_selected_os()
1435            kwargs["os_name"] = osobj and osobj.name or "generic"
1436
1437        return self._change_config(self.vm.define_os, kwargs)
1438
1439    def _apply_vcpus(self):
1440        kwargs = {}
1441
1442        if self._edited(EDIT_VCPUS):
1443            kwargs["vcpus"] = self._get_config_vcpus()
1444
1445        if self._edited(EDIT_CPU):
1446            kwargs["model"] = self._get_config_cpu_model()
1447            kwargs["secure"] = self.widget("cpu-secure").get_active()
1448
1449        if self._edited(EDIT_TOPOLOGY):
1450            do_top = self.widget("cpu-topology-enable").get_active()
1451            kwargs["clear_topology"] = not do_top
1452            kwargs["sockets"] = self.widget("cpu-sockets").get_value()
1453            kwargs["cores"] = self.widget("cpu-cores").get_value()
1454            kwargs["threads"] = self.widget("cpu-threads").get_value()
1455
1456        return self._change_config(self.vm.define_cpu, kwargs)
1457
1458    def _apply_memory(self):
1459        kwargs = {}
1460        hotplug_args = {}
1461
1462        if self._edited(EDIT_MEM):
1463            maxmem = uiutil.spin_get_helper(self.widget("mem-maxmem"))
1464            curmem = uiutil.spin_get_helper(self.widget("mem-memory"))
1465            curmem = int(curmem) * 1024
1466            maxmem = int(maxmem) * 1024
1467
1468            kwargs["memory"] = curmem
1469            kwargs["maxmem"] = maxmem
1470            hotplug_args["memory"] = kwargs["memory"]
1471            hotplug_args["maxmem"] = kwargs["maxmem"]
1472
1473        return self._change_config(
1474                self.vm.define_memory, kwargs,
1475                hotplug_args=hotplug_args)
1476
1477    def _apply_boot_options(self):
1478        kwargs = {}
1479
1480        if self._edited(EDIT_AUTOSTART):
1481            auto = self.widget("boot-autostart")
1482            try:
1483                self.vm.set_autostart(auto.get_active())
1484            except Exception as e:  # pragma: no cover
1485                self.err.show_err(
1486                    (_("Error changing autostart value: %s") % str(e)))
1487                return False
1488
1489        if self._edited(EDIT_BOOTORDER):
1490            kwargs["boot_order"] = self._get_config_boot_order()
1491
1492        if self._edited(EDIT_BOOTMENU):
1493            kwargs["boot_menu"] = self.widget("boot-menu").get_active()
1494
1495        if self._edited(EDIT_KERNEL):
1496            kwargs["kernel"] = self._get_text("boot-kernel", checksens=True)
1497            kwargs["initrd"] = self._get_text("boot-initrd", checksens=True)
1498            kwargs["dtb"] = self._get_text("boot-dtb", checksens=True)
1499            kwargs["kernel_args"] = self._get_text("boot-kernel-args",
1500                checksens=True)
1501
1502            if kwargs["initrd"] and not kwargs["kernel"]:
1503                msg = _("Cannot set initrd without specifying a kernel path")
1504                return self.err.val_err(msg)
1505            if kwargs["kernel_args"] and not kwargs["kernel"]:
1506                msg = _("Cannot set kernel arguments without specifying a kernel path")
1507                return self.err.val_err(msg)
1508
1509        if self._edited(EDIT_INIT):
1510            kwargs["init"] = self._get_text("boot-init-path")
1511            kwargs["initargs"] = self._get_text("boot-init-args") or ""
1512            if not kwargs["init"]:
1513                return self.err.val_err(_("An init path must be specified"))
1514
1515        return self._change_config(self.vm.define_boot, kwargs)
1516
1517
1518    def _apply_disk(self, devobj):
1519        kwargs = {}
1520
1521        if self._edited(EDIT_DISK_PATH):
1522            path = self._mediacombo.get_path()
1523
1524            names = virtinst.DeviceDisk.path_in_use_by(devobj.conn, path)
1525            if names:
1526                msg = (_("Disk '%(path)s' is already in use by other "
1527                       "guests %(names)s") %
1528                       {"path": path, "names": names})
1529                res = self.err.yes_no(msg,
1530                        _("Do you really want to use the disk?"))
1531                if not res:
1532                    return False
1533
1534            vmmAddStorage.check_path_search(self, self.conn, path)
1535            kwargs["path"] = path or None
1536
1537        if self._edited(EDIT_DISK):
1538            vals = self._addstorage.get_values()
1539            kwargs.update(vals)
1540
1541        if self._edited(EDIT_DISK_BUS):
1542            kwargs["bus"] = uiutil.get_list_selection(
1543                    self.widget("disk-bus"))
1544
1545        return self._change_config(
1546                self.vm.define_disk, kwargs, devobj=devobj)
1547
1548    def _apply_sound(self, devobj):
1549        kwargs = {}
1550
1551        if self._edited(EDIT_SOUND_MODEL):
1552            model = uiutil.get_list_selection(self.widget("sound-model"))
1553            if model:
1554                kwargs["model"] = model
1555
1556        return self._change_config(
1557                self.vm.define_sound, kwargs, devobj=devobj)
1558
1559    def _apply_smartcard(self, devobj):
1560        kwargs = {}
1561
1562        if self._edited(EDIT_SMARTCARD_MODE):
1563            model = uiutil.get_list_selection(self.widget("smartcard-mode"))
1564            if model:
1565                kwargs["model"] = model
1566
1567        return self._change_config(
1568                self.vm.define_smartcard, kwargs, devobj=devobj)
1569
1570    def _apply_network(self, devobj):
1571        kwargs = {}
1572
1573        if self._edited(EDIT_NET_MODEL):
1574            model = uiutil.get_list_selection(self.widget("network-model"))
1575            kwargs["model"] = model
1576
1577        if self._edited(EDIT_NET_SOURCE):
1578            (kwargs["ntype"], kwargs["source"], kwargs["mode"]) = (
1579                self.netlist.get_network_selection())
1580
1581        if self._edited(EDIT_NET_MAC):
1582            kwargs["macaddr"] = self.widget("network-mac-entry").get_text()
1583            virtinst.DeviceInterface.check_mac_in_use(
1584                    self.conn.get_backend(), kwargs["macaddr"])
1585
1586        if self._edited(EDIT_NET_LINKSTATE):
1587            kwargs["linkstate"] = self.widget("network-link-state-checkbox").get_active()
1588
1589        return self._change_config(
1590                self.vm.define_network, kwargs, devobj=devobj)
1591
1592    def _apply_graphics(self, devobj):
1593        kwargs = {}
1594        if self._edited(EDIT_GFX):
1595            kwargs = self.gfxdetails.get_values()
1596
1597        return self._change_config(
1598                self.vm.define_graphics, kwargs, devobj=devobj)
1599
1600    def _apply_video(self, devobj):
1601        kwargs = {}
1602
1603        if self._edited(EDIT_VIDEO_MODEL):
1604            model = uiutil.get_list_selection(self.widget("video-model"))
1605            if model:
1606                kwargs["model"] = model
1607
1608        if self._edited(EDIT_VIDEO_3D):
1609            kwargs["accel3d"] = self.widget("video-3d").get_active()
1610
1611        return self._change_config(
1612                self.vm.define_video, kwargs, devobj=devobj)
1613
1614    def _apply_controller(self, devobj):
1615        kwargs = {}
1616
1617        if self._edited(EDIT_CONTROLLER_MODEL):
1618            model = uiutil.get_list_selection(self.widget("controller-model"))
1619            kwargs["model"] = model
1620
1621        return self._change_config(
1622                self.vm.define_controller, kwargs, devobj=devobj)
1623
1624    def _apply_watchdog(self, devobj):
1625        kwargs = {}
1626
1627        if self._edited(EDIT_WATCHDOG_MODEL):
1628            kwargs["model"] = uiutil.get_list_selection(
1629                self.widget("watchdog-model"))
1630
1631        if self._edited(EDIT_WATCHDOG_ACTION):
1632            kwargs["action"] = uiutil.get_list_selection(
1633                self.widget("watchdog-action"))
1634
1635        return self._change_config(
1636                self.vm.define_watchdog, kwargs, devobj=devobj)
1637
1638    def _apply_filesystem(self, devobj):
1639        kwargs = {}
1640
1641        if self._edited(EDIT_FS):
1642            kwargs["newdev"] = self.fsDetails.update_device(devobj)
1643
1644        return self._change_config(
1645                self.vm.define_filesystem, kwargs, devobj=devobj)
1646
1647    def _apply_hostdev(self, devobj):
1648        kwargs = {}
1649
1650        if self._edited(EDIT_HOSTDEV_ROMBAR):
1651            kwargs["rom_bar"] = self.widget("hostdev-rombar").get_active()
1652
1653        return self._change_config(
1654                self.vm.define_hostdev, kwargs, devobj=devobj)
1655
1656    def _apply_tpm(self, devobj):
1657        kwargs = {}
1658
1659        if self._edited(EDIT_TPM_MODEL):
1660            model = uiutil.get_list_selection(self.widget("tpm-model"))
1661            kwargs["model"] = model
1662
1663        return self._change_config(
1664                self.vm.define_tpm, kwargs, devobj=devobj)
1665
1666    def _apply_vsock(self, devobj):
1667        auto_cid, cid = self.vsockdetails.get_values()
1668
1669        kwargs = {}
1670
1671        if self._edited(EDIT_VSOCK_AUTO):
1672            kwargs["auto_cid"] = auto_cid
1673        if self._edited(EDIT_VSOCK_CID):
1674            kwargs["cid"] = cid
1675
1676        return self._change_config(
1677                self.vm.define_vsock, kwargs, devobj=devobj)
1678
1679    ###########################
1680    # Details page refreshers #
1681    ###########################
1682
1683    def _refresh_page(self):
1684        row = self._get_hw_row()
1685        if not row:
1686            return  # pragma: no cover
1687
1688        pagetype = row[HW_LIST_COL_TYPE]
1689
1690        self.widget("config-remove").set_sensitive(True)
1691        self.widget("config-remove").set_tooltip_text(
1692                _("Remove this device from the virtual machine"))
1693
1694        try:
1695            dev = row[HW_LIST_COL_DEVICE]
1696            if dev:
1697                self._xmleditor.set_xml(_unindent_device_xml(dev.get_xml()))
1698            else:
1699                self._xmleditor.set_xml_from_libvirtobject(self.vm)
1700
1701            if pagetype == HW_LIST_TYPE_GENERAL:
1702                self._refresh_overview_page()
1703            elif pagetype == HW_LIST_TYPE_OS:
1704                self._refresh_os_page()
1705            elif pagetype == HW_LIST_TYPE_STATS:
1706                self._refresh_stats_page()
1707            elif pagetype == HW_LIST_TYPE_CPU:
1708                self._refresh_config_cpu()
1709            elif pagetype == HW_LIST_TYPE_MEMORY:
1710                self._refresh_config_memory()
1711            elif pagetype == HW_LIST_TYPE_BOOT:
1712                self._refresh_boot_page()
1713            elif pagetype == HW_LIST_TYPE_DISK:
1714                self._refresh_disk_page(dev)
1715            elif pagetype == HW_LIST_TYPE_NIC:
1716                self._refresh_network_page(dev)
1717            elif pagetype == HW_LIST_TYPE_INPUT:
1718                self._refresh_input_page(dev)
1719            elif pagetype == HW_LIST_TYPE_GRAPHICS:
1720                self._refresh_graphics_page(dev)
1721            elif pagetype == HW_LIST_TYPE_SOUND:
1722                self._refresh_sound_page(dev)
1723            elif pagetype == HW_LIST_TYPE_CHAR:
1724                self._refresh_char_page(dev)
1725            elif pagetype == HW_LIST_TYPE_HOSTDEV:
1726                self._refresh_hostdev_page(dev)
1727            elif pagetype == HW_LIST_TYPE_VIDEO:
1728                self._refresh_video_page(dev)
1729            elif pagetype == HW_LIST_TYPE_WATCHDOG:
1730                self._refresh_watchdog_page(dev)
1731            elif pagetype == HW_LIST_TYPE_CONTROLLER:
1732                self._refresh_controller_page(dev)
1733            elif pagetype == HW_LIST_TYPE_FILESYSTEM:
1734                self._refresh_filesystem_page(dev)
1735            elif pagetype == HW_LIST_TYPE_SMARTCARD:
1736                self._refresh_smartcard_page(dev)
1737            elif pagetype == HW_LIST_TYPE_REDIRDEV:
1738                self._refresh_redir_page(dev)
1739            elif pagetype == HW_LIST_TYPE_TPM:
1740                self._refresh_tpm_page(dev)
1741            elif pagetype == HW_LIST_TYPE_RNG:
1742                self._refresh_rng_page(dev)
1743            elif pagetype == HW_LIST_TYPE_PANIC:
1744                self._refresh_panic_page(dev)
1745            elif pagetype == HW_LIST_TYPE_VSOCK:
1746                self._refresh_vsock_page(dev)
1747        except Exception as e:  # pragma: no cover
1748            self.err.show_err(_("Error refreshing hardware page: %s") % str(e))
1749            # Don't return, we want the rest of the bits to run regardless
1750
1751        self._disable_apply()
1752        rem = pagetype in remove_pages
1753        self.widget("config-remove").set_visible(rem)
1754        self.widget("hw-panel").set_current_page(pagetype)
1755
1756    def _refresh_overview_page(self):
1757        # Basic details
1758        self.widget("overview-name").set_text(self.vm.get_name())
1759        self.widget("overview-uuid").set_text(self.vm.get_uuid())
1760        desc = self.vm.get_description() or ""
1761        desc_widget = self.widget("overview-description")
1762        desc_widget.get_buffer().set_text(desc)
1763
1764        title = self.vm.get_title()
1765        self.widget("overview-title").set_text(title or "")
1766
1767        # Hypervisor Details
1768        self.widget("overview-hv").set_text(self.vm.get_pretty_hv_type())
1769        arch = self.vm.get_arch() or _("Unknown")
1770        emu = self.vm.get_emulator() or _("None")
1771        self.widget("overview-arch").set_text(arch)
1772        self.widget("overview-emulator").set_text(emu)
1773
1774        # Firmware
1775        domcaps = self.vm.get_domain_capabilities()
1776        if self.vm.get_xmlobj().os.firmware == "efi":
1777            firmware = 'UEFI'
1778        else:
1779            firmware = domcaps.label_for_firmware_path(
1780                self.vm.get_xmlobj().os.loader)
1781        if self.widget("overview-firmware").is_visible():
1782            uiutil.set_list_selection(
1783                self.widget("overview-firmware"), firmware)
1784        elif self.widget("overview-firmware-label").is_visible():
1785            self.widget("overview-firmware-label").set_text(firmware)
1786
1787        # Machine settings
1788        machtype = self.vm.get_machtype() or _("Unknown")
1789        self.widget("machine-type-label").set_text(machtype)
1790        if self.widget("machine-type").is_visible():
1791            uiutil.set_list_selection(
1792                self.widget("machine-type"), machtype)
1793
1794        # Chipset
1795        chipset = _chipset_label_from_machine(machtype)
1796        self.widget("overview-chipset-label").set_text(chipset)
1797        if self.widget("overview-chipset").is_visible():
1798            uiutil.set_list_selection(
1799                self.widget("overview-chipset"), chipset)
1800
1801    def _refresh_os_page(self):
1802        self._os_list.select_os(self.vm.xmlobj.osinfo)
1803
1804        inspection_supported = self.config.inspection_supported()
1805        uiutil.set_grid_row_visible(self.widget("details-overview-error"),
1806                                    bool(self.vm.inspection.errorstr))
1807        if self.vm.inspection.errorstr:
1808            self.widget("details-overview-error").set_text(
1809                    self.vm.inspection.errorstr)
1810            inspection_supported = False
1811
1812        self.widget("details-inspection-apps").set_visible(inspection_supported)
1813        self.widget("details-inspection-refresh").set_visible(
1814                inspection_supported)
1815        if not inspection_supported:
1816            return
1817
1818        # Applications (also inspection data)
1819        apps = self.vm.inspection.applications or []
1820        apps_list = self.widget("inspection-apps")
1821        apps_model = apps_list.get_model()
1822        apps_model.clear()
1823        for app in apps:
1824            name = ""
1825            if app.display_name:
1826                name = app.display_name
1827            elif app.name:
1828                name = app.name
1829            version = ""
1830            if app.epoch > 0:
1831                version += str(app.epoch) + ":"
1832            if app.version:
1833                version += app.version
1834            if app.release:
1835                version += "-" + app.release
1836            summary = ""
1837            if app.summary:
1838                summary = app.summary
1839            elif app.description:
1840                summary = app.description
1841                pos = summary.find("\n")
1842                if pos > -1:
1843                    summary = _("%(summary)s ...") % {
1844                        "summary": summary[0:pos]
1845                    }
1846
1847            apps_model.append([name, version, summary])
1848
1849    def _refresh_stats_page(self):
1850        def _multi_color(text1, text2):
1851            return ('<span color="#82003B">%s</span> '
1852                    '<span color="#295C45">%s</span>' % (text1, text2))
1853        def _dsk_rx_tx_text(rx, tx, unit):
1854            opts = {"received": rx, "transferred": tx, "units": unit}
1855            return _multi_color(_("%(received)d %(units)s read") % opts,
1856                                _("%(transferred)d %(units)s write") % opts)
1857        def _net_rx_tx_text(rx, tx, unit):
1858            opts = {"received": rx, "transferred": tx, "units": unit}
1859            return _multi_color(_("%(received)d %(units)s in") % opts,
1860                                _("%(transferred)d %(units)s out") % opts)
1861
1862        cpu_txt = _("Disabled")
1863        mem_txt = _("Disabled")
1864        dsk_txt = _("Disabled")
1865        net_txt = _("Disabled")
1866
1867        if self.config.get_stats_enable_cpu_poll():
1868            cpu_txt = "%d %%" % self.vm.guest_cpu_time_percentage()
1869
1870        if self.config.get_stats_enable_memory_poll():
1871            cur_vm_memory = self.vm.stats_memory()
1872            vm_memory = self.vm.xmlobj.memory
1873            mem_txt = _("%(current-memory)s of %(total-memory)s") % {
1874                "current-memory": uiutil.pretty_mem(cur_vm_memory),
1875                "total-memory": uiutil.pretty_mem(vm_memory)
1876            }
1877
1878        if self.config.get_stats_enable_disk_poll():
1879            dsk_txt = _dsk_rx_tx_text(self.vm.disk_read_rate(),
1880                                      self.vm.disk_write_rate(), "KiB/s")
1881
1882        if self.config.get_stats_enable_net_poll():
1883            net_txt = _net_rx_tx_text(self.vm.network_rx_rate(),
1884                                      self.vm.network_tx_rate(), "KiB/s")
1885
1886        self.widget("overview-cpu-usage-text").set_text(cpu_txt)
1887        self.widget("overview-memory-usage-text").set_text(mem_txt)
1888        self.widget("overview-network-traffic-text").set_markup(net_txt)
1889        self.widget("overview-disk-usage-text").set_markup(dsk_txt)
1890
1891        self._graph_cpu.set_property("data_array",
1892                                          self.vm.guest_cpu_time_vector())
1893        self._graph_memory.set_property("data_array",
1894                                             self.vm.stats_memory_vector())
1895
1896        d1, d2 = self.vm.disk_io_vectors()
1897        self._graph_disk.set_property("data_array", d1 + d2)
1898
1899        n1, n2 = self.vm.network_traffic_vectors()
1900        self._graph_network.set_property("data_array", n1 + n2)
1901
1902    def _cpu_secure_is_available(self):
1903        domcaps = self.vm.get_domain_capabilities()
1904        features = domcaps.get_cpu_security_features()
1905        return self.vm.get_xmlobj().os.is_x86() and len(features) > 0
1906
1907    def _refresh_config_cpu(self):
1908        # Set topology first, because it impacts vcpus values
1909        cpu = self.vm.xmlobj.cpu
1910        show_top = cpu.has_topology()
1911        self.widget("cpu-topology-enable").set_active(show_top)
1912
1913        sockets = cpu.topology.sockets or 1
1914        cores = cpu.topology.cores or 1
1915        threads = cpu.topology.threads or 1
1916
1917        self.widget("cpu-sockets").set_value(sockets)
1918        self.widget("cpu-cores").set_value(cores)
1919        self.widget("cpu-threads").set_value(threads)
1920        if show_top:
1921            self.widget("cpu-topology-expander").set_expanded(True)
1922
1923        host_active_count = self.vm.conn.host_active_processor_count()
1924        vcpus = self.vm.xmlobj.vcpus
1925
1926        self.widget("cpu-vcpus").set_value(int(vcpus))
1927        self.widget("state-host-cpus").set_text(str(host_active_count))
1928
1929        # Trigger this again to make sure vcpus is correct
1930        self._sync_cpu_topology_ui()
1931
1932        # Warn about overcommit
1933        warn = bool(self._get_config_vcpus() > host_active_count)
1934        self.widget("cpu-vcpus-warn-box").set_visible(warn)
1935
1936        # CPU model config
1937        model = cpu.model or None
1938        if not model:
1939            if cpu.mode == "host-model" or cpu.mode == "host-passthrough":
1940                model = cpu.mode
1941
1942        if model:
1943            self.widget("cpu-model").get_child().set_text(model)
1944        else:
1945            uiutil.set_list_selection(
1946                self.widget("cpu-model"),
1947                virtinst.DomainCpu.SPECIAL_MODE_HV_DEFAULT, column=2)
1948
1949        is_host = (cpu.mode == "host-model")
1950        self.widget("cpu-copy-host").set_active(bool(is_host))
1951        self._cpu_copy_host_clicked_cb(self.widget("cpu-copy-host"))
1952
1953        if not self._cpu_secure_is_available():
1954            self.widget("cpu-secure").set_sensitive(False)
1955            self.widget("cpu-secure").set_tooltip_text(
1956                    "No security features to copy, the host is missing "
1957                    "security patches or the host CPU is not vulnerable.")
1958
1959        cpu.check_security_features(self.vm.get_xmlobj())
1960        self.widget("cpu-secure").set_active(cpu.secure)
1961
1962    def _refresh_config_memory(self):
1963        host_mem_widget = self.widget("state-host-memory")
1964        host_mem = self.vm.conn.host_memory_size() // 1024
1965        vm_cur_mem = self.vm.xmlobj.currentMemory / 1024.0
1966        vm_max_mem = self.vm.xmlobj.memory / 1024.0
1967
1968        host_mem_widget.set_text("%d MiB" % (int(round(host_mem))))
1969
1970        curmem = self.widget("mem-memory")
1971        maxmem = self.widget("mem-maxmem")
1972        curmem.set_value(int(round(vm_cur_mem)))
1973        maxmem.set_value(int(round(vm_max_mem)))
1974
1975    def _refresh_disk_page(self, disk):
1976        path = disk.get_source_path()
1977        devtype = disk.device
1978        bus = disk.bus
1979
1980        size = "-"
1981        if path:
1982            size = _("Unknown")
1983            vol = self.conn.get_vol_by_path(path)
1984            if vol:
1985                size = vol.get_pretty_capacity()
1986
1987        pretty_name = self._get_hw_row_label_for_device(disk)
1988
1989        self.widget("disk-target-type").set_text(pretty_name)
1990        self.widget("disk-size").set_text(size)
1991
1992        vmmAddHardware.populate_disk_bus_combo(self.vm, devtype,
1993            self.widget("disk-bus").get_model())
1994        uiutil.set_list_selection(self.widget("disk-bus"), bus)
1995        self.widget("disk-bus-label").set_text(
1996                vmmAddHardware.disk_pretty_bus(bus) or "-")
1997
1998        is_removable = disk.is_cdrom() or disk.is_floppy()
1999        self.widget("disk-source-box").set_visible(is_removable)
2000        self.widget("disk-source-label").set_visible(not is_removable)
2001
2002        self.widget("disk-source-label").set_text(path or "-")
2003        if is_removable:
2004            self._mediacombo.reset_state(is_floppy=disk.is_floppy())
2005            self._mediacombo.set_path(path or "")
2006
2007        self._addstorage.set_dev(disk)
2008
2009    def _refresh_network_page(self, net):
2010        vmmAddHardware.populate_network_model_combo(
2011            self.vm, self.widget("network-model"))
2012        uiutil.set_list_selection(self.widget("network-model"), net.model)
2013
2014        macaddr = net.macaddr or ""
2015        if self.widget("network-mac-label").is_visible():
2016            self.widget("network-mac-label").set_text(macaddr)
2017        else:
2018            self.widget("network-mac-entry").set_text(macaddr)
2019
2020        state = net.link_state == "up" or net.link_state is None
2021        self.widget("network-link-state-checkbox").set_active(state)
2022        self._set_network_ip_details(net)
2023
2024        self.netlist.set_dev(net)
2025
2026    def _refresh_input_page(self, inp):
2027        dev = vmmAddHardware.input_pretty_name(inp.type, inp.bus)
2028
2029        mode = None
2030        if inp.type == "tablet":
2031            mode = _("Absolute Movement")
2032        elif inp.type == "mouse":
2033            mode = _("Relative Movement")
2034
2035        self.widget("input-dev-type").set_text(dev)
2036        self.widget("input-dev-mode").set_text(mode or "")
2037        uiutil.set_grid_row_visible(self.widget("input-dev-mode"), bool(mode))
2038
2039        if ((inp.type == "mouse" and inp.bus in ("xen", "ps2")) or
2040            (inp.type == "keyboard" and inp.bus in ("xen", "ps2"))):
2041            self._disable_device_remove(
2042                _("Hypervisor does not support removing this device"))
2043
2044    def _refresh_graphics_page(self, gfx):
2045        pretty_type = vmmGraphicsDetails.graphics_pretty_type_simple(gfx.type)
2046        title = (_("%(graphicstype)s Server") % {"graphicstype": pretty_type})
2047        self.gfxdetails.set_dev(gfx)
2048        self.widget("graphics-title").set_markup("<b>%s</b>" % title)
2049
2050    def _refresh_sound_page(self, sound):
2051        uiutil.set_list_selection(self.widget("sound-model"), sound.model)
2052
2053    def _refresh_smartcard_page(self, sc):
2054        uiutil.set_list_selection(self.widget("smartcard-mode"), sc.mode)
2055
2056    def _refresh_redir_page(self, rd):
2057        address = None
2058        if rd.type == 'tcp':
2059            address = "%s:%s" % (rd.source.host, rd.source.service)
2060
2061        title = self._get_hw_row_label_for_device(rd)
2062        self.widget("redir-title").set_markup(title)
2063        self.widget("redir-type").set_text(
2064                vmmAddHardware.redirdev_pretty_type(rd.type))
2065
2066        self.widget("redir-address").set_text(address or "")
2067        uiutil.set_grid_row_visible(
2068            self.widget("redir-address"), bool(address))
2069
2070    def _refresh_tpm_page(self, tpmdev):
2071        def show_ui(widgetname, val):
2072            doshow = bool(val)
2073            uiutil.set_grid_row_visible(self.widget(widgetname), doshow)
2074            self.widget(widgetname).set_text(val or "-")
2075
2076        dev_type = tpmdev.type
2077        self.widget("tpm-dev-type").set_text(
2078                vmmAddHardware.tpm_pretty_type(dev_type))
2079
2080        vmmAddHardware.populate_tpm_model_combo(
2081            self.vm, self.widget("tpm-model"), tpmdev.version)
2082        uiutil.set_list_selection(self.widget("tpm-model"), tpmdev.model)
2083
2084        # Device type specific properties, only show if apply to the cur dev
2085        show_ui("tpm-device-path", tpmdev.device_path)
2086        show_ui("tpm-version", tpmdev.version)
2087
2088    def _refresh_panic_page(self, dev):
2089        model = dev.model or "isa"
2090        pmodel = vmmAddHardware.panic_pretty_model(model)
2091        self.widget("panic-model").set_text(pmodel)
2092
2093    def _refresh_rng_page(self, dev):
2094        is_random = dev.backend_model == "random"
2095        uiutil.set_grid_row_visible(self.widget("rng-device"), is_random)
2096
2097        self.widget("rng-type").set_text(
2098                vmmAddHardware.rng_pretty_type(dev.backend_model))
2099        self.widget("rng-device").set_text(dev.device or "")
2100
2101    def _refresh_vsock_page(self, dev):
2102        self.vsockdetails.set_dev(dev)
2103
2104    def _refresh_char_page(self, chardev):
2105        char_type = chardev.DEVICE_TYPE
2106        target_port = chardev.target_port
2107        dev_type = chardev.type or "pty"
2108        primary = self.vm.serial_is_console_dup(chardev)
2109        show_target_type = not (char_type in ["serial", "parallel"])
2110
2111        if char_type == "serial":
2112            typelabel = _("Serial Device")
2113        elif char_type == "parallel":
2114            typelabel = _("Parallel Device")
2115        elif char_type == "console":
2116            typelabel = _("Console Device")
2117        elif char_type == "channel":
2118            typelabel = _("Channel Device")
2119        else:  # pragma: no cover
2120            typelabel = _("%s Device") % char_type.capitalize()
2121
2122        if (target_port is not None and
2123                chardev.DEVICE_TYPE == "console"):
2124            typelabel += " %s" % (int(target_port) + 1)
2125        if target_port is not None and not show_target_type:
2126            typelabel += " %s" % (int(target_port) + 1)
2127        if primary:
2128            typelabel += " (%s)" % _("Primary Console")
2129        typelabel = "<b>%s</b>" % typelabel
2130
2131        self.widget("char-type").set_markup(typelabel)
2132        self.widget("char-dev-type").set_text(dev_type)
2133
2134        def show_ui(widgetname, val):
2135            doshow = bool(val)
2136            uiutil.set_grid_row_visible(self.widget(widgetname), doshow)
2137            self.widget(widgetname).set_text(val or "-")
2138
2139        def build_host_str(host, port):
2140            ret = ""
2141            if host:
2142                ret += host
2143            if port:
2144                ret += ":%s" % str(port)
2145            return ret
2146
2147        connect_str = build_host_str(
2148                chardev.source.connect_host, chardev.source.connect_service)
2149        bind_str = build_host_str(
2150                chardev.source.bind_host, chardev.source.bind_service)
2151        target_type = show_target_type and chardev.target_type or None
2152
2153        # Device type specific properties, only show if apply to the cur dev
2154        show_ui("char-source-host", connect_str)
2155        show_ui("char-bind-host", bind_str)
2156        show_ui("char-source-path", chardev.source.path)
2157        show_ui("char-target-type", target_type)
2158        show_ui("char-target-name", chardev.target_name)
2159        show_ui("char-target-state", chardev.target_state)
2160
2161    def _refresh_hostdev_page(self, hostdev):
2162        rom_bar = hostdev.rom_bar
2163        if rom_bar is None:
2164            rom_bar = True
2165
2166        devtype = hostdev.type
2167        if hostdev.type == 'usb':
2168            devtype = 'usb_device'
2169
2170        nodedev = None
2171        for trydev in self.vm.conn.filter_nodedevs(devtype):
2172            if trydev.xmlobj.compare_to_hostdev(hostdev):
2173                nodedev = trydev
2174
2175        pretty_name = None
2176        if nodedev:
2177            pretty_name = nodedev.pretty_name()
2178        if not pretty_name:
2179            pretty_name = vmmAddHardware.hostdev_pretty_name(hostdev)
2180
2181        uiutil.set_grid_row_visible(
2182            self.widget("hostdev-rombar"), hostdev.type == "pci")
2183
2184        devlabel = "<b>" + _("Physical %s Device") % hostdev.type.upper() + "</b>"
2185        self.widget("hostdev-title").set_markup(devlabel)
2186        self.widget("hostdev-source").set_text(pretty_name)
2187        self.widget("hostdev-rombar").set_active(rom_bar)
2188
2189    def _refresh_video_page(self, vid):
2190        model = vid.model
2191        uiutil.set_list_selection(self.widget("video-model"), model)
2192
2193        if vid.accel3d is None:
2194            self.widget("video-3d").set_inconsistent(True)
2195        else:
2196            self.widget("video-3d").set_active(vid.accel3d)
2197
2198        if (self.vm.xmlobj.devices.graphics and
2199            len(self.vm.xmlobj.devices.video) <= 1):
2200            self._disable_device_remove(
2201                _("Cannot remove last video device while "
2202                  "Graphics/Display is attached."))
2203
2204    def _refresh_watchdog_page(self, watch):
2205        model = watch.model
2206        action = watch.action
2207
2208        uiutil.set_list_selection(self.widget("watchdog-model"), model)
2209        uiutil.set_list_selection(self.widget("watchdog-action"), action)
2210
2211    def _refresh_controller_page(self, controller):
2212        uiutil.set_grid_row_visible(self.widget("device-list-label"), False)
2213        uiutil.set_grid_row_visible(self.widget("controller-device-box"), False)
2214
2215        if self.vm.get_xmlobj().os.is_x86() and controller.type == "usb":
2216            self._disable_device_remove(
2217                _("Hypervisor does not support removing this device"))
2218        if controller.type == "pci":
2219            self._disable_device_remove(
2220                _("Hypervisor does not support removing this device"))
2221        elif controller.type in ["scsi", "sata", "ide", "fdc"]:
2222            model = self.widget("controller-device-list").get_model()
2223            model.clear()
2224            disks = controller.get_attached_devices(self.vm.xmlobj)
2225            for disk in disks:
2226                name = self._get_hw_row_label_for_device(disk)
2227                infoStr = _("%(device)s on %(address)s") % {
2228                    "device": name,
2229                    "address": disk.address.pretty_desc(),
2230                }
2231                model.append([infoStr])
2232                self._disable_device_remove(
2233                    _("Cannot remove controller while devices are attached."))
2234            uiutil.set_grid_row_visible(
2235                    self.widget("device-list-label"), True)
2236            uiutil.set_grid_row_visible(
2237                    self.widget("controller-device-box"), True)
2238
2239        elif controller.type == "virtio-serial":
2240            devs = controller.get_attached_devices(self.vm.xmlobj)
2241            if devs:
2242                self._disable_device_remove(
2243                    _("Cannot remove controller while devices are attached."))
2244
2245        type_label = vmmAddHardware.controller_pretty_desc(controller)
2246        self.widget("controller-type").set_text(type_label)
2247
2248        combo = self.widget("controller-model")
2249        vmmAddHardware.populate_controller_model_combo(combo, controller.type)
2250        show_model = (controller.model or len(combo.get_model()) > 1)
2251        if controller.type == "pci":
2252            show_model = False
2253        uiutil.set_grid_row_visible(combo, show_model)
2254
2255        model = controller.model
2256        if controller.type == "usb" and "xhci" in str(model):
2257            model = "usb3"
2258        uiutil.set_list_selection(self.widget("controller-model"), model)
2259
2260    def _refresh_filesystem_page(self, dev):
2261        self.fsDetails.set_dev(dev)
2262
2263    def _refresh_boot_page(self):
2264        # Refresh autostart
2265        try:
2266            # Older libvirt versions return None if not supported
2267            autoval = self.vm.get_autostart()
2268        except libvirt.libvirtError:  # pragma: no cover
2269            autoval = None
2270
2271        # Autostart
2272        autostart_chk = self.widget("boot-autostart")
2273        enable_autostart = (autoval is not None)
2274        autostart_chk.set_sensitive(enable_autostart)
2275        autostart_chk.set_active(enable_autostart and autoval or False)
2276
2277        show_kernel = not self.vm.is_container()
2278        show_init = self.vm.is_container()
2279        show_boot = (not self.vm.is_container() and not self.vm.is_xenpv())
2280
2281        uiutil.set_grid_row_visible(
2282            self.widget("boot-order-frame"), show_boot)
2283        uiutil.set_grid_row_visible(
2284            self.widget("boot-kernel-expander"), show_kernel)
2285        uiutil.set_grid_row_visible(
2286            self.widget("boot-init-frame"), show_init)
2287
2288        # Kernel/initrd boot
2289        kernel, initrd, dtb, args = self.vm.get_boot_kernel_info()
2290        expand = bool(kernel or dtb or initrd or args)
2291
2292        def keep_text(wname, guestval):
2293            # If the user unsets kernel/initrd by unchecking the
2294            # 'enable kernel boot' box, we keep the previous values cached
2295            # in the text fields to allow easy switching back and forth.
2296            guestval = guestval or ""
2297            if self._get_text(wname) and not guestval:
2298                return
2299            self.widget(wname).set_text(guestval)
2300
2301        keep_text("boot-kernel", kernel)
2302        keep_text("boot-initrd", initrd)
2303        keep_text("boot-dtb", dtb)
2304        keep_text("boot-kernel-args", args)
2305        if expand:
2306            # Only 'expand' if requested, so a refresh doesn't
2307            # magically unexpand the UI the user just touched
2308            self.widget("boot-kernel-expander").set_expanded(True)
2309        self.widget("boot-kernel-enable").set_active(expand)
2310        self.widget("boot-kernel-enable").toggled()
2311
2312        # Only show dtb if it's supported
2313        arch = self.vm.get_arch() or ""
2314        show_dtb = (self._get_text("boot-dtb") or
2315            self.vm.get_hv_type() == "test" or
2316            "arm" in arch or "microblaze" in arch or "ppc" in arch)
2317        self.widget("boot-dtb-label").set_visible(show_dtb)
2318        self.widget("boot-dtb-box").set_visible(show_dtb)
2319
2320        # <init> populate
2321        init, initargs = self.vm.get_init()
2322        self.widget("boot-init-path").set_text(init or "")
2323        self.widget("boot-init-args").set_text(initargs or "")
2324
2325        # Boot menu populate
2326        menu = self.vm.get_boot_menu() or False
2327        self.widget("boot-menu").set_active(menu)
2328        self._refresh_boot_order()
2329
2330    def _make_boot_rows(self):
2331        if not self.vm.can_use_device_boot_order():
2332            return [
2333                ["hd", _("Hard Disk"), "drive-harddisk", False, True],
2334                ["cdrom", _("CDROM"), "media-optical", False, True],
2335                ["network", _("Network (PXE)"), "network-idle", False, True],
2336                ["fd", _("Floppy"), "media-floppy", False, True],
2337            ]
2338
2339        ret = []
2340        for dev in self.vm.get_bootable_devices():
2341            row = self._get_hw_row_for_device(dev)
2342            if not row:
2343                continue  # pragma: no cover
2344            label = row[HW_LIST_COL_LABEL]
2345            icon = row[HW_LIST_COL_ICON_NAME]
2346
2347            ret.append([dev.get_xml_id(), label, icon, False, True])
2348
2349        if not ret:
2350            ret.append([None, _("No bootable devices"), None, False, False])
2351        return ret
2352
2353    def _refresh_boot_order(self):
2354        boot_list = self.widget("boot-list")
2355        boot_model = boot_list.get_model()
2356        boot_model.clear()
2357        boot_rows = self._make_boot_rows()
2358        boot_order = self.vm.get_boot_order()
2359
2360        for key in boot_order:
2361            for row in boot_rows[:]:
2362                if key != row[BOOT_KEY]:
2363                    continue
2364
2365                row[BOOT_ACTIVE] = True
2366                boot_model.append(row)
2367                boot_rows.remove(row)
2368                break
2369
2370        for row in boot_rows:
2371            boot_model.append(row)
2372
2373
2374    ############################
2375    # Hardware list population #
2376    ############################
2377
2378    def _make_hw_list_entry(self, title, page_id, icon_name, devobj=None):
2379        hw_entry = []
2380        hw_entry.insert(HW_LIST_COL_LABEL, title)
2381        hw_entry.insert(HW_LIST_COL_ICON_NAME, icon_name)
2382        hw_entry.insert(HW_LIST_COL_TYPE, page_id)
2383        hw_entry.insert(HW_LIST_COL_DEVICE, devobj)
2384        hw_entry.insert(HW_LIST_COL_KEY, devobj or title)
2385        return hw_entry
2386
2387    def _init_hw_list(self):
2388        """
2389        Add the static entries to the hw list, like Overview
2390        """
2391        hw_list_model = self.widget("hw-list").get_model()
2392        hw_list_model.clear()
2393
2394        def add_hw_list_option(*args, **kwargs):
2395            hw_list_model.append(self._make_hw_list_entry(*args, **kwargs))
2396
2397        add_hw_list_option(_("Overview"), HW_LIST_TYPE_GENERAL, "computer")
2398        add_hw_list_option(_("OS information"), HW_LIST_TYPE_OS, "computer")
2399        if not self.is_customize_dialog:
2400            add_hw_list_option(_("Performance"), HW_LIST_TYPE_STATS,
2401                               _get_performance_icon_name())
2402        add_hw_list_option(_("CPUs"), HW_LIST_TYPE_CPU, "device_cpu")
2403        add_hw_list_option(_("Memory"), HW_LIST_TYPE_MEMORY, "device_mem")
2404        add_hw_list_option(_("Boot Options"), HW_LIST_TYPE_BOOT, "system-run")
2405
2406        self._repopulate_hw_list()
2407        self._set_hw_selection(0)
2408
2409    def _repopulate_hw_list(self):
2410        """
2411        Refresh the hardware list entries with the latest VM config
2412        """
2413        hw_list = self.widget("hw-list")
2414        hw_list_model = hw_list.get_model()
2415
2416        currentDevices = []
2417
2418        def dev_cmp(origdev, newdev):
2419            if not origdev:
2420                return False
2421
2422            if origdev == newdev:
2423                return True
2424
2425            return origdev.get_xml_id() == newdev.get_xml_id()
2426
2427        def update_hwlist(hwtype, dev, disk_bus_index=None):
2428            """
2429            See if passed hw is already in list, and if so, update info.
2430            If not in list, add it!
2431            """
2432            label = _label_for_device(dev, disk_bus_index)
2433            icon = _icon_for_device(dev)
2434
2435            currentDevices.append(dev)
2436
2437            insertAt = 0
2438            for row in hw_list_model:
2439                rowdev = row[HW_LIST_COL_DEVICE]
2440                if dev_cmp(rowdev, dev):
2441                    # Update existing HW info
2442                    row[HW_LIST_COL_DEVICE] = dev
2443                    row[HW_LIST_COL_LABEL] = label
2444                    row[HW_LIST_COL_ICON_NAME] = icon
2445                    return
2446
2447                if row[HW_LIST_COL_TYPE] <= hwtype:
2448                    insertAt += 1
2449
2450            # Add the new HW row
2451            hw_entry = self._make_hw_list_entry(label, hwtype, icon, dev)
2452            hw_list_model.insert(insertAt, hw_entry)
2453
2454
2455        consoles = self.vm.xmlobj.devices.console
2456        serials = self.vm.xmlobj.devices.serial
2457        if serials and consoles and self.vm.serial_is_console_dup(serials[0]):
2458            consoles.pop(0)
2459
2460        disks = self.vm.xmlobj.devices.disk
2461        for dev, _disk_bus_index in _calculate_disk_bus_index(disks):
2462            update_hwlist(HW_LIST_TYPE_DISK, dev, _disk_bus_index)
2463        for dev in self.vm.xmlobj.devices.interface:
2464            update_hwlist(HW_LIST_TYPE_NIC, dev)
2465        for dev in self.vm.xmlobj.devices.input:
2466            update_hwlist(HW_LIST_TYPE_INPUT, dev)
2467        for dev in self.vm.xmlobj.devices.graphics:
2468            update_hwlist(HW_LIST_TYPE_GRAPHICS, dev)
2469        for dev in self.vm.xmlobj.devices.sound:
2470            update_hwlist(HW_LIST_TYPE_SOUND, dev)
2471        for dev in serials:
2472            update_hwlist(HW_LIST_TYPE_CHAR, dev)
2473        for dev in self.vm.xmlobj.devices.parallel:
2474            update_hwlist(HW_LIST_TYPE_CHAR, dev)
2475        for dev in consoles:
2476            update_hwlist(HW_LIST_TYPE_CHAR, dev)
2477        for dev in self.vm.xmlobj.devices.channel:
2478            update_hwlist(HW_LIST_TYPE_CHAR, dev)
2479        for dev in self.vm.xmlobj.devices.hostdev:
2480            update_hwlist(HW_LIST_TYPE_HOSTDEV, dev)
2481        for dev in self.vm.xmlobj.devices.redirdev:
2482            update_hwlist(HW_LIST_TYPE_REDIRDEV, dev)
2483        for dev in self.vm.xmlobj.devices.video:
2484            update_hwlist(HW_LIST_TYPE_VIDEO, dev)
2485        for dev in self.vm.xmlobj.devices.watchdog:
2486            update_hwlist(HW_LIST_TYPE_WATCHDOG, dev)
2487
2488        for dev in self.vm.xmlobj.devices.controller:
2489            # skip USB2 ICH9 companion controllers
2490            if dev.model in ["ich9-uhci1", "ich9-uhci2", "ich9-uhci3"]:
2491                continue
2492
2493            # These are all parts of a default PCIe setup, which we
2494            # condense down to one listing
2495            if dev.model in ["pcie-root-port", "dmi-to-pci-bridge",
2496                             "pci-bridge"]:
2497                continue
2498
2499            update_hwlist(HW_LIST_TYPE_CONTROLLER, dev)
2500
2501        for dev in self.vm.xmlobj.devices.filesystem:
2502            update_hwlist(HW_LIST_TYPE_FILESYSTEM, dev)
2503        for dev in self.vm.xmlobj.devices.smartcard:
2504            update_hwlist(HW_LIST_TYPE_SMARTCARD, dev)
2505        for dev in self.vm.xmlobj.devices.tpm:
2506            update_hwlist(HW_LIST_TYPE_TPM, dev)
2507        for dev in self.vm.xmlobj.devices.rng:
2508            update_hwlist(HW_LIST_TYPE_RNG, dev)
2509        for dev in self.vm.xmlobj.devices.panic:
2510            update_hwlist(HW_LIST_TYPE_PANIC, dev)
2511        for dev in self.vm.xmlobj.devices.vsock:
2512            update_hwlist(HW_LIST_TYPE_VSOCK, dev)
2513
2514        devs = list(range(len(hw_list_model)))
2515        devs.reverse()
2516        for i in devs:
2517            _iter = hw_list_model.iter_nth_child(None, i)
2518            olddev = hw_list_model[i][HW_LIST_COL_DEVICE]
2519
2520            # Existing device, don't remove it
2521            if not olddev or olddev in currentDevices:
2522                continue
2523
2524            hw_list_model.remove(_iter)
2525
2526
2527    ################
2528    # UI listeners #
2529    ################
2530
2531    def _config_apply_clicked_cb(self, src):
2532        self._config_apply()
2533    def _config_cancel_clicked_cb(self, src):
2534        self._config_cancel()
2535    def _config_remove_clicked_cb(self, src):
2536        self._config_remove()
2537
2538    def _refresh_ip_clicked_cb(self, src):
2539        self._refresh_ip()
2540
2541    def _browse_kernel_clicked_cb(self, src):
2542        def cb(ignore, path):
2543            self.widget("boot-kernel").set_text(path)
2544        self._browse_file(cb)
2545    def _browse_initrd_clicked_cb(self, src):
2546        def cb(ignore, path):
2547            self.widget("boot-initrd").set_text(path)
2548        self._browse_file(cb)
2549    def _browse_dtb_clicked_cb(self, src):
2550        def cb(ignore, path):
2551            self.widget("boot-dtb").set_text(path)
2552        self._browse_file(cb)
2553
2554    def _xmleditor_xml_requested_cb(self, src):
2555        self._refresh_page()
2556    def _xmleditor_xml_reset_cb(self, src):
2557        self._refresh_page()
2558
2559    def _addhw_clicked_cb(self, src):
2560        self._show_addhw()
2561
2562    def _boot_kernel_toggled_cb(self, src):
2563        self.widget("boot-kernel-box").set_sensitive(src.get_active())
2564        self._enable_apply(EDIT_KERNEL)
2565    def _boot_list_changed_cb(self, src):
2566        self._config_bootdev_selected()
2567    def _boot_moveup_clicked_cb(self, src):
2568        self._config_boot_move(True)
2569    def _boot_movedown_clicked_cb(self, src):
2570        self._config_boot_move(False)
2571
2572    def _vm_inspection_changed_cb(self, vm):
2573        self._refresh_os_page()
2574