diff options
Diffstat (limited to 'src/leap/baseapp/mainwindow.py')
-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()) |