summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkali <kali@leap.se>2012-09-04 07:11:46 +0900
committerkali <kali@leap.se>2012-09-04 07:11:46 +0900
commit6ef92e257ce1e605194cb26ff6cb804c7d2c3418 (patch)
treea2cf102515e381859445a42703184fb32556e78d
parent83a3fed0d38e44e64cec027f9fd2fcd5a894f96a (diff)
parent3b752fcfac7a18891e2f948acae0cb4781678647 (diff)
Merge branch 'feature/qt-refactor' into develop
closes #474: refactor Qt Code
-rw-r--r--src/leap/baseapp/constants.py1
-rw-r--r--src/leap/baseapp/eip.py195
-rw-r--r--src/leap/baseapp/leap_app.py83
-rw-r--r--src/leap/baseapp/log.py60
-rw-r--r--src/leap/baseapp/mainwindow.py484
-rw-r--r--src/leap/baseapp/systray.py158
6 files changed, 532 insertions, 449 deletions
diff --git a/src/leap/baseapp/constants.py b/src/leap/baseapp/constants.py
new file mode 100644
index 00000000..763df23b
--- /dev/null
+++ b/src/leap/baseapp/constants.py
@@ -0,0 +1 @@
+TIMER_MILLISECONDS = 250.0
diff --git a/src/leap/baseapp/eip.py b/src/leap/baseapp/eip.py
new file mode 100644
index 00000000..029ce0ba
--- /dev/null
+++ b/src/leap/baseapp/eip.py
@@ -0,0 +1,195 @@
+import time
+
+from PyQt4 import QtCore
+
+from leap.baseapp.dialogs import ErrorDialog
+from leap.baseapp import constants
+from leap.eip import exceptions as eip_exceptions
+from leap.eip.eipconnection import EIPConnection
+
+
+class EIPConductorApp(object):
+ """
+ initializes an instance of EIPConnection,
+ gathers errors, and passes status-change signals
+ from Qt land along to the conductor.
+ Connects the eip connect/disconnect logic
+ to the switches in the app (buttons/menu items).
+ """
+
+ def __init__(self, *args, **kwargs):
+ opts = kwargs.pop('opts')
+ config_file = getattr(opts, 'config_file', None)
+
+ self.eip_service_started = False
+
+ # conductor (eip connection) is in charge of all
+ # vpn-related configuration / monitoring.
+ # we pass a tuple of signals that will be
+ # triggered when status changes.
+
+ self.conductor = EIPConnection(
+ watcher_cb=self.newLogLine.emit,
+ config_file=config_file,
+ status_signals=(self.statusChange.emit, ),
+ debug=self.debugmode)
+
+ # XXX remove skip download when sample service is ready
+ self.conductor.run_checks(skip_download=True)
+ self.error_check()
+
+ # XXX should receive "ready" signal
+ # it is called from LeapWindow now.
+ #if self.conductor.autostart:
+ #self.start_or_stopVPN()
+
+ if self.debugmode:
+ self.startStopButton.clicked.connect(
+ lambda: self.start_or_stopVPN())
+
+ def error_check(self):
+
+ # XXX refactor (by #504)
+
+ if self.conductor.missing_definition is True:
+ dialog = ErrorDialog()
+ dialog.criticalMessage(
+ 'The default '
+ 'definition.json file cannot be found',
+ 'error')
+
+ 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.<br/>'
+ 'Make sure you have '
+ '<b>polkit-gnome-authentication-agent-1</b> '
+ 'running and try again.',
+ 'error')
+
+ if self.conductor.missing_pkexec is True:
+ dialog = ErrorDialog()
+ dialog.warningMessage(
+ 'We could not find <b>pkexec</b> in your '
+ 'system.<br/> Do you want to try '
+ '<b>setuid workaround</b>? '
+ '(<i>DOES NOTHING YET</i>)',
+ 'error')
+
+ @QtCore.pyqtSlot()
+ def statusUpdate(self):
+ """
+ polls status and updates ui with real time
+ info about transferred bytes / connection state.
+ right now is triggered by a timer tick
+ (timer controlled by StatusAwareTrayIcon class)
+ """
+ # TODO I guess it's too expensive to poll
+ # continously. move to signal events instead.
+ # (i.e., subscribe to connection status changes
+ # from openvpn manager)
+
+ if not self.eip_service_started:
+ return
+
+ if self.conductor.with_errors:
+ #XXX how to wait on pkexec???
+ #something better that this workaround, plz!!
+ time.sleep(2)
+ 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.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.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)
+
+ @QtCore.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 what is optimum polling interval?
+ # too little is overkill, too much
+ # will miss transition states..
+
+ # XXX decouple! (timer is init by icons class).
+ # should bring it here?
+ # to its own class?
+
+ self.timer.start(constants.TIMER_MILLISECONDS)
+ 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
diff --git a/src/leap/baseapp/leap_app.py b/src/leap/baseapp/leap_app.py
new file mode 100644
index 00000000..def95da1
--- /dev/null
+++ b/src/leap/baseapp/leap_app.py
@@ -0,0 +1,83 @@
+from PyQt4 import QtGui
+
+from leap.gui import mainwindow_rc
+
+
+class MainWindow(object):
+ """
+ create the main window
+ for leap app
+ """
+
+ def __init__(self, *args, **kwargs):
+ # XXX set initial visibility
+ # debug = no visible
+
+ widget = QtGui.QWidget()
+ self.setCentralWidget(widget)
+
+ self.createWindowHeader()
+
+ # add widgets to layout
+ mainLayout = QtGui.QVBoxLayout()
+ mainLayout.addWidget(self.headerBox)
+ mainLayout.addWidget(self.statusIconBox)
+ if self.debugmode:
+ mainLayout.addWidget(self.statusBox)
+ mainLayout.addWidget(self.loggerBox)
+ widget.setLayout(mainLayout)
+
+ self.setWindowTitle("LEAP Client")
+ self.resize(400, 300)
+ self.set_statusbarMessage('ready')
+
+ def createWindowHeader(self):
+ """
+ description lines for main window
+ """
+ self.headerBox = QtGui.QGroupBox()
+ self.headerLabel = QtGui.QLabel("<font size=40><b>E</b>ncryption \
+<b>I</b>nternet <b>P</b>roxy</font>")
+ self.headerLabelSub = QtGui.QLabel("<i>trust your \
+technolust</i>")
+
+ pixmap = QtGui.QPixmap(':/images/leapfrog.jpg')
+ frog_lbl = QtGui.QLabel()
+ frog_lbl.setPixmap(pixmap)
+
+ headerLayout = QtGui.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:
+ QtGui.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()
+ QtGui.qApp.quit()
diff --git a/src/leap/baseapp/log.py b/src/leap/baseapp/log.py
new file mode 100644
index 00000000..0c98eb94
--- /dev/null
+++ b/src/leap/baseapp/log.py
@@ -0,0 +1,60 @@
+from PyQt4 import QtGui
+from PyQt4 import QtCore
+
+
+class LogPane(object):
+ """
+ a simple log pane
+ that writes new lines as they come
+ """
+
+ def createLogBrowser(self):
+ """
+ creates Browser widget for displaying logs
+ (in debug mode only).
+ """
+ self.loggerBox = QtGui.QGroupBox()
+ logging_layout = QtGui.QVBoxLayout()
+ self.logbrowser = QtGui.QTextBrowser()
+
+ startStopButton = QtGui.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 = QtGui.QGroupBox()
+ grid = QtGui.QGridLayout()
+
+ self.updateTS = QtGui.QLabel('')
+ self.status_label = QtGui.QLabel('Disconnected')
+ self.ip_label = QtGui.QLabel('')
+ self.remote_label = QtGui.QLabel('')
+
+ tun_read_label = QtGui.QLabel("tun read")
+ self.tun_read_bytes = QtGui.QLabel("0")
+ tun_write_label = QtGui.QLabel("tun write")
+ self.tun_write_bytes = QtGui.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)
+
+ @QtCore.pyqtSlot(str)
+ def onLoggerNewLine(self, line):
+ """
+ simple slot: writes new line to logger Pane.
+ """
+ if self.debugmode:
+ self.logbrowser.append(line[:-1])
diff --git a/src/leap/baseapp/mainwindow.py b/src/leap/baseapp/mainwindow.py
index 2f7a14dd..ac7fe9c4 100644
--- a/src/leap/baseapp/mainwindow.py
+++ b/src/leap/baseapp/mainwindow.py
@@ -1,471 +1,57 @@
# vim: set fileencoding=utf-8 :
#!/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,
- QHBoxLayout, QIcon,
- QPushButton, QGridLayout, QAction, QMenu,
- QTextBrowser, qApp)
-from PyQt4.QtCore import (pyqtSlot, pyqtSignal, QTimer)
+from PyQt4 import QtCore
+from PyQt4 import QtGui
-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
+from leap.baseapp.eip import EIPConductorApp
+from leap.baseapp.log import LogPane
+from leap.baseapp.systray import StatusAwareTrayIcon
+from leap.baseapp.leap_app import MainWindow
-class LeapWindow(QMainWindow):
- #XXX tbd: refactor into model / view / controller
- #and put in its own modules...
+class LeapWindow(QtGui.QMainWindow,
+ MainWindow, EIPConductorApp,
+ StatusAwareTrayIcon,
+ LogPane):
+ """
+ main window for the leap app.
+ Initializes all of its base classes
+ We keep here some signal initialization
+ that gets tricky otherwise.
+ """
- newLogLine = pyqtSignal([str])
- statusChange = pyqtSignal([object])
+ newLogLine = QtCore.pyqtSignal([str])
+ statusChange = QtCore.pyqtSignal([object])
def __init__(self, opts):
- super(LeapWindow, self).__init__()
+ logger.debug('init leap window')
self.debugmode = getattr(opts, 'debug', False)
- self.eip_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()
+ EIPConductorApp.__init__(self, opts=opts)
+ StatusAwareTrayIcon.__init__(self)
+ MainWindow.__init__(self)
# bind signals
+ # XXX move to parent classes init??
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)
-
- # 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')
-
- #
- # conductor is in charge of all
- # vpn-related configuration / monitoring.
- # we pass a tuple of signals that will be
- # triggered when status changes.
- #
- config_file = getattr(opts, 'config_file', None)
- self.conductor = EIPConnection(
- watcher_cb=self.newLogLine.emit,
- config_file=config_file,
- status_signals=(self.statusChange.emit, ),
- debug=self.debugmode)
-
- # XXX remove skip download when sample service is ready
- self.conductor.run_checks(skip_download=True)
-
- ####### error checking ################
- #
- # bunch of self checks.
- # XXX move somewhere else alltogether.
- #
- if self.conductor.missing_definition is True:
- dialog = ErrorDialog()
- dialog.criticalMessage(
- 'The default '
- 'definition.json file cannot be found',
- 'error')
-
- 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')
+ self.newLogLine.connect(
+ lambda line: self.onLoggerNewLine(line))
+ self.statusChange.connect(
+ lambda status: self.onStatusChange(status))
+ self.timer.timeout.connect(
+ lambda: self.onTimerTick())
- if self.conductor.missing_auth_agent is True:
- dialog = ErrorDialog()
- dialog.warningMessage(
- 'We could not find any authentication '
- 'agent in your system.<br/>'
- 'Make sure you have '
- '<b>polkit-gnome-authentication-agent-1</b> '
- 'running and try again.',
- 'error')
-
- if self.conductor.missing_pkexec is True:
- dialog = ErrorDialog()
- dialog.warningMessage(
- 'We could not find <b>pkexec</b> in your '
- 'system.<br/> Do you want to try '
- '<b>setuid workaround</b>? '
- '(<i>DOES NOTHING YET</i>)',
- 'error')
-
- ############ end error checking ###################
+ # ... all ready. go!
+ # could send "ready" signal instead
+ # eipapp should catch that
if self.conductor.autostart:
self.start_or_stopVPN()
-
- 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 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("<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 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)
- """
- 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):
- """
- 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.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 what is optimum polling interval?
- # too little is overkill, too much
- # will miss transition states..
-
- 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
-
- @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.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.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.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 cleanupAndQuit(self):
- """
- cleans state before shutting down app.
- """
- # TODO:make sure to shutdown all child process / threads
- # in conductor
- self.conductor.cleanup()
- qApp.quit()
diff --git a/src/leap/baseapp/systray.py b/src/leap/baseapp/systray.py
new file mode 100644
index 00000000..f3832473
--- /dev/null
+++ b/src/leap/baseapp/systray.py
@@ -0,0 +1,158 @@
+from PyQt4 import QtCore
+from PyQt4 import QtGui
+
+from leap.gui import mainwindow_rc
+
+
+class StatusAwareTrayIcon(object):
+ """
+ a mix of several functions needed
+ to create a systray and make it
+ get updated from conductor status
+ polling.
+ """
+ states = {
+ "disconnected": 0,
+ "connecting": 1,
+ "connected": 2}
+
+ iconpath = {
+ "disconnected": ':/images/conn_error.png',
+ "connecting": ':/images/conn_connecting.png',
+ "connected": ':/images/conn_connected.png'}
+
+ Icons = {
+ 'disconnected': lambda self: QtGui.QIcon(
+ self.iconpath['disconnected']),
+ 'connecting': lambda self: QtGui.QIcon(
+ self.iconpath['connecting']),
+ 'connected': lambda self: QtGui.QIcon(
+ self.iconpath['connected'])
+ }
+
+ def __init__(self, *args, **kwargs):
+ self.createIconGroupBox()
+ self.createActions()
+ self.createTrayIcon()
+ self.trayIcon.show()
+
+ # not sure if this really belongs here, but...
+ self.timer = QtCore.QTimer()
+
+ def createIconGroupBox(self):
+ """
+ dummy icongroupbox
+ (to be removed from here -- reference only)
+ """
+ con_widgets = {
+ 'disconnected': QtGui.QLabel(),
+ 'connecting': QtGui.QLabel(),
+ 'connected': QtGui.QLabel(),
+ }
+ con_widgets['disconnected'].setPixmap(
+ QtGui.QPixmap(
+ self.iconpath['disconnected']))
+ con_widgets['connecting'].setPixmap(
+ QtGui.QPixmap(
+ self.iconpath['connecting']))
+ con_widgets['connected'].setPixmap(
+ QtGui.QPixmap(
+ self.iconpath['connected'])),
+ self.ConnectionWidgets = con_widgets
+
+ self.statusIconBox = QtGui.QGroupBox("Connection Status")
+ statusIconLayout = QtGui.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 createTrayIcon(self):
+ """
+ creates the tray icon
+ """
+ self.trayIconMenu = QtGui.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 = QtGui.QSystemTrayIcon(self)
+ self.setIcon('disconnected')
+ self.trayIcon.setContextMenu(self.trayIconMenu)
+
+ def createActions(self):
+ """
+ creates actions to be binded to tray icon
+ """
+ self.connectVPNAction = QtGui.QAction("Connect to &VPN", self,
+ triggered=self.hide)
+ # XXX change action name on (dis)connect
+ self.dis_connectAction = QtGui.QAction(
+ "&(Dis)connect", self,
+ triggered=lambda: self.start_or_stopVPN())
+ self.minimizeAction = QtGui.QAction("Mi&nimize", self,
+ triggered=self.hide)
+ self.maximizeAction = QtGui.QAction("Ma&ximize", self,
+ triggered=self.showMaximized)
+ self.restoreAction = QtGui.QAction("&Restore", self,
+ triggered=self.showNormal)
+ self.quitAction = QtGui.QAction("&Quit", self,
+ triggered=self.cleanupAndQuit)
+
+ def setConnWidget(self, icon_name):
+ oldlayout = self.statusIconBox.layout()
+
+ for i in range(3):
+ oldlayout.itemAt(i).widget().hide()
+ new = self.states[icon_name]
+ oldlayout.itemAt(new).widget().show()
+
+ def setIcon(self, name):
+ icon = self.Icons.get(name)(self)
+ self.trayIcon.setIcon(icon)
+ self.setWindowIcon(icon)
+
+ def getIcon(self, icon_name):
+ return self.states.get(icon_name, None)
+
+ def setIconToolTip(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 (QtGui.QSystemTrayIcon.Trigger,
+ QtGui.QSystemTrayIcon.DoubleClick):
+ self.trayIconMenu.show()
+
+ @QtCore.pyqtSlot()
+ def onTimerTick(self):
+ self.statusUpdate()
+
+ @QtCore.pyqtSlot(object)
+ def onStatusChange(self, status):
+ """
+ slot for status changes. triggers new signals for
+ updating icon, status bar, etc.
+ """
+ icon_name = self.conductor.get_icon_name()
+ self.setIcon(icon_name)
+ # change connection pixmap widget
+ self.setConnWidget(icon_name)