diff --git a/backupdlg.ui b/backupdlg.ui
index f26c0af..f782343 100644
--- a/backupdlg.ui
+++ b/backupdlg.ui
@@ -22,20 +22,128 @@
-
-
-
-
- 9
- 50
- false
- false
- false
-
-
-
- Select VMs to backup:
-
-
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Shutdown all running selected VMs
+
+
+
+ :/shutdownvm.png:/shutdownvm.png
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Refresh running states.
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 75
+ true
+ true
+
+
+
+ color:rgb(255, 0, 0)
+
+
+ Some of the selected VMs are running (red). Running VMs can not be backuped!
+
+
+ true
+
+
+
+ -
+
+
+
+ 9
+ 50
+ false
+ false
+ false
+
+
+
+ Select VMs to backup:
+
+
+
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
-
+
+
+ Total size:
+
+
+
+ -
+
+
+ 0
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
@@ -187,6 +295,8 @@ p, li { white-space: pre-wrap; }
-
+
+
+
diff --git a/globalsettingsdlg.ui b/globalsettingsdlg.ui
index 3c195a7..c555ea1 100644
--- a/globalsettingsdlg.ui
+++ b/globalsettingsdlg.ui
@@ -90,7 +90,7 @@
-
- false
+ true
Minimal VM's memory:
@@ -100,36 +100,36 @@
-
- false
+ true
- MB
+ MiB
999999999
- 10
+ 50
-
- false
+ true
- dom0 memory margin:
+ dom0 memory boost:
-
-
+
- false
+ true
- MB
+ MiB
999999999
diff --git a/icons/apps.png b/icons/apps.png
new file mode 100644
index 0000000..24e6ec9
Binary files /dev/null and b/icons/apps.png differ
diff --git a/icons/backup.png b/icons/backup.png
new file mode 100644
index 0000000..f560da6
Binary files /dev/null and b/icons/backup.png differ
diff --git a/icons/firewall.png b/icons/firewall.png
deleted file mode 100644
index dd93c58..0000000
Binary files a/icons/firewall.png and /dev/null differ
diff --git a/icons/global-settings.png b/icons/global-settings.png
new file mode 100644
index 0000000..b837c7a
Binary files /dev/null and b/icons/global-settings.png differ
diff --git a/icons/restore.png b/icons/restore.png
new file mode 100644
index 0000000..38e7198
Binary files /dev/null and b/icons/restore.png differ
diff --git a/icons/running.png b/icons/running.png
new file mode 100644
index 0000000..2a4f8d9
Binary files /dev/null and b/icons/running.png differ
diff --git a/icons/settings.png b/icons/settings.png
new file mode 100644
index 0000000..e579edb
Binary files /dev/null and b/icons/settings.png differ
diff --git a/icons/show-all-running.png b/icons/show-all-running.png
new file mode 100644
index 0000000..d03c60c
Binary files /dev/null and b/icons/show-all-running.png differ
diff --git a/icons/wall.png b/icons/wall.png
new file mode 100644
index 0000000..de94384
Binary files /dev/null and b/icons/wall.png differ
diff --git a/mainwindow.ui b/mainwindow.ui
index 29b0e96..491cab8 100644
--- a/mainwindow.ui
+++ b/mainwindow.ui
@@ -273,6 +273,10 @@
+
+
+
+
@@ -353,7 +357,7 @@
- :/appsprefs.png:/appsprefs.png
+ :/apps.png:/apps.png
Select VM applications
@@ -386,8 +390,8 @@
- :/showallvms.png
- :/showallvms.png:/showallvms.png
+ :/show-all-running.png
+ :/showallvms.png:/show-all-running.png
Show/Hide inactive VMs
@@ -399,7 +403,7 @@
- :/redfirewall.png:/redfirewall.png
+ :/firewall.png:/firewall.png
Edit VM Firewall rules
@@ -502,7 +506,7 @@
- :/root.png:/root.png
+ :/settings.png:/settings.png
Settings
@@ -512,16 +516,28 @@
+
+
+ :/restore.png:/restore.png
+
Restore VMs from backup
+
+
+ :/backup.png:/backup.png
+
Backup VMs
+
+
+ :/global-settings.png:/global-settings.png
+
Global settings
diff --git a/qubesmanager/backup.py b/qubesmanager/backup.py
index 47efca4..4887aa1 100644
--- a/qubesmanager/backup.py
+++ b/qubesmanager/backup.py
@@ -23,6 +23,7 @@
import sys
import os
+import signal
from PyQt4.QtCore import *
from PyQt4.QtGui import *
@@ -48,18 +49,20 @@ from ui_backupdlg import *
from multiselectwidget import *
from backup_utils import *
+import grp,pwd
class BackupVMsWindow(Ui_Backup, QWizard):
__pyqtSignals__ = ("backup_progress(int)",)
- def __init__(self, app, qvm_collection, blk_manager, parent=None):
+ def __init__(self, app, qvm_collection, blk_manager, shutdown_vm_func, parent=None):
super(BackupVMsWindow, self).__init__(parent)
self.app = app
self.qvm_collection = qvm_collection
self.blk_manager = blk_manager
+ self.shutdown_vm_func = shutdown_vm_func
self.dev_mount_path = None
self.backup_dir = None
@@ -75,12 +78,18 @@ class BackupVMsWindow(Ui_Backup, QWizard):
self.setupUi(self)
+ self.show_running_vms_warning(False)
self.dir_line_edit.setReadOnly(True)
self.select_vms_widget = MultiSelectWidget(self)
self.verticalLayout.insertWidget(1, self.select_vms_widget)
self.connect(self, SIGNAL("currentIdChanged(int)"), self.current_page_changed)
+ self.connect(self.select_vms_widget, SIGNAL("selected_changed()"), self.check_running)
+ self.connect(self.select_vms_widget, SIGNAL("items_removed(list)"), self.vms_removed)
+ self.connect(self.select_vms_widget, SIGNAL("items_added(list)"), self.vms_added)
+ self.refresh_button.clicked.connect(self.check_running)
+ self.shutdown_running_vms_button.clicked.connect(self.shutdown_all_running_selected)
self.connect(self.dev_combobox, SIGNAL("activated(int)"), self.dev_combobox_activated)
self.connect(self, SIGNAL("backup_progress(int)"), self.progress_bar.setValue)
@@ -89,30 +98,126 @@ class BackupVMsWindow(Ui_Backup, QWizard):
#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()"))
-
+
+ self.total_size = 0
self.__fill_vms_list__()
fill_devs_list(self)
+ def show_running_vms_warning(self, show):
+ self.running_vms_warning.setVisible(show)
+ self.shutdown_running_vms_button.setVisible(show)
+ self.refresh_button.setVisible(show)
+
+ class VmListItem(QListWidgetItem):
+ def __init__(self, vm):
+ self.vm = vm
+ if vm.qid == 0:
+ local_user = grp.getgrnam('qubes').gr_mem[0]
+ home_dir = pwd.getpwnam(local_user).pw_dir
+ self.size = qubesutils.get_disk_usage(home_dir)
+ else:
+ self.size = self.get_vm_size(vm)
+ super(BackupVMsWindow.VmListItem, self).__init__(vm.name+ " (" + qubesutils.size_to_human(self.size) + ")")
+
+ def get_vm_size(self, vm):
+ size = 0
+ if vm.private_img is not None:
+ size += vm.get_disk_usage (vm.private_img)
+
+ if vm.updateable:
+ size += vm.get_disk_usage(vm.root_img)
+
+ return size
+
+
def __fill_vms_list__(self):
for vm in self.qvm_collection.values():
- if vm.is_running() and vm.qid != 0:
- self.excluded.append(vm.name)
- continue
-
if vm.is_appvm() and vm.internal:
- self.excluded.append(vm.name)
continue
-
if vm.is_template() and vm.installed_by_rpm:
- self.excluded.append(vm.name)
continue
+ item = BackupVMsWindow.VmListItem(vm)
if vm.include_in_backups == True:
- self.select_vms_widget.selected_list.addItem(vm.name)
+ self.select_vms_widget.selected_list.addItem(item)
+ self.total_size += item.size
else:
- self.select_vms_widget.available_list.addItem(vm.name)
+ self.select_vms_widget.available_list.addItem(item)
+ self.check_running()
+ self.total_size_label.setText(qubesutils.size_to_human(self.total_size))
+
+ def vms_added(self, items):
+ for i in items:
+ self.total_size += i.size
+ self.total_size_label.setText(qubesutils.size_to_human(self.total_size))
+
+ def vms_removed(self, items):
+ for i in items:
+ self.total_size -= i.size
+ self.total_size_label.setText(qubesutils.size_to_human(self.total_size))
+
+ def check_running(self):
+ some_selected_vms_running = False
+ for i in range(self.select_vms_widget.selected_list.count()):
+ item = self.select_vms_widget.selected_list.item(i)
+ if item.vm.is_running() and item.vm.qid != 0:
+ item.setForeground(QBrush(QColor(255, 0, 0)))
+ some_selected_vms_running = True
+ else:
+ item.setForeground(QBrush(QColor(0, 0, 0)))
+
+ self.show_running_vms_warning(some_selected_vms_running)
+
+ for i in range(self.select_vms_widget.available_list.count()):
+ item = self.select_vms_widget.available_list.item(i)
+ if item.vm.is_running() and item.vm.qid != 0:
+ item.setForeground(QBrush(QColor(255, 0, 0)))
+ else:
+ item.setForeground(QBrush(QColor(0, 0, 0)))
+
+ return some_selected_vms_running
+
+ def shutdown_all_running_selected(self):
+ names = []
+ vms = []
+ for i in range(self.select_vms_widget.selected_list.count()):
+ item = self.select_vms_widget.selected_list.item(i)
+ if item.vm.is_running() and item.vm.qid != 0:
+ names.append(item.vm.name)
+ vms.append(item.vm)
+
+ if len(vms) == 0:
+ return;
+
+ reply = QMessageBox.question(None, "VM Shutdown Confirmation",
+ "Are you sure you want to power down the following VMs: {0}?
"
+ "This will shutdown all the running applications within them.".format(', '.join(names)),
+ QMessageBox.Yes | QMessageBox.Cancel)
+
+ self.app.processEvents()
+
+ if reply == QMessageBox.Yes:
+ for vm in vms:
+ self.shutdown_vm_func(vm)
+
+ progress = QProgressDialog ("Shutting down VMs {0}...".format(', '.join(names)), "", 0, 0)
+ progress.setModal(True)
+ progress.show()
+
+ wait_time = 15.0
+ wait_for = wait_time
+ while self.check_running() and wait_for > 0:
+ self.app.processEvents()
+ time.sleep (0.1)
+ wait_for -= 0.1
+
+ progress.hide()
+
+ if self.check_running():
+ QMessageBox.information(None, "Strange", "Could not power down the VMs in {0} seconds...".format(wait_time))
+
+
-
def dev_combobox_activated(self, idx):
dev_combobox_activated(self, idx)
@@ -123,17 +228,30 @@ class BackupVMsWindow(Ui_Backup, QWizard):
def validateCurrentPage(self):
if self.currentPage() is self.select_vms_page:
+ for i in range(self.select_vms_widget.selected_list.count()):
+ if self.check_running() == True:
+ QMessageBox.information(None, "Wait!", "Some selected VMs are running. Running VMs can not be backuped. Please shut them down or remove them from the list.")
+ return False
+
+ del self.excluded[:]
for i in range(self.select_vms_widget.available_list.count()):
- vmname = self.select_vms_widget.available_list.item(i).text()
+ vmname = self.select_vms_widget.available_list.item(i).vm.name
self.excluded.append(vmname)
+
return True
def gather_output(self, s):
self.func_output.append(s)
def update_progress_bar(self, value):
- self.emit(SIGNAL("backup_progress(int)"), value)
+ if value == 100:
+ self.emit(SIGNAL("backup_progress(int)"), value)
+ def check_backup_progress(self, initial_usage, total_backup_size):
+ du = qubesutils.get_disk_usage(self.backup_dir)
+ done = du - initial_usage
+ percent = int((float(done)/total_backup_size)*100)
+ return percent
def __do_backup__(self, thread_monitor):
msg = []
@@ -152,23 +270,34 @@ class BackupVMsWindow(Ui_Backup, QWizard):
def current_page_changed(self, id):
if self.currentPage() is self.confirm_page:
del self.func_output[:]
- self.files_to_backup = qubesutils.backup_prepare(str(self.backup_dir), exclude_list = self.excluded, print_callback = self.gather_output)
+ try:
+ self.files_to_backup = qubesutils.backup_prepare(str(self.backup_dir), exclude_list = self.excluded, print_callback = self.gather_output)
+ except Exception as ex:
+ QMessageBox.critical(None, "Error while prepering backup.", "ERROR: {0}".format(ex))
self.textEdit.setReadOnly(True)
self.textEdit.setFontFamily("Monospace")
self.textEdit.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.button(self.CancelButton).setDisabled(True)
self.thread_monitor = ThreadMonitor()
+ initial_usage = qubesutils.get_disk_usage(self.backup_dir)
thread = threading.Thread (target= self.__do_backup__ , args=(self.thread_monitor,))
thread.daemon = True
thread.start()
+ self.button(self.CancelButton).setDisabled(False)
+ counter = 0
while not self.thread_monitor.is_finished():
self.app.processEvents()
time.sleep (0.1)
+ counter += 1
+ if counter == 20:
+ progress = self.check_backup_progress(initial_usage, self.total_size)
+ self.progress_bar.setValue(progress)
+ counter = 0
if not self.thread_monitor.success:
QMessageBox.warning (None, "Backup error!", "ERROR: {1}".format(self.vm.name, self.thread_monitor.error_msg))
@@ -179,6 +308,25 @@ class BackupVMsWindow(Ui_Backup, QWizard):
def reject(self):
+ #cancell clicked while the backup is in progress.
+ #calling kill on cp.
+ if self.currentPage() is self.commit_page:
+ manager_pid = os.getpid()
+ cp_pid_cmd = ["ps" ,"--ppid", str(manager_pid)]
+ pid = None
+
+ while not self.thread_monitor.is_finished():
+ cp_pid = subprocess.Popen(cp_pid_cmd, stdout = subprocess.PIPE)
+ output = cp_pid.stdout.read().split("\n")
+
+ for l in output:
+ if l.endswith("cp"):
+ pid = l.split(" ")[1]
+ break
+ if pid != None:
+ os.kill(int(pid), signal.SIGTERM)
+ break
+
if self.dev_mount_path != None:
umount_device(self.dev_mount_path)
self.done(0)
@@ -213,8 +361,6 @@ def handle_exception( exc_type, exc_value, exc_traceback ):
% ( line, filename ))
-
-
def main():
global qubes_host
diff --git a/qubesmanager/backup_utils.py b/qubesmanager/backup_utils.py
index 4cec39c..368b546 100644
--- a/qubesmanager/backup_utils.py
+++ b/qubesmanager/backup_utils.py
@@ -72,6 +72,8 @@ def umount_device(dev_mount_path):
def fill_devs_list(dialog):
dialog.dev_combobox.clear()
dialog.dev_combobox.addItem("None")
+
+ dialog.blk_manager.blk_lock.acquire()
for a in dialog.blk_manager.attached_devs:
if dialog.blk_manager.attached_devs[a]['attached_to']['vm'] == dialog.vm.name :
att = a + " " + unicode(dialog.blk_manager.attached_devs[a]['size']) + " " + dialog.blk_manager.attached_devs[a]['desc']
@@ -79,6 +81,8 @@ def fill_devs_list(dialog):
for a in dialog.blk_manager.free_devs:
att = a + " " + unicode(dialog.blk_manager.free_devs[a]['size']) + " " + dialog.blk_manager.free_devs[a]['desc']
dialog.dev_combobox.addItem(att, QVariant(a))
+ dialog.blk_manager.blk_lock.release()
+
dialog.dev_combobox.setCurrentIndex(0) #current selected is null ""
dialog.prev_dev_idx = 0
dialog.dir_line_edit.clear()
@@ -107,6 +111,7 @@ def dev_combobox_activated(dialog, idx):
if dialog.dev_combobox.currentText() != "None": #An existing device chosen
dev_name = str(dialog.dev_combobox.itemData(idx).toString())
+ dialog.blk_manager.blk_lock.acquire()
if dev_name in dialog.blk_manager.free_devs:
if dev_name.startswith(dialog.vm.name): # originally attached to dom0
dev_path = "/dev/"+dev_name.split(":")[1]
@@ -118,6 +123,7 @@ def dev_combobox_activated(dialog, idx):
if dev_name in dialog.blk_manager.attached_devs: #is attached to dom0
assert dialog.blk_manager.attached_devs[dev_name]['attached_to']['vm'] == dialog.vm.name
dev_path = "/dev/" + dialog.blk_manager.attached_devs[dev_name]['attached_to']['frontend']
+ dialog.blk_manager.blk_lock.release()
#check if device mounted
dialog.dev_mount_path = check_if_mounted(dev_path)
diff --git a/qubesmanager/global_settings.py b/qubesmanager/global_settings.py
index f47c805..5cc58d6 100644
--- a/qubesmanager/global_settings.py
+++ b/qubesmanager/global_settings.py
@@ -43,7 +43,12 @@ from operator import itemgetter
from ui_globalsettingsdlg import *
+from ConfigParser import SafeConfigParser
+from qubes.qubesutils import parse_size
+from qubes import qmemman_algo
+
dont_keep_dvm_in_memory_path = '/var/lib/qubes/dvmdata/dont_use_shm'
+qmemman_config_path = '/etc/qubes/qmemman.conf'
class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
@@ -187,13 +192,80 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
def __init_mem_defaults__(self):
+
+ #qmemman settings
+ self.qmemman_config = SafeConfigParser()
+ self.vm_min_mem_val = str(qmemman_algo.MIN_PREFMEM)
+ self.dom0_mem_boost_val = str(qmemman_algo.DOM0_MEM_BOOST)
+ self.qmemman_config.read(qmemman_config_path)
+ if self.qmemman_config.has_section('global'):
+ self.vm_min_mem_val = self.qmemman_config.get('global', 'vm-min-mem')
+ self.dom0_mem_boost_val = self.qmemman_config.get('global', 'dom0-mem-boost')
+
+ self.vm_min_mem_val = parse_size(self.vm_min_mem_val)
+ self.dom0_mem_boost_val = parse_size(self.dom0_mem_boost_val)
+
+ self.min_vm_mem.setValue(self.vm_min_mem_val/1024/1024)
+ self.dom0_mem_boost.setValue(self.dom0_mem_boost_val/1024/1024)
+
#keep dispvm in memory
exists = os.path.exists(dont_keep_dvm_in_memory_path)
self.dispvm_in_memory.setChecked( not exists)
def __apply_mem_defaults__(self):
+
+ #qmemman settings
+ current_min_vm_mem = self.min_vm_mem.value()
+ current_dom0_mem_boost = self.dom0_mem_boost.value()
+
+ if current_min_vm_mem*1024*1024 != self.vm_min_mem_val or current_dom0_mem_boost*1024*1024 != self.dom0_mem_boost_val:
+
+ current_min_vm_mem = str(current_min_vm_mem)+'M'
+ current_dom0_mem_boost = str(current_dom0_mem_boost)+'M'
+
+ if not self.qmemman_config.has_section('global'):
+ #add the whole section
+ self.qmemman_config.add_section('global')
+ self.qmemman_config.set('global', 'vm-min-mem', current_min_vm_mem)
+ self.qmemman_config.set('global', 'dom0-mem-boost', current_dom0_mem_boost)
+ self.qmemman_config.set('global', 'cache-margin-factor', str(qmemman_algo.CACHE_FACTOR))
+
+ qmemman_config_file = open(qmemman_config_path, 'a')
+ self.qmemman_config.write(qmemman_config_file)
+ qmemman_config_file.close()
+
+ else:
+ #If there already is a 'global' section, we don't use SafeConfigParser.write() - it would get rid of all the comments...
+
+ lines_to_add = {}
+ lines_to_add['vm-min-mem'] = "vm-min-mem = " + current_min_vm_mem + "\n"
+ lines_to_add['dom0-mem-boost'] = "dom0-mem-boost = " + current_dom0_mem_boost +"\n"
+
+ config_lines = []
+
+ qmemman_config_file = open(qmemman_config_path, 'r')
+ for l in qmemman_config_file:
+ if l.strip().startswith('vm-min-mem'):
+ config_lines.append(lines_to_add['vm-min-mem'])
+ del lines_to_add['vm-min-mem']
+ elif l.strip().startswith('dom0-mem-boost'):
+ config_lines.append(lines_to_add['dom0-mem-boost'])
+ del lines_to_add['dom0-mem-boost']
+ else:
+ config_lines.append(l)
+
+ qmemman_config_file.close()
+
+ for l in lines_to_add:
+ config_lines.append(l)
+
+ qmemman_config_file = open(qmemman_config_path, 'w')
+ qmemman_config_file.writelines(config_lines)
+ qmemman_config_file.close()
+
+ self.anything_changed = True
#keep dispvm in memory
was_checked = not os.path.exists(dont_keep_dvm_in_memory_path)
@@ -206,6 +278,7 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
os.remove(dont_keep_dvm_in_memory_path)
self.anything_changed = True
+
def reject(self):
self.done(0)
diff --git a/qubesmanager/main.py b/qubesmanager/main.py
index 9b46970..d12f083 100755
--- a/qubesmanager/main.py
+++ b/qubesmanager/main.py
@@ -557,9 +557,14 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow):
self.setupUi(self)
self.toolbar = self.toolBar
+ self.qubes_watch = qubesutils.QubesWatch()
self.qvm_collection = QubesVmCollection()
self.blk_manager = QubesBlockDevicesManager(self.qvm_collection)
-
+ 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.connect(self.table, SIGNAL("itemSelectionChanged()"), self.table_selection_changed)
self.table.setColumnWidth(0, self.column_width)
@@ -794,8 +799,10 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow):
rows_with_blk = None
if update_devs == True:
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'])
+ self.blk_manager.blk_lock.release()
if self.counter % 3 == 0 or out_of_schedule:
(self.last_measure_time, self.last_measure_results) = \
@@ -843,7 +850,7 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow):
def update_block_devices(self):
- res, msg = self.blk_manager.update()
+ res, msg = self.blk_manager.check_for_updates()
if msg != None and len(msg) > 0:
str = "\n".join(msg)
trayIcon.showMessage ("Qubes Manager", str, msecs=5000)
@@ -1142,16 +1149,22 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow):
app.processEvents()
if reply == QMessageBox.Yes:
- try:
- subprocess.check_call (["/usr/sbin/xl", "shutdown", vm.name])
- except Exception as ex:
- QMessageBox.warning (None, "Error shutting down VM!", "ERROR: {0}".format(ex))
- return
+ self.shutdown_vm(vm)
+
+
+ def shutdown_vm(self, vm):
+ try:
+ subprocess.check_call (["/usr/sbin/xl", "shutdown", vm.name])
+ except Exception as ex:
+ QMessageBox.warning (None, "Error shutting down VM!", "ERROR: {0}".format(ex))
+ return
+
+ trayIcon.showMessage ("Qubes Manager", "VM '{0}' is shutting down...".format(vm.name), msecs=3000)
+
+ self.shutdown_monitor[vm.qid] = VmShutdownMonitor (vm)
+ QTimer.singleShot (vm_shutdown_timeout, self.shutdown_monitor[vm.qid].check_if_vm_has_shutdown)
- trayIcon.showMessage ("Qubes Manager", "VM '{0}' is shutting down...".format(vm.name), msecs=3000)
- self.shutdown_monitor[vm.qid] = VmShutdownMonitor (vm)
- QTimer.singleShot (vm_shutdown_timeout, self.shutdown_monitor[vm.qid].check_if_vm_has_shutdown)
@pyqtSlot(name='on_action_settings_triggered')
def action_settings_triggered(self):
@@ -1235,7 +1248,7 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow):
@pyqtSlot(name='on_action_backup_triggered')
def action_backup_triggered(self):
- backup_window = BackupVMsWindow(app, self.qvm_collection, self.blk_manager)
+ backup_window = BackupVMsWindow(app, self.qvm_collection, self.blk_manager, self.shutdown_vm)
backup_window.exec_()
@@ -1298,6 +1311,8 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow):
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'] == vm.name:
@@ -1313,6 +1328,8 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow):
action = self.blk_menu.addAction(QIcon(":/add.png"), str)
action.setData(QVariant(d))
+ self.blk_manager.blk_lock.release()
+
if self.blk_menu.isEmpty():
self.blk_menu.setEnabled(False)
@@ -1322,10 +1339,13 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow):
def attach_dettach_device_triggered(self, action):
dev = str(action.data().toString())
vm = self.get_selected_vm()
+
+ self.blk_manager.blk_lock.acquire()
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()
class QubesBlockDevicesManager():
@@ -1338,43 +1358,75 @@ class QubesBlockDevicesManager():
self.current_attached = {}
self.devs_changed = False
+ self.last_update_time = time.time()
+ self.blk_state_changed = True
+ self.msg = []
+ self.check_counter = 0
+ self.blk_lock = threading.Lock()
+
+ self.update()
+
+ def block_devs_event(self, xid):
+ now = time.time()
+ #don't update more often than 1/10 s
+ if now - self.last_update_time >= 0.1:
+ self.last_update_time = now
+
+ self.blk_lock.acquire()
+
+ self.blk_state_changed = True
+
+ self.blk_lock.release()
+
+ def check_for_updates(self):
+ self.blk_lock.acquire()
+
+ ret = (self.blk_state_changed, self.msg)
+
+ if self.blk_state_changed == True:
+ self.check_counter += 1
+
+ self.update()
+ ret = (self.blk_state_changed, self.msg)
+
+ #let the update last for 3 manager-update cycles
+ if self.check_counter == 3:
+ self.check_counter = 0
+ self.blk_state_changed = False
+ self.msg = []
+
+ self.blk_lock.release()
+
+ return ret
+
+
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']))
+ self.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']))
+ self.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
+ self.__update_blk_entries__()
def __update_blk_entries__(self):
@@ -1402,16 +1454,12 @@ class QubesBlockDevicesManager():
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
-
-
class QubesTrayIcon(QSystemTrayIcon):
diff --git a/qubesmanager/multiselectwidget.py b/qubesmanager/multiselectwidget.py
index a9de974..b373f43 100644
--- a/qubesmanager/multiselectwidget.py
+++ b/qubesmanager/multiselectwidget.py
@@ -6,6 +6,8 @@ from ui_multiselectwidget import *
class MultiSelectWidget(Ui_MultiSelectWidget, QWidget):
__pyqtSignals__ = ("selected_changed()",)
+ __pyqtSignals__ = ("items_added(list)",)
+ __pyqtSignals__ = ("items_removed(list)",)
def __init__(self, parent=None):
super(MultiSelectWidget, self).__init__()
@@ -19,13 +21,19 @@ class MultiSelectWidget(Ui_MultiSelectWidget, QWidget):
def switch_selected(self, src, dst):
selected = src.selectedItems()
+ items = []
for s in selected:
row = src.indexFromItem(s).row()
item = src.takeItem(row)
dst.addItem(item)
+ items.append(item)
dst.sortItems()
self.emit(SIGNAL("selected_changed()"))
+ if src is self.selected_list:
+ self.emit(SIGNAL("items_removed(list)"), items)
+ else:
+ self.emit(SIGNAL("items_added(list)"), items)
def add_selected(self):
self.switch_selected(self.available_list, self.selected_list)
@@ -34,11 +42,18 @@ class MultiSelectWidget(Ui_MultiSelectWidget, QWidget):
self.switch_selected(self.selected_list, self.available_list)
def move_all(self, src, dst):
+ items = []
while src.count() > 0:
item = src.takeItem(0)
dst.addItem(item)
+ items.append(item)
dst.sortItems()
self.emit(SIGNAL("selected_changed()"))
+ if src is self.selected_list:
+ self.emit(SIGNAL("items_removed(list)"), items)
+ else:
+ self.emit(SIGNAL("items_added(list)"), items)
+
def add_all(self):
self.move_all(self.available_list, self.selected_list)
diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py
index 483a750..69bf3e8 100644
--- a/qubesmanager/settings.py
+++ b/qubesmanager/settings.py
@@ -358,7 +358,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
self.vcpus.setMaximum(QubesHost().no_cpus)
self.vcpus.setValue(int(self.vm.vcpus))
- self.include_in_balancing.setChecked('meminfo-writer' in self.vm.services and self.vm.services['meminfo-writer']==True)
+ self.include_in_balancing.setChecked(self.vm.services['meminfo-writer']==True)
#kernel
if self.vm.template is not None:
@@ -415,10 +415,8 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
self.vm.vcpus = self.vcpus.value()
self.anything_changed = True
- balancing_was_checked = ('meminfo-writer' in self.vm.services and self.vm.services['meminfo-writer']==True)
- if self.include_in_balancing.isChecked() != balancing_was_checked:
- self.new_srv_dict['meminfo-writer'] = self.include_in_balancing.isChecked()
- self.anything_changed = True
+ #include_in_memory_balancing applied in services tab
+
#kernel changed
if self.kernel.currentIndex() != self.kernel_idx:
@@ -489,6 +487,12 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
self.anything_changed = True
def include_in_balancing_state_changed(self, state):
+ for r in range (self.services_list.count()):
+ item = self.services_list.item(r)
+ if str(item.text()) == 'meminfo-writer':
+ item.setCheckState(state)
+ break;
+
if self.dev_list.selected_list.count() > 0:
if state == QtCore.Qt.Checked:
self.dmm_warning_adv.show()
@@ -496,6 +500,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
else:
self.dmm_warning_adv.hide()
self.dmm_warning_dev.hide()
+
def devices_selection_changed(self):
if self.include_in_balancing.isChecked():
if self.dev_list.selected_list.count() > 0 :
@@ -515,28 +520,54 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
else:
item.setCheckState(QtCore.Qt.Unchecked)
self.services_list.addItem(item)
+
+ self.connect(self.services_list, SIGNAL("itemClicked(QListWidgetItem *)"), self.services_item_clicked)
self.new_srv_dict = copy(self.vm.services)
def __add_service__(self):
srv = str(self.service_line_edit.text()).strip()
- if srv != "" and srv not in self.new_srv_dict:
- item = QListWidgetItem(srv)
- item.setCheckState(QtCore.Qt.Checked)
- self.services_list.addItem(item)
- self.new_srv_dict[srv] = True
+ if srv != "":
+ if srv in self.new_srv_dict:
+ QMessageBox.information(None, "", "Service already on the list!")
+ else:
+ item = QListWidgetItem(srv)
+ item.setCheckState(QtCore.Qt.Checked)
+ self.services_list.addItem(item)
+ self.new_srv_dict[srv] = True
def __remove_service__(self):
- row = self.services_list.currentRow()
- if row:
+ item = self.services_list.currentItem()
+ if str(item.text()) == 'meminfo-writer':
+ QMessageBox.information(None, "Service can not be removed", "Service meminfo-writer can not be removed from the list.")
+ else:
+ row = self.services_list.currentRow()
item = self.services_list.takeItem(row)
del self.new_srv_dict[str(item.text())]
+
+ def services_item_clicked(self, item):
+ if str(item.text()) == 'meminfo-writer':
+ if item.checkState() == QtCore.Qt.Checked:
+ if not self.include_in_balancing.isChecked():
+ self.include_in_balancing.setChecked(True)
+ elif item.checkState() == QtCore.Qt.Unchecked:
+ if self.include_in_balancing.isChecked():
+ self.include_in_balancing.setChecked(False)
+
+
def __apply_services_tab__(self):
- new_dict = {}
for r in range (self.services_list.count()):
item = self.services_list.item(r)
self.new_srv_dict[str(item.text())] = (item.checkState() == QtCore.Qt.Checked)
+ balancing_was_checked = (self.vm.services['meminfo-writer']==True)
+ balancing_is_checked = self.include_in_balancing.isChecked()
+ meminfo_writer_checked = (self.new_srv_dict['meminfo-writer'] == True)
+
+ if balancing_is_checked != meminfo_writer_checked:
+ if balancing_is_checked != balancing_was_checked:
+ self.new_srv_dict['meminfo-writer'] = balancing_is_checked
+
if self.new_srv_dict != self.vm.services:
self.vm.services = self.new_srv_dict
self.anything_changed = True
diff --git a/resources.qrc b/resources.qrc
index 662aaa4..639edfc 100644
--- a/resources.qrc
+++ b/resources.qrc
@@ -1,19 +1,26 @@
+ icons/apps.png
+ icons/settings.png
+ icons/wall.png
+ icons/restore.png
+ icons/backup.png
+ icons/global-settings.png
icons/on-icon/off.png
- icons/on-icon/on.png
+ icons/on-icon/on.png
+ icons/on-icon/show-all-running.png
icons/mount.png
icons/pencil.png
icons/redfirewall.png
icons/edit.png
icons/add.png
icons/flag-blue.png
+ icons/running.png
icons/flag-green.png
icons/flag-red.png
icons/flag-yellow.png
icons/remove.png
icons/appsprefs.png
- icons/newfirewall.png
icons/qubes.png
icons/appvm.png
icons/netvm.png
@@ -31,6 +38,6 @@
icons/pausevm.png
icons/showallvms.png
icons/showcpuload.png
- icons/firewall.png
+
diff --git a/rpm_spec/qmgr.spec b/rpm_spec/qmgr.spec
index 0942ec3..660ea58 100644
--- a/rpm_spec/qmgr.spec
+++ b/rpm_spec/qmgr.spec
@@ -11,7 +11,7 @@ Group: Qubes
Vendor: Invisible Things Lab
License: GPL
URL: http://fixme
-Requires: python, PyQt4, qubes-core-dom0 >= 1.7.15, kdebase
+Requires: python, PyQt4, qubes-core-dom0 > 1.7.17, kdebase
Requires: pmount, cryptsetup
BuildRequires: PyQt4-devel
AutoReq: 0
diff --git a/settingsdlg.ui b/settingsdlg.ui
index 4cb4d8a..c485c2a 100644
--- a/settingsdlg.ui
+++ b/settingsdlg.ui
@@ -29,7 +29,7 @@
- 1
+ 4
@@ -586,6 +586,10 @@
+
+
+ :/firewall.png:/firewall.png
+
Firewall rules
@@ -746,6 +750,10 @@
true
+
+
+ :/storagevm.png:/storagevm.png
+
Devices
@@ -776,7 +784,7 @@
- :/appsprefs.png:/appsprefs.png
+ :/apps.png:/apps.png
Applications