diff --git a/mainwindow.ui b/mainwindow.ui
index 7f17ac8..e8e0ade 100644
--- a/mainwindow.ui
+++ b/mainwindow.ui
@@ -229,7 +229,7 @@
0
0
769
- 25
+ 22
+
@@ -738,6 +739,18 @@
Run command in the specified VM
+
+
+ false
+
+
+
+ :/templatevm.png:/templatevm.png
+
+
+ Clone VM
+
+
diff --git a/qubesmanager/main.py b/qubesmanager/main.py
index 93e16cd..f2a5d9d 100755
--- a/qubesmanager/main.py
+++ b/qubesmanager/main.py
@@ -39,6 +39,7 @@ from qubes.qubes import dry_run
from qubes.qubes import qubes_guid_path
from qubes.qubes import QubesDaemonPidfile
from qubes.qubes import QubesHost
+from qubes import qubes
from qubes import qubesutils
import qubesmanager.resources_rc
@@ -754,6 +755,7 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow):
self.context_menu = QMenu(self)
self.context_menu.addAction(self.action_removevm)
self.context_menu.addAction(self.action_resumevm)
+ self.context_menu.addAction(self.action_clonevm)
self.context_menu.addAction(self.action_pausevm)
self.context_menu.addAction(self.action_shutdownvm)
self.context_menu.addAction(self.action_killvm)
@@ -1073,6 +1075,7 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow):
# Update available actions:
self.action_settings.setEnabled(vm.qid != 0)
self.action_removevm.setEnabled(not vm.installed_by_rpm and not (vm.last_running))
+ self.action_clonevm.setEnabled(not vm.installed_by_rpm and not (vm.last_running) and not vm.is_netvm())
self.action_resumevm.setEnabled(not vm.last_running)
self.action_pausevm.setEnabled(vm.last_running and vm.qid != 0)
self.action_shutdownvm.setEnabled(vm.last_running and vm.qid != 0)
@@ -1085,6 +1088,7 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow):
else:
self.action_settings.setEnabled(False)
self.action_removevm.setEnabled(False)
+ self.action_clonevm.setEnabled(False)
self.action_resumevm.setEnabled(False)
self.action_pausevm.setEnabled(False)
self.action_shutdownvm.setEnabled(False)
@@ -1213,6 +1217,68 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow):
thread_monitor.set_finished()
+ @pyqtSlot(name='on_action_clonevm_triggered')
+ def action_clonevm_triggered(self):
+ vm = self.get_selected_vm()
+
+ name_number = 1
+ name_format = vm.name + '-clone-%d'
+ while self.qvm_collection.get_vm_by_name(name_format % name_number):
+ name_number += 1
+
+ cmd = ['kdialog', '--title', 'Qubes clone VM', '--inputbox', 'Enter name for VM '+vm.name+' clone:', name_format % name_number]
+ kdialog = subprocess.Popen(cmd, stdout = subprocess.PIPE)
+ clone_name = kdialog.stdout.read().strip()
+ if clone_name == "":
+ return
+
+ thread_monitor = ThreadMonitor()
+ thread = threading.Thread (target=self.do_clone_vm, args=(vm, clone_name, thread_monitor))
+ thread.daemon = True
+ thread.start()
+
+ progress = QProgressDialog ("Cloning VM {0} to {1}...".format(vm.name, clone_name), "", 0, 0)
+ progress.setCancelButton(None)
+ progress.setModal(True)
+ progress.show()
+
+ while not thread_monitor.is_finished():
+ app.processEvents()
+ time.sleep (0.2)
+
+ progress.hide()
+
+ if not thread_monitor.success:
+ QMessageBox.warning (None, "Error while cloning VM", "Exception while cloning:
{0}".format(thread_monitor.error_msg))
+
+
+ def do_clone_vm(self, vm, dst_name, thread_monitor):
+ try:
+ self.qvm_collection.lock_db_for_writing()
+ self.qvm_collection.load()
+ src_vm = self.qvm_collection[vm.qid]
+
+ dst_vm = None
+ if isinstance(src_vm, qubes.QubesTemplateVm):
+ dst_vm = self.qvm_collection.add_new_templatevm(name=dst_name,
+ installed_by_rpm=False)
+ elif isinstance(src_vm, qubes.QubesAppVm):
+ dst_vm = self.qvm_collection.add_new_appvm(name=dst_name, template=src_vm.template,
+ label=src_vm.label)
+ elif hasattr(qubes, 'QubesHVm') and isinstance(src_vm, qubes.QubesHVm):
+ dst_vm = self.qvm_collection.add_new_hvm(name=dst_name, label=src_vm.label)
+
+ dst_vm.clone_attrs(src_vm)
+ dst_vm.clone_disk_files (src_vm=src_vm, verbose=False)
+ self.qvm_collection.save()
+ self.qvm_collection.unlock_db()
+ except Exception as ex:
+ if dst_vm:
+ self.qvm_collection.pop(dst_vm.qid)
+ self.qvm_collection.unlock_db()
+ thread_monitor.set_error_msg(str(ex))
+ thread_monitor.set_finished()
+
@pyqtSlot(name='on_action_resumevm_triggered')
def action_resumevm_triggered(self):
vm = self.get_selected_vm()