From dd990c04ac6d1330551dc13e1aa49e7a9c2a9ad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Fri, 13 Oct 2017 18:04:49 +0200 Subject: [PATCH 01/70] Firewall settings Modified VM Settings - Firewall tab to be easier to use and saner. --- qubesmanager/firewall.py | 295 +++++++++++++++++++++------------------ qubesmanager/settings.py | 132 ++++++------------ ui/newfwruledlg.ui | 97 ++++++------- ui/settingsdlg.ui | 223 ++++++++++++++++++----------- 4 files changed, 385 insertions(+), 362 deletions(-) diff --git a/qubesmanager/firewall.py b/qubesmanager/firewall.py index d482b3f..fa61186 100644 --- a/qubesmanager/firewall.py +++ b/qubesmanager/firewall.py @@ -143,17 +143,6 @@ class QubesFirewallRulesModel(QAbstractItemModel): def __init__(self, parent=None): QAbstractItemModel.__init__(self, parent) - self.__columnValues = { - 0: lambda x: "*" if self.children[x]["address"] == "0.0.0.0" and - self.children[x]["netmask"] == 0 else - self.children[x]["address"] + ("" if self.children[x][ "netmask"] == 32 else - " /{0}".format(self.children[x][ - "netmask"])), - 1: lambda x: "any" if self.children[x]["portBegin"] == 0 else - "{0}-{1}".format(self.children[x]["portBegin"], self.children[x][ - "portEnd"]) if self.children[x]["portEnd"] is not None else \ - self.get_service_name(self.children[x]["portBegin"]), - 2: lambda x: self.children[x]["proto"], } self.__columnNames = {0: "Address", 1: "Service", 2: "Protocol", } self.__services = list() pattern = re.compile("(?P[a-z][a-z0-9-]+)\s+(?P[0-9]+)/(?P[a-z]+)", re.IGNORECASE) @@ -171,23 +160,17 @@ class QubesFirewallRulesModel(QAbstractItemModel): from operator import attrgetter rev = (order == Qt.AscendingOrder) - if idx==0: - self.children.sort(key=lambda x: x['address'], reverse = rev) - if idx==1: - self.children.sort(key=lambda x: self.get_service_name(x[ - "portBegin"]) if x["portEnd"] == None else x["portBegin"], - reverse = rev) - if idx==2: - self.children.sort(key=lambda x: x['proto'], reverse - = rev) + self.children.sort(key = lambda x: self.get_column_string(idx, x) + , reverse = rev) + index1 = self.createIndex(0, 0) - index2 = self.createIndex(len(self)-1, len(self.__columnValues)-1) + index2 = self.createIndex(len(self)-1, len(self.__columnNames)-1) self.dataChanged.emit(index1, index2) def get_service_name(self, port): for service in self.__services: - if service[1] == port: + if str(service[1]) == str(port): return service[0] return str(port) @@ -197,129 +180,122 @@ class QubesFirewallRulesModel(QAbstractItemModel): return service[1] return None - def get_column_string(self, col, row): - return self.__columnValues[col](row) + def get_column_string(self, col, rule): + # Address + if col == 0: + if rule.dsthost is None: + return "*" + else: + if rule.dsthost.type == 'dst4'\ + and rule.dsthost.prefixlen == '32': + return str(rule.dsthost)[:-3] + elif rule.dsthost.type == 'dst6'\ + and rule.dsthost.prefixlen == '128': + return str(rule.dsthost)[:-4] + else: + return str(rule.dsthost) - - def rule_to_dict(self, rule): - if rule.dsthost is None: - raise FirewallModifiedOutsideError('no dsthost') - - d = {} - - if not rule.proto: - d['proto'] = 'any' - d['portBegin'] = 'any' - d['portEnd'] = None - - else: - d['proto'] = rule.proto + # Service + if col == 1: if rule.dstports is None: - raise FirewallModifiedOutsideError('no dstport') - d['portBegin'] = rule.dstports.range[0] - d['portEnd'] = rule.dstports.range[1] \ - if rule.dstports.range[0] != rule.dstports.range[1] \ - else None + return "any" + elif rule.dstports.range[0] != rule.dstports.range[1]: + return str(rule.dstports) + else: + return self.get_service_name(rule.dstports) - if rule.dsthost.type == 'dsthost': - d['address'] = str(rule.dsthost) - d['netmask'] = 32 - elif rule.dsthost.type == 'dst4': - network = ipaddress.IPv4Network(rule.dsthost) - d['address'] = str(network.network_address) - d['netmask'] = int(network.prefixlen) - else: - raise FirewallModifiedOutsideError( - 'cannot map dsthost.type={!s}'.format(rule.dsthost)) - - if rule.expire is not None: - d['expire'] = int(rule.expire) - - return d + # Protocol + if col == 2: + if rule.proto is None: + return "any" + else: + return str(rule.proto) + return "unknown" def get_firewall_conf(self, vm): conf = { 'allow': None, - 'allowDns': False, - 'allowIcmp': False, - 'allowYumProxy': False, + 'expire': 0, 'rules': [], } + allowDns = False + allowIcmp = False common_action = None - tentative_action = None reversed_rules = list(reversed(vm.firewall.rules)) + last_rule = reversed_rules.pop(0) + if last_rule == qubesadmin.firewall.Rule('action=accept') \ + or last_rule == qubesadmin.firewall.Rule('action=drop'): + common_action = last_rule.action + else: + FirewallModifiedOutsideError('Last rule must be either ' + 'drop all or accept all.') + + dns_rule = qubesadmin.firewall.Rule(None, + action='accept', specialtarget='dns') + icmp_rule = qubesadmin.firewall.Rule(None, + action='accept', proto='icmp') while reversed_rules: - rule = reversed_rules[0] - if rule.dsthost is not None or rule.proto is not None: - break - tentative_action = reversed_rules.pop(0).action + rule = reversed_rules.pop(0) - if not reversed_rules: - conf['allow'] = tentative_action == 'accept' - return conf - - for rule in reversed_rules: - if rule.specialtarget == 'dns': - conf['allowDns'] = (rule.action == 'accept') + if rule == dns_rule: + allowDns = True continue - if rule.proto == 'icmp': - if rule.icmptype is not None: - raise FirewallModifiedOutsideError( - 'cannot map icmptype != None') - conf['allowIcmp'] = (rule.action == 'accept') + if rule.proto == icmp_rule: + allowIcmp = True continue - if common_action is None: - common_action = rule.action - elif common_action != rule.action: - raise FirewallModifiedOutsideError('incoherent action') + if rule.specialtarget is not None or rule.icmptype is not None: + raise FirewallModifiedOutsideError("Rule type unknown!") - conf['rules'].insert(0, self.rule_to_dict(rule)) + if (rule.dsthost is not None or rule.proto is not None) \ + and rule.expire is None: + if rule.action == 'accept': + conf['rules'].insert(0, rule) + continue + else: + raise FirewallModifiedOutsideError('No blacklist support.') - if common_action is None or common_action != tentative_action: - # we've got only specialtarget and/or icmp - conf['allow'] = tentative_action == 'accept' - return conf + if rule.expire is not None and rule.dsthost is None \ + and rule.proto is None: + conf['expire'] = int(str(rule.expire)) + continue - raise FirewallModifiedOutsideError('it does not add up') + raise FirewallModifiedOutsideError('it does not add up.') + + conf['allow'] = (common_action == 'accept') + + if not allowIcmp and not conf['allow']: + raise FirewallModifiedOutsideError('ICMP must be allowed.') + + if not allowDns and not conf['allow']: + raise FirewallModifiedOutsideError('DNS must be allowed') + + return conf def write_firewall_conf(self, vm, conf): - common_action = qubesadmin.firewall.Action( - 'drop' if conf['allow'] else 'accept') - rules = [] for rule in conf['rules']: - kwargs = {} - if rule['proto'] != 'any': - kwargs['proto'] = rule['proto'] - if rule['portBegin'] != 'any': - kwargs['dstports'] = '-'.join(map(str, filter((lambda x: x), - (rule['portBegin'], rule['portEnd'])))) + rules.append(rule) - netmask = str(rule['netmask']) if rule['netmask'] != 32 else None - - rules.append(qubesadmin.firewall.Rule(None, - action=common_action, - dsthost='/'.join(map(str, filter((lambda x: x), - (rule['address'], netmask)))), - **kwargs)) - - if conf['allowDns']: + if not conf['allow']: rules.append(qubesadmin.firewall.Rule(None, action='accept', specialtarget='dns')) - if conf['allowIcmp']: + if not conf['allow']: rules.append(qubesadmin.firewall.Rule(None, action='accept', proto='icmp')) - if common_action == 'drop': + if conf['allow']: rules.append(qubesadmin.firewall.Rule(None, action='accept')) + else: + rules.append(qubesadmin.firewall.Rule(None, + action = 'drop')) vm.firewall.rules = rules @@ -331,58 +307,98 @@ class QubesFirewallRulesModel(QAbstractItemModel): conf = self.get_firewall_conf(vm) self.allow = conf["allow"] - self.allowDns = conf["allowDns"] - self.allowIcmp = conf["allowIcmp"] - self.allowYumProxy = conf["allowYumProxy"] - self.tempFullAccessExpireTime = 0 + + self.tempFullAccessExpireTime = conf['expire'] for rule in conf["rules"]: self.appendChild(rule) - if "expire" in rule and rule["address"] == "0.0.0.0": - self.tempFullAccessExpireTime = rule["expire"] def get_vm_name(self): return self.__vm.name - def apply_rules(self, allow, dns, icmp, yumproxy, tempFullAccess=False, + def apply_rules(self, allow, tempFullAccess=False, tempFullAccessTime=None): assert self.__vm is not None - if self.allow != allow or self.allowDns != dns or \ - self.allowIcmp != icmp or self.allowYumProxy != yumproxy or \ + if self.allow != allow or \ (self.tempFullAccessExpireTime != 0) != tempFullAccess: self.fw_changed = True conf = { "allow": allow, - "allowDns": dns, - "allowIcmp": icmp, - "allowYumProxy": yumproxy, "rules": list() } - for rule in self.children: - if "expire" in rule and rule["address"] == "0.0.0.0" and \ - rule["netmask"] == 0 and rule["proto"] == "any": - # rule already present, update its time - if tempFullAccess: - rule["expire"] = \ - int(datetime.datetime.now().strftime("%s")) + \ - tempFullAccessTime*60 - tempFullAccess = False - conf["rules"].append(rule) + conf['rules'].extend(self.children) if tempFullAccess and not allow: - conf["rules"].append({"address": "0.0.0.0", - "netmask": 0, - "proto": "any", - "expire": int( - datetime.datetime.now().strftime("%s"))+\ - tempFullAccessTime*60 - }) + conf["rules"].append(qubesadmin.firewall.Rule(None,action='accept' + , expire=int(datetime.datetime.now().strftime("%s"))+\ + tempFullAccessTime*60)) if self.fw_changed: self.write_firewall_conf(self.__vm, conf) + def populate_edit_dialog(self, dialog, row): + address = self.get_column_string(0, self.children[row]) + dialog.addressComboBox.setItemText(0, address) + dialog.addressComboBox.setCurrentIndex(0) + service = self.get_column_string(1, self.children[row]) + if service == "any": + service = "" + dialog.serviceComboBox.setItemText(0, service) + dialog.serviceComboBox.setCurrentIndex(0) + protocol = self.get_column_string(2, self.children[row]) + if protocol == "tcp": + dialog.tcp_radio.setChecked(True) + elif protocol == "udp": + dialog.udp_radio.setChecked(True) + else: + dialog.any_radio.setChecked(True) + + def run_rule_dialog(self, dialog, row = None): + if dialog.exec_(): + + address = str(dialog.addressComboBox.currentText()) + service = str(dialog.serviceComboBox.currentText()) + + rule = qubesadmin.firewall.Rule(None,action='accept') + + if address is not None and address != "*": + try: + rule.dsthost = address + except ValueError: + QMessageBox.warning(None, self.tr("Invalid address"), + self.tr("Address '{0}' is invalid.").format(address)) + + if dialog.tcp_radio.isChecked(): + rule.proto = 'tcp' + elif dialog.udp_radio.isChecked(): + rule.proto = 'udp' + + if '-' in service: + try: + rule.dstports = service + except ValueError: + QMessageBox.warning(None, self.tr("Invalid port or service"), + self.tr("Port number or service '{0}' is invalid.") + .format(service)) + elif service is not None: + try: + rule.dstports = service + except (TypeError, ValueError) as ex: + if self.get_service_port(service) is not None: + rule.dstports = self.get_service_port(service) + else: + QMessageBox.warning(None, + self.tr("Invalid port or service"), + self.tr("Port number or service '{0}' is invalid.") + .format(service)) + + if row is not None: + self.setChild(row, rule) + else: + self.appendChild(rule) + def index(self, row, column, parent=QModelIndex()): if not self.hasIndex(row, column, parent): return QModelIndex() @@ -396,7 +412,7 @@ class QubesFirewallRulesModel(QAbstractItemModel): return len(self) def columnCount(self, parent=QModelIndex()): - return len(self.__columnValues) + return len(self.__columnNames) def hasChildren(self, index=QModelIndex()): parentItem = index.internalPointer() @@ -407,7 +423,8 @@ class QubesFirewallRulesModel(QAbstractItemModel): def data(self, index, role=Qt.DisplayRole): if index.isValid() and role == Qt.DisplayRole: - return self.__columnValues[index.column()](index.row()) + return self.get_column_string(index.column() + ,self.children[index.row()]) def headerData(self, section, orientation, role=Qt.DisplayRole): if section < len(self.__columnNames) \ diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index 79d9a18..a36ba89 100755 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -80,7 +80,8 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): self.tabWidget.currentChanged.connect(self.current_tab_changed) -# self.tabWidget.setTabEnabled(self.tabs_indices["firewall"], vm.is_networked() and not vm.provides_network) + self.tabWidget.setTabEnabled(self.tabs_indices["firewall"], + vm.netvm is not None and not vm.provides_network) ###### basic tab self.__init_basic_tab__() @@ -96,8 +97,12 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): ###### firewall tab if self.tabWidget.isTabEnabled(self.tabs_indices['firewall']): model = QubesFirewallRulesModel() - model.set_vm(vm) - self.set_fw_model(model) + try: + model.set_vm(vm) + self.set_fw_model(model) + self.firewallModifiedOutsidelabel.setVisible(False) + except FirewallModifiedOutsideError as ex: + self.disable_all_fw_conf() self.newRuleButton.clicked.connect(self.new_rule_button_pressed) self.editRuleButton.clicked.connect(self.edit_rule_button_pressed) @@ -175,11 +180,8 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): ret.append(self.tr('Error while saving changes: ') + str(ex)) try: - if self.tabWidget.isTabEnabled(self.tabs_indices["firewall"]): + if self.policyAllowRadioButton.isEnabled(): self.fw_model.apply_rules(self.policyAllowRadioButton.isChecked(), - self.dnsCheckBox.isChecked(), - self.icmpCheckBox.isChecked(), - self.yumproxyCheckBox.isChecked(), self.tempFullAccess.isChecked(), self.tempFullAccessTime.value()) if self.fw_model.fw_changed: @@ -773,114 +775,58 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): 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) - self.yumproxyCheckBox.setChecked(model.allowYumProxy) if model.tempFullAccessExpireTime: self.tempFullAccess.setChecked(True) self.tempFullAccessTime.setValue( (model.tempFullAccessExpireTime - int(datetime.datetime.now().strftime("%s")))/60) + def disable_all_fw_conf(self): + self.firewallModifiedOutsidelabel.setVisible(True) + self.policyAllowRadioButton.setEnabled(False) + self.policyDenyRadioButton.setEnabled(False) + self.rulesTreeView.setEnabled(False) + self.newRuleButton.setEnabled(False) + self.editRuleButton.setEnabled(False) + self.deleteRuleButton.setEnabled(False) + self.firewalRulesLabel.setEnabled(False) + self.tempFullAccessWidget.setEnabled(False) + def set_allow(self, allow): self.policyAllowRadioButton.setChecked(allow) self.policyDenyRadioButton.setChecked(not allow) self.policy_changed(allow) def policy_changed(self, checked): - self.tempFullAccessWidget.setEnabled(self.policyDenyRadioButton.isChecked()) + self.rulesTreeView.setEnabled(self.policyDenyRadioButton.isChecked()) + self.newRuleButton.setEnabled(self.policyDenyRadioButton.isChecked()) + self.editRuleButton.setEnabled(self.policyDenyRadioButton.isChecked()) + self.deleteRuleButton.setEnabled(self.policyDenyRadioButton.isChecked()) + self.firewalRulesLabel.setEnabled( + self.policyDenyRadioButton.isChecked()) + self.tempFullAccessWidget.setEnabled( + self.policyDenyRadioButton.isChecked()) def new_rule_button_pressed(self): dialog = NewFwRuleDlg() - self.run_rule_dialog(dialog) + self.fw_model.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) - if service == "any": - service = "" - dialog.serviceComboBox.setItemText(0, service) - dialog.serviceComboBox.setCurrentIndex(0) - protocol = self.fw_model.get_column_string(2, row) - if protocol == "tcp": - dialog.tcp_radio.setChecked(True) - elif protocol == "udp": - dialog.udp_radio.setChecked(True) - else: - dialog.any_radio.setChecked(True) - self.run_rule_dialog(dialog, row) + selected = self.rulesTreeView.selectedIndexes() + + if len(selected) > 0: + dialog = NewFwRuleDlg() + dialog.set_ok_enabled(True) + row = self.rulesTreeView.selectedIndexes().pop().row() + self.fw_model.populate_edit_dialog(dialog, row) + self.fw_model.run_rule_dialog(dialog, row) def delete_rule_button_pressed(self): - for i in set([index.row() for index in self.rulesTreeView.selectedIndexes()]): + 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 dialog.any_radio.isChecked(): - protocol = "any" - port = 0 - else: - if dialog.tcp_radio.isChecked(): - protocol = "tcp" - elif dialog.udp_radio.isChecked(): - protocol = "udp" - else: - protocol = "any" - - 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, self.tr("Invalid service ports range"), - self.tr("Port {0} is lower than port {1}.").format( - port2, port)) - else: - item = {"address": address, - "netmask": netmask, - "portBegin": port, - "portEnd": port2, - "proto": protocol, - } - if row is not None: - self.fw_model.setChild(row, item) - else: - self.fw_model.appendChild(item) - else: - QMessageBox.warning(None, self.tr("Invalid service name"), - self.tr("Service '{0}' is unknown.").format(service)) - # Bases on the original code by: # Copyright (c) 2002-2007 Pascal Varet diff --git a/ui/newfwruledlg.ui b/ui/newfwruledlg.ui index 11da4ed..391e16c 100644 --- a/ui/newfwruledlg.ui +++ b/ui/newfwruledlg.ui @@ -31,10 +31,22 @@ 6 - - + + + + + 0 + 0 + + + + + 0 + 0 + + - Protocol + UDP @@ -45,13 +57,6 @@ - - - - true - - - @@ -59,6 +64,31 @@ + + + + + 0 + 0 + + + + + 71 + 0 + + + + + + + Any + + + true + + + @@ -66,6 +96,13 @@ + + + + true + + + @@ -85,44 +122,10 @@ - - - - - 0 - 0 - - - - - 0 - 0 - - + + - UDP - - - - - - - - 0 - 0 - - - - - 71 - 0 - - - - Any - - - true + Protocol diff --git a/ui/settingsdlg.ui b/ui/settingsdlg.ui index ee24036..1d711c1 100644 --- a/ui/settingsdlg.ui +++ b/ui/settingsdlg.ui @@ -29,7 +29,7 @@ - 1 + 2 @@ -655,48 +655,101 @@ Firewall rules - - - - Allow network access except... - - - - - - - - 323 - 0 - - - - Allow ICMP traffic - - - true - - - - - - Deny network access except... - - - - - - - Allow DNS queries - - - true - - + + + + + Allow all outgoing Internet connections + + + + + + + Limit outgoing Internet connections to ... + + + + - + + + Qt::Horizontal + + + + + + + NOTE: To block all network access, set Networking to (none) on the Basic settings tab. This tab provides a very simplified firewall configuration. All DNS requests and ICMP (pings) will be allowed. For more granular control, use the command line tool qvm-firewall. + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + true + + + + + + + List of allowed (whitelisted) addresses: + + + + + + + true + + + + 0 + + + 0 + + + 0 + + + + + Allow full access for + + + + + + + min + + + 5 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + QLayout::SetMaximumSize @@ -805,48 +858,57 @@ - - + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 139 + 142 + 142 + + + + + + + + + 75 + true + true + + - Allow connections to Updates Proxy + Firewall has been modified manually - please use qvm-firewall for any further configuration. - - - - true - - - - 0 - - - 0 - - - 0 - - - - - Allow full access for - - - - - - - min - - - 5 - - - - - - @@ -1035,11 +1097,6 @@ vcpus include_in_balancing kernel - policyAllowRadioButton - policyDenyRadioButton - icmpCheckBox - dnsCheckBox - yumproxyCheckBox newRuleButton rulesTreeView editRuleButton From 9ebd602407b72a615294b0169101723e2afe25fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Mon, 16 Oct 2017 03:10:19 +0200 Subject: [PATCH 02/70] One more place to use vm.klass instead of type(vm) --- qubesmanager/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index c184762..5bf7d64 100755 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -281,7 +281,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): self.autostart_vm.setVisible(False) #type - self.type_label.setText(type(self.vm).__name__) + self.type_label.setText(self.vm.klass) #installed by rpm self.rpm_label.setText('Yes' if self.vm.installed_by_rpm else 'No') From 43576d786cd56c5a1eacb4815e3bb345440b7a08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Mon, 16 Oct 2017 03:10:47 +0200 Subject: [PATCH 03/70] Fix setting include_in_balancing checkbox Generally vm.features values are strings, need to cast them to boot to use in such context. Additionally fix typo in feature name ('services' -> 'service'). Fixes QubesOS/qubes-issues#2947 Fixes QubesOS/qubes-issues#3104 --- qubesmanager/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index 5bf7d64..7b877b0 100755 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -452,7 +452,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): self.include_in_balancing.setEnabled(True) self.include_in_balancing.setChecked( - self.vm.features.get('services.meminfo-writer', True)) + bool(self.vm.features.get('service.meminfo-writer', True))) self.max_mem_size.setEnabled(self.include_in_balancing.isChecked()) #in case VM is HVM From b6738e63e34098a6e8e33618932827c958124a38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Mon, 16 Oct 2017 04:39:17 +0200 Subject: [PATCH 04/70] version 4.0.7 --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index d13e837..43beb40 100644 --- a/version +++ b/version @@ -1 +1 @@ -4.0.6 +4.0.7 From f0e363a054930eb082da1a1f87b21329efc82db4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Sat, 21 Oct 2017 01:48:56 +0200 Subject: [PATCH 05/70] version 4.0.8 --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index 43beb40..a2cec7a 100644 --- a/version +++ b/version @@ -1 +1 @@ -4.0.7 +4.0.8 From 458968d37fbd8dbb4add865ec85f7e87090b643a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Fri, 3 Nov 2017 22:20:34 +0100 Subject: [PATCH 06/70] Added Remove VM button Button added on the basic tab of VM Settings. Button active only when VM is shutdown; furthermore, requires the user to enter full VM name to confirm. --- qubesmanager/settings.py | 47 ++++++++++++++++++++++++++++++++++++++++ ui/settingsdlg.ui | 19 +++++++++++++--- 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index b42fdd8..7ed89a0 100755 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -86,6 +86,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): ###### basic tab self.__init_basic_tab__() self.rename_vm_button.clicked.connect(self.rename_vm) + self.delete_vm_button.clicked.connect(self.remove_vm) ###### advanced tab self.__init_advanced_tab__() @@ -237,6 +238,11 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): self.vmname.setValidator(QRegExpValidator(QRegExp("[a-zA-Z0-9-]*", Qt.CaseInsensitive), None)) self.vmname.setEnabled(False) self.rename_vm_button.setEnabled(not self.vm.is_running()) + self.delete_vm_button.setEnabled(not self.vm.is_running()) + + if self.vm.is_running(): + self.delete_vm_button.setText(self.tr('Delete VM ' + '(cannot delete a running VM)')) if self.vm.qid == 0: self.vmlabel.setVisible(False) @@ -435,6 +441,47 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): self.done(0) + def _remove_vm(self, t_monitor): + try: + del self.vm.app.domains[self.vm.name] + + except Exception as ex: + t_monitor.set_error_msg(str(ex)) + + t_monitor.set_finished() + + def remove_vm(self): + + answer, ok = QInputDialog.getText(self, self.tr('Delete VM') + , self.tr('Are you absolutely sure you want to delete this VM? ' + '
All VM settings and data will be irrevocably' + ' deleted.
If you are sure, please enter this ' + 'VM\'s name below.')) + + + if ok and answer == self.vm.name: + t_monitor = thread_monitor.ThreadMonitor() + thread = threading.Thread(target=self._remove_vm, + args=(t_monitor,)) + thread.daemon = True + thread.start() + + while not t_monitor.is_finished(): + self.qapp.processEvents() + time.sleep(0.1) + + if not t_monitor.success: + QMessageBox.warning(None, + self.tr("Error deleting the VM!"), + self.tr("ERROR: {}").format( + t_monitor.error_msg)) + + self.done(0) + + else: + QMessageBox.warning(None, self.tr("Removal cancelled") + , self.tr("The VM will not be removed.")) + ######### advanced tab def __init_advanced_tab__(self): diff --git a/ui/settingsdlg.ui b/ui/settingsdlg.ui index 1d711c1..38156c6 100644 --- a/ui/settingsdlg.ui +++ b/ui/settingsdlg.ui @@ -7,7 +7,7 @@ 0 0 773 - 573 + 581 @@ -29,7 +29,7 @@ - 2 + 0 @@ -170,7 +170,7 @@
- + Qt::Vertical @@ -373,6 +373,19 @@ + + + + background-color: qlineargradient(spread:pad, x1:1, y1:1, x2:1, y2:0, stop:0 rgba(255, 179, 179, 255), stop:1 rgba(255, 108, 108, 255)); +border-color: rgb(170, 0, 0); +border-style: solid; +border-width: 1px; + + + Delete VM + + + From b7e4e55d0581ce231b49482f264118555bf14db4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Fri, 3 Nov 2017 23:50:04 +0100 Subject: [PATCH 07/70] Added Clone VM button Button added on the basic tab of VM Settings. Also some refactoring to clean up the rename/clone/delete buttons. --- qubesmanager/settings.py | 76 +++++++++++++++++++++++----------------- ui/settingsdlg.ui | 7 ++++ 2 files changed, 50 insertions(+), 33 deletions(-) diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index 7ed89a0..2328d93 100755 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -87,6 +87,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): self.__init_basic_tab__() self.rename_vm_button.clicked.connect(self.rename_vm) self.delete_vm_button.clicked.connect(self.remove_vm) + self.clone_vm_button.clicked.connect(self.clone_vm) ###### advanced tab self.__init_advanced_tab__() @@ -407,6 +408,23 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): "allowed value.")) self.init_mem.setValue(self.max_mem_size.value() / 10) + def _run_in_thread(self, func, *args): + t_monitor = thread_monitor.ThreadMonitor() + thread = threading.Thread(target=func, args=(t_monitor, *args,)) + thread.daemon = True + thread.start() + + while not t_monitor.is_finished(): + self.qapp.processEvents() + time.sleep(0.1) + + if not t_monitor.success: + QMessageBox.warning(None, + self.tr("Error!"), + self.tr("ERROR: {}").format( + t_monitor.error_msg)) + + def _rename_vm(self, t_monitor, name): try: self.vm.app.clone_vm(self.vm, name) @@ -420,25 +438,13 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): def rename_vm(self): - new_vm_name, ok = QInputDialog.getText(self, self.tr('Rename VM'), self.tr('New name: (WARNING: all other changes will be discarded)')) + new_vm_name, ok = QInputDialog.getText(self + , self.tr('Rename VM') + , self.tr('New name: (WARNING: ' + 'all other changes will be discarded)')) if ok: - - t_monitor = thread_monitor.ThreadMonitor() - thread = threading.Thread(target=self._rename_vm, args=(t_monitor, new_vm_name,)) - thread.daemon = True - thread.start() - - while not t_monitor.is_finished(): - self.qapp.processEvents() - time.sleep (0.1) - - if not t_monitor.success: - QMessageBox.warning(None, - self.tr("Error renaming the VM!"), - self.tr("ERROR: {}").format( - t_monitor.error_msg)) - + self._run_in_thread(self._rename_vm, new_vm_name) self.done(0) def _remove_vm(self, t_monitor): @@ -460,28 +466,32 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): if ok and answer == self.vm.name: - t_monitor = thread_monitor.ThreadMonitor() - thread = threading.Thread(target=self._remove_vm, - args=(t_monitor,)) - thread.daemon = True - thread.start() - - while not t_monitor.is_finished(): - self.qapp.processEvents() - time.sleep(0.1) - - if not t_monitor.success: - QMessageBox.warning(None, - self.tr("Error deleting the VM!"), - self.tr("ERROR: {}").format( - t_monitor.error_msg)) - + self._run_in_thread(self._remove_vm) self.done(0) else: QMessageBox.warning(None, self.tr("Removal cancelled") , self.tr("The VM will not be removed.")) + def _clone_vm(self, t_monitor, name): + try: + self.vm.app.clone_vm(self.vm, name) + + except Exception as ex: + t_monitor.set_error_msg(str(ex)) + + t_monitor.set_finished() + + def clone_vm(self): + + cloned_vm_name, ok = QInputDialog.getText(self, self.tr('Clone VM') + , self.tr('Name for the cloned VM:')) + + if ok: + self._run_in_thread(self._clone_vm, cloned_vm_name) + QMessageBox.warning(None, self.tr("Success") + , self.tr("The VM was cloned successfully.")) + ######### advanced tab def __init_advanced_tab__(self): diff --git a/ui/settingsdlg.ui b/ui/settingsdlg.ui index 38156c6..1a0eca8 100644 --- a/ui/settingsdlg.ui +++ b/ui/settingsdlg.ui @@ -386,6 +386,13 @@ border-width: 1px; + + + + Clone VM + + + From efc49f3b7665e5660b65759c44754d96fb17379d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Sun, 5 Nov 2017 13:54:05 +0100 Subject: [PATCH 08/70] Fixed travis.yml So that it listed qubes4.0 and fedora 25 instead of the old values. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 66bde29..5e373ba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,4 +4,4 @@ language: generic install: git clone https://github.com/QubesOS/qubes-builder ~/qubes-builder script: ~/qubes-builder/scripts/travis-build env: - - DIST_DOM0=fc23 USE_QUBES_REPO_VERSION=3.2 USE_QUBES_REPO_TESTING=1 + - DIST_DOM0=fc25 USE_QUBES_REPO_VERSION=4.0 USE_QUBES_REPO_TESTING=1 From ef86e33deeb13d891f3f68318a958555398129b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Mon, 6 Nov 2017 19:45:36 +0100 Subject: [PATCH 09/70] Style fixes Changes requested by @marmarek --- qubesmanager/settings.py | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index 2328d93..b30b8fc 100755 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -438,9 +438,9 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): def rename_vm(self): - new_vm_name, ok = QInputDialog.getText(self - , self.tr('Rename VM') - , self.tr('New name: (WARNING: ' + new_vm_name, ok = QInputDialog.getText(self, + self.tr('Rename VM'), + self.tr('New name: (WARNING: ' 'all other changes will be discarded)')) if ok: @@ -458,10 +458,12 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): def remove_vm(self): - answer, ok = QInputDialog.getText(self, self.tr('Delete VM') - , self.tr('Are you absolutely sure you want to delete this VM? ' - '
All VM settings and data will be irrevocably' - ' deleted.
If you are sure, please enter this ' + answer, ok = QInputDialog.getText( + self, + self.tr('Delete VM'), + self.tr('Are you absolutely sure you want to delete this VM? ' + '
All VM settings and data will be irrevocably' + ' deleted.
If you are sure, please enter this ' 'VM\'s name below.')) @@ -469,9 +471,11 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): self._run_in_thread(self._remove_vm) self.done(0) - else: - QMessageBox.warning(None, self.tr("Removal cancelled") - , self.tr("The VM will not be removed.")) + elif ok: + QMessageBox.warning( + None, + self.tr("Removal cancelled"), + self.tr("The VM will not be removed.")) def _clone_vm(self, t_monitor, name): try: @@ -484,13 +488,17 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): def clone_vm(self): - cloned_vm_name, ok = QInputDialog.getText(self, self.tr('Clone VM') - , self.tr('Name for the cloned VM:')) + cloned_vm_name, ok = QInputDialog.getText( + self, + self.tr('Clone VM'), + self.tr('Name for the cloned VM:')) if ok: self._run_in_thread(self._clone_vm, cloned_vm_name) - QMessageBox.warning(None, self.tr("Success") - , self.tr("The VM was cloned successfully.")) + QMessageBox.warning( + None, + self.tr("Success"), + self.tr("The VM was cloned successfully.")) ######### advanced tab From 41b602ec48e5dbb4e87631fd991ae45393e08a86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Mon, 6 Nov 2017 20:36:09 +0100 Subject: [PATCH 10/70] Initial setup Initialize .pylintrc, put ignore in used files' headers. --- .pylintrc | 196 +++++++++++++++++++++++++++++++ qubesmanager/about.py | 1 + qubesmanager/backup.py | 1 + qubesmanager/backup_utils.py | 1 + qubesmanager/block.py | 1 + qubesmanager/clipboard.py | 1 + qubesmanager/informationnotes.py | 1 + qubesmanager/log_dialog.py | 1 + qubesmanager/releasenotes.py | 1 + qubesmanager/restore.py | 1 + qubesmanager/settings.py | 124 +++++++++---------- qubesmanager/table_widgets.py | 1 + 12 files changed, 268 insertions(+), 62 deletions(-) create mode 100644 .pylintrc diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..d302bd9 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,196 @@ +[MASTER] +persistent=no +ignore=tests + +[MESSAGES CONTROL] +# abstract-class-little-used: see http://www.logilab.org/ticket/111138 +# deprecated-method: +# enable again after disabling py-3.4.3 asyncio.ensure_future compat hack +disable= + abstract-class-little-used, + bad-continuation, + cyclic-import, + deprecated-method, + duplicate-code, + file-ignored, + fixme, + locally-disabled, + locally-enabled, + logging-format-interpolation, + missing-docstring, + star-args, + wrong-import-order + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html +output-format=colorized + +#files-output=no +reports=yes + +[TYPECHECK] +#ignored-classes= + +ignore-mixin-members=yes +generated-members= + iter_entry_points, + Element,ElementTree,QName,SubElement,fromstring,parse,tostring, + +[BASIC] + +# List of builtins function names that should not be used, separated by a comma +bad-functions= + +# Regular expression which should only match correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression which should only match correct module level names +const-rgx=(([A-Za-z_][A-Za-z0-9_]*)|(__.*__))$ + +# Regular expression which should only match correct class names +class-rgx=([A-Z_][a-zA-Z0-9]+|TC_\d\d_[a-zA-Z0-9_]+)$ + +# Regular expression which should only match correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct instance attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct list comprehension / +# generator expression variable names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Good variable names which should always be accepted, separated by a comma +good-names=e,i,j,k,m,p,v,ex,Run,_,log,vm + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Regular expression which should only match functions or classes name which do +# not require a docstring +no-docstring-rgx=__.*__ + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,FIX,XXX,TODO + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=80 + +# Maximum number of lines in a module +max-module-lines=3000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the beginning of the name of dummy variables +# (i.e. not used). +dummy-variables-rgx=_|dummy + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=35 + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of locals for function / method body +# Let's have max-args + 5 +max-locals=40 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +# 4x the default value +max-branches=48 + +# Maximum number of statements in function / method body +# Double default +max-statements=100 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=15 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=100 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception,EnvironmentError + +# vim: ft=conf diff --git a/qubesmanager/about.py b/qubesmanager/about.py index fb06102..280f820 100644 --- a/qubesmanager/about.py +++ b/qubesmanager/about.py @@ -1,5 +1,6 @@ #!/usr/bin/python2 # coding=utf-8 +# pylint: skip-file # # The Qubes OS Project, http://www.qubes-os.org # diff --git a/qubesmanager/backup.py b/qubesmanager/backup.py index bde407b..91821da 100644 --- a/qubesmanager/backup.py +++ b/qubesmanager/backup.py @@ -1,4 +1,5 @@ #!/usr/bin/python2 +# pylint: skip-file # # The Qubes OS Project, http://www.qubes-os.org # diff --git a/qubesmanager/backup_utils.py b/qubesmanager/backup_utils.py index 38b5931..aad4026 100644 --- a/qubesmanager/backup_utils.py +++ b/qubesmanager/backup_utils.py @@ -1,4 +1,5 @@ #!/usr/bin/python2 +# pylint: skip-file # # The Qubes OS Project, http://www.qubes-os.org # diff --git a/qubesmanager/block.py b/qubesmanager/block.py index a826a1e..e673123 100644 --- a/qubesmanager/block.py +++ b/qubesmanager/block.py @@ -1,5 +1,6 @@ #!/usr/bin/python2 # -*- coding: utf8 -*- +# pylint: skip-file # # The Qubes OS Project, http://www.qubes-os.org # diff --git a/qubesmanager/clipboard.py b/qubesmanager/clipboard.py index 00fe9f7..b0a4107 100644 --- a/qubesmanager/clipboard.py +++ b/qubesmanager/clipboard.py @@ -1,4 +1,5 @@ #!/usr/bin/python2 +# pylint: skip-file # # The Qubes OS Project, http://www.qubes-os.org # diff --git a/qubesmanager/informationnotes.py b/qubesmanager/informationnotes.py index b6aa946..23cb152 100644 --- a/qubesmanager/informationnotes.py +++ b/qubesmanager/informationnotes.py @@ -1,5 +1,6 @@ #!/usr/bin/python2 # coding=utf-8 +# pylint: skip-file # # The Qubes OS Project, http://www.qubes-os.org # diff --git a/qubesmanager/log_dialog.py b/qubesmanager/log_dialog.py index 6e7ced9..09d8f68 100644 --- a/qubesmanager/log_dialog.py +++ b/qubesmanager/log_dialog.py @@ -1,4 +1,5 @@ #!/usr/bin/python2 +# pylint: skip-file # # The Qubes OS Project, http://www.qubes-os.org # diff --git a/qubesmanager/releasenotes.py b/qubesmanager/releasenotes.py index 53b8752..c234a53 100644 --- a/qubesmanager/releasenotes.py +++ b/qubesmanager/releasenotes.py @@ -1,5 +1,6 @@ #!/usr/bin/python2 # coding=utf-8 +# pylint: skip-file # # The Qubes OS Project, http://www.qubes-os.org # diff --git a/qubesmanager/restore.py b/qubesmanager/restore.py index 552e3eb..c9e211b 100644 --- a/qubesmanager/restore.py +++ b/qubesmanager/restore.py @@ -1,4 +1,5 @@ #!/usr/bin/python2 +# pylint: skip-file # # The Qubes OS Project, http://www.qubes-os.org # diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index b30b8fc..496775b 100755 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -43,12 +43,12 @@ from . import thread_monitor from .appmenu_select import AppmenuSelectManager from .backup_utils import get_path_for_vm -from .firewall import * +from . import firewall -from .ui_settingsdlg import * +from . import ui_settingsdlg from .bootfromdevice import main as bootfromdevice -class VMSettingsWindow(Ui_SettingsDialog, QDialog): +class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, firewall.QDialog): tabs_indices = collections.OrderedDict(( ('basic', 0), ('advanced', 1), @@ -75,8 +75,8 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): 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.connect(self.buttonBox, firewall.SIGNAL("accepted()"), self.save_and_apply) + self.connect(self.buttonBox, firewall.SIGNAL("rejected()"), self.reject) self.tabWidget.currentChanged.connect(self.current_tab_changed) @@ -92,18 +92,18 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): ###### advanced tab self.__init_advanced_tab__() self.include_in_balancing.stateChanged.connect(self.include_in_balancing_state_changed) - self.connect(self.init_mem, SIGNAL("editingFinished()"), self.check_mem_changes) - self.connect(self.max_mem_size, SIGNAL("editingFinished()"), self.check_mem_changes) + self.connect(self.init_mem, firewall.SIGNAL("editingFinished()"), self.check_mem_changes) + self.connect(self.max_mem_size, firewall.SIGNAL("editingFinished()"), self.check_mem_changes) self.bootFromDeviceButton.clicked.connect(self.boot_from_cdrom_button_pressed) ###### firewall tab if self.tabWidget.isTabEnabled(self.tabs_indices['firewall']): - model = QubesFirewallRulesModel() + model = firewall.QubesFirewallRulesModel() try: model.set_vm(vm) self.set_fw_model(model) self.firewallModifiedOutsidelabel.setVisible(False) - except FirewallModifiedOutsideError as ex: + except firewall.FirewallModifiedOutsideError as ex: self.disable_all_fw_conf() self.newRuleButton.clicked.connect(self.new_rule_button_pressed) @@ -114,7 +114,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): ####### devices tab self.__init_devices_tab__() - self.connect(self.dev_list, SIGNAL("selected_changed()"), self.devices_selection_changed) + self.connect(self.dev_list, firewall.SIGNAL("selected_changed()"), self.devices_selection_changed) ####### services tab self.__init_services_tab__() @@ -141,7 +141,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): thread.daemon = True thread.start() - progress = QProgressDialog( + progress = firewall.QProgressDialog( self.tr("Applying settings to {0}...").format(self.vm.name), "", 0, 0) progress.setCancelButton(None) progress.setModal(True) @@ -154,9 +154,9 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): progress.hide() if not t_monitor.success: - QMessageBox.warning(None, - self.tr("Error while changing settings for {0}!").format(self.vm.name), - self.tr("ERROR: {0}").format(t_monitor.error_msg)) + firewall.QMessageBox.warning(None, + self.tr("Error while changing settings for {0}!").format(self.vm.name), + self.tr("ERROR: {0}").format(t_monitor.error_msg)) self.done(0) @@ -210,9 +210,9 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): netvm = self.vm.netvm if netvm is not None and \ not netvm.features.check_with_template('qubes-firewall', False): - QMessageBox.warning(None, - self.tr("VM configuration problem!"), - self.tr("The '{vm}' AppVM is network connected to " + firewall.QMessageBox.warning(None, + self.tr("VM configuration problem!"), + self.tr("The '{vm}' AppVM is network connected to " "'{netvm}', which does not support firewall!
" "You may edit the '{vm}' VM firewall rules, but these " "will not take any effect until you connect it to " @@ -236,7 +236,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): def __init_basic_tab__(self): self.vmname.setText(self.vm.name) - self.vmname.setValidator(QRegExpValidator(QRegExp("[a-zA-Z0-9-]*", Qt.CaseInsensitive), None)) + self.vmname.setValidator(firewall.QRegExpValidator(firewall.QRegExp("[a-zA-Z0-9-]*", firewall.Qt.CaseInsensitive), None)) self.vmname.setEnabled(False) self.rename_vm_button.setEnabled(not self.vm.is_running()) self.delete_vm_button.setEnabled(not self.vm.is_running()) @@ -394,16 +394,16 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): def check_mem_changes(self): if self.max_mem_size.value() < self.init_mem.value(): - QMessageBox.warning(None, - self.tr("Warning!"), - self.tr("Max memory can not be less than initial memory.
" + firewall.QMessageBox.warning(None, + self.tr("Warning!"), + self.tr("Max memory can not be less than initial memory.
" "Setting max memory to equal initial memory.")) self.max_mem_size.setValue(self.init_mem.value()) # Linux specific limit: init memory must not be below max_mem_size/10.79 in order to allow scaling up to max_mem_size (or else "add_memory() failed: -17" problem) if self.init_mem.value() * 10 < self.max_mem_size.value(): - QMessageBox.warning(None, - self.tr("Warning!"), - self.tr("Initial memory can not be less than one tenth " + firewall.QMessageBox.warning(None, + self.tr("Warning!"), + self.tr("Initial memory can not be less than one tenth " "Max memory.
Setting initial memory to the minimum " "allowed value.")) self.init_mem.setValue(self.max_mem_size.value() / 10) @@ -419,9 +419,9 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): time.sleep(0.1) if not t_monitor.success: - QMessageBox.warning(None, - self.tr("Error!"), - self.tr("ERROR: {}").format( + firewall.QMessageBox.warning(None, + self.tr("Error!"), + self.tr("ERROR: {}").format( t_monitor.error_msg)) @@ -438,9 +438,9 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): def rename_vm(self): - new_vm_name, ok = QInputDialog.getText(self, - self.tr('Rename VM'), - self.tr('New name: (WARNING: ' + new_vm_name, ok = firewall.QInputDialog.getText(self, + self.tr('Rename VM'), + self.tr('New name: (WARNING: ' 'all other changes will be discarded)')) if ok: @@ -458,7 +458,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): def remove_vm(self): - answer, ok = QInputDialog.getText( + answer, ok = firewall.QInputDialog.getText( self, self.tr('Delete VM'), self.tr('Are you absolutely sure you want to delete this VM? ' @@ -472,7 +472,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): self.done(0) elif ok: - QMessageBox.warning( + firewall.QMessageBox.warning( None, self.tr("Removal cancelled"), self.tr("The VM will not be removed.")) @@ -488,14 +488,14 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): def clone_vm(self): - cloned_vm_name, ok = QInputDialog.getText( + cloned_vm_name, ok = firewall.QInputDialog.getText( self, self.tr('Clone VM'), self.tr('Name for the cloned VM:')) if ok: self._run_in_thread(self._clone_vm, cloned_vm_name) - QMessageBox.warning( + firewall.QMessageBox.warning( None, self.tr("Success"), self.tr("The VM was cloned successfully.")) @@ -602,7 +602,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): for dev in lspci.splitlines(): devs.append((dev.rstrip(), dev.split(' ')[0])) - class DevListWidgetItem(QListWidgetItem): + class DevListWidgetItem(firewall.QListWidgetItem): def __init__(self, name, ident, parent = None): super(DevListWidgetItem, self).__init__(name, parent) self.ident = ident @@ -645,7 +645,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): for i in range(self.dev_list.selected_list.count())] for ident in new: if ident not in old: - ass = qubesadmin.devices.DeviceAssignment( + ass = firewall.qubesadmin.devices.DeviceAssignment( self.vm.app.domains['dom0'], ident.replace(':', '_'), persistent=True) @@ -671,7 +671,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): break if self.dev_list.selected_list.count() > 0: - if state == QtCore.Qt.Checked: + if state == ui_settingsdlg.QtCore.Qt.Checked: self.dmm_warning_adv.show() self.dmm_warning_dev.show() else: @@ -736,23 +736,23 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): if not feature.startswith('service.'): continue service = feature[len('service.'):] - item = QListWidgetItem(service) - item.setCheckState(QtCore.Qt.Checked - if self.vm.features[feature] else QtCore.Qt.Unchecked) + item = firewall.QListWidgetItem(service) + item.setCheckState(ui_settingsdlg.QtCore.Qt.Checked + if self.vm.features[feature] else ui_settingsdlg.QtCore.Qt.Unchecked) self.services_list.addItem(item) self.new_srv_dict[service] = self.vm.features[feature] - self.connect(self.services_list, SIGNAL("itemClicked(QListWidgetItem *)"), self.services_item_clicked) + self.connect(self.services_list, firewall.SIGNAL("itemClicked(QListWidgetItem *)"), self.services_item_clicked) def __add_service__(self): srv = str(self.service_line_edit.text()).strip() if srv != "": if srv in self.new_srv_dict: - QMessageBox.information(None, '', - self.tr('Service already on the list!')) + firewall.QMessageBox.information(None, '', + self.tr('Service already on the list!')) else: - item = QListWidgetItem(srv) - item.setCheckState(QtCore.Qt.Checked) + item = firewall.QListWidgetItem(srv) + item.setCheckState(ui_settingsdlg.QtCore.Qt.Checked) self.services_list.addItem(item) self.new_srv_dict[srv] = True @@ -762,9 +762,9 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): if not item: return if str(item.text()) == 'meminfo-writer': - QMessageBox.information(None, - self.tr('Service can not be removed'), - self.tr('Service meminfo-writer can not be removed from the list.')) + firewall.QMessageBox.information(None, + self.tr('Service can not be removed'), + self.tr('Service meminfo-writer can not be removed from the list.')) return row = self.services_list.currentRow() @@ -774,10 +774,10 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): def services_item_clicked(self, item): if str(item.text()) == 'meminfo-writer': - if item.checkState() == QtCore.Qt.Checked: + if item.checkState() == ui_settingsdlg.QtCore.Qt.Checked: if not self.include_in_balancing.isChecked(): self.include_in_balancing.setChecked(True) - elif item.checkState() == QtCore.Qt.Unchecked: + elif item.checkState() == ui_settingsdlg.QtCore.Qt.Unchecked: if self.include_in_balancing.isChecked(): self.include_in_balancing.setChecked(False) @@ -788,7 +788,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): try: 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) + self.new_srv_dict[str(item.text())] = (item.checkState() == ui_settingsdlg.QtCore.Qt.Checked) balancing_was_checked = self.vm.features.get('service.meminfo-writer', True) balancing_is_checked = self.include_in_balancing.isChecked() @@ -821,14 +821,14 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): 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.rulesTreeView.header().setResizeMode(firewall.QHeaderView.ResizeToContents) + self.rulesTreeView.header().setResizeMode(0, firewall.QHeaderView.Stretch) self.set_allow(model.allow) if model.tempFullAccessExpireTime: self.tempFullAccess.setChecked(True) self.tempFullAccessTime.setValue( (model.tempFullAccessExpireTime - - int(datetime.datetime.now().strftime("%s")))/60) + int(firewall.datetime.datetime.now().strftime("%s"))) / 60) def disable_all_fw_conf(self): self.firewallModifiedOutsidelabel.setVisible(True) @@ -857,7 +857,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): self.policyDenyRadioButton.isChecked()) def new_rule_button_pressed(self): - dialog = NewFwRuleDlg() + dialog = firewall.NewFwRuleDlg() self.fw_model.run_rule_dialog(dialog) def edit_rule_button_pressed(self): @@ -865,7 +865,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): selected = self.rulesTreeView.selectedIndexes() if len(selected) > 0: - dialog = NewFwRuleDlg() + dialog = firewall.NewFwRuleDlg() dialog.set_ok_enabled(True) row = self.rulesTreeView.selectedIndexes().pop().row() self.fw_model.populate_edit_dialog(dialog, row) @@ -883,7 +883,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): def handle_exception(exc_type, exc_value, exc_traceback): filename, line, dummy, dummy = traceback.extract_tb(exc_traceback).pop() - filename = os.path.basename(filename) + filename = firewall.os.path.basename(filename) error = "%s: %s" % (exc_type.__name__, exc_value) strace = "" @@ -896,9 +896,9 @@ def handle_exception(exc_type, exc_value, exc_traceback): strace += "line no.: %d\n" %line strace += "file: %s\n" %filename - msg_box = QMessageBox() + msg_box = firewall.QMessageBox() msg_box.setDetailedText(strace) - msg_box.setIcon(QMessageBox.Critical) + msg_box.setIcon(firewall.QMessageBox.Critical) msg_box.setWindowTitle("Houston, we have a problem...") msg_box.setText("Whoops. A critical error has occured. This is most likely a bug " "in Qubes Manager.

" @@ -909,7 +909,7 @@ def handle_exception(exc_type, exc_value, exc_traceback): msg_box.exec_() -parser = qubesadmin.tools.QubesArgumentParser(vmname_nargs=1) +parser = firewall.qubesadmin.tools.QubesArgumentParser(vmname_nargs=1) parser.add_argument('--tab', metavar='TAB', action='store', @@ -925,13 +925,13 @@ def main(args=None): args = parser.parse_args(args) vm = args.domains.pop() - qapp = QApplication(sys.argv) + qapp = firewall.QApplication(firewall.sys.argv) qapp.setOrganizationName('Invisible Things Lab') qapp.setOrganizationDomain("https://www.qubes-os.org/") qapp.setApplicationName("Qubes VM Settings") if not utils.is_debug(): - sys.excepthook = handle_exception + firewall.sys.excepthook = handle_exception settings_window = VMSettingsWindow(vm, qapp, args.tab) settings_window.show() diff --git a/qubesmanager/table_widgets.py b/qubesmanager/table_widgets.py index b5a662a..b90cfeb 100644 --- a/qubesmanager/table_widgets.py +++ b/qubesmanager/table_widgets.py @@ -1,5 +1,6 @@ #!/usr/bin/python2 # -*- coding: utf8 -*- +# pylint: skip-file # # The Qubes OS Project, http://www.qubes-os.org # From 3009610a6288ff1198a982abf522b4d937b3dd59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Mon, 6 Nov 2017 21:06:30 +0100 Subject: [PATCH 11/70] Fixed GPL license Replaced old, address-having, too-many-characters-in-a-line version with the new one with URL. --- qubesmanager/about.py | 5 ++--- qubesmanager/appmenu_select.py | 5 ++--- qubesmanager/backup.py | 5 ++--- qubesmanager/backup_utils.py | 5 ++--- qubesmanager/block.py | 5 ++--- qubesmanager/bootfromdevice.py | 5 ++--- qubesmanager/clipboard.py | 5 ++--- qubesmanager/create_new_vm.py | 5 ++--- qubesmanager/firewall.py | 5 ++--- qubesmanager/global_settings.py | 5 ++--- qubesmanager/informationnotes.py | 5 ++--- qubesmanager/log_dialog.py | 5 ++--- qubesmanager/releasenotes.py | 5 ++--- qubesmanager/restore.py | 5 ++--- qubesmanager/settings.py | 5 ++--- qubesmanager/table_widgets.py | 5 ++--- qubesmanager/thread_monitor.py | 5 ++--- 17 files changed, 34 insertions(+), 51 deletions(-) diff --git a/qubesmanager/about.py b/qubesmanager/about.py index 280f820..e28d920 100644 --- a/qubesmanager/about.py +++ b/qubesmanager/about.py @@ -17,9 +17,8 @@ # 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. +# You should have received a copy of the GNU Lesser General Public License along +# with this program; if not, see . # # from PyQt4.QtCore import SIGNAL, SLOT diff --git a/qubesmanager/appmenu_select.py b/qubesmanager/appmenu_select.py index c161f56..f130fdf 100755 --- a/qubesmanager/appmenu_select.py +++ b/qubesmanager/appmenu_select.py @@ -14,9 +14,8 @@ # 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. +# You should have received a copy of the GNU Lesser General Public License along +# with this program; if not, see . # # diff --git a/qubesmanager/backup.py b/qubesmanager/backup.py index 91821da..6d808fe 100644 --- a/qubesmanager/backup.py +++ b/qubesmanager/backup.py @@ -16,9 +16,8 @@ # 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. +# You should have received a copy of the GNU Lesser General Public License along +# with this program; if not, see . # # diff --git a/qubesmanager/backup_utils.py b/qubesmanager/backup_utils.py index aad4026..8a104e0 100644 --- a/qubesmanager/backup_utils.py +++ b/qubesmanager/backup_utils.py @@ -15,9 +15,8 @@ # 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. +# You should have received a copy of the GNU Lesser General Public License along +# with this program; if not, see . # # import re diff --git a/qubesmanager/block.py b/qubesmanager/block.py index e673123..ed4ff0a 100644 --- a/qubesmanager/block.py +++ b/qubesmanager/block.py @@ -16,9 +16,8 @@ # 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. +# You should have received a copy of the GNU Lesser General Public License along +# with this program; if not, see . import threading import time diff --git a/qubesmanager/bootfromdevice.py b/qubesmanager/bootfromdevice.py index d17e8fc..eb51e25 100644 --- a/qubesmanager/bootfromdevice.py +++ b/qubesmanager/bootfromdevice.py @@ -12,9 +12,8 @@ # 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. +# You should have received a copy of the GNU Lesser General Public License along +# with this program; if not, see . # # diff --git a/qubesmanager/clipboard.py b/qubesmanager/clipboard.py index b0a4107..f4c5785 100644 --- a/qubesmanager/clipboard.py +++ b/qubesmanager/clipboard.py @@ -17,9 +17,8 @@ # 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. +# You should have received a copy of the GNU Lesser General Public License along +# with this program; if not, see . # # diff --git a/qubesmanager/create_new_vm.py b/qubesmanager/create_new_vm.py index f75debc..5723a10 100644 --- a/qubesmanager/create_new_vm.py +++ b/qubesmanager/create_new_vm.py @@ -16,9 +16,8 @@ # 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. +# You should have received a copy of the GNU Lesser General Public License along +# with this program; if not, see . # # diff --git a/qubesmanager/firewall.py b/qubesmanager/firewall.py index fa61186..ef9ee7c 100644 --- a/qubesmanager/firewall.py +++ b/qubesmanager/firewall.py @@ -13,9 +13,8 @@ # 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. +# You should have received a copy of the GNU Lesser General Public License along +# with this program; if not, see . # # diff --git a/qubesmanager/global_settings.py b/qubesmanager/global_settings.py index eaf009f..e7412ac 100644 --- a/qubesmanager/global_settings.py +++ b/qubesmanager/global_settings.py @@ -15,9 +15,8 @@ # 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. +# You should have received a copy of the GNU Lesser General Public License along +# with this program; if not, see . # # diff --git a/qubesmanager/informationnotes.py b/qubesmanager/informationnotes.py index 23cb152..d0b82f1 100644 --- a/qubesmanager/informationnotes.py +++ b/qubesmanager/informationnotes.py @@ -17,9 +17,8 @@ # 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. +# You should have received a copy of the GNU Lesser General Public License along +# with this program; if not, see . # # from PyQt4.QtCore import SIGNAL diff --git a/qubesmanager/log_dialog.py b/qubesmanager/log_dialog.py index 09d8f68..f38182f 100644 --- a/qubesmanager/log_dialog.py +++ b/qubesmanager/log_dialog.py @@ -16,9 +16,8 @@ # 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. +# You should have received a copy of the GNU Lesser General Public License along +# with this program; if not, see . # # diff --git a/qubesmanager/releasenotes.py b/qubesmanager/releasenotes.py index c234a53..da3f907 100644 --- a/qubesmanager/releasenotes.py +++ b/qubesmanager/releasenotes.py @@ -17,9 +17,8 @@ # 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. +# You should have received a copy of the GNU Lesser General Public License along +# with this program; if not, see . # # from PyQt4.QtCore import SIGNAL diff --git a/qubesmanager/restore.py b/qubesmanager/restore.py index c9e211b..c1609c5 100644 --- a/qubesmanager/restore.py +++ b/qubesmanager/restore.py @@ -16,9 +16,8 @@ # 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. +# You should have received a copy of the GNU Lesser General Public License along +# with this program; if not, see . # # diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index 496775b..1c37a92 100755 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -17,9 +17,8 @@ # 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. +# You should have received a copy of the GNU Lesser General Public License along +# with this program; if not, see . # # diff --git a/qubesmanager/table_widgets.py b/qubesmanager/table_widgets.py index b90cfeb..e91806d 100644 --- a/qubesmanager/table_widgets.py +++ b/qubesmanager/table_widgets.py @@ -16,9 +16,8 @@ # 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. +# You should have received a copy of the GNU Lesser General Public License along +# with this program; if not, see . import os diff --git a/qubesmanager/thread_monitor.py b/qubesmanager/thread_monitor.py index 07fdca3..8389a7a 100644 --- a/qubesmanager/thread_monitor.py +++ b/qubesmanager/thread_monitor.py @@ -14,9 +14,8 @@ # 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. +# You should have received a copy of the GNU Lesser General Public License along +# with this program; if not, see . # # From 77038af71904ffe54b2351b41900bee75edafa04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Mon, 6 Nov 2017 21:07:32 +0100 Subject: [PATCH 12/70] List of files to ignore Added list of files to ignore to .pylintrc (all automatically generated files: ui_* and resources_rc.py) --- .pylintrc | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index d302bd9..0b4dc29 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,6 +1,19 @@ [MASTER] persistent=no -ignore=tests +ignore=tests, + ui_about.py, + ui_backupdlg.py, + ui_bootfromdevice.py, + ui_globalsettingsdlg.py, + ui_informationnotes.py, + ui_logdlg.py, + ui_multiselectwidget.py, + ui_newappvmdlg.py, + ui_newfwruledlg.py, + ui_releasenotes.py, + ui_restoredlg.py, + ui_settingsdlg.py, + resources_rc.py [MESSAGES CONTROL] # abstract-class-little-used: see http://www.logilab.org/ticket/111138 From eaccc323fb9f962e7fe08e4e519ef2c98f0282d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Mon, 6 Nov 2017 21:29:06 +0100 Subject: [PATCH 13/70] Fixed unused imports Fixed all unused imports errors. --- qubesmanager/appmenu_select.py | 9 --------- qubesmanager/create_new_vm.py | 3 --- qubesmanager/firewall.py | 4 ---- qubesmanager/multiselectwidget.py | 1 - qubesmanager/settings.py | 8 -------- qubesmanager/utils.py | 1 - 6 files changed, 26 deletions(-) diff --git a/qubesmanager/appmenu_select.py b/qubesmanager/appmenu_select.py index f130fdf..ad96379 100755 --- a/qubesmanager/appmenu_select.py +++ b/qubesmanager/appmenu_select.py @@ -19,18 +19,9 @@ # # -import os import subprocess -import sys -import time -from operator import itemgetter - -from PyQt4.QtCore import * from PyQt4.QtGui import * -from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent - -import qubesmanager.resources_rc # TODO description in tooltip # TODO icon diff --git a/qubesmanager/create_new_vm.py b/qubesmanager/create_new_vm.py index 5723a10..06076c9 100644 --- a/qubesmanager/create_new_vm.py +++ b/qubesmanager/create_new_vm.py @@ -21,7 +21,6 @@ # # -import os import sys import threading import time @@ -33,8 +32,6 @@ from PyQt4.QtGui import * import qubesadmin import qubesadmin.tools -import qubesmanager.resources_rc - from . import utils from .ui_newappvmdlg import Ui_NewVMDlg diff --git a/qubesmanager/firewall.py b/qubesmanager/firewall.py index ef9ee7c..3467005 100644 --- a/qubesmanager/firewall.py +++ b/qubesmanager/firewall.py @@ -19,11 +19,7 @@ # import datetime -import ipaddress -import os import re -import sys -import xml.etree.ElementTree from PyQt4.QtCore import * from PyQt4.QtGui import * diff --git a/qubesmanager/multiselectwidget.py b/qubesmanager/multiselectwidget.py index a1ab7c2..eb1b145 100644 --- a/qubesmanager/multiselectwidget.py +++ b/qubesmanager/multiselectwidget.py @@ -1,4 +1,3 @@ -import sys from PyQt4.QtCore import * from PyQt4.QtGui import * from .ui_multiselectwidget import * diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index 1c37a92..fcbb5c9 100755 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -24,28 +24,20 @@ import collections -import copy -import os -import os.path import subprocess -import sys import threading import time import traceback -import qubesadmin -import qubesadmin.tools from . import utils from . import multiselectwidget from . import thread_monitor from .appmenu_select import AppmenuSelectManager -from .backup_utils import get_path_for_vm from . import firewall from . import ui_settingsdlg -from .bootfromdevice import main as bootfromdevice class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, firewall.QDialog): tabs_indices = collections.OrderedDict(( diff --git a/qubesmanager/utils.py b/qubesmanager/utils.py index cca3647..b67ddf7 100644 --- a/qubesmanager/utils.py +++ b/qubesmanager/utils.py @@ -20,7 +20,6 @@ # along with this program. If not, see . # -import functools import os import re import qubesadmin From c252e9ec3ce683454180f27f69fea86ea2156239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Mon, 6 Nov 2017 22:46:35 +0100 Subject: [PATCH 14/70] Fixed wildcard imports Fixed wildcard imports and any further problems resulting from them. --- qubesmanager/appmenu_select.py | 4 +- qubesmanager/bootfromdevice.py | 18 ++++---- qubesmanager/create_new_vm.py | 19 ++++---- qubesmanager/firewall.py | 72 +++++++++++++++---------------- qubesmanager/global_settings.py | 25 ++++++----- qubesmanager/multiselectwidget.py | 23 +++++----- qubesmanager/settings.py | 69 +++++++++++++++-------------- qubesmanager/thread_monitor.py | 4 +- 8 files changed, 117 insertions(+), 117 deletions(-) diff --git a/qubesmanager/appmenu_select.py b/qubesmanager/appmenu_select.py index ad96379..942234b 100755 --- a/qubesmanager/appmenu_select.py +++ b/qubesmanager/appmenu_select.py @@ -21,11 +21,11 @@ import subprocess -from PyQt4.QtGui import * +import PyQt4.QtGui # TODO description in tooltip # TODO icon -class AppListWidgetItem(QListWidgetItem): +class AppListWidgetItem(PyQt4.QtGui.QListWidgetItem): def __init__(self, name, ident, parent=None): super(AppListWidgetItem, self).__init__(name, parent) # self.setToolTip(command) diff --git a/qubesmanager/bootfromdevice.py b/qubesmanager/bootfromdevice.py index eb51e25..593126d 100644 --- a/qubesmanager/bootfromdevice.py +++ b/qubesmanager/bootfromdevice.py @@ -17,14 +17,16 @@ # # +import sys import subprocess from . import utils -from .firewall import * -from .ui_bootfromdevice import * +from . import firewall +from . import ui_bootfromdevice +from PyQt4 import QtGui, QtCore import qubesadmin.tools.qvm_start as qvm_start -class VMBootFromDeviceWindow(Ui_BootDialog, QDialog): +class VMBootFromDeviceWindow(ui_bootfromdevice.Ui_BootDialog, QtGui.QDialog): def __init__(self, vm, qapp, parent=None): super(VMBootFromDeviceWindow, self).__init__(parent) @@ -34,8 +36,8 @@ class VMBootFromDeviceWindow(Ui_BootDialog, QDialog): self.setupUi(self) self.setWindowTitle(self.tr("Boot {vm} from device").format(vm=self.vm.name)) - self.connect(self.buttonBox, SIGNAL("accepted()"), self.save_and_apply) - self.connect(self.buttonBox, SIGNAL("rejected()"), self.reject) + self.connect(self.buttonBox, QtCore.SIGNAL("accepted()"), self.save_and_apply) + self.connect(self.buttonBox, QtCore.SIGNAL("rejected()"), self.reject) # populate buttons and such self.__init_buttons__() @@ -50,7 +52,7 @@ class VMBootFromDeviceWindow(Ui_BootDialog, QDialog): elif self.fileRadioButton.isChecked(): cdrom_location = str(self.vm_list[self.fileVM.currentIndex()]) + ":" + self.pathText.text() else: - QMessageBox.warning(None, + QtGui.QMessageBox.warning(None, self.tr( "ERROR!"), self.tr("No file or block device selected; please select one.")) @@ -102,7 +104,7 @@ class VMBootFromDeviceWindow(Ui_BootDialog, QDialog): self.pathText.setText(new_path) -parser = qubesadmin.tools.QubesArgumentParser(vmname_nargs=1) +parser = firewall.qubesadmin.tools.QubesArgumentParser(vmname_nargs=1) def main(args=None): global bootfromdevice_window @@ -110,7 +112,7 @@ def main(args=None): args = parser.parse_args(args) vm = args.domains.pop() - qapp = QApplication(sys.argv) + qapp = QtGui.QApplication(sys.argv) qapp.setOrganizationName('Invisible Things Lab') qapp.setOrganizationDomain("https://www.qubes-os.org/") qapp.setApplicationName("Qubes VM Settings") diff --git a/qubesmanager/create_new_vm.py b/qubesmanager/create_new_vm.py index 06076c9..4bb0325 100644 --- a/qubesmanager/create_new_vm.py +++ b/qubesmanager/create_new_vm.py @@ -26,8 +26,7 @@ import threading import time import subprocess -from PyQt4.QtCore import * -from PyQt4.QtGui import * +from PyQt4 import QtCore, QtGui import qubesadmin import qubesadmin.tools @@ -38,7 +37,7 @@ from .ui_newappvmdlg import Ui_NewVMDlg from .thread_monitor import ThreadMonitor -class NewVmDlg(QDialog, Ui_NewVMDlg): +class NewVmDlg(QtGui.QDialog, Ui_NewVMDlg): def __init__(self, qtapp, app, parent = None): super(NewVmDlg, self).__init__(parent) self.setupUi(self) @@ -69,13 +68,13 @@ class NewVmDlg(QDialog, Ui_NewVMDlg): (lambda vm: vm.provides_network), allow_internal=False, allow_default=True, allow_none=True) - self.name.setValidator(QRegExpValidator( - QRegExp("[a-zA-Z0-9-]*", Qt.CaseInsensitive), None)) + self.name.setValidator(QtGui.QRegExpValidator( + QtCore.QRegExp("[a-zA-Z0-9-]*", QtCore.Qt.CaseInsensitive), None)) self.name.selectAll() self.name.setFocus() if len(self.template_list) == 0: - QMessageBox.warning(None, + QtGui.QMessageBox.warning(None, self.tr('No template available!'), self.tr('Cannot create a qube when no template exists.')) @@ -103,7 +102,7 @@ class NewVmDlg(QDialog, Ui_NewVMDlg): except LookupError: pass else: - QMessageBox.warning(None, + QtGui.QMessageBox.warning(None, self.tr('Incorrect qube name!'), self.tr('A qube with the name {} already exists in the ' 'system!').format(name)) @@ -128,7 +127,7 @@ class NewVmDlg(QDialog, Ui_NewVMDlg): thread.daemon = True thread.start() - progress = QProgressDialog( + progress = QtGui.QProgressDialog( self.tr("Creating new qube {}...").format(name), "", 0, 0) progress.setCancelButton(None) progress.setModal(True) @@ -141,7 +140,7 @@ class NewVmDlg(QDialog, Ui_NewVMDlg): progress.hide() if not thread_monitor.success: - QMessageBox.warning(None, + QtGui.QMessageBox.warning(None, self.tr("Error creating the qube!"), self.tr("ERROR: {}").format(thread_monitor.error_msg)) @@ -214,7 +213,7 @@ parser = qubesadmin.tools.QubesArgumentParser() def main(args=None): args = parser.parse_args(args) - qtapp = QApplication(sys.argv) + qtapp = QtGui.QApplication(sys.argv) qtapp.setOrganizationName('Invisible Things Lab') qtapp.setOrganizationDomain('https://www.qubes-os.org/') qtapp.setApplicationName('Create qube') diff --git a/qubesmanager/firewall.py b/qubesmanager/firewall.py index 3467005..96c7543 100644 --- a/qubesmanager/firewall.py +++ b/qubesmanager/firewall.py @@ -21,9 +21,7 @@ import datetime import re -from PyQt4.QtCore import * -from PyQt4.QtGui import * - +from PyQt4 import QtCore, QtGui import qubesadmin.firewall from . import ui_newfwruledlg @@ -32,7 +30,7 @@ from . import ui_newfwruledlg class FirewallModifiedOutsideError(ValueError): pass -class QIPAddressValidator(QValidator): +class QIPAddressValidator(QtGui.QValidator): def __init__(self, parent = None): super (QIPAddressValidator, self).__init__(parent) @@ -40,10 +38,10 @@ class QIPAddressValidator(QValidator): hostname = str(input) if len(hostname) > 255 or len(hostname) == 0: - return (QValidator.Intermediate, input, pos) + return (QtGui.QValidator.Intermediate, input, pos) if hostname == "*": - return (QValidator.Acceptable, input, pos) + return (QtGui.QValidator.Acceptable, input, pos) unmask = hostname.split("/", 1) if len(unmask) == 2: @@ -51,27 +49,27 @@ class QIPAddressValidator(QValidator): mask = unmask[1] if mask.isdigit() or mask == "": if re.match("^([0-9]{1,3}\.){3}[0-9]{1,3}$", hostname) is None: - return (QValidator.Invalid, input, pos) + return (QtGui.QValidator.Invalid, input, pos) if mask != "": mask = int(unmask[1]) if mask < 0 or mask > 32: - return (QValidator.Invalid, input, pos) + return (QtGui.QValidator.Invalid, input, pos) else: - return (QValidator.Invalid, input, pos) + return (QtGui.QValidator.Invalid, input, pos) if hostname[-1:] == ".": hostname = hostname[:-1] if hostname[-1:] == "-": - return (QValidator.Intermediate, input, pos) + return (QtGui.QValidator.Intermediate, input, pos) allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?= len(self): return - self.beginRemoveRows(QModelIndex(), i, i) + self.beginRemoveRows(QtCore.QModelIndex(), i, i) del self.children[i] self.endRemoveRows() index = self.createIndex(i, 0) diff --git a/qubesmanager/global_settings.py b/qubesmanager/global_settings.py index e7412ac..2002fd3 100644 --- a/qubesmanager/global_settings.py +++ b/qubesmanager/global_settings.py @@ -22,20 +22,19 @@ import sys import os -from PyQt4.QtCore import * -from PyQt4.QtGui import * +from PyQt4 import QtCore, QtGui from qubesadmin import Qubes +from qubesadmin.utils import parse_size, updates_vms_status -from .ui_globalsettingsdlg import * +from . import ui_globalsettingsdlg from configparser import ConfigParser -from qubesadmin.utils import parse_size, updates_vms_status qmemman_config_path = '/etc/qubes/qmemman.conf' -class GlobalSettingsWindow(Ui_GlobalSettings, QDialog): +class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, QtGui.QDialog): def __init__(self, app, qvm_collection, parent=None): super(GlobalSettingsWindow, self).__init__(parent) @@ -45,9 +44,9 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog): self.setupUi(self) - self.connect(self.buttonBox, SIGNAL("accepted()"), self.save_and_apply) - self.connect(self.buttonBox, SIGNAL("rejected()"), self.reject) - + self.connect(self.buttonBox, QtCore.SIGNAL("accepted()"), self.save_and_apply) + self.connect(self.buttonBox, QtCore.SIGNAL("rejected()"), self.reject) + self.__init_system_defaults__() self.__init_kernel_defaults__() self.__init_mem_defaults__() @@ -251,7 +250,7 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog): self.updates_dom0.setChecked(self.updates_dom0_val) updates_vms = updates_vms_status(self.qvm_collection) if updates_vms is None: - self.updates_vm.setCheckState(Qt.PartiallyChecked) + self.updates_vm.setCheckState(QtCore.Qt.PartiallyChecked) else: self.updates_vm.setCheckState(updates_vms) @@ -259,7 +258,7 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog): if self.updates_dom0.isChecked() != self.updates_dom0_val: # TODO updates_dom0_toggle(self.qvm_collection, self.updates_dom0.isChecked()) raise NotImplementedError('Toggle dom0 updates not implemented') - if self.updates_vm.checkState() != Qt.PartiallyChecked: + if self.updates_vm.checkState() != QtCore.Qt.PartiallyChecked: for vm in self.qvm_collection.domains: vm.features['check-updates'] = bool(self.updates_vm.checkState()) @@ -286,18 +285,18 @@ def handle_exception( exc_type, exc_value, exc_traceback ): filename = os.path.basename( filename ) error = "%s: %s" % ( exc_type.__name__, exc_value ) - QMessageBox.critical(None, "Houston, we have a problem...", + QtGui.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 )) + % ( line, filename )) def main(): global qtapp - qtapp = QApplication(sys.argv) + qtapp = QtGui.QApplication(sys.argv) qtapp.setOrganizationName("The Qubes Project") qtapp.setOrganizationDomain("http://qubes-os.org") qtapp.setApplicationName("Qubes Global Settings") diff --git a/qubesmanager/multiselectwidget.py b/qubesmanager/multiselectwidget.py index eb1b145..214a1de 100644 --- a/qubesmanager/multiselectwidget.py +++ b/qubesmanager/multiselectwidget.py @@ -1,8 +1,7 @@ -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from .ui_multiselectwidget import * +from PyQt4 import QtCore, QtGui +from . import ui_multiselectwidget -class MultiSelectWidget(Ui_MultiSelectWidget, QWidget): +class MultiSelectWidget(ui_multiselectwidget.Ui_MultiSelectWidget, QtGui.QWidget): __pyqtSignals__ = ("selected_changed()",) __pyqtSignals__ = ("items_added(PyQt_PyObject)",) @@ -15,8 +14,8 @@ class MultiSelectWidget(Ui_MultiSelectWidget, QWidget): 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) + self.available_list.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) + self.selected_list.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) def switch_selected(self, src, dst): selected = src.selectedItems() @@ -28,11 +27,11 @@ class MultiSelectWidget(Ui_MultiSelectWidget, QWidget): dst.addItem(item) items.append(item) dst.sortItems() - self.emit(SIGNAL("selected_changed()")) + self.emit(QtCore.SIGNAL("selected_changed()")) if src is self.selected_list: - self.emit(SIGNAL("items_removed(PyQt_PyObject)"), items) + self.emit(QtCore.SIGNAL("items_removed(PyQt_PyObject)"), items) else: - self.emit(SIGNAL("items_added(PyQt_PyObject)"), items) + self.emit(QtCore.SIGNAL("items_added(PyQt_PyObject)"), items) def add_selected(self): self.switch_selected(self.available_list, self.selected_list) @@ -47,11 +46,11 @@ class MultiSelectWidget(Ui_MultiSelectWidget, QWidget): dst.addItem(item) items.append(item) dst.sortItems() - self.emit(SIGNAL("selected_changed()")) + self.emit(QtCore.SIGNAL("selected_changed()")) if src is self.selected_list: - self.emit(SIGNAL("items_removed(PyQt_PyObject)"), items) + self.emit(QtCore.SIGNAL("items_removed(PyQt_PyObject)"), items) else: - self.emit(SIGNAL("items_added(PyQt_PyObject)"), items) + self.emit(QtCore.SIGNAL("items_added(PyQt_PyObject)"), items) def add_all(self): diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index fcbb5c9..f3a824d 100755 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -28,7 +28,9 @@ import subprocess import threading import time import traceback - +import os +import sys +from qubesadmin.tools import QubesArgumentParser from . import utils from . import multiselectwidget @@ -36,10 +38,11 @@ from . import thread_monitor from .appmenu_select import AppmenuSelectManager from . import firewall +from PyQt4 import QtCore, QtGui from . import ui_settingsdlg -class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, firewall.QDialog): +class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): tabs_indices = collections.OrderedDict(( ('basic', 0), ('advanced', 1), @@ -66,8 +69,8 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, firewall.QDialog): assert (idx in range(self.tabWidget.count())) self.tabWidget.setCurrentIndex(idx) - self.connect(self.buttonBox, firewall.SIGNAL("accepted()"), self.save_and_apply) - self.connect(self.buttonBox, firewall.SIGNAL("rejected()"), self.reject) + self.connect(self.buttonBox, QtCore.SIGNAL("accepted()"), self.save_and_apply) + self.connect(self.buttonBox, QtCore.SIGNAL("rejected()"), self.reject) self.tabWidget.currentChanged.connect(self.current_tab_changed) @@ -83,8 +86,8 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, firewall.QDialog): ###### advanced tab self.__init_advanced_tab__() self.include_in_balancing.stateChanged.connect(self.include_in_balancing_state_changed) - self.connect(self.init_mem, firewall.SIGNAL("editingFinished()"), self.check_mem_changes) - self.connect(self.max_mem_size, firewall.SIGNAL("editingFinished()"), self.check_mem_changes) + self.connect(self.init_mem, QtCore.SIGNAL("editingFinished()"), self.check_mem_changes) + self.connect(self.max_mem_size, QtCore.SIGNAL("editingFinished()"), self.check_mem_changes) self.bootFromDeviceButton.clicked.connect(self.boot_from_cdrom_button_pressed) ###### firewall tab @@ -105,7 +108,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, firewall.QDialog): ####### devices tab self.__init_devices_tab__() - self.connect(self.dev_list, firewall.SIGNAL("selected_changed()"), self.devices_selection_changed) + self.connect(self.dev_list, QtCore.SIGNAL("selected_changed()"), self.devices_selection_changed) ####### services tab self.__init_services_tab__() @@ -132,7 +135,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, firewall.QDialog): thread.daemon = True thread.start() - progress = firewall.QProgressDialog( + progress = QtGui.QProgressDialog( self.tr("Applying settings to {0}...").format(self.vm.name), "", 0, 0) progress.setCancelButton(None) progress.setModal(True) @@ -145,7 +148,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, firewall.QDialog): progress.hide() if not t_monitor.success: - firewall.QMessageBox.warning(None, + QtGui.QMessageBox.warning(None, self.tr("Error while changing settings for {0}!").format(self.vm.name), self.tr("ERROR: {0}").format(t_monitor.error_msg)) @@ -201,7 +204,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, firewall.QDialog): netvm = self.vm.netvm if netvm is not None and \ not netvm.features.check_with_template('qubes-firewall', False): - firewall.QMessageBox.warning(None, + QtGui.QMessageBox.warning(None, self.tr("VM configuration problem!"), self.tr("The '{vm}' AppVM is network connected to " "'{netvm}', which does not support firewall!
" @@ -227,7 +230,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, firewall.QDialog): def __init_basic_tab__(self): self.vmname.setText(self.vm.name) - self.vmname.setValidator(firewall.QRegExpValidator(firewall.QRegExp("[a-zA-Z0-9-]*", firewall.Qt.CaseInsensitive), None)) + self.vmname.setValidator(QtGui.QRegExpValidator(QtCore.QRegExp("[a-zA-Z0-9-]*", QtCore.Qt.CaseInsensitive), None)) self.vmname.setEnabled(False) self.rename_vm_button.setEnabled(not self.vm.is_running()) self.delete_vm_button.setEnabled(not self.vm.is_running()) @@ -385,14 +388,14 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, firewall.QDialog): def check_mem_changes(self): if self.max_mem_size.value() < self.init_mem.value(): - firewall.QMessageBox.warning(None, + QtGui.QMessageBox.warning(None, self.tr("Warning!"), self.tr("Max memory can not be less than initial memory.
" "Setting max memory to equal initial memory.")) self.max_mem_size.setValue(self.init_mem.value()) # Linux specific limit: init memory must not be below max_mem_size/10.79 in order to allow scaling up to max_mem_size (or else "add_memory() failed: -17" problem) if self.init_mem.value() * 10 < self.max_mem_size.value(): - firewall.QMessageBox.warning(None, + QtGui.QMessageBox.warning(None, self.tr("Warning!"), self.tr("Initial memory can not be less than one tenth " "Max memory.
Setting initial memory to the minimum " @@ -410,7 +413,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, firewall.QDialog): time.sleep(0.1) if not t_monitor.success: - firewall.QMessageBox.warning(None, + QtGui.QMessageBox.warning(None, self.tr("Error!"), self.tr("ERROR: {}").format( t_monitor.error_msg)) @@ -429,7 +432,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, firewall.QDialog): def rename_vm(self): - new_vm_name, ok = firewall.QInputDialog.getText(self, + new_vm_name, ok = QtGui.QInputDialog.getText(self, self.tr('Rename VM'), self.tr('New name: (WARNING: ' 'all other changes will be discarded)')) @@ -449,7 +452,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, firewall.QDialog): def remove_vm(self): - answer, ok = firewall.QInputDialog.getText( + answer, ok = QtGui.QInputDialog.getText( self, self.tr('Delete VM'), self.tr('Are you absolutely sure you want to delete this VM? ' @@ -463,7 +466,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, firewall.QDialog): self.done(0) elif ok: - firewall.QMessageBox.warning( + QtGui.QMessageBox.warning( None, self.tr("Removal cancelled"), self.tr("The VM will not be removed.")) @@ -479,14 +482,14 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, firewall.QDialog): def clone_vm(self): - cloned_vm_name, ok = firewall.QInputDialog.getText( + cloned_vm_name, ok = QtGui.QInputDialog.getText( self, self.tr('Clone VM'), self.tr('Name for the cloned VM:')) if ok: self._run_in_thread(self._clone_vm, cloned_vm_name) - firewall.QMessageBox.warning( + QtGui.QMessageBox.warning( None, self.tr("Success"), self.tr("The VM was cloned successfully.")) @@ -593,7 +596,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, firewall.QDialog): for dev in lspci.splitlines(): devs.append((dev.rstrip(), dev.split(' ')[0])) - class DevListWidgetItem(firewall.QListWidgetItem): + class DevListWidgetItem(QtGui.QListWidgetItem): def __init__(self, name, ident, parent = None): super(DevListWidgetItem, self).__init__(name, parent) self.ident = ident @@ -727,22 +730,22 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, firewall.QDialog): if not feature.startswith('service.'): continue service = feature[len('service.'):] - item = firewall.QListWidgetItem(service) + item = QtGui.QListWidgetItem(service) item.setCheckState(ui_settingsdlg.QtCore.Qt.Checked if self.vm.features[feature] else ui_settingsdlg.QtCore.Qt.Unchecked) self.services_list.addItem(item) self.new_srv_dict[service] = self.vm.features[feature] - self.connect(self.services_list, firewall.SIGNAL("itemClicked(QListWidgetItem *)"), self.services_item_clicked) + self.connect(self.services_list, QtCore.SIGNAL("itemClicked(QListWidgetItem *)"), self.services_item_clicked) def __add_service__(self): srv = str(self.service_line_edit.text()).strip() if srv != "": if srv in self.new_srv_dict: - firewall.QMessageBox.information(None, '', + QtGui.QMessageBox.information(None, '', self.tr('Service already on the list!')) else: - item = firewall.QListWidgetItem(srv) + item = QtGui.QListWidgetItem(srv) item.setCheckState(ui_settingsdlg.QtCore.Qt.Checked) self.services_list.addItem(item) self.new_srv_dict[srv] = True @@ -753,7 +756,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, firewall.QDialog): if not item: return if str(item.text()) == 'meminfo-writer': - firewall.QMessageBox.information(None, + QtGui.QMessageBox.information(None, self.tr('Service can not be removed'), self.tr('Service meminfo-writer can not be removed from the list.')) return @@ -812,8 +815,8 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, firewall.QDialog): def set_fw_model(self, model): self.fw_model = model self.rulesTreeView.setModel(model) - self.rulesTreeView.header().setResizeMode(firewall.QHeaderView.ResizeToContents) - self.rulesTreeView.header().setResizeMode(0, firewall.QHeaderView.Stretch) + self.rulesTreeView.header().setResizeMode(QtGui.QHeaderView.ResizeToContents) + self.rulesTreeView.header().setResizeMode(0, QtGui.QHeaderView.Stretch) self.set_allow(model.allow) if model.tempFullAccessExpireTime: self.tempFullAccess.setChecked(True) @@ -874,7 +877,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, firewall.QDialog): def handle_exception(exc_type, exc_value, exc_traceback): filename, line, dummy, dummy = traceback.extract_tb(exc_traceback).pop() - filename = firewall.os.path.basename(filename) + filename = os.path.basename(filename) error = "%s: %s" % (exc_type.__name__, exc_value) strace = "" @@ -887,9 +890,9 @@ def handle_exception(exc_type, exc_value, exc_traceback): strace += "line no.: %d\n" %line strace += "file: %s\n" %filename - msg_box = firewall.QMessageBox() + msg_box = QtGui.QMessageBox() msg_box.setDetailedText(strace) - msg_box.setIcon(firewall.QMessageBox.Critical) + msg_box.setIcon(QtGui.QMessageBox.Critical) msg_box.setWindowTitle("Houston, we have a problem...") msg_box.setText("Whoops. A critical error has occured. This is most likely a bug " "in Qubes Manager.

" @@ -900,7 +903,7 @@ def handle_exception(exc_type, exc_value, exc_traceback): msg_box.exec_() -parser = firewall.qubesadmin.tools.QubesArgumentParser(vmname_nargs=1) +parser = QubesArgumentParser(vmname_nargs=1) parser.add_argument('--tab', metavar='TAB', action='store', @@ -916,13 +919,13 @@ def main(args=None): args = parser.parse_args(args) vm = args.domains.pop() - qapp = firewall.QApplication(firewall.sys.argv) + qapp = QtGui.QApplication(sys.argv) qapp.setOrganizationName('Invisible Things Lab') qapp.setOrganizationDomain("https://www.qubes-os.org/") qapp.setApplicationName("Qubes VM Settings") if not utils.is_debug(): - firewall.sys.excepthook = handle_exception + sys.excepthook = handle_exception settings_window = VMSettingsWindow(vm, qapp, args.tab) settings_window.show() diff --git a/qubesmanager/thread_monitor.py b/qubesmanager/thread_monitor.py index 8389a7a..9fbd545 100644 --- a/qubesmanager/thread_monitor.py +++ b/qubesmanager/thread_monitor.py @@ -20,11 +20,11 @@ # -from PyQt4.QtCore import * +import PyQt4.QtCore import threading -class ThreadMonitor(QObject): +class ThreadMonitor(PyQt4.QtCore.QObject): def __init__(self): self.success = True self.error_msg = None From 91572eea2d4740303db33b3234f461733b5489f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Mon, 6 Nov 2017 23:18:18 +0100 Subject: [PATCH 15/70] Fixed too-long lines Fixed lines over 80 characters. --- qubesmanager/appmenu_select.py | 4 +- qubesmanager/bootfromdevice.py | 23 +++-- qubesmanager/create_new_vm.py | 6 +- qubesmanager/firewall.py | 24 +++-- qubesmanager/global_settings.py | 62 ++++++++----- qubesmanager/multiselectwidget.py | 9 +- qubesmanager/settings.py | 142 +++++++++++++++++++----------- 7 files changed, 179 insertions(+), 91 deletions(-) diff --git a/qubesmanager/appmenu_select.py b/qubesmanager/appmenu_select.py index 942234b..fd84618 100755 --- a/qubesmanager/appmenu_select.py +++ b/qubesmanager/appmenu_select.py @@ -50,7 +50,9 @@ class AppmenuSelectManager: ).decode().strip().split('\n') if line] # Check if appmenu entry is really installed -# whitelisted = [a for a in whitelisted if os.path.exists('%s/apps/%s-%s' % (self.vm.dir_path, self.vm.name, a))] + # whitelisted = [a for a in whitelisted + # if os.path.exists('%s/apps/%s-%s' % + # (self.vm.dir_path, self.vm.name, a))] self.app_list.clear() diff --git a/qubesmanager/bootfromdevice.py b/qubesmanager/bootfromdevice.py index 593126d..b33e6d4 100644 --- a/qubesmanager/bootfromdevice.py +++ b/qubesmanager/bootfromdevice.py @@ -34,9 +34,13 @@ class VMBootFromDeviceWindow(ui_bootfromdevice.Ui_BootDialog, QtGui.QDialog): self.qapp = qapp self.setupUi(self) - self.setWindowTitle(self.tr("Boot {vm} from device").format(vm=self.vm.name)) + self.setWindowTitle( + self.tr("Boot {vm} from device").format(vm=self.vm.name)) - self.connect(self.buttonBox, QtCore.SIGNAL("accepted()"), self.save_and_apply) + self.connect( + self.buttonBox, + QtCore.SIGNAL("accepted()"), + self.save_and_apply) self.connect(self.buttonBox, QtCore.SIGNAL("rejected()"), self.reject) # populate buttons and such @@ -50,12 +54,14 @@ class VMBootFromDeviceWindow(ui_bootfromdevice.Ui_BootDialog, QtGui.QDialog): if self.blockDeviceRadioButton.isChecked(): cdrom_location = self.blockDeviceComboBox.currentText() elif self.fileRadioButton.isChecked(): - cdrom_location = str(self.vm_list[self.fileVM.currentIndex()]) + ":" + self.pathText.text() + cdrom_location = str( + self.vm_list[self.fileVM.currentIndex()]) + \ + ":" + self.pathText.text() else: - QtGui.QMessageBox.warning(None, - self.tr( - "ERROR!"), - self.tr("No file or block device selected; please select one.")) + QtGui.QMessageBox.warning( + None, + self.tr("ERROR!"), + self.tr("No file or block device selected; please select one.")) return qvm_start.main(['--cdrom', cdrom_location, self.vm.name]) @@ -87,7 +93,8 @@ class VMBootFromDeviceWindow(ui_bootfromdevice.Ui_BootDialog, QtGui.QDialog): ) def radio_button_clicked(self): - self.blockDeviceComboBox.setEnabled(self.blockDeviceRadioButton.isChecked()) + self.blockDeviceComboBox.setEnabled( + self.blockDeviceRadioButton.isChecked()) self.fileVM.setEnabled(self.fileRadioButton.isChecked()) self.selectFileButton.setEnabled(self.fileRadioButton.isChecked()) self.pathText.setEnabled(self.fileRadioButton.isChecked()) diff --git a/qubesmanager/create_new_vm.py b/qubesmanager/create_new_vm.py index 4bb0325..f14ab4c 100644 --- a/qubesmanager/create_new_vm.py +++ b/qubesmanager/create_new_vm.py @@ -46,7 +46,8 @@ class NewVmDlg(QtGui.QDialog, Ui_NewVMDlg): self.app = app # Theoretically we should be locking for writing here and unlock - # only after the VM creation finished. But the code would be more messy... + # only after the VM creation finished. But the code would be + # more messy... # Instead we lock for writing in the actual worker thread self.label_list, self.label_idx = utils.prepare_label_choice( self.label, @@ -94,7 +95,8 @@ class NewVmDlg(QtGui.QDialog, Ui_NewVMDlg): self.done(0) def accept(self): - vmclass = ('AppVM' if self.vm_type.currentIndex() == 0 else 'StandaloneVM') + vmclass = ('AppVM' if self.vm_type.currentIndex() == 0 + else 'StandaloneVM') name = str(self.name.text()) try: diff --git a/qubesmanager/firewall.py b/qubesmanager/firewall.py index 96c7543..07f5847 100644 --- a/qubesmanager/firewall.py +++ b/qubesmanager/firewall.py @@ -76,8 +76,11 @@ class NewFwRuleDlg (QtGui.QDialog, ui_newfwruledlg.Ui_NewFwRuleDlg): self.set_ok_enabled(False) self.addressComboBox.setValidator(QIPAddressValidator()) - self.addressComboBox.editTextChanged.connect(self.address_editing_finished) - self.serviceComboBox.setValidator(QtGui.QRegExpValidator(QtCore.QRegExp("[a-z][a-z0-9-]+|[0-9]+(-[0-9]+)?", QtCore.Qt.CaseInsensitive), None)) + self.addressComboBox.editTextChanged.connect( + self.address_editing_finished) + self.serviceComboBox.setValidator(QtGui.QRegExpValidator( + QtCore.QRegExp("[a-z][a-z0-9-]+|[0-9]+(-[0-9]+)?", + QtCore.Qt.CaseInsensitive), None)) self.serviceComboBox.setEnabled(False) self.serviceComboBox.setInsertPolicy(QtGui.QComboBox.InsertAtBottom) self.populate_combos() @@ -88,7 +91,8 @@ class NewFwRuleDlg (QtGui.QDialog, ui_newfwruledlg.Ui_NewFwRuleDlg): if len(self.serviceComboBox.currentText()) == 0: msg = QtGui.QMessageBox() msg.warning(self, self.tr("Firewall rule"), - self.tr("You need to fill service name/port for TCP/UDP rule")) + self.tr("You need to fill service " + "name/port for TCP/UDP rule")) return QtGui.QDialog.accept(self) @@ -138,13 +142,16 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): self.__columnNames = {0: "Address", 1: "Service", 2: "Protocol", } self.__services = list() - pattern = re.compile("(?P[a-z][a-z0-9-]+)\s+(?P[0-9]+)/(?P[a-z]+)", re.IGNORECASE) + pattern = re.compile( + "(?P[a-z][a-z0-9-]+)\s+(?P[0-9]+)/(?P[a-z]+)", + re.IGNORECASE) f = open('/etc/services', 'r') for line in f: match = pattern.match(line) if match is not None: service = match.groupdict() - self.__services.append( (service["name"], int(service["port"]),) ) + self.__services.append( + (service["name"], int(service["port"]),) ) f.close() self.fw_changed = False @@ -372,7 +379,9 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): try: rule.dstports = service except ValueError: - QtGui.QMessageBox.warning(None, self.tr("Invalid port or service"), + QtGui.QMessageBox.warning( + None, + self.tr("Invalid port or service"), self.tr("Port number or service '{0}' is invalid.") .format(service)) elif service is not None: @@ -421,7 +430,8 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole): if section < len(self.__columnNames) \ - and orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole: + and orientation == QtCore.Qt.Horizontal \ + and role == QtCore.Qt.DisplayRole: return self.__columnNames[section] @property diff --git a/qubesmanager/global_settings.py b/qubesmanager/global_settings.py index 2002fd3..cf1f4f7 100644 --- a/qubesmanager/global_settings.py +++ b/qubesmanager/global_settings.py @@ -34,7 +34,8 @@ from configparser import ConfigParser qmemman_config_path = '/etc/qubes/qmemman.conf' -class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, QtGui.QDialog): +class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, + QtGui.QDialog): def __init__(self, app, qvm_collection, parent=None): super(GlobalSettingsWindow, self).__init__(parent) @@ -44,7 +45,10 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, QtGui.QDialog self.setupUi(self) - self.connect(self.buttonBox, QtCore.SIGNAL("accepted()"), self.save_and_apply) + self.connect( + self.buttonBox, + QtCore.SIGNAL("accepted()"), + self.save_and_apply) self.connect(self.buttonBox, QtCore.SIGNAL("rejected()"), self.reject) self.__init_system_defaults__() @@ -87,7 +91,8 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, QtGui.QDialog self.clock_vm_combo.setCurrentIndex(self.clockvm_idx) # default netvm - netvms = [vm for vm in all_vms if getattr(vm, 'provides_network', False)] + netvms = [vm for vm in all_vms + if getattr(vm, 'provides_network', False)] self.netvm_idx = -1 current_netvm = self.qvm_collection.default_netvm @@ -150,7 +155,8 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, QtGui.QDialog def __init_kernel_defaults__(self): kernel_list = [] - # TODO system_path["qubes_kernels_base_dir"] idea: qubes.pulls['linux-kernel'].volumes + # TODO system_path["qubes_kernels_base_dir"] + # idea: qubes.pulls['linux-kernel'].volumes for k in os.listdir('/var/lib/qubes/vm-kernels'): kernel_list.append(k) @@ -181,8 +187,10 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, QtGui.QDialog 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 = \ + 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) @@ -197,7 +205,8 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, QtGui.QDialog 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: + 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' @@ -205,20 +214,28 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, QtGui.QDialog 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(1.3)) # removed qmemman_algo.CACHE_FACTOR + 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(1.3)) + # removed 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... + #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" + 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 = [] @@ -256,11 +273,13 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, QtGui.QDialog def __apply_updates__(self): if self.updates_dom0.isChecked() != self.updates_dom0_val: - # TODO updates_dom0_toggle(self.qvm_collection, self.updates_dom0.isChecked()) + # TODO updates_dom0_toggle( + # self.qvm_collection, self.updates_dom0.isChecked()) raise NotImplementedError('Toggle dom0 updates not implemented') if self.updates_vm.checkState() != QtCore.Qt.PartiallyChecked: for vm in self.qvm_collection.domains: - vm.features['check-updates'] = bool(self.updates_vm.checkState()) + vm.features['check-updates'] = \ + bool(self.updates_vm.checkState()) def reject(self): self.done(0) @@ -285,12 +304,13 @@ def handle_exception( exc_type, exc_value, exc_traceback ): filename = os.path.basename( filename ) error = "%s: %s" % ( exc_type.__name__, exc_value ) - QtGui.QMessageBox.critical(None, "Houston, we have a problem...", - "Whoops. A critical error has occured. This is most likely a bug " - "in Qubes Global Settings application.

" - "%s" % error + - "at line %d of file %s.

" - % ( line, filename )) + QtGui.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(): diff --git a/qubesmanager/multiselectwidget.py b/qubesmanager/multiselectwidget.py index 214a1de..57d640c 100644 --- a/qubesmanager/multiselectwidget.py +++ b/qubesmanager/multiselectwidget.py @@ -1,7 +1,8 @@ from PyQt4 import QtCore, QtGui from . import ui_multiselectwidget -class MultiSelectWidget(ui_multiselectwidget.Ui_MultiSelectWidget, QtGui.QWidget): +class MultiSelectWidget( + ui_multiselectwidget.Ui_MultiSelectWidget, QtGui.QWidget): __pyqtSignals__ = ("selected_changed()",) __pyqtSignals__ = ("items_added(PyQt_PyObject)",) @@ -14,8 +15,10 @@ class MultiSelectWidget(ui_multiselectwidget.Ui_MultiSelectWidget, QtGui.QWidget 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(QtGui.QAbstractItemView.ExtendedSelection) - self.selected_list.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) + self.available_list.setSelectionMode( + QtGui.QAbstractItemView.ExtendedSelection) + self.selected_list.setSelectionMode( + QtGui.QAbstractItemView.ExtendedSelection) def switch_selected(self, src, dst): selected = src.selectedItems() diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index f3a824d..4c29a5b 100755 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -69,7 +69,9 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): assert (idx in range(self.tabWidget.count())) self.tabWidget.setCurrentIndex(idx) - self.connect(self.buttonBox, QtCore.SIGNAL("accepted()"), self.save_and_apply) + self.connect(self.buttonBox, + QtCore.SIGNAL("accepted()"), + self.save_and_apply) self.connect(self.buttonBox, QtCore.SIGNAL("rejected()"), self.reject) self.tabWidget.currentChanged.connect(self.current_tab_changed) @@ -85,10 +87,16 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): ###### advanced tab self.__init_advanced_tab__() - self.include_in_balancing.stateChanged.connect(self.include_in_balancing_state_changed) - self.connect(self.init_mem, QtCore.SIGNAL("editingFinished()"), self.check_mem_changes) - self.connect(self.max_mem_size, QtCore.SIGNAL("editingFinished()"), self.check_mem_changes) - self.bootFromDeviceButton.clicked.connect(self.boot_from_cdrom_button_pressed) + self.include_in_balancing.stateChanged.connect( + self.include_in_balancing_state_changed) + self.connect(self.init_mem, + QtCore.SIGNAL("editingFinished()"), + self.check_mem_changes) + self.connect(self.max_mem_size, + QtCore.SIGNAL("editingFinished()"), + self.check_mem_changes) + self.bootFromDeviceButton.clicked.connect( + self.boot_from_cdrom_button_pressed) ###### firewall tab if self.tabWidget.isTabEnabled(self.tabs_indices['firewall']): @@ -102,13 +110,16 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): 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.deleteRuleButton.clicked.connect( + self.delete_rule_button_pressed) self.policyDenyRadioButton.clicked.connect(self.policy_changed) self.policyAllowRadioButton.clicked.connect(self.policy_changed) ####### devices tab self.__init_devices_tab__() - self.connect(self.dev_list, QtCore.SIGNAL("selected_changed()"), self.devices_selection_changed) + self.connect(self.dev_list, + QtCore.SIGNAL("selected_changed()"), + self.devices_selection_changed) ####### services tab self.__init_services_tab__() @@ -120,7 +131,8 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): self.app_list = multiselectwidget.MultiSelectWidget(self) self.apps_layout.addWidget(self.app_list) self.AppListManager = AppmenuSelectManager(self.vm, self.app_list) - self.refresh_apps_button.clicked.connect(self.refresh_apps_button_pressed) + self.refresh_apps_button.clicked.connect( + self.refresh_apps_button_pressed) def reject(self): self.done(0) @@ -131,12 +143,14 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): def save_and_apply(self): t_monitor = thread_monitor.ThreadMonitor() - thread = threading.Thread(target=self.__save_changes__, args=(t_monitor,)) + thread = threading.Thread(target=self.__save_changes__, + args=(t_monitor,)) thread.daemon = True thread.start() progress = QtGui.QProgressDialog( - self.tr("Applying settings to {0}...").format(self.vm.name), "", 0, 0) + self.tr("Applying settings to {0}...").format(self.vm.name), + "", 0, 0) progress.setCancelButton(None) progress.setModal(True) progress.show() @@ -148,9 +162,11 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): progress.hide() if not t_monitor.success: - QtGui.QMessageBox.warning(None, - self.tr("Error while changing settings for {0}!").format(self.vm.name), - self.tr("ERROR: {0}").format(t_monitor.error_msg)) + QtGui.QMessageBox.warning( + None, + self.tr("Error while changing settings for {0}!" + ).format(self.vm.name), + self.tr("ERROR: {0}").format(t_monitor.error_msg)) self.done(0) @@ -177,9 +193,10 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): try: if self.policyAllowRadioButton.isEnabled(): - self.fw_model.apply_rules(self.policyAllowRadioButton.isChecked(), - self.tempFullAccess.isChecked(), - self.tempFullAccessTime.value()) + self.fw_model.apply_rules( + self.policyAllowRadioButton.isChecked(), + self.tempFullAccess.isChecked(), + self.tempFullAccessTime.value()) if self.fw_model.fw_changed: # might modified vm.services self.anything_changed = True @@ -203,10 +220,13 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): if idx == self.tabs_indices["firewall"]: netvm = self.vm.netvm if netvm is not None and \ - not netvm.features.check_with_template('qubes-firewall', False): - QtGui.QMessageBox.warning(None, - self.tr("VM configuration problem!"), - self.tr("The '{vm}' AppVM is network connected to " + not netvm.features.check_with_template( + 'qubes-firewall', + False): + QtGui.QMessageBox.warning( + None, + self.tr("VM configuration problem!"), + self.tr("The '{vm}' AppVM is network connected to " "'{netvm}', which does not support firewall!
" "You may edit the '{vm}' VM firewall rules, but these " "will not take any effect until you connect it to " @@ -230,7 +250,10 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): def __init_basic_tab__(self): self.vmname.setText(self.vm.name) - self.vmname.setValidator(QtGui.QRegExpValidator(QtCore.QRegExp("[a-zA-Z0-9-]*", QtCore.Qt.CaseInsensitive), None)) + self.vmname.setValidator( + QtGui.QRegExpValidator( + QtCore.QRegExp("[a-zA-Z0-9-]*", + QtCore.Qt.CaseInsensitive), None)) self.vmname.setEnabled(False) self.rename_vm_button.setEnabled(not self.vm.is_running()) self.delete_vm_button.setEnabled(not self.vm.is_running()) @@ -341,7 +364,8 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): #include in backups try: - if self.vm.include_in_backups != self.include_in_backups.isChecked(): + if self.vm.include_in_backups != \ + self.include_in_backups.isChecked(): self.vm.include_in_backups = self.include_in_backups.isChecked() self.anything_changed = True except Exception as ex: @@ -388,16 +412,20 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): def check_mem_changes(self): if self.max_mem_size.value() < self.init_mem.value(): - QtGui.QMessageBox.warning(None, - self.tr("Warning!"), - self.tr("Max memory can not be less than initial memory.
" + QtGui.QMessageBox.warning( + None, + self.tr("Warning!"), + self.tr("Max memory can not be less than initial memory.
" "Setting max memory to equal initial memory.")) self.max_mem_size.setValue(self.init_mem.value()) - # Linux specific limit: init memory must not be below max_mem_size/10.79 in order to allow scaling up to max_mem_size (or else "add_memory() failed: -17" problem) + # Linux specific limit: init memory must not be below + # max_mem_size/10.79 in order to allow scaling up to + # max_mem_size (or else "add_memory() failed: -17" problem) if self.init_mem.value() * 10 < self.max_mem_size.value(): - QtGui.QMessageBox.warning(None, - self.tr("Warning!"), - self.tr("Initial memory can not be less than one tenth " + QtGui.QMessageBox.warning( + None, + self.tr("Warning!"), + self.tr("Initial memory can not be less than one tenth " "Max memory.
Setting initial memory to the minimum " "allowed value.")) self.init_mem.setValue(self.max_mem_size.value() / 10) @@ -432,10 +460,10 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): def rename_vm(self): - new_vm_name, ok = QtGui.QInputDialog.getText(self, - self.tr('Rename VM'), - self.tr('New name: (WARNING: ' - 'all other changes will be discarded)')) + new_vm_name, ok = QtGui.QInputDialog.getText( + self, + self.tr('Rename VM'), + self.tr('New name: (WARNING: all other changes will be discarded)')) if ok: self._run_in_thread(self._rename_vm, new_vm_name) @@ -565,7 +593,8 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): if hasattr(self.vm, "kernel") and self.kernel_groupbox.isVisible(): try: if self.kernel.currentIndex() != self.kernel_idx: - self.vm.kernel = self.kernel_list[self.kernel.currentIndex()] + self.vm.kernel = self.kernel_list[ + self.kernel.currentIndex()] self.anything_changed = True except Exception as ex: msg.append(str(ex)) @@ -613,7 +642,8 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): self.dev_list.available_list.addItem( DevListWidgetItem(name, ident)) - if self.dev_list.selected_list.count() > 0 and self.include_in_balancing.isChecked(): + if self.dev_list.selected_list.count() > 0\ + and self.include_in_balancing.isChecked(): self.dmm_warning_adv.show() self.dmm_warning_dev.show() else: @@ -709,7 +739,9 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): self.refresh_apps_button.setText(self.tr('Refresh in progress...')) t_monitor = thread_monitor.ThreadMonitor() - thread = threading.Thread(target=self.refresh_apps_in_vm, args=(t_monitor,)) + thread = threading.Thread( + target=self.refresh_apps_in_vm, + args=(t_monitor,)) thread.daemon = True thread.start() @@ -732,18 +764,24 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): service = feature[len('service.'):] item = QtGui.QListWidgetItem(service) item.setCheckState(ui_settingsdlg.QtCore.Qt.Checked - if self.vm.features[feature] else ui_settingsdlg.QtCore.Qt.Unchecked) + if self.vm.features[feature] + else ui_settingsdlg.QtCore.Qt.Unchecked) self.services_list.addItem(item) self.new_srv_dict[service] = self.vm.features[feature] - self.connect(self.services_list, QtCore.SIGNAL("itemClicked(QListWidgetItem *)"), self.services_item_clicked) + self.connect( + self.services_list, + QtCore.SIGNAL("itemClicked(QListWidgetItem *)"), + self.services_item_clicked) def __add_service__(self): srv = str(self.service_line_edit.text()).strip() if srv != "": if srv in self.new_srv_dict: - QtGui.QMessageBox.information(None, '', - self.tr('Service already on the list!')) + QtGui.QMessageBox.information( + None, + '', + self.tr('Service already on the list!')) else: item = QtGui.QListWidgetItem(srv) item.setCheckState(ui_settingsdlg.QtCore.Qt.Checked) @@ -756,9 +794,11 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): if not item: return if str(item.text()) == 'meminfo-writer': - QtGui.QMessageBox.information(None, - self.tr('Service can not be removed'), - self.tr('Service meminfo-writer can not be removed from the list.')) + QtGui.QMessageBox.information( + None, + self.tr('Service can not be removed'), + self.tr('Service meminfo-writer can not ' + 'be removed from the list.')) return row = self.services_list.currentRow() @@ -782,11 +822,14 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): try: for r in range(self.services_list.count()): item = self.services_list.item(r) - self.new_srv_dict[str(item.text())] = (item.checkState() == ui_settingsdlg.QtCore.Qt.Checked) + self.new_srv_dict[str(item.text())] = \ + (item.checkState() == ui_settingsdlg.QtCore.Qt.Checked) - balancing_was_checked = self.vm.features.get('service.meminfo-writer', True) + balancing_was_checked = self.vm.features.get( + 'service.meminfo-writer', True) balancing_is_checked = self.include_in_balancing.isChecked() - meminfo_writer_checked = self.new_srv_dict.get('meminfo-writer', True) + meminfo_writer_checked = self.new_srv_dict.get( + 'meminfo-writer', True) if balancing_is_checked != meminfo_writer_checked: if balancing_is_checked != balancing_was_checked: @@ -815,7 +858,8 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): def set_fw_model(self, model): self.fw_model = model self.rulesTreeView.setModel(model) - self.rulesTreeView.header().setResizeMode(QtGui.QHeaderView.ResizeToContents) + self.rulesTreeView.header().setResizeMode( + QtGui.QHeaderView.ResizeToContents) self.rulesTreeView.header().setResizeMode(0, QtGui.QHeaderView.Stretch) self.set_allow(model.allow) if model.tempFullAccessExpireTime: @@ -894,8 +938,8 @@ def handle_exception(exc_type, exc_value, exc_traceback): msg_box.setDetailedText(strace) msg_box.setIcon(QtGui.QMessageBox.Critical) msg_box.setWindowTitle("Houston, we have a problem...") - msg_box.setText("Whoops. A critical error has occured. This is most likely a bug " - "in Qubes Manager.

" + msg_box.setText("Whoops. A critical error has occured. " + "This is most likely a bug in Qubes Manager.

" "%s" % error + "
at line %d
of file %s.

" % ( line, filename )) From 2b24abb8c83b7a95a4c837cb073c2bf182ddf771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Mon, 6 Nov 2017 23:54:33 +0100 Subject: [PATCH 16/70] Fixed trailing and bad whitespaces Fixed trailing and bad whitespaces --- qubesmanager/create_new_vm.py | 4 ++-- qubesmanager/firewall.py | 34 +++++++++++++++---------------- qubesmanager/global_settings.py | 34 +++++++++++++++---------------- qubesmanager/multiselectwidget.py | 8 ++++---- qubesmanager/settings.py | 14 ++++++------- 5 files changed, 47 insertions(+), 47 deletions(-) diff --git a/qubesmanager/create_new_vm.py b/qubesmanager/create_new_vm.py index f14ab4c..5477c30 100644 --- a/qubesmanager/create_new_vm.py +++ b/qubesmanager/create_new_vm.py @@ -38,7 +38,7 @@ from .thread_monitor import ThreadMonitor class NewVmDlg(QtGui.QDialog, Ui_NewVMDlg): - def __init__(self, qtapp, app, parent = None): + def __init__(self, qtapp, app, parent=None): super(NewVmDlg, self).__init__(parent) self.setupUi(self) @@ -137,7 +137,7 @@ class NewVmDlg(QtGui.QDialog, Ui_NewVMDlg): while not thread_monitor.is_finished(): self.qtapp.processEvents() - time.sleep (0.1) + time.sleep(0.1) progress.hide() diff --git a/qubesmanager/firewall.py b/qubesmanager/firewall.py index 07f5847..d56a2ce 100644 --- a/qubesmanager/firewall.py +++ b/qubesmanager/firewall.py @@ -31,8 +31,8 @@ class FirewallModifiedOutsideError(ValueError): pass class QIPAddressValidator(QtGui.QValidator): - def __init__(self, parent = None): - super (QIPAddressValidator, self).__init__(parent) + def __init__(self, parent=None): + super(QIPAddressValidator, self).__init__(parent) def validate(self, input, pos): hostname = str(input) @@ -69,9 +69,9 @@ class QIPAddressValidator(QtGui.QValidator): return (QtGui.QValidator.Invalid, input, pos) -class NewFwRuleDlg (QtGui.QDialog, ui_newfwruledlg.Ui_NewFwRuleDlg): - def __init__(self, parent = None): - super (NewFwRuleDlg, self).__init__(parent) +class NewFwRuleDlg(QtGui.QDialog, ui_newfwruledlg.Ui_NewFwRuleDlg): + def __init__(self, parent=None): + super(NewFwRuleDlg, self).__init__(parent) self.setupUi(self) self.set_ok_enabled(False) @@ -105,8 +105,8 @@ class NewFwRuleDlg (QtGui.QDialog, ui_newfwruledlg.Ui_NewFwRuleDlg): displayed_services = [ '', 'http', 'https', 'ftp', 'ftps', 'smtp', - 'smtps', 'pop3', 'pop3s', 'imap', 'imaps', 'odmr', - 'nntp', 'nntps', 'ssh', 'telnet', 'telnets', 'ntp', + 'smtps', 'pop3', 'pop3s', 'imap', 'imaps', 'odmr', + 'nntp', 'nntps', 'ssh', 'telnet', 'telnets', 'ntp', 'snmp', 'ldap', 'ldaps', 'irc', 'ircs', 'xmpp-client', 'syslog', 'printer', 'nfs', 'x11', '1024-1234' @@ -151,7 +151,7 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): if match is not None: service = match.groupdict() self.__services.append( - (service["name"], int(service["port"]),) ) + (service["name"], int(service["port"]),)) f.close() self.fw_changed = False @@ -160,8 +160,8 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): from operator import attrgetter rev = (order == QtCore.Qt.AscendingOrder) - self.children.sort(key = lambda x: self.get_column_string(idx, x) - , reverse = rev) + self.children.sort(key=lambda x: self.get_column_string(idx, x) + , reverse=rev) index1 = self.createIndex(0, 0) index2 = self.createIndex(len(self)-1, len(self.__columnNames)-1) @@ -295,7 +295,7 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): action='accept')) else: rules.append(qubesadmin.firewall.Rule(None, - action = 'drop')) + action='drop')) vm.firewall.rules = rules @@ -324,14 +324,14 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): (self.tempFullAccessExpireTime != 0) != tempFullAccess: self.fw_changed = True - conf = { "allow": allow, + conf = {"allow": allow, "rules": list() } conf['rules'].extend(self.children) if tempFullAccess and not allow: - conf["rules"].append(qubesadmin.firewall.Rule(None,action='accept' + conf["rules"].append(qubesadmin.firewall.Rule(None, action='accept' , expire=int(datetime.datetime.now().strftime("%s"))+\ tempFullAccessTime*60)) @@ -355,13 +355,13 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): else: dialog.any_radio.setChecked(True) - def run_rule_dialog(self, dialog, row = None): + def run_rule_dialog(self, dialog, row=None): if dialog.exec_(): address = str(dialog.addressComboBox.currentText()) service = str(dialog.serviceComboBox.currentText()) - rule = qubesadmin.firewall.Rule(None,action='accept') + rule = qubesadmin.firewall.Rule(None, action='accept') if address is not None and address != "*": try: @@ -425,8 +425,8 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): def data(self, index, role=QtCore.Qt.DisplayRole): if index.isValid() and role == QtCore.Qt.DisplayRole: - return self.get_column_string(index.column() - ,self.children[index.row()]) + return self.get_column_string(index.column(), + self.children[index.row()]) def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole): if section < len(self.__columnNames) \ diff --git a/qubesmanager/global_settings.py b/qubesmanager/global_settings.py index cf1f4f7..7b27ed6 100644 --- a/qubesmanager/global_settings.py +++ b/qubesmanager/global_settings.py @@ -44,7 +44,7 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, self.qvm_collection = qvm_collection self.setupUi(self) - + self.connect( self.buttonBox, QtCore.SIGNAL("accepted()"), @@ -125,7 +125,7 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, updatevm_name = str(self.update_vm_combo.currentText()) updatevm_name = updatevm_name.split(' ')[0] updatevm = self.qvm_collection.domains[updatevm_name] - + self.qvm_collection.updatevm = updatevm #clockvm @@ -133,7 +133,7 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, clockvm_name = str(self.clock_vm_combo.currentText()) clockvm_name = clockvm_name.split(' ')[0] clockvm = self.qvm_collection.domains[clockvm_name] - + self.qvm_collection.clockvm = clockvm #default netvm @@ -141,7 +141,7 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, name = str(self.default_netvm_combo.currentText()) name = name.split(' ')[0] vm = self.qvm_collection.domains[name] - + self.qvm_collection.default_netvm = vm #default template @@ -149,7 +149,7 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, name = str(self.default_template_combo.currentText()) name = name.split(' ')[0] vm = self.qvm_collection.domains[name] - + self.qvm_collection.default_template = vm @@ -174,17 +174,17 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, if self.default_kernel_combo.currentIndex() != self.kernel_idx: kernel = str(self.default_kernel_combo.currentText()) kernel = kernel.split(' ')[0] - + self.qvm_collection.default_kernel = kernel - + def __init_mem_defaults__(self): #qmemman settings self.qmemman_config = ConfigParser() - self.vm_min_mem_val = '200MiB' #str(qmemman_algo.MIN_PREFMEM) + self.vm_min_mem_val = '200MiB' #str(qmemman_algo.MIN_PREFMEM) self.dom0_mem_boost_val = '350MiB' #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 = \ @@ -230,7 +230,7 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, #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" @@ -249,7 +249,7 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, del lines_to_add['dom0-mem-boost'] else: config_lines.append(l) - + qmemman_config_file.close() for l in lines_to_add: @@ -286,7 +286,7 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, def save_and_apply(self): - self.__apply_system_defaults__() + self.__apply_system_defaults__() self.__apply_kernel_defaults__() self.__apply_mem_defaults__() self.__apply_updates__() @@ -296,13 +296,13 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, # Bases on the original code by: # Copyright (c) 2002-2007 Pascal Varet -def handle_exception( exc_type, exc_value, exc_traceback ): +def handle_exception(exc_type, exc_value, exc_traceback): 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 ) + filename, line, dummy, dummy = traceback.extract_tb(exc_traceback).pop() + filename = os.path.basename(filename) + error = "%s: %s" % (exc_type.__name__, exc_value) QtGui.QMessageBox.critical( None, @@ -310,7 +310,7 @@ def handle_exception( exc_type, exc_value, exc_traceback ): "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 )) + % (line, filename)) def main(): diff --git a/qubesmanager/multiselectwidget.py b/qubesmanager/multiselectwidget.py index 57d640c..ca843d7 100644 --- a/qubesmanager/multiselectwidget.py +++ b/qubesmanager/multiselectwidget.py @@ -31,7 +31,7 @@ class MultiSelectWidget( items.append(item) dst.sortItems() self.emit(QtCore.SIGNAL("selected_changed()")) - if src is self.selected_list: + if src is self.selected_list: self.emit(QtCore.SIGNAL("items_removed(PyQt_PyObject)"), items) else: self.emit(QtCore.SIGNAL("items_added(PyQt_PyObject)"), items) @@ -40,8 +40,8 @@ class MultiSelectWidget( self.switch_selected(self.available_list, self.selected_list) def remove_selected(self): - self.switch_selected(self.selected_list, self.available_list) - + self.switch_selected(self.selected_list, self.available_list) + def move_all(self, src, dst): items = [] while src.count() > 0: @@ -50,7 +50,7 @@ class MultiSelectWidget( items.append(item) dst.sortItems() self.emit(QtCore.SIGNAL("selected_changed()")) - if src is self.selected_list: + if src is self.selected_list: self.emit(QtCore.SIGNAL("items_removed(PyQt_PyObject)"), items) else: self.emit(QtCore.SIGNAL("items_added(PyQt_PyObject)"), items) diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index 4c29a5b..d865ba9 100755 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -157,7 +157,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): while not t_monitor.is_finished(): self.qapp.processEvents() - time.sleep (0.1) + time.sleep(0.1) progress.hide() @@ -209,7 +209,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): except Exception as ex: ret += [self.tr("Applications tab:"), str(ex)] - if len(ret) > 0 : + if len(ret) > 0: t_monitor.set_error_msg('\n'.join(ret)) utils.debug('\n'.join(ret)) @@ -626,7 +626,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): devs.append((dev.rstrip(), dev.split(' ')[0])) class DevListWidgetItem(QtGui.QListWidgetItem): - def __init__(self, name, ident, parent = None): + def __init__(self, name, ident, parent=None): super(DevListWidgetItem, self).__init__(name, parent) self.ident = ident self.Type @@ -688,7 +688,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): return msg def include_in_balancing_state_changed(self, state): - for r in range (self.services_list.count()): + for r in range(self.services_list.count()): item = self.services_list.item(r) if str(item.text()) == 'meminfo-writer': item.setCheckState(state) @@ -705,7 +705,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): def devices_selection_changed(self): if self.include_in_balancing.isChecked(): - if self.dev_list.selected_list.count() > 0 : + if self.dev_list.selected_list.count() > 0: self.dmm_warning_adv.show() self.dmm_warning_dev.show() else: @@ -747,7 +747,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): while not t_monitor.is_finished(): self.qapp.processEvents() - time.sleep (0.1) + time.sleep(0.1) self.AppListManager = AppmenuSelectManager(self.vm, self.app_list) @@ -942,7 +942,7 @@ def handle_exception(exc_type, exc_value, exc_traceback): "This is most likely a bug in Qubes Manager.

" "%s" % error + "
at line %d
of file %s.

" - % ( line, filename )) + % (line, filename)) msg_box.exec_() From d7fbacf0d7b682a9de7a72b88af8d82b0c784e9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Wed, 8 Nov 2017 15:40:35 +0100 Subject: [PATCH 17/70] Fixed broad exceptions Fixed broad exception errors: replacing them with more precise error catching where sensible and adding appropriate pylint skip comments where not. --- qubesmanager/create_new_vm.py | 7 ++-- qubesmanager/settings.py | 63 +++++++++++++++++++++-------------- 2 files changed, 43 insertions(+), 27 deletions(-) diff --git a/qubesmanager/create_new_vm.py b/qubesmanager/create_new_vm.py index 5477c30..f8f29aa 100644 --- a/qubesmanager/create_new_vm.py +++ b/qubesmanager/create_new_vm.py @@ -30,6 +30,7 @@ from PyQt4 import QtCore, QtGui import qubesadmin import qubesadmin.tools +import qubesadmin.exc from . import utils @@ -174,8 +175,10 @@ class NewVmDlg(QtGui.QDialog, Ui_NewVMDlg): for k, v in properties.items(): setattr(vm, k, v) - except Exception as ex: - thread_monitor.set_error_msg(str(ex)) + except qubesadmin.exc.QubesException as qex: + thread_monitor.set_error_msg(str(qex)) + except Exception as ex: # pylint: disable=broad-except + thread_monitor.set_error_msg(repr(ex)) thread_monitor.set_finished() diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index d865ba9..e7897b0 100755 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -31,6 +31,7 @@ import traceback import os import sys from qubesadmin.tools import QubesArgumentParser +import qubesadmin.exc from . import utils from . import multiselectwidget @@ -188,8 +189,10 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): ret_tmp = self.__apply_services_tab__() if len(ret_tmp) > 0: ret += ["Sevices tab:"] + ret_tmp - except Exception as ex: - ret.append(self.tr('Error while saving changes: ') + str(ex)) + except qubesadmin.exc.QubesException as qex: + ret.append(self.tr('Error while saving changes: ') + str(qex)) + except Exception as ex: # pylint: disable=broad-except + ret.append(repr(ex)) try: if self.policyAllowRadioButton.isEnabled(): @@ -200,14 +203,18 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): if self.fw_model.fw_changed: # might modified vm.services self.anything_changed = True - except Exception as ex: - ret += [self.tr("Firewall tab:"), str(ex)] + except qubesadmin.exc.QubesException as qex: + ret+= [self.tr("Firewall tab:"), str(qex)] + except Exception as ex: # pylint: disable=broad-except + ret += [self.tr("Firewall tab:"), repr(ex)] try: if self.tabWidget.isTabEnabled(self.tabs_indices["applications"]): self.AppListManager.save_appmenu_select_changes() - except Exception as ex: - ret += [self.tr("Applications tab:"), str(ex)] + except qubesadmin.exc.QubesException as qex: + ret += [self.tr("Applications tab:"), str(qex)] + except Exception as ex: # pylint: disable=broad-except + ret += [self.tr("Applications tab:"), repr(ex)] if len(ret) > 0: t_monitor.set_error_msg('\n'.join(ret)) @@ -342,7 +349,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): label = self.label_list[self.vmlabel.currentIndex()] self.vm.label = label self.anything_changed = True - except Exception as ex: + except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) #vm template changed @@ -351,7 +358,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): self.vm.template = \ self.template_list[self.template_name.currentIndex()] self.anything_changed = True - except Exception as ex: + except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) #vm netvm changed @@ -359,7 +366,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): if self.netVM.currentIndex() != self.netvm_idx: self.vm.netvm = self.netvm_list[self.netVM.currentIndex()] self.anything_changed = True - except Exception as ex: + except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) #include in backups @@ -368,7 +375,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): self.include_in_backups.isChecked(): self.vm.include_in_backups = self.include_in_backups.isChecked() self.anything_changed = True - except Exception as ex: + except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) #run_in_debug_mode @@ -377,7 +384,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): if self.vm.debug != self.run_in_debug_mode.isChecked(): self.vm.debug = self.run_in_debug_mode.isChecked() self.anything_changed = True - except Exception as ex: + except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) #autostart_vm @@ -386,7 +393,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): if self.vm.autostart != self.autostart_vm.isChecked(): self.vm.autostart = self.autostart_vm.isChecked() self.anything_changed = True - except Exception as ex: + except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) #max priv storage @@ -395,7 +402,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): try: self.vm.volumes['private'].resize(priv_size * 1024**2) self.anything_changed = True - except Exception as ex: + except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) #max sys storage @@ -404,7 +411,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): try: self.vm.volumes['root'].resize(sys_size * 1024**2) self.anything_changed = True - except Exception as ex: + except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) return msg @@ -452,8 +459,10 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): self.vm.app.clone_vm(self.vm, name) del self.vm.app.domains[self.vm.name] - except Exception as ex: - t_monitor.set_error_msg(str(ex)) + except qubesadmin.exc.QubesException as qex: + t_monitor.set_error_msg(str(qex)) + except Exception as ex: # pylint: disable=broad-except + t_monitor.set_error_msg(repr(ex)) t_monitor.set_finished() @@ -473,8 +482,10 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): try: del self.vm.app.domains[self.vm.name] - except Exception as ex: - t_monitor.set_error_msg(str(ex)) + except qubesadmin.exc.QubesException as qex: + t_monitor.set_error_msg(str(qex)) + except Exception as ex: # pylint: disable=broad-except + t_monitor.set_error_msg(repr(ex)) t_monitor.set_finished() @@ -503,8 +514,10 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): try: self.vm.app.clone_vm(self.vm, name) - except Exception as ex: - t_monitor.set_error_msg(str(ex)) + except qubesadmin.exc.QubesException as qex: + t_monitor.set_error_msg(str(qex)) + except Exception as ex: # pylint: disable=broad-except + t_monitor.set_error_msg(repr(ex)) t_monitor.set_finished() @@ -584,7 +597,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): if self.vcpus.value() != int(self.vm.vcpus): self.vm.vcpus = self.vcpus.value() self.anything_changed = True - except Exception as ex: + except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) #include_in_memory_balancing applied in services tab @@ -596,7 +609,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): self.vm.kernel = self.kernel_list[ self.kernel.currentIndex()] self.anything_changed = True - except Exception as ex: + except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) #vm default_dispvm changed @@ -605,7 +618,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): self.vm.default_dispvm = \ self.default_dispvm_list[self.default_dispvm.currentIndex()] self.anything_changed = True - except Exception as ex: + except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) return msg @@ -680,7 +693,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): self.anything_changed = True - except Exception as ex: + except qubesadmin.exc.QubesException as ex: if utils.is_debug(): traceback.print_exc() msg.append(str(ex)) @@ -847,7 +860,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): service = feature[len('service.'):] if service not in self.new_srv_dict: del self.vm.features[feature] - except Exception as ex: + except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) return msg From 6e5d611f70d79892d7eeb8337083fdc8a1b7ae45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Wed, 8 Nov 2017 16:53:49 +0100 Subject: [PATCH 18/70] Fixed invalid names Fixed camel case, too long and too short variable and function names. --- .pylintrc | 2 +- qubesmanager/appmenu_select.py | 8 +-- qubesmanager/firewall.py | 78 ++++++++++++++------------- qubesmanager/global_settings.py | 12 ++--- qubesmanager/multiselectwidget.py | 4 +- qubesmanager/settings.py | 90 ++++++++++++++++--------------- ui/settingsdlg.ui | 30 +++++------ 7 files changed, 117 insertions(+), 107 deletions(-) diff --git a/.pylintrc b/.pylintrc index 0b4dc29..a7ae4d5 100644 --- a/.pylintrc +++ b/.pylintrc @@ -85,7 +85,7 @@ variable-rgx=[a-z_][a-z0-9_]{2,30}$ inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ # Good variable names which should always be accepted, separated by a comma -good-names=e,i,j,k,m,p,v,ex,Run,_,log,vm +good-names=e,i,j,k,m,p,v,ex,Run,_,log,vm,ok # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata diff --git a/qubesmanager/appmenu_select.py b/qubesmanager/appmenu_select.py index fd84618..b14c487 100755 --- a/qubesmanager/appmenu_select.py +++ b/qubesmanager/appmenu_select.py @@ -61,11 +61,11 @@ class AppmenuSelectManager: '--get-available', '--i-understand-format-is-unstable', self.vm.name]).decode().splitlines()] - for a in available_appmenus: - if a.ident in self.whitelisted: - self.app_list.selected_list.addItem(a) + for app in available_appmenus: + if app.ident in self.whitelisted: + self.app_list.selected_list.addItem(app) else: - self.app_list.available_list.addItem(a) + self.app_list.available_list.addItem(app) self.app_list.available_list.sortItems() self.app_list.selected_list.sortItems() diff --git a/qubesmanager/firewall.py b/qubesmanager/firewall.py index d56a2ce..ed60ded 100644 --- a/qubesmanager/firewall.py +++ b/qubesmanager/firewall.py @@ -74,7 +74,7 @@ class NewFwRuleDlg(QtGui.QDialog, ui_newfwruledlg.Ui_NewFwRuleDlg): super(NewFwRuleDlg, self).__init__(parent) self.setupUi(self) - self.set_ok_enabled(False) + self.set_ok_state(False) self.addressComboBox.setValidator(QIPAddressValidator()) self.addressComboBox.editTextChanged.connect( self.address_editing_finished) @@ -117,12 +117,12 @@ class NewFwRuleDlg(QtGui.QDialog, ui_newfwruledlg.Ui_NewFwRuleDlg): self.serviceComboBox.addItem(service) def address_editing_finished(self): - self.set_ok_enabled(True) + self.set_ok_state(True) - def set_ok_enabled(self, on): + def set_ok_state(self, ok_state): ok_button = self.buttonBox.button(QtGui.QDialogButtonBox.Ok) if ok_button is not None: - ok_button.setEnabled(on) + ok_button.setEnabled(ok_state) def on_tcp_radio_toggled(self, checked): if checked: @@ -140,19 +140,19 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): def __init__(self, parent=None): QtCore.QAbstractItemModel.__init__(self, parent) - self.__columnNames = {0: "Address", 1: "Service", 2: "Protocol", } + self.__column_names = {0: "Address", 1: "Service", 2: "Protocol", } self.__services = list() pattern = re.compile( "(?P[a-z][a-z0-9-]+)\s+(?P[0-9]+)/(?P[a-z]+)", re.IGNORECASE) - f = open('/etc/services', 'r') - for line in f: + file = open('/etc/services', 'r') + for line in file: match = pattern.match(line) if match is not None: service = match.groupdict() self.__services.append( (service["name"], int(service["port"]),)) - f.close() + file.close() self.fw_changed = False @@ -164,7 +164,7 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): , reverse=rev) index1 = self.createIndex(0, 0) - index2 = self.createIndex(len(self)-1, len(self.__columnNames)-1) + index2 = self.createIndex(len(self) - 1, len(self.__column_names) - 1) self.dataChanged.emit(index1, index2) @@ -219,8 +219,8 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): 'rules': [], } - allowDns = False - allowIcmp = False + allow_dns = False + allow_icmp = False common_action = None reversed_rules = list(reversed(vm.firewall.rules)) @@ -241,11 +241,11 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): rule = reversed_rules.pop(0) if rule == dns_rule: - allowDns = True + allow_dns = True continue if rule.proto == icmp_rule: - allowIcmp = True + allow_icmp = True continue if rule.specialtarget is not None or rule.icmptype is not None: @@ -268,10 +268,10 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): conf['allow'] = (common_action == 'accept') - if not allowIcmp and not conf['allow']: + if not allow_icmp and not conf['allow']: raise FirewallModifiedOutsideError('ICMP must be allowed.') - if not allowDns and not conf['allow']: + if not allow_dns and not conf['allow']: raise FirewallModifiedOutsideError('DNS must be allowed') return conf @@ -302,26 +302,26 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): def set_vm(self, vm): self.__vm = vm - self.clearChildren() + self.clear_children() conf = self.get_firewall_conf(vm) self.allow = conf["allow"] - self.tempFullAccessExpireTime = conf['expire'] + self.temp_full_access_expire_time = conf['expire'] for rule in conf["rules"]: - self.appendChild(rule) + self.append_child(rule) def get_vm_name(self): return self.__vm.name - def apply_rules(self, allow, tempFullAccess=False, - tempFullAccessTime=None): + def apply_rules(self, allow, temp_full_access=False, + temp_full_access_time=None): assert self.__vm is not None if self.allow != allow or \ - (self.tempFullAccessExpireTime != 0) != tempFullAccess: + (self.temp_full_access_expire_time != 0) != temp_full_access: self.fw_changed = True conf = {"allow": allow, @@ -330,10 +330,12 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): conf['rules'].extend(self.children) - if tempFullAccess and not allow: - conf["rules"].append(qubesadmin.firewall.Rule(None, action='accept' - , expire=int(datetime.datetime.now().strftime("%s"))+\ - tempFullAccessTime*60)) + if temp_full_access and not allow: + conf["rules"].append(qubesadmin.firewall.Rule( + None, + action='accept', + expire=int(datetime.datetime.now().strftime("%s")) + + temp_full_access_time * 60)) if self.fw_changed: self.write_firewall_conf(self.__vm, conf) @@ -397,9 +399,9 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): .format(service)) if row is not None: - self.setChild(row, rule) + self.set_child(row, rule) else: - self.appendChild(rule) + self.append_child(rule) def index(self, row, column, parent=QtCore.QModelIndex()): if not self.hasIndex(row, column, parent): @@ -410,15 +412,18 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): def parent(self, child): return QtCore.QModelIndex() + # pylint: disable=invalid-name def rowCount(self, parent=QtCore.QModelIndex()): return len(self) + # pylint: disable=invalid-name def columnCount(self, parent=QtCore.QModelIndex()): - return len(self.__columnNames) + return len(self.__column_names) + # pylint: disable=invalid-name def hasChildren(self, index=QtCore.QModelIndex()): - parentItem = index.internalPointer() - if parentItem is not None: + parent_item = index.internalPointer() + if parent_item is not None: return False else: return True @@ -428,17 +433,18 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): return self.get_column_string(index.column(), self.children[index.row()]) + # pylint: disable=invalid-name def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole): - if section < len(self.__columnNames) \ + if section < len(self.__column_names) \ and orientation == QtCore.Qt.Horizontal \ and role == QtCore.Qt.DisplayRole: - return self.__columnNames[section] + return self.__column_names[section] @property def children(self): return self.__children - def appendChild(self, child): + def append_child(self, child): row = len(self) self.beginInsertRows(QtCore.QModelIndex(), row, row) self.children.append(child) @@ -447,7 +453,7 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): self.dataChanged.emit(index, index) self.fw_changed = True - def removeChild(self, i): + def remove_child(self, i): if i >= len(self): return @@ -458,13 +464,13 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): self.dataChanged.emit(index, index) self.fw_changed = True - def setChild(self, i, child): + def set_child(self, i, child): self.children[i] = child index = self.createIndex(i, 0, child) self.dataChanged.emit(index, index) self.fw_changed = True - def clearChildren(self): + def clear_children(self): self.__children = list() def __len__(self): diff --git a/qubesmanager/global_settings.py b/qubesmanager/global_settings.py index 7b27ed6..2863f9f 100644 --- a/qubesmanager/global_settings.py +++ b/qubesmanager/global_settings.py @@ -240,20 +240,20 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, config_lines = [] qmemman_config_file = open(qmemman_config_path, 'r') - for l in qmemman_config_file: - if l.strip().startswith('vm-min-mem'): + for line in qmemman_config_file: + if line.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'): + elif line.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) + config_lines.append(line) qmemman_config_file.close() - for l in lines_to_add: - config_lines.append(l) + for line in lines_to_add: + config_lines.append(line) qmemman_config_file = open(qmemman_config_path, 'w') qmemman_config_file.writelines(config_lines) diff --git a/qubesmanager/multiselectwidget.py b/qubesmanager/multiselectwidget.py index ca843d7..24e657a 100644 --- a/qubesmanager/multiselectwidget.py +++ b/qubesmanager/multiselectwidget.py @@ -24,8 +24,8 @@ class MultiSelectWidget( selected = src.selectedItems() items = [] - for s in selected: - row = src.indexFromItem(s).row() + for selected_item in selected: + row = src.indexFromItem(selected_item).row() item = src.takeItem(row) dst.addItem(item) items.append(item) diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index e7897b0..77fa87e 100755 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -89,14 +89,14 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): ###### advanced tab self.__init_advanced_tab__() self.include_in_balancing.stateChanged.connect( - self.include_in_balancing_state_changed) + self.include_in_balancing_changed) self.connect(self.init_mem, QtCore.SIGNAL("editingFinished()"), self.check_mem_changes) self.connect(self.max_mem_size, QtCore.SIGNAL("editingFinished()"), self.check_mem_changes) - self.bootFromDeviceButton.clicked.connect( + self.boot_from_device_button.clicked.connect( self.boot_from_cdrom_button_pressed) ###### firewall tab @@ -105,16 +105,16 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): try: model.set_vm(vm) self.set_fw_model(model) - self.firewallModifiedOutsidelabel.setVisible(False) + self.firewall_modified_outside_label.setVisible(False) except firewall.FirewallModifiedOutsideError as ex: self.disable_all_fw_conf() - self.newRuleButton.clicked.connect(self.new_rule_button_pressed) - self.editRuleButton.clicked.connect(self.edit_rule_button_pressed) - self.deleteRuleButton.clicked.connect( + self.new_rule_button.clicked.connect(self.new_rule_button_pressed) + self.edit_rule_button.clicked.connect(self.edit_rule_button_pressed) + self.delete_rule_button.clicked.connect( self.delete_rule_button_pressed) - self.policyDenyRadioButton.clicked.connect(self.policy_changed) - self.policyAllowRadioButton.clicked.connect(self.policy_changed) + self.policy_deny_radio_button.clicked.connect(self.policy_changed) + self.policy_allow_radio_button.clicked.connect(self.policy_changed) ####### devices tab self.__init_devices_tab__() @@ -131,7 +131,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): if self.tabWidget.isTabEnabled(self.tabs_indices["applications"]): self.app_list = multiselectwidget.MultiSelectWidget(self) self.apps_layout.addWidget(self.app_list) - self.AppListManager = AppmenuSelectManager(self.vm, self.app_list) + self.app_list_manager = AppmenuSelectManager(self.vm, self.app_list) self.refresh_apps_button.clicked.connect( self.refresh_apps_button_pressed) @@ -195,11 +195,11 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): ret.append(repr(ex)) try: - if self.policyAllowRadioButton.isEnabled(): + if self.policy_allow_radio_button.isEnabled(): self.fw_model.apply_rules( - self.policyAllowRadioButton.isChecked(), - self.tempFullAccess.isChecked(), - self.tempFullAccessTime.value()) + self.policy_allow_radio_button.isChecked(), + self.temp_full_access.isChecked(), + self.temp_full_access_time.value()) if self.fw_model.fw_changed: # might modified vm.services self.anything_changed = True @@ -210,7 +210,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): try: if self.tabWidget.isTabEnabled(self.tabs_indices["applications"]): - self.AppListManager.save_appmenu_select_changes() + self.app_list_manager.save_appmenu_select_changes() except qubesadmin.exc.QubesException as qex: ret += [self.tr("Applications tab:"), str(qex)] except Exception as ex: # pylint: disable=broad-except @@ -700,9 +700,9 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): return msg - def include_in_balancing_state_changed(self, state): - for r in range(self.services_list.count()): - item = self.services_list.item(r) + def include_in_balancing_changed(self, state): + for i in range(self.services_list.count()): + item = self.services_list.item(i) if str(item.text()) == 'meminfo-writer': item.setCheckState(state) break @@ -762,7 +762,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): self.qapp.processEvents() time.sleep(0.1) - self.AppListManager = AppmenuSelectManager(self.vm, self.app_list) + self.app_list_manager = AppmenuSelectManager(self.vm, self.app_list) self.refresh_apps_button.setEnabled(True) self.refresh_apps_button.setText(self.tr('Refresh Applications')) @@ -833,8 +833,8 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): msg = [] try: - for r in range(self.services_list.count()): - item = self.services_list.item(r) + for i in range(self.services_list.count()): + item = self.services_list.item(i) self.new_srv_dict[str(item.text())] = \ (item.checkState() == ui_settingsdlg.QtCore.Qt.Checked) @@ -875,37 +875,41 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): QtGui.QHeaderView.ResizeToContents) self.rulesTreeView.header().setResizeMode(0, QtGui.QHeaderView.Stretch) self.set_allow(model.allow) - if model.tempFullAccessExpireTime: - self.tempFullAccess.setChecked(True) - self.tempFullAccessTime.setValue( - (model.tempFullAccessExpireTime - + if model.temp_full_access_expire_time: + self.temp_full_access.setChecked(True) + self.temp_full_access_time.setValue( + (model.temp_full_access_expire_time - int(firewall.datetime.datetime.now().strftime("%s"))) / 60) def disable_all_fw_conf(self): - self.firewallModifiedOutsidelabel.setVisible(True) - self.policyAllowRadioButton.setEnabled(False) - self.policyDenyRadioButton.setEnabled(False) + self.firewall_modified_outside_label.setVisible(True) + self.policy_allow_radio_button.setEnabled(False) + self.policy_deny_radio_button.setEnabled(False) self.rulesTreeView.setEnabled(False) - self.newRuleButton.setEnabled(False) - self.editRuleButton.setEnabled(False) - self.deleteRuleButton.setEnabled(False) - self.firewalRulesLabel.setEnabled(False) + self.new_rule_button.setEnabled(False) + self.edit_rule_button.setEnabled(False) + self.delete_rule_button.setEnabled(False) + self.firewal_rules_label.setEnabled(False) self.tempFullAccessWidget.setEnabled(False) def set_allow(self, allow): - self.policyAllowRadioButton.setChecked(allow) - self.policyDenyRadioButton.setChecked(not allow) + self.policy_allow_radio_button.setChecked(allow) + self.policy_deny_radio_button.setChecked(not allow) self.policy_changed(allow) - def policy_changed(self, checked): - self.rulesTreeView.setEnabled(self.policyDenyRadioButton.isChecked()) - self.newRuleButton.setEnabled(self.policyDenyRadioButton.isChecked()) - self.editRuleButton.setEnabled(self.policyDenyRadioButton.isChecked()) - self.deleteRuleButton.setEnabled(self.policyDenyRadioButton.isChecked()) - self.firewalRulesLabel.setEnabled( - self.policyDenyRadioButton.isChecked()) + def policy_changed(self): + self.rulesTreeView.setEnabled( + self.policy_deny_radio_button.isChecked()) + self.new_rule_button.setEnabled( + self.policy_deny_radio_button.isChecked()) + self.edit_rule_button.setEnabled( + self.policy_deny_radio_button.isChecked()) + self.delete_rule_button.setEnabled( + self.policy_deny_radio_button.isChecked()) + self.firewal_rules_label.setEnabled( + self.policy_deny_radio_button.isChecked()) self.tempFullAccessWidget.setEnabled( - self.policyDenyRadioButton.isChecked()) + self.policy_deny_radio_button.isChecked()) def new_rule_button_pressed(self): dialog = firewall.NewFwRuleDlg() @@ -917,7 +921,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): if len(selected) > 0: dialog = firewall.NewFwRuleDlg() - dialog.set_ok_enabled(True) + dialog.set_ok_state(True) row = self.rulesTreeView.selectedIndexes().pop().row() self.fw_model.populate_edit_dialog(dialog, row) self.fw_model.run_rule_dialog(dialog, row) @@ -925,7 +929,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): def delete_rule_button_pressed(self): for i in set([index.row() for index in self.rulesTreeView.selectedIndexes()]): - self.fw_model.removeChild(i) + self.fw_model.remove_child(i) # Bases on the original code by: diff --git a/ui/settingsdlg.ui b/ui/settingsdlg.ui index 1a0eca8..d3f1225 100644 --- a/ui/settingsdlg.ui +++ b/ui/settingsdlg.ui @@ -7,7 +7,7 @@ 0 0 773 - 581 + 609 @@ -29,7 +29,7 @@ - 0 + 1 @@ -487,7 +487,7 @@ border-width: 1px; - + Boot qube from CDROM @@ -678,14 +678,14 @@ border-width: 1px; - + Allow all outgoing Internet connections - + Limit outgoing Internet connections to ... @@ -714,7 +714,7 @@ border-width: 1px; - + List of allowed (whitelisted) addresses: @@ -736,14 +736,14 @@ border-width: 1px; 0 - + Allow full access for - + min @@ -811,7 +811,7 @@ border-width: 1px; - + @@ -828,7 +828,7 @@ border-width: 1px; - + @@ -845,7 +845,7 @@ border-width: 1px; - + @@ -879,7 +879,7 @@ border-width: 1px; - + @@ -1117,10 +1117,10 @@ border-width: 1px; vcpus include_in_balancing kernel - newRuleButton + new_rule_button rulesTreeView - editRuleButton - deleteRuleButton + edit_rule_button + delete_rule_button service_line_edit add_srv_button services_list From 04aa844a9dfea301e486834528275bd6578cdf01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Thu, 9 Nov 2017 15:38:03 +0100 Subject: [PATCH 19/70] Removed variable "anything_changed" It was assigned in 17 places and not used otherwise in any other place. It's highly likely it was useless. --- qubesmanager/settings.py | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index 77fa87e..5374e40 100755 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -173,8 +173,6 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): def __save_changes__(self, t_monitor): - self.anything_changed = False - ret = [] try: ret_tmp = self.__apply_basic_tab__() @@ -200,9 +198,6 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): self.policy_allow_radio_button.isChecked(), self.temp_full_access.isChecked(), self.temp_full_access_time.value()) - if self.fw_model.fw_changed: - # might modified vm.services - self.anything_changed = True except qubesadmin.exc.QubesException as qex: ret+= [self.tr("Firewall tab:"), str(qex)] except Exception as ex: # pylint: disable=broad-except @@ -348,7 +343,6 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): if self.vmlabel.currentIndex() != self.label_idx: label = self.label_list[self.vmlabel.currentIndex()] self.vm.label = label - self.anything_changed = True except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) @@ -357,7 +351,6 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): if self.template_name.currentIndex() != self.template_idx: self.vm.template = \ self.template_list[self.template_name.currentIndex()] - self.anything_changed = True except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) @@ -365,7 +358,6 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): try: if self.netVM.currentIndex() != self.netvm_idx: self.vm.netvm = self.netvm_list[self.netVM.currentIndex()] - self.anything_changed = True except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) @@ -374,7 +366,6 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): if self.vm.include_in_backups != \ self.include_in_backups.isChecked(): self.vm.include_in_backups = self.include_in_backups.isChecked() - self.anything_changed = True except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) @@ -383,7 +374,6 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): if self.run_in_debug_mode.isVisible(): if self.vm.debug != self.run_in_debug_mode.isChecked(): self.vm.debug = self.run_in_debug_mode.isChecked() - self.anything_changed = True except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) @@ -392,7 +382,6 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): if self.autostart_vm.isVisible(): if self.vm.autostart != self.autostart_vm.isChecked(): self.vm.autostart = self.autostart_vm.isChecked() - self.anything_changed = True except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) @@ -401,7 +390,6 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): if self.priv_img_size != priv_size: try: self.vm.volumes['private'].resize(priv_size * 1024**2) - self.anything_changed = True except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) @@ -410,7 +398,6 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): if self.root_img_size != sys_size: try: self.vm.volumes['root'].resize(sys_size * 1024**2) - self.anything_changed = True except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) @@ -588,15 +575,12 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): try: if self.init_mem.value() != int(self.vm.memory): self.vm.memory = self.init_mem.value() - self.anything_changed = True if self.max_mem_size.value() != int(self.vm.maxmem): self.vm.maxmem = self.max_mem_size.value() - self.anything_changed = True if self.vcpus.value() != int(self.vm.vcpus): self.vm.vcpus = self.vcpus.value() - self.anything_changed = True except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) @@ -608,7 +592,6 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): if self.kernel.currentIndex() != self.kernel_idx: self.vm.kernel = self.kernel_list[ self.kernel.currentIndex()] - self.anything_changed = True except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) @@ -617,7 +600,6 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): if self.default_dispvm.currentIndex() != self.default_dispvm_idx: self.vm.default_dispvm = \ self.default_dispvm_list[self.default_dispvm.currentIndex()] - self.anything_changed = True except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) @@ -691,8 +673,6 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): if ass.ident.replace('_', ':') not in new: self.vm.devices['pci'].detach(ass) - self.anything_changed = True - except qubesadmin.exc.QubesException as ex: if utils.is_debug(): traceback.print_exc() @@ -852,7 +832,6 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): feature = 'service.' + service if v != self.vm.features.get(feature, object()): self.vm.features[feature] = v - self.anything_changed = True for feature in self.vm.features: if not feature.startswith('service.'): From b9e4f99fecae6a3120b46eb30facc47a4a434a7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Thu, 9 Nov 2017 16:08:16 +0100 Subject: [PATCH 20/70] Fixed unusued arguments Places where the variable is used in a overriding method were marked for pylint to ignore them. --- qubesmanager/appmenu_select.py | 2 +- qubesmanager/firewall.py | 6 +++--- qubesmanager/multiselectwidget.py | 2 +- qubesmanager/settings.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/qubesmanager/appmenu_select.py b/qubesmanager/appmenu_select.py index b14c487..f241fc6 100755 --- a/qubesmanager/appmenu_select.py +++ b/qubesmanager/appmenu_select.py @@ -38,7 +38,7 @@ class AppListWidgetItem(PyQt4.QtGui.QListWidgetItem): class AppmenuSelectManager: - def __init__(self, vm, apps_multiselect, parent=None): + def __init__(self, vm, apps_multiselect): self.vm = vm self.app_list = apps_multiselect # this is a multiselect wiget self.whitelisted = None diff --git a/qubesmanager/firewall.py b/qubesmanager/firewall.py index ed60ded..6992afa 100644 --- a/qubesmanager/firewall.py +++ b/qubesmanager/firewall.py @@ -409,14 +409,14 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): return self.createIndex(row, column, self.children[row]) - def parent(self, child): + def parent(self, child): # pylint: disable=unused-argument return QtCore.QModelIndex() - # pylint: disable=invalid-name + # pylint: disable=invalid-name,unused-argument def rowCount(self, parent=QtCore.QModelIndex()): return len(self) - # pylint: disable=invalid-name + # pylint: disable=invalid-name,unused-argument def columnCount(self, parent=QtCore.QModelIndex()): return len(self.__column_names) diff --git a/qubesmanager/multiselectwidget.py b/qubesmanager/multiselectwidget.py index 24e657a..9754847 100644 --- a/qubesmanager/multiselectwidget.py +++ b/qubesmanager/multiselectwidget.py @@ -9,7 +9,7 @@ class MultiSelectWidget( __pyqtSignals__ = ("items_removed(PyQt_PyObject)",) def __init__(self, parent=None): - super(MultiSelectWidget, self).__init__() + super(MultiSelectWidget, self).__init__(parent) self.setupUi(self) self.add_selected_button.clicked.connect(self.add_selected) self.add_all_button.clicked.connect(self.add_all) diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index 5374e40..b5faa3d 100755 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -874,7 +874,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): def set_allow(self, allow): self.policy_allow_radio_button.setChecked(allow) self.policy_deny_radio_button.setChecked(not allow) - self.policy_changed(allow) + self.policy_changed() def policy_changed(self): self.rulesTreeView.setEnabled( From 3f7f5a9da3799b0343c102b66b7fb6271eb21bec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Thu, 9 Nov 2017 16:11:03 +0100 Subject: [PATCH 21/70] Firewall GUI fix Fixed error when service would be left empty, but the settings window would nonetheless emit an error. --- qubesmanager/firewall.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qubesmanager/firewall.py b/qubesmanager/firewall.py index 6992afa..d7b411a 100644 --- a/qubesmanager/firewall.py +++ b/qubesmanager/firewall.py @@ -386,7 +386,7 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): self.tr("Invalid port or service"), self.tr("Port number or service '{0}' is invalid.") .format(service)) - elif service is not None: + elif service is not None and service != "": try: rule.dstports = service except (TypeError, ValueError) as ex: From 695303f16ae9c3647734506cd6976d71a8889d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Thu, 9 Nov 2017 16:26:05 +0100 Subject: [PATCH 22/70] Fixed unused variables When possible removed, otherwise renamed to start with an underscore. --- qubesmanager/appmenu_select.py | 2 +- qubesmanager/firewall.py | 2 +- qubesmanager/settings.py | 2 +- qubesmanager/utils.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qubesmanager/appmenu_select.py b/qubesmanager/appmenu_select.py index f241fc6..3cf6c10 100755 --- a/qubesmanager/appmenu_select.py +++ b/qubesmanager/appmenu_select.py @@ -33,7 +33,7 @@ class AppListWidgetItem(PyQt4.QtGui.QListWidgetItem): @classmethod def from_line(cls, line): - ident, icon_name, name = line.strip().split(maxsplit=2) + ident, _icon_name, name = line.strip().split(maxsplit=2) return cls(name=name, ident=ident) diff --git a/qubesmanager/firewall.py b/qubesmanager/firewall.py index d7b411a..9a8427f 100644 --- a/qubesmanager/firewall.py +++ b/qubesmanager/firewall.py @@ -389,7 +389,7 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): elif service is not None and service != "": try: rule.dstports = service - except (TypeError, ValueError) as ex: + except (TypeError, ValueError): if self.get_service_port(service) is not None: rule.dstports = self.get_service_port(service) else: diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index b5faa3d..cc53ebe 100755 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -106,7 +106,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): model.set_vm(vm) self.set_fw_model(model) self.firewall_modified_outside_label.setVisible(False) - except firewall.FirewallModifiedOutsideError as ex: + except firewall.FirewallModifiedOutsideError: self.disable_all_fw_conf() self.new_rule_button.clicked.connect(self.new_rule_button_pressed) diff --git a/qubesmanager/utils.py b/qubesmanager/utils.py index b67ddf7..115ab8f 100644 --- a/qubesmanager/utils.py +++ b/qubesmanager/utils.py @@ -150,7 +150,7 @@ def get_path_from_vm(vm, service_name): if not vm: return None - stdout, stderr = vm.run_service_for_stdio(service_name) + stdout, _stderr = vm.run_service_for_stdio(service_name) untrusted_path = stdout.decode(encoding='ascii')[:path_max_len] From b7060e76b2e16fda4e7482b58208131aefc8bc1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Thu, 9 Nov 2017 16:34:09 +0100 Subject: [PATCH 23/70] Fixed no-self-use error Turned methods into static methods when possible. --- qubesmanager/firewall.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/qubesmanager/firewall.py b/qubesmanager/firewall.py index 9a8427f..1978f31 100644 --- a/qubesmanager/firewall.py +++ b/qubesmanager/firewall.py @@ -35,6 +35,7 @@ class QIPAddressValidator(QtGui.QValidator): super(QIPAddressValidator, self).__init__(parent) def validate(self, input, pos): + # pylint: disable=too-many-return-statements,no-self-use hostname = str(input) if len(hostname) > 255 or len(hostname) == 0: @@ -212,7 +213,8 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): return str(rule.proto) return "unknown" - def get_firewall_conf(self, vm): + @staticmethod + def get_firewall_conf(vm): conf = { 'allow': None, 'expire': 0, @@ -276,7 +278,8 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): return conf - def write_firewall_conf(self, vm, conf): + @staticmethod + def write_firewall_conf(vm, conf): rules = [] for rule in conf['rules']: @@ -409,7 +412,7 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): return self.createIndex(row, column, self.children[row]) - def parent(self, child): # pylint: disable=unused-argument + def parent(self, child): # pylint: disable=unused-argument,no-self-use return QtCore.QModelIndex() # pylint: disable=invalid-name,unused-argument @@ -420,7 +423,7 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): def columnCount(self, parent=QtCore.QModelIndex()): return len(self.__column_names) - # pylint: disable=invalid-name + # pylint: disable=invalid-name,no-self-use def hasChildren(self, index=QtCore.QModelIndex()): parent_item = index.internalPointer() if parent_item is not None: From ff4ee713b1c3a1255e76b8cc5339162c0fbb7695 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Thu, 9 Nov 2017 16:39:30 +0100 Subject: [PATCH 24/70] Removed unnecessary global variables Removed three variables set to global, which were all used only in their respective "main" function. --- qubesmanager/bootfromdevice.py | 2 -- qubesmanager/global_settings.py | 3 --- qubesmanager/settings.py | 2 -- 3 files changed, 7 deletions(-) diff --git a/qubesmanager/bootfromdevice.py b/qubesmanager/bootfromdevice.py index b33e6d4..be1d4c0 100644 --- a/qubesmanager/bootfromdevice.py +++ b/qubesmanager/bootfromdevice.py @@ -114,8 +114,6 @@ class VMBootFromDeviceWindow(ui_bootfromdevice.Ui_BootDialog, QtGui.QDialog): parser = firewall.qubesadmin.tools.QubesArgumentParser(vmname_nargs=1) def main(args=None): - global bootfromdevice_window - args = parser.parse_args(args) vm = args.domains.pop() diff --git a/qubesmanager/global_settings.py b/qubesmanager/global_settings.py index 2863f9f..96f0fab 100644 --- a/qubesmanager/global_settings.py +++ b/qubesmanager/global_settings.py @@ -314,8 +314,6 @@ def handle_exception(exc_type, exc_value, exc_traceback): def main(): - - global qtapp qtapp = QtGui.QApplication(sys.argv) qtapp.setOrganizationName("The Qubes Project") qtapp.setOrganizationDomain("http://qubes-os.org") @@ -325,7 +323,6 @@ def main(): app = Qubes() - global global_window global_window = GlobalSettingsWindow(qtapp, app) global_window.show() diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index cc53ebe..d3541bb 100755 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -954,8 +954,6 @@ parser.set_defaults( ) def main(args=None): - global settings_window - args = parser.parse_args(args) vm = args.domains.pop() From 0115c931d27f7a00a2256d96f9d1037bc2a0b76b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Thu, 9 Nov 2017 16:54:46 +0100 Subject: [PATCH 25/70] Variables defined outside init Defined them inside init and added description. --- qubesmanager/firewall.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qubesmanager/firewall.py b/qubesmanager/firewall.py index 1978f31..1639a42 100644 --- a/qubesmanager/firewall.py +++ b/qubesmanager/firewall.py @@ -156,6 +156,10 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): file.close() self.fw_changed = False + self.allow = None # is the default policy allow or deny + self.temp_full_access_expire_time = None # temporary full access time + self.__vm = None # VM that the model applies to + self.__children = None # list of rules in the FW def sort(self, idx, order): from operator import attrgetter From 59a9b7da402a48af4fe35f2c575b1d2a77ce9032 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Thu, 9 Nov 2017 17:09:55 +0100 Subject: [PATCH 26/70] Redefined variable type Marked places where redefining variable type was actually sensible as such. --- qubesmanager/global_settings.py | 2 +- qubesmanager/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qubesmanager/global_settings.py b/qubesmanager/global_settings.py index 96f0fab..5e3ea60 100644 --- a/qubesmanager/global_settings.py +++ b/qubesmanager/global_settings.py @@ -179,7 +179,7 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, def __init_mem_defaults__(self): - + # pylint: disable=redefined-variable-type #qmemman settings self.qmemman_config = ConfigParser() self.vm_min_mem_val = '200MiB' #str(qmemman_algo.MIN_PREFMEM) diff --git a/qubesmanager/utils.py b/qubesmanager/utils.py index 115ab8f..6138139 100644 --- a/qubesmanager/utils.py +++ b/qubesmanager/utils.py @@ -65,7 +65,7 @@ def prepare_choice(widget, holder, propname, choice, default, choice_list = filter(_filter_internal, choice_list) if filter_function is not None: choice_list = filter(filter_function, choice_list) - choice_list = list(choice_list) + choice_list = list(choice_list) # pylint: disable=redefined-variable-type if allow_default: choice_list.insert(0, qubesadmin.DEFAULT) From 6670f59853a548c8163bc68ea8b8949539861215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Thu, 9 Nov 2017 17:13:56 +0100 Subject: [PATCH 27/70] Fixed regex strings Fixed regex strings that were not marked as raw. --- qubesmanager/firewall.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/qubesmanager/firewall.py b/qubesmanager/firewall.py index 1639a42..8db6ba8 100644 --- a/qubesmanager/firewall.py +++ b/qubesmanager/firewall.py @@ -49,7 +49,7 @@ class QIPAddressValidator(QtGui.QValidator): hostname = unmask[0] mask = unmask[1] if mask.isdigit() or mask == "": - if re.match("^([0-9]{1,3}\.){3}[0-9]{1,3}$", hostname) is None: + if re.match(r"^([0-9]{1,3}\.){3}[0-9]{1,3}$", hostname) is None: return (QtGui.QValidator.Invalid, input, pos) if mask != "": mask = int(unmask[1]) @@ -64,7 +64,7 @@ class QIPAddressValidator(QtGui.QValidator): if hostname[-1:] == "-": return (QtGui.QValidator.Intermediate, input, pos) - allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?[a-z][a-z0-9-]+)\s+(?P[0-9]+)/(?P[a-z]+)", + r"(?P[a-z][a-z0-9-]+)\s+(?P[0-9]+)/" + r"(?P[a-z]+)", re.IGNORECASE) file = open('/etc/services', 'r') for line in file: From 3d841cf14f2da6f8e808587417427d5cc72e0338 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Thu, 9 Nov 2017 17:19:17 +0100 Subject: [PATCH 28/70] Checked too-many-returns error Checked both places with a 'too-many-return-statements' warning, both were sensible, marked them as such. --- qubesmanager/firewall.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qubesmanager/firewall.py b/qubesmanager/firewall.py index 8db6ba8..a825c86 100644 --- a/qubesmanager/firewall.py +++ b/qubesmanager/firewall.py @@ -187,6 +187,7 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): return None def get_column_string(self, col, rule): + # pylint: disable=too-many-return-statements # Address if col == 0: if rule.dsthost is None: From e543144aec15f61ffbdddb9a10e28e1ef935f759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Thu, 9 Nov 2017 17:23:23 +0100 Subject: [PATCH 29/70] Too-few-public-methods warning Checked, are sensible, disabled pylint warnings. --- qubesmanager/appmenu_select.py | 1 + qubesmanager/settings.py | 1 + 2 files changed, 2 insertions(+) diff --git a/qubesmanager/appmenu_select.py b/qubesmanager/appmenu_select.py index 3cf6c10..0e2f8be 100755 --- a/qubesmanager/appmenu_select.py +++ b/qubesmanager/appmenu_select.py @@ -25,6 +25,7 @@ import PyQt4.QtGui # TODO description in tooltip # TODO icon +# pylint: disable=too-few-public-methods class AppListWidgetItem(PyQt4.QtGui.QListWidgetItem): def __init__(self, name, ident, parent=None): super(AppListWidgetItem, self).__init__(name, parent) diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index d3541bb..e5c30ac 100755 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -620,6 +620,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): for dev in lspci.splitlines(): devs.append((dev.rstrip(), dev.split(' ')[0])) + # pylint: disable=too-few-public-methods class DevListWidgetItem(QtGui.QListWidgetItem): def __init__(self, name, ident, parent=None): super(DevListWidgetItem, self).__init__(name, parent) From 95599dff9fa7a706e440ae081915c65d5421fa43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Thu, 9 Nov 2017 17:30:22 +0100 Subject: [PATCH 30/70] A bunch of misc errors Fixed a bunch of small errors: bad indents, superfluous parentheses, missing whitespace, superfluous statement. --- qubesmanager/firewall.py | 2 +- qubesmanager/settings.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/qubesmanager/firewall.py b/qubesmanager/firewall.py index a825c86..a5d9d75 100644 --- a/qubesmanager/firewall.py +++ b/qubesmanager/firewall.py @@ -447,7 +447,7 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): if section < len(self.__column_names) \ and orientation == QtCore.Qt.Horizontal \ and role == QtCore.Qt.DisplayRole: - return self.__column_names[section] + return self.__column_names[section] @property def children(self): diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index e5c30ac..0599237 100755 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -67,7 +67,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): self.setWindowTitle(self.tr("Settings: {vm}").format(vm=self.vm.name)) if init_page in self.tabs_indices: idx = self.tabs_indices[init_page] - assert (idx in range(self.tabWidget.count())) + assert idx in range(self.tabWidget.count()) self.tabWidget.setCurrentIndex(idx) self.connect(self.buttonBox, @@ -199,7 +199,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): self.temp_full_access.isChecked(), self.temp_full_access_time.value()) except qubesadmin.exc.QubesException as qex: - ret+= [self.tr("Firewall tab:"), str(qex)] + ret += [self.tr("Firewall tab:"), str(qex)] except Exception as ex: # pylint: disable=broad-except ret += [self.tr("Firewall tab:"), repr(ex)] @@ -625,7 +625,6 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): def __init__(self, name, ident, parent=None): super(DevListWidgetItem, self).__init__(name, parent) self.ident = ident - self.Type persistent = [ass.ident.replace('_', ':') for ass in self.vm.devices['pci'].persistent()] From 8b296fab0d19070827e54a5f9577e10bb144172e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Thu, 9 Nov 2017 17:32:40 +0100 Subject: [PATCH 31/70] Disable too-many-attributes warning It's the settings window, it has a lot of attributes. --- qubesmanager/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index 0599237..cb93ee6 100755 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -43,6 +43,7 @@ from PyQt4 import QtCore, QtGui from . import ui_settingsdlg +# pylint: disable=too-many-instance-attributes class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): tabs_indices = collections.OrderedDict(( ('basic', 0), From 8cfc93c70c3c3348a886ff9c3df5ac5f61d3dba2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Thu, 9 Nov 2017 17:57:21 +0100 Subject: [PATCH 32/70] Misc errors part 2 Renamed variable from 'input' to 'input_string' and moved imports to a better place (that is, at the beginning of the module and not inside of a function). --- qubesmanager/firewall.py | 20 ++++++++++---------- qubesmanager/global_settings.py | 5 ++--- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/qubesmanager/firewall.py b/qubesmanager/firewall.py index a5d9d75..68b5cc0 100644 --- a/qubesmanager/firewall.py +++ b/qubesmanager/firewall.py @@ -34,15 +34,15 @@ class QIPAddressValidator(QtGui.QValidator): def __init__(self, parent=None): super(QIPAddressValidator, self).__init__(parent) - def validate(self, input, pos): + def validate(self, input_string, pos): # pylint: disable=too-many-return-statements,no-self-use - hostname = str(input) + hostname = str(input_string) if len(hostname) > 255 or len(hostname) == 0: - return (QtGui.QValidator.Intermediate, input, pos) + return (QtGui.QValidator.Intermediate, input_string, pos) if hostname == "*": - return (QtGui.QValidator.Acceptable, input, pos) + return (QtGui.QValidator.Acceptable, input_string, pos) unmask = hostname.split("/", 1) if len(unmask) == 2: @@ -50,25 +50,25 @@ class QIPAddressValidator(QtGui.QValidator): mask = unmask[1] if mask.isdigit() or mask == "": if re.match(r"^([0-9]{1,3}\.){3}[0-9]{1,3}$", hostname) is None: - return (QtGui.QValidator.Invalid, input, pos) + return (QtGui.QValidator.Invalid, input_string, pos) if mask != "": mask = int(unmask[1]) if mask < 0 or mask > 32: - return (QtGui.QValidator.Invalid, input, pos) + return (QtGui.QValidator.Invalid, input_string, pos) else: - return (QtGui.QValidator.Invalid, input, pos) + return (QtGui.QValidator.Invalid, input_string, pos) if hostname[-1:] == ".": hostname = hostname[:-1] if hostname[-1:] == "-": - return (QtGui.QValidator.Intermediate, input, pos) + return (QtGui.QValidator.Intermediate, input_string, pos) allowed = re.compile(r"(?!-)[A-Z\d-]{1,63}(? def handle_exception(exc_type, exc_value, exc_traceback): - 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) From c708830af2e77c3979a5bcf7b3b2946ad4310a5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Thu, 9 Nov 2017 18:34:29 +0100 Subject: [PATCH 33/70] Configuration files for travis Config file and package stubs, required for travis to check module correctness with pylint. --- .travis.yml | 13 ++++++++++--- .pylintrc => ci/pylintrc | 0 ci/requirements.txt | 11 +++++++++++ test-packages/qubesadmin/DEFAULT.py | 1 + test-packages/qubesadmin/__init__.py | 2 ++ test-packages/qubesadmin/devices.py | 4 ++++ test-packages/qubesadmin/exc.py | 4 ++++ test-packages/qubesadmin/firewall.py | 4 ++++ test-packages/qubesadmin/tools/__init__.py | 9 +++++++++ test-packages/qubesadmin/tools/qvm_start.py | 4 ++++ test-packages/qubesadmin/utils.py | 7 +++++++ 11 files changed, 56 insertions(+), 3 deletions(-) rename .pylintrc => ci/pylintrc (100%) create mode 100644 ci/requirements.txt create mode 100644 test-packages/qubesadmin/DEFAULT.py create mode 100644 test-packages/qubesadmin/__init__.py create mode 100644 test-packages/qubesadmin/devices.py create mode 100644 test-packages/qubesadmin/exc.py create mode 100644 test-packages/qubesadmin/firewall.py create mode 100644 test-packages/qubesadmin/tools/__init__.py create mode 100644 test-packages/qubesadmin/tools/qvm_start.py create mode 100644 test-packages/qubesadmin/utils.py diff --git a/.travis.yml b/.travis.yml index 5e373ba..49c5808 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,14 @@ sudo: required dist: trusty -language: generic -install: git clone https://github.com/QubesOS/qubes-builder ~/qubes-builder -script: ~/qubes-builder/scripts/travis-build +language: python +python: + - '3.5' +install: + - sudo apt-get install python-qt4 pyqt4-dev-tools + - pip install --quiet -r ci/requirements.txt + - git clone https://github.com/QubesOS/qubes-builder ~/qubes-builder +script: + - PYTHONPATH=test-packages pylint --rcfile=ci/pylintrc qubesmanager + - ~/qubes-builder/scripts/travis-build env: - DIST_DOM0=fc25 USE_QUBES_REPO_VERSION=4.0 USE_QUBES_REPO_TESTING=1 diff --git a/.pylintrc b/ci/pylintrc similarity index 100% rename from .pylintrc rename to ci/pylintrc diff --git a/ci/requirements.txt b/ci/requirements.txt new file mode 100644 index 0000000..ac54cec --- /dev/null +++ b/ci/requirements.txt @@ -0,0 +1,11 @@ +# WARNING: those requirements are used only for travis-ci.org +# they SHOULD NOT be used under normal conditions; use system package manager +coverage +codecov +docutils +jinja2 +lxml +pylint +sphinx +pydbus +PyYAML \ No newline at end of file diff --git a/test-packages/qubesadmin/DEFAULT.py b/test-packages/qubesadmin/DEFAULT.py new file mode 100644 index 0000000..c3b69bd --- /dev/null +++ b/test-packages/qubesadmin/DEFAULT.py @@ -0,0 +1 @@ +### mock qubesadmin.DEFAULT module \ No newline at end of file diff --git a/test-packages/qubesadmin/__init__.py b/test-packages/qubesadmin/__init__.py new file mode 100644 index 0000000..d9c1b4d --- /dev/null +++ b/test-packages/qubesadmin/__init__.py @@ -0,0 +1,2 @@ +class Qubes(object): + pass \ No newline at end of file diff --git a/test-packages/qubesadmin/devices.py b/test-packages/qubesadmin/devices.py new file mode 100644 index 0000000..e31e5c3 --- /dev/null +++ b/test-packages/qubesadmin/devices.py @@ -0,0 +1,4 @@ +### mock qubesadmin.devices module + +class DeviceAssignment(object): + pass \ No newline at end of file diff --git a/test-packages/qubesadmin/exc.py b/test-packages/qubesadmin/exc.py new file mode 100644 index 0000000..78835e2 --- /dev/null +++ b/test-packages/qubesadmin/exc.py @@ -0,0 +1,4 @@ +### mock qubesadmin.exc module + +class QubesException(BaseException): + pass \ No newline at end of file diff --git a/test-packages/qubesadmin/firewall.py b/test-packages/qubesadmin/firewall.py new file mode 100644 index 0000000..07a5151 --- /dev/null +++ b/test-packages/qubesadmin/firewall.py @@ -0,0 +1,4 @@ +### mock qubesadmin.firewall module + +class Rule(object): + pass \ No newline at end of file diff --git a/test-packages/qubesadmin/tools/__init__.py b/test-packages/qubesadmin/tools/__init__.py new file mode 100644 index 0000000..c448ad0 --- /dev/null +++ b/test-packages/qubesadmin/tools/__init__.py @@ -0,0 +1,9 @@ +### mock qubesadmin.tools module + +class QubesArgumentParser(object): + + def add_argument(self, *args, **kwargs): + pass + + def set_defaults(self, *args, **kwargs): + pass diff --git a/test-packages/qubesadmin/tools/qvm_start.py b/test-packages/qubesadmin/tools/qvm_start.py new file mode 100644 index 0000000..4385323 --- /dev/null +++ b/test-packages/qubesadmin/tools/qvm_start.py @@ -0,0 +1,4 @@ +### mock qvm_start module + +def main(*args, **kwargs): + pass \ No newline at end of file diff --git a/test-packages/qubesadmin/utils.py b/test-packages/qubesadmin/utils.py new file mode 100644 index 0000000..bcf4711 --- /dev/null +++ b/test-packages/qubesadmin/utils.py @@ -0,0 +1,7 @@ +### mock qubesadmin.utils module + +def parse_size(*args, **kwargs): + return args[0] + +def updates_vms_status(*args, **kwargs): + return args[0] \ No newline at end of file From 135060dfe75b0de7dd70022783d0727b6b9ea879 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Tue, 14 Nov 2017 15:29:57 +0100 Subject: [PATCH 34/70] Final corrections Errors that my local pylint ignored and travis' pylint didn't. --- ci/pylintrc | 1 + qubesmanager/appmenu_select.py | 2 +- qubesmanager/bootfromdevice.py | 4 ++-- qubesmanager/create_new_vm.py | 6 ++--- qubesmanager/firewall.py | 37 ++++++++++++------------------- qubesmanager/global_settings.py | 5 ++--- qubesmanager/multiselectwidget.py | 5 ++--- qubesmanager/settings.py | 18 +++++++-------- qubesmanager/thread_monitor.py | 3 +-- qubesmanager/utils.py | 6 ++--- 10 files changed, 38 insertions(+), 49 deletions(-) diff --git a/ci/pylintrc b/ci/pylintrc index a7ae4d5..d8bf535 100644 --- a/ci/pylintrc +++ b/ci/pylintrc @@ -32,6 +32,7 @@ disable= logging-format-interpolation, missing-docstring, star-args, + useless-super-delegation, wrong-import-order [REPORTS] diff --git a/qubesmanager/appmenu_select.py b/qubesmanager/appmenu_select.py index 0e2f8be..f042011 100755 --- a/qubesmanager/appmenu_select.py +++ b/qubesmanager/appmenu_select.py @@ -21,7 +21,7 @@ import subprocess -import PyQt4.QtGui +import PyQt4.QtGui # pylint: disable=import-error # TODO description in tooltip # TODO icon diff --git a/qubesmanager/bootfromdevice.py b/qubesmanager/bootfromdevice.py index be1d4c0..975364e 100644 --- a/qubesmanager/bootfromdevice.py +++ b/qubesmanager/bootfromdevice.py @@ -21,8 +21,8 @@ import sys import subprocess from . import utils from . import firewall -from . import ui_bootfromdevice -from PyQt4 import QtGui, QtCore +from . import ui_bootfromdevice # pylint: disable=no-name-in-module +from PyQt4 import QtGui, QtCore # pylint: disable=import-error import qubesadmin.tools.qvm_start as qvm_start diff --git a/qubesmanager/create_new_vm.py b/qubesmanager/create_new_vm.py index f8f29aa..d31c5db 100644 --- a/qubesmanager/create_new_vm.py +++ b/qubesmanager/create_new_vm.py @@ -26,7 +26,7 @@ import threading import time import subprocess -from PyQt4 import QtCore, QtGui +from PyQt4 import QtCore, QtGui # pylint: disable=import-error import qubesadmin import qubesadmin.tools @@ -34,7 +34,7 @@ import qubesadmin.exc from . import utils -from .ui_newappvmdlg import Ui_NewVMDlg +from .ui_newappvmdlg import Ui_NewVMDlg # pylint: disable=import-error from .thread_monitor import ThreadMonitor @@ -75,7 +75,7 @@ class NewVmDlg(QtGui.QDialog, Ui_NewVMDlg): self.name.selectAll() self.name.setFocus() - if len(self.template_list) == 0: + if not self.template_list: QtGui.QMessageBox.warning(None, self.tr('No template available!'), self.tr('Cannot create a qube when no template exists.')) diff --git a/qubesmanager/firewall.py b/qubesmanager/firewall.py index 68b5cc0..88ef311 100644 --- a/qubesmanager/firewall.py +++ b/qubesmanager/firewall.py @@ -21,16 +21,17 @@ import datetime import re -from PyQt4 import QtCore, QtGui +from PyQt4 import QtCore, QtGui # pylint: disable=import-error import qubesadmin.firewall -from . import ui_newfwruledlg +from . import ui_newfwruledlg # pylint: disable=no-name-in-module class FirewallModifiedOutsideError(ValueError): pass class QIPAddressValidator(QtGui.QValidator): + # pylint: disable=too-few-public-methods def __init__(self, parent=None): super(QIPAddressValidator, self).__init__(parent) @@ -38,7 +39,7 @@ class QIPAddressValidator(QtGui.QValidator): # pylint: disable=too-many-return-statements,no-self-use hostname = str(input_string) - if len(hostname) > 255 or len(hostname) == 0: + if len(hostname) > 255 or not hostname: return (QtGui.QValidator.Intermediate, input_string, pos) if hostname == "*": @@ -89,7 +90,7 @@ class NewFwRuleDlg(QtGui.QDialog, ui_newfwruledlg.Ui_NewFwRuleDlg): def accept(self): if self.tcp_radio.isChecked() or self.udp_radio.isChecked(): - if len(self.serviceComboBox.currentText()) == 0: + if not self.serviceComboBox.currentText(): msg = QtGui.QMessageBox() msg.warning(self, self.tr("Firewall rule"), self.tr("You need to fill service " @@ -163,8 +164,6 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): self.__children = None # list of rules in the FW def sort(self, idx, order): - from operator import attrgetter - rev = (order == QtCore.Qt.AscendingOrder) self.children.sort(key=lambda x: self.get_column_string(idx, x) , reverse=rev) @@ -192,31 +191,25 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): if col == 0: if rule.dsthost is None: return "*" - else: - if rule.dsthost.type == 'dst4'\ - and rule.dsthost.prefixlen == '32': - return str(rule.dsthost)[:-3] - elif rule.dsthost.type == 'dst6'\ - and rule.dsthost.prefixlen == '128': - return str(rule.dsthost)[:-4] - else: - return str(rule.dsthost) + if rule.dsthost.type == 'dst4' and rule.dsthost.prefixlen == '32': + return str(rule.dsthost)[:-3] + if rule.dsthost.type == 'dst6' and rule.dsthost.prefixlen == '128': + return str(rule.dsthost)[:-4] + return str(rule.dsthost) # Service if col == 1: if rule.dstports is None: return "any" - elif rule.dstports.range[0] != rule.dstports.range[1]: + if rule.dstports.range[0] != rule.dstports.range[1]: return str(rule.dstports) - else: - return self.get_service_name(rule.dstports) + return self.get_service_name(rule.dstports) # Protocol if col == 2: if rule.proto is None: return "any" - else: - return str(rule.proto) + return str(rule.proto) return "unknown" @staticmethod @@ -434,8 +427,7 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): parent_item = index.internalPointer() if parent_item is not None: return False - else: - return True + return True def data(self, index, role=QtCore.Qt.DisplayRole): if index.isValid() and role == QtCore.Qt.DisplayRole: @@ -484,4 +476,3 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): def __len__(self): return len(self.children) - diff --git a/qubesmanager/global_settings.py b/qubesmanager/global_settings.py index 661cf18..0526546 100644 --- a/qubesmanager/global_settings.py +++ b/qubesmanager/global_settings.py @@ -24,12 +24,12 @@ import sys import os import os.path import traceback -from PyQt4 import QtCore, QtGui +from PyQt4 import QtCore, QtGui # pylint: disable=import-error from qubesadmin import Qubes from qubesadmin.utils import parse_size, updates_vms_status -from . import ui_globalsettingsdlg +from . import ui_globalsettingsdlg # pylint: disable=no-name-in-module from configparser import ConfigParser @@ -181,7 +181,6 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, def __init_mem_defaults__(self): - # pylint: disable=redefined-variable-type #qmemman settings self.qmemman_config = ConfigParser() self.vm_min_mem_val = '200MiB' #str(qmemman_algo.MIN_PREFMEM) diff --git a/qubesmanager/multiselectwidget.py b/qubesmanager/multiselectwidget.py index 9754847..c733889 100644 --- a/qubesmanager/multiselectwidget.py +++ b/qubesmanager/multiselectwidget.py @@ -1,5 +1,5 @@ -from PyQt4 import QtCore, QtGui -from . import ui_multiselectwidget +from PyQt4 import QtCore, QtGui # pylint: disable=import-error +from . import ui_multiselectwidget # pylint: disable=no-name-in-module class MultiSelectWidget( ui_multiselectwidget.Ui_MultiSelectWidget, QtGui.QWidget): @@ -65,4 +65,3 @@ class MultiSelectWidget( def clear(self): self.available_list.clear() self.selected_list.clear() - diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index cb93ee6..6fb75f2 100755 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -39,9 +39,9 @@ from . import thread_monitor from .appmenu_select import AppmenuSelectManager from . import firewall -from PyQt4 import QtCore, QtGui +from PyQt4 import QtCore, QtGui # pylint: disable=import-error -from . import ui_settingsdlg +from . import ui_settingsdlg #pylint: disable=no-name-in-module # pylint: disable=too-many-instance-attributes class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): @@ -177,16 +177,16 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): ret = [] try: ret_tmp = self.__apply_basic_tab__() - if len(ret_tmp) > 0: + if ret_tmp: ret += ["Basic tab:"] + ret_tmp ret_tmp = self.__apply_advanced_tab__() - if len(ret_tmp) > 0: + if ret_tmp: ret += ["Advanced tab:"] + ret_tmp ret_tmp = self.__apply_devices_tab__() - if len(ret_tmp) > 0: + if ret_tmp: ret += ["Devices tab:"] + ret_tmp ret_tmp = self.__apply_services_tab__() - if len(ret_tmp) > 0: + if ret_tmp: ret += ["Sevices tab:"] + ret_tmp except qubesadmin.exc.QubesException as qex: ret.append(self.tr('Error while saving changes: ') + str(qex)) @@ -212,7 +212,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): except Exception as ex: # pylint: disable=broad-except ret += [self.tr("Applications tab:"), repr(ex)] - if len(ret) > 0: + if ret: t_monitor.set_error_msg('\n'.join(ret)) utils.debug('\n'.join(ret)) @@ -899,7 +899,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): selected = self.rulesTreeView.selectedIndexes() - if len(selected) > 0: + if selected: dialog = firewall.NewFwRuleDlg() dialog.set_ok_state(True) row = self.rulesTreeView.selectedIndexes().pop().row() @@ -923,7 +923,7 @@ def handle_exception(exc_type, exc_value, exc_traceback): strace = "" stacktrace = traceback.extract_tb(exc_traceback) - while len(stacktrace) > 0: + while stacktrace: (filename, line, func, txt) = stacktrace.pop() strace += "----\n" strace += "line: %s\n" %txt diff --git a/qubesmanager/thread_monitor.py b/qubesmanager/thread_monitor.py index 9fbd545..8515aff 100644 --- a/qubesmanager/thread_monitor.py +++ b/qubesmanager/thread_monitor.py @@ -20,7 +20,7 @@ # -import PyQt4.QtCore +import PyQt4.QtCore # pylint: disable=import-error import threading @@ -40,4 +40,3 @@ class ThreadMonitor(PyQt4.QtCore.QObject): def set_finished(self): self.event_finished.set() - diff --git a/qubesmanager/utils.py b/qubesmanager/utils.py index 6138139..f5f66d1 100644 --- a/qubesmanager/utils.py +++ b/qubesmanager/utils.py @@ -24,7 +24,7 @@ import os import re import qubesadmin -from PyQt4.QtGui import QIcon +from PyQt4.QtGui import QIcon # pylint: disable=import-error def _filter_internal(vm): return (not vm.klass == 'AdminVM' @@ -65,7 +65,7 @@ def prepare_choice(widget, holder, propname, choice, default, choice_list = filter(_filter_internal, choice_list) if filter_function is not None: choice_list = filter(filter_function, choice_list) - choice_list = list(choice_list) # pylint: disable=redefined-variable-type + choice_list = list(choice_list) if allow_default: choice_list.insert(0, qubesadmin.DEFAULT) @@ -154,7 +154,7 @@ def get_path_from_vm(vm, service_name): untrusted_path = stdout.decode(encoding='ascii')[:path_max_len] - if len(untrusted_path) == 0: + if not untrusted_path: return None if path_re.match(untrusted_path): assert '../' not in untrusted_path From 5f1e4803fe1691989215962a30da8dd2a9783489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Fri, 17 Nov 2017 17:34:38 +0100 Subject: [PATCH 35/70] Applied corrections from @marmarek --- .travis.yml | 1 - qubesmanager/bootfromdevice.py | 7 +++--- qubesmanager/firewall.py | 25 +++++++++------------ qubesmanager/global_settings.py | 2 +- qubesmanager/settings.py | 3 ++- test-packages/qubesadmin/DEFAULT.py | 1 - test-packages/qubesadmin/__init__.py | 4 +++- test-packages/qubesadmin/devices.py | 2 +- test-packages/qubesadmin/exc.py | 2 +- test-packages/qubesadmin/firewall.py | 2 +- test-packages/qubesadmin/tools/qvm_start.py | 2 +- test-packages/qubesadmin/utils.py | 2 +- 12 files changed, 25 insertions(+), 28 deletions(-) delete mode 100644 test-packages/qubesadmin/DEFAULT.py diff --git a/.travis.yml b/.travis.yml index 49c5808..e868fbe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,6 @@ language: python python: - '3.5' install: - - sudo apt-get install python-qt4 pyqt4-dev-tools - pip install --quiet -r ci/requirements.txt - git clone https://github.com/QubesOS/qubes-builder ~/qubes-builder script: diff --git a/qubesmanager/bootfromdevice.py b/qubesmanager/bootfromdevice.py index 975364e..ab54b98 100644 --- a/qubesmanager/bootfromdevice.py +++ b/qubesmanager/bootfromdevice.py @@ -20,10 +20,9 @@ import sys import subprocess from . import utils -from . import firewall from . import ui_bootfromdevice # pylint: disable=no-name-in-module from PyQt4 import QtGui, QtCore # pylint: disable=import-error -import qubesadmin.tools.qvm_start as qvm_start +from qubesadmin import tools class VMBootFromDeviceWindow(ui_bootfromdevice.Ui_BootDialog, QtGui.QDialog): @@ -63,7 +62,7 @@ class VMBootFromDeviceWindow(ui_bootfromdevice.Ui_BootDialog, QtGui.QDialog): self.tr("ERROR!"), self.tr("No file or block device selected; please select one.")) return - qvm_start.main(['--cdrom', cdrom_location, self.vm.name]) + tools.qvm_start.main(['--cdrom', cdrom_location, self.vm.name]) def __init_buttons__(self): self.fileVM.setEnabled(False) @@ -111,7 +110,7 @@ class VMBootFromDeviceWindow(ui_bootfromdevice.Ui_BootDialog, QtGui.QDialog): self.pathText.setText(new_path) -parser = firewall.qubesadmin.tools.QubesArgumentParser(vmname_nargs=1) +parser = tools.QubesArgumentParser(vmname_nargs=1) def main(args=None): args = parser.parse_args(args) diff --git a/qubesmanager/firewall.py b/qubesmanager/firewall.py index 88ef311..d8a3d1c 100644 --- a/qubesmanager/firewall.py +++ b/qubesmanager/firewall.py @@ -148,14 +148,13 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): r"(?P[a-z][a-z0-9-]+)\s+(?P[0-9]+)/" r"(?P[a-z]+)", re.IGNORECASE) - file = open('/etc/services', 'r') - for line in file: - match = pattern.match(line) - if match is not None: - service = match.groupdict() - self.__services.append( - (service["name"], int(service["port"]),)) - file.close() + with open('/etc/services', 'r') as file: + for line in file: + match = pattern.match(line) + if match is not None: + service = match.groupdict() + self.__services.append( + (service["name"], int(service["port"]),)) self.fw_changed = False self.allow = None # is the default policy allow or deny @@ -165,8 +164,8 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): def sort(self, idx, order): rev = (order == QtCore.Qt.AscendingOrder) - self.children.sort(key=lambda x: self.get_column_string(idx, x) - , reverse=rev) + self.children.sort(key=lambda x: self.get_column_string(idx, x), + reverse=rev) index1 = self.createIndex(0, 0) index2 = self.createIndex(len(self) - 1, len(self.__column_names) - 1) @@ -388,7 +387,7 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): self.tr("Invalid port or service"), self.tr("Port number or service '{0}' is invalid.") .format(service)) - elif service is not None and service != "": + elif service: try: rule.dstports = service except (TypeError, ValueError): @@ -425,9 +424,7 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): # pylint: disable=invalid-name,no-self-use def hasChildren(self, index=QtCore.QModelIndex()): parent_item = index.internalPointer() - if parent_item is not None: - return False - return True + return parent_item is None def data(self, index, role=QtCore.Qt.DisplayRole): if index.isValid() and role == QtCore.Qt.DisplayRole: diff --git a/qubesmanager/global_settings.py b/qubesmanager/global_settings.py index 0526546..c779326 100644 --- a/qubesmanager/global_settings.py +++ b/qubesmanager/global_settings.py @@ -158,7 +158,7 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, def __init_kernel_defaults__(self): kernel_list = [] # TODO system_path["qubes_kernels_base_dir"] - # idea: qubes.pulls['linux-kernel'].volumes + # idea: qubes.pools['linux-kernel'].volumes for k in os.listdir('/var/lib/qubes/vm-kernels'): kernel_list.append(k) diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index 6fb75f2..fa1305b 100755 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -31,6 +31,7 @@ import traceback import os import sys from qubesadmin.tools import QubesArgumentParser +from qubesadmin import devices import qubesadmin.exc from . import utils @@ -665,7 +666,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): for i in range(self.dev_list.selected_list.count())] for ident in new: if ident not in old: - ass = firewall.qubesadmin.devices.DeviceAssignment( + ass = devices.DeviceAssignment( self.vm.app.domains['dom0'], ident.replace(':', '_'), persistent=True) diff --git a/test-packages/qubesadmin/DEFAULT.py b/test-packages/qubesadmin/DEFAULT.py deleted file mode 100644 index c3b69bd..0000000 --- a/test-packages/qubesadmin/DEFAULT.py +++ /dev/null @@ -1 +0,0 @@ -### mock qubesadmin.DEFAULT module \ No newline at end of file diff --git a/test-packages/qubesadmin/__init__.py b/test-packages/qubesadmin/__init__.py index d9c1b4d..1801453 100644 --- a/test-packages/qubesadmin/__init__.py +++ b/test-packages/qubesadmin/__init__.py @@ -1,2 +1,4 @@ class Qubes(object): - pass \ No newline at end of file + pass + +DEFAULT = object() diff --git a/test-packages/qubesadmin/devices.py b/test-packages/qubesadmin/devices.py index e31e5c3..be5de38 100644 --- a/test-packages/qubesadmin/devices.py +++ b/test-packages/qubesadmin/devices.py @@ -1,4 +1,4 @@ ### mock qubesadmin.devices module class DeviceAssignment(object): - pass \ No newline at end of file + pass diff --git a/test-packages/qubesadmin/exc.py b/test-packages/qubesadmin/exc.py index 78835e2..087af2a 100644 --- a/test-packages/qubesadmin/exc.py +++ b/test-packages/qubesadmin/exc.py @@ -1,4 +1,4 @@ ### mock qubesadmin.exc module class QubesException(BaseException): - pass \ No newline at end of file + pass diff --git a/test-packages/qubesadmin/firewall.py b/test-packages/qubesadmin/firewall.py index 07a5151..e83c19f 100644 --- a/test-packages/qubesadmin/firewall.py +++ b/test-packages/qubesadmin/firewall.py @@ -1,4 +1,4 @@ ### mock qubesadmin.firewall module class Rule(object): - pass \ No newline at end of file + pass diff --git a/test-packages/qubesadmin/tools/qvm_start.py b/test-packages/qubesadmin/tools/qvm_start.py index 4385323..f04be20 100644 --- a/test-packages/qubesadmin/tools/qvm_start.py +++ b/test-packages/qubesadmin/tools/qvm_start.py @@ -1,4 +1,4 @@ ### mock qvm_start module def main(*args, **kwargs): - pass \ No newline at end of file + pass diff --git a/test-packages/qubesadmin/utils.py b/test-packages/qubesadmin/utils.py index bcf4711..afb7725 100644 --- a/test-packages/qubesadmin/utils.py +++ b/test-packages/qubesadmin/utils.py @@ -4,4 +4,4 @@ def parse_size(*args, **kwargs): return args[0] def updates_vms_status(*args, **kwargs): - return args[0] \ No newline at end of file + return args[0] From eeac042662e477d96f8025a5c18bd0a11fe4d172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Fri, 17 Nov 2017 19:12:06 +0100 Subject: [PATCH 36/70] Fixed bug with firewall GUI settings Fixed bug that led to correct firewall configuration not being recognized by GUI VM Settings. fixes QubesOS/qubes-issues#3289 --- qubesmanager/firewall.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qubesmanager/firewall.py b/qubesmanager/firewall.py index d8a3d1c..14b69dc 100644 --- a/qubesmanager/firewall.py +++ b/qubesmanager/firewall.py @@ -244,7 +244,7 @@ class QubesFirewallRulesModel(QtCore.QAbstractItemModel): allow_dns = True continue - if rule.proto == icmp_rule: + if rule == icmp_rule: allow_icmp = True continue From d7660113a9797262c157e5f12b24af7a2042beef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Fri, 17 Nov 2017 21:48:07 +0100 Subject: [PATCH 37/70] Added tooltip to better explain firewall settings Added a tooltip clarifying that changing firewall settings does not affect existing connections. fixes QubesOS/qubes-issues#2731 --- ui/settingsdlg.ui | 58 ++++++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/ui/settingsdlg.ui b/ui/settingsdlg.ui index d3f1225..eda8864 100644 --- a/ui/settingsdlg.ui +++ b/ui/settingsdlg.ui @@ -29,7 +29,7 @@ - 1 + 2 @@ -675,31 +675,6 @@ border-width: 1px; Firewall rules - - - - - - Allow all outgoing Internet connections - - - - - - - Limit outgoing Internet connections to ... - - - - - - - - - Qt::Horizontal - - - @@ -720,6 +695,13 @@ border-width: 1px; + + + + Qt::Horizontal + + + @@ -768,6 +750,27 @@ border-width: 1px; + + + + + + Allow all outgoing Internet connections + + + + + + + Changing firewall settings does NOT affect existing connections. + + + Limit outgoing Internet connections to ... + + + + + @@ -846,6 +849,9 @@ border-width: 1px; + + Changing firewall settings does NOT affect existing connections. + From 5b19c825e31374f33695e7d0cca5687420b48d50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Mon, 20 Nov 2017 14:20:01 +0100 Subject: [PATCH 38/70] Enabled Firewall config without a netvm Enabled Firewall tab even when there's no working netvm or the netvm doesn't support qubes-firewall. An appropriate warning label and message are shown in both cases. fixes QubesOS/qubes-issues#3290 --- qubesmanager/settings.py | 18 ++++-- ui/settingsdlg.ui | 121 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 127 insertions(+), 12 deletions(-) diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index fa1305b..7f41bab 100755 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -79,9 +79,6 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): self.tabWidget.currentChanged.connect(self.current_tab_changed) - self.tabWidget.setTabEnabled(self.tabs_indices["firewall"], - vm.netvm is not None and not vm.provides_network) - ###### basic tab self.__init_basic_tab__() self.rename_vm_button.clicked.connect(self.rename_vm) @@ -223,13 +220,26 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): def current_tab_changed(self, idx): if idx == self.tabs_indices["firewall"]: netvm = self.vm.netvm + self.no_netvm_label.setVisible(netvm is None) + self.netvm_no_firewall_label.setVisible( + netvm is not None and + not netvm.features.check_with_template('qubes-firewall', False)) + if netvm is None: + QtGui.QMessageBox.warning( + None, + self.tr("Qube configuration problem!"), + self.tr('This qube has networking disabled ' + '(Basic -> Networking) - network will be disabled. ' + 'If you want to use firewall, ' + 'please enable networking.') + ) if netvm is not None and \ not netvm.features.check_with_template( 'qubes-firewall', False): QtGui.QMessageBox.warning( None, - self.tr("VM configuration problem!"), + self.tr("Qube configuration problem!"), self.tr("The '{vm}' AppVM is network connected to " "'{netvm}', which does not support firewall!
" "You may edit the '{vm}' VM firewall rules, but these " diff --git a/ui/settingsdlg.ui b/ui/settingsdlg.ui index d3f1225..6e241f3 100644 --- a/ui/settingsdlg.ui +++ b/ui/settingsdlg.ui @@ -29,7 +29,7 @@ - 1 + 2 @@ -675,7 +675,7 @@ border-width: 1px; Firewall rules - + @@ -693,14 +693,14 @@ border-width: 1px; - + Qt::Horizontal - + NOTE: To block all network access, set Networking to (none) on the Basic settings tab. This tab provides a very simplified firewall configuration. All DNS requests and ICMP (pings) will be allowed. For more granular control, use the command line tool qvm-firewall. @@ -713,14 +713,14 @@ border-width: 1px; - + List of allowed (whitelisted) addresses: - + true @@ -768,7 +768,7 @@ border-width: 1px;
- + QLayout::SetMaximumSize @@ -878,7 +878,7 @@ border-width: 1px; - + @@ -929,6 +929,111 @@ border-width: 1px; + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 139 + 142 + 142 + + + + + + + + + 75 + true + true + + + + This qube has no networking - it will not have any network access anyway. + + + true + + + + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 139 + 142 + 142 + + + + + + + + + 75 + true + true + + + + Networking qube does not support 'qubes-firewall' - firewall restrictions will not be applied. + + +
From b840afa5faba5102909a919830650fb5ee99040a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Tue, 21 Nov 2017 02:33:17 +0100 Subject: [PATCH 39/70] Sort items in settingsdlg.ui / firewall tab This will ease resolving merge conflict --- ui/settingsdlg.ui | 234 +++++++++++++++++++++++----------------------- 1 file changed, 117 insertions(+), 117 deletions(-) diff --git a/ui/settingsdlg.ui b/ui/settingsdlg.ui index eda8864..ea97109 100644 --- a/ui/settingsdlg.ui +++ b/ui/settingsdlg.ui @@ -675,79 +675,55 @@ border-width: 1px; Firewall rules - - + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 139 + 142 + 142 + + + + + + + + + 75 + true + true + + - NOTE: To block all network access, set Networking to (none) on the Basic settings tab. This tab provides a very simplified firewall configuration. All DNS requests and ICMP (pings) will be allowed. For more granular control, use the command line tool qvm-firewall. + Firewall has been modified manually - please use qvm-firewall for any further configuration. - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - true - - - - - - - List of allowed (whitelisted) addresses: - - - - - - - Qt::Horizontal - - - - - - - true - - - - 0 - - - 0 - - - 0 - - - - - Allow full access for - - - - - - - min - - - 5 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - @@ -771,6 +747,20 @@ border-width: 1px;
+ + + + Qt::Horizontal + + + + + + + List of allowed (whitelisted) addresses: + + + @@ -884,54 +874,64 @@ border-width: 1px; - - - - - - - - - 255 - 0 - 0 - - - - - - - - - 255 - 0 - 0 - - - - - - - - - 139 - 142 - 142 - - - - - - - - - 75 - true - true - + + + + true + + + 0 + + + 0 + + + 0 + + + + + Allow full access for + + + + + + + min + + + 5 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + - Firewall has been modified manually - please use qvm-firewall for any further configuration. + NOTE: To block all network access, set Networking to (none) on the Basic settings tab. This tab provides a very simplified firewall configuration. All DNS requests and ICMP (pings) will be allowed. For more granular control, use the command line tool qvm-firewall. + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + true From 20cc24d52ab518516e798d8ca0fae1e2021c910c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Tue, 21 Nov 2017 02:40:15 +0100 Subject: [PATCH 40/70] Sort items in settingsdlg.ui / firewall tab This will ease resolving merge conflict --- ui/settingsdlg.ui | 414 +++++++++++++++++++++++----------------------- 1 file changed, 207 insertions(+), 207 deletions(-) diff --git a/ui/settingsdlg.ui b/ui/settingsdlg.ui index 6e241f3..6cda3e2 100644 --- a/ui/settingsdlg.ui +++ b/ui/settingsdlg.ui @@ -675,6 +675,162 @@ border-width: 1px; Firewall rules + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 139 + 142 + 142 + + + + + + + + + 75 + true + true + + + + This qube has no networking - it will not have any network access anyway. + + + true + + + + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 139 + 142 + 142 + + + + + + + + + 75 + true + true + + + + Networking qube does not support 'qubes-firewall' - firewall restrictions will not be applied. + + + + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 139 + 142 + 142 + + + + + + + + + 75 + true + true + + + + Firewall has been modified manually - please use qvm-firewall for any further configuration. + + + @@ -700,19 +856,6 @@ border-width: 1px; - - - - NOTE: To block all network access, set Networking to (none) on the Basic settings tab. This tab provides a very simplified firewall configuration. All DNS requests and ICMP (pings) will be allowed. For more granular control, use the command line tool qvm-firewall. - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - true - - - @@ -720,54 +863,6 @@ border-width: 1px; - - - - true - - - - 0 - - - 0 - - - 0 - - - - - Allow full access for - - - - - - - min - - - 5 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - @@ -878,162 +973,67 @@ border-width: 1px; - - - - - - - - - 255 - 0 - 0 - - - - - - - - - 255 - 0 - 0 - - - - - - - - - 139 - 142 - 142 - - - - - - - - - 75 - true - true - - - - Firewall has been modified manually - please use qvm-firewall for any further configuration. + + + + true + + + 0 + + + 0 + + + 0 + + + + + Allow full access for + + + + + + + min + + + 5 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + - - - - - - - - - 255 - 0 - 0 - - - - - - - - - 255 - 0 - 0 - - - - - - - - - 139 - 142 - 142 - - - - - - - - - 75 - true - true - - + + - This qube has no networking - it will not have any network access anyway. + NOTE: To block all network access, set Networking to (none) on the Basic settings tab. This tab provides a very simplified firewall configuration. All DNS requests and ICMP (pings) will be allowed. For more granular control, use the command line tool qvm-firewall. + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter true - - - - - - - - - 255 - 0 - 0 - - - - - - - - - 255 - 0 - 0 - - - - - - - - - 139 - 142 - 142 - - - - - - - - - 75 - true - true - - - - Networking qube does not support 'qubes-firewall' - firewall restrictions will not be applied. - - - From 05d393035b791650e30910a9cd4c9f607a478ee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Tue, 21 Nov 2017 05:07:30 +0100 Subject: [PATCH 41/70] version 4.0.9 --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index a2cec7a..7919852 100644 --- a/version +++ b/version @@ -1 +1 @@ -4.0.8 +4.0.9 From 7a4e4b35d5ee665d722d21f931481d3422452358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Sun, 10 Dec 2017 21:14:14 +0100 Subject: [PATCH 42/70] Initial backup-and-restore GUI restoration Initial restoration: basic backup-and-restore works, but - progress bar in backup does not work - selecting only some VMs to restore does not work - various miscellaneous enhancements are not yet in place --- qubesmanager/backup.py | 444 +++++++++++++++-------------------- qubesmanager/backup_utils.py | 115 ++++----- qubesmanager/restore.py | 193 +++++++-------- rpm_spec/qmgr.spec | 2 + setup.py | 4 +- ui/backupdlg.ui | 120 +++++----- 6 files changed, 392 insertions(+), 486 deletions(-) diff --git a/qubesmanager/backup.py b/qubesmanager/backup.py index 6d808fe..b89b1cb 100644 --- a/qubesmanager/backup.py +++ b/qubesmanager/backup.py @@ -1,4 +1,4 @@ -#!/usr/bin/python2 +#!/usr/bin/python3 # pylint: skip-file # # The Qubes OS Project, http://www.qubes-os.org @@ -21,83 +21,73 @@ # # -import sys -import os +import traceback + import signal import shutil -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 import backup -from qubes import qubesutils +from qubesadmin import Qubes, events, exc +from qubesadmin import utils as admin_utils +from qubes.storage.file import get_disk_usage -import qubesmanager.resources_rc - -from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent - -import time -from .thread_monitor import * -from operator import itemgetter - -from datetime import datetime -from string import replace +from PyQt4 import QtCore, QtGui # pylint: disable=import-error from .ui_backupdlg import * from .multiselectwidget import * from .backup_utils import * -import main -import grp,pwd - +from . import utils +import grp +import pwd +import sys +import os +from .thread_monitor import * +import time class BackupVMsWindow(Ui_Backup, QWizard): __pyqtSignals__ = ("backup_progress(int)",) - def __init__(self, app, qvm_collection, blk_manager, shutdown_vm_func, parent=None): + def __init__(self, app, qvm_collection, 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.backup_settings = QtCore.QSettings() self.func_output = [] self.selected_vms = [] self.tmpdir_to_remove = None self.canceled = False - self.vm = self.qvm_collection[0] self.files_to_backup = None - assert self.vm != None - self.setupUi(self) self.progress_status.text = self.tr("Backup in progress...") - self.show_running_vms_warning(False) self.dir_line_edit.setReadOnly(False) 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(PyQt_PyObject)"), self.vms_removed) - self.connect(self.select_vms_widget, SIGNAL("items_added(PyQt_PyObject)"), 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, SIGNAL("backup_progress(int)"), self.progress_bar.setValue) - self.dir_line_edit.connect(self.dir_line_edit, SIGNAL("textChanged(QString)"), self.backup_location_changed) + self.connect(self, SIGNAL("currentIdChanged(int)"), + self.current_page_changed) + self.connect(self.select_vms_widget, + SIGNAL("items_removed(PyQt_PyObject)"), + self.vms_removed) + self.connect(self.select_vms_widget, + SIGNAL("items_added(PyQt_PyObject)"), + self.vms_added) + self.connect(self, SIGNAL("backup_progress(int)"), + self.progress_bar.setValue) + self.dir_line_edit.connect(self.dir_line_edit, + SIGNAL("textChanged(QString)"), + self.backup_location_changed) self.select_vms_page.isComplete = self.has_selected_vms self.select_dir_page.isComplete = self.has_selected_dir_and_pass - #FIXME - #this causes to run isComplete() twice, I don't know why + # FIXME + # this causes to run isComplete() twice, I don't know why self.select_vms_page.connect( self.select_vms_widget, SIGNAL("selected_changed()"), @@ -112,40 +102,62 @@ class BackupVMsWindow(Ui_Backup, QWizard): self.backup_location_changed) self.total_size = 0 - self.__fill_vms_list__() - fill_appvms_list(self) - self.load_settings() + # TODO: is the is_running criterion really necessary? It's designed to + # avoid backuping a VM into itself or surprising the user with starting + # a VM when they didn't plan to. + # TODO: inform the user only running VMs are listed? + self.target_vm_list, self.target_vm_idx = utils.prepare_vm_choice( + self.appvm_combobox, + self.qvm_collection, + None, + self.qvm_collection.domains['dom0'], + (lambda vm: vm.klass != 'TemplateVM' and vm.is_running()), + allow_internal=False, + allow_default=False, + allow_none=False + ) + + selected = self.load_settings() + self.__fill_vms_list__(selected) def load_settings(self): - dest_vm_name = main.manager_window.manager_settings.value( - 'backup/vmname', defaultValue="") - dest_vm_idx = self.appvm_combobox.findText(dest_vm_name.toString()) - if dest_vm_idx > -1: - self.appvm_combobox.setCurrentIndex(dest_vm_idx) + try: + profile_data = load_backup_profile() + except Exception as ex: # TODO: fix just for file not found + return + if not profile_data: + return - if main.manager_window.manager_settings.contains('backup/path'): - dest_path = main.manager_window.manager_settings.value( - 'backup/path', defaultValue=None) - self.dir_line_edit.setText(dest_path.toString()) + if 'destination_vm' in profile_data: + dest_vm_name = profile_data['destination_vm'] + dest_vm_idx = self.appvm_combobox.findText(dest_vm_name) + if dest_vm_idx > -1: + self.appvm_combobox.setCurrentIndex(dest_vm_idx) - if main.manager_window.manager_settings.contains('backup/encrypt'): - encrypt = main.manager_window.manager_settings.value( - 'backup/encrypt', defaultValue=None) - self.encryption_checkbox.setChecked(encrypt.toBool()) + if 'destination_path' in profile_data: + dest_path = profile_data['destination_path'] + self.dir_line_edit.setText(dest_path) + + if 'passphrase_text' in profile_data: + self.passphrase_line_edit.setText(profile_data['passphrase_text']) + self.passphrase_line_edit_verify.setText( + profile_data['passphrase_text']) + # TODO: make a checkbox for saving the profile + # TODO: warn that unknown data will be overwritten + + if 'include' in profile_data: + return profile_data['include'] + + return None def save_settings(self): - main.manager_window.manager_settings.setValue( - 'backup/vmname', self.appvm_combobox.currentText()) - main.manager_window.manager_settings.setValue( - 'backup/path', self.dir_line_edit.text()) - main.manager_window.manager_settings.setValue( - 'backup/encrypt', self.encryption_checkbox.isChecked()) - - 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) + settings = {'destination_vm': self.appvm_combobox.currentText(), + 'destination_path': self.dir_line_edit.text(), + 'include': [vm.name for vm in self.selected_vms], + 'passphrase_text': self.passphrase_line_edit.text()} + # TODO: add compression when it is added + write_backup_profile(settings) class VmListItem(QListWidgetItem): def __init__(self, vm): @@ -153,114 +165,45 @@ class BackupVMsWindow(Ui_Backup, QWizard): 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) + self.size = 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) + ")") + self.size = vm.get_disk_utilization() + super(BackupVMsWindow.VmListItem, self).__init__( + vm.name + " (" + admin_utils.size_to_human(self.size) + ")") - def get_vm_size(self, vm): - size = 0 - if vm.private_img is not None: - size += qubesutils.get_disk_usage (vm.private_img) - - if vm.updateable: - size += qubesutils.get_disk_usage(vm.root_img) - - return size - - - def __fill_vms_list__(self): - for vm in self.qvm_collection.values(): - if vm.internal: + def __fill_vms_list__(self, selected=None): + for vm in self.qvm_collection.domains: + if vm.features.get('internal', False): continue item = BackupVMsWindow.VmListItem(vm) - if vm.include_in_backups == True: + if (selected is None and + getattr(vm, 'include_in_backups', True)) \ + or (selected and vm.name in selected): self.select_vms_widget.selected_list.addItem(item) self.total_size += item.size else: self.select_vms_widget.available_list.addItem(item) self.select_vms_widget.available_list.sortItems() self.select_vms_widget.selected_list.sortItems() - self.check_running() - self.total_size_label.setText(qubesutils.size_to_human(self.total_size)) + + self.unrecognized_config_label.setVisible( + selected is not None and + len(selected) != len(self.select_vms_widget.selected_list)) + self.total_size_label.setText( + admin_utils.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)) + self.total_size_label.setText( + admin_utils.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) = self.get_running_vms() - if len(vms) == 0: - return - - for vm in vms: - self.blk_manager.check_if_serves_as_backend(vm) - - reply = QMessageBox.question(None, self.tr("VM Shutdown Confirmation"), - self.tr( - "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: - - wait_time = 60.0 - for vm in vms: - self.shutdown_vm_func(vm, wait_time*1000) - - progress = QProgressDialog ("Shutting down VMs {0}...".format(', '.join(names)), "", 0, 0) - progress.setModal(True) - progress.show() - - wait_for = wait_time - while self.check_running() and wait_for > 0: - self.app.processEvents() - time.sleep (0.5) - wait_for -= 0.5 - - progress.hide() - - def get_running_vms(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) - return (names, vms) + self.total_size_label.setText( + admin_utils.size_to_human(self.total_size)) @pyqtSlot(name='on_select_path_button_clicked') def select_path_button_clicked(self): @@ -268,44 +211,43 @@ class BackupVMsWindow(Ui_Backup, QWizard): def validateCurrentPage(self): if self.currentPage() is self.select_vms_page: - if self.check_running(): - QMessageBox.information(None, - self.tr("Wait!"), - self.tr("Some selected VMs are running. " - "Running VMs can not be backuped. " - "Please shut them down or remove them from the list.")) - return False self.selected_vms = [] for i in range(self.select_vms_widget.selected_list.count()): - self.selected_vms.append(self.select_vms_widget.selected_list.item(i).vm) + self.selected_vms.append( + self.select_vms_widget.selected_list.item(i).vm) elif self.currentPage() is self.select_dir_page: backup_location = str(self.dir_line_edit.text()) if not backup_location: - QMessageBox.information(None, self.tr("Wait!"), + QMessageBox.information( + None, self.tr("Wait!"), self.tr("Enter backup target location first.")) return False - if self.appvm_combobox.currentIndex() == 0 and \ - not os.path.isdir(backup_location): - QMessageBox.information(None, self.tr("Wait!"), + if self.appvm_combobox.currentIndex() == 0 \ + and not os.path.isdir(backup_location): + QMessageBox.information( + None, self.tr("Wait!"), self.tr("Selected directory do not exists or " "not a directory (%s).") % backup_location) return False if not len(self.passphrase_line_edit.text()): - QMessageBox.information(None, self.tr("Wait!"), - self.tr("Enter passphrase for backup encryption/verification first.")) + QMessageBox.information( + None, self.tr("Wait!"), + self.tr("Enter passphrase for backup " + "encryption/verification first.")) return False - if self.passphrase_line_edit.text() != self.passphrase_line_edit_verify.text(): - QMessageBox.information(None, - self.tr("Wait!"), + if self.passphrase_line_edit.text() !=\ + self.passphrase_line_edit_verify.text(): + QMessageBox.information( + None, self.tr("Wait!"), self.tr("Enter the same passphrase in both fields.")) return False return True - def gather_output(self, s): - self.func_output.append(s) +# def gather_output(self, s): +# self.func_output.append(s) def update_progress_bar(self, value): self.emit(SIGNAL("backup_progress(int)"), value) @@ -314,23 +256,27 @@ class BackupVMsWindow(Ui_Backup, QWizard): msg = [] try: - backup.backup_do(self.dir_line_edit.text(), - self.files_to_backup, - self.passphrase_line_edit.text(), - progress_callback=self.update_progress_bar, - encrypted=self.encryption_checkbox.isChecked(), - appvm=self.target_appvm) - #simulate_long_lasting_proces(10, self.update_progress_bar) - except backup.BackupCanceledError as ex: - msg.append(str(ex)) - self.canceled = True - if ex.tmpdir: - self.tmpdir_to_remove = ex.tmpdir - except Exception as ex: + # TODO: this does nothing, events are not handled + events_dispatcher = events.EventsDispatcher(self.app) + events_dispatcher.add_handler('backup-progress', + self.update_progress_bar) + try: + vm = self.qvm_collection.domains[ + self.appvm_combobox.currentText()] + if not vm.is_running(): + vm.start() + self.qvm_collection.qubesd_call('dom0', + 'admin.backup.Execute', + 'qubes-manager-backup') + except exc.QubesException as err: + # TODO fixme + print('\nBackup error: {}'.format(err), file=sys.stderr) + return 1 + except Exception as ex: # TODO: fixme print("Exception:", ex) msg.append(str(ex)) - if len(msg) > 0 : + if len(msg) > 0: thread_monitor.set_error_msg('\n'.join(msg)) thread_monitor.set_finished() @@ -340,27 +286,13 @@ class BackupVMsWindow(Ui_Backup, QWizard): old_sigchld_handler = signal.signal(signal.SIGCHLD, signal.SIG_DFL) if self.currentPage() is self.confirm_page: - self.target_appvm = None - if self.appvm_combobox.currentIndex() != 0: #An existing appvm chosen - self.target_appvm = self.qvm_collection.get_vm_by_name( - self.appvm_combobox.currentText()) - - del self.func_output[:] - try: - self.files_to_backup = backup.backup_prepare( - self.selected_vms, - print_callback = self.gather_output, - hide_vm_names=self.encryption_checkbox.isChecked()) - except Exception as ex: - print("Exception:", ex) - QMessageBox.critical(None, - self.tr("Error while preparing backup."), - self.tr("ERROR: {0}").format(ex)) + self.save_settings() + backup_summary = self.qvm_collection.qubesd_call( + 'dom0', 'admin.backup.Info', 'qubes-manager-backup') self.textEdit.setReadOnly(True) self.textEdit.setFontFamily("Monospace") - self.textEdit.setText("\n".join(self.func_output)) - self.save_settings() + self.textEdit.setText(backup_summary.decode()) elif self.currentPage() is self.commit_page: self.button(self.FinishButton).setDisabled(True) @@ -370,29 +302,33 @@ class BackupVMsWindow(Ui_Backup, QWizard): and str(self.dir_line_edit.text()) .count("media/") > 0) self.thread_monitor = ThreadMonitor() - thread = threading.Thread (target= self.__do_backup__ , args=(self.thread_monitor,)) + thread = threading.Thread(target=self.__do_backup__, + args=(self.thread_monitor,)) thread.daemon = True thread.start() - counter = 0 while not self.thread_monitor.is_finished(): self.app.processEvents() - time.sleep (0.1) + time.sleep(0.1) if not self.thread_monitor.success: if self.canceled: self.progress_status.setText(self.tr("Backup aborted.")) if self.tmpdir_to_remove: - if QMessageBox.warning(None, self.tr("Backup aborted"), - self.tr("Do you want to remove temporary files from " - "%s?") % self.tmpdir_to_remove, - QMessageBox.Yes, QMessageBox.No) == QMessageBox.Yes: + if QMessageBox.warning( + None, self.tr("Backup aborted"), + self.tr( + "Do you want to remove temporary files " + "from %s?") % self.tmpdir_to_remove, + QMessageBox.Yes, QMessageBox.No) == \ + QMessageBox.Yes: shutil.rmtree(self.tmpdir_to_remove) else: self.progress_status.setText(self.tr("Backup error.")) - QMessageBox.warning(self, self.tr("Backup error!"), + QMessageBox.warning( + self, self.tr("Backup error!"), self.tr("ERROR: {}").format( - self.thread_monitor.error_msg)) + self.thread_monitor.error_msg)) else: self.progress_bar.setValue(100) self.progress_status.setText(self.tr("Backup finished.")) @@ -402,20 +338,21 @@ class BackupVMsWindow(Ui_Backup, QWizard): orig_text + self.tr( " Please unmount your backup volume and cancel " "the file selection dialog.")) - if self.target_appvm: - self.target_appvm.run("QUBESRPC %s dom0" % "qubes" - ".SelectDirectory") + if self.target_appvm: # FIXME I'm not sure if this works + self.target_appvm.run( + "QUBESRPC %s dom0" % "qubes.SelectDirectory") self.button(self.CancelButton).setEnabled(False) self.button(self.FinishButton).setEnabled(True) self.showFileDialog.setEnabled(False) signal.signal(signal.SIGCHLD, old_sigchld_handler) def reject(self): - #cancell clicked while the backup is in progress. - #calling kill on tar. + # cancell clicked while the backup is in progress. + # calling kill on tar. if self.currentPage() is self.commit_page: - if backup.backup_cancel(): - self.button(self.CancelButton).setDisabled(True) + pass # TODO: this does nothing + # if backup.backup_cancel(): + # self.button(self.CancelButton).setDisabled(True) else: self.done(0) @@ -425,61 +362,50 @@ class BackupVMsWindow(Ui_Backup, QWizard): def has_selected_dir_and_pass(self): if not len(self.passphrase_line_edit.text()): return False - if self.passphrase_line_edit.text() != self.passphrase_line_edit_verify.text(): + if self.passphrase_line_edit.text() != \ + self.passphrase_line_edit_verify.text(): return False return len(self.dir_line_edit.text()) > 0 - def backup_location_changed(self, new_dir = None): + def backup_location_changed(self, new_dir=None): self.select_dir_page.emit(SIGNAL("completeChanged()")) # 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 +def handle_exception(exc_type, exc_value, exc_traceback): + filename, line, dummy, dummy = traceback.extract_tb(exc_traceback).pop() + filename = os.path.basename(filename) + error = "%s: %s" % (exc_type.__name__, exc_value) - 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 )) + QtGui.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 app_main(): +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 Backup VMs") + qtapp = QtGui.QApplication(sys.argv) + qtapp.setOrganizationName("The Qubes Project") + qtapp.setOrganizationDomain("http://qubes-os.org") + qtapp.setApplicationName("Qubes Backup VMs") sys.excepthook = handle_exception - qvm_collection = QubesVmCollection() - qvm_collection.lock_db_for_reading() - qvm_collection.load() - qvm_collection.unlock_db() + app = Qubes() - global backup_window - backup_window = BackupVMsWindow() + backup_window = BackupVMsWindow(qtapp, app) backup_window.show() - app.exec_() - app.exit() - + qtapp.exec_() + qtapp.exit() if __name__ == "__main__": - app_main() + main() diff --git a/qubesmanager/backup_utils.py b/qubesmanager/backup_utils.py index 8a104e0..102b5be 100644 --- a/qubesmanager/backup_utils.py +++ b/qubesmanager/backup_utils.py @@ -20,90 +20,97 @@ # # import re -import sys -import os from PyQt4.QtCore import * from PyQt4.QtGui import * import subprocess -import time - -from .thread_monitor import * +from . import utils +import yaml path_re = re.compile(r"[a-zA-Z0-9/:.,_+=() -]*") path_max_len = 512 +# TODO: replace it with a more dynamic approach: allowing user to turn on or off +# profile saving +backup_profile_path = '/etc/qubes/backup/qubes-manager-backup.conf' + + def fill_appvms_list(dialog): dialog.appvm_combobox.clear() dialog.appvm_combobox.addItem("dom0") - dialog.appvm_combobox.setCurrentIndex(0) #current selected is null "" + dialog.appvm_combobox.setCurrentIndex(0) # current selected is null "" - for vm in dialog.qvm_collection.values(): - if vm.is_appvm() and vm.internal: + for vm in dialog.qvm_collection.domains: + if vm.klass == 'AppVM' and vm.features.get('internal', False): continue - if vm.is_template() and vm.installed_by_rpm: + if vm.klass == 'TemplateVM' and vm.installed_by_rpm: continue - if vm.is_running() and vm.qid != 0: + # TODO: is the is_running criterion really necessary? It's designed to + # avoid backuping a VM into itself or surprising the user with starting + # a VM when they didn't plan to. + # TODO: remove debug + debug = True + if (debug or vm.is_running()) and vm.qid != 0: dialog.appvm_combobox.addItem(vm.name) + def enable_dir_line_edit(dialog, boolean): dialog.dir_line_edit.setEnabled(boolean) dialog.select_path_button.setEnabled(boolean) -def get_path_for_vm(vm, service_name): - if not vm: - return None - proc = vm.run("QUBESRPC %s dom0" % service_name, passio_popen=True) - proc.stdin.close() - untrusted_path = proc.stdout.readline(path_max_len) - if len(untrusted_path) == 0: - return None - if path_re.match(untrusted_path): - assert '../' not in untrusted_path - assert '\0' not in untrusted_path - return untrusted_path.strip() - else: - return None -def select_path_button_clicked(dialog, select_file = False): +def select_path_button_clicked(dialog, select_file=False): backup_location = str(dialog.dir_line_edit.text()) file_dialog = QFileDialog() file_dialog.setReadOnly(True) - if select_file: - file_dialog_function = file_dialog.getOpenFileName - else: - file_dialog_function = file_dialog.getExistingDirectory - - new_appvm = None new_path = None - if dialog.appvm_combobox.currentIndex() != 0: #An existing appvm chosen - new_appvm = str(dialog.appvm_combobox.currentText()) - vm = dialog.qvm_collection.get_vm_by_name(new_appvm) - if vm: - new_path = get_path_for_vm(vm, "qubes.SelectFile" if select_file - else "qubes.SelectDirectory") - else: - new_path = file_dialog_function(dialog, - dialog.tr("Select backup location."), - backup_location if backup_location else '/') + # TODO: check if dom0 is available - if new_path != None: - if os.path.basename(new_path) == 'qubes.xml': - backup_location = os.path.dirname(new_path) - else: - backup_location = new_path - dialog.dir_line_edit.setText(backup_location) + new_appvm = str(dialog.appvm_combobox.currentText()) + vm = dialog.qvm_collection.domains[new_appvm] + try: + new_path = utils.get_path_from_vm( + vm, + "qubes.SelectFile" if select_file + else "qubes.SelectDirectory") + except subprocess.CalledProcessError as ex: + QMessageBox.warning( + None, + dialog.tr("Nothing selected!"), + dialog.tr("No file or directory selected.")) - if (new_path or new_appvm) and len(backup_location) > 0: + # TODO: check if this works for restore + if new_path: + dialog.dir_line_edit.setText(new_path) + + if new_path and len(backup_location) > 0: dialog.select_dir_page.emit(SIGNAL("completeChanged()")) -def simulate_long_lasting_proces(period, progress_callback): - for i in range(period): - progress_callback((i*100)/period) - time.sleep(1) - progress_callback(100) - return 0 +def load_backup_profile(): + with open(backup_profile_path) as profile_file: + profile_data = yaml.safe_load(profile_file) + return profile_data + + +def write_backup_profile(args): + '''Format limited backup profile (for GUI purposes and print it to + *output_stream* (a file or stdout) + + :param output_stream: file-like object ro print the profile to + :param args: dictionary with arguments + :param passphrase: passphrase to use + ''' + + acceptable_fields = ['include', 'passphrase_text', 'compression', + 'destination_vm', 'destination_path'] + + profile_data = {key: value for key, value in args.items() + if key in acceptable_fields} + + # TODO add compression parameter to GUI issue#943 + with open(backup_profile_path, 'w') as profile_file: + yaml.safe_dump(profile_data, profile_file) diff --git a/qubesmanager/restore.py b/qubesmanager/restore.py index c1609c5..f7f44b8 100644 --- a/qubesmanager/restore.py +++ b/qubesmanager/restore.py @@ -26,23 +26,15 @@ import os import shutil from PyQt4.QtCore import * from PyQt4.QtGui import * +from .thread_monitor import * +import time +import os.path +import traceback -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 import signal -from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent - -import time -from operator import itemgetter -from .thread_monitor import * - from qubes import backup -from qubes import qubesutils from .ui_restoredlg import * from .multiselectwidget import * @@ -50,17 +42,20 @@ from .multiselectwidget import * from .backup_utils import * from multiprocessing import Queue, Event from multiprocessing.queues import Empty +from qubesadmin import Qubes, events, exc +from qubesadmin import utils as admin_utils +from qubesadmin.backup import restore + class RestoreVMsWindow(Ui_Restore, QWizard): - __pyqtSignals__ = ("restore_progress(int)","backup_progress(int)") + __pyqtSignals__ = ("restore_progress(int)", "backup_progress(int)") - def __init__(self, app, qvm_collection, blk_manager, parent=None): + def __init__(self, app, qvm_collection, parent=None): super(RestoreVMsWindow, self).__init__(parent) self.app = app self.qvm_collection = qvm_collection - self.blk_manager = blk_manager self.restore_options = None self.vms_to_restore = None @@ -72,31 +67,36 @@ class RestoreVMsWindow(Ui_Restore, QWizard): self.excluded = {} - self.vm = self.qvm_collection[0] - - 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, SIGNAL("restore_progress(QString)"), self.commit_text_edit.append) - self.connect(self, SIGNAL("backup_progress(int)"), self.progress_bar.setValue) - self.dir_line_edit.connect(self.dir_line_edit, SIGNAL("textChanged(QString)"), self.backup_location_changed) + self.connect(self, + SIGNAL("currentIdChanged(int)"), self.current_page_changed) + self.connect(self, + SIGNAL("restore_progress(QString)"), + self.commit_text_edit.append) + self.connect(self, + SIGNAL("backup_progress(int)"), self.progress_bar.setValue) + self.dir_line_edit.connect(self.dir_line_edit, + SIGNAL("textChanged(QString)"), + self.backup_location_changed) self.connect(self.verify_only, SIGNAL("stateChanged(int)"), self.on_verify_only_toogled) self.select_dir_page.isComplete = self.has_selected_dir self.select_vms_page.isComplete = self.has_selected_vms self.confirm_page.isComplete = self.all_vms_good - #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()")) + # 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_appvms_list(self) - self.__init_restore_options__() +# self.__init_restore_options__() @pyqtSlot(name='on_select_path_button_clicked') def select_path_button_clicked(self): @@ -125,41 +125,40 @@ class RestoreVMsWindow(Ui_Restore, QWizard): self.select_vms_widget.selected_list.clear() self.select_vms_widget.available_list.clear() - self.target_appvm = None - if self.appvm_combobox.currentIndex() != 0: #An existing appvm chosen - self.target_appvm = self.qvm_collection.get_vm_by_name( - str(self.appvm_combobox.currentText())) + self.target_appvm = None # TODO: what is the purpose of this + if self.appvm_combobox.currentIndex() != 0: # An existing appvm chosen + self.target_appvm = self.qvm_collection.domains[ + str(self.appvm_combobox.currentText())] try: - self.vms_to_restore = backup.backup_restore_prepare( - self.dir_line_edit.text(), - self.passphrase_line_edit.text(), - options=self.restore_options, - host_collection=self.qvm_collection, - encrypted=self.encryption_checkbox.isChecked(), - appvm=self.target_appvm) + self.backup_restore = restore.BackupRestore( + self.qvm_collection, + self.dir_line_edit.text(), + self.target_appvm, + self.passphrase_line_edit.text() + ) + + # TODO: change text of ignore missing to ignore + # missing templates and netvms + if self.ignore_missing.isChecked(): + self.backup_restore.options.use_default_template = True + self.backup_restore.options.use_default_netvm = True + + if self.ignore_uname_mismatch.isChecked(): + self.backup_restore.options.ignore_username_mismatch = True + + if self.verify_only.isChecked(): + self.backup_restore.options.verify_only = True + + self.vms_to_restore = self.backup_restore.get_restore_info() for vmname in self.vms_to_restore: if vmname.startswith('$'): # Internal info continue self.select_vms_widget.available_list.addItem(vmname) - except QubesException as ex: - QMessageBox.warning (None, self.tr("Restore error!"), str(ex)) - - def __init_restore_options__(self): - if not self.restore_options: - self.restore_options = {} - backup.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']) + except exc.QubesException as ex: + QMessageBox.warning(None, self.tr("Restore error!"), str(ex)) def gather_output(self, s): self.func_output.append(s) @@ -178,25 +177,20 @@ class RestoreVMsWindow(Ui_Restore, QWizard): def __do_restore__(self, thread_monitor): err_msg = [] - self.qvm_collection.lock_db_for_writing() try: - backup.backup_restore_do(self.vms_to_restore, - self.qvm_collection, - print_callback=self.restore_output, - error_callback=self.restore_error_output, - progress_callback=self.update_progress_bar) + self.backup_restore.progress_callback = self.update_progress_bar + self.backup_restore.restore_do(self.vms_to_restore) + except backup.BackupCanceledError as ex: self.canceled = True self.tmpdir_to_remove = ex.tmpdir err_msg.append(str(ex)) except Exception as ex: - print ("Exception:", ex) err_msg.append(str(ex)) err_msg.append( - self.tr("Partially restored files left in " - "/var/tmp/restore_*, investigate them and/or clean them up")) + self.tr("Partially restored files left in /var/tmp/restore_*, " + "investigate them and/or clean them up")) - self.qvm_collection.unlock_db() if self.canceled: self.emit(SIGNAL("restore_progress(QString)"), '{0}' @@ -230,13 +224,15 @@ class RestoreVMsWindow(Ui_Restore, QWizard): del self.vms_to_restore[str(vmname)] del self.func_output[:] - self.vms_to_restore = backup.restore_info_verify(self.vms_to_restore, - self.qvm_collection) - backup.backup_restore_print_summary( - self.vms_to_restore, print_callback = self.gather_output) + # TODO: am I ignoring changes made by user? + self.vms_to_restore = self.backup_restore.restore_info_verify( + self.backup_restore.get_restore_info()) + self.func_output = self.backup_restore.get_restore_summary( + self.backup_restore.get_restore_info() + ) self.confirm_text_edit.setReadOnly(True) self.confirm_text_edit.setFontFamily("Monospace") - self.confirm_text_edit.setText("\n".join(self.func_output)) + self.confirm_text_edit.setText(self.func_output) self.confirm_page.emit(SIGNAL("completeChanged()")) @@ -275,13 +271,13 @@ class RestoreVMsWindow(Ui_Restore, QWizard): self.tr("Backup error!"), self.tr("ERROR: {0}") .format(self.thread_monitor.error_msg)) - if self.showFileDialog.isChecked(): + if self.showFileDialog.isChecked(): # TODO: this is not working self.emit(SIGNAL("restore_progress(QString)"), '{0}'.format( self.tr( "Please unmount your backup volume and cancel" " the file selection dialog."))) - if self.target_appvm: + if self.target_appvm: # TODO does this work at all? self.target_appvm.run("QUBESRPC %s dom0" % "qubes.SelectDirectory") else: @@ -298,16 +294,16 @@ class RestoreVMsWindow(Ui_Restore, QWizard): signal.signal(signal.SIGCHLD, old_sigchld_handler) def all_vms_good(self): - for vminfo in self.vms_to_restore.values(): - if not vminfo.has_key('vm'): + for vm_info in self.vms_to_restore.values(): + if not vm_info.vm: continue - if not vminfo['good-to-go']: + if not vm_info.good_to_go: return False return True - def reject(self): + def reject(self): # TODO: probably not working too if self.currentPage() is self.commit_page: - if backup.backup_cancel(): + if self.backup_restore.canceled: self.emit(SIGNAL("restore_progress(QString)"), '{0}' .format(self.tr("Aborting the operation..."))) @@ -331,58 +327,45 @@ class RestoreVMsWindow(Ui_Restore, QWizard): def has_selected_vms(self): return self.select_vms_widget.selected_list.count() > 0 - def backup_location_changed(self, new_dir = None): + def backup_location_changed(self, new_dir=None): self.select_dir_page.emit(SIGNAL("completeChanged()")) # 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 +def handle_exception(exc_type, exc_value, exc_traceback): - filename, line, dummy, dummy = traceback.extract_tb( exc_traceback ).pop() - filename = os.path.basename( filename ) - error = "%s: %s" % ( exc_type.__name__, exc_value ) + 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 " + "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 )) - - + % (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") + qtapp = QApplication(sys.argv) + qtapp.setOrganizationName("The Qubes Project") + qtapp.setOrganizationDomain("http://qubes-os.org") + qtapp.setApplicationName("Qubes Restore VMs") sys.excepthook = handle_exception - qvm_collection = QubesVmCollection() - qvm_collection.lock_db_for_reading() - qvm_collection.load() - qvm_collection.unlock_db() + app = Qubes() - global restore_window - restore_window = RestoreVMsWindow() + restore_window = RestoreVMsWindow(qtapp, app) restore_window.show() - app.exec_() - app.exit() - + qtapp.exec_() + qtapp.exit() if __name__ == "__main__": diff --git a/rpm_spec/qmgr.spec b/rpm_spec/qmgr.spec index 7c473fa..1a06cdd 100644 --- a/rpm_spec/qmgr.spec +++ b/rpm_spec/qmgr.spec @@ -60,6 +60,8 @@ rm -rf $RPM_BUILD_ROOT /usr/bin/qubes-vm-settings /usr/bin/qubes-vm-create /usr/bin/qubes-vm-boot-from-device +/usr/bin/qubes-backup +/usr/bin/qubes-backup-restore /usr/libexec/qubes-manager/mount_for_backup.sh /usr/libexec/qubes-manager/qvm_about.sh diff --git a/setup.py b/setup.py index cba28dd..c89c898 100644 --- a/setup.py +++ b/setup.py @@ -21,6 +21,8 @@ if __name__ == '__main__': 'qubes-global-settings = qubesmanager.global_settings:main', 'qubes-vm-settings = qubesmanager.settings:main', 'qubes-vm-create = qubesmanager.create_new_vm:main', - 'qubes-vm-boot-from-device = qubesmanager.bootfromdevice:main' + 'qubes-vm-boot-from-device = qubesmanager.bootfromdevice:main', + 'qubes-backup = qubesmanager.backup:main', + 'qubes-backup-restore = qubesmanager.restore:main' ], }) diff --git a/ui/backupdlg.ui b/ui/backupdlg.ui index 31760d5..6b50497 100644 --- a/ui/backupdlg.ui +++ b/ui/backupdlg.ui @@ -23,73 +23,8 @@ - - - - - 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 cannot be backed up! - - - true - - - - - - 9 - 50 - false - false - false - - Select VMs to backup: @@ -145,6 +80,57 @@ + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 139 + 142 + 142 + + + + + + + + + 75 + true + true + + + + Warning: unrecognized data found in configuration files. + + +
@@ -281,8 +267,8 @@ <!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="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> +</style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p></body></html>
From 9d73ceb99d4d8f9370409a9faf21211d9dff31f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Sun, 10 Dec 2017 22:11:07 +0100 Subject: [PATCH 43/70] Fixed imports Fixed import * and unused imports. --- qubesmanager/backup.py | 73 +++++++++++++++-------------- qubesmanager/backup_utils.py | 10 ++-- qubesmanager/restore.py | 91 +++++++++++++++++++----------------- 3 files changed, 90 insertions(+), 84 deletions(-) diff --git a/qubesmanager/backup.py b/qubesmanager/backup.py index b89b1cb..f8ccd0b 100644 --- a/qubesmanager/backup.py +++ b/qubesmanager/backup.py @@ -30,21 +30,23 @@ from qubesadmin import Qubes, events, exc from qubesadmin import utils as admin_utils from qubes.storage.file import get_disk_usage -from PyQt4 import QtCore, QtGui # pylint: disable=import-error +from PyQt4 import QtCore # pylint: disable=import-error +from PyQt4 import QtGui # pylint: disable=import-error +from . import ui_backupdlg +from . import multiselectwidget -from .ui_backupdlg import * -from .multiselectwidget import * - -from .backup_utils import * +from . import backup_utils from . import utils import grp import pwd import sys import os -from .thread_monitor import * +from . import thread_monitor +import threading import time -class BackupVMsWindow(Ui_Backup, QWizard): + +class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): __pyqtSignals__ = ("backup_progress(int)",) @@ -67,21 +69,21 @@ class BackupVMsWindow(Ui_Backup, QWizard): self.progress_status.text = self.tr("Backup in progress...") self.dir_line_edit.setReadOnly(False) - self.select_vms_widget = MultiSelectWidget(self) + self.select_vms_widget = multiselectwidget.MultiSelectWidget(self) self.verticalLayout.insertWidget(1, self.select_vms_widget) - self.connect(self, SIGNAL("currentIdChanged(int)"), + self.connect(self, QtCore.SIGNAL("currentIdChanged(int)"), self.current_page_changed) self.connect(self.select_vms_widget, - SIGNAL("items_removed(PyQt_PyObject)"), + QtCore.SIGNAL("items_removed(PyQt_PyObject)"), self.vms_removed) self.connect(self.select_vms_widget, - SIGNAL("items_added(PyQt_PyObject)"), + QtCore.SIGNAL("items_added(PyQt_PyObject)"), self.vms_added) - self.connect(self, SIGNAL("backup_progress(int)"), + self.connect(self, QtCore.SIGNAL("backup_progress(int)"), self.progress_bar.setValue) self.dir_line_edit.connect(self.dir_line_edit, - SIGNAL("textChanged(QString)"), + QtCore.SIGNAL("textChanged(QString)"), self.backup_location_changed) self.select_vms_page.isComplete = self.has_selected_vms @@ -90,15 +92,15 @@ class BackupVMsWindow(Ui_Backup, QWizard): # 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()")) + QtCore.SIGNAL("selected_changed()"), + QtCore.SIGNAL("completeChanged()")) self.passphrase_line_edit.connect( self.passphrase_line_edit, - SIGNAL("textChanged(QString)"), + QtCore.SIGNAL("textChanged(QString)"), self.backup_location_changed) self.passphrase_line_edit_verify.connect( self.passphrase_line_edit_verify, - SIGNAL("textChanged(QString)"), + QtCore.SIGNAL("textChanged(QString)"), self.backup_location_changed) self.total_size = 0 @@ -123,7 +125,7 @@ class BackupVMsWindow(Ui_Backup, QWizard): def load_settings(self): try: - profile_data = load_backup_profile() + profile_data = backup_utils.load_backup_profile() except Exception as ex: # TODO: fix just for file not found return if not profile_data: @@ -157,9 +159,9 @@ class BackupVMsWindow(Ui_Backup, QWizard): 'include': [vm.name for vm in self.selected_vms], 'passphrase_text': self.passphrase_line_edit.text()} # TODO: add compression when it is added - write_backup_profile(settings) + backup_utils.write_backup_profile(settings) - class VmListItem(QListWidgetItem): + class VmListItem(QtGui.QListWidgetItem): def __init__(self, vm): self.vm = vm if vm.qid == 0: @@ -205,9 +207,9 @@ class BackupVMsWindow(Ui_Backup, QWizard): self.total_size_label.setText( admin_utils.size_to_human(self.total_size)) - @pyqtSlot(name='on_select_path_button_clicked') + @QtCore.pyqtSlot(name='on_select_path_button_clicked') def select_path_button_clicked(self): - select_path_button_clicked(self) + backup_utils.select_path_button_clicked(self) def validateCurrentPage(self): if self.currentPage() is self.select_vms_page: @@ -220,26 +222,26 @@ class BackupVMsWindow(Ui_Backup, QWizard): elif self.currentPage() is self.select_dir_page: backup_location = str(self.dir_line_edit.text()) if not backup_location: - QMessageBox.information( + QtGui.QMessageBox.information( None, self.tr("Wait!"), self.tr("Enter backup target location first.")) return False if self.appvm_combobox.currentIndex() == 0 \ and not os.path.isdir(backup_location): - QMessageBox.information( + QtGui.QMessageBox.information( None, self.tr("Wait!"), self.tr("Selected directory do not exists or " "not a directory (%s).") % backup_location) return False if not len(self.passphrase_line_edit.text()): - QMessageBox.information( + QtGui.QMessageBox.information( None, self.tr("Wait!"), self.tr("Enter passphrase for backup " "encryption/verification first.")) return False if self.passphrase_line_edit.text() !=\ self.passphrase_line_edit_verify.text(): - QMessageBox.information( + QtGui.QMessageBox.information( None, self.tr("Wait!"), self.tr("Enter the same passphrase in both fields.")) return False @@ -250,7 +252,7 @@ class BackupVMsWindow(Ui_Backup, QWizard): # self.func_output.append(s) def update_progress_bar(self, value): - self.emit(SIGNAL("backup_progress(int)"), value) + self.emit(QtCore.SIGNAL("backup_progress(int)"), value) def __do_backup__(self, thread_monitor): msg = [] @@ -301,9 +303,10 @@ class BackupVMsWindow(Ui_Backup, QWizard): self.showFileDialog.setChecked(self.showFileDialog.isEnabled() and str(self.dir_line_edit.text()) .count("media/") > 0) - self.thread_monitor = ThreadMonitor() - thread = threading.Thread(target=self.__do_backup__, - args=(self.thread_monitor,)) + self.thread_monitor = thread_monitor.ThreadMonitor() + thread = threading.Thread( + target=self.__do_backup__, + args=(self.thread_monitor,)) thread.daemon = True thread.start() @@ -315,17 +318,17 @@ class BackupVMsWindow(Ui_Backup, QWizard): if self.canceled: self.progress_status.setText(self.tr("Backup aborted.")) if self.tmpdir_to_remove: - if QMessageBox.warning( + if QtGui.QMessageBox.warning( None, self.tr("Backup aborted"), self.tr( "Do you want to remove temporary files " "from %s?") % self.tmpdir_to_remove, - QMessageBox.Yes, QMessageBox.No) == \ - QMessageBox.Yes: + QtGui.QMessageBox.Yes, + QtGui.QMessageBox.No) == QtGui.QMessageBox.Yes: shutil.rmtree(self.tmpdir_to_remove) else: self.progress_status.setText(self.tr("Backup error.")) - QMessageBox.warning( + QtGui.QMessageBox.warning( self, self.tr("Backup error!"), self.tr("ERROR: {}").format( self.thread_monitor.error_msg)) @@ -368,7 +371,7 @@ class BackupVMsWindow(Ui_Backup, QWizard): return len(self.dir_line_edit.text()) > 0 def backup_location_changed(self, new_dir=None): - self.select_dir_page.emit(SIGNAL("completeChanged()")) + self.select_dir_page.emit(QtCore.SIGNAL("completeChanged()")) # Bases on the original code by: diff --git a/qubesmanager/backup_utils.py b/qubesmanager/backup_utils.py index 102b5be..083c4da 100644 --- a/qubesmanager/backup_utils.py +++ b/qubesmanager/backup_utils.py @@ -20,8 +20,8 @@ # # import re -from PyQt4.QtCore import * -from PyQt4.QtGui import * +from PyQt4 import QtGui +from PyQt4 import QtCore import subprocess from . import utils @@ -63,7 +63,7 @@ def enable_dir_line_edit(dialog, boolean): def select_path_button_clicked(dialog, select_file=False): backup_location = str(dialog.dir_line_edit.text()) - file_dialog = QFileDialog() + file_dialog = QtGui.QFileDialog() file_dialog.setReadOnly(True) new_path = None @@ -77,7 +77,7 @@ def select_path_button_clicked(dialog, select_file=False): "qubes.SelectFile" if select_file else "qubes.SelectDirectory") except subprocess.CalledProcessError as ex: - QMessageBox.warning( + QtGui.QMessageBox.warning( None, dialog.tr("Nothing selected!"), dialog.tr("No file or directory selected.")) @@ -87,7 +87,7 @@ def select_path_button_clicked(dialog, select_file=False): dialog.dir_line_edit.setText(new_path) if new_path and len(backup_location) > 0: - dialog.select_dir_page.emit(SIGNAL("completeChanged()")) + dialog.select_dir_page.emit(QtCore.SIGNAL("completeChanged()")) def load_backup_profile(): diff --git a/qubesmanager/restore.py b/qubesmanager/restore.py index f7f44b8..46e56f5 100644 --- a/qubesmanager/restore.py +++ b/qubesmanager/restore.py @@ -24,30 +24,29 @@ import sys import os import shutil -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from .thread_monitor import * +from PyQt4 import QtCore +from PyQt4 import QtGui +from . import thread_monitor +import threading import time import os.path import traceback -import qubesmanager.resources_rc import signal from qubes import backup -from .ui_restoredlg import * -from .multiselectwidget import * +from . import ui_restoredlg +from . import multiselectwidget -from .backup_utils import * +from . import backup_utils from multiprocessing import Queue, Event from multiprocessing.queues import Empty -from qubesadmin import Qubes, events, exc -from qubesadmin import utils as admin_utils +from qubesadmin import Qubes, exc from qubesadmin.backup import restore -class RestoreVMsWindow(Ui_Restore, QWizard): +class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): __pyqtSignals__ = ("restore_progress(int)", "backup_progress(int)") @@ -69,20 +68,20 @@ class RestoreVMsWindow(Ui_Restore, QWizard): self.setupUi(self) - self.select_vms_widget = MultiSelectWidget(self) + self.select_vms_widget = multiselectwidget.MultiSelectWidget(self) self.select_vms_layout.insertWidget(1, self.select_vms_widget) self.connect(self, - SIGNAL("currentIdChanged(int)"), self.current_page_changed) + QtCore.SIGNAL("currentIdChanged(int)"), self.current_page_changed) self.connect(self, - SIGNAL("restore_progress(QString)"), + QtCore.SIGNAL("restore_progress(QString)"), self.commit_text_edit.append) self.connect(self, - SIGNAL("backup_progress(int)"), self.progress_bar.setValue) + QtCore.SIGNAL("backup_progress(int)"), self.progress_bar.setValue) self.dir_line_edit.connect(self.dir_line_edit, - SIGNAL("textChanged(QString)"), + QtCore.SIGNAL("textChanged(QString)"), self.backup_location_changed) - self.connect(self.verify_only, SIGNAL("stateChanged(int)"), + self.connect(self.verify_only, QtCore.SIGNAL("stateChanged(int)"), self.on_verify_only_toogled) self.select_dir_page.isComplete = self.has_selected_dir @@ -92,15 +91,15 @@ class RestoreVMsWindow(Ui_Restore, QWizard): # 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()")) + QtCore.SIGNAL("selected_changed()"), + QtCore.SIGNAL("completeChanged()")) - fill_appvms_list(self) + backup_utils.fill_appvms_list(self) # self.__init_restore_options__() - @pyqtSlot(name='on_select_path_button_clicked') + @QtCore.pyqtSlot(name='on_select_path_button_clicked') def select_path_button_clicked(self): - select_path_button_clicked(self, True) + backup_utils.select_path_button_clicked(self, True) def on_ignore_missing_toggled(self, checked): self.restore_options['use-default-template'] = checked @@ -158,22 +157,22 @@ class RestoreVMsWindow(Ui_Restore, QWizard): continue self.select_vms_widget.available_list.addItem(vmname) except exc.QubesException as ex: - QMessageBox.warning(None, self.tr("Restore error!"), str(ex)) + QtGui.QMessageBox.warning(None, self.tr("Restore error!"), str(ex)) def gather_output(self, s): self.func_output.append(s) def restore_error_output(self, s): self.error_detected.set() - self.feedback_queue.put((SIGNAL("restore_progress(QString)"), + self.feedback_queue.put((QtCore.SIGNAL("restore_progress(QString)"), u'{0}'.format(s))) def restore_output(self, s): - self.feedback_queue.put((SIGNAL("restore_progress(QString)"), + self.feedback_queue.put((QtCore.SIGNAL("restore_progress(QString)"), u'{0}'.format(s))) def update_progress_bar(self, value): - self.feedback_queue.put((SIGNAL("backup_progress(int)"), value)) + self.feedback_queue.put((QtCore.SIGNAL("backup_progress(int)"), value)) def __do_restore__(self, thread_monitor): err_msg = [] @@ -192,17 +191,17 @@ class RestoreVMsWindow(Ui_Restore, QWizard): "investigate them and/or clean them up")) if self.canceled: - self.emit(SIGNAL("restore_progress(QString)"), + self.emit(QtCore.SIGNAL("restore_progress(QString)"), '{0}' .format(self.tr("Restore aborted!"))) elif len(err_msg) > 0 or self.error_detected.is_set(): if len(err_msg) > 0: thread_monitor.set_error_msg('\n'.join(err_msg)) - self.emit(SIGNAL("restore_progress(QString)"), + self.emit(QtCore.SIGNAL("restore_progress(QString)"), '{0}' .format(self.tr("Finished with errors!"))) else: - self.emit(SIGNAL("restore_progress(QString)"), + self.emit(QtCore.SIGNAL("restore_progress(QString)"), '{0}' .format(self.tr("Finished successfully!"))) @@ -234,7 +233,7 @@ class RestoreVMsWindow(Ui_Restore, QWizard): self.confirm_text_edit.setFontFamily("Monospace") self.confirm_text_edit.setText(self.func_output) - self.confirm_page.emit(SIGNAL("completeChanged()")) + self.confirm_page.emit(QtCore.SIGNAL("completeChanged()")) elif self.currentPage() is self.commit_page: self.button(self.FinishButton).setDisabled(True) @@ -243,8 +242,8 @@ class RestoreVMsWindow(Ui_Restore, QWizard): and str(self.dir_line_edit.text()) .count("media/") > 0) - self.thread_monitor = ThreadMonitor() - thread = threading.Thread (target= self.__do_restore__ , args=(self.thread_monitor,)) + self.thread_monitor = thread_monitor.ThreadMonitor() + thread = threading.Thread (target= self.__do_restore__, args=(self.thread_monitor,)) thread.daemon = True thread.start() @@ -260,19 +259,23 @@ class RestoreVMsWindow(Ui_Restore, QWizard): if not self.thread_monitor.success: if self.canceled: if self.tmpdir_to_remove and \ - QMessageBox.warning(None, self.tr("Restore aborted"), + QtGui.QMessageBox.warning( + None, + self.tr("Restore aborted"), self.tr("Do you want to remove temporary files " "from %s?") % self.tmpdir_to_remove, - QMessageBox.Yes, QMessageBox.No) == \ - QMessageBox.Yes: + QtGui.QMessageBox.Yes, + QtGui.QMessageBox.No) == QtGui.QMessageBox.Yes: shutil.rmtree(self.tmpdir_to_remove) else: - QMessageBox.warning(None, - self.tr("Backup error!"), self.tr("ERROR: {0}") - .format(self.thread_monitor.error_msg)) + QtGui.QMessageBox.warning( + None, + self.tr("Backup error!"), + self.tr("ERROR: {0}").format( + self.thread_monitor.error_msg)) if self.showFileDialog.isChecked(): # TODO: this is not working - self.emit(SIGNAL("restore_progress(QString)"), + self.emit(QtCore.SIGNAL("restore_progress(QString)"), '{0}'.format( self.tr( "Please unmount your backup volume and cancel" @@ -281,7 +284,7 @@ class RestoreVMsWindow(Ui_Restore, QWizard): self.target_appvm.run("QUBESRPC %s dom0" % "qubes.SelectDirectory") else: - file_dialog = QFileDialog() + file_dialog = QtGui.QFileDialog() file_dialog.setReadOnly(True) file_dialog.getExistingDirectory( self, self.tr("Detach backup device"), @@ -304,7 +307,7 @@ class RestoreVMsWindow(Ui_Restore, QWizard): def reject(self): # TODO: probably not working too if self.currentPage() is self.commit_page: if self.backup_restore.canceled: - self.emit(SIGNAL("restore_progress(QString)"), + self.emit(QtCore.SIGNAL("restore_progress(QString)"), '{0}' .format(self.tr("Aborting the operation..."))) self.button(self.CancelButton).setDisabled(True) @@ -328,7 +331,7 @@ class RestoreVMsWindow(Ui_Restore, QWizard): return self.select_vms_widget.selected_list.count() > 0 def backup_location_changed(self, new_dir=None): - self.select_dir_page.emit(SIGNAL("completeChanged()")) + self.select_dir_page.emit(QtCore.SIGNAL("completeChanged()")) # Bases on the original code by: @@ -340,18 +343,18 @@ def handle_exception(exc_type, exc_value, exc_traceback): filename = os.path.basename(filename) error = "%s: %s" % (exc_type.__name__, exc_value) - QMessageBox.critical(None, "Houston, we have a problem...", + QtGui.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)) + % (line, filename)) def main(): - qtapp = QApplication(sys.argv) + qtapp = QtGui.QApplication(sys.argv) qtapp.setOrganizationName("The Qubes Project") qtapp.setOrganizationDomain("http://qubes-os.org") qtapp.setApplicationName("Qubes Restore VMs") From 7bcde2158eaa9df26f3c34a60f10e6e8326a6108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Sun, 10 Dec 2017 23:17:39 +0100 Subject: [PATCH 44/70] Added optional saving of default backup profile Added checkbox for saving default backup profile. Removed useless "should I encrypt" checkbox (yes, you have to encrypt). --- qubesmanager/backup.py | 22 ++++--- qubesmanager/backup_utils.py | 36 ++++++----- ui/backupdlg.ui | 122 +++++++++++++++++++++++------------ ui/restoredlg.ui | 7 +- 4 files changed, 119 insertions(+), 68 deletions(-) diff --git a/qubesmanager/backup.py b/qubesmanager/backup.py index f8ccd0b..ef79692 100644 --- a/qubesmanager/backup.py +++ b/qubesmanager/backup.py @@ -145,21 +145,20 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): self.passphrase_line_edit.setText(profile_data['passphrase_text']) self.passphrase_line_edit_verify.setText( profile_data['passphrase_text']) - # TODO: make a checkbox for saving the profile - # TODO: warn that unknown data will be overwritten if 'include' in profile_data: return profile_data['include'] return None - def save_settings(self): + def save_settings(self, use_temp): settings = {'destination_vm': self.appvm_combobox.currentText(), 'destination_path': self.dir_line_edit.text(), 'include': [vm.name for vm in self.selected_vms], 'passphrase_text': self.passphrase_line_edit.text()} # TODO: add compression when it is added - backup_utils.write_backup_profile(settings) + + backup_utils.write_backup_profile(settings, use_temp) class VmListItem(QtGui.QListWidgetItem): def __init__(self, vm): @@ -267,9 +266,9 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): self.appvm_combobox.currentText()] if not vm.is_running(): vm.start() - self.qvm_collection.qubesd_call('dom0', - 'admin.backup.Execute', - 'qubes-manager-backup') + self.qvm_collection.qubesd_call( + 'dom0', 'admin.backup.Execute', + backup_utils.get_profile_name(True)) except exc.QubesException as err: # TODO fixme print('\nBackup error: {}'.format(err), file=sys.stderr) @@ -288,15 +287,20 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): old_sigchld_handler = signal.signal(signal.SIGCHLD, signal.SIG_DFL) if self.currentPage() is self.confirm_page: - self.save_settings() + self.save_settings(True) backup_summary = self.qvm_collection.qubesd_call( - 'dom0', 'admin.backup.Info', 'qubes-manager-backup') + 'dom0', 'admin.backup.Info', + backup_utils.get_profile_name(True)) self.textEdit.setReadOnly(True) self.textEdit.setFontFamily("Monospace") self.textEdit.setText(backup_summary.decode()) elif self.currentPage() is self.commit_page: + + if self.save_profile_checkbox.isChecked(): + self.save_settings(False) + self.button(self.FinishButton).setDisabled(True) self.showFileDialog.setEnabled( self.appvm_combobox.currentIndex() != 0) diff --git a/qubesmanager/backup_utils.py b/qubesmanager/backup_utils.py index 083c4da..4d21804 100644 --- a/qubesmanager/backup_utils.py +++ b/qubesmanager/backup_utils.py @@ -30,10 +30,6 @@ import yaml path_re = re.compile(r"[a-zA-Z0-9/:.,_+=() -]*") path_max_len = 512 -# TODO: replace it with a more dynamic approach: allowing user to turn on or off -# profile saving -backup_profile_path = '/etc/qubes/backup/qubes-manager-backup.conf' - def fill_appvms_list(dialog): dialog.appvm_combobox.clear() @@ -90,20 +86,16 @@ def select_path_button_clicked(dialog, select_file=False): dialog.select_dir_page.emit(QtCore.SIGNAL("completeChanged()")) -def load_backup_profile(): - with open(backup_profile_path) as profile_file: +def load_backup_profile(use_temp=False): + + path = get_profile_path(use_temp) + + with open(path) as profile_file: profile_data = yaml.safe_load(profile_file) return profile_data -def write_backup_profile(args): - '''Format limited backup profile (for GUI purposes and print it to - *output_stream* (a file or stdout) - - :param output_stream: file-like object ro print the profile to - :param args: dictionary with arguments - :param passphrase: passphrase to use - ''' +def write_backup_profile(args, use_temp=False): acceptable_fields = ['include', 'passphrase_text', 'compression', 'destination_vm', 'destination_path'] @@ -111,6 +103,20 @@ def write_backup_profile(args): profile_data = {key: value for key, value in args.items() if key in acceptable_fields} + path = get_profile_path(use_temp) + # TODO add compression parameter to GUI issue#943 - with open(backup_profile_path, 'w') as profile_file: + with open(path, 'w') as profile_file: yaml.safe_dump(profile_data, profile_file) + + +def get_profile_name(use_temp): + backup_profile_name = 'qubes-manager-backup' + temp_backup_profile_name = 'qubes-manager-backup-tmp' + + return temp_backup_profile_name if use_temp else backup_profile_name + + +def get_profile_path(use_temp): + path = '/etc/qubes/backup/' + get_profile_name(use_temp) + '.conf' + return path diff --git a/ui/backupdlg.ui b/ui/backupdlg.ui index 6b50497..93e970a 100644 --- a/ui/backupdlg.ui +++ b/ui/backupdlg.ui @@ -6,8 +6,8 @@ 0 0 - 650 - 399 + 737 + 420 @@ -137,6 +137,12 @@ + + + 0 + 0 + + Backup destination directory @@ -176,71 +182,103 @@ + + + 0 + 0 + + Backup security - + + QFormLayout::AllNonFixedFieldsGrow + + <html><head/><body><p>Encryption / Verification<br/>passphrase:</p></body></html> - - - - Encrypt backup: - - - - - - - Qt::LeftToRight - - - - - - true - - - - - - - <html><head/><body><p>Reenter passphrase:</p></body></html> - - - - + QLineEdit::Password - + + + + <html><head/><body><p>Reenter passphrase:</p></body></html> + + + + QLineEdit::Password - - - - <b>Note:</b> Even if you choose not to encrypt your backup, a password is still required in order to provide authenticated verification of the data. See the emergency restore instructions for more info. - - - true - - - + + + + + 0 + 0 + + + + Save backup profile + + + + + 10 + 30 + 267 + 25 + + + + Save settings as default backup profile: + + + + + + 270 + 30 + 539 + 25 + + + + + + + true + + + checkBox + groupBox_2 + groupBox_2 + label_8 + save_profile_checkbox + groupBox_2 + label_8 + label_8 + save_profile_checkbox + label_8 + save_profile_checkbox + +
diff --git a/ui/restoredlg.ui b/ui/restoredlg.ui index cc37988..3b377e4 100644 --- a/ui/restoredlg.ui +++ b/ui/restoredlg.ui @@ -147,6 +147,9 @@ + + true + @@ -211,8 +214,8 @@ <!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:10pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:9pt;"><br /></p></body></html> +</style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p></body></html>
From 7848b6b2c3b77ab56779119a86248729b966aeb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Sun, 10 Dec 2017 23:56:22 +0100 Subject: [PATCH 45/70] Fixed inability to select which VMs to restore Fixed bug that caused all VMs to be restored, regardless of what the user selected. --- qubesmanager/restore.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/qubesmanager/restore.py b/qubesmanager/restore.py index 46e56f5..4379a78 100644 --- a/qubesmanager/restore.py +++ b/qubesmanager/restore.py @@ -64,8 +64,6 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): self.tmpdir_to_remove = None self.error_detected = Event() - self.excluded = {} - self.setupUi(self) self.select_vms_widget = multiselectwidget.MultiSelectWidget(self) @@ -95,7 +93,6 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): QtCore.SIGNAL("completeChanged()")) backup_utils.fill_appvms_list(self) -# self.__init_restore_options__() @QtCore.pyqtSlot(name='on_select_path_button_clicked') def select_path_button_clicked(self): @@ -214,21 +211,20 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): 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 = {} + + self.vms_to_restore = self.backup_restore.get_restore_info() + 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)] + vmname = self.select_vms_widget.available_list.item(i).text() del self.vms_to_restore[str(vmname)] - del self.func_output[:] - # TODO: am I ignoring changes made by user? self.vms_to_restore = self.backup_restore.restore_info_verify( - self.backup_restore.get_restore_info()) + self.vms_to_restore) + self.func_output = self.backup_restore.get_restore_summary( - self.backup_restore.get_restore_info() + self.vms_to_restore ) + self.confirm_text_edit.setReadOnly(True) self.confirm_text_edit.setFontFamily("Monospace") self.confirm_text_edit.setText(self.func_output) @@ -243,7 +239,7 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): .count("media/") > 0) self.thread_monitor = thread_monitor.ThreadMonitor() - thread = threading.Thread (target= self.__do_restore__, args=(self.thread_monitor,)) + thread = threading.Thread (target=self.__do_restore__, args=(self.thread_monitor,)) thread.daemon = True thread.start() From 4e2835531ec54f63dfadc9596471514ae9b30636 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Mon, 11 Dec 2017 00:13:15 +0100 Subject: [PATCH 46/70] Usability fixes Added label to inform the user only running VMs are listed in backup GUI; made "ignore missing" label clearer. --- qubesmanager/backup.py | 4 -- qubesmanager/backup_utils.py | 9 +-- qubesmanager/restore.py | 4 +- ui/backupdlg.ui | 106 +++++++++++++++++++---------------- ui/restoredlg.ui | 2 +- 5 files changed, 62 insertions(+), 63 deletions(-) diff --git a/qubesmanager/backup.py b/qubesmanager/backup.py index ef79692..1ca9af3 100644 --- a/qubesmanager/backup.py +++ b/qubesmanager/backup.py @@ -105,10 +105,6 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): self.total_size = 0 - # TODO: is the is_running criterion really necessary? It's designed to - # avoid backuping a VM into itself or surprising the user with starting - # a VM when they didn't plan to. - # TODO: inform the user only running VMs are listed? self.target_vm_list, self.target_vm_idx = utils.prepare_vm_choice( self.appvm_combobox, self.qvm_collection, diff --git a/qubesmanager/backup_utils.py b/qubesmanager/backup_utils.py index 4d21804..3214c42 100644 --- a/qubesmanager/backup_utils.py +++ b/qubesmanager/backup_utils.py @@ -43,12 +43,7 @@ def fill_appvms_list(dialog): if vm.klass == 'TemplateVM' and vm.installed_by_rpm: continue - # TODO: is the is_running criterion really necessary? It's designed to - # avoid backuping a VM into itself or surprising the user with starting - # a VM when they didn't plan to. - # TODO: remove debug - debug = True - if (debug or vm.is_running()) and vm.qid != 0: + if vm.is_running() and vm.qid != 0: dialog.appvm_combobox.addItem(vm.name) @@ -63,7 +58,6 @@ def select_path_button_clicked(dialog, select_file=False): file_dialog.setReadOnly(True) new_path = None - # TODO: check if dom0 is available new_appvm = str(dialog.appvm_combobox.currentText()) vm = dialog.qvm_collection.domains[new_appvm] @@ -78,7 +72,6 @@ def select_path_button_clicked(dialog, select_file=False): dialog.tr("Nothing selected!"), dialog.tr("No file or directory selected.")) - # TODO: check if this works for restore if new_path: dialog.dir_line_edit.setText(new_path) diff --git a/qubesmanager/restore.py b/qubesmanager/restore.py index 4379a78..140e86f 100644 --- a/qubesmanager/restore.py +++ b/qubesmanager/restore.py @@ -121,7 +121,7 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): self.select_vms_widget.selected_list.clear() self.select_vms_widget.available_list.clear() - self.target_appvm = None # TODO: what is the purpose of this + self.target_appvm = None if self.appvm_combobox.currentIndex() != 0: # An existing appvm chosen self.target_appvm = self.qvm_collection.domains[ str(self.appvm_combobox.currentText())] @@ -134,8 +134,6 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): self.passphrase_line_edit.text() ) - # TODO: change text of ignore missing to ignore - # missing templates and netvms if self.ignore_missing.isChecked(): self.backup_restore.options.use_default_template = True self.backup_restore.options.use_default_netvm = True diff --git a/ui/backupdlg.ui b/ui/backupdlg.ui index 93e970a..1126671 100644 --- a/ui/backupdlg.ui +++ b/ui/backupdlg.ui @@ -180,53 +180,7 @@ - - - - - 0 - 0 - - - - Backup security - - - - QFormLayout::AllNonFixedFieldsGrow - - - - - <html><head/><body><p>Encryption / Verification<br/>passphrase:</p></body></html> - - - - - - - QLineEdit::Password - - - - - - - <html><head/><body><p>Reenter passphrase:</p></body></html> - - - - - - - QLineEdit::Password - - - - - - - + @@ -279,6 +233,64 @@ save_profile_checkbox + + + + + 0 + 0 + + + + Backup security + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + <html><head/><body><p>Encryption / Verification<br/>passphrase:</p></body></html> + + + + + + + QLineEdit::Password + + + + + + + <html><head/><body><p>Reenter passphrase:</p></body></html> + + + + + + + QLineEdit::Password + + + + + + + + + + + true + + + + NOTE: Only running VMs are listed. + + + diff --git a/ui/restoredlg.ui b/ui/restoredlg.ui index 3b377e4..ba31f6b 100644 --- a/ui/restoredlg.ui +++ b/ui/restoredlg.ui @@ -52,7 +52,7 @@ Ignore missing templates or netvms, restore VMs anyway. - ignore missing + ignore missing templates and net VMs From a1c6e7256999f0902334cd30764f56fef4e4e864 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Mon, 11 Dec 2017 00:50:40 +0100 Subject: [PATCH 47/70] First pylint pass Fixed errors pointed out by pylint. --- qubesmanager/backup.py | 50 +++++++++---------- qubesmanager/backup_utils.py | 2 +- qubesmanager/restore.py | 64 +++++++++++-------------- test-packages/qubes/storage/__init__.py | 1 + test-packages/qubes/storage/file.py | 2 + 5 files changed, 56 insertions(+), 63 deletions(-) create mode 100644 test-packages/qubes/storage/__init__.py create mode 100644 test-packages/qubes/storage/file.py diff --git a/qubesmanager/backup.py b/qubesmanager/backup.py index 1ca9af3..4a0879a 100644 --- a/qubesmanager/backup.py +++ b/qubesmanager/backup.py @@ -1,5 +1,4 @@ #!/usr/bin/python3 -# pylint: skip-file # # The Qubes OS Project, http://www.qubes-os.org # @@ -30,7 +29,7 @@ from qubesadmin import Qubes, events, exc from qubesadmin import utils as admin_utils from qubes.storage.file import get_disk_usage -from PyQt4 import QtCore # pylint: disable=import-error +from PyQt4 import QtCore # pylint: disable=import-error from PyQt4 import QtGui # pylint: disable=import-error from . import ui_backupdlg from . import multiselectwidget @@ -57,12 +56,10 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): self.qvm_collection = qvm_collection self.backup_settings = QtCore.QSettings() - self.func_output = [] self.selected_vms = [] self.tmpdir_to_remove = None self.canceled = False - - self.files_to_backup = None + self.thread_monitor = None self.setupUi(self) @@ -122,7 +119,12 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): def load_settings(self): try: profile_data = backup_utils.load_backup_profile() - except Exception as ex: # TODO: fix just for file not found + except FileNotFoundError as ex: # pylint: disable=unused-variable + return + except exc.QubesException as qex: # pylint: disable=unused-variable + QtGui.QMessageBox.information( + None, self.tr("Error loading backup profile"), + self.tr("Unable to load saved backup profile.")) return if not profile_data: return @@ -157,6 +159,7 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): backup_utils.write_backup_profile(settings, use_temp) class VmListItem(QtGui.QListWidgetItem): + # pylint: disable=too-few-public-methods def __init__(self, vm): self.vm = vm if vm.qid == 0: @@ -207,6 +210,7 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): backup_utils.select_path_button_clicked(self) def validateCurrentPage(self): + # pylint: disable=invalid-name if self.currentPage() is self.select_vms_page: self.selected_vms = [] @@ -243,13 +247,10 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): return True -# def gather_output(self, s): -# self.func_output.append(s) - def update_progress_bar(self, value): self.emit(QtCore.SIGNAL("backup_progress(int)"), value) - def __do_backup__(self, thread_monitor): + def __do_backup__(self, t_monitor): msg = [] try: @@ -257,29 +258,23 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): events_dispatcher = events.EventsDispatcher(self.app) events_dispatcher.add_handler('backup-progress', self.update_progress_bar) - try: - vm = self.qvm_collection.domains[ - self.appvm_combobox.currentText()] - if not vm.is_running(): - vm.start() - self.qvm_collection.qubesd_call( - 'dom0', 'admin.backup.Execute', - backup_utils.get_profile_name(True)) - except exc.QubesException as err: - # TODO fixme - print('\nBackup error: {}'.format(err), file=sys.stderr) - return 1 - except Exception as ex: # TODO: fixme - print("Exception:", ex) + vm = self.qvm_collection.domains[ + self.appvm_combobox.currentText()] + if not vm.is_running(): + vm.start() + self.qvm_collection.qubesd_call( + 'dom0', 'admin.backup.Execute', + backup_utils.get_profile_name(True)) + except Exception as ex: # pylint: disable=broad-except msg.append(str(ex)) if len(msg) > 0: - thread_monitor.set_error_msg('\n'.join(msg)) + t_monitor.set_error_msg('\n'.join(msg)) - thread_monitor.set_finished() + t_monitor.set_finished() - def current_page_changed(self, id): + def current_page_changed(self, page_id): # pylint: disable=unused-argument old_sigchld_handler = signal.signal(signal.SIGCHLD, signal.SIG_DFL) if self.currentPage() is self.confirm_page: @@ -371,6 +366,7 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): return len(self.dir_line_edit.text()) > 0 def backup_location_changed(self, new_dir=None): + # pylint: disable=unused-argument self.select_dir_page.emit(QtCore.SIGNAL("completeChanged()")) diff --git a/qubesmanager/backup_utils.py b/qubesmanager/backup_utils.py index 3214c42..6590b2f 100644 --- a/qubesmanager/backup_utils.py +++ b/qubesmanager/backup_utils.py @@ -1,5 +1,4 @@ #!/usr/bin/python2 -# pylint: skip-file # # The Qubes OS Project, http://www.qubes-os.org # @@ -67,6 +66,7 @@ def select_path_button_clicked(dialog, select_file=False): "qubes.SelectFile" if select_file else "qubes.SelectDirectory") except subprocess.CalledProcessError as ex: + # pylint: disable=unused-variable QtGui.QMessageBox.warning( None, dialog.tr("Nothing selected!"), diff --git a/qubesmanager/restore.py b/qubesmanager/restore.py index 140e86f..72418fe 100644 --- a/qubesmanager/restore.py +++ b/qubesmanager/restore.py @@ -1,5 +1,4 @@ #!/usr/bin/python2 -# pylint: skip-file # # The Qubes OS Project, http://www.qubes-os.org # @@ -22,13 +21,12 @@ # import sys -import os import shutil from PyQt4 import QtCore from PyQt4 import QtGui -from . import thread_monitor import threading import time +import os import os.path import traceback @@ -38,8 +36,9 @@ from qubes import backup from . import ui_restoredlg from . import multiselectwidget - from . import backup_utils +from . import thread_monitor + from multiprocessing import Queue, Event from multiprocessing.queues import Empty from qubesadmin import Qubes, exc @@ -56,13 +55,15 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): self.app = app self.qvm_collection = qvm_collection - self.restore_options = None self.vms_to_restore = None self.func_output = [] self.feedback_queue = Queue() self.canceled = False self.tmpdir_to_remove = None self.error_detected = Event() + self.thread_monitor = None + self.backup_restore = None + self.target_appvm = None self.setupUi(self) @@ -70,12 +71,14 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): self.select_vms_layout.insertWidget(1, self.select_vms_widget) self.connect(self, - QtCore.SIGNAL("currentIdChanged(int)"), self.current_page_changed) + QtCore.SIGNAL("currentIdChanged(int)"), + self.current_page_changed) self.connect(self, QtCore.SIGNAL("restore_progress(QString)"), self.commit_text_edit.append) self.connect(self, - QtCore.SIGNAL("backup_progress(int)"), self.progress_bar.setValue) + QtCore.SIGNAL("backup_progress(int)"), + self.progress_bar.setValue) self.dir_line_edit.connect(self.dir_line_edit, QtCore.SIGNAL("textChanged(QString)"), self.backup_location_changed) @@ -98,17 +101,7 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): def select_path_button_clicked(self): backup_utils.select_path_button_clicked(self, True) - 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_verify_only_toogled(self, checked): - self.restore_options['verify-only'] = bool(checked) - - def cleanupPage(self, p_int): + def cleanupPage(self, p_int): # pylint: disable=invalid-name if self.page(p_int) is self.select_vms_page: self.vms_to_restore = None else: @@ -154,22 +147,20 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): except exc.QubesException as ex: QtGui.QMessageBox.warning(None, self.tr("Restore error!"), str(ex)) - def gather_output(self, s): - self.func_output.append(s) - - def restore_error_output(self, s): + def restore_error_output(self, text): self.error_detected.set() self.feedback_queue.put((QtCore.SIGNAL("restore_progress(QString)"), - u'{0}'.format(s))) + u'{0}'.format(text))) - def restore_output(self, s): - self.feedback_queue.put((QtCore.SIGNAL("restore_progress(QString)"), - u'{0}'.format(s))) + def restore_output(self, text): + self.feedback_queue.put(( + QtCore.SIGNAL("restore_progress(QString)"), + u'{0}'.format(text))) def update_progress_bar(self, value): self.feedback_queue.put((QtCore.SIGNAL("backup_progress(int)"), value)) - def __do_restore__(self, thread_monitor): + def __do_restore__(self, t_monitor): err_msg = [] try: self.backup_restore.progress_callback = self.update_progress_bar @@ -179,7 +170,7 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): self.canceled = True self.tmpdir_to_remove = ex.tmpdir err_msg.append(str(ex)) - except Exception as ex: + except Exception as ex: # pylint: disable=broad-except err_msg.append(str(ex)) err_msg.append( self.tr("Partially restored files left in /var/tmp/restore_*, " @@ -191,7 +182,7 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): .format(self.tr("Restore aborted!"))) elif len(err_msg) > 0 or self.error_detected.is_set(): if len(err_msg) > 0: - thread_monitor.set_error_msg('\n'.join(err_msg)) + t_monitor.set_error_msg('\n'.join(err_msg)) self.emit(QtCore.SIGNAL("restore_progress(QString)"), '{0}' .format(self.tr("Finished with errors!"))) @@ -200,9 +191,9 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): '{0}' .format(self.tr("Finished successfully!"))) - thread_monitor.set_finished() + t_monitor.set_finished() - def current_page_changed(self, id): + def current_page_changed(self, page_id): # pylint: disable=unused-argument old_sigchld_handler = signal.signal(signal.SIGCHLD, signal.SIG_DFL) if self.currentPage() is self.select_vms_page: @@ -237,16 +228,18 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): .count("media/") > 0) self.thread_monitor = thread_monitor.ThreadMonitor() - thread = threading.Thread (target=self.__do_restore__, args=(self.thread_monitor,)) + 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) + time.sleep(0.1) try: - for (signal_to_emit,data) in iter(self.feedback_queue.get_nowait,None): - self.emit(signal_to_emit,data) + for (signal_to_emit, data) in iter( + self.feedback_queue.get_nowait, None): + self.emit(signal_to_emit, data) except Empty: pass @@ -325,6 +318,7 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): return self.select_vms_widget.selected_list.count() > 0 def backup_location_changed(self, new_dir=None): + # pylint: disable=unused-argument self.select_dir_page.emit(QtCore.SIGNAL("completeChanged()")) diff --git a/test-packages/qubes/storage/__init__.py b/test-packages/qubes/storage/__init__.py new file mode 100644 index 0000000..2ae2839 --- /dev/null +++ b/test-packages/qubes/storage/__init__.py @@ -0,0 +1 @@ +pass diff --git a/test-packages/qubes/storage/file.py b/test-packages/qubes/storage/file.py new file mode 100644 index 0000000..3171fd1 --- /dev/null +++ b/test-packages/qubes/storage/file.py @@ -0,0 +1,2 @@ +def get_disk_usage(*args): + return None From 78b4747e81310c69aafe8f7b05418b61d3bb0c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Mon, 11 Dec 2017 01:09:29 +0100 Subject: [PATCH 48/70] Second pylint pass Fixed errors pointed out by pylint on server. --- qubesmanager/backup.py | 12 ++++++------ qubesmanager/backup_utils.py | 9 ++++----- qubesmanager/restore.py | 10 +++++----- test-packages/qubes/backup/__init__.py | 5 +++++ test-packages/qubes/storage/file.py | 2 +- test-packages/qubesadmin/backup/__init__.py | 1 + .../qubesadmin/backup/restore/__init__.py | 19 +++++++++++++++++++ test-packages/qubesadmin/events.py | 5 +++++ test-packages/qubesadmin/exc.py | 2 +- test-packages/qubesadmin/utils.py | 3 +++ 10 files changed, 50 insertions(+), 18 deletions(-) create mode 100644 test-packages/qubes/backup/__init__.py create mode 100644 test-packages/qubesadmin/backup/__init__.py create mode 100644 test-packages/qubesadmin/backup/restore/__init__.py create mode 100644 test-packages/qubesadmin/events.py diff --git a/qubesmanager/backup.py b/qubesmanager/backup.py index 4a0879a..ccdd531 100644 --- a/qubesmanager/backup.py +++ b/qubesmanager/backup.py @@ -31,7 +31,7 @@ from qubes.storage.file import get_disk_usage from PyQt4 import QtCore # pylint: disable=import-error from PyQt4 import QtGui # pylint: disable=import-error -from . import ui_backupdlg +from . import ui_backupdlg # pylint: disable=no-name-in-module from . import multiselectwidget from . import backup_utils @@ -119,9 +119,9 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): def load_settings(self): try: profile_data = backup_utils.load_backup_profile() - except FileNotFoundError as ex: # pylint: disable=unused-variable + except FileNotFoundError: return - except exc.QubesException as qex: # pylint: disable=unused-variable + except exc.QubesException: QtGui.QMessageBox.information( None, self.tr("Error loading backup profile"), self.tr("Unable to load saved backup profile.")) @@ -232,7 +232,7 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): self.tr("Selected directory do not exists or " "not a directory (%s).") % backup_location) return False - if not len(self.passphrase_line_edit.text()): + if not self.passphrase_line_edit.text(): QtGui.QMessageBox.information( None, self.tr("Wait!"), self.tr("Enter passphrase for backup " @@ -268,7 +268,7 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): except Exception as ex: # pylint: disable=broad-except msg.append(str(ex)) - if len(msg) > 0: + if msg: t_monitor.set_error_msg('\n'.join(msg)) t_monitor.set_finished() @@ -358,7 +358,7 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): return self.select_vms_widget.selected_list.count() > 0 def has_selected_dir_and_pass(self): - if not len(self.passphrase_line_edit.text()): + if not self.passphrase_line_edit.text(): return False if self.passphrase_line_edit.text() != \ self.passphrase_line_edit_verify.text(): diff --git a/qubesmanager/backup_utils.py b/qubesmanager/backup_utils.py index 6590b2f..7774f77 100644 --- a/qubesmanager/backup_utils.py +++ b/qubesmanager/backup_utils.py @@ -19,8 +19,8 @@ # # import re -from PyQt4 import QtGui -from PyQt4 import QtCore +from PyQt4 import QtGui # pylint: disable=import-error +from PyQt4 import QtCore # pylint: disable=import-error import subprocess from . import utils @@ -65,8 +65,7 @@ def select_path_button_clicked(dialog, select_file=False): vm, "qubes.SelectFile" if select_file else "qubes.SelectDirectory") - except subprocess.CalledProcessError as ex: - # pylint: disable=unused-variable + except subprocess.CalledProcessError: QtGui.QMessageBox.warning( None, dialog.tr("Nothing selected!"), @@ -75,7 +74,7 @@ def select_path_button_clicked(dialog, select_file=False): if new_path: dialog.dir_line_edit.setText(new_path) - if new_path and len(backup_location) > 0: + if new_path and backup_location: dialog.select_dir_page.emit(QtCore.SIGNAL("completeChanged()")) diff --git a/qubesmanager/restore.py b/qubesmanager/restore.py index 72418fe..8425946 100644 --- a/qubesmanager/restore.py +++ b/qubesmanager/restore.py @@ -22,8 +22,8 @@ import sys import shutil -from PyQt4 import QtCore -from PyQt4 import QtGui +from PyQt4 import QtCore # pylint: disable=import-error +from PyQt4 import QtGui # pylint: disable=import-error import threading import time import os @@ -34,7 +34,7 @@ import signal from qubes import backup -from . import ui_restoredlg +from . import ui_restoredlg # pylint: disable=no-name-in-module from . import multiselectwidget from . import backup_utils from . import thread_monitor @@ -180,8 +180,8 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): self.emit(QtCore.SIGNAL("restore_progress(QString)"), '{0}' .format(self.tr("Restore aborted!"))) - elif len(err_msg) > 0 or self.error_detected.is_set(): - if len(err_msg) > 0: + elif err_msg or self.error_detected.is_set(): + if err_msg: t_monitor.set_error_msg('\n'.join(err_msg)) self.emit(QtCore.SIGNAL("restore_progress(QString)"), '{0}' diff --git a/test-packages/qubes/backup/__init__.py b/test-packages/qubes/backup/__init__.py new file mode 100644 index 0000000..dc24fbb --- /dev/null +++ b/test-packages/qubes/backup/__init__.py @@ -0,0 +1,5 @@ +class BackupCanceledError(BaseException): + + tmpdir = None + + pass diff --git a/test-packages/qubes/storage/file.py b/test-packages/qubes/storage/file.py index 3171fd1..1503cca 100644 --- a/test-packages/qubes/storage/file.py +++ b/test-packages/qubes/storage/file.py @@ -1,2 +1,2 @@ def get_disk_usage(*args): - return None + return 0 diff --git a/test-packages/qubesadmin/backup/__init__.py b/test-packages/qubesadmin/backup/__init__.py new file mode 100644 index 0000000..fc80254 --- /dev/null +++ b/test-packages/qubesadmin/backup/__init__.py @@ -0,0 +1 @@ +pass \ No newline at end of file diff --git a/test-packages/qubesadmin/backup/restore/__init__.py b/test-packages/qubesadmin/backup/restore/__init__.py new file mode 100644 index 0000000..aa328db --- /dev/null +++ b/test-packages/qubesadmin/backup/restore/__init__.py @@ -0,0 +1,19 @@ +class BackupRestore(object): + + options = object() + canceled = None + + def get_restore_info(self, *args): + pass + + def restore_do(self, *args): + pass + + def get_restore_info(self, *args): + pass + + def restore_info_verify(self, *args): + pass + + def get_restore_summary(self, *args): + pass diff --git a/test-packages/qubesadmin/events.py b/test-packages/qubesadmin/events.py new file mode 100644 index 0000000..68c1b58 --- /dev/null +++ b/test-packages/qubesadmin/events.py @@ -0,0 +1,5 @@ +class EventsDispatcher(object): + + def add_handler(self, *args): + pass + pass diff --git a/test-packages/qubesadmin/exc.py b/test-packages/qubesadmin/exc.py index 087af2a..a3635d7 100644 --- a/test-packages/qubesadmin/exc.py +++ b/test-packages/qubesadmin/exc.py @@ -1,4 +1,4 @@ -### mock qubesadmin.exc module +# pylint: disable=unused-variable### mock qubesadmin.exc module class QubesException(BaseException): pass diff --git a/test-packages/qubesadmin/utils.py b/test-packages/qubesadmin/utils.py index afb7725..6915104 100644 --- a/test-packages/qubesadmin/utils.py +++ b/test-packages/qubesadmin/utils.py @@ -5,3 +5,6 @@ def parse_size(*args, **kwargs): def updates_vms_status(*args, **kwargs): return args[0] + +def size_to_human(*args, **kwargs): + return args[0] From a4a58cc527584fa792311a61833df2762decac77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Mon, 11 Dec 2017 01:32:26 +0100 Subject: [PATCH 49/70] Fixed progress bars Partial fix - the progress bars now move, although they do not really indicate progress (just dance back and forth). Future fix would include making a tray icon to signal backup progress instead. --- qubesmanager/backup.py | 12 +----------- qubesmanager/restore.py | 12 +----------- ui/backupdlg.ui | 16 ++++------------ ui/restoredlg.ui | 3 +++ 4 files changed, 9 insertions(+), 34 deletions(-) diff --git a/qubesmanager/backup.py b/qubesmanager/backup.py index ccdd531..5660ecc 100644 --- a/qubesmanager/backup.py +++ b/qubesmanager/backup.py @@ -47,8 +47,6 @@ import time class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): - __pyqtSignals__ = ("backup_progress(int)",) - def __init__(self, app, qvm_collection, parent=None): super(BackupVMsWindow, self).__init__(parent) @@ -77,8 +75,6 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): self.connect(self.select_vms_widget, QtCore.SIGNAL("items_added(PyQt_PyObject)"), self.vms_added) - self.connect(self, QtCore.SIGNAL("backup_progress(int)"), - self.progress_bar.setValue) self.dir_line_edit.connect(self.dir_line_edit, QtCore.SIGNAL("textChanged(QString)"), self.backup_location_changed) @@ -247,17 +243,10 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): return True - def update_progress_bar(self, value): - self.emit(QtCore.SIGNAL("backup_progress(int)"), value) - def __do_backup__(self, t_monitor): msg = [] try: - # TODO: this does nothing, events are not handled - events_dispatcher = events.EventsDispatcher(self.app) - events_dispatcher.add_handler('backup-progress', - self.update_progress_bar) vm = self.qvm_collection.domains[ self.appvm_combobox.currentText()] if not vm.is_running(): @@ -328,6 +317,7 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): self.tr("ERROR: {}").format( self.thread_monitor.error_msg)) else: + self.progress_bar.setMaximum(100) self.progress_bar.setValue(100) self.progress_status.setText(self.tr("Backup finished.")) if self.showFileDialog.isChecked(): diff --git a/qubesmanager/restore.py b/qubesmanager/restore.py index 8425946..11166a2 100644 --- a/qubesmanager/restore.py +++ b/qubesmanager/restore.py @@ -47,8 +47,6 @@ from qubesadmin.backup import restore class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): - __pyqtSignals__ = ("restore_progress(int)", "backup_progress(int)") - def __init__(self, app, qvm_collection, parent=None): super(RestoreVMsWindow, self).__init__(parent) @@ -76,14 +74,9 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): self.connect(self, QtCore.SIGNAL("restore_progress(QString)"), self.commit_text_edit.append) - self.connect(self, - QtCore.SIGNAL("backup_progress(int)"), - self.progress_bar.setValue) self.dir_line_edit.connect(self.dir_line_edit, QtCore.SIGNAL("textChanged(QString)"), self.backup_location_changed) - self.connect(self.verify_only, QtCore.SIGNAL("stateChanged(int)"), - self.on_verify_only_toogled) self.select_dir_page.isComplete = self.has_selected_dir self.select_vms_page.isComplete = self.has_selected_vms @@ -157,13 +150,9 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): QtCore.SIGNAL("restore_progress(QString)"), u'{0}'.format(text))) - def update_progress_bar(self, value): - self.feedback_queue.put((QtCore.SIGNAL("backup_progress(int)"), value)) - def __do_restore__(self, t_monitor): err_msg = [] try: - self.backup_restore.progress_callback = self.update_progress_bar self.backup_restore.restore_do(self.vms_to_restore) except backup.BackupCanceledError as ex: @@ -276,6 +265,7 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): file_dialog.getExistingDirectory( self, self.tr("Detach backup device"), os.path.dirname(self.dir_line_edit.text())) + self.progress_bar.setMaximum(100) self.progress_bar.setValue(100) self.button(self.FinishButton).setEnabled(True) self.button(self.CancelButton).setEnabled(False) diff --git a/ui/backupdlg.ui b/ui/backupdlg.ui index 1126671..48ec476 100644 --- a/ui/backupdlg.ui +++ b/ui/backupdlg.ui @@ -214,23 +214,12 @@ - + true - checkBox - groupBox_2 - groupBox_2 - label_8 - save_profile_checkbox - groupBox_2 - label_8 - label_8 - save_profile_checkbox - label_8 - save_profile_checkbox @@ -360,6 +349,9 @@ p, li { white-space: pre-wrap; } + + 0 + 0 diff --git a/ui/restoredlg.ui b/ui/restoredlg.ui index ba31f6b..284b867 100644 --- a/ui/restoredlg.ui +++ b/ui/restoredlg.ui @@ -244,6 +244,9 @@ p, li { white-space: pre-wrap; } + + 0 + 0 From 14ebf9dc6dddde178b8d15210cd6f6861305e341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Mon, 11 Dec 2017 02:04:06 +0100 Subject: [PATCH 50/70] Fixed opening file dialog at the end Now, when "open file dialog to unmount" option is enabled, a fie dialog actually opens. --- qubesmanager/backup.py | 8 ++--- qubesmanager/backup_utils.py | 17 ++++++----- qubesmanager/restore.py | 58 ++++++++++++++---------------------- 3 files changed, 35 insertions(+), 48 deletions(-) diff --git a/qubesmanager/backup.py b/qubesmanager/backup.py index 5660ecc..202a967 100644 --- a/qubesmanager/backup.py +++ b/qubesmanager/backup.py @@ -25,7 +25,7 @@ import traceback import signal import shutil -from qubesadmin import Qubes, events, exc +from qubesadmin import Qubes, exc from qubesadmin import utils as admin_utils from qubes.storage.file import get_disk_usage @@ -326,16 +326,14 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): orig_text + self.tr( " Please unmount your backup volume and cancel " "the file selection dialog.")) - if self.target_appvm: # FIXME I'm not sure if this works - self.target_appvm.run( - "QUBESRPC %s dom0" % "qubes.SelectDirectory") + backup_utils.select_path_button_clicked(self, False, True) self.button(self.CancelButton).setEnabled(False) self.button(self.FinishButton).setEnabled(True) self.showFileDialog.setEnabled(False) signal.signal(signal.SIGCHLD, old_sigchld_handler) def reject(self): - # cancell clicked while the backup is in progress. + # cancel clicked while the backup is in progress. # calling kill on tar. if self.currentPage() is self.commit_page: pass # TODO: this does nothing diff --git a/qubesmanager/backup_utils.py b/qubesmanager/backup_utils.py index 7774f77..4effd14 100644 --- a/qubesmanager/backup_utils.py +++ b/qubesmanager/backup_utils.py @@ -51,7 +51,7 @@ def enable_dir_line_edit(dialog, boolean): dialog.select_path_button.setEnabled(boolean) -def select_path_button_clicked(dialog, select_file=False): +def select_path_button_clicked(dialog, select_file=False, read_only=False): backup_location = str(dialog.dir_line_edit.text()) file_dialog = QtGui.QFileDialog() file_dialog.setReadOnly(True) @@ -66,15 +66,18 @@ def select_path_button_clicked(dialog, select_file=False): "qubes.SelectFile" if select_file else "qubes.SelectDirectory") except subprocess.CalledProcessError: - QtGui.QMessageBox.warning( - None, - dialog.tr("Nothing selected!"), - dialog.tr("No file or directory selected.")) + if not read_only: + QtGui.QMessageBox.warning( + None, + dialog.tr("Nothing selected!"), + dialog.tr("No file or directory selected.")) + else: + return - if new_path: + if new_path and not read_only: dialog.dir_line_edit.setText(new_path) - if new_path and backup_location: + if new_path and backup_location and not read_only: dialog.select_dir_page.emit(QtCore.SIGNAL("completeChanged()")) diff --git a/qubesmanager/restore.py b/qubesmanager/restore.py index 11166a2..1278624 100644 --- a/qubesmanager/restore.py +++ b/qubesmanager/restore.py @@ -71,9 +71,6 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): self.connect(self, QtCore.SIGNAL("currentIdChanged(int)"), self.current_page_changed) - self.connect(self, - QtCore.SIGNAL("restore_progress(QString)"), - self.commit_text_edit.append) self.dir_line_edit.connect(self.dir_line_edit, QtCore.SIGNAL("textChanged(QString)"), self.backup_location_changed) @@ -140,15 +137,15 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): except exc.QubesException as ex: QtGui.QMessageBox.warning(None, self.tr("Restore error!"), str(ex)) + def append_output(self, text): + self.commit_text_edit.append(text) + def restore_error_output(self, text): self.error_detected.set() - self.feedback_queue.put((QtCore.SIGNAL("restore_progress(QString)"), - u'{0}'.format(text))) + self.append_output(u'{0}'.format(text)) def restore_output(self, text): - self.feedback_queue.put(( - QtCore.SIGNAL("restore_progress(QString)"), - u'{0}'.format(text))) + self.append_output(u'{0}'.format(text)) def __do_restore__(self, t_monitor): err_msg = [] @@ -166,19 +163,16 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): "investigate them and/or clean them up")) if self.canceled: - self.emit(QtCore.SIGNAL("restore_progress(QString)"), - '{0}' - .format(self.tr("Restore aborted!"))) + self.append_output('{0}'.format( + self.tr("Restore aborted!"))) elif err_msg or self.error_detected.is_set(): if err_msg: t_monitor.set_error_msg('\n'.join(err_msg)) - self.emit(QtCore.SIGNAL("restore_progress(QString)"), - '{0}' - .format(self.tr("Finished with errors!"))) + self.append_output('{0}'.format( + self.tr("Finished with errors!"))) else: - self.emit(QtCore.SIGNAL("restore_progress(QString)"), - '{0}' - .format(self.tr("Finished successfully!"))) + self.append_output('{0}'.format( + self.tr("Finished successfully!"))) t_monitor.set_finished() @@ -249,24 +243,17 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): self.tr("Backup error!"), self.tr("ERROR: {0}").format( self.thread_monitor.error_msg)) - - if self.showFileDialog.isChecked(): # TODO: this is not working - self.emit(QtCore.SIGNAL("restore_progress(QString)"), - '{0}'.format( - self.tr( - "Please unmount your backup volume and cancel" - " the file selection dialog."))) - if self.target_appvm: # TODO does this work at all? - self.target_appvm.run("QUBESRPC %s dom0" % - "qubes.SelectDirectory") - else: - file_dialog = QtGui.QFileDialog() - file_dialog.setReadOnly(True) - file_dialog.getExistingDirectory( - self, self.tr("Detach backup device"), - os.path.dirname(self.dir_line_edit.text())) self.progress_bar.setMaximum(100) self.progress_bar.setValue(100) + + if self.showFileDialog.isChecked(): + self.append_output( + '{0}'.format( + self.tr("Please unmount your backup volume and cancel " + "the file selection dialog."))) + self.app.processEvents() + backup_utils.select_path_button_clicked(self, False, True) + self.button(self.FinishButton).setEnabled(True) self.button(self.CancelButton).setEnabled(False) self.showFileDialog.setEnabled(False) @@ -284,9 +271,8 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): def reject(self): # TODO: probably not working too if self.currentPage() is self.commit_page: if self.backup_restore.canceled: - self.emit(QtCore.SIGNAL("restore_progress(QString)"), - '{0}' - .format(self.tr("Aborting the operation..."))) + self.append_output('{0}'.format( + self.tr("Aborting the operation..."))) self.button(self.CancelButton).setDisabled(True) else: self.done(0) From 7fd0c583033656890f0cbd5c5431f7b493a08d90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Mon, 11 Dec 2017 02:26:36 +0100 Subject: [PATCH 51/70] Restored 'cancel' functionality Now user can actually cancel a backup/restore. --- qubesmanager/backup.py | 27 +++++++++++---------------- qubesmanager/restore.py | 24 ++++++------------------ 2 files changed, 17 insertions(+), 34 deletions(-) diff --git a/qubesmanager/backup.py b/qubesmanager/backup.py index 202a967..7dad50d 100644 --- a/qubesmanager/backup.py +++ b/qubesmanager/backup.py @@ -55,7 +55,6 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): self.backup_settings = QtCore.QSettings() self.selected_vms = [] - self.tmpdir_to_remove = None self.canceled = False self.thread_monitor = None @@ -300,16 +299,10 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): if not self.thread_monitor.success: if self.canceled: - self.progress_status.setText(self.tr("Backup aborted.")) - if self.tmpdir_to_remove: - if QtGui.QMessageBox.warning( - None, self.tr("Backup aborted"), - self.tr( - "Do you want to remove temporary files " - "from %s?") % self.tmpdir_to_remove, - QtGui.QMessageBox.Yes, - QtGui.QMessageBox.No) == QtGui.QMessageBox.Yes: - shutil.rmtree(self.tmpdir_to_remove) + self.progress_status.setText( + self.tr( + "Backup aborted. " + "Temporary file may be left at backup location.")) else: self.progress_status.setText(self.tr("Backup error.")) QtGui.QMessageBox.warning( @@ -333,12 +326,14 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): signal.signal(signal.SIGCHLD, old_sigchld_handler) def reject(self): - # cancel clicked while the backup is in progress. - # calling kill on tar. if self.currentPage() is self.commit_page: - pass # TODO: this does nothing - # if backup.backup_cancel(): - # self.button(self.CancelButton).setDisabled(True) + self.canceled = True + self.qvm_collection.qubesd_call( + 'dom0', 'admin.backup.Cancel', + backup_utils.get_profile_name(True)) + self.progress_bar.setMaximum(100) + self.progress_bar.setValue(0) + self.button(self.CancelButton).setDisabled(True) else: self.done(0) diff --git a/qubesmanager/restore.py b/qubesmanager/restore.py index 1278624..93950a2 100644 --- a/qubesmanager/restore.py +++ b/qubesmanager/restore.py @@ -57,7 +57,6 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): self.func_output = [] self.feedback_queue = Queue() self.canceled = False - self.tmpdir_to_remove = None self.error_detected = Event() self.thread_monitor = None self.backup_restore = None @@ -154,7 +153,6 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): except backup.BackupCanceledError as ex: self.canceled = True - self.tmpdir_to_remove = ex.tmpdir err_msg.append(str(ex)) except Exception as ex: # pylint: disable=broad-except err_msg.append(str(ex)) @@ -227,17 +225,7 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): pass if not self.thread_monitor.success: - if self.canceled: - if self.tmpdir_to_remove and \ - QtGui.QMessageBox.warning( - None, - self.tr("Restore aborted"), - self.tr("Do you want to remove temporary files " - "from %s?") % self.tmpdir_to_remove, - QtGui.QMessageBox.Yes, - QtGui.QMessageBox.No) == QtGui.QMessageBox.Yes: - shutil.rmtree(self.tmpdir_to_remove) - else: + if not self.canceled: QtGui.QMessageBox.warning( None, self.tr("Backup error!"), @@ -268,12 +256,12 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): return False return True - def reject(self): # TODO: probably not working too + def reject(self): if self.currentPage() is self.commit_page: - if self.backup_restore.canceled: - self.append_output('{0}'.format( - self.tr("Aborting the operation..."))) - self.button(self.CancelButton).setDisabled(True) + self.backup_restore.canceled = True + self.append_output('{0}'.format( + self.tr("Aborting the operation..."))) + self.button(self.CancelButton).setDisabled(True) else: self.done(0) From 67a32b57485f9e4db328cdb6d037b3f0ab9b500a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Mon, 11 Dec 2017 02:34:31 +0100 Subject: [PATCH 52/70] Added 'compression' checkbox to backup GUI Added 'compress the backup' checkbox to the backup GUI. fixes QubesOS/qubes-issues#943 --- qubesmanager/backup.py | 7 +++++-- qubesmanager/backup_utils.py | 1 - ui/backupdlg.ui | 10 ++++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/qubesmanager/backup.py b/qubesmanager/backup.py index 7dad50d..307babf 100644 --- a/qubesmanager/backup.py +++ b/qubesmanager/backup.py @@ -139,6 +139,9 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): self.passphrase_line_edit_verify.setText( profile_data['passphrase_text']) + if 'compression' in profile_data: + self.compress_checkbox.setChecked(profile_data['compression']) + if 'include' in profile_data: return profile_data['include'] @@ -148,8 +151,8 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): settings = {'destination_vm': self.appvm_combobox.currentText(), 'destination_path': self.dir_line_edit.text(), 'include': [vm.name for vm in self.selected_vms], - 'passphrase_text': self.passphrase_line_edit.text()} - # TODO: add compression when it is added + 'passphrase_text': self.passphrase_line_edit.text(), + 'compression': self.compress_checkbox.isChecked()} backup_utils.write_backup_profile(settings, use_temp) diff --git a/qubesmanager/backup_utils.py b/qubesmanager/backup_utils.py index 4effd14..74ad2f0 100644 --- a/qubesmanager/backup_utils.py +++ b/qubesmanager/backup_utils.py @@ -100,7 +100,6 @@ def write_backup_profile(args, use_temp=False): path = get_profile_path(use_temp) - # TODO add compression parameter to GUI issue#943 with open(path, 'w') as profile_file: yaml.safe_dump(profile_data, profile_file) diff --git a/ui/backupdlg.ui b/ui/backupdlg.ui index 48ec476..5330fb3 100644 --- a/ui/backupdlg.ui +++ b/ui/backupdlg.ui @@ -80,6 +80,16 @@ + + + + Compress the backup + + + true + + + From df31207028e1f1e0d2943392373005f80d6cf124 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Mon, 11 Dec 2017 02:41:50 +0100 Subject: [PATCH 53/70] Final tiny fix of unused import Threw out unused import of shutil. --- qubesmanager/backup.py | 1 - qubesmanager/restore.py | 1 - 2 files changed, 2 deletions(-) diff --git a/qubesmanager/backup.py b/qubesmanager/backup.py index 307babf..71a329b 100644 --- a/qubesmanager/backup.py +++ b/qubesmanager/backup.py @@ -23,7 +23,6 @@ import traceback import signal -import shutil from qubesadmin import Qubes, exc from qubesadmin import utils as admin_utils diff --git a/qubesmanager/restore.py b/qubesmanager/restore.py index 93950a2..28c5b80 100644 --- a/qubesmanager/restore.py +++ b/qubesmanager/restore.py @@ -21,7 +21,6 @@ # import sys -import shutil from PyQt4 import QtCore # pylint: disable=import-error from PyQt4 import QtGui # pylint: disable=import-error import threading From bb8abff988509f738b3bf02c19bbf394075bbd5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Sat, 6 Jan 2018 02:08:34 +0100 Subject: [PATCH 54/70] Suggested changes included changes as suggested by @marmarek. --- ci/pylintrc | 1 + qubesmanager/backup.py | 63 ++++++++++++++++++++++----------- qubesmanager/backup_utils.py | 48 ++++++++++++++++--------- qubesmanager/restore.py | 60 +++++++++++++++++-------------- test-packages/qubesadmin/exc.py | 4 ++- 5 files changed, 112 insertions(+), 64 deletions(-) diff --git a/ci/pylintrc b/ci/pylintrc index d8bf535..d79fa35 100644 --- a/ci/pylintrc +++ b/ci/pylintrc @@ -27,6 +27,7 @@ disable= duplicate-code, file-ignored, fixme, + inconsistent-return-statements, locally-disabled, locally-enabled, logging-format-interpolation, diff --git a/qubesmanager/backup.py b/qubesmanager/backup.py index 71a329b..27dcea1 100644 --- a/qubesmanager/backup.py +++ b/qubesmanager/backup.py @@ -46,11 +46,11 @@ import time class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): - def __init__(self, app, qvm_collection, parent=None): + def __init__(self, qt_app, qubes_app, parent=None): super(BackupVMsWindow, self).__init__(parent) - self.app = app - self.qvm_collection = qvm_collection + self.qt_app = qt_app + self.qubes_app = qubes_app self.backup_settings = QtCore.QSettings() self.selected_vms = [] @@ -98,9 +98,9 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): self.target_vm_list, self.target_vm_idx = utils.prepare_vm_choice( self.appvm_combobox, - self.qvm_collection, + self.qubes_app, None, - self.qvm_collection.domains['dom0'], + self.qubes_app.domains['dom0'], (lambda vm: vm.klass != 'TemplateVM' and vm.is_running()), allow_internal=False, allow_default=False, @@ -111,6 +111,13 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): self.__fill_vms_list__(selected) def load_settings(self): + """ + Helper function that tries to load existing backup profile + (default path: /etc/qubes/backup/qubes-manager-backup.conf ) + and then apply its contents to the Backup window. + :return: list of vms to include in backup, if it exists in the profile, + or None if it does not + """ try: profile_data = backup_utils.load_backup_profile() except FileNotFoundError: @@ -147,6 +154,13 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): return None def save_settings(self, use_temp): + """ + Helper function that saves backup profile to either + /etc/qubes/backup/qubes-manager-backup.conf or + /etc/qubes/backup/qubes-manager-backup-tmp.conf + :param use_temp: whether to use temporary profile (True) or the default + backup profile (False) + """ settings = {'destination_vm': self.appvm_combobox.currentText(), 'destination_path': self.dir_line_edit.text(), 'include': [vm.name for vm in self.selected_vms], @@ -169,7 +183,7 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): vm.name + " (" + admin_utils.size_to_human(self.size) + ")") def __fill_vms_list__(self, selected=None): - for vm in self.qvm_collection.domains: + for vm in self.qubes_app.domains: if vm.features.get('internal', False): continue @@ -248,11 +262,11 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): msg = [] try: - vm = self.qvm_collection.domains[ + vm = self.qubes_app.domains[ self.appvm_combobox.currentText()] if not vm.is_running(): vm.start() - self.qvm_collection.qubesd_call( + self.qubes_app.qubesd_call( 'dom0', 'admin.backup.Execute', backup_utils.get_profile_name(True)) except Exception as ex: # pylint: disable=broad-except @@ -263,13 +277,19 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): t_monitor.set_finished() + @staticmethod + def cleanup_temporary_files(): + try: + os.remove(backup_utils.get_profile_path(use_temp=True)) + except FileNotFoundError: + pass def current_page_changed(self, page_id): # pylint: disable=unused-argument old_sigchld_handler = signal.signal(signal.SIGCHLD, signal.SIG_DFL) if self.currentPage() is self.confirm_page: - self.save_settings(True) - backup_summary = self.qvm_collection.qubesd_call( + self.save_settings(use_temp=True) + backup_summary = self.qubes_app.qubesd_call( 'dom0', 'admin.backup.Info', backup_utils.get_profile_name(True)) @@ -280,7 +300,7 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): elif self.currentPage() is self.commit_page: if self.save_profile_checkbox.isChecked(): - self.save_settings(False) + self.save_settings(use_temp=False) self.button(self.FinishButton).setDisabled(True) self.showFileDialog.setEnabled( @@ -296,7 +316,7 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): thread.start() while not self.thread_monitor.is_finished(): - self.app.processEvents() + self.qt_app.processEvents() time.sleep(0.1) if not self.thread_monitor.success: @@ -325,18 +345,21 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): self.button(self.CancelButton).setEnabled(False) self.button(self.FinishButton).setEnabled(True) self.showFileDialog.setEnabled(False) + self.cleanup_temporary_files() signal.signal(signal.SIGCHLD, old_sigchld_handler) def reject(self): if self.currentPage() is self.commit_page: self.canceled = True - self.qvm_collection.qubesd_call( + self.qubes_app.qubesd_call( 'dom0', 'admin.backup.Cancel', backup_utils.get_profile_name(True)) self.progress_bar.setMaximum(100) self.progress_bar.setValue(0) self.button(self.CancelButton).setDisabled(True) + self.cleanup_temporary_files() else: + self.cleanup_temporary_files() self.done(0) def has_selected_vms(self): @@ -374,21 +397,21 @@ def handle_exception(exc_type, exc_value, exc_traceback): def main(): - qtapp = QtGui.QApplication(sys.argv) - qtapp.setOrganizationName("The Qubes Project") - qtapp.setOrganizationDomain("http://qubes-os.org") - qtapp.setApplicationName("Qubes Backup VMs") + qt_app = QtGui.QApplication(sys.argv) + qt_app.setOrganizationName("The Qubes Project") + qt_app.setOrganizationDomain("http://qubes-os.org") + qt_app.setApplicationName("Qubes Backup VMs") sys.excepthook = handle_exception app = Qubes() - backup_window = BackupVMsWindow(qtapp, app) + backup_window = BackupVMsWindow(qt_app, app) backup_window.show() - qtapp.exec_() - qtapp.exit() + qt_app.exec_() + qt_app.exit() if __name__ == "__main__": diff --git a/qubesmanager/backup_utils.py b/qubesmanager/backup_utils.py index 74ad2f0..9efd8b2 100644 --- a/qubesmanager/backup_utils.py +++ b/qubesmanager/backup_utils.py @@ -31,15 +31,18 @@ path_max_len = 512 def fill_appvms_list(dialog): + """ + Helper function, designed to fill the destination vm combobox in both backup + and restore GUI tools. + :param dialog: QtGui.QWizard with a combobox called appvm_combobox + """ dialog.appvm_combobox.clear() dialog.appvm_combobox.addItem("dom0") dialog.appvm_combobox.setCurrentIndex(0) # current selected is null "" - for vm in dialog.qvm_collection.domains: - if vm.klass == 'AppVM' and vm.features.get('internal', False): - continue - if vm.klass == 'TemplateVM' and vm.installed_by_rpm: + for vm in dialog.qubes_app.domains: + if vm.features.get('internal', False) or vm.klass == 'TemplateVM': continue if vm.is_running() and vm.qid != 0: @@ -52,6 +55,17 @@ def enable_dir_line_edit(dialog, boolean): def select_path_button_clicked(dialog, select_file=False, read_only=False): + """ + Helper function that displays a file/directory selection wizard. Used by + backup and restore GUI tools. + :param dialog: QtGui.QWizard with a dir_line_edit text box that wants to + receive a file/directory path and appvm_combobox with VM to use + :param select_file: True: select file dialog; False: select directory + dialog + :param read_only: should the dir_line_edit be changed after selecting a file + or directory + :return: + """ backup_location = str(dialog.dir_line_edit.text()) file_dialog = QtGui.QFileDialog() file_dialog.setReadOnly(True) @@ -59,7 +73,7 @@ def select_path_button_clicked(dialog, select_file=False, read_only=False): new_path = None new_appvm = str(dialog.appvm_combobox.currentText()) - vm = dialog.qvm_collection.domains[new_appvm] + vm = dialog.qubes_app.domains[new_appvm] try: new_path = utils.get_path_from_vm( vm, @@ -81,6 +95,18 @@ def select_path_button_clicked(dialog, select_file=False, read_only=False): dialog.select_dir_page.emit(QtCore.SIGNAL("completeChanged()")) +def get_profile_name(use_temp): + backup_profile_name = 'qubes-manager-backup' + temp_backup_profile_name = 'qubes-manager-backup-tmp' + + return temp_backup_profile_name if use_temp else backup_profile_name + + +def get_profile_path(use_temp): + path = '/etc/qubes/backup/' + get_profile_name(use_temp) + '.conf' + return path + + def load_backup_profile(use_temp=False): path = get_profile_path(use_temp) @@ -102,15 +128,3 @@ def write_backup_profile(args, use_temp=False): with open(path, 'w') as profile_file: yaml.safe_dump(profile_data, profile_file) - - -def get_profile_name(use_temp): - backup_profile_name = 'qubes-manager-backup' - temp_backup_profile_name = 'qubes-manager-backup-tmp' - - return temp_backup_profile_name if use_temp else backup_profile_name - - -def get_profile_path(use_temp): - path = '/etc/qubes/backup/' + get_profile_name(use_temp) + '.conf' - return path diff --git a/qubesmanager/restore.py b/qubesmanager/restore.py index 28c5b80..b36ec52 100644 --- a/qubesmanager/restore.py +++ b/qubesmanager/restore.py @@ -28,6 +28,8 @@ import time import os import os.path import traceback +import logging +import logging.handlers import signal @@ -46,15 +48,22 @@ from qubesadmin.backup import restore class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): - def __init__(self, app, qvm_collection, parent=None): + def __init__(self, qt_app, qubes_app, parent=None): super(RestoreVMsWindow, self).__init__(parent) - self.app = app - self.qvm_collection = qvm_collection + self.qt_app = qt_app + self.qubes_app = qubes_app self.vms_to_restore = None self.func_output = [] + + # Set up logging self.feedback_queue = Queue() + handler = logging.handlers.QueueHandler(self.feedback_queue) + logger = logging.getLogger('qubesadmin.backup') + logger.addHandler(handler) + logger.setLevel(logging.INFO) + self.canceled = False self.error_detected = Event() self.thread_monitor = None @@ -104,12 +113,12 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): self.target_appvm = None if self.appvm_combobox.currentIndex() != 0: # An existing appvm chosen - self.target_appvm = self.qvm_collection.domains[ + self.target_appvm = self.qubes_app.domains[ str(self.appvm_combobox.currentText())] try: self.backup_restore = restore.BackupRestore( - self.qvm_collection, + self.qubes_app, self.dir_line_edit.text(), self.target_appvm, self.passphrase_line_edit.text() @@ -138,13 +147,6 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): def append_output(self, text): self.commit_text_edit.append(text) - def restore_error_output(self, text): - self.error_detected.set() - self.append_output(u'{0}'.format(text)) - - def restore_output(self, text): - self.append_output(u'{0}'.format(text)) - def __do_restore__(self, t_monitor): err_msg = [] try: @@ -212,14 +214,20 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): args=(self.thread_monitor,)) thread.daemon = True thread.start() - while not self.thread_monitor.is_finished(): - self.app.processEvents() + self.qt_app.processEvents() time.sleep(0.1) try: - for (signal_to_emit, data) in iter( - self.feedback_queue.get_nowait, None): - self.emit(signal_to_emit, data) + log_record = self.feedback_queue.get_nowait() + while log_record: + if log_record.levelno == logging.ERROR or\ + log_record.levelno == logging.CRITICAL: + output = '{0}'.format( + log_record.getMessage()) + else: + output = log_record.getMessage() + self.append_output(output) + log_record = self.feedback_queue.get_nowait() except Empty: pass @@ -238,7 +246,7 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): '{0}'.format( self.tr("Please unmount your backup volume and cancel " "the file selection dialog."))) - self.app.processEvents() + self.qt_app.processEvents() backup_utils.select_path_button_clicked(self, False, True) self.button(self.FinishButton).setEnabled(True) @@ -305,21 +313,21 @@ def handle_exception(exc_type, exc_value, exc_traceback): def main(): - qtapp = QtGui.QApplication(sys.argv) - qtapp.setOrganizationName("The Qubes Project") - qtapp.setOrganizationDomain("http://qubes-os.org") - qtapp.setApplicationName("Qubes Restore VMs") + qt_app = QtGui.QApplication(sys.argv) + qt_app.setOrganizationName("The Qubes Project") + qt_app.setOrganizationDomain("http://qubes-os.org") + qt_app.setApplicationName("Qubes Restore VMs") sys.excepthook = handle_exception - app = Qubes() + qubes_app = Qubes() - restore_window = RestoreVMsWindow(qtapp, app) + restore_window = RestoreVMsWindow(qt_app, qubes_app) restore_window.show() - qtapp.exec_() - qtapp.exit() + qt_app.exec_() + qt_app.exit() if __name__ == "__main__": diff --git a/test-packages/qubesadmin/exc.py b/test-packages/qubesadmin/exc.py index a3635d7..22a3a27 100644 --- a/test-packages/qubesadmin/exc.py +++ b/test-packages/qubesadmin/exc.py @@ -1,4 +1,6 @@ -# pylint: disable=unused-variable### mock qubesadmin.exc module +### mock qubesadmin.exc module +# pylint: disable=unused-variable + class QubesException(BaseException): pass From 3699f50be3e76ff63974f2fccc40d9503ac7d21d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Sat, 6 Jan 2018 02:32:19 +0100 Subject: [PATCH 55/70] Added menu item Added 'Backup Qubes' and 'Restore Backup' positions in the menu. --- qubes-backup-restore.desktop | 10 ++++++++++ qubes-backup.desktop | 10 ++++++++++ rpm_spec/qmgr.spec | 4 ++++ 3 files changed, 24 insertions(+) create mode 100644 qubes-backup-restore.desktop create mode 100644 qubes-backup.desktop diff --git a/qubes-backup-restore.desktop b/qubes-backup-restore.desktop new file mode 100644 index 0000000..8eb122c --- /dev/null +++ b/qubes-backup-restore.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Type=Application +Exec=qubes-backup-restore +Path=/user/bin +Icon=qubes-manager +Terminal=false +Name=Restore Backup +GenericName=Restore Backup +StartupNotify=false +Categories=System; diff --git a/qubes-backup.desktop b/qubes-backup.desktop new file mode 100644 index 0000000..90d5680 --- /dev/null +++ b/qubes-backup.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Type=Application +Exec=qubes-backup +Path=/user/bin +Icon=qubes-manager +Terminal=false +Name=Backup Qubes +GenericName=Backup Qubes +StartupNotify=false +Categories=System; diff --git a/rpm_spec/qmgr.spec b/rpm_spec/qmgr.spec index 1a06cdd..6ee0f64 100644 --- a/rpm_spec/qmgr.spec +++ b/rpm_spec/qmgr.spec @@ -43,6 +43,8 @@ cp qubesmanager/qvm_about.sh $RPM_BUILD_ROOT/usr/libexec/qubes-manager/ mkdir -p $RPM_BUILD_ROOT/usr/share/applications cp qubes-global-settings.desktop $RPM_BUILD_ROOT/usr/share/applications/ cp qubes-vm-create.desktop $RPM_BUILD_ROOT/usr/share/applications/ +cp qubes-backup.desktop $RPM_BUILD_ROOT/usr/share/applications/ +cp qubes-backup-restore.desktop $RPM_BUILD_ROOT/usr/share/applications/ %post @@ -110,3 +112,5 @@ rm -rf $RPM_BUILD_ROOT /usr/share/applications/qubes-global-settings.desktop /usr/share/applications/qubes-vm-create.desktop +/usr/share/applications/qubes-backup.desktop +/usr/share/applications/qubes-backup-restore.desktop From a84c5aba65a185d6005a3c8c9c998d1b35975877 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Sat, 6 Jan 2018 02:51:00 +0100 Subject: [PATCH 56/70] Fixed useless menu item Removed useless line in .desktop files. --- qubes-backup-restore.desktop | 1 - qubes-backup.desktop | 1 - qubes-global-settings.desktop | 1 - 3 files changed, 3 deletions(-) diff --git a/qubes-backup-restore.desktop b/qubes-backup-restore.desktop index 8eb122c..3983f9f 100644 --- a/qubes-backup-restore.desktop +++ b/qubes-backup-restore.desktop @@ -1,7 +1,6 @@ [Desktop Entry] Type=Application Exec=qubes-backup-restore -Path=/user/bin Icon=qubes-manager Terminal=false Name=Restore Backup diff --git a/qubes-backup.desktop b/qubes-backup.desktop index 90d5680..7e93247 100644 --- a/qubes-backup.desktop +++ b/qubes-backup.desktop @@ -1,7 +1,6 @@ [Desktop Entry] Type=Application Exec=qubes-backup -Path=/user/bin Icon=qubes-manager Terminal=false Name=Backup Qubes diff --git a/qubes-global-settings.desktop b/qubes-global-settings.desktop index 110d418..74a0d53 100644 --- a/qubes-global-settings.desktop +++ b/qubes-global-settings.desktop @@ -1,7 +1,6 @@ [Desktop Entry] Type=Application Exec=qubes-global-settings -Path=/user/bin Icon=qubes-manager Terminal=false Name=Qubes Global Settings From 443f48c648a15d22eaa877749cd88baa4ea53469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Wed, 20 Dec 2017 22:33:39 +0100 Subject: [PATCH 57/70] Recreation of the old qubes manager in a leaner, cut-down form. First commit, WIP. --- qubesmanager/main.py | 2165 +++++++++++++++++++++++++++++++++ qubesmanager/table_widgets.py | 511 +++----- ui/vtmanager.ui | 943 ++++++++++++++ 3 files changed, 3283 insertions(+), 336 deletions(-) create mode 100755 qubesmanager/main.py create mode 100644 ui/vtmanager.ui diff --git a/qubesmanager/main.py b/qubesmanager/main.py new file mode 100755 index 0000000..2f3f9f2 --- /dev/null +++ b/qubesmanager/main.py @@ -0,0 +1,2165 @@ +#!/usr/bin/python3 +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2012 Agnieszka Kostrzewa +# Copyright (C) 2012 Marek Marczykowski-Górecki +# +# Copyright (C) 2017 Wojtek Porczyk +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with this program; if not, see . +# +# + +# TODO: cleanup imports +import sys +import os +import os.path +import signal +import subprocess +import time +from datetime import datetime, timedelta +import traceback + +from qubesadmin import Qubes +from qubesadmin import exc + +from PyQt4 import QtGui +from PyQt4 import QtCore +from PyQt4 import Qt +from PyQt4.QtDBus import QDBusVariant, QDBusMessage +from PyQt4.QtDBus import QDBusConnection +from PyQt4.QtDBus import QDBusInterface, QDBusAbstractAdaptor +from pyinotify import WatchManager, ThreadedNotifier, EventsCodes, \ + ProcessEvent + +from . import ui_vtmanager +from . import thread_monitor +from . import table_widgets +import threading + +# from qubes.qubes import QubesVmCollection +# from qubes.qubes import QubesException +# from qubes.qubes import system_path +# from qubes.qubes import QubesDaemonPidfile +# from qubes.qubes import QubesHost +from qubesmanager.about import AboutDialog +# import table_widgets +# from block import QubesBlockDevicesManager +# from table_widgets import VmTypeWidget, VmLabelWidget, VmNameItem, \ +# VmInfoWidget, VmTemplateItem, VmNetvmItem, VmUsageBarWidget, ChartWidget, \ +# VmSizeOnDiskItem, VmInternalItem, VmIPItem, VmIncludeInBackupsItem, \ +# VmLastBackupItem +# from qubes.qubes import QubesHVm +# from qubes import qubesutils +# from ui_mainwindow import * +# from create_new_vm import NewVmDlg +# from settings import VMSettingsWindow +# from restore import RestoreVMsWindow +# from backup import BackupVMsWindow +# from global_settings import GlobalSettingsWindow +# from networknotes import NetworkNotesDialog +# from log_dialog import LogDialog + +# TODO: probably unneeded +update_suggestion_interval = 14 # 14 days +# TODO: probably unneeded +dbus_object_path = '/org/qubesos/QubesManager' +dbus_interface = 'org.qubesos.QubesManager' +system_bus = None +session_bus = None + + +# TODO: probably unneeded +class QMVmState: + ErrorMsg = 1 + AudioRecAvailable = 2 + AudioRecAllowed = 3 + + +#TODO: this is actually needed O_O +class SearchBox(QtGui.QLineEdit): + def __init__(self, parent=None): + super(SearchBox, self).__init__(parent) + self.focusing = False + + def focusInEvent(self, e): + super(SearchBox, self).focusInEvent(e) + self.selectAll() + self.focusing = True + + def mousePressEvent(self, e): + super(SearchBox, self).mousePressEvent(e) + if self.focusing: + self.selectAll() + self.focusing = False + + +class VmRowInTable(object): + cpu_graph_hue = 210 + mem_graph_hue = 120 + + def __init__(self, vm, row_no, table): + self.vm = vm + self.row_no = row_no + + table_widgets.row_height = VmManagerWindow.row_height + table.setRowHeight(row_no, VmManagerWindow.row_height) + + self.type_widget = table_widgets.VmTypeWidget(vm) + table.setCellWidget(row_no, VmManagerWindow.columns_indices['Type'], + self.type_widget) + table.setItem(row_no, VmManagerWindow.columns_indices['Type'], + self.type_widget.tableItem) + + self.label_widget = table_widgets.VmLabelWidget(vm) + table.setCellWidget(row_no, VmManagerWindow.columns_indices['Label'], + self.label_widget) + table.setItem(row_no, VmManagerWindow.columns_indices['Label'], + self.label_widget.tableItem) + + self.name_widget = table_widgets.VmNameItem(vm) + table.setItem(row_no, VmManagerWindow.columns_indices['Name'], + self.name_widget) + + self.info_widget = table_widgets.VmInfoWidget(vm) + table.setCellWidget(row_no, VmManagerWindow.columns_indices['State'], + self.info_widget) + table.setItem(row_no, VmManagerWindow.columns_indices['State'], + self.info_widget.tableItem) + + self.template_widget = table_widgets.VmTemplateItem(vm) + table.setItem(row_no, VmManagerWindow.columns_indices['Template'], + self.template_widget) + + self.netvm_widget = table_widgets.VmNetvmItem(vm) + table.setItem(row_no, VmManagerWindow.columns_indices['NetVM'], + self.netvm_widget) + + # self.cpu_usage_widget = table_widgets.VmUsageBarWidget( + # 0, 100, "%v %", + # # lambda v, val: val if v.last_running else 0, + # lambda v, val: val, + # vm, 0, self.cpu_graph_hue) + # table.setCellWidget(row_no, VmManagerWindow.columns_indices['CPU'], + # self.cpu_usage_widget) + # table.setItem(row_no, VmManagerWindow.columns_indices['CPU'], + # self.cpu_usage_widget.tableItem) + + # self.load_widget = table_widgets.ChartWidget( + # vm, + # lambda v, val: val if v.last_running else 0, + # self.cpu_graph_hue, 0) + # table.setCellWidget(row_no, + # VmManagerWindow.columns_indices['CPU Graph'], + # self.load_widget) + # table.setItem(row_no, VmManagerWindow.columns_indices['CPU Graph'], + # self.load_widget.tableItem) + + # self.mem_usage_widget = table_widgets.VmUsageBarWidget( + # 0, qubes_host.memory_total / 1024, "%v MB", + # lambda v, val: v.get_mem() / 1024, + # vm, 0, self.mem_graph_hue) + # table.setCellWidget(row_no, VmManagerWindow.columns_indices['MEM'], + # self.mem_usage_widget) + # table.setItem(row_no, VmManagerWindow.columns_indices['MEM'], + # self.mem_usage_widget.tableItem) + # + # # self.mem_widget = table_widgets.ChartWidget( + # # vm, lambda v, val: v.get_mem() * 100 / qubes_host.memory_total, + # # self.mem_graph_hue, 0) + # table.setCellWidget(row_no, + # VmManagerWindow.columns_indices['MEM Graph'], + # self.mem_widget) + # table.setItem(row_no, VmManagerWindow.columns_indices['MEM Graph'], + # self.mem_widget.tableItem) + + self.size_widget = table_widgets.VmSizeOnDiskItem(vm) + table.setItem(row_no, VmManagerWindow.columns_indices['Size'], + self.size_widget) + + self.internal_widget = table_widgets.VmInternalItem(vm) + table.setItem(row_no, VmManagerWindow.columns_indices['Internal'], + self.internal_widget) + + self.ip_widget = table_widgets.VmIPItem(vm) + table.setItem(row_no, VmManagerWindow.columns_indices['IP'], + self.ip_widget) + + self.include_in_backups_widget = table_widgets.VmIncludeInBackupsItem(vm) + table.setItem(row_no, VmManagerWindow.columns_indices[ + 'Backups'], self.include_in_backups_widget) + + self.last_backup_widget = table_widgets.VmLastBackupItem(vm) + table.setItem(row_no, VmManagerWindow.columns_indices[ + 'Last backup'], self.last_backup_widget) + + def update(self, blk_visible=None, cpu_load=None, update_size_on_disk=False, + rec_visible=None): + """ + Update info in a single VM row + :param blk_visible: if not None, show/hide block icon, otherwise + don't change its visibility + :param cpu_load: current CPU load (if applicable), in percents + :param update_size_on_disk: should disk utilization be updated? the + widget will extract the data from VM object + :param rec_visible: if not None, show/hide mic icon, otherwise don't + change its visibility + :return: None + """ + self.info_widget.update_vm_state(self.vm, blk_visible, rec_visible) + if cpu_load is not None: + self.cpu_usage_widget.update_load(self.vm, cpu_load) + self.mem_usage_widget.update_load(self.vm, None) + self.load_widget.update_load(self.vm, cpu_load) + self.mem_widget.update_load(self.vm, None) + if update_size_on_disk: + self.size_widget.update() + + +vm_shutdown_timeout = 20000 # in msec +vm_restart_check_timeout= 1000 # in msec + + +class VmShutdownMonitor(QtCore.QObject): + def __init__(self, vm, shutdown_time=vm_shutdown_timeout, check_time=vm_restart_check_timeout, and_restart=False, caller=None): + QtCore.QObject.__init__(self) + self.vm = vm + self.shutdown_time = shutdown_time + self.check_time = check_time + self.and_restart = and_restart + self.shutdown_started = datetime.now() + self.caller = caller + + def restart_vm_if_needed(self): + if self.and_restart and self.caller: + self.caller.start_vm(self.vm) + + def check_again_later(self): + # noinspection PyTypeChecker,PyCallByClass + QtCore.QTimer.singleShot(self.check_time, self.check_if_vm_has_shutdown) + + def timeout_reached(self): + actual = datetime.now() - self.shutdown_started + allowed = timedelta(milliseconds=self.shutdown_time) + + return actual > allowed + + def check_if_vm_has_shutdown(self): + vm = self.vm + vm_is_running = vm.is_running() + vm_start_time = vm.get_start_time() + if vm_is_running and vm_start_time and vm_start_time < self.shutdown_started: + if self.timeout_reached(): + reply = QtGui.QMessageBox.question( + None, self.tr("VM Shutdown"), + self.tr("The VM '{0}' hasn't shutdown within the last " + "{1} seconds, do you want to kill it?
").format( + vm.name, self.shutdown_time / 1000), + self.tr("Kill it!"), + self.tr("Wait another {0} seconds...").format( + self.shutdown_time / 1000)) + if reply == 0: + vm.force_shutdown() + self.restart_vm_if_needed() + else: + self.shutdown_started = datetime.now() + self.check_again_later() + else: + self.check_again_later() + else: + if vm_is_running: + # Due to unknown reasons, Xen sometimes reports that a domain + # is running even though its start-up timestamp is not valid. + # Make sure that "restart_vm_if_needed" is not called until + # the domain has been completely shut down according to Xen. + self.check_again_later() + return + + self.restart_vm_if_needed() + +class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): + row_height = 30 + column_width = 200 + min_visible_rows = 10 + show_inactive_vms = True + show_internal_vms = False + search = "" + # suppress saving settings while initializing widgets + settings_loaded = False + # TODO: does this work + columns_indices = {"Type": 0, + "Label": 1, + "Name": 2, + "State": 3, + "Template": 4, + "NetVM": 5, + # "CPU": 6, # delete + # "CPU Graph": 7, # delete + # "MEM": 8, # delete + # "MEM Graph": 9, # delete + "Size": 6, + "Internal": 7, + "IP": 8, + "Backups": 9, + "Last backup": 10, + } + + def __init__(self, qvm_collection, parent=None): + super(VmManagerWindow, self).__init__() + self.setupUi(self) + self.toolbar = self.toolBar + + self.manager_settings = QtCore.QSettings() #TODO use Qt settings mechanism + + # self.qubes_watch = qubesutils.QubesWatch() + self.qvm_collection = qvm_collection + # self.qubes_watch.setup_domain_watch(self.domain_state_changed_callback) + # self.qubes_watch.setup_block_watch(self.blk_manager.block_devs_event) + # self.blk_watch_thread = threading.Thread( + # target=self.qubes_watch.watch_loop) + # self.blk_watch_thread.daemon = True + # self.blk_watch_thread.start() + + self.searchbox = SearchBox() #TODO check if this works + self.searchbox.setValidator(QtGui.QRegExpValidator( + QtCore.QRegExp("[a-zA-Z0-9-]*", QtCore.Qt.CaseInsensitive), None)) + self.searchContainer.addWidget(self.searchbox) + + self.connect(self.table, QtCore.SIGNAL("itemSelectionChanged()"), + self.table_selection_changed) + + self.table.setColumnWidth(0, self.column_width) + + self.sort_by_column = "Type" + self.sort_order = QtCore.Qt.AscendingOrder + + self.screen_number = -1 + self.screen_changed = False + + self.vms_list = [] + self.vms_in_table = {} + self.reload_table = False + self.running_vms_count = 0 + self.internal_vms_count = 0 + + self.vm_errors = {} + self.vm_rec = {} + + self.frame_width = 0 + self.frame_height = 0 + + self.move(self.x(), 0) + + self.columns_actions = { + self.columns_indices["Type"]: self.action_vm_type, + self.columns_indices["Label"]: self.action_label, + self.columns_indices["Name"]: self.action_name, + self.columns_indices["State"]: self.action_state, + self.columns_indices["Template"]: self.action_template, + self.columns_indices["NetVM"]: self.action_netvm, + # self.columns_indices["CPU"]: self.action_cpu, + # self.columns_indices["CPU Graph"]: self.action_cpu_graph, + # self.columns_indices["MEM"]: self.action_mem, + # self.columns_indices["MEM Graph"]: self.action_mem_graph, + self.columns_indices["Size"]: self.action_size_on_disk, + self.columns_indices["Internal"]: self.action_internal, + self.columns_indices["IP"]: self + .action_ip, self.columns_indices["Backups"]: self + .action_backups, self.columns_indices["Last backup"]: self + .action_last_backup + } + + # TODO: make refresh button + self.visible_columns_count = len(self.columns_indices) + # self.table.setColumnHidden(self.columns_indices["CPU"], True) + # self.action_cpu.setChecked(False) + # self.table.setColumnHidden(self.columns_indices["CPU Graph"], True) + # self.action_cpu_graph.setChecked(False) + # self.table.setColumnHidden(self.columns_indices["MEM Graph"], True) + # self.action_mem_graph.setChecked(False) + self.table.setColumnHidden(self.columns_indices["Size"], True) + self.action_size_on_disk.setChecked(False) + self.table.setColumnHidden(self.columns_indices["Internal"], True) + self.action_internal.setChecked(False) + self.table.setColumnHidden(self.columns_indices["IP"], True) + self.action_ip.setChecked(False) + self.table.setColumnHidden(self.columns_indices["Backups"], True) + self.action_backups.setChecked(False) + self.table.setColumnHidden(self.columns_indices["Last backup"], True) + self.action_last_backup.setChecked(False) + + self.table.setColumnWidth(self.columns_indices["State"], 80) + self.table.setColumnWidth(self.columns_indices["Name"], 150) + self.table.setColumnWidth(self.columns_indices["Label"], 40) + self.table.setColumnWidth(self.columns_indices["Type"], 40) + self.table.setColumnWidth(self.columns_indices["Size"], 100) + self.table.setColumnWidth(self.columns_indices["Internal"], 60) + self.table.setColumnWidth(self.columns_indices["IP"], 100) + self.table.setColumnWidth(self.columns_indices["Backups"], 60) + self.table.setColumnWidth(self.columns_indices["Last backup"], 90) + + self.table.horizontalHeader().setResizeMode(QtGui.QHeaderView.Fixed) + + self.table.sortItems(self.columns_indices[self.sort_by_column], + self.sort_order) + + self.context_menu = QtGui.QMenu(self) + + self.context_menu.addAction(self.action_settings) + self.context_menu.addAction(self.action_editfwrules) + self.context_menu.addAction(self.action_appmenus) + self.context_menu.addAction(self.action_set_keyboard_layout) + self.context_menu.addMenu(self.blk_menu) + self.context_menu.addAction(self.action_toggle_audio_input) + self.context_menu.addSeparator() + + self.context_menu.addAction(self.action_updatevm) + self.context_menu.addAction(self.action_run_command_in_vm) + self.context_menu.addAction(self.action_resumevm) + self.context_menu.addAction(self.action_startvm_tools_install) + self.context_menu.addAction(self.action_pausevm) + self.context_menu.addAction(self.action_shutdownvm) + self.context_menu.addAction(self.action_restartvm) + self.context_menu.addAction(self.action_killvm) + self.context_menu.addSeparator() + + self.context_menu.addAction(self.action_clonevm) + self.context_menu.addAction(self.action_removevm) + self.context_menu.addSeparator() + + self.context_menu.addMenu(self.logs_menu) + self.context_menu.addSeparator() + + self.tools_context_menu = QtGui.QMenu(self) + self.tools_context_menu.addAction(self.action_toolbar) + self.tools_context_menu.addAction(self.action_menubar) + + self.table_selection_changed() + + self.connect( + self.table.horizontalHeader(), + QtCore.SIGNAL("sortIndicatorChanged(int, Qt::SortOrder)"), + self.sortIndicatorChanged) + self.connect(self.table, + QtCore.SIGNAL("customContextMenuRequested(const QPoint&)"), + self.open_context_menu) + self.connect(self.menubar, + QtCore.SIGNAL("customContextMenuRequested(const QPoint&)"), + lambda pos: self.open_tools_context_menu(self.menubar, + pos)) + self.connect(self.toolBar, + QtCore.SIGNAL("customContextMenuRequested(const QPoint&)"), + lambda pos: self.open_tools_context_menu(self.toolBar, + pos)) + self.connect(self.blk_menu, QtCore.SIGNAL("triggered(QAction *)"), + self.attach_dettach_device_triggered) + self.connect(self.logs_menu, QtCore.SIGNAL("triggered(QAction *)"), + self.show_log) + + self.connect(self.searchbox, QtCore.SIGNAL("textChanged(const QString&)"), + self.do_search) + + self.table.setContentsMargins(0, 0, 0, 0) + self.centralwidget.layout().setContentsMargins(0, 0, 0, 0) + self.layout().setContentsMargins(0, 0, 0, 0) + + self.connect(self.action_menubar, QtCore.SIGNAL("toggled(bool)"), + self.showhide_menubar) + self.connect(self.action_toolbar, QtCore.SIGNAL("toggled(bool)"), + self.showhide_toolbar) + + self.register_dbus_watches() + + self.load_manager_settings() + + self.action_showallvms.setChecked(self.show_inactive_vms) + self.action_showinternalvms.setChecked(self.show_internal_vms) + + self.fill_table() + + self.counter = 0 + self.update_size_on_disk = False + self.shutdown_monitor = {} + self.last_measure_results = {} + self.last_measure_time = time.time() + # noinspection PyCallByClass,PyTypeChecker + # QtCore.QTimer.singleShot(self.update_interval, self.update_table) + + QubesDbusNotifyServerAdaptor(self) + + def load_manager_settings(self): #TODO: replace with a Qt-based settings gizmo also make it work + # visible columns + # self.manager_settings.beginGroup("columns") + # for col in self.columns_indices.keys(): + # col_no = self.columns_indices[col] + # visible = self.manager_settings.value( + # col, + # defaultValue=not self.table.isColumnHidden(col_no)) + # self.columns_actions[col_no].setChecked(visible) + # self.manager_settings.endGroup() + # self.show_inactive_vms = self.manager_settings.value( + # "view/show_inactive_vms", defaultValue=False) + # self.show_internal_vms = self.manager_settings.value( + # "view/show_internal_vms", defaultValue=False) + # self.sort_by_column = str( + # self.manager_settings.value("view/sort_column", + # defaultValue=self.sort_by_column)) + # # self.sort_order = QtCore.Qt.SortOrder( #TODO does not seem to work + # # self.manager_settings.value("view/sort_order", + # # defaultValue=self.sort_order)[ + # # 0]) + # self.table.sortItems(self.columns_indices[self.sort_by_column], + # self.sort_order) + # if not self.manager_settings.value("view/menubar_visible", + # defaultValue=True): + # self.action_menubar.setChecked(False) + # if not self.manager_settings.value("view/toolbar_visible", + # defaultValue=True): + # self.action_toolbar.setChecked(False) + # x = self.manager_settings.value('position/x', defaultValue=-1)[ + # 0] + # y = self.manager_settings.value('position/y', defaultValue=-1)[ + # 0] + # if x != -1 or y != -1: + # self.move(x, y) + self.settings_loaded = True + + def show(self): + super(VmManagerWindow, self).show() + self.screen_number = app.desktop().screenNumber(self) + + # def set_table_geom_size(self): + # + # desktop_width = app.desktop().availableGeometry( + # self).width() - self.frame_width # might be wrong... + # desktop_height = app.desktop().availableGeometry( + # self).height() - self.frame_height # might be wrong... + # desktop_height -= self.row_height # UGLY! to somehow ommit taskbar... + # + # w = self.table.horizontalHeader().length() + \ + # self.table.verticalScrollBar().width() + \ + # 2 * self.table.frameWidth() + 1 + # + # h = self.table.horizontalHeader().height() + \ + # 2 * self.table.frameWidth() + # + # mainwindow_to_add = 0 + # + # available_space = desktop_height + # if self.menubar.isVisible(): + # menubar_height = (self.menubar.sizeHint().height() + + # self.menubar.contentsMargins().top() + + # self.menubar.contentsMargins().bottom()) + # available_space -= menubar_height + # mainwindow_to_add += menubar_height + # if self.toolbar.isVisible(): + # toolbar_height = (self.toolbar.sizeHint().height() + + # self.toolbar.contentsMargins().top() + + # self.toolbar.contentsMargins().bottom()) + # available_space -= toolbar_height + # mainwindow_to_add += toolbar_height + # if w >= desktop_width: + # available_space -= self.table.horizontalScrollBar().height() + # h += self.table.horizontalScrollBar().height() + # + # # account for search box height + # available_space -= self.searchbox.height() + # h += self.searchbox.height() + # + # default_rows = int(available_space / self.row_height) + # + # n = sum(not self.table.isRowHidden(row) for row in + # range(self.table.rowCount())) + # + # if n > default_rows: + # h += default_rows * self.row_height + # self.table.verticalScrollBar().show() + # else: + # h += n * self.row_height + # self.table.verticalScrollBar().hide() + # w -= self.table.verticalScrollBar().width() + # + # w = min(desktop_width, w) + # + # self.centralwidget.setFixedHeight(h) + # + # h += mainwindow_to_add + # + # self.setMaximumHeight(h) + # self.setMinimumHeight(h) + # + # self.table.setFixedWidth(w) + # self.centralwidget.setFixedWidth(w) + # # don't change the following two lines to setFixedWidth! + # self.setMaximumWidth(w) + # self.setMinimumWidth(w) + + def moveEvent(self, event): + super(VmManagerWindow, self).moveEvent(event) + screen_number = app.desktop().screenNumber(self) + if self.screen_number != screen_number: + self.screen_changed = True + self.screen_number = screen_number + if self.settings_loaded: + self.manager_settings.setValue('position/x', self.x()) + self.manager_settings.setValue('position/y', self.y()) + # do not sync for performance reasons + + def domain_state_changed_callback(self, name=None, uuid=None): + if name is not None: + vm = self.qvm_collection.domains[name] + if vm: + vm.refresh() + + def get_vms_list(self): + # self.qvm_collection.lock_db_for_reading() + # self.qvm_collection.load() + # self.qvm_collection.unlock_db() + + running_count = 0 + internal_count = 0 + + vms_list = [vm for vm in self.qvm_collection.domains] + for vm in vms_list: + # vm.last_power_state = vm.get_power_state() + # vm.last_running = vm.is_running() + # if vm.last_running: + # running_count += 1 + # if vm.internal: + # internal_count += 1 + # vm.qubes_manager_state = {} + # self.update_audio_rec_info(vm) + # vm.qubes_manager_state[QMVmState.ErrorMsg] = self.vm_errors[ + # vm.qid] if vm.qid in self.vm_errors else None + running_count +=1 + + self.running_vms_count = running_count + self.internal_vms_count = internal_count + return vms_list + + def fill_table(self): + # save current selection + row_index = self.table.currentRow() + selected_qid = -1 + if row_index != -1: + vm_item = self.table.item(row_index, self.columns_indices["Name"]) + if vm_item: + selected_qid = vm_item.qid + + self.table.setSortingEnabled(False) + self.table.clearContents() + vms_list = self.get_vms_list() + self.table.setRowCount(len(vms_list)) + + vms_in_table = {} + + row_no = 0 + for vm in vms_list: + # if vm.internal: + # continue + vm_row = VmRowInTable(vm, row_no, self.table) + vms_in_table[vm.qid] = vm_row + + row_no += 1 + + self.table.setRowCount(row_no) + self.vms_list = vms_list + self.vms_in_table = vms_in_table + self.reload_table = False + if selected_qid in vms_in_table.keys(): + self.table.setCurrentItem( + self.vms_in_table[selected_qid].name_widget) + self.table.setSortingEnabled(True) + + self.showhide_vms() + # self.set_table_geom_size() + + def showhide_vms(self): + if self.show_inactive_vms and self.show_internal_vms and not self.search: + for row_no in range(self.table.rowCount()): + self.table.setRowHidden(row_no, False) + else: + for row_no in range(self.table.rowCount()): + widget = self.table.cellWidget(row_no, + self.columns_indices["State"]) + running = False + internal = False + # running = widget.vm.last_running + # internal = widget.vm.internal + name = widget.vm.name + + show = (running or self.show_inactive_vms) and \ + (not internal or self.show_internal_vms) and \ + (self.search in widget.vm.name or not self.search) + self.table.setRowHidden(row_no, not show) + + @QtCore.pyqtSlot(str) + def do_search(self, search): + self.search = str(search) + self.showhide_vms() + # self.set_table_geom_size() + + @QtCore.pyqtSlot(name='on_action_search_triggered') + def action_search_triggered(self): + self.searchbox.setFocus() + + def mark_table_for_update(self): + self.reload_table = True + + # When calling update_table() directly, always use out_of_schedule=True! + def update_table(self, out_of_schedule=False): + + update_devs = self.update_block_devices() or out_of_schedule + reload_table = self.reload_table + + if manager_window.isVisible(): + # some_vms_have_changed_power_state = False + # for vm in self.vms_list: + # state = vm.get_power_state() + # if vm.last_power_state != state: + # if state == "Running" and \ + # self.vm_errors.get(vm.qid, "") \ + # .startswith("Error starting VM:"): + # self.clear_error(vm.qid) + # prev_running = vm.last_running + # vm.last_power_state = state + # vm.last_running = vm.is_running() + # self.update_audio_rec_info(vm) + # if not prev_running and vm.last_running: + # self.running_vms_count += 1 + # some_vms_have_changed_power_state = True + # # Clear error state when VM just started + # self.clear_error(vm.qid) + # elif prev_running and not vm.last_running: + # # FIXME: remove when recAllowed state will be preserved + # if self.vm_rec.has_key(vm.name): + # self.vm_rec.pop(vm.name) + # self.running_vms_count -= 1 + # some_vms_have_changed_power_state = True + # else: + # # pulseaudio agent register itself some time after VM + # # startup + # if state == "Running" and not vm.qubes_manager_state[ + # QMVmState.AudioRecAvailable]: + # self.update_audio_rec_info(vm) + # if self.vm_errors.get(vm.qid, "") == \ + # "Error starting VM: Cannot execute qrexec-daemon!" \ + # and vm.is_qrexec_running(): + # self.clear_error(vm.qid) + pass + + if self.screen_changed: + reload_table = True + self.screen_changed = False + + if reload_table: + self.fill_table() + update_devs = True + + # if not self.show_inactive_vms and \ + # some_vms_have_changed_power_state: + # self.showhide_vms() + # self.set_table_geom_size() + + # if self.sort_by_column == \ + # "State" and some_vms_have_changed_power_state: + # self.table.sortItems(self.columns_indices[self.sort_by_column], + # self.sort_order) + + blk_visible = None + rows_with_blk = None + # if update_devs: + # rows_with_blk = [] + # self.blk_manager.blk_lock.acquire() + # for d in self.blk_manager.attached_devs: + # rows_with_blk.append( + # self.blk_manager.attached_devs[d]['attached_to'][ + # 'vm'].qid) + # self.blk_manager.blk_lock.release() + + if (not self.table.isColumnHidden(self.columns_indices['Size'])) and \ + self.counter % 60 == 0 or out_of_schedule: + self.update_size_on_disk = True + + if self.counter % 3 == 0 or out_of_schedule: + # (self.last_measure_time, self.last_measure_results) = \ + # qubes_host.measure_cpu_usage(self.qvm_collection, + # self.last_measure_results, + # self.last_measure_time) + + for vm_row in self.vms_in_table.values(): + cur_cpu_load = None + # if vm_row.vm.get_xid() in self.last_measure_results: + # cur_cpu_load = self.last_measure_results[vm_row.vm.xid][ + # 'cpu_usage'] + # else: + # cur_cpu_load = 0 + + if rows_with_blk is not None: + if vm_row.vm.qid in rows_with_blk: + blk_visible = True + else: + blk_visible = False + + vm_row.update(blk_visible=blk_visible, + cpu_load=cur_cpu_load, + update_size_on_disk=self.update_size_on_disk, + rec_visible=self.vm_rec.get(vm_row.vm.name, + False)) + + else: + for vm_row in self.vms_in_table.values(): + if rows_with_blk is not None: + if vm_row.vm.qid in rows_with_blk: + blk_visible = True + else: + blk_visible = False + + vm_row.update(blk_visible=blk_visible, + update_size_on_disk=self.update_size_on_disk, + rec_visible=self.vm_rec.get(vm_row.vm.name, + False)) + + if self.sort_by_column in ["CPU", "CPU Graph", "MEM", "MEM Graph", + "State", "Size", "Internal"]: + # "State": needed to sort after reload (fill_table sorts items + # with setSortingEnabled, but by that time the widgets values + # are not correct yet). + self.table.sortItems(self.columns_indices[self.sort_by_column], + self.sort_order) + + self.table_selection_changed() + + self.update_size_on_disk = False + if not out_of_schedule: + self.counter += 1 + # noinspection PyCallByClass,PyTypeChecker + # QtCore.QTimer.singleShot(self.update_interval, self.update_table) + + def update_block_devices(self): + pass + # res, msg = self.blk_manager.check_for_updates() + # if msg is not None and len(msg) > 0: + # trayIcon.showMessage('\n'.join(msg), msecs=5000) + # return res + + # noinspection PyPep8Naming + @QtCore.pyqtSlot(bool, str) + def recAllowedChanged(self, state, vmname): + self.vm_rec[str(vmname)] = bool(state) + + def register_dbus_watches(self): + global session_bus + + if not session_bus: + session_bus = QDBusConnection.sessionBus() + + if not session_bus.connect("", # service + "", # path + "org.QubesOS.Audio", # interface + "RecAllowedChanged", # name + self.recAllowedChanged): # slot + print(session_bus.lastError().message()) + + # noinspection PyPep8Naming + def sortIndicatorChanged(self, column, order): + self.sort_by_column = [name for name in self.columns_indices.keys() if + self.columns_indices[name] == column][0] + self.sort_order = order + if self.settings_loaded: + self.manager_settings.setValue('view/sort_column', + self.sort_by_column) + self.manager_settings.setValue('view/sort_order', self.sort_order) + self.manager_settings.sync() + + def table_selection_changed(self): + + vm = self.get_selected_vm() + + if vm is not None: + # Update available actions: + self.action_settings.setEnabled(vm.qid != 0) + self.action_removevm.setEnabled( + not vm.installed_by_rpm and not vm.last_running) + self.action_clonevm.setEnabled( + not vm.last_running and not vm.is_netvm()) + # self.action_resumevm.setEnabled(not vm.last_running or + # vm.last_power_state == "Paused") + try: + pass + # self.action_startvm_tools_install.setVisible( + # isinstance(vm, QubesHVm)) + except NameError: + # ignore non existing QubesHVm + pass + self.action_startvm_tools_install.setEnabled(not vm.last_running) + # self.action_pausevm.setEnabled( + # vm.last_running and + # vm.last_power_state != "Paused" and + # vm.qid != 0) + # self.action_shutdownvm.setEnabled( + # vm.last_running and + # vm.last_power_state != "Paused" and + # vm.qid != 0) + # self.action_restartvm.setEnabled( + # vm.last_running and + # vm.last_power_state != "Paused" and + # vm.qid != 0 and + # not vm.is_disposablevm()) + # self.action_killvm.setEnabled((vm.last_running or + # vm.last_power_state == "Paused") and + # vm.qid != 0) + # self.action_appmenus.setEnabled(vm.qid != 0 and + # not vm.internal and + # not vm.is_disposablevm()) + # self.action_editfwrules.setEnabled(vm.is_networked() and not ( + # vm.is_netvm() and not vm.is_proxyvm())) + # self.action_updatevm.setEnabled(vm.is_updateable() or vm.qid == 0) + # self.action_toggle_audio_input.setEnabled( + # vm.qubes_manager_state[QMVmState.AudioRecAvailable]) + # self.action_run_command_in_vm.setEnabled( + # not vm.last_power_state == "Paused" and vm.qid != 0) + # self.action_set_keyboard_layout.setEnabled( + # vm.qid != 0 and + # vm.last_running and + # vm.last_power_state != "Paused") + else: + self.action_settings.setEnabled(False) + self.action_removevm.setEnabled(False) + self.action_startvm_tools_install.setVisible(False) + self.action_startvm_tools_install.setEnabled(False) + self.action_clonevm.setEnabled(False) + self.action_resumevm.setEnabled(False) + self.action_pausevm.setEnabled(False) + self.action_shutdownvm.setEnabled(False) + self.action_restartvm.setEnabled(False) + self.action_killvm.setEnabled(False) + self.action_appmenus.setEnabled(False) + self.action_editfwrules.setEnabled(False) + self.action_updatevm.setEnabled(False) + self.action_toggle_audio_input.setEnabled(False) + self.action_run_command_in_vm.setEnabled(False) + self.action_set_keyboard_layout.setEnabled(False) + + def closeEvent(self, event): + # There is something borked in Qt, + # as the logic here is inverted on X11 + if event.spontaneous(): + self.hide() + event.ignore() + + def set_error(self, qid, message): + for vm in self.vms_list: + if vm.qid == qid: + vm.qubes_manager_state[QMVmState.ErrorMsg] = message + # Store error in separate dict to make it immune to VM list reload + self.vm_errors[qid] = str(message) + + def clear_error(self, qid): + self.vm_errors.pop(qid, None) + for vm in self.vms_list: + if vm.qid == qid: + vm.qubes_manager_state[QMVmState.ErrorMsg] = None + + def clear_error_exact(self, qid, message): + for vm in self.vms_list: + if vm.qid == qid: + if vm.qubes_manager_state[QMVmState.ErrorMsg] == message: + vm.qubes_manager_state[QMVmState.ErrorMsg] = None + self.vm_errors.pop(qid, None) + + @QtCore.pyqtSlot(name='on_action_createvm_triggered') + def action_createvm_triggered(self): + pass + # dialog = NewVmDlg(app, self.qvm_collection, trayIcon) + # dialog.exec_() + + def get_selected_vm(self): + # vm selection relies on the VmInfo widget's value used + # for sorting by VM name + row_index = self.table.currentRow() + if row_index != -1: + vm_item = self.table.item(row_index, self.columns_indices["Name"]) + # here is possible race with update_table timer so check + # if really got the item + if vm_item is None: + return None + qid = vm_item.qid + assert self.vms_in_table[qid] is not None + vm = self.vms_in_table[qid].vm + return vm + else: + return None + + @QtCore.pyqtSlot(name='on_action_removevm_triggered') + def action_removevm_triggered(self): + + vm = self.get_selected_vm() + assert not vm.is_running() + assert not vm.installed_by_rpm + + vm = self.qvm_collection[vm.qid] + + if vm.is_template(): + dependent_vms = self.qvm_collection.domains[vm.qid] + if len(dependent_vms) > 0: + QtGui.QMessageBox.warning( + None, self.tr("Warning!"), + self.tr("This Template VM cannot be removed, because there is at " + "least one AppVM that is based on it.
" + "If you want to remove this Template VM and all " + "the AppVMs based on it," + "you should first remove each individual AppVM that uses " + "this template.")) + + return + + (requested_name, ok) = QtGui.QInputDialog.getText( + None, self.tr("VM Removal Confirmation"), + self.tr("Are you sure you want to remove the VM '{0}'?
" + "All data on this VM's private storage will be lost!

" + "Type the name of the VM ({1}) below to confirm:") + .format(vm.name, vm.name)) + + if not ok: + # user clicked cancel + return + + elif requested_name != vm.name: + # name did not match + QtGui.QMessageBox.warning(None, self.tr("VM removal confirmation failed"), + self.tr("Entered name did not match! Not removing {0}.").format(vm.name)) + return + + else: + # remove the VM + t_monitor = thread_monitor.ThreadMonitor() + thread = threading.Thread(target=self.do_remove_vm, + args=(vm, t_monitor)) + thread.daemon = True + thread.start() + + progress = QtGui.QProgressDialog( + self.tr("Removing VM: {0}...").format(vm.name), "", 0, 0) + progress.setCancelButton(None) + progress.setModal(True) + progress.show() + + while not thread_monitor.is_finished(): + app.processEvents() + time.sleep(0.1) + + progress.hide() + + if thread_monitor.success: + trayIcon.showMessage( + self.tr("VM '{0}' has been removed.").format(vm.name), msecs=3000) + else: + QtGui.QMessageBox.warning(None, self.tr("Error removing VM!"), + self.tr("ERROR: {0}").format( + thread_monitor.error_msg)) + + @staticmethod + def do_remove_vm(vm, thread_monitor): + qc = Qubes() + try: + vm = qc.domains[vm.qid] + + # TODO: the following two conditions should really be checked + # by qvm_collection.pop() overload... + if vm.is_template() and \ + qc.default_template_qid == vm.qid: + qc.default_template_qid = None + if vm.is_netvm() and \ + qc.default_netvm_qid == vm.qid: + qc.default_netvm_qid = None + + # qc.pop(vm.qid) + # qc.save() + vm.remove_from_disk() + except Exception as ex: + thread_monitor.set_error_msg(str(ex)) + + thread_monitor.set_finished() + + @QtCore.pyqtSlot(name='on_action_clonevm_triggered') + def action_clonevm_triggered(self): + vm = self.get_selected_vm() + + name_number = 1 + name_format = vm.name + '-clone-%d' + while self.qvm_collection.domains[name_format % name_number]: + name_number += 1 + + (clone_name, ok) = QtGui.QInputDialog.getText( + self, self.tr('Qubes clone VM'), + self.tr('Enter name for VM {} clone:').format(vm.name), + text=(name_format % name_number)) + if not ok or clone_name == "": + return + + t_monitor = thread_monitor.ThreadMonitor() + thread = threading.Thread(target=self.do_clone_vm, + args=(vm, str(clone_name), t_monitor)) + thread.daemon = True + thread.start() + + progress = QtGui.QProgressDialog( + self.tr("Cloning VM {0} to {1}...").format(vm.name, + clone_name), "", 0, + 0) + progress.setCancelButton(None) + progress.setModal(True) + progress.show() + + while not t_monitor.is_finished(): + app.processEvents() + time.sleep(0.2) + + progress.hide() + + if not t_monitor.success: + QtGui.QMessageBox.warning(None, self.tr("Error while cloning VM"), + self.tr("Exception while cloning:
{0}").format( + t_monitor.error_msg)) + + @staticmethod + def do_clone_vm(vm, dst_name, thread_monitor): + dst_vm = None + qc = Qubes() + try: + src_vm = qc[vm.qid] + + dst_vm = qc.add_new_vm(src_vm.__class__.__name__, + name=dst_name, + template=src_vm.template, + installed_by_rpm=False) + + dst_vm.clone_attrs(src_vm) + dst_vm.clone_disk_files(src_vm=src_vm, verbose=False) + qc.save() + except Exception as ex: + if dst_vm: + qc.pop(dst_vm.qid) + dst_vm.remove_from_disk() + thread_monitor.set_error_msg(str(ex)) + thread_monitor.set_finished() + + @QtCore.pyqtSlot(name='on_action_resumevm_triggered') + def action_resumevm_triggered(self): + vm = self.get_selected_vm() + + # if vm.get_power_state() in ["Paused", "Suspended"]: + # try: + # vm.resume() + # except Exception as ex: + # QtGui.QMessageBox.warning(None, self.tr("Error unpausing VM!"), + # self.tr("ERROR: {0}").format(ex)) + # return + + + self.start_vm(vm) + + def start_vm(self, vm): + assert not vm.is_running() + t_monitor = thread_monitor.ThreadMonitor() + thread = threading.Thread(target=self.do_start_vm, + args=(vm, t_monitor)) + thread.daemon = True + thread.start() + + trayIcon.showMessage(self.tr("Starting '{0}'...").format(vm.name), msecs=3000) + + while not t_monitor.is_finished(): + app.processEvents() + time.sleep(0.1) + + if t_monitor.success: + trayIcon.showMessage(self.tr("VM '{0}' has been started.").format(vm.name), + msecs=3000) + else: + trayIcon.showMessage( + self.tr("Error starting VM '{0}': {1}").format( + vm.name, t_monitor.error_msg), + msecs=3000) + self.set_error(vm.qid, + self.tr("Error starting VM: %s") % t_monitor.error_msg) + + @staticmethod + def do_start_vm(vm, t_monitor): + try: + vm.start() + except Exception as ex: + t_monitor.set_error_msg(str(ex)) + t_monitor.set_finished() + return + + t_monitor.set_finished() + + @QtCore.pyqtSlot(name='on_action_startvm_tools_install_triggered') + def action_startvm_tools_install_triggered(self): + vm = self.get_selected_vm() + assert not vm.is_running() + + windows_tools_installed = \ + os.path.exists('/usr/lib/qubes/qubes-windows-tools.iso') + if not windows_tools_installed: + msg = QtGui.QMessageBox() + msg.warning(self, self.tr("Error starting VM!"), + self.tr("You need to install 'qubes-windows-tools' " + "package to use this option")) + return + + + t_monitor = thread_monitor.ThreadMonitor() + thread = threading.Thread(target=self.do_start_vm_tools_install, + args=(vm, t_monitor)) + thread.daemon = True + thread.start() + + trayIcon.showMessage(self.tr("Starting '{0}'...").format(vm.name), msecs=3000) + + while not t_monitor.is_finished(): + app.processEvents() + time.sleep(0.1) + + if t_monitor.success: + trayIcon.showMessage(self.tr("VM '{0}' has been started. Start Qubes " + "Tools installation from attached CD") + .format(vm.name), msecs=3000) + else: + trayIcon.showMessage( + self.tr("Error starting VM '{0}': {1}") + .format(vm.name, t_monitor.error_msg), + msecs=3000) + self.set_error(vm.qid, + self.tr("Error starting VM: %s") % t_monitor.error_msg) + + # noinspection PyMethodMayBeStatic + def do_start_vm_tools_install(self, vm, thread_monitor): + prev_drive = vm.drive + try: + vm.drive = 'cdrom:dom0:/usr/lib/qubes/qubes-windows-tools.iso' + vm.start() + except Exception as ex: + thread_monitor.set_error_msg(str(ex)) + thread_monitor.set_finished() + return + finally: + vm.drive = prev_drive + + thread_monitor.set_finished() + + @QtCore.pyqtSlot(name='on_action_pausevm_triggered') + def action_pausevm_triggered(self): + vm = self.get_selected_vm() + assert vm.is_running() + try: + vm.pause() + except Exception as ex: + QtGui.QMessageBox.warning(None, self.tr("Error pausing VM!"), + self.tr("ERROR: {0}").format(ex)) + return + + @QtCore.pyqtSlot(name='on_action_shutdownvm_triggered') + def action_shutdownvm_triggered(self): + vm = self.get_selected_vm() + assert vm.is_running() + + # self.blk_manager.check_if_serves_as_backend(vm) + + reply = QtGui.QMessageBox.question( + None, self.tr("VM Shutdown Confirmation"), + self.tr("Are you sure you want to power down the VM '{0}'?
" + "This will shutdown all the running applications " + "within this VM.").format(vm.name), + QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel) + + app.processEvents() + + if reply == QtGui.QMessageBox.Yes: + self.shutdown_vm(vm) + + def shutdown_vm(self, vm, shutdown_time=vm_shutdown_timeout, + check_time=vm_restart_check_timeout, and_restart=False): + try: + vm.shutdown() + except Exception as ex: + QtGui.QMessageBox.warning(None, self.tr("Error shutting down VM!"), + self.tr("ERROR: {0}").format(ex)) + return + + trayIcon.showMessage(self.tr("VM '{0}' is shutting down...").format(vm.name), + msecs=3000) + + self.shutdown_monitor[vm.qid] = VmShutdownMonitor(vm, shutdown_time, + check_time, and_restart, self) + # noinspection PyCallByClass,PyTypeChecker + QtCore.QTimer.singleShot(check_time, self.shutdown_monitor[ + vm.qid].check_if_vm_has_shutdown) + + @QtCore.pyqtSlot(name='on_action_restartvm_triggered') + def action_restartvm_triggered(self): + vm = self.get_selected_vm() + assert vm.is_running() + + # self.blk_manager.check_if_serves_as_backend(vm) + + reply = QtGui.QMessageBox.question( + None, self.tr("VM Restart Confirmation"), + self.tr("Are you sure you want to restart the VM '{0}'?
" + "This will shutdown all the running applications " + "within this VM.").format(vm.name), + QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel) + + app.processEvents() + + if reply == QtGui.QMessageBox.Yes: + self.shutdown_vm(vm, and_restart=True) + + @QtCore.pyqtSlot(name='on_action_killvm_triggered') + def action_killvm_triggered(self): + vm = self.get_selected_vm() + assert vm.is_running() or vm.is_paused() + + reply = QtGui.QMessageBox.question( + None, self.tr("VM Kill Confirmation"), + self.tr("Are you sure you want to kill the VM '{0}'?
" + "This will end (not shutdown!) all the running " + "applications within this VM.").format(vm.name), + QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel, + QtGui.QMessageBox.Cancel) + + app.processEvents() + + if reply == QtGui.QMessageBox.Yes: + try: + vm.force_shutdown() + except Exception as ex: + QtGui.QMessageBox.critical( + None, self.tr("Error while killing VM!"), + self.tr("An exception ocurred while killing {0}.
" + "ERROR: {1}").format(vm.name, ex)) + return + + trayIcon.showMessage(self.tr("VM '{0}' killed!") + .format(vm.name), msecs=3000) + + @QtCore.pyqtSlot(name='on_action_settings_triggered') + def action_settings_triggered(self): + pass + #TODO this should work, actually, but please not now + # vm = self.get_selected_vm() + # settings_window = VMSettingsWindow(vm, app, self.qvm_collection, + # "basic") + # settings_window.exec_() + + @QtCore.pyqtSlot(name='on_action_appmenus_triggered') + def action_appmenus_triggered(self): + pass + #TODO this should work, actually, but please not now + # vm = self.get_selected_vm() + # settings_window = VMSettingsWindow(vm, app, self.qvm_collection, + # "applications") + # settings_window.exec_() + + def update_audio_rec_info(self, vm): + vm.qubes_manager_state[QMVmState.AudioRecAvailable] = ( + session_bus.interface().isServiceRegistered( + 'org.QubesOS.Audio.%s' % vm.name).value()) + if vm.qubes_manager_state[QMVmState.AudioRecAvailable]: + vm.qubes_manager_state[ + QMVmState.AudioRecAllowed] = self.get_audio_rec_allowed(vm.name) + else: + vm.qubes_manager_state[QMVmState.AudioRecAllowed] = False + + # noinspection PyMethodMayBeStatic + def get_audio_rec_allowed(self, vmname): + properties = QDBusInterface('org.QubesOS.Audio.%s' % vmname, + '/org/qubesos/audio', + 'org.freedesktop.DBus.Properties', + session_bus) + + current_audio = properties.call('Get', 'org.QubesOS.Audio', + 'RecAllowed') + if current_audio.type() == current_audio.ReplyMessage: + value = current_audio.arguments()[0].toPyObject() + return bool(value) + return False + + @QtCore.pyqtSlot(name='on_action_toggle_audio_input_triggered') + def action_toggle_audio_input_triggered(self): + vm = self.get_selected_vm() + properties = QDBusInterface('org.QubesOS.Audio.%s' % vm.name, + '/org/qubesos/audio', + 'org.freedesktop.DBus.Properties', + session_bus) + properties.call('Set', 'org.QubesOS.Audio', 'RecAllowed', + QDBusVariant(not self.get_audio_rec_allowed(vm.name))) + # icon will be updated based on dbus signal + + @QtCore.pyqtSlot(name='on_action_updatevm_triggered') + def action_updatevm_triggered(self): + vm = self.get_selected_vm() + + if not vm.is_running(): + reply = QtGui.QMessageBox.question( + None, self.tr("VM Update Confirmation"), + self.tr("{0}
The VM has to be running to be updated.
" + "Do you want to start it?
").format(vm.name), + QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel) + if reply != QtGui.QMessageBox.Yes: + return + trayIcon.showMessage(self.tr("Starting '{0}'...").format(vm.name), + msecs=3000) + + app.processEvents() + + t_monitor = thread_monitor.ThreadMonitor() + thread = threading.Thread(target=self.do_update_vm, + args=(vm, t_monitor)) + thread.daemon = True + thread.start() + + progress = QtGui.QProgressDialog( + self.tr("{0}
Please wait for the updater to " + "launch...").format(vm.name), "", 0, 0) + progress.setCancelButton(None) + progress.setModal(True) + progress.show() + + while not t_monitor.is_finished(): + app.processEvents() + time.sleep(0.2) + + progress.hide() + + if vm.qid != 0: + if not t_monitor.success: + QtGui.QMessageBox.warning(None, self.tr("Error VM update!"), + self.tr("ERROR: {0}").format( + t_monitor.error_msg)) + + @staticmethod + def do_update_vm(vm, thread_monitor): + try: + if vm.qid == 0: + subprocess.check_call( + ["/usr/bin/qubes-dom0-update", "--clean", "--gui"]) + else: + if not vm.is_running(): + trayIcon.showMessage( + "Starting the '{0}' VM...".format(vm.name), + msecs=3000) + vm.start() + vm.run_service("qubes.InstallUpdatesGUI", gui=True, + user="root", wait=False) + except Exception as ex: + thread_monitor.set_error_msg(str(ex)) + thread_monitor.set_finished() + return + thread_monitor.set_finished() + + + @QtCore.pyqtSlot(name='on_action_run_command_in_vm_triggered') + def action_run_command_in_vm_triggered(self): + vm = self.get_selected_vm() + + (command_to_run, ok) = QtGui.QInputDialog.getText( + self, self.tr('Qubes command entry'), + self.tr('Run command in {}:').format(vm.name)) + if not ok or command_to_run == "": + return + t_monitor = thread_monitor.ThreadMonitor() + thread = threading.Thread(target=self.do_run_command_in_vm, args=( + vm, command_to_run, t_monitor)) + thread.daemon = True + thread.start() + + while not t_monitor.is_finished(): + app.processEvents() + time.sleep(0.2) + + if not t_monitor.success: + QtGui.QMessageBox.warning(None, self.tr("Error while running command"), + self.tr("Exception while running command:
{0}").format( + t_monitor.error_msg)) + + @staticmethod + def do_run_command_in_vm(vm, command_to_run, thread_monitor): + try: + vm.run(command_to_run, verbose=False, autostart=True, + notify_function=lambda lvl, msg: trayIcon.showMessage( + msg, msecs=3000)) + except Exception as ex: + thread_monitor.set_error_msg(str(ex)) + thread_monitor.set_finished() + + @QtCore.pyqtSlot(name='on_action_set_keyboard_layout_triggered') + def action_set_keyboard_layout_triggered(self): + vm = self.get_selected_vm() + vm.run('qubes-change-keyboard-layout', verbose=False) + + @QtCore.pyqtSlot(name='on_action_showallvms_triggered') + def action_showallvms_triggered(self): + self.show_inactive_vms = self.action_showallvms.isChecked() + + self.showhide_vms() + # self.set_table_geom_size() + if self.settings_loaded: + self.manager_settings.setValue('view/show_inactive_vms', + self.show_inactive_vms) + self.manager_settings.sync() + + @QtCore.pyqtSlot(name='on_action_showinternalvms_triggered') + def action_showinternalvms_triggered(self): + self.show_internal_vms = self.action_showinternalvms.isChecked() + + self.showhide_vms() + # self.set_table_geom_size() + if self.settings_loaded: + self.manager_settings.setValue('view/show_internal_vms', + self.show_internal_vms) + self.manager_settings.sync() + + @QtCore.pyqtSlot(name='on_action_editfwrules_triggered') + def action_editfwrules_triggered(self): + pass + #TODO: revive + # vm = self.get_selected_vm() + # settings_window = VMSettingsWindow(vm, app, self.qvm_collection, + # "firewall") + # settings_window.exec_() + + @QtCore.pyqtSlot(name='on_action_global_settings_triggered') + def action_global_settings_triggered(self): + pass + #TODO: revive + # global_settings_window = GlobalSettingsWindow(app, self.qvm_collection) + # global_settings_window.exec_() + + @QtCore.pyqtSlot(name='on_action_show_network_triggered') + def action_show_network_triggered(self): + pass + #TODO: revive + # network_notes_dialog = NetworkNotesDialog() + # network_notes_dialog.exec_() + + @QtCore.pyqtSlot(name='on_action_restore_triggered') + def action_restore_triggered(self): + pass + #TODO: revive + # restore_window = RestoreVMsWindow(app, self.qvm_collection, + # self.blk_manager) + # restore_window.exec_() + + @QtCore.pyqtSlot(name='on_action_backup_triggered') + def action_backup_triggered(self): + pass + #TODO: revive + # backup_window = BackupVMsWindow(app, self.qvm_collection, + # self.blk_manager, self.shutdown_vm) + # backup_window.exec_() + + def showhide_menubar(self, checked): + self.menubar.setVisible(checked) + # self.set_table_geom_size() + if not checked: + self.context_menu.addAction(self.action_menubar) + else: + self.context_menu.removeAction(self.action_menubar) + if self.settings_loaded: + self.manager_settings.setValue('view/menubar_visible', checked) + self.manager_settings.sync() + + def showhide_toolbar(self, checked): + self.toolbar.setVisible(checked) + # self.set_table_geom_size() + if not checked: + self.context_menu.addAction(self.action_toolbar) + else: + self.context_menu.removeAction(self.action_toolbar) + if self.settings_loaded: + self.manager_settings.setValue('view/toolbar_visible', checked) + self.manager_settings.sync() + + def showhide_column(self, col_num, show): + self.table.setColumnHidden(col_num, not show) + # self.set_table_geom_size() + val = 1 if show else -1 + self.visible_columns_count += val + + if self.visible_columns_count == 1: + # disable hiding the last one + for c in self.columns_actions: + if self.columns_actions[c].isChecked(): + self.columns_actions[c].setEnabled(False) + break + elif self.visible_columns_count == 2 and val == 1: + # enable hiding previously disabled column + for c in self.columns_actions: + if not self.columns_actions[c].isEnabled(): + self.columns_actions[c].setEnabled(True) + break + + if self.settings_loaded: + col_name = [name for name in self.columns_indices.keys() if + self.columns_indices[name] == col_num][0] + self.manager_settings.setValue('columns/%s' % col_name, show) + self.manager_settings.sync() + + def on_action_vm_type_toggled(self, checked): + self.showhide_column(self.columns_indices['Type'], checked) + + def on_action_label_toggled(self, checked): + self.showhide_column(self.columns_indices['Label'], checked) + + def on_action_name_toggled(self, checked): + self.showhide_column(self.columns_indices['Name'], checked) + + def on_action_state_toggled(self, checked): + self.showhide_column(self.columns_indices['State'], checked) + + def on_action_internal_toggled(self, checked): + self.showhide_column(self.columns_indices['Internal'], checked) + + def on_action_ip_toggled(self, checked): + self.showhide_column(self.columns_indices['IP'], checked) + + def on_action_backups_toggled(self, checked): + self.showhide_column(self.columns_indices['Backups'], checked) + + def on_action_last_backup_toggled(self, checked): + self.showhide_column(self.columns_indices['Last backup'], checked) + + def on_action_template_toggled(self, checked): + self.showhide_column(self.columns_indices['Template'], checked) + + def on_action_netvm_toggled(self, checked): + self.showhide_column(self.columns_indices['NetVM'], checked) + + def on_action_cpu_toggled(self, checked): + self.showhide_column(self.columns_indices['CPU'], checked) + + def on_action_cpu_graph_toggled(self, checked): + self.showhide_column(self.columns_indices['CPU Graph'], checked) + + def on_action_mem_toggled(self, checked): + self.showhide_column(self.columns_indices['MEM'], checked) + + def on_action_mem_graph_toggled(self, checked): + self.showhide_column(self.columns_indices['MEM Graph'], checked) + + def on_action_size_on_disk_toggled(self, checked): + self.showhide_column(self.columns_indices['Size'], checked) + + @QtCore.pyqtSlot(name='on_action_about_qubes_triggered') + def action_about_qubes_triggered(self): + about = AboutDialog() + about.exec_() + + def createPopupMenu(self): + menu = QtGui.QMenu() + menu.addAction(self.action_toolbar) + menu.addAction(self.action_menubar) + return menu + + def open_tools_context_menu(self, widget, point): + self.tools_context_menu.exec_(widget.mapToGlobal(point)) + + @QtCore.pyqtSlot('const QPoint&') + def open_context_menu(self, point): + vm = self.get_selected_vm() + + running = vm.is_running() + + # logs menu + self.logs_menu.clear() + + if vm.qid == 0: + logfiles = ["/var/log/xen/console/hypervisor.log"] + else: + logfiles = [ + "/var/log/xen/console/guest-" + vm.name + ".log", + "/var/log/xen/console/guest-" + vm.name + "-dm.log", + "/var/log/qubes/guid." + vm.name + ".log", + "/var/log/qubes/qrexec." + vm.name + ".log", + ] + + menu_empty = True + for logfile in logfiles: + if os.path.exists(logfile): + action = self.logs_menu.addAction(QtGui.QIcon(":/log.png"), logfile) + action.setData(QtCore.QVariant(logfile)) + menu_empty = False + + self.logs_menu.setEnabled(not menu_empty) + + # blk menu + if not running: + self.blk_menu.setEnabled(False) + else: + self.blk_menu.clear() + self.blk_menu.setEnabled(True) + + # self.blk_manager.blk_lock.acquire() + # if len(self.blk_manager.attached_devs) > 0: + # for d in self.blk_manager.attached_devs: + # if (self.blk_manager.attached_devs[d] + # ['attached_to']['vm'].qid == vm.qid): + # text = self.tr("Detach {dev} {size} {desc}").format( + # dev=d, + # size= + # self.blk_manager.attached_devs[d]['size'], + # desc=self.blk_manager.attached_devs[d]['desc']) + # action = self.blk_menu.addAction(QtGui.QIcon(":/remove.png"), + # text) + # action.setData(QtCore.QVariant(d)) + # + # if len(self.blk_manager.free_devs) > 0: + # for d in self.blk_manager.free_devs: + # if d.startswith(vm.name): + # continue + # # skip partitions heuristic + # if d[-1].isdigit() and \ + # d[0:-1] in self.blk_manager.current_blk: + # continue + # text = self.tr("Attach {dev} {size} {desc}").format( + # dev=d, + # size= + # self.blk_manager.free_devs[d]['size'], + # desc=self.blk_manager.free_devs[d]['desc']) + # action = self.blk_menu.addAction(QtGui.QIcon(":/add.png"), text) + # action.setData(QtCore.QVariant(d)) + # + # self.blk_manager.blk_lock.release() + + if self.blk_menu.isEmpty(): + self.blk_menu.setEnabled(False) + + self.context_menu.exec_(self.table.mapToGlobal(point)) + + @QtCore.pyqtSlot('QAction *') + def show_log(self, action): + pass + #TODO fixme + # log = str(action.data()) + # log_dialog = LogDialog(app, log) + # log_dialog.exec_() + + @QtCore.pyqtSlot('QAction *') + def attach_dettach_device_triggered(self, action): + pass + # dev = str(action.data()) + # vm = self.get_selected_vm() + # + # self.blk_manager.blk_lock.acquire() + # try: + # if dev in self.blk_manager.attached_devs: + # self.blk_manager.detach_device(vm, dev) + # else: + # self.blk_manager.attach_device(vm, dev) + # self.blk_manager.blk_lock.release() + # except exc.QubesException as e: + # self.blk_manager.blk_lock.release() + # QtGui.QMessageBox.critical(None, + # self.tr("Block attach/detach error!"), str(e)) + + +class QubesTrayIcon(QtGui.QSystemTrayIcon): + def __init__(self, icon): + QtGui.QSystemTrayIcon.__init__(self, icon) + self.menu = QtGui.QMenu() + + action_showmanager = self.create_action( + self.tr("Open VM Manager"), + slot=show_manager, icon="qubes") + # action_copy = self.create_action( + # self.tr("Copy Dom0 clipboard"), icon="copy", + # slot=do_dom0_copy) + action_backup = self.create_action(self.tr("Make backup")) + action_preferences = self.create_action(self.tr("Preferences")) + action_set_netvm = self.create_action(self.tr("Set default NetVM"), + icon="networking") + action_sys_info = self.create_action(self.tr("System Info"), icon="dom0") + action_exit = self.create_action(self.tr("Exit"), slot=exit_app) + + action_backup.setDisabled(True) + action_preferences.setDisabled(True) + action_set_netvm.setDisabled(True) + action_sys_info.setDisabled(True) + + # self.blk_manager = blk_manager + + self.blk_menu = QtGui.QMenu(self.menu) + self.blk_menu.setTitle(self.tr("Block devices")) + action_blk_menu = self.create_action(self.tr("Block devices")) + action_blk_menu.setMenu(self.blk_menu) + + self.add_actions(self.menu, (action_showmanager, + # action_copy, + action_blk_menu, + action_backup, + action_sys_info, + None, + action_preferences, + action_set_netvm, + None, + action_exit)) + + self.setContextMenu(self.menu) + + # self.connect(self, + # QtCore.SIGNAL("activated (QtGui.QSystemTrayIcon::ActivationReason)"), + # self.icon_clicked) + + self.tray_notifier_type = None + self.tray_notifier = QDBusInterface("org.freedesktop.Notifications", + "/org/freedesktop/Notifications", + "org.freedesktop.Notifications", + session_bus) + srv_info = self.tray_notifier.call("GetServerInformation") + if srv_info.type() == QDBusMessage.ReplyMessage and \ + len(srv_info.arguments()) > 1: + self.tray_notifier_type = srv_info.arguments()[1] + + if os.path.exists(table_widgets.qubes_dom0_updates_stat_file): + self.showMessage(self.tr("Qubes dom0 updates available."), msecs=0) + + def update_blk_menu(self): + global manager_window + + def create_vm_submenu(dev): + blk_vm_menu = QtGui.QMenu(self.blk_menu) + blk_vm_menu.triggered.connect( + lambda a, trig_dev=dev: self.attach_device_triggered(a, + trig_dev)) + for this_vm in sorted(manager_window.qvm_collection.domains, + key=lambda x: x.name): + if not this_vm.is_running(): + continue + if this_vm.qid == 0: + # skip dom0 to prevent (fatal) mistakes + continue + this_action = blk_vm_menu.addAction(QtGui.QIcon(":/add.png"), + this_vm.name) + this_action.setData(QtCore.QVariant(this_vm)) + return blk_vm_menu + + self.blk_menu.clear() + self.blk_menu.setEnabled(True) + + # self.blk_manager.blk_lock.acquire() + # if len(self.blk_manager.attached_devs) > 0: + # for d in self.blk_manager.attached_devs: + # vm = self.blk_manager.attached_devs[d]['attached_to']['vm'] + # text = self.tr("Detach {dev} {desc} ({size}) from {vm}").format( + # dev=d, + # desc=self.blk_manager.attached_devs[d]['desc'], + # size=self.blk_manager.attached_devs[d]['size'], + # vm=vm.name) + # action = self.blk_menu.addAction(QtGui.QIcon(":/remove.png"), text) + # action.setData(QtCore.QVariant(d)) + # action.triggered.connect( + # lambda b, a=action: self.dettach_device_triggered(a)) + # + # if len(self.blk_manager.free_devs) > 0: + # for d in self.blk_manager.free_devs: + # # skip partitions heuristic + # if d[-1].isdigit() and d[0:-1] in self.blk_manager.current_blk: + # continue + # text = self.tr("Attach {dev} {size} {desc}").format( + # dev=d, + # size=self.blk_manager.free_devs[d]['size'], + # desc=self.blk_manager.free_devs[d]['desc'] + # ) + # action = self.blk_menu.addAction(QtGui.QIcon(":/add.png"), text) + # action.setMenu(create_vm_submenu(d)) + # + # self.blk_manager.blk_lock.release() + + if self.blk_menu.isEmpty(): + self.blk_menu.setEnabled(False) + + @QtCore.pyqtSlot('QAction *') + def attach_device_triggered(self, action, dev): + pass + # vm = action.data().toPyObject() + # + # self.blk_manager.blk_lock.acquire() + # try: + # self.blk_manager.attach_device(vm, dev) + # self.blk_manager.blk_lock.release() + # except exc.QubesException as e: + # self.blk_manager.blk_lock.release() + # QtGui.QMessageBox.critical(None, + # self.tr("Block attach/detach error!"), str(e)) + + @QtCore.pyqtSlot('QAction *') + def dettach_device_triggered(self, action): + pass + # dev = str(action.data()) + # vm = self.blk_manager.attached_devs[dev]['attached_to']['vm'] + # + # self.blk_manager.blk_lock.acquire() + # try: + # self.blk_manager.detach_device(vm, dev) + # self.blk_manager.blk_lock.release() + # except exc.QubesException as e: + # self.blk_manager.blk_lock.release() + # QtGui.QMessageBox.critical(None, + # self.tr("Block attach/detach error!"), str(e)) + + def icon_clicked(self, reason): + if reason == QtGui.QSystemTrayIcon.Context: + self.update_blk_menu() + # Handle the right click normally, i.e. display the context menu + return + else: + bring_manager_to_front() + + # noinspection PyMethodMayBeStatic + def add_actions(self, target, actions): + for action in actions: + if action is None: + target.addSeparator() + else: + target.addAction(action) + + def showMessage(self, message, msecs, **kwargs): + # QtDBus bindings doesn't use introspection to get proper method + # parameters types, so must cast explicitly + # v_replace_id = QtCore.QVariant(0) + # v_replace_id.convert(QtCore.QVariant.UInt) + # v_actions = QtCore.QVariant([]) + # v_actions.convert(QtCore.QVariant.StringList) + # if self.tray_notifier_type == "KDE": + # message = message.replace('\n', '
\n') + # self.tray_notifier.call("Notify", "Qubes", v_replace_id, + # "qubes-manager", "Qubes VM Manager", + # message, v_actions, QtCore.QVariant.fromMap({}), msecs) + pass + + def create_action(self, text, slot=None, shortcut=None, icon=None, + tip=None, checkable=False, signal="triggered()"): + action = QtGui.QAction(text, self) + if icon is not None: + action.setIcon(QtGui.QIcon(":/%s.png" % icon)) + if shortcut is not None: + action.setShortcut(shortcut) + if tip is not None: + action.setToolTip(tip) + action.setStatusTip(tip) + if slot is not None: + self.connect(action, QtCore.SIGNAL(signal), slot) + if checkable: + action.setCheckable(True) + return action + + +class QubesDbusNotifyServerAdaptor(QDBusAbstractAdaptor): + """ This provides the DBus adaptor to the outside world""" + + # Q_CLASSINFO("D-Bus Interface", dbus_interface) + + @QtCore.pyqtSlot(str, str) + def notify_error(self, vmname, message): + vm = self.parent().qvm_collection.domains[vmname] + if vm: + self.parent().set_error(vm.qid, message) + else: + # ignore VM-not-found error + pass + + @QtCore.pyqtSlot(str, str) + def clear_error_exact(self, vmname, message): + vm = self.parent().qvm_collection.domains[vmname] + if vm: + self.parent().clear_error_exact(vm.qid, message) + else: + # ignore VM-not-found error + pass + + @QtCore.pyqtSlot(str) + def clear_error(self, vmname): + vm = self.parent().qvm_collection.domains[vmname] + if vm: + self.parent().clear_error(vm.qid) + else: + # ignore VM-not-found error + pass + + @QtCore.pyqtSlot() + def show_manager(self): + bring_manager_to_front() + + +# def get_frame_size(): +# w = 0 +# h = 0 +# cmd = ['/usr/bin/xprop', '-name', 'Qubes VM Manager', '|', 'grep', +# '_NET_FRAME_EXTENTS'] +# xprop = subprocess.Popen(cmd, stdout=subprocess.PIPE) +# for l in xprop.stdout: +# line = l.split('=') +# if len(line) == 2: +# line = line[1].strip().split(',') +# if len(line) == 4: +# w = int(line[0].strip()) + int(line[1].strip()) +# h = int(line[2].strip()) + int(line[3].strip()) +# break +# # in case of some weird window managers we have to assume sth... +# if w <= 0: +# w = 10 +# if h <= 0: +# h = 30 +# +# manager_window.frame_width = w +# manager_window.frame_height = h +# return + + +def show_manager(): + manager_window.show() + # manager_window.set_table_geom_size() + manager_window.repaint() + manager_window.update_table(out_of_schedule=True) + app.processEvents() + + # get_frame_size() + # print manager_window.frame_width, " x ", manager_window.frame_height + # manager_window.set_table_geom_size() + + +def bring_manager_to_front(): + if manager_window.isVisible(): + subprocess.check_call( + ['/usr/bin/wmctrl', '-R', str(manager_window.windowTitle())]) + + else: + show_manager() + + +def show_running_manager_via_dbus(): + global system_bus + if system_bus is None: + system_bus = QDBusConnection.systemBus() + + qubes_manager = QDBusInterface('org.qubesos.QubesManager', + '/org/qubesos/QubesManager', + 'org.qubesos.QubesManager', system_bus) + qubes_manager.call('show_manager') + + +def exit_app(): + # notifier.stop() + app.exit() + + +# Bases on the original code by: +# Copyright (c) 2002-2007 Pascal Varet + +def handle_exception(exc_type, exc_value, exc_traceback): + for f in traceback.extract_stack(exc_traceback): + print(f[0], f[1]) + filename, line, dummy, dummy = traceback.extract_tb(exc_traceback).pop() + filename = os.path.basename(filename) + error = "%s: %s" % (exc_type.__name__, exc_value) + + QtGui.QMessageBox.critical( + None, + "Houston, we have a problem...", + "Whoops. A critical error has occured. This is most likely a bug " + "in Volume and Template Manager application.

%s" % + error + "at line %d of file %s.

" + % (line, filename)) + +def sighup_handler(signum, frame): + os.execl("/usr/bin/qubes-manager", "qubes-manager") + + +def main(): + signal.signal(signal.SIGHUP, sighup_handler) + + global system_bus + system_bus = QDBusConnection.systemBus() + # Avoid starting more than one instance of the app + # if not system_bus.registerService('org.qubesos.QubesManager'): + # show_running_manager_via_dbus() + # return + + # global qubes_host + # qubes_host = QubesHost() + + global app + app = QtGui.QApplication(sys.argv) + app.setOrganizationName("The Qubes Project") + app.setOrganizationDomain("http://qubes-os.org") + app.setApplicationName("Qubes VM Manager") + app.setWindowIcon(QtGui.QIcon.fromTheme("qubes-manager")) + # app.setAttribute(Qt.AA_DontShowIconsInMenus, False) + + # qt_translator = QTranslator() + # locale = QLocale.system().name() + # i18n_dir = os.path.join( + # os.path.dirname(os.path.realpath(__file__)), + # 'i18n') + # qt_translator.load("qubesmanager_{!s}.qm".format(locale), i18n_dir) + # app.installTranslator(qt_translator) + + sys.excepthook = handle_exception + + global session_bus + session_bus = QDBusConnection.sessionBus() + + qvm_collection = Qubes() + + global trayIcon + trayIcon = QubesTrayIcon(QtGui.QIcon.fromTheme("qubes-manager")) + + global manager_window + manager_window = VmManagerWindow(qvm_collection) + + # global wm + # wm = WatchManager() + # global notifier + # # notifier = ThreadedNotifier(wm, QubesManagerFileWatcher( + # # manager_window.mark_table_for_update)) + # notifier.start() + # wm.add_watch(system_path["qubes_store_filename"], + # EventsCodes.OP_FLAGS.get('IN_MODIFY')) + # wm.add_watch(os.path.dirname(system_path["qubes_store_filename"]), + # EventsCodes.OP_FLAGS.get('IN_MOVED_TO')) + # if os.path.exists(qubes_clipboard_info_file): + # wm.add_watch(qubes_clipboard_info_file, + # EventsCodes.OP_FLAGS.get('IN_CLOSE_WRITE')) + # wm.add_watch(os.path.dirname(qubes_clipboard_info_file), + # EventsCodes.OP_FLAGS.get('IN_CREATE')) + # wm.add_watch(os.path.dirname(table_widgets.qubes_dom0_updates_stat_file), + # EventsCodes.OP_FLAGS.get('IN_CREATE')) + + system_bus.registerObject(dbus_object_path, manager_window) + + threading.currentThread().setName("QtMainThread") + trayIcon.show() + + show_manager() + app.exec_() + + trayIcon = None + + +if __name__ == "__main__": + main() diff --git a/qubesmanager/table_widgets.py b/qubesmanager/table_widgets.py index e91806d..fe2cd49 100644 --- a/qubesmanager/table_widgets.py +++ b/qubesmanager/table_widgets.py @@ -1,10 +1,10 @@ -#!/usr/bin/python2 +#!/usr/bin/python3 # -*- coding: utf8 -*- -# pylint: skip-file # # The Qubes OS Project, http://www.qubes-os.org # -# Copyright (C) 2014 Marek Marczykowski-Górecki +# Copyright (C) 2014 Marek Marczykowski-Górecki +# # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -19,46 +19,43 @@ # You should have received a copy of the GNU Lesser General Public License along # with this program; if not, see . -import os - from PyQt4 import QtGui +from PyQt4 import QtCore -from PyQt4.QtCore import QSize, Qt - -from PyQt4.QtGui import QTableWidgetItem, QHBoxLayout, QIcon, QLabel, QWidget, \ - QSizePolicy, QSpacerItem, QFont, QColor, QProgressBar, QPainter, QPen -import time -from qubes.qubes import vm_files -import main - +# TODO: are those needed? qubes_dom0_updates_stat_file = '/var/lib/qubes/updates/dom0-updates-available' -power_order = Qt.DescendingOrder -update_order = Qt.AscendingOrder +power_order = QtCore.Qt.DescendingOrder +update_order = QtCore.Qt.AscendingOrder row_height = 30 -class VmIconWidget (QWidget): +# TODO: do I need to find icons? +class VmIconWidget(QtGui.QWidget): def __init__(self, icon_path, enabled=True, size_multiplier=0.7, - tooltip = None, parent=None, icon_sz = (32, 32)): + tooltip=None, parent=None, icon_sz=(32, 32)): super(VmIconWidget, self).__init__(parent) - self.label_icon = QLabel() + # TODO: check with Marek how icons should be done + self.label_icon = QtGui.QLabel() if icon_path[0] in ':/': - icon = QIcon (icon_path) + icon = QtGui.QIcon(icon_path) else: - icon = QIcon.fromTheme(icon_path) - icon_sz = QSize (row_height * size_multiplier, row_height * size_multiplier) - icon_pixmap = icon.pixmap(icon_sz, QIcon.Disabled if not enabled else QIcon.Normal) - self.label_icon.setPixmap (icon_pixmap) - self.label_icon.setFixedSize (icon_sz) - if tooltip != None: + icon = QtGui.QIcon.fromTheme(icon_path) + icon_sz = QtCore.QSize(row_height * size_multiplier, + row_height * size_multiplier) + icon_pixmap = icon.pixmap( + icon_sz, + QtGui.QIcon.Disabled if not enabled else QtGui.QIcon.Normal) + self.label_icon.setPixmap(icon_pixmap) + self.label_icon.setFixedSize(icon_sz) + if tooltip is not None: self.label_icon.setToolTip(tooltip) - layout = QHBoxLayout() + layout = QtGui.QHBoxLayout() layout.addWidget(self.label_icon) - layout.setContentsMargins(0,0,0,0) + layout.setContentsMargins(0, 0, 0, 0) self.setLayout(layout) def setToolTip(self, tooltip): @@ -67,9 +64,9 @@ class VmIconWidget (QWidget): else: self.label_icon.setToolTip('') -class VmTypeWidget(VmIconWidget): - class VmTypeItem(QTableWidgetItem): +class VmTypeWidget(VmIconWidget): + class VmTypeItem(QtGui.QTableWidgetItem): def __init__(self, value, vm): super(VmTypeWidget.VmTypeItem, self).__init__() self.value = value @@ -90,34 +87,41 @@ class VmTypeWidget(VmIconWidget): def __init__(self, vm, parent=None): (icon_path, tooltip) = self.get_vm_icon(vm) - super (VmTypeWidget, self).__init__(icon_path, True, 0.8, tooltip, parent) + super(VmTypeWidget, self).__init__( + icon_path, True, 0.8, tooltip, parent) self.vm = vm self.tableItem = self.VmTypeItem(self.value, vm) + self.value = None + + # TODO: seriously, are numbers the best idea here? + # TODO: add "provides network column + # TODO: in type make vmtype + # 'AdminVM': '0', + # 'TemplateVM': 't', + # 'AppVM': 'a', + # 'StandaloneVM': 's', + # 'DispVM': 'd', def get_vm_icon(self, vm): - if vm.qid == 0: + if vm.klass == 'AdminVM': self.value = 0 - return (":/dom0.png", "Dom0") - elif vm.is_netvm() and not vm.is_proxyvm(): - self.value = 1 - return (":/netvm.png", "NetVM") - elif vm.is_proxyvm(): - self.value = 2 - return (":/proxyvm.png", "ProxyVM") - elif vm.is_appvm() and vm.template is None: - self.value = 4 - return (":/standalonevm.png", "StandaloneVM") - elif vm.is_template(): + icon_name = "dom0" + elif vm.klass == 'TemplateVM': self.value = 3 - return (":/templatevm.png", "TemplateVM") - elif vm.is_appvm() or vm.is_disposablevm(): + icon_name = "templatevm" + elif vm.klass == 'StandaloneVM': + self.value = 4 + icon_name = "standalonevm" + else: self.value = 5 + vm.label.index - return (":/appvm.png", "AppVM") + icon_name = "appvm" + + return ":/" + icon_name + ".png", vm.klass class VmLabelWidget(VmIconWidget): - class VmLabelItem(QTableWidgetItem): + class VmLabelItem(QtGui.QTableWidgetItem): def __init__(self, value, vm): super(VmLabelWidget.VmLabelItem, self).__init__() self.value = value @@ -126,6 +130,7 @@ class VmLabelWidget(VmIconWidget): def set_value(self, value): self.value = value + # TODO: figure a prettier sorting method? def __lt__(self, other): if self.vm.qid == 0: return True @@ -138,26 +143,23 @@ class VmLabelWidget(VmIconWidget): def __init__(self, vm, parent=None): icon_path = self.get_vm_icon_path(vm) - super (VmLabelWidget, self).__init__(icon_path, True, 0.8, None, parent) + super(VmLabelWidget, self).__init__(icon_path, True, 0.8, None, parent) self.vm = vm self.tableItem = self.VmLabelItem(self.value, vm) + self.value = None def get_vm_icon_path(self, vm): - if vm.qid == 0: - self.value = 100 - return ":/off.png" - else: - self.value = vm.label.index - return vm.label.icon + self.value = vm.label.index + return vm.label.icon - -class VmNameItem (QTableWidgetItem): +class VmNameItem (QtGui.QTableWidgetItem): def __init__(self, vm): super(VmNameItem, self).__init__() - self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled) + # TODO: is this needed + self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) self.setText(vm.name) - self.setTextAlignment(Qt.AlignVCenter) + self.setTextAlignment(QtCore.Qt.AlignVCenter) self.qid = vm.qid def __lt__(self, other): @@ -168,38 +170,40 @@ class VmNameItem (QTableWidgetItem): return super(VmNameItem, self).__lt__(other) -class VmStatusIcon(QLabel): +# TODO: current status - it should work... +# TODO: maybe dbus events? +class VmStatusIcon(QtGui.QLabel): def __init__(self, vm, parent=None): - super (VmStatusIcon, self).__init__(parent) + super(VmStatusIcon, self).__init__(parent) self.vm = vm self.set_on_icon() - self.previous_power_state = vm.last_power_state + # TODO: rename previous power state to something better? + self.previous_power_state = self.vm.get_power_state() def update(self): - if self.previous_power_state != self.vm.last_power_state: + self.previous_power_state = self.vm.get_power_state() + if self.previous_power_state != self.vm.get_power_state(): self.set_on_icon() - self.previous_power_state = self.vm.last_power_state + self.previous_power_state = self.vm.get_power_state() def set_on_icon(self): - if self.vm.last_power_state == "Running": - icon = QIcon (":/on.png") - elif self.vm.last_power_state in ["Paused", "Suspended"]: - icon = QIcon (":/paused.png") - elif self.vm.last_power_state in ["Transient", "Halting", "Dying"]: - icon = QIcon (":/transient.png") + if self.vm.get_power_state() == "Running": + icon = QtGui.QIcon(":/on.png") + elif self.vm.get_power_state() in ["Paused", "Suspended"]: + icon = QtGui.QIcon(":/paused.png") + elif self.vm.get_power_state() in ["Transient", "Halting", "Dying"]: + icon = QtGui.QIcon(":/transient.png") else: - icon = QIcon (":/off.png") + icon = QtGui.QIcon(":/off.png") - icon_sz = QSize (row_height * 0.5, row_height *0.5) + icon_sz = QtCore.QSize(row_height * 0.5, row_height * 0.5) icon_pixmap = icon.pixmap(icon_sz) - self.setPixmap (icon_pixmap) - self.setFixedSize (icon_sz) + self.setPixmap(icon_pixmap) + self.setFixedSize(icon_sz) - -class VmInfoWidget (QWidget): - - class VmInfoItem (QTableWidgetItem): +class VmInfoWidget (QtGui.QWidget): + class VmInfoItem (QtGui.QTableWidgetItem): def __init__(self, upd_info_item, vm): super(VmInfoWidget.VmInfoItem, self).__init__() self.upd_info_item = upd_info_item @@ -213,7 +217,9 @@ class VmInfoWidget (QWidget): self_val = self.upd_info_item.value other_val = other.upd_info_item.value - if self.tableWidget().horizontalHeader().sortIndicatorOrder() == update_order: + # TODO: is this shit needed? + if self.tableWidget().\ + horizontalHeader().sortIndicatorOrder() == update_order: # the result will be sorted by upd, sorting order: Ascending self_val += 1 if self.vm.is_running() else 0 other_val += 1 if other.vm.is_running() else 0 @@ -221,22 +227,26 @@ class VmInfoWidget (QWidget): return self.vm.qid < other.vm.qid else: return self_val > other_val - elif self.tableWidget().horizontalHeader().sortIndicatorOrder() == power_order: - #the result will be sorted by power state, sorting order: Descending - self_val = -(self_val/10 + 10*(1 if self.vm.is_running() else 0)) - other_val = -(other_val/10 + 10*(1 if other.vm.is_running() else 0)) + elif self.tableWidget().\ + horizontalHeader().sortIndicatorOrder() == power_order: + # the result will be sorted by power state, + # sorting order: Descending + self_val = -(self_val/10 + + 10*(1 if self.vm.is_running() else 0)) + other_val = -(other_val/10 + + 10*(1 if other.vm.is_running() else 0)) if self_val == other_val: return self.vm.qid < other.vm.qid else: return self_val > other_val else: - #it would be strange if this happened + # it would be strange if this happened return - def __init__(self, vm, parent = None): - super (VmInfoWidget, self).__init__(parent) + def __init__(self, vm, parent=None): + super(VmInfoWidget, self).__init__(parent) self.vm = vm - layout = QHBoxLayout () + layout = QtGui.QHBoxLayout() self.on_icon = VmStatusIcon(vm) self.upd_info = VmUpdateInfoWidget(vm, show_text=False) @@ -247,11 +257,13 @@ class VmInfoWidget (QWidget): layout.addWidget(self.on_icon) layout.addWidget(self.upd_info) layout.addWidget(self.error_icon) - layout.addItem(QSpacerItem(0, 10, QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)) + layout.addItem(QtGui.QSpacerItem(0, 10, + QtGui.QSizePolicy.Expanding, + QtGui.QSizePolicy.Expanding)) layout.addWidget(self.blk_icon) layout.addWidget(self.rec_icon) - layout.setContentsMargins(5,0,5,0) + layout.setContentsMargins(5, 0, 5, 0) self.setLayout(layout) self.rec_icon.setVisible(False) @@ -263,42 +275,36 @@ class VmInfoWidget (QWidget): def update_vm_state(self, vm, blk_visible, rec_visible=None): self.on_icon.update() self.upd_info.update_outdated(vm) - if blk_visible != None: + if blk_visible is not None: self.blk_icon.setVisible(blk_visible) - if rec_visible != None: + if rec_visible is not None: self.rec_icon.setVisible(rec_visible) - self.error_icon.setToolTip(vm.qubes_manager_state[main.QMVmState - .ErrorMsg]) - self.error_icon.setVisible(vm.qubes_manager_state[main.QMVmState - .ErrorMsg] is not None) + # TODO: are these needed? + # self.error_icon.setToolTip(vm.qubes_manager_state[main.QMVmState + # .ErrorMsg]) + # self.error_icon.setVisible(vm.qubes_manager_state[main.QMVmState + # .ErrorMsg] is not None) -class VmTemplateItem (QTableWidgetItem): +# TODO add main to git history as a saner name and with a decent comment +# TODO and rename that shit +class VmTemplateItem (QtGui.QTableWidgetItem): def __init__(self, vm): super(VmTemplateItem, self).__init__() - self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled) + self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) self.vm = vm if vm.template is not None: self.setText(vm.template.name) else: - font = QFont() - font.setStyle(QFont.StyleItalic) + font = QtGui.QFont() + font.setStyle(QtGui.QFont.StyleItalic) self.setFont(font) - self.setTextColor(QColor("gray")) + self.setTextColor(QtGui.QColor("gray")) - if vm.is_appvm(): # and vm.template is None - self.setText("StandaloneVM") - elif vm.is_template(): - self.setText("TemplateVM") - elif vm.qid == 0: - self.setText("AdminVM") - elif vm.is_netvm(): - self.setText("NetVM") - else: - self.setText("---") + self.setText(vm.klass) - self.setTextAlignment(Qt.AlignVCenter) + self.setTextAlignment(QtCore.Qt.AlignVCenter) def __lt__(self, other): if self.vm.qid == 0: @@ -311,22 +317,20 @@ class VmTemplateItem (QTableWidgetItem): return super(VmTemplateItem, self).__lt__(other) - - -class VmNetvmItem (QTableWidgetItem): +class VmNetvmItem (QtGui.QTableWidgetItem): def __init__(self, vm): super(VmNetvmItem, self).__init__() - self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled) + self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) self.vm = vm - if vm.is_netvm() and not vm.is_proxyvm(): + # TODO: differentiate without no net vm/ no networking? + # TODO: mark provides network somehow? + if vm.netvm is None: self.setText("n/a") - elif vm.netvm is not None: - self.setText(vm.netvm.name) else: - self.setText("---") + self.setText(vm.netvm.name) - self.setTextAlignment(Qt.AlignVCenter) + self.setTextAlignment(QtCore.Qt.AlignVCenter) def __lt__(self, other): if self.vm.qid == 0: @@ -338,18 +342,17 @@ class VmNetvmItem (QTableWidgetItem): else: return super(VmNetvmItem, self).__lt__(other) -class VmInternalItem(QTableWidgetItem): + +class VmInternalItem(QtGui.QTableWidgetItem): def __init__(self, vm): super(VmInternalItem, self).__init__() - self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled) + self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) self.vm = vm - self.internal = self.vm.internal + self.internal = vm.features.get('internal', False) + # TODO: should default be false - if self.internal: - self.setText("Yes") - else: - self.setText("") + self.setText("Yes" if self.internal else "") def __lt__(self, other): if self.vm.qid == 0: @@ -359,144 +362,10 @@ class VmInternalItem(QTableWidgetItem): return super(VmInternalItem, self).__lt__(other) -class VmUsageBarWidget (QWidget): +# features man qvm-features +class VmUpdateInfoWidget(QtGui.QWidget): - class VmUsageBarItem (QTableWidgetItem): - def __init__(self, value, vm): - super(VmUsageBarWidget.VmUsageBarItem, self).__init__() - self.value = value - self.vm = vm - - def set_value(self, value): - self.value = value - - def __lt__(self, other): - if self.vm.qid == 0: - return True - elif other.vm.qid == 0: - return False - elif self.value == other.value: - return self.vm.qid < other.vm.qid - else: - return int(self.value) < int(other.value) - - def __init__(self, min, max, format, update_func, vm, load, hue=210, parent = None): - super (VmUsageBarWidget, self).__init__(parent) - - - self.min = min - self.max = max - self.update_func = update_func - self.value = min - - self.widget = QProgressBar() - self.widget.setMinimum(min) - self.widget.setMaximum(max) - self.widget.setFormat(format) - - self.widget.setStyleSheet( - "QProgressBar:horizontal{" +\ - "border: 1px solid hsv({0}, 100, 250);".format(hue) +\ - "border-radius: 4px;\ - background: transparent;\ - text-align: center;\ - }\ - QProgressBar::chunk:horizontal {\ - background: qlineargradient(x1: 1, y1: 0.5, x2: 1, y2: 0.5, " +\ - "stop: 0 hsv({0}, 170, 207),".format(hue) + - " stop: 1 white); \ - }" - ) - - layout = QHBoxLayout() - layout.addWidget(self.widget) - - self.setLayout(layout) - self.tableItem = self.VmUsageBarItem(min, vm) - - self.update_load(vm, load) - - - - def update_load(self, vm, load): - self.value = self.update_func(vm, load) - self.widget.setValue(self.value) - self.tableItem.set_value(self.value) - -class ChartWidget (QWidget): - - class ChartItem (QTableWidgetItem): - def __init__(self, value, vm): - super(ChartWidget.ChartItem, self).__init__() - self.value = value - self.vm = vm - - def set_value(self, value): - self.value = value - - def __lt__(self, other): - if self.vm.qid == 0: - return True - elif other.vm.qid == 0: - return False - elif self.value == other.value: - return self.vm.qid < other.vm.qid - else: - return self.value < other.value - - def __init__(self, vm, update_func, hue, load = 0, parent = None): - super (ChartWidget, self).__init__(parent) - self.update_func = update_func - self.hue = hue - if hue < 0 or hue > 255: - self.hue = 255 - self.load = load - assert self.load >= 0 and self.load <= 100, "load = {0}".format(self.load) - self.load_history = [self.load] - self.tableItem = ChartWidget.ChartItem(self.load, vm) - - def update_load (self, vm, load): - self.load = self.update_func(vm, load) - - assert self.load >= 0, "load = {0}".format(self.load) - # assert self.load >= 0 and self.load <= 100, "load = {0}".format(self.load) - if self.load > 100: - # FIXME: This is an ugly workaround for cpu_load:/ - self.load = 100 - - self.load_history.append (self.load) - self.tableItem.set_value(self.load) - self.repaint() - - def paintEvent (self, Event = None): - p = QPainter (self) - dx = 4 - - W = self.width() - H = self.height() - 5 - N = len(self.load_history) - if N > W/dx: - tail = N - W/dx - N = W/dx - self.load_history = self.load_history[tail:] - - assert len(self.load_history) == N - - for i in range (0, N-1): - val = self.load_history[N- i - 1] - sat = 70 + val*(255-70)/100 - color = QColor.fromHsv (self.hue, sat, 255) - pen = QPen (color) - pen.setWidth(dx-1) - p.setPen(pen) - if val > 0: - p.drawLine (W - i*dx - dx, H , W - i*dx - dx, H - (H - 5) * val/100) - - - -class VmUpdateInfoWidget(QWidget): - - class VmUpdateInfoItem (QTableWidgetItem): + class VmUpdateInfoItem (QtGui.QTableWidgetItem): def __init__(self, value, vm): super(VmUpdateInfoWidget.VmUpdateInfoItem, self).__init__() self.value = 0 @@ -521,16 +390,16 @@ class VmUpdateInfoWidget(QWidget): else: return self.value < other.value - def __init__(self, vm, show_text=True, parent = None): - super (VmUpdateInfoWidget, self).__init__(parent) - layout = QHBoxLayout () + def __init__(self, vm, show_text=True, parent=None): + super(VmUpdateInfoWidget, self).__init__(parent) + layout = QtGui.QHBoxLayout() self.show_text = show_text if self.show_text: - self.label=QLabel("") - layout.addWidget(self.label, alignment=Qt.AlignCenter) + self.label = QtGui.QLabel("") + layout.addWidget(self.label, alignment=QtCore.Qt.AlignCenter) else: - self.icon = QLabel("") - layout.addWidget(self.icon, alignment=Qt.AlignCenter) + self.icon = QtGui.QLabel("") + layout.addWidget(self.icon, alignment=QtCore.Qt.AlignCenter) self.setLayout(layout) self.previous_outdated_state = None @@ -539,65 +408,27 @@ class VmUpdateInfoWidget(QWidget): self.tableItem = VmUpdateInfoWidget.VmUpdateInfoItem(self.value, vm) def update_outdated(self, vm): - if vm.type == "HVM": - return - if vm.is_outdated(): - outdated_state = "outdated" - # During TemplateVM shutdown, there's an interval of a few seconds - # during which vm.template.is_running() returns false but - # vm.is_outdated() does not yet return true, so the icon disappears. - # This looks goofy, but we've decided not to fix it at this time - # (2015-02-09). - elif vm.template and vm.template.is_running(): + outdated_state = False + + try: + for vol in vm.volumes: + if vol.is_outdated(): + outdated_state = "outdated" + break + except AttributeError: + pass + + if not outdated_state and vm.template and vm.template.is_running(): outdated_state = "to-be-outdated" - else: - outdated_state = None - if outdated_state != self.previous_outdated_state: self.update_status_widget(outdated_state) - self.previous_outdated_state = outdated_state - if not vm.is_updateable(): - return - - if vm.qid == 0: - update_recommended = self.previous_update_recommended - if os.path.exists(qubes_dom0_updates_stat_file): - update_recommended = True - else: - update_recommended = False - - else: - update_recommended = self.previous_update_recommended - stat_file_path = vm.dir_path + '/' + vm_files["updates_stat_file"] - if not os.path.exists(stat_file_path): - update_recommended = False - else: - if (not hasattr(vm, "updates_stat_file_read_time")) or vm.updates_stat_file_read_time <= os.path.getmtime(stat_file_path): - - stat_file = open(stat_file_path, "r") - updates = stat_file.read().strip() - stat_file.close() - if updates.isdigit(): - updates = int(updates) - else: - updates = 0 - - if updates == 0: - update_recommended = False - else: - update_recommended = True - vm.updates_stat_file_read_time = time.time() - - if update_recommended and not self.previous_update_recommended: - self.update_status_widget("update") - elif self.previous_update_recommended and not update_recommended: - self.update_status_widget(None) - - self.previous_update_recommended = update_recommended - + updates_available = vm.features.get('updates-available', False) + if updates_available != self.previous_update_recommended: + self.update_status_widget("update" if updates_available else None) + self.previous_update_recommended = updates_available def update_status_widget(self, state): self.value = state @@ -618,7 +449,7 @@ class VmUpdateInfoWidget(QWidget): tooltip_text = self.tr( "The TemplateVM must be stopped before changes from its " "current session can be picked up by this VM.") - elif state is None: + else: label_text = "" icon_path = None tooltip_text = None @@ -632,26 +463,27 @@ class VmUpdateInfoWidget(QWidget): self.icon = VmIconWidget(icon_path, True, 0.7) self.icon.setToolTip(tooltip_text) else: - self.icon = QLabel(label_text) - self.layout().addWidget(self.icon, alignment=Qt.AlignCenter) + self.icon = QtGui.QLabel(label_text) + self.layout().addWidget(self.icon, alignment=QtCore.Qt.AlignCenter) -class VmSizeOnDiskItem (QTableWidgetItem): +class VmSizeOnDiskItem (QtGui.QTableWidgetItem): def __init__(self, vm): super(VmSizeOnDiskItem, self).__init__() - self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled) + self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) self.vm = vm self.value = 0 self.update() - self.setTextAlignment(Qt.AlignVCenter) + self.setTextAlignment(QtCore.Qt.AlignVCenter) def update(self): if self.vm.qid == 0: self.setText("n/a") else: + self.value = 10 self.value = self.vm.get_disk_utilization()/(1024*1024) - self.setText( str(self.value) + " MiB") + self.setText(str(self.value) + " MiB") def __lt__(self, other): if self.vm.qid == 0: @@ -663,17 +495,20 @@ class VmSizeOnDiskItem (QTableWidgetItem): else: return self.value < other.value -class VmIPItem(QTableWidgetItem): + +class VmIPItem(QtGui.QTableWidgetItem): def __init__(self, vm): super(VmIPItem, self).__init__() - self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled) + self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + # TODO: check if you don't need a try around here self.vm = vm self.ip = self.vm.ip if self.ip: self.setText(self.ip) else: self.setText("n/a") + self.setText("n/a") def __lt__(self, other): if self.vm.qid == 0: @@ -682,16 +517,18 @@ class VmIPItem(QTableWidgetItem): return False return super(VmIPItem, self).__lt__(other) -class VmIncludeInBackupsItem(QTableWidgetItem): + +class VmIncludeInBackupsItem(QtGui.QTableWidgetItem): def __init__(self, vm): super(VmIncludeInBackupsItem, self).__init__() - self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled) + self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) self.vm = vm if self.vm.include_in_backups: self.setText("Yes") else: self.setText("") + self.setText("") def __lt__(self, other): if self.vm.qid == 0: @@ -703,16 +540,18 @@ class VmIncludeInBackupsItem(QTableWidgetItem): else: return self.vm.include_in_backups < other.vm.include_in_backups -class VmLastBackupItem(QTableWidgetItem): + +class VmLastBackupItem(QtGui.QTableWidgetItem): def __init__(self, vm): super(VmLastBackupItem, self).__init__() - self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled) + self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) self.vm = vm if self.vm.backup_timestamp: - self.setText(str(self.vm.backup_timestamp.date())) + self.setText(self.vm.backup_timestamp) else: self.setText("") + self.setText("") def __lt__(self, other): if self.vm.qid == 0: diff --git a/ui/vtmanager.ui b/ui/vtmanager.ui new file mode 100644 index 0000000..4e89f45 --- /dev/null +++ b/ui/vtmanager.ui @@ -0,0 +1,943 @@ + + + VmManagerWindow + + + + 0 + 0 + 769 + 385 + + + + + 0 + 0 + + + + Qt::DefaultContextMenu + + + Qubes VM Manager + + + + + + + + + + + + true + + + + 0 + 0 + + + + false + + + true + + + + + + + QLayout::SetDefaultConstraint + + + 0 + + + 0 + + + + + 6 + + + 6 + + + + + Search: + + + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + + 200 + 30 + + + + Qt::CustomContextMenu + + + false + + + 0 + + + Qt::ScrollBarAsNeeded + + + Qt::ScrollBarAsNeeded + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + false + + + Qt::NoPen + + + true + + + false + + + 10 + + + 15 + + + false + + + 150 + + + 150 + + + false + + + false + + + + Nowy wiersz + + + + + + + + + + + + + + + + + + + + + + + + Name + + + VM name + + + + + State + + + Update info + + + + + Template + + + VM's template + + + + + NetVM + + + VM's netVM + + + + + CPU + + + + + CPU Graph + + + CPU usage graph + + + + + MEM + + + + + MEM Graph + + + Memory usage graph + + + + + Size + + + + + Internal + + + + + IP + + + + + Backups + + + + + Last backup + + + + + + + + + + 0 + 0 + 769 + 28 + + + + Qt::CustomContextMenu + + + + &System + + + + + + + + + &View + + + + + + + + + + + + + + + + + + + + + + + + + + + + V&M + + + + &Logs + + + + :/log.png:/log.png + + + + + Attach/detach &block devices + + + + :/mount.png:/mount.png + + + + + + + + + + + + + + + + + + + + + + + + + + &About + + + + + + + + + + + Qt::CustomContextMenu + + + toolBar + + + Qt::BottomToolBarArea|Qt::TopToolBarArea + + + false + + + TopToolBarArea + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + :/createvm.png:/createvm.png + + + Create &New VM + + + Create a new VM + + + + + false + + + + :/removevm.png:/removevm.png + + + &Delete VM + + + Remove an existing VM (must be stopped first) + + + + + false + + + + :/resumevm.png:/resumevm.png + + + Start/Resume V&M + + + Start/Resume selected VM + + + + + false + + + + :/pausevm.png:/pausevm.png + + + &Pause VM + + + Pause selected VM + + + + + false + + + + :/shutdownvm.png:/shutdownvm.png + + + &Shutdown VM + + + Shutdown selected VM + + + + + false + + + + :/restartvm.png:/restartvm.png + + + Restar&t VM + + + Restart selected VM + + + + + false + + + + :/apps.png:/apps.png + + + Add/remove app s&hortcuts + + + Add/remove app shortcuts for this VM + + + + + false + + + + :/updateable.png:/updateable.png + + + &Update VM + + + Update VM system + + + + + false + + + + :/mic.png:/mic.png + + + Attach/detach &audio-input to the VM + + + Attach/detach audio-input to the VM + + + + + true + + + false + + + + :/show-all-running.png + :/showallvms.png:/show-all-running.png + + + Show/Hide inactive VMs + + + Show/Hide inactive VMs + + + + + + :/firewall.png:/firewall.png + + + Edit VM &firewall rules + + + Edit VM firewall rules + + + + + true + + + + :/showcpuload.png:/showcpuload.png + + + Show graphs + + + Show Graphs + + + + + Options + + + + + View + + + + + true + + + true + + + &CPU + + + + + true + + + true + + + CPU &Graph + + + + + true + + + true + + + &MEM + + + + + true + + + true + + + M&EM Graph + + + + + true + + + true + + + &Template + + + + + true + + + true + + + &NetVM + + + + + + :/settings.png:/settings.png + + + VM s&ettings + + + VM Settings + + + + + + :/restore.png:/restore.png + + + &Restore VMs from backup + + + + + + :/backup.png:/backup.png + + + &Backup VMs + + + + + + :/global-settings.png:/global-settings.png + + + &Global settings + + + + + + :/networking.png:/networking.png + + + &Qubes Network + + + + + true + + + true + + + &State + + + + + + :/killvm.png:/killvm.png + + + &Kill VM + + + Kill selected VM + + + + + + :/kbd-layout.png:/kbd-layout.png + + + Set keyboard la&yout + + + Set keyboard layout per VM + + + + + true + + + true + + + T&ype + + + VM Type + + + + + true + + + true + + + &Label + + + + + true + + + true + + + N&ame + + + + + true + + + true + + + Show tool bar + + + + + true + + + true + + + Show menu bar + + + + + + + + + + &Qubes OS + + + + + true + + + true + + + Si&ze + + + Size on Disk + + + + + + :/run-command.png:/run-command.png + + + &Run command in VM + + + Run command in the specified VM + + + + + false + + + + :/templatevm.png:/templatevm.png + + + &Clone VM + + + + + true + + + true + + + Inte&rnal + + + Is an internal VM + + + + + true + + + false + + + + :/show-all-running.png:/show-all-running.png + + + Show/Hide internal VMs + + + + + false + + + + :/resumevm.png:/resumevm.png + + + Start VM for Window Tools installation + + + Start VM for Window Tools installation + + + + + true + + + true + + + &IP + + + + + true + + + true + + + Include in &backups + + + + + true + + + true + + + Last back&up + + + + + Search + + + Ctrl+F + + + + + + + + From d57e58ae4d9e2980bc2ff19be2e25badc83bedc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Fri, 5 Jan 2018 17:31:15 +0100 Subject: [PATCH 58/70] Further work on qubes manager --- qubesmanager/main.py | 1239 ++++++--------------------------- qubesmanager/table_widgets.py | 60 +- rpm_spec/qmgr.spec | 3 + setup.py | 3 +- ui/vtmanager.ui | 32 +- 5 files changed, 243 insertions(+), 1094 deletions(-) diff --git a/qubesmanager/main.py b/qubesmanager/main.py index 2f3f9f2..82ac8ed 100755 --- a/qubesmanager/main.py +++ b/qubesmanager/main.py @@ -22,7 +22,6 @@ # # -# TODO: cleanup imports import sys import os import os.path @@ -33,52 +32,20 @@ from datetime import datetime, timedelta import traceback from qubesadmin import Qubes -from qubesadmin import exc from PyQt4 import QtGui from PyQt4 import QtCore -from PyQt4 import Qt -from PyQt4.QtDBus import QDBusVariant, QDBusMessage -from PyQt4.QtDBus import QDBusConnection -from PyQt4.QtDBus import QDBusInterface, QDBusAbstractAdaptor -from pyinotify import WatchManager, ThreadedNotifier, EventsCodes, \ - ProcessEvent from . import ui_vtmanager from . import thread_monitor from . import table_widgets +from . import settings +from . import global_settings +from . import restore +from . import backup import threading -# from qubes.qubes import QubesVmCollection -# from qubes.qubes import QubesException -# from qubes.qubes import system_path -# from qubes.qubes import QubesDaemonPidfile -# from qubes.qubes import QubesHost from qubesmanager.about import AboutDialog -# import table_widgets -# from block import QubesBlockDevicesManager -# from table_widgets import VmTypeWidget, VmLabelWidget, VmNameItem, \ -# VmInfoWidget, VmTemplateItem, VmNetvmItem, VmUsageBarWidget, ChartWidget, \ -# VmSizeOnDiskItem, VmInternalItem, VmIPItem, VmIncludeInBackupsItem, \ -# VmLastBackupItem -# from qubes.qubes import QubesHVm -# from qubes import qubesutils -# from ui_mainwindow import * -# from create_new_vm import NewVmDlg -# from settings import VMSettingsWindow -# from restore import RestoreVMsWindow -# from backup import BackupVMsWindow -# from global_settings import GlobalSettingsWindow -# from networknotes import NetworkNotesDialog -# from log_dialog import LogDialog - -# TODO: probably unneeded -update_suggestion_interval = 14 # 14 days -# TODO: probably unneeded -dbus_object_path = '/org/qubesos/QubesManager' -dbus_interface = 'org.qubesos.QubesManager' -system_bus = None -session_bus = None # TODO: probably unneeded @@ -88,7 +55,6 @@ class QMVmState: AudioRecAllowed = 3 -#TODO: this is actually needed O_O class SearchBox(QtGui.QLineEdit): def __init__(self, parent=None): super(SearchBox, self).__init__(parent) @@ -107,12 +73,12 @@ class SearchBox(QtGui.QLineEdit): class VmRowInTable(object): - cpu_graph_hue = 210 - mem_graph_hue = 120 def __init__(self, vm, row_no, table): self.vm = vm self.row_no = row_no + # TODO: replace a million different widgets with a more generic + # VmFeatureWidget or VMPropertyWidget table_widgets.row_height = VmManagerWindow.row_height table.setRowHeight(row_no, VmManagerWindow.row_height) @@ -147,44 +113,6 @@ class VmRowInTable(object): table.setItem(row_no, VmManagerWindow.columns_indices['NetVM'], self.netvm_widget) - # self.cpu_usage_widget = table_widgets.VmUsageBarWidget( - # 0, 100, "%v %", - # # lambda v, val: val if v.last_running else 0, - # lambda v, val: val, - # vm, 0, self.cpu_graph_hue) - # table.setCellWidget(row_no, VmManagerWindow.columns_indices['CPU'], - # self.cpu_usage_widget) - # table.setItem(row_no, VmManagerWindow.columns_indices['CPU'], - # self.cpu_usage_widget.tableItem) - - # self.load_widget = table_widgets.ChartWidget( - # vm, - # lambda v, val: val if v.last_running else 0, - # self.cpu_graph_hue, 0) - # table.setCellWidget(row_no, - # VmManagerWindow.columns_indices['CPU Graph'], - # self.load_widget) - # table.setItem(row_no, VmManagerWindow.columns_indices['CPU Graph'], - # self.load_widget.tableItem) - - # self.mem_usage_widget = table_widgets.VmUsageBarWidget( - # 0, qubes_host.memory_total / 1024, "%v MB", - # lambda v, val: v.get_mem() / 1024, - # vm, 0, self.mem_graph_hue) - # table.setCellWidget(row_no, VmManagerWindow.columns_indices['MEM'], - # self.mem_usage_widget) - # table.setItem(row_no, VmManagerWindow.columns_indices['MEM'], - # self.mem_usage_widget.tableItem) - # - # # self.mem_widget = table_widgets.ChartWidget( - # # vm, lambda v, val: v.get_mem() * 100 / qubes_host.memory_total, - # # self.mem_graph_hue, 0) - # table.setCellWidget(row_no, - # VmManagerWindow.columns_indices['MEM Graph'], - # self.mem_widget) - # table.setItem(row_no, VmManagerWindow.columns_indices['MEM Graph'], - # self.mem_widget.tableItem) - self.size_widget = table_widgets.VmSizeOnDiskItem(vm) table.setItem(row_no, VmManagerWindow.columns_indices['Size'], self.size_widget) @@ -197,7 +125,8 @@ class VmRowInTable(object): table.setItem(row_no, VmManagerWindow.columns_indices['IP'], self.ip_widget) - self.include_in_backups_widget = table_widgets.VmIncludeInBackupsItem(vm) + self.include_in_backups_widget = \ + table_widgets.VmIncludeInBackupsItem(vm) table.setItem(row_no, VmManagerWindow.columns_indices[ 'Backups'], self.include_in_backups_widget) @@ -205,35 +134,26 @@ class VmRowInTable(object): table.setItem(row_no, VmManagerWindow.columns_indices[ 'Last backup'], self.last_backup_widget) - def update(self, blk_visible=None, cpu_load=None, update_size_on_disk=False, - rec_visible=None): + def update(self, update_size_on_disk=False): """ Update info in a single VM row - :param blk_visible: if not None, show/hide block icon, otherwise - don't change its visibility - :param cpu_load: current CPU load (if applicable), in percents :param update_size_on_disk: should disk utilization be updated? the widget will extract the data from VM object - :param rec_visible: if not None, show/hide mic icon, otherwise don't - change its visibility :return: None """ - self.info_widget.update_vm_state(self.vm, blk_visible, rec_visible) - if cpu_load is not None: - self.cpu_usage_widget.update_load(self.vm, cpu_load) - self.mem_usage_widget.update_load(self.vm, None) - self.load_widget.update_load(self.vm, cpu_load) - self.mem_widget.update_load(self.vm, None) + self.info_widget.update_vm_state(self.vm) if update_size_on_disk: self.size_widget.update() vm_shutdown_timeout = 20000 # in msec -vm_restart_check_timeout= 1000 # in msec +vm_restart_check_timeout = 1000 # in msec class VmShutdownMonitor(QtCore.QObject): - def __init__(self, vm, shutdown_time=vm_shutdown_timeout, check_time=vm_restart_check_timeout, and_restart=False, caller=None): + def __init__(self, vm, shutdown_time=vm_shutdown_timeout, + check_time=vm_restart_check_timeout, + and_restart=False, caller=None): QtCore.QObject.__init__(self) self.vm = vm self.shutdown_time = shutdown_time @@ -245,6 +165,7 @@ class VmShutdownMonitor(QtCore.QObject): def restart_vm_if_needed(self): if self.and_restart and self.caller: self.caller.start_vm(self.vm) +# TODO: can i kill running vm def check_again_later(self): # noinspection PyTypeChecker,PyCallByClass @@ -260,12 +181,14 @@ class VmShutdownMonitor(QtCore.QObject): vm = self.vm vm_is_running = vm.is_running() vm_start_time = vm.get_start_time() - if vm_is_running and vm_start_time and vm_start_time < self.shutdown_started: + if vm_is_running and vm_start_time \ + and vm_start_time < self.shutdown_started: if self.timeout_reached(): reply = QtGui.QMessageBox.question( None, self.tr("VM Shutdown"), - self.tr("The VM '{0}' hasn't shutdown within the last " - "{1} seconds, do you want to kill it?
").format( + self.tr( + "The VM '{0}' hasn't shutdown within the last " + "{1} seconds, do you want to kill it?
").format( vm.name, self.shutdown_time / 1000), self.tr("Kill it!"), self.tr("Wait another {0} seconds...").format( @@ -289,26 +212,20 @@ class VmShutdownMonitor(QtCore.QObject): self.restart_vm_if_needed() + class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): row_height = 30 column_width = 200 min_visible_rows = 10 - show_inactive_vms = True - show_internal_vms = False search = "" # suppress saving settings while initializing widgets settings_loaded = False - # TODO: does this work columns_indices = {"Type": 0, "Label": 1, "Name": 2, "State": 3, "Template": 4, "NetVM": 5, - # "CPU": 6, # delete - # "CPU Graph": 7, # delete - # "MEM": 8, # delete - # "MEM Graph": 9, # delete "Size": 6, "Internal": 7, "IP": 8, @@ -321,18 +238,11 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): self.setupUi(self) self.toolbar = self.toolBar - self.manager_settings = QtCore.QSettings() #TODO use Qt settings mechanism + self.manager_settings = QtCore.QSettings(self) - # self.qubes_watch = qubesutils.QubesWatch() self.qvm_collection = qvm_collection - # self.qubes_watch.setup_domain_watch(self.domain_state_changed_callback) - # self.qubes_watch.setup_block_watch(self.blk_manager.block_devs_event) - # self.blk_watch_thread = threading.Thread( - # target=self.qubes_watch.watch_loop) - # self.blk_watch_thread.daemon = True - # self.blk_watch_thread.start() - self.searchbox = SearchBox() #TODO check if this works + self.searchbox = SearchBox() # TODO check if this works self.searchbox.setValidator(QtGui.QRegExpValidator( QtCore.QRegExp("[a-zA-Z0-9-]*", QtCore.Qt.CaseInsensitive), None)) self.searchContainer.addWidget(self.searchbox) @@ -351,8 +261,6 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): self.vms_list = [] self.vms_in_table = {} self.reload_table = False - self.running_vms_count = 0 - self.internal_vms_count = 0 self.vm_errors = {} self.vm_rec = {} @@ -369,10 +277,6 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): self.columns_indices["State"]: self.action_state, self.columns_indices["Template"]: self.action_template, self.columns_indices["NetVM"]: self.action_netvm, - # self.columns_indices["CPU"]: self.action_cpu, - # self.columns_indices["CPU Graph"]: self.action_cpu_graph, - # self.columns_indices["MEM"]: self.action_mem, - # self.columns_indices["MEM Graph"]: self.action_mem_graph, self.columns_indices["Size"]: self.action_size_on_disk, self.columns_indices["Internal"]: self.action_internal, self.columns_indices["IP"]: self @@ -383,12 +287,6 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): # TODO: make refresh button self.visible_columns_count = len(self.columns_indices) - # self.table.setColumnHidden(self.columns_indices["CPU"], True) - # self.action_cpu.setChecked(False) - # self.table.setColumnHidden(self.columns_indices["CPU Graph"], True) - # self.action_cpu_graph.setChecked(False) - # self.table.setColumnHidden(self.columns_indices["MEM Graph"], True) - # self.action_mem_graph.setChecked(False) self.table.setColumnHidden(self.columns_indices["Size"], True) self.action_size_on_disk.setChecked(False) self.table.setColumnHidden(self.columns_indices["Internal"], True) @@ -417,11 +315,11 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): self.context_menu = QtGui.QMenu(self) + # TODO: check if this works, check all options self.context_menu.addAction(self.action_settings) self.context_menu.addAction(self.action_editfwrules) self.context_menu.addAction(self.action_appmenus) self.context_menu.addAction(self.action_set_keyboard_layout) - self.context_menu.addMenu(self.blk_menu) self.context_menu.addAction(self.action_toggle_audio_input) self.context_menu.addSeparator() @@ -463,12 +361,11 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): QtCore.SIGNAL("customContextMenuRequested(const QPoint&)"), lambda pos: self.open_tools_context_menu(self.toolBar, pos)) - self.connect(self.blk_menu, QtCore.SIGNAL("triggered(QAction *)"), - self.attach_dettach_device_triggered) self.connect(self.logs_menu, QtCore.SIGNAL("triggered(QAction *)"), self.show_log) - self.connect(self.searchbox, QtCore.SIGNAL("textChanged(const QString&)"), + self.connect(self.searchbox, + QtCore.SIGNAL("textChanged(const QString&)"), self.do_search) self.table.setContentsMargins(0, 0, 0, 0) @@ -480,13 +377,8 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): self.connect(self.action_toolbar, QtCore.SIGNAL("toggled(bool)"), self.showhide_toolbar) - self.register_dbus_watches() - self.load_manager_settings() - self.action_showallvms.setChecked(self.show_inactive_vms) - self.action_showinternalvms.setChecked(self.show_internal_vms) - self.fill_table() self.counter = 0 @@ -494,129 +386,39 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): self.shutdown_monitor = {} self.last_measure_results = {} self.last_measure_time = time.time() - # noinspection PyCallByClass,PyTypeChecker - # QtCore.QTimer.singleShot(self.update_interval, self.update_table) - QubesDbusNotifyServerAdaptor(self) - - def load_manager_settings(self): #TODO: replace with a Qt-based settings gizmo also make it work + def load_manager_settings(self): # visible columns # self.manager_settings.beginGroup("columns") - # for col in self.columns_indices.keys(): - # col_no = self.columns_indices[col] - # visible = self.manager_settings.value( - # col, - # defaultValue=not self.table.isColumnHidden(col_no)) - # self.columns_actions[col_no].setChecked(visible) + for col in self.columns_indices.keys(): + col_no = self.columns_indices[col] + visible = self.manager_settings.value( + 'columns/%s' % col, + defaultValue=not self.table.isColumnHidden(col_no)) + self.columns_actions[col_no].setChecked(visible == "true") # self.manager_settings.endGroup() - # self.show_inactive_vms = self.manager_settings.value( - # "view/show_inactive_vms", defaultValue=False) - # self.show_internal_vms = self.manager_settings.value( - # "view/show_internal_vms", defaultValue=False) - # self.sort_by_column = str( - # self.manager_settings.value("view/sort_column", - # defaultValue=self.sort_by_column)) - # # self.sort_order = QtCore.Qt.SortOrder( #TODO does not seem to work - # # self.manager_settings.value("view/sort_order", - # # defaultValue=self.sort_order)[ - # # 0]) - # self.table.sortItems(self.columns_indices[self.sort_by_column], - # self.sort_order) - # if not self.manager_settings.value("view/menubar_visible", - # defaultValue=True): - # self.action_menubar.setChecked(False) - # if not self.manager_settings.value("view/toolbar_visible", - # defaultValue=True): - # self.action_toolbar.setChecked(False) - # x = self.manager_settings.value('position/x', defaultValue=-1)[ - # 0] - # y = self.manager_settings.value('position/y', defaultValue=-1)[ - # 0] - # if x != -1 or y != -1: - # self.move(x, y) + + self.sort_by_column = str( + self.manager_settings.value("view/sort_column", + defaultValue=self.sort_by_column)) + self.sort_order = QtCore.Qt.SortOrder( + self.manager_settings.value("view/sort_order", + defaultValue=self.sort_order)[ + 0]) + self.table.sortItems(self.columns_indices[self.sort_by_column], + self.sort_order) + if not self.manager_settings.value("view/menubar_visible", + defaultValue=True): + self.action_menubar.setChecked(False) + if not self.manager_settings.value("view/toolbar_visible", + defaultValue=True): + self.action_toolbar.setChecked(False) self.settings_loaded = True def show(self): super(VmManagerWindow, self).show() self.screen_number = app.desktop().screenNumber(self) - # def set_table_geom_size(self): - # - # desktop_width = app.desktop().availableGeometry( - # self).width() - self.frame_width # might be wrong... - # desktop_height = app.desktop().availableGeometry( - # self).height() - self.frame_height # might be wrong... - # desktop_height -= self.row_height # UGLY! to somehow ommit taskbar... - # - # w = self.table.horizontalHeader().length() + \ - # self.table.verticalScrollBar().width() + \ - # 2 * self.table.frameWidth() + 1 - # - # h = self.table.horizontalHeader().height() + \ - # 2 * self.table.frameWidth() - # - # mainwindow_to_add = 0 - # - # available_space = desktop_height - # if self.menubar.isVisible(): - # menubar_height = (self.menubar.sizeHint().height() + - # self.menubar.contentsMargins().top() + - # self.menubar.contentsMargins().bottom()) - # available_space -= menubar_height - # mainwindow_to_add += menubar_height - # if self.toolbar.isVisible(): - # toolbar_height = (self.toolbar.sizeHint().height() + - # self.toolbar.contentsMargins().top() + - # self.toolbar.contentsMargins().bottom()) - # available_space -= toolbar_height - # mainwindow_to_add += toolbar_height - # if w >= desktop_width: - # available_space -= self.table.horizontalScrollBar().height() - # h += self.table.horizontalScrollBar().height() - # - # # account for search box height - # available_space -= self.searchbox.height() - # h += self.searchbox.height() - # - # default_rows = int(available_space / self.row_height) - # - # n = sum(not self.table.isRowHidden(row) for row in - # range(self.table.rowCount())) - # - # if n > default_rows: - # h += default_rows * self.row_height - # self.table.verticalScrollBar().show() - # else: - # h += n * self.row_height - # self.table.verticalScrollBar().hide() - # w -= self.table.verticalScrollBar().width() - # - # w = min(desktop_width, w) - # - # self.centralwidget.setFixedHeight(h) - # - # h += mainwindow_to_add - # - # self.setMaximumHeight(h) - # self.setMinimumHeight(h) - # - # self.table.setFixedWidth(w) - # self.centralwidget.setFixedWidth(w) - # # don't change the following two lines to setFixedWidth! - # self.setMaximumWidth(w) - # self.setMinimumWidth(w) - - def moveEvent(self, event): - super(VmManagerWindow, self).moveEvent(event) - screen_number = app.desktop().screenNumber(self) - if self.screen_number != screen_number: - self.screen_changed = True - self.screen_number = screen_number - if self.settings_loaded: - self.manager_settings.setValue('position/x', self.x()) - self.manager_settings.setValue('position/y', self.y()) - # do not sync for performance reasons - def domain_state_changed_callback(self, name=None, uuid=None): if name is not None: vm = self.qvm_collection.domains[name] @@ -624,30 +426,7 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): vm.refresh() def get_vms_list(self): - # self.qvm_collection.lock_db_for_reading() - # self.qvm_collection.load() - # self.qvm_collection.unlock_db() - - running_count = 0 - internal_count = 0 - - vms_list = [vm for vm in self.qvm_collection.domains] - for vm in vms_list: - # vm.last_power_state = vm.get_power_state() - # vm.last_running = vm.is_running() - # if vm.last_running: - # running_count += 1 - # if vm.internal: - # internal_count += 1 - # vm.qubes_manager_state = {} - # self.update_audio_rec_info(vm) - # vm.qubes_manager_state[QMVmState.ErrorMsg] = self.vm_errors[ - # vm.qid] if vm.qid in self.vm_errors else None - running_count +=1 - - self.running_vms_count = running_count - self.internal_vms_count = internal_count - return vms_list + return [vm for vm in self.qvm_collection.domains] def fill_table(self): # save current selection @@ -684,25 +463,16 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): self.table.setSortingEnabled(True) self.showhide_vms() - # self.set_table_geom_size() - def showhide_vms(self): - if self.show_inactive_vms and self.show_internal_vms and not self.search: + def showhide_vms(self): # TODO: just show all all the time? + if not self.search: for row_no in range(self.table.rowCount()): self.table.setRowHidden(row_no, False) else: for row_no in range(self.table.rowCount()): widget = self.table.cellWidget(row_no, self.columns_indices["State"]) - running = False - internal = False - # running = widget.vm.last_running - # internal = widget.vm.internal - name = widget.vm.name - - show = (running or self.show_inactive_vms) and \ - (not internal or self.show_internal_vms) and \ - (self.search in widget.vm.name or not self.search) + show = (self.search in widget.vm.name or not self.search) self.table.setRowHidden(row_no, not show) @QtCore.pyqtSlot(str) @@ -721,7 +491,6 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.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 reload_table = self.reload_table if manager_window.isVisible(): @@ -768,72 +537,20 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): self.fill_table() update_devs = True - # if not self.show_inactive_vms and \ - # some_vms_have_changed_power_state: - # self.showhide_vms() - # self.set_table_geom_size() - # if self.sort_by_column == \ # "State" and some_vms_have_changed_power_state: - # self.table.sortItems(self.columns_indices[self.sort_by_column], + # self.table.sortItems(self.columns_indices[self.sort_by_column], # self.sort_order) - blk_visible = None - rows_with_blk = None - # if update_devs: - # rows_with_blk = [] - # self.blk_manager.blk_lock.acquire() - # for d in self.blk_manager.attached_devs: - # rows_with_blk.append( - # self.blk_manager.attached_devs[d]['attached_to'][ - # 'vm'].qid) - # self.blk_manager.blk_lock.release() - - if (not self.table.isColumnHidden(self.columns_indices['Size'])) and \ - self.counter % 60 == 0 or out_of_schedule: + if (not self.table.isColumnHidden(self.columns_indices['Size'])) \ + and self.counter % 60 == 0 or out_of_schedule: self.update_size_on_disk = True - if self.counter % 3 == 0 or out_of_schedule: - # (self.last_measure_time, self.last_measure_results) = \ - # qubes_host.measure_cpu_usage(self.qvm_collection, - # self.last_measure_results, - # self.last_measure_time) + for vm_row in self.vms_in_table.values(): + vm_row.update(update_size_on_disk=self.update_size_on_disk) + # TODO: fix these for saner opts TODO2: is it fixed? - for vm_row in self.vms_in_table.values(): - cur_cpu_load = None - # if vm_row.vm.get_xid() in self.last_measure_results: - # cur_cpu_load = self.last_measure_results[vm_row.vm.xid][ - # 'cpu_usage'] - # else: - # cur_cpu_load = 0 - - if rows_with_blk is not None: - if vm_row.vm.qid in rows_with_blk: - blk_visible = True - else: - blk_visible = False - - vm_row.update(blk_visible=blk_visible, - cpu_load=cur_cpu_load, - update_size_on_disk=self.update_size_on_disk, - rec_visible=self.vm_rec.get(vm_row.vm.name, - False)) - - else: - for vm_row in self.vms_in_table.values(): - if rows_with_blk is not None: - if vm_row.vm.qid in rows_with_blk: - blk_visible = True - else: - blk_visible = False - - vm_row.update(blk_visible=blk_visible, - update_size_on_disk=self.update_size_on_disk, - rec_visible=self.vm_rec.get(vm_row.vm.name, - False)) - - if self.sort_by_column in ["CPU", "CPU Graph", "MEM", "MEM Graph", - "State", "Size", "Internal"]: + if self.sort_by_column in ["CPU", "State", "Size", "Internal"]: # "State": needed to sort after reload (fill_table sorts items # with setSortingEnabled, but by that time the widgets values # are not correct yet). @@ -843,36 +560,12 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): self.table_selection_changed() self.update_size_on_disk = False - if not out_of_schedule: - self.counter += 1 - # noinspection PyCallByClass,PyTypeChecker - # QtCore.QTimer.singleShot(self.update_interval, self.update_table) - - def update_block_devices(self): - pass - # res, msg = self.blk_manager.check_for_updates() - # if msg is not None and len(msg) > 0: - # trayIcon.showMessage('\n'.join(msg), msecs=5000) - # return res # noinspection PyPep8Naming @QtCore.pyqtSlot(bool, str) def recAllowedChanged(self, state, vmname): self.vm_rec[str(vmname)] = bool(state) - def register_dbus_watches(self): - global session_bus - - if not session_bus: - session_bus = QDBusConnection.sessionBus() - - if not session_bus.connect("", # service - "", # path - "org.QubesOS.Audio", # interface - "RecAllowedChanged", # name - self.recAllowedChanged): # slot - print(session_bus.lastError().message()) - # noinspection PyPep8Naming def sortIndicatorChanged(self, column, order): self.sort_by_column = [name for name in self.columns_indices.keys() if @@ -885,18 +578,19 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): self.manager_settings.sync() def table_selection_changed(self): + # TODO: and this should actually work, fixit vm = self.get_selected_vm() if vm is not None: # Update available actions: self.action_settings.setEnabled(vm.qid != 0) - self.action_removevm.setEnabled( - not vm.installed_by_rpm and not vm.last_running) - self.action_clonevm.setEnabled( - not vm.last_running and not vm.is_netvm()) - # self.action_resumevm.setEnabled(not vm.last_running or - # vm.last_power_state == "Paused") + self.action_removevm.setEnabled(vm.klass != 'AdminVM' + and not vm.is_running()) + # TODO: think about this + self.action_clonevm.setEnabled(vm.klass != 'AdminVM') + self.action_resumevm.setEnabled(vm.get_power_state() == "Paused") + # TODO: check try: pass # self.action_startvm_tools_install.setVisible( @@ -904,37 +598,33 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): except NameError: # ignore non existing QubesHVm pass - self.action_startvm_tools_install.setEnabled(not vm.last_running) - # self.action_pausevm.setEnabled( - # vm.last_running and - # vm.last_power_state != "Paused" and - # vm.qid != 0) - # self.action_shutdownvm.setEnabled( - # vm.last_running and - # vm.last_power_state != "Paused" and - # vm.qid != 0) - # self.action_restartvm.setEnabled( - # vm.last_running and - # vm.last_power_state != "Paused" and - # vm.qid != 0 and - # not vm.is_disposablevm()) - # self.action_killvm.setEnabled((vm.last_running or - # vm.last_power_state == "Paused") and - # vm.qid != 0) - # self.action_appmenus.setEnabled(vm.qid != 0 and - # not vm.internal and - # not vm.is_disposablevm()) - # self.action_editfwrules.setEnabled(vm.is_networked() and not ( - # vm.is_netvm() and not vm.is_proxyvm())) + self.action_startvm_tools_install.setEnabled( + getattr(vm, 'updateable', False)) + # TODO: add qvm boot from device to this and add there windows tools + self.action_pausevm.setEnabled(vm.get_power_state() != "Paused" and + vm.qid != 0) + self.action_shutdownvm.setEnabled(vm.get_power_state() != "Paused" + and vm.qid != 0) + self.action_restartvm.setEnabled(vm.get_power_state() != "Paused" + and vm.qid != 0 + and vm.klass != 'DisposableVM') + self.action_killvm.setEnabled(vm.get_power_state() == "Paused" and + vm.qid != 0) + # TODO: check conditions + self.action_appmenus.setEnabled( + vm.klass != 'AdminVM' and vm.klass != 'DisposableMV' + and not vm.features.get('internal', False)) + self.action_editfwrules.setEnabled(True) # TODO: remove this and make sure the option is enabled in designer + # TODO: this should work # self.action_updatevm.setEnabled(vm.is_updateable() or vm.qid == 0) + # TODO: this should work # self.action_toggle_audio_input.setEnabled( # vm.qubes_manager_state[QMVmState.AudioRecAvailable]) - # self.action_run_command_in_vm.setEnabled( - # not vm.last_power_state == "Paused" and vm.qid != 0) - # self.action_set_keyboard_layout.setEnabled( - # vm.qid != 0 and - # vm.last_running and - # vm.last_power_state != "Paused") + self.action_run_command_in_vm.setEnabled( + not vm.get_power_state() == "Paused" and vm.qid != 0) + self.action_set_keyboard_layout.setEnabled( + vm.qid != 0 and + vm.get_power_state() != "Paused") else: self.action_settings.setEnabled(False) self.action_removevm.setEnabled(False) @@ -953,13 +643,6 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): self.action_run_command_in_vm.setEnabled(False) self.action_set_keyboard_layout.setEnabled(False) - def closeEvent(self, event): - # There is something borked in Qt, - # as the logic here is inverted on X11 - if event.spontaneous(): - self.hide() - event.ignore() - def set_error(self, qid, message): for vm in self.vms_list: if vm.qid == qid: @@ -981,10 +664,8 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): self.vm_errors.pop(qid, None) @QtCore.pyqtSlot(name='on_action_createvm_triggered') - def action_createvm_triggered(self): - pass - # dialog = NewVmDlg(app, self.qvm_collection, trayIcon) - # dialog.exec_() + def action_createvm_triggered(self): # TODO: this should work + subprocess.check_call('qubes-vm-create') def get_selected_vm(self): # vm selection relies on the VmInfo widget's value used @@ -1004,7 +685,7 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): return None @QtCore.pyqtSlot(name='on_action_removevm_triggered') - def action_removevm_triggered(self): + def action_removevm_triggered(self): # TODO: this should work vm = self.get_selected_vm() assert not vm.is_running() @@ -1013,25 +694,27 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): vm = self.qvm_collection[vm.qid] if vm.is_template(): - dependent_vms = self.qvm_collection.domains[vm.qid] - if len(dependent_vms) > 0: + dependent_vms = 0 + for single_vm in self.qvm_collection.domains: + if getattr(single_vm, 'template', None) == vm: + dependent_vms += 1 + if dependent_vms > 0: QtGui.QMessageBox.warning( None, self.tr("Warning!"), - self.tr("This Template VM cannot be removed, because there is at " - "least one AppVM that is based on it.
" - "If you want to remove this Template VM and all " - "the AppVMs based on it," - "you should first remove each individual AppVM that uses " - "this template.")) - + self.tr("This Template VM cannot be removed, " + "because there is at least one AppVM that is based " + "on it.
If you want to remove this " + "Template VM and all the AppVMs based on it, you " + "should first remove each individual AppVM that " + "uses this template.")) return (requested_name, ok) = QtGui.QInputDialog.getText( None, self.tr("VM Removal Confirmation"), - self.tr("Are you sure you want to remove the VM '{0}'?
" - "All data on this VM's private storage will be lost!

" - "Type the name of the VM ({1}) below to confirm:") - .format(vm.name, vm.name)) + self.tr("Are you sure you want to remove the VM '{0}'?
" + "All data on this VM's private storage will be lost!" + "

Type the name of the VM ({1}) below to " + "confirm:").format(vm.name, vm.name)) if not ok: # user clicked cancel @@ -1039,15 +722,19 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): elif requested_name != vm.name: # name did not match - QtGui.QMessageBox.warning(None, self.tr("VM removal confirmation failed"), - self.tr("Entered name did not match! Not removing {0}.").format(vm.name)) + QtGui.QMessageBox.warning( + None, + self.tr("VM removal confirmation failed"), + self.tr( + "Entered name did not match! Not removing " + "{0}.").format(vm.name)) return else: # remove the VM t_monitor = thread_monitor.ThreadMonitor() thread = threading.Thread(target=self.do_remove_vm, - args=(vm, t_monitor)) + args=(vm, self.qvm_collection, t_monitor)) thread.daemon = True thread.start() @@ -1057,45 +744,30 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): progress.setModal(True) progress.show() - while not thread_monitor.is_finished(): + while not t_monitor.is_finished(): app.processEvents() time.sleep(0.1) progress.hide() - if thread_monitor.success: - trayIcon.showMessage( - self.tr("VM '{0}' has been removed.").format(vm.name), msecs=3000) + if t_monitor.success: + pass else: QtGui.QMessageBox.warning(None, self.tr("Error removing VM!"), - self.tr("ERROR: {0}").format( - thread_monitor.error_msg)) + self.tr("ERROR: {0}").format( + t_monitor.error_msg)) @staticmethod - def do_remove_vm(vm, thread_monitor): - qc = Qubes() + def do_remove_vm(vm, qvm_collection, t_monitor): try: - vm = qc.domains[vm.qid] - - # TODO: the following two conditions should really be checked - # by qvm_collection.pop() overload... - if vm.is_template() and \ - qc.default_template_qid == vm.qid: - qc.default_template_qid = None - if vm.is_netvm() and \ - qc.default_netvm_qid == vm.qid: - qc.default_netvm_qid = None - - # qc.pop(vm.qid) - # qc.save() - vm.remove_from_disk() + del qvm_collection.domains[vm.name] except Exception as ex: - thread_monitor.set_error_msg(str(ex)) + t_monitor.set_error_msg(str(ex)) - thread_monitor.set_finished() + t_monitor.set_finished() @QtCore.pyqtSlot(name='on_action_clonevm_triggered') - def action_clonevm_triggered(self): + def action_clonevm_triggered(self): # TODO: this should work vm = self.get_selected_vm() name_number = 1 @@ -1112,14 +784,14 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): t_monitor = thread_monitor.ThreadMonitor() thread = threading.Thread(target=self.do_clone_vm, - args=(vm, str(clone_name), t_monitor)) + args=(vm, self.qvm_collection, + clone_name, t_monitor)) thread.daemon = True thread.start() progress = QtGui.QProgressDialog( - self.tr("Cloning VM {0} to {1}...").format(vm.name, - clone_name), "", 0, - 0) + self.tr("Cloning VM {0} to {1}...").format( + vm.name, clone_name), "", 0, 0) progress.setCancelButton(None) progress.setModal(True) progress.show() @@ -1131,48 +803,38 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): progress.hide() if not t_monitor.success: - QtGui.QMessageBox.warning(None, self.tr("Error while cloning VM"), - self.tr("Exception while cloning:
{0}").format( - t_monitor.error_msg)) + QtGui.QMessageBox.warning( + None, + self.tr("Error while cloning VM"), + self.tr("Exception while cloning:
{0}").format( + t_monitor.error_msg)) @staticmethod - def do_clone_vm(vm, dst_name, thread_monitor): + def do_clone_vm(src_vm, qvm_collection, dst_name, t_monitor): dst_vm = None - qc = Qubes() try: - src_vm = qc[vm.qid] - - dst_vm = qc.add_new_vm(src_vm.__class__.__name__, - name=dst_name, - template=src_vm.template, - installed_by_rpm=False) - - dst_vm.clone_attrs(src_vm) - dst_vm.clone_disk_files(src_vm=src_vm, verbose=False) - qc.save() + dst_vm = qvm_collection.clone_vm(src_vm, dst_name) except Exception as ex: if dst_vm: - qc.pop(dst_vm.qid) - dst_vm.remove_from_disk() - thread_monitor.set_error_msg(str(ex)) - thread_monitor.set_finished() + pass # TODO: should I remove any remnants? + t_monitor.set_error_msg(str(ex)) + t_monitor.set_finished() @QtCore.pyqtSlot(name='on_action_resumevm_triggered') def action_resumevm_triggered(self): vm = self.get_selected_vm() - # if vm.get_power_state() in ["Paused", "Suspended"]: - # try: - # vm.resume() - # except Exception as ex: - # QtGui.QMessageBox.warning(None, self.tr("Error unpausing VM!"), - # self.tr("ERROR: {0}").format(ex)) - # return - + if vm.get_power_state() in ["Paused", "Suspended"]: + try: + vm.unpause() + except Exception as ex: + QtGui.QMessageBox.warning(None, self.tr("Error unpausing VM!"), + self.tr("ERROR: {0}").format(ex)) + return self.start_vm(vm) - def start_vm(self, vm): + def start_vm(self, vm): # TODO: this should work assert not vm.is_running() t_monitor = thread_monitor.ThreadMonitor() thread = threading.Thread(target=self.do_start_vm, @@ -1180,22 +842,14 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): thread.daemon = True thread.start() - trayIcon.showMessage(self.tr("Starting '{0}'...").format(vm.name), msecs=3000) - while not t_monitor.is_finished(): app.processEvents() time.sleep(0.1) - if t_monitor.success: - trayIcon.showMessage(self.tr("VM '{0}' has been started.").format(vm.name), - msecs=3000) - else: - trayIcon.showMessage( - self.tr("Error starting VM '{0}': {1}").format( - vm.name, t_monitor.error_msg), - msecs=3000) - self.set_error(vm.qid, - self.tr("Error starting VM: %s") % t_monitor.error_msg) + if not t_monitor.success: + self.set_error( + vm.qid, + self.tr("Error starting VM: %s") % t_monitor.error_msg) @staticmethod def do_start_vm(vm, t_monitor): @@ -1209,6 +863,7 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): t_monitor.set_finished() @QtCore.pyqtSlot(name='on_action_startvm_tools_install_triggered') + # TODO: replace with boot from device def action_startvm_tools_install_triggered(self): vm = self.get_selected_vm() assert not vm.is_running() @@ -1218,49 +873,40 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): if not windows_tools_installed: msg = QtGui.QMessageBox() msg.warning(self, self.tr("Error starting VM!"), - self.tr("You need to install 'qubes-windows-tools' " - "package to use this option")) + self.tr("You need to install 'qubes-windows-tools' " + "package to use this option")) return - t_monitor = thread_monitor.ThreadMonitor() thread = threading.Thread(target=self.do_start_vm_tools_install, args=(vm, t_monitor)) thread.daemon = True thread.start() - trayIcon.showMessage(self.tr("Starting '{0}'...").format(vm.name), msecs=3000) - while not t_monitor.is_finished(): app.processEvents() time.sleep(0.1) - if t_monitor.success: - trayIcon.showMessage(self.tr("VM '{0}' has been started. Start Qubes " - "Tools installation from attached CD") - .format(vm.name), msecs=3000) - else: - trayIcon.showMessage( - self.tr("Error starting VM '{0}': {1}") - .format(vm.name, t_monitor.error_msg), - msecs=3000) - self.set_error(vm.qid, - self.tr("Error starting VM: %s") % t_monitor.error_msg) + if not t_monitor.success: + self.set_error( + vm.qid, + self.tr("Error starting VM: %s") % t_monitor.error_msg) # noinspection PyMethodMayBeStatic - def do_start_vm_tools_install(self, vm, thread_monitor): + def do_start_vm_tools_install(self, vm, t_monitor): + # TODO: should this work? prev_drive = vm.drive try: vm.drive = 'cdrom:dom0:/usr/lib/qubes/qubes-windows-tools.iso' vm.start() except Exception as ex: - thread_monitor.set_error_msg(str(ex)) - thread_monitor.set_finished() + t_monitor.set_error_msg(str(ex)) + t_monitor.set_finished() return finally: vm.drive = prev_drive - thread_monitor.set_finished() + t_monitor.set_finished() @QtCore.pyqtSlot(name='on_action_pausevm_triggered') def action_pausevm_triggered(self): @@ -1269,8 +915,10 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): try: vm.pause() except Exception as ex: - QtGui.QMessageBox.warning(None, self.tr("Error pausing VM!"), - self.tr("ERROR: {0}").format(ex)) + QtGui.QMessageBox.warning( + None, + self.tr("Error pausing VM!"), + self.tr("ERROR: {0}").format(ex)) return @QtCore.pyqtSlot(name='on_action_shutdownvm_triggered') @@ -1278,34 +926,32 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): vm = self.get_selected_vm() assert vm.is_running() - # self.blk_manager.check_if_serves_as_backend(vm) - reply = QtGui.QMessageBox.question( None, self.tr("VM Shutdown Confirmation"), - self.tr("Are you sure you want to power down the VM '{0}'?
" - "This will shutdown all the running applications " - "within this VM.").format(vm.name), - QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel) + self.tr("Are you sure you want to power down the VM" + " '{0}'?
This will shutdown all the " + "running applications within this VM.").format( + vm.name), QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel) - app.processEvents() + app.processEvents() # TODO: is this needed?? if reply == QtGui.QMessageBox.Yes: self.shutdown_vm(vm) def shutdown_vm(self, vm, shutdown_time=vm_shutdown_timeout, - check_time=vm_restart_check_timeout, and_restart=False): + check_time=vm_restart_check_timeout, and_restart=False): try: vm.shutdown() except Exception as ex: - QtGui.QMessageBox.warning(None, self.tr("Error shutting down VM!"), - self.tr("ERROR: {0}").format(ex)) + QtGui.QMessageBox.warning( + None, + self.tr("Error shutting down VM!"), + self.tr("ERROR: {0}").format(ex)) return - trayIcon.showMessage(self.tr("VM '{0}' is shutting down...").format(vm.name), - msecs=3000) - self.shutdown_monitor[vm.qid] = VmShutdownMonitor(vm, shutdown_time, - check_time, and_restart, self) + check_time, + and_restart, self) # noinspection PyCallByClass,PyTypeChecker QtCore.QTimer.singleShot(check_time, self.shutdown_monitor[ vm.qid].check_if_vm_has_shutdown) @@ -1315,13 +961,11 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): vm = self.get_selected_vm() assert vm.is_running() - # self.blk_manager.check_if_serves_as_backend(vm) - reply = QtGui.QMessageBox.question( None, self.tr("VM Restart Confirmation"), self.tr("Are you sure you want to restart the VM '{0}'?
" - "This will shutdown all the running applications " - "within this VM.").format(vm.name), + "This will shutdown all the running applications " + "within this VM.").format(vm.name), QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel) app.processEvents() @@ -1337,8 +981,9 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): reply = QtGui.QMessageBox.question( None, self.tr("VM Kill Confirmation"), self.tr("Are you sure you want to kill the VM '{0}'?
" - "This will end (not shutdown!) all the running " - "applications within this VM.").format(vm.name), + "This will end (not shutdown!) all the " + "running applications within this VM.").format( + vm.name), QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel, QtGui.QMessageBox.Cancel) @@ -1350,65 +995,26 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): except Exception as ex: QtGui.QMessageBox.critical( None, self.tr("Error while killing VM!"), - self.tr("An exception ocurred while killing {0}.
" - "ERROR: {1}").format(vm.name, ex)) + self.tr( + "An exception ocurred while killing {0}.
" + "ERROR: {1}").format(vm.name, ex)) return - trayIcon.showMessage(self.tr("VM '{0}' killed!") - .format(vm.name), msecs=3000) - @QtCore.pyqtSlot(name='on_action_settings_triggered') def action_settings_triggered(self): - pass - #TODO this should work, actually, but please not now - # vm = self.get_selected_vm() - # settings_window = VMSettingsWindow(vm, app, self.qvm_collection, - # "basic") - # settings_window.exec_() + vm = self.get_selected_vm() + settings_window = settings.VMSettingsWindow(vm, app, "basic") + settings_window.exec_() @QtCore.pyqtSlot(name='on_action_appmenus_triggered') def action_appmenus_triggered(self): pass - #TODO this should work, actually, but please not now + # TODO this should work, actually, but please not now # vm = self.get_selected_vm() # settings_window = VMSettingsWindow(vm, app, self.qvm_collection, # "applications") # settings_window.exec_() - def update_audio_rec_info(self, vm): - vm.qubes_manager_state[QMVmState.AudioRecAvailable] = ( - session_bus.interface().isServiceRegistered( - 'org.QubesOS.Audio.%s' % vm.name).value()) - if vm.qubes_manager_state[QMVmState.AudioRecAvailable]: - vm.qubes_manager_state[ - QMVmState.AudioRecAllowed] = self.get_audio_rec_allowed(vm.name) - else: - vm.qubes_manager_state[QMVmState.AudioRecAllowed] = False - - # noinspection PyMethodMayBeStatic - def get_audio_rec_allowed(self, vmname): - properties = QDBusInterface('org.QubesOS.Audio.%s' % vmname, - '/org/qubesos/audio', - 'org.freedesktop.DBus.Properties', - session_bus) - - current_audio = properties.call('Get', 'org.QubesOS.Audio', - 'RecAllowed') - if current_audio.type() == current_audio.ReplyMessage: - value = current_audio.arguments()[0].toPyObject() - return bool(value) - return False - - @QtCore.pyqtSlot(name='on_action_toggle_audio_input_triggered') - def action_toggle_audio_input_triggered(self): - vm = self.get_selected_vm() - properties = QDBusInterface('org.QubesOS.Audio.%s' % vm.name, - '/org/qubesos/audio', - 'org.freedesktop.DBus.Properties', - session_bus) - properties.call('Set', 'org.QubesOS.Audio', 'RecAllowed', - QDBusVariant(not self.get_audio_rec_allowed(vm.name))) - # icon will be updated based on dbus signal @QtCore.pyqtSlot(name='on_action_updatevm_triggered') def action_updatevm_triggered(self): @@ -1422,8 +1028,6 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel) if reply != QtGui.QMessageBox.Yes: return - trayIcon.showMessage(self.tr("Starting '{0}'...").format(vm.name), - msecs=3000) app.processEvents() @@ -1453,16 +1057,13 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): t_monitor.error_msg)) @staticmethod - def do_update_vm(vm, thread_monitor): + def do_update_vm(vm, thread_monitor): #TODO: fixme try: if vm.qid == 0: subprocess.check_call( ["/usr/bin/qubes-dom0-update", "--clean", "--gui"]) else: if not vm.is_running(): - trayIcon.showMessage( - "Starting the '{0}' VM...".format(vm.name), - msecs=3000) vm.start() vm.run_service("qubes.InstallUpdatesGUI", gui=True, user="root", wait=False) @@ -1498,80 +1099,49 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): t_monitor.error_msg)) @staticmethod - def do_run_command_in_vm(vm, command_to_run, thread_monitor): + def do_run_command_in_vm(vm, command_to_run, t_monitor): try: - vm.run(command_to_run, verbose=False, autostart=True, - notify_function=lambda lvl, msg: trayIcon.showMessage( - msg, msecs=3000)) + vm.run(command_to_run, verbose=False, autostart=True) except Exception as ex: - thread_monitor.set_error_msg(str(ex)) - thread_monitor.set_finished() + t_monitor.set_error_msg(str(ex)) + t_monitor.set_finished() @QtCore.pyqtSlot(name='on_action_set_keyboard_layout_triggered') def action_set_keyboard_layout_triggered(self): vm = self.get_selected_vm() vm.run('qubes-change-keyboard-layout', verbose=False) - @QtCore.pyqtSlot(name='on_action_showallvms_triggered') - def action_showallvms_triggered(self): - self.show_inactive_vms = self.action_showallvms.isChecked() - - self.showhide_vms() - # self.set_table_geom_size() - if self.settings_loaded: - self.manager_settings.setValue('view/show_inactive_vms', - self.show_inactive_vms) - self.manager_settings.sync() - - @QtCore.pyqtSlot(name='on_action_showinternalvms_triggered') - def action_showinternalvms_triggered(self): - self.show_internal_vms = self.action_showinternalvms.isChecked() - - self.showhide_vms() - # self.set_table_geom_size() - if self.settings_loaded: - self.manager_settings.setValue('view/show_internal_vms', - self.show_internal_vms) - self.manager_settings.sync() + #TODO: remove showallvms / show inactive vms / show internal vms @QtCore.pyqtSlot(name='on_action_editfwrules_triggered') def action_editfwrules_triggered(self): - pass - #TODO: revive - # vm = self.get_selected_vm() - # settings_window = VMSettingsWindow(vm, app, self.qvm_collection, - # "firewall") - # settings_window.exec_() + vm = self.get_selected_vm() + settings_window = settings.VMSettingsWindow(vm, app, "firewall") + settings_window.exec_() @QtCore.pyqtSlot(name='on_action_global_settings_triggered') def action_global_settings_triggered(self): - pass - #TODO: revive - # global_settings_window = GlobalSettingsWindow(app, self.qvm_collection) - # global_settings_window.exec_() + global_settings_window = global_settings.GlobalSettingsWindow( + app, + self.qvm_collection) + global_settings_window.exec_() @QtCore.pyqtSlot(name='on_action_show_network_triggered') def action_show_network_triggered(self): pass - #TODO: revive + #TODO: revive TODO: what is this thing?? # network_notes_dialog = NetworkNotesDialog() # network_notes_dialog.exec_() @QtCore.pyqtSlot(name='on_action_restore_triggered') def action_restore_triggered(self): - pass - #TODO: revive - # restore_window = RestoreVMsWindow(app, self.qvm_collection, - # self.blk_manager) - # restore_window.exec_() + restore_window = restore.RestoreVMsWindow(app, self.qvm_collection) + restore_window.exec_() @QtCore.pyqtSlot(name='on_action_backup_triggered') def action_backup_triggered(self): - pass - #TODO: revive - # backup_window = BackupVMsWindow(app, self.qvm_collection, - # self.blk_manager, self.shutdown_vm) - # backup_window.exec_() + backup_window = backup.BackupVMsWindow(app, self.qvm_collection) + backup_window.exec_() def showhide_menubar(self, checked): self.menubar.setVisible(checked) @@ -1597,17 +1167,17 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): def showhide_column(self, col_num, show): self.table.setColumnHidden(col_num, not show) - # self.set_table_geom_size() + val = 1 if show else -1 self.visible_columns_count += val - if self.visible_columns_count == 1: + if self.visible_columns_count == 1: # TODO: is this working at all?? # disable hiding the last one for c in self.columns_actions: if self.columns_actions[c].isChecked(): self.columns_actions[c].setEnabled(False) break - elif self.visible_columns_count == 2 and val == 1: + elif self.visible_columns_count == 2 and val == 1: # TODO: likewise?? # enable hiding previously disabled column for c in self.columns_actions: if not self.columns_actions[c].isEnabled(): @@ -1650,18 +1220,6 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): def on_action_netvm_toggled(self, checked): self.showhide_column(self.columns_indices['NetVM'], checked) - def on_action_cpu_toggled(self, checked): - self.showhide_column(self.columns_indices['CPU'], checked) - - def on_action_cpu_graph_toggled(self, checked): - self.showhide_column(self.columns_indices['CPU Graph'], checked) - - def on_action_mem_toggled(self, checked): - self.showhide_column(self.columns_indices['MEM'], checked) - - def on_action_mem_graph_toggled(self, checked): - self.showhide_column(self.columns_indices['MEM Graph'], checked) - def on_action_size_on_disk_toggled(self, checked): self.showhide_column(self.columns_indices['Size'], checked) @@ -1707,48 +1265,6 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): self.logs_menu.setEnabled(not menu_empty) - # blk menu - if not running: - self.blk_menu.setEnabled(False) - else: - self.blk_menu.clear() - self.blk_menu.setEnabled(True) - - # self.blk_manager.blk_lock.acquire() - # if len(self.blk_manager.attached_devs) > 0: - # for d in self.blk_manager.attached_devs: - # if (self.blk_manager.attached_devs[d] - # ['attached_to']['vm'].qid == vm.qid): - # text = self.tr("Detach {dev} {size} {desc}").format( - # dev=d, - # size= - # self.blk_manager.attached_devs[d]['size'], - # desc=self.blk_manager.attached_devs[d]['desc']) - # action = self.blk_menu.addAction(QtGui.QIcon(":/remove.png"), - # text) - # action.setData(QtCore.QVariant(d)) - # - # if len(self.blk_manager.free_devs) > 0: - # for d in self.blk_manager.free_devs: - # if d.startswith(vm.name): - # continue - # # skip partitions heuristic - # if d[-1].isdigit() and \ - # d[0:-1] in self.blk_manager.current_blk: - # continue - # text = self.tr("Attach {dev} {size} {desc}").format( - # dev=d, - # size= - # self.blk_manager.free_devs[d]['size'], - # desc=self.blk_manager.free_devs[d]['desc']) - # action = self.blk_menu.addAction(QtGui.QIcon(":/add.png"), text) - # action.setData(QtCore.QVariant(d)) - # - # self.blk_manager.blk_lock.release() - - if self.blk_menu.isEmpty(): - self.blk_menu.setEnabled(False) - self.context_menu.exec_(self.table.mapToGlobal(point)) @QtCore.pyqtSlot('QAction *') @@ -1759,312 +1275,15 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): # log_dialog = LogDialog(app, log) # log_dialog.exec_() - @QtCore.pyqtSlot('QAction *') - def attach_dettach_device_triggered(self, action): - pass - # dev = str(action.data()) - # vm = self.get_selected_vm() - # - # self.blk_manager.blk_lock.acquire() - # try: - # if dev in self.blk_manager.attached_devs: - # self.blk_manager.detach_device(vm, dev) - # else: - # self.blk_manager.attach_device(vm, dev) - # self.blk_manager.blk_lock.release() - # except exc.QubesException as e: - # self.blk_manager.blk_lock.release() - # QtGui.QMessageBox.critical(None, - # self.tr("Block attach/detach error!"), str(e)) - - -class QubesTrayIcon(QtGui.QSystemTrayIcon): - def __init__(self, icon): - QtGui.QSystemTrayIcon.__init__(self, icon) - self.menu = QtGui.QMenu() - - action_showmanager = self.create_action( - self.tr("Open VM Manager"), - slot=show_manager, icon="qubes") - # action_copy = self.create_action( - # self.tr("Copy Dom0 clipboard"), icon="copy", - # slot=do_dom0_copy) - action_backup = self.create_action(self.tr("Make backup")) - action_preferences = self.create_action(self.tr("Preferences")) - action_set_netvm = self.create_action(self.tr("Set default NetVM"), - icon="networking") - action_sys_info = self.create_action(self.tr("System Info"), icon="dom0") - action_exit = self.create_action(self.tr("Exit"), slot=exit_app) - - action_backup.setDisabled(True) - action_preferences.setDisabled(True) - action_set_netvm.setDisabled(True) - action_sys_info.setDisabled(True) - - # self.blk_manager = blk_manager - - self.blk_menu = QtGui.QMenu(self.menu) - self.blk_menu.setTitle(self.tr("Block devices")) - action_blk_menu = self.create_action(self.tr("Block devices")) - action_blk_menu.setMenu(self.blk_menu) - - self.add_actions(self.menu, (action_showmanager, - # action_copy, - action_blk_menu, - action_backup, - action_sys_info, - None, - action_preferences, - action_set_netvm, - None, - action_exit)) - - self.setContextMenu(self.menu) - - # self.connect(self, - # QtCore.SIGNAL("activated (QtGui.QSystemTrayIcon::ActivationReason)"), - # self.icon_clicked) - - self.tray_notifier_type = None - self.tray_notifier = QDBusInterface("org.freedesktop.Notifications", - "/org/freedesktop/Notifications", - "org.freedesktop.Notifications", - session_bus) - srv_info = self.tray_notifier.call("GetServerInformation") - if srv_info.type() == QDBusMessage.ReplyMessage and \ - len(srv_info.arguments()) > 1: - self.tray_notifier_type = srv_info.arguments()[1] - - if os.path.exists(table_widgets.qubes_dom0_updates_stat_file): - self.showMessage(self.tr("Qubes dom0 updates available."), msecs=0) - - def update_blk_menu(self): - global manager_window - - def create_vm_submenu(dev): - blk_vm_menu = QtGui.QMenu(self.blk_menu) - blk_vm_menu.triggered.connect( - lambda a, trig_dev=dev: self.attach_device_triggered(a, - trig_dev)) - for this_vm in sorted(manager_window.qvm_collection.domains, - key=lambda x: x.name): - if not this_vm.is_running(): - continue - if this_vm.qid == 0: - # skip dom0 to prevent (fatal) mistakes - continue - this_action = blk_vm_menu.addAction(QtGui.QIcon(":/add.png"), - this_vm.name) - this_action.setData(QtCore.QVariant(this_vm)) - return blk_vm_menu - - self.blk_menu.clear() - self.blk_menu.setEnabled(True) - - # self.blk_manager.blk_lock.acquire() - # if len(self.blk_manager.attached_devs) > 0: - # for d in self.blk_manager.attached_devs: - # vm = self.blk_manager.attached_devs[d]['attached_to']['vm'] - # text = self.tr("Detach {dev} {desc} ({size}) from {vm}").format( - # dev=d, - # desc=self.blk_manager.attached_devs[d]['desc'], - # size=self.blk_manager.attached_devs[d]['size'], - # vm=vm.name) - # action = self.blk_menu.addAction(QtGui.QIcon(":/remove.png"), text) - # action.setData(QtCore.QVariant(d)) - # action.triggered.connect( - # lambda b, a=action: self.dettach_device_triggered(a)) - # - # if len(self.blk_manager.free_devs) > 0: - # for d in self.blk_manager.free_devs: - # # skip partitions heuristic - # if d[-1].isdigit() and d[0:-1] in self.blk_manager.current_blk: - # continue - # text = self.tr("Attach {dev} {size} {desc}").format( - # dev=d, - # size=self.blk_manager.free_devs[d]['size'], - # desc=self.blk_manager.free_devs[d]['desc'] - # ) - # action = self.blk_menu.addAction(QtGui.QIcon(":/add.png"), text) - # action.setMenu(create_vm_submenu(d)) - # - # self.blk_manager.blk_lock.release() - - if self.blk_menu.isEmpty(): - self.blk_menu.setEnabled(False) - - @QtCore.pyqtSlot('QAction *') - def attach_device_triggered(self, action, dev): - pass - # vm = action.data().toPyObject() - # - # self.blk_manager.blk_lock.acquire() - # try: - # self.blk_manager.attach_device(vm, dev) - # self.blk_manager.blk_lock.release() - # except exc.QubesException as e: - # self.blk_manager.blk_lock.release() - # QtGui.QMessageBox.critical(None, - # self.tr("Block attach/detach error!"), str(e)) - - @QtCore.pyqtSlot('QAction *') - def dettach_device_triggered(self, action): - pass - # dev = str(action.data()) - # vm = self.blk_manager.attached_devs[dev]['attached_to']['vm'] - # - # self.blk_manager.blk_lock.acquire() - # try: - # self.blk_manager.detach_device(vm, dev) - # self.blk_manager.blk_lock.release() - # except exc.QubesException as e: - # self.blk_manager.blk_lock.release() - # QtGui.QMessageBox.critical(None, - # self.tr("Block attach/detach error!"), str(e)) - - def icon_clicked(self, reason): - if reason == QtGui.QSystemTrayIcon.Context: - self.update_blk_menu() - # Handle the right click normally, i.e. display the context menu - return - else: - bring_manager_to_front() - - # noinspection PyMethodMayBeStatic - def add_actions(self, target, actions): - for action in actions: - if action is None: - target.addSeparator() - else: - target.addAction(action) - - def showMessage(self, message, msecs, **kwargs): - # QtDBus bindings doesn't use introspection to get proper method - # parameters types, so must cast explicitly - # v_replace_id = QtCore.QVariant(0) - # v_replace_id.convert(QtCore.QVariant.UInt) - # v_actions = QtCore.QVariant([]) - # v_actions.convert(QtCore.QVariant.StringList) - # if self.tray_notifier_type == "KDE": - # message = message.replace('\n', '
\n') - # self.tray_notifier.call("Notify", "Qubes", v_replace_id, - # "qubes-manager", "Qubes VM Manager", - # message, v_actions, QtCore.QVariant.fromMap({}), msecs) - pass - - def create_action(self, text, slot=None, shortcut=None, icon=None, - tip=None, checkable=False, signal="triggered()"): - action = QtGui.QAction(text, self) - if icon is not None: - action.setIcon(QtGui.QIcon(":/%s.png" % icon)) - if shortcut is not None: - action.setShortcut(shortcut) - if tip is not None: - action.setToolTip(tip) - action.setStatusTip(tip) - if slot is not None: - self.connect(action, QtCore.SIGNAL(signal), slot) - if checkable: - action.setCheckable(True) - return action - - -class QubesDbusNotifyServerAdaptor(QDBusAbstractAdaptor): - """ This provides the DBus adaptor to the outside world""" - - # Q_CLASSINFO("D-Bus Interface", dbus_interface) - - @QtCore.pyqtSlot(str, str) - def notify_error(self, vmname, message): - vm = self.parent().qvm_collection.domains[vmname] - if vm: - self.parent().set_error(vm.qid, message) - else: - # ignore VM-not-found error - pass - - @QtCore.pyqtSlot(str, str) - def clear_error_exact(self, vmname, message): - vm = self.parent().qvm_collection.domains[vmname] - if vm: - self.parent().clear_error_exact(vm.qid, message) - else: - # ignore VM-not-found error - pass - - @QtCore.pyqtSlot(str) - def clear_error(self, vmname): - vm = self.parent().qvm_collection.domains[vmname] - if vm: - self.parent().clear_error(vm.qid) - else: - # ignore VM-not-found error - pass - - @QtCore.pyqtSlot() - def show_manager(self): - bring_manager_to_front() - - -# def get_frame_size(): -# w = 0 -# h = 0 -# cmd = ['/usr/bin/xprop', '-name', 'Qubes VM Manager', '|', 'grep', -# '_NET_FRAME_EXTENTS'] -# xprop = subprocess.Popen(cmd, stdout=subprocess.PIPE) -# for l in xprop.stdout: -# line = l.split('=') -# if len(line) == 2: -# line = line[1].strip().split(',') -# if len(line) == 4: -# w = int(line[0].strip()) + int(line[1].strip()) -# h = int(line[2].strip()) + int(line[3].strip()) -# break -# # in case of some weird window managers we have to assume sth... -# if w <= 0: -# w = 10 -# if h <= 0: -# h = 30 -# -# manager_window.frame_width = w -# manager_window.frame_height = h -# return - def show_manager(): manager_window.show() - # manager_window.set_table_geom_size() manager_window.repaint() manager_window.update_table(out_of_schedule=True) app.processEvents() - # get_frame_size() - # print manager_window.frame_width, " x ", manager_window.frame_height - # manager_window.set_table_geom_size() - - -def bring_manager_to_front(): - if manager_window.isVisible(): - subprocess.check_call( - ['/usr/bin/wmctrl', '-R', str(manager_window.windowTitle())]) - - else: - show_manager() - - -def show_running_manager_via_dbus(): - global system_bus - if system_bus is None: - system_bus = QDBusConnection.systemBus() - - qubes_manager = QDBusInterface('org.qubesos.QubesManager', - '/org/qubesos/QubesManager', - 'org.qubesos.QubesManager', system_bus) - qubes_manager.call('show_manager') - def exit_app(): - # notifier.stop() app.exit() @@ -2093,42 +1312,17 @@ def sighup_handler(signum, frame): def main(): signal.signal(signal.SIGHUP, sighup_handler) - global system_bus - system_bus = QDBusConnection.systemBus() - # Avoid starting more than one instance of the app - # if not system_bus.registerService('org.qubesos.QubesManager'): - # show_running_manager_via_dbus() - # return - - # global qubes_host - # qubes_host = QubesHost() - global app app = QtGui.QApplication(sys.argv) app.setOrganizationName("The Qubes Project") app.setOrganizationDomain("http://qubes-os.org") app.setApplicationName("Qubes VM Manager") app.setWindowIcon(QtGui.QIcon.fromTheme("qubes-manager")) - # app.setAttribute(Qt.AA_DontShowIconsInMenus, False) - - # qt_translator = QTranslator() - # locale = QLocale.system().name() - # i18n_dir = os.path.join( - # os.path.dirname(os.path.realpath(__file__)), - # 'i18n') - # qt_translator.load("qubesmanager_{!s}.qm".format(locale), i18n_dir) - # app.installTranslator(qt_translator) sys.excepthook = handle_exception - global session_bus - session_bus = QDBusConnection.sessionBus() - qvm_collection = Qubes() - global trayIcon - trayIcon = QubesTrayIcon(QtGui.QIcon.fromTheme("qubes-manager")) - global manager_window manager_window = VmManagerWindow(qvm_collection) @@ -2150,16 +1344,13 @@ def main(): # wm.add_watch(os.path.dirname(table_widgets.qubes_dom0_updates_stat_file), # EventsCodes.OP_FLAGS.get('IN_CREATE')) - system_bus.registerObject(dbus_object_path, manager_window) - threading.currentThread().setName("QtMainThread") - trayIcon.show() show_manager() app.exec_() - trayIcon = None - if __name__ == "__main__": main() + +# TODO: change file name to something better \ No newline at end of file diff --git a/qubesmanager/table_widgets.py b/qubesmanager/table_widgets.py index fe2cd49..9acc05e 100644 --- a/qubesmanager/table_widgets.py +++ b/qubesmanager/table_widgets.py @@ -23,7 +23,6 @@ from PyQt4 import QtGui from PyQt4 import QtCore # TODO: are those needed? -qubes_dom0_updates_stat_file = '/var/lib/qubes/updates/dom0-updates-available' power_order = QtCore.Qt.DescendingOrder update_order = QtCore.Qt.AscendingOrder @@ -31,7 +30,6 @@ update_order = QtCore.Qt.AscendingOrder row_height = 30 -# TODO: do I need to find icons? class VmIconWidget(QtGui.QWidget): def __init__(self, icon_path, enabled=True, size_multiplier=0.7, tooltip=None, parent=None, icon_sz=(32, 32)): @@ -81,7 +79,7 @@ class VmTypeWidget(VmIconWidget): elif other.vm.qid == 0: return False elif self.value == other.value: - return self.vm.qid < other.vm.qid + return self.vm.name < other.vm.name else: return self.value < other.value @@ -137,7 +135,7 @@ class VmLabelWidget(VmIconWidget): elif other.vm.qid == 0: return False elif self.value == other.value: - return self.vm.qid < other.vm.qid + return self.vm.name < other.vm.name else: return self.value < other.value @@ -217,14 +215,14 @@ class VmInfoWidget (QtGui.QWidget): self_val = self.upd_info_item.value other_val = other.upd_info_item.value - # TODO: is this shit needed? + if self.tableWidget().\ horizontalHeader().sortIndicatorOrder() == update_order: # the result will be sorted by upd, sorting order: Ascending self_val += 1 if self.vm.is_running() else 0 other_val += 1 if other.vm.is_running() else 0 if self_val == other_val: - return self.vm.qid < other.vm.qid + return self.vm.name < other.vm.name else: return self_val > other_val elif self.tableWidget().\ @@ -236,7 +234,7 @@ class VmInfoWidget (QtGui.QWidget): other_val = -(other_val/10 + 10*(1 if other.vm.is_running() else 0)) if self_val == other_val: - return self.vm.qid < other.vm.qid + return self.vm.name < other.vm.name else: return self_val > other_val else: @@ -272,19 +270,10 @@ class VmInfoWidget (QtGui.QWidget): self.tableItem = self.VmInfoItem(self.upd_info.tableItem, vm) - def update_vm_state(self, vm, blk_visible, rec_visible=None): + def update_vm_state(self, vm): self.on_icon.update() self.upd_info.update_outdated(vm) - if blk_visible is not None: - self.blk_icon.setVisible(blk_visible) - if rec_visible is not None: - self.rec_icon.setVisible(rec_visible) - # TODO: are these needed? - # self.error_icon.setToolTip(vm.qubes_manager_state[main.QMVmState - # .ErrorMsg]) - # self.error_icon.setVisible(vm.qubes_manager_state[main.QMVmState - # .ErrorMsg] is not None) - + # TODO: add updating things like label? name? evrything? size? # TODO add main to git history as a saner name and with a decent comment # TODO and rename that shit @@ -294,7 +283,7 @@ class VmTemplateItem (QtGui.QTableWidgetItem): self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) self.vm = vm - if vm.template is not None: + if getattr(vm, 'template', None) is not None: self.setText(vm.template.name) else: font = QtGui.QFont() @@ -312,7 +301,7 @@ class VmTemplateItem (QtGui.QTableWidgetItem): elif other.vm.qid == 0: return False elif self.text() == other.text(): - return self.vm.qid < other.vm.qid + return self.vm.name < other.vm.name else: return super(VmTemplateItem, self).__lt__(other) @@ -325,7 +314,7 @@ class VmNetvmItem (QtGui.QTableWidgetItem): # TODO: differentiate without no net vm/ no networking? # TODO: mark provides network somehow? - if vm.netvm is None: + if getattr(vm, 'netvm', None) is None: self.setText("n/a") else: self.setText(vm.netvm.name) @@ -338,7 +327,7 @@ class VmNetvmItem (QtGui.QTableWidgetItem): elif other.vm.qid == 0: return False elif self.text() == other.text(): - return self.vm.qid < other.vm.qid + return self.vm.name < other.vm.name else: return super(VmNetvmItem, self).__lt__(other) @@ -350,7 +339,6 @@ class VmInternalItem(QtGui.QTableWidgetItem): self.vm = vm self.internal = vm.features.get('internal', False) - # TODO: should default be false self.setText("Yes" if self.internal else "") @@ -386,7 +374,7 @@ class VmUpdateInfoWidget(QtGui.QWidget): elif other.vm.qid == 0: return False elif self.value == other.value: - return self.vm.qid < other.vm.qid + return self.vm.name < other.vm.name else: return self.value < other.value @@ -419,7 +407,8 @@ class VmUpdateInfoWidget(QtGui.QWidget): except AttributeError: pass - if not outdated_state and vm.template and vm.template.is_running(): + if not outdated_state and getattr(vm, 'template', None)\ + and vm.template.is_running(): outdated_state = "to-be-outdated" if outdated_state != self.previous_outdated_state: self.update_status_widget(outdated_state) @@ -491,24 +480,19 @@ class VmSizeOnDiskItem (QtGui.QTableWidgetItem): elif other.vm.qid == 0: return False elif self.value == other.value: - return self.vm.qid < other.vm.qid + return self.vm.name < other.vm.name else: return self.value < other.value - +# TODO: replace these widgets with a generic widgets class VmIPItem(QtGui.QTableWidgetItem): def __init__(self, vm): super(VmIPItem, self).__init__() self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - # TODO: check if you don't need a try around here self.vm = vm - self.ip = self.vm.ip - if self.ip: - self.setText(self.ip) - else: - self.setText("n/a") - self.setText("n/a") + self.ip = getattr(self.vm, 'ip', None) + self.setText(self.ip if self.ip is not None else 'n/a') def __lt__(self, other): if self.vm.qid == 0: @@ -524,7 +508,7 @@ class VmIncludeInBackupsItem(QtGui.QTableWidgetItem): self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) self.vm = vm - if self.vm.include_in_backups: + if getattr(self.vm, 'include_in_backups', None): self.setText("Yes") else: self.setText("") @@ -536,7 +520,7 @@ class VmIncludeInBackupsItem(QtGui.QTableWidgetItem): elif other.vm.qid == 0: return False elif self.vm.include_in_backups == other.vm.include_in_backups: - return self.vm.qid < other.vm.qid + return self.vm.name < other.vm.name else: return self.vm.include_in_backups < other.vm.include_in_backups @@ -547,7 +531,7 @@ class VmLastBackupItem(QtGui.QTableWidgetItem): self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) self.vm = vm - if self.vm.backup_timestamp: + if getattr(self.vm, 'backup_timestamp', None): self.setText(self.vm.backup_timestamp) else: self.setText("") @@ -559,7 +543,7 @@ class VmLastBackupItem(QtGui.QTableWidgetItem): elif other.vm.qid == 0: return False elif self.vm.backup_timestamp == other.vm.backup_timestamp: - return self.vm.qid < other.vm.qid + return self.vm.name < other.vm.name elif not self.vm.backup_timestamp: return False elif not other.vm.backup_timestamp: diff --git a/rpm_spec/qmgr.spec b/rpm_spec/qmgr.spec index 6ee0f64..27ed4e5 100644 --- a/rpm_spec/qmgr.spec +++ b/rpm_spec/qmgr.spec @@ -64,6 +64,7 @@ rm -rf $RPM_BUILD_ROOT /usr/bin/qubes-vm-boot-from-device /usr/bin/qubes-backup /usr/bin/qubes-backup-restore +/usr/bin/qubes-template-volume-manager /usr/libexec/qubes-manager/mount_for_backup.sh /usr/libexec/qubes-manager/qvm_about.sh @@ -87,6 +88,7 @@ rm -rf $RPM_BUILD_ROOT %{python3_sitelib}/qubesmanager/informationnotes.py %{python3_sitelib}/qubesmanager/create_new_vm.py %{python3_sitelib}/qubesmanager/thread_monitor.py +%{python3_sitelib}/qubesmanager/main.py %{python3_sitelib}/qubesmanager/utils.py %{python3_sitelib}/qubesmanager/bootfromdevice.py @@ -104,6 +106,7 @@ rm -rf $RPM_BUILD_ROOT %{python3_sitelib}/qubesmanager/ui_about.py %{python3_sitelib}/qubesmanager/ui_releasenotes.py %{python3_sitelib}/qubesmanager/ui_informationnotes.py +%{python3_sitelib}/qubesmanager/ui_vtmanager.py %{python3_sitelib}/qubesmanager/i18n/qubesmanager_*.qm %{python3_sitelib}/qubesmanager/i18n/qubesmanager_*.ts diff --git a/setup.py b/setup.py index c89c898..940e99b 100644 --- a/setup.py +++ b/setup.py @@ -23,6 +23,7 @@ if __name__ == '__main__': 'qubes-vm-create = qubesmanager.create_new_vm:main', 'qubes-vm-boot-from-device = qubesmanager.bootfromdevice:main', 'qubes-backup = qubesmanager.backup:main', - 'qubes-backup-restore = qubesmanager.restore:main' + 'qubes-backup-restore = qubesmanager.restore:main', + 'qubes-template-volume-manager = qubesmanager.main:main' ], }) diff --git a/ui/vtmanager.ui b/ui/vtmanager.ui index 4e89f45..03932fd 100644 --- a/ui/vtmanager.ui +++ b/ui/vtmanager.ui @@ -136,7 +136,7 @@ 10
- 15 + 11 false @@ -209,32 +209,6 @@ VM's netVM - - - CPU - - - - - CPU Graph - - - CPU usage graph - - - - - MEM - - - - - MEM Graph - - - Memory usage graph - - Size @@ -295,10 +269,6 @@ - - - - From 8b5e2a8d665d3bcdd7fb32cadce038ad15eb6d5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Mon, 8 Jan 2018 02:46:41 +0100 Subject: [PATCH 59/70] Qube Manager in working order Everything that should work works. Further improvements that would be nice: - replace untidy multitude of QtGui Items with one generic type - restore Qubes Network functionality - add Boot From Device action (and easy way to install windows tool from there) fixes QubesOS/qubes-issues#2966 QubesOS/qubes-issues#2132 --- qubes-qube-manager.desktop | 9 + qubesmanager/log_dialog.py | 40 +- qubesmanager/{main.py => qube_manager.py} | 498 ++++++++-------------- qubesmanager/table_widgets.py | 26 +- rpm_spec/qmgr.spec | 6 +- setup.py | 2 +- ui/{vtmanager.ui => qubemanager.ui} | 115 +---- 7 files changed, 233 insertions(+), 463 deletions(-) create mode 100644 qubes-qube-manager.desktop rename qubesmanager/{main.py => qube_manager.py} (73%) rename ui/{vtmanager.ui => qubemanager.ui} (90%) diff --git a/qubes-qube-manager.desktop b/qubes-qube-manager.desktop new file mode 100644 index 0000000..220f8d5 --- /dev/null +++ b/qubes-qube-manager.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Type=Application +Exec=qubes-qube-manager +Icon=qubes-manager +Terminal=false +Name=Qube Manager +GenericName=Qube Manager +StartupNotify=false +Categories=System; diff --git a/qubesmanager/log_dialog.py b/qubesmanager/log_dialog.py index f38182f..23af4b1 100644 --- a/qubesmanager/log_dialog.py +++ b/qubesmanager/log_dialog.py @@ -1,5 +1,4 @@ -#!/usr/bin/python2 -# pylint: skip-file +#!/usr/bin/python3 # # The Qubes OS Project, http://www.qubes-os.org # @@ -21,22 +20,17 @@ # # -import sys -import os -from PyQt4.QtCore import * -from PyQt4.QtGui import * +from PyQt4 import QtCore +from PyQt4 import QtGui -from qubes.qubes import QubesException - -import qubesmanager.resources_rc - -from .ui_logdlg import * -from .clipboard import * +from . import ui_logdlg +from . import clipboard # Display only this size of log LOG_DISPLAY_SIZE = 1024*1024 -class LogDialog(Ui_LogDialog, QDialog): + +class LogDialog(ui_logdlg.Ui_LogDialog, QtGui.QDialog): def __init__(self, app, log_path, parent=None): super(LogDialog, self).__init__(parent) @@ -46,24 +40,26 @@ class LogDialog(Ui_LogDialog, QDialog): self.setupUi(self) self.setWindowTitle(log_path) - - self.connect(self.copy_to_qubes_clipboard, SIGNAL("clicked()"), self.copy_to_qubes_clipboard_triggered) - + + self.connect(self.copy_to_qubes_clipboard, + QtCore.SIGNAL("clicked()"), + self.copy_to_qubes_clipboard_triggered) + self.__init_log_text__() def __init_log_text__(self): self.displayed_text = "" log = open(self.log_path) - log.seek(0, os.SEEK_END) + log.seek(0, clipboard.os.SEEK_END) if log.tell() > LOG_DISPLAY_SIZE: - self.displayed_text = self.tr("(Showing only last %d bytes of file)\n") % LOG_DISPLAY_SIZE - log.seek(-LOG_DISPLAY_SIZE, os.SEEK_END) + self.displayed_text = self.tr( + "(Showing only last %d bytes of file)\n") % LOG_DISPLAY_SIZE + log.seek(-LOG_DISPLAY_SIZE, clipboard.os.SEEK_END) else: - log.seek(0, os.SEEK_SET) + log.seek(0, clipboard.os.SEEK_SET) self.displayed_text += log.read() log.close() self.log_text.setPlainText(self.displayed_text) - def copy_to_qubes_clipboard_triggered(self): - copy_text_to_qubes_clipboard(self.displayed_text) + clipboard.copy_text_to_qubes_clipboard(self.displayed_text) diff --git a/qubesmanager/main.py b/qubesmanager/qube_manager.py similarity index 73% rename from qubesmanager/main.py rename to qubesmanager/qube_manager.py index 82ac8ed..fe73b84 100755 --- a/qubesmanager/main.py +++ b/qubesmanager/qube_manager.py @@ -25,7 +25,6 @@ import sys import os import os.path -import signal import subprocess import time from datetime import datetime, timedelta @@ -36,19 +35,19 @@ from qubesadmin import Qubes from PyQt4 import QtGui from PyQt4 import QtCore -from . import ui_vtmanager +from . import ui_qubemanager from . import thread_monitor from . import table_widgets from . import settings from . import global_settings from . import restore from . import backup +from . import log_dialog import threading from qubesmanager.about import AboutDialog -# TODO: probably unneeded class QMVmState: ErrorMsg = 1 AudioRecAvailable = 2 @@ -77,7 +76,7 @@ class VmRowInTable(object): def __init__(self, vm, row_no, table): self.vm = vm self.row_no = row_no - # TODO: replace a million different widgets with a more generic + # TODO: replace a various different widgets with a more generic # VmFeatureWidget or VMPropertyWidget table_widgets.row_height = VmManagerWindow.row_height @@ -165,7 +164,6 @@ class VmShutdownMonitor(QtCore.QObject): def restart_vm_if_needed(self): if self.and_restart and self.caller: self.caller.start_vm(self.vm) -# TODO: can i kill running vm def check_again_later(self): # noinspection PyTypeChecker,PyCallByClass @@ -180,7 +178,8 @@ class VmShutdownMonitor(QtCore.QObject): def check_if_vm_has_shutdown(self): vm = self.vm vm_is_running = vm.is_running() - vm_start_time = vm.get_start_time() + vm_start_time = datetime.fromtimestamp( + float(getattr(vm, 'start_time', None))) if vm_is_running and vm_start_time \ and vm_start_time < self.shutdown_started: if self.timeout_reached(): @@ -213,7 +212,7 @@ class VmShutdownMonitor(QtCore.QObject): self.restart_vm_if_needed() -class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): +class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): row_height = 30 column_width = 200 min_visible_rows = 10 @@ -233,16 +232,17 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): "Last backup": 10, } - def __init__(self, qvm_collection, parent=None): + def __init__(self, qubes_app, qt_app, parent=None): super(VmManagerWindow, self).__init__() self.setupUi(self) self.toolbar = self.toolBar self.manager_settings = QtCore.QSettings(self) - self.qvm_collection = qvm_collection + self.qubes_app = qubes_app + self.qt_app = qt_app - self.searchbox = SearchBox() # TODO check if this works + self.searchbox = SearchBox() self.searchbox.setValidator(QtGui.QRegExpValidator( QtCore.QRegExp("[a-zA-Z0-9-]*", QtCore.Qt.CaseInsensitive), None)) self.searchContainer.addWidget(self.searchbox) @@ -255,9 +255,6 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): self.sort_by_column = "Type" self.sort_order = QtCore.Qt.AscendingOrder - self.screen_number = -1 - self.screen_changed = False - self.vms_list = [] self.vms_in_table = {} self.reload_table = False @@ -285,7 +282,6 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): .action_last_backup } - # TODO: make refresh button self.visible_columns_count = len(self.columns_indices) self.table.setColumnHidden(self.columns_indices["Size"], True) self.action_size_on_disk.setChecked(False) @@ -315,12 +311,10 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): self.context_menu = QtGui.QMenu(self) - # TODO: check if this works, check all options self.context_menu.addAction(self.action_settings) self.context_menu.addAction(self.action_editfwrules) self.context_menu.addAction(self.action_appmenus) self.context_menu.addAction(self.action_set_keyboard_layout) - self.context_menu.addAction(self.action_toggle_audio_input) self.context_menu.addSeparator() self.context_menu.addAction(self.action_updatevm) @@ -384,19 +378,15 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): self.counter = 0 self.update_size_on_disk = False self.shutdown_monitor = {} - self.last_measure_results = {} - self.last_measure_time = time.time() def load_manager_settings(self): # visible columns - # self.manager_settings.beginGroup("columns") for col in self.columns_indices.keys(): col_no = self.columns_indices[col] visible = self.manager_settings.value( 'columns/%s' % col, defaultValue=not self.table.isColumnHidden(col_no)) self.columns_actions[col_no].setChecked(visible == "true") - # self.manager_settings.endGroup() self.sort_by_column = str( self.manager_settings.value("view/sort_column", @@ -415,18 +405,8 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): self.action_toolbar.setChecked(False) self.settings_loaded = True - def show(self): - super(VmManagerWindow, self).show() - self.screen_number = app.desktop().screenNumber(self) - - def domain_state_changed_callback(self, name=None, uuid=None): - if name is not None: - vm = self.qvm_collection.domains[name] - if vm: - vm.refresh() - def get_vms_list(self): - return [vm for vm in self.qvm_collection.domains] + return [vm for vm in self.qubes_app.domains] def fill_table(self): # save current selection @@ -446,8 +426,6 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): row_no = 0 for vm in vms_list: - # if vm.internal: - # continue vm_row = VmRowInTable(vm, row_no, self.table) vms_in_table[vm.qid] = vm_row @@ -464,7 +442,7 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): self.showhide_vms() - def showhide_vms(self): # TODO: just show all all the time? + def showhide_vms(self): if not self.search: for row_no in range(self.table.rowCount()): self.table.setRowHidden(row_no, False) @@ -479,8 +457,8 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): def do_search(self, search): self.search = str(search) self.showhide_vms() - # self.set_table_geom_size() + # noinspection PyArgumentList @QtCore.pyqtSlot(name='on_action_search_triggered') def action_search_triggered(self): self.searchbox.setFocus() @@ -488,78 +466,16 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): def mark_table_for_update(self): self.reload_table = True - # When calling update_table() directly, always use out_of_schedule=True! - def update_table(self, out_of_schedule=False): + def update_table(self): - reload_table = self.reload_table + self.fill_table() + # TODO: instead of manually refreshing the entire table, use dbus events - if manager_window.isVisible(): - # some_vms_have_changed_power_state = False - # for vm in self.vms_list: - # state = vm.get_power_state() - # if vm.last_power_state != state: - # if state == "Running" and \ - # self.vm_errors.get(vm.qid, "") \ - # .startswith("Error starting VM:"): - # self.clear_error(vm.qid) - # prev_running = vm.last_running - # vm.last_power_state = state - # vm.last_running = vm.is_running() - # self.update_audio_rec_info(vm) - # if not prev_running and vm.last_running: - # self.running_vms_count += 1 - # some_vms_have_changed_power_state = True - # # Clear error state when VM just started - # self.clear_error(vm.qid) - # elif prev_running and not vm.last_running: - # # FIXME: remove when recAllowed state will be preserved - # if self.vm_rec.has_key(vm.name): - # self.vm_rec.pop(vm.name) - # self.running_vms_count -= 1 - # some_vms_have_changed_power_state = True - # else: - # # pulseaudio agent register itself some time after VM - # # startup - # if state == "Running" and not vm.qubes_manager_state[ - # QMVmState.AudioRecAvailable]: - # self.update_audio_rec_info(vm) - # if self.vm_errors.get(vm.qid, "") == \ - # "Error starting VM: Cannot execute qrexec-daemon!" \ - # and vm.is_qrexec_running(): - # self.clear_error(vm.qid) - pass + # reapply sorting + if self.sort_by_column: + self.table.sortByColumn(self.columns_indices[self.sort_by_column]) - if self.screen_changed: - reload_table = True - self.screen_changed = False - - if reload_table: - self.fill_table() - update_devs = True - - # if self.sort_by_column == \ - # "State" and some_vms_have_changed_power_state: - # self.table.sortItems(self.columns_indices[self.sort_by_column], - # self.sort_order) - - if (not self.table.isColumnHidden(self.columns_indices['Size'])) \ - and self.counter % 60 == 0 or out_of_schedule: - self.update_size_on_disk = True - - for vm_row in self.vms_in_table.values(): - vm_row.update(update_size_on_disk=self.update_size_on_disk) - # TODO: fix these for saner opts TODO2: is it fixed? - - if self.sort_by_column in ["CPU", "State", "Size", "Internal"]: - # "State": needed to sort after reload (fill_table sorts items - # with setSortingEnabled, but by that time the widgets values - # are not correct yet). - self.table.sortItems(self.columns_indices[self.sort_by_column], - self.sort_order) - - self.table_selection_changed() - - self.update_size_on_disk = False + self.table_selection_changed() # noinspection PyPep8Naming @QtCore.pyqtSlot(bool, str) @@ -578,58 +494,46 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): self.manager_settings.sync() def table_selection_changed(self): - # TODO: and this should actually work, fixit vm = self.get_selected_vm() - if vm is not None: + if vm is not None and vm in self.qubes_app.domains: + + # TODO: add boot from device to menu and add windows tools there # Update available actions: - self.action_settings.setEnabled(vm.qid != 0) - self.action_removevm.setEnabled(vm.klass != 'AdminVM' - and not vm.is_running()) - # TODO: think about this + self.action_settings.setEnabled(vm.klass != 'AdminVM') + self.action_removevm.setEnabled( + vm.klass != 'AdminVM' and not vm.is_running()) self.action_clonevm.setEnabled(vm.klass != 'AdminVM') - self.action_resumevm.setEnabled(vm.get_power_state() == "Paused") - # TODO: check - try: - pass - # self.action_startvm_tools_install.setVisible( - # isinstance(vm, QubesHVm)) - except NameError: - # ignore non existing QubesHVm - pass - self.action_startvm_tools_install.setEnabled( - getattr(vm, 'updateable', False)) - # TODO: add qvm boot from device to this and add there windows tools - self.action_pausevm.setEnabled(vm.get_power_state() != "Paused" and - vm.qid != 0) - self.action_shutdownvm.setEnabled(vm.get_power_state() != "Paused" - and vm.qid != 0) - self.action_restartvm.setEnabled(vm.get_power_state() != "Paused" - and vm.qid != 0 - and vm.klass != 'DisposableVM') - self.action_killvm.setEnabled(vm.get_power_state() == "Paused" and - vm.qid != 0) - # TODO: check conditions + self.action_resumevm.setEnabled( + not vm.is_running() or vm.get_power_state() == "Paused") + self.action_pausevm.setEnabled( + vm.is_running() and vm.get_power_state() != "Paused" + and vm.klass != 'AdminVM') + self.action_shutdownvm.setEnabled( + vm.is_running() and vm.get_power_state() != "Paused" + and vm.klass != 'AdminVM') + self.action_restartvm.setEnabled( + vm.is_running() and vm.get_power_state() != "Paused" + and vm.klass != 'AdminVM' and vm.klass != 'DisposableVM') + self.action_killvm.setEnabled( + (vm.get_power_state() == "Paused" or vm.is_running()) + and vm.klass != 'AdminVM') + self.action_appmenus.setEnabled( vm.klass != 'AdminVM' and vm.klass != 'DisposableMV' and not vm.features.get('internal', False)) - self.action_editfwrules.setEnabled(True) # TODO: remove this and make sure the option is enabled in designer - # TODO: this should work - # self.action_updatevm.setEnabled(vm.is_updateable() or vm.qid == 0) - # TODO: this should work - # self.action_toggle_audio_input.setEnabled( - # vm.qubes_manager_state[QMVmState.AudioRecAvailable]) + self.action_editfwrules.setEnabled(vm.klass != 'AdminVM') + self.action_updatevm.setEnabled(getattr(vm, 'updateable', False) + or vm.qid == 0) self.action_run_command_in_vm.setEnabled( not vm.get_power_state() == "Paused" and vm.qid != 0) self.action_set_keyboard_layout.setEnabled( vm.qid != 0 and - vm.get_power_state() != "Paused") + vm.get_power_state() != "Paused" and vm.is_running()) else: self.action_settings.setEnabled(False) self.action_removevm.setEnabled(False) - self.action_startvm_tools_install.setVisible(False) - self.action_startvm_tools_install.setEnabled(False) self.action_clonevm.setEnabled(False) self.action_resumevm.setEnabled(False) self.action_pausevm.setEnabled(False) @@ -639,7 +543,6 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): self.action_appmenus.setEnabled(False) self.action_editfwrules.setEnabled(False) self.action_updatevm.setEnabled(False) - self.action_toggle_audio_input.setEnabled(False) self.action_run_command_in_vm.setEnabled(False) self.action_set_keyboard_layout.setEnabled(False) @@ -663,8 +566,9 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): vm.qubes_manager_state[QMVmState.ErrorMsg] = None self.vm_errors.pop(qid, None) + # noinspection PyArgumentList @QtCore.pyqtSlot(name='on_action_createvm_triggered') - def action_createvm_triggered(self): # TODO: this should work + def action_createvm_triggered(self): subprocess.check_call('qubes-vm-create') def get_selected_vm(self): @@ -684,18 +588,15 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): else: return None + # noinspection PyArgumentList @QtCore.pyqtSlot(name='on_action_removevm_triggered') - def action_removevm_triggered(self): # TODO: this should work + def action_removevm_triggered(self): vm = self.get_selected_vm() - assert not vm.is_running() - assert not vm.installed_by_rpm - vm = self.qvm_collection[vm.qid] - - if vm.is_template(): + if vm.klass == 'TemplateVM': dependent_vms = 0 - for single_vm in self.qvm_collection.domains: + for single_vm in self.qubes_app.domains: if getattr(single_vm, 'template', None) == vm: dependent_vms += 1 if dependent_vms > 0: @@ -734,7 +635,7 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): # remove the VM t_monitor = thread_monitor.ThreadMonitor() thread = threading.Thread(target=self.do_remove_vm, - args=(vm, self.qvm_collection, t_monitor)) + args=(vm, self.qubes_app, t_monitor)) thread.daemon = True thread.start() @@ -745,7 +646,7 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): progress.show() while not t_monitor.is_finished(): - app.processEvents() + self.qt_app.processEvents() time.sleep(0.1) progress.hide() @@ -757,22 +658,25 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): self.tr("ERROR: {0}").format( t_monitor.error_msg)) + self.update_table() + @staticmethod - def do_remove_vm(vm, qvm_collection, t_monitor): + def do_remove_vm(vm, qubes_app, t_monitor): try: - del qvm_collection.domains[vm.name] + del qubes_app.domains[vm.name] except Exception as ex: t_monitor.set_error_msg(str(ex)) t_monitor.set_finished() + # noinspection PyArgumentList @QtCore.pyqtSlot(name='on_action_clonevm_triggered') - def action_clonevm_triggered(self): # TODO: this should work + def action_clonevm_triggered(self): vm = self.get_selected_vm() name_number = 1 name_format = vm.name + '-clone-%d' - while self.qvm_collection.domains[name_format % name_number]: + while name_format % name_number in self.qubes_app.domains.keys(): name_number += 1 (clone_name, ok) = QtGui.QInputDialog.getText( @@ -784,7 +688,7 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): t_monitor = thread_monitor.ThreadMonitor() thread = threading.Thread(target=self.do_clone_vm, - args=(vm, self.qvm_collection, + args=(vm, self.qubes_app, clone_name, t_monitor)) thread.daemon = True thread.start() @@ -797,7 +701,7 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): progress.show() while not t_monitor.is_finished(): - app.processEvents() + self.qt_app.processEvents() time.sleep(0.2) progress.hide() @@ -809,17 +713,20 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): self.tr("Exception while cloning:
{0}").format( t_monitor.error_msg)) + self.update_table() + @staticmethod - def do_clone_vm(src_vm, qvm_collection, dst_name, t_monitor): + def do_clone_vm(src_vm, qubes_app, dst_name, t_monitor): dst_vm = None try: - dst_vm = qvm_collection.clone_vm(src_vm, dst_name) + dst_vm = qubes_app.clone_vm(src_vm, dst_name) except Exception as ex: - if dst_vm: - pass # TODO: should I remove any remnants? t_monitor.set_error_msg(str(ex)) + if dst_vm: + pass t_monitor.set_finished() + # noinspection PyArgumentList @QtCore.pyqtSlot(name='on_action_resumevm_triggered') def action_resumevm_triggered(self): vm = self.get_selected_vm() @@ -833,9 +740,11 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): return self.start_vm(vm) + self.update_table() - def start_vm(self, vm): # TODO: this should work - assert not vm.is_running() + def start_vm(self, vm): + if vm.is_running(): + return t_monitor = thread_monitor.ThreadMonitor() thread = threading.Thread(target=self.do_start_vm, args=(vm, t_monitor)) @@ -843,7 +752,7 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): thread.start() while not t_monitor.is_finished(): - app.processEvents() + self.qt_app.processEvents() time.sleep(0.1) if not t_monitor.success: @@ -851,6 +760,8 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): vm.qid, self.tr("Error starting VM: %s") % t_monitor.error_msg) + self.update_table() + @staticmethod def do_start_vm(vm, t_monitor): try: @@ -862,51 +773,11 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): t_monitor.set_finished() + # noinspection PyArgumentList @QtCore.pyqtSlot(name='on_action_startvm_tools_install_triggered') # TODO: replace with boot from device def action_startvm_tools_install_triggered(self): - vm = self.get_selected_vm() - assert not vm.is_running() - - windows_tools_installed = \ - os.path.exists('/usr/lib/qubes/qubes-windows-tools.iso') - if not windows_tools_installed: - msg = QtGui.QMessageBox() - msg.warning(self, self.tr("Error starting VM!"), - self.tr("You need to install 'qubes-windows-tools' " - "package to use this option")) - return - - t_monitor = thread_monitor.ThreadMonitor() - thread = threading.Thread(target=self.do_start_vm_tools_install, - args=(vm, t_monitor)) - thread.daemon = True - thread.start() - - while not t_monitor.is_finished(): - app.processEvents() - time.sleep(0.1) - - if not t_monitor.success: - self.set_error( - vm.qid, - self.tr("Error starting VM: %s") % t_monitor.error_msg) - - # noinspection PyMethodMayBeStatic - def do_start_vm_tools_install(self, vm, t_monitor): - # TODO: should this work? - prev_drive = vm.drive - try: - vm.drive = 'cdrom:dom0:/usr/lib/qubes/qubes-windows-tools.iso' - vm.start() - except Exception as ex: - t_monitor.set_error_msg(str(ex)) - t_monitor.set_finished() - return - finally: - vm.drive = prev_drive - - t_monitor.set_finished() + pass @QtCore.pyqtSlot(name='on_action_pausevm_triggered') def action_pausevm_triggered(self): @@ -914,6 +785,7 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): assert vm.is_running() try: vm.pause() + self.update_table() except Exception as ex: QtGui.QMessageBox.warning( None, @@ -921,6 +793,7 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): self.tr("ERROR: {0}").format(ex)) return + # noinspection PyArgumentList @QtCore.pyqtSlot(name='on_action_shutdownvm_triggered') def action_shutdownvm_triggered(self): vm = self.get_selected_vm() @@ -933,11 +806,13 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): "running applications within this VM.").format( vm.name), QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel) - app.processEvents() # TODO: is this needed?? + self.qt_app.processEvents() if reply == QtGui.QMessageBox.Yes: self.shutdown_vm(vm) + self.update_table() + def shutdown_vm(self, vm, shutdown_time=vm_shutdown_timeout, check_time=vm_restart_check_timeout, and_restart=False): try: @@ -956,6 +831,7 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): QtCore.QTimer.singleShot(check_time, self.shutdown_monitor[ vm.qid].check_if_vm_has_shutdown) + # noinspection PyArgumentList @QtCore.pyqtSlot(name='on_action_restartvm_triggered') def action_restartvm_triggered(self): vm = self.get_selected_vm() @@ -968,11 +844,14 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): "within this VM.").format(vm.name), QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel) - app.processEvents() + self.qt_app.processEvents() if reply == QtGui.QMessageBox.Yes: self.shutdown_vm(vm, and_restart=True) + self.update_table() + + # noinspection PyArgumentList @QtCore.pyqtSlot(name='on_action_killvm_triggered') def action_killvm_triggered(self): vm = self.get_selected_vm() @@ -987,7 +866,7 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel, QtGui.QMessageBox.Cancel) - app.processEvents() + self.qt_app.processEvents() if reply == QtGui.QMessageBox.Yes: try: @@ -1000,22 +879,30 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): "ERROR: {1}").format(vm.name, ex)) return + # noinspection PyArgumentList @QtCore.pyqtSlot(name='on_action_settings_triggered') def action_settings_triggered(self): vm = self.get_selected_vm() - settings_window = settings.VMSettingsWindow(vm, app, "basic") - settings_window.exec_() + if vm: + settings_window = settings.VMSettingsWindow( + vm, self.qt_app, "basic") + settings_window.exec_() + # noinspection PyArgumentList @QtCore.pyqtSlot(name='on_action_appmenus_triggered') def action_appmenus_triggered(self): - pass - # TODO this should work, actually, but please not now - # vm = self.get_selected_vm() - # settings_window = VMSettingsWindow(vm, app, self.qvm_collection, - # "applications") - # settings_window.exec_() + vm = self.get_selected_vm() + if vm: + settings_window = settings.VMSettingsWindow( + vm, self.qt_app, "applications") + settings_window.exec_() + # noinspection PyArgumentList + @QtCore.pyqtSlot(name='on_action_refresh_list_triggered') + def action_refresh_list_triggered(self): + self.update_table() + # noinspection PyArgumentList @QtCore.pyqtSlot(name='on_action_updatevm_triggered') def action_updatevm_triggered(self): vm = self.get_selected_vm() @@ -1023,13 +910,14 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): if not vm.is_running(): reply = QtGui.QMessageBox.question( None, self.tr("VM Update Confirmation"), - self.tr("{0}
The VM has to be running to be updated.
" - "Do you want to start it?
").format(vm.name), + self.tr( + "{0}
The VM has to be running to be updated.
" + "Do you want to start it?
").format(vm.name), QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel) if reply != QtGui.QMessageBox.Yes: return - app.processEvents() + self.qt_app.processEvents() t_monitor = thread_monitor.ThreadMonitor() thread = threading.Thread(target=self.do_update_vm, @@ -1038,26 +926,30 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): thread.start() progress = QtGui.QProgressDialog( - self.tr("{0}
Please wait for the updater to " - "launch...").format(vm.name), "", 0, 0) + self.tr( + "{0}
Please wait for the updater to " + "launch...").format(vm.name), "", 0, 0) progress.setCancelButton(None) progress.setModal(True) progress.show() while not t_monitor.is_finished(): - app.processEvents() + self.qt_app.processEvents() time.sleep(0.2) progress.hide() if vm.qid != 0: if not t_monitor.success: - QtGui.QMessageBox.warning(None, self.tr("Error VM update!"), - self.tr("ERROR: {0}").format( - t_monitor.error_msg)) + QtGui.QMessageBox.warning( + None, + self.tr("Error VM update!"), + self.tr("ERROR: {0}").format(t_monitor.error_msg)) + + self.update_table() @staticmethod - def do_update_vm(vm, thread_monitor): #TODO: fixme + def do_update_vm(vm, t_monitor): try: if vm.qid == 0: subprocess.check_call( @@ -1065,15 +957,15 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): else: if not vm.is_running(): vm.start() - vm.run_service("qubes.InstallUpdatesGUI", gui=True, + vm.run_service("qubes.InstallUpdatesGUI", user="root", wait=False) except Exception as ex: - thread_monitor.set_error_msg(str(ex)) - thread_monitor.set_finished() + t_monitor.set_error_msg(str(ex)) + t_monitor.set_finished() return - thread_monitor.set_finished() - + t_monitor.set_finished() + # noinspection PyArgumentList @QtCore.pyqtSlot(name='on_action_run_command_in_vm_triggered') def action_run_command_in_vm_triggered(self): vm = self.get_selected_vm() @@ -1090,62 +982,66 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): thread.start() while not t_monitor.is_finished(): - app.processEvents() + self.qt_app.processEvents() time.sleep(0.2) if not t_monitor.success: - QtGui.QMessageBox.warning(None, self.tr("Error while running command"), + QtGui.QMessageBox.warning( + None, self.tr("Error while running command"), self.tr("Exception while running command:
{0}").format( t_monitor.error_msg)) @staticmethod def do_run_command_in_vm(vm, command_to_run, t_monitor): try: - vm.run(command_to_run, verbose=False, autostart=True) + vm.run(command_to_run) except Exception as ex: t_monitor.set_error_msg(str(ex)) t_monitor.set_finished() + # noinspection PyArgumentList @QtCore.pyqtSlot(name='on_action_set_keyboard_layout_triggered') def action_set_keyboard_layout_triggered(self): vm = self.get_selected_vm() - vm.run('qubes-change-keyboard-layout', verbose=False) - - #TODO: remove showallvms / show inactive vms / show internal vms + vm.run('qubes-change-keyboard-layout') + # noinspection PyArgumentList @QtCore.pyqtSlot(name='on_action_editfwrules_triggered') def action_editfwrules_triggered(self): vm = self.get_selected_vm() - settings_window = settings.VMSettingsWindow(vm, app, "firewall") + settings_window = settings.VMSettingsWindow(vm, self.qt_app, "firewall") settings_window.exec_() + # noinspection PyArgumentList @QtCore.pyqtSlot(name='on_action_global_settings_triggered') def action_global_settings_triggered(self): global_settings_window = global_settings.GlobalSettingsWindow( - app, - self.qvm_collection) + self.qt_app, + self.qubes_app) global_settings_window.exec_() + # noinspection PyArgumentList @QtCore.pyqtSlot(name='on_action_show_network_triggered') def action_show_network_triggered(self): pass - #TODO: revive TODO: what is this thing?? + # TODO: revive for 4.1 # network_notes_dialog = NetworkNotesDialog() # network_notes_dialog.exec_() + # noinspection PyArgumentList @QtCore.pyqtSlot(name='on_action_restore_triggered') def action_restore_triggered(self): - restore_window = restore.RestoreVMsWindow(app, self.qvm_collection) + restore_window = restore.RestoreVMsWindow(self.qt_app, self.qubes_app) restore_window.exec_() + # noinspection PyArgumentList @QtCore.pyqtSlot(name='on_action_backup_triggered') def action_backup_triggered(self): - backup_window = backup.BackupVMsWindow(app, self.qvm_collection) + backup_window = backup.BackupVMsWindow(self.qt_app, self.qubes_app) backup_window.exec_() def showhide_menubar(self, checked): self.menubar.setVisible(checked) - # self.set_table_geom_size() if not checked: self.context_menu.addAction(self.action_menubar) else: @@ -1156,7 +1052,6 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): def showhide_toolbar(self, checked): self.toolbar.setVisible(checked) - # self.set_table_geom_size() if not checked: self.context_menu.addAction(self.action_toolbar) else: @@ -1171,13 +1066,13 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): val = 1 if show else -1 self.visible_columns_count += val - if self.visible_columns_count == 1: # TODO: is this working at all?? + if self.visible_columns_count == 1: # disable hiding the last one for c in self.columns_actions: if self.columns_actions[c].isChecked(): self.columns_actions[c].setEnabled(False) break - elif self.visible_columns_count == 2 and val == 1: # TODO: likewise?? + elif self.visible_columns_count == 2 and val == 1: # enable hiding previously disabled column for c in self.columns_actions: if not self.columns_actions[c].isEnabled(): @@ -1223,6 +1118,7 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): def on_action_size_on_disk_toggled(self, checked): self.showhide_column(self.columns_indices['Size'], checked) + # noinspection PyArgumentList @QtCore.pyqtSlot(name='on_action_about_qubes_triggered') def action_about_qubes_triggered(self): about = AboutDialog() @@ -1241,8 +1137,6 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): def open_context_menu(self, point): vm = self.get_selected_vm() - running = vm.is_running() - # logs menu self.logs_menu.clear() @@ -1259,98 +1153,70 @@ class VmManagerWindow(ui_vtmanager.Ui_VmManagerWindow, QtGui.QMainWindow): menu_empty = True for logfile in logfiles: if os.path.exists(logfile): - action = self.logs_menu.addAction(QtGui.QIcon(":/log.png"), logfile) - action.setData(QtCore.QVariant(logfile)) + action = self.logs_menu.addAction(QtGui.QIcon(":/log.png"), + logfile) + action.setData(logfile) menu_empty = False self.logs_menu.setEnabled(not menu_empty) - self.context_menu.exec_(self.table.mapToGlobal(point)) @QtCore.pyqtSlot('QAction *') def show_log(self, action): - pass - #TODO fixme - # log = str(action.data()) - # log_dialog = LogDialog(app, log) - # log_dialog.exec_() - - -def show_manager(): - manager_window.show() - manager_window.repaint() - manager_window.update_table(out_of_schedule=True) - app.processEvents() - - -def exit_app(): - app.exit() + log = str(action.data()) + log_dlg = log_dialog.LogDialog(self.qt_app, log) + log_dlg.exec_() # Bases on the original code by: # Copyright (c) 2002-2007 Pascal Varet def handle_exception(exc_type, exc_value, exc_traceback): - for f in traceback.extract_stack(exc_traceback): - print(f[0], f[1]) + filename, line, dummy, dummy = traceback.extract_tb(exc_traceback).pop() filename = os.path.basename(filename) error = "%s: %s" % (exc_type.__name__, exc_value) - QtGui.QMessageBox.critical( - None, - "Houston, we have a problem...", - "Whoops. A critical error has occured. This is most likely a bug " - "in Volume and Template Manager application.

%s" % - error + "at line %d of file %s.

" - % (line, filename)) + strace = "" + stacktrace = traceback.extract_tb(exc_traceback) + while stacktrace: + (filename, line, func, txt) = stacktrace.pop() + strace += "----\n" + strace += "line: %s\n" % txt + strace += "func: %s\n" % func + strace += "line no.: %d\n" % line + strace += "file: %s\n" % filename -def sighup_handler(signum, frame): - os.execl("/usr/bin/qubes-manager", "qubes-manager") + msg_box = QtGui.QMessageBox() + msg_box.setDetailedText(strace) + msg_box.setIcon(QtGui.QMessageBox.Critical) + msg_box.setWindowTitle("Houston, we have a problem...") + msg_box.setText("Whoops. A critical error has occured. " + "This is most likely a bug in Qubes Manager.

" + "%s" % error + + "
at line %d
of file %s.

" + % (line, filename)) + + msg_box.exec_() def main(): - signal.signal(signal.SIGHUP, sighup_handler) - - global app - app = QtGui.QApplication(sys.argv) - app.setOrganizationName("The Qubes Project") - app.setOrganizationDomain("http://qubes-os.org") - app.setApplicationName("Qubes VM Manager") - app.setWindowIcon(QtGui.QIcon.fromTheme("qubes-manager")) + qt_app = QtGui.QApplication(sys.argv) + qt_app.setOrganizationName("The Qubes Project") + qt_app.setOrganizationDomain("http://qubes-os.org") + qt_app.setApplicationName("Qubes VM Manager") + qt_app.setWindowIcon(QtGui.QIcon.fromTheme("qubes-manager")) sys.excepthook = handle_exception - qvm_collection = Qubes() + qubes_app = Qubes() - global manager_window - manager_window = VmManagerWindow(qvm_collection) + manager_window = VmManagerWindow(qubes_app, qt_app) - # global wm - # wm = WatchManager() - # global notifier - # # notifier = ThreadedNotifier(wm, QubesManagerFileWatcher( - # # manager_window.mark_table_for_update)) - # notifier.start() - # wm.add_watch(system_path["qubes_store_filename"], - # EventsCodes.OP_FLAGS.get('IN_MODIFY')) - # wm.add_watch(os.path.dirname(system_path["qubes_store_filename"]), - # EventsCodes.OP_FLAGS.get('IN_MOVED_TO')) - # if os.path.exists(qubes_clipboard_info_file): - # wm.add_watch(qubes_clipboard_info_file, - # EventsCodes.OP_FLAGS.get('IN_CLOSE_WRITE')) - # wm.add_watch(os.path.dirname(qubes_clipboard_info_file), - # EventsCodes.OP_FLAGS.get('IN_CREATE')) - # wm.add_watch(os.path.dirname(table_widgets.qubes_dom0_updates_stat_file), - # EventsCodes.OP_FLAGS.get('IN_CREATE')) - - threading.currentThread().setName("QtMainThread") - - show_manager() - app.exec_() + manager_window.show() + manager_window.update_table() + qt_app.exec_() if __name__ == "__main__": main() - -# TODO: change file name to something better \ No newline at end of file diff --git a/qubesmanager/table_widgets.py b/qubesmanager/table_widgets.py index 9acc05e..f72b278 100644 --- a/qubesmanager/table_widgets.py +++ b/qubesmanager/table_widgets.py @@ -22,7 +22,6 @@ from PyQt4 import QtGui from PyQt4 import QtCore -# TODO: are those needed? power_order = QtCore.Qt.DescendingOrder update_order = QtCore.Qt.AscendingOrder @@ -35,7 +34,6 @@ class VmIconWidget(QtGui.QWidget): tooltip=None, parent=None, icon_sz=(32, 32)): super(VmIconWidget, self).__init__(parent) - # TODO: check with Marek how icons should be done self.label_icon = QtGui.QLabel() if icon_path[0] in ':/': icon = QtGui.QIcon(icon_path) @@ -91,14 +89,7 @@ class VmTypeWidget(VmIconWidget): self.tableItem = self.VmTypeItem(self.value, vm) self.value = None - # TODO: seriously, are numbers the best idea here? - # TODO: add "provides network column - # TODO: in type make vmtype - # 'AdminVM': '0', - # 'TemplateVM': 't', - # 'AppVM': 'a', - # 'StandaloneVM': 's', - # 'DispVM': 'd', + # TODO: add "provides network" column def get_vm_icon(self, vm): if vm.klass == 'AdminVM': @@ -128,7 +119,6 @@ class VmLabelWidget(VmIconWidget): def set_value(self, value): self.value = value - # TODO: figure a prettier sorting method? def __lt__(self, other): if self.vm.qid == 0: return True @@ -154,7 +144,6 @@ class VmLabelWidget(VmIconWidget): class VmNameItem (QtGui.QTableWidgetItem): def __init__(self, vm): super(VmNameItem, self).__init__() - # TODO: is this needed self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) self.setText(vm.name) self.setTextAlignment(QtCore.Qt.AlignVCenter) @@ -168,14 +157,11 @@ class VmNameItem (QtGui.QTableWidgetItem): return super(VmNameItem, self).__lt__(other) -# TODO: current status - it should work... -# TODO: maybe dbus events? class VmStatusIcon(QtGui.QLabel): def __init__(self, vm, parent=None): super(VmStatusIcon, self).__init__(parent) self.vm = vm self.set_on_icon() - # TODO: rename previous power state to something better? self.previous_power_state = self.vm.get_power_state() def update(self): @@ -273,10 +259,8 @@ class VmInfoWidget (QtGui.QWidget): def update_vm_state(self, vm): self.on_icon.update() self.upd_info.update_outdated(vm) - # TODO: add updating things like label? name? evrything? size? -# TODO add main to git history as a saner name and with a decent comment -# TODO and rename that shit + class VmTemplateItem (QtGui.QTableWidgetItem): def __init__(self, vm): super(VmTemplateItem, self).__init__() @@ -312,8 +296,6 @@ class VmNetvmItem (QtGui.QTableWidgetItem): self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) self.vm = vm - # TODO: differentiate without no net vm/ no networking? - # TODO: mark provides network somehow? if getattr(vm, 'netvm', None) is None: self.setText("n/a") else: @@ -484,7 +466,7 @@ class VmSizeOnDiskItem (QtGui.QTableWidgetItem): else: return self.value < other.value -# TODO: replace these widgets with a generic widgets + class VmIPItem(QtGui.QTableWidgetItem): def __init__(self, vm): super(VmIPItem, self).__init__() @@ -512,7 +494,6 @@ class VmIncludeInBackupsItem(QtGui.QTableWidgetItem): self.setText("Yes") else: self.setText("") - self.setText("") def __lt__(self, other): if self.vm.qid == 0: @@ -535,7 +516,6 @@ class VmLastBackupItem(QtGui.QTableWidgetItem): self.setText(self.vm.backup_timestamp) else: self.setText("") - self.setText("") def __lt__(self, other): if self.vm.qid == 0: diff --git a/rpm_spec/qmgr.spec b/rpm_spec/qmgr.spec index 27ed4e5..f0d8297 100644 --- a/rpm_spec/qmgr.spec +++ b/rpm_spec/qmgr.spec @@ -64,7 +64,7 @@ rm -rf $RPM_BUILD_ROOT /usr/bin/qubes-vm-boot-from-device /usr/bin/qubes-backup /usr/bin/qubes-backup-restore -/usr/bin/qubes-template-volume-manager +/usr/bin/qubes-qube-manager /usr/libexec/qubes-manager/mount_for_backup.sh /usr/libexec/qubes-manager/qvm_about.sh @@ -88,7 +88,7 @@ rm -rf $RPM_BUILD_ROOT %{python3_sitelib}/qubesmanager/informationnotes.py %{python3_sitelib}/qubesmanager/create_new_vm.py %{python3_sitelib}/qubesmanager/thread_monitor.py -%{python3_sitelib}/qubesmanager/main.py +%{python3_sitelib}/qubesmanager/qube_manager.py %{python3_sitelib}/qubesmanager/utils.py %{python3_sitelib}/qubesmanager/bootfromdevice.py @@ -106,7 +106,7 @@ rm -rf $RPM_BUILD_ROOT %{python3_sitelib}/qubesmanager/ui_about.py %{python3_sitelib}/qubesmanager/ui_releasenotes.py %{python3_sitelib}/qubesmanager/ui_informationnotes.py -%{python3_sitelib}/qubesmanager/ui_vtmanager.py +%{python3_sitelib}/qubesmanager/ui_qubemanager.py %{python3_sitelib}/qubesmanager/i18n/qubesmanager_*.qm %{python3_sitelib}/qubesmanager/i18n/qubesmanager_*.ts diff --git a/setup.py b/setup.py index 940e99b..28d2c07 100644 --- a/setup.py +++ b/setup.py @@ -24,6 +24,6 @@ if __name__ == '__main__': 'qubes-vm-boot-from-device = qubesmanager.bootfromdevice:main', 'qubes-backup = qubesmanager.backup:main', 'qubes-backup-restore = qubesmanager.restore:main', - 'qubes-template-volume-manager = qubesmanager.main:main' + 'qubes-qube-manager = qubesmanager.qube_manager:main' ], }) diff --git a/ui/vtmanager.ui b/ui/qubemanager.ui similarity index 90% rename from ui/vtmanager.ui rename to ui/qubemanager.ui index 03932fd..7107bf3 100644 --- a/ui/vtmanager.ui +++ b/ui/qubemanager.ui @@ -278,10 +278,9 @@ - - +
@@ -321,7 +320,6 @@ - @@ -369,13 +367,12 @@ - - + @@ -494,40 +491,6 @@ Update VM system - - - false - - - - :/mic.png:/mic.png - - - Attach/detach &audio-input to the VM - - - Attach/detach audio-input to the VM - - - - - true - - - false - - - - :/show-all-running.png - :/showallvms.png:/show-all-running.png - - - Show/Hide inactive VMs - - - Show/Hide inactive VMs - - @@ -565,50 +528,6 @@ View - - - true - - - true - - - &CPU - - - - - true - - - true - - - CPU &Graph - - - - - true - - - true - - - &MEM - - - - - true - - - true - - - M&EM Graph - - true @@ -678,6 +597,9 @@ &Qubes Network + + false + @@ -834,21 +756,6 @@ Is an internal VM - - - true - - - false - - - - :/show-all-running.png:/show-all-running.png - - - Show/Hide internal VMs - - false @@ -905,6 +812,18 @@ Ctrl+F + + + + :/outdated.png:/outdated.png + + + Refresh Qube List + + + Refresh Qube List + + From 41b8e6a4fba2c856e7c16f379f41478503357e4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Mon, 8 Jan 2018 03:01:02 +0100 Subject: [PATCH 60/70] Replaced 'VM' with 'Qube' Renamed 'VM' to 'Qube' in the user interface for Qube Manager. --- qubesmanager/qube_manager.py | 80 ++++++++++++++++++------------------ ui/qubemanager.ui | 75 ++++++++++++++++++--------------- 2 files changed, 83 insertions(+), 72 deletions(-) diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py index fe73b84..cfa0127 100755 --- a/qubesmanager/qube_manager.py +++ b/qubesmanager/qube_manager.py @@ -184,9 +184,9 @@ class VmShutdownMonitor(QtCore.QObject): and vm_start_time < self.shutdown_started: if self.timeout_reached(): reply = QtGui.QMessageBox.question( - None, self.tr("VM Shutdown"), + None, self.tr("Qube Shutdown"), self.tr( - "The VM '{0}' hasn't shutdown within the last " + "The Qube '{0}' hasn't shutdown within the last " "{1} seconds, do you want to kill it?
").format( vm.name, self.shutdown_time / 1000), self.tr("Kill it!"), @@ -602,20 +602,20 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): if dependent_vms > 0: QtGui.QMessageBox.warning( None, self.tr("Warning!"), - self.tr("This Template VM cannot be removed, " - "because there is at least one AppVM that is based " + self.tr("This Template Qube cannot be removed, " + "because there is at least one Qube that is based " "on it.
If you want to remove this " - "Template VM and all the AppVMs based on it, you " - "should first remove each individual AppVM that " + "Template Qube and all the Qubes based on it, you " + "should first remove each individual Qube that " "uses this template.")) return (requested_name, ok) = QtGui.QInputDialog.getText( - None, self.tr("VM Removal Confirmation"), - self.tr("Are you sure you want to remove the VM '{0}'?
" - "All data on this VM's private storage will be lost!" - "

Type the name of the VM ({1}) below to " - "confirm:").format(vm.name, vm.name)) + None, self.tr("Qube Removal Confirmation"), + self.tr("Are you sure you want to remove the Qube '{0}'" + "?
All data on this Qube's private storage will be " + "lost!

Type the name of the Qube ({1}) below " + "to confirm:").format(vm.name, vm.name)) if not ok: # user clicked cancel @@ -625,7 +625,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): # name did not match QtGui.QMessageBox.warning( None, - self.tr("VM removal confirmation failed"), + self.tr("Qube removal confirmation failed"), self.tr( "Entered name did not match! Not removing " "{0}.").format(vm.name)) @@ -640,7 +640,8 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): thread.start() progress = QtGui.QProgressDialog( - self.tr("Removing VM: {0}...").format(vm.name), "", 0, 0) + self.tr( + "Removing Qube: {0}...").format(vm.name), "", 0, 0) progress.setCancelButton(None) progress.setModal(True) progress.show() @@ -654,7 +655,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): if t_monitor.success: pass else: - QtGui.QMessageBox.warning(None, self.tr("Error removing VM!"), + QtGui.QMessageBox.warning(None, self.tr("Error removing Qube!"), self.tr("ERROR: {0}").format( t_monitor.error_msg)) @@ -680,8 +681,8 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): name_number += 1 (clone_name, ok) = QtGui.QInputDialog.getText( - self, self.tr('Qubes clone VM'), - self.tr('Enter name for VM {} clone:').format(vm.name), + self, self.tr('Qubes clone Qube'), + self.tr('Enter name for Qube {} clone:').format(vm.name), text=(name_format % name_number)) if not ok or clone_name == "": return @@ -694,7 +695,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): thread.start() progress = QtGui.QProgressDialog( - self.tr("Cloning VM {0} to {1}...").format( + self.tr("Cloning Qube {0} to {1}...").format( vm.name, clone_name), "", 0, 0) progress.setCancelButton(None) progress.setModal(True) @@ -709,7 +710,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): if not t_monitor.success: QtGui.QMessageBox.warning( None, - self.tr("Error while cloning VM"), + self.tr("Error while cloning Qube"), self.tr("Exception while cloning:
{0}").format( t_monitor.error_msg)) @@ -735,8 +736,9 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): try: vm.unpause() except Exception as ex: - QtGui.QMessageBox.warning(None, self.tr("Error unpausing VM!"), - self.tr("ERROR: {0}").format(ex)) + QtGui.QMessageBox.warning( + None, self.tr("Error unpausing Qube!"), + self.tr("ERROR: {0}").format(ex)) return self.start_vm(vm) @@ -758,7 +760,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): if not t_monitor.success: self.set_error( vm.qid, - self.tr("Error starting VM: %s") % t_monitor.error_msg) + self.tr("Error starting Qube: %s") % t_monitor.error_msg) self.update_table() @@ -789,7 +791,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): except Exception as ex: QtGui.QMessageBox.warning( None, - self.tr("Error pausing VM!"), + self.tr("Error pausing Qube!"), self.tr("ERROR: {0}").format(ex)) return @@ -800,10 +802,10 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): assert vm.is_running() reply = QtGui.QMessageBox.question( - None, self.tr("VM Shutdown Confirmation"), - self.tr("Are you sure you want to power down the VM" + None, self.tr("Qube Shutdown Confirmation"), + self.tr("Are you sure you want to power down the Qube" " '{0}'?
This will shutdown all the " - "running applications within this VM.").format( + "running applications within this Qube.").format( vm.name), QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel) self.qt_app.processEvents() @@ -820,7 +822,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): except Exception as ex: QtGui.QMessageBox.warning( None, - self.tr("Error shutting down VM!"), + self.tr("Error shutting down Qube!"), self.tr("ERROR: {0}").format(ex)) return @@ -838,10 +840,10 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): assert vm.is_running() reply = QtGui.QMessageBox.question( - None, self.tr("VM Restart Confirmation"), - self.tr("Are you sure you want to restart the VM '{0}'?
" - "This will shutdown all the running applications " - "within this VM.").format(vm.name), + None, self.tr("Qube Restart Confirmation"), + self.tr("Are you sure you want to restart the Qube '{0}'?" + "
This will shutdown all the running " + "applications within this Qube.").format(vm.name), QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel) self.qt_app.processEvents() @@ -858,10 +860,10 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): assert vm.is_running() or vm.is_paused() reply = QtGui.QMessageBox.question( - None, self.tr("VM Kill Confirmation"), - self.tr("Are you sure you want to kill the VM '{0}'?
" + None, self.tr("Qube Kill Confirmation"), + self.tr("Are you sure you want to kill the Qube '{0}'?
" "This will end (not shutdown!) all the " - "running applications within this VM.").format( + "running applications within this Qube.").format( vm.name), QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel, QtGui.QMessageBox.Cancel) @@ -873,7 +875,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): vm.force_shutdown() except Exception as ex: QtGui.QMessageBox.critical( - None, self.tr("Error while killing VM!"), + None, self.tr("Error while killing Qube!"), self.tr( "An exception ocurred while killing {0}.
" "ERROR: {1}").format(vm.name, ex)) @@ -909,10 +911,10 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): if not vm.is_running(): reply = QtGui.QMessageBox.question( - None, self.tr("VM Update Confirmation"), + None, self.tr("Qube Update Confirmation"), self.tr( - "{0}
The VM has to be running to be updated.
" - "Do you want to start it?
").format(vm.name), + "{0}
The Qube has to be running to be updated." + "
Do you want to start it?
").format(vm.name), QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel) if reply != QtGui.QMessageBox.Yes: return @@ -943,7 +945,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): if not t_monitor.success: QtGui.QMessageBox.warning( None, - self.tr("Error VM update!"), + self.tr("Error on Qube update!"), self.tr("ERROR: {0}").format(t_monitor.error_msg)) self.update_table() @@ -1204,7 +1206,7 @@ def main(): qt_app = QtGui.QApplication(sys.argv) qt_app.setOrganizationName("The Qubes Project") qt_app.setOrganizationDomain("http://qubes-os.org") - qt_app.setApplicationName("Qubes VM Manager") + qt_app.setApplicationName("Qube Manager") qt_app.setWindowIcon(QtGui.QIcon.fromTheme("qubes-manager")) sys.excepthook = handle_exception diff --git a/ui/qubemanager.ui b/ui/qubemanager.ui index 7107bf3..150dafb 100644 --- a/ui/qubemanager.ui +++ b/ui/qubemanager.ui @@ -20,7 +20,7 @@ Qt::DefaultContextMenu - Qubes VM Manager + Qube Manager @@ -284,7 +284,7 @@ - V&M + &Qube @@ -380,10 +380,10 @@ :/createvm.png:/createvm.png - Create &New VM + Create &New Qube - Create a new VM + Create a new Qube @@ -395,10 +395,10 @@ :/removevm.png:/removevm.png - &Delete VM + &Delete Qube - Remove an existing VM (must be stopped first) + Remove an existing Qube (must be stopped first) @@ -410,10 +410,10 @@ :/resumevm.png:/resumevm.png - Start/Resume V&M + Start/Resu&me Qube - Start/Resume selected VM + Start/Resume selected Qube @@ -425,10 +425,10 @@ :/pausevm.png:/pausevm.png - &Pause VM + &Pause Qube - Pause selected VM + Pause selected Qube @@ -440,10 +440,10 @@ :/shutdownvm.png:/shutdownvm.png - &Shutdown VM + &Shutdown Qube - Shutdown selected VM + Shutdown selected Qube @@ -455,10 +455,10 @@ :/restartvm.png:/restartvm.png - Restar&t VM + Restar&t Qube - Restart selected VM + Restart selected Qube @@ -473,7 +473,7 @@ Add/remove app s&hortcuts - Add/remove app shortcuts for this VM + Add/remove app shortcuts for this Qube @@ -485,10 +485,10 @@ :/updateable.png:/updateable.png - &Update VM + &Update Qube - Update VM system + Update Qube system @@ -497,10 +497,10 @@ :/firewall.png:/firewall.png - Edit VM &firewall rules + Edit Qube &firewall rules - Edit VM firewall rules + Edit Qube firewall rules @@ -556,10 +556,10 @@ :/settings.png:/settings.png - VM s&ettings + Qube s&ettings - VM Settings + Qube Settings @@ -568,7 +568,10 @@ :/restore.png:/restore.png - &Restore VMs from backup + &Restore Qubes from backup + + + Restore Qubes from backup @@ -577,7 +580,10 @@ :/backup.png:/backup.png - &Backup VMs + &Backup Qubes + + + Backup Qubes @@ -618,10 +624,10 @@ :/killvm.png:/killvm.png - &Kill VM + &Kill Qube - Kill selected VM + Kill selected Qube @@ -633,7 +639,7 @@ Set keyboard la&yout - Set keyboard layout per VM + Set keyboard layout per Qube @@ -647,7 +653,7 @@ T&ype - VM Type + Qube Type @@ -724,10 +730,10 @@ :/run-command.png:/run-command.png - &Run command in VM + &Run command in Qube - Run command in the specified VM + Run command in the specified Qube @@ -739,7 +745,10 @@ :/templatevm.png:/templatevm.png - &Clone VM + &Clone Qube + + + Clone Qube @@ -753,7 +762,7 @@ Inte&rnal - Is an internal VM + Is an internal Qube @@ -765,10 +774,10 @@ :/resumevm.png:/resumevm.png - Start VM for Window Tools installation + Start Qube for Window Tools installation - Start VM for Window Tools installation + Start Qube for Window Tools installation From 899ca6d394917887478650129bd7b70d5bfca54a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Mon, 8 Jan 2018 03:06:42 +0100 Subject: [PATCH 61/70] Pylint corrections Removed errors discovered by pylint, or told it to be quiet when needed. --- ci/pylintrc | 2 +- qubesmanager/log_dialog.py | 11 ++-- qubesmanager/qube_manager.py | 117 +++++++++++++--------------------- qubesmanager/table_widgets.py | 62 ++++++++---------- 4 files changed, 79 insertions(+), 113 deletions(-) diff --git a/ci/pylintrc b/ci/pylintrc index d79fa35..85eefb5 100644 --- a/ci/pylintrc +++ b/ci/pylintrc @@ -87,7 +87,7 @@ variable-rgx=[a-z_][a-z0-9_]{2,30}$ inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ # Good variable names which should always be accepted, separated by a comma -good-names=e,i,j,k,m,p,v,ex,Run,_,log,vm,ok +good-names=e,i,j,k,m,p,v,ex,Run,_,log,vm,ok,ip # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata diff --git a/qubesmanager/log_dialog.py b/qubesmanager/log_dialog.py index 23af4b1..af96abf 100644 --- a/qubesmanager/log_dialog.py +++ b/qubesmanager/log_dialog.py @@ -20,10 +20,10 @@ # # -from PyQt4 import QtCore -from PyQt4 import QtGui +from PyQt4 import QtCore # pylint: disable=import-error +from PyQt4 import QtGui # pylint: disable=import-error -from . import ui_logdlg +from . import ui_logdlg # pylint: disable=no-name-in-module from . import clipboard # Display only this size of log @@ -31,6 +31,7 @@ LOG_DISPLAY_SIZE = 1024*1024 class LogDialog(ui_logdlg.Ui_LogDialog, QtGui.QDialog): + # pylint: disable=too-few-public-methods def __init__(self, app, log_path, parent=None): super(LogDialog, self).__init__(parent) @@ -43,7 +44,7 @@ class LogDialog(ui_logdlg.Ui_LogDialog, QtGui.QDialog): self.connect(self.copy_to_qubes_clipboard, QtCore.SIGNAL("clicked()"), - self.copy_to_qubes_clipboard_triggered) + self.copy_to_clipboard_triggered) self.__init_log_text__() @@ -61,5 +62,5 @@ class LogDialog(ui_logdlg.Ui_LogDialog, QtGui.QDialog): log.close() self.log_text.setPlainText(self.displayed_text) - def copy_to_qubes_clipboard_triggered(self): + def copy_to_clipboard_triggered(self): clipboard.copy_text_to_qubes_clipboard(self.displayed_text) diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py index cfa0127..9942977 100755 --- a/qubesmanager/qube_manager.py +++ b/qubesmanager/qube_manager.py @@ -31,11 +31,12 @@ from datetime import datetime, timedelta import traceback from qubesadmin import Qubes +from qubesadmin import exc -from PyQt4 import QtGui -from PyQt4 import QtCore +from PyQt4 import QtGui # pylint: disable=import-error +from PyQt4 import QtCore # pylint: disable=import-error -from . import ui_qubemanager +from . import ui_qubemanager # pylint: disable=no-name-in-module from . import thread_monitor from . import table_widgets from . import settings @@ -48,23 +49,17 @@ import threading from qubesmanager.about import AboutDialog -class QMVmState: - ErrorMsg = 1 - AudioRecAvailable = 2 - AudioRecAllowed = 3 - - class SearchBox(QtGui.QLineEdit): def __init__(self, parent=None): super(SearchBox, self).__init__(parent) self.focusing = False - def focusInEvent(self, e): + def focusInEvent(self, e): # pylint: disable=invalid-name super(SearchBox, self).focusInEvent(e) self.selectAll() self.focusing = True - def mousePressEvent(self, e): + def mousePressEvent(self, e): # pylint: disable=invalid-name super(SearchBox, self).mousePressEvent(e) if self.focusing: self.selectAll() @@ -72,6 +67,7 @@ class SearchBox(QtGui.QLineEdit): class VmRowInTable(object): + # pylint: disable=too-few-public-methods def __init__(self, vm, row_no, table): self.vm = vm @@ -86,13 +82,13 @@ class VmRowInTable(object): table.setCellWidget(row_no, VmManagerWindow.columns_indices['Type'], self.type_widget) table.setItem(row_no, VmManagerWindow.columns_indices['Type'], - self.type_widget.tableItem) + self.type_widget.table_item) self.label_widget = table_widgets.VmLabelWidget(vm) table.setCellWidget(row_no, VmManagerWindow.columns_indices['Label'], self.label_widget) table.setItem(row_no, VmManagerWindow.columns_indices['Label'], - self.label_widget.tableItem) + self.label_widget.table_item) self.name_widget = table_widgets.VmNameItem(vm) table.setItem(row_no, VmManagerWindow.columns_indices['Name'], @@ -102,7 +98,7 @@ class VmRowInTable(object): table.setCellWidget(row_no, VmManagerWindow.columns_indices['State'], self.info_widget) table.setItem(row_no, VmManagerWindow.columns_indices['State'], - self.info_widget.tableItem) + self.info_widget.table_item) self.template_widget = table_widgets.VmTemplateItem(vm) table.setItem(row_no, VmManagerWindow.columns_indices['Template'], @@ -213,6 +209,7 @@ class VmShutdownMonitor(QtCore.QObject): class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): + # pylint: disable=too-many-instance-attributes row_height = 30 column_width = 200 min_visible_rows = 10 @@ -233,6 +230,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): } def __init__(self, qubes_app, qt_app, parent=None): + # pylint: disable=unused-argument super(VmManagerWindow, self).__init__() self.setupUi(self) self.toolbar = self.toolBar @@ -259,9 +257,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): self.vms_in_table = {} self.reload_table = False - self.vm_errors = {} - self.vm_rec = {} - self.frame_width = 0 self.frame_height = 0 @@ -343,7 +338,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): self.connect( self.table.horizontalHeader(), QtCore.SIGNAL("sortIndicatorChanged(int, Qt::SortOrder)"), - self.sortIndicatorChanged) + self.sort_indicator_changed) self.connect(self.table, QtCore.SIGNAL("customContextMenuRequested(const QPoint&)"), self.open_context_menu) @@ -381,11 +376,11 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): def load_manager_settings(self): # visible columns - for col in self.columns_indices.keys(): + for col in self.columns_indices: col_no = self.columns_indices[col] visible = self.manager_settings.value( 'columns/%s' % col, - defaultValue=not self.table.isColumnHidden(col_no)) + defaultValue="true") self.columns_actions[col_no].setChecked(visible == "true") self.sort_by_column = str( @@ -393,8 +388,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): defaultValue=self.sort_by_column)) self.sort_order = QtCore.Qt.SortOrder( self.manager_settings.value("view/sort_order", - defaultValue=self.sort_order)[ - 0]) + defaultValue=self.sort_order)) self.table.sortItems(self.columns_indices[self.sort_by_column], self.sort_order) if not self.manager_settings.value("view/menubar_visible", @@ -478,13 +472,8 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): self.table_selection_changed() # noinspection PyPep8Naming - @QtCore.pyqtSlot(bool, str) - def recAllowedChanged(self, state, vmname): - self.vm_rec[str(vmname)] = bool(state) - - # noinspection PyPep8Naming - def sortIndicatorChanged(self, column, order): - self.sort_by_column = [name for name in self.columns_indices.keys() if + def sort_indicator_changed(self, column, order): + self.sort_by_column = [name for name in self.columns_indices if self.columns_indices[name] == column][0] self.sort_order = order if self.settings_loaded: @@ -546,29 +535,9 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): self.action_run_command_in_vm.setEnabled(False) self.action_set_keyboard_layout.setEnabled(False) - def set_error(self, qid, message): - for vm in self.vms_list: - if vm.qid == qid: - vm.qubes_manager_state[QMVmState.ErrorMsg] = message - # Store error in separate dict to make it immune to VM list reload - self.vm_errors[qid] = str(message) - - def clear_error(self, qid): - self.vm_errors.pop(qid, None) - for vm in self.vms_list: - if vm.qid == qid: - vm.qubes_manager_state[QMVmState.ErrorMsg] = None - - def clear_error_exact(self, qid, message): - for vm in self.vms_list: - if vm.qid == qid: - if vm.qubes_manager_state[QMVmState.ErrorMsg] == message: - vm.qubes_manager_state[QMVmState.ErrorMsg] = None - self.vm_errors.pop(qid, None) - # noinspection PyArgumentList @QtCore.pyqtSlot(name='on_action_createvm_triggered') - def action_createvm_triggered(self): + def action_createvm_triggered(self): # pylint: disable=no-self-use subprocess.check_call('qubes-vm-create') def get_selected_vm(self): @@ -665,7 +634,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): def do_remove_vm(vm, qubes_app, t_monitor): try: del qubes_app.domains[vm.name] - except Exception as ex: + except exc.QubesException as ex: t_monitor.set_error_msg(str(ex)) t_monitor.set_finished() @@ -721,7 +690,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): dst_vm = None try: dst_vm = qubes_app.clone_vm(src_vm, dst_name) - except Exception as ex: + except exc.QubesException as ex: t_monitor.set_error_msg(str(ex)) if dst_vm: pass @@ -735,7 +704,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): if vm.get_power_state() in ["Paused", "Suspended"]: try: vm.unpause() - except Exception as ex: + except exc.QubesException as ex: QtGui.QMessageBox.warning( None, self.tr("Error unpausing Qube!"), self.tr("ERROR: {0}").format(ex)) @@ -758,9 +727,10 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): time.sleep(0.1) if not t_monitor.success: - self.set_error( - vm.qid, - self.tr("Error starting Qube: %s") % t_monitor.error_msg) + QtGui.QMessageBox.warning( + None, + self.tr("Error starting Qube!"), + self.tr("ERROR: {0}").format(t_monitor.error_msg)) self.update_table() @@ -768,7 +738,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): def do_start_vm(vm, t_monitor): try: vm.start() - except Exception as ex: + except exc.QubesException as ex: t_monitor.set_error_msg(str(ex)) t_monitor.set_finished() return @@ -779,6 +749,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): @QtCore.pyqtSlot(name='on_action_startvm_tools_install_triggered') # TODO: replace with boot from device def action_startvm_tools_install_triggered(self): + # pylint: disable=invalid-name pass @QtCore.pyqtSlot(name='on_action_pausevm_triggered') @@ -788,7 +759,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): try: vm.pause() self.update_table() - except Exception as ex: + except exc.QubesException as ex: QtGui.QMessageBox.warning( None, self.tr("Error pausing Qube!"), @@ -819,7 +790,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): check_time=vm_restart_check_timeout, and_restart=False): try: vm.shutdown() - except Exception as ex: + except exc.QubesException as ex: QtGui.QMessageBox.warning( None, self.tr("Error shutting down Qube!"), @@ -873,7 +844,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): if reply == QtGui.QMessageBox.Yes: try: vm.force_shutdown() - except Exception as ex: + except exc.QubesException as ex: QtGui.QMessageBox.critical( None, self.tr("Error while killing Qube!"), self.tr( @@ -961,7 +932,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): vm.start() vm.run_service("qubes.InstallUpdatesGUI", user="root", wait=False) - except Exception as ex: + except (ChildProcessError, exc.QubesException) as ex: t_monitor.set_error_msg(str(ex)) t_monitor.set_finished() return @@ -970,6 +941,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): # noinspection PyArgumentList @QtCore.pyqtSlot(name='on_action_run_command_in_vm_triggered') def action_run_command_in_vm_triggered(self): + # pylint: disable=invalid-name vm = self.get_selected_vm() (command_to_run, ok) = QtGui.QInputDialog.getText( @@ -997,13 +969,14 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): def do_run_command_in_vm(vm, command_to_run, t_monitor): try: vm.run(command_to_run) - except Exception as ex: + except (ChildProcessError, exc.QubesException) as ex: t_monitor.set_error_msg(str(ex)) t_monitor.set_finished() # noinspection PyArgumentList @QtCore.pyqtSlot(name='on_action_set_keyboard_layout_triggered') def action_set_keyboard_layout_triggered(self): + # pylint: disable=invalid-name vm = self.get_selected_vm() vm.run('qubes-change-keyboard-layout') @@ -1016,7 +989,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): # noinspection PyArgumentList @QtCore.pyqtSlot(name='on_action_global_settings_triggered') - def action_global_settings_triggered(self): + def action_global_settings_triggered(self): # pylint: disable=invalid-name global_settings_window = global_settings.GlobalSettingsWindow( self.qt_app, self.qubes_app) @@ -1070,19 +1043,19 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): if self.visible_columns_count == 1: # disable hiding the last one - for c in self.columns_actions: - if self.columns_actions[c].isChecked(): - self.columns_actions[c].setEnabled(False) + for col in self.columns_actions: + if self.columns_actions[col].isChecked(): + self.columns_actions[col].setEnabled(False) break elif self.visible_columns_count == 2 and val == 1: # enable hiding previously disabled column - for c in self.columns_actions: - if not self.columns_actions[c].isEnabled(): - self.columns_actions[c].setEnabled(True) + for col in self.columns_actions: + if not self.columns_actions[col].isEnabled(): + self.columns_actions[col].setEnabled(True) break if self.settings_loaded: - col_name = [name for name in self.columns_indices.keys() if + col_name = [name for name in self.columns_indices if self.columns_indices[name] == col_num][0] self.manager_settings.setValue('columns/%s' % col_name, show) self.manager_settings.sync() @@ -1122,11 +1095,11 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): # noinspection PyArgumentList @QtCore.pyqtSlot(name='on_action_about_qubes_triggered') - def action_about_qubes_triggered(self): + def action_about_qubes_triggered(self): # pylint: disable=no-self-use about = AboutDialog() about.exec_() - def createPopupMenu(self): + def createPopupMenu(self): # pylint: disable=invalid-name menu = QtGui.QMenu() menu.addAction(self.action_toolbar) menu.addAction(self.action_menubar) diff --git a/qubesmanager/table_widgets.py b/qubesmanager/table_widgets.py index f72b278..7477dcc 100644 --- a/qubesmanager/table_widgets.py +++ b/qubesmanager/table_widgets.py @@ -19,8 +19,9 @@ # You should have received a copy of the GNU Lesser General Public License along # with this program; if not, see . -from PyQt4 import QtGui -from PyQt4 import QtCore +from PyQt4 import QtGui # pylint: disable=import-error +from PyQt4 import QtCore # pylint: disable=import-error +# pylint: disable=too-few-public-methods power_order = QtCore.Qt.DescendingOrder update_order = QtCore.Qt.AscendingOrder @@ -54,7 +55,7 @@ class VmIconWidget(QtGui.QWidget): layout.setContentsMargins(0, 0, 0, 0) self.setLayout(layout) - def setToolTip(self, tooltip): + def setToolTip(self, tooltip): # pylint: disable=invalid-name if tooltip is not None: self.label_icon.setToolTip(tooltip) else: @@ -78,15 +79,14 @@ class VmTypeWidget(VmIconWidget): return False elif self.value == other.value: return self.vm.name < other.vm.name - else: - return self.value < other.value + return self.value < other.value def __init__(self, vm, parent=None): (icon_path, tooltip) = self.get_vm_icon(vm) super(VmTypeWidget, self).__init__( icon_path, True, 0.8, tooltip, parent) self.vm = vm - self.tableItem = self.VmTypeItem(self.value, vm) + self.table_item = self.VmTypeItem(self.value, vm) self.value = None # TODO: add "provides network" column @@ -126,14 +126,13 @@ class VmLabelWidget(VmIconWidget): return False elif self.value == other.value: return self.vm.name < other.vm.name - else: - return self.value < other.value + return self.value < other.value def __init__(self, vm, parent=None): icon_path = self.get_vm_icon_path(vm) super(VmLabelWidget, self).__init__(icon_path, True, 0.8, None, parent) self.vm = vm - self.tableItem = self.VmLabelItem(self.value, vm) + self.table_item = self.VmLabelItem(self.value, vm) self.value = None def get_vm_icon_path(self, vm): @@ -141,7 +140,7 @@ class VmLabelWidget(VmIconWidget): return vm.label.icon -class VmNameItem (QtGui.QTableWidgetItem): +class VmNameItem(QtGui.QTableWidgetItem): def __init__(self, vm): super(VmNameItem, self).__init__() self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) @@ -186,14 +185,15 @@ class VmStatusIcon(QtGui.QLabel): self.setFixedSize(icon_sz) -class VmInfoWidget (QtGui.QWidget): - class VmInfoItem (QtGui.QTableWidgetItem): +class VmInfoWidget(QtGui.QWidget): + class VmInfoItem(QtGui.QTableWidgetItem): def __init__(self, upd_info_item, vm): super(VmInfoWidget.VmInfoItem, self).__init__() self.upd_info_item = upd_info_item self.vm = vm def __lt__(self, other): + # pylint: disable=too-many-return-statements if self.vm.qid == 0: return True elif other.vm.qid == 0: @@ -209,8 +209,7 @@ class VmInfoWidget (QtGui.QWidget): other_val += 1 if other.vm.is_running() else 0 if self_val == other_val: return self.vm.name < other.vm.name - else: - return self_val > other_val + return self_val > other_val elif self.tableWidget().\ horizontalHeader().sortIndicatorOrder() == power_order: # the result will be sorted by power state, @@ -221,8 +220,7 @@ class VmInfoWidget (QtGui.QWidget): 10*(1 if other.vm.is_running() else 0)) if self_val == other_val: return self.vm.name < other.vm.name - else: - return self_val > other_val + return self_val > other_val else: # it would be strange if this happened return @@ -254,14 +252,14 @@ class VmInfoWidget (QtGui.QWidget): self.blk_icon.setVisible(False) self.error_icon.setVisible(False) - self.tableItem = self.VmInfoItem(self.upd_info.tableItem, vm) + self.table_item = self.VmInfoItem(self.upd_info.table_item, vm) def update_vm_state(self, vm): self.on_icon.update() self.upd_info.update_outdated(vm) -class VmTemplateItem (QtGui.QTableWidgetItem): +class VmTemplateItem(QtGui.QTableWidgetItem): def __init__(self, vm): super(VmTemplateItem, self).__init__() self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) @@ -286,11 +284,10 @@ class VmTemplateItem (QtGui.QTableWidgetItem): return False elif self.text() == other.text(): return self.vm.name < other.vm.name - else: - return super(VmTemplateItem, self).__lt__(other) + return super(VmTemplateItem, self).__lt__(other) -class VmNetvmItem (QtGui.QTableWidgetItem): +class VmNetvmItem(QtGui.QTableWidgetItem): def __init__(self, vm): super(VmNetvmItem, self).__init__() self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) @@ -310,8 +307,7 @@ class VmNetvmItem (QtGui.QTableWidgetItem): return False elif self.text() == other.text(): return self.vm.name < other.vm.name - else: - return super(VmNetvmItem, self).__lt__(other) + return super(VmNetvmItem, self).__lt__(other) class VmInternalItem(QtGui.QTableWidgetItem): @@ -335,7 +331,7 @@ class VmInternalItem(QtGui.QTableWidgetItem): # features man qvm-features class VmUpdateInfoWidget(QtGui.QWidget): - class VmUpdateInfoItem (QtGui.QTableWidgetItem): + class VmUpdateInfoItem(QtGui.QTableWidgetItem): def __init__(self, value, vm): super(VmUpdateInfoWidget.VmUpdateInfoItem, self).__init__() self.value = 0 @@ -357,8 +353,7 @@ class VmUpdateInfoWidget(QtGui.QWidget): return False elif self.value == other.value: return self.vm.name < other.vm.name - else: - return self.value < other.value + return self.value < other.value def __init__(self, vm, show_text=True, parent=None): super(VmUpdateInfoWidget, self).__init__(parent) @@ -375,7 +370,7 @@ class VmUpdateInfoWidget(QtGui.QWidget): self.previous_outdated_state = None self.previous_update_recommended = None self.value = None - self.tableItem = VmUpdateInfoWidget.VmUpdateInfoItem(self.value, vm) + self.table_item = VmUpdateInfoWidget.VmUpdateInfoItem(self.value, vm) def update_outdated(self, vm): @@ -403,7 +398,7 @@ class VmUpdateInfoWidget(QtGui.QWidget): def update_status_widget(self, state): self.value = state - self.tableItem.set_value(state) + self.table_item.set_value(state) if state == "update": label_text = "Check updates" icon_path = ":/update-recommended.png" @@ -438,7 +433,7 @@ class VmUpdateInfoWidget(QtGui.QWidget): self.layout().addWidget(self.icon, alignment=QtCore.Qt.AlignCenter) -class VmSizeOnDiskItem (QtGui.QTableWidgetItem): +class VmSizeOnDiskItem(QtGui.QTableWidgetItem): def __init__(self, vm): super(VmSizeOnDiskItem, self).__init__() self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) @@ -463,8 +458,7 @@ class VmSizeOnDiskItem (QtGui.QTableWidgetItem): return False elif self.value == other.value: return self.vm.name < other.vm.name - else: - return self.value < other.value + return self.value < other.value class VmIPItem(QtGui.QTableWidgetItem): @@ -502,8 +496,7 @@ class VmIncludeInBackupsItem(QtGui.QTableWidgetItem): return False elif self.vm.include_in_backups == other.vm.include_in_backups: return self.vm.name < other.vm.name - else: - return self.vm.include_in_backups < other.vm.include_in_backups + return self.vm.include_in_backups < other.vm.include_in_backups class VmLastBackupItem(QtGui.QTableWidgetItem): @@ -528,5 +521,4 @@ class VmLastBackupItem(QtGui.QTableWidgetItem): return False elif not other.vm.backup_timestamp: return True - else: - return self.vm.backup_timestamp < other.vm.backup_timestamp + return self.vm.backup_timestamp < other.vm.backup_timestamp From e19793ea7aeb6b2548088672ce09f09f8b44e7c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Mon, 8 Jan 2018 04:00:48 +0100 Subject: [PATCH 62/70] Minor display fix Changed "size on disk" widget to display only two decimal places. --- qubesmanager/table_widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qubesmanager/table_widgets.py b/qubesmanager/table_widgets.py index 7477dcc..ba4b460 100644 --- a/qubesmanager/table_widgets.py +++ b/qubesmanager/table_widgets.py @@ -448,7 +448,7 @@ class VmSizeOnDiskItem(QtGui.QTableWidgetItem): self.setText("n/a") else: self.value = 10 - self.value = self.vm.get_disk_utilization()/(1024*1024) + self.value = round(self.vm.get_disk_utilization()/(1024*1024), 2) self.setText(str(self.value) + " MiB") def __lt__(self, other): From 1cee3a5fe36d368c914c56b2c4f8e45058545d2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Mon, 8 Jan 2018 04:10:56 +0100 Subject: [PATCH 63/70] Fixed 'About' window Surprisingly, it has not been touched since 3.2. It should work now. --- qubesmanager/about.py | 12 +++++------- qubesmanager/informationnotes.py | 17 +++++++++-------- qubesmanager/releasenotes.py | 11 +++++------ 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/qubesmanager/about.py b/qubesmanager/about.py index e28d920..b12ae36 100644 --- a/qubesmanager/about.py +++ b/qubesmanager/about.py @@ -1,6 +1,5 @@ -#!/usr/bin/python2 +#!/usr/bin/python3 # coding=utf-8 -# pylint: skip-file # # The Qubes OS Project, http://www.qubes-os.org # @@ -21,16 +20,15 @@ # with this program; if not, see . # # -from PyQt4.QtCore import SIGNAL, SLOT -from PyQt4.QtGui import QDialog, QIcon +from PyQt4.QtCore import SIGNAL, SLOT # pylint: disable=import-error +from PyQt4.QtGui import QDialog, QIcon # pylint: disable=import-error from qubesmanager.releasenotes import ReleaseNotesDialog from qubesmanager.informationnotes import InformationNotesDialog -from .ui_about import * +from . import ui_about # pylint: disable=no-name-in-module - -class AboutDialog(Ui_AboutDialog, QDialog): +class AboutDialog(ui_about.Ui_AboutDialog, QDialog): def __init__(self): super(AboutDialog, self).__init__() diff --git a/qubesmanager/informationnotes.py b/qubesmanager/informationnotes.py index d0b82f1..b512d3f 100644 --- a/qubesmanager/informationnotes.py +++ b/qubesmanager/informationnotes.py @@ -1,6 +1,5 @@ -#!/usr/bin/python2 +#!/usr/bin/python3 # coding=utf-8 -# pylint: skip-file # # The Qubes OS Project, http://www.qubes-os.org # @@ -21,17 +20,19 @@ # with this program; if not, see . # # -from PyQt4.QtCore import SIGNAL -from PyQt4.QtGui import QDialog, QIcon +from PyQt4.QtGui import QDialog # pylint: disable=import-error -from .ui_informationnotes import * +from . import ui_informationnotes # pylint: disable=no-name-in-module import subprocess -class InformationNotesDialog(Ui_InformationNotesDialog, QDialog): +class InformationNotesDialog(ui_informationnotes.Ui_InformationNotesDialog, + QDialog): + # pylint: disable=too-few-public-methods def __init__(self): super(InformationNotesDialog, self).__init__() self.setupUi(self) - details = subprocess.check_output(['/usr/libexec/qubes-manager/qvm_about.sh']) - self.informationNotes.setText(details) + details = subprocess.check_output( + ['/usr/libexec/qubes-manager/qvm_about.sh']) + self.informationNotes.setText(details.decode()) diff --git a/qubesmanager/releasenotes.py b/qubesmanager/releasenotes.py index da3f907..fd781d5 100644 --- a/qubesmanager/releasenotes.py +++ b/qubesmanager/releasenotes.py @@ -1,6 +1,5 @@ -#!/usr/bin/python2 +#!/usr/bin/python3 # coding=utf-8 -# pylint: skip-file # # The Qubes OS Project, http://www.qubes-os.org # @@ -21,13 +20,13 @@ # with this program; if not, see . # # -from PyQt4.QtCore import SIGNAL -from PyQt4.QtGui import QDialog, QIcon +from PyQt4.QtGui import QDialog # pylint: disable=import-error -from .ui_releasenotes import * +from . import ui_releasenotes # pylint: disable=no-name-in-module -class ReleaseNotesDialog(Ui_ReleaseNotesDialog, QDialog): +class ReleaseNotesDialog(ui_releasenotes.Ui_ReleaseNotesDialog, QDialog): + # pylint: disable=too-few-public-methods def __init__(self): super(ReleaseNotesDialog, self).__init__() From 839696f6f9e3250ff893ffeda98d3a4dd1281df0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Mon, 8 Jan 2018 20:00:55 +0100 Subject: [PATCH 64/70] Fixed missing import Fixed missing import in boot-from-cdrom tool. fixes QubesOS/qubes-issues#3421 --- qubesmanager/bootfromdevice.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qubesmanager/bootfromdevice.py b/qubesmanager/bootfromdevice.py index ab54b98..839104a 100644 --- a/qubesmanager/bootfromdevice.py +++ b/qubesmanager/bootfromdevice.py @@ -23,6 +23,7 @@ from . import utils from . import ui_bootfromdevice # pylint: disable=no-name-in-module from PyQt4 import QtGui, QtCore # pylint: disable=import-error from qubesadmin import tools +from qubesadmin.tools import qvm_start class VMBootFromDeviceWindow(ui_bootfromdevice.Ui_BootDialog, QtGui.QDialog): @@ -45,7 +46,6 @@ class VMBootFromDeviceWindow(ui_bootfromdevice.Ui_BootDialog, QtGui.QDialog): # populate buttons and such self.__init_buttons__() - def reject(self): self.done(0) @@ -62,7 +62,7 @@ class VMBootFromDeviceWindow(ui_bootfromdevice.Ui_BootDialog, QtGui.QDialog): self.tr("ERROR!"), self.tr("No file or block device selected; please select one.")) return - tools.qvm_start.main(['--cdrom', cdrom_location, self.vm.name]) + qvm_start.main(['--cdrom', cdrom_location, self.vm.name]) def __init_buttons__(self): self.fileVM.setEnabled(False) @@ -112,6 +112,7 @@ class VMBootFromDeviceWindow(ui_bootfromdevice.Ui_BootDialog, QtGui.QDialog): parser = tools.QubesArgumentParser(vmname_nargs=1) + def main(args=None): args = parser.parse_args(args) vm = args.domains.pop() From 45496f73dec46b012551877fd654bd6132659105 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Mon, 8 Jan 2018 20:05:25 +0100 Subject: [PATCH 65/70] Added warning that the VM is currently running Added warning that the VM is currently running. User still must shut it manually (by design). --- qubesmanager/bootfromdevice.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/qubesmanager/bootfromdevice.py b/qubesmanager/bootfromdevice.py index 839104a..518c7fb 100644 --- a/qubesmanager/bootfromdevice.py +++ b/qubesmanager/bootfromdevice.py @@ -45,6 +45,8 @@ class VMBootFromDeviceWindow(ui_bootfromdevice.Ui_BootDialog, QtGui.QDialog): # populate buttons and such self.__init_buttons__() + # warn user if the VM is currently running + self.__warn_if_running__() def reject(self): self.done(0) @@ -62,8 +64,21 @@ class VMBootFromDeviceWindow(ui_bootfromdevice.Ui_BootDialog, QtGui.QDialog): self.tr("ERROR!"), self.tr("No file or block device selected; please select one.")) return + + # warn user if the VM is currently running + self.__warn_if_running__() + qvm_start.main(['--cdrom', cdrom_location, self.vm.name]) + def __warn_if_running__(self): + if self.vm.is_running(): + QtGui.QMessageBox.warning( + None, + self.tr("Warning!"), + self.tr("VM must be turned off before booting it from" + "device. Please turn off the VM.") + ) + def __init_buttons__(self): self.fileVM.setEnabled(False) self.selectFileButton.setEnabled(False) From f86139d0d20e36b6fe2bf814a3d51ba584dbbba9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Mon, 8 Jan 2018 20:06:57 +0100 Subject: [PATCH 66/70] Renamed 'VM' to 'qube' in GUI For consistency with new terminology. --- qubesmanager/bootfromdevice.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qubesmanager/bootfromdevice.py b/qubesmanager/bootfromdevice.py index 518c7fb..458abef 100644 --- a/qubesmanager/bootfromdevice.py +++ b/qubesmanager/bootfromdevice.py @@ -75,8 +75,8 @@ class VMBootFromDeviceWindow(ui_bootfromdevice.Ui_BootDialog, QtGui.QDialog): QtGui.QMessageBox.warning( None, self.tr("Warning!"), - self.tr("VM must be turned off before booting it from" - "device. Please turn off the VM.") + self.tr("Qube must be turned off before booting it from" + "device. Please turn off the qube.") ) def __init_buttons__(self): @@ -135,7 +135,7 @@ def main(args=None): qapp = QtGui.QApplication(sys.argv) qapp.setOrganizationName('Invisible Things Lab') qapp.setOrganizationDomain("https://www.qubes-os.org/") - qapp.setApplicationName("Qubes VM Settings") + qapp.setApplicationName("Boot Qube From Device") # if not utils.is_debug(): #FIXME # sys.excepthook = handle_exception From b1dc2458f63f9575feec24100cce7b74524881d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Wed, 10 Jan 2018 01:54:30 +0100 Subject: [PATCH 67/70] Code review changes As requested by @marmarek --- qubesmanager/log_dialog.py | 7 ++++--- qubesmanager/qube_manager.py | 33 ++++++++++++--------------------- qubesmanager/table_widgets.py | 1 - ui/qubemanager.ui | 2 +- 4 files changed, 17 insertions(+), 26 deletions(-) diff --git a/qubesmanager/log_dialog.py b/qubesmanager/log_dialog.py index af96abf..0f4232b 100644 --- a/qubesmanager/log_dialog.py +++ b/qubesmanager/log_dialog.py @@ -25,6 +25,7 @@ from PyQt4 import QtGui # pylint: disable=import-error from . import ui_logdlg # pylint: disable=no-name-in-module from . import clipboard +import os # Display only this size of log LOG_DISPLAY_SIZE = 1024*1024 @@ -51,13 +52,13 @@ class LogDialog(ui_logdlg.Ui_LogDialog, QtGui.QDialog): def __init_log_text__(self): self.displayed_text = "" log = open(self.log_path) - log.seek(0, clipboard.os.SEEK_END) + log.seek(0, os.SEEK_END) if log.tell() > LOG_DISPLAY_SIZE: self.displayed_text = self.tr( "(Showing only last %d bytes of file)\n") % LOG_DISPLAY_SIZE - log.seek(-LOG_DISPLAY_SIZE, clipboard.os.SEEK_END) + log.seek(-LOG_DISPLAY_SIZE, os.SEEK_END) else: - log.seek(0, clipboard.os.SEEK_SET) + log.seek(0, os.SEEK_SET) self.displayed_text += log.read() log.close() self.log_text.setPlainText(self.displayed_text) diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py index 9942977..9cb6ef1 100755 --- a/qubesmanager/qube_manager.py +++ b/qubesmanager/qube_manager.py @@ -174,8 +174,11 @@ class VmShutdownMonitor(QtCore.QObject): def check_if_vm_has_shutdown(self): vm = self.vm vm_is_running = vm.is_running() - vm_start_time = datetime.fromtimestamp( - float(getattr(vm, 'start_time', None))) + try: + vm_start_time = datetime.fromtimestamp(float(vm.start_time)) + except AttributeError: + vm_start_time = None + if vm_is_running and vm_start_time \ and vm_start_time < self.shutdown_started: if self.timeout_reached(): @@ -233,7 +236,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): # pylint: disable=unused-argument super(VmManagerWindow, self).__init__() self.setupUi(self) - self.toolbar = self.toolBar self.manager_settings = QtCore.QSettings(self) @@ -278,16 +280,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): } self.visible_columns_count = len(self.columns_indices) - self.table.setColumnHidden(self.columns_indices["Size"], True) - self.action_size_on_disk.setChecked(False) - self.table.setColumnHidden(self.columns_indices["Internal"], True) - self.action_internal.setChecked(False) - self.table.setColumnHidden(self.columns_indices["IP"], True) - self.action_ip.setChecked(False) - self.table.setColumnHidden(self.columns_indices["Backups"], True) - self.action_backups.setChecked(False) - self.table.setColumnHidden(self.columns_indices["Last backup"], True) - self.action_last_backup.setChecked(False) self.table.setColumnWidth(self.columns_indices["State"], 80) self.table.setColumnWidth(self.columns_indices["Name"], 150) @@ -333,8 +325,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): self.tools_context_menu.addAction(self.action_toolbar) self.tools_context_menu.addAction(self.action_menubar) - self.table_selection_changed() - self.connect( self.table.horizontalHeader(), QtCore.SIGNAL("sortIndicatorChanged(int, Qt::SortOrder)"), @@ -346,9 +336,9 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): QtCore.SIGNAL("customContextMenuRequested(const QPoint&)"), lambda pos: self.open_tools_context_menu(self.menubar, pos)) - self.connect(self.toolBar, + self.connect(self.toolbar, QtCore.SIGNAL("customContextMenuRequested(const QPoint&)"), - lambda pos: self.open_tools_context_menu(self.toolBar, + lambda pos: self.open_tools_context_menu(self.toolbar, pos)) self.connect(self.logs_menu, QtCore.SIGNAL("triggered(QAction *)"), self.show_log) @@ -376,12 +366,14 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): def load_manager_settings(self): # visible columns + self.visible_columns_count = 0 for col in self.columns_indices: col_no = self.columns_indices[col] visible = self.manager_settings.value( 'columns/%s' % col, defaultValue="true") self.columns_actions[col_no].setChecked(visible == "true") + self.visible_columns_count += 1 self.sort_by_column = str( self.manager_settings.value("view/sort_column", @@ -414,7 +406,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): self.table.setSortingEnabled(False) self.table.clearContents() vms_list = self.get_vms_list() - self.table.setRowCount(len(vms_list)) vms_in_table = {} @@ -444,7 +435,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): for row_no in range(self.table.rowCount()): widget = self.table.cellWidget(row_no, self.columns_indices["State"]) - show = (self.search in widget.vm.name or not self.search) + show = (self.search in widget.vm.name) self.table.setRowHidden(row_no, not show) @QtCore.pyqtSlot(str) @@ -504,13 +495,13 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): and vm.klass != 'AdminVM') self.action_restartvm.setEnabled( vm.is_running() and vm.get_power_state() != "Paused" - and vm.klass != 'AdminVM' and vm.klass != 'DisposableVM') + and vm.klass != 'AdminVM' and vm.klass != 'DispVM') self.action_killvm.setEnabled( (vm.get_power_state() == "Paused" or vm.is_running()) and vm.klass != 'AdminVM') self.action_appmenus.setEnabled( - vm.klass != 'AdminVM' and vm.klass != 'DisposableMV' + vm.klass != 'AdminVM' and vm.klass != 'DispVM' and not vm.features.get('internal', False)) self.action_editfwrules.setEnabled(vm.klass != 'AdminVM') self.action_updatevm.setEnabled(getattr(vm, 'updateable', False) diff --git a/qubesmanager/table_widgets.py b/qubesmanager/table_widgets.py index ba4b460..06ec486 100644 --- a/qubesmanager/table_widgets.py +++ b/qubesmanager/table_widgets.py @@ -164,7 +164,6 @@ class VmStatusIcon(QtGui.QLabel): self.previous_power_state = self.vm.get_power_state() def update(self): - self.previous_power_state = self.vm.get_power_state() if self.previous_power_state != self.vm.get_power_state(): self.set_on_icon() self.previous_power_state = self.vm.get_power_state() diff --git a/ui/qubemanager.ui b/ui/qubemanager.ui index 150dafb..4a2cdc9 100644 --- a/ui/qubemanager.ui +++ b/ui/qubemanager.ui @@ -335,7 +335,7 @@ - + Qt::CustomContextMenu From 5c2f3b425eff902fccf3f1bdbbe2f572693a8b09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Fri, 12 Jan 2018 04:09:25 +0100 Subject: [PATCH 68/70] Added .desktop file to rpm spec Because I forgot it the first time round. Sorry. --- rpm_spec/qmgr.spec | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rpm_spec/qmgr.spec b/rpm_spec/qmgr.spec index f0d8297..877caaa 100644 --- a/rpm_spec/qmgr.spec +++ b/rpm_spec/qmgr.spec @@ -45,7 +45,7 @@ cp qubes-global-settings.desktop $RPM_BUILD_ROOT/usr/share/applications/ cp qubes-vm-create.desktop $RPM_BUILD_ROOT/usr/share/applications/ cp qubes-backup.desktop $RPM_BUILD_ROOT/usr/share/applications/ cp qubes-backup-restore.desktop $RPM_BUILD_ROOT/usr/share/applications/ - +cp qubes-qube-manager.desktop $RPM_BUILD_ROOT/usr/share/applications/ %post update-desktop-database &> /dev/null || : @@ -117,3 +117,4 @@ rm -rf $RPM_BUILD_ROOT /usr/share/applications/qubes-vm-create.desktop /usr/share/applications/qubes-backup.desktop /usr/share/applications/qubes-backup-restore.desktop +/usr/share/applications/qubes-qube-manager.desktop From 670c41e4ffdf5d2fc560d6ab325509847a852abe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Marczykowska-G=C3=B3recka?= Date: Fri, 12 Jan 2018 04:30:47 +0100 Subject: [PATCH 69/70] Fixed minor error in VM Settings When init page was 'firewall', the tool incorrectly displayed warnings about wrong firewall vm configuration, without actually checking if they should be displayed. --- qubesmanager/settings.py | 54 +++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index 7f41bab..0c66754 100755 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -114,6 +114,8 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): self.delete_rule_button_pressed) self.policy_deny_radio_button.clicked.connect(self.policy_changed) self.policy_allow_radio_button.clicked.connect(self.policy_changed) + if init_page == 'firewall': + self.check_network_availability() ####### devices tab self.__init_devices_tab__() @@ -217,36 +219,38 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): t_monitor.set_finished() - def current_tab_changed(self, idx): - if idx == self.tabs_indices["firewall"]: - netvm = self.vm.netvm - self.no_netvm_label.setVisible(netvm is None) - self.netvm_no_firewall_label.setVisible( - netvm is not None and - not netvm.features.check_with_template('qubes-firewall', False)) - if netvm is None: - QtGui.QMessageBox.warning( - None, - self.tr("Qube configuration problem!"), - self.tr('This qube has networking disabled ' - '(Basic -> Networking) - network will be disabled. ' - 'If you want to use firewall, ' - 'please enable networking.') - ) - if netvm is not None and \ - not netvm.features.check_with_template( - 'qubes-firewall', - False): - QtGui.QMessageBox.warning( - None, - self.tr("Qube configuration problem!"), - self.tr("The '{vm}' AppVM is network connected to " + def check_network_availability(self): + netvm = self.vm.netvm + self.no_netvm_label.setVisible(netvm is None) + self.netvm_no_firewall_label.setVisible( + netvm is not None and + not netvm.features.check_with_template('qubes-firewall', False)) + if netvm is None: + QtGui.QMessageBox.warning( + None, + self.tr("Qube configuration problem!"), + self.tr('This qube has networking disabled ' + '(Basic -> Networking) - network will be disabled. ' + 'If you want to use firewall, ' + 'please enable networking.') + ) + if netvm is not None and \ + not netvm.features.check_with_template( + 'qubes-firewall', + False): + QtGui.QMessageBox.warning( + None, + self.tr("Qube configuration problem!"), + self.tr("The '{vm}' AppVM is network connected to " "'{netvm}', which does not support firewall!
" "You may edit the '{vm}' VM firewall rules, but these " "will not take any effect until you connect it to " "a working Firewall VM.").format( - vm=self.vm.name, netvm=netvm.name)) + vm=self.vm.name, netvm=netvm.name)) + def current_tab_changed(self, idx): + if idx == self.tabs_indices["firewall"]: + self.check_network_availability() ######### basic tab From 35c0e12008dbc7eec46ca5b69ca4cd6e66179b2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Fri, 12 Jan 2018 06:20:28 +0100 Subject: [PATCH 70/70] version 4.0.10 --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index 7919852..2d2d681 100644 --- a/version +++ b/version @@ -1 +1 @@ -4.0.9 +4.0.10