diff --git a/ui/details.ui b/ui/details.ui
index 659996a7c..454e58f30 100644
--- a/ui/details.ui
+++ b/ui/details.ui
@@ -1,8 +1,7 @@
-
-
+
-
-
diff --git a/ui/vmwindow.ui b/ui/vmwindow.ui
new file mode 100644
index 000000000..6c78890ac
--- /dev/null
+++ b/ui/vmwindow.ui
@@ -0,0 +1,825 @@
+
+
+
+
+
+
+ True
+ False
+ gtk-apply
+
+
+ False
+ Virtual Machine
+ 800
+ 600
+
+
+
+
+
+
+
+
+
+
+ True
+ False
+ vertical
+
+
+
+ False
+ False
+ 0
+
+
+
+
+ True
+ False
+
+
+ True
+ False
+
+
+ True
+ False
+ Show the graphical console
+ Console
+ icon_console
+ True
+
+
+
+ False
+ True
+
+
+
+
+ True
+ False
+ Show virtual hardware details
+ Details
+ gtk-info
+ control-vm-console
+
+
+
+ False
+ True
+
+
+
+
+ True
+ False
+
+
+ False
+ True
+
+
+
+
+ True
+ False
+ Power on the virtual machine
+ Run
+ True
+ gtk-media-play
+
+
+
+ False
+ True
+
+
+
+
+ True
+ False
+ Pause the virtual machine
+ Pause
+ True
+ gtk-media-pause
+
+
+
+ False
+ True
+
+
+
+
+
+ False
+ False
+
+
+
+
+ True
+ False
+
+
+ False
+ True
+
+
+
+
+ True
+ False
+ Snapshots
+ vm_clone_wizard
+ control-vm-console
+
+
+
+ False
+ True
+
+
+
+
+ True
+ False
+ Switch to fullscreen view
+ end
+ gtk-fullscreen
+
+
+
+ True
+ True
+
+
+
+
+ True
+ True
+ 0
+
+
+
+
+ False
+ both-horiz
+
+
+ True
+ False
+ Begin Installation
+ True
+ _Begin Installation
+ True
+ gtk-apply
+
+
+
+ False
+ True
+
+
+
+
+ True
+ False
+ True
+ _Cancel Installation
+ True
+ gtk-cancel
+
+
+
+ False
+ True
+
+
+
+
+ True
+ True
+ 1
+
+
+
+
+ False
+ True
+ 1
+
+
+
+
+ True
+ True
+ False
+
+
+
+ True
+ False
+
+
+
+
+
+
+
+ True
+ False
+ Details
+
+
+ False
+
+
+
+
+ True
+ True
+ False
+
+
+
+ True
+ False
+ center
+ center
+ <b>The console is currently unavailable</b>
+ True
+ center
+ True
+ True
+ 60
+ 60
+
+
+ True
+
+
+
+
+ True
+ False
+ Unavailable
+
+
+ False
+
+
+
+
+ True
+ False
+ 3
+ 3
+ 3
+ 3
+ 3
+
+
+ True
+ False
+ start
+ _Password:
+ True
+ console-auth-password
+
+
+ 1
+ 2
+ GTK_FILL
+
+
+
+
+
+ True
+ True
+ False
+
+
+
+ 1
+ 2
+ 1
+ 2
+
+
+
+
+
+ _Save this password in your keyring
+ True
+ True
+ False
+ Check to save password, uncheck to forget password.
+ start
+ True
+ True
+
+
+ 1
+ 2
+ 2
+ 3
+ GTK_FILL
+
+
+
+
+
+ True
+ False
+ start
+ _Username:
+ True
+ console-auth-username
+
+
+ GTK_FILL
+
+
+
+
+
+ True
+ True
+
+
+
+ 1
+ 2
+
+
+
+
+
+ _Login
+ True
+ True
+ False
+ image70
+ True
+
+
+
+ 2
+ 3
+ 1
+ 2
+ GTK_FILL
+
+
+
+
+
+ True
+ False
+
+
+
+
+
+ 2
+ 3
+ GTK_FILL
+ GTK_FILL
+
+
+
+
+ True
+ False
+
+
+
+
+
+ 2
+ 3
+ GTK_FILL
+ GTK_FILL
+
+
+
+
+ True
+ False
+
+
+
+
+
+ 2
+ 3
+ 2
+ 3
+ GTK_FILL
+ GTK_FILL
+
+
+
+
+ 1
+
+
+
+
+ True
+ False
+ Auth
+
+
+ 1
+ False
+
+
+
+
+ True
+ True
+
+
+ serial-pages
+
+
+
+
+ 2
+
+
+
+
+ True
+ False
+ Serial
+
+
+ 2
+ False
+
+
+
+
+ True
+ False
+
+
+ True
+ True
+
+
+ True
+ False
+ queue
+ none
+
+
+
+
+
+ console-gfx-viewport
+
+
+
+
+
+
+ -1
+
+
+
+
+ 3
+
+
+
+
+ True
+ False
+ Graphics
+
+
+ 3
+ False
+
+
+
+
+ 1
+
+
+
+
+ True
+ False
+ Console
+
+
+ 1
+ False
+
+
+
+
+ True
+ False
+ vertical
+
+
+
+
+
+ 2
+
+
+
+
+ True
+ False
+ Snapshots
+
+
+ 2
+ False
+
+
+
+
+ True
+ True
+ 2
+
+
+
+
+
+
diff --git a/virtManager/baseclass.py b/virtManager/baseclass.py
index 07bdeccc6..ccd307081 100644
--- a/virtManager/baseclass.py
+++ b/virtManager/baseclass.py
@@ -26,7 +26,7 @@ class vmmGObject(GObject.GObject):
# Singleton reference, if applicable (vmmSystray, vmmInspection, ...)
_instance = None
- # windowlist mapping, if applicable (vmmDetails, vmmHost, ...)
+ # windowlist mapping, if applicable (vmmVMWindow, vmmHost, ...)
_instances = None
# This saves a bunch of imports and typing
diff --git a/virtManager/console.py b/virtManager/console.py
index f0d741415..07fb3fe6a 100644
--- a/virtManager/console.py
+++ b/virtManager/console.py
@@ -11,10 +11,10 @@ from gi.repository import Gtk
from gi.repository import Gdk
from .baseclass import vmmGObject, vmmGObjectUI
-from .details import DETAILS_PAGE_CONSOLE
from .serialcon import vmmSerialConsole
from .sshtunnels import ConnectionInfo
from .viewers import SpiceViewer, VNCViewer, have_spice_gtk
+from .vmwindow import DETAILS_PAGE_CONSOLE
# console-pages IDs
@@ -223,7 +223,7 @@ class vmmConsolePages(vmmGObjectUI):
self._serial_consoles = []
self._init_menus()
- # Signals are added by vmmDetails. Don't use connect_signals here
+ # Signals are added by vmmVMWindow. Don't use connect_signals here
# or it changes will be overwritten
self.widget("console-gfx-scroll").connect("size-allocate",
@@ -996,9 +996,9 @@ class vmmConsolePages(vmmGObjectUI):
src.show_all()
- ##########################
- # API used by vmmDetails #
- ##########################
+ ###########################
+ # API used by vmmVMWindow #
+ ###########################
def details_viewer_is_visible(self):
return bool(self._viewer and self._viewer.console_get_visible())
diff --git a/virtManager/create.py b/virtManager/create.py
index 2424ae7fa..ec4740bc1 100644
--- a/virtManager/create.py
+++ b/virtManager/create.py
@@ -21,15 +21,15 @@ from virtinst import util
from . import uiutil
from .addstorage import vmmAddStorage
from .asyncjob import vmmAsyncJob
-from .connmanager import vmmConnectionManager
from .baseclass import vmmGObjectUI
-from .details import vmmDetails
+from .connmanager import vmmConnectionManager
from .domain import vmmDomainVirtinst
from .engine import vmmEngine
from .mediacombo import vmmMediaCombo
from .netlist import vmmNetworkList
from .oslist import vmmOSList
from .storagebrowse import vmmStorageBrowser
+from .vmwindow import vmmVMWindow
# Number of seconds to wait for media detection
DETECT_TIMEOUT = 20
@@ -2018,10 +2018,10 @@ class vmmCreate(vmmGObjectUI):
logging.debug("User closed customize window, closing wizard")
self._close_requested()
- # We specifically don't use vmmDetails.get_instance here since
- # it's not a top level Details window
+ # We specifically don't use vmmVMWindow.get_instance here since
+ # it's not a top level VM window
self._cleanup_customize_window()
- self._customize_window = vmmDetails(virtinst_guest, self.topwin)
+ self._customize_window = vmmVMWindow(virtinst_guest, self.topwin)
self._customize_window.connect(
"customize-finished", start_install_wrapper, guest)
self._customize_window.connect("closed", config_canceled)
@@ -2045,7 +2045,7 @@ class vmmCreate(vmmGObjectUI):
self._close()
# Launch details dialog for new VM
- vmmDetails.get_instance(self, foundvm).show()
+ vmmVMWindow.get_instance(self, foundvm).show()
def _start_install(self, guest):
diff --git a/virtManager/details.py b/virtManager/details.py
index f32959873..1bad112a8 100644
--- a/virtManager/details.py
+++ b/virtManager/details.py
@@ -7,7 +7,6 @@
import logging
import traceback
-from gi.repository import Gdk
from gi.repository import Gtk
import libvirt
@@ -15,19 +14,16 @@ import libvirt
import virtinst
from virtinst import util
-from . import vmmenu
from . import uiutil
from .addhardware import vmmAddHardware
from .addstorage import vmmAddStorage
from .baseclass import vmmGObjectUI
-from .engine import vmmEngine
from .fsdetails import vmmFSDetails
from .gfxdetails import vmmGraphicsDetails
from .graphwidgets import Sparkline
from .mediacombo import vmmMediaCombo
from .netlist import vmmNetworkList
from .oslist import vmmOSList
-from .snapshots import vmmSnapshotPage
from .storagebrowse import vmmStorageBrowser
from .vsockdetails import vmmVsockDetails
@@ -155,12 +151,6 @@ remove_pages = [HW_LIST_TYPE_NIC, HW_LIST_TYPE_INPUT,
BOOT_ACTIVE,
BOOT_CAN_SELECT) = range(5)
-# Main tab pages
-(DETAILS_PAGE_DETAILS,
- DETAILS_PAGE_CONSOLE,
- DETAILS_PAGE_SNAPSHOTS) = range(3)
-
-
def _calculate_disk_bus_index(disklist):
# Iterate through all disks and calculate what number they are
@@ -342,78 +332,19 @@ def _warn_cpu_thread_topo(threads, cpu_model):
return False
-def _label_for_os_type(os_type):
- typemap = {
- "dos": _("MS-DOS/FreeDOS"),
- "freebsd": _("FreeBSD"),
- "hurd": _("GNU/Hurd"),
- "linux": _("Linux"),
- "minix": _("MINIX"),
- "netbsd": _("NetBSD"),
- "openbsd": _("OpenBSD"),
- "windows": _("Microsoft Windows"),
- }
- try:
- return typemap[os_type]
- except KeyError:
- return _("unknown")
-
-
class vmmDetails(vmmGObjectUI):
- __gsignals__ = {
- "customize-finished": (vmmGObjectUI.RUN_FIRST, None, []),
- "closed": (vmmGObjectUI.RUN_FIRST, None, []),
- }
+ def __init__(self, vm, builder, topwin, is_customize_dialog):
+ vmmGObjectUI.__init__(self, "details.ui",
+ None, builder=builder, topwin=topwin)
- @classmethod
- def get_instance(cls, parentobj, vm):
- try:
- # Maintain one dialog per VM
- key = "%s+%s" % (vm.conn.get_uri(), vm.get_uuid())
- if cls._instances is None:
- cls._instances = {}
- if key not in cls._instances:
- cls._instances[key] = vmmDetails(vm)
- return cls._instances[key]
- except Exception as e:
- if not parentobj:
- raise
- parentobj.err.show_err(
- _("Error launching details: %s") % str(e))
-
- def __init__(self, vm, parent=None):
- vmmGObjectUI.__init__(self, "details.ui", "vmm-details")
self.vm = vm
-
- self.is_customize_dialog = False
- if parent:
- # Details window is being abused as a 'configure before install'
- # dialog, set things as appropriate
- self.is_customize_dialog = True
- self.topwin.set_type_hint(Gdk.WindowTypeHint.DIALOG)
- self.topwin.set_transient_for(parent)
-
- self.widget("toolbar-box").show()
- self.widget("customize-toolbar").show()
- self.widget("details-toolbar").hide()
- self.widget("details-menubar").hide()
- pages = self.widget("details-pages")
- pages.set_current_page(DETAILS_PAGE_DETAILS)
- else:
- self.conn.connect("vm-removed", self._vm_removed)
-
self.active_edits = []
+ self.top_box = self.widget("details-top-box")
self.addhw = None
self.storage_browser = None
self._mediacombo = None
-
- self.ignoreDetails = False
-
- from .console import vmmConsolePages
- self.console = vmmConsolePages(self.vm, self.builder, self.topwin)
- self.snapshots = vmmSnapshotPage(self.vm, self.builder, self.topwin)
- self.widget("snapshot-placeholder").add(self.snapshots.top_box)
+ self.is_customize_dialog = is_customize_dialog
self._mediacombo = vmmMediaCombo(self.conn, self.builder, self.topwin)
self.widget("disk-source-align").add(self._mediacombo.top_box)
@@ -466,20 +397,9 @@ class vmmDetails(vmmGObjectUI):
self.vsockdetails.connect("changed-cid",
lambda *x: self.enable_apply(x, EDIT_VSOCK_CID))
- # Set default window size
- w, h = self.vm.get_details_window_size()
- if w <= 0:
- w = 800
- if h <= 0:
- h = 600
- self.topwin.set_default_size(w, h)
- self._window_size = None
-
self.oldhwkey = None
self.addhwmenu = None
self._addhwmenuitems = None
- self._shutdownmenu = None
- self._vmmenu = None
self._os_list = None
self.init_menus()
self.init_details()
@@ -491,35 +411,8 @@ class vmmDetails(vmmGObjectUI):
self.init_graphs()
self.builder.connect_signals({
- "on_close_details_clicked": self.close,
- "on_details_menu_close_activate": self.close,
- "on_vmm_details_delete_event": self._window_delete_event,
- "on_vmm_details_configure_event": self.window_resized,
- "on_details_menu_quit_activate": self.exit_app,
"on_hw_list_changed": self.hw_changed,
- "on_control_vm_details_toggled": self.details_console_changed,
- "on_control_vm_console_toggled": self.details_console_changed,
- "on_control_snapshots_toggled": self.details_console_changed,
- "on_control_run_clicked": self.control_vm_run,
- "on_control_shutdown_clicked": self.control_vm_shutdown,
- "on_control_pause_toggled": self.control_vm_pause,
- "on_control_fullscreen_toggled": self.control_fullscreen,
-
- "on_details_customize_finish_clicked": self.customize_finish,
- "on_details_cancel_customize_clicked": self._customize_cancel_clicked,
-
- "on_details_menu_virtual_manager_activate": self.control_vm_menu,
- "on_details_menu_screenshot_activate": self.control_vm_screenshot,
- "on_details_menu_usb_redirection": self.control_vm_usb_redirection,
- "on_details_menu_view_toolbar_activate": self.toggle_toolbar,
- "on_details_menu_view_manager_activate": self.view_manager,
- "on_details_menu_view_details_toggled": self.details_console_changed,
- "on_details_menu_view_console_toggled": self.details_console_changed,
- "on_details_menu_view_snapshots_toggled": self.details_console_changed,
-
- "on_details_pages_switch_page": self.switch_page,
-
"on_overview_name_changed": lambda *x: self.enable_apply(x, EDIT_NAME),
"on_overview_title_changed": lambda *x: self.enable_apply(x, EDIT_TITLE),
"on_machine_type_changed": lambda *x: self.enable_apply(x, EDIT_MACHTYPE),
@@ -613,40 +506,10 @@ class vmmDetails(vmmGObjectUI):
"on_hw_list_button_press_event": self.popup_addhw_menu,
"on_tpm_model_combo_changed": lambda *x: self.enable_apply(x, EDIT_TPM_MODEL),
-
- # Listeners stored in vmmConsolePages
- "on_details_menu_view_fullscreen_activate": (
- self.console.details_toggle_fullscreen),
- "on_details_menu_view_size_to_vm_activate": (
- self.console.details_size_to_vm),
- "on_details_menu_view_scale_always_toggled": (
- self.console.details_scaling_ui_changed_cb),
- "on_details_menu_view_scale_fullscreen_toggled": (
- self.console.details_scaling_ui_changed_cb),
- "on_details_menu_view_scale_never_toggled": (
- self.console.details_scaling_ui_changed_cb),
- "on_details_menu_view_resizeguest_toggled": (
- self.console.details_resizeguest_ui_changed_cb),
-
- "on_console_pages_switch_page": (
- self.console.details_page_changed),
- "on_console_auth_password_activate": (
- self.console.details_auth_login),
- "on_console_auth_login_clicked": (
- self.console.details_auth_login),
})
- # Deliberately keep all this after signal connection
- self.vm.connect("state-changed", self.refresh_vm_state)
- self.vm.connect("resources-sampled", self.refresh_resources)
- self.vm.connect("inspection-changed",
- lambda *x: self.refresh_os_page())
-
self.populate_hw_list()
-
self.hw_selected()
- self.refresh_vm_state()
- self.activate_default_page()
@property
@@ -665,17 +528,6 @@ class vmmDetails(vmmGObjectUI):
self._mediacombo.cleanup()
self._mediacombo = None
- self.console.cleanup()
- self.console = None
- self.snapshots.cleanup()
- self.snapshots = None
- self._shutdownmenu.destroy()
- self._shutdownmenu = None
- self._vmmenu.destroy()
- self._vmmenu = None
-
- if self._window_size:
- self.vm.set_details_window_size(*self._window_size)
self.conn.disconnect_by_obj(self)
self.vm = None
@@ -691,90 +543,12 @@ class vmmDetails(vmmGObjectUI):
self.vsockdetails.cleanup()
self.vsockdetails = None
- def show(self):
- logging.debug("Showing VM details: %s", self.vm)
- vis = self.is_visible()
- self.topwin.present()
- if vis:
- return
-
- vmmEngine.get_instance().increment_window_counter()
- self.refresh_vm_state()
-
- def customize_finish(self, src):
- ignore = src
- if self.has_unapplied_changes(self.get_hw_row()):
- return
- self.emit("customize-finished")
-
- def _vm_removed(self, _conn, connkey):
- if self.vm.get_connkey() == connkey:
- self.cleanup()
-
- def _customize_cancel(self):
- logging.debug("Asking to cancel customization")
-
- result = self.err.yes_no(
- _("This will abort the installation. Are you sure?"))
- if not result:
- logging.debug("Customize cancel aborted")
- return
-
- logging.debug("Canceling customization")
- return self._close()
-
- def _customize_cancel_clicked(self, src):
- ignore = src
- return self._customize_cancel()
-
- def _window_delete_event(self, ignore1=None, ignore2=None):
- return self.close()
-
- def close(self, ignore1=None, ignore2=None):
- if self.is_visible():
- logging.debug("Closing VM details: %s", self.vm)
- return self._close()
-
- def _close(self):
- fs = self.widget("details-menu-view-fullscreen")
- if fs.get_active():
- fs.set_active(False)
-
- if not self.is_visible():
- return
-
- self.topwin.hide()
- if self.console.details_viewer_is_visible():
- try:
- self.console.details_close_viewer()
- except Exception:
- logging.error("Failure when disconnecting from desktop server")
-
- self.emit("closed")
- vmmEngine.get_instance().decrement_window_counter()
- return 1
-
##########################
# Initialization helpers #
##########################
def init_menus(self):
- # Virtual Machine menu
- self._shutdownmenu = vmmenu.VMShutdownMenu(self, lambda: self.vm)
- self.widget("control-shutdown").set_menu(self._shutdownmenu)
- self.widget("control-shutdown").set_icon_name("system-shutdown")
-
- topmenu = self.widget("details-vm-menu")
- submenu = topmenu.get_submenu()
- self._vmmenu = vmmenu.VMActionMenu(
- self, lambda: self.vm, show_open=False)
- for child in submenu.get_children():
- submenu.remove(child)
- self._vmmenu.add(child)
- topmenu.set_submenu(self._vmmenu)
- topmenu.show_all()
-
# Add HW popup menu
self.addhwmenu = Gtk.Menu()
@@ -799,9 +573,6 @@ class vmmDetails(vmmGObjectUI):
self.addhwmenu.add(i)
self.widget("hw-panel").set_show_tabs(False)
- self.widget("details-pages").set_show_tabs(False)
- self.widget("details-menu-view-toolbar").set_active(
- self.config.get_details_show_toolbar())
def init_graphs(self):
@@ -1114,11 +885,6 @@ class vmmDetails(vmmGObjectUI):
# Window state listeners #
##########################
- def window_resized(self, ignore, ignore2):
- if not self.is_visible():
- return
- self._window_size = self.topwin.get_size()
-
def popup_addhw_menu(self, widget, event):
ignore = widget
if event.button != 3:
@@ -1142,24 +908,6 @@ class vmmDetails(vmmGObjectUI):
self.addhwmenu.popup_at_pointer(event)
- def control_fullscreen(self, src):
- menu = self.widget("details-menu-view-fullscreen")
- if src.get_active() != menu.get_active():
- menu.set_active(src.get_active())
-
- def toggle_toolbar(self, src):
- if self.is_customize_dialog:
- return
-
- active = src.get_active()
- self.config.set_details_show_toolbar(active)
-
- if (active and not
- self.widget("details-menu-view-fullscreen").get_active()):
- self.widget("toolbar-box").show()
- else:
- self.widget("toolbar-box").hide()
-
def get_boot_selection(self):
return uiutil.get_list_selected_row(self.widget("boot-list"))
@@ -1281,113 +1029,10 @@ class vmmDetails(vmmGObjectUI):
self.widget("hw-panel").set_current_page(pagetype)
- def details_console_changed(self, src):
- if self.ignoreDetails:
- return
-
- if not src.get_active():
- return
-
- is_details = (src == self.widget("control-vm-details") or
- src == self.widget("details-menu-view-details"))
- is_snapshot = (src == self.widget("control-snapshots") or
- src == self.widget("details-menu-view-snapshots"))
-
- pages = self.widget("details-pages")
- if pages.get_current_page() == DETAILS_PAGE_DETAILS:
- if self.has_unapplied_changes(self.get_hw_row()):
- self.sync_details_console_view(True)
- return
- self.disable_apply()
-
- if is_details:
- pages.set_current_page(DETAILS_PAGE_DETAILS)
- elif is_snapshot:
- self.snapshots.show_page()
- pages.set_current_page(DETAILS_PAGE_SNAPSHOTS)
- else:
- pages.set_current_page(DETAILS_PAGE_CONSOLE)
-
- def sync_details_console_view(self, newpage):
- details = self.widget("control-vm-details")
- details_menu = self.widget("details-menu-view-details")
- console = self.widget("control-vm-console")
- console_menu = self.widget("details-menu-view-console")
- snapshot = self.widget("control-snapshots")
- snapshot_menu = self.widget("details-menu-view-snapshots")
-
- is_details = newpage == DETAILS_PAGE_DETAILS
- is_snapshot = newpage == DETAILS_PAGE_SNAPSHOTS
- is_console = not is_details and not is_snapshot
-
- try:
- self.ignoreDetails = True
-
- details.set_active(is_details)
- details_menu.set_active(is_details)
- snapshot.set_active(is_snapshot)
- snapshot_menu.set_active(is_snapshot)
- console.set_active(is_console)
- console_menu.set_active(is_console)
- finally:
- self.ignoreDetails = False
-
- def switch_page(self, notebook=None, ignore2=None, newpage=None):
- for i in range(notebook.get_n_pages()):
- w = notebook.get_nth_page(i)
- w.set_visible(i == newpage)
-
- self.page_refresh(newpage)
-
- self.sync_details_console_view(newpage)
- self.console.details_refresh_can_fullscreen()
-
- def change_run_text(self, can_restore):
- if can_restore:
- text = _("_Restore")
- else:
- text = _("_Run")
- strip_text = text.replace("_", "")
-
- self.widget("details-vm-menu").get_submenu().change_run_text(text)
- self.widget("control-run").set_label(strip_text)
-
- def refresh_vm_state(self, ignore=None):
- vm = self.vm
- status = self.vm.status()
-
- self.widget("details-menu-view-toolbar").set_active(
- self.config.get_details_show_toolbar())
- self.toggle_toolbar(self.widget("details-menu-view-toolbar"))
-
- active = vm.is_active()
- run = vm.is_runable()
- stop = vm.is_stoppable()
- paused = vm.is_paused()
-
- if vm.managedsave_supported:
- self.change_run_text(vm.has_managed_save())
-
- self.widget("control-run").set_sensitive(run)
- self.widget("control-shutdown").set_sensitive(stop)
- self.widget("control-shutdown").get_menu().update_widget_states(vm)
- self.widget("control-pause").set_sensitive(stop)
-
- if paused:
- pauseTooltip = _("Resume the virtual machine")
- else:
- pauseTooltip = _("Pause the virtual machine")
- self.widget("control-pause").set_tooltip_text(pauseTooltip)
-
- self.widget("details-vm-menu").get_submenu().update_widget_states(vm)
- self.set_pause_state(paused)
-
+ def vmwindow_refresh_vm_state(self):
+ active = self.vm.is_active()
self.widget("overview-name").set_editable(not active)
- self.console.details_update_widget_states()
- if not run:
- self.activate_default_console_page()
-
reason = self.vm.run_status_reason()
if reason:
status = "%s (%s)" % (self.vm.run_status(), reason)
@@ -1395,68 +1040,14 @@ class vmmDetails(vmmGObjectUI):
status = self.vm.run_status()
self.widget("overview-status-text").set_text(status)
self.widget("overview-status-icon").set_from_icon_name(
- self.vm.run_status_icon_name(), Gtk.IconSize.BUTTON)
-
- details = self.widget("details-pages")
- self.page_refresh(details.get_current_page())
-
- errmsg = self.vm.snapshots_supported()
- cansnap = not bool(errmsg)
- self.widget("control-snapshots").set_sensitive(cansnap)
- self.widget("details-menu-view-snapshots").set_sensitive(cansnap)
- tooltip = _("Manage VM snapshots")
- if not cansnap:
- tooltip += "\n" + errmsg
- self.widget("control-snapshots").set_tooltip_text(tooltip)
+ self.vm.run_status_icon_name(),
+ Gtk.IconSize.BUTTON)
#############################
# External action listeners #
#############################
- def view_manager(self, _src):
- from .manager import vmmManager
- vmmManager.get_instance(self).show()
-
- def exit_app(self, _src):
- vmmEngine.get_instance().exit_app()
-
- def activate_default_console_page(self):
- pages = self.widget("details-pages")
-
- # console.activate_default_console_page() will as a side effect
- # switch to DETAILS_PAGE_CONSOLE. However this code path is triggered
- # when the user runs a VM while they are focused on the details page,
- # and we don't want to switch pages out from under them.
- origpage = pages.get_current_page()
- self.console.details_activate_default_console_page()
- pages.set_current_page(origpage)
-
- # activate_* are called from engine.py via CLI options
- def activate_default_page(self):
- if self.is_customize_dialog:
- return
- pages = self.widget("details-pages")
- pages.set_current_page(DETAILS_PAGE_CONSOLE)
- self.activate_default_console_page()
-
- def activate_console_page(self):
- pages = self.widget("details-pages")
- pages.set_current_page(DETAILS_PAGE_CONSOLE)
-
- def activate_performance_page(self):
- self.widget("details-pages").set_current_page(DETAILS_PAGE_DETAILS)
- index = 0
- model = self.widget("hw-list").get_model()
- for idx, row in enumerate(model):
- if row[HW_LIST_COL_TYPE] == HW_LIST_TYPE_STATS:
- index = idx
- break
- self.set_hw_selection(index)
-
- def activate_config_page(self):
- self.widget("details-pages").set_current_page(DETAILS_PAGE_DETAILS)
-
def add_hardware(self, src_ignore):
try:
if self.addhw is None:
@@ -1471,102 +1062,6 @@ class vmmDetails(vmmGObjectUI):
devobj = self.get_hw_row()[HW_LIST_COL_DEVICE]
self.remove_device(devobj)
- def set_pause_state(self, state):
- src = self.widget("control-pause")
- try:
- src.handler_block_by_func(self.control_vm_pause)
- src.set_active(state)
- finally:
- src.handler_unblock_by_func(self.control_vm_pause)
-
- def control_vm_pause(self, src):
- do_pause = src.get_active()
-
- # Set button state back to original value: just let the status
- # update function fix things for us
- self.set_pause_state(not do_pause)
-
- if do_pause:
- vmmenu.VMActionUI.suspend(self, self.vm)
- else:
- vmmenu.VMActionUI.resume(self, self.vm)
-
- def control_vm_menu(self, src_ignore):
- can_usb = bool(self.console.details_viewer_has_usb_redirection() and
- self.vm.has_spicevmc_type_redirdev())
- self.widget("details-menu-usb-redirection").set_sensitive(can_usb)
-
- def control_vm_run(self, src_ignore):
- if self.has_unapplied_changes(self.get_hw_row()):
- return
- vmmenu.VMActionUI.run(self, self.vm)
-
- def control_vm_shutdown(self, src_ignore):
- vmmenu.VMActionUI.shutdown(self, self.vm)
-
- def control_vm_screenshot(self, src):
- ignore = src
- try:
- return self._take_screenshot()
- except Exception as e:
- self.err.show_err(_("Error taking screenshot: %s") % str(e))
-
- def control_vm_usb_redirection(self, src):
- ignore = src
- spice_usbdev_dialog = self.err
-
- spice_usbdev_widget = self.console.details_viewer_get_usb_widget()
- if not spice_usbdev_widget:
- self.err.show_err(_("Error initializing spice USB device widget"))
- return
-
- spice_usbdev_widget.show()
- spice_usbdev_dialog.show_info(_("Select USB devices for redirection"),
- widget=spice_usbdev_widget,
- buttons=Gtk.ButtonsType.CLOSE)
-
- def _take_screenshot(self):
- image = self.console.details_viewer_get_pixbuf()
-
- metadata = {
- 'tEXt::Hypervisor URI': self.vm.conn.get_uri(),
- 'tEXt::Domain Name': self.vm.get_name(),
- 'tEXt::Domain UUID': self.vm.get_uuid(),
- 'tEXt::Generator App': self.config.get_appname(),
- 'tEXt::Generator Version': self.config.get_appversion(),
- }
-
- ret = image.save_to_bufferv(
- 'png', list(metadata.keys()), list(metadata.values())
- )
- # On Fedora 19, ret is (bool, str)
- # Someday the bindings might be fixed to just return the str, try
- # and future proof it a bit
- if isinstance(ret, tuple) and len(ret) >= 2:
- ret = ret[1]
- # F24 rawhide, ret[1] is a named tuple with a 'buffer' element...
- if hasattr(ret, "buffer"):
- ret = ret.buffer
-
- import datetime
- now = str(datetime.datetime.now()).split(".")[0].replace(" ", "_")
- default = "Screenshot_%s_%s.png" % (self.vm.get_name(), now)
-
- path = self.err.browse_local(
- self.vm.conn, _("Save Virtual Machine Screenshot"),
- _type=("png", _("PNG files")),
- dialog_type=Gtk.FileChooserAction.SAVE,
- browse_reason=self.config.CONFIG_DIR_SCREENSHOT,
- default_name=default)
- if not path:
- logging.debug("No screenshot path given, skipping save.")
- return
-
- filename = path
- if not filename.endswith(".png"):
- filename += ".png"
- open(filename, "wb").write(ret)
-
############################
# Details/Hardware getters #
@@ -2393,40 +1888,15 @@ class vmmDetails(vmmGObjectUI):
buttons=Gtk.ButtonsType.OK,
dialog_type=Gtk.MessageType.INFO)
+ #######################
+ # vmwindow Public API #
+ #######################
- ########################
- # Details page refresh #
- ########################
-
- def refresh_resources(self, ignore):
- details = self.widget("details-pages")
- page = details.get_current_page()
-
- # If the dialog is visible, we want to make sure the XML is always
- # up to date
- try:
- if self.is_visible():
- self.vm.ensure_latest_xml()
- except libvirt.libvirtError as e:
- if util.exception_is_libvirt_error(e, "VIR_ERR_NO_DOMAIN"):
- self.close()
- return
- raise
-
- # Stats page needs to be refreshed every tick
- if (page == DETAILS_PAGE_DETAILS and
- self.get_hw_row()[HW_LIST_COL_TYPE] == HW_LIST_TYPE_STATS):
+ def vmwindow_resources_refreshed(self):
+ if self.get_hw_row()[HW_LIST_COL_TYPE] == HW_LIST_TYPE_STATS:
self.refresh_stats_page()
- def page_refresh(self, page):
- if page != DETAILS_PAGE_DETAILS:
- return
-
- # This function should only be called when the VM xml actually
- # changes (not every time it is refreshed). This saves us from blindly
- # parsing the xml every tick
-
- # Add / remove new devices
+ def vmwindow_page_refresh(self):
self.repopulate_hw_list()
pagetype = self.get_hw_row()[HW_LIST_COL_TYPE]
@@ -2440,6 +1910,23 @@ class vmmDetails(vmmGObjectUI):
self.hw_selected(pagetype=pagetype)
+ def vmwindow_activate_performance_page(self):
+ index = 0
+ model = self.widget("hw-list").get_model()
+ for idx, row in enumerate(model):
+ if row[HW_LIST_COL_TYPE] == HW_LIST_TYPE_STATS:
+ index = idx
+ break
+ self.set_hw_selection(index)
+
+ def vmwindow_has_unapplied_changes(self):
+ return self.has_unapplied_changes(self.get_hw_row())
+
+
+ ########################
+ # Details page refresh #
+ ########################
+
def refresh_overview_page(self):
# Basic details
self.widget("overview-name").set_text(self.vm.get_name())
diff --git a/virtManager/engine.py b/virtManager/engine.py
index 8c7bfd79c..3d9ca7d3a 100644
--- a/virtManager/engine.py
+++ b/virtManager/engine.py
@@ -476,8 +476,8 @@ class vmmEngine(vmmGObject):
raise RuntimeError("%s does not have VM '%s'" %
(uri, clistr))
- from .details import vmmDetails
- details = vmmDetails.get_instance(None, vm)
+ from .vmwindow import vmmVMWindow
+ details = vmmVMWindow.get_instance(None, vm)
if page == self.CLI_SHOW_DOMAIN_PERFORMANCE:
details.activate_performance_page()
diff --git a/virtManager/vmmenu.py b/virtManager/vmmenu.py
index 7005c2f3f..dcbbb47d2 100644
--- a/virtManager/vmmenu.py
+++ b/virtManager/vmmenu.py
@@ -327,5 +327,5 @@ class VMActionUI(object):
@staticmethod
def show(src, vm):
- from .details import vmmDetails
- vmmDetails.get_instance(src, vm).show()
+ from .vmwindow import vmmVMWindow
+ vmmVMWindow.get_instance(src, vm).show()
diff --git a/virtManager/vmwindow.py b/virtManager/vmwindow.py
new file mode 100644
index 000000000..be394cf25
--- /dev/null
+++ b/virtManager/vmwindow.py
@@ -0,0 +1,573 @@
+# Copyright (C) 2006-2008, 2013, 2014 Red Hat, Inc.
+# Copyright (C) 2006 Daniel P. Berrange
+#
+# This work is licensed under the GNU GPLv2 or later.
+# See the COPYING file in the top-level directory.
+
+import logging
+
+from gi.repository import Gdk
+from gi.repository import Gtk
+
+import libvirt
+
+from virtinst import util
+
+from . import vmmenu
+from .baseclass import vmmGObjectUI
+from .details import vmmDetails
+from .engine import vmmEngine
+from .snapshots import vmmSnapshotPage
+
+
+# Main tab pages
+(DETAILS_PAGE_DETAILS,
+ DETAILS_PAGE_CONSOLE,
+ DETAILS_PAGE_SNAPSHOTS) = range(3)
+
+
+class vmmVMWindow(vmmGObjectUI):
+ __gsignals__ = {
+ "customize-finished": (vmmGObjectUI.RUN_FIRST, None, []),
+ "closed": (vmmGObjectUI.RUN_FIRST, None, []),
+ }
+
+ @classmethod
+ def get_instance(cls, parentobj, vm):
+ try:
+ # Maintain one dialog per VM
+ key = "%s+%s" % (vm.conn.get_uri(), vm.get_uuid())
+ if cls._instances is None:
+ cls._instances = {}
+ if key not in cls._instances:
+ cls._instances[key] = vmmVMWindow(vm)
+ return cls._instances[key]
+ except Exception as e:
+ if not parentobj:
+ raise
+ parentobj.err.show_err(
+ _("Error launching details: %s") % str(e))
+
+ def __init__(self, vm, parent=None):
+ vmmGObjectUI.__init__(self, "vmwindow.ui", "vmm-vmwindow")
+ self.vm = vm
+
+ self.is_customize_dialog = False
+ if parent:
+ # Details window is being abused as a 'configure before install'
+ # dialog, set things as appropriate
+ self.is_customize_dialog = True
+ self.topwin.set_type_hint(Gdk.WindowTypeHint.DIALOG)
+ self.topwin.set_transient_for(parent)
+
+ self.widget("toolbar-box").show()
+ self.widget("customize-toolbar").show()
+ self.widget("details-toolbar").hide()
+ self.widget("details-menubar").hide()
+ pages = self.widget("details-pages")
+ pages.set_current_page(DETAILS_PAGE_DETAILS)
+ else:
+ self.conn.connect("vm-removed", self._vm_removed)
+
+ self._mediacombo = None
+
+ self.ignoreDetails = False
+
+ from .console import vmmConsolePages
+ self.console = vmmConsolePages(self.vm, self.builder, self.topwin)
+ self.snapshots = vmmSnapshotPage(self.vm, self.builder, self.topwin)
+ self.widget("snapshot-placeholder").add(self.snapshots.top_box)
+ self._details = vmmDetails(self.vm, self.builder, self.topwin,
+ self.is_customize_dialog)
+ self.widget("details-placeholder").add(self._details.top_box)
+
+ # Set default window size
+ w, h = self.vm.get_details_window_size()
+ if w <= 0:
+ w = 800
+ if h <= 0:
+ h = 600
+ self.topwin.set_default_size(w, h)
+ self._window_size = None
+
+ self._shutdownmenu = None
+ self._vmmenu = None
+ self.init_menus()
+
+ self.builder.connect_signals({
+ "on_close_details_clicked": self.close,
+ "on_details_menu_close_activate": self.close,
+ "on_vmm_details_delete_event": self._window_delete_event,
+ "on_vmm_details_configure_event": self.window_resized,
+ "on_details_menu_quit_activate": self.exit_app,
+
+ "on_control_vm_details_toggled": self.details_console_changed,
+ "on_control_vm_console_toggled": self.details_console_changed,
+ "on_control_snapshots_toggled": self.details_console_changed,
+ "on_control_run_clicked": self.control_vm_run,
+ "on_control_shutdown_clicked": self.control_vm_shutdown,
+ "on_control_pause_toggled": self.control_vm_pause,
+ "on_control_fullscreen_toggled": self.control_fullscreen,
+
+ "on_details_customize_finish_clicked": self.customize_finish,
+ "on_details_cancel_customize_clicked": self._customize_cancel_clicked,
+
+ "on_details_menu_virtual_manager_activate": self.control_vm_menu,
+ "on_details_menu_screenshot_activate": self.control_vm_screenshot,
+ "on_details_menu_usb_redirection": self.control_vm_usb_redirection,
+ "on_details_menu_view_toolbar_activate": self.toggle_toolbar,
+ "on_details_menu_view_manager_activate": self.view_manager,
+ "on_details_menu_view_details_toggled": self.details_console_changed,
+ "on_details_menu_view_console_toggled": self.details_console_changed,
+ "on_details_menu_view_snapshots_toggled": self.details_console_changed,
+
+ "on_details_pages_switch_page": self.switch_page,
+
+ # Listeners stored in vmmConsolePages
+ "on_details_menu_view_fullscreen_activate": (
+ self.console.details_toggle_fullscreen),
+ "on_details_menu_view_size_to_vm_activate": (
+ self.console.details_size_to_vm),
+ "on_details_menu_view_scale_always_toggled": (
+ self.console.details_scaling_ui_changed_cb),
+ "on_details_menu_view_scale_fullscreen_toggled": (
+ self.console.details_scaling_ui_changed_cb),
+ "on_details_menu_view_scale_never_toggled": (
+ self.console.details_scaling_ui_changed_cb),
+ "on_details_menu_view_resizeguest_toggled": (
+ self.console.details_resizeguest_ui_changed_cb),
+
+ "on_console_pages_switch_page": (
+ self.console.details_page_changed),
+ "on_console_auth_password_activate": (
+ self.console.details_auth_login),
+ "on_console_auth_login_clicked": (
+ self.console.details_auth_login),
+ })
+
+ # Deliberately keep all this after signal connection
+ self.vm.connect("state-changed", self.refresh_vm_state)
+ self.vm.connect("resources-sampled", self.refresh_resources)
+ self.vm.connect("inspection-changed",
+ lambda *x: self._details.refresh_os_page())
+
+ self.refresh_vm_state()
+ self.activate_default_page()
+
+
+ @property
+ def conn(self):
+ return self.vm.conn
+
+ def _cleanup(self):
+ self.console.cleanup()
+ self.console = None
+ self.snapshots.cleanup()
+ self.snapshots = None
+ self._shutdownmenu.destroy()
+ self._shutdownmenu = None
+ self._vmmenu.destroy()
+ self._vmmenu = None
+
+ if self._window_size:
+ self.vm.set_details_window_size(*self._window_size)
+
+ self.conn.disconnect_by_obj(self)
+ self.vm = None
+
+ def show(self):
+ logging.debug("Showing VM details: %s", self.vm)
+ vis = self.is_visible()
+ self.topwin.present()
+ if vis:
+ return
+
+ vmmEngine.get_instance().increment_window_counter()
+ self.refresh_vm_state()
+
+ def customize_finish(self, src):
+ ignore = src
+ if self._details.vmwindow_has_unapplied_changes():
+ return
+ self.emit("customize-finished")
+
+ def _vm_removed(self, _conn, connkey):
+ if self.vm.get_connkey() == connkey:
+ self.cleanup()
+
+ def _customize_cancel(self):
+ logging.debug("Asking to cancel customization")
+
+ result = self.err.yes_no(
+ _("This will abort the installation. Are you sure?"))
+ if not result:
+ logging.debug("Customize cancel aborted")
+ return
+
+ logging.debug("Canceling customization")
+ return self._close()
+
+ def _customize_cancel_clicked(self, src):
+ ignore = src
+ return self._customize_cancel()
+
+ def _window_delete_event(self, ignore1=None, ignore2=None):
+ return self.close()
+
+ def close(self, ignore1=None, ignore2=None):
+ if self.is_visible():
+ logging.debug("Closing VM details: %s", self.vm)
+ return self._close()
+
+ def _close(self):
+ fs = self.widget("details-menu-view-fullscreen")
+ if fs.get_active():
+ fs.set_active(False)
+
+ if not self.is_visible():
+ return
+
+ self.topwin.hide()
+ if self.console.details_viewer_is_visible():
+ try:
+ self.console.details_close_viewer()
+ except Exception:
+ logging.error("Failure when disconnecting from desktop server")
+
+ self.emit("closed")
+ vmmEngine.get_instance().decrement_window_counter()
+ return 1
+
+
+ ##########################
+ # Initialization helpers #
+ ##########################
+
+ def init_menus(self):
+ # Virtual Machine menu
+ self._shutdownmenu = vmmenu.VMShutdownMenu(self, lambda: self.vm)
+ self.widget("control-shutdown").set_menu(self._shutdownmenu)
+ self.widget("control-shutdown").set_icon_name("system-shutdown")
+
+ topmenu = self.widget("details-vm-menu")
+ submenu = topmenu.get_submenu()
+ self._vmmenu = vmmenu.VMActionMenu(
+ self, lambda: self.vm, show_open=False)
+ for child in submenu.get_children():
+ submenu.remove(child)
+ self._vmmenu.add(child)
+ topmenu.set_submenu(self._vmmenu)
+ topmenu.show_all()
+
+ self.widget("details-pages").set_show_tabs(False)
+ self.widget("details-menu-view-toolbar").set_active(
+ self.config.get_details_show_toolbar())
+
+
+ ##########################
+ # Window state listeners #
+ ##########################
+
+ def window_resized(self, ignore, ignore2):
+ if not self.is_visible():
+ return
+ self._window_size = self.topwin.get_size()
+
+ def control_fullscreen(self, src):
+ menu = self.widget("details-menu-view-fullscreen")
+ if src.get_active() != menu.get_active():
+ menu.set_active(src.get_active())
+
+ def toggle_toolbar(self, src):
+ if self.is_customize_dialog:
+ return
+
+ active = src.get_active()
+ self.config.set_details_show_toolbar(active)
+
+ if (active and not
+ self.widget("details-menu-view-fullscreen").get_active()):
+ self.widget("toolbar-box").show()
+ else:
+ self.widget("toolbar-box").hide()
+
+ def details_console_changed(self, src):
+ if self.ignoreDetails:
+ return
+
+ if not src.get_active():
+ return
+
+ is_details = (src == self.widget("control-vm-details") or
+ src == self.widget("details-menu-view-details"))
+ is_snapshot = (src == self.widget("control-snapshots") or
+ src == self.widget("details-menu-view-snapshots"))
+
+ pages = self.widget("details-pages")
+ if pages.get_current_page() == DETAILS_PAGE_DETAILS:
+ if self._details.vmwindow_has_unapplied_changes():
+ self.sync_details_console_view(True)
+ return
+ self._details.disable_apply()
+
+ if is_details:
+ pages.set_current_page(DETAILS_PAGE_DETAILS)
+ elif is_snapshot:
+ self.snapshots.show_page()
+ pages.set_current_page(DETAILS_PAGE_SNAPSHOTS)
+ else:
+ pages.set_current_page(DETAILS_PAGE_CONSOLE)
+
+ def sync_details_console_view(self, newpage):
+ details = self.widget("control-vm-details")
+ details_menu = self.widget("details-menu-view-details")
+ console = self.widget("control-vm-console")
+ console_menu = self.widget("details-menu-view-console")
+ snapshot = self.widget("control-snapshots")
+ snapshot_menu = self.widget("details-menu-view-snapshots")
+
+ is_details = newpage == DETAILS_PAGE_DETAILS
+ is_snapshot = newpage == DETAILS_PAGE_SNAPSHOTS
+ is_console = not is_details and not is_snapshot
+
+ try:
+ self.ignoreDetails = True
+
+ details.set_active(is_details)
+ details_menu.set_active(is_details)
+ snapshot.set_active(is_snapshot)
+ snapshot_menu.set_active(is_snapshot)
+ console.set_active(is_console)
+ console_menu.set_active(is_console)
+ finally:
+ self.ignoreDetails = False
+
+ def switch_page(self, notebook=None, ignore2=None, newpage=None):
+ for i in range(notebook.get_n_pages()):
+ w = notebook.get_nth_page(i)
+ w.set_visible(i == newpage)
+
+ self.page_refresh(newpage)
+
+ self.sync_details_console_view(newpage)
+ self.console.details_refresh_can_fullscreen()
+
+ def change_run_text(self, can_restore):
+ if can_restore:
+ text = _("_Restore")
+ else:
+ text = _("_Run")
+ strip_text = text.replace("_", "")
+
+ self.widget("details-vm-menu").get_submenu().change_run_text(text)
+ self.widget("control-run").set_label(strip_text)
+
+ def refresh_vm_state(self, ignore=None):
+ vm = self.vm
+
+ self.widget("details-menu-view-toolbar").set_active(
+ self.config.get_details_show_toolbar())
+ self.toggle_toolbar(self.widget("details-menu-view-toolbar"))
+
+ run = vm.is_runable()
+ stop = vm.is_stoppable()
+ paused = vm.is_paused()
+
+ if vm.managedsave_supported:
+ self.change_run_text(vm.has_managed_save())
+
+ self.widget("control-run").set_sensitive(run)
+ self.widget("control-shutdown").set_sensitive(stop)
+ self.widget("control-shutdown").get_menu().update_widget_states(vm)
+ self.widget("control-pause").set_sensitive(stop)
+
+ if paused:
+ pauseTooltip = _("Resume the virtual machine")
+ else:
+ pauseTooltip = _("Pause the virtual machine")
+ self.widget("control-pause").set_tooltip_text(pauseTooltip)
+
+ self.widget("details-vm-menu").get_submenu().update_widget_states(vm)
+ self.set_pause_state(paused)
+
+ errmsg = self.vm.snapshots_supported()
+ cansnap = not bool(errmsg)
+ self.widget("control-snapshots").set_sensitive(cansnap)
+ self.widget("details-menu-view-snapshots").set_sensitive(cansnap)
+ tooltip = _("Manage VM snapshots")
+ if not cansnap:
+ tooltip += "\n" + errmsg
+ self.widget("control-snapshots").set_tooltip_text(tooltip)
+
+ details = self.widget("details-pages")
+ self.page_refresh(details.get_current_page())
+
+ self._details.vmwindow_refresh_vm_state()
+ self.console.details_update_widget_states()
+ if not run:
+ self.activate_default_console_page()
+
+
+ #############################
+ # External action listeners #
+ #############################
+
+ def view_manager(self, _src):
+ from .manager import vmmManager
+ vmmManager.get_instance(self).show()
+
+ def exit_app(self, _src):
+ vmmEngine.get_instance().exit_app()
+
+ def activate_default_console_page(self):
+ pages = self.widget("details-pages")
+
+ # console.activate_default_console_page() will as a side effect
+ # switch to DETAILS_PAGE_CONSOLE. However this code path is triggered
+ # when the user runs a VM while they are focused on the details page,
+ # and we don't want to switch pages out from under them.
+ origpage = pages.get_current_page()
+ self.console.details_activate_default_console_page()
+ pages.set_current_page(origpage)
+
+ # activate_* are called from engine.py via CLI options
+ def activate_default_page(self):
+ if self.is_customize_dialog:
+ return
+ pages = self.widget("details-pages")
+ pages.set_current_page(DETAILS_PAGE_CONSOLE)
+ self.activate_default_console_page()
+
+ def activate_console_page(self):
+ pages = self.widget("details-pages")
+ pages.set_current_page(DETAILS_PAGE_CONSOLE)
+
+ def activate_performance_page(self):
+ self.widget("details-pages").set_current_page(DETAILS_PAGE_DETAILS)
+ self._details.vmwindow_activate_performance_page()
+
+ def activate_config_page(self):
+ self.widget("details-pages").set_current_page(DETAILS_PAGE_DETAILS)
+
+ def set_pause_state(self, state):
+ src = self.widget("control-pause")
+ try:
+ src.handler_block_by_func(self.control_vm_pause)
+ src.set_active(state)
+ finally:
+ src.handler_unblock_by_func(self.control_vm_pause)
+
+ def control_vm_pause(self, src):
+ do_pause = src.get_active()
+
+ # Set button state back to original value: just let the status
+ # update function fix things for us
+ self.set_pause_state(not do_pause)
+
+ if do_pause:
+ vmmenu.VMActionUI.suspend(self, self.vm)
+ else:
+ vmmenu.VMActionUI.resume(self, self.vm)
+
+ def control_vm_menu(self, src_ignore):
+ can_usb = bool(self.console.details_viewer_has_usb_redirection() and
+ self.vm.has_spicevmc_type_redirdev())
+ self.widget("details-menu-usb-redirection").set_sensitive(can_usb)
+
+ def control_vm_run(self, src_ignore):
+ if self._details.vmwindow_has_unapplied_changes():
+ return
+ vmmenu.VMActionUI.run(self, self.vm)
+
+ def control_vm_shutdown(self, src_ignore):
+ vmmenu.VMActionUI.shutdown(self, self.vm)
+
+ def control_vm_screenshot(self, src):
+ ignore = src
+ try:
+ return self._take_screenshot()
+ except Exception as e:
+ self.err.show_err(_("Error taking screenshot: %s") % str(e))
+
+ def control_vm_usb_redirection(self, src):
+ ignore = src
+ spice_usbdev_dialog = self.err
+
+ spice_usbdev_widget = self.console.details_viewer_get_usb_widget()
+ if not spice_usbdev_widget:
+ self.err.show_err(_("Error initializing spice USB device widget"))
+ return
+
+ spice_usbdev_widget.show()
+ spice_usbdev_dialog.show_info(_("Select USB devices for redirection"),
+ widget=spice_usbdev_widget,
+ buttons=Gtk.ButtonsType.CLOSE)
+
+ def _take_screenshot(self):
+ image = self.console.details_viewer_get_pixbuf()
+
+ metadata = {
+ 'tEXt::Hypervisor URI': self.vm.conn.get_uri(),
+ 'tEXt::Domain Name': self.vm.get_name(),
+ 'tEXt::Domain UUID': self.vm.get_uuid(),
+ 'tEXt::Generator App': self.config.get_appname(),
+ 'tEXt::Generator Version': self.config.get_appversion(),
+ }
+
+ ret = image.save_to_bufferv(
+ 'png', list(metadata.keys()), list(metadata.values())
+ )
+ # On Fedora 19, ret is (bool, str)
+ # Someday the bindings might be fixed to just return the str, try
+ # and future proof it a bit
+ if isinstance(ret, tuple) and len(ret) >= 2:
+ ret = ret[1]
+ # F24 rawhide, ret[1] is a named tuple with a 'buffer' element...
+ if hasattr(ret, "buffer"):
+ ret = ret.buffer
+
+ import datetime
+ now = str(datetime.datetime.now()).split(".")[0].replace(" ", "_")
+ default = "Screenshot_%s_%s.png" % (self.vm.get_name(), now)
+
+ path = self.err.browse_local(
+ self.vm.conn, _("Save Virtual Machine Screenshot"),
+ _type=("png", _("PNG files")),
+ dialog_type=Gtk.FileChooserAction.SAVE,
+ browse_reason=self.config.CONFIG_DIR_SCREENSHOT,
+ default_name=default)
+ if not path:
+ logging.debug("No screenshot path given, skipping save.")
+ return
+
+ filename = path
+ if not filename.endswith(".png"):
+ filename += ".png"
+ open(filename, "wb").write(ret)
+
+
+ ########################
+ # Details page refresh #
+ ########################
+
+ def refresh_resources(self, ignore):
+ details = self.widget("details-pages")
+ page = details.get_current_page()
+
+ # If the dialog is visible, we want to make sure the XML is always
+ # up to date
+ try:
+ if self.is_visible():
+ self.vm.ensure_latest_xml()
+ except libvirt.libvirtError as e:
+ if util.exception_is_libvirt_error(e, "VIR_ERR_NO_DOMAIN"):
+ self.close()
+ return
+ raise
+
+ if page == DETAILS_PAGE_DETAILS:
+ self._details.vmwindow_resources_refreshed()
+
+ def page_refresh(self, page):
+ if page == DETAILS_PAGE_DETAILS:
+ self._details.vmwindow_page_refresh()