summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--changes/feature_always_show_context_menu3
-rw-r--r--changes/feature_display_provider_in_tray2
-rw-r--r--changes/feature_osx-eip-scripts3
-rw-r--r--changes/feature_refactor_login2
-rwxr-xr-xpkg/osx/install/client.down.sh32
-rwxr-xr-xpkg/osx/install/client.up.sh29
-rw-r--r--pkg/requirements-testing.pip2
-rw-r--r--src/leap/config/leapsettings.py38
-rw-r--r--src/leap/gui/login.py212
-rw-r--r--src/leap/gui/mainwindow.py251
-rw-r--r--src/leap/gui/ui/login.ui129
-rw-r--r--src/leap/gui/ui/mainwindow.ui216
-rw-r--r--src/leap/platform_init/initializers.py121
-rw-r--r--src/leap/services/abstractbootstrapper.py18
-rw-r--r--src/leap/services/eip/eipbootstrapper.py4
-rw-r--r--src/leap/services/eip/vpnlaunchers.py96
-rw-r--r--src/leap/services/mail/smtpbootstrapper.py9
-rw-r--r--src/leap/util/leap_log_handler.py81
19 files changed, 890 insertions, 360 deletions
diff --git a/Makefile b/Makefile
index bb4b5854..8cdbe487 100644
--- a/Makefile
+++ b/Makefile
@@ -20,7 +20,7 @@ TRANSLAT_DIR = data/translations
PROJFILE = data/leap_client.pro
#UI files to compile
-UI_FILES = loggerwindow.ui mainwindow.ui wizard.ui
+UI_FILES = loggerwindow.ui mainwindow.ui wizard.ui login.ui
#Qt resource files to compile
RESOURCES = locale.qrc loggerwindow.qrc mainwindow.qrc
diff --git a/changes/feature_always_show_context_menu b/changes/feature_always_show_context_menu
new file mode 100644
index 00000000..9e94726e
--- /dev/null
+++ b/changes/feature_always_show_context_menu
@@ -0,0 +1,3 @@
+ o Clicking in the tray icon will always show the context menu
+ instead of activating the window under certain
+ circumstances. Closes #2788 \ No newline at end of file
diff --git a/changes/feature_display_provider_in_tray b/changes/feature_display_provider_in_tray
new file mode 100644
index 00000000..b37e90a2
--- /dev/null
+++ b/changes/feature_display_provider_in_tray
@@ -0,0 +1,2 @@
+ o Display the default provider configured in the systray menu. Close
+ #2813 \ No newline at end of file
diff --git a/changes/feature_osx-eip-scripts b/changes/feature_osx-eip-scripts
new file mode 100644
index 00000000..2b418291
--- /dev/null
+++ b/changes/feature_osx-eip-scripts
@@ -0,0 +1,3 @@
+ o OSX: Add dialog with suggestion to install up/down scripts if these not found.
+ Closes: #1264, #2759, #2249
+ o Workaround for pyside breaking with multiple inheritance. Closes #2827
diff --git a/changes/feature_refactor_login b/changes/feature_refactor_login
new file mode 100644
index 00000000..41e1dc50
--- /dev/null
+++ b/changes/feature_refactor_login
@@ -0,0 +1,2 @@
+ o Refactor login to its own widget and remove Utils menu. Closes
+ #2789 \ No newline at end of file
diff --git a/pkg/osx/install/client.down.sh b/pkg/osx/install/client.down.sh
index 66467c08..52ba4de6 100755
--- a/pkg/osx/install/client.down.sh
+++ b/pkg/osx/install/client.down.sh
@@ -2,7 +2,8 @@
# Note: must be bash; uses bash-specific tricks
#
# ******************************************************************************************************************
-# This Tunnelblick script does everything! It handles TUN and TAP interfaces,
+# Based on the Tunnelblick script that just "does everything!"
+# It handles TUN and TAP interfaces,
# pushed configurations and DHCP leases. :)
#
# This is the "Down" version of the script, executed after the connection is
@@ -11,6 +12,7 @@
# Created by: Nick Williams (using original code and parts of old Tblk scripts)
#
# ******************************************************************************************************************
+# TODO: review and adapt version 3 of the clientX.down.sh
trap "" TSTP
trap "" HUP
@@ -26,30 +28,30 @@ if ! scutil -w State:/Network/OpenVPN &>/dev/null -t 1 ; then
exit 0
fi
-# NOTE: This script does not use any arguments passed to it by OpenVPN, so it doesn't shift Tunnelblick options out of the argument list
+# NOTE: This script does not use any arguments passed to it by OpenVPN, so it doesn't shift LEAPClient options out of the argument list
# Get info saved by the up script
-TUNNELBLICK_CONFIG="$(/usr/sbin/scutil <<-EOF
+LEAPCLIENT_CONFIG="$(/usr/sbin/scutil <<-EOF
open
show State:/Network/OpenVPN
quit
EOF)"
-ARG_MONITOR_NETWORK_CONFIGURATION="$(echo "${TUNNELBLICK_CONFIG}" | grep -i '^[[:space:]]*MonitorNetwork :' | sed -e 's/^.*: //g')"
-LEASEWATCHER_PLIST_PATH="$(echo "${TUNNELBLICK_CONFIG}" | grep -i '^[[:space:]]*LeaseWatcherPlistPath :' | sed -e 's/^.*: //g')"
-PSID="$(echo "${TUNNELBLICK_CONFIG}" | grep -i '^[[:space:]]*Service :' | sed -e 's/^.*: //g')"
-SCRIPT_LOG_FILE="$(echo "${TUNNELBLICK_CONFIG}" | grep -i '^[[:space:]]*ScriptLogFile :' | sed -e 's/^.*: //g')"
-# Don't need: ARG_RESTORE_ON_DNS_RESET="$(echo "${TUNNELBLICK_CONFIG}" | grep -i '^[[:space:]]*RestoreOnDNSReset :' | sed -e 's/^.*: //g')"
-# Don't need: ARG_RESTORE_ON_WINS_RESET="$(echo "${TUNNELBLICK_CONFIG}" | grep -i '^[[:space:]]*RestoreOnWINSReset :' | sed -e 's/^.*: //g')"
-# Don't need: PROCESS="$(echo "${TUNNELBLICK_CONFIG}" | grep -i '^[[:space:]]*PID :' | sed -e 's/^.*: //g')"
-# Don't need: ARG_IGNORE_OPTION_FLAGS="$(echo "${TUNNELBLICK_CONFIG}" | grep -i '^[[:space:]]*IgnoreOptionFlags :' | sed -e 's/^.*: //g')"
-ARG_TAP="$(echo "${TUNNELBLICK_CONFIG}" | grep -i '^[[:space:]]*IsTapInterface :' | sed -e 's/^.*: //g')"
-bRouteGatewayIsDhcp="$(echo "${TUNNELBLICK_CONFIG}" | grep -i '^[[:space:]]*RouteGatewayIsDhcp :' | sed -e 's/^.*: //g')"
+ARG_MONITOR_NETWORK_CONFIGURATION="$(echo "${LEAPCLIENT_CONFIG}" | grep -i '^[[:space:]]*MonitorNetwork :' | sed -e 's/^.*: //g')"
+LEASEWATCHER_PLIST_PATH="$(echo "${LEAPCLIENT_CONFIG}" | grep -i '^[[:space:]]*LeaseWatcherPlistPath :' | sed -e 's/^.*: //g')"
+PSID="$(echo "${LEAPCLIENT_CONFIG}" | grep -i '^[[:space:]]*Service :' | sed -e 's/^.*: //g')"
+SCRIPT_LOG_FILE="$(echo "${LEAPCLIENT_CONFIG}" | grep -i '^[[:space:]]*ScriptLogFile :' | sed -e 's/^.*: //g')"
+# Don't need: ARG_RESTORE_ON_DNS_RESET="$(echo "${LEAPCLIENT_CONFIG}" | grep -i '^[[:space:]]*RestoreOnDNSReset :' | sed -e 's/^.*: //g')"
+# Don't need: ARG_RESTORE_ON_WINS_RESET="$(echo "${LEAPCLIENT_CONFIG}" | grep -i '^[[:space:]]*RestoreOnWINSReset :' | sed -e 's/^.*: //g')"
+# Don't need: PROCESS="$(echo "${LEAPCLIENT_CONFIG}" | grep -i '^[[:space:]]*PID :' | sed -e 's/^.*: //g')"
+# Don't need: ARG_IGNORE_OPTION_FLAGS="$(echo "${LEAPCLIENT_CONFIG}" | grep -i '^[[:space:]]*IgnoreOptionFlags :' | sed -e 's/^.*: //g')"
+ARG_TAP="$(echo "${LEAPCLIENT_CONFIG}" | grep -i '^[[:space:]]*IsTapInterface :' | sed -e 's/^.*: //g')"
+bRouteGatewayIsDhcp="$(echo "${LEAPCLIENT_CONFIG}" | grep -i '^[[:space:]]*RouteGatewayIsDhcp :' | sed -e 's/^.*: //g')"
# @param String message - The message to log
logMessage()
{
- echo "$(date '+%a %b %e %T %Y') *Tunnelblick $LOG_MESSAGE_COMMAND: "${@} >> "${SCRIPT_LOG_FILE}"
+ echo "$(date '+%a %b %e %T %Y') *LEAP CLient $LOG_MESSAGE_COMMAND: "${@} >> "${SCRIPT_LOG_FILE}"
}
trim()
@@ -97,7 +99,7 @@ WINS_OLD="$(/usr/sbin/scutil <<-EOF
quit
EOF)"
TB_NO_SUCH_KEY="<dictionary> {
- TunnelblickNoSuchKey : true
+ LEAPClientNoSuchKey : true
}"
if [ "${DNS_OLD}" = "${TB_NO_SUCH_KEY}" ] ; then
diff --git a/pkg/osx/install/client.up.sh b/pkg/osx/install/client.up.sh
index fc7e341a..be9814c2 100755
--- a/pkg/osx/install/client.up.sh
+++ b/pkg/osx/install/client.up.sh
@@ -2,7 +2,8 @@
# Note: must be bash; uses bash-specific tricks
#
# ******************************************************************************************************************
-# This Tunnelblick script does everything! It handles TUN and TAP interfaces,
+# Taken from the Tunnelblick script that "just does everything!"
+# It handles TUN and TAP interfaces,
# pushed configurations, DHCP with DNS and WINS, and renewed DHCP leases. :)
#
# This is the "Up" version of the script, executed after the interface is
@@ -11,6 +12,7 @@
# Created by: Nick Williams (using original code and parts of old Tblk scripts)
#
# ******************************************************************************************************************
+# TODO: review and adapt revision 3 of the clientX-up.sh instead
trap "" TSTP
trap "" HUP
@@ -19,7 +21,7 @@ export PATH="/bin:/sbin:/usr/sbin:/usr/bin"
# Process optional arguments (if any) for the script
# Each one begins with a "-"
-# They come from Tunnelblick, and come first, before the OpenVPN arguments
+# They come from the leap-client invocation, and come first, before the OpenVPN arguments
# So we set ARG_ script variables to their values and shift them out of the argument list
# When we're done, only the OpenVPN arguments remain for the rest of the script to use
ARG_MONITOR_NETWORK_CONFIGURATION="false"
@@ -63,24 +65,25 @@ readonly ARG_MONITOR_NETWORK_CONFIGURATION ARG_RESTORE_ON_DNS_RESET ARG_RESTORE_
# then convert to regular config /Users/Jonathan/Library/Application Support/Tunnelblick/Configurations/Folder/Subfolder/config.ovpn
# to get the script log path
# Note: "/Users/..." works even if the home directory has a different path; it is used in the name of the log file, and is not used as a path to get to anything.
-readonly TBALTPREFIX="/Library/Application Support/Tunnelblick/Users/"
+readonly TBALTPREFIX="/Library/Application Support/LEAP Client/Users/"
readonly TBALTPREFIXLEN="${#TBALTPREFIX}"
readonly TBCONFIGSTART="${config:0:$TBALTPREFIXLEN}"
if [ "$TBCONFIGSTART" = "$TBALTPREFIX" ] ; then
readonly TBBASE="${config:$TBALTPREFIXLEN}"
readonly TBSUFFIX="${TBBASE#*/}"
readonly TBUSERNAME="${TBBASE%%/*}"
- readonly TBCONFIG="/Users/$TBUSERNAME/Library/Application Support/Tunnelblick/Configurations/$TBSUFFIX"
+ readonly TBCONFIG="/Users/$TBUSERNAME/Library/Application Support/LEAP Client/Configurations/$TBSUFFIX"
else
readonly TBCONFIG="${config}"
fi
readonly CONFIG_PATH_DASHES_SLASHES="$(echo "${TBCONFIG}" | sed -e 's/-/--/g' | sed -e 's/\//-S/g')"
-readonly SCRIPT_LOG_FILE="/Library/Application Support/Tunnelblick/Logs/${CONFIG_PATH_DASHES_SLASHES}.script.log"
+# XXX PUT LOGS SOMEWHERE BETTER
+readonly SCRIPT_LOG_FILE="/Users/$LEAPUSER/.config/leap/logs/${CONFIG_PATH_DASHES_SLASHES}.script.log"
readonly TB_RESOURCE_PATH=$(dirname "${0}")
-LEASEWATCHER_PLIST_PATH="/Library/Application Support/Tunnelblick/LeaseWatch.plist"
+LEASEWATCHER_PLIST_PATH="/Users/$LEAPUSER/.config/leap/logs/LeaseWatch.plist"
readonly OSVER="$(sw_vers | grep 'ProductVersion:' | grep -o '10\.[0-9]*')"
@@ -92,7 +95,7 @@ bRouteGatewayIsDhcp="false"
readonly LOG_MESSAGE_COMMAND=$(basename "${0}")
logMessage()
{
- echo "$(date '+%a %b %e %T %Y') *Tunnelblick $LOG_MESSAGE_COMMAND: "${@} >> "${SCRIPT_LOG_FILE}"
+ echo "$(date '+%a %b %e %T %Y') *LEAP Client $LOG_MESSAGE_COMMAND: "${@} >> "${SCRIPT_LOG_FILE}"
}
# @param String string - Content to trim
@@ -270,7 +273,7 @@ EOF )"
fi
# Now, do the aggregation
- # Save the openvpn process ID and the Network Primary Service ID, leasewather.plist path, logfile path, and optional arguments from Tunnelblick,
+ # Save the openvpn process ID and the Network Primary Service ID, leasewather.plist path, logfile path, and optional arguments from LEAP Client,
# then save old and new DNS and WINS settings
# PPID is a bash-script variable that contains the process ID of the parent of the process running the script (i.e., OpenVPN's process ID)
# config is an environmental variable set to the configuration path by OpenVPN prior to running this up script
@@ -290,7 +293,7 @@ EOF )"
CORRECT_OLD_WINS_KEY="State:"
fi
- # If we are not expecting any WINS value, add <TunnelblickNoSuchKey : true> to the expected WINS setup
+ # If we are not expecting any WINS value, add <LEAPClientNoSuchKey : true> to the expected WINS setup
NO_NOSUCH_KEY_WINS="#"
if [ "${NO_NB}" = "#" -a "${AGG_WINS}" = "#" -a "${NO_WG}" = "#" ] ; then
NO_NOSUCH_KEY_WINS=""
@@ -315,14 +318,14 @@ EOF )"
set State:/Network/OpenVPN
# First, back up the device's current DNS and WINS configurations
- # Indicate 'no such key' by a dictionary with a single entry: "TunnelblickNoSuchKey : true"
+ # Indicate 'no such key' by a dictionary with a single entry: "LEAPClientNoSuchKey : true"
d.init
- d.add TunnelblickNoSuchKey true
+ d.add LEAPClientNoSuchKey true
get ${CORRECT_OLD_DNS_KEY}/Network/Service/${PSID}/DNS
set State:/Network/OpenVPN/OldDNS
d.init
- d.add TunnelblickNoSuchKey true
+ d.add LEAPClientNoSuchKey true
get ${CORRECT_OLD_WINS_KEY}/Network/Service/${PSID}/SMB
set State:/Network/OpenVPN/OldSMB
@@ -353,7 +356,7 @@ EOF )"
${NO_NB}d.add NetBIOSName ${STATIC_NETBIOSNAME}
${AGG_WINS}d.add WINSAddresses * ${ALL_WINS_SERVERS}
${NO_WG}d.add Workgroup ${STATIC_WORKGROUP}
- ${NO_NOSUCH_KEY_WINS}d.add TunnelblickNoSuchKey true
+ ${NO_NOSUCH_KEY_WINS}d.add LEAPClientNoSuchKey true
set State:/Network/OpenVPN/SMB
# We are done
diff --git a/pkg/requirements-testing.pip b/pkg/requirements-testing.pip
index 5405a75b..2df5fe56 100644
--- a/pkg/requirements-testing.pip
+++ b/pkg/requirements-testing.pip
@@ -5,7 +5,7 @@ nose-progressive
unittest2 # TODO we should include this dep only for python2.6
coverage
-pep8==1.1
+pep8>=1.1
tox
#sphinx>=1.1.2
diff --git a/src/leap/config/leapsettings.py b/src/leap/config/leapsettings.py
index 006be851..88b7d8c9 100644
--- a/src/leap/config/leapsettings.py
+++ b/src/leap/config/leapsettings.py
@@ -62,10 +62,10 @@ class LeapSettings(object):
GEOMETRY_KEY = "Geometry"
WINDOWSTATE_KEY = "WindowState"
USER_KEY = "User"
- AUTOLOGIN_KEY = "AutoLogin"
PROPERPROVIDER_KEY = "ProperProvider"
REMEMBER_KEY = "RememberUserAndPass"
DEFAULTPROVIDER_KEY = "DefaultProvider"
+ ALERTMISSING_KEY = "AlertMissingScripts"
def __init__(self, standalone=False):
"""
@@ -191,24 +191,6 @@ class LeapSettings(object):
leap_assert_type(remember, bool)
self._settings.setValue(self.REMEMBER_KEY, remember)
- def get_autologin(self):
- """
- Returns True if the app should automatically login, False otherwise
-
- :rtype: bool
- """
- return to_bool(self._settings.value(self.AUTOLOGIN_KEY, False))
-
- def set_autologin(self, autologin):
- """
- Sets whether the app should automatically login
-
- :param autologin: True if the app should autologin, False otherwise
- :type autologin: bool
- """
- leap_assert_type(autologin, bool)
- self._settings.setValue(self.AUTOLOGIN_KEY, autologin)
-
# TODO: make this scale with multiple providers, we are assuming
# just one for now
def get_properprovider(self):
@@ -249,3 +231,21 @@ class LeapSettings(object):
"""
leap_assert(len(provider) > 0, "We cannot save an empty provider")
self._settings.setValue(self.DEFAULTPROVIDER_KEY, provider)
+
+ def get_alert_missing_scripts(self):
+ """
+ Returns the setting for alerting of missing up/down scripts.
+
+ :rtype: bool
+ """
+ return to_bool(self._settings.value(self.ALERTMISSING_KEY, True))
+
+ def set_alert_missing_scripts(self, value):
+ """
+ Sets the setting for alerting of missing up/down scripts.
+
+ :param value: the value to set
+ :type value: bool
+ """
+ leap_assert_type(value, bool)
+ self._settings.setValue(self.ALERTMISSING_KEY, value)
diff --git a/src/leap/gui/login.py b/src/leap/gui/login.py
new file mode 100644
index 00000000..c367b2fb
--- /dev/null
+++ b/src/leap/gui/login.py
@@ -0,0 +1,212 @@
+# -*- coding: utf-8 -*-
+# login.py
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+Login widget implementation
+"""
+import logging
+import keyring
+
+from PySide import QtCore, QtGui
+from ui_login import Ui_LoginWidget
+
+logger = logging.getLogger(__name__)
+
+
+class LoginWidget(QtGui.QWidget):
+ """
+ Login widget that emits signals to display the wizard or to
+ perform login.
+ """
+
+ # Emitted when the login button is clicked
+ login = QtCore.Signal()
+ # Emitted when the user selects "Other..." in the provider
+ # combobox or click "Create Account"
+ show_wizard = QtCore.Signal()
+
+ def __init__(self, settings, parent=None):
+ """
+ Constructs the LoginWidget.
+
+ :param settings: client wide settings
+ :type settings: LeapSettings
+ :param parent: The parent widget for this widget
+ :type parent: QWidget or None
+ """
+ QtGui.QWidget.__init__(self, parent)
+
+ self._settings = settings
+ self._selected_provider_index = -1
+
+ self.ui = Ui_LoginWidget()
+ self.ui.setupUi(self)
+
+ self.ui.chkRemember.stateChanged.connect(
+ self._remember_state_changed)
+ self.ui.chkRemember.setEnabled(keyring.get_keyring() is not None)
+
+ self.ui.lnPassword.setEchoMode(QtGui.QLineEdit.Password)
+
+ self.ui.btnLogin.clicked.connect(self.login)
+ self.ui.lnPassword.returnPressed.connect(self.login)
+
+ self.ui.lnUser.returnPressed.connect(self._focus_password)
+
+ self.ui.cmbProviders.currentIndexChanged.connect(
+ self._current_provider_changed)
+ self.ui.btnCreateAccount.clicked.connect(
+ self.show_wizard)
+
+ def _remember_state_changed(self, state):
+ """
+ Saves the remember state in the LeapSettings
+
+ :param state: possible stats can be Checked, Unchecked and
+ PartiallyChecked
+ :type state: QtCore.Qt.CheckState
+ """
+ enable = True if state == QtCore.Qt.Checked else False
+ self._settings.set_remember(enable)
+
+ def set_providers(self, provider_list):
+ """
+ Set the provider list to provider_list plus an "Other..." item
+ that triggers the wizard
+
+ :param provider_list: list of providers
+ :type provider_list: list of str
+ """
+ self.ui.cmbProviders.blockSignals(True)
+ self.ui.cmbProviders.clear()
+ self.ui.cmbProviders.addItems(provider_list + ["Other..."])
+ self.ui.cmbProviders.blockSignals(False)
+
+ def select_provider_by_name(self, name):
+ """
+ Given a provider name/domain, it selects it in the combobox
+
+ :param name: name or domain for the provider
+ :type name: str
+ """
+ provider_index = self.ui.cmbProviders.findText(name)
+ self.ui.cmbProviders.setCurrentIndex(provider_index)
+
+ def get_selected_provider(self):
+ """
+ Returns the selected provider in the combobox
+ """
+ return self.ui.cmbProviders.currentText()
+
+ def set_remember(self, value):
+ """
+ Checks the remember user and password checkbox
+
+ :param value: True to mark it checked, False otherwise
+ :type value: bool
+ """
+ self.ui.chkRemember.setChecked(value)
+
+ def get_remember(self):
+ """
+ Returns the remember checkbox state
+
+ :rtype: bool
+ """
+ return self.ui.chkRemember.isChecked()
+
+ def set_user(self, user):
+ """
+ Sets the user and focuses on the next field, password.
+
+ :param user: user to set the field to
+ :type user: str
+ """
+ self.ui.lnUser.setText(user)
+ self._focus_password()
+
+ def get_user(self):
+ """
+ Returns the user that appears in the widget
+
+ :rtype: str
+ """
+ return self.ui.lnUser.text()
+
+ def set_password(self, password):
+ """
+ Sets the password for the widget
+
+ :param password: password to set
+ :type password: str
+ """
+ self.ui.lnPassword.setText(password)
+
+ def get_password(self):
+ """
+ Returns the password that appears in the widget
+
+ :rtype: str
+ """
+ return self.ui.lnPassword.text()
+
+ def set_status(self, status, error=True):
+ """
+ Sets the status label at the login stage to status
+
+ :param status: status message
+ :type status: str
+ """
+ if error:
+ status = "<font color='red'><b>%s</b></font>" % (status,)
+ self.ui.lblStatus.setText(status)
+
+ def set_enabled(self, enabled=False):
+ """
+ Enables or disables all the login widgets
+
+ :param enabled: wether they should be enabled or not
+ :type enabled: bool
+ """
+ self.ui.lnUser.setEnabled(enabled)
+ self.ui.lnPassword.setEnabled(enabled)
+ self.ui.btnLogin.setEnabled(enabled)
+ self.ui.chkRemember.setEnabled(enabled)
+ self.ui.cmbProviders.setEnabled(enabled)
+
+ def _focus_password(self):
+ """
+ Focuses in the password lineedit
+ """
+ self.ui.lnPassword.setFocus()
+
+ def _current_provider_changed(self, param):
+ """
+ SLOT
+ TRIGGERS: self.ui.cmbProviders.currentIndexChanged
+ """
+ if param == (self.ui.cmbProviders.count() - 1):
+ self.show_wizard.emit()
+ # Leave the previously selected provider in the combobox
+ prev_provider = 0
+ if self._selected_provider_index != -1:
+ prev_provider = self._selected_provider_index
+ self.ui.cmbProviders.blockSignals(True)
+ self.ui.cmbProviders.setCurrentIndex(prev_provider)
+ self.ui.cmbProviders.blockSignals(False)
+ else:
+ self._selected_provider_index = param
diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py
index 752f5ef1..e135c798 100644
--- a/src/leap/gui/mainwindow.py
+++ b/src/leap/gui/mainwindow.py
@@ -37,6 +37,7 @@ from leap.config.providerconfig import ProviderConfig
from leap.crypto.srpauth import SRPAuth
from leap.gui.loggerwindow import LoggerWindow
from leap.gui.wizard import Wizard
+from leap.gui.login import LoginWidget
from leap.services.eip.eipbootstrapper import EIPBootstrapper
from leap.services.eip.eipconfig import EIPConfig
from leap.services.eip.providerbootstrapper import ProviderBootstrapper
@@ -135,11 +136,18 @@ class MainWindow(QtGui.QMainWindow):
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
- self.ui.lnPassword.setEchoMode(QtGui.QLineEdit.Password)
+ self._settings = LeapSettings(standalone)
+
+ self._login_widget = LoginWidget(
+ self._settings,
+ self.ui.stackedWidget.widget(self.LOGIN_INDEX))
+ self.ui.loginLayout.addWidget(self._login_widget)
- self.ui.btnLogin.clicked.connect(self._login)
- self.ui.lnUser.returnPressed.connect(self._focus_password)
- self.ui.lnPassword.returnPressed.connect(self._login)
+ self._login_widget.login.connect(self._login)
+ self._login_widget.show_wizard.connect(
+ self._launch_wizard)
+
+ self.ui.btnShowLog.clicked.connect(self._show_logger_window)
self.ui.stackedWidget.setCurrentIndex(self.LOGIN_INDEX)
@@ -206,10 +214,6 @@ class MainWindow(QtGui.QMainWindow):
self._vpn.qtsigs.process_finished.connect(
self._eip_finished)
- self.ui.chkRemember.stateChanged.connect(
- self._remember_state_changed)
- self.ui.chkRemember.setEnabled(keyring.get_keyring() is not None)
-
self.ui.action_sign_out.setEnabled(False)
self.ui.action_sign_out.triggered.connect(self._logout)
self.ui.action_about_leap.triggered.connect(self._about)
@@ -223,8 +227,12 @@ class MainWindow(QtGui.QMainWindow):
self._systray = None
+ self._action_eip_provider = QtGui.QAction(
+ self.tr("No default provider"), self)
+ self._action_eip_provider.setEnabled(False)
self._action_eip_status = QtGui.QAction(
- self.tr("Encrypted internet is OFF"), self)
+ self.tr("Encrypted internet is OFF"),
+ self)
self._action_eip_status.setEnabled(False)
self._action_eip_startstop = QtGui.QAction(
self.tr("Turn encryption ON"), self)
@@ -243,7 +251,6 @@ class MainWindow(QtGui.QMainWindow):
self._action_visible.triggered.connect(self._toggle_visible)
self._enabled_services = []
- self._settings = LeapSettings(standalone)
self._center_window()
@@ -280,6 +287,13 @@ class MainWindow(QtGui.QMainWindow):
self._finish_init()
def _rejected_wizard(self):
+ """
+ SLOT
+ TRIGGERS: self._wizard.rejected
+
+ Called if the wizard has been cancelled or closed before
+ finishing.
+ """
if self._wizard_firstrun:
self._settings.set_properprovider(False)
self.quit()
@@ -287,11 +301,24 @@ class MainWindow(QtGui.QMainWindow):
self._finish_init()
def _launch_wizard(self):
+ """
+ SLOT
+ TRIGGERS:
+ self._login_widget.show_wizard
+ self.ui.action_wizard.triggered
+
+ Also called in first run.
+
+ Launches the wizard, creating the object itself if not already
+ there.
+ """
if self._wizard is None:
self._wizard = Wizard(bypass_checks=self._bypass_checks)
self._wizard.accepted.connect(self._finish_init)
+ self.setVisible(False)
self._wizard.exec_()
self._wizard = None
+ self.setVisible(True)
def _get_leap_logging_handler(self):
"""
@@ -309,6 +336,11 @@ class MainWindow(QtGui.QMainWindow):
def _show_logger_window(self):
"""
+ SLOT
+ TRIGGERS:
+ self.ui.action_show_logs.triggered
+ self.ui.btnShowLog.clicked
+
Displays the window with the history of messages logged until now
and displays the new ones on arrival.
"""
@@ -318,14 +350,12 @@ class MainWindow(QtGui.QMainWindow):
logger.error('Leap logger handler not found')
else:
self._logger_window = LoggerWindow(handler=leap_log_handler)
- self._logger_window.show()
+ self._logger_window.setVisible(
+ not self._logger_window.isVisible())
+ self.ui.btnShowLog.setChecked(self._logger_window.isVisible())
else:
- self._logger_window.show()
-
- def _remember_state_changed(self, state):
- enable = True if state == QtCore.Qt.Checked else False
- self.ui.chkAutoLogin.setEnabled(enable)
- self._settings.set_remember(enable)
+ self._logger_window.setVisible(not self._logger_window.isVisible())
+ self.ui.btnShowLog.setChecked(self._logger_window.isVisible())
def _new_updates_available(self, req):
"""
@@ -376,8 +406,21 @@ class MainWindow(QtGui.QMainWindow):
msg)
def _finish_init(self):
- self.ui.cmbProviders.clear()
- self.ui.cmbProviders.addItems(self._configured_providers())
+ """
+ SLOT
+ TRIGGERS:
+ self._wizard.accepted
+
+ Also called at the end of the constructor if not first run,
+ and after _rejected_wizard if not first run.
+
+ Implements the behavior after either constructing the
+ mainwindow object, loading the saved user/password, or after
+ the wizard has been executed.
+ """
+ # XXX: May be this can be divided into two methods?
+
+ self._login_widget.set_providers(self._configured_providers())
self._show_systray()
self.show()
@@ -387,20 +430,18 @@ class MainWindow(QtGui.QMainWindow):
# select the configured provider in the combo box
domain = self._wizard.get_domain()
- provider_index = self.ui.cmbProviders.findText(domain)
- self.ui.cmbProviders.setCurrentIndex(provider_index)
+ self._login_widget.select_provider_by_name(domain)
- self.ui.chkRemember.setChecked(self._wizard.get_remember())
+ self._login_widget.set_remember(self._wizard.get_remember())
self._enabled_services = list(self._wizard.get_services())
self._settings.set_enabled_services(
- self.ui.cmbProviders.currentText(),
+ self._login_widget.get_selected_provider(),
self._enabled_services)
if possible_username is not None:
- self.ui.lnUser.setText(possible_username)
- self._focus_password()
+ self._login_widget.set_user(possible_username)
if possible_password is not None:
- self.ui.lnPassword.setText(possible_password)
- self.ui.chkRemember.setChecked(True)
+ self._login_widget.set_password(possible_password)
+ self._login_widget.set_remember(True)
self._login()
self._wizard = None
self._settings.set_properprovider(True)
@@ -411,7 +452,6 @@ class MainWindow(QtGui.QMainWindow):
return
saved_user = self._settings.get_user()
- auto_login = self._settings.get_autologin()
try:
username, domain = saved_user.split('@')
@@ -422,15 +462,12 @@ class MainWindow(QtGui.QMainWindow):
if saved_user is not None:
# fill the username
- self.ui.lnUser.setText(username)
+ self._login_widget.set_user(username)
# select the configured provider in the combo box
- provider_index = self.ui.cmbProviders.findText(domain)
- self.ui.cmbProviders.setCurrentIndex(provider_index)
+ self._login_widget.select_provider_by_name(domain)
- self.ui.chkRemember.setChecked(True)
- self.ui.chkAutoLogin.setEnabled(self.ui.chkRemember
- .isEnabled())
+ self._login_widget.set_remember(True)
saved_password = None
try:
@@ -441,12 +478,8 @@ class MainWindow(QtGui.QMainWindow):
logger.debug("Incorrect Password. %r." % (e,))
if saved_password is not None:
- self.ui.lnPassword.setText(saved_password.decode("utf8"))
-
- # Only automatically login if there is a saved user
- # and the password was retrieved right
- self.ui.chkAutoLogin.setChecked(auto_login)
- if auto_login and saved_password:
+ self._login_widget.set_password(
+ saved_password.decode("utf8"))
self._login()
def _try_autostart_eip(self):
@@ -460,6 +493,8 @@ class MainWindow(QtGui.QMainWindow):
"provider configured")
return
+ self._action_eip_provider.setText(default_provider)
+
self._enabled_services = self._settings.get_enabled_services(
default_provider)
@@ -487,37 +522,48 @@ class MainWindow(QtGui.QMainWindow):
systrayMenu.addSeparator()
systrayMenu.addAction(self.ui.action_quit)
systrayMenu.addSeparator()
+ systrayMenu.addAction(self._action_eip_provider)
systrayMenu.addAction(self._action_eip_status)
systrayMenu.addAction(self._action_eip_startstop)
self._systray = QtGui.QSystemTrayIcon(self)
self._systray.setContextMenu(systrayMenu)
self._systray.setIcon(QtGui.QIcon(self.ERROR_ICON))
self._systray.setVisible(True)
- self._systray.activated.connect(self._toggle_visible)
+ self._systray.activated.connect(self._tray_activated)
- def _toggle_visible(self, reason=None):
+ def _tray_activated(self, reason=None):
"""
SLOT
TRIGGER: self._systray.activated
- Toggles the window visibility
+ Displays the context menu from the tray icon
"""
get_action = lambda visible: (
- self.tr("Show"),
- self.tr("Hide"))[int(visible)]
-
- minimized = self.isMinimized()
+ self.tr("Show Main Window"),
+ self.tr("Hide Main Window"))[int(visible)]
if reason != QtGui.QSystemTrayIcon.Context:
- # do show
- if minimized:
- self.showNormal()
- self.setVisible(not self.isVisible())
-
# set labels
visible = self.isVisible()
self._action_visible.setText(get_action(visible))
+ context_menu = self._systray.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._systray.geometry().center())
+
+ def _toggle_visible(self):
+ """
+ SLOT
+ TRIGGER: self._action_visible.triggered
+
+ Toggles the window visibility
+ """
+ self.setVisible(not self.isVisible())
+
def _center_window(self):
"""
Centers the mainwindow based on the desktop geometry
@@ -542,6 +588,9 @@ class MainWindow(QtGui.QMainWindow):
def _about(self):
"""
+ SLOT
+ TRIGGERS: self.ui.action_about_leap.triggered
+
Display the About LEAP dialog
"""
QtGui.QMessageBox.about(
@@ -579,7 +628,6 @@ class MainWindow(QtGui.QMainWindow):
self._settings.set_geometry(self.saveGeometry())
self._settings.set_windowstate(self.saveState())
- self._settings.set_autologin(self.ui.chkAutoLogin.isChecked())
QtGui.QMainWindow.closeEvent(self, e)
@@ -614,23 +662,6 @@ class MainWindow(QtGui.QMainWindow):
is_proper_provider = self._settings.get_properprovider()
return not (has_provider_on_disk and is_proper_provider)
- def _focus_password(self):
- """
- Focuses in the password lineedit
- """
- self.ui.lnPassword.setFocus()
-
- def _set_status(self, status, error=True):
- """
- Sets the status label at the login stage to status
-
- :param status: status message
- :type status: str
- """
- if error:
- status = "<font color='red'><b>%s</b></font>" % (status,)
- self.ui.lblStatus.setText(status)
-
def _set_eip_status(self, status, error=False):
"""
Sets the status label at the VPN stage to status
@@ -643,28 +674,13 @@ class MainWindow(QtGui.QMainWindow):
status = "<font color='red'><b>%s</b></font>" % (status,)
self.ui.lblEIPStatus.setText(status)
- def _login_set_enabled(self, enabled=False):
- """
- Enables or disables all the login widgets
-
- :param enabled: wether they should be enabled or not
- :type enabled: bool
- """
- self.ui.lnUser.setEnabled(enabled)
- self.ui.lnPassword.setEnabled(enabled)
- self.ui.btnLogin.setEnabled(enabled)
- self.ui.chkRemember.setEnabled(enabled)
- if not enabled:
- self.ui.chkAutoLogin.setEnabled(False)
- self.ui.cmbProviders.setEnabled(enabled)
-
def _download_provider_config(self):
"""
Starts the bootstrapping sequence. It will download the
provider configuration if it's not present, otherwise will
emit the corresponding signals inmediately
"""
- provider = self.ui.cmbProviders.currentText()
+ provider = self._login_widget.get_selected_provider()
self._provider_bootstrapper.run_provider_select_checks(
provider,
@@ -684,7 +700,7 @@ class MainWindow(QtGui.QMainWindow):
:type data: dict
"""
if data[self._provider_bootstrapper.PASSED_KEY]:
- provider = self.ui.cmbProviders.currentText()
+ provider = self._login_widget.get_selected_provider()
if self._provider_config.loaded() or \
self._provider_config.load(os.path.join("leap",
"providers",
@@ -694,12 +710,13 @@ class MainWindow(QtGui.QMainWindow):
self._provider_config,
download_if_needed=True)
else:
- self._set_status(
+ self._login_widget.set_status(
self.tr("Could not load provider configuration"))
- self._login_set_enabled(True)
+ self._login_widget.set_enabled(True)
else:
- self._set_status(data[self._provider_bootstrapper.ERROR_KEY])
- self._login_set_enabled(True)
+ self._login_widget.set_status(
+ data[self._provider_bootstrapper.ERROR_KEY])
+ self._login_widget.set_enabled(True)
def _login(self):
"""
@@ -715,29 +732,32 @@ class MainWindow(QtGui.QMainWindow):
"""
leap_assert(self._provider_config, "We need a provider config")
- username = self.ui.lnUser.text()
- password = self.ui.lnPassword.text()
- provider = self.ui.cmbProviders.currentText()
+ username = self._login_widget.get_user()
+ password = self._login_widget.get_password()
+ provider = self._login_widget.get_selected_provider()
self._enabled_services = self._settings.get_enabled_services(
- self.ui.cmbProviders.currentText())
+ self._login_widget.get_selected_provider())
if len(provider) == 0:
- self._set_status(self.tr("Please select a valid provider"))
+ self._login_widget.set_status(
+ self.tr("Please select a valid provider"))
return
if len(username) == 0:
- self._set_status(self.tr("Please provide a valid username"))
+ self._login_widget.set_status(
+ self.tr("Please provide a valid username"))
return
if len(password) == 0:
- self._set_status(self.tr("Please provide a valid Password"))
+ self._login_widget.set_status(
+ self.tr("Please provide a valid Password"))
return
- self._set_status(self.tr("Logging in..."), error=False)
- self._login_set_enabled(False)
+ self._login_widget.set_status(self.tr("Logging in..."), error=False)
+ self._login_widget.set_enabled(False)
- if self.ui.chkRemember.isChecked():
+ if self._login_widget.get_remember():
# in the keyring and in the settings
# we store the value 'usename@provider'
username_domain = (username + '@' + provider).encode("utf8")
@@ -765,8 +785,8 @@ class MainWindow(QtGui.QMainWindow):
leap_assert(self._provider_config, "We need a provider config!")
if data[self._provider_bootstrapper.PASSED_KEY]:
- username = self.ui.lnUser.text().encode("utf8")
- password = self.ui.lnPassword.text().encode("utf8")
+ username = self._login_widget.get_user().encode("utf8")
+ password = self._login_widget.get_password().encode("utf8")
if self._srp_auth is None:
self._srp_auth = SRPAuth(self._provider_config)
@@ -778,8 +798,9 @@ class MainWindow(QtGui.QMainWindow):
# TODO: Add errback!
self._login_defer = self._srp_auth.authenticate(username, password)
else:
- self._set_status(data[self._provider_bootstrapper.ERROR_KEY])
- self._login_set_enabled(True)
+ self._login_widget.set_status(
+ data[self._provider_bootstrapper.ERROR_KEY])
+ self._login_widget.set_enabled(True)
def _authentication_finished(self, ok, message):
"""
@@ -789,7 +810,7 @@ class MainWindow(QtGui.QMainWindow):
Once the user is properly authenticated, try starting the EIP
service
"""
- self._set_status(message, error=not ok)
+ self._login_widget.set_status(message, error=not ok)
if ok:
self.ui.action_sign_out.setEnabled(True)
# We leave a bit of room for the user to see the
@@ -798,7 +819,7 @@ class MainWindow(QtGui.QMainWindow):
QtCore.QTimer.singleShot(1000, self._switch_to_status)
self._login_defer = None
else:
- self._login_set_enabled(True)
+ self._login_widget.set_enabled(True)
def _switch_to_status(self):
"""
@@ -809,8 +830,8 @@ class MainWindow(QtGui.QMainWindow):
self._soledad_bootstrapper.run_soledad_setup_checks(
self._provider_config,
- self.ui.lnUser.text(),
- self.ui.lnPassword.text(),
+ self._login_widget.get_user(),
+ self._login_widget.get_password(),
download_if_needed=True)
self._download_eip_config()
@@ -948,6 +969,7 @@ class MainWindow(QtGui.QMainWindow):
self._settings.set_defaultprovider(
provider_config.get_domain())
+ self._action_eip_provider.setText(provider_config.get_domain())
self.ui.btnEipStartStop.setText(self.tr("Turn Encryption OFF"))
self.ui.btnEipStartStop.disconnect(self)
self.ui.btnEipStartStop.clicked.connect(
@@ -1163,9 +1185,9 @@ class MainWindow(QtGui.QMainWindow):
"""
self.ui.action_sign_out.setEnabled(False)
self.ui.stackedWidget.setCurrentIndex(self.LOGIN_INDEX)
- self.ui.lnPassword.setText("")
- self._login_set_enabled(True)
- self._set_status("")
+ self._login_widget.set_password("")
+ self._login_widget.set_enabled(True)
+ self._login_widget.set_status("")
def _intermediate_stage(self, data):
"""
@@ -1182,8 +1204,9 @@ class MainWindow(QtGui.QMainWindow):
"""
passed = data[self._provider_bootstrapper.PASSED_KEY]
if not passed:
- self._login_set_enabled(True)
- self._set_status(data[self._provider_bootstrapper.ERROR_KEY])
+ self._login_widget.set_enabled(True)
+ self._login_widget.set_status(
+ data[self._provider_bootstrapper.ERROR_KEY])
def _eip_intermediate_stage(self, data):
"""
diff --git a/src/leap/gui/ui/login.ui b/src/leap/gui/ui/login.ui
new file mode 100644
index 00000000..88c9ef44
--- /dev/null
+++ b/src/leap/gui/ui/login.ui
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>LoginWidget</class>
+ <widget class="QWidget" name="LoginWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>356</width>
+ <height>219</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="5" column="2">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="1" colspan="2">
+ <widget class="QComboBox" name="cmbProviders"/>
+ </item>
+ <item row="5" column="0">
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="6" column="1">
+ <widget class="QPushButton" name="btnCreateAccount">
+ <property name="text">
+ <string>Create a new account</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>&lt;b&gt;Provider:&lt;/b&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1" colspan="2">
+ <widget class="QLineEdit" name="lnPassword">
+ <property name="inputMask">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1" colspan="2">
+ <widget class="QLineEdit" name="lnUser"/>
+ </item>
+ <item row="4" column="1" colspan="2">
+ <widget class="QCheckBox" name="chkRemember">
+ <property name="text">
+ <string>Remember username and password</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>&lt;b&gt;Username:&lt;/b&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>&lt;b&gt;Password:&lt;/b&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1">
+ <widget class="QPushButton" name="btnLogin">
+ <property name="text">
+ <string>Log In</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="3">
+ <widget class="QLabel" name="lblStatus">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>cmbProviders</tabstop>
+ <tabstop>lnUser</tabstop>
+ <tabstop>lnPassword</tabstop>
+ <tabstop>chkRemember</tabstop>
+ <tabstop>btnLogin</tabstop>
+ <tabstop>btnCreateAccount</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/leap/gui/ui/mainwindow.ui b/src/leap/gui/ui/mainwindow.ui
index fdf5c167..4874a324 100644
--- a/src/leap/gui/ui/mainwindow.ui
+++ b/src/leap/gui/ui/mainwindow.ui
@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
- <width>415</width>
+ <width>429</width>
<height>579</height>
</rect>
</property>
@@ -28,19 +28,6 @@
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
- <item row="15" column="2">
- <spacer name="verticalSpacer_2">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
<item row="6" column="2">
<spacer name="verticalSpacer">
<property name="orientation">
@@ -54,18 +41,21 @@
</property>
</spacer>
</item>
- <item row="7" column="3" colspan="2">
- <spacer name="horizontalSpacer_2">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
+ <item row="7" column="2">
+ <widget class="QLabel" name="label">
+ <property name="autoFillBackground">
+ <bool>false</bool>
</property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
+ <property name="text">
+ <string/>
</property>
- </spacer>
+ <property name="pixmap">
+ <pixmap resource="../../../../data/resources/mainwindow.qrc">:/images/leap-color-big.png</pixmap>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
</item>
<item row="7" column="0" colspan="2">
<spacer name="horizontalSpacer">
@@ -80,25 +70,18 @@
</property>
</spacer>
</item>
- <item row="8" column="0" colspan="5">
+ <item row="10" column="0" colspan="5">
<widget class="QStackedWidget" name="stackedWidget">
<property name="currentIndex">
- <number>1</number>
+ <number>0</number>
</property>
- <widget class="QWidget" name="page">
+ <widget class="QWidget" name="loginPage">
<layout class="QGridLayout" name="gridLayout_2">
- <item row="4" column="2">
- <widget class="QCheckBox" name="chkRemember">
- <property name="text">
- <string>Remember</string>
- </property>
- </widget>
- </item>
- <item row="2" column="2" colspan="2">
- <widget class="QLineEdit" name="lnUser"/>
+ <item row="0" column="1">
+ <layout class="QHBoxLayout" name="loginLayout"/>
</item>
- <item row="2" column="0">
- <spacer name="horizontalSpacer_3">
+ <item row="0" column="2">
+ <spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
@@ -110,15 +93,8 @@
</property>
</spacer>
</item>
- <item row="6" column="2">
- <widget class="QPushButton" name="btnLogin">
- <property name="text">
- <string>Login</string>
- </property>
- </widget>
- </item>
- <item row="2" column="4">
- <spacer name="horizontalSpacer_4">
+ <item row="0" column="0">
+ <spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
@@ -130,66 +106,6 @@
</property>
</spacer>
</item>
- <item row="1" column="1">
- <widget class="QLabel" name="label_4">
- <property name="text">
- <string>&lt;b&gt;Provider:&lt;/b&gt;</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QLabel" name="label_3">
- <property name="text">
- <string>&lt;b&gt;Password:&lt;/b&gt;</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
- </item>
- <item row="1" column="2" colspan="2">
- <widget class="QComboBox" name="cmbProviders"/>
- </item>
- <item row="3" column="2" colspan="2">
- <widget class="QLineEdit" name="lnPassword">
- <property name="inputMask">
- <string/>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QLabel" name="label_2">
- <property name="text">
- <string>&lt;b&gt;User:&lt;/b&gt;</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
- </item>
- <item row="0" column="0" colspan="5">
- <widget class="QLabel" name="lblStatus">
- <property name="text">
- <string/>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- </widget>
- </item>
- <item row="5" column="2" colspan="2">
- <widget class="QCheckBox" name="chkAutoLogin">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="text">
- <string>Automatically login</string>
- </property>
- </widget>
- </item>
</layout>
</widget>
<widget class="QWidget" name="page_2">
@@ -295,7 +211,7 @@
<item row="2" column="1" colspan="4">
<widget class="QPushButton" name="btnEipStartStop">
<property name="text">
- <string>Start EIP</string>
+ <string>Turn Encryption ON</string>
</property>
</widget>
</item>
@@ -303,21 +219,31 @@
</widget>
</widget>
</item>
- <item row="7" column="2">
- <widget class="QLabel" name="label">
- <property name="autoFillBackground">
- <bool>false</bool>
+ <item row="7" column="3" colspan="2">
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
</property>
- <property name="text">
- <string/>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
</property>
- <property name="pixmap">
- <pixmap resource="../../../../data/resources/mainwindow.qrc">:/images/leap-color-big.png</pixmap>
+ </spacer>
+ </item>
+ <item row="17" column="2">
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
</property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
</property>
- </widget>
+ </spacer>
</item>
<item row="0" column="0" colspan="5">
<layout class="QGridLayout" name="gridLayout_4">
@@ -388,6 +314,39 @@
</item>
</layout>
</item>
+ <item row="9" column="2" colspan="3">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <spacer name="horizontalSpacer_10">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnShowLog">
+ <property name="text">
+ <string>Show Log</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
@@ -395,8 +354,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>415</width>
- <height>21</height>
+ <width>429</width>
+ <height>25</height>
</rect>
</property>
<widget class="QMenu" name="menuSession">
@@ -415,15 +374,7 @@
<addaction name="separator"/>
<addaction name="action_about_leap"/>
</widget>
- <widget class="QMenu" name="menuSettings">
- <property name="title">
- <string>&amp;Utils</string>
- </property>
- <addaction name="action_wizard"/>
- <addaction name="action_show_logs"/>
- </widget>
<addaction name="menuSession"/>
- <addaction name="menuSettings"/>
<addaction name="menuHelp"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
@@ -458,13 +409,6 @@
</property>
</action>
</widget>
- <tabstops>
- <tabstop>lnUser</tabstop>
- <tabstop>lnPassword</tabstop>
- <tabstop>chkRemember</tabstop>
- <tabstop>btnLogin</tabstop>
- <tabstop>cmbProviders</tabstop>
- </tabstops>
<resources>
<include location="../../../../data/resources/mainwindow.qrc"/>
<include location="../../../../data/resources/locale.qrc"/>
diff --git a/src/leap/platform_init/initializers.py b/src/leap/platform_init/initializers.py
index 2e8cbe95..d72dc61f 100644
--- a/src/leap/platform_init/initializers.py
+++ b/src/leap/platform_init/initializers.py
@@ -22,10 +22,15 @@ Platform dependant initializing code
import logging
import os
import platform
+import stat
import subprocess
+import tempfile
from PySide import QtGui
+from leap.config.leapsettings import LeapSettings
+from leap.services.eip import vpnlaunchers
+
logger = logging.getLogger(__name__)
# NOTE we could use a deferToThread here, but should
@@ -75,6 +80,30 @@ def _windows_has_tap_device():
return False
+def _get_missing_updown_dialog():
+ """
+ Creates a dialog for notifying of missing updown scripts.
+ Returns that dialog.
+
+ :rtype: QtGui.QMessageBox instance
+ """
+ msg = QtGui.QMessageBox()
+ msg.setWindowTitle(msg.tr("Missing up/down scripts"))
+ msg.setText(msg.tr(
+ "LEAPClient needs to install up/down scripts "
+ "for Encrypted Internet to work properly. "
+ "Would you like to proceed?"))
+ msg.setInformativeText(msg.tr(
+ "It looks like either you have not installed "
+ "LEAP Client in a permanent location or you have an "
+ "incomplete installation. This will ask for "
+ "administrative privileges."))
+ msg.setStandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)
+ msg.addButton("No, don't ask again", QtGui.QMessageBox.RejectRole)
+ msg.setDefaultButton(QtGui.QMessageBox.Yes)
+ return msg
+
+
def WindowsInitializer():
"""
Raises a dialog in case that the windows tap driver has not been found
@@ -128,15 +157,73 @@ def _darwin_has_tun_kext():
return has_tun_and_startup
+def _darwin_install_missing_scripts(badexec, notfound):
+ """
+ Tries to install the missing up/down scripts.
+
+ :param badexec: error for notifying execution error during command.
+ :type badexec: str
+ :param notfound: error for notifying missing path.
+ :type notfound: str
+ """
+ # We expect to execute this from some way of bundle, since
+ # the up/down scripts should be put in place by the installer.
+ installer_path = os.path.join(
+ os.getcwd(),
+ "..",
+ "Resources",
+ "openvpn")
+ launcher = vpnlaunchers.DarwinVPNLauncher
+ if os.path.isdir(installer_path):
+ tempscript = tempfile.mktemp()
+ try:
+ cmd = launcher.OSASCRIPT_BIN
+ scriptlines = launcher.cmd_for_missing_scripts(installer_path)
+ with open(tempscript, 'w') as f:
+ f.write(scriptlines)
+ st = os.stat(tempscript)
+ os.chmod(tempscript, st.st_mode | stat.S_IEXEC | stat.S_IXUSR |
+ stat.S_IXGRP | stat.S_IXOTH)
+
+ osascript = launcher.OSX_ASADMIN % ("/bin/sh %s" % (tempscript,),)
+ cmdline = ["%s -e '%s'" % (cmd, osascript)]
+ ret = subprocess.call(
+ cmdline, stdout=subprocess.PIPE,
+ shell=True)
+ assert(ret)
+ except Exception as exc:
+ logger.error(badexec)
+ logger.error("Error was: %r" % (exc,))
+ f.close()
+ finally:
+ # XXX remove file
+ pass
+ else:
+ logger.error(notfound)
+ logger.debug('path searched: %s' % (installer_path,))
+
+
def DarwinInitializer():
"""
Raises a dialog in case that the osx tuntap driver has not been found
in the registry, asking the user for permission to install the driver
"""
- NOTFOUND_MSG = ("Tried to install tuntaposx kext, but the installer "
- "is not found inside this bundle.")
- BADEXEC_MSG = ("Tried to install tuntaposx kext, but the installer "
- "failed to be launched.")
+ # XXX split this function into several
+
+ NOTFOUND_MSG = ("Tried to install %s, but %s "
+ "not found inside this bundle.")
+ BADEXEC_MSG = ("Tried to install %s, but %s "
+ "failed to %s.")
+
+ TUNTAP_NOTFOUND_MSG = NOTFOUND_MSG % (
+ "tuntaposx kext", "the installer")
+ TUNTAP_BADEXEC_MSG = BADEXEC_MSG % (
+ "tuntaposx kext", "the installer", "be launched")
+
+ UPDOWN_NOTFOUND_MSG = NOTFOUND_MSG % (
+ "updown scripts", "those were")
+ UPDOWN_BADEXEC_MSG = BADEXEC_MSG % (
+ "updown scripts", "they", "be copied")
# TODO DRY this with other cases, and
# factor out to _should_install() function.
@@ -170,6 +257,28 @@ def DarwinInitializer():
cmd, stdout=subprocess.PIPE,
shell=True)
except:
- logger.error(BADEXEC_MSG)
+ logger.error(TUNTAP_BADEXEC_MSG)
else:
- logger.error(NOTFOUND_MSG)
+ logger.error(TUNTAP_NOTFOUND_MSG)
+
+ config = LeapSettings()
+ alert_missing = config.get_alert_missing_scripts()
+ missing_scripts = vpnlaunchers.DarwinVPNLauncher.missing_updown_scripts
+ if alert_missing and missing_scripts():
+ msg = _get_missing_updown_dialog()
+ ret = msg.exec_()
+
+ if ret == QtGui.QMessageBox.Yes:
+ _darwin_install_missing_scripts(
+ UPDOWN_BADEXEC_MSG,
+ UPDOWN_NOTFOUND_MSG)
+
+ elif ret == QtGui.QMessageBox.No:
+ logger.debug("Not installing missing scripts, "
+ "user decided to ignore our warning.")
+
+ elif ret == QtGui.QMessageBox.Rejected:
+ logger.debug(
+ "Setting alert_missing_scripts to False, we will not "
+ "ask again")
+ config.set_alert_missing_scripts(False)
diff --git a/src/leap/services/abstractbootstrapper.py b/src/leap/services/abstractbootstrapper.py
index 7bebdc15..2cbd56bc 100644
--- a/src/leap/services/abstractbootstrapper.py
+++ b/src/leap/services/abstractbootstrapper.py
@@ -48,14 +48,14 @@ class AbstractBootstrapper(QtCore.QObject):
"""
QtCore.QObject.__init__(self)
- leap_assert(self._gui_errback.im_func == \
- AbstractBootstrapper._gui_errback.im_func,
+ leap_assert(self._gui_errback.im_func ==
+ AbstractBootstrapper._gui_errback.im_func,
"Cannot redefine _gui_errback")
- leap_assert(self._errback.im_func == \
- AbstractBootstrapper._errback.im_func,
+ leap_assert(self._errback.im_func ==
+ AbstractBootstrapper._errback.im_func,
"Cannot redefine _errback")
- leap_assert(self._gui_notify.im_func == \
- AbstractBootstrapper._gui_notify.im_func,
+ leap_assert(self._gui_notify.im_func ==
+ AbstractBootstrapper._gui_notify.im_func,
"Cannot redefine _gui_notify")
# **************************************************** #
@@ -87,9 +87,9 @@ class AbstractBootstrapper(QtCore.QObject):
if self._err_msg is not None \
else str(failure.value)
self._signal_to_emit.emit({
- self.PASSED_KEY: False,
- self.ERROR_KEY: err_msg
- })
+ self.PASSED_KEY: False,
+ self.ERROR_KEY: err_msg
+ })
failure.trap(Exception)
def _errback(self, failure, signal=None):
diff --git a/src/leap/services/eip/eipbootstrapper.py b/src/leap/services/eip/eipbootstrapper.py
index 51c3dab4..4da8f90f 100644
--- a/src/leap/services/eip/eipbootstrapper.py
+++ b/src/leap/services/eip/eipbootstrapper.py
@@ -173,8 +173,8 @@ class EIPBootstrapper(AbstractBootstrapper):
cb_chain = [
(self._download_config, self.download_config),
- (self._download_client_certificates, \
- self.download_client_certificate)
+ (self._download_client_certificates,
+ self.download_client_certificate)
]
self.addCallbackChain(cb_chain)
diff --git a/src/leap/services/eip/vpnlaunchers.py b/src/leap/services/eip/vpnlaunchers.py
index 952d3618..6c2ff006 100644
--- a/src/leap/services/eip/vpnlaunchers.py
+++ b/src/leap/services/eip/vpnlaunchers.py
@@ -29,6 +29,7 @@ except ImportError:
pass # ignore, probably windows
from abc import ABCMeta, abstractmethod
+from functools import partial
from leap.common.check import leap_assert, leap_assert_type
from leap.common.files import which
@@ -105,22 +106,37 @@ def get_platform_launcher():
def _is_pkexec_in_system():
+ """
+ Checks the existence of the pkexec binary in system.
+ """
pkexec_path = which('pkexec')
if len(pkexec_path) == 0:
return False
return True
-def _has_updown_scripts(path):
+def _has_updown_scripts(path, warn=True):
"""
- Checks the existence of the up/down scripts
+ Checks the existence of the up/down scripts.
+
+ :param path: the path to be checked
+ :type path: str
+
+ :param warn: whether we should log the absence
+ :type warn: bool
+
+ :rtype: bool
"""
- # XXX should check permissions too
is_file = os.path.isfile(path)
- if not is_file:
- logger.error("Could not find up/down scripts. " +
- "Might produce DNS leaks.")
- return is_file
+ if warn and not is_file:
+ logger.error("Could not find up/down script %s. "
+ "Might produce DNS leaks." % (path,))
+
+ is_exe = os.access(path, os.X_OK)
+ if warn and not is_exe:
+ logger.error("Up/down script %s is not executable. "
+ "Might produce DNS leaks." % (path,))
+ return is_file and is_exe
def _is_auth_agent_running():
@@ -229,7 +245,6 @@ class LinuxVPNLauncher(VPNLauncher):
openvpn_configuration = eipconfig.get_openvpn_configuration()
- # FIXME: sanitize this! --
for key, value in openvpn_configuration.items():
args += ['--%s' % (key,), value]
@@ -294,15 +309,40 @@ class DarwinVPNLauncher(VPNLauncher):
OSASCRIPT_BIN = '/usr/bin/osascript'
OSX_ASADMIN = "do shell script \"%s\" with administrator privileges"
- OPENVPN_BIN = 'openvpn.leap'
- INSTALL_PATH = "/Applications/LEAPClient.app/"
+
+ INSTALL_PATH = "/Applications/LEAP\ Client.app"
# OPENVPN_BIN = "/%s/Contents/Resources/openvpn.leap" % (
# self.INSTALL_PATH,)
- UP_SCRIPT = "/%s/client.up.sh" % (INSTALL_PATH,)
- DOWN_SCRIPT = "/%s/client.down.sh" % (INSTALL_PATH,)
+ OPENVPN_BIN = 'openvpn.leap'
+ OPENVPN_PATH = "%s/Contents/Resources/openvpn" % (INSTALL_PATH,)
+
+ UP_SCRIPT = "%s/client.up.sh" % (OPENVPN_PATH,)
+ DOWN_SCRIPT = "%s/client.down.sh" % (OPENVPN_PATH,)
+ OPENVPN_DOWN_PLUGIN = '%s/openvpn-down-root.so' % (OPENVPN_PATH,)
+
+ UPDOWN_FILES = (UP_SCRIPT, DOWN_SCRIPT, OPENVPN_DOWN_PLUGIN)
+
+ @classmethod
+ def missing_updown_scripts(kls):
+ """
+ Returns what updown scripts are missing.
+ :rtype: list
+ """
+ file_exist = partial(_has_updown_scripts, warn=False)
+ zipped = zip(kls.UPDOWN_FILES, map(file_exist, kls.UPDOWN_FILES))
+ missing = filter(lambda (path, exists): exists is False, zipped)
+ return [path for path, exists in missing]
- # TODO: Add
- # OPENVPN_DOWN_ROOT = "/usr/lib/openvpn/openvpn-down-root.so"
+ @classmethod
+ def cmd_for_missing_scripts(kls, frompath):
+ """
+ Returns a command that can copy the missing scripts.
+ :rtype: str
+ """
+ to = kls.OPENVPN_PATH
+ cmd = "#!/bin/sh\nmkdir -p %s\ncp \"%s/\"* %s" % (to, frompath, to)
+ #return kls.OSX_ASADMIN % cmd
+ return cmd
def get_vpn_command(self, eipconfig=None, providerconfig=None,
socket_host=None, socket_port="unix"):
@@ -365,20 +405,19 @@ class DarwinVPNLauncher(VPNLauncher):
'server'
]
- # FIXME: sanitize this! --
-
openvpn_configuration = eipconfig.get_openvpn_configuration()
for key, value in openvpn_configuration.items():
args += ['--%s' % (key,), value]
+ user = getpass.getuser()
args += [
- '--user', getpass.getuser(),
+ '--user', user,
'--group', grp.getgrgid(os.getgroups()[-1]).gr_name
]
if socket_port == "unix":
args += [
- '--management-client-user', getpass.getuser()
+ '--management-client-user', user
]
args += [
@@ -391,13 +430,21 @@ class DarwinVPNLauncher(VPNLauncher):
args += [
'--up', self.UP_SCRIPT,
]
+
if _has_updown_scripts(self.DOWN_SCRIPT):
args += [
- '--down', self.DOWN_SCRIPT,
- # FIXME add down-plugin
- # '--plugin', self.OPENVPN_DOWN_ROOT,
- # '\'script_type=down %s\'' % self.DOWN_SCRIPT
- ]
+ '--down', self.DOWN_SCRIPT]
+
+ # should have the down script too
+ if _has_updown_scripts(self.OPENVPN_DOWN_PLUGIN):
+ args += [
+ '--plugin', self.OPENVPN_DOWN_PLUGIN,
+ '\'%s\'' % self.DOWN_SCRIPT
+ ]
+
+ # we set user to be passed to the up/down scripts
+ args += [
+ '--setenv', "LEAPUSER", "%s" % (user,)]
args += [
'--cert', eipconfig.get_client_cert_path(providerconfig),
@@ -405,6 +452,9 @@ class DarwinVPNLauncher(VPNLauncher):
'--ca', providerconfig.get_ca_cert_path()
]
+ # We are using osascript until we can write a proper wrapper
+ # for privilege escalation.
+
command = self.OSASCRIPT_BIN
cmd_args = ["-e", self.OSX_ASADMIN % (' '.join(args),)]
diff --git a/src/leap/services/mail/smtpbootstrapper.py b/src/leap/services/mail/smtpbootstrapper.py
index ea480c6d..e8af5349 100644
--- a/src/leap/services/mail/smtpbootstrapper.py
+++ b/src/leap/services/mail/smtpbootstrapper.py
@@ -94,11 +94,10 @@ class SMTPBootstrapper(AbstractBootstrapper):
# Not modified
if res.status_code == 304:
logger.debug("SMTP definition has not been modified")
- self._smtp_config.load(os.path.join("leap",
- "providers",
- self._provider_config.\
- get_domain(),
- "smtp-service.json"))
+ self._smtp_config.load(os.path.join(
+ "leap", "providers",
+ self._provider_config.get_domain(),
+ "smtp-service.json"))
else:
smtp_definition, mtime = get_content(res)
diff --git a/src/leap/util/leap_log_handler.py b/src/leap/util/leap_log_handler.py
index 0e598032..5b8ae789 100644
--- a/src/leap/util/leap_log_handler.py
+++ b/src/leap/util/leap_log_handler.py
@@ -14,7 +14,6 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
"""
Custom handler for the logger window.
"""
@@ -23,23 +22,27 @@ import logging
from PySide import QtCore
-class LeapLogHandler(logging.Handler, QtCore.QObject):
+class LogHandler(logging.Handler):
"""
- Custom logging handler. It emits Qt signals so it can be plugged to a gui.
- Also stores an history of logs that can be fetched after connect to a gui.
+ This is the custom handler that implements our desired formatting
+ and also keeps a history of all the logged events.
"""
- # All dicts returned are of the form
- # {'record': LogRecord, 'message': str}
- new_log = QtCore.Signal(dict)
MESSAGE_KEY = 'message'
RECORD_KEY = 'record'
- def __init__(self):
- logging.Handler.__init__(self)
- QtCore.QObject.__init__(self)
+ # TODO This is going to eat lots of memory after some time.
+ # Should be pruned at some moment.
+ _log_history = []
- self._log_history = []
+ def __init__(self, qtsignal):
+ """
+ LogHander initialization.
+ Calls parent method and keeps a reference to the qtsignal
+ that will be used to fire the gui update.
+ """
+ logging.Handler.__init__(self)
+ self._qtsignal = qtsignal
def _set_format(self, logging_level):
"""
@@ -66,6 +69,7 @@ class LeapLogHandler(logging.Handler, QtCore.QObject):
format_attrs = [time, name, level, message]
log_format = ' - '.join(format_attrs)
formatter = logging.Formatter(log_format)
+
self.setFormatter(formatter)
def emit(self, logRecord):
@@ -74,8 +78,6 @@ class LeapLogHandler(logging.Handler, QtCore.QObject):
logging module.
This method reimplements logging.Handler.emit that is fired
in every logged message.
- QObject.emit gets in the way on the PySide signal model but we
- workarouded that issue.
:param logRecord: the record emitted by the logging module.
:type logRecord: logging.LogRecord.
@@ -83,17 +85,64 @@ class LeapLogHandler(logging.Handler, QtCore.QObject):
self._set_format(logRecord.levelname)
log = self.format(logRecord)
log_item = {self.RECORD_KEY: logRecord, self.MESSAGE_KEY: log}
- self._log_history.append(log_item)
+ self._qtsignal(log_item)
+
+
+class HandlerAdapter(object):
+ """
+ New style class that accesses all attributes from the LogHandler.
+
+ Used as a workaround for a problem with multiple inheritance with Pyside
+ that surfaced under OSX with pyside 1.1.0.
+ """
+ MESSAGE_KEY = 'message'
+ RECORD_KEY = 'record'
+
+ def __init__(self, qtsignal):
+ self._handler = LogHandler(qtsignal=qtsignal)
+
+ def setLevel(self, *args, **kwargs):
+ return self._handler.setLevel(*args, **kwargs)
+
+ def handle(self, *args, **kwargs):
+ return self._handler.handle(*args, **kwargs)
+
+ @property
+ def level(self):
+ return self._handler.level
+
+
+class LeapLogHandler(QtCore.QObject, HandlerAdapter):
+ """
+ Custom logging handler. It emits Qt signals so it can be plugged to a gui.
+
+ Its inner handler also stores an history of logs that can be fetched after
+ having been connected to a gui.
+ """
+ # All dicts returned are of the form
+ # {'record': LogRecord, 'message': str}
+ new_log = QtCore.Signal(dict)
+
+ def __init__(self):
+ """
+ LeapLogHandler initialization.
+ Initializes parent classes.
+ """
+ QtCore.QObject.__init__(self)
+ HandlerAdapter.__init__(self, qtsignal=self.qtsignal)
+ def qtsignal(self, log_item):
# WARNING: the new-style connection does NOT work because PySide
# translates the emit method to self.emit, and that collides with
# the emit method for logging.Handler
# self.new_log.emit(log_item)
- QtCore.QObject.emit(self, QtCore.SIGNAL('new_log(PyObject)'), log_item)
+ QtCore.QObject.emit(
+ self,
+ QtCore.SIGNAL('new_log(PyObject)'), log_item)
@property
def log_history(self):
"""
Returns the history of the logged messages.
"""
- return self._log_history
+ return self._handler._log_history