mirror of
https://github.com/virt-manager/virt-manager.git
synced 2026-08-12 05:54:53 -05:00
virtManager: move device UI files to virtManager/device/
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
# Copyright (C) 2014 Red Hat, Inc.
|
||||
#
|
||||
# This work is licensed under the GNU GPLv2 or later.
|
||||
# See the COPYING file in the top-level directory.
|
||||
|
||||
import os
|
||||
|
||||
from gi.repository import Gtk
|
||||
|
||||
import virtinst
|
||||
from virtinst import log
|
||||
|
||||
from .. import uiutil
|
||||
from ..baseclass import vmmGObjectUI
|
||||
|
||||
|
||||
class vmmAddStorage(vmmGObjectUI):
|
||||
__gsignals__ = {
|
||||
"browse-clicked": (vmmGObjectUI.RUN_FIRST, None, [object]),
|
||||
"storage-toggled": (vmmGObjectUI.RUN_FIRST, None, [object])
|
||||
}
|
||||
|
||||
def __init__(self, conn, builder, topwin):
|
||||
vmmGObjectUI.__init__(self, "addstorage.ui", None,
|
||||
builder=builder, topwin=topwin)
|
||||
self.conn = conn
|
||||
|
||||
self.builder.connect_signals({
|
||||
"on_storage_browse_clicked": self._browse_storage,
|
||||
"on_storage_select_toggled": self._toggle_storage_select,
|
||||
})
|
||||
|
||||
self.top_box = self.widget("storage-box")
|
||||
|
||||
def _cleanup(self):
|
||||
self.conn = None
|
||||
|
||||
|
||||
##########################
|
||||
# Initialization methods #
|
||||
##########################
|
||||
|
||||
def _host_disk_space(self):
|
||||
try:
|
||||
pool = self.conn.get_default_pool()
|
||||
if pool and pool.is_active():
|
||||
# Rate limit this, since it can be spammed at dialog startup time
|
||||
if pool.secs_since_last_refresh() > 10:
|
||||
pool.refresh()
|
||||
avail = int(pool.get_available())
|
||||
return float(avail / 1024.0 / 1024.0 / 1024.0)
|
||||
except Exception:
|
||||
log.exception("Error determining host disk space")
|
||||
return -1
|
||||
|
||||
def _update_host_space(self):
|
||||
widget = self.widget("phys-hd-label")
|
||||
max_storage = self._host_disk_space()
|
||||
|
||||
def pretty_storage(size):
|
||||
if size == -1:
|
||||
return "Unknown GiB"
|
||||
return "%.1f GiB" % float(size)
|
||||
|
||||
hd_label = (_("%s available in the default location") %
|
||||
pretty_storage(max_storage))
|
||||
hd_label = ("<span color='#484848'>%s</span>" % hd_label)
|
||||
widget.set_markup(hd_label)
|
||||
|
||||
|
||||
##############
|
||||
# Public API #
|
||||
##############
|
||||
|
||||
@staticmethod
|
||||
def check_path_search(src, conn, path):
|
||||
skip_paths = src.config.get_perms_fix_ignore()
|
||||
searchdata = virtinst.DeviceDisk.check_path_search(
|
||||
conn.get_backend(), path)
|
||||
|
||||
broken_paths = searchdata.fixlist[:]
|
||||
for p in broken_paths[:]:
|
||||
if p in skip_paths:
|
||||
broken_paths.remove(p)
|
||||
|
||||
if not broken_paths:
|
||||
return
|
||||
|
||||
log.debug("No search access for dirs: %s", broken_paths)
|
||||
resp, chkres = src.err.warn_chkbox(
|
||||
_("The emulator may not have search permissions "
|
||||
"for the path '%s'.") % path,
|
||||
_("Do you want to correct this now?"),
|
||||
_("Don't ask about these directories again."),
|
||||
buttons=Gtk.ButtonsType.YES_NO)
|
||||
|
||||
if chkres:
|
||||
src.config.add_perms_fix_ignore(broken_paths)
|
||||
if not resp:
|
||||
return
|
||||
|
||||
log.debug("Attempting to correct permission issues.")
|
||||
errors = virtinst.DeviceDisk.fix_path_search(
|
||||
conn.get_backend(), searchdata)
|
||||
if not errors:
|
||||
return
|
||||
|
||||
errmsg = _("Errors were encountered changing permissions for the "
|
||||
"following directories:")
|
||||
details = ""
|
||||
for p, error in errors.items():
|
||||
if p not in broken_paths:
|
||||
continue
|
||||
details += "%s : %s\n" % (p, error)
|
||||
details += "\nIt is very likely the VM will fail to start up."
|
||||
|
||||
log.debug("Permission errors:\n%s", details)
|
||||
|
||||
ignore, chkres = src.err.err_chkbox(errmsg, details,
|
||||
_("Don't ask about these directories again."))
|
||||
|
||||
if chkres:
|
||||
src.config.add_perms_fix_ignore(list(errors.keys()))
|
||||
|
||||
def reset_state(self):
|
||||
self._update_host_space()
|
||||
self.widget("storage-create").set_active(True)
|
||||
self.widget("storage-size").set_value(20)
|
||||
self.widget("storage-entry").set_text("")
|
||||
self.widget("storage-create-box").set_sensitive(True)
|
||||
|
||||
storage_tooltip = None
|
||||
|
||||
can_storage = (not self.conn.is_remote() or
|
||||
self.conn.is_storage_capable())
|
||||
use_storage = self.widget("storage-select")
|
||||
storage_area = self.widget("storage-box")
|
||||
|
||||
storage_area.set_sensitive(can_storage)
|
||||
if not can_storage:
|
||||
storage_tooltip = _("Connection does not support storage"
|
||||
" management.")
|
||||
use_storage.set_sensitive(True)
|
||||
storage_area.set_tooltip_text(storage_tooltip or "")
|
||||
|
||||
def get_default_path(self, name, collideguest=None):
|
||||
pool = self.conn.get_default_pool()
|
||||
if not pool:
|
||||
return
|
||||
|
||||
fmt = self.conn.get_default_storage_format()
|
||||
suffix = virtinst.StorageVolume.get_file_extension_for_format(fmt)
|
||||
suffix = suffix or ".img"
|
||||
|
||||
path = virtinst.StorageVolume.find_free_name(
|
||||
self.conn.get_backend(), pool.get_backend(), name,
|
||||
suffix=suffix, collideguest=collideguest)
|
||||
|
||||
return os.path.join(pool.xmlobj.target_path, path)
|
||||
|
||||
def is_default_storage(self):
|
||||
return self.widget("storage-create").get_active()
|
||||
|
||||
def build_device(self, vmname,
|
||||
path=None, device="disk", collideguest=None):
|
||||
if path is None:
|
||||
if self.is_default_storage():
|
||||
path = self.get_default_path(vmname, collideguest=collideguest)
|
||||
else:
|
||||
path = self.widget("storage-entry").get_text().strip()
|
||||
|
||||
disk = virtinst.DeviceDisk(self.conn.get_backend())
|
||||
disk.path = path or None
|
||||
disk.device = device
|
||||
|
||||
if disk.wants_storage_creation():
|
||||
pool = disk.get_parent_pool()
|
||||
size = uiutil.spin_get_helper(self.widget("storage-size"))
|
||||
sparse = False
|
||||
|
||||
vol_install = virtinst.DeviceDisk.build_vol_install(
|
||||
disk.conn, os.path.basename(disk.path), pool,
|
||||
size, sparse)
|
||||
disk.set_vol_install(vol_install)
|
||||
|
||||
fmt = self.conn.get_default_storage_format()
|
||||
if disk.get_vol_install().supports_property("format"):
|
||||
log.debug("Using default prefs format=%s for path=%s",
|
||||
fmt, disk.path)
|
||||
disk.get_vol_install().format = fmt
|
||||
else:
|
||||
log.debug("path=%s can not use default prefs format=%s, "
|
||||
"not setting it", disk.path, fmt)
|
||||
|
||||
return disk
|
||||
|
||||
def validate_device(self, disk):
|
||||
if not disk.path and disk.device in ["disk", "lun"]:
|
||||
return self.err.val_err(_("A storage path must be specified."))
|
||||
|
||||
disk.validate()
|
||||
|
||||
isfatal, errmsg = disk.is_size_conflict()
|
||||
if not isfatal and errmsg:
|
||||
# Fatal errors are reported when setting 'size'
|
||||
res = self.err.ok_cancel(_("Not Enough Free Space"), errmsg)
|
||||
if not res:
|
||||
return False
|
||||
|
||||
# Disk collision
|
||||
names = disk.is_conflict_disk()
|
||||
if names:
|
||||
res = self.err.yes_no(
|
||||
_('Disk "%s" is already in use by other guests %s') %
|
||||
(disk.path, names),
|
||||
_("Do you really want to use the disk?"))
|
||||
if not res:
|
||||
return False
|
||||
|
||||
self.check_path_search(self, self.conn, disk.path)
|
||||
|
||||
|
||||
#############
|
||||
# Listeners #
|
||||
#############
|
||||
|
||||
def _browse_storage(self, ignore):
|
||||
self.emit("browse-clicked", self.widget("storage-entry"))
|
||||
|
||||
def _toggle_storage_select(self, src):
|
||||
act = src.get_active()
|
||||
self.widget("storage-browse-box").set_sensitive(act)
|
||||
self.emit("storage-toggled", src)
|
||||
@@ -0,0 +1,317 @@
|
||||
# Copyright (C) 2006-2007, 2013, 2014 Red Hat, Inc.
|
||||
# Copyright (C) 2006 Hugh O. Brock <hbrock@redhat.com>
|
||||
# Copyright (C) 2014 SUSE LINUX Products GmbH, Nuernberg, Germany.
|
||||
#
|
||||
# This work is licensed under the GNU GPLv2 or later.
|
||||
# See the COPYING file in the top-level directory.
|
||||
|
||||
from gi.repository import Gtk
|
||||
|
||||
from virtinst import DeviceFilesystem
|
||||
|
||||
from .. import uiutil
|
||||
from ..baseclass import vmmGObjectUI
|
||||
from ..storagebrowse import vmmStorageBrowser
|
||||
|
||||
|
||||
class vmmFSDetails(vmmGObjectUI):
|
||||
__gsignals__ = {
|
||||
"changed": (vmmGObjectUI.RUN_FIRST, None, [])
|
||||
}
|
||||
|
||||
def __init__(self, vm, builder, topwin):
|
||||
vmmGObjectUI.__init__(self, "fsdetails.ui",
|
||||
None, builder=builder, topwin=topwin)
|
||||
|
||||
self.vm = vm
|
||||
self.conn = vm.conn
|
||||
|
||||
self._dev = None
|
||||
self.storage_browser = None
|
||||
|
||||
self.builder.connect_signals({
|
||||
"on_fs_type_combo_changed": self.change_field,
|
||||
"on_fs_driver_combo_changed": self.change_field,
|
||||
"on_fs_source_browse_clicked": self.browse_fs_source,
|
||||
"on_fs_mode_combo_changed": self.notify_change,
|
||||
"on_fs_wrpolicy_combo_changed": self.notify_change,
|
||||
"on_fs_readonly_toggled": self.notify_change,
|
||||
"on_fs_format_combo_changed": self.notify_change,
|
||||
"on_fs_source_changed": self.notify_change,
|
||||
"on_fs_ram_source_changed": self.notify_change,
|
||||
"on_fs_target_changed": self.notify_change,
|
||||
})
|
||||
|
||||
self.set_initial_state()
|
||||
self.top_box = self.widget("vmm-fs-details")
|
||||
|
||||
def _cleanup(self):
|
||||
self.vm = None
|
||||
self.conn = None
|
||||
self._dev = None
|
||||
|
||||
if self.storage_browser:
|
||||
self.storage_browser.cleanup()
|
||||
self.storage_browser = None
|
||||
|
||||
def show_pair_combo(self, basename, show_combo):
|
||||
combo = self.widget(basename + "-combo")
|
||||
label = self.widget(basename + "-label")
|
||||
|
||||
combo.set_visible(show_combo)
|
||||
label.set_visible(not show_combo)
|
||||
|
||||
def show_check_button(self, basename, show):
|
||||
check = self.widget(basename)
|
||||
check.set_visible(show)
|
||||
|
||||
##########################
|
||||
# Initialization methods #
|
||||
##########################
|
||||
|
||||
def set_initial_state(self):
|
||||
def simple_store_set(comboname, values, sort=True, capitalize=True):
|
||||
combo = self.widget(comboname)
|
||||
# [XML value, label]
|
||||
model = Gtk.ListStore(str, str)
|
||||
combo.set_model(model)
|
||||
uiutil.init_combo_text_column(combo, 1)
|
||||
if sort:
|
||||
model.set_sort_column_id(0, Gtk.SortType.ASCENDING)
|
||||
|
||||
for xmlval in values:
|
||||
label = xmlval
|
||||
if xmlval is None:
|
||||
label = "Default"
|
||||
if capitalize:
|
||||
label = label.capitalize()
|
||||
model.append([xmlval, label])
|
||||
|
||||
# Filesystem widgets
|
||||
if self.conn.is_openvz():
|
||||
simple_store_set("fs-type-combo",
|
||||
[DeviceFilesystem.TYPE_MOUNT,
|
||||
DeviceFilesystem.TYPE_TEMPLATE], sort=False)
|
||||
elif self.conn.is_lxc():
|
||||
simple_store_set("fs-type-combo",
|
||||
[DeviceFilesystem.TYPE_MOUNT,
|
||||
DeviceFilesystem.TYPE_FILE,
|
||||
DeviceFilesystem.TYPE_BLOCK,
|
||||
DeviceFilesystem.TYPE_RAM], sort=False)
|
||||
else:
|
||||
simple_store_set("fs-type-combo", [DeviceFilesystem.TYPE_MOUNT])
|
||||
self.widget("fs-type-label").set_text(DeviceFilesystem.TYPE_MOUNT)
|
||||
|
||||
simple_store_set("fs-mode-combo", DeviceFilesystem.MODES + [None])
|
||||
|
||||
drivers = []
|
||||
if self.conn.is_qemu() or self.conn.is_test():
|
||||
drivers += [DeviceFilesystem.DRIVER_PATH,
|
||||
DeviceFilesystem.DRIVER_HANDLE]
|
||||
if self.conn.is_lxc() or self.conn.is_test():
|
||||
drivers += [DeviceFilesystem.DRIVER_LOOP,
|
||||
DeviceFilesystem.DRIVER_NBD]
|
||||
simple_store_set("fs-driver-combo", drivers + [None])
|
||||
|
||||
simple_store_set("fs-format-combo", ["raw", "qcow2"], capitalize=False)
|
||||
simple_store_set("fs-wrpolicy-combo",
|
||||
DeviceFilesystem.WRPOLICIES + [None])
|
||||
self.show_pair_combo("fs-type",
|
||||
self.conn.is_openvz() or self.conn.is_lxc())
|
||||
self.show_check_button("fs-readonly",
|
||||
self.conn.is_qemu() or
|
||||
self.conn.is_test() or
|
||||
self.conn.is_lxc())
|
||||
|
||||
def reset_state(self):
|
||||
self.widget("fs-type-combo").set_active(0)
|
||||
self.widget("fs-mode-combo").set_active(0)
|
||||
self.widget("fs-driver-combo").set_active(0)
|
||||
self.widget("fs-format-combo").set_active(0)
|
||||
self.widget("fs-wrpolicy-combo").set_active(0)
|
||||
self.widget("fs-source").set_text("")
|
||||
self.widget("fs-target").set_text("")
|
||||
self.widget("fs-readonly").set_active(False)
|
||||
|
||||
# Getters
|
||||
def get_dev(self):
|
||||
return self._dev
|
||||
|
||||
def get_config_fs_mode(self):
|
||||
return uiutil.get_list_selection(self.widget("fs-mode-combo"),
|
||||
check_visible=True)
|
||||
|
||||
def get_config_fs_wrpolicy(self):
|
||||
return uiutil.get_list_selection(self.widget("fs-wrpolicy-combo"),
|
||||
check_visible=True)
|
||||
|
||||
def get_config_fs_type(self):
|
||||
if self.widget("fs-type-label").is_visible():
|
||||
return self.widget("fs-type-label").get_text()
|
||||
return uiutil.get_list_selection(self.widget("fs-type-combo"),
|
||||
check_visible=True)
|
||||
|
||||
def get_config_fs_readonly(self):
|
||||
if not self.widget("fs-readonly").is_visible():
|
||||
return None
|
||||
return self.widget("fs-readonly").get_active()
|
||||
|
||||
def get_config_fs_driver(self):
|
||||
return uiutil.get_list_selection(self.widget("fs-driver-combo"),
|
||||
check_visible=True)
|
||||
|
||||
def get_config_fs_format(self):
|
||||
return uiutil.get_list_selection(self.widget("fs-format-combo"),
|
||||
check_visible=True)
|
||||
|
||||
# Setters
|
||||
def set_dev(self, dev):
|
||||
self._dev = dev
|
||||
|
||||
self.set_config_value("fs-type", dev.type)
|
||||
self.set_config_value("fs-mode", dev.accessmode)
|
||||
self.set_config_value("fs-driver", dev.driver)
|
||||
self.set_config_value("fs-wrpolicy", dev.wrpolicy)
|
||||
self.set_config_value("fs-format", dev.format)
|
||||
if dev.type != DeviceFilesystem.TYPE_RAM:
|
||||
self.widget("fs-source").set_text(dev.source)
|
||||
else:
|
||||
self.widget("fs-ram-source-spin").set_value(int(dev.source) // 1024)
|
||||
self.widget("fs-target").set_text(dev.target or "")
|
||||
self.widget("fs-readonly").set_active(dev.readonly)
|
||||
|
||||
self.show_pair_combo("fs-type",
|
||||
self.conn.is_openvz() or self.conn.is_lxc())
|
||||
|
||||
def set_config_value(self, name, value):
|
||||
combo = self.widget("%s-combo" % name)
|
||||
label = self.widget("%s-label" % name)
|
||||
|
||||
uiutil.set_list_selection(combo, value)
|
||||
if label:
|
||||
label.set_text(value or "default")
|
||||
|
||||
# listeners
|
||||
def notify_change(self, ignore):
|
||||
self.emit("changed")
|
||||
|
||||
def browse_fs_source(self, ignore1):
|
||||
self._browse_file(self.widget("fs-source"), isdir=True)
|
||||
|
||||
def update_fs_rows(self):
|
||||
fstype = self.get_config_fs_type()
|
||||
fsdriver = self.get_config_fs_driver()
|
||||
ismount = bool(
|
||||
fstype == DeviceFilesystem.TYPE_MOUNT or
|
||||
self.conn.is_qemu() or self.conn.is_test())
|
||||
|
||||
show_mode = bool(ismount and
|
||||
(fsdriver == DeviceFilesystem.DRIVER_PATH or
|
||||
fsdriver is None))
|
||||
uiutil.set_grid_row_visible(self.widget("fs-mode-box"), show_mode)
|
||||
|
||||
show_wrpol = bool(ismount and
|
||||
fsdriver and (fsdriver == DeviceFilesystem.DRIVER_PATH or
|
||||
fsdriver == DeviceFilesystem.DRIVER_HANDLE))
|
||||
uiutil.set_grid_row_visible(self.widget("fs-wrpolicy-box"),
|
||||
show_wrpol)
|
||||
|
||||
show_ram_source = fstype == DeviceFilesystem.TYPE_RAM
|
||||
uiutil.set_grid_row_visible(
|
||||
self.widget("fs-ram-source-box"), show_ram_source)
|
||||
uiutil.set_grid_row_visible(
|
||||
self.widget("fs-source-box"), not show_ram_source)
|
||||
|
||||
show_format = bool(
|
||||
fsdriver == DeviceFilesystem.DRIVER_NBD)
|
||||
uiutil.set_grid_row_visible(self.widget("fs-format-box"), show_format)
|
||||
self.show_pair_combo("fs-format", True)
|
||||
|
||||
show_mode_combo = False
|
||||
show_driver_combo = False
|
||||
show_wrpolicy_combo = self.conn.is_qemu() or self.conn.is_test()
|
||||
if fstype == DeviceFilesystem.TYPE_TEMPLATE:
|
||||
source_text = _("Te_mplate:")
|
||||
else:
|
||||
source_text = _("_Source path:")
|
||||
show_mode_combo = self.conn.is_qemu() or self.conn.is_test()
|
||||
show_driver_combo = (self.conn.is_qemu() or
|
||||
self.conn.is_lxc() or
|
||||
self.conn.is_test())
|
||||
|
||||
self.widget("fs-source-title").set_text(source_text)
|
||||
self.widget("fs-source-title").set_use_underline(True)
|
||||
self.show_pair_combo("fs-mode", show_mode_combo)
|
||||
self.show_pair_combo("fs-driver", show_driver_combo)
|
||||
self.show_pair_combo("fs-wrpolicy", show_wrpolicy_combo)
|
||||
|
||||
def change_field(self, src):
|
||||
self.update_fs_rows()
|
||||
self.notify_change(src)
|
||||
|
||||
# Page validation method
|
||||
def validate_page_filesystem(self):
|
||||
conn = self.conn.get_backend()
|
||||
source = self.widget("fs-source").get_text()
|
||||
target = self.widget("fs-target").get_text()
|
||||
usage = uiutil.spin_get_helper(self.widget("fs-ram-source-spin"))
|
||||
mode = self.get_config_fs_mode()
|
||||
fstype = self.get_config_fs_type()
|
||||
readonly = self.get_config_fs_readonly()
|
||||
driver = self.get_config_fs_driver()
|
||||
fsformat = self.get_config_fs_format()
|
||||
wrpolicy = self.get_config_fs_wrpolicy()
|
||||
|
||||
if not source and fstype != DeviceFilesystem.TYPE_RAM:
|
||||
return self.err.val_err(_("A filesystem source must be specified"))
|
||||
elif usage == 0 and fstype == DeviceFilesystem.TYPE_RAM:
|
||||
return self.err.val_err(
|
||||
_("A RAM filesystem usage must be specified"))
|
||||
if not target:
|
||||
return self.err.val_err(_("A filesystem target must be specified"))
|
||||
|
||||
try:
|
||||
self._dev = DeviceFilesystem(conn)
|
||||
if fstype == DeviceFilesystem.TYPE_RAM:
|
||||
self._dev.source = usage
|
||||
self._dev.units = 'MiB'
|
||||
else:
|
||||
self._dev.source = source
|
||||
self._dev.target = target
|
||||
self._dev.validate_target(target)
|
||||
if mode:
|
||||
self._dev.accessmode = mode
|
||||
if fstype:
|
||||
self._dev.type = fstype
|
||||
if readonly:
|
||||
self._dev.readonly = readonly
|
||||
if driver:
|
||||
self._dev.driver = driver
|
||||
if driver == DeviceFilesystem.DRIVER_LOOP:
|
||||
self._dev.format = "raw"
|
||||
elif driver == DeviceFilesystem.DRIVER_NBD:
|
||||
self._dev.format = fsformat
|
||||
if wrpolicy:
|
||||
self._dev.wrpolicy = wrpolicy
|
||||
except Exception as e:
|
||||
return self.err.val_err(_("Filesystem parameter error"), e)
|
||||
|
||||
def _browse_file(self, textent, isdir=False):
|
||||
def set_storage_cb(src, path):
|
||||
if path:
|
||||
textent.set_text(path)
|
||||
|
||||
reason = (isdir and
|
||||
self.config.CONFIG_DIR_FS or
|
||||
self.config.CONFIG_DIR_IMAGE)
|
||||
|
||||
if self.storage_browser and self.storage_browser.conn != self.conn:
|
||||
self.storage_browser.cleanup()
|
||||
self.storage_browser = None
|
||||
if self.storage_browser is None:
|
||||
self.storage_browser = vmmStorageBrowser(self.conn)
|
||||
|
||||
self.storage_browser.set_finish_cb(set_storage_cb)
|
||||
self.storage_browser.set_browse_reason(reason)
|
||||
|
||||
self.storage_browser.show(self.topwin.get_ancestor(Gtk.Window))
|
||||
@@ -0,0 +1,386 @@
|
||||
# Copyright (C) 2006-2007, 2013, 2014 Red Hat, Inc.
|
||||
# Copyright (C) 2006 Hugh O. Brock <hbrock@redhat.com>
|
||||
# Copyright (C) 2014 SUSE LINUX Products GmbH, Nuernberg, Germany.
|
||||
#
|
||||
# This work is licensed under the GNU GPLv2 or later.
|
||||
# See the COPYING file in the top-level directory.
|
||||
|
||||
from gi.repository import Gtk
|
||||
|
||||
import virtinst
|
||||
|
||||
from .. import uiutil
|
||||
from ..baseclass import vmmGObjectUI
|
||||
|
||||
|
||||
class vmmGraphicsDetails(vmmGObjectUI):
|
||||
__gsignals__ = {
|
||||
"changed-password": (vmmGObjectUI.RUN_FIRST, None, []),
|
||||
"changed-port": (vmmGObjectUI.RUN_FIRST, None, []),
|
||||
"changed-tlsport": (vmmGObjectUI.RUN_FIRST, None, []),
|
||||
"changed-type": (vmmGObjectUI.RUN_FIRST, None, []),
|
||||
"changed-listen": (vmmGObjectUI.RUN_FIRST, None, []),
|
||||
"changed-address": (vmmGObjectUI.RUN_FIRST, None, []),
|
||||
"changed-keymap": (vmmGObjectUI.RUN_FIRST, None, []),
|
||||
"changed-opengl": (vmmGObjectUI.RUN_FIRST, None, []),
|
||||
"changed-rendernode": (vmmGObjectUI.RUN_FIRST, None, []),
|
||||
}
|
||||
|
||||
def __init__(self, vm, builder, topwin):
|
||||
vmmGObjectUI.__init__(self, "gfxdetails.ui",
|
||||
None, builder=builder, topwin=topwin)
|
||||
self.vm = vm
|
||||
self.conn = vm.conn
|
||||
|
||||
self.builder.connect_signals({
|
||||
"on_graphics_type_changed": self._change_graphics_type,
|
||||
"on_graphics_port_auto_toggled": self._change_port_auto,
|
||||
"on_graphics_tlsport_auto_toggled": self._change_tlsport_auto,
|
||||
"on_graphics_use_password": self._change_password_chk,
|
||||
"on_graphics_show_password": self._show_password_chk,
|
||||
|
||||
"on_graphics_listen_type_changed": self._change_graphics_listen,
|
||||
"on_graphics_password_changed": lambda ignore: self.emit("changed-password"),
|
||||
"on_graphics_address_changed": lambda ignore: self.emit("changed-address"),
|
||||
"on_graphics_tlsport_changed": lambda ignore: self.emit("changed-tlsport"),
|
||||
"on_graphics_port_changed": lambda ignore: self.emit("changed-port"),
|
||||
"on_graphics_keymap_changed": lambda ignore: self.emit("changed-keymap"),
|
||||
"on_graphics_opengl_toggled": self._change_opengl,
|
||||
"on_graphics_rendernode_changed": lambda ignore: self.emit("changed-rendernode")
|
||||
})
|
||||
|
||||
self._init_ui()
|
||||
self.top_box = self.widget("graphics-box")
|
||||
|
||||
def _cleanup(self):
|
||||
self.vm = None
|
||||
self.conn = None
|
||||
|
||||
|
||||
#####################
|
||||
# Pretty UI helpers #
|
||||
#####################
|
||||
|
||||
@staticmethod
|
||||
def graphics_pretty_type_simple(gtype):
|
||||
if (gtype in [virtinst.DeviceGraphics.TYPE_VNC,
|
||||
virtinst.DeviceGraphics.TYPE_SDL,
|
||||
virtinst.DeviceGraphics.TYPE_RDP]):
|
||||
return str(gtype).upper()
|
||||
return str(gtype).capitalize()
|
||||
|
||||
|
||||
##########################
|
||||
# Initialization methods #
|
||||
##########################
|
||||
|
||||
def _init_ui(self):
|
||||
graphics_list = self.widget("graphics-type")
|
||||
graphics_model = Gtk.ListStore(str, str)
|
||||
graphics_list.set_model(graphics_model)
|
||||
uiutil.init_combo_text_column(graphics_list, 1)
|
||||
graphics_model.clear()
|
||||
graphics_model.append(["spice", _("Spice server")])
|
||||
graphics_model.append(["vnc", _("VNC server")])
|
||||
|
||||
graphics_listen_list = self.widget("graphics-listen-type")
|
||||
graphics_listen_model = Gtk.ListStore(str, str)
|
||||
graphics_listen_list.set_model(graphics_listen_model)
|
||||
uiutil.init_combo_text_column(graphics_listen_list, 1)
|
||||
graphics_listen_model.clear()
|
||||
graphics_listen_model.append(["address", _("Address")])
|
||||
graphics_listen_model.append(["none", _("None")])
|
||||
|
||||
self.widget("graphics-address").set_model(Gtk.ListStore(str, str))
|
||||
uiutil.init_combo_text_column(self.widget("graphics-address"), 1)
|
||||
|
||||
model = self.widget("graphics-address").get_model()
|
||||
model.clear()
|
||||
model.append([None, _("Hypervisor default")])
|
||||
model.append(["127.0.0.1", _("Localhost only")])
|
||||
model.append(["0.0.0.0", _("All interfaces")])
|
||||
|
||||
# Keymap
|
||||
combo = self.widget("graphics-keymap")
|
||||
model = Gtk.ListStore(str, str)
|
||||
combo.set_model(model)
|
||||
uiutil.init_combo_text_column(combo, 1)
|
||||
|
||||
model.append(["auto", _("Auto")])
|
||||
model.append([virtinst.DeviceGraphics.KEYMAP_LOCAL,
|
||||
_("Copy local keymap")])
|
||||
for k in virtinst.DeviceGraphics.valid_keymaps():
|
||||
model.append([k, k])
|
||||
|
||||
# Host GPU rendernode
|
||||
combo = self.widget("graphics-rendernode")
|
||||
model = Gtk.ListStore(str, str)
|
||||
combo.set_model(model)
|
||||
uiutil.init_combo_text_column(combo, 1)
|
||||
model.append([None, _("Auto")])
|
||||
devs = self.conn.filter_nodedevs("drm")
|
||||
for i in devs:
|
||||
drm = i.xmlobj
|
||||
if not drm.is_drm_render():
|
||||
continue
|
||||
rendernode = drm.get_devnode().path
|
||||
model.append([rendernode, i.pretty_name()])
|
||||
|
||||
def _get_config_graphics_ports(self):
|
||||
port = uiutil.spin_get_helper(self.widget("graphics-port"))
|
||||
tlsport = uiutil.spin_get_helper(self.widget("graphics-tlsport"))
|
||||
gtype = uiutil.get_list_selection(self.widget("graphics-type"))
|
||||
|
||||
if self.widget("graphics-port-auto").get_active():
|
||||
port = -1
|
||||
if self.widget("graphics-tlsport-auto").get_active():
|
||||
tlsport = -1
|
||||
|
||||
if gtype != "spice":
|
||||
tlsport = None
|
||||
return port, tlsport
|
||||
|
||||
|
||||
##############
|
||||
# Public API #
|
||||
##############
|
||||
|
||||
def reset_state(self):
|
||||
uiutil.set_grid_row_visible(self.widget("graphics-display"), False)
|
||||
uiutil.set_grid_row_visible(self.widget("graphics-xauth"), False)
|
||||
|
||||
self.widget("graphics-type").set_active(0)
|
||||
self.widget("graphics-listen-type").set_active(0)
|
||||
self.widget("graphics-address").set_active(0)
|
||||
self.widget("graphics-keymap").set_active(0)
|
||||
|
||||
# Select last entry in the list, which should be a rendernode path
|
||||
rendermodel = self.widget("graphics-rendernode").get_model()
|
||||
self.widget("graphics-rendernode").set_active_iter(rendermodel[-1].iter)
|
||||
|
||||
self._change_ports()
|
||||
self.widget("graphics-port-auto").set_active(True)
|
||||
self.widget("graphics-tlsport-auto").set_active(True)
|
||||
self.widget("graphics-password").set_text("")
|
||||
self.widget("graphics-password").set_sensitive(False)
|
||||
self.widget("graphics-password-chk").set_active(False)
|
||||
|
||||
def get_values(self):
|
||||
gtype = uiutil.get_list_selection(self.widget("graphics-type"))
|
||||
port, tlsport = self._get_config_graphics_ports()
|
||||
listen = uiutil.get_list_selection(self.widget("graphics-listen-type"))
|
||||
addr = uiutil.get_list_selection(self.widget("graphics-address"))
|
||||
keymap = uiutil.get_list_selection(self.widget("graphics-keymap"))
|
||||
if keymap == "auto":
|
||||
keymap = None
|
||||
|
||||
passwd = self.widget("graphics-password").get_text()
|
||||
if not self.widget("graphics-password-chk").get_active():
|
||||
passwd = None
|
||||
|
||||
gl = self.widget("graphics-opengl").get_active()
|
||||
rendernode = uiutil.get_list_selection(self.widget("graphics-rendernode"))
|
||||
|
||||
return gtype, port, tlsport, listen, addr, passwd, keymap, gl, rendernode
|
||||
|
||||
def set_dev(self, gfx):
|
||||
self.reset_state()
|
||||
|
||||
def set_port(basename, val):
|
||||
auto = self.widget(basename + "-auto")
|
||||
widget = self.widget(basename)
|
||||
auto.set_inconsistent(False)
|
||||
label = auto.get_label().split(" (")[0]
|
||||
|
||||
if val == -1 or gfx.autoport:
|
||||
auto.set_active(True)
|
||||
if val and val != -1:
|
||||
label += " (%s %s)" % (_("Port"), val)
|
||||
elif val is None:
|
||||
auto.set_inconsistent(True)
|
||||
else:
|
||||
auto.set_active(False)
|
||||
widget.set_value(val)
|
||||
|
||||
auto.set_label(label)
|
||||
|
||||
gtype = gfx.type
|
||||
is_vnc = (gtype == "vnc")
|
||||
is_sdl = (gtype == "sdl")
|
||||
is_spice = (gtype == "spice")
|
||||
pretty_type = vmmGraphicsDetails.graphics_pretty_type_simple(gtype)
|
||||
title = (_("%(graphicstype)s Server") % {"graphicstype": pretty_type})
|
||||
|
||||
if is_vnc or is_spice:
|
||||
use_passwd = gfx.passwd is not None
|
||||
|
||||
set_port("graphics-port", gfx.port)
|
||||
listentype = gfx.get_first_listen_type()
|
||||
if listentype and listentype == 'none':
|
||||
uiutil.set_list_selection(self.widget("graphics-listen-type"), 'none')
|
||||
else:
|
||||
uiutil.set_list_selection(self.widget("graphics-listen-type"), 'address')
|
||||
uiutil.set_list_selection(
|
||||
self.widget("graphics-address"), gfx.listen)
|
||||
uiutil.set_list_selection(
|
||||
self.widget("graphics-keymap"), gfx.keymap or None)
|
||||
|
||||
self.widget("graphics-password").set_text(gfx.passwd or "")
|
||||
self.widget("graphics-password-chk").set_active(use_passwd)
|
||||
self.widget("graphics-password").set_sensitive(use_passwd)
|
||||
|
||||
if is_spice:
|
||||
set_port("graphics-tlsport", gfx.tlsPort)
|
||||
|
||||
opengl_warning = ""
|
||||
rendernode_warning = ""
|
||||
opengl_supported = self.conn.support.conn_spice_gl()
|
||||
rendernode_supported = self.conn.support.conn_spice_rendernode()
|
||||
|
||||
# * If spicegl isn't supported, show a warning icon and
|
||||
# and desensitive everything
|
||||
# * If qemu:///system and rendernode isn't supported,
|
||||
# show a warning icon and desensitize everything, since
|
||||
# rendernode support is needed for it to work out of the box.
|
||||
# * Otherwise, enable all UI, but show warning icons anyways
|
||||
# for potential config issues
|
||||
|
||||
glval = False
|
||||
renderval = None
|
||||
glsensitive = False
|
||||
if not opengl_supported:
|
||||
opengl_warning = (
|
||||
_("Hypervisor/libvirt does not support spice GL"))
|
||||
elif not rendernode_supported:
|
||||
rendernode_warning = (
|
||||
_("Hypervisor/libvirt does not support manual rendernode"))
|
||||
if self.conn.is_qemu_system():
|
||||
opengl_warning = rendernode_warning
|
||||
|
||||
if not opengl_warning:
|
||||
glval = bool(gfx.gl)
|
||||
glsensitive = True
|
||||
if not rendernode_warning:
|
||||
renderval = gfx.rendernode or None
|
||||
|
||||
if opengl_warning:
|
||||
pass
|
||||
elif not [v for v in self.vm.xmlobj.devices.video if
|
||||
(v.model == "virtio" and v.accel3d)]:
|
||||
opengl_warning = _("Spice GL requires "
|
||||
"virtio graphics configured with accel3d.")
|
||||
elif gfx.get_first_listen_type() not in ["none", "socket"]:
|
||||
opengl_warning = _("Graphics listen type does not support "
|
||||
"spice GL.")
|
||||
|
||||
self.widget("graphics-opengl").set_active(glval)
|
||||
if glval:
|
||||
# Only sync rendernode UI with XML, if gl=on, otherwise
|
||||
# we want to preserve the suggested rendernode already
|
||||
# selected in the UI
|
||||
uiutil.set_list_selection(
|
||||
self.widget("graphics-rendernode"), renderval)
|
||||
|
||||
self.widget("graphics-opengl").set_sensitive(glsensitive)
|
||||
self.widget("graphics-opengl-warn").set_tooltip_text(
|
||||
opengl_warning or None)
|
||||
self.widget("graphics-opengl-warn").set_visible(
|
||||
bool(opengl_warning))
|
||||
|
||||
self.widget("graphics-rendernode").set_sensitive(
|
||||
rendernode_supported)
|
||||
self.widget("graphics-rendernode-warn").set_tooltip_text(
|
||||
rendernode_warning or None)
|
||||
self.widget("graphics-rendernode-warn").set_visible(
|
||||
bool(rendernode_warning))
|
||||
|
||||
if is_sdl:
|
||||
title = _("Local SDL Window")
|
||||
|
||||
self.widget("graphics-display").set_text(
|
||||
gfx.display or _("Unknown"))
|
||||
self.widget("graphics-xauth").set_text(
|
||||
gfx.xauth or _("Unknown"))
|
||||
|
||||
uiutil.set_list_selection(self.widget("graphics-type"), gtype)
|
||||
return title
|
||||
|
||||
|
||||
#############
|
||||
# Listeners #
|
||||
#############
|
||||
|
||||
def _show_rows_from_type(self):
|
||||
hide_all = ["graphics-xauth", "graphics-display", "graphics-address",
|
||||
"graphics-password-box", "graphics-keymap", "graphics-port-box",
|
||||
"graphics-tlsport-box", "graphics-opengl-box"]
|
||||
|
||||
gtype = uiutil.get_list_selection(self.widget("graphics-type"))
|
||||
listen = uiutil.get_list_selection(self.widget("graphics-listen-type"))
|
||||
|
||||
sdl_rows = ["graphics-xauth", "graphics-display"]
|
||||
vnc_rows = ["graphics-password-box", "graphics-keymap"]
|
||||
if listen == 'address':
|
||||
vnc_rows.extend(["graphics-port-box", "graphics-address"])
|
||||
spice_rows = vnc_rows[:]
|
||||
if listen == 'address':
|
||||
spice_rows.extend(["graphics-tlsport-box"])
|
||||
spice_rows.extend(["graphics-opengl-box"])
|
||||
|
||||
rows = []
|
||||
if gtype == "sdl":
|
||||
rows = sdl_rows
|
||||
elif gtype == "vnc":
|
||||
rows = vnc_rows
|
||||
elif gtype == "spice":
|
||||
rows = spice_rows
|
||||
|
||||
for row in hide_all:
|
||||
uiutil.set_grid_row_visible(self.widget(row), row in rows)
|
||||
|
||||
def _change_graphics_type(self, ignore):
|
||||
self._show_rows_from_type()
|
||||
self.emit("changed-type")
|
||||
|
||||
def _change_graphics_listen(self, ignore):
|
||||
self._show_rows_from_type()
|
||||
self.emit("changed-listen")
|
||||
|
||||
def _change_opengl(self, ignore):
|
||||
uiutil.set_grid_row_visible(
|
||||
self.widget("graphics-rendernode-box"),
|
||||
self.widget("graphics-opengl").get_active())
|
||||
self.emit("changed-opengl")
|
||||
|
||||
def _change_port_auto(self, ignore):
|
||||
self.widget("graphics-port-auto").set_inconsistent(False)
|
||||
self._change_ports()
|
||||
self.emit("changed-port")
|
||||
|
||||
def _change_tlsport_auto(self, ignore):
|
||||
self.widget("graphics-tlsport-auto").set_inconsistent(False)
|
||||
self._change_ports()
|
||||
self.emit("changed-tlsport")
|
||||
|
||||
def _change_ports(self):
|
||||
is_auto = (self.widget("graphics-port-auto").get_active() or
|
||||
self.widget("graphics-port-auto").get_inconsistent())
|
||||
is_tlsauto = (self.widget("graphics-tlsport-auto").get_active() or
|
||||
self.widget("graphics-tlsport-auto").get_inconsistent())
|
||||
|
||||
self.widget("graphics-port").set_visible(not is_auto)
|
||||
self.widget("graphics-tlsport").set_visible(not is_tlsauto)
|
||||
|
||||
def _change_password_chk(self, ignore=None):
|
||||
if self.widget("graphics-password-chk").get_active():
|
||||
self.widget("graphics-password").set_sensitive(True)
|
||||
else:
|
||||
self.widget("graphics-password").set_text("")
|
||||
self.widget("graphics-password").set_sensitive(False)
|
||||
self.emit("changed-password")
|
||||
|
||||
def _show_password_chk(self, ignore=None):
|
||||
if self.widget("graphics-visiblity-chk").get_active():
|
||||
self.widget("graphics-password").set_visibility(True)
|
||||
else:
|
||||
self.widget("graphics-password").set_visibility(False)
|
||||
@@ -0,0 +1,193 @@
|
||||
# Copyright (C) 2014 Red Hat, Inc.
|
||||
#
|
||||
# This work is licensed under the GNU GPLv2 or later.
|
||||
# See the COPYING file in the top-level directory.
|
||||
|
||||
from gi.repository import Gtk
|
||||
|
||||
from .. import uiutil
|
||||
from ..baseclass import vmmGObject, vmmGObjectUI
|
||||
|
||||
|
||||
class vmmMediaCombo(vmmGObjectUI):
|
||||
__gsignals__ = {
|
||||
"changed": (vmmGObject.RUN_FIRST, None, [object]),
|
||||
"activate": (vmmGObject.RUN_FIRST, None, [object]),
|
||||
}
|
||||
|
||||
MEDIA_TYPE_FLOPPY = "floppy"
|
||||
MEDIA_TYPE_CDROM = "cdrom"
|
||||
|
||||
MEDIA_FIELDS_NUM = 4
|
||||
(MEDIA_FIELD_PATH,
|
||||
MEDIA_FIELD_LABEL,
|
||||
MEDIA_FIELD_HAS_MEDIA,
|
||||
MEDIA_FIELD_KEY) = range(MEDIA_FIELDS_NUM)
|
||||
|
||||
def __init__(self, conn, builder, topwin):
|
||||
vmmGObjectUI.__init__(self, None, None, builder=builder, topwin=topwin)
|
||||
self.conn = conn
|
||||
|
||||
self.top_box = None
|
||||
self._combo = None
|
||||
self._populated = False
|
||||
self._init_ui()
|
||||
|
||||
self._iso_rows = []
|
||||
self._cdrom_rows = []
|
||||
self._floppy_rows = []
|
||||
self._rows_inited = False
|
||||
|
||||
self.add_gsettings_handle(
|
||||
self.config.on_iso_paths_changed(self._iso_paths_changed_cb))
|
||||
|
||||
|
||||
def _cleanup(self):
|
||||
self.conn = None
|
||||
self.top_box.destroy()
|
||||
self.top_box = None
|
||||
|
||||
|
||||
##########################
|
||||
# Initialization methods #
|
||||
##########################
|
||||
|
||||
def _init_ui(self):
|
||||
self.top_box = Gtk.Box()
|
||||
self.top_box.set_spacing(6)
|
||||
self.top_box.set_orientation(Gtk.Orientation.HORIZONTAL)
|
||||
self._combo = Gtk.ComboBox(has_entry=True)
|
||||
self._combo.set_entry_text_column(self.MEDIA_FIELD_LABEL)
|
||||
self._combo.get_accessible().set_name("media-combo")
|
||||
def separator_cb(_model, _iter):
|
||||
return _model[_iter][self.MEDIA_FIELD_PATH] is None
|
||||
self._combo.set_row_separator_func(separator_cb)
|
||||
|
||||
self._entry = self._combo.get_child()
|
||||
self._entry.set_placeholder_text(_("No media selected"))
|
||||
self._entry.set_hexpand(True)
|
||||
self._entry.get_accessible().set_name("media-entry")
|
||||
self._entry.connect("changed", self._on_entry_changed_cb)
|
||||
self._entry.connect("activate", self._on_entry_activated_cb)
|
||||
self._entry.connect("icon-press", self._on_entry_icon_press_cb)
|
||||
|
||||
self._browse = Gtk.Button()
|
||||
|
||||
self.top_box.add(self._combo)
|
||||
self.top_box.show_all()
|
||||
|
||||
# [path, label, has_media?, device key]
|
||||
store = Gtk.ListStore(str, str, bool, str)
|
||||
self._combo.set_model(store)
|
||||
|
||||
def _make_row(self, path, label, has_media, key):
|
||||
row = [None] * self.MEDIA_FIELDS_NUM
|
||||
row[self.MEDIA_FIELD_PATH] = path
|
||||
row[self.MEDIA_FIELD_LABEL] = label
|
||||
row[self.MEDIA_FIELD_HAS_MEDIA] = has_media
|
||||
row[self.MEDIA_FIELD_KEY] = key
|
||||
return row
|
||||
|
||||
def _make_nodedev_rows(self, media_type):
|
||||
rows = []
|
||||
for nodedev in self.conn.filter_nodedevs("storage"):
|
||||
if not (nodedev.xmlobj.device_type == "storage" and
|
||||
nodedev.xmlobj.drive_type in ["cdrom", "floppy"]):
|
||||
continue
|
||||
if nodedev.xmlobj.drive_type != media_type:
|
||||
continue
|
||||
|
||||
media_label = nodedev.xmlobj.media_label
|
||||
if not nodedev.xmlobj.media_available:
|
||||
media_label = _("No media detected")
|
||||
elif not nodedev.xmlobj.media_label:
|
||||
media_label = _("Media Unknown")
|
||||
label = "%s (%s)" % (media_label, nodedev.xmlobj.block)
|
||||
|
||||
row = self._make_row(nodedev.xmlobj.block, label,
|
||||
nodedev.xmlobj.media_available,
|
||||
nodedev.xmlobj.name)
|
||||
rows.append(row)
|
||||
return rows
|
||||
|
||||
def _make_iso_rows(self):
|
||||
rows = []
|
||||
for path in self.config.get_iso_paths():
|
||||
row = self._make_row(path, path, True, path)
|
||||
rows.append(row)
|
||||
return rows
|
||||
|
||||
def _init_rows(self):
|
||||
self._cdrom_rows = self._make_nodedev_rows("cdrom")
|
||||
self._floppy_rows = self._make_nodedev_rows("floppy")
|
||||
self._iso_rows = self._make_iso_rows()
|
||||
self._rows_inited = True
|
||||
|
||||
|
||||
################
|
||||
# UI callbacks #
|
||||
################
|
||||
|
||||
def _on_entry_changed_cb(self, src):
|
||||
self.emit("changed", self._entry)
|
||||
|
||||
def _on_entry_activated_cb(self, src):
|
||||
self.emit("activate", self._entry)
|
||||
|
||||
def _on_entry_icon_press_cb(self, src, icon_pos, event):
|
||||
self._entry.set_text("")
|
||||
|
||||
def _iso_paths_changed_cb(self):
|
||||
self._iso_rows = self._make_iso_rows()
|
||||
|
||||
|
||||
##############
|
||||
# Public API #
|
||||
##############
|
||||
|
||||
def set_conn(self, conn):
|
||||
if conn == self.conn:
|
||||
return
|
||||
self.conn = conn
|
||||
self._init_rows()
|
||||
|
||||
def reset_state(self, is_floppy=False):
|
||||
if not self._rows_inited:
|
||||
self._init_rows()
|
||||
|
||||
model = self._combo.get_model()
|
||||
model.clear()
|
||||
|
||||
for row in self._iso_rows:
|
||||
model.append(row)
|
||||
|
||||
nodedev_rows = self._cdrom_rows
|
||||
if is_floppy:
|
||||
nodedev_rows = self._floppy_rows
|
||||
|
||||
if len(model) and nodedev_rows:
|
||||
model.append(self._make_row(None, None, False, None))
|
||||
for row in nodedev_rows:
|
||||
model.append(row)
|
||||
|
||||
self._combo.set_active(-1)
|
||||
|
||||
def get_path(self, store_media=True):
|
||||
ret = uiutil.get_list_selection(
|
||||
self._combo, column=self.MEDIA_FIELD_PATH)
|
||||
if store_media and not ret.startswith("/dev"):
|
||||
self.config.add_iso_path(ret)
|
||||
return ret
|
||||
|
||||
def set_path(self, path):
|
||||
uiutil.set_list_selection(
|
||||
self._combo, path, column=self.MEDIA_FIELD_PATH)
|
||||
self._entry.set_position(-1)
|
||||
|
||||
def set_mnemonic_label(self, label):
|
||||
label.set_mnemonic_widget(self._entry)
|
||||
|
||||
def show_clear_icon(self):
|
||||
pos = Gtk.EntryIconPosition.SECONDARY
|
||||
self._entry.set_icon_from_icon_name(pos, "edit-clear-symbolic")
|
||||
self._entry.set_icon_activatable(pos, True)
|
||||
@@ -0,0 +1,545 @@
|
||||
# Copyright (C) 2014 Red Hat, Inc.
|
||||
#
|
||||
# This work is licensed under the GNU GPLv2 or later.
|
||||
# See the COPYING file in the top-level directory.
|
||||
|
||||
import collections
|
||||
|
||||
from gi.repository import Gtk
|
||||
|
||||
import virtinst
|
||||
from virtinst import log
|
||||
|
||||
from .. import uiutil
|
||||
from ..baseclass import vmmGObjectUI
|
||||
|
||||
|
||||
NetDev = collections.namedtuple('Netdev', ['name', 'is_bridge', 'slave_names'])
|
||||
|
||||
|
||||
class vmmNetworkList(vmmGObjectUI):
|
||||
__gsignals__ = {
|
||||
"changed": (vmmGObjectUI.RUN_FIRST, None, []),
|
||||
"changed-vport": (vmmGObjectUI.RUN_FIRST, None, [])
|
||||
}
|
||||
|
||||
def __init__(self, conn, builder, topwin):
|
||||
vmmGObjectUI.__init__(self, "netlist.ui",
|
||||
None, builder=builder, topwin=topwin)
|
||||
self.conn = conn
|
||||
|
||||
self.builder.connect_signals({
|
||||
"on_net_source_changed": self._on_net_source_changed,
|
||||
"on_net_source_mode_changed": self._emit_changed,
|
||||
"on_net_portgroup_changed": self._emit_changed,
|
||||
"on_net_bridge_name_changed": self._emit_changed,
|
||||
|
||||
"on_vport_type_changed": self._emit_vport_changed,
|
||||
"on_vport_managerid_changed": self._emit_vport_changed,
|
||||
"on_vport_typeid_changed": self._emit_vport_changed,
|
||||
"on_vport_typeidversion_changed": self._emit_vport_changed,
|
||||
"on_vport_instanceid_changed": self._emit_vport_changed,
|
||||
})
|
||||
|
||||
self._init_ui()
|
||||
self.top_label = self.widget("net-source-label")
|
||||
self.top_box = self.widget("net-source-box")
|
||||
self.top_vport = self.widget("vport-expander")
|
||||
|
||||
def _cleanup(self):
|
||||
self.conn.disconnect_by_obj(self)
|
||||
self.conn = None
|
||||
|
||||
self.top_label.destroy()
|
||||
self.top_box.destroy()
|
||||
self.top_vport.destroy()
|
||||
|
||||
|
||||
##########################
|
||||
# Initialization methods #
|
||||
##########################
|
||||
|
||||
def _init_ui(self):
|
||||
# [ network type, source name, label, sensitive?, net is active,
|
||||
# manual bridge, net instance]
|
||||
model = Gtk.ListStore(str, str, str, bool, bool, bool, object)
|
||||
combo = self.widget("net-source")
|
||||
combo.set_model(model)
|
||||
|
||||
text = Gtk.CellRendererText()
|
||||
combo.pack_start(text, True)
|
||||
combo.add_attribute(text, 'text', 2)
|
||||
combo.add_attribute(text, 'sensitive', 3)
|
||||
|
||||
combo = self.widget("net-source-mode")
|
||||
# [xml value, label]
|
||||
model = Gtk.ListStore(str, str)
|
||||
combo.set_model(model)
|
||||
uiutil.init_combo_text_column(combo, 1)
|
||||
|
||||
model.append(["bridge", _("Bridge")])
|
||||
model.append(["vepa", "VEPA"])
|
||||
model.append(["private", _("Private")])
|
||||
model.append(["passthrough", _("Passthrough")])
|
||||
combo.set_active(0)
|
||||
|
||||
combo = self.widget("net-portgroup")
|
||||
# [xml value, label]
|
||||
model = Gtk.ListStore(str, str)
|
||||
combo.set_model(model)
|
||||
uiutil.init_combo_text_column(combo, 1)
|
||||
|
||||
self.conn.connect("net-added", self._repopulate_network_list)
|
||||
self.conn.connect("net-removed", self._repopulate_network_list)
|
||||
self.conn.connect("interface-added", self._repopulate_network_list)
|
||||
self.conn.connect("interface-removed", self._repopulate_network_list)
|
||||
|
||||
def _pretty_network_desc(self, nettype, source=None, netobj=None):
|
||||
if nettype == virtinst.DeviceInterface.TYPE_USER:
|
||||
return _("Usermode networking")
|
||||
|
||||
extra = None
|
||||
if nettype == virtinst.DeviceInterface.TYPE_BRIDGE:
|
||||
ret = _("Bridge")
|
||||
elif nettype == virtinst.DeviceInterface.TYPE_VIRTUAL:
|
||||
ret = _("Virtual network")
|
||||
if netobj:
|
||||
extra = ": %s" % netobj.pretty_forward_mode()
|
||||
else:
|
||||
ret = nettype.capitalize()
|
||||
|
||||
if source:
|
||||
ret += " '%s'" % source
|
||||
if extra:
|
||||
ret += " %s" % extra
|
||||
|
||||
return ret
|
||||
|
||||
def _build_source_row(self, nettype, source_name,
|
||||
label, is_sensitive, is_running, manual_bridge=False, key=None):
|
||||
return [nettype, source_name, label,
|
||||
is_sensitive, is_running, manual_bridge,
|
||||
key]
|
||||
|
||||
def _find_virtual_networks(self):
|
||||
rows = []
|
||||
vnet_bridges = []
|
||||
default_label = None
|
||||
|
||||
for net in self.conn.list_nets():
|
||||
nettype = virtinst.DeviceInterface.TYPE_VIRTUAL
|
||||
|
||||
label = self._pretty_network_desc(nettype, net.get_name(), net)
|
||||
if not net.is_active():
|
||||
label += " (%s)" % _("Inactive")
|
||||
|
||||
if net.get_xmlobj().virtualport_type == "openvswitch":
|
||||
label += " (OpenVSwitch)"
|
||||
|
||||
if net.get_name() == "default":
|
||||
default_label = label
|
||||
|
||||
rows.append(self._build_source_row(
|
||||
nettype, net.get_name(), label, True,
|
||||
net.is_active(), key=net.get_connkey()))
|
||||
|
||||
# Build a list of vnet bridges, so we know not to list them
|
||||
# in the physical interface list
|
||||
vnet_bridge = net.get_bridge_device()
|
||||
if vnet_bridge:
|
||||
vnet_bridges.append(vnet_bridge)
|
||||
|
||||
if not rows:
|
||||
label = _("No virtual networks available")
|
||||
rows.append(self._build_source_row(
|
||||
None, None, label, False, False))
|
||||
|
||||
return rows, vnet_bridges, default_label
|
||||
|
||||
def _find_physical_devices(self, vnet_bridges):
|
||||
rows = []
|
||||
can_default = False
|
||||
default_label = None
|
||||
skip_ifaces = ["lo"]
|
||||
|
||||
vnet_taps = []
|
||||
for vm in self.conn.list_vms():
|
||||
for nic in vm.get_interface_devices_norefresh():
|
||||
if nic.target_dev and nic.target_dev not in vnet_taps:
|
||||
vnet_taps.append(nic.target_dev)
|
||||
|
||||
netdevs = {}
|
||||
for iface in self.conn.list_interfaces():
|
||||
name = iface.get_name()
|
||||
netdevs[name] = NetDev(name, iface.is_bridge(),
|
||||
iface.get_interface_names())
|
||||
for nodedev in self.conn.filter_nodedevs("net"):
|
||||
if nodedev.xmlobj.interface not in netdevs:
|
||||
netdev = NetDev(nodedev.xmlobj.interface, False, [])
|
||||
netdevs[nodedev.xmlobj.interface] = netdev
|
||||
|
||||
# For every bridge used by a virtual network, and any slaves of
|
||||
# those devices, don't list them.
|
||||
for vnet_bridge in vnet_bridges:
|
||||
slave_names = netdevs.pop(vnet_bridge,
|
||||
NetDev(None, None, [])).slave_names
|
||||
for slave in slave_names:
|
||||
netdevs.pop(slave, None)
|
||||
|
||||
for name, is_bridge, slave_names in list(netdevs.values()):
|
||||
if ((name in vnet_taps) or
|
||||
(name in [v + "-nic" for v in vnet_bridges]) or
|
||||
(name in skip_ifaces)):
|
||||
# Don't list this, as it is basically duplicating
|
||||
# virtual net info
|
||||
continue
|
||||
|
||||
sensitive = True
|
||||
source_name = name
|
||||
|
||||
label = _("Host device %s") % (name)
|
||||
if is_bridge:
|
||||
nettype = virtinst.DeviceInterface.TYPE_BRIDGE
|
||||
if slave_names:
|
||||
extra = (_("Host device %s") % slave_names[0])
|
||||
can_default = True
|
||||
else:
|
||||
extra = _("Empty bridge")
|
||||
label = _("Bridge %s: %s") % (name, extra)
|
||||
|
||||
elif self.conn.is_qemu() or self.conn.is_test():
|
||||
nettype = virtinst.DeviceInterface.TYPE_DIRECT
|
||||
label += (": %s" % _("macvtap"))
|
||||
|
||||
else:
|
||||
nettype = None
|
||||
sensitive = False
|
||||
source_name = None
|
||||
label += (": %s" % _("Not bridged"))
|
||||
|
||||
if can_default and not default_label:
|
||||
default_label = label
|
||||
|
||||
rows.append(self._build_source_row(
|
||||
nettype, source_name, label, sensitive, True,
|
||||
key=name))
|
||||
|
||||
return rows, default_label
|
||||
|
||||
def _populate_network_model(self, model):
|
||||
model.clear()
|
||||
|
||||
def _add_manual_bridge_row():
|
||||
manual_row = self._build_source_row(
|
||||
None, None, _("Specify shared device name"),
|
||||
True, False, manual_bridge=True)
|
||||
model.append(manual_row)
|
||||
|
||||
if self.conn.is_qemu_session():
|
||||
nettype = virtinst.DeviceInterface.TYPE_USER
|
||||
r = self._build_source_row(
|
||||
nettype, None, self._pretty_network_desc(nettype), True, True)
|
||||
model.append(r)
|
||||
|
||||
_add_manual_bridge_row()
|
||||
return
|
||||
|
||||
(vnets, vnet_bridges, default_net) = self._find_virtual_networks()
|
||||
(iface_rows, default_bridge) = self._find_physical_devices(
|
||||
vnet_bridges)
|
||||
|
||||
# Sorting is:
|
||||
# 1) Bridges
|
||||
# 2) Virtual networks
|
||||
# 3) direct/macvtap
|
||||
# 4) Disabled list entries
|
||||
# Each category sorted alphabetically
|
||||
bridges = [row for row in iface_rows if row[0] == "bridge"]
|
||||
direct = [row for row in iface_rows if row[0] == "direct"]
|
||||
disabled = [row for row in iface_rows if row[0] is None]
|
||||
|
||||
for rows in [bridges, vnets, direct, disabled]:
|
||||
rows.sort(key=lambda r: r[2])
|
||||
for row in rows:
|
||||
model.append(row)
|
||||
|
||||
# If there is a bridge device, default to that
|
||||
# If not, use 'default' network
|
||||
# If not present, use first list entry
|
||||
# If list empty, use no network devices
|
||||
label = default_bridge or default_net
|
||||
|
||||
default = 0
|
||||
if not len(model):
|
||||
row = self._build_source_row(
|
||||
None, None, _("No networking"), True, False)
|
||||
model.insert(0, row)
|
||||
default = 0
|
||||
elif label:
|
||||
default = [idx for idx, model_label in enumerate(model) if
|
||||
model_label[2] == label][0]
|
||||
|
||||
_add_manual_bridge_row()
|
||||
return default
|
||||
|
||||
def _check_network_is_running(self, net):
|
||||
# Make sure VirtualNetwork is running
|
||||
if not net.type == virtinst.DeviceInterface.TYPE_VIRTUAL:
|
||||
return
|
||||
devname = net.source
|
||||
|
||||
netobj = None
|
||||
if net.type == virtinst.DeviceInterface.TYPE_VIRTUAL:
|
||||
for n in self.conn.list_nets():
|
||||
if n.get_name() == devname:
|
||||
netobj = n
|
||||
break
|
||||
|
||||
if not netobj or netobj.is_active():
|
||||
return
|
||||
|
||||
res = self.err.yes_no(_("Virtual Network is not active."),
|
||||
_("Virtual Network '%s' is not active. "
|
||||
"Would you like to start the network "
|
||||
"now?") % devname)
|
||||
if not res:
|
||||
return
|
||||
|
||||
# Try to start the network
|
||||
try:
|
||||
netobj.start()
|
||||
log.debug("Started network '%s'", devname)
|
||||
except Exception as e:
|
||||
return self.err.show_err(_("Could not start virtual network "
|
||||
"'%s': %s") % (devname, str(e)))
|
||||
|
||||
|
||||
###############
|
||||
# Public APIs #
|
||||
###############
|
||||
|
||||
def get_network_row(self):
|
||||
return uiutil.get_list_selected_row(self.widget("net-source"))
|
||||
|
||||
def get_network_selection(self):
|
||||
bridge_entry = self.widget("net-bridge-name")
|
||||
row = self.get_network_row()
|
||||
if not row:
|
||||
return None, None, None, None
|
||||
|
||||
net_type = row[0]
|
||||
net_src = row[1]
|
||||
net_check_bridge = row[5]
|
||||
|
||||
if net_check_bridge and bridge_entry:
|
||||
net_type = virtinst.DeviceInterface.TYPE_BRIDGE
|
||||
net_src = bridge_entry.get_text() or None
|
||||
|
||||
mode = None
|
||||
if self.widget("net-source-mode").is_visible():
|
||||
mode = uiutil.get_list_selection(self.widget("net-source-mode"))
|
||||
|
||||
portgroup = None
|
||||
if self.widget("net-portgroup").is_visible():
|
||||
portgroup = uiutil.get_list_selection(self.widget("net-portgroup"))
|
||||
|
||||
return net_type, net_src, mode, portgroup or None
|
||||
|
||||
def get_vport(self):
|
||||
vport_type = self.widget("vport-type").get_text()
|
||||
vport_managerid = self.widget("vport-managerid").get_text()
|
||||
vport_typeid = self.widget("vport-typeid").get_text()
|
||||
vport_idver = self.widget("vport-typeidversion").get_text()
|
||||
vport_instid = self.widget("vport-instanceid").get_text()
|
||||
|
||||
return (vport_type, vport_managerid, vport_typeid,
|
||||
vport_idver, vport_instid)
|
||||
|
||||
def build_device(self, macaddr, model=None):
|
||||
nettype, devname, mode, portgroup = self.get_network_selection()
|
||||
|
||||
net = virtinst.DeviceInterface(self.conn.get_backend())
|
||||
net.type = nettype
|
||||
net.source = devname
|
||||
net.macaddr = macaddr
|
||||
net.model = model
|
||||
net.source_mode = mode
|
||||
net.portgroup = portgroup
|
||||
if net.model == "spapr-vlan":
|
||||
net.address.set_addrstr("spapr-vio")
|
||||
|
||||
if net.type == "direct":
|
||||
(vport_type, vport_managerid, vport_typeid,
|
||||
vport_idver, vport_instid) = self.get_vport()
|
||||
|
||||
net.virtualport.type = vport_type or None
|
||||
net.virtualport.managerid = vport_managerid or None
|
||||
net.virtualport.typeid = vport_typeid or None
|
||||
net.virtualport.typeidversion = vport_idver or None
|
||||
net.virtualport.instanceid = vport_instid or None
|
||||
|
||||
return net
|
||||
|
||||
def validate_device(self, net):
|
||||
self._check_network_is_running(net)
|
||||
net.validate()
|
||||
|
||||
def reset_state(self):
|
||||
self._repopulate_network_list()
|
||||
|
||||
net_err = None
|
||||
if (not self.conn.is_nodedev_capable() or
|
||||
not self.conn.is_interface_capable()):
|
||||
net_err = _("Libvirt version does not support "
|
||||
"physical interface listing.")
|
||||
|
||||
net_warn = self.widget("net-source-warn")
|
||||
net_warn.set_visible(bool(net_err))
|
||||
net_warn.set_tooltip_text(net_err or "")
|
||||
|
||||
self.widget("net-bridge-name").set_text("")
|
||||
self.widget("net-source-mode").set_active(0)
|
||||
self.widget("net-portgroup").get_child().set_text("")
|
||||
|
||||
self.widget("vport-type").set_text("")
|
||||
self.widget("vport-managerid").set_text("")
|
||||
self.widget("vport-typeid").set_text("")
|
||||
self.widget("vport-typeidversion").set_text("")
|
||||
self.widget("vport-instanceid").set_text("")
|
||||
|
||||
def set_dev(self, net):
|
||||
self.reset_state()
|
||||
|
||||
nettype = net.type
|
||||
source = net.source
|
||||
if net.network:
|
||||
# If using type=network with a forward mode=bridge network,
|
||||
# on domain startup the runtime XML will be changed to
|
||||
# type=bridge and both source/@bridge and source/@network will
|
||||
# be filled in. For our purposes, treat this as a type=network
|
||||
source = net.network
|
||||
nettype = "network"
|
||||
|
||||
source_mode = net.source_mode
|
||||
is_direct = (net.type == "direct")
|
||||
|
||||
uiutil.set_list_selection(self.widget("net-source-mode"), source_mode)
|
||||
|
||||
# Virtualport config
|
||||
self.widget("vport-expander").set_visible(is_direct)
|
||||
|
||||
vport = net.virtualport
|
||||
self.widget("vport-type").set_text(vport.type or "")
|
||||
self.widget("vport-managerid").set_text(str(vport.managerid or ""))
|
||||
self.widget("vport-typeid").set_text(str(vport.typeid or ""))
|
||||
self.widget("vport-typeidversion").set_text(
|
||||
str(vport.typeidversion or ""))
|
||||
self.widget("vport-instanceid").set_text(vport.instanceid or "")
|
||||
|
||||
# Find the matching row in the net list
|
||||
combo = self.widget("net-source")
|
||||
rowiter = None
|
||||
for row in combo.get_model():
|
||||
if row[0] == nettype and row[1] == source:
|
||||
rowiter = row.iter
|
||||
break
|
||||
if not rowiter:
|
||||
if nettype == "bridge":
|
||||
rowiter = combo.get_model()[-1].iter
|
||||
self.widget("net-bridge-name").set_text(source)
|
||||
if not rowiter:
|
||||
desc = self._pretty_network_desc(nettype, source)
|
||||
combo.get_model().insert(0,
|
||||
self._build_source_row(nettype, source, desc, True, True))
|
||||
rowiter = combo.get_model()[0].iter
|
||||
|
||||
combo.set_active_iter(rowiter)
|
||||
combo.emit("changed")
|
||||
|
||||
if net.portgroup:
|
||||
uiutil.set_list_selection(self.widget("net-portgroup"), net.portgroup)
|
||||
|
||||
|
||||
#############
|
||||
# Listeners #
|
||||
#############
|
||||
|
||||
def _emit_changed(self, *args, **kwargs):
|
||||
ignore1 = args
|
||||
ignore2 = kwargs
|
||||
self.emit("changed")
|
||||
|
||||
def _emit_vport_changed(self, *args, **kwargs):
|
||||
ignore1 = args
|
||||
ignore2 = kwargs
|
||||
self.emit("changed-vport")
|
||||
|
||||
def _repopulate_network_list(self, *args, **kwargs):
|
||||
ignore1 = args
|
||||
ignore2 = kwargs
|
||||
|
||||
netlist = self.widget("net-source")
|
||||
current_label = uiutil.get_list_selection(netlist, column=2)
|
||||
|
||||
model = netlist.get_model()
|
||||
if not model:
|
||||
return
|
||||
|
||||
try:
|
||||
if model:
|
||||
netlist.set_model(None)
|
||||
default_idx = self._populate_network_model(model)
|
||||
finally:
|
||||
netlist.set_model(model)
|
||||
|
||||
for row in netlist.get_model():
|
||||
if current_label and row[2] == current_label:
|
||||
netlist.set_active_iter(row.iter)
|
||||
return
|
||||
|
||||
if default_idx is None:
|
||||
default_idx = 0
|
||||
netlist.set_active(default_idx)
|
||||
|
||||
|
||||
def _populate_portgroups(self, portgroups):
|
||||
combo = self.widget("net-portgroup")
|
||||
model = combo.get_model()
|
||||
model.clear()
|
||||
|
||||
default = None
|
||||
for p in portgroups:
|
||||
model.append([p.name, p.name])
|
||||
if p.default:
|
||||
default = p.name
|
||||
|
||||
uiutil.set_list_selection(combo, default)
|
||||
|
||||
def _on_net_source_changed(self, src):
|
||||
ignore = src
|
||||
self._emit_changed()
|
||||
row = self.get_network_row()
|
||||
if not row:
|
||||
return
|
||||
|
||||
is_openvswitch = row[2].endswith("(OpenVSwitch)")
|
||||
is_direct = (row[0] == virtinst.DeviceInterface.TYPE_DIRECT)
|
||||
self.widget("vport-expander").set_visible(is_direct or is_openvswitch)
|
||||
uiutil.set_grid_row_visible(self.widget("net-source-mode"), is_direct)
|
||||
uiutil.set_grid_row_visible(
|
||||
self.widget("net-macvtap-warn-box"), is_direct)
|
||||
if is_direct and self.widget("net-source-mode").get_active() == -1:
|
||||
self.widget("net-source-mode").set_active(0)
|
||||
|
||||
show_bridge = row[5]
|
||||
uiutil.set_grid_row_visible(
|
||||
self.widget("net-bridge-name"), show_bridge)
|
||||
|
||||
portgroups = []
|
||||
connkey = row[6]
|
||||
if connkey and row[0] == virtinst.DeviceInterface.TYPE_VIRTUAL:
|
||||
portgroups = self.conn.get_net(connkey).get_xmlobj().portgroups
|
||||
|
||||
uiutil.set_grid_row_visible(
|
||||
self.widget("net-portgroup"), bool(portgroups))
|
||||
self._populate_portgroups(portgroups)
|
||||
@@ -0,0 +1,76 @@
|
||||
# Copyright (C) 2018 VMware, Inc.
|
||||
#
|
||||
# This work is licensed under the GNU GPLv2 or later.
|
||||
# See the COPYING file in the top-level directory.
|
||||
|
||||
from .. import uiutil
|
||||
from ..baseclass import vmmGObjectUI
|
||||
|
||||
|
||||
class vmmVsockDetails(vmmGObjectUI):
|
||||
__gsignals__ = {
|
||||
"changed-auto-cid": (vmmGObjectUI.RUN_FIRST, None, []),
|
||||
"changed-cid": (vmmGObjectUI.RUN_FIRST, None, []),
|
||||
}
|
||||
|
||||
MIN_GUEST_CID = 3
|
||||
|
||||
def __init__(self, vm, builder, topwin):
|
||||
super().__init__("vsockdetails.ui", None,
|
||||
builder=builder, topwin=topwin)
|
||||
self.vm = vm
|
||||
self.conn = vm.conn
|
||||
|
||||
self.builder.connect_signals({
|
||||
"on_vsock_auto_toggled": self._vsock_auto_toggled,
|
||||
"on_vsock_cid_changed": lambda ignore: self.emit("changed-cid"),
|
||||
})
|
||||
|
||||
self.top_box = self.widget("vsock-box")
|
||||
|
||||
def _cleanup(self):
|
||||
self.vm = None
|
||||
self.conn = None
|
||||
|
||||
|
||||
##############
|
||||
# Public API #
|
||||
##############
|
||||
|
||||
def reset_state(self):
|
||||
self.widget("vsock-auto").set_active(True)
|
||||
self.widget("vsock-cid").set_value(self.MIN_GUEST_CID)
|
||||
self.widget("vsock-cid").set_visible(False)
|
||||
|
||||
def get_values(self):
|
||||
auto_cid = self.widget("vsock-auto").get_active()
|
||||
cid = uiutil.spin_get_helper(self.widget("vsock-cid"))
|
||||
return auto_cid, cid
|
||||
|
||||
def set_dev(self, dev):
|
||||
self.reset_state()
|
||||
|
||||
is_auto = bool(dev.auto_cid)
|
||||
if dev.cid is not None:
|
||||
cid = int(dev.cid)
|
||||
else:
|
||||
cid = self.MIN_GUEST_CID
|
||||
|
||||
label = self.widget("vsock-auto").get_label().split(" (")[0]
|
||||
if is_auto and self.vm.is_active():
|
||||
label += " (%s %s)" % (_("CID"), cid)
|
||||
self.widget("vsock-auto").set_label(label)
|
||||
|
||||
self.widget("vsock-auto").set_active(is_auto)
|
||||
self.widget("vsock-cid").set_value(cid)
|
||||
self.widget("vsock-cid").set_visible(not is_auto)
|
||||
|
||||
|
||||
#############
|
||||
# Listeners #
|
||||
#############
|
||||
|
||||
def _vsock_auto_toggled(self, ignore):
|
||||
is_auto = self.widget("vsock-auto").get_active()
|
||||
self.widget("vsock-cid").set_visible(not is_auto)
|
||||
self.emit("changed-auto-cid")
|
||||
Reference in New Issue
Block a user