+
+def handle_exception( exc_type, exc_value, exc_traceback ):
+ import sys
+ import os.path
+ import traceback
+
+ filename, line, dummy, dummy = traceback.extract_tb( exc_traceback ).pop()
+ filename = os.path.basename( filename )
+ error = "%s: %s" % ( exc_type.__name__, exc_value )
+
+ QMessageBox.critical(None, "Houston, we have a problem...",
+ "Whoops. A critical error has occured. This is most likely a bug "
+ "in Qubes Global Settings application.
"
+ "%s" % error +
+ "at line %d of file %s.
"
+ % ( line, filename ))
+
+
+def main():
+
+ global qubes_host
+ qubes_host = QubesHost()
+
+ global app
+ app = QApplication(sys.argv)
+ app.setOrganizationName("The Qubes Project")
+ app.setOrganizationDomain("http://qubes-os.org")
+ app.setApplicationName("Qubes Global Settings")
+
+ sys.excepthook = handle_exception
+
+ qvm_collection = QubesVmCollection()
+ qvm_collection.lock_db_for_reading()
+ qvm_collection.load()
+ qvm_collection.unlock_db()
+
+ global global_window
+ global_window = GlobalSettingsWindow()
+
+ global_window.show()
+
+ app.exec_()
+ app.exit()
+
+
+
+if __name__ == "__main__":
+ main()
diff --git a/qubesmanager/main.py b/qubesmanager/main.py
index c97f7c3..1ed9384 100755
--- a/qubesmanager/main.py
+++ b/qubesmanager/main.py
@@ -35,17 +35,19 @@ from qubes.qubes import QubesDaemonPidfile
from qubes.qubes import QubesHost
from qubes import qubesutils
-import qubesmanager.qrc_resources
+import qubesmanager.resources_rc
import ui_newappvmdlg
-from appmenu_select import AppmenuSelectWindow
-
-from firewall import EditFwRulesDlg, QubesFirewallRulesModel
+from ui_mainwindow import *
+from settings import VMSettingsWindow
+from restore import RestoreVMsWindow
+from backup import BackupVMsWindow
+from global_settings import GlobalSettingsWindow
+from thread_monitor import *
from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent
import subprocess
import time
-import threading
from datetime import datetime,timedelta
updates_stat_file = 'last_update.stat'
@@ -99,76 +101,265 @@ class VmStatusIcon(QLabel):
class VmInfoWidget (QWidget):
+ class VmInfoItem (QTableWidgetItem):
+ def __init__(self, name, qid):
+ super(VmInfoWidget.VmInfoItem, self).__init__()
+ self.value = (name, qid)
+
+ def set_value(self, value):
+ self.value = value
+
+ def __lt__(self, other):
+ return self.value[0] < other.value[0] #compare vm.name
+
+
def __init__(self, vm, parent = None):
super (VmInfoWidget, self).__init__(parent)
- layout0 = QHBoxLayout()
+ layout = QHBoxLayout ()
self.label_name = QLabel (vm.name)
-
- self.vm_running = vm.last_power_state
- layout0.addWidget(self.label_name, alignment=Qt.AlignLeft)
-
- layout1 = QHBoxLayout()
-
- if vm.template_vm is not None:
- self.label_tmpl = QLabel ("" + (vm.template_vm.name) + "")
- elif vm.is_appvm(): # and vm.template_vm is None
- self.label_tmpl = QLabel ("StandaloneVM")
- elif vm.is_template():
- self.label_tmpl = QLabel ("TemplateVM")
- elif vm.qid == 0:
- self.label_tmpl = QLabel ("AdminVM")
- elif vm.is_netvm():
- self.label_tmpl = QLabel ("NetVM")
- else:
- self.label_tmpl = QLabel ("")
-
- label_icon_networked = self.set_icon(":/networking.png", vm.is_networked())
- layout1.addWidget(label_icon_networked, alignment=Qt.AlignLeft)
-
- if vm.is_updateable():
- label_icon_updtbl = self.set_icon(":/updateable.png", True)
- layout1.addWidget(label_icon_updtbl, alignment=Qt.AlignLeft)
-
- layout1.addWidget(self.label_tmpl, alignment=Qt.AlignLeft)
-
- layout1.addStretch()
-
- layout2 = QVBoxLayout ()
- layout2.addLayout(layout0)
- layout2.addLayout(layout1)
-
- layout3 = QHBoxLayout ()
self.vm_icon = VmStatusIcon(vm)
- layout3.addWidget(self.vm_icon)
- layout3.addSpacing (10)
- layout3.addLayout(layout2)
+ self.blk_icon = VmIconWidget(":/mount.png")
- self.setLayout(layout3)
+ layout.addWidget(self.vm_icon)
+ layout.addSpacing (10)
+ layout.addWidget(self.label_name, alignment=Qt.AlignLeft)
+ layout.addSpacing (10)
+ layout.addWidget(self.blk_icon, alignment=Qt.AlignRight)
- self.previous_outdated = False
- self.previous_update_recommended = False
+ self.setLayout(layout)
+
+ self.blk_icon.setVisible(False)
+
+ self.tableItem = self.VmInfoItem(vm.name, vm.qid)
+
+ def update_vm_state (self, vm, blk_visible):
+ self.vm_icon.update()
+ if blk_visible != None:
+ self.blk_icon.setVisible(blk_visible)
+
+
+
+
+class VmTemplateItem (QTableWidgetItem):
+ def __init__(self, vm):
+ super(VmTemplateItem, self).__init__()
+
+ if vm.template_vm is not None:
+ self.setText(vm.template_vm.name)
+ else:
+ font = QFont()
+ font.setStyle(QFont.StyleItalic)
+ self.setFont(font)
+ self.setTextColor(QColor("gray"))
+
+ if vm.is_appvm(): # and vm.template_vm 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.setTextAlignment(Qt.AlignHCenter)
+
+
+class VmIconWidget (QWidget):
+ def __init__(self, icon_path, enabled=True, parent=None):
+ super(VmIconWidget, self).__init__(parent)
- def set_icon(self, icon_path, enabled = True):
label_icon = QLabel()
icon = QIcon (icon_path)
- icon_sz = QSize (VmManagerWindow.row_height * 0.3, VmManagerWindow.row_height * 0.3)
+ icon_sz = QSize (VmManagerWindow.row_height * 0.7, VmManagerWindow.row_height * 0.7)
icon_pixmap = icon.pixmap(icon_sz, QIcon.Disabled if not enabled else QIcon.Normal)
label_icon.setPixmap (icon_pixmap)
label_icon.setFixedSize (icon_sz)
- return label_icon
+
+ layout = QVBoxLayout()
+ layout.addWidget(label_icon)
+ layout.setContentsMargins(0,0,0,0)
+ self.setLayout(layout)
- def update_vm_state (self, vm):
- self.vm_icon.update()
+
+class VmNetvmItem (QTableWidgetItem):
+ def __init__(self, vm):
+ super(VmNetvmItem, self).__init__()
+
+ if vm.is_netvm() and not vm.is_proxyvm():
+ self.setText("n/a")
+ elif vm.netvm_vm is not None:
+ self.setText(vm.netvm_vm.name)
+ else:
+ self.setText("---")
+
+ self.setTextAlignment(Qt.AlignHCenter)
+
+
+class VmUsageBarWidget (QWidget):
+
+ class VmUsageBarItem (QTableWidgetItem):
+ def __init__(self, value):
+ super(VmUsageBarWidget.VmUsageBarItem, self).__init__()
+ self.value = value
+
+ def set_value(self, value):
+ self.value = value
+
+ def __lt__(self, other):
+ return self.value < 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: white;\
+ text-align: center;\
+ }\
+ QProgressBar::chunk:horizontal {\
+ background: qlineargradient(x1: 0, 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)
+
+ 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):
+ super(ChartWidget.ChartItem, self).__init__()
+ self.value = value
+
+ def set_value(self, value):
+ self.value = value
+
+ def __lt__(self, other):
+ return self.value < other.value
+
+ 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)
+
+ 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):
+ def __init__(self, value):
+ super(VmUpdateInfoWidget.VmUpdateInfoItem, self).__init__()
+ self.value = value
+
+ def set_value(self, value):
+ self.value = value
+
+ def __lt__(self, other):
+ if self.value == "outdated":
+ return other.value == "outdated"
+ elif self.value == "update":
+ return other.value == "outdated" or other.value == "update"
+ elif self.value == "ok":
+ return other.value == "outdated" or other.value == "update" or other.value == "ok"
+ else:
+ return True
+
+ def __init__(self, vm, show_text=True, parent = None):
+ super (VmUpdateInfoWidget, self).__init__(parent)
+ layout = QHBoxLayout ()
+ self.show_text = show_text
+ if self.show_text:
+ self.label=QLabel("")
+ layout.addWidget(self.label, alignment=Qt.AlignCenter)
+ else:
+ self.icon = QLabel("")
+ layout.addWidget(self.icon, alignment=Qt.AlignHCenter)
+ self.setLayout(layout)
+
+ self.previous_outdated = False
+ self.previous_update_recommended = None
+ self.value = None
+ self.tableItem = VmUpdateInfoWidget.VmUpdateInfoItem(self.value)
def update_outdated(self, vm):
outdated = vm.is_outdated()
- if outdated != self.previous_outdated:
- if outdated:
- self.label_name.setText(vm.name + " (outdated)")
- else:
- self.label_name.setText(vm.name)
+ if outdated and not self.previous_outdated:
+ self.update_status_widget("outdated")
+
self.previous_outdated = outdated
if vm.is_updateable():
update_recommended = self.previous_update_recommended
@@ -179,143 +370,45 @@ class VmInfoWidget (QWidget):
update_recommended = True
else:
update_recommended = False
- if update_recommended != self.previous_update_recommended:
- if update_recommended:
- self.label_name.setText(vm.name + " (check updates)")
- else:
- self.label_name.setText(vm.name)
- self.previous_update_recommended = update_recommended
+ if not self.show_text and self.previous_update_recommended != False:
+ self.update_status_widget("ok")
+
+ if update_recommended and not self.previous_update_recommended:
+ self.update_status_widget("update")
+ self.previous_update_recommended = update_recommended
-class VmUsageWidget (QWidget):
- def __init__(self, vm, cpu_load = 0, parent = None):
- super (VmUsageWidget, self).__init__(parent)
+ def update_status_widget(self, state):
+ self.value = state
+ self.tableItem.set_value(state)
+ if state == "ok":
+ label_text = ""
+ icon_path = ":/flag-green.png"
+ tooltip_text = "VM up to date"
+ elif state == "update":
+ label_text = "Check updates"
+ icon_path = ":/flag-yellow.png"
+ tooltip_text = "Update recommended"
+ elif state == "outdated":
+ label_text = "VM outdated"
+ icon_path = ":/flag-red.png"
+ tooltip_text = "VM outdated"
- self.cpu_widget = QProgressBar()
- self.mem_widget = QProgressBar()
- self.cpu_widget.setMinimum(0)
- self.cpu_widget.setMaximum(100)
- self.mem_widget.setMinimum(0)
- self.mem_widget.setMaximum(qubes_host.memory_total/1024)
- self.mem_widget.setFormat ("%v MB");
- self.cpu_label = QLabel("CPU")
- self.mem_label = QLabel("MEM")
-
- layout_cpu = QHBoxLayout()
- layout_cpu.addWidget(self.cpu_label)
- layout_cpu.addWidget(self.cpu_widget)
-
- layout_mem = QHBoxLayout()
- layout_mem.addWidget(self.mem_label)
- layout_mem.addWidget(self.mem_widget)
-
- layout = QVBoxLayout()
- layout.addLayout(layout_cpu)
- layout.addLayout(layout_mem)
-
- self.setLayout(layout)
-
- self.update_load(vm, cpu_load)
-
- def update_load(self, vm, cpu_load):
- self.cpu_load = cpu_load if vm.last_power_state else 0
- self.mem_load = vm.get_mem()/1024 if vm.last_power_state else 0
-
- self.cpu_widget.setValue(self.cpu_load)
- self.mem_widget.setValue(self.mem_load)
-
- def resizeEvent(self, Event = None):
- label_width = max(self.mem_label.width(), self.cpu_label.width())
- self.mem_label.setMinimumWidth(label_width)
- self.cpu_label.setMinimumWidth(label_width)
- super (VmUsageWidget, self).resizeEvent(Event)
-
-class LoadChartWidget (QWidget):
-
- def __init__(self, vm, cpu_load = 0, parent = None):
- super (LoadChartWidget, self).__init__(parent)
- self.load = cpu_load if vm.last_power_state else 0
- assert self.load >= 0 and self.load <= 100, "load = {0}".format(self.load)
- self.load_history = [self.load]
-
- def update_load (self, vm, cpu_load):
- self.load = cpu_load if vm.last_power_state else 0
- 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 :/
- self.load = 100
-
- self.load_history.append (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]
- hue = 200
- sat = 70 + val*(255-70)/100
- color = QColor.fromHsv (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 MemChartWidget (QWidget):
-
- def __init__(self, vm, parent = None):
- super (MemChartWidget, self).__init__(parent)
- self.load = vm.get_mem()*100/qubes_host.memory_total if vm.last_power_state else 0
- assert self.load >= 0 and self.load <= 100, "mem = {0}".format(self.load)
- self.load_history = [self.load]
-
- def update_load (self, vm):
- self.load = vm.get_mem()*100/qubes_host.memory_total if vm.last_power_state else 0
- assert self.load >= 0 and self.load <= 100, "load = {0}".format(self.load)
- self.load_history.append (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]
- hue = 120
- sat = 70 + val*(255-70)/100
- color = QColor.fromHsv (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)
+ if self.show_text:
+ self.label.setText(label_text)
+ else:
+ self.layout().removeWidget(self.icon)
+ self.icon.deleteLater()
+ self.icon = VmIconWidget(icon_path, True)
+ self.icon.setToolTip(tooltip_text)
+ self.layout().addWidget(self.icon, alignment=Qt.AlignCenter)
class VmRowInTable(object):
- def __init__(self, vm, row_no, table):
+ cpu_graph_hue = 210
+ mem_graph_hue = 120
+
+ def __init__(self, vm, row_no, table, block_manager):
self.vm = vm
self.row_no = row_no
@@ -323,24 +416,46 @@ class VmRowInTable(object):
self.info_widget = VmInfoWidget(vm)
table.setCellWidget(row_no, 0, self.info_widget)
+ table.setItem(row_no, 0, self.info_widget.tableItem)
- self.usage_widget = VmUsageWidget(vm)
- table.setCellWidget(row_no, 1, self.usage_widget)
+ self.upd_widget = VmUpdateInfoWidget(vm, False)
+ table.setCellWidget(row_no, 1, self.upd_widget)
+ table.setItem(row_no, 1, self.upd_widget.tableItem)
- self.load_widget = LoadChartWidget(vm)
- table.setCellWidget(row_no, 2, self.load_widget)
+ self.template_widget = VmTemplateItem(vm)
+ table.setItem(row_no, 2, self.template_widget)
+
+ self.netvm_widget = VmNetvmItem(vm)
+ table.setItem(row_no, 3, self.netvm_widget)
- self.mem_widget = MemChartWidget(vm)
- table.setCellWidget(row_no, 3, self.mem_widget)
+ self.cpu_usage_widget = VmUsageBarWidget(0, 100, "%v %",
+ lambda vm, val: val if vm.last_power_state else 0, vm, 0, self.cpu_graph_hue)
+ table.setCellWidget(row_no, 4, self.cpu_usage_widget)
+ table.setItem(row_no, 4, self.cpu_usage_widget.tableItem)
+
+ self.load_widget = ChartWidget(vm, lambda vm, val: val if vm.last_power_state else 0, self.cpu_graph_hue, 0 )
+ table.setCellWidget(row_no, 5, self.load_widget)
+ table.setItem(row_no, 5, self.load_widget.tableItem)
+
+ self.mem_usage_widget = VmUsageBarWidget(0, qubes_host.memory_total/1024, "%v MB",
+ lambda vm, val: vm.get_mem()/1024 if vm.last_power_state else 0, vm, 0, self.mem_graph_hue)
+ table.setCellWidget(row_no, 6, self.mem_usage_widget)
+ table.setItem(row_no, 6, self.mem_usage_widget.tableItem)
- def update(self, counter, cpu_load = None):
- self.info_widget.update_vm_state(self.vm)
+ self.mem_widget = ChartWidget(vm, lambda vm, val: vm.get_mem()*100/qubes_host.memory_total if vm.last_power_state else 0, self.mem_graph_hue, 0)
+ table.setCellWidget(row_no, 7, self.mem_widget)
+ table.setItem(row_no, 7, self.mem_widget.tableItem)
+
+
+ def update(self, counter, blk_visible = None, cpu_load = None):
+ self.info_widget.update_vm_state(self.vm, blk_visible)
if cpu_load is not None:
- self.usage_widget.update_load(self.vm, cpu_load)
+ 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)
- self.info_widget.update_outdated(self.vm)
+ self.mem_widget.update_load(self.vm, None)
+ self.upd_widget.update_outdated(self.vm)
class NewAppVmDlg (QDialog, ui_newappvmdlg.Ui_NewAppVMDlg):
def __init__(self, parent = None):
@@ -370,117 +485,71 @@ class VmShutdownMonitor(QObject):
else:
QTimer.singleShot (vm_shutdown_timeout, self.check_if_vm_has_shutdown)
-class ThreadMonitor(QObject):
- def __init__(self):
- self.success = True
- self.error_msg = None
- self.event_finished = threading.Event()
- def set_error_msg(self, error_msg):
- self.success = False
- self.error_msg = error_msg
- self.set_finished()
-
- def is_finished(self):
- return self.event_finished.is_set()
-
- def set_finished(self):
- self.event_finished.set()
-
-
-class VmManagerWindow(QMainWindow):
- columns_widths = [250, 200, 150, 150]
- row_height = 50
- max_visible_rows = 14
+class VmManagerWindow(Ui_VmManagerWindow, QMainWindow):
+ row_height = 30
+ column_width = 200
+ max_visible_rows = 7
update_interval = 1000 # in msec
show_inactive_vms = True
- columns_states = { 0: [0, 1], 1: [0, 2, 3] }
+ columns_indices = { "Name": 0,
+ "Upd": 1,
+ "Template": 2,
+ "NetVM": 3,
+ "CPU": 4,
+ "CPU Graph": 5,
+ "MEM": 6,
+ "MEM Graph": 7,}
+
+
def __init__(self, parent=None):
- super(VmManagerWindow, self).__init__(parent)
-
-
- self.action_createvm = self.createAction ("Create AppVM", slot=self.create_appvm,
- icon="createvm", tip="Create a new AppVM")
-
- self.action_removevm = self.createAction ("Remove AppVM", slot=self.remove_appvm,
- icon="removevm", tip="Remove an existing AppVM (must be stopped first)")
-
- self.action_resumevm = self.createAction ("Start/Resume VM", slot=self.resume_vm,
- icon="resumevm", tip="Start/Resume a VM")
-
- self.action_pausevm = self.createAction ("Pause VM", slot=self.pause_vm,
- icon="pausevm", tip="Pause a running VM")
-
- self.action_shutdownvm = self.createAction ("Shutdown VM", slot=self.shutdown_vm,
- icon="shutdownvm", tip="Shutdown a running VM")
-
- self.action_appmenus = self.createAction ("Select VM applications", slot=self.appmenus_select,
- icon="root", tip="Select applications present in menu for this VM")
-
- self.action_updatevm = self.createAction ("Update VM", slot=self.update_vm,
- icon="updateable", tip="Update VM system")
-
- self.action_showallvms = self.createAction ("Show/Hide Inactive VMs", slot=self.toggle_inactive_view, checkable=True,
- icon="showallvms", tip="Show/Hide Inactive VMs")
-
- self.action_showcpuload = self.createAction ("Show/Hide CPU Load chart", slot=self.showcpuload, checkable=True,
- icon="showcpuload", tip="Show/Hide CPU Load chart")
-
- self.action_editfwrules = self.createAction ("Edit VM Firewall rules", slot=self.edit_fw_rules,
- icon="firewall", tip="Edit VM Firewall rules")
-
-
- self.action_removevm.setDisabled(True)
- self.action_resumevm.setDisabled(True)
- self.action_pausevm.setDisabled(True)
- self.action_shutdownvm.setDisabled(True)
- self.action_appmenus.setDisabled(True)
- self.action_updatevm.setDisabled(True)
-
- self.action_showallvms.setChecked(self.show_inactive_vms)
-
- self.toolbar = self.addToolBar ("Toolbar")
- self.toolbar.setFloatable(False)
- self.addActions (self.toolbar, (self.action_createvm, self.action_removevm,
- None,
- self.action_resumevm, self.action_shutdownvm,
- self.action_editfwrules, self.action_appmenus,
- self.action_updatevm,
- None,
- self.action_showcpuload,
- self.action_showallvms,
- ))
-
- self.table = QTableWidget()
- self.setCentralWidget(self.table)
- self.table.clear()
- self.table.setColumnCount(len(VmManagerWindow.columns_widths))
- for (col, width) in enumerate (VmManagerWindow.columns_widths):
- self.table.setColumnWidth (col, width)
-
- self.table.horizontalHeader().setResizeMode(QHeaderView.Stretch)
- self.table.horizontalHeader().setResizeMode(0, QHeaderView.Fixed)
- self.table.setAlternatingRowColors(True)
- self.table.verticalHeader().hide()
- self.table.horizontalHeader().hide()
- self.table.setGridStyle(Qt.NoPen)
- self.table.setSortingEnabled(False)
- self.table.setSelectionBehavior(QTableWidget.SelectRows)
- self.table.setSelectionMode(QTableWidget.SingleSelection)
-
- self.__cpugraphs = self.action_showcpuload.isChecked()
- self.update_table_columns()
-
+ super(VmManagerWindow, self).__init__()
+ self.setupUi(self)
+ self.toolbar = self.toolBar
+
self.qvm_collection = QubesVmCollection()
- self.setWindowTitle("Qubes VM Manager")
-
+ self.blk_manager = QubesBlockDevicesManager(self.qvm_collection)
+
self.connect(self.table, SIGNAL("itemSelectionChanged()"), self.table_selection_changed)
-
+
cur_pos = self.pos()
- self.setFixedWidth (self.get_minimum_table_width())
+ self.table.setColumnWidth(0, self.column_width)
+ self.setSizeIncrement(QtCore.QSize(200, 30))
+ self.centralwidget.setSizeIncrement(QtCore.QSize(200, 30))
+ self.table.setSizeIncrement(QtCore.QSize(200, 30))
self.fill_table()
self.move(cur_pos)
+
+ self.table.setColumnHidden( self.columns_indices["NetVM"], True)
+ self.actionNetVM.setChecked(False)
+ self.table.setColumnHidden( self.columns_indices["CPU Graph"], True)
+ self.actionCPU_Graph.setChecked(False)
+ self.table.setColumnHidden( self.columns_indices["MEM Graph"], True)
+ self.actionMEM_Graph.setChecked(False)
+ self.table.setColumnWidth(self.columns_indices["Upd"], 50)
+
+ self.table.sortItems(self.columns_indices["MEM"], Qt.DescendingOrder)
+
+ self.context_menu = QMenu(self)
+ self.context_menu.addAction(self.action_settings)
+ self.context_menu.addAction(self.action_removevm)
+ self.context_menu.addAction(self.action_resumevm)
+ self.context_menu.addAction(self.action_pausevm)
+ self.context_menu.addAction(self.action_shutdownvm)
+ self.context_menu.addAction(self.action_appmenus)
+ self.context_menu.addAction(self.action_editfwrules)
+ self.context_menu.addAction(self.action_updatevm)
+
+ self.blk_menu = QMenu("Block devices")
+ self.context_menu.addMenu(self.blk_menu)
+
+ self.connect(self.table, SIGNAL("customContextMenuRequested(const QPoint&)"), self.open_context_menu)
+ self.connect(self.blk_menu, SIGNAL("triggered(QAction *)"), self.attach_dettach_device_triggered)
+
+ self.table.setContentsMargins(0,0,0,0)
+ self.centralwidget.layout().setContentsMargins(0,0,0,0)
+ self.layout().setContentsMargins(0,0,0,0)
self.counter = 0
self.shutdown_monitor = {}
@@ -488,45 +557,43 @@ class VmManagerWindow(QMainWindow):
self.last_measure_time = time.time()
QTimer.singleShot (self.update_interval, self.update_table)
+ def show(self):
+ super(VmManagerWindow, self).show()
+ self.set_table_geom_height()
+ self.update_table_columns()
+
def set_table_geom_height(self):
- # TODO: '6' -- WTF?!
- tbl_H = self.toolbar.height() + 6 + \
- self.table.horizontalHeader().height() + 6
+ minH = self.table.horizontalHeader().height() +\
+ 2*self.table.frameWidth()
+ #All this sizing is kind of magic, so change it only if you have to
+ #or if you know what you're doing :)
+
n = self.table.rowCount();
- if n > VmManagerWindow.max_visible_rows:
- n = VmManagerWindow.max_visible_rows
- for i in range (0, n):
- tbl_H += self.table.rowHeight(i)
- self.setFixedHeight(tbl_H)
+ maxH = minH
+ if n >= self.max_visible_rows:
+ minH += self.max_visible_rows*self.row_height
+ maxH += n*self.row_height
+ else:
+ minH += n*self.row_height
+ maxH = minH
+
+ self.centralwidget.setMinimumHeight(minH)
+ self.centralwidget.setMaximumHeight(maxH)
+ mainwindow_to_add = self.menubar.height() +\
+ self.toolbar.height() + \
+ self.menubar.contentsMargins().top() + self.menubar.contentsMargins().bottom() +\
+ self.toolbar.contentsMargins().top() + self.toolbar.contentsMargins().bottom()
- def addActions(self, target, actions):
- for action in actions:
- if action is None:
- target.addSeparator()
- else:
- target.addAction(action)
-
-
- def createAction(self, text, slot=None, shortcut=None, icon=None,
- tip=None, checkable=False, signal="triggered()"):
- action = QAction(text, self)
- if icon is not None:
- action.setIcon(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, SIGNAL(signal), slot)
- if checkable:
- action.setCheckable(True)
- return action
+ maxH += mainwindow_to_add
+ minH += mainwindow_to_add
+ self.setMaximumHeight(maxH)
+ self.setMinimumHeight(minH)
+
def get_vms_list(self):
self.qvm_collection.lock_db_for_reading()
self.qvm_collection.load()
@@ -559,11 +626,12 @@ class VmManagerWindow(QMainWindow):
return vms_to_display
def fill_table(self):
- self.table.clear()
+ self.table.setSortingEnabled(False)
+ self.table.clearContents()
vms_list = self.get_vms_list()
self.table.setRowCount(len(vms_list))
- vms_in_table = []
+ vms_in_table = {}
row_no = 0
for vm in vms_list:
@@ -571,15 +639,15 @@ class VmManagerWindow(QMainWindow):
continue
if vm.internal:
continue
- vm_row = VmRowInTable (vm, row_no, self.table)
- vms_in_table.append (vm_row)
+ vm_row = VmRowInTable (vm, row_no, self.table, self.blk_manager)
+ vms_in_table[vm.qid] = vm_row
row_no += 1
self.table.setRowCount(row_no)
- self.set_table_geom_height()
self.vms_list = vms_list
self.vms_in_table = vms_in_table
self.reload_table = False
+ self.table.setSortingEnabled(True)
def mark_table_for_update(self):
@@ -587,7 +655,7 @@ class VmManagerWindow(QMainWindow):
# 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
if manager_window.isVisible():
some_vms_have_changed_power_state = False
for vm in self.vms_list:
@@ -598,69 +666,103 @@ class VmManagerWindow(QMainWindow):
if self.reload_table or ((not self.show_inactive_vms) and some_vms_have_changed_power_state):
self.fill_table()
+ update_devs=True
+
+ blk_visible = None
+ rows_with_blk = None
+ if update_devs == True:
+ rows_with_blk = []
+ for d in self.blk_manager.attached_devs:
+ rows_with_blk.append( self.blk_manager.attached_devs[d]['attached_to']['vm'])
if self.counter % 3 == 0 or out_of_schedule:
(self.last_measure_time, self.last_measure_results) = \
qubes_host.measure_cpu_usage(self.last_measure_results,
self.last_measure_time)
- for vm_row in self.vms_in_table:
+ 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
- vm_row.update(self.counter, cpu_load = cur_cpu_load)
- else:
- for vm_row in self.vms_in_table:
- vm_row.update(self.counter)
- self.table_selection_changed()
+ if rows_with_blk != None:
+ if vm_row.vm.name in rows_with_blk:
+ blk_visible = True
+ else:
+ blk_visible = False
+
+ vm_row.update(self.counter, blk_visible=blk_visible, cpu_load = cur_cpu_load)
+ else:
+ for vm_row in self.vms_in_table.values():
+ if rows_with_blk != None:
+ if vm_row.vm.name in rows_with_blk:
+ blk_visible = True
+ else:
+ blk_visible = False
+
+ vm_row.update(self.counter, blk_visible=blk_visible)
+
+ #self.table_selection_changed()
if not out_of_schedule:
self.counter += 1
QTimer.singleShot (self.update_interval, self.update_table)
def update_table_columns(self):
- state = 1 if self.__cpugraphs else 0
- columns = self.columns_states[state]
- for i in range(0, self.table.columnCount()):
- enabled = columns.count(i) > 0
- self.table.setColumnHidden(i, not enabled)
+ table_width = self.table.horizontalHeader().length() +\
+ self.table.verticalScrollBar().width() + \
+ 2*self.table.frameWidth() + 1
- self.setMinimumWidth(self.get_minimum_table_width())
+ self.table.setFixedWidth( table_width )
+ self.centralwidget.setFixedWidth(table_width)
+ self.setFixedWidth(table_width)
+
+ def update_block_devices(self):
+ res, msg = self.blk_manager.update()
+ if msg != None and len(msg) > 0:
+ str = "\n".join(msg)
+ trayIcon.showMessage ("Qubes Manager", str, msecs=5000)
+ return res
def table_selection_changed (self):
+
vm = self.get_selected_vm()
- # Update available actions:
+ if vm != None:
+ # Update available actions:
+ self.action_settings.setEnabled(True)
+ self.action_removevm.setEnabled(not vm.installed_by_rpm and not vm.last_power_state)
+ self.action_resumevm.setEnabled(not vm.last_power_state)
+ self.action_pausevm.setEnabled(vm.last_power_state and vm.qid != 0)
+ self.action_shutdownvm.setEnabled(not vm.is_netvm() and vm.last_power_state and vm.qid != 0)
+ self.action_appmenus.setEnabled(not vm.is_netvm())
+ 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)
+ else:
+ self.action_settings.setEnabled(False)
+ self.action_removevm.setEnabled(False)
+ self.action_resumevm.setEnabled(False)
+ self.action_pausevm.setEnabled(False)
+ self.action_shutdownvm.setEnabled(False)
+ self.action_appmenus.setEnabled(False)
+ self.action_editfwrules.setEnabled(False)
+ self.action_updatevm.setEnabled(False)
- self.action_removevm.setEnabled(not vm.installed_by_rpm and not vm.last_power_state)
- self.action_resumevm.setEnabled(not vm.last_power_state)
- self.action_pausevm.setEnabled(vm.last_power_state and vm.qid != 0)
- self.action_shutdownvm.setEnabled(not vm.is_netvm() and vm.last_power_state and vm.qid != 0)
- self.action_appmenus.setEnabled(not vm.is_netvm())
- 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)
- def get_minimum_table_width(self):
- tbl_W = 0
- for (col, w) in enumerate(VmManagerWindow.columns_widths):
- if not self.table.isColumnHidden(col):
- tbl_W += w
-
- return tbl_W
def closeEvent (self, event):
if event.spontaneous(): # There is something borked in Qt, as the logic here is inverted on X11
self.hide()
event.ignore()
- def create_appvm(self):
+
+ @pyqtSlot(name='on_action_createvm_triggered')
+ def action_createvm_triggered(self):
dialog = NewAppVmDlg()
-
# Theoretically we should be locking for writing here and unlock
# only after the VM creation finished. But the code would be more messy...
# Instead we lock for writing in the actual worker thread
@@ -746,12 +848,19 @@ class VmManagerWindow(QMainWindow):
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()
- assert self.vms_in_table[row_index] is not None
- vm = self.vms_in_table[row_index].vm
- return vm
+ if row_index != None:
+ (vm_name, qid) = self.table.item(row_index, self.columns_indices["Name"]).value
+ assert self.vms_in_table[qid] is not None
+ vm = self.vms_in_table[qid].vm
+ return vm
+ else:
+ return None
+
+ @pyqtSlot(name='on_action_removevm_triggered')
+ def action_removevm_triggered(self):
- def remove_appvm(self):
vm = self.get_selected_vm()
assert not vm.is_running()
assert not vm.installed_by_rpm
@@ -820,7 +929,8 @@ class VmManagerWindow(QMainWindow):
thread_monitor.set_finished()
- def resume_vm(self):
+ @pyqtSlot(name='on_action_resumevm_triggered')
+ def action_resumevm_triggered(self):
vm = self.get_selected_vm()
assert not vm.is_running()
@@ -862,7 +972,8 @@ class VmManagerWindow(QMainWindow):
thread_monitor.set_finished()
- def pause_vm(self):
+ @pyqtSlot(name='on_action_pausevm_triggered')
+ def action_pausevm_triggered(self):
vm = self.get_selected_vm()
assert vm.is_running()
try:
@@ -871,7 +982,8 @@ class VmManagerWindow(QMainWindow):
QMessageBox.warning (None, "Error pausing VM!", "ERROR: {0}".format(ex))
return
- def shutdown_vm(self):
+ @pyqtSlot(name='on_action_shutdownvm_triggered')
+ def action_shutdownvm_triggered(self):
vm = self.get_selected_vm()
assert vm.is_running()
@@ -893,12 +1005,22 @@ class VmManagerWindow(QMainWindow):
self.shutdown_monitor[vm.qid] = VmShutdownMonitor (vm)
QTimer.singleShot (vm_shutdown_timeout, self.shutdown_monitor[vm.qid].check_if_vm_has_shutdown)
- def appmenus_select(self):
+ @pyqtSlot(name='on_action_settings_triggered')
+ def action_settings_triggered(self):
vm = self.get_selected_vm()
- select_window = AppmenuSelectWindow(vm)
- select_window.exec_()
+ settings_window = VMSettingsWindow(vm, app, self.qvm_collection, "basic")
+ settings_window.exec_()
+
- def update_vm(self):
+ @pyqtSlot(name='on_action_appmenus_triggered')
+ def action_appmenus_triggered(self):
+ vm = self.get_selected_vm()
+ settings_window = VMSettingsWindow(vm, app, self.qvm_collection, "applications")
+ settings_window.exec_()
+
+
+ @pyqtSlot(name='on_action_updatevm_triggered')
+ def action_updatevm_triggered(self):
vm = self.get_selected_vm()
if not vm.is_running():
@@ -939,28 +1061,186 @@ class VmManagerWindow(QMainWindow):
return
thread_monitor.set_finished()
- def showcpuload(self):
- self.__cpugraphs = self.action_showcpuload.isChecked()
- self.update_table_columns()
-
- def toggle_inactive_view(self):
+ @pyqtSlot(name='on_action_showallvms_triggered')
+ def action_showallvms_triggered(self):
self.show_inactive_vms = self.action_showallvms.isChecked()
self.mark_table_for_update()
self.update_table(out_of_schedule = True)
+ self.set_table_geom_height()
- def edit_fw_rules(self):
+ @pyqtSlot(name='on_action_editfwrules_triggered')
+ def action_editfwrules_triggered(self):
vm = self.get_selected_vm()
- dialog = EditFwRulesDlg()
- model = QubesFirewallRulesModel()
- model.set_vm(vm)
- dialog.set_model(model)
+ settings_window = VMSettingsWindow(vm, app, self.qvm_collection, "firewall")
+ settings_window.exec_()
+
+ @pyqtSlot(name='on_action_global_settings_triggered')
+ def action_global_settings_triggered(self):
+ global_settings_window = GlobalSettingsWindow()
+ global_settings_window.exec_()
+
+
+ @pyqtSlot(name='on_action_restore_triggered')
+ def action_restore_triggered(self):
+ restore_window = RestoreVMsWindow(app, self.qvm_collection, self.blk_manager)
+ restore_window.exec_()
+
+ @pyqtSlot(name='on_action_backup_triggered')
+ def action_backup_triggered(self):
+ backup_window = BackupVMsWindow(app, self.qvm_collection, self.blk_manager)
+ backup_window.exec_()
+
+
+
+ def showhide_collumn(self, col_num, show):
+ self.table.setColumnHidden( col_num, not show)
+ self.update_table_columns()
+
+ def on_actionUpd_toggled(self, checked):
+ self.showhide_collumn( self.columns_indices['Upd'], checked)
+
+ def on_actionTemplate_toggled(self, checked):
+ self.showhide_collumn( self.columns_indices['Template'], checked)
+
+ def on_actionNetVM_toggled(self, checked):
+ self.showhide_collumn( self.columns_indices['NetVM'], checked)
+
+ def on_actionCPU_toggled(self, checked):
+ self.showhide_collumn( self.columns_indices['CPU'], checked)
+
+ def on_actionCPU_Graph_toggled(self, checked):
+ self.showhide_collumn( self.columns_indices['CPU Graph'], checked)
+
+ def on_actionMEM_toggled(self, checked):
+ self.showhide_collumn( self.columns_indices['MEM'], checked)
+
+ def on_actionMEM_Graph_toggled(self, checked):
+ self.showhide_collumn( self.columns_indices['MEM Graph'], checked)
+
+
+ @pyqtSlot('const QPoint&')
+ def open_context_menu(self, point):
+ vm = self.get_selected_vm()
+ if not vm.is_running():
+ self.blk_menu.setEnabled(False)
+ else:
+ self.blk_menu.clear()
+ self.blk_menu.setEnabled(True)
+ 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'] == vm.name:
+ str = "Detach " + d + " " + unicode(self.blk_manager.attached_devs[d]['size']) + " " + self.blk_manager.attached_devs[d]['desc']
+ action = self.blk_menu.addAction(QIcon(":/remove.png"), str)
+ action.setData(QVariant(d))
+
+ if len(self.blk_manager.free_devs) > 0:
+ for d in self.blk_manager.free_devs:
+ if d.startswith(vm.name):
+ continue
+ str = "Attach " + d + " " + unicode(self.blk_manager.free_devs[d]['size']) + " " + self.blk_manager.free_devs[d]['desc']
+ action = self.blk_menu.addAction(QIcon(":/add.png"), str)
+ action.setData(QVariant(d))
+
+ if self.blk_menu.isEmpty():
+ self.blk_menu.setEnabled(False)
+
+ self.context_menu.exec_(self.table.mapToGlobal(point))
+
+ @pyqtSlot('QAction *')
+ def attach_dettach_device_triggered(self, action):
+ dev = str(action.data().toString())
+ vm = self.get_selected_vm()
+ if dev in self.blk_manager.attached_devs:
+ self.blk_manager.detach_device(vm, dev)
+ else:
+ self.blk_manager.attach_device(vm, dev)
+
+
+class QubesBlockDevicesManager():
+ def __init__(self, qvm_collection):
+ self.qvm_collection = qvm_collection
+ self.attached_devs = {}
+ self.free_devs = {}
+
+ self.current_blk = {}
+ self.current_attached = {}
+ self.devs_changed = False
+
+ def update(self):
+ blk = qubesutils.block_list()
+ msg = []
+ for b in blk:
+ att = qubesutils.block_check_attached(None, blk[b]['device'], backend_xid = blk[b]['xid'])
+ if b in self.current_blk:
+ if blk[b] == self.current_blk[b]:
+ if self.current_attached[b] != att: #devices the same, sth with attaching changed
+ self.current_attached[b] = att
+ self.devs_changed = True
+ else: #device changed ?!
+ self.current_blk[b] = blk[b]
+ self.current_attached[b] = att
+ self.devs_changed = True
+ else: #new device
+ self.current_blk[b] = blk[b]
+ self.current_attached[b] = att
+ self.devs_changed = True
+ msg.append("Attached new device: {0}".format(blk[b]['device']))
+
+ to_delete = []
+ for b in self.current_blk: #remove devices that are not there anymore
+ if b not in blk:
+ to_delete.append(b)
+ self.devs_changed = True
+ msg.append("Detached device: {0}".format(self.current_blk[b]['device']))
+
+ for d in to_delete:
+ del self.current_blk[d]
+ del self.current_attached[d]
+
+ if self.devs_changed == True:
+ self.devs_changed = False
+ self.__update_blk_entries__()
+ return True, msg
+ else:
+ return False, None
+
+
+ def __update_blk_entries__(self):
+ self.free_devs.clear()
+ self.attached_devs.clear()
+
+ for b in self.current_attached:
+ if self.current_attached[b]:
+ self.attached_devs[b] = self.__make_entry__(b, self.current_blk[b], self.current_attached[b])
+ else:
+ self.free_devs[b] = self.__make_entry__(b, self.current_blk[b], None)
+
+ def __make_entry__(self, k, dev, att):
+ size_str = qubesutils.bytes_to_kmg(dev['size'])
+ entry = { 'dev': dev['device'],
+ 'backend_name': dev['vm'],
+ 'desc': dev['desc'],
+ 'size': size_str,
+ 'attached_to': att, }
+ return entry
+
+ def attach_device(self, vm, dev):
+ backend_vm_name = self.free_devs[dev]['backend_name']
+ dev_id = self.free_devs[dev]['dev']
+ backend_vm = self.qvm_collection.get_vm_by_name(backend_vm_name)
+ trayIcon.showMessage ("Qubes Manager", "{0} - attaching {1}".format(vm.name, dev), msecs=3000)
+ qubesutils.block_attach(vm, backend_vm, dev_id)
+ self.devs_changed = True
+
+ def detach_device(self, vm, dev_name):
+ dev_id = self.attached_devs[dev_name]['attached_to']['devid']
+ vm_xid = self.attached_devs[dev_name]['attached_to']['xid']
+ trayIcon.showMessage ("Qubes Manager", "{0} - detaching {1}".format(vm.name, dev_name), msecs=3000)
+ qubesutils.block_detach(None, dev_id, vm_xid)
+ self.devs_changed = True
+
- if vm.netvm_vm is not None and not vm.netvm_vm.is_proxyvm():
- QMessageBox.warning (None, "VM configuration problem!", "The '{0}' AppVM is not network connected to a FirewallVM!".format(vm.name) +\
- "You may edit the '{0}' VM firewall rules, but these will not take any effect until you connect it to a working Firewall VM.".format(vm.name))
- if dialog.exec_():
- model.apply_rules()
class QubesTrayIcon(QSystemTrayIcon):
def __init__(self, icon):
@@ -1095,4 +1375,3 @@ def main():
app.exec_()
trayIcon = None
-
diff --git a/qubesmanager/multiselectwidget.py b/qubesmanager/multiselectwidget.py
new file mode 100644
index 0000000..a9de974
--- /dev/null
+++ b/qubesmanager/multiselectwidget.py
@@ -0,0 +1,52 @@
+import sys
+from PyQt4.QtCore import *
+from PyQt4.QtGui import *
+from ui_multiselectwidget import *
+
+class MultiSelectWidget(Ui_MultiSelectWidget, QWidget):
+
+ __pyqtSignals__ = ("selected_changed()",)
+
+ def __init__(self, parent=None):
+ super(MultiSelectWidget, self).__init__()
+ self.setupUi(self);
+ self.add_selected_button.clicked.connect(self.add_selected)
+ self.add_all_button.clicked.connect(self.add_all)
+ self.remove_selected_button.clicked.connect(self.remove_selected)
+ self.remove_all_button.clicked.connect(self.remove_all)
+ self.available_list.setSelectionMode(QAbstractItemView.ExtendedSelection)
+ self.selected_list.setSelectionMode(QAbstractItemView.ExtendedSelection)
+
+ def switch_selected(self, src, dst):
+ selected = src.selectedItems()
+
+ for s in selected:
+ row = src.indexFromItem(s).row()
+ item = src.takeItem(row)
+ dst.addItem(item)
+ dst.sortItems()
+ self.emit(SIGNAL("selected_changed()"))
+
+ def add_selected(self):
+ self.switch_selected(self.available_list, self.selected_list)
+
+ def remove_selected(self):
+ self.switch_selected(self.selected_list, self.available_list)
+
+ def move_all(self, src, dst):
+ while src.count() > 0:
+ item = src.takeItem(0)
+ dst.addItem(item)
+ dst.sortItems()
+ self.emit(SIGNAL("selected_changed()"))
+
+ def add_all(self):
+ self.move_all(self.available_list, self.selected_list)
+
+ def remove_all(self):
+ self.move_all(self.selected_list, self.available_list)
+
+ def clear(self):
+ self.available_list.clear()
+ self.selected_list.clear()
+
diff --git a/qubesmanager/restore.py b/qubesmanager/restore.py
new file mode 100644
index 0000000..a2d509e
--- /dev/null
+++ b/qubesmanager/restore.py
@@ -0,0 +1,276 @@
+#!/usr/bin/python2.6
+#
+# The Qubes OS Project, http://www.qubes-os.org
+#
+# Copyright (C) 2012 Agnieszka Kostrzewa
+# Copyright (C) 2012 Marek Marczykowski
+#
+# 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 General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+#
+
+import sys
+import os
+from PyQt4.QtCore import *
+from PyQt4.QtGui import *
+
+from qubes.qubes import QubesVmCollection
+from qubes.qubes import QubesException
+from qubes.qubes import QubesDaemonPidfile
+from qubes.qubes import QubesHost
+from qubes.qubes import qubes_base_dir
+import qubesmanager.resources_rc
+
+from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent
+
+import subprocess
+import time
+from operator import itemgetter
+from thread_monitor import *
+
+from qubes import qubesutils
+
+from ui_restoredlg import *
+from multiselectwidget import *
+
+from backup_utils import *
+
+
+
+class RestoreVMsWindow(Ui_Restore, QWizard):
+
+ __pyqtSignals__ = ("restore_progress(int)",)
+
+ def __init__(self, app, qvm_collection, blk_manager, parent=None):
+ super(RestoreVMsWindow, self).__init__(parent)
+
+ self.app = app
+ self.qvm_collection = qvm_collection
+ self.blk_manager = blk_manager
+
+ self.dev_mount_path = None
+ self.backup_dir = None
+ self.restore_options = None
+ self.backup_vms_list = None
+ self.func_output = []
+
+ self.excluded = {}
+
+ for vm in self.qvm_collection.values():
+ if vm.qid == 0:
+ self.vm = vm
+ break;
+
+ assert self.vm != None
+
+ self.setupUi(self)
+
+ self.select_vms_widget = MultiSelectWidget(self)
+ self.select_vms_layout.insertWidget(1, self.select_vms_widget)
+
+ self.connect(self, SIGNAL("currentIdChanged(int)"), self.current_page_changed)
+ self.connect(self.dev_combobox, SIGNAL("activated(int)"), self.dev_combobox_activated)
+ self.connect(self, SIGNAL("restore_progress(QString)"), self.commit_text_edit.append)
+
+ self.select_dir_page.isComplete = self.has_selected_dir
+ self.select_vms_page.isComplete = self.has_selected_vms
+ #FIXME
+ #this causes to run isComplete() twice, I don't know why
+ self.select_vms_page.connect(self.select_vms_widget, SIGNAL("selected_changed()"), SIGNAL("completeChanged()"))
+
+ fill_devs_list(self)
+ self.__init_restore_options__()
+
+
+ def dev_combobox_activated(self, idx):
+ dev_combobox_activated(self, idx)
+
+
+ @pyqtSlot(name='on_select_path_button_clicked')
+ def select_path_button_clicked(self):
+ select_path_button_clicked(self)
+
+ def on_ignore_missing_toggled(self, checked):
+ self.restore_options['use-default-template'] = checked
+ self.restore_options['use-default-netvm'] = checked
+
+ def on_ignore_uname_mismatch_toggled(self, checked):
+ self.restore_options['ignore-username-mismatch'] = checked
+
+ def on_skip_dom0_toggled(self, checked):
+ self.restore_options['dom0-home'] = checked
+
+
+ def __fill_vms_list__(self):
+ if self.backup_vms_list != None:
+ return
+
+ self.select_vms_widget.selected_list.clear()
+ self.select_vms_widget.available_list.clear()
+
+ self.vms_to_restore = qubesutils.backup_restore_prepare(str(self.backup_dir), self.restore_options, self.qvm_collection)
+ for vmname in self.vms_to_restore:
+ self.select_vms_widget.available_list.addItem(vmname)
+
+ def __init_restore_options__(self):
+ if not self.restore_options:
+ self.restore_options = {}
+ qubesutils.backup_restore_set_defaults(self.restore_options)
+
+ if 'use-default-template' in self.restore_options and 'use-default-netvm' in self.restore_options:
+ val = self.restore_options['use-default-template'] and self.restore_options['use-default-netvm']
+ self.ignore_missing.setChecked(val)
+ else:
+ self.ignore_missing.setChecked(False)
+
+ if 'ignore-username-mismatch' in self.restore_options:
+ self.ignore_uname_mismatch.setChecked(self.restore_options['ignore-username-mismatch'])
+
+ if 'dom0-home' in self.restore_options:
+ self.skip_dom0.setChecked(self.restore_options['dom0-home'])
+
+
+
+ def gather_output(self, s):
+ self.func_output.append(s)
+
+ def restore_error_output(self, s):
+ self.emit(SIGNAL("restore_progress(QString)"), '{0}'.format(s))
+
+
+ def restore_output(self, s):
+ self.emit(SIGNAL("restore_progress(QString)"),'{0}'.format(s))
+
+
+ def __do_restore__(self, thread_monitor):
+ err_msg = []
+ self.qvm_collection.lock_db_for_writing()
+ try:
+ qubesutils.backup_restore_do(str(self.backup_dir), self.vms_to_restore, self.qvm_collection, self.restore_output, self.restore_error_output)
+ except Exception as ex:
+ err_msg.append(str(ex))
+
+ self.qvm_collection.unlock_db()
+ if len(err_msg) > 0 :
+ thread_monitor.set_error_msg('\n'.join(err_msg))
+ self.emit(SIGNAL("restore_progress(QString)"),'{0}'.format("Finished with errors!"))
+ else:
+ self.emit(SIGNAL("restore_progress(QString)"),'{0}'.format("Finished successfully!"))
+
+ thread_monitor.set_finished()
+
+
+ def current_page_changed(self, id):
+
+ if self.currentPage() is self.select_vms_page:
+ self.__fill_vms_list__()
+
+ elif self.currentPage() is self.confirm_page:
+ for v in self.excluded:
+ self.vms_to_restore[v] = self.excluded[v]
+ self.excluded = {}
+ for i in range(self.select_vms_widget.available_list.count()):
+ vmname = self.select_vms_widget.available_list.item(i).text()
+ self.excluded[str(vmname)] = self.vms_to_restore[str(vmname)]
+ del self.vms_to_restore[str(vmname)]
+
+ del self.func_output[:]
+ qubesutils.backup_restore_print_summary(self.vms_to_restore, print_callback = self.gather_output)
+ self.confirm_text_edit.setReadOnly(True)
+ self.confirm_text_edit.setFontFamily("Monospace")
+ self.confirm_text_edit.setText("\n".join(self.func_output))
+
+
+ elif self.currentPage() is self.commit_page:
+ self.button(self.CancelButton).setDisabled(True)
+ self.button(self.FinishButton).setDisabled(True)
+
+ self.thread_monitor = ThreadMonitor()
+ thread = threading.Thread (target= self.__do_restore__ , args=(self.thread_monitor,))
+ thread.daemon = True
+ thread.start()
+
+ while not self.thread_monitor.is_finished():
+ self.app.processEvents()
+ time.sleep (0.1)
+
+ #if not self.thread_monitor.success:
+ #QMessageBox.warning (None, "Backup error!", "ERROR: {1}".format(self.vm.name, self.thread_monitor.error_msg))
+
+ umount_device(self.dev_mount_path)
+ self.button(self.FinishButton).setEnabled(True)
+
+
+
+ def has_selected_dir(self):
+ return self.backup_dir != None
+
+ def has_selected_vms(self):
+ return self.select_vms_widget.selected_list.count() > 0
+
+
+
+# Bases on the original code by:
+# Copyright (c) 2002-2007 Pascal Varet
+
+def handle_exception( exc_type, exc_value, exc_traceback ):
+ import sys
+ import os.path
+ import traceback
+
+ filename, line, dummy, dummy = traceback.extract_tb( exc_traceback ).pop()
+ filename = os.path.basename( filename )
+ error = "%s: %s" % ( exc_type.__name__, exc_value )
+
+ QMessageBox.critical(None, "Houston, we have a problem...",
+ "Whoops. A critical error has occured. This is most likely a bug "
+ "in Qubes Restore VMs application.
"
+ "%s" % error +
+ "at line %d of file %s.
"
+ % ( line, filename ))
+
+
+
+
+def main():
+
+ global qubes_host
+ qubes_host = QubesHost()
+
+ global app
+ app = QApplication(sys.argv)
+ app.setOrganizationName("The Qubes Project")
+ app.setOrganizationDomain("http://qubes-os.org")
+ app.setApplicationName("Qubes Restore VMs")
+
+ sys.excepthook = handle_exception
+
+ qvm_collection = QubesVmCollection()
+ qvm_collection.lock_db_for_reading()
+ qvm_collection.load()
+ qvm_collection.unlock_db()
+
+ global restore_window
+ restore_window = RestoreVMsWindow()
+
+ restore_window.show()
+
+ app.exec_()
+ app.exit()
+
+
+
+if __name__ == "__main__":
+ main()
diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py
new file mode 100644
index 0000000..9ca2475
--- /dev/null
+++ b/qubesmanager/settings.py
@@ -0,0 +1,403 @@
+#!/usr/bin/python2.6
+#
+# The Qubes OS Project, http://www.qubes-os.org
+#
+# Copyright (C) 2012 Agnieszka Kostrzewa
+# Copyright (C) 2012 Marek Marczykowski
+#
+# 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 General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+#
+
+import sys
+import os
+from PyQt4.QtCore import *
+from PyQt4.QtGui import *
+
+from qubes.qubes import QubesVmCollection
+from qubes.qubes import QubesVmLabels
+from qubes.qubes import QubesException
+from qubes.qubes import qubes_appmenu_create_cmd
+from qubes.qubes import qubes_appmenu_remove_cmd
+from qubes.qubes import QubesDaemonPidfile
+from qubes.qubes import QubesHost
+from qubes.qubes import qrexec_client_path
+
+import qubesmanager.resources_rc
+
+from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent
+
+import subprocess
+import time
+import threading
+from operator import itemgetter
+
+from ui_settingsdlg import *
+from multiselectwidget import *
+from appmenu_select import *
+from firewall import *
+
+
+class VMSettingsWindow(Ui_SettingsDialog, QDialog):
+ tabs_indices = {"basic": 0,
+ "advanced": 1,
+ "firewall": 2,
+ "devices": 3,
+ "applications": 4,
+ "services": 5,}
+
+ def __init__(self, vm, app, qvm_collection, init_page="basic", parent=None):
+ super(VMSettingsWindow, self).__init__(parent)
+
+ self.app = app
+ self.qvm_collection = qvm_collection
+ self.vm = vm
+ if self.vm.template_vm:
+ self.source_vm = self.vm.template_vm
+ else:
+ self.source_vm = self.vm
+
+ self.setupUi(self)
+ if init_page in self.tabs_indices:
+ idx = self.tabs_indices[init_page]
+ assert (idx in range(self.tabWidget.count()))
+ self.tabWidget.setCurrentIndex(idx)
+
+ self.connect(self.buttonBox, SIGNAL("accepted()"), self.save_and_apply)
+ self.connect(self.buttonBox, SIGNAL("rejected()"), self.reject)
+
+ self.tabWidget.currentChanged.connect(self.current_tab_changed)
+
+ ###### basic tab
+ self.__init_basic_tab__()
+
+ ###### firewall tab
+
+ model = QubesFirewallRulesModel()
+ model.set_vm(vm)
+ self.set_fw_model(model)
+
+
+ self.newRuleButton.clicked.connect(self.new_rule_button_pressed)
+ self.editRuleButton.clicked.connect(self.edit_rule_button_pressed)
+ self.deleteRuleButton.clicked.connect(self.delete_rule_button_pressed)
+ self.policyAllowRadioButton.toggled.connect(self.policy_radio_toggled)
+ self.dnsCheckBox.toggled.connect(self.dns_checkbox_toggled)
+ self.icmpCheckBox.toggled.connect(self.icmp_checkbox_toggled)
+
+ ####### devices tab
+ self.dev_list = MultiSelectWidget(self)
+ self.devices_layout.addWidget(self.dev_list)
+
+ ####### apps tab
+ if not vm.is_netvm():
+ self.app_list = MultiSelectWidget(self)
+ self.apps_layout.addWidget(self.app_list)
+ self.AppListManager = AppmenuSelectManager(self.vm, self.app_list)
+ else:
+ self.tabWidget.setTabEnabled(self.tabs_indices["applications"], False)
+
+ def reject(self):
+ self.done(0)
+
+ #needed not to close the dialog before applying changes
+ def accept(self):
+ pass
+
+ def save_and_apply(self):
+ thread_monitor = ThreadMonitor()
+ thread = threading.Thread (target=self.__save_changes__, args=(thread_monitor,))
+ thread.daemon = True
+ thread.start()
+
+ progress = QProgressDialog ("Applying settings to {0}...".format(self.vm.name), "", 0, 0)
+ progress.setCancelButton(None)
+ progress.setModal(True)
+ progress.show()
+
+ while not thread_monitor.is_finished():
+ self.app.processEvents()
+ time.sleep (0.1)
+
+ progress.hide()
+
+ if not thread_monitor.success:
+ QMessageBox.warning (None, "Error while changing settings for {0}!", "ERROR: {1}".format(self.vm.name, thread_monitor.error_msg))
+
+ self.done(0)
+
+ def __save_changes__(self, thread_monitor):
+ ret = self.__apply_basic_tab__()
+ if len(ret) > 0 :
+ thread_monitor.set_error_msg('\n'.join(ret))
+ thread_monitor.set_finished()
+ return
+ #self.fw_model.apply_rules()
+ self.AppListManager.save_appmenu_select_changes()
+ thread_monitor.set_finished()
+
+ def current_tab_changed(self, idx):
+ if idx == self.tabs_indices["firewall"]:
+ if self.vm.netvm_vm is not None and not self.vm.netvm_vm.is_proxyvm():
+ QMessageBox.warning (None, "VM configuration problem!", "The '{0}' AppVM is not network connected to a FirewallVM!".format(self.vm.name) +\
+ "You may edit the '{0}' VM firewall rules, but these will not take any effect until you connect it to a working Firewall VM.".format(self.vm.name))
+
+
+
+ ######### basic tab
+
+ def __init_basic_tab__(self):
+ self.vmname.setText(self.vm.name)
+
+ #self.qvm_collection.lock_db_for_reading()
+ #self.qvm_collection.load()
+ #self.qvm_collection.unlock_db()
+
+ self.label_list = QubesVmLabels.values()
+ self.label_list.sort(key=lambda l: l.index)
+ self.label_idx = 0
+ for (i, label) in enumerate(self.label_list):
+ if label == self.vm.label:
+ self.label_idx = i
+ self.vmlabel.insertItem(i, label.name)
+ self.vmlabel.setItemIcon (i, QIcon(label.icon_path))
+ self.vmlabel.setCurrentIndex(self.label_idx)
+
+ if not self.vm.is_template() and self.vm.template_vm is not None:
+ template_vm_list = [vm for vm in self.qvm_collection.values() if not vm.internal and vm.is_template()]
+ self.template_idx = 0
+ for (i, vm) in enumerate(template_vm_list):
+ text = vm.name
+ if vm is self.qvm_collection.get_default_template_vm():
+ text += " (default)"
+ if vm.qid == self.vm.template_vm.qid:
+ self.template_idx = i
+ text += " (current)"
+ self.template_name.insertItem(i, text)
+ self.template_name.setCurrentIndex(self.template_idx)
+ else:
+ self.template_name.setEnabled(False)
+
+ if not self.vm.is_netvm():
+ netvm_list = [vm for vm in self.qvm_collection.values() if not vm.internal and vm.is_netvm()]
+ self.netvm_idx = 0
+ for (i, vm) in enumerate(netvm_list):
+ text = vm.name
+ if vm is self.qvm_collection.get_default_netvm_vm():
+ text += " (default)"
+ if vm.qid == self.vm.netvm_vm.qid:
+ self.netvm_idx = i
+ text += " (current)"
+ self.netVM.insertItem(i, text)
+ self.netVM.setCurrentIndex(self.netvm_idx)
+ else:
+ self.netVM.setEnabled(False)
+
+ #self.vmname.selectAll()
+ #self.vmname.setFocus()
+
+ def __apply_basic_tab__(self):
+ msg = []
+
+ if self.vm.is_running():
+ msg.append("Can't change settings of a running VM.")
+ msg.append("telemele")
+ return msg
+
+ # vmname changed
+ vmname = str(self.vmname.text())
+ if self.vm.name != vmname:
+ if self.qvm_collection.get_vm_by_name(vmname) is not None:
+ msg.append("A VM named {0} already exists in the system!".format(vmname))
+ else:
+ oldname = self.vm.name
+ try:
+ self.qvm_collection.lock_db_for_writing()
+ self.vm.pre_rename(vmname)
+ self.vm.set_name(vmname)
+ self.vm.post_rename(oldname)
+ self.qvm_collection.save()
+ except Exception as ex:
+ msg.append(str(ex))
+ finally:
+ self.qvm_collection.unlock_db()
+
+ #vm label changed
+ if self.vmlabel.currentIndex() != self.label_idx:
+ label = self.label_list[self.vmlabel.currentIndex()]
+ self.qvm_collection.lock_db_for_writing()
+ self.vm.label = label
+ self.qvm_collection.save()
+ self.qvm_collection.unlock_db()
+
+ return msg
+
+ # template_vm = template_vm_list[dialog.template_name.currentIndex()]
+ # allow_networking = dialog.allow_networking.isChecked()
+
+ ######### firewall tab related
+
+ def set_fw_model(self, model):
+ self.fw_model = model
+ self.rulesTreeView.setModel(model)
+ self.rulesTreeView.header().setResizeMode(QHeaderView.ResizeToContents)
+ self.rulesTreeView.header().setResizeMode(0, QHeaderView.Stretch)
+ self.set_allow(model.allow)
+ self.dnsCheckBox.setChecked(model.allowDns)
+ self.icmpCheckBox.setChecked(model.allowIcmp)
+
+ def set_allow(self, allow):
+ self.policyAllowRadioButton.setChecked(allow)
+ self.policyDenyRadioButton.setChecked(not allow)
+
+ def policy_radio_toggled(self, on):
+ self.fw_model.allow = self.policyAllowRadioButton.isChecked()
+
+ def dns_checkbox_toggled(self, on):
+ self.fw_model.allowDns = on
+
+ def icmp_checkbox_toggled(self, on):
+ self.fw_model.allowIcmp = on
+
+ def new_rule_button_pressed(self):
+ dialog = NewFwRuleDlg()
+ self.run_rule_dialog(dialog)
+
+ def edit_rule_button_pressed(self):
+ dialog = NewFwRuleDlg()
+ dialog.set_ok_enabled(True)
+ selected = self.rulesTreeView.selectedIndexes()
+ if len(selected) > 0:
+ row = self.rulesTreeView.selectedIndexes().pop().row()
+ address = self.fw_model.get_column_string(0, row).replace(' ', '')
+ dialog.addressComboBox.setItemText(0, address)
+ dialog.addressComboBox.setCurrentIndex(0)
+ service = self.fw_model.get_column_string(1, row)
+ dialog.serviceComboBox.setItemText(0, service)
+ dialog.serviceComboBox.setCurrentIndex(0)
+ self.run_rule_dialog(dialog, row)
+
+ def delete_rule_button_pressed(self):
+ for i in set([index.row() for index in self.rulesTreeView.selectedIndexes()]):
+ self.fw_model.removeChild(i)
+
+ def run_rule_dialog(self, dialog, row = None):
+ if dialog.exec_():
+ address = str(dialog.addressComboBox.currentText())
+ service = str(dialog.serviceComboBox.currentText())
+ port = None
+ port2 = None
+
+ unmask = address.split("/", 1)
+ if len(unmask) == 2:
+ address = unmask[0]
+ netmask = int(unmask[1])
+ else:
+ netmask = 32
+
+ if address == "*":
+ address = "0.0.0.0"
+ netmask = 0
+
+ if service == "*":
+ service = "0"
+ try:
+ range = service.split("-", 1)
+ if len(range) == 2:
+ port = int(range[0])
+ port2 = int(range[1])
+ else:
+ port = int(service)
+ except (TypeError, ValueError) as ex:
+ port = self.fw_model.get_service_port(service)
+
+ if port is not None:
+ if port2 is not None and port2 <= port:
+ QMessageBox.warning(None, "Invalid service ports range", "Port {0} is lower than port {1}.".format(port2, port))
+ else:
+ item = QubesFirewallRuleItem(address, netmask, port, port2)
+ if row is not None:
+ self.fw_model.setChild(row, item)
+ else:
+ self.fw_model.appendChild(item)
+ else:
+ QMessageBox.warning(None, "Invalid service name", "Service '{0} is unknown.".format(service))
+
+
+# Bases on the original code by:
+# Copyright (c) 2002-2007 Pascal Varet
+
+def handle_exception( exc_type, exc_value, exc_traceback ):
+ import sys
+ import os.path
+ import traceback
+
+ filename, line, dummy, dummy = traceback.extract_tb( exc_traceback ).pop()
+ filename = os.path.basename( filename )
+ error = "%s: %s" % ( exc_type.__name__, exc_value )
+
+ QMessageBox.critical(None, "Houston, we have a problem...",
+ "Whoops. A critical error has occured. This is most likely a bug "
+ "in Qubes VM Settings application.
"
+ "%s" % error +
+ "at line %d of file %s.
"
+ % ( line, filename ))
+
+
+def main():
+
+ global qubes_host
+ qubes_host = QubesHost()
+
+ global app
+ app = QApplication(sys.argv)
+ app.setOrganizationName("The Qubes Project")
+ app.setOrganizationDomain("http://qubes-os.org")
+ app.setApplicationName("Qubes VM Settings")
+
+ sys.excepthook = handle_exception
+
+ qvm_collection = QubesVmCollection()
+ qvm_collection.lock_db_for_reading()
+ qvm_collection.load()
+ qvm_collection.unlock_db()
+
+ vm = None
+
+ if len(sys.argv) > 1:
+ vm = qvm_collection.get_vm_by_name(sys.argv[1])
+ if vm is None or vm.qid not in qvm_collection:
+ QMessageBox.critical(None, "Qubes VM Settings Error",
+ "A VM with the name '{0}' does not exist in the system.".format(sys.argv[1]))
+ sys.exit(1)
+ else:
+ vms_list = [vm.name for vm in qvm_collection.values() if (vm.is_appvm() or vm.is_template())]
+ vmname = QInputDialog.getItem(None, "Select VM", "Select VM:", vms_list, editable = False)
+ if not vmname[1]:
+ sys.exit(1)
+ vm = qvm_collection.get_vm_by_name(vmname[0])
+
+
+ global settings_window
+ settings_window = VMSettingsWindow(vm, app, qvm_collection, "basic")
+
+ settings_window.show()
+
+ app.exec_()
+ app.exit()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/qubesmanager/thread_monitor.py b/qubesmanager/thread_monitor.py
new file mode 100644
index 0000000..4d5e915
--- /dev/null
+++ b/qubesmanager/thread_monitor.py
@@ -0,0 +1,44 @@
+#!/usr/bin/python2.6
+#
+# The Qubes OS Project, http://www.qubes-os.org
+#
+# Copyright (C) 2011 Marek Marczykowski
+#
+# 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 General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+#
+
+
+from PyQt4.QtCore import *
+
+import threading
+
+class ThreadMonitor(QObject):
+ def __init__(self):
+ self.success = True
+ self.error_msg = None
+ self.event_finished = threading.Event()
+
+ def set_error_msg(self, error_msg):
+ self.success = False
+ self.error_msg = error_msg
+ self.set_finished()
+
+ def is_finished(self):
+ return self.event_finished.is_set()
+
+ def set_finished(self):
+ self.event_finished.set()
+
diff --git a/resources.qrc b/resources.qrc
index c6e6649..935b43a 100644
--- a/resources.qrc
+++ b/resources.qrc
@@ -1,22 +1,35 @@
-
-
-icons/qubes.png
-icons/appvm.png
-icons/netvm.png
-icons/networking.png
-icons/dom0.png
-icons/storagevm.png
-icons/templatevm.png
-icons/updateable.png
-icons/home.png
-icons/root.png
-icons/createvm.png
-icons/removevm.png
-icons/shutdownvm.png
-icons/resumevm.png
-icons/pausevm.png
-icons/showallvms.png
-icons/showcpuload.png
-icons/firewall.png
-
+
+
+ icons/mount.png
+ icons/pencil.png
+ icons/redfirewall.png
+ icons/edit.png
+ icons/add.png
+ icons/flag-blue.png
+ icons/flag-green.png
+ icons/flag-red.png
+ icons/flag-yellow.png
+ icons/remove.png
+ icons/on.png
+ icons/appsprefs.png
+ icons/newfirewall.png
+ icons/qubes.png
+ icons/appvm.png
+ icons/netvm.png
+ icons/networking.png
+ icons/dom0.png
+ icons/storagevm.png
+ icons/templatevm.png
+ icons/updateable.png
+ icons/home.png
+ icons/root.png
+ icons/createvm.png
+ icons/removevm.png
+ icons/shutdownvm.png
+ icons/resumevm.png
+ icons/pausevm.png
+ icons/showallvms.png
+ icons/showcpuload.png
+ icons/firewall.png
+
diff --git a/restoredlg.ui b/restoredlg.ui
new file mode 100644
index 0000000..3c908e9
--- /dev/null
+++ b/restoredlg.ui
@@ -0,0 +1,229 @@
+
+
+ Restore
+
+
+
+ 0
+ 0
+ 700
+ 399
+
+
+
+ Qubes Restore VMs
+
+
+
+
+
+ QWizard::NoBackButtonOnLastPage|QWizard::NoBackButtonOnStartPage
+
+
+
+ -
+
+
+
+ 50
+ false
+
+
+
+ Backup source location
+
+
+
-
+
+
+
+ 50
+ false
+
+
+
+ Device
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
-
+
+ dev1
+
+
+ -
+
+ longdeviceblablabla
+
+
+ -
+
+ dev2
+
+
+ -
+
+ dev3
+
+
+
+
+ -
+
+
+ Backup directory:
+
+
+
+ -
+
+
+ -
+
+
+ ...
+
+
+
+
+
+
+ -
+
+
+
+ 50
+ false
+
+
+
+ Restore options
+
+
+
-
+
+
+ Ignore missing templates or netvms, restore VMs anyway.
+
+
+ ignore missing
+
+
+
+ -
+
+
+ skip dom0
+
+
+
+ -
+
+
+ Ignore dom0 username mismatch while restoring homedir.
+
+
+ ignore username mismatch
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 215
+
+
+
+
+
+
+
+
+ -
+
+
+ VMs to restore
+
+
+
+
+
+
+
+
+ -
+
+
+
+ 9
+ 50
+ false
+ false
+ false
+
+
+
+ You're about to perform the following actions:
+
+
+
+ -
+
+
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
+<html><head><meta name="qrichtext" content="1" /><style type="text/css">
+p, li { white-space: pre-wrap; }
+</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;">
+<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">A lot of info<br />A lot of info<br />A lot of info<br />A lot of info<br />A lot of info<br />A lot of info<br />A lot of info A lot of info A lot of info A lot of info A lot of info A lot of info</p>
+<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">A lot of info A lot of info A lot of info A lot of info A lot of info A lot of info</p>
+<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">A lot of info A lot of info A lot of info A lot of info A lot of info A lot of info</p>
+<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">A lot of info A lot of info A lot of info A lot of info A lot of info A lot of info</p>
+<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">A lot of info A lot of info A lot of info A lot of info A lot of info A lot of info<br />A lot of info</p></body></html>
+
+
+
+ -
+
+
+
+ 9
+ 50
+ false
+ false
+ false
+
+
+
+ To continue press Next.
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
diff --git a/rpm_spec/qmgr.spec b/rpm_spec/qmgr.spec
index 012c2c8..e45f853 100644
--- a/rpm_spec/qmgr.spec
+++ b/rpm_spec/qmgr.spec
@@ -33,12 +33,25 @@ cp qubes-appmenu-select $RPM_BUILD_ROOT/usr/bin
mkdir -p $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager/
cp qubesmanager/main.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
cp qubesmanager/appmenu_select.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
+cp qubesmanager/backup.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
+cp qubesmanager/backup_utils.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
cp qubesmanager/firewall.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
-cp qubesmanager/qrc_resources.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
+cp qubesmanager/global_settings.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
+cp qubesmanager/multiselectwidget.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
+cp qubesmanager/restore.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
+cp qubesmanager/settings.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
+cp qubesmanager/thread_monitor.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
+cp qubesmanager/resources_rc.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
cp qubesmanager/__init__.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
+cp qubesmanager/ui_backupdlg.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
+cp qubesmanager/ui_editfwrulesdlg.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
+cp qubesmanager/ui_globalsettingsdlg.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
+cp qubesmanager/ui_mainwindow.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
+cp qubesmanager/ui_multiselectwidget.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
cp qubesmanager/ui_newappvmdlg.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
cp qubesmanager/ui_newfwruledlg.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
-cp qubesmanager/ui_editfwrulesdlg.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
+cp qubesmanager/ui_restoredlg.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
+cp qubesmanager/ui_settingsdlg.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
mkdir -p $RPM_BUILD_ROOT/usr/share/applications
cp qubes-manager.desktop $RPM_BUILD_ROOT/usr/share/applications
@@ -67,21 +80,60 @@ rm -rf $RPM_BUILD_ROOT
%{python_sitearch}/qubesmanager/appmenu_select.py
%{python_sitearch}/qubesmanager/appmenu_select.pyc
%{python_sitearch}/qubesmanager/appmenu_select.pyo
+%{python_sitearch}/qubesmanager/backup.py
+%{python_sitearch}/qubesmanager/backup.pyc
+%{python_sitearch}/qubesmanager/backup.pyo
+%{python_sitearch}/qubesmanager/backup_utils.py
+%{python_sitearch}/qubesmanager/backup_utils.pyc
+%{python_sitearch}/qubesmanager/backup_utils.pyo
%{python_sitearch}/qubesmanager/firewall.py
%{python_sitearch}/qubesmanager/firewall.pyc
%{python_sitearch}/qubesmanager/firewall.pyo
-%{python_sitearch}/qubesmanager/qrc_resources.py
-%{python_sitearch}/qubesmanager/qrc_resources.pyc
-%{python_sitearch}/qubesmanager/qrc_resources.pyo
+%{python_sitearch}/qubesmanager/global_settings.py
+%{python_sitearch}/qubesmanager/global_settings.pyc
+%{python_sitearch}/qubesmanager/global_settings.pyo
+%{python_sitearch}/qubesmanager/multiselectwidget.py
+%{python_sitearch}/qubesmanager/multiselectwidget.pyc
+%{python_sitearch}/qubesmanager/multiselectwidget.pyo
+%{python_sitearch}/qubesmanager/restore.py
+%{python_sitearch}/qubesmanager/restore.pyc
+%{python_sitearch}/qubesmanager/restore.pyo
+%{python_sitearch}/qubesmanager/settings.py
+%{python_sitearch}/qubesmanager/settings.pyc
+%{python_sitearch}/qubesmanager/settings.pyo
+%{python_sitearch}/qubesmanager/thread_monitor.py
+%{python_sitearch}/qubesmanager/thread_monitor.pyc
+%{python_sitearch}/qubesmanager/thread_monitor.pyo
+%{python_sitearch}/qubesmanager/resources_rc.py
+%{python_sitearch}/qubesmanager/resources_rc.pyc
+%{python_sitearch}/qubesmanager/resources_rc.pyo
+%{python_sitearch}/qubesmanager/ui_backupdlg.py
+%{python_sitearch}/qubesmanager/ui_backupdlg.pyc
+%{python_sitearch}/qubesmanager/ui_backupdlg.pyo
+%{python_sitearch}/qubesmanager/ui_editfwrulesdlg.py
+%{python_sitearch}/qubesmanager/ui_editfwrulesdlg.pyc
+%{python_sitearch}/qubesmanager/ui_editfwrulesdlg.pyo
+%{python_sitearch}/qubesmanager/ui_globalsettingsdlg.py
+%{python_sitearch}/qubesmanager/ui_globalsettingsdlg.pyc
+%{python_sitearch}/qubesmanager/ui_globalsettingsdlg.pyo
+%{python_sitearch}/qubesmanager/ui_mainwindow.py
+%{python_sitearch}/qubesmanager/ui_mainwindow.pyc
+%{python_sitearch}/qubesmanager/ui_mainwindow.pyo
+%{python_sitearch}/qubesmanager/ui_multiselectwidget.py
+%{python_sitearch}/qubesmanager/ui_multiselectwidget.pyc
+%{python_sitearch}/qubesmanager/ui_multiselectwidget.pyo
%{python_sitearch}/qubesmanager/ui_newappvmdlg.py
%{python_sitearch}/qubesmanager/ui_newappvmdlg.pyc
%{python_sitearch}/qubesmanager/ui_newappvmdlg.pyo
%{python_sitearch}/qubesmanager/ui_newfwruledlg.py
%{python_sitearch}/qubesmanager/ui_newfwruledlg.pyc
%{python_sitearch}/qubesmanager/ui_newfwruledlg.pyo
-%{python_sitearch}/qubesmanager/ui_editfwrulesdlg.py
-%{python_sitearch}/qubesmanager/ui_editfwrulesdlg.pyc
-%{python_sitearch}/qubesmanager/ui_editfwrulesdlg.pyo
+%{python_sitearch}/qubesmanager/ui_restoredlg.py
+%{python_sitearch}/qubesmanager/ui_restoredlg.pyc
+%{python_sitearch}/qubesmanager/ui_restoredlg.pyo
+%{python_sitearch}/qubesmanager/ui_settingsdlg.py
+%{python_sitearch}/qubesmanager/ui_settingsdlg.pyc
+%{python_sitearch}/qubesmanager/ui_settingsdlg.pyo
/usr/share/applications/qubes-manager.desktop
diff --git a/settingsdlg.ui b/settingsdlg.ui
new file mode 100644
index 0000000..99ece3d
--- /dev/null
+++ b/settingsdlg.ui
@@ -0,0 +1,807 @@
+
+
+ SettingsDialog
+
+
+
+ 0
+ 0
+ 694
+ 483
+
+
+
+ Settings
+
+
+
+ :/root.png:/root.png
+
+
+ -
+
+
-
+
+
+ true
+
+
+
+
+
+ 5
+
+
+
+
+
+
+ Basic
+
+
+
-
+
+
+ Settings
+
+
+
-
+
+
+ Name & label:
+
+
+
+ -
+
+
+ myappvm
+
+
+
+ -
+
+
+ true
+
+
+
+ -
+
+
+ Template:
+
+
+
+ -
+
+
+ -
+
+
+ NetVM:
+
+
+
+ -
+
+
+ -
+
+
+ false
+
+
+ Include in backups by default
+
+
+ true
+
+
+
+
+
+
+ -
+
+
+ false
+
+
+ Info
+
+
+
+ QFormLayout::AllNonFixedFieldsGrow
+
+
-
+
+
+ Type:
+
+
+
+ -
+
+
+
+ 75
+ true
+
+
+
+ AppVM
+
+
+
+ -
+
+
+ Installed by RPM:
+
+
+
+ -
+
+
+
+ 75
+ true
+
+
+
+ No
+
+
+
+
+
+
+ -
+
+
+ false
+
+
+ Disk storage
+
+
+
-
+
+
+ false
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ 10000
+
+
+ 2
+
+
+
+ -
+
+
+ GB
+
+
+
+ -
+
+
+ Private storage max. size:
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 73
+
+
+
+
+
+
+
+
+ false
+
+
+ Advanced
+
+
+ -
+
+
+ Memory/CPU
+
+
+
-
+
+
+ Initial memory:
+
+
+
+ -
+
+
+
+ 75
+ true
+
+
+
+ xx
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ MB
+
+
+
+ -
+
+
+ Max memory:
+
+
+
+ -
+
+
+ false
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ 10000
+
+
+ 100
+
+
+ 400
+
+
+
+ -
+
+
+ MB
+
+
+
+ -
+
+
+ VCPUs no.:
+
+
+
+ -
+
+
+ false
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ 1
+
+
+
+ -
+
+
+ Include in memory balancing
+
+
+
+
+
+
+ -
+
+
+ Kernel
+
+
+
-
+
+
+ Kernel:
+
+
+
+ -
+
+
+ -
+
+
+ Kernel opts:
+
+
+
+ -
+
+
+
+ 75
+ true
+
+
+
+ []
+
+
+
+
+
+
+ -
+
+
+ Paths
+
+
+
-
+
+
+ dir:
+
+
+
+ -
+
+
+
+ 50
+ false
+
+
+
+ dir_path
+
+
+
+ -
+
+
+ config:
+
+
+
+ -
+
+
+
+ 50
+ false
+
+
+
+ config_path
+
+
+
+ -
+
+
+ root img:
+
+
+
+ -
+
+
+ root_img_path
+
+
+
+ -
+
+
+ root volatile img:
+
+
+
+ -
+
+
+ volatile_path
+
+
+
+ -
+
+
+ private img:
+
+
+
+ -
+
+
+ private_path
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 88
+
+
+
+
+
+
+
+
+ Firewall rules
+
+
+ -
+
+
+ Allow network access except...
+
+
+
+ -
+
+
+ Deny network access except...
+
+
+
+ -
+
+
+ QLayout::SetMaximumSize
+
+
-
+
+
-
+
+
+ false
+
+
+ false
+
+
+ false
+
+
+ true
+
+
+ true
+
+
+ 40
+
+
+ false
+
+
+
+ -
+
+
-
+
+
+ Allow ICMP traffic
+
+
+ true
+
+
+
+ -
+
+
+ Allow DNS queries
+
+
+ true
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+
+
+
+ :/add.png:/add.png
+
+
+
+ 24
+ 24
+
+
+
+
+ -
+
+
+
+
+
+
+ :/pencil.png:/pencil.png
+
+
+
+ 24
+ 24
+
+
+
+
+ -
+
+
+
+
+
+
+ :/remove.png:/remove.png
+
+
+
+ 24
+ 24
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+ Devices
+
+
+ -
+
+
+
+
+
+
+
+ :/appsprefs.png:/appsprefs.png
+
+
+ Applications
+
+
+ -
+
+
+
+
+
+
+ false
+
+
+ Services
+
+
+ -
+
+
+ -
+
+
+
+
+
+
+ :/add.png:/add.png
+
+
+
+ 24
+ 24
+
+
+
+
+ -
+
+
-
+
+ ntpd
+
+
+ Checked
+
+
+ -
+
+ cupsd
+
+
+ Checked
+
+
+ -
+
+ meminfo
+
+
+ Checked
+
+
+
+
+ -
+
+
+ Checked services will be turned on.
+
+
+
+ -
+
+
+ Unchecked services will be turned off.
+
+
+
+ -
+
+
+ Unlisted services will follow default VM's settings.
+
+
+
+ -
+
+
+
+
+
+
+ :/remove.png:/remove.png
+
+
+
+ 24
+ 24
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ SettingsDialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ SettingsDialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/version b/version
index 0664a8f..26aaba0 100644
--- a/version
+++ b/version
@@ -1 +1 @@
-1.1.6
+1.2.0