diff --git a/mainwindow.ui b/mainwindow.ui index b216176..0de6d08 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -82,7 +82,7 @@ - Qt::ActionsContextMenu + Qt::CustomContextMenu false @@ -198,11 +198,6 @@ Memory usage graph - - - Block Devices - - @@ -235,7 +230,6 @@ - @@ -257,11 +251,12 @@ false - + + @@ -493,17 +488,6 @@ NetVM - - - true - - - true - - - Block Devices - - diff --git a/qubesmanager/main.py b/qubesmanager/main.py index 981157e..bfbc1e4 100755 --- a/qubesmanager/main.py +++ b/qubesmanager/main.py @@ -120,17 +120,24 @@ class VmInfoWidget (QWidget): self.label_name = QLabel (vm.name) self.vm_icon = VmStatusIcon(vm) + self.blk_icon = VmIconWidget(":/mount.png") 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.setLayout(layout) + self.blk_icon.setVisible(False) + self.tableItem = self.VmInfoItem(vm.name, vm.qid) - def update_vm_state (self, vm): + def update_vm_state (self, vm, blk_visible): self.vm_icon.update() + if blk_visible != None: + self.blk_icon.setVisible(blk_visible) @@ -167,13 +174,14 @@ class VmIconWidget (QWidget): label_icon = QLabel() icon = QIcon (icon_path) - icon_sz = QSize (VmManagerWindow.row_height * 0.8, VmManagerWindow.row_height * 0.8) + 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) layout = QVBoxLayout() layout.addWidget(label_icon) + layout.setContentsMargins(0,0,0,0) self.setLayout(layout) @@ -396,131 +404,6 @@ class VmUpdateInfoWidget(QWidget): self.layout().addWidget(self.icon, alignment=Qt.AlignCenter) -class VmBlockDevicesWidget(QWidget): - def __init__(self, vm, block_manager, parent=None): - super(VmBlockDevicesWidget, self).__init__(parent) - - self.vm = vm - self.block_manager = block_manager - self.free_devs = self.block_manager.free_devs - self.att_devs = self.block_manager.attached_devs - - self.combo = QComboBox() - - layout = QVBoxLayout() - layout.addWidget(self.combo) - self.setLayout(layout) - - self.connect(self.combo, SIGNAL("activated(int)"), self.combo_activated) - - def update(self, vm): - self.combo.clear() - self.combo.addItem("None") - for a in self.att_devs: - if self.att_devs[a]['attached_to']['vm'] == vm.name: - att = self.att_devs[a]['dev'] + ": " + unicode(self.att_devs[a]['size']) + " " + self.att_devs[a]['desc'] - self.combo.addItem(att, QVariant(a)) - self.combo.setCurrentIndex(1) - break - if self.combo.count() == 1: - for d in self.free_devs: - str = self.free_devs[d]['dev'] + ": " + unicode(self.free_devs[d]['size']) + " " + self.free_devs[d]['desc'] - self.combo.addItem(str, QVariant(d)) - self.prev_idx = self.combo.currentIndex(); - - def combo_activated(self, idx): - if idx == self.prev_idx: #nothing has changed - return - #there was a change - if self.combo.currentText() != "None": #device attached: - self.prev_idx = idx - dev_name = str(self.combo.itemData(idx).toString()) - self.block_manager.attach_device(self.vm, dev_name) - - else: - dev_name = str(self.combo.itemData(self.prev_idx).toString()) - self.prev_idx = idx - self.block_manager.detach_device(self.vm, dev_name) - - - -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() - 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 - - for b in self.current_blk: #remove devices that are not there anymore - if b not in blk: - del self.current_blk[b] - del self.current_attached[b] - self.devs_changed = True - - if self.devs_changed == True: - self.devs_changed = False - self.__update_blk_entries__() - return True - else: - return False - - - 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 - - class VmRowInTable(object): cpu_graph_hue = 210 @@ -565,21 +448,15 @@ class VmRowInTable(object): table.setCellWidget(row_no, 7, self.mem_widget) table.setItem(row_no, 7, self.mem_widget.tableItem) - self.blockdevices_widget = VmBlockDevicesWidget(vm, block_manager) - table.setCellWidget(row_no, 8, self.blockdevices_widget) - - def update(self, counter, update_devs = False, cpu_load = None): - self.info_widget.update_vm_state(self.vm) - self.blockdevices_widget.setEnabled(self.vm.is_running()) + 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.cpu_usage_widget.update_load(self.vm, cpu_load) self.mem_usage_widget.update_load(self.vm, None) self.load_widget.update_load(self.vm, cpu_load) self.mem_widget.update_load(self.vm, None) self.upd_widget.update_outdated(self.vm) - if self.blockdevices_widget.isEnabled() and update_devs: - self.blockdevices_widget.update(self.vm) class NewAppVmDlg (QDialog, ui_newappvmdlg.Ui_NewAppVMDlg): def __init__(self, parent = None): @@ -640,8 +517,7 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow): "CPU": 4, "CPU Graph": 5, "MEM": 6, - "MEM Graph": 7, - "Block Device": 8 } + "MEM Graph": 7,} @@ -651,7 +527,7 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow): self.toolbar = self.toolBar self.qvm_collection = QubesVmCollection() - self.blkManager = QubesBlockDevicesManager(self.qvm_collection) + self.blk_manager = QubesBlockDevicesManager(self.qvm_collection) self.connect(self.table, SIGNAL("itemSelectionChanged()"), self.table_selection_changed) @@ -669,27 +545,30 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow): self.actionCPU_Graph.setChecked(False) self.table.setColumnHidden( self.columns_indices["MEM Graph"], True) self.actionMEM_Graph.setChecked(False) - self.table.setColumnHidden( self.columns_indices["Block Device"], True) - self.actionBlock_Devices.setChecked(False) self.table.setColumnWidth(self.columns_indices["Upd"], 50) - self.table.setColumnWidth(self.columns_indices["Block Device"], 250) self.table.sortItems(self.columns_indices["MEM"], Qt.DescendingOrder) - self.table.addAction(self.action_settings) - self.table.addAction(self.action_removevm) - self.table.addAction(self.action_resumevm) - self.table.addAction(self.action_pausevm) - self.table.addAction(self.action_shutdownvm) - self.table.addAction(self.action_appmenus) - self.table.addAction(self.action_editfwrules) - self.table.addAction(self.action_updatevm) + 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 = {} self.last_measure_results = {} @@ -778,7 +657,7 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow): continue if vm.internal: continue - vm_row = VmRowInTable (vm, row_no, self.table, self.blkManager) + vm_row = VmRowInTable (vm, row_no, self.table, self.blk_manager) vms_in_table[vm.qid] = vm_row row_no += 1 @@ -794,6 +673,7 @@ class VmManagerWindow(Ui_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: @@ -802,11 +682,17 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow): vm.last_power_state = state some_vms_have_changed_power_state = True - update_devs = self.update_block_devices() 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, @@ -818,10 +704,23 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow): cur_cpu_load = self.last_measure_results[vm_row.vm.xid]['cpu_usage'] else: cur_cpu_load = 0 - vm_row.update(self.counter, update_devs=update_devs, cpu_load = cur_cpu_load) + + 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(): - vm_row.update(self.counter, update_devs=update_devs) + 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() @@ -840,14 +739,11 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow): self.setFixedWidth(table_width) def update_block_devices(self): - if self.table.isColumnHidden( self.columns_indices['Block Device']): - return False - - if self.blkManager.update(): - return True - else: - return False - + 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): @@ -880,6 +776,7 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow): self.hide() event.ignore() + @pyqtSlot(name='on_action_createvm_triggered') def action_createvm_triggered(self): dialog = NewAppVmDlg() @@ -1238,8 +1135,125 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow): def on_actionMEM_Graph_toggled(self, checked): self.showhide_collumn( self.columns_indices['MEM Graph'], checked) - def on_actionBlock_Devices_toggled(self, checked): - self.showhide_collumn( self.columns_indices['Block Device'], 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 self.blk_menu.isEmpty() and len(self.blk_manager.free_devs) > 0: + for d in self.blk_manager.free_devs: + 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 diff --git a/resources.qrc b/resources.qrc index 6d47a74..935b43a 100644 --- a/resources.qrc +++ b/resources.qrc @@ -1,5 +1,6 @@ + icons/mount.png icons/pencil.png icons/redfirewall.png icons/edit.png