diff options
| -rw-r--r-- | src/leap/baseapp/mainwindow.py | 506 | 
1 files changed, 270 insertions, 236 deletions
| diff --git a/src/leap/baseapp/mainwindow.py b/src/leap/baseapp/mainwindow.py index 2f7a14dd..ca9b79b3 100644 --- a/src/leap/baseapp/mainwindow.py +++ b/src/leap/baseapp/mainwindow.py @@ -2,7 +2,9 @@  #!/usr/bin/env python  import logging  import time +logging.basicConfig()  logger = logging.getLogger(name=__name__) +logger.setLevel(logging.DEBUG)  from PyQt4.QtGui import (QMainWindow, QWidget, QVBoxLayout, QMessageBox,                           QSystemTrayIcon, QGroupBox, QLabel, QPixmap, @@ -14,63 +16,22 @@ from PyQt4.QtCore import (pyqtSlot, pyqtSignal, QTimer)  from leap.baseapp.dialogs import ErrorDialog  from leap.eip import exceptions as eip_exceptions  from leap.eip.eipconnection import EIPConnection -from leap.gui import mainwindow_rc - - -class LeapWindow(QMainWindow): -    #XXX tbd: refactor into model / view / controller -    #and put in its own modules... - -    newLogLine = pyqtSignal([str]) -    statusChange = pyqtSignal([object]) - -    def __init__(self, opts): -        super(LeapWindow, self).__init__() -        self.debugmode = getattr(opts, 'debug', False) - -        self.eip_service_started = False - -        self.createWindowHeader() -        self.createIconGroupBox() -        self.createActions() -        self.createTrayIcon() -        if self.debugmode: -            self.createLogBrowser() - -        # create timer -        self.timer = QTimer() - -        # bind signals -        self.trayIcon.activated.connect(self.iconActivated) -        self.newLogLine.connect(self.onLoggerNewLine) -        self.statusChange.connect(self.onStatusChange) -        self.timer.timeout.connect(self.onTimerTick) - -        widget = QWidget() -        self.setCentralWidget(widget) +from leap.gui import mainwindow_rc -        # 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) -        self.trayIcon.show() -        self.setWindowTitle("LEAP Client") -        self.resize(400, 300) -        self.set_statusbarMessage('ready') +class EIPConductorApp(object): +    def __init__(self, *args, **kwargs):          #          # conductor is in charge of all          # vpn-related configuration / monitoring.          # we pass a tuple of signals that will be          # triggered when status changes.          # +        opts = kwargs.pop('opts')          config_file = getattr(opts, 'config_file', None) +          self.conductor = EIPConnection(              watcher_cb=self.newLogLine.emit,              config_file=config_file, @@ -79,7 +40,11 @@ class LeapWindow(QMainWindow):          # XXX remove skip download when sample service is ready          self.conductor.run_checks(skip_download=True) +        self.error_check() +        if self.conductor.autostart: +            self.start_or_stopVPN() +    def error_check(self):          ####### error checking ################          #          # bunch of self checks. @@ -142,78 +107,89 @@ class LeapWindow(QMainWindow):                  'error')          ############ end error checking ################### - -        if self.conductor.autostart: -            self.start_or_stopVPN() - -    def closeEvent(self, event): +    @pyqtSlot() +    def statusUpdate(self):          """ -        redefines close event (persistent window behaviour) +        called on timer tick +        polls status and updates ui with real time +        info about transferred bytes / connection state.          """ -        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 " -                                    "<b>Quit</b> in the " -                                    "context menu of the system tray entry.") -            self.hide() -            event.ignore() +        # XXX it's too expensive to poll +        # continously. move to signal events instead. + +        if not self.eip_service_started: +            return + +        # XXX remove all access to manager layer +        # from here. +        if self.conductor.with_errors: +            #XXX how to wait on pkexec??? +            #something better that this workaround, plz!! +            time.sleep(5) +            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.setIconToolTip() + +        ts = time.strftime("%a %b %d %X", ts)          if self.debugmode: -            self.cleanupAndQuit() +            self.updateTS.setText(ts) +            self.status_label.setText(con_status) +            self.ip_label.setText(ip) +            self.remote_label.setText(remote) -    def setIcon(self, name): -        icon = self.Icons.get(name) -        self.trayIcon.setIcon(icon) -        self.setWindowIcon(icon) +        # status i/o -    def setToolTip(self): -        """ -        get readable status and place it on systray tooltip -        """ -        status = self.conductor.status.get_readable_status() -        self.trayIcon.setToolTip(status) +        status = self.conductor.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) -    def iconActivated(self, reason): +    @pyqtSlot() +    def start_or_stopVPN(self):          """ -        handles left click, left double click -        showing the trayicon menu +        stub for running child process with vpn          """ -        #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() +        if self.eip_service_started is False: +            try: +                self.conductor.connect() +                # XXX move this to error queue +            except eip_exceptions.EIPNoCommandError: +                dialog = ErrorDialog() +                dialog.warningMessage( +                    'No suitable openvpn command found. ' +                    '<br/>(Might be a permissions problem)', +                    'error') +            if self.debugmode: +                self.startStopButton.setText('&Disconnect') +            self.eip_service_started = True -    def createWindowHeader(self): -        """ -        description lines for main window -        """ -        #XXX good candidate to refactor out! :) -        self.headerBox = QGroupBox() -        self.headerLabel = QLabel("<font size=40><b>E</b>ncryption \ -<b>I</b>nternet <b>P</b>roxy</font>") -        self.headerLabelSub = QLabel("<i>trust your \ -technolust</i>") +            # XXX what is optimum polling interval? +            # too little is overkill, too much +            # will miss transition states.. -        pixmap = QPixmap(':/images/leapfrog.jpg') -        frog_lbl = QLabel() -        frog_lbl.setPixmap(pixmap) +            self.timer.start(250.0) +            return +        if self.eip_service_started is True: +            self.conductor.disconnect() +            if self.debugmode: +                self.startStopButton.setText('&Connect') +            self.eip_service_started = False +            self.timer.stop() +            return -        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) +class StatusAwareTrayIcon(object):      def createIconGroupBox(self):          """ @@ -254,6 +230,25 @@ technolust</i>")          statusIconLayout.itemAt(2).widget().hide()          self.statusIconBox.setLayout(statusIconLayout) +    def createTrayIcon(self): +        """ +        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 createActions(self):          """          creates actions to be binded to tray icon @@ -261,8 +256,9 @@ technolust</i>")          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.dis_connectAction = QAction( +            "&(Dis)connect", self, +            triggered=lambda: self.start_or_stopVPN())          self.minimizeAction = QAction("Mi&nimize", self,                                        triggered=self.hide)          self.maximizeAction = QAction("Ma&ximize", self, @@ -272,24 +268,128 @@ technolust</i>")          self.quitAction = QAction("&Quit", self,                                    triggered=self.cleanupAndQuit) -    def createTrayIcon(self): +    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() + +    def setIcon(self, name): +        icon = self.Icons.get(name) +        self.trayIcon.setIcon(icon) +        self.setWindowIcon(icon) + +    def getIcon(self, icon_name): +        # XXX get from connection dict +        icons = {'disconnected': 0, +                 'connecting': 1, +                 'connected': 2} +        return icons.get(icon_name, None) + +    def setIconToolTip(self):          """ -        creates the tray icon +        get readable status and place it on systray tooltip          """ -        self.trayIconMenu = QMenu(self) +        status = self.conductor.status.get_readable_status() +        self.trayIcon.setToolTip(status) -        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) +    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() -        self.trayIcon = QSystemTrayIcon(self) -        self.setIcon('disconnected') -        self.trayIcon.setContextMenu(self.trayIconMenu) +    @pyqtSlot() +    def onTimerTick(self): +        self.statusUpdate() + +    @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) + + +class LeapMainWindow(object): + +    def createWindowHeader(self): +        """ +        description lines for main window +        """ +        #XXX good candidate to refactor out! :) +        self.headerBox = QGroupBox() +        self.headerLabel = QLabel("<font size=40><b>E</b>ncryption \ +<b>I</b>nternet <b>P</b>roxy</font>") +        self.headerLabelSub = QLabel("<i>trust your \ +technolust</i>") + +        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 set_statusbarMessage(self, msg): +        self.statusBar().showMessage(msg) + +    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 " +                                    "<b>Quit</b> in the " +                                    "context menu of the system tray entry.") +            self.hide() +            event.ignore() +        if self.debugmode: +            self.cleanupAndQuit() + +    def cleanupAndQuit(self): +        """ +        cleans state before shutting down app. +        """ +        # TODO:make sure to shutdown all child process / threads +        # in conductor +        # XXX send signal instead? +        self.conductor.cleanup() +        qApp.quit() + + +class LogPane(object):      def createLogBrowser(self):          """ @@ -301,7 +401,7 @@ technolust</i>")          self.logbrowser = QTextBrowser()          startStopButton = QPushButton("&Connect") -        startStopButton.clicked.connect(self.start_or_stopVPN) +        #startStopButton.clicked.connect(self.start_or_stopVPN)          self.startStopButton = startStopButton          logging_layout.addWidget(self.logbrowser) @@ -342,130 +442,64 @@ technolust</i>")          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.eip_service_started is False: -            try: -                self.conductor.connect() -                # XXX move this to error queue -            except eip_exceptions.EIPNoCommandError: -                dialog = ErrorDialog() -                dialog.warningMessage( -                    'No suitable openvpn command found. ' -                    '<br/>(Might be a permissions problem)', -                    'error') -            if self.debugmode: -                self.startStopButton.setText('&Disconnect') -            self.eip_service_started = True +# XXX +# main (leave only this here) +class LeapWindow(QMainWindow, LeapMainWindow, EIPConductorApp, +                 StatusAwareTrayIcon, +                 LogPane): -            # XXX what is optimum polling interval? -            # too little is overkill, too much -            # will miss transition states.. +    newLogLine = pyqtSignal([str]) +    statusChange = pyqtSignal([object]) -            self.timer.start(250.0) -            return -        if self.eip_service_started is True: -            self.conductor.disconnect() -            if self.debugmode: -                self.startStopButton.setText('&Connect') -            self.eip_service_started = False -            self.timer.stop() -            return +    def __init__(self, opts): +        logger.debug('init leap window') +        super(LeapWindow, self).__init__() -    @pyqtSlot() -    def onTimerTick(self): -        self.statusUpdate() +        self.debugmode = getattr(opts, 'debug', False) +        self.eip_service_started = False -    @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. +        # create timer +        self.timer = QTimer() -        if not self.eip_service_started: -            return +        if self.debugmode: +            self.createLogBrowser() +        EIPConductorApp.__init__(self, opts=opts) -        # XXX remove all access to manager layer -        # from here. -        if self.conductor.with_errors: -            #XXX how to wait on pkexec??? -            #something better that this workaround, plz!! -            time.sleep(5) -            print('errors. disconnect.') -            self.start_or_stopVPN()  # is stop +        # LeapWindow init +        self.createWindowHeader() -        state = self.conductor.poll_connection_state() -        if not state: -            return +        # StatusAwareTrayIcon init +        self.createIconGroupBox() +        self.createActions() +        self.createTrayIcon() -        ts, con_status, ok, ip, remote = state -        self.set_statusbarMessage(con_status) -        self.setToolTip() +        widget = QWidget() +        self.setCentralWidget(widget) -        ts = time.strftime("%a %b %d %X", ts) +        # add widgets to layout +        mainLayout = QVBoxLayout() +        mainLayout.addWidget(self.headerBox) +        mainLayout.addWidget(self.statusIconBox)          if self.debugmode: -            self.updateTS.setText(ts) -            self.status_label.setText(con_status) -            self.ip_label.setText(ip) -            self.remote_label.setText(remote) +            mainLayout.addWidget(self.statusBox) +            mainLayout.addWidget(self.loggerBox) +        widget.setLayout(mainLayout) -        # status i/o +        # move to icons? +        self.trayIcon.show() +        self.setWindowTitle("LEAP Client") +        self.resize(400, 300) +        self.set_statusbarMessage('ready') -        status = self.conductor.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) +        # bind signals +        # XXX move to parent classes init?? +        self.trayIcon.activated.connect(self.iconActivated) +        self.newLogLine.connect(lambda line: self.onLoggerNewLine(line)) +        self.statusChange.connect(lambda status: self.onStatusChange(status)) +        self.timer.timeout.connect(lambda: self.onTimerTick()) -    def cleanupAndQuit(self): -        """ -        cleans state before shutting down app. -        """ -        # TODO:make sure to shutdown all child process / threads -        # in conductor -        self.conductor.cleanup() -        qApp.quit() +        # move to eipconductor init? +        if self.debugmode: +            self.startStopButton.clicked.connect( +                lambda: self.start_or_stopVPN()) | 
