From 555210630659018785fdb9d2318081a76b49fb4c Mon Sep 17 00:00:00 2001 From: kali Date: Thu, 31 Jan 2013 05:26:18 +0900 Subject: actually merge the release/v0.2.0 branch! My life has been a lie until this moment... I had done: git merge -s ours release/v0.2.0 to avoid deleting the debian folder... but that left the src untouched... Now I just rm'd the src folder and did a git checkout release/v0.2.0 src/ ... and merge happy! :) --- src/leap/baseapp/mainwindow.py | 596 +++++++++++------------------------------ 1 file changed, 160 insertions(+), 436 deletions(-) (limited to 'src/leap/baseapp/mainwindow.py') diff --git a/src/leap/baseapp/mainwindow.py b/src/leap/baseapp/mainwindow.py index 85129a9b..91b0dc61 100644 --- a/src/leap/baseapp/mainwindow.py +++ b/src/leap/baseapp/mainwindow.py @@ -1,467 +1,191 @@ # vim: set fileencoding=utf-8 : #!/usr/bin/env python import logging -import time -logger = logging.getLogger(name=__name__) - -from PyQt4.QtGui import (QMainWindow, QWidget, QVBoxLayout, QMessageBox, - QSystemTrayIcon, QGroupBox, QLabel, QPixmap, - QHBoxLayout, QIcon, - QPushButton, QGridLayout, QAction, QMenu, - QTextBrowser, qApp) -from PyQt4.QtCore import (pyqtSlot, pyqtSignal, QTimer) -from leap.baseapp.dialogs import ErrorDialog -from leap.eip.conductor import (EIPConductor, - EIPNoCommandError) +import sip +sip.setapi('QString', 2) +sip.setapi('QVariant', 2) -from leap.eip.config import (EIPInitBadKeyFilePermError) -# from leap.eip import exceptions as eip_exceptions +from PyQt4 import QtCore +from PyQt4 import QtGui -from leap.gui import mainwindow_rc +from leap.baseapp.eip import EIPConductorAppMixin +from leap.baseapp.log import LogPaneMixin +from leap.baseapp.systray import StatusAwareTrayIconMixin +from leap.baseapp.network import NetworkCheckerAppMixin +from leap.baseapp.leap_app import MainWindowMixin +from leap.eip.checks import ProviderCertChecker +from leap.gui.threads import FunThread +logger = logging.getLogger(name=__name__) -class LeapWindow(QMainWindow): - #XXX tbd: refactor into model / view / controller - #and put in its own modules... - newLogLine = pyqtSignal([str]) - statusChange = pyqtSignal([object]) +class LeapWindow(QtGui.QMainWindow, + MainWindowMixin, EIPConductorAppMixin, + StatusAwareTrayIconMixin, + NetworkCheckerAppMixin, + LogPaneMixin): + """ + main window for the leap app. + Initializes all of its base classes + We keep here some signal initialization + that gets tricky otherwise. + """ + + # signals + + newLogLine = QtCore.pyqtSignal([str]) + mainappReady = QtCore.pyqtSignal([]) + initReady = QtCore.pyqtSignal([]) + networkError = QtCore.pyqtSignal([object]) + triggerEIPError = QtCore.pyqtSignal([object]) + start_eipconnection = QtCore.pyqtSignal([]) + shutdownSignal = QtCore.pyqtSignal([]) + initNetworkChecker = QtCore.pyqtSignal([]) + + # this is status change got from openvpn management + openvpnStatusChange = QtCore.pyqtSignal([object]) + # this is global eip status + eipStatusChange = QtCore.pyqtSignal([str]) def __init__(self, opts): - super(LeapWindow, self).__init__() + logger.debug('init leap window') self.debugmode = getattr(opts, 'debug', False) - - self.vpn_service_started = False - - self.createWindowHeader() - self.createIconGroupBox() - - self.createActions() - self.createTrayIcon() + super(LeapWindow, self).__init__() if self.debugmode: self.createLogBrowser() - # create timer - self.timer = QTimer() - - # bind signals + settings = QtCore.QSettings() + self.provider_domain = settings.value("provider_domain", None) + self.username = settings.value("username", None) - self.trayIcon.activated.connect(self.iconActivated) - self.newLogLine.connect(self.onLoggerNewLine) - self.statusChange.connect(self.onStatusChange) - self.timer.timeout.connect(self.onTimerTick) + logger.debug('provider: %s', self.provider_domain) + logger.debug('username: %s', self.username) - widget = QWidget() - self.setCentralWidget(widget) + provider = self.provider_domain + EIPConductorAppMixin.__init__( + self, opts=opts, provider=provider) + StatusAwareTrayIconMixin.__init__(self) - # add widgets to layout - mainLayout = QVBoxLayout() - mainLayout.addWidget(self.headerBox) - mainLayout.addWidget(self.statusIconBox) - if self.debugmode: - mainLayout.addWidget(self.statusBox) - mainLayout.addWidget(self.loggerBox) - widget.setLayout(mainLayout) + # XXX network checker should probably not + # trigger run_checks on init... but wait + # for ready signal instead... + NetworkCheckerAppMixin.__init__(self, provider=provider) + MainWindowMixin.__init__(self) - self.trayIcon.show() - config_file = getattr(opts, 'config_file', None) + geom_key = "DebugGeometry" if self.debugmode else "Geometry" + geom = settings.value(geom_key) + if geom: + self.restoreGeometry(geom) - # - # conductor is in charge of all - # vpn-related configuration / monitoring. - # we pass a tuple of signals that will be - # triggered when status changes. - # - self.conductor = EIPConductor( - watcher_cb=self.newLogLine.emit, - config_file=config_file, - status_signals=(self.statusChange.emit, ), - debug=self.debugmode) + # XXX check for wizard + self.wizard_done = settings.value("FirstRunWizardDone") - # - # bunch of self checks. - # XXX move somewhere else alltogether. - # + self.initchecks = FunThread(self.run_eip_checks) - if self.conductor.missing_provider is True: - dialog = ErrorDialog() - dialog.criticalMessage( - 'Missing provider. Add a remote_ip entry ' - 'under section [provider] in eip.cfg', - 'error') - - if self.conductor.missing_vpn_keyfile is True: - dialog = ErrorDialog() - dialog.criticalMessage( - 'Could not find the vpn keys file', - 'error') - - # ... btw, review pending. - # os.kill of subprocess fails if we have - # some of this errors. - - if self.conductor.bad_provider is True: - dialog = ErrorDialog() - dialog.criticalMessage( - 'Bad provider entry. Check that remote_ip entry ' - 'has an IP under section [provider] in eip.cfg', - 'error') - - if self.conductor.bad_keyfile_perms is True: - dialog = ErrorDialog() - dialog.criticalMessage( - 'The vpn keys file has bad permissions', - 'error') - - if self.conductor.missing_auth_agent is True: - dialog = ErrorDialog() - dialog.warningMessage( - 'We could not find any authentication ' - 'agent in your system.
' - 'Make sure you have ' - 'polkit-gnome-authentication-agent-1 ' - 'running and try again.', - 'error') - - if self.conductor.missing_pkexec is True: - dialog = ErrorDialog() - dialog.warningMessage( - 'We could not find pkexec in your ' - 'system.
Do you want to try ' - 'setuid workaround? ' - '(DOES NOTHING YET)', - 'error') - - self.setWindowTitle("LEAP Client") - self.resize(400, 300) - - self.set_statusbarMessage('ready') - - if self.conductor.autostart: - self.start_or_stopVPN() + # bind signals + self.initchecks.finished.connect( + lambda: logger.debug('Initial checks thread finished')) + self.trayIcon.activated.connect(self.iconActivated) + self.newLogLine.connect( + lambda line: self.onLoggerNewLine(line)) + self.timer.timeout.connect( + lambda: self.onTimerTick()) + self.networkError.connect( + lambda exc: self.onNetworkError(exc)) + self.triggerEIPError.connect( + lambda exc: self.onEIPError(exc)) - def closeEvent(self, event): - """ - redefines close event (persistent window behaviour) - """ - if self.trayIcon.isVisible() and not self.debugmode: - QMessageBox.information(self, "Systray", - "The program will keep running " - "in the system tray. To " - "terminate the program, choose " - "Quit in the " - "context menu of the system tray entry.") - self.hide() - event.ignore() if self.debugmode: + self.startStopButton.clicked.connect( + lambda: self.start_or_stopVPN()) + self.start_eipconnection.connect( + self.do_start_eipconnection) + self.shutdownSignal.connect( + self.cleanupAndQuit) + self.initNetworkChecker.connect( + lambda: self.init_network_checker(self.conductor.provider)) + + # status change. + # TODO unify + self.openvpnStatusChange.connect( + lambda status: self.onOpenVPNStatusChange(status)) + self.eipStatusChange.connect( + lambda newstatus: self.onEIPConnStatusChange(newstatus)) + self.eipStatusChange.connect( + lambda newstatus: self.toggleEIPAct()) + + # do first run wizard and init signals + self.mainappReady.connect(self.do_first_run_wizard_check) + self.initReady.connect(self.runchecks_and_eipconnect) + + # ... all ready. go! + # connected to do_first_run_wizard_check + self.mainappReady.emit() + + def do_first_run_wizard_check(self): + """ + checks whether first run wizard needs to be run + launches it if needed + and emits initReady signal if not. + """ + + logger.debug('first run wizard check...') + need_wizard = False + + # do checks (can overlap if wizard was interrupted) + if not self.wizard_done: + need_wizard = True + + if not self.provider_domain: + need_wizard = True + else: + pcertchecker = ProviderCertChecker(domain=self.provider_domain) + if not pcertchecker.is_cert_valid(do_raise=False): + logger.warning('missing valid client cert. need wizard') + need_wizard = True + + # launch wizard if needed + if need_wizard: + logger.debug('running first run wizard') + self.launch_first_run_wizard() + else: # no wizard needed + self.initReady.emit() + + def launch_first_run_wizard(self): + """ + launches wizard and blocks + """ + from leap.gui.firstrun.wizard import FirstRunWizard + wizard = FirstRunWizard( + self.conductor, + parent=self, + username=self.username, + start_eipconnection_signal=self.start_eipconnection, + eip_statuschange_signal=self.eipStatusChange, + quitcallback=self.onWizardCancel) + wizard.show() + + def onWizardCancel(self): + if not self.wizard_done: + logger.debug( + 'clicked on Cancel during first ' + 'run wizard. shutting down') self.cleanupAndQuit() - def setIcon(self, name): - icon = self.Icons.get(name) - self.trayIcon.setIcon(icon) - self.setWindowIcon(icon) - - def setToolTip(self): - """ - get readable status and place it on systray tooltip - """ - status = self.conductor.status.get_readable_status() - self.trayIcon.setToolTip(status) - - def iconActivated(self, reason): - """ - handles left click, left double click - showing the trayicon menu - """ - #XXX there's a bug here! - #menu shows on (0,0) corner first time, - #until double clicked at least once. - if reason in (QSystemTrayIcon.Trigger, - QSystemTrayIcon.DoubleClick): - self.trayIconMenu.show() - - def createWindowHeader(self): - """ - description lines for main window - """ - #XXX good candidate to refactor out! :) - self.headerBox = QGroupBox() - self.headerLabel = QLabel("Encryption \ -Internet Proxy") - self.headerLabelSub = QLabel("trust your \ -technolust") - - pixmap = QPixmap(':/images/leapfrog.jpg') - frog_lbl = QLabel() - frog_lbl.setPixmap(pixmap) - - headerLayout = QHBoxLayout() - headerLayout.addWidget(frog_lbl) - headerLayout.addWidget(self.headerLabel) - headerLayout.addWidget(self.headerLabelSub) - headerLayout.addStretch() - self.headerBox.setLayout(headerLayout) - - def getIcon(self, icon_name): - # XXX get from connection dict - icons = {'disconnected': 0, - 'connecting': 1, - 'connected': 2} - return icons.get(icon_name, None) - - def createIconGroupBox(self): - """ - dummy icongroupbox - (to be removed from here -- reference only) + def runchecks_and_eipconnect(self): """ - icons = { - 'disconnected': ':/images/conn_error.png', - 'connecting': ':/images/conn_connecting.png', - 'connected': ':/images/conn_connected.png' - } - con_widgets = { - 'disconnected': QLabel(), - 'connecting': QLabel(), - 'connected': QLabel(), - } - con_widgets['disconnected'].setPixmap( - QPixmap(icons['disconnected'])) - con_widgets['connecting'].setPixmap( - QPixmap(icons['connecting'])) - con_widgets['connected'].setPixmap( - QPixmap(icons['connected'])), - self.ConnectionWidgets = con_widgets - - con_icons = { - 'disconnected': QIcon(icons['disconnected']), - 'connecting': QIcon(icons['connecting']), - 'connected': QIcon(icons['connected']) - } - self.Icons = con_icons - - self.statusIconBox = QGroupBox("Connection Status") - statusIconLayout = QHBoxLayout() - statusIconLayout.addWidget(self.ConnectionWidgets['disconnected']) - statusIconLayout.addWidget(self.ConnectionWidgets['connecting']) - statusIconLayout.addWidget(self.ConnectionWidgets['connected']) - statusIconLayout.itemAt(1).widget().hide() - statusIconLayout.itemAt(2).widget().hide() - self.statusIconBox.setLayout(statusIconLayout) - - def createActions(self): - """ - creates actions to be binded to tray icon - """ - self.connectVPNAction = QAction("Connect to &VPN", self, - triggered=self.hide) - # XXX change action name on (dis)connect - self.dis_connectAction = QAction("&(Dis)connect", self, - triggered=self.start_or_stopVPN) - self.minimizeAction = QAction("Mi&nimize", self, - triggered=self.hide) - self.maximizeAction = QAction("Ma&ximize", self, - triggered=self.showMaximized) - self.restoreAction = QAction("&Restore", self, - triggered=self.showNormal) - self.quitAction = QAction("&Quit", self, - triggered=self.cleanupAndQuit) - - def createTrayIcon(self): + shows icon and run init checks """ - creates the tray icon - """ - self.trayIconMenu = QMenu(self) - - self.trayIconMenu.addAction(self.connectVPNAction) - self.trayIconMenu.addAction(self.dis_connectAction) - self.trayIconMenu.addSeparator() - self.trayIconMenu.addAction(self.minimizeAction) - self.trayIconMenu.addAction(self.maximizeAction) - self.trayIconMenu.addAction(self.restoreAction) - self.trayIconMenu.addSeparator() - self.trayIconMenu.addAction(self.quitAction) - - self.trayIcon = QSystemTrayIcon(self) - self.setIcon('disconnected') - self.trayIcon.setContextMenu(self.trayIconMenu) - - def createLogBrowser(self): - """ - creates Browser widget for displaying logs - (in debug mode only). - """ - self.loggerBox = QGroupBox() - logging_layout = QVBoxLayout() - self.logbrowser = QTextBrowser() - - startStopButton = QPushButton("&Connect") - startStopButton.clicked.connect(self.start_or_stopVPN) - self.startStopButton = startStopButton - - logging_layout.addWidget(self.logbrowser) - logging_layout.addWidget(self.startStopButton) - self.loggerBox.setLayout(logging_layout) - - # status box - - self.statusBox = QGroupBox() - grid = QGridLayout() - - self.updateTS = QLabel('') - self.status_label = QLabel('Disconnected') - self.ip_label = QLabel('') - self.remote_label = QLabel('') - - tun_read_label = QLabel("tun read") - self.tun_read_bytes = QLabel("0") - tun_write_label = QLabel("tun write") - self.tun_write_bytes = QLabel("0") - - grid.addWidget(self.updateTS, 0, 0) - grid.addWidget(self.status_label, 0, 1) - grid.addWidget(self.ip_label, 1, 0) - grid.addWidget(self.remote_label, 1, 1) - grid.addWidget(tun_read_label, 2, 0) - grid.addWidget(self.tun_read_bytes, 2, 1) - grid.addWidget(tun_write_label, 3, 0) - grid.addWidget(self.tun_write_bytes, 3, 1) - - self.statusBox.setLayout(grid) - - @pyqtSlot(str) - def onLoggerNewLine(self, line): - """ - simple slot: writes new line to logger Pane. - """ - if self.debugmode: - self.logbrowser.append(line[:-1]) - - def set_statusbarMessage(self, msg): - self.statusBar().showMessage(msg) - - @pyqtSlot(object) - def onStatusChange(self, status): - """ - slot for status changes. triggers new signals for - updating icon, status bar, etc. - """ - - #print('STATUS CHANGED! (on Qt-land)') - #print('%s -> %s' % (status.previous, status.current)) - icon_name = self.conductor.get_icon_name() - self.setIcon(icon_name) - #print 'icon = ', icon_name - - # change connection pixmap widget - self.setConnWidget(icon_name) - - def setConnWidget(self, icon_name): - #print 'changing icon to %s' % icon_name - oldlayout = self.statusIconBox.layout() - - # XXX reuse with icons - # XXX move states to StateWidget - states = {"disconnected": 0, - "connecting": 1, - "connected": 2} - - for i in range(3): - oldlayout.itemAt(i).widget().hide() - new = states[icon_name] - oldlayout.itemAt(new).widget().show() - - @pyqtSlot() - def start_or_stopVPN(self): - """ - stub for running child process with vpn - """ - if self.vpn_service_started is False: - try: - self.conductor.connect() - except EIPNoCommandError: - dialog = ErrorDialog() - dialog.warningMessage( - 'No suitable openvpn command found. ' - '
(Might be a permissions problem)', - 'error') - if self.debugmode: - self.startStopButton.setText('&Disconnect') - self.vpn_service_started = True - - # XXX what is optimum polling interval? - # too little is overkill, too much - # will miss transition states.. - - self.timer.start(250.0) - return - if self.vpn_service_started is True: - self.conductor.disconnect() - # FIXME this should trigger also - # statuschange event. why isn't working?? - if self.debugmode: - self.startStopButton.setText('&Connect') - self.vpn_service_started = False - self.timer.stop() - return - - @pyqtSlot() - def onTimerTick(self): - self.statusUpdate() - - @pyqtSlot() - def statusUpdate(self): - """ - called on timer tick - polls status and updates ui with real time - info about transferred bytes / connection state. - """ - # XXX it's too expensive to poll - # continously. move to signal events instead. - - if not self.vpn_service_started: - return - - # XXX remove all access to manager layer - # from here. - if self.conductor.manager.with_errors: - #XXX how to wait on pkexec??? - #something better that this workaround, plz!! - time.sleep(10) - print('errors. disconnect.') - self.start_or_stopVPN() # is stop - - state = self.conductor.poll_connection_state() - if not state: - return - - ts, con_status, ok, ip, remote = state - self.set_statusbarMessage(con_status) - self.setToolTip() - - ts = time.strftime("%a %b %d %X", ts) - if self.debugmode: - self.updateTS.setText(ts) - self.status_label.setText(con_status) - self.ip_label.setText(ip) - self.remote_label.setText(remote) - - # status i/o - - status = self.conductor.manager.get_status_io() - if status and self.debugmode: - #XXX move this to systray menu indicators - ts, (tun_read, tun_write, tcp_read, tcp_write, auth_read) = status - ts = time.strftime("%a %b %d %X", ts) - self.updateTS.setText(ts) - self.tun_read_bytes.setText(tun_read) - self.tun_write_bytes.setText(tun_write) + self.show_systray_icon() + self.initchecks.begin() - def cleanupAndQuit(self): + def do_start_eipconnection(self): """ - cleans state before shutting down app. + shows icon and init eip connection + called from the end of wizard """ - # TODO:make sure to shutdown all child process / threads - # in conductor - self.conductor.cleanup() - qApp.quit() + self.show_systray_icon() + # this will setup the command + self.conductor.run_openvpn_checks() + self.start_or_stopVPN() -- cgit v1.2.3