Browse Source

Recreation of the old qubes manager in a leaner, cut-down form. First
commit, WIP.

Marta Marczykowska-Górecka 6 years ago
parent
commit
443f48c648
3 changed files with 3285 additions and 338 deletions
  1. 2165 0
      qubesmanager/main.py
  2. 177 338
      qubesmanager/table_widgets.py
  3. 943 0
      ui/vtmanager.ui

+ 2165 - 0
qubesmanager/main.py

@@ -0,0 +1,2165 @@
+#!/usr/bin/python3
+#
+# The Qubes OS Project, http://www.qubes-os.org
+#
+# Copyright (C) 2012  Agnieszka Kostrzewa <agnieszka.kostrzewa@gmail.com>
+# Copyright (C) 2012  Marek Marczykowski-Górecki
+#                       <marmarek@invisiblethingslab.com>
+# Copyright (C) 2017  Wojtek Porczyk <woju@invisiblethingslab.com>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program; if not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+# TODO: cleanup imports
+import sys
+import os
+import os.path
+import signal
+import subprocess
+import time
+from datetime import datetime, timedelta
+import traceback
+
+from qubesadmin import Qubes
+from qubesadmin import exc
+
+from PyQt4 import QtGui
+from PyQt4 import QtCore
+from PyQt4 import Qt
+from PyQt4.QtDBus import QDBusVariant, QDBusMessage
+from PyQt4.QtDBus import QDBusConnection
+from PyQt4.QtDBus import QDBusInterface, QDBusAbstractAdaptor
+from pyinotify import WatchManager, ThreadedNotifier, EventsCodes, \
+    ProcessEvent
+
+from . import ui_vtmanager
+from . import thread_monitor
+from . import table_widgets
+import threading
+
+# from qubes.qubes import QubesVmCollection
+# from qubes.qubes import QubesException
+# from qubes.qubes import system_path
+# from qubes.qubes import QubesDaemonPidfile
+# from qubes.qubes import QubesHost
+from qubesmanager.about import AboutDialog
+# import table_widgets
+# from block import QubesBlockDevicesManager
+# from table_widgets import VmTypeWidget, VmLabelWidget, VmNameItem, \
+#     VmInfoWidget, VmTemplateItem, VmNetvmItem, VmUsageBarWidget, ChartWidget, \
+#     VmSizeOnDiskItem, VmInternalItem, VmIPItem, VmIncludeInBackupsItem, \
+#     VmLastBackupItem
+# from qubes.qubes import QubesHVm
+# from qubes import qubesutils
+# from ui_mainwindow import *
+# from create_new_vm import NewVmDlg
+# from settings import VMSettingsWindow
+# from restore import RestoreVMsWindow
+# from backup import BackupVMsWindow
+# from global_settings import GlobalSettingsWindow
+# from networknotes import NetworkNotesDialog
+# from log_dialog import LogDialog
+
+# TODO: probably unneeded
+update_suggestion_interval = 14  # 14 days
+# TODO: probably unneeded
+dbus_object_path = '/org/qubesos/QubesManager'
+dbus_interface = 'org.qubesos.QubesManager'
+system_bus = None
+session_bus = None
+
+
+# TODO: probably unneeded
+class QMVmState:
+    ErrorMsg = 1
+    AudioRecAvailable = 2
+    AudioRecAllowed = 3
+
+
+#TODO: this is actually needed O_O
+class SearchBox(QtGui.QLineEdit):
+    def __init__(self, parent=None):
+        super(SearchBox, self).__init__(parent)
+        self.focusing = False
+
+    def focusInEvent(self, e):
+        super(SearchBox, self).focusInEvent(e)
+        self.selectAll()
+        self.focusing = True
+
+    def mousePressEvent(self, e):
+        super(SearchBox, self).mousePressEvent(e)
+        if self.focusing:
+            self.selectAll()
+            self.focusing = False
+
+
+class VmRowInTable(object):
+    cpu_graph_hue = 210
+    mem_graph_hue = 120
+
+    def __init__(self, vm, row_no, table):
+        self.vm = vm
+        self.row_no = row_no
+
+        table_widgets.row_height = VmManagerWindow.row_height
+        table.setRowHeight(row_no, VmManagerWindow.row_height)
+
+        self.type_widget = table_widgets.VmTypeWidget(vm)
+        table.setCellWidget(row_no, VmManagerWindow.columns_indices['Type'],
+                            self.type_widget)
+        table.setItem(row_no, VmManagerWindow.columns_indices['Type'],
+                      self.type_widget.tableItem)
+
+        self.label_widget = table_widgets.VmLabelWidget(vm)
+        table.setCellWidget(row_no, VmManagerWindow.columns_indices['Label'],
+                            self.label_widget)
+        table.setItem(row_no, VmManagerWindow.columns_indices['Label'],
+                      self.label_widget.tableItem)
+
+        self.name_widget = table_widgets.VmNameItem(vm)
+        table.setItem(row_no, VmManagerWindow.columns_indices['Name'],
+                      self.name_widget)
+
+        self.info_widget = table_widgets.VmInfoWidget(vm)
+        table.setCellWidget(row_no, VmManagerWindow.columns_indices['State'],
+                            self.info_widget)
+        table.setItem(row_no, VmManagerWindow.columns_indices['State'],
+                      self.info_widget.tableItem)
+
+        self.template_widget = table_widgets.VmTemplateItem(vm)
+        table.setItem(row_no, VmManagerWindow.columns_indices['Template'],
+                      self.template_widget)
+
+        self.netvm_widget = table_widgets.VmNetvmItem(vm)
+        table.setItem(row_no, VmManagerWindow.columns_indices['NetVM'],
+                      self.netvm_widget)
+
+        # self.cpu_usage_widget = table_widgets.VmUsageBarWidget(
+        #     0, 100, "%v %",
+        #     # lambda v, val: val if v.last_running else 0,
+        #     lambda v, val: val,
+        #     vm, 0, self.cpu_graph_hue)
+        # table.setCellWidget(row_no, VmManagerWindow.columns_indices['CPU'],
+        #                     self.cpu_usage_widget)
+        # table.setItem(row_no, VmManagerWindow.columns_indices['CPU'],
+        #               self.cpu_usage_widget.tableItem)
+
+        # self.load_widget = table_widgets.ChartWidget(
+        #     vm,
+        #     lambda v, val: val if v.last_running else 0,
+        #     self.cpu_graph_hue, 0)
+        # table.setCellWidget(row_no,
+        #                     VmManagerWindow.columns_indices['CPU Graph'],
+        #                     self.load_widget)
+        # table.setItem(row_no, VmManagerWindow.columns_indices['CPU Graph'],
+        #               self.load_widget.tableItem)
+
+        # self.mem_usage_widget = table_widgets.VmUsageBarWidget(
+        #     0, qubes_host.memory_total / 1024, "%v MB",
+        #     lambda v, val: v.get_mem() / 1024,
+        #     vm, 0, self.mem_graph_hue)
+        # table.setCellWidget(row_no, VmManagerWindow.columns_indices['MEM'],
+        #                     self.mem_usage_widget)
+        # table.setItem(row_no, VmManagerWindow.columns_indices['MEM'],
+        #               self.mem_usage_widget.tableItem)
+        #
+        # # self.mem_widget = table_widgets.ChartWidget(
+        # #     vm, lambda v, val: v.get_mem() * 100 / qubes_host.memory_total,
+        # #     self.mem_graph_hue, 0)
+        # table.setCellWidget(row_no,
+        #                     VmManagerWindow.columns_indices['MEM Graph'],
+        #                     self.mem_widget)
+        # table.setItem(row_no, VmManagerWindow.columns_indices['MEM Graph'],
+        #               self.mem_widget.tableItem)
+
+        self.size_widget = table_widgets.VmSizeOnDiskItem(vm)
+        table.setItem(row_no, VmManagerWindow.columns_indices['Size'],
+                      self.size_widget)
+
+        self.internal_widget = table_widgets.VmInternalItem(vm)
+        table.setItem(row_no, VmManagerWindow.columns_indices['Internal'],
+                      self.internal_widget)
+
+        self.ip_widget = table_widgets.VmIPItem(vm)
+        table.setItem(row_no, VmManagerWindow.columns_indices['IP'],
+                      self.ip_widget)
+
+        self.include_in_backups_widget = table_widgets.VmIncludeInBackupsItem(vm)
+        table.setItem(row_no, VmManagerWindow.columns_indices[
+            'Backups'], self.include_in_backups_widget)
+
+        self.last_backup_widget = table_widgets.VmLastBackupItem(vm)
+        table.setItem(row_no, VmManagerWindow.columns_indices[
+            'Last backup'], self.last_backup_widget)
+
+    def update(self, blk_visible=None, cpu_load=None, update_size_on_disk=False,
+               rec_visible=None):
+        """
+        Update info in a single VM row
+        :param blk_visible: if not None, show/hide block icon, otherwise
+        don't change its visibility
+        :param cpu_load: current CPU load (if applicable), in percents
+        :param update_size_on_disk: should disk utilization be updated? the
+        widget will extract the data from VM object
+        :param rec_visible: if not None, show/hide mic icon, otherwise don't
+        change its visibility
+        :return: None
+        """
+        self.info_widget.update_vm_state(self.vm, blk_visible, rec_visible)
+        if cpu_load is not None:
+            self.cpu_usage_widget.update_load(self.vm, cpu_load)
+            self.mem_usage_widget.update_load(self.vm, None)
+            self.load_widget.update_load(self.vm, cpu_load)
+            self.mem_widget.update_load(self.vm, None)
+        if update_size_on_disk:
+            self.size_widget.update()
+
+
+vm_shutdown_timeout = 20000  # in msec
+vm_restart_check_timeout= 1000 # in msec
+
+
+class VmShutdownMonitor(QtCore.QObject):
+    def __init__(self, vm, shutdown_time=vm_shutdown_timeout, check_time=vm_restart_check_timeout, and_restart=False, caller=None):
+        QtCore.QObject.__init__(self)
+        self.vm = vm
+        self.shutdown_time = shutdown_time
+        self.check_time = check_time
+        self.and_restart = and_restart
+        self.shutdown_started = datetime.now()
+        self.caller = caller
+
+    def restart_vm_if_needed(self):
+        if self.and_restart and self.caller:
+            self.caller.start_vm(self.vm)
+
+    def check_again_later(self):
+        # noinspection PyTypeChecker,PyCallByClass
+        QtCore.QTimer.singleShot(self.check_time, self.check_if_vm_has_shutdown)
+
+    def timeout_reached(self):
+        actual = datetime.now() - self.shutdown_started
+        allowed = timedelta(milliseconds=self.shutdown_time)
+
+        return actual > allowed
+
+    def check_if_vm_has_shutdown(self):
+        vm = self.vm
+        vm_is_running = vm.is_running()
+        vm_start_time = vm.get_start_time()
+        if vm_is_running and vm_start_time and vm_start_time < self.shutdown_started:
+            if self.timeout_reached():
+                reply = QtGui.QMessageBox.question(
+                    None, self.tr("VM Shutdown"),
+                    self.tr("The VM <b>'{0}'</b> hasn't shutdown within the last "
+                    "{1} seconds, do you want to kill it?<br>").format(
+                        vm.name, self.shutdown_time / 1000),
+                    self.tr("Kill it!"),
+                    self.tr("Wait another {0} seconds...").format(
+                        self.shutdown_time / 1000))
+                if reply == 0:
+                    vm.force_shutdown()
+                    self.restart_vm_if_needed()
+                else:
+                    self.shutdown_started = datetime.now()
+                    self.check_again_later()
+            else:
+                self.check_again_later()
+        else:
+            if vm_is_running:
+                # Due to unknown reasons, Xen sometimes reports that a domain
+                # is running even though its start-up timestamp is not valid.
+                # Make sure that "restart_vm_if_needed" is not called until
+                # the domain has been completely shut down according to Xen.
+                self.check_again_later()
+                return
+
+            self.restart_vm_if_needed()
+
+class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow):
+    row_height = 30
+    column_width = 200
+    min_visible_rows = 10
+    show_inactive_vms = True
+    show_internal_vms = False
+    search = ""
+    # suppress saving settings while initializing widgets
+    settings_loaded = False
+    # TODO: does this work
+    columns_indices = {"Type": 0,
+                       "Label": 1,
+                       "Name": 2,
+                       "State": 3,
+                       "Template": 4,
+                       "NetVM": 5,
+                       # "CPU": 6, # delete
+                       # "CPU Graph": 7, # delete
+                       # "MEM": 8, # delete
+                       # "MEM Graph": 9, # delete
+                       "Size": 6,
+                       "Internal": 7,
+                       "IP": 8,
+                       "Backups": 9,
+                       "Last backup": 10,
+                       }
+
+    def __init__(self, qvm_collection, parent=None):
+        super(VmManagerWindow, self).__init__()
+        self.setupUi(self)
+        self.toolbar = self.toolBar
+
+        self.manager_settings = QtCore.QSettings() #TODO use Qt settings mechanism
+
+        # self.qubes_watch = qubesutils.QubesWatch()
+        self.qvm_collection = qvm_collection
+        # self.qubes_watch.setup_domain_watch(self.domain_state_changed_callback)
+        # self.qubes_watch.setup_block_watch(self.blk_manager.block_devs_event)
+        # self.blk_watch_thread = threading.Thread(
+        #     target=self.qubes_watch.watch_loop)
+        # self.blk_watch_thread.daemon = True
+        # self.blk_watch_thread.start()
+
+        self.searchbox = SearchBox() #TODO check if this works
+        self.searchbox.setValidator(QtGui.QRegExpValidator(
+            QtCore.QRegExp("[a-zA-Z0-9-]*", QtCore.Qt.CaseInsensitive), None))
+        self.searchContainer.addWidget(self.searchbox)
+
+        self.connect(self.table, QtCore.SIGNAL("itemSelectionChanged()"),
+                     self.table_selection_changed)
+
+        self.table.setColumnWidth(0, self.column_width)
+
+        self.sort_by_column = "Type"
+        self.sort_order = QtCore.Qt.AscendingOrder
+
+        self.screen_number = -1
+        self.screen_changed = False
+
+        self.vms_list = []
+        self.vms_in_table = {}
+        self.reload_table = False
+        self.running_vms_count = 0
+        self.internal_vms_count = 0
+
+        self.vm_errors = {}
+        self.vm_rec = {}
+
+        self.frame_width = 0
+        self.frame_height = 0
+
+        self.move(self.x(), 0)
+
+        self.columns_actions = {
+            self.columns_indices["Type"]: self.action_vm_type,
+            self.columns_indices["Label"]: self.action_label,
+            self.columns_indices["Name"]: self.action_name,
+            self.columns_indices["State"]: self.action_state,
+            self.columns_indices["Template"]: self.action_template,
+            self.columns_indices["NetVM"]: self.action_netvm,
+            # self.columns_indices["CPU"]: self.action_cpu,
+            # self.columns_indices["CPU Graph"]: self.action_cpu_graph,
+            # self.columns_indices["MEM"]: self.action_mem,
+            # self.columns_indices["MEM Graph"]: self.action_mem_graph,
+            self.columns_indices["Size"]: self.action_size_on_disk,
+            self.columns_indices["Internal"]: self.action_internal,
+            self.columns_indices["IP"]: self
+                .action_ip, self.columns_indices["Backups"]: self
+                .action_backups, self.columns_indices["Last backup"]: self
+            .action_last_backup
+        }
+
+        # TODO: make refresh button
+        self.visible_columns_count = len(self.columns_indices)
+        # self.table.setColumnHidden(self.columns_indices["CPU"], True)
+        # self.action_cpu.setChecked(False)
+        # self.table.setColumnHidden(self.columns_indices["CPU Graph"], True)
+        # self.action_cpu_graph.setChecked(False)
+        # self.table.setColumnHidden(self.columns_indices["MEM Graph"], True)
+        # self.action_mem_graph.setChecked(False)
+        self.table.setColumnHidden(self.columns_indices["Size"], True)
+        self.action_size_on_disk.setChecked(False)
+        self.table.setColumnHidden(self.columns_indices["Internal"], True)
+        self.action_internal.setChecked(False)
+        self.table.setColumnHidden(self.columns_indices["IP"], True)
+        self.action_ip.setChecked(False)
+        self.table.setColumnHidden(self.columns_indices["Backups"], True)
+        self.action_backups.setChecked(False)
+        self.table.setColumnHidden(self.columns_indices["Last backup"], True)
+        self.action_last_backup.setChecked(False)
+
+        self.table.setColumnWidth(self.columns_indices["State"], 80)
+        self.table.setColumnWidth(self.columns_indices["Name"], 150)
+        self.table.setColumnWidth(self.columns_indices["Label"], 40)
+        self.table.setColumnWidth(self.columns_indices["Type"], 40)
+        self.table.setColumnWidth(self.columns_indices["Size"], 100)
+        self.table.setColumnWidth(self.columns_indices["Internal"], 60)
+        self.table.setColumnWidth(self.columns_indices["IP"], 100)
+        self.table.setColumnWidth(self.columns_indices["Backups"], 60)
+        self.table.setColumnWidth(self.columns_indices["Last backup"], 90)
+
+        self.table.horizontalHeader().setResizeMode(QtGui.QHeaderView.Fixed)
+
+        self.table.sortItems(self.columns_indices[self.sort_by_column],
+                             self.sort_order)
+
+        self.context_menu = QtGui.QMenu(self)
+
+        self.context_menu.addAction(self.action_settings)
+        self.context_menu.addAction(self.action_editfwrules)
+        self.context_menu.addAction(self.action_appmenus)
+        self.context_menu.addAction(self.action_set_keyboard_layout)
+        self.context_menu.addMenu(self.blk_menu)
+        self.context_menu.addAction(self.action_toggle_audio_input)
+        self.context_menu.addSeparator()
+
+        self.context_menu.addAction(self.action_updatevm)
+        self.context_menu.addAction(self.action_run_command_in_vm)
+        self.context_menu.addAction(self.action_resumevm)
+        self.context_menu.addAction(self.action_startvm_tools_install)
+        self.context_menu.addAction(self.action_pausevm)
+        self.context_menu.addAction(self.action_shutdownvm)
+        self.context_menu.addAction(self.action_restartvm)
+        self.context_menu.addAction(self.action_killvm)
+        self.context_menu.addSeparator()
+
+        self.context_menu.addAction(self.action_clonevm)
+        self.context_menu.addAction(self.action_removevm)
+        self.context_menu.addSeparator()
+
+        self.context_menu.addMenu(self.logs_menu)
+        self.context_menu.addSeparator()
+
+        self.tools_context_menu = QtGui.QMenu(self)
+        self.tools_context_menu.addAction(self.action_toolbar)
+        self.tools_context_menu.addAction(self.action_menubar)
+
+        self.table_selection_changed()
+
+        self.connect(
+            self.table.horizontalHeader(),
+            QtCore.SIGNAL("sortIndicatorChanged(int, Qt::SortOrder)"),
+            self.sortIndicatorChanged)
+        self.connect(self.table,
+                     QtCore.SIGNAL("customContextMenuRequested(const QPoint&)"),
+                     self.open_context_menu)
+        self.connect(self.menubar,
+                     QtCore.SIGNAL("customContextMenuRequested(const QPoint&)"),
+                     lambda pos: self.open_tools_context_menu(self.menubar,
+                                                              pos))
+        self.connect(self.toolBar,
+                     QtCore.SIGNAL("customContextMenuRequested(const QPoint&)"),
+                     lambda pos: self.open_tools_context_menu(self.toolBar,
+                                                              pos))
+        self.connect(self.blk_menu, QtCore.SIGNAL("triggered(QAction *)"),
+                     self.attach_dettach_device_triggered)
+        self.connect(self.logs_menu, QtCore.SIGNAL("triggered(QAction *)"),
+                     self.show_log)
+
+        self.connect(self.searchbox, QtCore.SIGNAL("textChanged(const QString&)"),
+                     self.do_search)
+
+        self.table.setContentsMargins(0, 0, 0, 0)
+        self.centralwidget.layout().setContentsMargins(0, 0, 0, 0)
+        self.layout().setContentsMargins(0, 0, 0, 0)
+
+        self.connect(self.action_menubar, QtCore.SIGNAL("toggled(bool)"),
+                     self.showhide_menubar)
+        self.connect(self.action_toolbar, QtCore.SIGNAL("toggled(bool)"),
+                     self.showhide_toolbar)
+
+        self.register_dbus_watches()
+
+        self.load_manager_settings()
+
+        self.action_showallvms.setChecked(self.show_inactive_vms)
+        self.action_showinternalvms.setChecked(self.show_internal_vms)
+
+        self.fill_table()
+
+        self.counter = 0
+        self.update_size_on_disk = False
+        self.shutdown_monitor = {}
+        self.last_measure_results = {}
+        self.last_measure_time = time.time()
+        # noinspection PyCallByClass,PyTypeChecker
+        # QtCore.QTimer.singleShot(self.update_interval, self.update_table)
+
+        QubesDbusNotifyServerAdaptor(self)
+
+    def load_manager_settings(self): #TODO: replace with a Qt-based settings gizmo also make it work
+        # visible columns
+        # self.manager_settings.beginGroup("columns")
+        # for col in self.columns_indices.keys():
+        #     col_no = self.columns_indices[col]
+        #     visible = self.manager_settings.value(
+        #         col,
+        #         defaultValue=not self.table.isColumnHidden(col_no))
+        #     self.columns_actions[col_no].setChecked(visible)
+        # self.manager_settings.endGroup()
+        # self.show_inactive_vms = self.manager_settings.value(
+        #     "view/show_inactive_vms", defaultValue=False)
+        # self.show_internal_vms = self.manager_settings.value(
+        #     "view/show_internal_vms", defaultValue=False)
+        # self.sort_by_column = str(
+        #     self.manager_settings.value("view/sort_column",
+        #                                 defaultValue=self.sort_by_column))
+        # # self.sort_order = QtCore.Qt.SortOrder(  #TODO does not seem to work
+        # #     self.manager_settings.value("view/sort_order",
+        # #                                 defaultValue=self.sort_order)[
+        # #         0])
+        # self.table.sortItems(self.columns_indices[self.sort_by_column],
+        #                      self.sort_order)
+        # if not self.manager_settings.value("view/menubar_visible",
+        #                                    defaultValue=True):
+        #     self.action_menubar.setChecked(False)
+        # if not self.manager_settings.value("view/toolbar_visible",
+        #                                    defaultValue=True):
+        #     self.action_toolbar.setChecked(False)
+        # x = self.manager_settings.value('position/x', defaultValue=-1)[
+        #     0]
+        # y = self.manager_settings.value('position/y', defaultValue=-1)[
+        #     0]
+        # if x != -1 or y != -1:
+        #     self.move(x, y)
+        self.settings_loaded = True
+
+    def show(self):
+        super(VmManagerWindow, self).show()
+        self.screen_number = app.desktop().screenNumber(self)
+
+    # def set_table_geom_size(self):
+    #
+    #     desktop_width = app.desktop().availableGeometry(
+    #         self).width() - self.frame_width  # might be wrong...
+    #     desktop_height = app.desktop().availableGeometry(
+    #         self).height() - self.frame_height  # might be wrong...
+    #     desktop_height -= self.row_height  # UGLY! to somehow ommit taskbar...
+    #
+    #     w = self.table.horizontalHeader().length() + \
+    #         self.table.verticalScrollBar().width() + \
+    #         2 * self.table.frameWidth() + 1
+    #
+    #     h = self.table.horizontalHeader().height() + \
+    #         2 * self.table.frameWidth()
+    #
+    #     mainwindow_to_add = 0
+    #
+    #     available_space = desktop_height
+    #     if self.menubar.isVisible():
+    #         menubar_height = (self.menubar.sizeHint().height() +
+    #                           self.menubar.contentsMargins().top() +
+    #                           self.menubar.contentsMargins().bottom())
+    #         available_space -= menubar_height
+    #         mainwindow_to_add += menubar_height
+    #     if self.toolbar.isVisible():
+    #         toolbar_height = (self.toolbar.sizeHint().height() +
+    #                           self.toolbar.contentsMargins().top() +
+    #                           self.toolbar.contentsMargins().bottom())
+    #         available_space -= toolbar_height
+    #         mainwindow_to_add += toolbar_height
+    #     if w >= desktop_width:
+    #         available_space -= self.table.horizontalScrollBar().height()
+    #         h += self.table.horizontalScrollBar().height()
+    #
+    #     # account for search box height
+    #     available_space -= self.searchbox.height()
+    #     h += self.searchbox.height()
+    #
+    #     default_rows = int(available_space / self.row_height)
+    #
+    #     n = sum(not self.table.isRowHidden(row) for row in
+    #             range(self.table.rowCount()))
+    #
+    #     if n > default_rows:
+    #         h += default_rows * self.row_height
+    #         self.table.verticalScrollBar().show()
+    #     else:
+    #         h += n * self.row_height
+    #         self.table.verticalScrollBar().hide()
+    #         w -= self.table.verticalScrollBar().width()
+    #
+    #     w = min(desktop_width, w)
+    #
+    #     self.centralwidget.setFixedHeight(h)
+    #
+    #     h += mainwindow_to_add
+    #
+    #     self.setMaximumHeight(h)
+    #     self.setMinimumHeight(h)
+    #
+    #     self.table.setFixedWidth(w)
+    #     self.centralwidget.setFixedWidth(w)
+    #     # don't change the following two lines to setFixedWidth!
+    #     self.setMaximumWidth(w)
+    #     self.setMinimumWidth(w)
+
+    def moveEvent(self, event):
+        super(VmManagerWindow, self).moveEvent(event)
+        screen_number = app.desktop().screenNumber(self)
+        if self.screen_number != screen_number:
+            self.screen_changed = True
+            self.screen_number = screen_number
+        if self.settings_loaded:
+            self.manager_settings.setValue('position/x', self.x())
+            self.manager_settings.setValue('position/y', self.y())
+            # do not sync for performance reasons
+
+    def domain_state_changed_callback(self, name=None, uuid=None):
+        if name is not None:
+            vm = self.qvm_collection.domains[name]
+            if vm:
+                vm.refresh()
+
+    def get_vms_list(self):
+        # self.qvm_collection.lock_db_for_reading()
+        # self.qvm_collection.load()
+        # self.qvm_collection.unlock_db()
+
+        running_count = 0
+        internal_count = 0
+
+        vms_list = [vm for vm in self.qvm_collection.domains]
+        for vm in vms_list:
+            # vm.last_power_state = vm.get_power_state()
+            # vm.last_running = vm.is_running()
+            # if vm.last_running:
+            #     running_count += 1
+            # if vm.internal:
+            #     internal_count += 1
+            # vm.qubes_manager_state = {}
+            # self.update_audio_rec_info(vm)
+            # vm.qubes_manager_state[QMVmState.ErrorMsg] = self.vm_errors[
+            #     vm.qid] if vm.qid in self.vm_errors else None
+            running_count +=1
+
+        self.running_vms_count = running_count
+        self.internal_vms_count = internal_count
+        return vms_list
+
+    def fill_table(self):
+        # save current selection
+        row_index = self.table.currentRow()
+        selected_qid = -1
+        if row_index != -1:
+            vm_item = self.table.item(row_index, self.columns_indices["Name"])
+            if vm_item:
+                selected_qid = vm_item.qid
+
+        self.table.setSortingEnabled(False)
+        self.table.clearContents()
+        vms_list = self.get_vms_list()
+        self.table.setRowCount(len(vms_list))
+
+        vms_in_table = {}
+
+        row_no = 0
+        for vm in vms_list:
+            # if vm.internal:
+            #     continue
+            vm_row = VmRowInTable(vm, row_no, self.table)
+            vms_in_table[vm.qid] = vm_row
+
+            row_no += 1
+
+        self.table.setRowCount(row_no)
+        self.vms_list = vms_list
+        self.vms_in_table = vms_in_table
+        self.reload_table = False
+        if selected_qid in vms_in_table.keys():
+            self.table.setCurrentItem(
+                self.vms_in_table[selected_qid].name_widget)
+        self.table.setSortingEnabled(True)
+
+        self.showhide_vms()
+        # self.set_table_geom_size()
+
+    def showhide_vms(self):
+        if self.show_inactive_vms and self.show_internal_vms and not self.search:
+            for row_no in range(self.table.rowCount()):
+                self.table.setRowHidden(row_no, False)
+        else:
+            for row_no in range(self.table.rowCount()):
+                widget = self.table.cellWidget(row_no,
+                                               self.columns_indices["State"])
+                running = False
+                internal = False
+                # running = widget.vm.last_running
+                # internal = widget.vm.internal
+                name = widget.vm.name
+
+                show = (running or self.show_inactive_vms) and \
+                       (not internal or self.show_internal_vms) and \
+                       (self.search in widget.vm.name or not self.search)
+                self.table.setRowHidden(row_no, not show)
+
+    @QtCore.pyqtSlot(str)
+    def do_search(self, search):
+        self.search = str(search)
+        self.showhide_vms()
+        # self.set_table_geom_size()
+
+    @QtCore.pyqtSlot(name='on_action_search_triggered')
+    def action_search_triggered(self):
+        self.searchbox.setFocus()
+
+    def mark_table_for_update(self):
+        self.reload_table = True
+
+    # When calling update_table() directly, always use out_of_schedule=True!
+    def update_table(self, out_of_schedule=False):
+
+        update_devs = self.update_block_devices() or out_of_schedule
+        reload_table = self.reload_table
+
+        if manager_window.isVisible():
+            # some_vms_have_changed_power_state = False
+            # for vm in self.vms_list:
+            #     state = vm.get_power_state()
+            #     if vm.last_power_state != state:
+            #         if state == "Running" and \
+            #                 self.vm_errors.get(vm.qid, "") \
+            #                 .startswith("Error starting VM:"):
+            #             self.clear_error(vm.qid)
+            #         prev_running = vm.last_running
+            #         vm.last_power_state = state
+            #         vm.last_running = vm.is_running()
+            #         self.update_audio_rec_info(vm)
+            #         if not prev_running and vm.last_running:
+            #             self.running_vms_count += 1
+            #             some_vms_have_changed_power_state = True
+            #             # Clear error state when VM just started
+            #             self.clear_error(vm.qid)
+            #         elif prev_running and not vm.last_running:
+            #             # FIXME: remove when recAllowed state will be preserved
+            #             if self.vm_rec.has_key(vm.name):
+            #                 self.vm_rec.pop(vm.name)
+            #             self.running_vms_count -= 1
+            #             some_vms_have_changed_power_state = True
+            #     else:
+            #         # pulseaudio agent register itself some time after VM
+            #         # startup
+            #         if state == "Running" and not vm.qubes_manager_state[
+            #                 QMVmState.AudioRecAvailable]:
+            #             self.update_audio_rec_info(vm)
+            #     if self.vm_errors.get(vm.qid, "") == \
+            #             "Error starting VM: Cannot execute qrexec-daemon!" \
+            #             and vm.is_qrexec_running():
+            #         self.clear_error(vm.qid)
+            pass
+
+            if self.screen_changed:
+                reload_table = True
+                self.screen_changed = False
+
+            if reload_table:
+                self.fill_table()
+                update_devs = True
+
+            # if not self.show_inactive_vms and \
+            #         some_vms_have_changed_power_state:
+            #     self.showhide_vms()
+            #     self.set_table_geom_size()
+
+            # if self.sort_by_column == \
+            #         "State" and some_vms_have_changed_power_state:
+            #     self.table.sortItems(self.columns_indices[self.sort_by_column],
+            #                          self.sort_order)
+
+            blk_visible = None
+            rows_with_blk = None
+            # if update_devs:
+            #     rows_with_blk = []
+            #     self.blk_manager.blk_lock.acquire()
+            #     for d in self.blk_manager.attached_devs:
+            #         rows_with_blk.append(
+            #             self.blk_manager.attached_devs[d]['attached_to'][
+            #                 'vm'].qid)
+            #     self.blk_manager.blk_lock.release()
+
+            if (not self.table.isColumnHidden(self.columns_indices['Size'])) and \
+                    self.counter % 60 == 0 or out_of_schedule:
+                self.update_size_on_disk = True
+
+            if self.counter % 3 == 0 or out_of_schedule:
+                # (self.last_measure_time, self.last_measure_results) = \
+                #     qubes_host.measure_cpu_usage(self.qvm_collection,
+                #                                  self.last_measure_results,
+                #                                  self.last_measure_time)
+
+                for vm_row in self.vms_in_table.values():
+                    cur_cpu_load = None
+                    # if vm_row.vm.get_xid() in self.last_measure_results:
+                    #     cur_cpu_load = self.last_measure_results[vm_row.vm.xid][
+                    #         'cpu_usage']
+                    # else:
+                    #     cur_cpu_load = 0
+
+                    if rows_with_blk is not None:
+                        if vm_row.vm.qid in rows_with_blk:
+                            blk_visible = True
+                        else:
+                            blk_visible = False
+
+                    vm_row.update(blk_visible=blk_visible,
+                                  cpu_load=cur_cpu_load,
+                                  update_size_on_disk=self.update_size_on_disk,
+                                  rec_visible=self.vm_rec.get(vm_row.vm.name,
+                                                              False))
+
+            else:
+                for vm_row in self.vms_in_table.values():
+                    if rows_with_blk is not None:
+                        if vm_row.vm.qid in rows_with_blk:
+                            blk_visible = True
+                        else:
+                            blk_visible = False
+
+                    vm_row.update(blk_visible=blk_visible,
+                                  update_size_on_disk=self.update_size_on_disk,
+                                  rec_visible=self.vm_rec.get(vm_row.vm.name,
+                                                              False))
+
+            if self.sort_by_column in ["CPU", "CPU Graph", "MEM", "MEM Graph",
+                                       "State", "Size", "Internal"]:
+                # "State": needed to sort after reload (fill_table sorts items
+                # with setSortingEnabled, but by that time the widgets values
+                # are not correct yet).
+                self.table.sortItems(self.columns_indices[self.sort_by_column],
+                                     self.sort_order)
+
+            self.table_selection_changed()
+
+        self.update_size_on_disk = False
+        if not out_of_schedule:
+            self.counter += 1
+            # noinspection PyCallByClass,PyTypeChecker
+            # QtCore.QTimer.singleShot(self.update_interval, self.update_table)
+
+    def update_block_devices(self):
+        pass
+        # res, msg = self.blk_manager.check_for_updates()
+        # if msg is not None and len(msg) > 0:
+        #     trayIcon.showMessage('\n'.join(msg), msecs=5000)
+        # return res
+
+    # noinspection PyPep8Naming
+    @QtCore.pyqtSlot(bool, str)
+    def recAllowedChanged(self, state, vmname):
+        self.vm_rec[str(vmname)] = bool(state)
+
+    def register_dbus_watches(self):
+        global session_bus
+
+        if not session_bus:
+            session_bus = QDBusConnection.sessionBus()
+
+        if not session_bus.connect("",  # service
+                                  "",  # path
+                                   "org.QubesOS.Audio",  # interface
+                                   "RecAllowedChanged",  # name
+                                   self.recAllowedChanged):  # slot
+            print(session_bus.lastError().message())
+
+    # noinspection PyPep8Naming
+    def sortIndicatorChanged(self, column, order):
+        self.sort_by_column = [name for name in self.columns_indices.keys() if
+                               self.columns_indices[name] == column][0]
+        self.sort_order = order
+        if self.settings_loaded:
+            self.manager_settings.setValue('view/sort_column',
+                                           self.sort_by_column)
+            self.manager_settings.setValue('view/sort_order', self.sort_order)
+            self.manager_settings.sync()
+
+    def table_selection_changed(self):
+
+        vm = self.get_selected_vm()
+
+        if vm is not None:
+            # Update available actions:
+            self.action_settings.setEnabled(vm.qid != 0)
+            self.action_removevm.setEnabled(
+                not vm.installed_by_rpm and not vm.last_running)
+            self.action_clonevm.setEnabled(
+                not vm.last_running and not vm.is_netvm())
+            # self.action_resumevm.setEnabled(not vm.last_running or
+            #                                 vm.last_power_state == "Paused")
+            try:
+                pass
+                # self.action_startvm_tools_install.setVisible(
+                #     isinstance(vm, QubesHVm))
+            except NameError:
+                # ignore non existing QubesHVm
+                pass
+            self.action_startvm_tools_install.setEnabled(not vm.last_running)
+            # self.action_pausevm.setEnabled(
+            #     vm.last_running and
+            #     vm.last_power_state != "Paused" and
+            #     vm.qid != 0)
+            # self.action_shutdownvm.setEnabled(
+            #     vm.last_running and
+            #     vm.last_power_state != "Paused" and
+            #     vm.qid != 0)
+            # self.action_restartvm.setEnabled(
+            #     vm.last_running and
+            #     vm.last_power_state != "Paused" and
+            #     vm.qid != 0 and
+            #     not vm.is_disposablevm())
+            # self.action_killvm.setEnabled((vm.last_running or
+            #                                vm.last_power_state == "Paused") and
+            #                               vm.qid != 0)
+            # self.action_appmenus.setEnabled(vm.qid != 0 and
+            #                                 not vm.internal and
+            #                                 not vm.is_disposablevm())
+            # self.action_editfwrules.setEnabled(vm.is_networked() and not (
+            #     vm.is_netvm() and not vm.is_proxyvm()))
+            # self.action_updatevm.setEnabled(vm.is_updateable() or vm.qid == 0)
+            # self.action_toggle_audio_input.setEnabled(
+            #     vm.qubes_manager_state[QMVmState.AudioRecAvailable])
+            # self.action_run_command_in_vm.setEnabled(
+            #     not vm.last_power_state == "Paused" and vm.qid != 0)
+            # self.action_set_keyboard_layout.setEnabled(
+            #     vm.qid != 0 and
+            #     vm.last_running and
+            #     vm.last_power_state != "Paused")
+        else:
+            self.action_settings.setEnabled(False)
+            self.action_removevm.setEnabled(False)
+            self.action_startvm_tools_install.setVisible(False)
+            self.action_startvm_tools_install.setEnabled(False)
+            self.action_clonevm.setEnabled(False)
+            self.action_resumevm.setEnabled(False)
+            self.action_pausevm.setEnabled(False)
+            self.action_shutdownvm.setEnabled(False)
+            self.action_restartvm.setEnabled(False)
+            self.action_killvm.setEnabled(False)
+            self.action_appmenus.setEnabled(False)
+            self.action_editfwrules.setEnabled(False)
+            self.action_updatevm.setEnabled(False)
+            self.action_toggle_audio_input.setEnabled(False)
+            self.action_run_command_in_vm.setEnabled(False)
+            self.action_set_keyboard_layout.setEnabled(False)
+
+    def closeEvent(self, event):
+        # There is something borked in Qt,
+        # as the logic here is inverted on X11
+        if event.spontaneous():
+            self.hide()
+            event.ignore()
+
+    def set_error(self, qid, message):
+        for vm in self.vms_list:
+            if vm.qid == qid:
+                vm.qubes_manager_state[QMVmState.ErrorMsg] = message
+        # Store error in separate dict to make it immune to VM list reload
+        self.vm_errors[qid] = str(message)
+
+    def clear_error(self, qid):
+        self.vm_errors.pop(qid, None)
+        for vm in self.vms_list:
+            if vm.qid == qid:
+                vm.qubes_manager_state[QMVmState.ErrorMsg] = None
+
+    def clear_error_exact(self, qid, message):
+        for vm in self.vms_list:
+            if vm.qid == qid:
+                if vm.qubes_manager_state[QMVmState.ErrorMsg] == message:
+                    vm.qubes_manager_state[QMVmState.ErrorMsg] = None
+                    self.vm_errors.pop(qid, None)
+
+    @QtCore.pyqtSlot(name='on_action_createvm_triggered')
+    def action_createvm_triggered(self):
+        pass
+        # dialog = NewVmDlg(app, self.qvm_collection, trayIcon)
+        # dialog.exec_()
+
+    def get_selected_vm(self):
+        # vm selection relies on the VmInfo widget's value used
+        # for sorting by VM name
+        row_index = self.table.currentRow()
+        if row_index != -1:
+            vm_item = self.table.item(row_index, self.columns_indices["Name"])
+            # here is possible race with update_table timer so check
+            # if really got the item
+            if vm_item is None:
+                return None
+            qid = vm_item.qid
+            assert self.vms_in_table[qid] is not None
+            vm = self.vms_in_table[qid].vm
+            return vm
+        else:
+            return None
+
+    @QtCore.pyqtSlot(name='on_action_removevm_triggered')
+    def action_removevm_triggered(self):
+
+        vm = self.get_selected_vm()
+        assert not vm.is_running()
+        assert not vm.installed_by_rpm
+
+        vm = self.qvm_collection[vm.qid]
+
+        if vm.is_template():
+            dependent_vms = self.qvm_collection.domains[vm.qid]
+            if len(dependent_vms) > 0:
+                QtGui.QMessageBox.warning(
+                    None, self.tr("Warning!"),
+                    self.tr("This Template VM cannot be removed, because there is at "
+                    "least one AppVM that is based on it.<br>"
+                    "<small>If you want to remove this Template VM and all "
+                    "the AppVMs based on it,"
+                    "you should first remove each individual AppVM that uses "
+                    "this template.</small>"))
+
+                return
+
+        (requested_name, ok) = QtGui.QInputDialog.getText(
+            None, self.tr("VM Removal Confirmation"),
+            self.tr("Are you sure you want to remove the VM <b>'{0}'</b>?<br>"
+            "All data on this VM's private storage will be lost!<br><br>"
+            "Type the name of the VM (<b>{1}</b>) below to confirm:")
+            .format(vm.name, vm.name))
+
+        if not ok:
+            # user clicked cancel
+            return
+
+        elif requested_name != vm.name:
+            # name did not match
+            QtGui.QMessageBox.warning(None, self.tr("VM removal confirmation failed"),
+                self.tr("Entered name did not match! Not removing {0}.").format(vm.name))
+            return
+
+        else:
+            # remove the VM
+            t_monitor = thread_monitor.ThreadMonitor()
+            thread = threading.Thread(target=self.do_remove_vm,
+                                      args=(vm, t_monitor))
+            thread.daemon = True
+            thread.start()
+
+            progress = QtGui.QProgressDialog(
+                self.tr("Removing VM: <b>{0}</b>...").format(vm.name), "", 0, 0)
+            progress.setCancelButton(None)
+            progress.setModal(True)
+            progress.show()
+
+            while not thread_monitor.is_finished():
+                app.processEvents()
+                time.sleep(0.1)
+
+            progress.hide()
+
+            if thread_monitor.success:
+                trayIcon.showMessage(
+                    self.tr("VM '{0}' has been removed.").format(vm.name), msecs=3000)
+            else:
+                QtGui.QMessageBox.warning(None, self.tr("Error removing VM!"),
+                                    self.tr("ERROR: {0}").format(
+                                        thread_monitor.error_msg))
+
+    @staticmethod
+    def do_remove_vm(vm, thread_monitor):
+        qc = Qubes()
+        try:
+            vm = qc.domains[vm.qid]
+
+            # TODO: the following two conditions should really be checked
+            # by qvm_collection.pop() overload...
+            if vm.is_template() and \
+                    qc.default_template_qid == vm.qid:
+                qc.default_template_qid = None
+            if vm.is_netvm() and \
+                    qc.default_netvm_qid == vm.qid:
+                qc.default_netvm_qid = None
+
+            # qc.pop(vm.qid)
+            # qc.save()
+            vm.remove_from_disk()
+        except Exception as ex:
+            thread_monitor.set_error_msg(str(ex))
+
+        thread_monitor.set_finished()
+
+    @QtCore.pyqtSlot(name='on_action_clonevm_triggered')
+    def action_clonevm_triggered(self):
+        vm = self.get_selected_vm()
+
+        name_number = 1
+        name_format = vm.name + '-clone-%d'
+        while self.qvm_collection.domains[name_format % name_number]:
+            name_number += 1
+
+        (clone_name, ok) = QtGui.QInputDialog.getText(
+            self, self.tr('Qubes clone VM'),
+            self.tr('Enter name for VM <b>{}</b> clone:').format(vm.name),
+            text=(name_format % name_number))
+        if not ok or clone_name == "":
+            return
+
+        t_monitor = thread_monitor.ThreadMonitor()
+        thread = threading.Thread(target=self.do_clone_vm,
+                                  args=(vm, str(clone_name), t_monitor))
+        thread.daemon = True
+        thread.start()
+
+        progress = QtGui.QProgressDialog(
+            self.tr("Cloning VM <b>{0}</b> to <b>{1}</b>...").format(vm.name,
+                                                            clone_name), "", 0,
+            0)
+        progress.setCancelButton(None)
+        progress.setModal(True)
+        progress.show()
+
+        while not t_monitor.is_finished():
+            app.processEvents()
+            time.sleep(0.2)
+
+        progress.hide()
+
+        if not t_monitor.success:
+            QtGui.QMessageBox.warning(None, self.tr("Error while cloning VM"),
+                                self.tr("Exception while cloning:<br>{0}").format(
+                                    t_monitor.error_msg))
+
+    @staticmethod
+    def do_clone_vm(vm, dst_name, thread_monitor):
+        dst_vm = None
+        qc = Qubes()
+        try:
+            src_vm = qc[vm.qid]
+
+            dst_vm = qc.add_new_vm(src_vm.__class__.__name__,
+                                   name=dst_name,
+                                   template=src_vm.template,
+                                   installed_by_rpm=False)
+
+            dst_vm.clone_attrs(src_vm)
+            dst_vm.clone_disk_files(src_vm=src_vm, verbose=False)
+            qc.save()
+        except Exception as ex:
+            if dst_vm:
+                qc.pop(dst_vm.qid)
+                dst_vm.remove_from_disk()
+            thread_monitor.set_error_msg(str(ex))
+        thread_monitor.set_finished()
+
+    @QtCore.pyqtSlot(name='on_action_resumevm_triggered')
+    def action_resumevm_triggered(self):
+        vm = self.get_selected_vm()
+
+        # if vm.get_power_state() in ["Paused", "Suspended"]:
+        #     try:
+        #         vm.resume()
+        #     except Exception as ex:
+        #         QtGui.QMessageBox.warning(None, self.tr("Error unpausing VM!"),
+        #                             self.tr("ERROR: {0}").format(ex))
+        #     return
+
+
+        self.start_vm(vm)
+
+    def start_vm(self, vm):
+        assert not vm.is_running()
+        t_monitor = thread_monitor.ThreadMonitor()
+        thread = threading.Thread(target=self.do_start_vm,
+                                  args=(vm, t_monitor))
+        thread.daemon = True
+        thread.start()
+
+        trayIcon.showMessage(self.tr("Starting '{0}'...").format(vm.name), msecs=3000)
+
+        while not t_monitor.is_finished():
+            app.processEvents()
+            time.sleep(0.1)
+
+        if t_monitor.success:
+            trayIcon.showMessage(self.tr("VM '{0}' has been started.").format(vm.name),
+                                 msecs=3000)
+        else:
+            trayIcon.showMessage(
+                self.tr("Error starting VM <b>'{0}'</b>: {1}").format(
+                    vm.name, t_monitor.error_msg),
+                msecs=3000)
+            self.set_error(vm.qid,
+                           self.tr("Error starting VM: %s") % t_monitor.error_msg)
+
+    @staticmethod
+    def do_start_vm(vm, t_monitor):
+        try:
+            vm.start()
+        except Exception as ex:
+            t_monitor.set_error_msg(str(ex))
+            t_monitor.set_finished()
+            return
+
+        t_monitor.set_finished()
+
+    @QtCore.pyqtSlot(name='on_action_startvm_tools_install_triggered')
+    def action_startvm_tools_install_triggered(self):
+        vm = self.get_selected_vm()
+        assert not vm.is_running()
+
+        windows_tools_installed = \
+            os.path.exists('/usr/lib/qubes/qubes-windows-tools.iso')
+        if not windows_tools_installed:
+            msg = QtGui.QMessageBox()
+            msg.warning(self, self.tr("Error starting VM!"),
+                self.tr("You need to install 'qubes-windows-tools' "
+                "package to use this option"))
+            return
+
+
+        t_monitor = thread_monitor.ThreadMonitor()
+        thread = threading.Thread(target=self.do_start_vm_tools_install,
+                                  args=(vm, t_monitor))
+        thread.daemon = True
+        thread.start()
+
+        trayIcon.showMessage(self.tr("Starting '{0}'...").format(vm.name), msecs=3000)
+
+        while not t_monitor.is_finished():
+            app.processEvents()
+            time.sleep(0.1)
+
+        if t_monitor.success:
+            trayIcon.showMessage(self.tr("VM '{0}' has been started. Start Qubes "
+                                 "Tools installation from attached CD")
+                                 .format(vm.name), msecs=3000)
+        else:
+            trayIcon.showMessage(
+                self.tr("Error starting VM <b>'{0}'</b>: {1}")
+                    .format(vm.name, t_monitor.error_msg),
+                msecs=3000)
+            self.set_error(vm.qid,
+                           self.tr("Error starting VM: %s") % t_monitor.error_msg)
+
+    # noinspection PyMethodMayBeStatic
+    def do_start_vm_tools_install(self, vm, thread_monitor):
+        prev_drive = vm.drive
+        try:
+            vm.drive = 'cdrom:dom0:/usr/lib/qubes/qubes-windows-tools.iso'
+            vm.start()
+        except Exception as ex:
+            thread_monitor.set_error_msg(str(ex))
+            thread_monitor.set_finished()
+            return
+        finally:
+            vm.drive = prev_drive
+
+        thread_monitor.set_finished()
+
+    @QtCore.pyqtSlot(name='on_action_pausevm_triggered')
+    def action_pausevm_triggered(self):
+        vm = self.get_selected_vm()
+        assert vm.is_running()
+        try:
+            vm.pause()
+        except Exception as ex:
+            QtGui.QMessageBox.warning(None, self.tr("Error pausing VM!"),
+                                self.tr("ERROR: {0}").format(ex))
+            return
+
+    @QtCore.pyqtSlot(name='on_action_shutdownvm_triggered')
+    def action_shutdownvm_triggered(self):
+        vm = self.get_selected_vm()
+        assert vm.is_running()
+
+        # self.blk_manager.check_if_serves_as_backend(vm)
+
+        reply = QtGui.QMessageBox.question(
+            None, self.tr("VM Shutdown Confirmation"),
+            self.tr("Are you sure you want to power down the VM <b>'{0}'</b>?<br>"
+            "<small>This will shutdown all the running applications "
+            "within this VM.</small>").format(vm.name),
+            QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel)
+
+        app.processEvents()
+
+        if reply == QtGui.QMessageBox.Yes:
+            self.shutdown_vm(vm)
+
+    def shutdown_vm(self, vm, shutdown_time=vm_shutdown_timeout,
+            check_time=vm_restart_check_timeout, and_restart=False):
+        try:
+            vm.shutdown()
+        except Exception as ex:
+            QtGui.QMessageBox.warning(None, self.tr("Error shutting down VM!"),
+                                self.tr("ERROR: {0}").format(ex))
+            return
+
+        trayIcon.showMessage(self.tr("VM '{0}' is shutting down...").format(vm.name),
+                             msecs=3000)
+
+        self.shutdown_monitor[vm.qid] = VmShutdownMonitor(vm, shutdown_time,
+            check_time, and_restart, self)
+        # noinspection PyCallByClass,PyTypeChecker
+        QtCore.QTimer.singleShot(check_time, self.shutdown_monitor[
+            vm.qid].check_if_vm_has_shutdown)
+
+    @QtCore.pyqtSlot(name='on_action_restartvm_triggered')
+    def action_restartvm_triggered(self):
+        vm = self.get_selected_vm()
+        assert vm.is_running()
+
+        # self.blk_manager.check_if_serves_as_backend(vm)
+
+        reply = QtGui.QMessageBox.question(
+            None, self.tr("VM Restart Confirmation"),
+            self.tr("Are you sure you want to restart the VM <b>'{0}'</b>?<br>"
+            "<small>This will shutdown all the running applications "
+            "within this VM.</small>").format(vm.name),
+            QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel)
+
+        app.processEvents()
+
+        if reply == QtGui.QMessageBox.Yes:
+            self.shutdown_vm(vm, and_restart=True)
+
+    @QtCore.pyqtSlot(name='on_action_killvm_triggered')
+    def action_killvm_triggered(self):
+        vm = self.get_selected_vm()
+        assert vm.is_running() or vm.is_paused()
+
+        reply = QtGui.QMessageBox.question(
+            None, self.tr("VM Kill Confirmation"),
+            self.tr("Are you sure you want to kill the VM <b>'{0}'</b>?<br>"
+            "<small>This will end <b>(not shutdown!)</b> all the running "
+            "applications within this VM.</small>").format(vm.name),
+            QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel,
+            QtGui.QMessageBox.Cancel)
+
+        app.processEvents()
+
+        if reply == QtGui.QMessageBox.Yes:
+            try:
+                vm.force_shutdown()
+            except Exception as ex:
+                QtGui.QMessageBox.critical(
+                    None, self.tr("Error while killing VM!"),
+                    self.tr("<b>An exception ocurred while killing {0}.</b><br>"
+                    "ERROR: {1}").format(vm.name, ex))
+                return
+
+            trayIcon.showMessage(self.tr("VM '{0}' killed!")
+                .format(vm.name), msecs=3000)
+
+    @QtCore.pyqtSlot(name='on_action_settings_triggered')
+    def action_settings_triggered(self):
+        pass
+        #TODO this should work, actually, but please not now
+        # vm = self.get_selected_vm()
+        # settings_window = VMSettingsWindow(vm, app, self.qvm_collection,
+        #                                    "basic")
+        # settings_window.exec_()
+
+    @QtCore.pyqtSlot(name='on_action_appmenus_triggered')
+    def action_appmenus_triggered(self):
+        pass
+        #TODO this should work, actually, but please not now
+        # vm = self.get_selected_vm()
+        # settings_window = VMSettingsWindow(vm, app, self.qvm_collection,
+        #                                    "applications")
+        # settings_window.exec_()
+
+    def update_audio_rec_info(self, vm):
+        vm.qubes_manager_state[QMVmState.AudioRecAvailable] = (
+            session_bus.interface().isServiceRegistered(
+                'org.QubesOS.Audio.%s' % vm.name).value())
+        if vm.qubes_manager_state[QMVmState.AudioRecAvailable]:
+            vm.qubes_manager_state[
+                QMVmState.AudioRecAllowed] = self.get_audio_rec_allowed(vm.name)
+        else:
+            vm.qubes_manager_state[QMVmState.AudioRecAllowed] = False
+
+    # noinspection PyMethodMayBeStatic
+    def get_audio_rec_allowed(self, vmname):
+        properties = QDBusInterface('org.QubesOS.Audio.%s' % vmname,
+                                    '/org/qubesos/audio',
+                                    'org.freedesktop.DBus.Properties',
+                                    session_bus)
+
+        current_audio = properties.call('Get', 'org.QubesOS.Audio',
+                                        'RecAllowed')
+        if current_audio.type() == current_audio.ReplyMessage:
+            value = current_audio.arguments()[0].toPyObject()
+            return bool(value)
+        return False
+
+    @QtCore.pyqtSlot(name='on_action_toggle_audio_input_triggered')
+    def action_toggle_audio_input_triggered(self):
+        vm = self.get_selected_vm()
+        properties = QDBusInterface('org.QubesOS.Audio.%s' % vm.name,
+                                    '/org/qubesos/audio',
+                                    'org.freedesktop.DBus.Properties',
+                                    session_bus)
+        properties.call('Set', 'org.QubesOS.Audio', 'RecAllowed',
+                        QDBusVariant(not self.get_audio_rec_allowed(vm.name)))
+        # icon will be updated based on dbus signal
+
+    @QtCore.pyqtSlot(name='on_action_updatevm_triggered')
+    def action_updatevm_triggered(self):
+        vm = self.get_selected_vm()
+
+        if not vm.is_running():
+            reply = QtGui.QMessageBox.question(
+                None, self.tr("VM Update Confirmation"),
+                self.tr("<b>{0}</b><br>The VM has to be running to be updated.<br>"
+                "Do you want to start it?<br>").format(vm.name),
+                QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel)
+            if reply != QtGui.QMessageBox.Yes:
+                return
+            trayIcon.showMessage(self.tr("Starting '{0}'...").format(vm.name),
+                                 msecs=3000)
+
+        app.processEvents()
+
+        t_monitor = thread_monitor.ThreadMonitor()
+        thread = threading.Thread(target=self.do_update_vm,
+                                  args=(vm, t_monitor))
+        thread.daemon = True
+        thread.start()
+
+        progress = QtGui.QProgressDialog(
+                self.tr("<b>{0}</b><br>Please wait for the updater to "
+                "launch...").format(vm.name), "", 0, 0)
+        progress.setCancelButton(None)
+        progress.setModal(True)
+        progress.show()
+
+        while not t_monitor.is_finished():
+            app.processEvents()
+            time.sleep(0.2)
+
+        progress.hide()
+
+        if vm.qid != 0:
+            if not t_monitor.success:
+                QtGui.QMessageBox.warning(None, self.tr("Error VM update!"),
+                                    self.tr("ERROR: {0}").format(
+                                        t_monitor.error_msg))
+
+    @staticmethod
+    def do_update_vm(vm, thread_monitor):
+        try:
+            if vm.qid == 0:
+                subprocess.check_call(
+                    ["/usr/bin/qubes-dom0-update", "--clean", "--gui"])
+            else:
+                if not vm.is_running():
+                    trayIcon.showMessage(
+                        "Starting the '{0}' VM...".format(vm.name),
+                        msecs=3000)
+                    vm.start()
+                vm.run_service("qubes.InstallUpdatesGUI", gui=True,
+                               user="root", wait=False)
+        except Exception as ex:
+            thread_monitor.set_error_msg(str(ex))
+            thread_monitor.set_finished()
+            return
+        thread_monitor.set_finished()
+
+
+    @QtCore.pyqtSlot(name='on_action_run_command_in_vm_triggered')
+    def action_run_command_in_vm_triggered(self):
+        vm = self.get_selected_vm()
+
+        (command_to_run, ok) = QtGui.QInputDialog.getText(
+            self, self.tr('Qubes command entry'),
+            self.tr('Run command in <b>{}</b>:').format(vm.name))
+        if not ok or command_to_run == "":
+            return
+        t_monitor = thread_monitor.ThreadMonitor()
+        thread = threading.Thread(target=self.do_run_command_in_vm, args=(
+            vm, command_to_run, t_monitor))
+        thread.daemon = True
+        thread.start()
+
+        while not t_monitor.is_finished():
+            app.processEvents()
+            time.sleep(0.2)
+
+        if not t_monitor.success:
+            QtGui.QMessageBox.warning(None, self.tr("Error while running command"),
+                self.tr("Exception while running command:<br>{0}").format(
+                    t_monitor.error_msg))
+
+    @staticmethod
+    def do_run_command_in_vm(vm, command_to_run, thread_monitor):
+        try:
+            vm.run(command_to_run, verbose=False, autostart=True,
+                   notify_function=lambda lvl, msg: trayIcon.showMessage(
+                       msg, msecs=3000))
+        except Exception as ex:
+            thread_monitor.set_error_msg(str(ex))
+        thread_monitor.set_finished()
+
+    @QtCore.pyqtSlot(name='on_action_set_keyboard_layout_triggered')
+    def action_set_keyboard_layout_triggered(self):
+        vm = self.get_selected_vm()
+        vm.run('qubes-change-keyboard-layout', verbose=False)
+
+    @QtCore.pyqtSlot(name='on_action_showallvms_triggered')
+    def action_showallvms_triggered(self):
+        self.show_inactive_vms = self.action_showallvms.isChecked()
+
+        self.showhide_vms()
+        # self.set_table_geom_size()
+        if self.settings_loaded:
+            self.manager_settings.setValue('view/show_inactive_vms',
+                                           self.show_inactive_vms)
+            self.manager_settings.sync()
+
+    @QtCore.pyqtSlot(name='on_action_showinternalvms_triggered')
+    def action_showinternalvms_triggered(self):
+        self.show_internal_vms = self.action_showinternalvms.isChecked()
+
+        self.showhide_vms()
+        # self.set_table_geom_size()
+        if self.settings_loaded:
+            self.manager_settings.setValue('view/show_internal_vms',
+                                           self.show_internal_vms)
+            self.manager_settings.sync()
+
+    @QtCore.pyqtSlot(name='on_action_editfwrules_triggered')
+    def action_editfwrules_triggered(self):
+        pass
+        #TODO: revive
+        # vm = self.get_selected_vm()
+        # settings_window = VMSettingsWindow(vm, app, self.qvm_collection,
+        #                                    "firewall")
+        # settings_window.exec_()
+
+    @QtCore.pyqtSlot(name='on_action_global_settings_triggered')
+    def action_global_settings_triggered(self):
+        pass
+        #TODO: revive
+        # global_settings_window = GlobalSettingsWindow(app, self.qvm_collection)
+        # global_settings_window.exec_()
+
+    @QtCore.pyqtSlot(name='on_action_show_network_triggered')
+    def action_show_network_triggered(self):
+        pass
+        #TODO: revive
+        # network_notes_dialog = NetworkNotesDialog()
+        # network_notes_dialog.exec_()
+
+    @QtCore.pyqtSlot(name='on_action_restore_triggered')
+    def action_restore_triggered(self):
+        pass
+        #TODO: revive
+        # restore_window = RestoreVMsWindow(app, self.qvm_collection,
+        #                                   self.blk_manager)
+        # restore_window.exec_()
+
+    @QtCore.pyqtSlot(name='on_action_backup_triggered')
+    def action_backup_triggered(self):
+        pass
+        #TODO: revive
+        # backup_window = BackupVMsWindow(app, self.qvm_collection,
+        #                                 self.blk_manager, self.shutdown_vm)
+        # backup_window.exec_()
+
+    def showhide_menubar(self, checked):
+        self.menubar.setVisible(checked)
+        # self.set_table_geom_size()
+        if not checked:
+            self.context_menu.addAction(self.action_menubar)
+        else:
+            self.context_menu.removeAction(self.action_menubar)
+        if self.settings_loaded:
+            self.manager_settings.setValue('view/menubar_visible', checked)
+            self.manager_settings.sync()
+
+    def showhide_toolbar(self, checked):
+        self.toolbar.setVisible(checked)
+        # self.set_table_geom_size()
+        if not checked:
+            self.context_menu.addAction(self.action_toolbar)
+        else:
+            self.context_menu.removeAction(self.action_toolbar)
+        if self.settings_loaded:
+            self.manager_settings.setValue('view/toolbar_visible', checked)
+            self.manager_settings.sync()
+
+    def showhide_column(self, col_num, show):
+        self.table.setColumnHidden(col_num, not show)
+        # self.set_table_geom_size()
+        val = 1 if show else -1
+        self.visible_columns_count += val
+
+        if self.visible_columns_count == 1:
+            # disable hiding the last one
+            for c in self.columns_actions:
+                if self.columns_actions[c].isChecked():
+                    self.columns_actions[c].setEnabled(False)
+                    break
+        elif self.visible_columns_count == 2 and val == 1:
+            # enable hiding previously disabled column
+            for c in self.columns_actions:
+                if not self.columns_actions[c].isEnabled():
+                    self.columns_actions[c].setEnabled(True)
+                    break
+
+        if self.settings_loaded:
+            col_name = [name for name in self.columns_indices.keys() if
+                        self.columns_indices[name] == col_num][0]
+            self.manager_settings.setValue('columns/%s' % col_name, show)
+            self.manager_settings.sync()
+
+    def on_action_vm_type_toggled(self, checked):
+        self.showhide_column(self.columns_indices['Type'], checked)
+
+    def on_action_label_toggled(self, checked):
+        self.showhide_column(self.columns_indices['Label'], checked)
+
+    def on_action_name_toggled(self, checked):
+        self.showhide_column(self.columns_indices['Name'], checked)
+
+    def on_action_state_toggled(self, checked):
+        self.showhide_column(self.columns_indices['State'], checked)
+
+    def on_action_internal_toggled(self, checked):
+        self.showhide_column(self.columns_indices['Internal'], checked)
+
+    def on_action_ip_toggled(self, checked):
+        self.showhide_column(self.columns_indices['IP'], checked)
+
+    def on_action_backups_toggled(self, checked):
+        self.showhide_column(self.columns_indices['Backups'], checked)
+
+    def on_action_last_backup_toggled(self, checked):
+        self.showhide_column(self.columns_indices['Last backup'], checked)
+
+    def on_action_template_toggled(self, checked):
+        self.showhide_column(self.columns_indices['Template'], checked)
+
+    def on_action_netvm_toggled(self, checked):
+        self.showhide_column(self.columns_indices['NetVM'], checked)
+
+    def on_action_cpu_toggled(self, checked):
+        self.showhide_column(self.columns_indices['CPU'], checked)
+
+    def on_action_cpu_graph_toggled(self, checked):
+        self.showhide_column(self.columns_indices['CPU Graph'], checked)
+
+    def on_action_mem_toggled(self, checked):
+        self.showhide_column(self.columns_indices['MEM'], checked)
+
+    def on_action_mem_graph_toggled(self, checked):
+        self.showhide_column(self.columns_indices['MEM Graph'], checked)
+
+    def on_action_size_on_disk_toggled(self, checked):
+        self.showhide_column(self.columns_indices['Size'], checked)
+
+    @QtCore.pyqtSlot(name='on_action_about_qubes_triggered')
+    def action_about_qubes_triggered(self):
+        about = AboutDialog()
+        about.exec_()
+
+    def createPopupMenu(self):
+        menu = QtGui.QMenu()
+        menu.addAction(self.action_toolbar)
+        menu.addAction(self.action_menubar)
+        return menu
+
+    def open_tools_context_menu(self, widget, point):
+        self.tools_context_menu.exec_(widget.mapToGlobal(point))
+
+    @QtCore.pyqtSlot('const QPoint&')
+    def open_context_menu(self, point):
+        vm = self.get_selected_vm()
+
+        running = vm.is_running()
+
+        # logs menu
+        self.logs_menu.clear()
+
+        if vm.qid == 0:
+            logfiles = ["/var/log/xen/console/hypervisor.log"]
+        else:
+            logfiles = [
+                "/var/log/xen/console/guest-" + vm.name + ".log",
+                "/var/log/xen/console/guest-" + vm.name + "-dm.log",
+                "/var/log/qubes/guid." + vm.name + ".log",
+                "/var/log/qubes/qrexec." + vm.name + ".log",
+            ]
+
+        menu_empty = True
+        for logfile in logfiles:
+            if os.path.exists(logfile):
+                action = self.logs_menu.addAction(QtGui.QIcon(":/log.png"), logfile)
+                action.setData(QtCore.QVariant(logfile))
+                menu_empty = False
+
+        self.logs_menu.setEnabled(not menu_empty)
+
+        # blk menu
+        if not running:
+            self.blk_menu.setEnabled(False)
+        else:
+            self.blk_menu.clear()
+            self.blk_menu.setEnabled(True)
+
+            # self.blk_manager.blk_lock.acquire()
+            # if len(self.blk_manager.attached_devs) > 0:
+            #     for d in self.blk_manager.attached_devs:
+            #         if (self.blk_manager.attached_devs[d]
+            #                 ['attached_to']['vm'].qid == vm.qid):
+            #             text = self.tr("Detach {dev} {size} {desc}").format(
+            #                 dev=d,
+            #                 size=
+            #                     self.blk_manager.attached_devs[d]['size'],
+            #                 desc=self.blk_manager.attached_devs[d]['desc'])
+            #             action = self.blk_menu.addAction(QtGui.QIcon(":/remove.png"),
+            #                                              text)
+            #             action.setData(QtCore.QVariant(d))
+            #
+            # if len(self.blk_manager.free_devs) > 0:
+            #     for d in self.blk_manager.free_devs:
+            #         if d.startswith(vm.name):
+            #             continue
+            #         # skip partitions heuristic
+            #         if d[-1].isdigit() and \
+            #                 d[0:-1] in self.blk_manager.current_blk:
+            #             continue
+            #         text = self.tr("Attach {dev} {size} {desc}").format(
+            #             dev=d,
+            #             size=
+            #                 self.blk_manager.free_devs[d]['size'],
+            #             desc=self.blk_manager.free_devs[d]['desc'])
+            #         action = self.blk_menu.addAction(QtGui.QIcon(":/add.png"), text)
+            #         action.setData(QtCore.QVariant(d))
+            #
+            # self.blk_manager.blk_lock.release()
+
+            if self.blk_menu.isEmpty():
+                self.blk_menu.setEnabled(False)
+
+        self.context_menu.exec_(self.table.mapToGlobal(point))
+
+    @QtCore.pyqtSlot('QAction *')
+    def show_log(self, action):
+        pass
+        #TODO fixme
+        # log = str(action.data())
+        # log_dialog = LogDialog(app, log)
+        # log_dialog.exec_()
+
+    @QtCore.pyqtSlot('QAction *')
+    def attach_dettach_device_triggered(self, action):
+        pass
+        # dev = str(action.data())
+        # vm = self.get_selected_vm()
+        #
+        # self.blk_manager.blk_lock.acquire()
+        # try:
+        #     if dev in self.blk_manager.attached_devs:
+        #         self.blk_manager.detach_device(vm, dev)
+        #     else:
+        #         self.blk_manager.attach_device(vm, dev)
+        #     self.blk_manager.blk_lock.release()
+        # except exc.QubesException as e:
+        #     self.blk_manager.blk_lock.release()
+        #     QtGui.QMessageBox.critical(None,
+        #         self.tr("Block attach/detach error!"), str(e))
+
+
+class QubesTrayIcon(QtGui.QSystemTrayIcon):
+    def __init__(self, icon):
+        QtGui.QSystemTrayIcon.__init__(self, icon)
+        self.menu = QtGui.QMenu()
+
+        action_showmanager = self.create_action(
+            self.tr("Open VM Manager"),
+            slot=show_manager, icon="qubes")
+        # action_copy = self.create_action(
+        #     self.tr("Copy Dom0 clipboard"), icon="copy",
+        #     slot=do_dom0_copy)
+        action_backup = self.create_action(self.tr("Make backup"))
+        action_preferences = self.create_action(self.tr("Preferences"))
+        action_set_netvm = self.create_action(self.tr("Set default NetVM"),
+                                              icon="networking")
+        action_sys_info = self.create_action(self.tr("System Info"), icon="dom0")
+        action_exit = self.create_action(self.tr("Exit"), slot=exit_app)
+
+        action_backup.setDisabled(True)
+        action_preferences.setDisabled(True)
+        action_set_netvm.setDisabled(True)
+        action_sys_info.setDisabled(True)
+
+        # self.blk_manager = blk_manager
+
+        self.blk_menu = QtGui.QMenu(self.menu)
+        self.blk_menu.setTitle(self.tr("Block devices"))
+        action_blk_menu = self.create_action(self.tr("Block devices"))
+        action_blk_menu.setMenu(self.blk_menu)
+
+        self.add_actions(self.menu, (action_showmanager,
+                                     # action_copy,
+                                     action_blk_menu,
+                                     action_backup,
+                                     action_sys_info,
+                                     None,
+                                     action_preferences,
+                                     action_set_netvm,
+                                     None,
+                                     action_exit))
+
+        self.setContextMenu(self.menu)
+
+        # self.connect(self,
+        #              QtCore.SIGNAL("activated (QtGui.QSystemTrayIcon::ActivationReason)"),
+        #              self.icon_clicked)
+
+        self.tray_notifier_type = None
+        self.tray_notifier = QDBusInterface("org.freedesktop.Notifications",
+                                            "/org/freedesktop/Notifications",
+                                            "org.freedesktop.Notifications",
+                                            session_bus)
+        srv_info = self.tray_notifier.call("GetServerInformation")
+        if srv_info.type() == QDBusMessage.ReplyMessage and \
+                len(srv_info.arguments()) > 1:
+            self.tray_notifier_type = srv_info.arguments()[1]
+
+        if os.path.exists(table_widgets.qubes_dom0_updates_stat_file):
+            self.showMessage(self.tr("Qubes dom0 updates available."), msecs=0)
+
+    def update_blk_menu(self):
+        global manager_window
+
+        def create_vm_submenu(dev):
+            blk_vm_menu = QtGui.QMenu(self.blk_menu)
+            blk_vm_menu.triggered.connect(
+                lambda a, trig_dev=dev: self.attach_device_triggered(a,
+                                                                     trig_dev))
+            for this_vm in sorted(manager_window.qvm_collection.domains,
+                                  key=lambda x: x.name):
+                if not this_vm.is_running():
+                    continue
+                if this_vm.qid == 0:
+                    # skip dom0 to prevent (fatal) mistakes
+                    continue
+                this_action = blk_vm_menu.addAction(QtGui.QIcon(":/add.png"),
+                                                    this_vm.name)
+                this_action.setData(QtCore.QVariant(this_vm))
+            return blk_vm_menu
+
+        self.blk_menu.clear()
+        self.blk_menu.setEnabled(True)
+
+        # self.blk_manager.blk_lock.acquire()
+        # if len(self.blk_manager.attached_devs) > 0:
+        #     for d in self.blk_manager.attached_devs:
+        #         vm = self.blk_manager.attached_devs[d]['attached_to']['vm']
+        #         text = self.tr("Detach {dev} {desc} ({size}) from {vm}").format(
+        #             dev=d,
+        #             desc=self.blk_manager.attached_devs[d]['desc'],
+        #             size=self.blk_manager.attached_devs[d]['size'],
+        #             vm=vm.name)
+        #         action = self.blk_menu.addAction(QtGui.QIcon(":/remove.png"), text)
+        #         action.setData(QtCore.QVariant(d))
+        #         action.triggered.connect(
+        #             lambda b, a=action: self.dettach_device_triggered(a))
+        #
+        # if len(self.blk_manager.free_devs) > 0:
+        #     for d in self.blk_manager.free_devs:
+        #         # skip partitions heuristic
+        #         if d[-1].isdigit() and d[0:-1] in self.blk_manager.current_blk:
+        #             continue
+        #         text = self.tr("Attach  {dev} {size} {desc}").format(
+        #             dev=d,
+        #             size=self.blk_manager.free_devs[d]['size'],
+        #             desc=self.blk_manager.free_devs[d]['desc']
+        #         )
+        #         action = self.blk_menu.addAction(QtGui.QIcon(":/add.png"), text)
+        #         action.setMenu(create_vm_submenu(d))
+        #
+        # self.blk_manager.blk_lock.release()
+
+        if self.blk_menu.isEmpty():
+            self.blk_menu.setEnabled(False)
+
+    @QtCore.pyqtSlot('QAction *')
+    def attach_device_triggered(self, action, dev):
+        pass
+        # vm = action.data().toPyObject()
+        #
+        # self.blk_manager.blk_lock.acquire()
+        # try:
+        #     self.blk_manager.attach_device(vm, dev)
+        #     self.blk_manager.blk_lock.release()
+        # except exc.QubesException as e:
+        #     self.blk_manager.blk_lock.release()
+        #     QtGui.QMessageBox.critical(None,
+        #         self.tr("Block attach/detach error!"), str(e))
+
+    @QtCore.pyqtSlot('QAction *')
+    def dettach_device_triggered(self, action):
+        pass
+        # dev = str(action.data())
+        # vm = self.blk_manager.attached_devs[dev]['attached_to']['vm']
+        #
+        # self.blk_manager.blk_lock.acquire()
+        # try:
+        #     self.blk_manager.detach_device(vm, dev)
+        #     self.blk_manager.blk_lock.release()
+        # except exc.QubesException as e:
+        #     self.blk_manager.blk_lock.release()
+        #     QtGui.QMessageBox.critical(None,
+        #         self.tr("Block attach/detach error!"), str(e))
+
+    def icon_clicked(self, reason):
+        if reason == QtGui.QSystemTrayIcon.Context:
+            self.update_blk_menu()
+            # Handle the right click normally, i.e. display the context menu
+            return
+        else:
+            bring_manager_to_front()
+
+    # noinspection PyMethodMayBeStatic
+    def add_actions(self, target, actions):
+        for action in actions:
+            if action is None:
+                target.addSeparator()
+            else:
+                target.addAction(action)
+
+    def showMessage(self, message, msecs, **kwargs):
+        # QtDBus bindings doesn't use introspection to get proper method
+        # parameters types, so must cast explicitly
+        # v_replace_id = QtCore.QVariant(0)
+        # v_replace_id.convert(QtCore.QVariant.UInt)
+        # v_actions = QtCore.QVariant([])
+        # v_actions.convert(QtCore.QVariant.StringList)
+        # if self.tray_notifier_type == "KDE":
+        #     message = message.replace('\n', '<br/>\n')
+        # self.tray_notifier.call("Notify", "Qubes", v_replace_id,
+        #                         "qubes-manager", "Qubes VM Manager",
+        #                         message, v_actions, QtCore.QVariant.fromMap({}), msecs)
+        pass
+
+    def create_action(self, text, slot=None, shortcut=None, icon=None,
+                      tip=None, checkable=False, signal="triggered()"):
+        action = QtGui.QAction(text, self)
+        if icon is not None:
+            action.setIcon(QtGui.QIcon(":/%s.png" % icon))
+        if shortcut is not None:
+            action.setShortcut(shortcut)
+        if tip is not None:
+            action.setToolTip(tip)
+            action.setStatusTip(tip)
+        if slot is not None:
+            self.connect(action, QtCore.SIGNAL(signal), slot)
+        if checkable:
+            action.setCheckable(True)
+        return action
+
+
+class QubesDbusNotifyServerAdaptor(QDBusAbstractAdaptor):
+    """ This provides the DBus adaptor to the outside world"""
+
+    # Q_CLASSINFO("D-Bus Interface", dbus_interface)
+
+    @QtCore.pyqtSlot(str, str)
+    def notify_error(self, vmname, message):
+        vm = self.parent().qvm_collection.domains[vmname]
+        if vm:
+            self.parent().set_error(vm.qid, message)
+        else:
+            # ignore VM-not-found error
+            pass
+
+    @QtCore.pyqtSlot(str, str)
+    def clear_error_exact(self, vmname, message):
+        vm = self.parent().qvm_collection.domains[vmname]
+        if vm:
+            self.parent().clear_error_exact(vm.qid, message)
+        else:
+            # ignore VM-not-found error
+            pass
+
+    @QtCore.pyqtSlot(str)
+    def clear_error(self, vmname):
+        vm = self.parent().qvm_collection.domains[vmname]
+        if vm:
+            self.parent().clear_error(vm.qid)
+        else:
+            # ignore VM-not-found error
+            pass
+
+    @QtCore.pyqtSlot()
+    def show_manager(self):
+        bring_manager_to_front()
+
+
+# def get_frame_size():
+#     w = 0
+#     h = 0
+#     cmd = ['/usr/bin/xprop', '-name', 'Qubes VM Manager', '|', 'grep',
+#            '_NET_FRAME_EXTENTS']
+#     xprop = subprocess.Popen(cmd, stdout=subprocess.PIPE)
+#     for l in xprop.stdout:
+#         line = l.split('=')
+#         if len(line) == 2:
+#             line = line[1].strip().split(',')
+#             if len(line) == 4:
+#                 w = int(line[0].strip()) + int(line[1].strip())
+#                 h = int(line[2].strip()) + int(line[3].strip())
+#                 break
+#     # in case of some weird window managers we have to assume sth...
+#     if w <= 0:
+#         w = 10
+#     if h <= 0:
+#         h = 30
+#
+#     manager_window.frame_width = w
+#     manager_window.frame_height = h
+#     return
+
+
+def show_manager():
+    manager_window.show()
+    # manager_window.set_table_geom_size()
+    manager_window.repaint()
+    manager_window.update_table(out_of_schedule=True)
+    app.processEvents()
+
+    # get_frame_size()
+    # print manager_window.frame_width, " x ", manager_window.frame_height
+    # manager_window.set_table_geom_size()
+
+
+def bring_manager_to_front():
+    if manager_window.isVisible():
+        subprocess.check_call(
+            ['/usr/bin/wmctrl', '-R', str(manager_window.windowTitle())])
+
+    else:
+        show_manager()
+
+
+def show_running_manager_via_dbus():
+    global system_bus
+    if system_bus is None:
+        system_bus = QDBusConnection.systemBus()
+
+    qubes_manager = QDBusInterface('org.qubesos.QubesManager',
+                                   '/org/qubesos/QubesManager',
+                                   'org.qubesos.QubesManager', system_bus)
+    qubes_manager.call('show_manager')
+
+
+def exit_app():
+    # notifier.stop()
+    app.exit()
+
+
+# Bases on the original code by:
+# Copyright (c) 2002-2007 Pascal Varet <p.varet@gmail.com>
+
+def handle_exception(exc_type, exc_value, exc_traceback):
+    for f in traceback.extract_stack(exc_traceback):
+        print(f[0], f[1])
+    filename, line, dummy, dummy = traceback.extract_tb(exc_traceback).pop()
+    filename = os.path.basename(filename)
+    error = "%s: %s" % (exc_type.__name__, exc_value)
+
+    QtGui.QMessageBox.critical(
+        None,
+        "Houston, we have a problem...",
+        "Whoops. A critical error has occured. This is most likely a bug "
+        "in Volume and Template Manager application.<br><br><b><i>%s</i></b>" %
+        error + "at <b>line %d</b> of file <b>%s</b>.<br/><br/>"
+        % (line, filename))
+
+def sighup_handler(signum, frame):
+    os.execl("/usr/bin/qubes-manager", "qubes-manager")
+
+
+def main():
+    signal.signal(signal.SIGHUP, sighup_handler)
+
+    global system_bus
+    system_bus = QDBusConnection.systemBus()
+    # Avoid starting more than one instance of the app
+    # if not system_bus.registerService('org.qubesos.QubesManager'):
+    #     show_running_manager_via_dbus()
+    #     return
+
+    # global qubes_host
+    # qubes_host = QubesHost()
+
+    global app
+    app = QtGui.QApplication(sys.argv)
+    app.setOrganizationName("The Qubes Project")
+    app.setOrganizationDomain("http://qubes-os.org")
+    app.setApplicationName("Qubes VM Manager")
+    app.setWindowIcon(QtGui.QIcon.fromTheme("qubes-manager"))
+    # app.setAttribute(Qt.AA_DontShowIconsInMenus, False)
+
+    # qt_translator = QTranslator()
+    # locale = QLocale.system().name()
+    # i18n_dir = os.path.join(
+    #     os.path.dirname(os.path.realpath(__file__)),
+    #     'i18n')
+    # qt_translator.load("qubesmanager_{!s}.qm".format(locale), i18n_dir)
+    # app.installTranslator(qt_translator)
+
+    sys.excepthook = handle_exception
+
+    global session_bus
+    session_bus = QDBusConnection.sessionBus()
+
+    qvm_collection = Qubes()
+
+    global trayIcon
+    trayIcon = QubesTrayIcon(QtGui.QIcon.fromTheme("qubes-manager"))
+
+    global manager_window
+    manager_window = VmManagerWindow(qvm_collection)
+
+    # global wm
+    # wm = WatchManager()
+    # global notifier
+    # # notifier = ThreadedNotifier(wm, QubesManagerFileWatcher(
+    # #     manager_window.mark_table_for_update))
+    # notifier.start()
+    # wm.add_watch(system_path["qubes_store_filename"],
+    #              EventsCodes.OP_FLAGS.get('IN_MODIFY'))
+    # wm.add_watch(os.path.dirname(system_path["qubes_store_filename"]),
+    #              EventsCodes.OP_FLAGS.get('IN_MOVED_TO'))
+    # if os.path.exists(qubes_clipboard_info_file):
+    #     wm.add_watch(qubes_clipboard_info_file,
+    #                  EventsCodes.OP_FLAGS.get('IN_CLOSE_WRITE'))
+    # wm.add_watch(os.path.dirname(qubes_clipboard_info_file),
+    #              EventsCodes.OP_FLAGS.get('IN_CREATE'))
+    # wm.add_watch(os.path.dirname(table_widgets.qubes_dom0_updates_stat_file),
+    #              EventsCodes.OP_FLAGS.get('IN_CREATE'))
+
+    system_bus.registerObject(dbus_object_path, manager_window)
+
+    threading.currentThread().setName("QtMainThread")
+    trayIcon.show()
+
+    show_manager()
+    app.exec_()
+
+    trayIcon = None
+
+
+if __name__ == "__main__":
+    main()

+ 177 - 338
qubesmanager/table_widgets.py

@@ -1,10 +1,10 @@
-#!/usr/bin/python2
+#!/usr/bin/python3
 # -*- coding: utf8 -*-
-# pylint: skip-file
 #
 # The Qubes OS Project, http://www.qubes-os.org
 #
-# Copyright (C) 2014 Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>
+# Copyright (C) 2014 Marek Marczykowski-Górecki
+#                       <marmarek@invisiblethingslab.com>
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -19,46 +19,43 @@
 # You should have received a copy of the GNU Lesser General Public License along
 # with this program; if not, see <http://www.gnu.org/licenses/>.
 
-import os
-
 from PyQt4 import QtGui
+from PyQt4 import QtCore
 
-from PyQt4.QtCore import QSize, Qt
-
-from PyQt4.QtGui import QTableWidgetItem, QHBoxLayout, QIcon, QLabel, QWidget, \
-    QSizePolicy, QSpacerItem, QFont, QColor, QProgressBar, QPainter, QPen
-import time
-from qubes.qubes import vm_files
-import main
-
+# TODO: are those needed?
 qubes_dom0_updates_stat_file = '/var/lib/qubes/updates/dom0-updates-available'
-power_order = Qt.DescendingOrder
-update_order = Qt.AscendingOrder
+power_order = QtCore.Qt.DescendingOrder
+update_order = QtCore.Qt.AscendingOrder
 
 
 row_height = 30
 
 
-class VmIconWidget (QWidget):
+# TODO: do I need to find icons?
+class VmIconWidget(QtGui.QWidget):
     def __init__(self, icon_path, enabled=True, size_multiplier=0.7,
-                 tooltip  = None, parent=None, icon_sz = (32, 32)):
+                 tooltip=None, parent=None, icon_sz=(32, 32)):
         super(VmIconWidget, self).__init__(parent)
 
-        self.label_icon = QLabel()
+        # TODO: check with Marek how icons should be done
+        self.label_icon = QtGui.QLabel()
         if icon_path[0] in ':/':
-            icon = QIcon (icon_path)
+            icon = QtGui.QIcon(icon_path)
         else:
-            icon = QIcon.fromTheme(icon_path)
-        icon_sz = QSize (row_height * size_multiplier, row_height * size_multiplier)
-        icon_pixmap = icon.pixmap(icon_sz, QIcon.Disabled if not enabled else QIcon.Normal)
-        self.label_icon.setPixmap (icon_pixmap)
-        self.label_icon.setFixedSize (icon_sz)
-        if tooltip != None:
+            icon = QtGui.QIcon.fromTheme(icon_path)
+        icon_sz = QtCore.QSize(row_height * size_multiplier,
+                               row_height * size_multiplier)
+        icon_pixmap = icon.pixmap(
+            icon_sz,
+            QtGui.QIcon.Disabled if not enabled else QtGui.QIcon.Normal)
+        self.label_icon.setPixmap(icon_pixmap)
+        self.label_icon.setFixedSize(icon_sz)
+        if tooltip is not None:
             self.label_icon.setToolTip(tooltip)
 
-        layout = QHBoxLayout()
+        layout = QtGui.QHBoxLayout()
         layout.addWidget(self.label_icon)
-        layout.setContentsMargins(0,0,0,0)
+        layout.setContentsMargins(0, 0, 0, 0)
         self.setLayout(layout)
 
     def setToolTip(self, tooltip):
@@ -67,9 +64,9 @@ class VmIconWidget (QWidget):
         else:
             self.label_icon.setToolTip('')
 
-class VmTypeWidget(VmIconWidget):
 
-    class VmTypeItem(QTableWidgetItem):
+class VmTypeWidget(VmIconWidget):
+    class VmTypeItem(QtGui.QTableWidgetItem):
         def __init__(self, value, vm):
             super(VmTypeWidget.VmTypeItem, self).__init__()
             self.value = value
@@ -90,34 +87,41 @@ class VmTypeWidget(VmIconWidget):
 
     def __init__(self, vm, parent=None):
         (icon_path, tooltip) = self.get_vm_icon(vm)
-        super (VmTypeWidget, self).__init__(icon_path, True, 0.8, tooltip, parent)
+        super(VmTypeWidget, self).__init__(
+            icon_path, True, 0.8, tooltip, parent)
         self.vm = vm
         self.tableItem = self.VmTypeItem(self.value, vm)
+        self.value = None
+
+    # TODO: seriously, are numbers the best idea here?
+    # TODO: add "provides network column
+    # TODO: in type make vmtype
+    #         'AdminVM': '0',
+    #         'TemplateVM': 't',
+    #         'AppVM': 'a',
+    #         'StandaloneVM': 's',
+    #         'DispVM': 'd',
 
     def get_vm_icon(self, vm):
-        if vm.qid == 0:
+        if vm.klass == 'AdminVM':
             self.value = 0
-            return (":/dom0.png", "Dom0")
-        elif vm.is_netvm() and not vm.is_proxyvm():
-            self.value = 1
-            return (":/netvm.png", "NetVM")
-        elif vm.is_proxyvm():
-            self.value = 2
-            return (":/proxyvm.png", "ProxyVM")
-        elif vm.is_appvm() and vm.template is None:
-            self.value = 4
-            return (":/standalonevm.png", "StandaloneVM")
-        elif vm.is_template():
+            icon_name = "dom0"
+        elif vm.klass == 'TemplateVM':
             self.value = 3
-            return (":/templatevm.png", "TemplateVM")
-        elif vm.is_appvm() or vm.is_disposablevm():
+            icon_name = "templatevm"
+        elif vm.klass == 'StandaloneVM':
+            self.value = 4
+            icon_name = "standalonevm"
+        else:
             self.value = 5 + vm.label.index
-            return (":/appvm.png", "AppVM")
+            icon_name = "appvm"
+
+        return ":/" + icon_name + ".png", vm.klass
 
 
 class VmLabelWidget(VmIconWidget):
 
-    class VmLabelItem(QTableWidgetItem):
+    class VmLabelItem(QtGui.QTableWidgetItem):
         def __init__(self, value, vm):
             super(VmLabelWidget.VmLabelItem, self).__init__()
             self.value = value
@@ -126,6 +130,7 @@ class VmLabelWidget(VmIconWidget):
         def set_value(self, value):
             self.value = value
 
+        # TODO: figure a prettier sorting method?
         def __lt__(self, other):
             if self.vm.qid == 0:
                 return True
@@ -138,26 +143,23 @@ class VmLabelWidget(VmIconWidget):
 
     def __init__(self, vm, parent=None):
         icon_path = self.get_vm_icon_path(vm)
-        super (VmLabelWidget, self).__init__(icon_path, True, 0.8, None, parent)
+        super(VmLabelWidget, self).__init__(icon_path, True, 0.8, None, parent)
         self.vm = vm
         self.tableItem = self.VmLabelItem(self.value, vm)
+        self.value = None
 
     def get_vm_icon_path(self, vm):
-        if vm.qid == 0:
-            self.value = 100
-            return ":/off.png"
-        else:
-            self.value = vm.label.index
-            return vm.label.icon
+        self.value = vm.label.index
+        return vm.label.icon
 
 
-
-class VmNameItem (QTableWidgetItem):
+class VmNameItem (QtGui.QTableWidgetItem):
     def __init__(self, vm):
         super(VmNameItem, self).__init__()
-        self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
+        # TODO: is this needed
+        self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
         self.setText(vm.name)
-        self.setTextAlignment(Qt.AlignVCenter)
+        self.setTextAlignment(QtCore.Qt.AlignVCenter)
         self.qid = vm.qid
 
     def __lt__(self, other):
@@ -168,38 +170,40 @@ class VmNameItem (QTableWidgetItem):
         return super(VmNameItem, self).__lt__(other)
 
 
-class VmStatusIcon(QLabel):
+# TODO: current status - it should work...
+# TODO: maybe dbus events?
+class VmStatusIcon(QtGui.QLabel):
     def __init__(self, vm, parent=None):
-        super (VmStatusIcon, self).__init__(parent)
+        super(VmStatusIcon, self).__init__(parent)
         self.vm = vm
         self.set_on_icon()
-        self.previous_power_state = vm.last_power_state
+        # TODO: rename previous power state to something better?
+        self.previous_power_state = self.vm.get_power_state()
 
     def update(self):
-        if self.previous_power_state != self.vm.last_power_state:
+        self.previous_power_state = self.vm.get_power_state()
+        if self.previous_power_state != self.vm.get_power_state():
             self.set_on_icon()
-            self.previous_power_state = self.vm.last_power_state
+            self.previous_power_state = self.vm.get_power_state()
 
     def set_on_icon(self):
-        if self.vm.last_power_state == "Running":
-            icon = QIcon (":/on.png")
-        elif self.vm.last_power_state in ["Paused", "Suspended"]:
-            icon = QIcon (":/paused.png")
-        elif self.vm.last_power_state in ["Transient", "Halting", "Dying"]:
-            icon = QIcon (":/transient.png")
+        if self.vm.get_power_state() == "Running":
+            icon = QtGui.QIcon(":/on.png")
+        elif self.vm.get_power_state() in ["Paused", "Suspended"]:
+            icon = QtGui.QIcon(":/paused.png")
+        elif self.vm.get_power_state() in ["Transient", "Halting", "Dying"]:
+            icon = QtGui.QIcon(":/transient.png")
         else:
-            icon = QIcon (":/off.png")
+            icon = QtGui.QIcon(":/off.png")
 
-        icon_sz = QSize (row_height * 0.5, row_height *0.5)
+        icon_sz = QtCore.QSize(row_height * 0.5, row_height * 0.5)
         icon_pixmap = icon.pixmap(icon_sz)
-        self.setPixmap (icon_pixmap)
-        self.setFixedSize (icon_sz)
-
-
+        self.setPixmap(icon_pixmap)
+        self.setFixedSize(icon_sz)
 
-class VmInfoWidget (QWidget):
 
-    class VmInfoItem (QTableWidgetItem):
+class VmInfoWidget (QtGui.QWidget):
+    class VmInfoItem (QtGui.QTableWidgetItem):
         def __init__(self, upd_info_item, vm):
             super(VmInfoWidget.VmInfoItem, self).__init__()
             self.upd_info_item = upd_info_item
@@ -213,7 +217,9 @@ class VmInfoWidget (QWidget):
 
             self_val = self.upd_info_item.value
             other_val = other.upd_info_item.value
-            if self.tableWidget().horizontalHeader().sortIndicatorOrder() == update_order:
+            # TODO: is this shit needed?
+            if self.tableWidget().\
+                    horizontalHeader().sortIndicatorOrder() == update_order:
                 # the result will be sorted by upd, sorting order: Ascending
                 self_val += 1 if self.vm.is_running() else 0
                 other_val += 1 if other.vm.is_running() else 0
@@ -221,22 +227,26 @@ class VmInfoWidget (QWidget):
                     return self.vm.qid < other.vm.qid
                 else:
                     return self_val > other_val
-            elif self.tableWidget().horizontalHeader().sortIndicatorOrder() == power_order:
-                #the result will be sorted by power state, sorting order: Descending
-                self_val = -(self_val/10 + 10*(1 if self.vm.is_running() else 0))
-                other_val = -(other_val/10 + 10*(1 if other.vm.is_running() else 0))
+            elif self.tableWidget().\
+                    horizontalHeader().sortIndicatorOrder() == power_order:
+                # the result will be sorted by power state,
+                # sorting order: Descending
+                self_val = -(self_val/10 +
+                             10*(1 if self.vm.is_running() else 0))
+                other_val = -(other_val/10 +
+                              10*(1 if other.vm.is_running() else 0))
                 if self_val == other_val:
                     return self.vm.qid < other.vm.qid
                 else:
                     return self_val > other_val
             else:
-                #it would be strange if this happened
+                # it would be strange if this happened
                 return
 
-    def __init__(self, vm, parent = None):
-        super (VmInfoWidget, self).__init__(parent)
+    def __init__(self, vm, parent=None):
+        super(VmInfoWidget, self).__init__(parent)
         self.vm = vm
-        layout = QHBoxLayout ()
+        layout = QtGui.QHBoxLayout()
 
         self.on_icon = VmStatusIcon(vm)
         self.upd_info = VmUpdateInfoWidget(vm, show_text=False)
@@ -247,11 +257,13 @@ class VmInfoWidget (QWidget):
         layout.addWidget(self.on_icon)
         layout.addWidget(self.upd_info)
         layout.addWidget(self.error_icon)
-        layout.addItem(QSpacerItem(0, 10, QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding))
+        layout.addItem(QtGui.QSpacerItem(0, 10,
+                                         QtGui.QSizePolicy.Expanding,
+                                         QtGui.QSizePolicy.Expanding))
         layout.addWidget(self.blk_icon)
         layout.addWidget(self.rec_icon)
 
-        layout.setContentsMargins(5,0,5,0)
+        layout.setContentsMargins(5, 0, 5, 0)
         self.setLayout(layout)
 
         self.rec_icon.setVisible(False)
@@ -263,42 +275,36 @@ class VmInfoWidget (QWidget):
     def update_vm_state(self, vm, blk_visible, rec_visible=None):
         self.on_icon.update()
         self.upd_info.update_outdated(vm)
-        if blk_visible != None:
+        if blk_visible is not None:
             self.blk_icon.setVisible(blk_visible)
-        if rec_visible != None:
+        if rec_visible is not None:
             self.rec_icon.setVisible(rec_visible)
-        self.error_icon.setToolTip(vm.qubes_manager_state[main.QMVmState
-            .ErrorMsg])
-        self.error_icon.setVisible(vm.qubes_manager_state[main.QMVmState
-                                   .ErrorMsg] is not None)
+        # TODO: are these needed?
+        # self.error_icon.setToolTip(vm.qubes_manager_state[main.QMVmState
+        #     .ErrorMsg])
+        # self.error_icon.setVisible(vm.qubes_manager_state[main.QMVmState
+        #                            .ErrorMsg] is not None)
 
 
-class VmTemplateItem (QTableWidgetItem):
+# TODO add main to git history as a saner name and with a decent comment
+# TODO and rename that shit
+class VmTemplateItem (QtGui.QTableWidgetItem):
     def __init__(self, vm):
         super(VmTemplateItem, self).__init__()
-        self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
+        self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
         self.vm = vm
 
         if vm.template is not None:
             self.setText(vm.template.name)
         else:
-            font = QFont()
-            font.setStyle(QFont.StyleItalic)
+            font = QtGui.QFont()
+            font.setStyle(QtGui.QFont.StyleItalic)
             self.setFont(font)
-            self.setTextColor(QColor("gray"))
-
-            if vm.is_appvm(): # and vm.template is None
-                self.setText("StandaloneVM")
-            elif vm.is_template():
-                self.setText("TemplateVM")
-            elif vm.qid == 0:
-                self.setText("AdminVM")
-            elif vm.is_netvm():
-                self.setText("NetVM")
-            else:
-                self.setText("---")
+            self.setTextColor(QtGui.QColor("gray"))
+
+            self.setText(vm.klass)
 
-        self.setTextAlignment(Qt.AlignVCenter)
+        self.setTextAlignment(QtCore.Qt.AlignVCenter)
 
     def __lt__(self, other):
         if self.vm.qid == 0:
@@ -311,22 +317,20 @@ class VmTemplateItem (QTableWidgetItem):
             return super(VmTemplateItem, self).__lt__(other)
 
 
-
-
-class VmNetvmItem (QTableWidgetItem):
+class VmNetvmItem (QtGui.QTableWidgetItem):
     def __init__(self, vm):
         super(VmNetvmItem, self).__init__()
-        self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
+        self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
         self.vm = vm
 
-        if vm.is_netvm() and not vm.is_proxyvm():
+        # TODO: differentiate without no net vm/ no networking?
+        # TODO: mark provides network somehow?
+        if vm.netvm is None:
             self.setText("n/a")
-        elif vm.netvm is not None:
-            self.setText(vm.netvm.name)
         else:
-            self.setText("---")
+            self.setText(vm.netvm.name)
 
-        self.setTextAlignment(Qt.AlignVCenter)
+        self.setTextAlignment(QtCore.Qt.AlignVCenter)
 
     def __lt__(self, other):
         if self.vm.qid == 0:
@@ -338,18 +342,17 @@ class VmNetvmItem (QTableWidgetItem):
         else:
             return super(VmNetvmItem, self).__lt__(other)
 
-class VmInternalItem(QTableWidgetItem):
+
+class VmInternalItem(QtGui.QTableWidgetItem):
     def __init__(self, vm):
         super(VmInternalItem, self).__init__()
-        self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
+        self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
 
         self.vm = vm
-        self.internal = self.vm.internal
+        self.internal = vm.features.get('internal', False)
+        # TODO: should default be false
 
-        if self.internal:
-            self.setText("Yes")
-        else:
-            self.setText("")
+        self.setText("Yes" if self.internal else "")
 
     def __lt__(self, other):
         if self.vm.qid == 0:
@@ -359,144 +362,10 @@ class VmInternalItem(QTableWidgetItem):
         return super(VmInternalItem, self).__lt__(other)
 
 
-class VmUsageBarWidget (QWidget):
-
-    class VmUsageBarItem (QTableWidgetItem):
-        def __init__(self, value, vm):
-            super(VmUsageBarWidget.VmUsageBarItem, self).__init__()
-            self.value = value
-            self.vm = vm
-
-        def set_value(self, value):
-            self.value = value
-
-        def __lt__(self, other):
-            if self.vm.qid == 0:
-                return True
-            elif other.vm.qid == 0:
-                return False
-            elif self.value == other.value:
-                return self.vm.qid < other.vm.qid
-            else:
-                return int(self.value) < int(other.value)
-
-    def __init__(self, min, max, format, update_func, vm, load, hue=210, parent = None):
-        super (VmUsageBarWidget, self).__init__(parent)
-
-
-        self.min = min
-        self.max = max
-        self.update_func = update_func
-        self.value = min
-
-        self.widget = QProgressBar()
-        self.widget.setMinimum(min)
-        self.widget.setMaximum(max)
-        self.widget.setFormat(format)
-
-        self.widget.setStyleSheet(
-                                    "QProgressBar:horizontal{" +\
-                                        "border: 1px solid hsv({0}, 100, 250);".format(hue) +\
-                                        "border-radius: 4px;\
-                                        background: transparent;\
-                                        text-align: center;\
-                                    }\
-                                    QProgressBar::chunk:horizontal {\
-                                        background: qlineargradient(x1: 1, y1: 0.5, x2: 1, y2: 0.5, " +\
-                                        "stop: 0 hsv({0}, 170, 207),".format(hue) +
-                                        " stop: 1 white); \
-                                    }"
-            )
-
-        layout = QHBoxLayout()
-        layout.addWidget(self.widget)
-
-        self.setLayout(layout)
-        self.tableItem = self.VmUsageBarItem(min, vm)
-
-        self.update_load(vm, load)
-
-
-
-    def update_load(self, vm, load):
-        self.value = self.update_func(vm, load)
-        self.widget.setValue(self.value)
-        self.tableItem.set_value(self.value)
-
-class ChartWidget (QWidget):
-
-    class ChartItem (QTableWidgetItem):
-        def __init__(self, value, vm):
-            super(ChartWidget.ChartItem, self).__init__()
-            self.value = value
-            self.vm = vm
-
-        def set_value(self, value):
-            self.value = value
-
-        def __lt__(self, other):
-            if self.vm.qid == 0:
-                return True
-            elif other.vm.qid == 0:
-                return False
-            elif self.value == other.value:
-                return self.vm.qid < other.vm.qid
-            else:
-                return self.value < other.value
+# features man qvm-features
+class VmUpdateInfoWidget(QtGui.QWidget):
 
-    def __init__(self, vm, update_func, hue, load = 0, parent = None):
-        super (ChartWidget, self).__init__(parent)
-        self.update_func = update_func
-        self.hue = hue
-        if hue < 0 or hue > 255:
-            self.hue = 255
-        self.load = load
-        assert self.load >= 0 and self.load <= 100, "load = {0}".format(self.load)
-        self.load_history = [self.load]
-        self.tableItem = ChartWidget.ChartItem(self.load, vm)
-
-    def update_load (self, vm, load):
-        self.load = self.update_func(vm, load)
-
-        assert self.load >= 0, "load = {0}".format(self.load)
-        # assert self.load >= 0 and self.load <= 100, "load = {0}".format(self.load)
-        if self.load > 100:
-            # FIXME: This is an ugly workaround for cpu_load:/
-            self.load = 100
-
-        self.load_history.append (self.load)
-        self.tableItem.set_value(self.load)
-        self.repaint()
-
-    def paintEvent (self, Event = None):
-        p = QPainter (self)
-        dx = 4
-
-        W = self.width()
-        H = self.height() - 5
-        N = len(self.load_history)
-        if N > W/dx:
-            tail = N - W/dx
-            N = W/dx
-            self.load_history = self.load_history[tail:]
-
-        assert len(self.load_history) == N
-
-        for i in range (0, N-1):
-            val = self.load_history[N- i - 1]
-            sat = 70 + val*(255-70)/100
-            color = QColor.fromHsv (self.hue, sat, 255)
-            pen = QPen (color)
-            pen.setWidth(dx-1)
-            p.setPen(pen)
-            if val > 0:
-                p.drawLine (W - i*dx - dx, H , W - i*dx - dx, H - (H - 5) * val/100)
-
-
-
-class VmUpdateInfoWidget(QWidget):
-
-    class VmUpdateInfoItem (QTableWidgetItem):
+    class VmUpdateInfoItem (QtGui.QTableWidgetItem):
         def __init__(self, value, vm):
             super(VmUpdateInfoWidget.VmUpdateInfoItem, self).__init__()
             self.value = 0
@@ -521,16 +390,16 @@ class VmUpdateInfoWidget(QWidget):
             else:
                 return self.value < other.value
 
-    def __init__(self, vm, show_text=True, parent = None):
-        super (VmUpdateInfoWidget, self).__init__(parent)
-        layout = QHBoxLayout ()
+    def __init__(self, vm, show_text=True, parent=None):
+        super(VmUpdateInfoWidget, self).__init__(parent)
+        layout = QtGui.QHBoxLayout()
         self.show_text = show_text
         if self.show_text:
-            self.label=QLabel("")
-            layout.addWidget(self.label, alignment=Qt.AlignCenter)
+            self.label = QtGui.QLabel("")
+            layout.addWidget(self.label, alignment=QtCore.Qt.AlignCenter)
         else:
-            self.icon =  QLabel("")
-            layout.addWidget(self.icon, alignment=Qt.AlignCenter)
+            self.icon = QtGui.QLabel("")
+            layout.addWidget(self.icon, alignment=QtCore.Qt.AlignCenter)
         self.setLayout(layout)
 
         self.previous_outdated_state = None
@@ -539,65 +408,27 @@ class VmUpdateInfoWidget(QWidget):
         self.tableItem = VmUpdateInfoWidget.VmUpdateInfoItem(self.value, vm)
 
     def update_outdated(self, vm):
-        if vm.type == "HVM":
-            return
-
-        if vm.is_outdated():
-            outdated_state = "outdated"
-        # During TemplateVM shutdown, there's an interval of a few seconds
-        # during which vm.template.is_running() returns false but
-        # vm.is_outdated() does not yet return true, so the icon disappears.
-        # This looks goofy, but we've decided not to fix it at this time
-        # (2015-02-09).
-        elif vm.template and vm.template.is_running():
-            outdated_state = "to-be-outdated"
-        else:
-            outdated_state = None
 
+        outdated_state = False
+
+        try:
+            for vol in vm.volumes:
+                if vol.is_outdated():
+                    outdated_state = "outdated"
+                    break
+        except AttributeError:
+            pass
+
+        if not outdated_state and vm.template and vm.template.is_running():
+            outdated_state = "to-be-outdated"
         if outdated_state != self.previous_outdated_state:
             self.update_status_widget(outdated_state)
-
         self.previous_outdated_state = outdated_state
 
-        if not vm.is_updateable():
-            return
-
-        if vm.qid == 0:
-            update_recommended = self.previous_update_recommended
-            if os.path.exists(qubes_dom0_updates_stat_file):
-                update_recommended = True
-            else:
-                update_recommended = False
-
-        else:
-            update_recommended = self.previous_update_recommended
-            stat_file_path = vm.dir_path + '/' + vm_files["updates_stat_file"]
-            if not os.path.exists(stat_file_path):
-                update_recommended = False
-            else:
-                if (not hasattr(vm, "updates_stat_file_read_time")) or vm.updates_stat_file_read_time <= os.path.getmtime(stat_file_path):
-
-                        stat_file = open(stat_file_path, "r")
-                        updates = stat_file.read().strip()
-                        stat_file.close()
-                        if updates.isdigit():
-                            updates = int(updates)
-                        else:
-                            updates = 0
-
-                        if updates == 0:
-                            update_recommended = False
-                        else:
-                            update_recommended = True
-                        vm.updates_stat_file_read_time = time.time()
-
-        if update_recommended and not self.previous_update_recommended:
-            self.update_status_widget("update")
-        elif self.previous_update_recommended and not update_recommended:
-            self.update_status_widget(None)
-
-        self.previous_update_recommended = update_recommended
-
+        updates_available = vm.features.get('updates-available', False)
+        if updates_available != self.previous_update_recommended:
+            self.update_status_widget("update" if updates_available else None)
+        self.previous_update_recommended = updates_available
 
     def update_status_widget(self, state):
         self.value = state
@@ -618,7 +449,7 @@ class VmUpdateInfoWidget(QWidget):
             tooltip_text = self.tr(
                 "The TemplateVM must be stopped before changes from its "
                 "current session can be picked up by this VM.")
-        elif state is None:
+        else:
             label_text = ""
             icon_path = None
             tooltip_text = None
@@ -632,26 +463,27 @@ class VmUpdateInfoWidget(QWidget):
                 self.icon = VmIconWidget(icon_path, True, 0.7)
                 self.icon.setToolTip(tooltip_text)
             else:
-                self.icon = QLabel(label_text)
-            self.layout().addWidget(self.icon, alignment=Qt.AlignCenter)
+                self.icon = QtGui.QLabel(label_text)
+            self.layout().addWidget(self.icon, alignment=QtCore.Qt.AlignCenter)
 
 
-class VmSizeOnDiskItem (QTableWidgetItem):
+class VmSizeOnDiskItem (QtGui.QTableWidgetItem):
     def __init__(self, vm):
         super(VmSizeOnDiskItem, self).__init__()
-        self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
+        self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
 
         self.vm = vm
         self.value = 0
         self.update()
-        self.setTextAlignment(Qt.AlignVCenter)
+        self.setTextAlignment(QtCore.Qt.AlignVCenter)
 
     def update(self):
         if self.vm.qid == 0:
             self.setText("n/a")
         else:
+            self.value = 10
             self.value = self.vm.get_disk_utilization()/(1024*1024)
-            self.setText( str(self.value) + " MiB")
+            self.setText(str(self.value) + " MiB")
 
     def __lt__(self, other):
         if self.vm.qid == 0:
@@ -663,17 +495,20 @@ class VmSizeOnDiskItem (QTableWidgetItem):
         else:
             return self.value < other.value
 
-class VmIPItem(QTableWidgetItem):
+
+class VmIPItem(QtGui.QTableWidgetItem):
     def __init__(self, vm):
         super(VmIPItem, self).__init__()
-        self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
+        self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
 
+        # TODO: check if you don't need a try around here
         self.vm = vm
         self.ip = self.vm.ip
         if self.ip:
             self.setText(self.ip)
         else:
             self.setText("n/a")
+        self.setText("n/a")
 
     def __lt__(self, other):
         if self.vm.qid == 0:
@@ -682,16 +517,18 @@ class VmIPItem(QTableWidgetItem):
             return False
         return super(VmIPItem, self).__lt__(other)
 
-class VmIncludeInBackupsItem(QTableWidgetItem):
+
+class VmIncludeInBackupsItem(QtGui.QTableWidgetItem):
     def __init__(self, vm):
         super(VmIncludeInBackupsItem, self).__init__()
-        self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
+        self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
 
         self.vm = vm
         if self.vm.include_in_backups:
             self.setText("Yes")
         else:
             self.setText("")
+        self.setText("")
 
     def __lt__(self, other):
         if self.vm.qid == 0:
@@ -703,16 +540,18 @@ class VmIncludeInBackupsItem(QTableWidgetItem):
         else:
             return self.vm.include_in_backups < other.vm.include_in_backups
 
-class VmLastBackupItem(QTableWidgetItem):
+
+class VmLastBackupItem(QtGui.QTableWidgetItem):
     def __init__(self, vm):
         super(VmLastBackupItem, self).__init__()
-        self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
+        self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
 
         self.vm = vm
         if self.vm.backup_timestamp:
-            self.setText(str(self.vm.backup_timestamp.date()))
+            self.setText(self.vm.backup_timestamp)
         else:
             self.setText("")
+        self.setText("")
 
     def __lt__(self, other):
         if self.vm.qid == 0:

+ 943 - 0
ui/vtmanager.ui

@@ -0,0 +1,943 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>VmManagerWindow</class>
+ <widget class="QMainWindow" name="VmManagerWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>769</width>
+    <height>385</height>
+   </rect>
+  </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Fixed" vsizetype="Maximum">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="contextMenuPolicy">
+   <enum>Qt::DefaultContextMenu</enum>
+  </property>
+  <property name="windowTitle">
+   <string>Qubes VM Manager</string>
+  </property>
+  <property name="windowIcon">
+   <iconset theme="qubes-manager">
+    <normaloff/>
+   </iconset>
+  </property>
+  <property name="locale">
+   <locale language="English" country="UnitedStates"/>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <property name="enabled">
+    <bool>true</bool>
+   </property>
+   <property name="sizePolicy">
+    <sizepolicy hsizetype="Fixed" vsizetype="Expanding">
+     <horstretch>0</horstretch>
+     <verstretch>0</verstretch>
+    </sizepolicy>
+   </property>
+   <property name="acceptDrops">
+    <bool>false</bool>
+   </property>
+   <property name="autoFillBackground">
+    <bool>true</bool>
+   </property>
+   <property name="locale">
+    <locale language="English" country="UnitedStates"/>
+   </property>
+   <layout class="QGridLayout" name="gridLayout">
+    <property name="sizeConstraint">
+     <enum>QLayout::SetDefaultConstraint</enum>
+    </property>
+    <property name="margin">
+     <number>0</number>
+    </property>
+    <property name="spacing">
+     <number>0</number>
+    </property>
+    <item row="0" column="0">
+     <layout class="QHBoxLayout" name="searchContainer">
+      <property name="spacing">
+       <number>6</number>
+      </property>
+      <property name="leftMargin">
+       <number>6</number>
+      </property>
+      <item>
+       <widget class="QLabel" name="label">
+        <property name="text">
+         <string>Search:</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </item>
+    <item row="1" column="0">
+     <widget class="QTableWidget" name="table">
+      <property name="minimumSize">
+       <size>
+        <width>0</width>
+        <height>0</height>
+       </size>
+      </property>
+      <property name="maximumSize">
+       <size>
+        <width>16777215</width>
+        <height>16777215</height>
+       </size>
+      </property>
+      <property name="sizeIncrement">
+       <size>
+        <width>200</width>
+        <height>30</height>
+       </size>
+      </property>
+      <property name="contextMenuPolicy">
+       <enum>Qt::CustomContextMenu</enum>
+      </property>
+      <property name="acceptDrops">
+       <bool>false</bool>
+      </property>
+      <property name="lineWidth">
+       <number>0</number>
+      </property>
+      <property name="verticalScrollBarPolicy">
+       <enum>Qt::ScrollBarAsNeeded</enum>
+      </property>
+      <property name="horizontalScrollBarPolicy">
+       <enum>Qt::ScrollBarAsNeeded</enum>
+      </property>
+      <property name="alternatingRowColors">
+       <bool>true</bool>
+      </property>
+      <property name="selectionMode">
+       <enum>QAbstractItemView::SingleSelection</enum>
+      </property>
+      <property name="selectionBehavior">
+       <enum>QAbstractItemView::SelectRows</enum>
+      </property>
+      <property name="showGrid">
+       <bool>false</bool>
+      </property>
+      <property name="gridStyle">
+       <enum>Qt::NoPen</enum>
+      </property>
+      <property name="sortingEnabled">
+       <bool>true</bool>
+      </property>
+      <property name="cornerButtonEnabled">
+       <bool>false</bool>
+      </property>
+      <property name="rowCount">
+       <number>10</number>
+      </property>
+      <property name="columnCount">
+       <number>15</number>
+      </property>
+      <attribute name="horizontalHeaderCascadingSectionResizes">
+       <bool>false</bool>
+      </attribute>
+      <attribute name="horizontalHeaderDefaultSectionSize">
+       <number>150</number>
+      </attribute>
+      <attribute name="horizontalHeaderMinimumSectionSize">
+       <number>150</number>
+      </attribute>
+      <attribute name="verticalHeaderVisible">
+       <bool>false</bool>
+      </attribute>
+      <attribute name="verticalHeaderCascadingSectionResizes">
+       <bool>false</bool>
+      </attribute>
+      <row>
+       <property name="text">
+        <string>Nowy wiersz</string>
+       </property>
+      </row>
+      <row/>
+      <row/>
+      <row/>
+      <row/>
+      <row/>
+      <row/>
+      <row/>
+      <row/>
+      <row/>
+      <column>
+       <property name="text">
+        <string/>
+       </property>
+      </column>
+      <column>
+       <property name="text">
+        <string/>
+       </property>
+      </column>
+      <column>
+       <property name="text">
+        <string>Name</string>
+       </property>
+       <property name="toolTip">
+        <string>VM name</string>
+       </property>
+      </column>
+      <column>
+       <property name="text">
+        <string>State</string>
+       </property>
+       <property name="toolTip">
+        <string>Update info</string>
+       </property>
+      </column>
+      <column>
+       <property name="text">
+        <string>Template</string>
+       </property>
+       <property name="toolTip">
+        <string>VM's template</string>
+       </property>
+      </column>
+      <column>
+       <property name="text">
+        <string>NetVM</string>
+       </property>
+       <property name="toolTip">
+        <string>VM's netVM</string>
+       </property>
+      </column>
+      <column>
+       <property name="text">
+        <string>CPU</string>
+       </property>
+      </column>
+      <column>
+       <property name="text">
+        <string>CPU Graph</string>
+       </property>
+       <property name="toolTip">
+        <string>CPU usage graph</string>
+       </property>
+      </column>
+      <column>
+       <property name="text">
+        <string>MEM</string>
+       </property>
+      </column>
+      <column>
+       <property name="text">
+        <string>MEM Graph</string>
+       </property>
+       <property name="toolTip">
+        <string>Memory usage graph</string>
+       </property>
+      </column>
+      <column>
+       <property name="text">
+        <string>Size</string>
+       </property>
+      </column>
+      <column>
+       <property name="text">
+        <string>Internal</string>
+       </property>
+      </column>
+      <column>
+       <property name="text">
+        <string>IP</string>
+       </property>
+      </column>
+      <column>
+       <property name="text">
+        <string>Backups</string>
+       </property>
+      </column>
+      <column>
+       <property name="text">
+        <string>Last backup</string>
+       </property>
+      </column>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QMenuBar" name="menubar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>769</width>
+     <height>28</height>
+    </rect>
+   </property>
+   <property name="contextMenuPolicy">
+    <enum>Qt::CustomContextMenu</enum>
+   </property>
+   <widget class="QMenu" name="menu_system">
+    <property name="title">
+     <string>&amp;System</string>
+    </property>
+    <addaction name="action_global_settings"/>
+    <addaction name="action_show_network"/>
+    <addaction name="action_backup"/>
+    <addaction name="action_restore"/>
+   </widget>
+   <widget class="QMenu" name="menu_view">
+    <property name="title">
+     <string>&amp;View</string>
+    </property>
+    <addaction name="action_vm_type"/>
+    <addaction name="action_label"/>
+    <addaction name="action_name"/>
+    <addaction name="action_state"/>
+    <addaction name="action_template"/>
+    <addaction name="action_netvm"/>
+    <addaction name="action_cpu"/>
+    <addaction name="action_cpu_graph"/>
+    <addaction name="action_mem"/>
+    <addaction name="action_mem_graph"/>
+    <addaction name="action_size_on_disk"/>
+    <addaction name="action_internal"/>
+    <addaction name="action_ip"/>
+    <addaction name="action_backups"/>
+    <addaction name="action_last_backup"/>
+    <addaction name="separator"/>
+    <addaction name="action_toolbar"/>
+    <addaction name="action_menubar"/>
+    <addaction name="separator"/>
+    <addaction name="action_showallvms"/>
+    <addaction name="action_showinternalvms"/>
+    <addaction name="separator"/>
+    <addaction name="action_search"/>
+   </widget>
+   <widget class="QMenu" name="menu_vm">
+    <property name="title">
+     <string>V&amp;M</string>
+    </property>
+    <widget class="QMenu" name="logs_menu">
+     <property name="title">
+      <string>&amp;Logs</string>
+     </property>
+     <property name="icon">
+      <iconset resource="../resources.qrc">
+       <normaloff>:/log.png</normaloff>:/log.png</iconset>
+     </property>
+    </widget>
+    <widget class="QMenu" name="blk_menu">
+     <property name="title">
+      <string>Attach/detach &amp;block devices</string>
+     </property>
+     <property name="icon">
+      <iconset resource="../resources.qrc">
+       <normaloff>:/mount.png</normaloff>:/mount.png</iconset>
+     </property>
+    </widget>
+    <addaction name="action_createvm"/>
+    <addaction name="action_removevm"/>
+    <addaction name="action_clonevm"/>
+    <addaction name="separator"/>
+    <addaction name="action_resumevm"/>
+    <addaction name="action_pausevm"/>
+    <addaction name="action_shutdownvm"/>
+    <addaction name="action_restartvm"/>
+    <addaction name="action_killvm"/>
+    <addaction name="separator"/>
+    <addaction name="action_settings"/>
+    <addaction name="action_editfwrules"/>
+    <addaction name="action_appmenus"/>
+    <addaction name="action_updatevm"/>
+    <addaction name="action_run_command_in_vm"/>
+    <addaction name="action_set_keyboard_layout"/>
+    <addaction name="action_toggle_audio_input"/>
+    <addaction name="separator"/>
+    <addaction name="logs_menu"/>
+    <addaction name="blk_menu"/>
+   </widget>
+   <widget class="QMenu" name="menu_about">
+    <property name="title">
+     <string>&amp;About</string>
+    </property>
+    <addaction name="action_about_qubes"/>
+   </widget>
+   <addaction name="menu_system"/>
+   <addaction name="menu_vm"/>
+   <addaction name="menu_view"/>
+   <addaction name="menu_about"/>
+  </widget>
+  <widget class="QToolBar" name="toolBar">
+   <property name="contextMenuPolicy">
+    <enum>Qt::CustomContextMenu</enum>
+   </property>
+   <property name="windowTitle">
+    <string>toolBar</string>
+   </property>
+   <property name="allowedAreas">
+    <set>Qt::BottomToolBarArea|Qt::TopToolBarArea</set>
+   </property>
+   <property name="floatable">
+    <bool>false</bool>
+   </property>
+   <attribute name="toolBarArea">
+    <enum>TopToolBarArea</enum>
+   </attribute>
+   <attribute name="toolBarBreak">
+    <bool>false</bool>
+   </attribute>
+   <addaction name="action_createvm"/>
+   <addaction name="action_removevm"/>
+   <addaction name="separator"/>
+   <addaction name="action_resumevm"/>
+   <addaction name="action_pausevm"/>
+   <addaction name="action_shutdownvm"/>
+   <addaction name="action_restartvm"/>
+   <addaction name="separator"/>
+   <addaction name="action_settings"/>
+   <addaction name="action_editfwrules"/>
+   <addaction name="action_appmenus"/>
+   <addaction name="action_updatevm"/>
+   <addaction name="action_set_keyboard_layout"/>
+   <addaction name="action_toggle_audio_input"/>
+   <addaction name="separator"/>
+   <addaction name="action_global_settings"/>
+   <addaction name="action_backup"/>
+   <addaction name="action_restore"/>
+   <addaction name="separator"/>
+   <addaction name="action_showallvms"/>
+  </widget>
+  <action name="action_createvm">
+   <property name="icon">
+    <iconset resource="../resources.qrc">
+     <normaloff>:/createvm.png</normaloff>:/createvm.png</iconset>
+   </property>
+   <property name="text">
+    <string>Create &amp;New VM</string>
+   </property>
+   <property name="toolTip">
+    <string>Create a new VM</string>
+   </property>
+  </action>
+  <action name="action_removevm">
+   <property name="enabled">
+    <bool>false</bool>
+   </property>
+   <property name="icon">
+    <iconset resource="../resources.qrc">
+     <normaloff>:/removevm.png</normaloff>:/removevm.png</iconset>
+   </property>
+   <property name="text">
+    <string>&amp;Delete VM</string>
+   </property>
+   <property name="toolTip">
+    <string>Remove an existing VM (must be stopped first)</string>
+   </property>
+  </action>
+  <action name="action_resumevm">
+   <property name="enabled">
+    <bool>false</bool>
+   </property>
+   <property name="icon">
+    <iconset resource="../resources.qrc">
+     <normaloff>:/resumevm.png</normaloff>:/resumevm.png</iconset>
+   </property>
+   <property name="text">
+    <string>Start/Resume V&amp;M</string>
+   </property>
+   <property name="toolTip">
+    <string>Start/Resume selected VM</string>
+   </property>
+  </action>
+  <action name="action_pausevm">
+   <property name="enabled">
+    <bool>false</bool>
+   </property>
+   <property name="icon">
+    <iconset resource="../resources.qrc">
+     <normaloff>:/pausevm.png</normaloff>:/pausevm.png</iconset>
+   </property>
+   <property name="text">
+    <string>&amp;Pause VM</string>
+   </property>
+   <property name="toolTip">
+    <string>Pause selected VM</string>
+   </property>
+  </action>
+  <action name="action_shutdownvm">
+   <property name="enabled">
+    <bool>false</bool>
+   </property>
+   <property name="icon">
+    <iconset resource="../resources.qrc">
+     <normaloff>:/shutdownvm.png</normaloff>:/shutdownvm.png</iconset>
+   </property>
+   <property name="text">
+    <string>&amp;Shutdown VM</string>
+   </property>
+   <property name="toolTip">
+    <string>Shutdown selected VM</string>
+   </property>
+  </action>
+  <action name="action_restartvm">
+   <property name="enabled">
+    <bool>false</bool>
+   </property>
+   <property name="icon">
+    <iconset resource="../resources.qrc">
+     <normaloff>:/restartvm.png</normaloff>:/restartvm.png</iconset>
+   </property>
+   <property name="text">
+    <string>Restar&amp;t VM</string>
+   </property>
+   <property name="toolTip">
+    <string>Restart selected VM</string>
+   </property>
+  </action>
+  <action name="action_appmenus">
+   <property name="enabled">
+    <bool>false</bool>
+   </property>
+   <property name="icon">
+    <iconset resource="../resources.qrc">
+     <normaloff>:/apps.png</normaloff>:/apps.png</iconset>
+   </property>
+   <property name="text">
+    <string>Add/remove app s&amp;hortcuts</string>
+   </property>
+   <property name="toolTip">
+    <string>Add/remove app shortcuts for this VM</string>
+   </property>
+  </action>
+  <action name="action_updatevm">
+   <property name="enabled">
+    <bool>false</bool>
+   </property>
+   <property name="icon">
+    <iconset resource="../resources.qrc">
+     <normaloff>:/updateable.png</normaloff>:/updateable.png</iconset>
+   </property>
+   <property name="text">
+    <string>&amp;Update VM</string>
+   </property>
+   <property name="toolTip">
+    <string>Update VM system</string>
+   </property>
+  </action>
+  <action name="action_toggle_audio_input">
+   <property name="enabled">
+    <bool>false</bool>
+   </property>
+   <property name="icon">
+    <iconset resource="../resources.qrc">
+     <normaloff>:/mic.png</normaloff>:/mic.png</iconset>
+   </property>
+   <property name="text">
+    <string>Attach/detach &amp;audio-input to the VM</string>
+   </property>
+   <property name="toolTip">
+    <string>Attach/detach audio-input to the VM</string>
+   </property>
+  </action>
+  <action name="action_showallvms">
+   <property name="checkable">
+    <bool>true</bool>
+   </property>
+   <property name="checked">
+    <bool>false</bool>
+   </property>
+   <property name="icon">
+    <iconset resource="../resources.qrc">
+     <normaloff>:/show-all-running.png</normaloff>
+     <selectedoff>:/showallvms.png</selectedoff>:/show-all-running.png</iconset>
+   </property>
+   <property name="text">
+    <string>Show/Hide inactive VMs</string>
+   </property>
+   <property name="toolTip">
+    <string>Show/Hide inactive VMs</string>
+   </property>
+  </action>
+  <action name="action_editfwrules">
+   <property name="icon">
+    <iconset resource="../resources.qrc">
+     <normaloff>:/firewall.png</normaloff>:/firewall.png</iconset>
+   </property>
+   <property name="text">
+    <string>Edit VM &amp;firewall rules</string>
+   </property>
+   <property name="toolTip">
+    <string>Edit VM firewall rules</string>
+   </property>
+  </action>
+  <action name="action_showgraphs">
+   <property name="checkable">
+    <bool>true</bool>
+   </property>
+   <property name="icon">
+    <iconset resource="../resources.qrc">
+     <normaloff>:/showcpuload.png</normaloff>:/showcpuload.png</iconset>
+   </property>
+   <property name="text">
+    <string>Show graphs</string>
+   </property>
+   <property name="toolTip">
+    <string>Show Graphs</string>
+   </property>
+  </action>
+  <action name="action_options">
+   <property name="text">
+    <string>Options</string>
+   </property>
+  </action>
+  <action name="action_view">
+   <property name="text">
+    <string>View</string>
+   </property>
+  </action>
+  <action name="action_cpu">
+   <property name="checkable">
+    <bool>true</bool>
+   </property>
+   <property name="checked">
+    <bool>true</bool>
+   </property>
+   <property name="text">
+    <string>&amp;CPU</string>
+   </property>
+  </action>
+  <action name="action_cpu_graph">
+   <property name="checkable">
+    <bool>true</bool>
+   </property>
+   <property name="checked">
+    <bool>true</bool>
+   </property>
+   <property name="text">
+    <string>CPU &amp;Graph</string>
+   </property>
+  </action>
+  <action name="action_mem">
+   <property name="checkable">
+    <bool>true</bool>
+   </property>
+   <property name="checked">
+    <bool>true</bool>
+   </property>
+   <property name="text">
+    <string>&amp;MEM</string>
+   </property>
+  </action>
+  <action name="action_mem_graph">
+   <property name="checkable">
+    <bool>true</bool>
+   </property>
+   <property name="checked">
+    <bool>true</bool>
+   </property>
+   <property name="text">
+    <string>M&amp;EM Graph</string>
+   </property>
+  </action>
+  <action name="action_template">
+   <property name="checkable">
+    <bool>true</bool>
+   </property>
+   <property name="checked">
+    <bool>true</bool>
+   </property>
+   <property name="text">
+    <string>&amp;Template</string>
+   </property>
+  </action>
+  <action name="action_netvm">
+   <property name="checkable">
+    <bool>true</bool>
+   </property>
+   <property name="checked">
+    <bool>true</bool>
+   </property>
+   <property name="text">
+    <string>&amp;NetVM</string>
+   </property>
+  </action>
+  <action name="action_settings">
+   <property name="icon">
+    <iconset resource="../resources.qrc">
+     <normaloff>:/settings.png</normaloff>:/settings.png</iconset>
+   </property>
+   <property name="text">
+    <string>VM s&amp;ettings</string>
+   </property>
+   <property name="toolTip">
+    <string>VM Settings</string>
+   </property>
+  </action>
+  <action name="action_restore">
+   <property name="icon">
+    <iconset resource="../resources.qrc">
+     <normaloff>:/restore.png</normaloff>:/restore.png</iconset>
+   </property>
+   <property name="text">
+    <string>&amp;Restore VMs from backup</string>
+   </property>
+  </action>
+  <action name="action_backup">
+   <property name="icon">
+    <iconset resource="../resources.qrc">
+     <normaloff>:/backup.png</normaloff>:/backup.png</iconset>
+   </property>
+   <property name="text">
+    <string>&amp;Backup VMs</string>
+   </property>
+  </action>
+  <action name="action_global_settings">
+   <property name="icon">
+    <iconset resource="../resources.qrc">
+     <normaloff>:/global-settings.png</normaloff>:/global-settings.png</iconset>
+   </property>
+   <property name="text">
+    <string>&amp;Global settings</string>
+   </property>
+  </action>
+  <action name="action_show_network">
+   <property name="icon">
+    <iconset resource="../resources.qrc">
+     <normaloff>:/networking.png</normaloff>:/networking.png</iconset>
+   </property>
+   <property name="text">
+    <string>&amp;Qubes Network</string>
+   </property>
+  </action>
+  <action name="action_state">
+   <property name="checkable">
+    <bool>true</bool>
+   </property>
+   <property name="checked">
+    <bool>true</bool>
+   </property>
+   <property name="text">
+    <string>&amp;State</string>
+   </property>
+  </action>
+  <action name="action_killvm">
+   <property name="icon">
+    <iconset resource="../resources.qrc">
+     <normaloff>:/killvm.png</normaloff>:/killvm.png</iconset>
+   </property>
+   <property name="text">
+    <string>&amp;Kill VM</string>
+   </property>
+   <property name="toolTip">
+    <string>Kill selected VM</string>
+   </property>
+  </action>
+  <action name="action_set_keyboard_layout">
+   <property name="icon">
+    <iconset resource="../resources.qrc">
+     <normaloff>:/kbd-layout.png</normaloff>:/kbd-layout.png</iconset>
+   </property>
+   <property name="text">
+    <string>Set keyboard la&amp;yout</string>
+   </property>
+   <property name="toolTip">
+    <string>Set keyboard layout per VM</string>
+   </property>
+  </action>
+  <action name="action_vm_type">
+   <property name="checkable">
+    <bool>true</bool>
+   </property>
+   <property name="checked">
+    <bool>true</bool>
+   </property>
+   <property name="text">
+    <string>T&amp;ype</string>
+   </property>
+   <property name="toolTip">
+    <string>VM Type</string>
+   </property>
+  </action>
+  <action name="action_label">
+   <property name="checkable">
+    <bool>true</bool>
+   </property>
+   <property name="checked">
+    <bool>true</bool>
+   </property>
+   <property name="text">
+    <string>&amp;Label</string>
+   </property>
+  </action>
+  <action name="action_name">
+   <property name="checkable">
+    <bool>true</bool>
+   </property>
+   <property name="checked">
+    <bool>true</bool>
+   </property>
+   <property name="text">
+    <string>N&amp;ame</string>
+   </property>
+  </action>
+  <action name="action_toolbar">
+   <property name="checkable">
+    <bool>true</bool>
+   </property>
+   <property name="checked">
+    <bool>true</bool>
+   </property>
+   <property name="text">
+    <string>Show tool bar</string>
+   </property>
+  </action>
+  <action name="action_menubar">
+   <property name="checkable">
+    <bool>true</bool>
+   </property>
+   <property name="checked">
+    <bool>true</bool>
+   </property>
+   <property name="text">
+    <string>Show menu bar</string>
+   </property>
+  </action>
+  <action name="action_about_qubes">
+   <property name="icon">
+    <iconset theme="qubes-manager">
+     <normaloff/>
+    </iconset>
+   </property>
+   <property name="text">
+    <string>&amp;Qubes OS</string>
+   </property>
+  </action>
+  <action name="action_size_on_disk">
+   <property name="checkable">
+    <bool>true</bool>
+   </property>
+   <property name="checked">
+    <bool>true</bool>
+   </property>
+   <property name="text">
+    <string>Si&amp;ze</string>
+   </property>
+   <property name="toolTip">
+    <string>Size on Disk</string>
+   </property>
+  </action>
+  <action name="action_run_command_in_vm">
+   <property name="icon">
+    <iconset resource="../resources.qrc">
+     <normaloff>:/run-command.png</normaloff>:/run-command.png</iconset>
+   </property>
+   <property name="text">
+    <string>&amp;Run command in VM</string>
+   </property>
+   <property name="toolTip">
+    <string>Run command in the specified VM</string>
+   </property>
+  </action>
+  <action name="action_clonevm">
+   <property name="enabled">
+    <bool>false</bool>
+   </property>
+   <property name="icon">
+    <iconset resource="../resources.qrc">
+     <normaloff>:/templatevm.png</normaloff>:/templatevm.png</iconset>
+   </property>
+   <property name="text">
+    <string>&amp;Clone VM</string>
+   </property>
+  </action>
+  <action name="action_internal">
+   <property name="checkable">
+    <bool>true</bool>
+   </property>
+   <property name="checked">
+    <bool>true</bool>
+   </property>
+   <property name="text">
+    <string>Inte&amp;rnal</string>
+   </property>
+   <property name="toolTip">
+    <string>Is an internal VM</string>
+   </property>
+  </action>
+  <action name="action_showinternalvms">
+   <property name="checkable">
+    <bool>true</bool>
+   </property>
+   <property name="checked">
+    <bool>false</bool>
+   </property>
+   <property name="icon">
+    <iconset resource="../resources.qrc">
+     <normaloff>:/show-all-running.png</normaloff>:/show-all-running.png</iconset>
+   </property>
+   <property name="text">
+    <string>Show/Hide internal VMs</string>
+   </property>
+  </action>
+  <action name="action_startvm_tools_install">
+   <property name="enabled">
+    <bool>false</bool>
+   </property>
+   <property name="icon">
+    <iconset resource="../resources.qrc">
+     <normaloff>:/resumevm.png</normaloff>:/resumevm.png</iconset>
+   </property>
+   <property name="text">
+    <string>Start VM for Window Tools installation</string>
+   </property>
+   <property name="toolTip">
+    <string>Start VM for Window Tools installation</string>
+   </property>
+  </action>
+  <action name="action_ip">
+   <property name="checkable">
+    <bool>true</bool>
+   </property>
+   <property name="checked">
+    <bool>true</bool>
+   </property>
+   <property name="text">
+    <string>&amp;IP</string>
+   </property>
+  </action>
+  <action name="action_backups">
+   <property name="checkable">
+    <bool>true</bool>
+   </property>
+   <property name="checked">
+    <bool>true</bool>
+   </property>
+   <property name="text">
+    <string>Include in &amp;backups</string>
+   </property>
+  </action>
+  <action name="action_last_backup">
+   <property name="checkable">
+    <bool>true</bool>
+   </property>
+   <property name="checked">
+    <bool>true</bool>
+   </property>
+   <property name="text">
+    <string>Last back&amp;up</string>
+   </property>
+  </action>
+  <action name="action_search">
+   <property name="text">
+    <string>Search</string>
+   </property>
+   <property name="shortcut">
+    <string>Ctrl+F</string>
+   </property>
+  </action>
+ </widget>
+ <resources>
+  <include location="../resources.qrc"/>
+ </resources>
+ <connections/>
+</ui>