summaryrefslogtreecommitdiff
path: root/src/leap/baseapp
diff options
context:
space:
mode:
authorkali <kali@leap.se>2013-02-15 09:31:51 +0900
committerkali <kali@leap.se>2013-02-15 09:31:51 +0900
commit9cea9c8a34343f8792d65b96f93ae22bd8685878 (patch)
tree9f512367b1d47ced5614702a00f3ff0a8fe746d7 /src/leap/baseapp
parent7159734ec6c0b76fc7f3737134cd22fdaaaa7d58 (diff)
parent1032e07a50c8bb265ff9bd31b3bb00e83ddb451e (diff)
Merge branch 'release/v0.2.0'
Conflicts: README.txt
Diffstat (limited to 'src/leap/baseapp')
-rw-r--r--src/leap/baseapp/config.py40
-rw-r--r--src/leap/baseapp/constants.py6
-rw-r--r--src/leap/baseapp/dialogs.py61
-rw-r--r--src/leap/baseapp/eip.py243
-rw-r--r--src/leap/baseapp/leap_app.py153
-rw-r--r--src/leap/baseapp/log.py69
-rw-r--r--src/leap/baseapp/mainwindow.py529
-rw-r--r--src/leap/baseapp/network.py63
-rw-r--r--src/leap/baseapp/permcheck.py17
-rw-r--r--src/leap/baseapp/systray.py268
10 files changed, 1041 insertions, 408 deletions
diff --git a/src/leap/baseapp/config.py b/src/leap/baseapp/config.py
deleted file mode 100644
index efdb4726..00000000
--- a/src/leap/baseapp/config.py
+++ /dev/null
@@ -1,40 +0,0 @@
-import ConfigParser
-import os
-
-
-def get_config(config_file=None):
- """
- temporary method for getting configs,
- mainly for early stage development process.
- in the future we will get preferences
- from the storage api
- """
- config = ConfigParser.ConfigParser()
- #config.readfp(open('defaults.cfg'))
- #XXX does this work on win / mac also???
- conf_path_list = ['eip.cfg', # XXX build a
- # proper path with platform-specific places
- # XXX make .config/foo
- os.path.expanduser('~/.eip.cfg')]
- if config_file:
- config.readfp(config_file)
- else:
- config.read(conf_path_list)
- return config
-
-
-# XXX wrapper around config? to get default values
-
-def get_with_defaults(config, section, option):
- if config.has_option(section, option):
- return config.get(section, option)
- else:
- # XXX lookup in defaults dict???
- pass
-
-
-def get_vpn_stdout_mockup():
- command = "python"
- args = ["-u", "-c", "from eip_client import fakeclient;\
-fakeclient.write_output()"]
- return command, args
diff --git a/src/leap/baseapp/constants.py b/src/leap/baseapp/constants.py
new file mode 100644
index 00000000..e312be21
--- /dev/null
+++ b/src/leap/baseapp/constants.py
@@ -0,0 +1,6 @@
+# This timer used for polling vpn manager state.
+
+# XXX what is an optimum polling interval?
+# too little will be overkill, too much will
+# miss transition states.
+TIMER_MILLISECONDS = 250.0
diff --git a/src/leap/baseapp/dialogs.py b/src/leap/baseapp/dialogs.py
new file mode 100644
index 00000000..d256fc99
--- /dev/null
+++ b/src/leap/baseapp/dialogs.py
@@ -0,0 +1,61 @@
+# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
+import logging
+
+from PyQt4.QtGui import (QDialog, QFrame, QPushButton, QLabel, QMessageBox)
+
+logger = logging.getLogger(name=__name__)
+
+
+class ErrorDialog(QDialog):
+ def __init__(self, parent=None, errtype=None, msg=None, label=None):
+ super(ErrorDialog, self).__init__(parent)
+ frameStyle = QFrame.Sunken | QFrame.Panel
+ self.warningLabel = QLabel()
+ self.warningLabel.setFrameStyle(frameStyle)
+ self.warningButton = QPushButton("QMessageBox.&warning()")
+
+ if msg is not None:
+ self.msg = msg
+ if label is not None:
+ self.label = label
+ if errtype == "critical":
+ self.criticalMessage(self.msg, self.label)
+
+ def warningMessage(self, msg, label):
+ msgBox = QMessageBox(QMessageBox.Warning,
+ "LEAP Client Error",
+ msg,
+ QMessageBox.NoButton, self)
+ msgBox.addButton("&Ok", QMessageBox.AcceptRole)
+ if msgBox.exec_() == QMessageBox.AcceptRole:
+ pass
+ # do whatever we want to do after
+ # closing the dialog. we can pass that
+ # in the constructor
+
+ def criticalMessage(self, msg, label):
+ msgBox = QMessageBox(QMessageBox.Critical,
+ "LEAP Client Error",
+ msg,
+ QMessageBox.NoButton, self)
+ msgBox.addButton("&Ok", QMessageBox.AcceptRole)
+ msgBox.exec_()
+
+ # It's critical, so we exit.
+ # We should better emit a signal and connect it
+ # with the proper shutdownAndQuit method, but
+ # this suffices for now.
+ logger.info('Quitting')
+ import sys
+ sys.exit()
+
+ def confirmMessage(self, msg, label, action):
+ msgBox = QMessageBox(QMessageBox.Critical,
+ self.tr("LEAP Client Error"),
+ msg,
+ QMessageBox.NoButton, self)
+ msgBox.addButton("&Ok", QMessageBox.AcceptRole)
+ msgBox.addButton("&Cancel", QMessageBox.RejectRole)
+
+ if msgBox.exec_() == QMessageBox.AcceptRole:
+ action()
diff --git a/src/leap/baseapp/eip.py b/src/leap/baseapp/eip.py
new file mode 100644
index 00000000..b34cc82e
--- /dev/null
+++ b/src/leap/baseapp/eip.py
@@ -0,0 +1,243 @@
+from __future__ import print_function
+import logging
+import time
+#import sys
+
+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
+from leap.base.checks import EVENT_CONNECT_REFUSED
+from leap.util import geo
+
+logger = logging.getLogger(name=__name__)
+
+
+class EIPConductorAppMixin(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).
+ """
+ ERR_DIALOG = False
+
+ def __init__(self, *args, **kwargs):
+ opts = kwargs.pop('opts')
+ config_file = getattr(opts, 'config_file', None)
+ provider = kwargs.pop('provider')
+
+ 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,
+ checker_signals=(self.eipStatusChange.emit, ),
+ status_signals=(self.openvpnStatusChange.emit, ),
+ debug=self.debugmode,
+ ovpn_verbosity=opts.openvpn_verb,
+ provider=provider)
+
+ # Do we want to enable the skip checks w/o being
+ # in debug mode??
+ #self.skip_download = opts.no_provider_checks
+ #self.skip_verify = opts.no_ca_verify
+ self.skip_download = False
+ self.skip_verify = False
+
+ def run_eip_checks(self):
+ """
+ runs eip checks and
+ the error checking loop
+ """
+ logger.debug('running EIP CHECKS')
+ self.conductor.run_checks(
+ skip_download=self.skip_download,
+ skip_verify=self.skip_verify)
+ self.error_check()
+
+ self.start_eipconnection.emit()
+
+ def error_check(self):
+ """
+ consumes the conductor error queue.
+ pops errors, and acts accordingly (launching user dialogs).
+ """
+ logger.debug('error check')
+
+ errq = self.conductor.error_queue
+ while errq.qsize() != 0:
+ logger.debug('%s errors left in conductor queue', errq.qsize())
+ # we get exception and original traceback from queue
+ error, tb = errq.get()
+
+ # redundant log, debugging the loop.
+ logger.error('%s: %s', error.__class__.__name__, error.message)
+
+ if issubclass(error.__class__, eip_exceptions.EIPClientError):
+ self.triggerEIPError.emit(error)
+
+ else:
+ # deprecated form of raising exception.
+ raise error, None, tb
+
+ if error.failfirst is True:
+ break
+
+ @QtCore.pyqtSlot(object)
+ def onEIPError(self, error):
+ """
+ check severity and launches
+ dialogs informing user about the errors.
+ in the future we plan to derive errors to
+ our log viewer.
+ """
+ if self.ERR_DIALOG:
+ logger.warning('another error dialog suppressed')
+ return
+
+ # XXX this is actually a one-shot.
+ # On the dialog there should be
+ # a reset signal binded to the ok button
+ # or something like that.
+ self.ERR_DIALOG = True
+
+ if getattr(error, 'usermessage', None):
+ message = error.usermessage
+ else:
+ message = error.message
+
+ # XXX
+ # check headless = False before
+ # launching dialog.
+ # (so Qt tests can assert stuff)
+
+ if error.critical:
+ logger.critical(error.message)
+ #critical error (non recoverable),
+ #we give user some info and quit.
+ #(critical error dialog will exit app)
+ ErrorDialog(errtype="critical",
+ msg=message,
+ label="critical error")
+
+ elif error.warning:
+ logger.warning(error.message)
+
+ else:
+ dialog = ErrorDialog()
+ dialog.warningMessage(message, '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:
+ # there is a race condition
+ # going on here. Depending on how long we take
+ # to init the qt app, the management socket
+ # is not ready yet.
+ return
+
+ #if self.conductor.with_errors:
+ #XXX how to wait on pkexec???
+ #something better that this workaround, plz!!
+ #I removed the pkexec pass authentication at all.
+ #time.sleep(5)
+ #logger.debug('timeout')
+ #logger.error('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)
+ self.remote_country.setText(
+ geo.get_country_name(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)
+
+ # connection information via management interface
+ log = self.conductor.get_log()
+ error_matrix = [(EVENT_CONNECT_REFUSED, (self.start_or_stopVPN, ))]
+ if hasattr(self.network_checker, 'checker'):
+ self.network_checker.checker.parse_log_and_react(log, error_matrix)
+
+ @QtCore.pyqtSlot()
+ def start_or_stopVPN(self, **kwargs):
+ """
+ stub for running child process with vpn
+ """
+ if self.conductor.has_errors():
+ logger.debug('not starting vpn; conductor has errors')
+ return
+
+ if self.eip_service_started is False:
+ try:
+ self.conductor.connect()
+
+ except eip_exceptions.EIPNoCommandError as exc:
+ logger.error('tried to run openvpn but no command is set')
+ self.triggerEIPError.emit(exc)
+
+ except Exception as err:
+ # raise generic exception (Bad Thing Happened?)
+ logger.exception(err)
+ else:
+ # no errors, so go on.
+ if self.debugmode:
+ self.startStopButton.setText(self.tr('&Disconnect'))
+ self.eip_service_started = True
+ self.toggleEIPAct()
+
+ # XXX decouple! (timer is init by icons class).
+ # we could bring Timer Init to this Mixin
+ # or to its own Mixin.
+ self.timer.start(constants.TIMER_MILLISECONDS)
+ return
+
+ if self.eip_service_started is True:
+ self.network_checker.stop()
+ self.conductor.disconnect()
+ if self.debugmode:
+ self.startStopButton.setText(self.tr('&Connect'))
+ self.eip_service_started = False
+ self.toggleEIPAct()
+ 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..4d3aebd6
--- /dev/null
+++ b/src/leap/baseapp/leap_app.py
@@ -0,0 +1,153 @@
+import logging
+
+import sip
+sip.setapi('QVariant', 2)
+
+from PyQt4 import QtCore
+from PyQt4 import QtGui
+
+from leap.gui import mainwindow_rc
+
+logger = logging.getLogger(name=__name__)
+
+
+APP_LOGO = ':/images/leap-color-small.png'
+
+
+class MainWindowMixin(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)
+
+ mainLayout = QtGui.QVBoxLayout()
+ # add widgets to layout
+ #self.createWindowHeader()
+ #mainLayout.addWidget(self.headerBox)
+
+ # created in systray
+ mainLayout.addWidget(self.statusIconBox)
+ if self.debugmode:
+ mainLayout.addWidget(self.statusBox)
+ mainLayout.addWidget(self.loggerBox)
+ widget.setLayout(mainLayout)
+
+ self.createMainActions()
+ self.createMainMenus()
+
+ self.setWindowTitle("LEAP Client")
+ self.set_app_icon()
+ self.set_statusbarMessage('ready')
+
+ def createMainActions(self):
+ #self.openAct = QtGui.QAction("&Open...", self, shortcut="Ctrl+O",
+ #triggered=self.open)
+
+ self.firstRunWizardAct = QtGui.QAction(
+ "&First run wizard...", self,
+ triggered=self.stop_connection_and_launch_first_run_wizard)
+ self.aboutAct = QtGui.QAction("&About", self, triggered=self.about)
+
+ #self.aboutQtAct = QtGui.QAction("About &Qt", self,
+ #triggered=QtGui.qApp.aboutQt)
+
+ def createMainMenus(self):
+ self.connMenu = QtGui.QMenu("&Connections", self)
+ #self.viewMenu.addSeparator()
+ self.connMenu.addAction(self.quitAction)
+
+ self.settingsMenu = QtGui.QMenu("&Settings", self)
+ self.settingsMenu.addAction(self.firstRunWizardAct)
+
+ self.helpMenu = QtGui.QMenu("&Help", self)
+ self.helpMenu.addAction(self.aboutAct)
+ #self.helpMenu.addAction(self.aboutQtAct)
+
+ self.menuBar().addMenu(self.connMenu)
+ self.menuBar().addMenu(self.settingsMenu)
+ self.menuBar().addMenu(self.helpMenu)
+
+ def stop_connection_and_launch_first_run_wizard(self):
+ settings = QtCore.QSettings()
+ settings.setValue('FirstRunWizardDone', False)
+ logger.debug('should run first run wizard again...')
+
+ status = self.conductor.get_icon_name()
+ if status != "disconnected":
+ self.start_or_stopVPN()
+
+ self.launch_first_run_wizard()
+ #from leap.gui.firstrunwizard import FirstRunWizard
+ #wizard = FirstRunWizard(
+ #parent=self,
+ #success_cb=self.initReady.emit)
+ #wizard.show()
+
+ def set_app_icon(self):
+ icon = QtGui.QIcon(APP_LOGO)
+ self.setWindowIcon(icon)
+
+ #def createWindowHeader(self):
+ #"""
+ #description lines for main window
+ #"""
+ #self.headerBox = QtGui.QGroupBox()
+ #self.headerLabel = QtGui.QLabel(
+ #"<font size=40>LEAP Encryption Access Project</font>")
+ #self.headerLabelSub = QtGui.QLabel(
+ #"<br><i>your internet encryption toolkit</i>")
+#
+ #pixmap = QtGui.QPixmap(APP_LOGO)
+ #leap_lbl = QtGui.QLabel()
+ #leap_lbl.setPixmap(pixmap)
+#
+ #headerLayout = QtGui.QHBoxLayout()
+ #headerLayout.addWidget(leap_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()
+ return
+ self.cleanupAndQuit()
+
+ def cleanupAndQuit(self):
+ """
+ cleans state before shutting down app.
+ """
+ # save geometry for restoring
+ settings = QtCore.QSettings()
+ geom_key = "DebugGeometry" if self.debugmode else "Geometry"
+ settings.setValue(geom_key, self.saveGeometry())
+
+ # TODO:make sure to shutdown all child process / threads
+ # in conductor
+ # XXX send signal instead?
+ logger.info('Shutting down')
+ self.conductor.disconnect(shutdown=True)
+ logger.info('Exiting. Bye.')
+ QtGui.qApp.quit()
diff --git a/src/leap/baseapp/log.py b/src/leap/baseapp/log.py
new file mode 100644
index 00000000..636e5bae
--- /dev/null
+++ b/src/leap/baseapp/log.py
@@ -0,0 +1,69 @@
+import logging
+
+from PyQt4 import QtGui
+from PyQt4 import QtCore
+
+vpnlogger = logging.getLogger('leap.openvpn')
+
+
+class LogPaneMixin(object):
+ """
+ a simple log pane
+ that writes new lines as they come
+ """
+ EXCLUDES = ('MANAGEMENT',)
+
+ 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(self.tr("&Connect"))
+ 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(self.tr('Disconnected'))
+ self.ip_label = QtGui.QLabel('')
+ self.remote_label = QtGui.QLabel('')
+ self.remote_country = 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(self.remote_country, 2, 1)
+ grid.addWidget(tun_read_label, 3, 0)
+ grid.addWidget(self.tun_read_bytes, 3, 1)
+ grid.addWidget(tun_write_label, 4, 0)
+ grid.addWidget(self.tun_write_bytes, 4, 1)
+
+ self.statusBox.setLayout(grid)
+
+ @QtCore.pyqtSlot(str)
+ def onLoggerNewLine(self, line):
+ """
+ simple slot: writes new line to logger Pane.
+ """
+ msg = line[:-1]
+ if self.debugmode and all(map(lambda w: w not in msg,
+ LogPaneMixin.EXCLUDES)):
+ self.logbrowser.append(msg)
+ vpnlogger.info(msg)
diff --git a/src/leap/baseapp/mainwindow.py b/src/leap/baseapp/mainwindow.py
index 68b6de8f..91b0dc61 100644
--- a/src/leap/baseapp/mainwindow.py
+++ b/src/leap/baseapp/mainwindow.py
@@ -1,398 +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)
+import sip
+sip.setapi('QString', 2)
+sip.setapi('QVariant', 2)
+
+from PyQt4 import QtCore
+from PyQt4 import QtGui
-from leap.gui import mainwindow_rc
-from leap.eip.conductor import EIPConductor
+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()
+ settings = QtCore.QSettings()
+ self.provider_domain = settings.value("provider_domain", None)
+ self.username = settings.value("username", None)
- # bind signals
+ logger.debug('provider: %s', self.provider_domain)
+ logger.debug('username: %s', self.username)
- self.trayIcon.activated.connect(self.iconActivated)
- self.newLogLine.connect(self.onLoggerNewLine)
- self.statusChange.connect(self.onStatusChange)
- self.timer.timeout.connect(self.onTimerTick)
+ provider = self.provider_domain
+ EIPConductorAppMixin.__init__(
+ self, opts=opts, provider=provider)
+ StatusAwareTrayIconMixin.__init__(self)
- widget = QWidget()
- self.setCentralWidget(widget)
+ # 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)
- # 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)
+ 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.
- #
- config_file = getattr(opts, 'config_file', None)
- self.conductor = EIPConductor(
- watcher_cb=self.newLogLine.emit,
- config_file=config_file,
- status_signals=(self.statusChange.emit, ))
+ # XXX check for wizard
+ self.wizard_done = settings.value("FirstRunWizardDone")
- self.trayIcon.show()
+ self.initchecks = FunThread(self.run_eip_checks)
- self.setWindowTitle("Leap")
- 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 "
- "<b>Quit</b> 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):
+ def runchecks_and_eipconnect(self):
"""
- handles left click, left double click
- showing the trayicon menu
+ shows icon and run init checks
"""
- #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.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:
- self.conductor.connect()
- 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()
diff --git a/src/leap/baseapp/network.py b/src/leap/baseapp/network.py
new file mode 100644
index 00000000..dc5182a4
--- /dev/null
+++ b/src/leap/baseapp/network.py
@@ -0,0 +1,63 @@
+from __future__ import print_function
+
+import logging
+
+logger = logging.getLogger(name=__name__)
+
+from PyQt4 import QtCore
+
+from leap.baseapp.dialogs import ErrorDialog
+from leap.base.network import NetworkCheckerThread
+
+from leap.util.misc import null_check
+
+
+class NetworkCheckerAppMixin(object):
+ """
+ initialize an instance of the Network Checker,
+ which gathers error and passes them on.
+ """
+ ERR_NETERR = False
+
+ def __init__(self, *args, **kwargs):
+ provider = kwargs.pop('provider', None)
+ self.network_checker = None
+ if provider:
+ self.init_network_checker(provider)
+
+ def init_network_checker(self, provider):
+ null_check(provider, "provider_domain")
+ if not self.network_checker:
+ self.network_checker = NetworkCheckerThread(
+ error_cb=self.networkError.emit,
+ debug=self.debugmode,
+ provider=provider)
+ self.network_checker.start()
+
+ @QtCore.pyqtSlot(object)
+ def runNetworkChecks(self):
+ logger.debug('running checks (from NetworkChecker Mixin slot)')
+ self.network_checker.run_checks()
+
+ @QtCore.pyqtSlot(object)
+ def onNetworkError(self, exc):
+ """
+ slot that receives a network exceptions
+ and raises a user error message
+ """
+ # FIXME this should not HANDLE anything after
+ # the network check thread has been stopped.
+
+ logger.debug('handling network exception')
+ if not self.ERR_NETERR:
+ self.ERR_NETERR = True
+
+ logger.error(exc.message)
+ dialog = ErrorDialog(parent=self)
+ if exc.critical:
+ dialog.criticalMessage(exc.usermessage, "network error")
+ else:
+ dialog.warningMessage(exc.usermessage, "network error")
+
+ self.start_or_stopVPN()
+ self.network_checker.stop()
diff --git a/src/leap/baseapp/permcheck.py b/src/leap/baseapp/permcheck.py
new file mode 100644
index 00000000..6b74cb6e
--- /dev/null
+++ b/src/leap/baseapp/permcheck.py
@@ -0,0 +1,17 @@
+import commands
+import os
+
+from leap.util.fileutil import which
+
+
+def is_pkexec_in_system():
+ pkexec_path = which('pkexec')
+ if not pkexec_path:
+ return False
+ return os.access(pkexec_path, os.X_OK)
+
+
+def is_auth_agent_running():
+ return bool(
+ commands.getoutput(
+ 'ps aux | grep polkit-[g]nome-authentication-agent-1'))
diff --git a/src/leap/baseapp/systray.py b/src/leap/baseapp/systray.py
new file mode 100644
index 00000000..77eb3fe9
--- /dev/null
+++ b/src/leap/baseapp/systray.py
@@ -0,0 +1,268 @@
+import logging
+import sys
+
+import sip
+sip.setapi('QString', 2)
+sip.setapi('QVariant', 2)
+
+from PyQt4 import QtCore
+from PyQt4 import QtGui
+
+from leap import __branding as BRANDING
+from leap import __version__ as VERSION
+
+from leap.gui import mainwindow_rc
+
+logger = logging.getLogger(__name__)
+
+
+class StatusAwareTrayIconMixin(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()
+
+ # not sure if this really belongs here, but...
+ self.timer = QtCore.QTimer()
+
+ def show_systray_icon(self):
+ #logger.debug('showing tray icon................')
+ self.trayIcon.show()
+
+ 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(
+ self.tr("EIP 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.leapConnStatus = QtGui.QLabel(
+ self.tr("<b>disconnected</b>"))
+ statusIconLayout.addWidget(self.leapConnStatus)
+
+ self.statusIconBox.setLayout(statusIconLayout)
+
+ def createTrayIcon(self):
+ """
+ creates the tray icon
+ """
+ self.trayIconMenu = QtGui.QMenu(self)
+
+ self.trayIconMenu.addAction(self.connAct)
+ self.trayIconMenu.addSeparator()
+ self.trayIconMenu.addAction(self.detailsAct)
+ self.trayIconMenu.addSeparator()
+ self.trayIconMenu.addAction(self.aboutAct)
+ # we should get this hidden inside the "about" dialog
+ # (as a little button maybe)
+ #self.trayIconMenu.addAction(self.aboutQtAct)
+ self.trayIconMenu.addSeparator()
+ self.trayIconMenu.addAction(self.quitAction)
+
+ self.trayIcon = QtGui.QSystemTrayIcon(self)
+ self.setIcon('disconnected')
+ self.trayIcon.setContextMenu(self.trayIconMenu)
+
+ #self.trayIconMenu.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
+ #self.trayIconMenu.customContextMenuRequested.connect(
+ #self.on_context_menu)
+
+ #def bad(self):
+ #logger.error('this should not be called')
+
+ def createActions(self):
+ """
+ creates actions to be binded to tray icon
+ """
+ # XXX change action name on (dis)connect
+ self.connAct = QtGui.QAction(
+ self.tr("Encryption ON turn &off"),
+ self,
+ triggered=lambda: self.start_or_stopVPN())
+
+ self.detailsAct = QtGui.QAction(
+ self.tr("&Details..."),
+ self,
+ triggered=self.detailsWin)
+ self.aboutAct = QtGui.QAction(
+ self.tr("&About"), self,
+ triggered=self.about)
+ self.aboutQtAct = QtGui.QAction(
+ self.tr("About Q&t"), self,
+ triggered=QtGui.qApp.aboutQt)
+ self.quitAction = QtGui.QAction(
+ self.tr("&Quit"), self,
+ triggered=self.cleanupAndQuit)
+
+ def toggleEIPAct(self):
+ # this is too simple by now.
+ # XXX get STATUS CONSTANTS INSTEAD
+
+ icon_status = self.conductor.get_icon_name()
+ if icon_status == "connected":
+ self.connAct.setEnabled(True)
+ self.connAct.setText(
+ self.tr('Encryption ON turn o&ff'))
+ return
+ if icon_status == "disconnected":
+ self.connAct.setEnabled(True)
+ self.connAct.setText(
+ self.tr('Encryption OFF turn &on'))
+ return
+ if icon_status == "connecting":
+ self.connAct.setDisabled(True)
+ self.connAct.setText(self.tr('connecting...'))
+ return
+
+ def detailsWin(self):
+ visible = self.isVisible()
+ if visible:
+ self.hide()
+ else:
+ self.show()
+ if sys.platform == "darwin":
+ self.raise_()
+
+ def about(self):
+ # move to widget
+ flavor = BRANDING.get('short_name', None)
+ content = self.tr(
+ ("LEAP client<br>"
+ "(version <b>%s</b>)<br>" % VERSION))
+ if flavor:
+ content = content + ('<br>Flavor: <i>%s</i><br>' % flavor)
+ content = content + (
+ "<br><a href='https://leap.se/'>"
+ "https://leap.se</a>")
+ QtGui.QMessageBox.about(self, self.tr("About"), content)
+
+ 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_fun = self.Icons.get(name)
+ if icon_fun and callable(icon_fun):
+ icon = icon_fun(self)
+ self.trayIcon.setIcon(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
+ """
+ if reason in (QtGui.QSystemTrayIcon.Trigger,
+ QtGui.QSystemTrayIcon.DoubleClick):
+ context_menu = self.trayIcon.contextMenu()
+ # for some reason, context_menu.show()
+ # is failing in a way beyond my understanding.
+ # (not working the first time it's clicked).
+ # this works however.
+ # XXX in osx it shows some glitches.
+ context_menu.exec_(self.trayIcon.geometry().center())
+
+ @QtCore.pyqtSlot()
+ def onTimerTick(self):
+ self.statusUpdate()
+
+ @QtCore.pyqtSlot(object)
+ def onOpenVPNStatusChange(self, status):
+ """
+ updates icon, according to the openvpn status change.
+ """
+ icon_name = self.conductor.get_icon_name()
+ if not icon_name:
+ return
+
+ # XXX refactor. Use QStateMachine
+
+ if icon_name in ("disconnected", "connected"):
+ self.eipStatusChange.emit(icon_name)
+
+ if icon_name in ("connecting"):
+ # let's see how it matches
+ leap_status_name = self.conductor.get_leap_status()
+ self.eipStatusChange.emit(leap_status_name)
+
+ if icon_name == "connected":
+ # When we change to "connected', we launch
+ # the network checker.
+ self.initNetworkChecker.emit()
+
+ self.setIcon(icon_name)
+ # change connection pixmap widget
+ self.setConnWidget(icon_name)
+
+ @QtCore.pyqtSlot(str)
+ def onEIPConnStatusChange(self, newstatus):
+ """
+ slot for EIP status changes
+ not to be confused with onOpenVPNStatusChange.
+ this only updates the non-debug LEAP Status line
+ next to the connection icon.
+ """
+ # XXX move bold to style sheet
+ self.leapConnStatus.setText(
+ "<b>%s</b>" % newstatus)