From 35c77f1e762acde8e5f635a99f9fad9e706d7d4f Mon Sep 17 00:00:00 2001 From: kali Date: Thu, 31 Jan 2013 05:39:47 +0900 Subject: fix tests (resources hash + argparse) --- src/leap/gui/tests/test_mainwindow_rc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/tests/test_mainwindow_rc.py b/src/leap/gui/tests/test_mainwindow_rc.py index 67b9fae0..9f5172f7 100644 --- a/src/leap/gui/tests/test_mainwindow_rc.py +++ b/src/leap/gui/tests/test_mainwindow_rc.py @@ -26,7 +26,7 @@ class MainWindowResourcesTest(unittest.TestCase): def test_mainwindow_resources_hash(self): self.assertEqual( hashlib.md5(mainwindow_rc.qt_resource_data).hexdigest(), - '53e196f29061d8f08f112e5a2e64eb53') + 'e04cb467985ba38b9eb91e7689f9458f') if __name__ == "__main__": unittest.main() -- cgit v1.2.3 From 5ff29dc57e2877a14e705d09b7042cddf4165d0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 6 Mar 2013 15:27:23 -0300 Subject: Remove everything to start from scratch --- src/leap/gui/__init__.py | 11 - src/leap/gui/constants.py | 13 - src/leap/gui/firstrun/__init__.py | 28 - src/leap/gui/firstrun/connect.py | 214 ---- src/leap/gui/firstrun/constants.py | 0 src/leap/gui/firstrun/intro.py | 68 -- src/leap/gui/firstrun/last.py | 119 --- src/leap/gui/firstrun/login.py | 332 ------ src/leap/gui/firstrun/mixins.py | 18 - src/leap/gui/firstrun/providerinfo.py | 106 -- src/leap/gui/firstrun/providerselect.py | 471 -------- src/leap/gui/firstrun/providersetup.py | 157 --- src/leap/gui/firstrun/register.py | 387 ------- .../firstrun/tests/integration/fake_provider.py | 302 ------ src/leap/gui/firstrun/wizard.py | 309 ------ src/leap/gui/locale_rc.py | 813 -------------- src/leap/gui/mainwindow_rc.py | 1130 -------------------- src/leap/gui/progress.py | 488 --------- src/leap/gui/styles.py | 16 - src/leap/gui/tests/__init__.py | 0 src/leap/gui/tests/integration/fake_user_signup.py | 84 -- src/leap/gui/tests/test_firstrun_login.py | 212 ---- src/leap/gui/tests/test_firstrun_providerselect.py | 203 ---- src/leap/gui/tests/test_firstrun_register.py | 244 ----- src/leap/gui/tests/test_firstrun_wizard.py | 137 --- src/leap/gui/tests/test_mainwindow_rc.py | 32 - src/leap/gui/tests/test_progress.py | 449 -------- src/leap/gui/tests/test_threads.py | 27 - src/leap/gui/threads.py | 21 - src/leap/gui/utils.py | 34 - 30 files changed, 6425 deletions(-) delete mode 100644 src/leap/gui/__init__.py delete mode 100644 src/leap/gui/constants.py delete mode 100644 src/leap/gui/firstrun/__init__.py delete mode 100644 src/leap/gui/firstrun/connect.py delete mode 100644 src/leap/gui/firstrun/constants.py delete mode 100644 src/leap/gui/firstrun/intro.py delete mode 100644 src/leap/gui/firstrun/last.py delete mode 100644 src/leap/gui/firstrun/login.py delete mode 100644 src/leap/gui/firstrun/mixins.py delete mode 100644 src/leap/gui/firstrun/providerinfo.py delete mode 100644 src/leap/gui/firstrun/providerselect.py delete mode 100644 src/leap/gui/firstrun/providersetup.py delete mode 100644 src/leap/gui/firstrun/register.py delete mode 100755 src/leap/gui/firstrun/tests/integration/fake_provider.py delete mode 100755 src/leap/gui/firstrun/wizard.py delete mode 100644 src/leap/gui/locale_rc.py delete mode 100644 src/leap/gui/mainwindow_rc.py delete mode 100644 src/leap/gui/progress.py delete mode 100644 src/leap/gui/styles.py delete mode 100644 src/leap/gui/tests/__init__.py delete mode 100644 src/leap/gui/tests/integration/fake_user_signup.py delete mode 100644 src/leap/gui/tests/test_firstrun_login.py delete mode 100644 src/leap/gui/tests/test_firstrun_providerselect.py delete mode 100644 src/leap/gui/tests/test_firstrun_register.py delete mode 100644 src/leap/gui/tests/test_firstrun_wizard.py delete mode 100644 src/leap/gui/tests/test_mainwindow_rc.py delete mode 100644 src/leap/gui/tests/test_progress.py delete mode 100644 src/leap/gui/tests/test_threads.py delete mode 100644 src/leap/gui/threads.py delete mode 100644 src/leap/gui/utils.py (limited to 'src/leap/gui') diff --git a/src/leap/gui/__init__.py b/src/leap/gui/__init__.py deleted file mode 100644 index 804bfbc1..00000000 --- a/src/leap/gui/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -try: - import sip - sip.setapi('QString', 2) - sip.setapi('QVariant', 2) -except ValueError: - pass - -import firstrun -import firstrun.wizard - -__all__ = ['firstrun', 'firstrun.wizard'] diff --git a/src/leap/gui/constants.py b/src/leap/gui/constants.py deleted file mode 100644 index 277f3540..00000000 --- a/src/leap/gui/constants.py +++ /dev/null @@ -1,13 +0,0 @@ -import time - -APP_LOGO = ':/images/leap-color-small.png' - -# bare is the username portion of a JID -# full includes the "at" and some extra chars -# that can be allowed for fqdn - -BARE_USERNAME_REGEX = r"^[A-Za-z\d_]+$" -FULL_USERNAME_REGEX = r"^[A-Za-z\d_@.-]+$" - -GUI_PAUSE_FOR_USER_SECONDS = 1 -pause_for_user = lambda: time.sleep(GUI_PAUSE_FOR_USER_SECONDS) diff --git a/src/leap/gui/firstrun/__init__.py b/src/leap/gui/firstrun/__init__.py deleted file mode 100644 index 2a523d6a..00000000 --- a/src/leap/gui/firstrun/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -try: - import sip - sip.setapi('QString', 2) - sip.setapi('QVariant', 2) -except ValueError: - pass - -import intro -import connect -import last -import login -import mixins -import providerinfo -import providerselect -import providersetup -import register - -__all__ = [ - 'intro', - 'connect', - 'last', - 'login', - 'mixins', - 'providerinfo', - 'providerselect', - 'providersetup', - 'register', - ] # ,'wizard'] diff --git a/src/leap/gui/firstrun/connect.py b/src/leap/gui/firstrun/connect.py deleted file mode 100644 index ad7bb13a..00000000 --- a/src/leap/gui/firstrun/connect.py +++ /dev/null @@ -1,214 +0,0 @@ -""" -Provider Setup Validation Page, -used in First Run Wizard -""" -import logging - -from PyQt4 import QtGui - -#import requests - -from leap.gui.progress import ValidationPage -from leap.util.web import get_https_domain_and_port - -from leap.base import auth -from leap.gui.constants import APP_LOGO - -logger = logging.getLogger(__name__) - - -class ConnectionPage(ValidationPage): - - def __init__(self, parent=None): - super(ConnectionPage, self).__init__(parent) - self.current_page = "connect" - - title = self.tr("Connecting...") - subtitle = self.tr("Setting up a encrypted " - "connection with the provider") - - self.setTitle(title) - self.setSubTitle(subtitle) - - self.setPixmap( - QtGui.QWizard.LogoPixmap, - QtGui.QPixmap(APP_LOGO)) - - def _do_checks(self, update_signal=None): - """ - executes actual checks in a separate thread - - we initialize the srp protocol register - and try to register user. - """ - wizard = self.wizard() - full_domain = self.field('provider_domain') - domain, port = get_https_domain_and_port(full_domain) - - pconfig = wizard.eipconfigchecker(domain=domain) - # this should be persisted... - pconfig.defaultprovider.load() - pconfig.set_api_domain() - - pCertChecker = wizard.providercertchecker( - domain=domain) - pCertChecker.set_api_domain(pconfig.apidomain) - - ########################################### - # Set Credentials. - # username and password are in different fields - # if they were stored in log_in or sign_up pages. - from_login = wizard.from_login - - unamek_base = 'userName' - passwk_base = 'userPassword' - unamek = 'login_%s' % unamek_base if from_login else unamek_base - passwk = 'login_%s' % passwk_base if from_login else passwk_base - - username = self.field(unamek) - password = self.field(passwk) - credentials = username, password - - yield(("head_sentinel", 0), lambda: None) - - ################################################## - # 1) fetching eip service config - ################################################## - def fetcheipconf(): - try: - pconfig.fetch_eip_service_config() - - # XXX get specific exception - except Exception as exc: - return self.fail(exc.message) - - yield((self.tr("Getting EIP configuration files"), 40), - fetcheipconf) - - ################################################## - # 2) getting client certificate - ################################################## - - def fetcheipcert(): - try: - downloaded = pCertChecker.download_new_client_cert( - credentials=credentials) - if not downloaded: - logger.error('Could not download client cert') - return False - - except auth.SRPAuthenticationError as exc: - return self.fail(self.tr( - "Authentication error: %s" % exc.message)) - - except Exception as exc: - return self.fail(exc.message) - else: - return True - - yield((self.tr("Getting EIP certificate"), 80), - fetcheipcert) - - ################ - # end ! - ################ - self.set_done() - yield(("end_sentinel", 100), lambda: None) - - def on_checks_validation_ready(self): - """ - called after _do_checks has finished - (connected to checker thread finished signal) - """ - # here we go! :) - if self.is_done(): - nextbutton = self.wizard().button(QtGui.QWizard.NextButton) - nextbutton.setFocus() - - full_domain = self.field('provider_domain') - domain, port = get_https_domain_and_port(full_domain) - _domain = u"%s:%s" % ( - domain, port) if port != 443 else unicode(domain) - self.run_eip_checks_for_provider_and_connect(_domain) - - def run_eip_checks_for_provider_and_connect(self, domain): - wizard = self.wizard() - conductor = wizard.conductor - start_eip_signal = getattr( - wizard, - 'start_eipconnection_signal', None) - - if conductor: - conductor.set_provider_domain(domain) - # we could run some of the checks to be - # sure everything is in order, but - # I see no point in doing it, we assume - # we've gone thru all checks during the wizard. - #conductor.run_checks() - #self.conductor = conductor - #errors = self.eip_error_check() - #if not errors and start_eip_signal: - if start_eip_signal: - start_eip_signal.emit() - - else: - logger.warning( - "No conductor found. This means that " - "probably the wizard has been launched " - "in an stand-alone way.") - - self.set_done() - - #def eip_error_check(self): - #""" - #a version of the main app error checker, - #but integrated within the connecting page of the wizard. - #consumes the conductor error queue. - #pops errors, and add those to the wizard page - #""" - # TODO handle errors. - # We should redirect them to the log viewer - # with a brief message. - # XXX move to LAST PAGE instead. - #logger.debug('eip error check from connecting page') - #errq = self.conductor.error_queue - - #def _do_validation(self): - #""" - #called after _do_checks has finished - #(connected to checker thread finished signal) - #""" - #from_login = self.wizard().from_login - #prevpage = "login" if from_login else "signup" - - #wizard = self.wizard() - #if self.errors: - #logger.debug('going back with errors') - #logger.error(self.errors) - #name, first_error = self.pop_first_error() - #wizard.set_validation_error( - #prevpage, - #first_error) - #self.go_back() - - def nextId(self): - wizard = self.wizard() - return wizard.get_page_index('lastpage') - - def initializePage(self): - super(ConnectionPage, self).initializePage() - self.set_undone() - cancelbutton = self.wizard().button(QtGui.QWizard.CancelButton) - cancelbutton.hide() - self.completeChanged.emit() - - wizard = self.wizard() - eip_statuschange_signal = wizard.eip_statuschange_signal - if eip_statuschange_signal: - eip_statuschange_signal.connect( - lambda status: self.send_status( - status)) - - def send_status(self, status): - wizard = self.wizard() - wizard.openvpn_status.append(status) diff --git a/src/leap/gui/firstrun/constants.py b/src/leap/gui/firstrun/constants.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/leap/gui/firstrun/intro.py b/src/leap/gui/firstrun/intro.py deleted file mode 100644 index b519362f..00000000 --- a/src/leap/gui/firstrun/intro.py +++ /dev/null @@ -1,68 +0,0 @@ -""" -Intro page used in first run wizard -""" - -from PyQt4 import QtGui - -from leap.gui.constants import APP_LOGO - - -class IntroPage(QtGui.QWizardPage): - def __init__(self, parent=None): - super(IntroPage, self).__init__(parent) - - self.setTitle(self.tr("First run wizard")) - - #self.setPixmap( - #QtGui.QWizard.WatermarkPixmap, - #QtGui.QPixmap(':/images/watermark1.png')) - - self.setPixmap( - QtGui.QWizard.LogoPixmap, - QtGui.QPixmap(APP_LOGO)) - - label = QtGui.QLabel(self.tr( - "Now we will guide you through " - "some configuration that is needed before you " - "can connect for the first time.

" - "If you ever need to modify these options again, " - "you can find the wizard in the 'Settings' menu from the " - "main window.

" - "Do you want to sign up for a new account, or log " - "in with an already existing username?
")) - label.setWordWrap(True) - - radiobuttonGroup = QtGui.QGroupBox() - - self.sign_up = QtGui.QRadioButton( - self.tr("Sign up for a new account")) - self.sign_up.setChecked(True) - self.log_in = QtGui.QRadioButton( - self.tr("Log In with my credentials")) - - radiobLayout = QtGui.QVBoxLayout() - radiobLayout.addWidget(self.sign_up) - radiobLayout.addWidget(self.log_in) - radiobuttonGroup.setLayout(radiobLayout) - - layout = QtGui.QVBoxLayout() - layout.addWidget(label) - layout.addWidget(radiobuttonGroup) - self.setLayout(layout) - - #self.registerField('is_signup', self.sign_up) - - def validatePage(self): - return True - - def nextId(self): - """ - returns next id - in a non-linear wizard - """ - if self.sign_up.isChecked(): - next_ = 'providerselection' - if self.log_in.isChecked(): - next_ = 'login' - wizard = self.wizard() - return wizard.get_page_index(next_) diff --git a/src/leap/gui/firstrun/last.py b/src/leap/gui/firstrun/last.py deleted file mode 100644 index f3e467db..00000000 --- a/src/leap/gui/firstrun/last.py +++ /dev/null @@ -1,119 +0,0 @@ -""" -Last Page, used in First Run Wizard -""" -import logging - -from PyQt4 import QtGui - -from leap.util.coroutines import coroutine -from leap.gui.constants import APP_LOGO - -logger = logging.getLogger(__name__) - - -class LastPage(QtGui.QWizardPage): - def __init__(self, parent=None): - super(LastPage, self).__init__(parent) - - self.setTitle(self.tr( - "Connecting to Encrypted Internet Proxy service...")) - - self.setPixmap( - QtGui.QWizard.LogoPixmap, - QtGui.QPixmap(APP_LOGO)) - - #self.setPixmap( - #QtGui.QWizard.WatermarkPixmap, - #QtGui.QPixmap(':/images/watermark2.png')) - - self.label = QtGui.QLabel() - self.label.setWordWrap(True) - - self.wizard_done = False - - # XXX REFACTOR to a Validating Page... - self.status_line_1 = QtGui.QLabel() - self.status_line_2 = QtGui.QLabel() - self.status_line_3 = QtGui.QLabel() - self.status_line_4 = QtGui.QLabel() - self.status_line_5 = QtGui.QLabel() - - layout = QtGui.QVBoxLayout() - layout.addWidget(self.label) - - # make loop - layout.addWidget(self.status_line_1) - layout.addWidget(self.status_line_2) - layout.addWidget(self.status_line_3) - layout.addWidget(self.status_line_4) - layout.addWidget(self.status_line_5) - - self.setLayout(layout) - - def isComplete(self): - return self.wizard_done - - def set_status_line(self, line, status): - statusline = getattr(self, 'status_line_%s' % line) - if statusline: - statusline.setText(status) - - def set_finished_status(self): - self.setTitle(self.tr('You are now using an encrypted connection!')) - finishText = self.wizard().buttonText( - QtGui.QWizard.FinishButton) - finishText = finishText.replace('&', '') - self.label.setText(self.tr( - "Click '%s' to end the wizard and " - "save your settings." % finishText)) - self.wizard_done = True - self.completeChanged.emit() - - @coroutine - def eip_status_handler(self): - # XXX this can be changed to use - # signals. See progress.py - logger.debug('logging status in last page') - self.validation_done = False - status_count = 1 - try: - while True: - status = (yield) - status_count += 1 - # XXX add to line... - logger.debug('status --> %s', status) - self.set_status_line(status_count, status) - if status == "connected": - self.set_finished_status() - self.completeChanged.emit() - break - self.completeChanged.emit() - except GeneratorExit: - pass - except StopIteration: - pass - - def initializePage(self): - super(LastPage, self).initializePage() - wizard = self.wizard() - wizard.button(QtGui.QWizard.FinishButton).setDisabled(True) - - handler = self.eip_status_handler() - - # get statuses done in prev page - for st in wizard.openvpn_status: - self.send_status(handler.send, st) - - # bind signal for events yet to come - eip_statuschange_signal = wizard.eip_statuschange_signal - if eip_statuschange_signal: - eip_statuschange_signal.connect( - lambda status: self.send_status( - handler.send, status)) - self.completeChanged.emit() - - def send_status(self, cb, status): - try: - cb(status) - except StopIteration: - pass diff --git a/src/leap/gui/firstrun/login.py b/src/leap/gui/firstrun/login.py deleted file mode 100644 index 3707d3ff..00000000 --- a/src/leap/gui/firstrun/login.py +++ /dev/null @@ -1,332 +0,0 @@ -""" -LogIn Page, used inf First Run Wizard -""" -from PyQt4 import QtCore -from PyQt4 import QtGui - -import requests - -from leap.base import auth -from leap.gui.firstrun.mixins import UserFormMixIn -from leap.gui.progress import InlineValidationPage -from leap.gui import styles - -from leap.gui.constants import APP_LOGO, FULL_USERNAME_REGEX - - -class LogInPage(InlineValidationPage, UserFormMixIn): # InlineValidationPage - - def __init__(self, parent=None): - - super(LogInPage, self).__init__(parent) - self.current_page = "login" - - self.setTitle(self.tr("Log In")) - self.setSubTitle(self.tr("Log in with your credentials")) - self.current_page = "login" - - self.setPixmap( - QtGui.QWizard.LogoPixmap, - QtGui.QPixmap(APP_LOGO)) - - self.setupSteps() - self.setupUI() - - self.do_confirm_next = False - - def setupUI(self): - userNameLabel = QtGui.QLabel(self.tr("User &name:")) - userNameLineEdit = QtGui.QLineEdit() - userNameLineEdit.cursorPositionChanged.connect( - self.reset_validation_status) - userNameLabel.setBuddy(userNameLineEdit) - - # let's add regex validator - usernameRe = QtCore.QRegExp(FULL_USERNAME_REGEX) - userNameLineEdit.setValidator( - QtGui.QRegExpValidator(usernameRe, self)) - - #userNameLineEdit.setPlaceholderText( - #'username@provider.example.org') - self.userNameLineEdit = userNameLineEdit - - userPasswordLabel = QtGui.QLabel(self.tr("&Password:")) - self.userPasswordLineEdit = QtGui.QLineEdit() - self.userPasswordLineEdit.setEchoMode( - QtGui.QLineEdit.Password) - userPasswordLabel.setBuddy(self.userPasswordLineEdit) - - self.registerField('login_userName*', self.userNameLineEdit) - self.registerField('login_userPassword*', self.userPasswordLineEdit) - - layout = QtGui.QGridLayout() - layout.setColumnMinimumWidth(0, 20) - - validationMsg = QtGui.QLabel("") - validationMsg.setStyleSheet(styles.ErrorLabelStyleSheet) - self.validationMsg = validationMsg - - layout.addWidget(validationMsg, 0, 3) - layout.addWidget(userNameLabel, 1, 0) - layout.addWidget(self.userNameLineEdit, 1, 3) - layout.addWidget(userPasswordLabel, 2, 0) - layout.addWidget(self.userPasswordLineEdit, 2, 3) - - # add validation frame - self.setupValidationFrame() - layout.addWidget(self.valFrame, 4, 2, 4, 2) - self.valFrame.hide() - - self.nextText(self.tr("Log in")) - self.setLayout(layout) - - #self.registerField('is_login_wizard') - - # actual checks - - def _do_checks(self): - - full_username = self.userNameLineEdit.text() - ########################### - # 0) check user@domain form - ########################### - - def checkusername(): - if full_username.count('@') != 1: - return self.fail( - self.tr( - "Username must be in the username@provider form.")) - else: - return True - - yield(("head_sentinel", 0), checkusername) - - username, domain = full_username.split('@') - password = self.userPasswordLineEdit.text() - - # We try a call to an authenticated - # page here as a mean to catch - # srp authentication errors while - wizard = self.wizard() - eipconfigchecker = wizard.eipconfigchecker(domain=domain) - - ######################## - # 1) try name resolution - ######################## - # show the frame before going on... - QtCore.QMetaObject.invokeMethod( - self, "showStepsFrame") - - # Able to contact domain? - # can get definition? - # two-by-one - def resolvedomain(): - try: - eipconfigchecker.fetch_definition(domain=domain) - - # we're using requests here for all - # the possible error cases that it catches. - except requests.exceptions.ConnectionError as exc: - return self.fail(exc.message[1]) - except requests.exceptions.HTTPError as exc: - return self.fail(exc.message) - except Exception as exc: - # XXX get catchall error msg - return self.fail( - exc.message) - else: - return True - - yield((self.tr("Resolving domain name"), 20), resolvedomain) - - wizard.set_providerconfig( - eipconfigchecker.defaultprovider.config) - - ######################## - # 2) do authentication - ######################## - credentials = username, password - pCertChecker = wizard.providercertchecker( - domain=domain) - - def validate_credentials(): - ################# - # FIXME #BUG #638 - verify = False - - try: - pCertChecker.download_new_client_cert( - credentials=credentials, - verify=verify) - - except auth.SRPAuthenticationError as exc: - return self.fail( - self.tr("Authentication error: %s" % exc.message)) - - except Exception as exc: - return self.fail(exc.message) - - else: - return True - - yield(('Validating credentials', 60), validate_credentials) - - self.set_done() - yield(("end_sentinel", 100), lambda: None) - - def green_validation_status(self): - val = self.validationMsg - val.setText(self.tr('Credentials validated.')) - val.setStyleSheet(styles.GreenLineEdit) - - def on_checks_validation_ready(self): - """ - after checks - """ - if self.is_done(): - self.disableFields() - self.cleanup_errormsg() - self.clean_wizard_errors(self.current_page) - # make the user confirm the transition - # to next page. - self.nextText('&Next') - self.nextFocus() - self.green_validation_status() - self.do_confirm_next = True - - # ui update - - def nextText(self, text): - self.setButtonText( - QtGui.QWizard.NextButton, text) - - def nextFocus(self): - self.wizard().button( - QtGui.QWizard.NextButton).setFocus() - - def disableNextButton(self): - self.wizard().button( - QtGui.QWizard.NextButton).setDisabled(True) - - def onUserNamePositionChanged(self, *args): - if self.initial_username_sample: - self.userNameLineEdit.setText('') - # XXX set regular color - self.initial_username_sample = None - - def onUserNameTextChanged(self, *args): - if self.initial_username_sample: - k = args[0][-1] - self.initial_username_sample = None - self.userNameLineEdit.setText(k) - - def disableFields(self): - for field in (self.userNameLineEdit, - self.userPasswordLineEdit): - field.setDisabled(True) - - def populateErrors(self): - # XXX could move this to ValidationMixin - # used in providerselect and register too - - errors = self.wizard().get_validation_error( - self.current_page) - showerr = self.validationMsg.setText - - if errors: - bad_str = getattr(self, 'bad_string', None) - cur_str = self.userNameLineEdit.text() - - if bad_str is None: - # first time we fall here. - # save the current bad_string value - self.bad_string = cur_str - showerr(errors) - else: - # not the first time - if cur_str == bad_str: - showerr(errors) - else: - self.focused_field = False - showerr('') - - def cleanup_errormsg(self): - """ - we reset bad_string to None - should be called before leaving the page - """ - self.bad_string = None - - def paintEvent(self, event): - """ - we hook our populate errors - on paintEvent because we need it to catch - when user enters the page coming from next, - and initializePage does not cover that case. - Maybe there's a better event to hook upon. - """ - super(LogInPage, self).paintEvent(event) - self.populateErrors() - - def set_prevalidation_error(self, error): - self.prevalidation_error = error - - # pagewizard methods - - def nextId(self): - wizard = self.wizard() - if not wizard: - return - if wizard.is_provider_setup is False: - next_ = 'providersetupvalidation' - if wizard.is_provider_setup is True: - # XXX bad name, ok, gonna change that - next_ = 'signupvalidation' - return wizard.get_page_index(next_) - - def initializePage(self): - super(LogInPage, self).initializePage() - username = self.userNameLineEdit - username.setText('username@provider.example.org') - username.cursorPositionChanged.connect( - self.onUserNamePositionChanged) - username.textChanged.connect( - self.onUserNameTextChanged) - self.initial_username_sample = True - self.validationMsg.setText('') - self.valFrame.hide() - - def reset_validation_status(self): - """ - empty the validation msg - and clean the inline validation widget. - """ - self.validationMsg.setText('') - self.steps.removeAllSteps() - self.clearTable() - - def validatePage(self): - """ - if not register done, do checks. - if done, wait for click. - """ - self.disableNextButton() - self.cleanup_errormsg() - self.clean_wizard_errors(self.current_page) - - if self.do_confirm_next: - full_username = self.userNameLineEdit.text() - password = self.userPasswordLineEdit.text() - username, domain = full_username.split('@') - self.setField('provider_domain', domain) - self.setField('login_userName', username) - self.setField('login_userPassword', password) - self.wizard().from_login = True - - return True - - if not self.is_done(): - self.reset_validation_status() - self.do_checks() - - return self.is_done() diff --git a/src/leap/gui/firstrun/mixins.py b/src/leap/gui/firstrun/mixins.py deleted file mode 100644 index c4731893..00000000 --- a/src/leap/gui/firstrun/mixins.py +++ /dev/null @@ -1,18 +0,0 @@ -""" -mixins used in First Run Wizard -""" - - -class UserFormMixIn(object): - - def reset_validation_status(self): - """ - empty the validation msg - """ - self.validationMsg.setText('') - - def set_validation_status(self, msg): - """ - set generic validation status - """ - self.validationMsg.setText(msg) diff --git a/src/leap/gui/firstrun/providerinfo.py b/src/leap/gui/firstrun/providerinfo.py deleted file mode 100644 index cff4caca..00000000 --- a/src/leap/gui/firstrun/providerinfo.py +++ /dev/null @@ -1,106 +0,0 @@ -""" -Provider Info Page, used in First run Wizard -""" -import logging - -from PyQt4 import QtGui - -from leap.gui.constants import APP_LOGO -from leap.util.translations import translate - -logger = logging.getLogger(__name__) - - -class ProviderInfoPage(QtGui.QWizardPage): - - def __init__(self, parent=None): - super(ProviderInfoPage, self).__init__(parent) - - self.setTitle(self.tr("Provider Information")) - self.setSubTitle(self.tr( - "Services offered by this provider")) - - self.setPixmap( - QtGui.QWizard.LogoPixmap, - QtGui.QPixmap(APP_LOGO)) - - self.create_info_panel() - - def create_info_panel(self): - # Use stacked widget instead - # of reparenting the layout. - - infoWidget = QtGui.QStackedWidget() - - info = QtGui.QWidget() - layout = QtGui.QVBoxLayout() - - displayName = QtGui.QLabel("") - description = QtGui.QLabel("") - enrollment_policy = QtGui.QLabel("") - - # XXX set stylesheet... - # prettify a little bit. - # bigger fonts and so on... - - # We could use a QFrame here - - layout.addWidget(displayName) - layout.addWidget(description) - layout.addWidget(enrollment_policy) - layout.addStretch(1) - - info.setLayout(layout) - infoWidget.addWidget(info) - - pageLayout = QtGui.QVBoxLayout() - pageLayout.addWidget(infoWidget) - self.setLayout(pageLayout) - - # add refs to self to allow for - # updates. - # Watch out! Have to get rid of these references! - # this should be better handled with signals !! - self.displayName = displayName - self.description = description - self.description.setWordWrap(True) - self.enrollment_policy = enrollment_policy - - def show_provider_info(self): - - # XXX get multilingual objects - # directly from the config object - - lang = "en" - pconfig = self.wizard().providerconfig - - dn = pconfig.get('name') - display_name = dn[lang] if dn else '' - domain_name = self.field('provider_domain') - - self.displayName.setText( - "%s https://%s" % (display_name, domain_name)) - - desc = pconfig.get('description') - - #description_text = desc[lang] if desc else '' - description_text = translate(desc) if desc else '' - - self.description.setText( - "%s" % description_text) - - # XXX should translate this... - enroll = pconfig.get('enrollment_policy') - if enroll: - self.enrollment_policy.setText( - '%s: %s' % ( - self.tr('enrollment policy'), - enroll)) - - def nextId(self): - wizard = self.wizard() - next_ = "providersetupvalidation" - return wizard.get_page_index(next_) - - def initializePage(self): - self.show_provider_info() diff --git a/src/leap/gui/firstrun/providerselect.py b/src/leap/gui/firstrun/providerselect.py deleted file mode 100644 index 917b16fd..00000000 --- a/src/leap/gui/firstrun/providerselect.py +++ /dev/null @@ -1,471 +0,0 @@ -""" -Select Provider Page, used in First Run Wizard -""" -import logging - -import requests - -from PyQt4 import QtCore -from PyQt4 import QtGui - -from leap.base import exceptions as baseexceptions -#from leap.crypto import certs -from leap.eip import exceptions as eipexceptions -from leap.gui.progress import InlineValidationPage -from leap.gui import styles -from leap.gui.utils import delay -from leap.util.web import get_https_domain_and_port - -from leap.gui.constants import APP_LOGO - -logger = logging.getLogger(__name__) - - -class SelectProviderPage(InlineValidationPage): - - launchChecks = QtCore.pyqtSignal() - - def __init__(self, parent=None, providers=None): - super(SelectProviderPage, self).__init__(parent) - self.current_page = 'providerselection' - - self.setTitle(self.tr("Enter Provider")) - self.setSubTitle(self.tr( - "Please enter the domain of the provider you want " - "to use for your connection") - ) - self.setPixmap( - QtGui.QWizard.LogoPixmap, - QtGui.QPixmap(APP_LOGO)) - - self.did_cert_check = False - - self.done = False - - self.setupSteps() - self.setupUI() - - self.launchChecks.connect( - self.launch_checks) - - self.providerNameEdit.editingFinished.connect( - lambda: self.providerCheckButton.setFocus(True)) - - def setupUI(self): - """ - initializes the UI - """ - providerNameLabel = QtGui.QLabel("h&ttps://") - # note that we expect the bare domain name - # we will add the scheme later - providerNameEdit = QtGui.QLineEdit() - providerNameEdit.cursorPositionChanged.connect( - self.reset_validation_status) - providerNameLabel.setBuddy(providerNameEdit) - - # add regex validator - providerDomainRe = QtCore.QRegExp(r"^[a-z1-9_\-\.]+$") - providerNameEdit.setValidator( - QtGui.QRegExpValidator(providerDomainRe, self)) - self.providerNameEdit = providerNameEdit - - # Eventually we will seed a list of - # well known providers here. - - #providercombo = QtGui.QComboBox() - #if providers: - #for provider in providers: - #providercombo.addItem(provider) - #providerNameSelect = providercombo - - self.registerField("provider_domain*", self.providerNameEdit) - #self.registerField('provider_name_index', providerNameSelect) - - validationMsg = QtGui.QLabel("") - validationMsg.setStyleSheet(styles.ErrorLabelStyleSheet) - self.validationMsg = validationMsg - providerCheckButton = QtGui.QPushButton(self.tr("chec&k!")) - self.providerCheckButton = providerCheckButton - - # cert info - - # this is used in the callback - # for the checkbox changes. - # tricky, since the first time came - # from the exception message. - # should get string from exception too! - self.bad_cert_status = self.tr( - "Server certificate could not be verified.") - - self.certInfo = QtGui.QLabel("") - self.certInfo.setWordWrap(True) - self.certWarning = QtGui.QLabel("") - self.trustProviderCertCheckBox = QtGui.QCheckBox( - self.tr("&Trust this provider certificate.")) - - self.trustProviderCertCheckBox.stateChanged.connect( - self.onTrustCheckChanged) - self.providerNameEdit.textChanged.connect( - self.onProviderChanged) - self.providerCheckButton.clicked.connect( - self.onCheckButtonClicked) - - layout = QtGui.QGridLayout() - layout.addWidget(validationMsg, 0, 2) - layout.addWidget(providerNameLabel, 1, 1) - layout.addWidget(providerNameEdit, 1, 2) - layout.addWidget(providerCheckButton, 1, 3) - - # add certinfo group - # XXX not shown now. should move to validation box. - #layout.addWidget(certinfoGroup, 4, 1, 4, 2) - #self.certinfoGroup = certinfoGroup - #self.certinfoGroup.hide() - - # add validation frame - self.setupValidationFrame() - layout.addWidget(self.valFrame, 4, 2, 4, 2) - self.valFrame.hide() - - self.setLayout(layout) - - # certinfo - - def setupCertInfoGroup(self): # pragma: no cover - # XXX not used now. - certinfoGroup = QtGui.QGroupBox( - self.tr("Certificate validation")) - certinfoLayout = QtGui.QVBoxLayout() - certinfoLayout.addWidget(self.certInfo) - certinfoLayout.addWidget(self.certWarning) - certinfoLayout.addWidget(self.trustProviderCertCheckBox) - certinfoGroup.setLayout(certinfoLayout) - self.certinfoGroup = self.certinfoGroup - - # progress frame - - def setupValidationFrame(self): - qframe = QtGui.QFrame - valFrame = qframe() - valFrame.setFrameStyle(qframe.NoFrame) - valframeLayout = QtGui.QVBoxLayout() - zeros = (0, 0, 0, 0) - valframeLayout.setContentsMargins(*zeros) - - valframeLayout.addWidget(self.stepsTableWidget) - valFrame.setLayout(valframeLayout) - self.valFrame = valFrame - - @QtCore.pyqtSlot() - def onDisableCheckButton(self): - #print 'CHECK BUTTON DISABLED!!!' - self.providerCheckButton.setDisabled(True) - - @QtCore.pyqtSlot() - def launch_checks(self): - self.do_checks() - - def onCheckButtonClicked(self): - QtCore.QMetaObject.invokeMethod( - self, "onDisableCheckButton") - - QtCore.QMetaObject.invokeMethod( - self, "showStepsFrame") - - delay(self, "launch_checks") - - def _do_checks(self): - """ - generator that yields actual checks - that are executed in a separate thread - """ - - wizard = self.wizard() - full_domain = self.providerNameEdit.text() - - # we check if we have a port in the domain string. - domain, port = get_https_domain_and_port(full_domain) - _domain = u"%s:%s" % (domain, port) if port != 443 else unicode(domain) - - netchecker = wizard.netchecker() - providercertchecker = wizard.providercertchecker() - eipconfigchecker = wizard.eipconfigchecker(domain=_domain) - - yield(("head_sentinel", 0), lambda: None) - - ######################## - # 1) try name resolution - ######################## - - def namecheck(): - """ - in which we check if - we are able to name resolve - this domain - """ - try: - #import ipdb;ipdb.set_trace() - netchecker.check_name_resolution( - domain) - - except baseexceptions.LeapException as exc: - logger.error(exc.message) - return self.fail(exc.usermessage) - - except Exception as exc: - return self.fail(exc.message) - - else: - return True - - logger.debug('checking name resolution') - yield((self.tr("Checking if it is a valid provider"), 20), namecheck) - - ######################### - # 2) try https connection - ######################### - - def httpscheck(): - """ - in which we check - if the provider - is offering service over - https - """ - try: - providercertchecker.is_https_working( - "https://%s" % _domain, - verify=True) - - except eipexceptions.HttpsBadCertError as exc: - logger.debug('exception') - return self.fail(exc.usermessage) - # XXX skipping for now... - ############################################## - # We had this validation logic - # in the provider selection page before - ############################################## - #if self.trustProviderCertCheckBox.isChecked(): - #pass - #else: - #fingerprint = certs.get_cert_fingerprint( - #domain=domain, sep=" ") - - # it's ok if we've trusted this fgprt before - #trustedcrts = wizard.trusted_certs - #if trustedcrts and \ - # fingerprint.replace(' ', '') in trustedcrts: - #pass - #else: - # let your user face panick :P - #self.add_cert_info(fingerprint) - #self.did_cert_check = True - #self.completeChanged.emit() - #return False - - except baseexceptions.LeapException as exc: - return self.fail(exc.usermessage) - - except Exception as exc: - return self.fail(exc.message) - - else: - return True - - logger.debug('checking https connection') - yield((self.tr("Checking for a secure connection"), 40), httpscheck) - - ################################## - # 3) try download provider info... - ################################## - - def fetchinfo(): - try: - # XXX we already set _domain in the initialization - # so it should not be needed here. - eipconfigchecker.fetch_definition(domain=_domain) - wizard.set_providerconfig( - eipconfigchecker.defaultprovider.config) - except requests.exceptions.SSLError: - return self.fail(self.tr( - "Could not get info from provider.")) - except requests.exceptions.ConnectionError: - return self.fail(self.tr( - "Could not download provider info " - "(refused conn.).")) - - except Exception as exc: - return self.fail( - self.tr(exc.message)) - else: - return True - - yield((self.tr("Getting info from the provider"), 80), fetchinfo) - - # done! - - self.done = True - yield(("end_sentinel", 100), lambda: None) - - def on_checks_validation_ready(self): - """ - called after _do_checks has finished. - """ - self.domain_checked = True - self.completeChanged.emit() - # let's set focus... - if self.is_done(): - self.wizard().clean_validation_error(self.current_page) - nextbutton = self.wizard().button(QtGui.QWizard.NextButton) - nextbutton.setFocus() - else: - self.providerNameEdit.setFocus() - - # cert trust verification - # (disabled for now) - - def is_insecure_cert_trusted(self): - return self.trustProviderCertCheckBox.isChecked() - - def onTrustCheckChanged(self, state): # pragma: no cover XXX - checked = False - if state == 2: - checked = True - - if checked: - self.reset_validation_status() - else: - self.set_validation_status(self.bad_cert_status) - - # trigger signal to redraw next button - self.completeChanged.emit() - - def add_cert_info(self, certinfo): # pragma: no cover XXX - self.certWarning.setText( - self.tr("Do you want to trust this provider certificate?")) - # XXX Check if this needs to abstracted to remove certinfo - self.certInfo.setText( - self.tr('SHA-256 fingerprint: %s
' % certinfo)) - self.certInfo.setWordWrap(True) - self.certinfoGroup.show() - - def onProviderChanged(self, text): - self.done = False - provider = self.providerNameEdit.text() - if provider: - self.providerCheckButton.setDisabled(False) - else: - self.providerCheckButton.setDisabled(True) - self.completeChanged.emit() - - def reset_validation_status(self): - """ - empty the validation msg - and clean the inline validation widget. - """ - self.validationMsg.setText('') - self.steps.removeAllSteps() - self.clearTable() - self.domain_checked = False - - # pagewizard methods - - def isComplete(self): - provider = self.providerNameEdit.text() - - if not self.is_done(): - return False - - if not provider: - return False - else: - if self.is_insecure_cert_trusted(): - return True - if not self.did_cert_check: - if self.is_done(): - # XXX sure? - return True - return False - - def populateErrors(self): - # XXX could move this to ValidationMixin - # with some defaults for the validating fields - # (now it only allows one field, manually specified) - - #logger.debug('getting errors') - errors = self.wizard().get_validation_error( - self.current_page) - if errors: - bad_str = getattr(self, 'bad_string', None) - cur_str = self.providerNameEdit.text() - showerr = self.validationMsg.setText - markred = lambda: self.providerNameEdit.setStyleSheet( - styles.ErrorLineEdit) - umarkrd = lambda: self.providerNameEdit.setStyleSheet( - styles.RegularLineEdit) - if bad_str is None: - # first time we fall here. - # save the current bad_string value - self.bad_string = cur_str - showerr(errors) - markred() - else: - # not the first time - # XXX hey, this is getting convoluted. - # roll out this. - # but be careful about all the possibilities - # with going back and forth once you - # enter a domain. - if cur_str == bad_str: - showerr(errors) - markred() - else: - if not getattr(self, 'domain_checked', None): - showerr('') - umarkrd() - else: - self.bad_string = cur_str - showerr(errors) - - def cleanup_errormsg(self): - """ - we reset bad_string to None - should be called before leaving the page - """ - self.bad_string = None - self.domain_checked = False - - def paintEvent(self, event): - """ - we hook our populate errors - on paintEvent because we need it to catch - when user enters the page coming from next, - and initializePage does not cover that case. - Maybe there's a better event to hook upon. - """ - super(SelectProviderPage, self).paintEvent(event) - self.populateErrors() - - def initializePage(self): - self.validationMsg.setText('') - if hasattr(self, 'certinfoGroup'): - # XXX remove ? - self.certinfoGroup.hide() - self.done = False - self.providerCheckButton.setDisabled(True) - self.valFrame.hide() - self.steps.removeAllSteps() - self.clearTable() - - def validatePage(self): - # some cleanup before we leave the page - self.cleanup_errormsg() - - # go - return True - - def nextId(self): - wizard = self.wizard() - if not wizard: - return - return wizard.get_page_index('providerinfo') diff --git a/src/leap/gui/firstrun/providersetup.py b/src/leap/gui/firstrun/providersetup.py deleted file mode 100644 index 47060f6e..00000000 --- a/src/leap/gui/firstrun/providersetup.py +++ /dev/null @@ -1,157 +0,0 @@ -""" -Provider Setup Validation Page, -used if First Run Wizard -""" -import logging - -import requests - -from PyQt4 import QtGui - -from leap.base import exceptions as baseexceptions -from leap.gui.progress import ValidationPage - -from leap.gui.constants import APP_LOGO - -logger = logging.getLogger(__name__) - - -class ProviderSetupValidationPage(ValidationPage): - def __init__(self, parent=None): - super(ProviderSetupValidationPage, self).__init__(parent) - self.current_page = "providersetupvalidation" - - # XXX needed anymore? - #is_signup = self.field("is_signup") - #self.is_signup = is_signup - - self.setTitle(self.tr("Provider setup")) - self.setSubTitle( - self.tr("Gathering configuration options for this provider")) - - self.setPixmap( - QtGui.QWizard.LogoPixmap, - QtGui.QPixmap(APP_LOGO)) - - def _do_checks(self): - """ - generator that yields actual checks - that are executed in a separate thread - """ - - full_domain = self.field('provider_domain') - wizard = self.wizard() - pconfig = wizard.providerconfig - - #pCertChecker = wizard.providercertchecker - #certchecker = pCertChecker(domain=full_domain) - pCertChecker = wizard.providercertchecker( - domain=full_domain) - - yield(("head_sentinel", 0), lambda: None) - - ######################## - # 1) fetch ca cert - ######################## - - def fetchcacert(): - if pconfig: - ca_cert_uri = pconfig.get('ca_cert_uri').geturl() - else: - ca_cert_uri = None - - # XXX check scheme == "https" - # XXX passing verify == False because - # we have trusted right before. - # We should check it's the same domain!!! - # (Check with the trusted fingerprints dict - # or something smart) - try: - pCertChecker.download_ca_cert( - uri=ca_cert_uri, - verify=False) - - except baseexceptions.LeapException as exc: - logger.error(exc.message) - # XXX this should be _ method - return self.fail(self.tr(exc.usermessage)) - - except Exception as exc: - return self.fail(exc.message) - - else: - return True - - yield((self.tr('Fetching CA certificate'), 30), - fetchcacert) - - ######################### - # 2) check CA fingerprint - ######################### - - def checkcafingerprint(): - # XXX get the real thing!!! - pass - #ca_cert_fingerprint = pconfig.get('ca_cert_fingerprint', None) - - # XXX get fingerprint dict (types) - #sha256_fpr = ca_cert_fingerprint.split('=')[1] - - #validate_fpr = pCertChecker.check_ca_cert_fingerprint( - #fingerprint=sha256_fpr) - #if not validate_fpr: - # XXX update validationMsg - # should catch exception - #return False - - yield((self.tr("Checking CA fingerprint"), 60), - checkcafingerprint) - - ######################### - # 2) check CA fingerprint - ######################### - - def validatecacert(): - api_uri = pconfig.get('api_uri', None) - try: - pCertChecker.verify_api_https(api_uri) - except requests.exceptions.SSLError as exc: - return self.fail("Validation Error") - except Exception as exc: - return self.fail(exc.msg) - else: - return True - - yield((self.tr('Validating api certificate'), 90), validatecacert) - - self.set_done() - yield(('end_sentinel', 100), lambda: None) - - def on_checks_validation_ready(self): - """ - called after _do_checks has finished - (connected to checker thread finished signal) - """ - wizard = self.wizard() - prevpage = "login" if wizard.from_login else "providerselection" - - if self.errors: - logger.debug('going back with errors') - name, first_error = self.pop_first_error() - wizard.set_validation_error( - prevpage, - first_error) - - def nextId(self): - wizard = self.wizard() - from_login = wizard.from_login - if from_login: - next_ = 'connect' - else: - next_ = 'signup' - return wizard.get_page_index(next_) - - def initializePage(self): - super(ProviderSetupValidationPage, self).initializePage() - self.set_undone() - self.completeChanged.emit() diff --git a/src/leap/gui/firstrun/register.py b/src/leap/gui/firstrun/register.py deleted file mode 100644 index 15278330..00000000 --- a/src/leap/gui/firstrun/register.py +++ /dev/null @@ -1,387 +0,0 @@ -""" -Register User Page, used in First Run Wizard -""" -import json -import logging -import socket - -import requests - -from PyQt4 import QtCore -from PyQt4 import QtGui - -from leap.gui.firstrun.mixins import UserFormMixIn - -logger = logging.getLogger(__name__) - -from leap.base import auth -from leap.gui import styles -from leap.gui.constants import APP_LOGO, BARE_USERNAME_REGEX -from leap.gui.progress import InlineValidationPage -from leap.gui.styles import ErrorLabelStyleSheet - - -class RegisterUserPage(InlineValidationPage, UserFormMixIn): - - def __init__(self, parent=None): - - super(RegisterUserPage, self).__init__(parent) - self.current_page = "signup" - - self.setTitle(self.tr("Sign Up")) - # subtitle is set in the initializePage - - self.setPixmap( - QtGui.QWizard.LogoPixmap, - QtGui.QPixmap(APP_LOGO)) - - # commit page means there's no way back after this... - # XXX should change the text on the "commit" button... - self.setCommitPage(True) - - self.setupSteps() - self.setupUI() - self.do_confirm_next = False - self.focused_field = False - - def setupUI(self): - userNameLabel = QtGui.QLabel(self.tr("User &name:")) - userNameLineEdit = QtGui.QLineEdit() - userNameLineEdit.cursorPositionChanged.connect( - self.reset_validation_status) - userNameLabel.setBuddy(userNameLineEdit) - - # let's add regex validator - usernameRe = QtCore.QRegExp(BARE_USERNAME_REGEX) - userNameLineEdit.setValidator( - QtGui.QRegExpValidator(usernameRe, self)) - self.userNameLineEdit = userNameLineEdit - - userPasswordLabel = QtGui.QLabel(self.tr("&Password:")) - self.userPasswordLineEdit = QtGui.QLineEdit() - self.userPasswordLineEdit.setEchoMode( - QtGui.QLineEdit.Password) - userPasswordLabel.setBuddy(self.userPasswordLineEdit) - - userPassword2Label = QtGui.QLabel(self.tr("Password (again):")) - self.userPassword2LineEdit = QtGui.QLineEdit() - self.userPassword2LineEdit.setEchoMode( - QtGui.QLineEdit.Password) - userPassword2Label.setBuddy(self.userPassword2LineEdit) - - rememberPasswordCheckBox = QtGui.QCheckBox( - self.tr("&Remember username and password.")) - rememberPasswordCheckBox.setChecked(True) - - self.registerField('userName*', self.userNameLineEdit) - self.registerField('userPassword*', self.userPasswordLineEdit) - self.registerField('userPassword2*', self.userPassword2LineEdit) - - # XXX missing password confirmation - # XXX validator! - - self.registerField('rememberPassword', rememberPasswordCheckBox) - - layout = QtGui.QGridLayout() - layout.setColumnMinimumWidth(0, 20) - - validationMsg = QtGui.QLabel("") - validationMsg.setStyleSheet(ErrorLabelStyleSheet) - - self.validationMsg = validationMsg - - layout.addWidget(validationMsg, 0, 3) - layout.addWidget(userNameLabel, 1, 0) - layout.addWidget(self.userNameLineEdit, 1, 3) - layout.addWidget(userPasswordLabel, 2, 0) - layout.addWidget(userPassword2Label, 3, 0) - layout.addWidget(self.userPasswordLineEdit, 2, 3) - layout.addWidget(self.userPassword2LineEdit, 3, 3) - layout.addWidget(rememberPasswordCheckBox, 4, 3, 4, 4) - - # add validation frame - self.setupValidationFrame() - layout.addWidget(self.valFrame, 5, 2, 5, 2) - self.valFrame.hide() - - self.setLayout(layout) - self.commitText("Sign up!") - - # commit button - - def commitText(self, text): - # change "commit" button text - self.setButtonText( - QtGui.QWizard.CommitButton, text) - - @property - def commitButton(self): - return self.wizard().button(QtGui.QWizard.CommitButton) - - def commitFocus(self): - self.commitButton.setFocus() - - def disableCommitButton(self): - self.commitButton.setDisabled(True) - - def disableFields(self): - for field in (self.userNameLineEdit, - self.userPasswordLineEdit, - self.userPassword2LineEdit): - field.setDisabled(True) - - # error painting - def paintEvent(self, event): - """ - we hook our populate errors - on paintEvent because we need it to catch - when user enters the page coming from next, - and initializePage does not cover that case. - Maybe there's a better event to hook upon. - """ - super(RegisterUserPage, self).paintEvent(event) - self.populateErrors() - - def markRedAndGetFocus(self, field): - field.setStyleSheet(styles.ErrorLineEdit) - if not self.focused_field: - self.focused_field = True - field.setFocus(QtCore.Qt.OtherFocusReason) - - def markRegular(self, field): - field.setStyleSheet(styles.RegularLineEdit) - - def populateErrors(self): - def showerr(text): - self.validationMsg.setText(text) - err_lower = text.lower() - if "username" in err_lower: - self.markRedAndGetFocus( - self.userNameLineEdit) - if "password" in err_lower: - self.markRedAndGetFocus( - self.userPasswordLineEdit) - - def unmarkred(): - for field in (self.userNameLineEdit, - self.userPasswordLineEdit, - self.userPassword2LineEdit): - self.markRegular(field) - - errors = self.wizard().get_validation_error( - self.current_page) - if errors: - bad_str = getattr(self, 'bad_string', None) - cur_str = self.userNameLineEdit.text() - #prev_er = getattr(self, 'prevalidation_error', None) - - if bad_str is None: - # first time we fall here. - # save the current bad_string value - self.bad_string = cur_str - showerr(errors) - else: - #if prev_er: - #showerr(prev_er) - #return - # not the first time - if cur_str == bad_str: - showerr(errors) - else: - self.focused_field = False - showerr('') - unmarkred() - else: - # no errors - self.focused_field = False - unmarkred() - - def cleanup_errormsg(self): - """ - we reset bad_string to None - should be called before leaving the page - """ - self.bad_string = None - - def green_validation_status(self): - val = self.validationMsg - val.setText(self.tr('Registration succeeded!')) - val.setStyleSheet(styles.GreenLineEdit) - - def reset_validation_status(self): - """ - empty the validation msg - and clean the inline validation widget. - """ - self.validationMsg.setText('') - self.steps.removeAllSteps() - self.clearTable() - - # actual checks - - def _do_checks(self): - """ - generator that yields actual checks - that are executed in a separate thread - """ - wizard = self.wizard() - - provider = self.field('provider_domain') - username = self.userNameLineEdit.text() - password = self.userPasswordLineEdit.text() - password2 = self.userPassword2LineEdit.text() - - pconfig = wizard.eipconfigchecker(domain=provider) - pconfig.defaultprovider.load() - pconfig.set_api_domain() - - def checkpass(): - # we better have here - # some call to a password checker... - # to assess strenght and avoid silly stuff. - - if password != password2: - return self.fail(self.tr('Password does not match..')) - - if len(password) < 6: - #self.set_prevalidation_error('Password too short.') - return self.fail(self.tr('Password too short.')) - - if password == "123456": - # joking, but not too much. - #self.set_prevalidation_error('Password too obvious.') - return self.fail(self.tr('Password too obvious.')) - - # go - return True - - yield(("head_sentinel", 0), checkpass) - - # XXX should emit signal for .show the frame! - # XXX HERE! - - ################################################## - # 1) register user - ################################################## - - # show the frame before going on... - QtCore.QMetaObject.invokeMethod( - self, "showStepsFrame") - - def register(): - - signup = auth.LeapSRPRegister( - schema="https", - provider=pconfig.apidomain, - verify=pconfig.cacert) - try: - ok, req = signup.register_user( - username, password) - - except socket.timeout: - return self.fail( - self.tr("Error connecting to provider (timeout)")) - - except requests.exceptions.ConnectionError as exc: - logger.error(exc.message) - return self.fail( - self.tr('Error Connecting to provider (connerr).')) - except Exception as exc: - return self.fail(exc.message) - - # XXX check for != OK instead??? - - if req.status_code in (404, 500): - return self.fail( - self.tr( - "Error during registration (%s)") % req.status_code) - - try: - validation_msgs = json.loads(req.content) - errors = validation_msgs.get('errors', None) - logger.debug('validation errors: %s' % validation_msgs) - except ValueError: - # probably bad json returned - return self.fail( - self.tr( - "Could not register (bad response)")) - - if errors and errors.get('login', None): - # XXX this sometimes catch the blank username - # but we're not allowing that (soon) - return self.fail( - self.tr('Username not available.')) - - return True - - logger.debug('registering user') - yield(("Registering username", 40), register) - - self.set_done() - yield(("end_sentinel", 100), lambda: None) - - def on_checks_validation_ready(self): - """ - after checks - """ - if self.is_done(): - self.disableFields() - self.cleanup_errormsg() - self.clean_wizard_errors(self.current_page) - # make the user confirm the transition - # to next page. - self.commitText('Connect!') - self.commitFocus() - self.green_validation_status() - self.do_confirm_next = True - - # pagewizard methods - - def validatePage(self): - """ - if not register done, do checks. - if done, wait for click. - """ - self.disableCommitButton() - self.cleanup_errormsg() - self.clean_wizard_errors(self.current_page) - - # After a successful validation - # (ie, success register with server) - # we change the commit button text - # and set this flag to True. - if self.do_confirm_next: - return True - - if not self.is_done(): - # calls checks, which after successful - # execution will call on_checks_validation_ready - self.reset_validation_status() - self.do_checks() - - return self.is_done() - - def initializePage(self): - """ - inits wizard page - """ - provider = unicode(self.field('provider_domain')) - if provider: - # here we should have provider - # but in tests we might not. - - # XXX this error causes a segfault on free() - # that we might want to get fixed ... - #self.setSubTitle( - #self.tr("Register a new user with provider %s.") % - #provider) - self.setSubTitle( - self.tr("Register a new user with provider %s" % - provider)) - self.validationMsg.setText('') - self.userPassword2LineEdit.setText('') - self.valFrame.hide() - - def nextId(self): - wizard = self.wizard() - return wizard.get_page_index('connect') diff --git a/src/leap/gui/firstrun/tests/integration/fake_provider.py b/src/leap/gui/firstrun/tests/integration/fake_provider.py deleted file mode 100755 index 668db5d1..00000000 --- a/src/leap/gui/firstrun/tests/integration/fake_provider.py +++ /dev/null @@ -1,302 +0,0 @@ -#!/usr/bin/env python -"""A server faking some of the provider resources and apis, -used for testing Leap Client requests - -It needs that you create a subfolder named 'certs', -and that you place the following files: - -[ ] certs/leaptestscert.pem -[ ] certs/leaptestskey.pem -[ ] certs/cacert.pem -[ ] certs/openvpn.pem - -[ ] provider.json -[ ] eip-service.json -""" -# XXX NOTE: intended for manual debug. -# I intend to include this as a regular test after 0.2.0 release -# (so we can add twisted as a dep there) -import binascii -import json -import os -import sys - -# python SRP LIB (! important MUST be >=1.0.1 !) -import srp - -# GnuTLS Example -- is not working as expected -#from gnutls import crypto -#from gnutls.constants import COMP_LZO, COMP_DEFLATE, COMP_NULL -#from gnutls.interfaces.twisted import X509Credentials - -# Going with OpenSSL as a workaround instead -# But we DO NOT want to introduce this dependency. -from OpenSSL import SSL - -from zope.interface import Interface, Attribute, implements - -from twisted.web.server import Site -from twisted.web.static import File -from twisted.web.resource import Resource -from twisted.internet import reactor - -from leap.testing.https_server import where - -# See -# http://twistedmatrix.com/documents/current/web/howto/web-in-60/index.htmln -# for more examples - -""" -Testing the FAKE_API: -##################### - - 1) register an user - >> curl -d "user[login]=me" -d "user[password_salt]=foo" \ - -d "user[password_verifier]=beef" http://localhost:8000/1/users.json - << {"errors": null} - - 2) check that if you try to register again, it will fail: - >> curl -d "user[login]=me" -d "user[password_salt]=foo" \ - -d "user[password_verifier]=beef" http://localhost:8000/1/users.json - << {"errors": {"login": "already taken!"}} - -""" - -# Globals to mock user/sessiondb - -USERDB = {} -SESSIONDB = {} - - -safe_unhexlify = lambda x: binascii.unhexlify(x) \ - if (len(x) % 2 == 0) else binascii.unhexlify('0' + x) - - -class IUser(Interface): - login = Attribute("User login.") - salt = Attribute("Password salt.") - verifier = Attribute("Password verifier.") - session = Attribute("Session.") - svr = Attribute("Server verifier.") - - -class User(object): - implements(IUser) - - def __init__(self, login, salt, verifier): - self.login = login - self.salt = salt - self.verifier = verifier - self.session = None - - def set_server_verifier(self, svr): - self.svr = svr - - def set_session(self, session): - SESSIONDB[session] = self - self.session = session - - -class FakeUsers(Resource): - def __init__(self, name): - self.name = name - - def render_POST(self, request): - args = request.args - - login = args['user[login]'][0] - salt = args['user[password_salt]'][0] - verifier = args['user[password_verifier]'][0] - - if login in USERDB: - return "%s\n" % json.dumps( - {'errors': {'login': 'already taken!'}}) - - print login, verifier, salt - user = User(login, salt, verifier) - USERDB[login] = user - return json.dumps({'errors': None}) - - -def get_user(request): - login = request.args.get('login') - if login: - user = USERDB.get(login[0], None) - if user: - return user - - session = request.getSession() - user = SESSIONDB.get(session, None) - return user - - -class FakeSession(Resource): - def __init__(self, name): - self.name = name - - def render_GET(self, request): - return "%s\n" % json.dumps({'errors': None}) - - def render_POST(self, request): - - user = get_user(request) - - if not user: - # XXX get real error from demo provider - return json.dumps({'errors': 'no such user'}) - - A = request.args['A'][0] - - _A = safe_unhexlify(A) - _salt = safe_unhexlify(user.salt) - _verifier = safe_unhexlify(user.verifier) - - svr = srp.Verifier( - user.login, - _salt, - _verifier, - _A, - hash_alg=srp.SHA256, - ng_type=srp.NG_1024) - - s, B = svr.get_challenge() - - _B = binascii.hexlify(B) - - print 'login = %s' % user.login - print 'salt = %s' % user.salt - print 'len(_salt) = %s' % len(_salt) - print 'vkey = %s' % user.verifier - print 'len(vkey) = %s' % len(_verifier) - print 's = %s' % binascii.hexlify(s) - print 'B = %s' % _B - print 'len(B) = %s' % len(_B) - - session = request.getSession() - user.set_session(session) - user.set_server_verifier(svr) - - # yep, this is tricky. - # some things are *already* unhexlified. - data = { - 'salt': user.salt, - 'B': _B, - 'errors': None} - - return json.dumps(data) - - def render_PUT(self, request): - - # XXX check session??? - user = get_user(request) - - if not user: - print 'NO USER' - return json.dumps({'errors': 'no such user'}) - - data = request.content.read() - auth = data.split("client_auth=") - M = auth[1] if len(auth) > 1 else None - # if not H, return - if not M: - return json.dumps({'errors': 'no M proof passed by client'}) - - svr = user.svr - HAMK = svr.verify_session(binascii.unhexlify(M)) - if HAMK is None: - print 'verification failed!!!' - raise Exception("Authentication failed!") - #import ipdb;ipdb.set_trace() - - assert svr.authenticated() - print "***" - print 'server authenticated user SRP!' - print "***" - - return json.dumps( - {'M2': binascii.hexlify(HAMK), 'errors': None}) - - -class API_Sessions(Resource): - def getChild(self, name, request): - return FakeSession(name) - - -def get_certs_path(): - script_path = os.path.realpath(os.path.dirname(sys.argv[0])) - certs_path = os.path.join(script_path, 'certs') - return certs_path - - -def get_TLS_credentials(): - # XXX this is giving errors - # XXX REview! We want to use gnutls! - - cert = crypto.X509Certificate( - open(where('leaptestscert.pem')).read()) - key = crypto.X509PrivateKey( - open(where('leaptestskey.pem')).read()) - ca = crypto.X509Certificate( - open(where('cacert.pem')).read()) - #crl = crypto.X509CRL(open(certs_path + '/crl.pem').read()) - #cred = crypto.X509Credentials(cert, key, [ca], [crl]) - cred = X509Credentials(cert, key, [ca]) - cred.verify_peer = True - cred.session_params.compressions = (COMP_LZO, COMP_DEFLATE, COMP_NULL) - return cred - - -class OpenSSLServerContextFactory: - # XXX workaround for broken TLS interface - # from gnuTLS. - - def getContext(self): - """Create an SSL context. - This is a sample implementation that loads a certificate from a file - called 'server.pem'.""" - - ctx = SSL.Context(SSL.SSLv23_METHOD) - #certs_path = get_certs_path() - #ctx.use_certificate_file(certs_path + '/leaptestscert.pem') - #ctx.use_privatekey_file(certs_path + '/leaptestskey.pem') - ctx.use_certificate_file(where('leaptestscert.pem')) - ctx.use_privatekey_file(where('leaptestskey.pem')) - return ctx - - -def serve_fake_provider(): - root = Resource() - root.putChild("provider.json", File("./provider.json")) - config = Resource() - config.putChild( - "eip-service.json", - File("./eip-service.json")) - apiv1 = Resource() - apiv1.putChild("config", config) - apiv1.putChild("sessions.json", API_Sessions()) - apiv1.putChild("users.json", FakeUsers(None)) - apiv1.putChild("cert", File(get_certs_path() + '/openvpn.pem')) - root.putChild("1", apiv1) - - cred = get_TLS_credentials() - - factory = Site(root) - - # regular http (for debugging with curl) - reactor.listenTCP(8000, factory) - - # TLS with gnutls --- seems broken :( - #reactor.listenTLS(8003, factory, cred) - - # OpenSSL - reactor.listenSSL(8443, factory, OpenSSLServerContextFactory()) - - reactor.run() - - -if __name__ == "__main__": - - from twisted.python import log - log.startLogging(sys.stdout) - - serve_fake_provider() diff --git a/src/leap/gui/firstrun/wizard.py b/src/leap/gui/firstrun/wizard.py deleted file mode 100755 index f198dca0..00000000 --- a/src/leap/gui/firstrun/wizard.py +++ /dev/null @@ -1,309 +0,0 @@ -#!/usr/bin/env python -import logging - -import sip -try: - sip.setapi('QString', 2) - sip.setapi('QVariant', 2) -except ValueError: - pass - -from PyQt4 import QtCore -from PyQt4 import QtGui - -from leap.base import checks as basechecks -from leap.crypto import leapkeyring -from leap.eip import checks as eipchecks - -from leap.gui import firstrun - -from leap.gui import mainwindow_rc - -try: - from collections import OrderedDict -except ImportError: - # We must be in 2.6 - from leap.util.dicts import OrderedDict - -logger = logging.getLogger(__name__) - -""" -~~~~~~~~~~~~~~~~~~~~~~~~~~ -Work in progress! -~~~~~~~~~~~~~~~~~~~~~~~~~~ -This wizard still needs to be refactored out. - -TODO-ish: - -[X] Break file in wizard / pages files (and its own folder). -[ ] Separate presentation from logic. -[ ] Have a "manager" class for connections, that can be - dep-injected for testing. -[ ] Document signals used / expected. -[ ] Separate style from widgets. -[ ] Fix TOFU Widget for provider cert. -[X] Refactor widgets out. -[ ] Follow more MVC style. -[ ] Maybe separate "first run wizard" into different wizards - that share some of the pages? -""" - - -def get_pages_dict(): - return OrderedDict(( - ('intro', firstrun.intro.IntroPage), - ('providerselection', - firstrun.providerselect.SelectProviderPage), - ('login', firstrun.login.LogInPage), - ('providerinfo', firstrun.providerinfo.ProviderInfoPage), - ('providersetupvalidation', - firstrun.providersetup.ProviderSetupValidationPage), - ('signup', firstrun.register.RegisterUserPage), - ('connect', - firstrun.connect.ConnectionPage), - ('lastpage', firstrun.last.LastPage) - )) - - -class FirstRunWizard(QtGui.QWizard): - - def __init__( - self, - conductor_instance, - parent=None, - pages_dict=None, - username=None, - providers=None, - success_cb=None, is_provider_setup=False, - trusted_certs=None, - netchecker=basechecks.LeapNetworkChecker, - providercertchecker=eipchecks.ProviderCertChecker, - eipconfigchecker=eipchecks.EIPConfigChecker, - start_eipconnection_signal=None, - eip_statuschange_signal=None, - debug_server=None, - quitcallback=None): - super(FirstRunWizard, self).__init__( - parent, - QtCore.Qt.WindowStaysOnTopHint) - - # we keep a reference to the conductor - # to be able to launch eip checks and connection - # in the connection page, before the wizard has ended. - self.conductor = conductor_instance - - self.username = username - self.providers = providers - - # success callback - self.success_cb = success_cb - - # is provider setup? - self.is_provider_setup = is_provider_setup - - # a dict with trusted fingerprints - # in the form {'nospacesfingerprint': ['host1', 'host2']} - self.trusted_certs = trusted_certs - - # Checkers - self.netchecker = netchecker - self.providercertchecker = providercertchecker - self.eipconfigchecker = eipconfigchecker - - # debug server - self.debug_server = debug_server - - # Signals - # will be emitted in connecting page - self.start_eipconnection_signal = start_eipconnection_signal - self.eip_statuschange_signal = eip_statuschange_signal - - if quitcallback is not None: - self.button( - QtGui.QWizard.CancelButton).clicked.connect( - quitcallback) - - self.providerconfig = None - # previously registered - # if True, jumps to LogIn page. - # by setting 1st page?? - #self.is_previously_registered = is_previously_registered - # XXX ??? ^v - self.is_previously_registered = bool(self.username) - self.from_login = False - - pages_dict = pages_dict or get_pages_dict() - self.add_pages_from_dict(pages_dict) - - self.validation_errors = {} - self.openvpn_status = [] - - self.setPixmap( - QtGui.QWizard.BannerPixmap, - QtGui.QPixmap(':/images/banner.png')) - self.setPixmap( - QtGui.QWizard.BackgroundPixmap, - QtGui.QPixmap(':/images/background.png')) - - # set options - self.setOption(QtGui.QWizard.IndependentPages, on=False) - self.setOption(QtGui.QWizard.NoBackButtonOnStartPage, on=True) - - self.setWindowTitle("First Run Wizard") - - # TODO: set style for MAC / windows ... - #self.setWizardStyle() - - # - # setup pages in wizard - # - - def add_pages_from_dict(self, pages_dict): - """ - @param pages_dict: the dictionary with pages, where - values are a tuple of InstanceofWizardPage, kwargs. - @type pages_dict: dict - """ - for name, page in pages_dict.items(): - # XXX check for is_previously registered - # and skip adding the signup branch if so - self.addPage(page()) - self.pages_dict = pages_dict - - def get_page_index(self, page_name): - """ - returns the index of the given page - @param page_name: the name of the desired page - @type page_name: str - @rparam: index of page in wizard - @rtype: int - """ - return self.pages_dict.keys().index(page_name) - - # - # validation errors - # - - def set_validation_error(self, pagename, error): - self.validation_errors[pagename] = error - - def clean_validation_error(self, pagename): - vald = self.validation_errors - if pagename in vald: - del vald[pagename] - - def get_validation_error(self, pagename): - return self.validation_errors.get(pagename, None) - - def accept(self): - """ - final step in the wizard. - gather the info, update settings - and call the success callback if any has been passed. - """ - super(FirstRunWizard, self).accept() - - # username and password are in different fields - # if they were stored in log_in or sign_up pages. - from_login = self.from_login - unamek_base = 'userName' - passwk_base = 'userPassword' - unamek = 'login_%s' % unamek_base if from_login else unamek_base - passwk = 'login_%s' % passwk_base if from_login else passwk_base - - username = self.field(unamek) - password = self.field(passwk) - provider = self.field('provider_domain') - remember_pass = self.field('rememberPassword') - - logger.debug('chosen provider: %s', provider) - logger.debug('username: %s', username) - logger.debug('remember password: %s', remember_pass) - - # we are assuming here that we only remember one username - # in the form username@provider.domain - # We probably could extend this to support some form of - # profiles. - - settings = QtCore.QSettings() - - settings.setValue("FirstRunWizardDone", True) - settings.setValue("provider_domain", provider) - full_username = "%s@%s" % (username, provider) - - settings.setValue("remember_user_and_pass", remember_pass) - - if remember_pass: - settings.setValue("username", full_username) - seed = self.get_random_str(10) - settings.setValue("%s_seed" % provider, seed) - - # XXX #744: comment out for 0.2.0 release - # if we need to have a version of python-keyring < 0.9 - leapkeyring.leap_set_password( - full_username, password, seed=seed) - - logger.debug('First Run Wizard Done.') - cb = self.success_cb - if cb and callable(cb): - self.success_cb() - - # misc helpers - - def get_random_str(self, n): - """ - returns a random string - :param n: the length of the desired string - :rvalue: str - """ - from string import (ascii_uppercase, ascii_lowercase, digits) - from random import choice - return ''.join(choice( - ascii_uppercase + - ascii_lowercase + - digits) for x in range(n)) - - def set_providerconfig(self, providerconfig): - """ - sets a providerconfig attribute - used when we fetch and parse a json configuration - """ - self.providerconfig = providerconfig - - def get_provider_by_index(self): # pragma: no cover - """ - returns the value of a provider given its index. - this was used in the select provider page, - in the case where we were preseeding providers in a combobox - """ - # Leaving it here for the moment when we go back at the - # option of preseeding with known provider values. - provider = self.field('provider_index') - return self.providers[provider] - - -if __name__ == '__main__': - # standalone test - # it can be (somehow) run against - # gui/tests/integration/fake_user_signup.py - - import sys - import logging - logging.basicConfig() - logger = logging.getLogger() - logger.setLevel(logging.DEBUG) - - app = QtGui.QApplication(sys.argv) - server = sys.argv[1] if len(sys.argv) > 1 else None - - trusted_certs = { - "3DF83F316BFA0186" - "0A11A5C9C7FC24B9" - "18C62B941192CC1A" - "49AE62218B2A4B7C": ['springbok']} - - wizard = FirstRunWizard( - None, trusted_certs=trusted_certs, - debug_server=server) - wizard.show() - sys.exit(app.exec_()) diff --git a/src/leap/gui/locale_rc.py b/src/leap/gui/locale_rc.py deleted file mode 100644 index 8c383709..00000000 --- a/src/leap/gui/locale_rc.py +++ /dev/null @@ -1,813 +0,0 @@ -# -*- coding: utf-8 -*- - -# Resource object code -# -# Created: Fri Jan 25 18:19:04 2013 -# by: The Resource Compiler for PyQt (Qt v4.8.2) -# -# WARNING! All changes made in this file will be lost! - -from PyQt4 import QtCore - -qt_resource_data = "\ -\x00\x00\x17\x94\ -\x3c\ -\xb8\x64\x18\xca\xef\x9c\x95\xcd\x21\x1c\xbf\x60\xa1\xbd\xdd\x42\ -\x00\x00\x01\x30\x00\x8f\x9b\xbe\x00\x00\x14\x69\x01\x23\x92\xe5\ -\x00\x00\x10\x2f\x01\x87\x64\x8e\x00\x00\x08\xbe\x01\xa8\xbe\x7e\ -\x00\x00\x0d\xf4\x02\x2c\xac\xe9\x00\x00\x0b\x9c\x02\x3a\xce\xbf\ -\x00\x00\x15\xe2\x02\x6e\x0f\xe5\x00\x00\x09\x2d\x02\x87\x60\x9e\ -\x00\x00\x06\xc6\x02\xaa\x52\x6e\x00\x00\x07\xc9\x02\xf2\xe0\x59\ -\x00\x00\x0a\x6c\x03\xec\x70\x0e\x00\x00\x10\x9c\x04\xd4\x45\xee\ -\x00\x00\x0d\x3c\x05\xb7\x8f\x59\x00\x00\x0c\x35\x06\x3e\x6a\x9e\ -\x00\x00\x06\x01\x06\x40\xa8\x7e\x00\x00\x0b\x02\x06\xee\xff\x6e\ -\x00\x00\x13\x50\x08\x13\xe8\xae\x00\x00\x0c\xc2\x08\x7a\x64\xee\ -\x00\x00\x11\x8b\x08\xe6\x98\x33\x00\x00\x05\x93\x08\xe6\x98\x33\ -\x00\x00\x0f\xb0\x09\x5c\x35\xe1\x00\x00\x0e\x96\x09\x74\x75\x4e\ -\x00\x00\x0d\x9c\x09\x98\x34\x0e\x00\x00\x12\x55\x09\xd8\x1f\x95\ -\x00\x00\x15\x19\x09\xfc\x2c\x8e\x00\x00\x05\x19\x09\xfe\x05\x90\ -\x00\x00\x0f\x06\x0a\x74\xb8\x1e\x00\x00\x00\xe6\x0a\xfd\x99\xfe\ -\x00\x00\x00\x6d\x0b\xd2\x4b\x3f\x00\x00\x07\x7d\x0c\x44\x41\xbe\ -\x00\x00\x00\x00\x0c\xc0\x94\x05\x00\x00\x09\xf2\x0d\x0d\x9d\xc5\ -\x00\x00\x06\x5f\x0d\x15\x34\x70\x00\x00\x09\x98\x0e\x36\x15\x54\ -\x00\x00\x08\x47\x0e\x7e\xf5\xee\x00\x00\x0f\x42\x0e\x91\x50\x3e\ -\x00\x00\x15\x76\x0e\xc0\xbb\x72\x00\x00\x12\xfb\x0f\x27\x0d\x6e\ -\x00\x00\x11\x22\x69\x00\x00\x16\x43\x03\x00\x00\x00\x3e\x00\x41\ -\x00\x73\x00\x73\x00\x69\x00\x73\x00\x74\x00\x65\x00\x6e\x00\x74\ -\x00\x20\x00\x66\x00\xfc\x00\x72\x00\x20\x00\x65\x00\x72\x00\x73\ -\x00\x74\x00\x6d\x00\x61\x00\x6c\x00\x69\x00\x67\x00\x65\x00\x6e\ -\x00\x20\x00\x53\x00\x74\x00\x61\x00\x72\x00\x74\x08\x00\x00\x00\ -\x00\x06\x00\x00\x00\x11\x46\x69\x72\x73\x74\x20\x72\x75\x6e\x20\ -\x77\x69\x7a\x61\x72\x64\x2e\x07\x00\x00\x00\x09\x49\x6e\x74\x72\ -\x6f\x50\x61\x67\x65\x01\x03\x00\x00\x00\x40\x00\x4d\x00\x69\x00\ -\x74\x00\x20\x00\x62\x00\x65\x00\x73\x00\x74\x00\x65\x00\x68\x00\ -\x65\x00\x6e\x00\x64\x00\x65\x00\x6e\x00\x20\x00\x44\x00\x61\x00\ -\x74\x00\x65\x00\x6e\x00\x20\x00\x65\x00\x69\x00\x6e\x00\x6c\x00\ -\x6f\x00\x67\x00\x67\x00\x65\x00\x6e\x00\x2e\x08\x00\x00\x00\x00\ -\x06\x00\x00\x00\x1b\x4c\x6f\x67\x20\x49\x6e\x20\x77\x69\x74\x68\ -\x20\x6d\x79\x20\x63\x72\x65\x64\x65\x6e\x74\x69\x61\x6c\x73\x2e\ -\x07\x00\x00\x00\x09\x49\x6e\x74\x72\x6f\x50\x61\x67\x65\x01\x03\ -\x00\x00\x02\xb8\x00\x57\x00\x69\x00\x72\x00\x20\x00\x77\x00\x65\ -\x00\x72\x00\x64\x00\x65\x00\x6e\x00\x20\x00\x64\x00\x69\x00\x63\ -\x00\x68\x00\x20\x00\x6e\x00\x75\x00\x6e\x00\x20\x00\x64\x00\x75\ -\x00\x72\x00\x63\x00\x68\x00\x20\x00\x65\x00\x69\x00\x6e\x00\x69\ -\x00\x67\x00\x65\x00\x20\x00\x4b\x00\x6f\x00\x6e\x00\x66\x00\x69\ -\x00\x67\x00\x75\x00\x72\x00\x61\x00\x74\x00\x69\x00\x6f\x00\x6e\ -\x00\x65\x00\x6e\x00\x20\x00\x66\x00\xfc\x00\x68\x00\x72\x00\x65\ -\x00\x6e\x00\x2c\x00\x20\x00\x64\x00\x69\x00\x65\x00\x20\x00\x64\ -\x00\x75\x00\x20\x00\x66\x00\xfc\x00\x72\x00\x20\x00\x64\x00\x65\ -\x00\x6e\x00\x20\x00\x65\x00\x72\x00\x73\x00\x74\x00\x65\x00\x6e\ -\x00\x20\x00\x53\x00\x74\x00\x61\x00\x72\x00\x74\x00\x20\x00\x62\ -\x00\x65\x00\x6e\x00\xf6\x00\x74\x00\x69\x00\x67\x00\x73\x00\x74\ -\x00\x2e\x00\x3c\x00\x62\x00\x72\x00\x3e\x00\x3c\x00\x62\x00\x72\ -\x00\x3e\x00\x57\x00\x65\x00\x6e\x00\x6e\x00\x20\x00\x64\x00\x75\ -\x00\x20\x00\x64\x00\x69\x00\x65\x00\x73\x00\x65\x00\x20\x00\x4b\ -\x00\x6f\x00\x6e\x00\x66\x00\x69\x00\x67\x00\x75\x00\x72\x00\x61\ -\x00\x74\x00\x69\x00\x6f\x00\x6e\x00\x65\x00\x6e\x00\x20\x00\x6a\ -\x00\x65\x00\x6d\x00\x61\x00\x6c\x00\x73\x00\x20\x00\xe4\x00\x6e\ -\x00\x64\x00\x65\x00\x72\x00\x6e\x00\x20\x00\x6d\x00\x75\x00\x73\ -\x00\x73\x00\x74\x00\x2c\x00\x20\x00\x66\x00\x69\x00\x6e\x00\x64\ -\x00\x65\x00\x73\x00\x74\x00\x20\x00\x64\x00\x75\x00\x20\x00\x64\ -\x00\x65\x00\x6e\x00\x20\x00\x41\x00\x73\x00\x73\x00\x69\x00\x73\ -\x00\x74\x00\x65\x00\x6e\x00\x74\x00\x65\x00\x6e\x00\x20\x00\x69\ -\x00\x6d\x00\x20\x00\x27\x00\x3c\x00\x69\x00\x3e\x00\x45\x00\x69\ -\x00\x6e\x00\x73\x00\x74\x00\x65\x00\x6c\x00\x6c\x00\x75\x00\x6e\ -\x00\x67\x00\x65\x00\x6e\x00\x3c\x00\x2f\x00\x69\x00\x3e\x00\x27\ -\x00\x2d\x00\x4d\x00\x65\x00\x6e\x00\xfc\x00\x20\x00\x64\x00\x65\ -\x00\x73\x00\x20\x00\x48\x00\x61\x00\x75\x00\x70\x00\x66\x00\x65\ -\x00\x6e\x00\x73\x00\x74\x00\x65\x00\x72\x00\x73\x00\x2e\x00\x3c\ -\x00\x62\x00\x72\x00\x3e\x00\x3c\x00\x62\x00\x72\x00\x3e\x00\x4d\ -\x00\xf6\x00\x63\x00\x68\x00\x74\x00\x65\x00\x73\x00\x74\x00\x20\ -\x00\x64\x00\x75\x00\x20\x00\x64\x00\x69\x00\x63\x00\x68\x00\x20\ -\x00\x66\x00\xfc\x00\x72\x00\x20\x00\x65\x00\x69\x00\x6e\x00\x65\ -\x00\x6e\x00\x20\x00\x6e\x00\x65\x00\x75\x00\x65\x00\x6e\x00\x20\ -\x00\x41\x00\x63\x00\x63\x00\x6f\x00\x75\x00\x6e\x00\x74\x00\x20\ -\x00\x3c\x00\x62\x00\x3e\x00\x61\x00\x6e\x00\x6d\x00\x65\x00\x6c\ -\x00\x64\x00\x65\x00\x6e\x00\x3c\x00\x2f\x00\x62\x00\x3e\x00\x20\ -\x00\x6f\x00\x64\x00\x65\x00\x72\x00\x20\x00\x6d\x00\x69\x00\x74\ -\x00\x20\x00\x65\x00\x69\x00\x6e\x00\x65\x00\x6d\x00\x20\x00\x62\ -\x00\x65\x00\x73\x00\x74\x00\x65\x00\x68\x00\x65\x00\x6e\x00\x64\ -\x00\x65\x00\x6e\x00\x20\x00\x55\x00\x73\x00\x65\x00\x72\x00\x6e\ -\x00\x61\x00\x6d\x00\x65\x00\x6e\x00\x20\x00\x3c\x00\x62\x00\x3e\ -\x00\x65\x00\x69\x00\x6e\x00\x6c\x00\x6f\x00\x67\x00\x67\x00\x65\ -\x00\x6e\x00\x3c\x00\x2f\x00\x62\x00\x3e\x00\x3f\x08\x00\x00\x00\ -\x00\x06\x00\x00\x01\x5d\x4e\x6f\x77\x20\x77\x65\x20\x77\x69\x6c\ -\x6c\x20\x67\x75\x69\x64\x65\x20\x79\x6f\x75\x20\x74\x68\x72\x6f\ -\x75\x67\x68\x20\x73\x6f\x6d\x65\x20\x63\x6f\x6e\x66\x69\x67\x75\ -\x72\x61\x74\x69\x6f\x6e\x20\x74\x68\x61\x74\x20\x69\x73\x20\x6e\ -\x65\x65\x64\x65\x64\x20\x62\x65\x66\x6f\x72\x65\x20\x79\x6f\x75\ -\x20\x63\x61\x6e\x20\x63\x6f\x6e\x6e\x65\x63\x74\x20\x66\x6f\x72\ -\x20\x74\x68\x65\x20\x66\x69\x72\x73\x74\x20\x74\x69\x6d\x65\x2e\ -\x3c\x62\x72\x3e\x3c\x62\x72\x3e\x49\x66\x20\x79\x6f\x75\x20\x65\ -\x76\x65\x72\x20\x6e\x65\x65\x64\x20\x74\x6f\x20\x6d\x6f\x64\x69\ -\x66\x79\x20\x74\x68\x65\x73\x65\x20\x6f\x70\x74\x69\x6f\x6e\x73\ -\x20\x61\x67\x61\x69\x6e\x2c\x20\x79\x6f\x75\x20\x63\x61\x6e\x20\ -\x66\x69\x6e\x64\x20\x74\x68\x65\x20\x77\x69\x7a\x61\x72\x64\x20\ -\x69\x6e\x20\x74\x68\x65\x20\x27\x3c\x69\x3e\x53\x65\x74\x74\x69\ -\x6e\x67\x73\x3c\x2f\x69\x3e\x27\x20\x6d\x65\x6e\x75\x20\x66\x72\ -\x6f\x6d\x20\x74\x68\x65\x20\x6d\x61\x69\x6e\x20\x77\x69\x6e\x64\ -\x6f\x77\x2e\x3c\x62\x72\x3e\x3c\x62\x72\x3e\x44\x6f\x20\x79\x6f\ -\x75\x20\x77\x61\x6e\x74\x20\x74\x6f\x20\x3c\x62\x3e\x73\x69\x67\ -\x6e\x20\x75\x70\x3c\x2f\x62\x3e\x20\x66\x6f\x72\x20\x61\x20\x6e\ -\x65\x77\x20\x61\x63\x63\x6f\x75\x6e\x74\x2c\x20\x6f\x72\x20\x3c\ -\x62\x3e\x6c\x6f\x67\x20\x69\x6e\x3c\x2f\x62\x3e\x20\x77\x69\x74\ -\x68\x20\x61\x6e\x20\x61\x6c\x72\x65\x61\x64\x79\x20\x65\x78\x69\ -\x73\x74\x69\x6e\x67\x20\x75\x73\x65\x72\x6e\x61\x6d\x65\x3f\x3c\ -\x62\x72\x3e\x07\x00\x00\x00\x09\x49\x6e\x74\x72\x6f\x50\x61\x67\ -\x65\x01\x03\x00\x00\x00\x42\x00\x46\x00\xfc\x00\x72\x00\x20\x00\ -\x65\x00\x69\x00\x6e\x00\x65\x00\x6e\x00\x20\x00\x6e\x00\x65\x00\ -\x75\x00\x65\x00\x6e\x00\x20\x00\x41\x00\x63\x00\x63\x00\x6f\x00\ -\x75\x00\x6e\x00\x74\x00\x20\x00\x61\x00\x6e\x00\x6d\x00\x65\x00\ -\x6c\x00\x64\x00\x65\x00\x6e\x00\x2e\x08\x00\x00\x00\x00\x06\x00\ -\x00\x00\x1a\x53\x69\x67\x6e\x20\x75\x70\x20\x66\x6f\x72\x20\x61\ -\x20\x6e\x65\x77\x20\x61\x63\x63\x6f\x75\x6e\x74\x2e\x07\x00\x00\ -\x00\x09\x49\x6e\x74\x72\x6f\x50\x61\x67\x65\x01\x03\x00\x00\x00\ -\x38\x00\x41\x00\x75\x00\x74\x00\x68\x00\x65\x00\x6e\x00\x74\x00\ -\x69\x00\x66\x00\x69\x00\x7a\x00\x69\x00\x65\x00\x72\x00\x75\x00\ -\x6e\x00\x67\x00\x73\x00\x66\x00\x65\x00\x68\x00\x6c\x00\x65\x00\ -\x72\x00\x3a\x00\x20\x00\x25\x00\x73\x08\x00\x00\x00\x00\x06\x00\ -\x00\x00\x18\x41\x75\x74\x68\x65\x6e\x74\x69\x63\x61\x74\x69\x6f\ -\x6e\x20\x65\x72\x72\x6f\x72\x3a\x20\x25\x73\x07\x00\x00\x00\x09\ -\x4c\x6f\x67\x49\x6e\x50\x61\x67\x65\x01\x03\x00\x00\x00\x2a\x00\ -\x41\x00\x6e\x00\x6d\x00\x65\x00\x6c\x00\x64\x00\x65\x00\x64\x00\ -\x61\x00\x74\x00\x65\x00\x6e\x00\x20\x00\x6b\x00\x6f\x00\x72\x00\ -\x72\x00\x65\x00\x6b\x00\x74\x00\x2e\x08\x00\x00\x00\x00\x06\x00\ -\x00\x00\x16\x43\x72\x65\x64\x65\x6e\x74\x69\x61\x6c\x73\x20\x76\ -\x61\x6c\x69\x64\x61\x74\x65\x64\x2e\x07\x00\x00\x00\x09\x4c\x6f\ -\x67\x49\x6e\x50\x61\x67\x65\x01\x03\x00\x00\x00\x34\x00\x41\x00\ -\x75\x00\x66\x00\x6c\x00\xf6\x00\x73\x00\x65\x00\x6e\x00\x20\x00\ -\x64\x00\x65\x00\x73\x00\x20\x00\x44\x00\x6f\x00\x6d\x00\x61\x00\ -\x69\x00\x6e\x00\x2d\x00\x4e\x00\x61\x00\x6d\x00\x65\x00\x6e\x00\ -\x73\x08\x00\x00\x00\x00\x06\x00\x00\x00\x15\x52\x65\x73\x6f\x6c\ -\x76\x69\x6e\x67\x20\x64\x6f\x6d\x61\x69\x6e\x20\x6e\x61\x6d\x65\ -\x07\x00\x00\x00\x09\x4c\x6f\x67\x49\x6e\x50\x61\x67\x65\x01\x03\ -\x00\x00\x00\x6a\x00\x44\x00\x65\x00\x72\x00\x20\x00\x55\x00\x73\ -\x00\x65\x00\x72\x00\x6e\x00\x61\x00\x6d\x00\x65\x00\x20\x00\x6d\ -\x00\x75\x00\x73\x00\x73\x00\x20\x00\x69\x00\x6e\x00\x20\x00\x64\ -\x00\x65\x00\x72\x00\x20\x00\x46\x00\x6f\x00\x72\x00\x6d\x00\x20\ -\x00\x75\x00\x73\x00\x65\x00\x72\x00\x6e\x00\x61\x00\x6d\x00\x65\ -\x00\x40\x00\x70\x00\x72\x00\x6f\x00\x76\x00\x69\x00\x64\x00\x65\ -\x00\x72\x00\x20\x00\x73\x00\x65\x00\x69\x00\x6e\x00\x2e\x08\x00\ -\x00\x00\x00\x06\x00\x00\x00\x2f\x55\x73\x65\x72\x6e\x61\x6d\x65\ -\x20\x6d\x75\x73\x74\x20\x62\x65\x20\x69\x6e\x20\x74\x68\x65\x20\ -\x75\x73\x65\x72\x6e\x61\x6d\x65\x40\x70\x72\x6f\x76\x69\x64\x65\ -\x72\x20\x66\x6f\x72\x6d\x2e\x07\x00\x00\x00\x09\x4c\x6f\x67\x49\ -\x6e\x50\x61\x67\x65\x01\x03\x00\x00\x00\x1a\x00\x50\x00\x72\x00\ -\x6f\x00\x76\x00\x69\x00\x64\x00\x65\x00\x72\x00\x2d\x00\x69\x00\ -\x6e\x00\x66\x00\x6f\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0d\x50\ -\x72\x6f\x76\x69\x64\x65\x72\x20\x49\x6e\x66\x6f\x07\x00\x00\x00\ -\x10\x50\x72\x6f\x76\x69\x64\x65\x72\x49\x6e\x66\x6f\x50\x61\x67\ -\x65\x01\x03\x00\x00\x00\x3e\x00\x44\x00\x61\x00\x73\x00\x20\x00\ -\x69\x00\x73\x00\x74\x00\x2c\x00\x20\x00\x77\x00\x61\x00\x73\x00\ -\x20\x00\x64\x00\x65\x00\x72\x00\x20\x00\x50\x00\x72\x00\x6f\x00\ -\x76\x00\x69\x00\x64\x00\x65\x00\x72\x00\x20\x00\x73\x00\x61\x00\ -\x67\x00\x74\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x1b\x54\ -\x68\x69\x73\x20\x69\x73\x20\x77\x68\x61\x74\x20\x70\x72\x6f\x76\ -\x69\x64\x65\x72\x20\x73\x61\x79\x73\x2e\x07\x00\x00\x00\x10\x50\ -\x72\x6f\x76\x69\x64\x65\x72\x49\x6e\x66\x6f\x50\x61\x67\x65\x01\ -\x03\x00\x00\x00\x30\x00\xdc\x00\x62\x00\x65\x00\x72\x00\x70\x00\ -\x72\x00\xfc\x00\x66\x00\x65\x00\x20\x00\x43\x00\x41\x00\x2d\x00\ -\x46\x00\x69\x00\x6e\x00\x67\x00\x65\x00\x72\x00\x70\x00\x72\x00\ -\x69\x00\x6e\x00\x74\x08\x00\x00\x00\x00\x06\x00\x00\x00\x17\x43\ -\x68\x65\x63\x6b\x69\x6e\x67\x20\x43\x41\x20\x66\x69\x6e\x67\x65\ -\x72\x70\x72\x69\x6e\x74\x07\x00\x00\x00\x1b\x50\x72\x6f\x76\x69\ -\x64\x65\x72\x53\x65\x74\x75\x70\x56\x61\x6c\x69\x64\x61\x74\x69\ -\x6f\x6e\x50\x61\x67\x65\x01\x03\x00\x00\x00\x2e\x00\x46\x00\xfc\ -\x00\x68\x00\x72\x00\x65\x00\x20\x00\x61\x00\x75\x00\x74\x00\x6f\ -\x00\x63\x00\x6f\x00\x6e\x00\x66\x00\x69\x00\x67\x00\x20\x00\x64\ -\x00\x75\x00\x72\x00\x63\x00\x68\x00\x2e\x08\x00\x00\x00\x00\x06\ -\x00\x00\x00\x11\x44\x6f\x69\x6e\x67\x20\x61\x75\x74\x6f\x63\x6f\ -\x6e\x66\x69\x67\x2e\x07\x00\x00\x00\x1b\x50\x72\x6f\x76\x69\x64\ -\x65\x72\x53\x65\x74\x75\x70\x56\x61\x6c\x69\x64\x61\x74\x69\x6f\ -\x6e\x50\x61\x67\x65\x01\x03\x00\x00\x00\x24\x00\x48\x00\x6f\x00\ -\x6c\x00\x65\x00\x20\x00\x43\x00\x41\x00\x2d\x00\x5a\x00\x65\x00\ -\x72\x00\x74\x00\x69\x00\x66\x00\x69\x00\x6b\x00\x61\x00\x74\x08\ -\x00\x00\x00\x00\x06\x00\x00\x00\x17\x46\x65\x74\x63\x68\x69\x6e\ -\x67\x20\x43\x41\x20\x63\x65\x72\x74\x69\x66\x69\x63\x61\x74\x65\ -\x07\x00\x00\x00\x1b\x50\x72\x6f\x76\x69\x64\x65\x72\x53\x65\x74\ -\x75\x70\x56\x61\x6c\x69\x64\x61\x74\x69\x6f\x6e\x50\x61\x67\x65\ -\x01\x03\x00\x00\x00\x1c\x00\x50\x00\x72\x00\x6f\x00\x76\x00\x69\ -\x00\x64\x00\x65\x00\x72\x00\x2d\x00\x53\x00\x65\x00\x74\x00\x75\ -\x00\x70\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0e\x50\x72\x6f\x76\ -\x69\x64\x65\x72\x20\x73\x65\x74\x75\x70\x07\x00\x00\x00\x1b\x50\ -\x72\x6f\x76\x69\x64\x65\x72\x53\x65\x74\x75\x70\x56\x61\x6c\x69\ -\x64\x61\x74\x69\x6f\x6e\x50\x61\x67\x65\x01\x03\x00\x00\x00\x30\ -\x00\xdc\x00\x62\x00\x65\x00\x72\x00\x70\x00\x72\x00\xfc\x00\x66\ -\x00\x65\x00\x20\x00\x41\x00\x50\x00\x49\x00\x2d\x00\x5a\x00\x65\ -\x00\x72\x00\x74\x00\x69\x00\x66\x00\x69\x00\x6b\x00\x61\x00\x74\ -\x08\x00\x00\x00\x00\x06\x00\x00\x00\x1a\x56\x61\x6c\x69\x64\x61\ -\x74\x69\x6e\x67\x20\x61\x70\x69\x20\x63\x65\x72\x74\x69\x66\x69\ -\x63\x61\x74\x65\x07\x00\x00\x00\x1b\x50\x72\x6f\x76\x69\x64\x65\ -\x72\x53\x65\x74\x75\x70\x56\x61\x6c\x69\x64\x61\x74\x69\x6f\x6e\ -\x50\x61\x67\x65\x01\x03\x00\x00\x00\x50\x00\x4b\x00\x6f\x00\x6e\ -\x00\x6e\x00\x74\x00\x65\x00\x20\x00\x6e\x00\x69\x00\x63\x00\x68\ -\x00\x74\x00\x20\x00\x72\x00\x65\x00\x67\x00\x69\x00\x73\x00\x74\ -\x00\x72\x00\x69\x00\x65\x00\x72\x00\x65\x00\x6e\x00\x20\x00\x28\ -\x00\x62\x00\x61\x00\x64\x00\x20\x00\x72\x00\x65\x00\x73\x00\x70\ -\x00\x6f\x00\x6e\x00\x73\x00\x65\x00\x29\x08\x00\x00\x00\x00\x06\ -\x00\x00\x00\x21\x43\x6f\x75\x6c\x64\x20\x6e\x6f\x74\x20\x72\x65\ -\x67\x69\x73\x74\x65\x72\x20\x28\x62\x61\x64\x20\x72\x65\x73\x70\ -\x6f\x6e\x73\x65\x29\x07\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\ -\x65\x72\x55\x73\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x4e\ -\x00\x56\x00\x65\x00\x72\x00\x62\x00\x69\x00\x6e\x00\x64\x00\x75\ -\x00\x6e\x00\x67\x00\x73\x00\x66\x00\x65\x00\x68\x00\x6c\x00\x65\ -\x00\x72\x00\x20\x00\x7a\x00\x75\x00\x20\x00\x50\x00\x72\x00\x6f\ -\x00\x76\x00\x69\x00\x64\x00\x65\x00\x72\x00\x20\x00\x28\x00\x63\ -\x00\x6f\x00\x6e\x00\x6e\x00\x65\x00\x72\x00\x72\x00\x29\x08\x00\ -\x00\x00\x00\x06\x00\x00\x00\x27\x45\x72\x72\x6f\x72\x20\x43\x6f\ -\x6e\x6e\x65\x63\x74\x69\x6e\x67\x20\x74\x6f\x20\x70\x72\x6f\x76\ -\x69\x64\x65\x72\x20\x28\x63\x6f\x6e\x6e\x65\x72\x72\x29\x2e\x07\ -\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\x65\x72\x55\x73\x65\x72\ -\x50\x61\x67\x65\x01\x03\x00\x00\x00\x4e\x00\x56\x00\x65\x00\x72\ -\x00\x62\x00\x69\x00\x6e\x00\x64\x00\x75\x00\x6e\x00\x67\x00\x73\ -\x00\x66\x00\x65\x00\x68\x00\x6c\x00\x65\x00\x72\x00\x20\x00\x7a\ -\x00\x75\x00\x20\x00\x50\x00\x72\x00\x6f\x00\x76\x00\x69\x00\x64\ -\x00\x65\x00\x72\x00\x20\x00\x28\x00\x74\x00\x69\x00\x6d\x00\x65\ -\x00\x6f\x00\x75\x00\x74\x00\x29\x08\x00\x00\x00\x00\x06\x00\x00\ -\x00\x26\x45\x72\x72\x6f\x72\x20\x63\x6f\x6e\x6e\x65\x63\x74\x69\ -\x6e\x67\x20\x74\x6f\x20\x70\x72\x6f\x76\x69\x64\x65\x72\x20\x28\ -\x74\x69\x6d\x65\x6f\x75\x74\x29\x07\x00\x00\x00\x10\x52\x65\x67\ -\x69\x73\x74\x65\x72\x55\x73\x65\x72\x50\x61\x67\x65\x01\x03\x00\ -\x00\x00\x4a\x00\x46\x00\x65\x00\x68\x00\x6c\x00\x65\x00\x72\x00\ -\x20\x00\x77\x00\xe4\x00\x68\x00\x72\x00\x65\x00\x6e\x00\x64\x00\ -\x20\x00\x64\x00\x65\x00\x72\x00\x20\x00\x52\x00\x65\x00\x67\x00\ -\x69\x00\x73\x00\x74\x00\x72\x00\x69\x00\x65\x00\x72\x00\x75\x00\ -\x6e\x00\x67\x00\x20\x00\x28\x00\x25\x00\x73\x00\x29\x08\x00\x00\ -\x00\x00\x06\x00\x00\x00\x1e\x45\x72\x72\x6f\x72\x20\x64\x75\x72\ -\x69\x6e\x67\x20\x72\x65\x67\x69\x73\x74\x72\x61\x74\x69\x6f\x6e\ -\x20\x28\x25\x73\x29\x07\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\ -\x65\x72\x55\x73\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x3c\ -\x00\x50\x00\x61\x00\x73\x00\x73\x00\x77\x00\x6f\x00\x72\x00\x74\ -\x00\x20\x00\x73\x00\x74\x00\x69\x00\x6d\x00\x6d\x00\x74\x00\x20\ -\x00\x6e\x00\x69\x00\x63\x00\x68\x00\x74\x00\x20\x00\xfc\x00\x62\ -\x00\x65\x00\x72\x00\x69\x00\x65\x00\x6e\x00\x2e\x08\x00\x00\x00\ -\x00\x06\x00\x00\x00\x19\x50\x61\x73\x73\x77\x6f\x72\x64\x20\x64\ -\x6f\x65\x73\x20\x6e\x6f\x74\x20\x6d\x61\x74\x63\x68\x2e\x2e\x07\ -\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\x65\x72\x55\x73\x65\x72\ -\x50\x61\x67\x65\x01\x03\x00\x00\x00\x26\x00\x50\x00\x61\x00\x73\ -\x00\x73\x00\x77\x00\x6f\x00\x72\x00\x74\x00\x20\x00\x7a\x00\x75\ -\x00\x20\x00\x73\x00\x69\x00\x6d\x00\x70\x00\x65\x00\x6c\x00\x2e\ -\x08\x00\x00\x00\x00\x06\x00\x00\x00\x15\x50\x61\x73\x73\x77\x6f\ -\x72\x64\x20\x74\x6f\x6f\x20\x6f\x62\x76\x69\x6f\x75\x73\x2e\x07\ -\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\x65\x72\x55\x73\x65\x72\ -\x50\x61\x67\x65\x01\x03\x00\x00\x00\x20\x00\x50\x00\x61\x00\x73\ -\x00\x73\x00\x77\x00\x6f\x00\x72\x00\x74\x00\x20\x00\x7a\x00\x75\ -\x00\x20\x00\x6b\x00\x75\x00\x72\x00\x7a\x08\x00\x00\x00\x00\x06\ -\x00\x00\x00\x13\x50\x61\x73\x73\x77\x6f\x72\x64\x20\x74\x6f\x6f\ -\x20\x73\x68\x6f\x72\x74\x2e\x07\x00\x00\x00\x10\x52\x65\x67\x69\ -\x73\x74\x65\x72\x55\x73\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\ -\x00\x58\x00\x52\x00\x65\x00\x67\x00\x69\x00\x73\x00\x74\x00\x72\ -\x00\x69\x00\x65\x00\x72\x00\x65\x00\x20\x00\x65\x00\x69\x00\x6e\ -\x00\x65\x00\x6e\x00\x20\x00\x6e\x00\x65\x00\x75\x00\x65\x00\x6e\ -\x00\x20\x00\x55\x00\x73\x00\x65\x00\x72\x00\x20\x00\x62\x00\x65\ -\x00\x69\x00\x20\x00\x50\x00\x72\x00\x6f\x00\x76\x00\x69\x00\x64\ -\x00\x65\x00\x72\x00\x20\x00\x25\x00\x73\x08\x00\x00\x00\x00\x06\ -\x00\x00\x00\x25\x52\x65\x67\x69\x73\x74\x65\x72\x20\x61\x20\x6e\ -\x65\x77\x20\x75\x73\x65\x72\x20\x77\x69\x74\x68\x20\x70\x72\x6f\ -\x76\x69\x64\x65\x72\x20\x25\x73\x2e\x07\x00\x00\x00\x10\x52\x65\ -\x67\x69\x73\x74\x65\x72\x55\x73\x65\x72\x50\x61\x67\x65\x01\x03\ -\x00\x00\x00\x34\x00\x52\x00\x65\x00\x67\x00\x69\x00\x73\x00\x74\ -\x00\x72\x00\x69\x00\x65\x00\x72\x00\x75\x00\x6e\x00\x67\x00\x20\ -\x00\x65\x00\x72\x00\x66\x00\x6f\x00\x6c\x00\x67\x00\x72\x00\x65\ -\x00\x69\x00\x63\x00\x68\x00\x21\x08\x00\x00\x00\x00\x06\x00\x00\ -\x00\x17\x52\x65\x67\x69\x73\x74\x72\x61\x74\x69\x6f\x6e\x20\x73\ -\x75\x63\x63\x65\x65\x64\x65\x64\x21\x07\x00\x00\x00\x10\x52\x65\ -\x67\x69\x73\x74\x65\x72\x55\x73\x65\x72\x50\x61\x67\x65\x01\x03\ -\x00\x00\x00\x10\x00\x41\x00\x6e\x00\x6d\x00\x65\x00\x6c\x00\x64\ -\x00\x65\x00\x6e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x07\x53\x69\ -\x67\x6e\x20\x55\x70\x07\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\ -\x65\x72\x55\x73\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x32\ -\x00\x55\x00\x73\x00\x65\x00\x72\x00\x6e\x00\x61\x00\x6d\x00\x65\ -\x00\x20\x00\x6e\x00\x69\x00\x63\x00\x68\x00\x74\x00\x20\x00\x76\ -\x00\x65\x00\x72\x00\x66\x00\xfc\x00\x67\x00\x62\x00\x61\x00\x72\ -\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x17\x55\x73\x65\x72\ -\x6e\x61\x6d\x65\x20\x6e\x6f\x74\x20\x61\x76\x61\x69\x6c\x61\x62\ -\x6c\x65\x2e\x07\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\x65\x72\ -\x55\x73\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x38\x00\x41\ -\x00\x75\x00\x74\x00\x68\x00\x65\x00\x6e\x00\x74\x00\x69\x00\x66\ -\x00\x69\x00\x7a\x00\x69\x00\x65\x00\x72\x00\x75\x00\x6e\x00\x67\ -\x00\x73\x00\x66\x00\x65\x00\x68\x00\x6c\x00\x65\x00\x72\x00\x3a\ -\x00\x20\x00\x25\x00\x73\x08\x00\x00\x00\x00\x06\x00\x00\x00\x18\ -\x41\x75\x74\x68\x65\x6e\x74\x69\x63\x61\x74\x69\x6f\x6e\x20\x65\ -\x72\x72\x6f\x72\x3a\x20\x25\x73\x07\x00\x00\x00\x1a\x52\x65\x67\ -\x69\x73\x74\x65\x72\x55\x73\x65\x72\x56\x61\x6c\x69\x64\x61\x74\ -\x69\x6f\x6e\x50\x61\x67\x65\x01\x03\x00\x00\x00\x26\x00\x48\x00\ -\x6f\x00\x6c\x00\x65\x00\x20\x00\x45\x00\x49\x00\x50\x00\x2d\x00\ -\x5a\x00\x65\x00\x72\x00\x74\x00\x69\x00\x66\x00\x69\x00\x6b\x00\ -\x61\x00\x74\x08\x00\x00\x00\x00\x06\x00\x00\x00\x18\x46\x65\x74\ -\x63\x68\x69\x6e\x67\x20\x65\x69\x70\x20\x63\x65\x72\x74\x69\x66\ -\x69\x63\x61\x74\x65\x07\x00\x00\x00\x1a\x52\x65\x67\x69\x73\x74\ -\x65\x72\x55\x73\x65\x72\x56\x61\x6c\x69\x64\x61\x74\x69\x6f\x6e\ -\x50\x61\x67\x65\x01\x03\x00\x00\x00\x3c\x00\x48\x00\x6f\x00\x6c\ -\x00\x65\x00\x20\x00\x50\x00\x72\x00\x6f\x00\x76\x00\x69\x00\x64\ -\x00\x65\x00\x72\x00\x2d\x00\x4b\x00\x6f\x00\x6e\x00\x66\x00\x69\ -\x00\x67\x00\x75\x00\x72\x00\x61\x00\x74\x00\x69\x00\x6f\x00\x6e\ -\x00\x2e\x00\x2e\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x1b\ -\x46\x65\x74\x63\x68\x69\x6e\x67\x20\x70\x72\x6f\x76\x69\x64\x65\ -\x72\x20\x63\x6f\x6e\x66\x69\x67\x2e\x2e\x2e\x07\x00\x00\x00\x1a\ -\x52\x65\x67\x69\x73\x74\x65\x72\x55\x73\x65\x72\x56\x61\x6c\x69\ -\x64\x61\x74\x69\x6f\x6e\x50\x61\x67\x65\x01\x03\x00\x00\x00\x2c\ -\x00\x5a\x00\x65\x00\x72\x00\x74\x00\x69\x00\x66\x00\x69\x00\x6b\ -\x00\x61\x00\x74\x00\x73\x00\xfc\x00\x62\x00\x65\x00\x72\x00\x70\ -\x00\x72\x00\xfc\x00\x66\x00\x75\x00\x6e\x00\x67\x08\x00\x00\x00\ -\x00\x06\x00\x00\x00\x16\x43\x65\x72\x74\x69\x66\x69\x63\x61\x74\ -\x65\x20\x76\x61\x6c\x69\x64\x61\x74\x69\x6f\x6e\x07\x00\x00\x00\ -\x12\x53\x65\x6c\x65\x63\x74\x50\x72\x6f\x76\x69\x64\x65\x72\x50\ -\x61\x67\x65\x01\x03\x00\x00\x00\x72\x00\x4b\x00\x6f\x00\x6e\x00\ -\x6e\x00\x74\x00\x65\x00\x20\x00\x50\x00\x72\x00\x6f\x00\x76\x00\ -\x69\x00\x64\x00\x65\x00\x72\x00\x2d\x00\x49\x00\x6e\x00\x66\x00\ -\x6f\x00\x20\x00\x6e\x00\x69\x00\x63\x00\x68\x00\x74\x00\x20\x00\ -\x68\x00\x65\x00\x72\x00\x75\x00\x6e\x00\x74\x00\x65\x00\x72\x00\ -\x6c\x00\x61\x00\x64\x00\x65\x00\x6e\x00\x20\x00\x28\x00\x72\x00\ -\x65\x00\x66\x00\x75\x00\x73\x00\x65\x00\x64\x00\x20\x00\x63\x00\ -\x6f\x00\x6e\x00\x6e\x00\x2e\x00\x29\x00\x2e\x08\x00\x00\x00\x00\ -\x06\x00\x00\x00\x31\x43\x6f\x75\x6c\x64\x20\x6e\x6f\x74\x20\x64\ -\x6f\x77\x6e\x6c\x6f\x61\x64\x20\x70\x72\x6f\x76\x69\x64\x65\x72\ -\x20\x69\x6e\x66\x6f\x20\x28\x72\x65\x66\x75\x73\x65\x64\x20\x63\ -\x6f\x6e\x6e\x2e\x29\x2e\x07\x00\x00\x00\x12\x53\x65\x6c\x65\x63\ -\x74\x50\x72\x6f\x76\x69\x64\x65\x72\x50\x61\x67\x65\x01\x03\x00\ -\x00\x00\x5e\x00\x4b\x00\x6f\x00\x6e\x00\x6e\x00\x74\x00\x65\x00\ -\x20\x00\x6b\x00\x65\x00\x69\x00\x6e\x00\x65\x00\x20\x00\x49\x00\ -\x6e\x00\x66\x00\x6f\x00\x72\x00\x6d\x00\x61\x00\x74\x00\x69\x00\ -\x6f\x00\x6e\x00\x20\x00\x76\x00\x6f\x00\x6d\x00\x20\x00\x50\x00\ -\x72\x00\x6f\x00\x76\x00\x69\x00\x64\x00\x65\x00\x72\x00\x20\x00\ -\x62\x00\x65\x00\x6b\x00\x6f\x00\x6d\x00\x6d\x00\x65\x00\x6e\x00\ -\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x21\x43\x6f\x75\x6c\x64\ -\x20\x6e\x6f\x74\x20\x67\x65\x74\x20\x69\x6e\x66\x6f\x20\x66\x72\ -\x6f\x6d\x20\x70\x72\x6f\x76\x69\x64\x65\x72\x2e\x07\x00\x00\x00\ -\x12\x53\x65\x6c\x65\x63\x74\x50\x72\x6f\x76\x69\x64\x65\x72\x50\ -\x61\x67\x65\x01\x03\x00\x00\x00\x20\x00\x47\x00\x69\x00\x62\x00\ -\x20\x00\x50\x00\x72\x00\x6f\x00\x76\x00\x69\x00\x64\x00\x65\x00\ -\x72\x00\x20\x00\x65\x00\x69\x00\x6e\x08\x00\x00\x00\x00\x06\x00\ -\x00\x00\x0e\x45\x6e\x74\x65\x72\x20\x50\x72\x6f\x76\x69\x64\x65\ -\x72\x07\x00\x00\x00\x12\x53\x65\x6c\x65\x63\x74\x50\x72\x6f\x76\ -\x69\x64\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\xa6\x00\x42\ -\x00\x69\x00\x74\x00\x74\x00\x65\x00\x20\x00\x67\x00\x69\x00\x62\ -\x00\x20\x00\x64\x00\x69\x00\x65\x00\x20\x00\x44\x00\x6f\x00\x6d\ -\x00\x61\x00\x69\x00\x6e\x00\x20\x00\x64\x00\x65\x00\x73\x00\x20\ -\x00\x50\x00\x72\x00\x6f\x00\x76\x00\x69\x00\x64\x00\x65\x00\x72\ -\x00\x73\x00\x20\x00\x61\x00\x6e\x00\x2c\x00\x20\x00\x64\x00\x65\ -\x00\x6e\x00\x20\x00\x64\x00\x75\x00\x20\x00\x66\x00\xfc\x00\x72\ -\x00\x20\x00\x64\x00\x65\x00\x69\x00\x6e\x00\x65\x00\x20\x00\x56\ -\x00\x65\x00\x72\x00\x62\x00\x69\x00\x6e\x00\x64\x00\x75\x00\x6e\ -\x00\x67\x00\x20\x00\x6e\x00\x75\x00\x74\x00\x7a\x00\x65\x00\x6e\ -\x00\x20\x00\x6d\x00\xf6\x00\x63\x00\x68\x00\x74\x00\x65\x00\x73\ -\x00\x74\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x4c\x50\x6c\ -\x65\x61\x73\x65\x20\x65\x6e\x74\x65\x72\x20\x74\x68\x65\x20\x64\ -\x6f\x6d\x61\x69\x6e\x20\x6f\x66\x20\x74\x68\x65\x20\x70\x72\x6f\ -\x76\x69\x64\x65\x72\x20\x79\x6f\x75\x20\x77\x61\x6e\x74\x20\x74\ -\x6f\x20\x75\x73\x65\x20\x66\x6f\x72\x20\x79\x6f\x75\x72\x20\x63\ -\x6f\x6e\x6e\x65\x63\x74\x69\x6f\x6e\x2e\x07\x00\x00\x00\x12\x53\ -\x65\x6c\x65\x63\x74\x50\x72\x6f\x76\x69\x64\x65\x72\x50\x61\x67\ -\x65\x01\x03\x00\x00\x00\x60\x00\x53\x00\x65\x00\x72\x00\x76\x00\ -\x65\x00\x72\x00\x2d\x00\x5a\x00\x65\x00\x72\x00\x74\x00\x69\x00\ -\x66\x00\x69\x00\x6b\x00\x61\x00\x74\x00\x20\x00\x6b\x00\x6f\x00\ -\x6e\x00\x6e\x00\x74\x00\x65\x00\x20\x00\x6e\x00\x69\x00\x63\x00\ -\x68\x00\x74\x00\x20\x00\x62\x00\x65\x00\x73\x00\x74\x00\xe4\x00\ -\x74\x00\x69\x00\x67\x00\x74\x00\x20\x00\x77\x00\x65\x00\x72\x00\ -\x64\x00\x65\x00\x6e\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\ -\x29\x53\x65\x72\x76\x65\x72\x20\x63\x65\x72\x74\x69\x66\x69\x63\ -\x61\x74\x65\x20\x63\x6f\x75\x6c\x64\x20\x6e\x6f\x74\x20\x62\x65\ -\x20\x76\x65\x72\x69\x66\x69\x65\x64\x2e\x07\x00\x00\x00\x12\x53\ -\x65\x6c\x65\x63\x74\x50\x72\x6f\x76\x69\x64\x65\x72\x50\x61\x67\ -\x65\x01\x03\x00\x00\x00\x22\x00\x50\x00\x72\x00\xfc\x00\x66\x00\ -\x65\x00\x20\x00\x44\x00\x6f\x00\x6d\x00\x61\x00\x69\x00\x6e\x00\ -\x2d\x00\x4e\x00\x61\x00\x6d\x00\x65\x08\x00\x00\x00\x00\x06\x00\ -\x00\x00\x14\x63\x68\x65\x63\x6b\x69\x6e\x67\x20\x64\x6f\x6d\x61\ -\x69\x6e\x20\x6e\x61\x6d\x65\x07\x00\x00\x00\x12\x53\x65\x6c\x65\ -\x63\x74\x50\x72\x6f\x76\x69\x64\x65\x72\x50\x61\x67\x65\x01\x03\ -\x00\x00\x00\x2c\x00\x50\x00\x72\x00\xfc\x00\x66\x00\x65\x00\x20\ -\x00\x48\x00\x54\x00\x54\x00\x50\x00\x53\x00\x2d\x00\x56\x00\x65\ -\x00\x72\x00\x62\x00\x69\x00\x6e\x00\x64\x00\x75\x00\x6e\x00\x67\ -\x08\x00\x00\x00\x00\x06\x00\x00\x00\x19\x63\x68\x65\x63\x6b\x69\ -\x6e\x67\x20\x68\x74\x74\x70\x73\x20\x63\x6f\x6e\x6e\x65\x63\x74\ -\x69\x6f\x6e\x07\x00\x00\x00\x12\x53\x65\x6c\x65\x63\x74\x50\x72\ -\x6f\x76\x69\x64\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x24\ -\x00\x48\x00\x6f\x00\x6c\x00\x65\x00\x20\x00\x50\x00\x72\x00\x6f\ -\x00\x76\x00\x69\x00\x64\x00\x65\x00\x72\x00\x2d\x00\x49\x00\x6e\ -\x00\x66\x00\x6f\x08\x00\x00\x00\x00\x06\x00\x00\x00\x16\x66\x65\ -\x74\x63\x68\x69\x6e\x67\x20\x70\x72\x6f\x76\x69\x64\x65\x72\x20\ -\x69\x6e\x66\x6f\x07\x00\x00\x00\x12\x53\x65\x6c\x65\x63\x74\x50\ -\x72\x6f\x76\x69\x64\x65\x72\x50\x61\x67\x65\x01\x88\x00\x00\x00\ -\x02\x01\x01\ -\x00\x00\x18\x32\ -\x3c\ -\xb8\x64\x18\xca\xef\x9c\x95\xcd\x21\x1c\xbf\x60\xa1\xbd\xdd\x42\ -\x00\x00\x01\x38\x00\x8f\x9b\xbe\x00\x00\x14\x83\x01\x23\x92\xe5\ -\x00\x00\x10\x3d\x01\x87\x64\x8e\x00\x00\x08\x7a\x01\xa8\xbe\x7e\ -\x00\x00\x0e\x02\x02\x2c\xac\xe9\x00\x00\x0b\x8a\x02\x3a\xce\xbf\ -\x00\x00\x16\x62\x02\x6e\x0f\xe5\x00\x00\x08\xdd\x02\x87\x60\x9e\ -\x00\x00\x06\x6e\x02\xaa\x52\x6e\x00\x00\x07\x6b\x02\xf2\xe0\x59\ -\x00\x00\x0a\x5e\x03\xec\x70\x0e\x00\x00\x10\xb8\x04\xd4\x45\xee\ -\x00\x00\x0d\x24\x05\xb7\x8f\x59\x00\x00\x0c\x27\x06\x3e\x6a\x9e\ -\x00\x00\x05\x9f\x06\x40\xa8\x7e\x00\x00\x0a\xea\x06\xee\xff\x6e\ -\x00\x00\x13\x74\x08\x13\xe8\xae\x00\x00\x0c\xa6\x08\x7a\x64\xee\ -\x00\x00\x11\xc5\x08\xe6\x98\x33\x00\x00\x05\x35\x08\xe6\x98\x33\ -\x00\x00\x0f\xc2\x09\x5c\x35\xe1\x00\x00\x0e\xaa\x09\x74\x75\x4e\ -\x00\x00\x0d\x94\x09\x98\x34\x0e\x00\x00\x12\x89\x09\xd8\x1f\x95\ -\x00\x00\x15\x79\x09\xeb\x5c\xb1\x00\x00\x15\x35\x09\xfc\x2c\x8e\ -\x00\x00\x04\xc7\x09\xfe\x05\x90\x00\x00\x0f\x16\x0a\x74\xb8\x1e\ -\x00\x00\x00\xd6\x0a\xfd\x99\xfe\x00\x00\x00\x51\x0b\xd2\x4b\x3f\ -\x00\x00\x07\x15\x0c\x44\x41\xbe\x00\x00\x00\x00\x0c\xc0\x94\x05\ -\x00\x00\x09\xd6\x0d\x0d\x9d\xc5\x00\x00\x06\x01\x0d\x15\x34\x70\ -\x00\x00\x09\x62\x0e\x36\x15\x54\x00\x00\x07\xed\x0e\x7e\xf5\xee\ -\x00\x00\x0f\x5a\x0e\x91\x50\x3e\x00\x00\x15\xee\x0e\xc0\xbb\x72\ -\x00\x00\x13\x1b\x0f\x27\x0d\x6e\x00\x00\x11\x54\x69\x00\x00\x16\ -\xd9\x03\x00\x00\x00\x22\x00\x50\x00\x72\x00\x69\x00\x6d\x00\x65\ -\x00\x72\x00\x61\x00\x20\x00\x43\x00\x6f\x00\x6e\x00\x65\x00\x78\ -\x00\x69\x00\x6f\x00\x6e\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\ -\x00\x11\x46\x69\x72\x73\x74\x20\x72\x75\x6e\x20\x77\x69\x7a\x61\ -\x72\x64\x2e\x07\x00\x00\x00\x09\x49\x6e\x74\x72\x6f\x50\x61\x67\ -\x65\x01\x03\x00\x00\x00\x4c\x00\x4c\x00\x6f\x00\x67\x00\x75\x00\ -\x65\x00\x61\x00\x72\x00\x6d\x00\x65\x00\x20\x00\x63\x00\x6f\x00\ -\x6e\x00\x20\x00\x75\x00\x6e\x00\x20\x00\x75\x00\x73\x00\x75\x00\ -\x61\x00\x72\x00\x69\x00\x6f\x00\x20\x00\x71\x00\x75\x00\x65\x00\ -\x20\x00\x79\x00\x61\x00\x20\x00\x74\x00\x65\x00\x6e\x00\x67\x00\ -\x6f\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x1b\x4c\x6f\x67\ -\x20\x49\x6e\x20\x77\x69\x74\x68\x20\x6d\x79\x20\x63\x72\x65\x64\ -\x65\x6e\x74\x69\x61\x6c\x73\x2e\x07\x00\x00\x00\x09\x49\x6e\x74\ -\x72\x6f\x50\x61\x67\x65\x01\x03\x00\x00\x02\x76\x00\x56\x00\x61\ -\x00\x6d\x00\x6f\x00\x73\x00\x20\x00\x61\x00\x20\x00\x63\x00\x6f\ -\x00\x6e\x00\x66\x00\x69\x00\x67\x00\x75\x00\x72\x00\x61\x00\x72\ -\x00\x20\x00\x61\x00\x6c\x00\x67\x00\x75\x00\x6e\x00\x61\x00\x73\ -\x00\x20\x00\x63\x00\x6f\x00\x73\x00\x61\x00\x73\x00\x20\x00\x61\ -\x00\x6e\x00\x74\x00\x65\x00\x73\x00\x20\x00\x64\x00\x65\x00\x20\ -\x00\x71\x00\x75\x00\x65\x00\x20\x00\x74\x00\x65\x00\x20\x00\x70\ -\x00\x75\x00\x65\x00\x64\x00\x61\x00\x73\x00\x20\x00\x63\x00\x6f\ -\x00\x6e\x00\x65\x00\x63\x00\x74\x00\x61\x00\x72\x00\x20\x00\x70\ -\x00\x6f\x00\x72\x00\x20\x00\x70\x00\x72\x00\x69\x00\x6d\x00\x65\ -\x00\x72\x00\x61\x00\x20\x00\x76\x00\x65\x00\x7a\x00\x2e\x00\x3c\ -\x00\x62\x00\x72\x00\x3e\x00\x3c\x00\x62\x00\x72\x00\x3e\x00\x53\ -\x00\x69\x00\x20\x00\x6e\x00\x65\x00\x63\x00\x65\x00\x73\x00\x69\ -\x00\x74\x00\x61\x00\x73\x00\x20\x00\x6d\x00\x6f\x00\x64\x00\x69\ -\x00\x66\x00\x69\x00\x63\x00\x61\x00\x72\x00\x20\x00\x65\x00\x73\ -\x00\x74\x00\x61\x00\x73\x00\x20\x00\x6f\x00\x70\x00\x63\x00\x69\ -\x00\x6f\x00\x6e\x00\x65\x00\x73\x00\x20\x00\x64\x00\x65\x00\x20\ -\x00\x6e\x00\x75\x00\x65\x00\x76\x00\x6f\x00\x2c\x00\x20\x00\x70\ -\x00\x75\x00\x65\x00\x64\x00\x65\x00\x73\x00\x20\x00\x65\x00\x6e\ -\x00\x63\x00\x6f\x00\x6e\x00\x74\x00\x72\x00\x61\x00\x72\x00\x20\ -\x00\x65\x00\x73\x00\x74\x00\x65\x00\x20\x00\x61\x00\x73\x00\x69\ -\x00\x73\x00\x74\x00\x65\x00\x6e\x00\x74\x00\x65\x00\x20\x00\x65\ -\x00\x6e\x00\x20\x00\x65\x00\x6c\x00\x20\x00\x6d\x00\x65\x00\x6e\ -\x00\x75\x00\x20\x00\x64\x00\x65\x00\x20\x00\x27\x00\x3c\x00\x69\ -\x00\x3e\x00\x4f\x00\x70\x00\x63\x00\x69\x00\x6f\x00\x6e\x00\x65\ -\x00\x73\x00\x3c\x00\x2f\x00\x69\x00\x3e\x00\x27\x00\x20\x00\x65\ -\x00\x6e\x00\x20\x00\x6c\x00\x61\x00\x20\x00\x76\x00\x65\x00\x6e\ -\x00\x74\x00\x61\x00\x6e\x00\x61\x00\x20\x00\x70\x00\x72\x00\x69\ -\x00\x6e\x00\x63\x00\x69\x00\x70\x00\x61\x00\x6c\x00\x2e\x00\x3c\ -\x00\x62\x00\x72\x00\x3e\x00\x3c\x00\x62\x00\x72\x00\x3e\x00\x51\ -\x00\x75\x00\x69\x00\x65\x00\x72\x00\x65\x00\x73\x00\x20\x00\x3c\ -\x00\x62\x00\x3e\x00\x72\x00\x65\x00\x67\x00\x69\x00\x73\x00\x74\ -\x00\x72\x00\x61\x00\x72\x00\x3c\x00\x2f\x00\x62\x00\x3e\x00\x20\ -\x00\x75\x00\x6e\x00\x61\x00\x20\x00\x6e\x00\x75\x00\x65\x00\x76\ -\x00\x61\x00\x20\x00\x63\x00\x75\x00\x65\x00\x6e\x00\x74\x00\x61\ -\x00\x2c\x00\x20\x00\x6f\x00\x20\x00\x3c\x00\x62\x00\x3e\x00\x6c\ -\x00\x6f\x00\x67\x00\x75\x00\x65\x00\x61\x00\x72\x00\x74\x00\x65\ -\x00\x3c\x00\x2f\x00\x62\x00\x3e\x00\x20\x00\x63\x00\x6f\x00\x6e\ -\x00\x20\x00\x74\x00\x75\x00\x20\x00\x75\x00\x73\x00\x75\x00\x61\ -\x00\x72\x00\x69\x00\x6f\x00\x3f\x00\x3c\x00\x62\x00\x72\x00\x3e\ -\x00\x20\x08\x00\x00\x00\x00\x06\x00\x00\x01\x5d\x4e\x6f\x77\x20\ -\x77\x65\x20\x77\x69\x6c\x6c\x20\x67\x75\x69\x64\x65\x20\x79\x6f\ -\x75\x20\x74\x68\x72\x6f\x75\x67\x68\x20\x73\x6f\x6d\x65\x20\x63\ -\x6f\x6e\x66\x69\x67\x75\x72\x61\x74\x69\x6f\x6e\x20\x74\x68\x61\ -\x74\x20\x69\x73\x20\x6e\x65\x65\x64\x65\x64\x20\x62\x65\x66\x6f\ -\x72\x65\x20\x79\x6f\x75\x20\x63\x61\x6e\x20\x63\x6f\x6e\x6e\x65\ -\x63\x74\x20\x66\x6f\x72\x20\x74\x68\x65\x20\x66\x69\x72\x73\x74\ -\x20\x74\x69\x6d\x65\x2e\x3c\x62\x72\x3e\x3c\x62\x72\x3e\x49\x66\ -\x20\x79\x6f\x75\x20\x65\x76\x65\x72\x20\x6e\x65\x65\x64\x20\x74\ -\x6f\x20\x6d\x6f\x64\x69\x66\x79\x20\x74\x68\x65\x73\x65\x20\x6f\ -\x70\x74\x69\x6f\x6e\x73\x20\x61\x67\x61\x69\x6e\x2c\x20\x79\x6f\ -\x75\x20\x63\x61\x6e\x20\x66\x69\x6e\x64\x20\x74\x68\x65\x20\x77\ -\x69\x7a\x61\x72\x64\x20\x69\x6e\x20\x74\x68\x65\x20\x27\x3c\x69\ -\x3e\x53\x65\x74\x74\x69\x6e\x67\x73\x3c\x2f\x69\x3e\x27\x20\x6d\ -\x65\x6e\x75\x20\x66\x72\x6f\x6d\x20\x74\x68\x65\x20\x6d\x61\x69\ -\x6e\x20\x77\x69\x6e\x64\x6f\x77\x2e\x3c\x62\x72\x3e\x3c\x62\x72\ -\x3e\x44\x6f\x20\x79\x6f\x75\x20\x77\x61\x6e\x74\x20\x74\x6f\x20\ -\x3c\x62\x3e\x73\x69\x67\x6e\x20\x75\x70\x3c\x2f\x62\x3e\x20\x66\ -\x6f\x72\x20\x61\x20\x6e\x65\x77\x20\x61\x63\x63\x6f\x75\x6e\x74\ -\x2c\x20\x6f\x72\x20\x3c\x62\x3e\x6c\x6f\x67\x20\x69\x6e\x3c\x2f\ -\x62\x3e\x20\x77\x69\x74\x68\x20\x61\x6e\x20\x61\x6c\x72\x65\x61\ -\x64\x79\x20\x65\x78\x69\x73\x74\x69\x6e\x67\x20\x75\x73\x65\x72\ -\x6e\x61\x6d\x65\x3f\x3c\x62\x72\x3e\x07\x00\x00\x00\x09\x49\x6e\ -\x74\x72\x6f\x50\x61\x67\x65\x01\x03\x00\x00\x00\x36\x00\x52\x00\ -\x65\x00\x67\x00\x69\x00\x73\x00\x74\x00\x72\x00\x61\x00\x72\x00\ -\x20\x00\x75\x00\x6e\x00\x61\x00\x20\x00\x63\x00\x75\x00\x65\x00\ -\x6e\x00\x74\x00\x61\x00\x20\x00\x6e\x00\x75\x00\x65\x00\x76\x00\ -\x61\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x1a\x53\x69\x67\ -\x6e\x20\x75\x70\x20\x66\x6f\x72\x20\x61\x20\x6e\x65\x77\x20\x61\ -\x63\x63\x6f\x75\x6e\x74\x2e\x07\x00\x00\x00\x09\x49\x6e\x74\x72\ -\x6f\x50\x61\x67\x65\x01\x03\x00\x00\x00\x34\x00\x45\x00\x72\x00\ -\x72\x00\x6f\x00\x72\x00\x20\x00\x64\x00\x65\x00\x20\x00\x61\x00\ -\x75\x00\x74\x00\x65\x00\x6e\x00\x74\x00\x69\x00\x63\x00\x61\x00\ -\x63\x00\x69\x00\x6f\x00\x6e\x00\x3a\x00\x20\x00\x25\x00\x73\x08\ -\x00\x00\x00\x00\x06\x00\x00\x00\x18\x41\x75\x74\x68\x65\x6e\x74\ -\x69\x63\x61\x74\x69\x6f\x6e\x20\x65\x72\x72\x6f\x72\x3a\x20\x25\ -\x73\x07\x00\x00\x00\x09\x4c\x6f\x67\x49\x6e\x50\x61\x67\x65\x01\ -\x03\x00\x00\x00\x2e\x00\x43\x00\x72\x00\x65\x00\x64\x00\x65\x00\ -\x6e\x00\x63\x00\x69\x00\x61\x00\x6c\x00\x65\x00\x73\x00\x20\x00\ -\x76\x00\x61\x00\x6c\x00\x69\x00\x64\x00\x61\x00\x64\x00\x61\x00\ -\x73\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x16\x43\x72\x65\ -\x64\x65\x6e\x74\x69\x61\x6c\x73\x20\x76\x61\x6c\x69\x64\x61\x74\ -\x65\x64\x2e\x07\x00\x00\x00\x09\x4c\x6f\x67\x49\x6e\x50\x61\x67\ -\x65\x01\x03\x00\x00\x00\x3a\x00\x52\x00\x65\x00\x73\x00\x6f\x00\ -\x6c\x00\x76\x00\x69\x00\x65\x00\x6e\x00\x64\x00\x6f\x00\x20\x00\ -\x6e\x00\x6f\x00\x6d\x00\x62\x00\x72\x00\x65\x00\x20\x00\x64\x00\ -\x65\x00\x20\x00\x64\x00\x6f\x00\x6d\x00\x69\x00\x6e\x00\x69\x00\ -\x6f\x08\x00\x00\x00\x00\x06\x00\x00\x00\x15\x52\x65\x73\x6f\x6c\ -\x76\x69\x6e\x67\x20\x64\x6f\x6d\x61\x69\x6e\x20\x6e\x61\x6d\x65\ -\x07\x00\x00\x00\x09\x4c\x6f\x67\x49\x6e\x50\x61\x67\x65\x01\x03\ -\x00\x00\x00\x5a\x00\x45\x00\x6c\x00\x20\x00\x75\x00\x73\x00\x75\ -\x00\x61\x00\x72\x00\x69\x00\x6f\x00\x20\x00\x74\x00\x69\x00\x65\ -\x00\x6e\x00\x65\x00\x20\x00\x71\x00\x75\x00\x65\x00\x20\x00\x73\ -\x00\x65\x00\x72\x00\x20\x00\x75\x00\x73\x00\x75\x00\x61\x00\x72\ -\x00\x69\x00\x6f\x00\x40\x00\x74\x00\x75\x00\x2e\x00\x70\x00\x72\ -\x00\x6f\x00\x76\x00\x65\x00\x65\x00\x64\x00\x6f\x00\x72\x08\x00\ -\x00\x00\x00\x06\x00\x00\x00\x2f\x55\x73\x65\x72\x6e\x61\x6d\x65\ -\x20\x6d\x75\x73\x74\x20\x62\x65\x20\x69\x6e\x20\x74\x68\x65\x20\ -\x75\x73\x65\x72\x6e\x61\x6d\x65\x40\x70\x72\x6f\x76\x69\x64\x65\ -\x72\x20\x66\x6f\x72\x6d\x2e\x07\x00\x00\x00\x09\x4c\x6f\x67\x49\ -\x6e\x50\x61\x67\x65\x01\x03\x00\x00\x00\x24\x00\x49\x00\x6e\x00\ -\x66\x00\x6f\x00\x20\x00\x64\x00\x65\x00\x6c\x00\x20\x00\x50\x00\ -\x72\x00\x6f\x00\x76\x00\x65\x00\x65\x00\x64\x00\x6f\x00\x72\x08\ -\x00\x00\x00\x00\x06\x00\x00\x00\x0d\x50\x72\x6f\x76\x69\x64\x65\ -\x72\x20\x49\x6e\x66\x6f\x07\x00\x00\x00\x10\x50\x72\x6f\x76\x69\ -\x64\x65\x72\x49\x6e\x66\x6f\x50\x61\x67\x65\x01\x03\x00\x00\x00\ -\x42\x00\x45\x00\x73\x00\x74\x00\x6f\x00\x20\x00\x65\x00\x73\x00\ -\x20\x00\x6c\x00\x6f\x00\x20\x00\x71\x00\x75\x00\x65\x00\x20\x00\ -\x64\x00\x69\x00\x63\x00\x65\x00\x20\x00\x65\x00\x6c\x00\x20\x00\ -\x70\x00\x72\x00\x6f\x00\x76\x00\x65\x00\x65\x00\x64\x00\x6f\x00\ -\x72\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x1b\x54\x68\x69\ -\x73\x20\x69\x73\x20\x77\x68\x61\x74\x20\x70\x72\x6f\x76\x69\x64\ -\x65\x72\x20\x73\x61\x79\x73\x2e\x07\x00\x00\x00\x10\x50\x72\x6f\ -\x76\x69\x64\x65\x72\x49\x6e\x66\x6f\x50\x61\x67\x65\x01\x03\x00\ -\x00\x00\x46\x00\x43\x00\x6f\x00\x6d\x00\x70\x00\x72\x00\x6f\x00\ -\x62\x00\x61\x00\x6e\x00\x64\x00\x6f\x00\x20\x00\x65\x00\x6c\x00\ -\x20\x00\x66\x00\x69\x00\x6e\x00\x67\x00\x65\x00\x72\x00\x70\x00\ -\x72\x00\x69\x00\x6e\x00\x74\x00\x20\x00\x64\x00\x65\x00\x20\x00\ -\x6c\x00\x61\x00\x20\x00\x43\x00\x41\x08\x00\x00\x00\x00\x06\x00\ -\x00\x00\x17\x43\x68\x65\x63\x6b\x69\x6e\x67\x20\x43\x41\x20\x66\ -\x69\x6e\x67\x65\x72\x70\x72\x69\x6e\x74\x07\x00\x00\x00\x1b\x50\ -\x72\x6f\x76\x69\x64\x65\x72\x53\x65\x74\x75\x70\x56\x61\x6c\x69\ -\x64\x61\x74\x69\x6f\x6e\x50\x61\x67\x65\x01\x03\x00\x00\x00\x22\ -\x00\x41\x00\x75\x00\x74\x00\x6f\x00\x63\x00\x6f\x00\x6e\x00\x66\ -\x00\x69\x00\x67\x00\x75\x00\x72\x00\x61\x00\x6e\x00\x64\x00\x6f\ -\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x11\x44\x6f\x69\x6e\ -\x67\x20\x61\x75\x74\x6f\x63\x6f\x6e\x66\x69\x67\x2e\x07\x00\x00\ -\x00\x1b\x50\x72\x6f\x76\x69\x64\x65\x72\x53\x65\x74\x75\x70\x56\ -\x61\x6c\x69\x64\x61\x74\x69\x6f\x6e\x50\x61\x67\x65\x01\x03\x00\ -\x00\x00\x3e\x00\x4f\x00\x62\x00\x74\x00\x65\x00\x6e\x00\x69\x00\ -\x65\x00\x6e\x00\x64\x00\x6f\x00\x20\x00\x63\x00\x65\x00\x72\x00\ -\x74\x00\x69\x00\x66\x00\x69\x00\x63\x00\x61\x00\x64\x00\x6f\x00\ -\x20\x00\x64\x00\x65\x00\x20\x00\x6c\x00\x61\x00\x20\x00\x43\x00\ -\x41\x08\x00\x00\x00\x00\x06\x00\x00\x00\x17\x46\x65\x74\x63\x68\ -\x69\x6e\x67\x20\x43\x41\x20\x63\x65\x72\x74\x69\x66\x69\x63\x61\ -\x74\x65\x07\x00\x00\x00\x1b\x50\x72\x6f\x76\x69\x64\x65\x72\x53\ -\x65\x74\x75\x70\x56\x61\x6c\x69\x64\x61\x74\x69\x6f\x6e\x50\x61\ -\x67\x65\x01\x03\x00\x00\x00\x36\x00\x43\x00\x6f\x00\x6e\x00\x66\ -\x00\x69\x00\x67\x00\x75\x00\x72\x00\x61\x00\x63\x00\x69\x00\x6f\ -\x00\x6e\x00\x20\x00\x64\x00\x65\x00\x6c\x00\x20\x00\x50\x00\x72\ -\x00\x6f\x00\x76\x00\x65\x00\x65\x00\x64\x00\x6f\x00\x72\x08\x00\ -\x00\x00\x00\x06\x00\x00\x00\x0e\x50\x72\x6f\x76\x69\x64\x65\x72\ -\x20\x73\x65\x74\x75\x70\x07\x00\x00\x00\x1b\x50\x72\x6f\x76\x69\ -\x64\x65\x72\x53\x65\x74\x75\x70\x56\x61\x6c\x69\x64\x61\x74\x69\ -\x6f\x6e\x50\x61\x67\x65\x01\x03\x00\x00\x00\x3e\x00\x56\x00\x61\ -\x00\x6c\x00\x69\x00\x64\x00\x61\x00\x6e\x00\x64\x00\x6f\x00\x20\ -\x00\x63\x00\x65\x00\x72\x00\x74\x00\x69\x00\x66\x00\x69\x00\x63\ -\x00\x61\x00\x64\x00\x6f\x00\x20\x00\x64\x00\x65\x00\x20\x00\x6c\ -\x00\x61\x00\x20\x00\x61\x00\x70\x00\x69\x08\x00\x00\x00\x00\x06\ -\x00\x00\x00\x1a\x56\x61\x6c\x69\x64\x61\x74\x69\x6e\x67\x20\x61\ -\x70\x69\x20\x63\x65\x72\x74\x69\x66\x69\x63\x61\x74\x65\x07\x00\ -\x00\x00\x1b\x50\x72\x6f\x76\x69\x64\x65\x72\x53\x65\x74\x75\x70\ -\x56\x61\x6c\x69\x64\x61\x74\x69\x6f\x6e\x50\x61\x67\x65\x01\x03\ -\x00\x00\x00\x46\x00\x4e\x00\x6f\x00\x20\x00\x73\x00\x65\x00\x20\ -\x00\x70\x00\x75\x00\x64\x00\x6f\x00\x20\x00\x72\x00\x65\x00\x67\ -\x00\x69\x00\x73\x00\x74\x00\x72\x00\x61\x00\x72\x00\x20\x00\x28\ -\x00\x62\x00\x61\x00\x64\x00\x20\x00\x72\x00\x65\x00\x73\x00\x70\ -\x00\x6f\x00\x6e\x00\x73\x00\x65\x00\x29\x08\x00\x00\x00\x00\x06\ -\x00\x00\x00\x21\x43\x6f\x75\x6c\x64\x20\x6e\x6f\x74\x20\x72\x65\ -\x67\x69\x73\x74\x65\x72\x20\x28\x62\x61\x64\x20\x72\x65\x73\x70\ -\x6f\x6e\x73\x65\x29\x07\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\ -\x65\x72\x55\x73\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x54\ -\x00\x45\x00\x72\x00\x72\x00\x6f\x00\x72\x00\x20\x00\x63\x00\x6f\ -\x00\x6e\x00\x65\x00\x63\x00\x74\x00\x61\x00\x6e\x00\x64\x00\x6f\ -\x00\x73\x00\x65\x00\x20\x00\x61\x00\x6c\x00\x20\x00\x70\x00\x72\ -\x00\x6f\x00\x76\x00\x65\x00\x65\x00\x64\x00\x6f\x00\x72\x00\x20\ -\x00\x28\x00\x63\x00\x6f\x00\x6e\x00\x6e\x00\x65\x00\x72\x00\x72\ -\x00\x29\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x27\x45\x72\ -\x72\x6f\x72\x20\x43\x6f\x6e\x6e\x65\x63\x74\x69\x6e\x67\x20\x74\ -\x6f\x20\x70\x72\x6f\x76\x69\x64\x65\x72\x20\x28\x63\x6f\x6e\x6e\ -\x65\x72\x72\x29\x2e\x07\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\ -\x65\x72\x55\x73\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x52\ -\x00\x45\x00\x72\x00\x72\x00\x6f\x00\x72\x00\x20\x00\x63\x00\x6f\ -\x00\x6e\x00\x65\x00\x63\x00\x74\x00\x61\x00\x6e\x00\x64\x00\x6f\ -\x00\x73\x00\x65\x00\x20\x00\x61\x00\x6c\x00\x20\x00\x70\x00\x72\ -\x00\x6f\x00\x76\x00\x65\x00\x65\x00\x64\x00\x6f\x00\x72\x00\x20\ -\x00\x28\x00\x74\x00\x69\x00\x6d\x00\x65\x00\x6f\x00\x75\x00\x74\ -\x00\x29\x08\x00\x00\x00\x00\x06\x00\x00\x00\x26\x45\x72\x72\x6f\ -\x72\x20\x63\x6f\x6e\x6e\x65\x63\x74\x69\x6e\x67\x20\x74\x6f\x20\ -\x70\x72\x6f\x76\x69\x64\x65\x72\x20\x28\x74\x69\x6d\x65\x6f\x75\ -\x74\x29\x07\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\x65\x72\x55\ -\x73\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x3c\x00\x45\x00\ -\x72\x00\x72\x00\x6f\x00\x72\x00\x20\x00\x64\x00\x75\x00\x72\x00\ -\x61\x00\x6e\x00\x74\x00\x65\x00\x20\x00\x65\x00\x6c\x00\x20\x00\ -\x72\x00\x65\x00\x67\x00\x69\x00\x73\x00\x74\x00\x72\x00\x6f\x00\ -\x20\x00\x28\x00\x25\x00\x73\x00\x29\x08\x00\x00\x00\x00\x06\x00\ -\x00\x00\x1e\x45\x72\x72\x6f\x72\x20\x64\x75\x72\x69\x6e\x67\x20\ -\x72\x65\x67\x69\x73\x74\x72\x61\x74\x69\x6f\x6e\x20\x28\x25\x73\ -\x29\x07\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\x65\x72\x55\x73\ -\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x40\x00\x4c\x00\x61\ -\x00\x73\x00\x20\x00\x63\x00\x6f\x00\x6e\x00\x74\x00\x72\x00\x61\ -\x00\x73\x00\x65\x00\x6e\x00\x61\x00\x73\x00\x20\x00\x6e\x00\x6f\ -\x00\x20\x00\x73\x00\x6f\x00\x6e\x00\x20\x00\x69\x00\x67\x00\x75\ -\x00\x61\x00\x6c\x00\x65\x00\x73\x00\x2e\x00\x2e\x08\x00\x00\x00\ -\x00\x06\x00\x00\x00\x19\x50\x61\x73\x73\x77\x6f\x72\x64\x20\x64\ -\x6f\x65\x73\x20\x6e\x6f\x74\x20\x6d\x61\x74\x63\x68\x2e\x2e\x07\ -\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\x65\x72\x55\x73\x65\x72\ -\x50\x61\x67\x65\x01\x03\x00\x00\x00\x36\x00\x43\x00\x6f\x00\x6e\ -\x00\x74\x00\x72\x00\x61\x00\x73\x00\x65\x00\x6e\x00\x61\x00\x20\ -\x00\x64\x00\x65\x00\x6d\x00\x61\x00\x73\x00\x69\x00\x61\x00\x64\ -\x00\x6f\x00\x20\x00\x6f\x00\x62\x00\x76\x00\x69\x00\x61\x00\x2e\ -\x08\x00\x00\x00\x00\x06\x00\x00\x00\x15\x50\x61\x73\x73\x77\x6f\ -\x72\x64\x20\x74\x6f\x6f\x20\x6f\x62\x76\x69\x6f\x75\x73\x2e\x07\ -\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\x65\x72\x55\x73\x65\x72\ -\x50\x61\x67\x65\x01\x03\x00\x00\x00\x36\x00\x43\x00\x6f\x00\x6e\ -\x00\x74\x00\x72\x00\x61\x00\x73\x00\x65\x00\x6e\x00\x61\x00\x20\ -\x00\x64\x00\x65\x00\x6d\x00\x61\x00\x73\x00\x69\x00\x61\x00\x64\ -\x00\x6f\x00\x20\x00\x63\x00\x6f\x00\x72\x00\x74\x00\x61\x00\x2e\ -\x08\x00\x00\x00\x00\x06\x00\x00\x00\x13\x50\x61\x73\x73\x77\x6f\ -\x72\x64\x20\x74\x6f\x6f\x20\x73\x68\x6f\x72\x74\x2e\x07\x00\x00\ -\x00\x10\x52\x65\x67\x69\x73\x74\x65\x72\x55\x73\x65\x72\x50\x61\ -\x67\x65\x01\x03\x00\x00\x00\x5e\x00\x52\x00\x65\x00\x67\x00\x69\ -\x00\x73\x00\x74\x00\x72\x00\x61\x00\x72\x00\x20\x00\x75\x00\x6e\ -\x00\x20\x00\x6e\x00\x75\x00\x65\x00\x76\x00\x6f\x00\x20\x00\x75\ -\x00\x73\x00\x75\x00\x61\x00\x72\x00\x69\x00\x6f\x00\x20\x00\x63\ -\x00\x6f\x00\x6e\x00\x20\x00\x65\x00\x6c\x00\x20\x00\x70\x00\x72\ -\x00\x6f\x00\x76\x00\x65\x00\x65\x00\x64\x00\x6f\x00\x72\x00\x20\ -\x00\x25\x00\x73\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x25\ -\x52\x65\x67\x69\x73\x74\x65\x72\x20\x61\x20\x6e\x65\x77\x20\x75\ -\x73\x65\x72\x20\x77\x69\x74\x68\x20\x70\x72\x6f\x76\x69\x64\x65\ -\x72\x20\x25\x73\x2e\x07\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\ -\x65\x72\x55\x73\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x30\ -\x00\x43\x00\x75\x00\x65\x00\x6e\x00\x74\x00\x61\x00\x20\x00\x63\ -\x00\x72\x00\x65\x00\x61\x00\x64\x00\x61\x00\x20\x00\x63\x00\x6f\ -\x00\x6e\x00\x20\x00\x65\x00\x78\x00\x69\x00\x74\x00\x6f\x00\x21\ -\x08\x00\x00\x00\x00\x06\x00\x00\x00\x17\x52\x65\x67\x69\x73\x74\ -\x72\x61\x74\x69\x6f\x6e\x20\x73\x75\x63\x63\x65\x65\x64\x65\x64\ -\x21\x07\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\x65\x72\x55\x73\ -\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x18\x00\x4e\x00\x75\ -\x00\x65\x00\x76\x00\x61\x00\x20\x00\x43\x00\x75\x00\x65\x00\x6e\ -\x00\x74\x00\x61\x08\x00\x00\x00\x00\x06\x00\x00\x00\x07\x53\x69\ -\x67\x6e\x20\x55\x70\x07\x00\x00\x00\x10\x52\x65\x67\x69\x73\x74\ -\x65\x72\x55\x73\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x2c\ -\x00\x55\x00\x73\x00\x75\x00\x61\x00\x72\x00\x69\x00\x6f\x00\x20\ -\x00\x6e\x00\x6f\x00\x20\x00\x64\x00\x69\x00\x73\x00\x70\x00\x6f\ -\x00\x6e\x00\x69\x00\x62\x00\x6c\x00\x65\x00\x2e\x08\x00\x00\x00\ -\x00\x06\x00\x00\x00\x17\x55\x73\x65\x72\x6e\x61\x6d\x65\x20\x6e\ -\x6f\x74\x20\x61\x76\x61\x69\x6c\x61\x62\x6c\x65\x2e\x07\x00\x00\ -\x00\x10\x52\x65\x67\x69\x73\x74\x65\x72\x55\x73\x65\x72\x50\x61\ -\x67\x65\x01\x03\x00\x00\x00\x34\x00\x45\x00\x72\x00\x72\x00\x6f\ -\x00\x72\x00\x20\x00\x64\x00\x65\x00\x20\x00\x61\x00\x75\x00\x74\ -\x00\x65\x00\x6e\x00\x74\x00\x69\x00\x63\x00\x61\x00\x63\x00\x69\ -\x00\x6f\x00\x6e\x00\x3a\x00\x20\x00\x25\x00\x73\x08\x00\x00\x00\ -\x00\x06\x00\x00\x00\x18\x41\x75\x74\x68\x65\x6e\x74\x69\x63\x61\ -\x74\x69\x6f\x6e\x20\x65\x72\x72\x6f\x72\x3a\x20\x25\x73\x07\x00\ -\x00\x00\x1a\x52\x65\x67\x69\x73\x74\x65\x72\x55\x73\x65\x72\x56\ -\x61\x6c\x69\x64\x61\x74\x69\x6f\x6e\x50\x61\x67\x65\x01\x03\x00\ -\x00\x00\x34\x00\x4f\x00\x62\x00\x74\x00\x65\x00\x6e\x00\x69\x00\ -\x65\x00\x6e\x00\x64\x00\x6f\x00\x20\x00\x63\x00\x65\x00\x72\x00\ -\x74\x00\x69\x00\x66\x00\x69\x00\x63\x00\x61\x00\x64\x00\x6f\x00\ -\x20\x00\x65\x00\x69\x00\x70\x08\x00\x00\x00\x00\x06\x00\x00\x00\ -\x18\x46\x65\x74\x63\x68\x69\x6e\x67\x20\x65\x69\x70\x20\x63\x65\ -\x72\x74\x69\x66\x69\x63\x61\x74\x65\x07\x00\x00\x00\x1a\x52\x65\ -\x67\x69\x73\x74\x65\x72\x55\x73\x65\x72\x56\x61\x6c\x69\x64\x61\ -\x74\x69\x6f\x6e\x50\x61\x67\x65\x01\x03\x00\x00\x00\x52\x00\x4f\ -\x00\x62\x00\x74\x00\x65\x00\x6e\x00\x69\x00\x65\x00\x6e\x00\x64\ -\x00\x6f\x00\x20\x00\x63\x00\x6f\x00\x6e\x00\x66\x00\x69\x00\x67\ -\x00\x75\x00\x72\x00\x61\x00\x63\x00\x69\x00\x6f\x00\x6e\x00\x20\ -\x00\x64\x00\x65\x00\x6c\x00\x20\x00\x70\x00\x72\x00\x6f\x00\x76\ -\x00\x65\x00\x65\x00\x64\x00\x6f\x00\x72\x00\x2e\x00\x2e\x00\x2e\ -\x08\x00\x00\x00\x00\x06\x00\x00\x00\x1b\x46\x65\x74\x63\x68\x69\ -\x6e\x67\x20\x70\x72\x6f\x76\x69\x64\x65\x72\x20\x63\x6f\x6e\x66\ -\x69\x67\x2e\x2e\x2e\x07\x00\x00\x00\x1a\x52\x65\x67\x69\x73\x74\ -\x65\x72\x55\x73\x65\x72\x56\x61\x6c\x69\x64\x61\x74\x69\x6f\x6e\ -\x50\x61\x67\x65\x01\x03\x00\x00\x00\x34\x00\x56\x00\x61\x00\x6c\ -\x00\x69\x00\x64\x00\x61\x00\x63\x00\x69\x00\x6f\x00\x6e\x00\x20\ -\x00\x64\x00\x65\x00\x6c\x00\x20\x00\x63\x00\x65\x00\x72\x00\x74\ -\x00\x69\x00\x66\x00\x69\x00\x63\x00\x61\x00\x64\x00\x6f\x08\x00\ -\x00\x00\x00\x06\x00\x00\x00\x16\x43\x65\x72\x74\x69\x66\x69\x63\ -\x61\x74\x65\x20\x76\x61\x6c\x69\x64\x61\x74\x69\x6f\x6e\x07\x00\ -\x00\x00\x12\x53\x65\x6c\x65\x63\x74\x50\x72\x6f\x76\x69\x64\x65\ -\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x6c\x00\x6e\x00\x6f\x00\ -\x20\x00\x73\x00\x65\x00\x20\x00\x70\x00\x75\x00\x64\x00\x6f\x00\ -\x20\x00\x6f\x00\x62\x00\x74\x00\x65\x00\x6e\x00\x65\x00\x72\x00\ -\x20\x00\x69\x00\x6e\x00\x66\x00\x6f\x00\x20\x00\x64\x00\x65\x00\ -\x6c\x00\x20\x00\x70\x00\x72\x00\x6f\x00\x76\x00\x65\x00\x65\x00\ -\x64\x00\x6f\x00\x72\x00\x20\x00\x28\x00\x72\x00\x65\x00\x66\x00\ -\x75\x00\x73\x00\x65\x00\x64\x00\x20\x00\x63\x00\x6f\x00\x6e\x00\ -\x6e\x00\x2e\x00\x29\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\ -\x31\x43\x6f\x75\x6c\x64\x20\x6e\x6f\x74\x20\x64\x6f\x77\x6e\x6c\ -\x6f\x61\x64\x20\x70\x72\x6f\x76\x69\x64\x65\x72\x20\x69\x6e\x66\ -\x6f\x20\x28\x72\x65\x66\x75\x73\x65\x64\x20\x63\x6f\x6e\x6e\x2e\ -\x29\x2e\x07\x00\x00\x00\x12\x53\x65\x6c\x65\x63\x74\x50\x72\x6f\ -\x76\x69\x64\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x4a\x00\ -\x6e\x00\x6f\x00\x20\x00\x73\x00\x65\x00\x20\x00\x70\x00\x75\x00\ -\x64\x00\x6f\x00\x20\x00\x6f\x00\x62\x00\x74\x00\x65\x00\x6e\x00\ -\x65\x00\x72\x00\x20\x00\x69\x00\x6e\x00\x66\x00\x6f\x00\x20\x00\ -\x64\x00\x65\x00\x6c\x00\x20\x00\x70\x00\x72\x00\x6f\x00\x76\x00\ -\x65\x00\x65\x00\x64\x00\x6f\x00\x72\x08\x00\x00\x00\x00\x06\x00\ -\x00\x00\x21\x43\x6f\x75\x6c\x64\x20\x6e\x6f\x74\x20\x67\x65\x74\ -\x20\x69\x6e\x66\x6f\x20\x66\x72\x6f\x6d\x20\x70\x72\x6f\x76\x69\ -\x64\x65\x72\x2e\x07\x00\x00\x00\x12\x53\x65\x6c\x65\x63\x74\x50\ -\x72\x6f\x76\x69\x64\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\ -\x24\x00\x45\x00\x6e\x00\x74\x00\x72\x00\x61\x00\x20\x00\x74\x00\ -\x75\x00\x20\x00\x50\x00\x72\x00\x6f\x00\x76\x00\x65\x00\x65\x00\ -\x64\x00\x6f\x00\x72\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0e\x45\ -\x6e\x74\x65\x72\x20\x50\x72\x6f\x76\x69\x64\x65\x72\x07\x00\x00\ -\x00\x12\x53\x65\x6c\x65\x63\x74\x50\x72\x6f\x76\x69\x64\x65\x72\ -\x50\x61\x67\x65\x01\x03\x00\x00\x00\x9c\x00\x50\x00\x6f\x00\x72\ -\x00\x20\x00\x66\x00\x61\x00\x76\x00\x6f\x00\x72\x00\x2c\x00\x20\ -\x00\x72\x00\x65\x00\x6c\x00\x6c\x00\x65\x00\x6e\x00\x61\x00\x20\ -\x00\x65\x00\x6c\x00\x20\x00\x64\x00\x6f\x00\x6d\x00\x69\x00\x6e\ -\x00\x69\x00\x6f\x00\x20\x00\x64\x00\x65\x00\x6c\x00\x20\x00\x70\ -\x00\x72\x00\x6f\x00\x76\x00\x65\x00\x65\x00\x64\x00\x6f\x00\x72\ -\x00\x20\x00\x71\x00\x75\x00\x65\x00\x20\x00\x71\x00\x75\x00\x69\ -\x00\x65\x00\x72\x00\x61\x00\x73\x00\x20\x00\x75\x00\x73\x00\x61\ -\x00\x72\x00\x20\x00\x70\x00\x61\x00\x72\x00\x61\x00\x20\x00\x74\ -\x00\x75\x00\x20\x00\x63\x00\x6f\x00\x6e\x00\x65\x00\x78\x00\x69\ -\x00\x6f\x00\x6e\x00\x2e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x4c\ -\x50\x6c\x65\x61\x73\x65\x20\x65\x6e\x74\x65\x72\x20\x74\x68\x65\ -\x20\x64\x6f\x6d\x61\x69\x6e\x20\x6f\x66\x20\x74\x68\x65\x20\x70\ -\x72\x6f\x76\x69\x64\x65\x72\x20\x79\x6f\x75\x20\x77\x61\x6e\x74\ -\x20\x74\x6f\x20\x75\x73\x65\x20\x66\x6f\x72\x20\x79\x6f\x75\x72\ -\x20\x63\x6f\x6e\x6e\x65\x63\x74\x69\x6f\x6e\x2e\x07\x00\x00\x00\ -\x12\x53\x65\x6c\x65\x63\x74\x50\x72\x6f\x76\x69\x64\x65\x72\x50\ -\x61\x67\x65\x01\x03\x00\x00\x00\x62\x00\x4e\x00\x6f\x00\x20\x00\ -\x73\x00\x65\x00\x20\x00\x70\x00\x75\x00\x64\x00\x6f\x00\x20\x00\ -\x76\x00\x65\x00\x72\x00\x69\x00\x66\x00\x69\x00\x63\x00\x61\x00\ -\x72\x00\x20\x00\x65\x00\x6c\x00\x20\x00\x63\x00\x65\x00\x72\x00\ -\x74\x00\x69\x00\x66\x00\x69\x00\x63\x00\x61\x00\x64\x00\x6f\x00\ -\x20\x00\x64\x00\x65\x00\x6c\x00\x20\x00\x73\x00\x65\x00\x72\x00\ -\x76\x00\x69\x00\x64\x00\x6f\x00\x72\x00\x2e\x08\x00\x00\x00\x00\ -\x06\x00\x00\x00\x29\x53\x65\x72\x76\x65\x72\x20\x63\x65\x72\x74\ -\x69\x66\x69\x63\x61\x74\x65\x20\x63\x6f\x75\x6c\x64\x20\x6e\x6f\ -\x74\x20\x62\x65\x20\x76\x65\x72\x69\x66\x69\x65\x64\x2e\x07\x00\ -\x00\x00\x12\x53\x65\x6c\x65\x63\x74\x50\x72\x6f\x76\x69\x64\x65\ -\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x16\x00\x63\x00\x6f\x00\ -\x6d\x00\x70\x00\x72\x00\x6f\x00\x26\x00\x62\x00\x61\x00\x72\x00\ -\x21\x08\x00\x00\x00\x00\x06\x00\x00\x00\x07\x63\x68\x65\x63\x26\ -\x6b\x21\x07\x00\x00\x00\x12\x53\x65\x6c\x65\x63\x74\x50\x72\x6f\ -\x76\x69\x64\x65\x72\x50\x61\x67\x65\x01\x03\x00\x00\x00\x3a\x00\ -\x63\x00\x6f\x00\x6d\x00\x70\x00\x72\x00\x6f\x00\x62\x00\x61\x00\ -\x6e\x00\x64\x00\x6f\x00\x20\x00\x6e\x00\x6f\x00\x6d\x00\x62\x00\ -\x72\x00\x65\x00\x20\x00\x64\x00\x65\x00\x20\x00\x64\x00\x6f\x00\ -\x6d\x00\x69\x00\x6e\x00\x69\x00\x6f\x08\x00\x00\x00\x00\x06\x00\ -\x00\x00\x14\x63\x68\x65\x63\x6b\x69\x6e\x67\x20\x64\x6f\x6d\x61\ -\x69\x6e\x20\x6e\x61\x6d\x65\x07\x00\x00\x00\x12\x53\x65\x6c\x65\ -\x63\x74\x50\x72\x6f\x76\x69\x64\x65\x72\x50\x61\x67\x65\x01\x03\ -\x00\x00\x00\x34\x00\x63\x00\x6f\x00\x6d\x00\x70\x00\x72\x00\x6f\ -\x00\x62\x00\x61\x00\x6e\x00\x64\x00\x6f\x00\x20\x00\x63\x00\x6f\ -\x00\x6e\x00\x65\x00\x78\x00\x69\x00\x6f\x00\x6e\x00\x20\x00\x68\ -\x00\x74\x00\x74\x00\x70\x00\x73\x08\x00\x00\x00\x00\x06\x00\x00\ -\x00\x19\x63\x68\x65\x63\x6b\x69\x6e\x67\x20\x68\x74\x74\x70\x73\ -\x20\x63\x6f\x6e\x6e\x65\x63\x74\x69\x6f\x6e\x07\x00\x00\x00\x12\ -\x53\x65\x6c\x65\x63\x74\x50\x72\x6f\x76\x69\x64\x65\x72\x50\x61\ -\x67\x65\x01\x03\x00\x00\x00\x3a\x00\x6f\x00\x62\x00\x74\x00\x65\ -\x00\x6e\x00\x69\x00\x65\x00\x6e\x00\x64\x00\x6f\x00\x20\x00\x69\ -\x00\x6e\x00\x66\x00\x6f\x00\x20\x00\x64\x00\x65\x00\x6c\x00\x20\ -\x00\x70\x00\x72\x00\x65\x00\x76\x00\x65\x00\x65\x00\x64\x00\x6f\ -\x00\x72\x08\x00\x00\x00\x00\x06\x00\x00\x00\x16\x66\x65\x74\x63\ -\x68\x69\x6e\x67\x20\x70\x72\x6f\x76\x69\x64\x65\x72\x20\x69\x6e\ -\x66\x6f\x07\x00\x00\x00\x12\x53\x65\x6c\x65\x63\x74\x50\x72\x6f\ -\x76\x69\x64\x65\x72\x50\x61\x67\x65\x01\x88\x00\x00\x00\x02\x01\ -\x01\ -" - -qt_resource_name = "\ -\x00\x0c\ -\x0d\xfc\x11\x13\ -\x00\x74\ -\x00\x72\x00\x61\x00\x6e\x00\x73\x00\x6c\x00\x61\x00\x74\x00\x69\x00\x6f\x00\x6e\x00\x73\ -\x00\x05\ -\x00\x6a\x85\x7d\ -\x00\x64\ -\x00\x65\x00\x2e\x00\x71\x00\x6d\ -\x00\x05\ -\x00\x6c\x65\x7d\ -\x00\x65\ -\x00\x73\x00\x2e\x00\x71\x00\x6d\ -" - -qt_resource_struct = "\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x02\ -\x00\x00\x00\x1e\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x00\x2e\x00\x00\x00\x00\x00\x01\x00\x00\x17\x98\ -" - -def qInitResources(): - QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) - -def qCleanupResources(): - QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) - -qInitResources() diff --git a/src/leap/gui/mainwindow_rc.py b/src/leap/gui/mainwindow_rc.py deleted file mode 100644 index 9d16a35e..00000000 --- a/src/leap/gui/mainwindow_rc.py +++ /dev/null @@ -1,1130 +0,0 @@ -# -*- coding: utf-8 -*- - -# Resource object code -# -# Created: Wed Jan 30 06:06:54 2013 -# by: The Resource Compiler for PyQt (Qt v4.8.2) -# -# WARNING! All changes made in this file will be lost! - -from PyQt4 import QtCore - -qt_resource_data = "\ -\x00\x00\x05\x95\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x40\x00\x00\x00\x40\x08\x03\x00\x00\x00\x9d\xb7\x81\xec\ -\x00\x00\x00\x03\x73\x42\x49\x54\x08\x08\x08\xdb\xe1\x4f\xe0\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x37\x5d\x00\x00\x37\x5d\x01\ -\x19\x80\x46\x5d\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\ -\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\x70\ -\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x00\x1f\x74\x45\x58\ -\x74\x54\x69\x74\x6c\x65\x00\x47\x6e\x6f\x6d\x65\x20\x53\x79\x6d\ -\x62\x6f\x6c\x69\x63\x20\x49\x63\x6f\x6e\x20\x54\x68\x65\x6d\x65\ -\x8e\xa4\x29\xab\x00\x00\x02\x13\x50\x4c\x54\x45\xff\xff\xff\xff\ -\x00\x00\xff\x00\x00\xaa\x00\x00\xbf\x00\x00\xbf\xbf\xbf\xd5\x00\ -\x00\xc6\x00\x00\xc4\x00\x00\xbb\xbb\xbb\xcc\x00\x00\xcf\x00\x00\ -\xcc\x00\x00\xce\x00\x00\xb9\xb9\xb9\xc2\xc2\xc2\xce\x00\x00\xca\ -\x00\x00\xcc\x00\x00\xcd\x00\x00\xcc\x00\x00\xc1\xc1\xc1\xce\x00\ -\x00\xca\x00\x00\xcb\x00\x00\xcd\x00\x00\xcb\x00\x00\xcd\x00\x00\ -\xce\x00\x00\xbe\xbe\xbe\xcc\x00\x00\xbf\xbf\xbf\xbe\xbe\xbe\xcd\ -\x00\x00\xcb\x00\x00\xcc\x00\x00\xcd\x00\x00\xcc\x00\x00\xcc\x00\ -\x00\xbe\xbe\xbe\xbf\xbf\xbf\xcb\x00\x00\xcb\x00\x00\xcb\x00\x00\ -\xcc\x00\x00\xcc\x00\x00\xbf\xbf\xbf\xcd\x00\x00\xcb\x00\x00\xcc\ -\x00\x00\xcc\x00\x00\xbf\xbf\xbf\xcc\x00\x00\xcc\x00\x00\xcc\x00\ -\x00\xbe\xbe\xbe\xcc\x00\x00\xbe\xbe\xbe\xcc\x00\x00\xcc\x00\x00\ -\xcc\x00\x00\xcc\x00\x00\xbe\xbe\xbe\xbe\xbe\xbe\xcc\x00\x00\xcc\ -\x00\x00\xcc\x00\x00\xcc\x00\x00\xbe\xb7\xb7\xbe\xb8\xb8\xbe\xba\ -\xba\xbe\xbc\xbc\xbe\xbd\xbd\xbe\xbe\xbe\xbf\xaa\xaa\xbf\xab\xab\ -\xbf\xac\xac\xbf\xad\xad\xbf\xae\xae\xbf\xb0\xb0\xbf\xb1\xb1\xbf\ -\xb4\xb4\xbf\xb6\xb6\xbf\xb7\xb7\xc0\x9c\x9c\xc0\x9d\x9d\xc0\xa1\ -\xa1\xc0\xa2\xa2\xc0\xa4\xa4\xc0\xa5\xa5\xc0\xa6\xa6\xc0\xa7\xa7\ -\xc0\xa8\xa8\xc1\x8d\x8d\xc1\x91\x91\xc1\x94\x94\xc1\x95\x95\xc1\ -\x96\x96\xc1\x99\x99\xc1\x9c\x9c\xc2\x82\x82\xc2\x87\x87\xc2\x88\ -\x88\xc2\x8d\x8d\xc2\x8e\x8e\xc3\x73\x73\xc3\x74\x74\xc3\x76\x76\ -\xc3\x79\x79\xc3\x7c\x7c\xc3\x7d\x7d\xc3\x7f\x7f\xc4\x67\x67\xc4\ -\x6c\x6c\xc4\x6d\x6d\xc4\x6e\x6e\xc4\x70\x70\xc5\x59\x59\xc5\x5d\ -\x5d\xc5\x5f\x5f\xc5\x62\x62\xc5\x63\x63\xc6\x4c\x4c\xc6\x4f\x4f\ -\xc6\x50\x50\xc6\x53\x53\xc6\x56\x56\xc6\x58\x58\xc7\x3e\x3e\xc7\ -\x41\x41\xc7\x43\x43\xc7\x45\x45\xc7\x46\x46\xc7\x47\x47\xc7\x4b\ -\x4b\xc8\x31\x31\xc8\x35\x35\xc8\x36\x36\xc8\x38\x38\xc8\x3a\x3a\ -\xc8\x3c\x3c\xc9\x22\x22\xc9\x25\x25\xc9\x26\x26\xc9\x27\x27\xc9\ -\x28\x28\xc9\x2a\x2a\xc9\x2d\x2d\xc9\x2e\x2e\xca\x16\x16\xca\x17\ -\x17\xca\x1a\x1a\xca\x1b\x1b\xca\x1c\x1c\xca\x1d\x1d\xca\x1e\x1e\ -\xca\x20\x20\xca\x21\x21\xcb\x07\x07\xcb\x09\x09\xcb\x0a\x0a\xcb\ -\x0c\x0c\xcb\x0d\x0d\xcb\x0e\x0e\xcb\x0f\x0f\xcb\x10\x10\xcb\x11\ -\x11\xcb\x12\x12\xcb\x13\x13\xcc\x00\x00\xcc\x01\x01\xcc\x02\x02\ -\xcc\x03\x03\xcc\x04\x04\xcc\x05\x05\xcc\x06\x06\xcc\x07\x07\xd4\ -\x0d\x79\xbb\x00\x00\x00\x44\x74\x52\x4e\x53\x00\x01\x02\x03\x04\ -\x04\x06\x09\x0d\x0f\x0f\x10\x14\x15\x16\x19\x1a\x1d\x1e\x24\x28\ -\x29\x2a\x30\x36\x3d\x40\x42\x43\x4b\x55\x58\x5e\x60\x63\x64\x65\ -\x6e\x73\x7d\x7f\x8a\x94\x99\x9a\xaa\xb2\xbb\xbc\xc3\xc9\xca\xd2\ -\xd5\xde\xe0\xe3\xe6\xe8\xed\xef\xf6\xf7\xfa\xfa\xfb\xfc\xfd\xef\ -\xfa\x14\xec\x00\x00\x02\x79\x49\x44\x41\x54\x58\xc3\xed\x96\xd7\ -\x5b\x13\x41\x14\x47\x47\x05\x15\x5b\x2c\xa0\x58\x62\xb0\x26\xb6\ -\xa0\x46\xc5\x28\x12\x6c\x49\xae\x0d\x62\x01\xc5\x82\x58\x10\x7b\ -\x2f\xd8\x1b\x8a\xbd\x00\x62\x41\x45\x14\xf5\x04\x51\xff\x44\x1f\ -\x76\x37\x1f\x09\xc9\x66\x37\x8f\xc8\xef\xed\xee\xf7\x9d\xb3\x33\ -\x73\x67\x66\x57\xa9\xfe\x98\x65\x94\xd3\xe3\xf5\xfb\xbd\x1e\xe7\ -\xa8\x8c\xf0\x6c\x77\x40\xf4\x04\xdc\xd9\xf6\xf9\x3c\x9f\xf4\x88\ -\x2f\xcf\x2e\x5f\x50\x22\x71\x29\x29\xb0\xf9\xfe\x04\x5e\xa4\xc4\ -\xd6\x18\xb2\x7d\xd2\x2b\x3e\x3b\xeb\xe0\x96\x24\x71\xdb\xe8\x5f\ -\x20\x99\x20\x60\xbd\x9b\x4e\x83\x29\x72\x39\x1c\xae\x22\xa3\x72\ -\x5a\x16\x78\x0c\x3e\x47\x29\xa5\x72\x0c\x83\xc7\xb2\xc0\xab\x13\ -\x2e\xad\x74\xe9\xa5\xd7\xb2\xc0\xaf\x13\x0e\xad\x74\xe8\xa5\xdf\ -\x12\x3c\x31\x57\x29\x63\xd2\xc6\xb3\x58\x3d\x60\xda\xa0\x74\x7c\ -\x6e\x71\xf1\x94\x94\x82\x21\x0b\x98\x9b\x86\x1f\xb9\x0c\x56\xcf\ -\x4c\x21\xd8\xb4\x18\x98\x65\x2e\x28\x04\xe0\x66\x38\x99\xe0\x60\ -\x0b\xc0\x9a\x7c\x53\xc1\x84\xe5\x00\x3c\xd9\xda\x7b\x1f\x1d\x6e\ -\x07\x60\xce\x40\xf3\x21\x8c\x7e\x01\xc0\xf3\x0d\x89\xfc\xd9\x2e\ -\x80\xee\xa3\x69\xbb\xb0\xed\x29\x00\x3f\x0f\xc4\xe1\xa1\x6b\x00\ -\x7c\x39\x22\x69\x05\x12\xbe\x05\xc0\xa7\x43\x3d\xf8\x48\x23\x00\ -\xad\x15\x62\x41\x20\x72\xac\x1b\x20\x7a\x26\xc6\x57\xb5\x01\xd0\ -\x54\x26\xd6\x04\x52\xd7\x01\xc0\x83\xa0\xc6\x57\x77\x02\x70\x2f\ -\x28\x56\x05\x52\xf9\x16\x80\x87\xe5\x22\x22\x27\x7f\x03\x44\xaf\ -\xc4\xb7\xd6\x5c\x20\x65\xcd\x00\xbc\xdf\x29\x72\x19\x80\x3f\xa7\ -\xc4\x96\x40\x82\x0d\x00\x7c\xdd\x7b\x17\x80\xce\x6a\xb1\x29\x10\ -\xa9\x8f\x02\x44\x01\x68\xab\x12\xfb\x02\x39\xfd\x17\x3d\x8d\x11\ -\xc9\x44\x20\x35\x5a\x33\xb8\x1a\x92\xcc\x04\x17\x7e\x69\x82\x57\ -\x9b\x33\x12\x84\x6e\x18\x33\xa0\x65\x7b\x06\x82\xc8\x63\x00\x3e\ -\x03\xd0\x5e\x6b\x5b\xb0\xe7\x19\x00\x2f\x37\x9e\xef\x02\xe8\x3a\ -\x67\x53\x50\xf3\x03\x80\xdb\xeb\x44\x6a\xb5\x7b\xe0\x7a\xc8\x8e\ -\x40\xef\xe0\xc5\xb0\x88\xc8\x8e\x37\x00\x3c\x8a\x58\x17\x68\x7b\ -\xa8\xfb\xb8\x3e\xea\x2d\xaf\x01\xf8\xb8\xdb\xa2\x20\x78\x1f\x80\ -\x8e\xba\xd8\xc2\xad\xbf\x03\xc0\xf7\xfd\x96\x04\x65\x4d\x00\xbc\ -\xab\xec\xd1\xfc\xf0\xa5\xd8\x89\x4a\x2b\xa8\x68\x05\xa0\xb9\x3c\ -\xfe\x4a\x3c\x61\x9c\xe9\x74\xfc\x64\x6d\xfb\x36\x04\x13\x2f\xd5\ -\x7d\xdf\xb4\x5b\x65\xb0\x39\x3f\xa3\x14\x20\x5a\x9f\xe4\xf7\x60\ -\xd7\x07\x00\x16\x8d\x30\x15\xcc\x03\x58\x35\x35\xf1\x9b\xa8\x94\ -\x52\x6a\xf8\x42\x80\x95\xe3\x4c\x05\x59\x85\xb0\x22\x5f\x25\x15\ -\xa8\xac\xf9\x50\x3a\x29\xcd\x1a\x0c\x5b\xba\x64\xac\x4a\x21\x50\ -\x6a\xf6\xda\xe9\x69\xbb\x30\x66\xa8\x4a\x2d\x50\xe3\xed\xfd\x2d\ -\x5a\x3e\x40\x7d\x44\x20\x36\xd3\x2f\xe8\x9b\x82\xff\x38\xff\x00\ -\xc1\x36\x30\x95\xf0\x66\xca\x60\x00\x00\x00\x00\x49\x45\x4e\x44\ -\xae\x42\x60\x82\ -\x00\x00\x04\xec\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ -\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ -\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x06\xec\x00\x00\x06\xec\ -\x01\x1e\x75\x38\x35\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ -\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ -\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x00\x13\x74\x45\ -\x58\x74\x41\x75\x74\x68\x6f\x72\x00\x52\x6f\x64\x6e\x65\x79\x20\ -\x44\x61\x77\x65\x73\x0e\xd8\x7e\x1d\x00\x00\x04\x4a\x49\x44\x41\ -\x54\x48\x89\x8d\x96\x5d\x6c\x53\x65\x18\xc7\x7f\xef\x39\x6b\xbb\ -\x7e\x9c\x75\x65\xad\x2b\x9b\xfb\xd0\x31\xdd\x14\xb6\x8c\x19\x44\ -\x90\x44\x63\x82\x42\x88\x5e\x90\x98\xcc\x19\x15\x13\xd4\x18\x76\ -\x61\xd4\x18\xe3\x85\x57\xca\x05\xe1\xc2\x0c\xa3\xa8\x51\xd0\x4c\ -\x12\xe3\x85\x31\x80\x26\x6a\xe2\x85\x23\xb0\x38\xb6\xc1\x1c\xce\ -\xb1\x40\x59\xf6\xe5\xca\xda\xae\xed\xfa\x75\x7a\x5e\x2f\x4e\xd7\ -\x59\xd6\x32\xfe\xc9\x7b\xf3\x9e\xe7\xf9\xff\x9f\xe7\xff\x9e\xf3\ -\x9c\x57\x48\x29\x59\x0f\xbd\x7b\x85\x0d\x17\xed\x1e\xbb\xb2\x07\ -\x20\x94\x30\x7e\x22\xc6\x48\xcf\x59\x99\x5a\x2f\x57\x94\x12\xf8\ -\xec\x55\x61\x71\x65\x6d\x47\xfc\xbe\xda\x47\x9d\x5a\xa5\xbf\xda\ -\x69\xaf\xda\xe0\x28\x2f\x07\x58\x5c\x4e\x26\xe7\xe3\x89\x9b\xf1\ -\x68\x78\x6e\x6e\x61\xfa\x8f\x98\x9a\x7a\xfb\x95\xe3\x32\x73\xc7\ -\x02\x9f\x76\x89\x8e\xba\xda\xda\x2f\xb7\x37\xdf\xdf\xe6\x2a\x13\ -\x8a\x94\x06\x82\xc2\x38\x89\x40\x08\x85\x98\x2e\x8d\xf3\x13\xe3\ -\x97\xa6\xa6\xa7\x5f\x7e\xed\x94\x1c\x5a\x57\xa0\xef\xa0\xfd\x70\ -\x5b\xf3\x96\x03\xcd\xde\x8a\x6a\x61\x64\xd7\x73\xc0\x14\x53\x54\ -\x26\x82\x4b\xf3\x97\x26\x2e\x7f\xd5\xfd\x79\xe2\xdd\x92\x02\x27\ -\x5f\x2a\x7b\xe1\x89\xce\x1d\xc7\xbc\x76\x55\x13\xc5\x98\xac\x4e\ -\x10\x0a\xa4\xa2\x6b\x45\x80\x60\x22\x1b\xfd\x6d\xf0\xdc\xa1\x17\ -\x4f\xe8\x5f\xaf\x11\x38\xfa\x9c\xf0\x6e\xdb\xf4\xc0\xf9\x6d\xf5\ -\xfe\x26\x30\xf2\x89\xca\xc6\x76\xd4\x07\xf7\xa3\xd4\x74\x80\xd5\ -\x65\x6e\xa6\xe3\x64\x03\xfd\x64\x2f\x9e\x40\x46\x67\xff\x27\xa3\ -\x30\x70\x63\x6e\x72\xe0\xea\xd8\xf6\x37\xbf\x95\x41\x73\x27\x87\ -\x06\x8f\xa7\x6f\x6b\x7d\x4d\x01\x39\x80\x52\xff\x08\x4a\xe3\xae\ -\x55\xf2\x5c\x27\x6a\xf3\x6e\x2c\x7b\x8f\x9a\x5d\xe5\x61\xb0\xb5\ -\xbe\xa6\xa9\xc1\xe3\xe9\x5b\x95\x04\x7a\xbb\x44\x47\x5b\x53\xcb\ -\x4e\x15\xbd\x98\x31\xc8\x70\x00\xfd\xfc\xc7\x64\xce\xbc\x81\x7e\ -\xe1\x13\xc8\x75\x2d\xb4\x8d\x28\xb5\x0f\x15\xc4\xaa\xe8\xb4\x35\ -\xb5\xec\xec\xed\x12\x1d\x00\x65\x00\xee\x72\x65\x9f\x5f\x73\x38\ -\x05\x6b\x0f\x35\x3b\xf6\x03\xfa\xc0\xf1\x3c\x29\xb3\xc3\xa8\xf7\ -\x3e\x8e\xf0\xb5\x98\x22\xf6\x0d\x05\xf1\x02\xf0\x6b\x0e\xa7\xbb\ -\x5c\xd9\x07\x0c\x29\x00\x9a\xc3\xd5\x69\x55\xd5\xe2\xd5\x47\xe7\ -\x56\xc9\x01\xe1\xbe\x1b\xe1\xb9\x67\xf5\x79\x70\x7c\x4d\x8e\x55\ -\x55\xd1\x1c\xae\xce\xbc\x45\x15\x6e\x5f\x9d\x90\xc5\xed\x29\xa8\ -\xae\xa2\x06\xcb\x53\x47\xa0\xcc\x66\x76\x37\xfa\x3d\xd9\xa9\x81\ -\xb5\x71\x52\xa7\xc2\xed\xab\x83\x9c\x45\x76\xbb\x56\x25\xa5\xa4\ -\xe8\xab\xb9\x02\x9b\x86\x65\xf7\x87\x08\xcd\x6f\x92\x8f\x9f\x21\ -\xf5\xdd\xf3\xa0\xa7\x10\xe5\x6e\x44\x45\x2d\x38\x7d\x08\x21\x90\ -\xd2\xe4\xcc\x0b\x24\x12\xd1\x9b\x42\xbd\xab\x81\x6c\xba\x28\xb7\ -\x94\x06\x65\xcd\x4f\x22\x2a\x1b\x00\x30\xa6\xff\x24\xd5\xb7\x1f\ -\x74\x73\x14\xc9\x64\x04\x99\x8c\x80\xc5\x8e\xe2\xae\x03\xab\x93\ -\x44\x22\x7a\x33\x6f\xd1\x52\x64\x61\x0a\xb5\xbc\x28\xb1\xb1\x34\ -\x83\x91\xb3\xc1\x98\x1d\xc1\x98\x1d\x41\x3f\xd7\x9b\x27\x2f\x40\ -\x26\x81\x11\xfc\x07\x99\x8a\x99\x9c\x2b\x1d\x44\x97\x63\x83\xc9\ -\xe8\xfc\x33\x36\x23\x05\xaa\x05\xd2\xcb\xc8\x74\xcc\xfc\x88\x72\ -\x5d\xa5\x7f\x3c\x74\x3b\x03\x0b\x90\x52\xed\x44\x97\x63\x83\x79\ -\x81\x48\xd2\x38\x3d\x1b\xcf\xbc\x53\x1f\xb9\xe4\x44\x1a\x45\x93\ -\xac\xcf\x7e\x83\xda\xb8\xcb\x2c\xf4\xd7\xf7\xd1\x2f\x9e\x2c\xce\ -\x2e\x14\xe6\xd2\x65\xf1\x48\xd2\x38\x0d\x39\x8b\x7a\x4e\xc9\xa1\ -\xd1\xc0\xb5\xfe\xac\xb7\xb5\x64\x55\xc2\xe5\x47\x54\x36\x98\xe7\ -\x60\xd3\x4a\xc6\x65\xbd\xad\x8c\x06\xae\xf5\xf7\xe4\x26\x6b\x7e\ -\x54\x04\x42\xa1\xee\xe1\x90\x31\x29\x1c\xde\xd2\xbd\xaf\x03\xe1\ -\xf0\x32\x1c\x32\x26\x03\xa1\x50\x77\x7e\xef\xd6\x69\xfa\x58\x7b\ -\xe7\x31\x5f\x78\x54\x23\xb3\x5c\x90\xac\xf8\xdb\x10\x0e\xf3\xab\ -\x35\x82\x13\xc8\xa5\xe9\x42\x76\x8b\x83\x85\xca\xcd\xd1\xdf\x47\ -\x06\x8b\x4f\xd3\x15\xf4\x1d\xb4\x1f\xde\xd2\xd4\x7a\x60\x93\x1a\ -\xaa\x26\x74\xfd\xce\x4a\xf7\x34\x72\x35\xeb\x99\xbf\x3c\x79\xe5\ -\xf6\xff\x83\x15\x7c\xf0\xb4\xd8\xbe\xb9\xa9\xe6\x8b\x1d\x0d\xd5\ -\xad\xae\xd8\x94\x22\x13\x21\x90\xb7\xcc\x29\xa1\x22\xec\x1e\x62\ -\xae\x3a\xa3\xff\xfa\xfc\xdf\xe7\xc6\x66\x5e\x3f\xf2\x0b\xfd\x52\ -\x16\x8e\x84\x02\x01\x21\x84\x0a\x54\x01\x95\x9a\x1d\xdf\x7b\x7b\ -\xac\x6f\xdd\x57\xb7\xb1\x6d\x83\xbb\xd2\x53\xe3\x10\x2e\x9f\xcd\ -\xb0\x00\xfc\x9b\x54\xf4\x99\x84\x8c\x2d\x86\xc3\xe1\x2b\x81\xd9\ -\xbf\x0e\xff\x9c\xfe\x28\x9e\x22\x08\x84\x80\xb0\x94\x32\x5c\xb2\ -\x03\x21\x84\x13\xf0\x00\xee\xdc\xd2\x5c\x56\x3c\x5b\xeb\x69\x79\ -\xb8\x51\x74\x18\x12\xe5\xc2\x75\x39\x3c\x74\x83\xc9\x78\x86\x10\ -\x10\x03\x96\x80\x48\x6e\x2d\x4a\xb9\x7a\x01\x28\x79\xab\xc8\x89\ -\x59\x00\x2b\x60\xcb\x2d\x0b\xa0\x02\x3a\x90\x02\xd2\x40\x12\xc8\ -\x48\x79\xab\x87\x26\xfe\x03\x26\x93\xd5\x41\x51\x76\x98\xdb\x00\ -\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x01\xaa\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x40\x00\x00\x00\x40\x08\x03\x00\x00\x00\x9d\xb7\x81\xec\ -\x00\x00\x00\x03\x73\x42\x49\x54\x08\x08\x08\xdb\xe1\x4f\xe0\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x37\x5d\x00\x00\x37\x5d\x01\ -\x19\x80\x46\x5d\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\ -\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\x70\ -\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x00\x1f\x74\x45\x58\ -\x74\x54\x69\x74\x6c\x65\x00\x47\x6e\x6f\x6d\x65\x20\x53\x79\x6d\ -\x62\x6f\x6c\x69\x63\x20\x49\x63\x6f\x6e\x20\x54\x68\x65\x6d\x65\ -\x8e\xa4\x29\xab\x00\x00\x00\x36\x50\x4c\x54\x45\xff\xff\xff\xbf\ -\xbf\xbf\xbb\xbb\xbb\xb9\xb9\xb9\xc2\xc2\xc2\xc1\xc1\xc1\xbe\xbe\ -\xbe\xbf\xbf\xbf\xbe\xbe\xbe\xbe\xbe\xbe\xbf\xbf\xbf\xbf\xbf\xbf\ -\xbf\xbf\xbf\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\ -\xbe\xbe\xf1\xb6\xe9\xa5\x00\x00\x00\x11\x74\x52\x4e\x53\x00\x04\ -\x0f\x16\x19\x29\x4b\x58\x5e\x7d\x7f\xb2\xca\xe0\xe6\xf7\xfa\x2a\ -\xb3\x5d\x53\x00\x00\x00\x9e\x49\x44\x41\x54\x58\xc3\xed\x95\xb9\ -\x12\x83\x30\x0c\x05\x31\x18\x1b\x1f\x18\xeb\xff\x7f\x36\xc9\x20\ -\xcd\x24\xe1\x92\xe8\x00\x6d\xf7\x8a\x5d\xc0\x2e\x68\x1a\x65\x8f\ -\xce\xc5\x54\x4a\x8a\xae\x3b\xa5\x9b\x50\x01\xa9\xc1\xc8\xfd\x3e\ -\xc3\x17\xb9\x97\xfa\xc3\x04\x3f\x4c\x83\xf0\xf9\x7f\xfe\xbb\x20\ -\x7a\x07\x93\x61\x41\x96\x9c\x43\x80\x15\x82\xe0\xfe\xea\x5a\xa0\ -\xf2\x6f\xd3\x91\x33\x7a\x6b\xfd\x48\xcb\xb1\x03\x91\xfc\xf6\xb3\ -\x5a\x2a\x44\x76\x20\xa1\xe1\xe7\xe9\x71\x26\x76\xa0\xa0\x61\xe7\ -\x69\x71\x16\x76\x80\x3e\x7a\x6b\xdf\x3d\x00\x07\x68\x40\x03\x1a\ -\xd0\x80\x06\x9e\x15\xd8\xfb\xc1\x88\xd1\xc0\xe5\x02\x20\x44\x03\ -\xf7\x0c\x3c\x98\x17\xb4\xcd\x62\x13\x3b\x4c\x60\xe6\x00\x00\x00\ -\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x02\xc8\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x40\x00\x00\x00\x40\x08\x03\x00\x00\x00\x9d\xb7\x81\xec\ -\x00\x00\x00\x03\x73\x42\x49\x54\x08\x08\x08\xdb\xe1\x4f\xe0\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x37\x5d\x00\x00\x37\x5d\x01\ -\x19\x80\x46\x5d\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\ -\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\x70\ -\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x00\x1f\x74\x45\x58\ -\x74\x54\x69\x74\x6c\x65\x00\x47\x6e\x6f\x6d\x65\x20\x53\x79\x6d\ -\x62\x6f\x6c\x69\x63\x20\x49\x63\x6f\x6e\x20\x54\x68\x65\x6d\x65\ -\x8e\xa4\x29\xab\x00\x00\x00\xb4\x50\x4c\x54\x45\xff\xff\xff\xff\ -\xff\xff\x80\x80\x80\xbf\xbf\xbf\xcc\xcc\xcc\xbf\xbf\xbf\xc6\xc6\ -\xc6\xb3\xb3\xb3\xc8\xc8\xc8\xc3\xc3\xc3\xba\xba\xba\xc4\xc4\xc4\ -\xbd\xbd\xbd\xb9\xb9\xb9\xb9\xb9\xb9\xbf\xbf\xbf\xbc\xbc\xbc\xbd\ -\xbd\xbd\xbe\xbe\xbe\xbd\xbd\xbd\xbc\xbc\xbc\xbe\xbe\xbe\xbe\xbe\ -\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbf\xbf\xbf\xbd\xbd\xbd\xbe\xbe\xbe\ -\xbf\xbf\xbf\xbd\xbd\xbd\xbf\xbf\xbf\xbe\xbe\xbe\xbe\xbe\xbe\xbe\ -\xbe\xbe\xbe\xbe\xbe\xbd\xbd\xbd\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\ -\xbe\xbe\xbe\xbe\xbd\xbd\xbd\xbf\xbf\xbf\xbe\xbe\xbe\xbe\xbe\xbe\ -\xbe\xbe\xbe\xbf\xbf\xbf\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\ -\xbe\xbe\xbe\xbe\xbe\xbd\xbd\xbd\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\ -\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\ -\xe4\x72\x0e\xe3\x00\x00\x00\x3b\x74\x52\x4e\x53\x00\x01\x02\x04\ -\x05\x08\x09\x0a\x0e\x11\x1a\x1a\x1f\x21\x2c\x2c\x35\x36\x3b\x3e\ -\x41\x43\x47\x4b\x4e\x50\x55\x56\x57\x59\x63\x66\x71\x86\x89\x90\ -\x95\x96\x9d\x9e\xa7\xaa\xad\xb5\xb8\xbe\xc0\xc3\xc5\xc9\xcd\xd6\ -\xe0\xe6\xef\xf5\xf7\xfc\xfd\xec\xba\xa4\x27\x00\x00\x01\x14\x49\ -\x44\x41\x54\x58\xc3\xed\x95\x5b\x57\x82\x40\x14\x85\x9d\x44\xb4\ -\x40\x50\x29\x92\x64\xb2\xd4\xee\xf7\xbc\x54\xf2\xff\xff\x97\xb2\ -\x98\x91\xe2\x20\xed\xe3\x4b\x2b\xe4\x7b\xfb\xd6\x39\x7b\x73\x99\ -\x87\xa9\xd5\x2a\x8a\x68\x58\xae\x17\x04\x9e\x6b\x35\x76\x8a\x0b\ -\x27\x94\x8a\xd0\x11\xfc\x7c\xcb\x97\xdf\xf0\x5b\xdc\x7c\x7b\x20\ -\x7f\x30\x68\x33\x9f\x9f\xc9\xaf\x1b\x58\xef\x20\x7c\x49\xf0\x39\ -\xff\xc1\x91\x39\x38\x8c\xf3\x0b\xf3\x0a\x42\xfc\x34\x2d\x9d\xe9\ -\xdb\xa6\x69\xf7\xb5\x59\x70\x81\xab\xf3\x46\x6c\x86\x6e\x70\xe1\ -\x02\x4f\x25\xec\x44\x6d\xa5\x1e\x5c\x10\xa8\x84\x99\xa8\xa9\x34\ -\x80\x0b\xf4\x47\x6f\xf3\xb2\x17\xc8\x5f\xa8\x0a\xfe\x45\xc1\xc9\ -\x9a\x6d\xe1\x78\x56\xdd\x76\xc5\x74\xc7\x6f\xef\x93\x63\xdc\x33\ -\x18\x97\x5f\x51\xcc\x55\x13\x73\xc2\x28\x52\xdc\x60\x9e\xa5\xb7\ -\xd4\x0b\xd1\x29\xe2\x84\xeb\xcd\x3c\xba\x47\x9c\x30\x4d\x17\xe6\ -\x88\x13\x66\xe9\xc2\x42\x00\x4e\xb8\x4d\x17\x1e\x11\x27\x9c\xa5\ -\x0b\x43\xc4\x29\x77\x7a\xfe\x24\x20\x27\x1c\x3e\x24\xf3\x97\x23\ -\xcc\x73\x38\x7f\xfe\xf8\x7c\xbd\x38\x80\x3d\x87\xba\xc1\xf3\x3d\ -\xa0\xa3\xd8\xb9\x80\x7d\xad\xff\x6d\x81\x64\x52\x15\x94\xb3\x60\ -\x8f\x59\x01\x25\xba\xb5\x2a\xd7\xa3\x29\x75\x00\x00\x00\x00\x49\ -\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x27\x74\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x80\x00\x00\x00\x66\x08\x06\x00\x00\x00\x03\x23\x99\x54\ -\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ -\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x02\x3a\x00\x00\x02\x3a\ -\x01\xfe\x36\x29\x51\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ -\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ -\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x26\xf1\x49\x44\ -\x41\x54\x78\xda\xed\x9d\x05\x58\x54\x59\xff\xc7\x7f\x77\x86\x14\ -\x90\x50\x14\x41\x94\x10\x51\x09\x45\xb0\x50\xb0\x10\x10\x13\x6b\ -\x2d\x6c\xb0\xdd\xb5\xdb\x35\xd7\x5a\x5b\x09\xdb\xb5\xdb\xb5\x0b\ -\x19\x42\xba\x15\x24\x0c\x14\x29\x15\x29\x25\x06\x66\x7e\xff\x73\ -\x86\xb9\x38\xb0\x60\xbc\xaf\xfe\x5f\x70\x67\x9e\xe7\xf3\xec\xaa\ -\xc0\x1d\xe6\xfb\xb9\xe7\x9e\x3e\x80\x88\x20\xe5\xdf\x8b\xf4\x43\ -\xf8\x9a\x0f\x09\x40\x51\x2a\xc0\xbf\x27\x6c\x5d\x42\x77\xc2\x28\ -\x42\x34\x21\x9f\xc0\x27\xcc\x21\x34\x94\xf8\x3a\x19\xa9\x00\x3f\ -\x4f\xe8\xfa\x84\x49\x84\xab\x84\x62\x02\xd6\x40\x1e\xe1\x00\x21\ -\x96\xb0\x59\x2a\x40\xdd\x0f\xbe\x1d\xe1\x2d\x1b\x70\x23\x79\x79\ -\xec\xa7\xa5\x85\xab\x5b\xb7\x46\x2f\x73\x6b\xf4\x6c\xb0\x12\x97\ -\xa8\x4e\xc6\xd1\x4a\x4e\x68\x2d\xdf\x0e\x75\xb8\x8d\x90\x03\x1c\ -\x56\x86\x9b\x52\x01\xea\x5e\xe0\xc3\x08\xbb\x08\xd3\x09\xf3\x08\ -\x6f\x54\x64\x64\x70\x93\xa9\x29\xbe\x74\x74\x44\x1c\x32\xa4\x82\ -\xe2\xc1\x83\x05\x99\xba\xde\xc2\x77\xba\xfe\x28\x49\x82\xf6\x55\ -\x6c\x29\xdb\x9c\xfe\xb0\xd4\x9f\xe1\x31\xf0\x6f\x13\x60\x11\x7b\ -\xb7\x73\x18\x06\x27\xe9\xe9\x61\x66\xbf\x7e\x95\x82\x97\x24\xdb\ -\xe4\x68\x5c\x55\x01\x28\x8f\xb5\x2f\xa3\x9e\x8c\x0e\xfd\x39\x01\ -\x04\x6d\xa9\x00\x75\x23\x7c\x2f\x1a\xbc\xa5\x66\x7d\xf4\xb0\xb0\ -\xc0\x94\x2a\x77\x7c\x75\x14\x77\x5f\xe9\x53\x9d\x00\x94\xa8\x26\ -\xe7\x45\x8f\x04\xf2\x33\x93\x08\x06\x52\x01\x6a\xbf\x00\x9a\x84\ -\xe7\x2b\x26\x70\x30\xca\xa5\xfd\x17\xc3\xa7\x94\x0d\x18\x17\x56\ -\x93\x00\x94\xb0\x26\xa7\xb1\x31\xb7\x01\xfd\xe1\x45\x84\x38\x82\ -\x8e\x54\x80\xda\x2b\x00\x43\xc8\x98\x3b\x06\x30\xf5\x06\x60\xc9\ -\x1c\x9b\xf8\x2f\x4a\xe0\x3c\x24\xff\x5d\x33\xbf\xb2\xcf\x49\xf0\ -\x50\xeb\x38\x36\xe0\xa8\xb2\x15\xc3\x35\x3f\xf8\xfd\xdb\x12\x8e\ -\x88\x1f\x3d\x43\xe9\xdf\x49\x05\xf8\xb6\x0f\xd1\x63\xe2\x40\xc0\ -\x1c\x1e\xe0\xfb\x07\x90\x57\x3a\xab\xfb\x17\x25\xc8\x6b\x75\xe6\ -\xc9\xe7\x04\xa0\xb8\x6b\xac\x60\x05\x08\xfb\x01\xef\xd9\x98\x70\ -\x81\xca\x4b\xaf\xa1\xa4\xda\x00\xad\xec\x7f\x41\x0e\x87\x4b\xff\ -\xf1\x11\x15\x41\x2a\xc0\xd7\x7f\x98\x9e\x43\x7a\x95\x0b\x20\xc2\ -\x07\xde\x97\xce\xe8\x91\xf0\x39\x01\x8a\xba\xad\xe1\x7d\x49\x80\ -\xe9\x03\xf7\xa3\x4d\xa7\xc9\xac\x04\x7f\x7e\xc7\xf7\x3b\x88\xf6\ -\x3d\x34\x92\xd7\xc2\xfa\xb6\xe3\xb1\xdb\xbe\x68\xec\xeb\x1e\x8e\ -\x93\x4e\x25\x09\xe7\x1c\x8b\xc2\x96\x96\x3d\xd8\x6b\x3e\x24\x58\ -\x48\x05\xf8\xfc\x87\x29\x4f\xc8\xe9\x69\x25\x21\x00\xe5\x01\x64\ -\x97\x4e\xef\x95\x54\x63\x3d\xa0\xdf\xc4\xe0\xcf\x85\xef\x6d\x1b\ -\xf0\x68\xd3\xf2\xe7\xb8\xfc\xd7\x60\x36\x8c\xb4\xef\x54\xdc\xaf\ -\xe6\x32\x5c\xe1\x9e\xd6\x1e\xd8\x65\xf8\x45\x64\xd6\x3d\xc1\x4e\ -\x07\x9e\xa6\x39\xdd\x42\x64\x71\xbc\xf2\x11\x3b\xad\xb9\x86\x5c\ -\x05\x25\x51\x8b\x44\x2a\xc0\xe7\x3f\x54\x2b\x1a\x90\x85\x71\x15\ -\x01\x44\x25\x01\xf3\xb6\x6c\x6a\xaf\xa7\xd5\x09\x20\x1c\x32\x34\ -\xbb\xa6\xf0\xdf\x36\x0b\x28\xfb\x73\xc9\xf3\x64\x2a\xc0\xba\xc5\ -\xf1\x58\xaf\xe5\x50\x7a\xa1\xa7\xff\xed\xb8\x03\xe1\x9c\x22\x47\ -\x11\xcf\xb7\xbb\x82\xa3\x06\xdd\x10\x02\x09\x9f\xc2\xac\x4f\x10\ -\xd8\x9c\xc9\x8e\x1b\x72\xb2\x18\x6d\x3c\x62\xb1\xdd\xa2\x13\xa8\ -\xd0\xb0\x29\xfd\xa6\x77\x84\x66\x75\x52\x00\x71\x30\x46\xdf\xa3\ -\x42\xf3\x85\xeb\xb4\xa1\x02\xe8\x6b\x57\x23\x00\x21\x97\xc7\x64\ -\x09\xa6\xf4\x7e\x5e\x9d\x04\x39\x2d\x2f\x3d\xab\x4e\x80\xdb\xfd\ -\xa3\xfd\x69\xf8\x94\x51\x2e\x97\xb1\xe9\xcc\xb7\xf4\x42\x31\xff\ -\xc5\x7b\xec\x4a\x08\x57\x97\x55\x47\x9f\x8e\x01\xb8\xbc\x9f\x3f\ -\x1f\xd6\xc6\x0b\x68\xf8\xf2\x6b\x13\x0b\x7a\xce\x7b\x19\x36\x7b\ -\xd2\x8b\x0f\x33\xc6\xc4\xb0\xa5\x0d\x5f\xdc\x2d\xdd\xb5\x4e\xd6\ -\x01\xc8\x6b\x3e\x41\x20\xd1\xcf\xbe\x91\x50\xef\x07\x5d\xab\x19\ -\xbd\x8e\x7a\xfd\xea\x05\x10\x49\xf0\x80\xc9\x14\x4c\xb6\x4b\xa9\ -\x2a\x40\x61\x97\x3f\xfc\xaa\x86\x9f\xa5\x17\x50\xb4\x79\xe9\xf3\ -\x74\x1a\xfe\x8a\x25\x2f\x22\xf5\xe6\x95\x60\xb3\x05\x02\x64\x38\ -\x72\xb1\xdf\xf8\xbe\x5a\xd3\xe2\x9e\xf0\x44\x51\x46\x19\xed\x0d\ -\xc7\xe0\x8d\x8e\xf7\xf1\xa8\x53\x5c\x21\x09\xbe\x50\x6f\xd5\xcb\ -\xc7\xe3\xa6\xa7\x3e\x5a\x31\xb3\x50\xb0\xe6\x57\xc4\xd9\xe3\x12\ -\xb0\x6d\x2b\x17\xfa\x8d\xf7\xea\x6c\x33\x90\xbc\x38\x84\xbd\xa2\ -\xe0\x3b\x8f\xc3\xb6\xee\xbe\xa8\xda\x67\x12\x82\xbc\xe8\x59\x96\ -\x46\x58\x49\x68\xfc\x9d\xaf\x59\x4f\xd4\x03\xc8\x21\x2d\x00\x9f\ -\x9a\x25\x20\x8f\x83\x74\xc1\x64\xfb\x57\x92\x02\x94\x3a\x4d\x79\ -\x58\x55\x80\x8b\x23\x1e\xf1\x68\xf8\x6b\x97\xa7\x3c\x36\x58\x50\ -\x56\xd8\x6c\x81\x10\x29\x5c\x65\x6d\x7a\x31\xcb\x2f\xbc\x17\x5a\ -\xe2\xad\xa0\xb5\x78\x86\xe1\xa0\xa5\x76\x2f\x5c\x6a\x73\x08\x6f\ -\x8c\x79\x83\xde\xe3\x3f\xe2\x61\xc7\xa0\xd7\xc3\x17\xbd\x09\x5a\ -\x31\xb7\x38\x83\x86\xbe\x62\x56\x2e\x4e\x1e\xf5\x37\xea\x36\xe9\ -\xc8\xde\xf9\x2f\x69\x93\xb0\x4e\x0a\x40\x5e\xb2\x84\xd3\x20\xab\ -\x88\x30\xee\x10\xca\xac\x7d\x54\x36\x80\x14\x9a\xfd\x22\x05\xa8\ -\xb5\xe8\x14\x32\xdd\x5d\x59\x11\x4a\x08\xe7\x09\xd3\xe8\x5d\xf2\ -\x9d\xae\x5d\x4a\x3f\xc0\xf4\x3b\x50\x52\xa3\x00\x14\x6f\x26\x4d\ -\x30\xc1\xfe\xf5\xa7\xfe\x80\xa1\xe9\x92\xe1\xa7\xb5\x78\xf8\x7e\ -\xd3\xb2\xe7\xb9\x1b\x96\xbd\x78\x66\xbc\xb0\xec\x3d\x1b\x3e\x45\ -\xae\x91\x05\xbd\xd0\xe2\x1a\x46\x1c\x97\x10\xa2\xe8\x7b\xd0\x53\ -\x6b\x8d\x6e\x96\xeb\xf1\xd0\xc0\x20\x3c\xe4\x74\x1d\x8f\xd9\x6d\ -\x29\xe2\x39\x4c\x49\xf5\x1b\xec\x70\x6f\xd1\x8c\x5d\x89\xdb\xb6\ -\x79\xe7\x78\x79\x46\xe0\x86\xf5\x37\xb1\xad\xb9\x2d\x8a\x47\x29\ -\xb7\x10\xfa\x10\xb8\x75\xb2\x23\x48\x3c\x18\x13\x00\x8d\x8d\x11\ -\x56\xc5\x22\x78\x21\xb6\xba\xfc\x21\x82\x0a\xc0\x62\x71\x2c\x86\ -\x2f\x33\xfd\x28\xc2\x5c\x62\xbb\xbd\x0c\x82\x2e\x31\x9e\x11\x59\ -\x9f\x25\x6e\x0f\x53\x21\x9a\xfe\x87\xe3\xfc\x54\x2a\x4c\xbc\x04\ -\x85\x9f\x15\x40\xd4\x3a\x60\x5e\x09\x26\x3a\xa6\xb1\x12\xbc\x37\ -\xba\xf6\x8a\x15\xe0\xf8\xc4\x78\xde\x1f\xcb\x5e\xa4\x9b\x2c\x2c\ -\x49\x93\x0c\x9f\xa2\xa0\xe7\x40\x2f\x36\x5d\x7c\x4d\x6d\xc2\x6f\ -\x84\x60\xd1\x68\xa3\x0a\xe0\xf8\xe6\x4d\x71\x6f\x47\x07\xbc\xd5\ -\xdd\x09\x53\xed\x74\x05\xe8\x20\x87\x94\x8f\x4e\xb2\x61\x27\xc6\ -\x37\xbb\xe3\xe6\xba\x9b\xef\xe6\xe6\x85\xe3\xc7\xff\x89\xfd\xfa\ -\xb9\xa2\xac\xac\x3c\xfd\x41\x7e\xb4\x0e\x53\xa7\x7b\x02\xc5\x95\ -\x30\x3e\x74\x18\x85\xb0\xbb\x40\x14\x3e\x78\x0a\x72\xfb\x45\x61\ -\xb1\xa4\x00\x94\xbe\x61\xfc\x22\xcd\x75\xa7\xb2\x80\x37\x49\x08\ -\x3b\xda\x22\x6c\xe1\x22\x4c\x21\x12\x58\x13\xd4\x2a\xc6\xe6\xe9\ -\x64\x8d\xf5\xb4\xb8\xa5\x77\x04\xc1\x84\x30\x9e\xb0\x9b\x70\x99\ -\xb0\x96\xe0\x2c\xee\x48\xd1\x20\xf8\xb3\x03\x41\xbc\xfd\x5f\x08\ -\xbf\x42\x02\xce\x4b\xc1\x38\xc7\x0c\x51\x3d\xa0\xd3\x36\x7f\x1a\ -\xfe\x8b\xd6\x81\x69\xa4\xe8\xcf\x6c\xbf\x20\xef\x65\xd5\xf0\xb5\ -\xa7\x24\xa3\x7c\x93\xce\xf4\x1a\x0b\x09\xf7\x69\xfd\xa6\x21\xa9\ -\x73\xb8\x3a\x01\xde\xdb\x4c\xc4\x3b\x08\xc8\x06\xce\x22\x70\x90\ -\x7b\xee\x33\x9a\x1b\xba\x6a\x7c\xdb\xc8\xa9\x6e\x1e\x48\xc3\xb7\ -\xb3\x73\x43\x15\x15\x51\x17\x33\xed\xfc\x99\xf0\x23\x2b\xc8\xff\ -\x9f\xdd\xb0\x7e\xa2\xf0\x49\xf0\xdc\xbd\x25\x02\xcd\x3d\xa5\xf1\ -\x1d\x0e\x09\xaf\xad\x7f\x9f\xeb\xb3\x29\x37\xc7\xe7\x4f\xc2\x8e\ -\xdc\xf7\xbc\x5d\xf9\xd9\xbe\xfb\x3e\x64\xfb\xed\xfd\xf0\xd6\xd7\ -\x32\xfa\xde\x35\x48\x9c\x2d\x80\x08\xd7\x7c\xf0\x34\x7f\x0f\x1e\ -\x1c\xf2\xfd\x24\xc4\xd5\x84\x11\x04\x53\x82\xbc\x28\xd4\xec\xea\ -\x26\x70\x70\xb9\x5c\x54\x56\x56\xc3\x46\x8d\x9a\xa2\x9e\x5e\x6b\ -\x6c\xdd\xba\x03\x5a\x5a\xf5\xc1\x3f\x16\xb7\xfd\x3a\x01\x44\x8f\ -\x03\xce\x0b\xc1\x78\xa7\xcc\x32\x87\xe9\xa2\x8a\xe0\x81\xe9\x09\ -\xbc\x8e\xb3\x52\xd2\xd9\xd0\x75\xa6\xa5\xa2\x7a\xcf\x6d\x28\xa7\ -\xd5\xa1\xe2\xba\xea\xca\x80\x13\x1c\x00\x6f\x6e\x00\x2c\xba\x01\ -\x58\x7a\xbb\x9c\x9c\xd3\xf0\x4c\x22\xfc\xdc\xb8\x61\x32\xbc\xfd\ -\xae\x50\x30\x63\x84\xc3\x1b\x1a\xbc\xb3\xf3\x32\xd4\xd2\x6a\xc1\ -\x36\xeb\xdc\x68\x5d\xe9\xa7\x18\x0b\x20\x2f\x17\xfa\xc1\x18\x8d\ -\x3a\x8a\xbd\x97\x3d\x2f\x1a\xbc\x15\x4b\x09\x48\x19\xb9\x4f\x18\ -\x71\xa4\xe0\x4d\xe6\x19\x61\x06\xb2\x1c\xe3\xbf\x2e\xe8\x16\x1e\ -\x1c\x6b\x19\x1e\x8a\xc6\x31\xd7\x78\x90\x38\x0b\x45\x3c\x99\x91\ -\x0a\x67\xdb\xc5\x81\x17\x23\x14\x89\x40\xd9\x07\x28\xb7\x82\x8b\ -\x4d\x5c\x95\xb1\xfd\xb4\x26\xd8\x6b\xbd\xbe\x70\xd8\x49\xb3\xd2\ -\x95\x07\x37\xe1\xc9\x93\x8f\xfe\xc1\xdc\xdf\xef\x65\x8d\x5b\x1e\ -\x86\x2b\x96\x8e\xc5\xb7\xf7\x81\xff\x35\x12\xe4\xde\xe7\x3c\x13\ -\x8c\x1d\x18\x93\xd0\x36\x24\xa9\xcf\xbc\x77\x91\xda\x0b\x32\x51\ -\x65\xfa\x29\x94\xd7\xed\x4e\x1e\x4f\xe5\x13\x44\x54\x95\x00\x5d\ -\xec\x00\xaf\xae\x03\x2c\xbc\xfe\x29\xf4\x4a\x9c\x67\x82\x49\xf0\ -\x65\x59\x03\x65\x7d\x8f\x4d\x81\xec\x3d\x93\x98\x82\x09\x23\x26\ -\x08\xc6\x8c\xd9\x8c\x2d\x5b\xd2\x92\x83\xa1\x2d\x22\x77\x5a\x62\ -\xfd\x54\x83\x41\xf4\x99\x4d\x6b\xae\x8a\x6a\xba\x68\xbf\xf4\x39\ -\xb2\xe1\xb3\x0c\xdc\x54\xf4\x61\xd1\x9d\xd8\x34\x1a\xfe\xb6\x94\ -\x48\x61\xdb\x2b\x17\x91\x86\xcf\xa2\x7b\x6f\x47\xbc\x6c\xc2\xf4\ -\x97\xca\x4f\x5c\x1f\x37\x79\xe2\x12\x6d\x1e\xda\x27\xca\xfa\xa6\ -\xee\x8b\x7e\xde\x46\x85\xa3\x03\xcd\xd0\x35\xcc\x12\xdd\xc2\x2b\ -\xe3\x1a\x6a\xc5\xdf\x72\xce\x2b\x53\x32\xfc\xcd\x7b\xc2\x22\x68\ -\xf8\x2c\x8b\xd7\x79\x66\xbd\x7b\x20\x93\xfe\x45\x01\xfc\x98\x92\ -\xfc\x20\xe5\x80\x81\xee\x81\x17\x65\x02\x62\xa3\x21\x2a\x0a\x99\ -\x07\x0f\xb0\x37\xf9\xf8\xfa\xc8\x00\x2e\xe9\x08\x78\x71\x26\xe0\ -\xf5\x79\x80\xb7\x17\x41\x49\xc4\x06\x78\x99\xe1\x0e\x21\xef\x0f\ -\xc1\x43\x42\x60\xee\x61\xc2\x51\x08\xcc\xda\xc8\x5c\xb9\x34\x81\ -\x49\xf2\x72\x03\x5c\x37\x4a\xb6\x64\xdc\xc8\x05\x68\x65\x35\x00\ -\x65\x64\x2a\x9e\xf3\xed\x7e\xda\xd1\x40\xf1\xb3\xf8\x8d\xa2\x5a\ -\x33\x34\xe8\x3a\x0b\x5b\x74\x5f\x80\xc6\x76\x2b\xb1\x8d\xe3\x06\ -\x34\x1d\xb0\x0d\xcd\x9d\xf7\xa2\xdd\xcc\x95\xa8\x37\xd5\x15\x35\ -\x47\x0c\x47\x55\x5b\x1b\xd4\xed\x6d\x8c\x63\x3d\xb4\x71\xe6\x05\ -\x4d\x74\xbb\xa8\xfd\xe1\xb7\xc0\x16\xb1\xf3\x1e\x99\x06\xcd\x8c\ -\x6a\x17\x33\x2d\xd2\xe2\xc5\xd4\x70\xcb\xdc\xaa\xc1\x57\xa6\x7d\ -\xf1\xae\xbf\x8f\x24\xd0\xf0\xbd\x0e\xc5\x04\x8f\x5f\xf1\x29\x7c\ -\x96\x99\x6b\xee\xe4\xbf\xba\xab\x11\x47\x42\x2e\xcc\x8b\x90\x4b\ -\xfe\x98\xa4\x14\x5c\x98\xde\x80\x57\x9c\xaf\xe5\xc7\x2f\xd1\x89\ -\x2c\x15\xea\xbe\x2e\xc5\x66\x82\x37\x45\xda\x41\xbd\xa2\xa6\x05\ -\x40\x4c\x0c\x52\x34\x3d\x3c\x8a\x4e\x93\x8f\xef\x73\x5c\x56\x81\ -\x17\x7e\x66\xc0\x8b\x71\x84\xb0\x27\x83\xa1\xd0\xcf\x19\x42\x68\ -\xf8\xf3\x07\xab\x62\x4f\xdb\x91\xa8\xa4\xa4\xce\xce\x2c\x1a\xf9\ -\xaf\x18\x0e\xa6\x03\x16\x6c\x53\x8c\xe0\x4d\x7b\xbd\x24\xfe\xcc\ -\x7e\x18\x67\xc5\x1d\x23\xb4\x9d\xfc\x17\xc3\x40\x5e\x37\x67\x40\ -\xaf\x28\xc0\xfb\xf8\x4f\xee\x0a\x99\x92\xbf\x8b\xe5\xd3\x4f\x17\ -\x28\x3f\x39\xf4\xae\x41\xd8\xee\xf4\x26\xfe\x1b\x53\x9a\xf3\x56\ -\x26\x19\xf1\x16\xc6\xb5\x09\x98\x1b\x63\x11\xb4\xfb\xe2\x5f\xb7\ -\x7f\x59\x12\x14\x3a\x70\x61\x40\x80\xdd\x5c\x3f\x5e\x97\xd9\xde\ -\x3c\xd3\xe9\xb7\x02\xf4\xa7\x5c\x0f\x6f\x38\xe1\x52\x62\x83\x91\ -\x57\xef\xbe\x2b\x68\xf5\x88\x04\x8d\x35\x90\xbb\x3b\x86\x9b\xb5\ -\x3e\x8c\xc1\x7a\x31\xbe\x31\x54\x80\x11\xae\xae\xe1\x55\x03\x3f\ -\xcb\x85\x8f\x77\x74\x21\x24\xcc\x06\x78\xf1\x83\x20\x85\x84\x8e\ -\x92\x78\xf6\x02\x81\x4b\x2f\x0d\x6c\xa4\xa9\xc7\x36\xeb\x68\x25\ -\x56\xe9\x5f\x35\x1f\x40\xdc\x2c\x72\xa8\xd2\xf7\xdd\x91\x36\xd3\ -\x6a\xf8\xfa\x06\x74\x84\x8d\x36\xe1\xac\x07\x01\x7a\x44\x54\x2f\ -\xc2\x03\x84\xe2\x50\x52\x53\x48\x44\x78\xf8\x06\xc1\xa7\x10\x21\ -\xb8\x90\x2f\x9f\xb0\xe0\xfc\x6a\x1f\x9d\x8d\xf7\x6f\xc2\xc8\xd3\ -\x58\x2d\xc3\xcf\x47\x42\xb7\x50\x81\xae\xf3\xcd\xcc\x62\xa1\x5e\ -\x56\x75\x02\xf8\xa4\xa9\xf9\x6f\x08\x03\xa4\xcc\x0a\x33\x7e\x4e\ -\x04\x28\x76\xd7\xd4\xcc\xa5\xa1\x5f\x52\x81\x67\xfe\xe6\xf0\x20\ -\xd6\x11\x42\xe9\x5d\x4e\xe0\xc7\x3b\x43\x46\xf4\x00\x48\xf2\xb5\ -\x87\xe4\xd3\x5d\xe1\xfd\x36\x0b\xf2\x7d\x2d\x01\xad\x1a\x71\x59\ -\xd1\xaf\xd4\x96\x59\x44\x75\x69\x30\xa7\x05\xe1\x22\xfd\x00\x3b\ -\xf7\x07\xdc\x43\x2a\x5a\x97\x7d\x00\x03\x23\xa0\xa4\xb0\x98\x8e\ -\xc0\x41\x99\xe4\xb7\xf0\x12\xad\xa3\x7a\x6c\xbd\x9a\x62\xbd\xf1\ -\x56\x1e\x73\x3e\x39\xa5\x7a\x01\xce\x64\x82\x6d\xd0\x1b\xe8\x1a\ -\x86\x14\xfb\x79\xfb\x63\x49\xe0\x7c\xc9\xf0\xf3\x4a\x75\x22\xd9\ -\xf0\x59\xfa\xde\x72\xbb\x7a\x51\x1b\x6e\xdd\xb4\x82\xbb\xb7\xac\ -\x21\xf0\x82\x15\x3c\x3a\x6a\x06\x19\x07\xdb\x40\xd1\x01\x63\xc0\ -\xfd\x84\xe5\xcd\x01\xfb\x37\x00\xd4\x95\xaf\xd4\x32\xa1\x4d\xd1\ -\x3e\xd2\x19\x41\xff\x9d\x08\xd6\x84\x4b\xea\xea\xc0\x0f\x0a\xfa\ -\xe7\x97\xe4\x16\xd6\xcf\x99\x74\x64\x4f\x40\xd7\x4d\x37\x91\x62\ -\xbe\xd7\x2f\x1c\xee\x65\xd0\xb0\x73\xab\x08\x50\x06\xbd\x7d\xa2\ -\xd9\xf0\x59\x36\x9d\x9a\xe5\x2b\x21\x40\xa1\xc7\x23\xd9\x97\x6c\ -\xf0\x0b\xbc\x8c\xe2\x06\x19\xac\x0c\x18\x2c\xbf\xde\x9f\x06\x2d\ -\x89\x3b\xb9\xc3\x67\xe9\x00\xda\xa8\x92\x16\x81\x4c\xa5\xd0\xe9\ -\x80\xcd\x49\x3a\xe8\x25\x9d\x12\xf6\x7d\x45\x68\xa4\xac\x0c\xaf\ -\xee\xdd\xfb\xf4\xd7\x67\x43\x07\x05\xd9\x6c\xbe\xfe\x96\x0d\x9f\ -\xd2\xe0\xdc\x93\xd7\x22\x01\x26\x9c\x8f\xaa\x24\x40\xff\x9b\x3e\ -\x55\xc3\x67\x09\x4b\xec\xee\x4f\x05\x08\x7b\xdb\x80\xb7\x3e\x98\ -\x53\x3a\x7d\x6e\xaf\x87\x7d\x95\xff\x7c\xd4\x07\xf6\x21\xc5\x41\ -\x66\x51\x1a\x0d\x7d\xab\x21\x69\xfa\x35\x06\x34\x27\xed\x7e\x59\ -\xa6\x22\xf0\x5c\x82\x0f\x61\x1b\x61\x6c\x6d\x9f\x35\x5c\xe7\x67\ -\xf8\xc8\x93\x22\x76\xa7\x97\xa6\x70\xf0\x9e\xc3\x51\x92\xc1\x53\ -\xac\x37\xdf\xca\x65\xee\xa6\xa3\x48\x80\x05\x77\x78\x9f\x9e\xfb\ -\x17\x42\x49\xd0\xc2\x9a\x04\x90\xef\x11\x58\x1c\xff\xdc\xe2\xbe\ -\x4b\xdf\xf1\x3e\xf6\x9c\x3d\x19\x6c\xf0\x2c\xa6\xdc\x5e\xa8\xa7\ -\x00\xc8\x94\x07\xfe\x4c\xdc\xfb\x48\xbb\xb9\x0d\x7e\xf4\xb0\xb6\ -\x54\x80\xca\x02\x4c\x94\x51\x50\xc4\xf6\x73\xfe\xc4\xaa\xe1\x53\ -\x4c\x3c\x02\x9e\x88\xc2\xa7\x6c\x0f\x7d\x28\x16\x20\x0d\x6c\x42\ -\xde\x57\x13\xfc\x47\x85\x2e\x01\x61\x7a\xed\x2f\x3d\xe8\xd9\x6a\ -\x5b\xdc\xd0\xa6\xab\x5f\xf6\x01\xf7\x5c\xc9\xe0\x7b\xc3\x4e\xd4\ -\x02\x4b\xf6\x4e\x4f\xa1\xd7\xff\x9e\x03\x33\x52\x01\xbe\x5d\x80\ -\x4d\x1a\x46\x6d\x70\x52\x50\x2a\x0e\x38\x19\x14\x42\x42\xff\x20\ -\x29\x80\xfa\xc5\xa4\xdc\x0a\x01\xce\x3e\x7b\x41\xc2\xe7\x43\x2f\ -\xbf\xc7\xe2\xc0\xf9\xb2\xd6\x41\xd1\x3a\xed\xaf\xde\xef\xda\x7a\ -\x6f\xf8\x30\xfd\x95\x45\x23\xf4\x97\xa3\x24\x83\x1a\x6c\x08\x92\ -\x14\xa0\x09\x54\x0c\xc7\x7a\xd0\x29\x66\xd2\xa5\x61\xff\x7b\x01\ -\x22\xf4\x7b\xf5\x17\x09\x40\x19\x73\x3f\xf9\x79\xb7\xcd\xb7\x12\ -\xc5\xc5\x7f\x36\x73\x4f\x5c\xfc\x53\xee\x66\x94\xc9\x0d\xb8\x7a\ -\xb9\x91\xd5\xad\x7b\x1d\x4c\x3c\x03\x9c\x0d\x57\xbf\xaf\x1a\x78\ -\x75\x38\xca\xef\xf0\xa3\xe1\x77\x83\x35\xa4\xc8\x17\x75\xfb\xae\ -\x94\x2e\x0f\xaf\x25\x6b\xf6\x09\x65\x6d\x27\xcc\xae\x10\x80\x32\ -\xf1\xe1\xab\xa2\xde\x1e\xbe\xbe\x66\xfb\x7c\x83\xc8\xf3\x3f\x53\ -\xf5\x68\x9c\xaf\xd9\xf4\x4b\xbc\x41\x6d\xff\x48\xfd\x9a\xc0\xab\ -\x32\x5c\x6f\x45\xa1\x0d\xb3\xb6\xb4\x29\xd8\xb0\x13\x31\xe4\xa4\ -\x02\xd4\x0e\x01\x3a\xd0\xe2\xd8\x76\xd5\x8e\x4a\x02\x50\x5c\x1e\ -\xa6\xc4\x1b\xfd\x7a\xe1\x82\x8b\xe9\x2a\xff\xb1\x6d\x56\xf8\x8d\ -\x36\x5e\xc6\x1b\x65\x44\x59\xee\x3f\xd2\x60\x45\xe8\x2f\x06\xcb\ -\x1f\x8f\xd0\x5f\xf6\x92\x04\x9c\xf7\x35\x12\x74\x6b\xe0\x82\x1c\ -\x90\xa5\x17\x5d\x22\xdd\x20\xa2\xf6\x08\xe0\x4a\x05\xe8\x7f\xe0\ -\x8a\x64\xf8\x02\xbb\x7b\xb1\x3e\x9c\xc3\xe7\xf8\x30\xe7\x52\x54\ -\xc3\xf1\x27\x92\x27\x59\xae\x4c\x99\xd2\x7e\x05\x7e\x06\xfe\xa4\ -\x76\x2b\x32\x27\xb6\x5d\x91\x34\xc1\x74\x79\xd4\x78\x93\x15\x81\ -\x63\x5b\x2f\xf7\x1d\x63\xbc\xc2\x67\xa4\xd1\x72\x1f\x6b\xad\xd1\ -\x69\xb2\x1c\x79\xb6\xeb\xb6\xa1\x54\x80\xda\x23\x00\x9d\x40\x8a\ -\x63\x6e\xc7\x8a\xc2\x9f\x10\xf8\xea\x75\xd3\x73\x77\x62\xe0\xd0\ -\x59\x14\x31\xfb\x72\x02\xcc\xb8\x8c\x72\x6e\x17\x72\x47\x76\x59\ -\x13\xfa\x05\x09\x24\x29\x1d\xd5\x6e\x75\x58\x3b\x83\x9d\x01\xb2\ -\x8d\x8f\x15\x80\xa2\x93\x50\x5c\xf1\x3b\x26\xdd\x22\xa6\x76\x09\ -\xb0\x55\xbe\xbe\x9a\x28\x7c\xe7\x07\x4f\x82\xe4\x8e\x5e\xc8\xab\ -\x08\x9f\x32\xe3\xca\x6b\x2a\x40\x39\x97\x84\xbd\xfb\x6c\xf7\x21\ -\xe1\x0a\x6b\x08\x5d\x30\xb6\xed\xef\x51\x1d\x8c\xb6\xfb\x29\x68\ -\x1d\xcd\x06\xcd\xe3\x58\x81\x7c\x45\xb3\xcf\x46\x2a\x40\xed\x12\ -\x60\x87\x9a\xb1\x29\x1a\x6e\xf2\xc0\x4a\xc1\xb3\x4c\xbf\x94\xff\ -\x49\x80\x72\x8c\x46\x1c\xa2\x25\x41\x2e\x1b\xfc\xb8\x76\xab\x1e\ -\x5b\x1b\x6f\xe5\x29\x69\x1d\xce\xac\x14\xba\x24\x32\xba\xec\x28\ -\x25\x23\x15\xa0\xf6\x84\xdf\x5d\xb4\x79\x53\x27\x13\x04\xa7\x65\ -\x08\xbb\x8f\xa5\x57\x0a\xff\xe0\xd9\xb2\xaa\xe1\xb3\x68\x8c\x39\ -\xf1\xd0\xc6\x78\xab\x8f\x6a\x93\xc3\xaf\x6a\x0c\x5d\x12\x46\x81\ -\x5e\x70\xa7\x74\x97\xb0\xda\x13\x3e\x9d\xdf\xff\x1e\xe4\x49\xad\ -\x7c\x8e\x1d\x42\xa7\xf9\xaf\xc1\xca\x4b\x00\x53\x8f\xf9\xc3\x81\ -\x33\xaf\x45\x02\x78\x9d\xcb\xae\x14\xfc\xb4\xcb\x2f\x60\xe0\x79\ -\x1e\x18\x9d\x7c\xf6\x55\xa1\xb3\x34\x70\x67\x8b\xff\x61\x52\x01\ -\x6a\x8f\x00\x83\x45\xa1\x74\x32\x40\x58\xe0\x88\x30\x75\x54\x12\ -\x58\x7a\xa1\x88\x8e\x5e\x25\xf0\xdb\x5f\x3c\xd8\x75\x2e\x86\x3c\ -\x02\xd2\x60\x18\x09\xbd\xcd\xc9\x27\xdf\x14\xba\x24\xf5\x67\xb0\ -\xa3\x79\x9a\x52\x01\x6a\x8f\x00\xbb\x80\xcb\x21\xcf\xf8\x9e\xe5\ -\x02\x50\x3a\xed\x8e\xae\x90\x80\xd2\xc1\x33\x48\xad\xe1\xb6\x6b\ -\x8a\x1a\xfb\x83\x48\x90\x1f\xfe\x63\x01\x64\x8d\xe9\x05\x4f\x4a\ -\x77\x0a\xad\x5d\x02\x3c\x06\xb3\xa6\x9f\xc2\xa7\x8c\x9e\x11\x54\ -\x49\x00\xf3\x6d\xbe\xd0\x72\x0d\xea\x68\x2e\x78\x68\xcd\x4c\xcb\ -\x32\x52\x5c\x19\x5c\x4f\xdd\x33\x80\x84\x9a\xfb\xd5\xe1\xab\x6f\ -\x62\x8b\xff\x8e\x52\x01\x6a\x59\xef\x1f\x8c\x53\x25\xcf\xff\x9e\ -\xf9\x9f\x24\xe8\x5b\x46\x82\x4f\xab\x10\xc0\x78\xc3\x0b\x2a\x00\ -\x45\x51\x7f\x55\x6a\x17\x8e\xdb\x23\x1b\x98\x82\xdd\xc0\x95\xdf\ -\x4a\x61\x69\xb8\x8a\xba\xbb\x1f\x09\xf9\xed\x67\x05\x50\xb4\xa3\ -\x17\x8c\x97\xee\x15\x5c\xbb\x04\x38\x06\xaa\x44\x00\x4f\x82\xbb\ -\xcc\x47\x58\xd6\x2d\xa0\x42\x82\x7e\x2b\x7c\xca\x05\xf0\x7c\xc5\ -\x86\xcf\xc2\x18\xad\x29\x6d\xad\x3c\xdb\x87\x48\x20\xa0\x22\x88\ -\x11\x98\xc8\x2d\x89\x56\x53\xdb\xc3\x63\x34\x8f\xa5\x55\x0a\xbf\ -\xe1\x41\x52\xfb\x57\xa4\x17\x9c\x23\x15\xa0\xf6\x84\xaf\x44\xf8\ -\x08\xb6\xe2\x05\x21\x2c\x9b\xf4\x1e\x12\x01\xf2\xe0\xd7\x41\xd9\ -\x44\x80\x22\xb0\xd8\xe3\x5b\x55\x00\x16\x0d\x9d\x25\xd1\xa4\x14\ -\x48\x97\x90\xa0\x02\x33\xd9\x05\x71\xea\xaa\xbb\x78\x8c\xc6\x4e\ -\x3e\xa8\x4c\xa2\x17\x2c\x20\xa8\x49\x05\xa8\x6d\xb5\xff\x39\x55\ -\x04\xa0\xec\xad\x97\x0a\x8b\xed\x62\xa1\xc7\x66\x7f\x30\xd9\x12\ -\x52\x93\x00\x14\x19\xc3\xd5\xef\x3b\xca\x4e\x0d\xae\x4e\x02\x16\ -\x65\x68\x8c\x3f\xcb\x5e\xc0\x3f\x93\x00\x5d\x45\x6b\x08\xa6\x56\ -\x23\x00\xc5\x93\x29\xe3\xce\xed\x7d\x9d\xd3\x6a\xed\xeb\xcf\x09\ -\xc0\xa2\xaf\x31\xd7\x8f\x84\x5d\x58\x35\xfc\x8e\x30\x0a\x45\x25\ -\xcd\x4f\xdc\xf4\xab\xcb\x75\x80\x67\xd0\xaa\x5a\x01\x3e\x5a\x9c\ -\x52\xe1\x9d\x08\x36\x7f\xf7\x21\x5d\xe1\x65\x7c\x58\xc3\x94\xcd\ -\x3b\xba\x05\xd8\x8e\x9c\xc8\x53\x6d\xbf\xec\x11\x09\xbc\xb8\x3a\ -\x09\x94\x9b\x2f\x7f\x6a\xcd\xb8\x26\x49\x0a\x60\x00\x9d\xbf\xeb\ -\x4e\x5f\x52\x01\xbe\xaf\x00\x0f\x44\x8f\x81\xf5\x50\x26\x0e\xbe\ -\xc8\xfc\xb4\x0a\xef\x78\x90\xf9\x9b\x1b\xe1\x96\x48\x89\x4f\x6a\ -\xea\x8b\x6f\xc8\x97\x4b\xc0\xcf\xe0\xf2\x7d\xef\x36\x8f\x9f\xb7\ -\xca\xc1\xd7\xbc\xef\xf4\x00\x79\xd3\x55\xcf\x88\x00\x42\x2a\x01\ -\xc7\x68\x75\x91\xb9\xe2\x4c\x5f\x56\x80\xfa\xe5\xc5\xbf\x41\x0d\ -\x13\x50\xe8\x32\x73\x2d\xf1\x76\x33\x2d\xc4\x4b\xde\xe9\x6a\xa7\ -\x5e\x84\xe1\xe2\xbd\x0b\x56\x8a\x17\x7e\x3c\x15\xcf\x0e\x3e\x21\ -\xde\x0d\x65\xad\x78\x51\x0c\x5d\xc2\x3e\x44\xbc\xd1\x83\xd5\xff\ -\x7a\x8c\xa1\x2e\x85\x6f\x4a\x67\x00\x81\x31\x09\x7e\x3b\x60\xeb\ -\x03\x4a\xe1\x47\x03\xcd\x32\xd9\xe0\x59\xbc\xa3\xcc\x43\xaa\x0a\ -\x50\x1d\x39\xcf\x14\xf2\xce\x9d\x6d\x13\x31\x76\xf6\x50\x9f\x66\ -\x36\xf3\x42\x74\x1a\x8f\xce\x6d\xcf\x0c\xa3\x17\x12\x8a\x17\x70\ -\x44\x88\x43\x7c\x2b\xee\x0d\xc4\xaf\x45\x56\x59\x09\x1b\xb4\x6f\ -\x8b\x6d\x46\x4f\x47\xdb\xa1\xd3\xd0\xa2\xa7\x33\x1a\xb6\xed\x8a\ -\x8d\x74\x5b\xa0\x82\x52\xfd\xaa\x5f\xef\x4b\xd7\x4d\x4a\x05\xf8\ -\x7c\xf8\xa2\x0d\x1e\xb8\x26\x0c\xea\x2d\x55\x44\xf7\x7b\x6d\xf2\ -\xab\x06\x2f\xc1\x07\x41\x16\x53\xf2\x35\x12\x88\xc8\x84\x14\x7c\ -\xc6\x04\xf8\xb8\xcb\xf0\x17\x2e\x6b\x8d\x93\xcf\x0d\xc7\x89\x67\ -\x86\xe1\x84\x53\x43\x71\xdc\xf1\x21\xe8\x72\x6c\x30\x8e\x3d\x3c\ -\x08\x47\x1f\x1c\x88\xa3\xf6\x0f\xc0\x5f\x3c\xfa\xe3\x88\x7d\x4e\ -\x38\x7c\x77\x5f\x1c\xba\xd3\x11\x87\x6c\x77\x40\xe7\xad\xf6\xe8\ -\xf0\x7b\x1f\x6c\xbb\x68\x16\xda\xfe\xe5\x89\x8e\xf7\xff\x46\xcd\ -\x33\xe7\x0a\x06\x5c\x0a\x2b\xf6\x0a\x47\xac\xca\xde\xc0\x22\xb4\ -\x58\xf8\x17\x82\xe9\x04\x04\x35\x03\xfa\x0b\x66\xfe\xaf\xb6\x9e\ -\xaf\x2b\x02\xf8\xa9\xb4\x51\xc0\x31\xbb\x9a\xe1\xce\x73\x6d\xf0\ -\x82\x9f\x45\xe9\x67\x04\xc0\x8c\x97\x6a\x91\xd5\x86\x9d\x05\x05\ -\xf8\x9a\x89\xc4\x58\xae\x0f\xde\x96\x0d\xc5\xa3\x0a\xd9\xb8\x5f\ -\x11\xf9\x5e\xf5\x9e\xfd\x7d\xd2\xbc\x6c\xc1\xb9\x01\x25\x8b\xc2\ -\xdd\xf0\x5b\x19\x7b\x73\x59\x92\xc3\x9d\x8b\xd9\x8e\xde\x57\x91\ -\xd2\x34\x24\x24\x12\x22\x9f\xe0\x9a\x88\x92\x77\xd5\x09\xe0\xf2\ -\xe0\x59\x02\x2c\x79\x16\x07\x83\x6e\xbe\x03\x7b\x2f\x04\xc5\x86\ -\xf4\x97\xec\x2b\x15\xa0\xfa\xf0\xe9\xd6\x2f\xd8\x7c\xa5\x16\x76\ -\x7a\x6e\x52\x41\xef\xa7\x26\x6f\x5d\x12\xcd\xa2\x97\xc6\xb5\xf5\ -\xdb\x17\xd3\x8e\x77\x26\xd2\x22\xec\x5a\x84\xe5\x2b\x22\x80\x20\ -\xf4\x91\x11\x8f\x04\x2e\xc4\x0c\x78\x8a\x49\x8c\x3f\x06\xc8\xf8\ -\xe1\x59\xf9\x64\x3c\xa0\x28\xa0\x81\x4b\xf2\xf1\x88\xfa\x43\x1e\ -\xaf\xeb\xc7\x7b\xbe\x36\x69\x8b\xc3\xdd\x84\xdf\x12\xfc\x82\xb0\ -\x99\xb9\x83\x79\x27\xfd\xd9\xe0\x29\x7a\x41\x41\x31\x34\x7c\x42\ -\x91\x67\x35\xe1\x2f\x0e\xcb\x4f\x80\xa0\xd0\x5c\x98\x23\xcc\x81\ -\xd9\xa5\x6f\xc1\xe1\x00\x82\xac\x32\xfd\x45\xb7\x49\x05\xa8\x5e\ -\x00\xba\x32\x18\xdb\xde\x6f\x51\x49\x80\x6a\xe0\xf7\x4e\x6e\x15\ -\xbb\x3c\x58\xcf\xfb\xe9\x85\x06\xa1\x78\x44\x21\xaf\x6a\xd8\x55\ -\xe0\xa7\x9e\xd7\xe3\xdd\xf7\xb5\x41\xca\x59\x7f\x47\xde\xb7\x84\ -\xef\x16\xb8\x26\xcc\xd1\xfb\xef\x0c\xc9\xf0\x0d\x03\x03\xe3\xc4\ -\xe1\xa3\x62\x44\x62\x42\xd5\xf0\x37\x86\x17\xa7\x71\x82\x82\x33\ -\x95\xce\x3e\x8a\xa2\x7d\x8c\x22\xfa\x9e\x10\x02\x47\x86\xfe\xa2\ -\x7b\xa5\x02\x54\x2f\xc0\x19\x86\xcb\xa0\xf9\x9d\xca\x02\x74\x7c\ -\x6e\x52\x6c\x9f\xd8\x2a\x7a\x71\xb0\x9e\x8f\xf7\x95\x86\x91\x45\ -\x87\x94\x3e\x7e\x21\xf0\x4f\x1c\xa8\x97\x19\x75\xd3\x3c\x86\x0d\ -\x9f\xb2\x31\x78\x4c\xfc\xd7\x04\x3f\x2f\x6c\x46\xde\x60\x9f\xe3\ -\x95\xee\x7a\x4a\xcb\x87\x01\x09\x6c\xf8\x14\xfd\x88\x94\x20\xc9\ -\xf0\x77\x86\x97\xe5\xc9\x06\x85\x24\x43\x50\x10\x26\xa4\xf4\x0d\ -\x4b\x7a\xe4\xec\xab\xb7\xfc\x75\x22\x8c\x89\x65\x2b\x83\x9e\x52\ -\x01\xaa\x17\x60\x8f\x68\xc3\xa7\xfa\x5c\xb4\xd8\xa3\x8d\x13\x8f\ -\x68\xe7\xfa\x5f\x6a\x18\x5d\x72\xb0\x5e\xd1\x57\x07\x2e\x01\xff\ -\xa0\x4a\x8c\xbf\x77\xe7\x2c\xc9\xf0\xef\xfa\xd9\xbe\xfc\x9a\xf0\ -\x5d\x83\xd6\x84\x3b\xde\xbf\x92\x5e\x35\xfc\xd6\x81\x7e\xc9\x24\ -\x74\xa1\xa4\x00\x76\x91\x59\x81\x6c\xf8\xee\xe1\x42\x7e\xfd\xa0\ -\xf0\x48\x1a\xbe\x6a\xf0\x83\x22\x4c\xb3\x2c\x26\xa0\x20\xd5\xb2\ -\x70\xc5\xfa\xbe\xa8\xd0\x40\xb4\xdf\x6f\xa9\x78\xc3\x08\x39\xa9\ -\x00\xd5\x6f\x9b\x5e\xd8\xc3\x1a\xf0\xdc\x06\xe6\x4d\xf6\x2e\x05\ -\x7f\x12\x66\xf1\xb7\x86\x9f\x73\xb2\x31\xcf\x9b\xd7\xad\x54\x32\ -\x7c\xca\xe9\xc0\xbe\x3e\x9f\x7d\xd6\x87\xce\xc8\x77\xe6\x1d\xf7\ -\xab\x1a\x3c\xc5\x24\x80\x97\x42\x02\x17\x48\x86\x4f\x99\x19\x51\ -\x10\xc7\x0a\xa0\x13\x1c\x13\x40\xc3\xa7\x2c\x7d\xbc\x26\x84\x86\ -\x2f\xc9\x8b\x60\x33\x1c\xda\x4f\x9d\x2d\x09\x62\xfe\x3f\xf7\x0a\ -\xaa\x4b\xfd\x00\xf7\xb8\x5c\xc0\xb4\x58\x10\x86\x9c\x86\xe0\xe3\ -\x33\x98\xb7\xc9\x9b\x64\x7d\x84\xfb\x15\x33\xbf\x22\xfc\x0f\xc9\ -\x57\x5a\x3e\xac\x1a\x3c\xcb\xfa\x90\x71\x49\x35\x3e\xeb\x83\x7e\ -\x8f\xe8\xeb\x7d\x25\xad\xba\xf0\xcd\xfd\x7d\x52\x49\xd8\xa5\x55\ -\xc3\xa7\x6c\x8f\x28\x2b\xa0\xe1\x5b\x86\x26\xf9\xb0\xe1\x53\x96\ -\x4f\x33\xf6\x7e\xb9\x59\xfb\x9a\xf0\x6e\xab\x20\x0c\x34\xf1\xc3\ -\xc7\xe6\x91\xfc\x64\x8b\x3c\x2a\x82\xcf\x85\x96\x68\xde\x5a\x91\ -\x9d\x85\xb4\xea\x7b\x37\x0d\xc5\xd3\xe9\x74\xea\xaa\x00\x4b\xe9\ -\x1d\x12\x78\xb3\xbc\x49\x97\x74\x1f\x7c\xe9\x86\x4b\x07\xa6\x02\ -\x3f\x68\x95\x4c\x00\xdf\x5d\xe1\x71\x75\xe1\x0b\x0f\xd6\x7b\x11\ -\x72\xa7\x7d\x72\x4d\xe1\xdf\xf5\xb5\x7d\x56\x5d\xf0\xf3\xc3\x67\ -\x14\x38\xf3\xfe\xaa\xf6\xae\xa7\xb4\xbf\xb6\x2b\x9d\x7b\xf7\x98\ -\x3f\x44\xc6\xa7\x55\x0d\x9f\x13\xf1\x24\x83\x86\x3f\x20\xe4\xb5\ -\xbf\x64\xf8\x1a\xb7\xce\xe7\xae\x19\x02\xc2\xdd\x7d\x74\x7c\x22\ -\x5c\x14\xee\x15\x1c\xd6\x7d\x8c\x37\x5a\x22\x45\x78\xc3\x28\xf7\ -\xd5\x09\xc3\x77\x01\xee\x7a\xb8\x75\xa6\x16\xb6\x6c\x2a\x87\xe2\ -\xce\x28\xd3\xef\xf0\xd9\xa9\x11\x22\xc5\x9d\x5c\x74\xc7\x55\xd5\ -\xba\x28\xc0\x1d\x83\xe6\x80\xc2\xac\x4f\xed\xfa\xf4\x10\xf0\xf3\ -\x9a\x0a\x42\x2a\x02\xe5\xfa\x7c\x4e\xec\xfb\x5d\x0a\x0f\x69\x0d\ -\x9f\x86\x5f\x74\x44\x35\x98\xf7\xc0\x3a\xbf\xa6\xf0\x29\xa7\x02\ -\xfa\xfd\xa3\xf8\x9f\x1a\xb4\x2a\x92\xdc\xf5\xaf\x6b\x0a\xbf\xf3\ -\x8d\xfd\x6f\xc0\xab\xef\x47\xf0\x72\x44\xf0\x72\x2a\x83\x4b\xeb\ -\x83\x20\x24\x3c\x9a\x15\x40\x2d\xf2\x69\x98\x5b\xd8\xbb\x08\x12\ -\x7a\xa9\xa4\x00\x43\x56\xf5\x8d\x20\x02\xe0\xba\xc1\x72\xa9\xbb\ -\x7b\x19\xe6\x06\x3b\xcb\x06\x26\xcd\x50\xf4\x2d\xbb\xd4\x22\x97\ -\x15\x81\xa5\xf8\x8a\x11\xf6\xb5\xaa\xd8\x33\x79\xda\x7f\xb9\x49\ -\xe7\xd5\x7a\x5a\xda\x68\x7f\xec\x0a\xfb\x98\x59\x5b\xd7\xea\x00\ -\xb4\xef\x5d\xb8\x6e\x89\x38\xfc\x34\x48\xc3\x27\xc0\xc3\x48\x48\ -\x4c\x3c\x09\x37\xf7\x4f\x85\x52\x56\x02\xca\x89\x59\x4c\x46\xd4\ -\x8e\xfa\x37\xef\xf3\x6c\xde\x7e\x2e\x7c\xca\xba\xb0\x71\xcf\x2b\ -\xee\xfa\xb0\xe9\x1f\x86\xfa\x1e\xf3\x25\x21\x0b\x6b\x0a\xdf\xe6\ -\xf6\x5f\xef\x18\xaf\xbe\xef\xcb\xc3\xaf\xc2\x09\xb7\x24\xf0\xbf\ -\xe3\xa7\x1d\x96\x78\x8b\x04\x9e\x2f\x19\x3e\x13\xf8\x10\x57\x0e\ -\x97\xcf\xa2\x02\x50\xb6\xda\x37\xe1\xed\xe8\xd9\xa2\xcc\x7f\x90\ -\x82\x5f\xd4\x30\x78\x9b\xb5\xb1\x61\x40\x55\x09\x3e\x5e\x6a\x85\ -\xc3\xac\x0d\xd8\xd0\x8e\xfe\x27\x87\x58\x93\xd7\x64\xfa\xfd\x03\ -\x2e\xf3\xd0\x2d\x0b\x51\xd5\xc0\x88\xfe\xe5\x8d\xba\x26\x40\x7f\ -\xfa\x4b\x5c\xdc\xc7\x60\xb6\x0f\xa4\x60\x38\x08\x09\x28\x26\xeb\ -\xce\x16\x08\x22\x12\x14\x4a\x4a\x40\xd9\x3f\x8d\x53\x74\x7e\x4f\ -\x33\xbf\x7b\xde\x5d\x13\xaa\x0b\xff\xb6\x6f\xf7\xc4\x8a\xbb\x3e\ -\x70\x55\x54\x5f\xef\xbf\x53\x6b\x0a\x9e\xd2\xe3\xce\xe9\x5c\xce\ -\xfe\x7e\x69\xd5\x86\x4f\x60\x3c\xec\xcb\x1a\x4d\x33\x0e\x30\x1c\ -\x62\x98\xac\xb7\x65\x6b\x68\xf3\x9d\x1e\x48\x69\x46\xff\xbb\xc3\ -\x1d\x7f\x99\x65\x1b\x4b\xc2\x2f\x13\x49\x30\x98\x93\x47\x04\xc8\ -\x26\xe0\xbd\x01\x4a\x3e\x51\x43\x01\x1f\x8d\x96\x89\x2a\x3c\xd1\ -\xfc\x99\xa4\x04\xd9\xa7\xac\x70\x56\xef\xa1\xa8\x28\x5b\x8f\xad\ -\x20\xb6\xff\x86\xcf\xad\x3e\x2d\xf2\x9b\x3b\x0c\x14\x85\x4f\x69\ -\xe9\xe2\x4a\xff\xc1\xa7\xae\x09\x20\xea\x0d\xfc\x63\xa3\x1d\x66\ -\xdd\xe3\xbe\x91\x08\x5f\xc4\x9b\xdb\xe0\x73\x6b\x13\x3c\x3e\x38\ -\x1d\x72\xaa\x4a\x50\x51\x2a\xac\xd1\x88\xba\x7d\xa3\x63\x30\x09\ -\xbe\x8c\x15\xe0\xaf\xc0\x01\x3e\x0b\xc2\x67\x92\xbb\xfe\xe8\x67\ -\xef\x7a\x8a\xdd\xdd\xf3\x1f\xb8\xfb\x07\x24\xd7\x14\xbe\xca\x72\ -\xab\x44\x43\xa7\x86\xa9\x2d\x1c\x35\x50\x8c\xd0\x60\xf6\x48\x6f\ -\x12\x7c\x3e\x0d\x9f\xc5\x7a\xe5\xac\xcc\x95\x43\xb9\xb4\x2e\x80\ -\x9b\x1c\x1b\xfa\x53\x01\x28\x57\x9d\xd4\x7c\x89\x04\x42\x42\xc9\ -\xf3\xb9\xca\x3e\x82\xab\x46\x85\xac\x04\x39\x67\x2c\x84\x2b\x9d\ -\x96\xa2\x89\x56\x3b\x14\x0d\x88\x01\xec\x24\xa8\x7c\xc5\xe7\xb6\ -\x96\x23\x23\x83\x23\x1e\x26\x60\xdf\x98\x8c\xcc\x46\x17\x1f\x16\ -\x28\xfc\xf6\x3b\xfd\x87\xb8\xba\x26\x00\x1d\x8a\x15\xae\xde\x3e\ -\x0e\xb7\x7a\x4d\x2c\x4b\xf1\xd1\xe1\x55\x29\x05\x0a\xfd\x76\x41\ -\xfa\xed\xcd\x90\x7c\x70\x26\x64\xd6\x24\x01\xe5\xf0\x7c\xc5\xd4\ -\xab\x67\x4d\x79\xe4\xf1\x90\xb3\x30\x68\x81\x8f\xe3\x83\x2b\xaf\ -\x68\xc0\x0e\x77\xaf\x7c\x74\xb8\x76\x3e\xd3\xfe\xf2\x99\xe7\x76\ -\x67\x8e\xc7\xd9\x1d\x3d\x14\xd9\x6b\xbf\x57\x70\xcf\xbd\x7b\xfd\ -\x7b\x6c\xdb\xce\x93\xdb\x3f\x38\xba\xba\xe0\xe5\x36\xdb\xe4\xeb\ -\xf6\x6b\xf4\x86\x0d\xde\xa0\xb7\xba\xd0\xb0\x8f\xba\x90\xfd\xb3\ -\xfe\x40\x83\x2c\xdd\xd5\x6b\x93\x25\x25\x68\xbd\x71\x03\x4e\xee\ -\xa3\x80\xbf\x3b\x33\xb8\xd1\x56\xff\x2d\x2b\xc1\x05\x27\xcd\x87\ -\x44\x00\x3e\x2d\x0d\xa2\x87\x33\xa9\xd9\x3b\xb5\x42\x59\x09\xf2\ -\xcf\xb5\x13\xac\xed\xbf\x01\xc7\x58\x4d\x45\x25\x39\x51\xd7\xf1\ -\x6b\x3a\xac\xfc\x99\xcf\x8b\x4a\x82\xda\xd6\x63\xd0\x72\xd7\x89\ -\x8f\xca\xa1\x19\xa5\x04\x94\x9b\x30\x87\x6d\x65\xc8\xd5\xb5\x4a\ -\xe0\xb3\x65\x1b\x47\xe3\x16\x2f\x37\x11\xd7\xce\xdb\x85\x0b\xc3\ -\x98\x4c\x56\x82\x7c\x5f\x08\x20\x8f\x02\x24\xbc\x3e\x3c\x0b\x52\ -\x6a\x12\xc0\xd3\x0d\xf2\x56\x4d\x82\x80\x4e\x2e\x72\xc1\x5d\xa6\ -\x2d\x88\xef\x3a\x75\x41\x0e\xa1\x8c\x80\x35\xd1\x74\xce\xf0\x7f\ -\x14\xfb\xdc\xdd\xbd\x51\xd3\x59\x57\xa8\xd7\x4b\x0d\xb5\x2c\x94\ -\x51\xad\xb9\x02\xca\xab\x70\x45\x67\x1b\x30\x1c\x40\x45\x0d\x19\ -\xd4\x68\xa1\x88\x3a\x1d\x55\xd0\xa0\x8f\x06\x36\x1e\x6c\x87\xcd\ -\x36\xef\x14\x09\x50\xbf\x57\x1f\xe4\x90\xaf\xeb\x63\x0a\x38\xa3\ -\x9b\x12\xb2\x02\x50\x4e\xd8\x6b\x45\x44\x0e\x85\x0f\x54\x02\x4a\ -\xdc\x04\xd9\xe0\x92\x33\xfa\x69\x54\x82\xc2\x4b\xe6\xa5\x1b\x06\ -\xae\x2f\x59\xe3\xb4\x1b\xcd\xb5\xad\xd8\xba\xc1\x55\xf6\xc0\x28\ -\x71\x91\x4f\x0f\xa5\xc8\xaa\x27\xd7\x0c\x5b\xb6\xde\x89\xcc\xaf\ -\xe1\xc2\xdd\x57\x03\x72\x56\xde\x7f\x94\xde\xe8\xef\xd0\x62\xb9\ -\x21\x2e\xec\xee\xea\x75\x4e\x80\x93\x0b\x56\x8f\xa8\x10\x80\xb2\ -\xe7\xd0\xb8\xec\xbc\x20\x95\x60\xb1\x04\xc2\x90\xfd\x10\x2f\x96\ -\xe0\xdd\xd1\x39\xf0\x44\x22\xf4\x82\xd5\x93\x21\xa0\xdb\x28\x6e\ -\x88\x8c\xb3\x4c\x31\x0c\x96\x41\x4a\x8b\xa1\xf6\xaf\xab\x0b\xdc\ -\xda\x6d\x3e\x5a\x0c\x9f\x80\x46\x3d\x1c\xb1\xf1\xb0\x5e\x08\x1e\ -\x0e\x08\xfb\xec\x11\xf6\xf4\x41\xd8\x65\x87\x32\x0b\x3b\xa0\x72\ -\x5b\x75\x94\x53\xe6\x4a\x8e\xeb\x17\x88\xb7\xbe\xa5\x5b\xdd\x9e\ -\x13\xef\x1e\x26\xfa\x37\x56\x08\x15\xa3\x26\xa8\xd4\xbe\x03\x5b\ -\x8c\xff\x45\x8f\xb1\x33\x6e\x02\xf8\x9b\x95\x4e\x25\x09\x0e\xf5\ -\xd6\x49\x88\x1a\x02\xd9\xac\x04\x84\x0f\xaf\x96\xa9\xf2\x84\xd7\ -\x8c\x4a\x4b\x2e\x99\xf1\xb7\x0d\xdd\xf2\x66\xbb\xf3\x51\x9c\xd4\ -\x79\x0e\x1a\x36\x34\x66\xa7\xb0\xd1\x93\x48\x72\x64\x38\xca\x68\ -\xd2\x64\x25\x76\xea\x1c\x82\xcc\xfc\xd8\x32\x58\xf1\x12\xbd\xee\ -\x86\x17\x12\x70\xcb\xd9\xbb\xd8\xb0\x45\x2b\xfa\xf5\x7b\xea\x62\ -\x33\xd0\x6b\xce\x32\xe7\x4a\x02\xb0\x04\x5f\xb7\xf0\x23\x02\x7c\ -\x2c\x09\x82\x68\xb1\x00\x78\x67\x33\x14\xac\x9c\x0b\xf7\xba\x4f\ -\xe0\x44\x28\x0c\x95\x29\x62\x43\x97\x44\x61\x80\x06\x5a\xbb\xce\ -\xc5\x76\xc3\xc6\x8b\xc2\x6e\x62\x62\x81\x2a\x8d\x9b\x20\x7d\x6e\ -\x56\x33\xd1\xa3\x4c\x7c\xe7\xd0\x49\x22\x61\xb4\x63\x4a\x7c\x8c\ -\xeb\x2c\x71\x1d\x85\xa9\xe1\x94\x92\xf6\xe2\x23\x5f\x27\x11\x42\ -\xc4\xb3\x9a\x4c\x25\xef\x58\x55\x05\xee\x9b\x3e\xcd\xd5\x71\x59\ -\xe7\xe6\x9f\x44\xe8\xde\x34\x37\x7c\x08\xa4\x4b\x48\x80\xd1\x23\ -\x98\xe4\x7c\x2f\x9d\x58\xfe\x15\x93\xd2\x7d\x23\x77\x24\x52\x09\ -\x28\x8b\xed\xfe\x40\x0b\xdd\x49\xd8\xc3\xe8\x2e\x8e\x68\x5f\x8c\ -\x1d\xba\x45\xf1\x61\xe9\xd3\x22\x1a\xbe\xcc\xaa\x97\x1f\x68\xf8\ -\x7f\x9e\xbb\x8b\x4d\x9a\xe9\xd3\x8b\x26\x4b\x9e\xc7\x54\x97\x04\ -\x08\x9c\x36\x7f\x40\xb5\x02\x50\x8e\xfc\x35\xec\x39\x3f\x44\x36\ -\x3e\xee\x04\x04\x53\x01\x96\xad\x01\x3f\xdb\x65\x20\x24\x20\xa5\ -\xdb\x12\x28\xea\xb8\x10\xde\x58\xcc\x65\x22\x9a\x3a\x32\xe9\x4a\ -\x2d\x18\x04\x25\x06\x19\x2e\xf7\x99\x78\xd7\xaf\x75\x84\x79\xe2\ -\xa0\xe8\x49\x23\x3d\x68\x97\x2c\xa1\xb9\x38\x28\xe6\x07\xfe\x6e\ -\x0a\x84\x99\x74\xeb\x39\x7d\x55\x05\xfc\xa5\x55\x23\xdc\x68\x63\ -\x80\x9b\xbb\xe9\x0a\xef\xf7\x67\xde\x4b\x4a\x40\x2b\x8a\x09\x6e\ -\x0a\xfe\x85\x67\x8d\xdf\x1c\x18\xbb\x27\x8a\x95\x60\xa0\x99\x6f\ -\xc1\x48\x4b\x44\x4b\xfb\xe7\x2f\x49\xf0\x1f\x69\xf8\x14\xb5\xa5\ -\x51\x1f\x69\xf8\xda\xcd\x0d\xd8\x3d\x8e\x9a\x7f\xb7\x9e\x40\xf2\ -\xea\x26\x3e\xe0\x60\x9f\x78\x20\x43\xfd\x07\x9e\x38\x52\x30\x69\ -\xb6\x63\x8d\x02\x50\xfe\xdc\x3f\x85\x1f\x7e\xad\xd5\xed\x75\x6b\ -\x18\x9e\xed\x12\x88\xb7\x59\x08\x01\x5d\x7f\x85\xbb\x9d\xa7\xc0\ -\xdd\x0e\x63\xc1\xcf\x72\x28\x3c\x6e\xd7\x1f\x0a\x2d\x06\x02\x2a\ -\x37\xac\xd8\xb4\x99\xa9\x45\x92\xcb\x88\x0f\xd7\x48\x90\xe5\x30\ -\xd8\xbe\xb1\x0a\xba\x99\x35\xc2\x33\x76\x9c\xb2\x2a\x12\x60\xf4\ -\x30\xc8\x4e\x5d\xa3\xe5\x7b\x68\xdc\x8e\x30\x2a\xc0\xe8\x0e\xfc\ -\xc7\x96\xce\xf9\x41\x24\xf4\x12\x36\x7c\x91\x00\xb3\xef\xa0\xb6\ -\x9e\x21\x7b\xcc\xbd\xee\x7f\x3d\x16\x20\xde\xbd\x7b\x66\xf9\xc1\ -\xc5\x0c\x6a\x69\x59\x60\x8b\x16\x0e\xa8\xa0\x20\x3a\x41\x3b\x94\ -\xa0\xfc\x03\x3e\x18\x1d\x5a\x0c\xf7\x18\xd4\x06\xef\x25\xad\xc9\ -\x7b\x8a\x5e\x58\x13\x87\x5e\x2d\xe2\x99\x8d\x94\x7b\x4d\x43\xae\ -\x89\xe6\xed\x45\xe1\xbf\xa7\x13\x3c\x6b\x69\x69\x47\x8f\xd5\x1b\ -\x45\x97\xa6\xd1\xdf\x5b\x43\x81\x8b\x03\xf4\xf5\xd0\xa5\x8d\x7d\ -\xe9\x62\xab\xee\x2f\xf7\xf7\xe8\x18\x7a\xc9\xb1\x45\xec\x83\x81\ -\x1a\xa9\x61\x23\x95\x22\x0e\x8c\xdc\x16\x60\x3a\xea\xfd\x03\x12\ -\x78\x99\x64\xf8\x22\x86\xba\xb3\x67\x32\xea\x7c\xf5\x60\x10\x0d\ -\x51\xfc\xec\xa2\x73\xf1\x1d\xc5\x56\xd2\xe2\xf1\x06\x1d\xb6\xe4\ -\x70\x64\x50\x53\xb3\x0d\x9a\x9a\xfe\x82\xd6\xd6\x0b\xd0\xcd\x2d\ -\x1c\x47\x8d\xba\x8a\x9c\xf2\x89\x0d\x93\x7f\xd0\x06\xd1\xe8\x34\ -\xae\x2d\x0e\x76\xb5\x2c\x3c\x13\x38\x3f\xb4\x9a\xf0\x8b\xa6\x06\ -\x8f\x0e\x00\x2f\x03\x34\x1b\xca\xc9\xab\x29\x7c\xf3\xbe\x80\x32\ -\xe5\x3b\x78\x4f\xad\x03\x8f\x3d\x2a\xc2\x48\xda\x6e\xa7\xbf\x7f\ -\x13\x65\x23\x1c\x6c\x3c\x1f\x47\x99\xac\xae\x84\x8b\xf9\xea\x57\ -\x8a\x2e\x57\x7d\x60\x46\xa0\x1f\x2c\x4a\x0c\x83\x15\x29\x39\x15\ -\x02\xb4\xe9\x2f\xea\x45\xfc\xe2\x68\xa0\xb8\xf8\x99\x2a\xee\x6d\ -\x12\x56\x54\x7e\x68\x9b\x53\x9d\x3c\x3f\x9a\x76\x41\x68\x35\x08\ -\x65\x4c\x47\xa3\x99\xb9\x0b\x5a\x5a\xba\x89\xe8\xd2\x65\x6e\x19\ -\x15\x80\x62\x6a\x64\x57\xa9\x97\xe9\x3b\x7e\x10\xa3\x64\xe5\xb9\ -\xe8\xec\x66\xc9\x22\xfc\xdd\x73\xb8\x4f\xb2\xd0\x53\x40\xc3\x4f\ -\x14\x7a\x64\x74\xba\xd6\x3d\x9e\x86\x4f\x10\x58\x0c\x02\x61\x4d\ -\x02\x68\xea\x57\x0c\xb9\x72\xeb\x50\xfd\x87\x8a\x30\x82\x96\xba\ -\x72\x5c\x05\xec\xa2\x33\xa4\x92\x00\x13\x2d\x36\xa4\xd9\x0f\x3c\ -\x14\x0c\x03\xbc\xb0\x82\xc1\x47\xf2\x60\xd0\x1e\x92\x9f\x68\x3c\ -\xc1\xe9\x6b\x04\x58\x29\x3a\xcc\x71\xd0\x7a\x84\x9d\xb9\x08\x8b\ -\x83\x11\xa6\xdd\x44\x70\xf5\x21\xff\x8d\x40\x20\x01\xb3\x70\xc7\ -\xdc\x7a\x61\xda\x79\x5e\x06\x15\xa0\x43\x87\xe9\xa2\xf0\x27\xba\ -\x5c\xcf\xf3\x73\x11\x1d\x70\x28\xa8\x5a\xd1\xf8\x0e\x1f\xc0\x2a\ -\x8d\x26\xf5\xb0\xff\x74\x13\x1c\xfc\x9b\x79\xce\x90\xb9\xe6\xd9\ -\xce\x73\xdb\xbe\x9f\xb4\xa1\x9b\xbf\x6f\xee\xc6\x50\xad\x13\x6d\ -\xb3\xc4\xe1\x23\x78\x1a\xe4\xd6\x14\xbe\xb1\x6d\xc5\x19\x84\xbd\ -\xeb\xe8\xd6\x38\x8c\x78\x53\xea\x94\xa6\x2a\xad\xd0\xd9\x78\x81\ -\x48\x80\xf1\x16\xab\xd2\xb7\x38\x1f\x2d\xe3\x0c\xd8\xff\xa6\x92\ -\x04\x66\xa3\xd8\xdd\xcb\xe5\x3e\x2b\x80\xf8\x28\xd3\x12\x70\xde\ -\x58\x7e\x9e\x5f\x55\x3c\x05\x7c\xd8\x51\x90\x0c\x1b\xdf\x06\xc3\ -\xe2\xa7\x71\x30\x2d\x92\x4f\x44\x78\xdf\xdc\x61\x47\x30\x95\xc0\ -\xcd\x35\x2c\x37\xf1\xb7\x89\x69\xb8\xb4\x1f\x5a\x6a\x89\xea\x02\ -\x17\xbe\xe7\x9e\x7a\xb4\x6e\x61\x3c\x03\xf0\x97\x37\x95\x71\x7c\ -\x2a\xef\xcf\x39\xa0\x5f\x5c\x11\x3e\x81\xf1\x34\x48\xad\x56\x80\ -\x01\x80\xf5\xca\xcf\x1c\xbc\x52\xe7\x57\xf3\x94\x0f\xef\x5e\x92\ -\xe7\xd6\xc3\xae\xba\xc3\x71\xb8\xe9\x6f\xa2\x96\x80\x75\x7f\xaf\ -\xa8\x8a\xf0\x9d\x76\x23\xc8\x8b\xd6\x20\x1c\xff\xe2\x84\x10\xf1\ -\x71\xae\x61\xd0\xdc\x12\x61\x6b\x06\x0d\x1c\xc1\xa3\x14\x19\x0f\ -\xbe\x90\xf1\x2c\x2b\x61\xbc\xf0\x23\xb3\x1f\xf3\x09\xb9\x1c\x2f\ -\x7c\xcf\xf1\x12\x66\x2a\x7a\xf0\x43\xd4\xb6\xe7\xdc\x6f\x35\xcf\ -\xef\xda\xcd\xd9\x6b\xee\xd3\xf0\x29\x57\x87\x59\x61\x63\x25\x79\ -\xb6\xe2\x41\x7b\xa5\x14\xbe\xc3\x48\x20\xf6\xb8\x58\x29\xfc\x32\ -\xab\x10\x55\x9e\x64\xf0\x2c\x1c\x77\xfd\x84\xea\x04\xd0\x35\x07\ -\x76\xda\x95\xd1\x4f\xb3\xac\xab\xbc\x0f\xa2\xd8\xb0\x81\x29\xce\ -\xef\xb5\x16\xd7\x0f\x3e\x84\x4c\xbf\x7d\x7c\x91\x00\xe4\x71\x2d\ -\x7e\x8c\xdb\x7f\xd5\x8c\x20\xf2\x6a\x29\x3e\xa9\x52\xf4\x8d\x0d\ -\x9a\x34\x47\xa7\x49\xcb\x71\xc3\xd5\xe7\x9f\x66\xb6\x86\x10\x78\ -\x28\xf4\xba\x8e\xaf\xbd\x4e\xe1\x63\xcf\x83\xf8\x6c\xe7\xce\x9c\ -\xd7\xcb\x7a\xf6\xbc\x9e\x60\xa7\x7f\x25\xdb\xb5\x4b\x6c\xde\x5c\ -\x87\xb2\x92\x45\x7d\xf1\xf8\x80\x76\xd8\x49\x5b\x8d\x3d\xfd\x72\ -\xc3\x7f\x7a\x46\x0e\x5d\x4e\x25\xa3\x0c\x38\x3c\xad\x22\xfc\xdc\ -\x66\x37\x1a\x87\x57\x17\x3e\x45\x66\x4f\xf3\xe8\xaa\xe1\x9b\x39\ -\x00\x72\x65\x45\xbf\x97\xfb\x4f\xb7\xb6\xaf\x7c\xc5\x14\xed\x98\ -\x42\xfd\x06\x2d\xb1\xa1\x95\x2b\x42\x97\xb9\x28\x7a\x9c\x03\x2c\ -\xff\xa6\x29\x61\xb4\x87\x48\x7c\x5a\x17\x6d\xd3\x1f\xa7\x77\x31\ -\xc3\x30\x68\xd2\xc5\x01\xe7\xfc\x7e\x0b\xb7\xef\x78\x87\x5e\xe4\ -\x91\x20\xc9\xc2\x85\xb7\x43\xa6\x8e\xdf\xfa\x76\xae\x31\x93\xe2\ -\xae\x0a\xde\xcf\x0c\x99\xd0\xb7\x76\x8d\x53\x82\x86\x98\x15\x65\ -\xcc\xee\x8d\xa1\x13\xba\xa2\x8b\xa9\x0e\xca\x71\x39\x42\xf1\x49\ -\x61\xa3\xbe\xe5\xa4\x2c\xba\x22\xa8\xa9\x53\x79\xf8\xc3\x32\x99\ -\x67\xaa\xa7\x75\x5f\x54\x1b\xfe\x7e\xc3\xb7\x70\xac\x65\x98\xf2\ -\x6e\xad\xbf\xab\x0a\xa0\xa1\x2b\x0a\xff\x43\x6d\x6d\xf6\x7d\x07\ -\x09\xb8\xe2\xd2\xb6\x58\xa2\xe7\xf2\xab\x16\xb8\x7e\xcd\xa6\x0c\ -\x13\xc4\x7d\xdc\xa4\x99\xc7\x25\x4d\xbf\xbe\xa4\xd2\x77\x0e\xdd\ -\xdd\xf9\x42\x4f\xcf\x52\xf2\xff\xfb\xf3\xe9\xb1\xa7\xb3\xbb\x98\ -\xf2\x7e\x33\x86\xe2\x0d\x0d\xc1\xff\xb8\x2c\x84\x67\xb5\x82\x24\ -\xb4\x04\x61\x72\x37\xf5\x22\x9f\x7e\xad\x30\x6c\x5c\x17\x5c\x67\ -\x6b\x8c\x3a\x2a\xa2\x7d\xf7\xf2\x09\x07\xe8\x96\x2f\x5f\xb8\xbe\ -\x21\xed\x82\xed\xb0\x1d\xb0\x97\x1f\xf7\x8d\xec\x61\xbd\x7c\x71\ -\xe0\xc5\x70\xc4\x30\x09\xce\xb7\x0e\x03\xef\x76\x8f\x21\xaa\x63\ -\x2e\xc4\x5b\x0b\xe0\x9c\x1a\x4f\x71\x0b\x04\x48\x86\x6f\xd4\x15\ -\xfe\x31\x0b\xe6\xa7\x5e\xed\x0b\x20\x2f\x39\xe5\xeb\xbb\x4d\x0a\ -\x25\x2f\x3b\xf1\x81\xc8\xa2\x0f\x54\x49\x49\x03\x1d\x1d\xe7\x89\ -\x0e\x3b\x16\x31\x7e\x4b\x1e\x11\xe0\x03\x01\x97\xe9\x02\xcf\x93\ -\x54\x2a\x2f\x2a\x82\x7f\x81\x19\x64\x10\x11\x90\xf2\xbc\xb3\xb2\ -\xc0\xdb\xde\x10\x77\xd9\x18\x62\xb7\xa6\x1a\x6c\x30\xe1\xe2\x93\ -\x37\x7a\x8b\x9b\xa1\x7b\xc5\xa7\x68\xe6\x92\xc6\x0f\x82\x61\x3d\ -\x34\x3a\x5c\x1f\x99\xc3\x46\x69\x70\xc7\x2c\x19\x42\x3a\x7c\x24\ -\x61\x23\x3c\x91\x20\xae\xf3\x47\x38\xa6\x10\x4c\x57\x0c\xab\xfc\ -\x01\x3c\x36\xfc\x76\xfd\x01\x15\x54\x2a\x4e\xf7\xa8\xf7\x6f\x59\ -\xf2\xfd\x43\x67\x05\x93\x57\x6b\xf1\x81\x48\x6f\x15\x15\xeb\xa3\ -\xb5\xf5\x2f\x38\x79\xf2\x3e\x91\x04\x63\x2d\x5a\xbd\xa4\x02\x50\ -\x16\x18\x42\xc4\x3e\x2e\xe4\x78\x01\x14\xde\x56\x05\x9f\x92\xb6\ -\x90\xcf\x8a\x40\x49\xeb\xa8\x88\x17\x2c\x35\x70\x54\xd3\x7a\xa8\ -\xc0\x65\x10\x28\xc6\xf5\x10\x9c\x1b\x21\xac\xd0\x47\x38\x65\x86\ -\x10\xd5\xb9\x72\xd0\xd5\x11\x63\x95\x09\x07\x39\xf1\xec\x5e\x01\ -\xea\xab\x3e\x09\xa0\xdd\xa6\xe2\xee\xef\x2f\x0d\xfb\x3b\x4f\x0b\ -\xa7\x6d\x4b\xda\xc1\x40\x38\xa4\xac\xac\x2e\xb0\xb5\x1d\x8b\xe3\ -\x46\xaf\xc3\x99\x46\x20\x60\x25\x98\xdb\x12\x52\x76\xc9\xc3\x53\ -\x22\x01\x12\xde\x05\x36\x02\x5f\x41\x7b\x28\x95\x14\x81\x12\x66\ -\x29\x83\x1c\x5f\xab\x2f\x87\x5d\x95\x50\xf3\x44\x38\xc0\xa4\x49\ -\x6e\x16\xa1\xb9\x04\x7c\x69\xf8\x26\x7d\xc8\x23\x8b\x2b\x0a\xff\ -\x92\x34\xe8\x1f\xbc\x2e\x80\xee\xa4\x4d\x78\x51\xbf\xbe\x26\xda\ -\xea\xaa\x21\x2b\x00\xe5\x57\x63\x28\xd8\xa2\x0a\x21\x62\x09\xf0\ -\x20\x03\x2f\xe2\x9a\x43\x50\x15\x09\x84\x23\x16\xeb\x46\x7c\x53\ -\xf8\xbe\x46\xa1\x24\xf0\x82\xaa\xbb\x85\xe8\xcc\x81\x10\x2a\x80\ -\x5a\x93\x8a\x31\xfa\xa6\xd2\xa0\xff\x1f\x16\x86\x88\xc7\x0f\xe8\ -\xa8\xa0\xd0\x88\x3c\x77\xa7\xb7\x00\x49\x11\x84\xbf\x37\x01\x1f\ -\x56\x02\xca\x51\x19\x88\x4d\x35\x82\x47\xac\x04\x59\xe6\x70\x53\ -\x25\xac\xd3\xe3\xaf\x0a\xff\x7a\x13\x5f\x12\x76\x59\x75\x7b\x05\ -\xe9\x8c\x86\xf7\x86\x9d\x2a\x8a\xfe\x79\xd2\x90\xff\x9f\x57\x06\ -\x89\x77\xf2\x7e\xaa\x2c\x43\x9a\x6d\xba\x95\x24\xc0\x45\xfa\x10\ -\xe8\xc1\x81\x8f\x92\x22\x9c\x51\x80\xa0\x1c\x13\x48\x79\xd7\x09\ -\x1e\xdc\xb2\x51\xe3\x91\x80\x05\x35\x06\x4f\x6b\xfa\x67\x69\x07\ -\x50\x0d\x1b\x45\x11\x9a\x0e\x06\x94\xab\x27\x0a\xff\x61\x5d\x3f\ -\xd2\xad\xce\x2e\x0d\x13\x2f\x41\xf2\xa4\x07\x2b\x5a\x69\x00\xce\ -\x69\xf9\x49\x02\x52\x2f\x48\xd8\x23\x07\x69\x92\x12\x10\xf8\x57\ -\x35\xe0\x46\x96\x2e\xf8\x5a\x9f\x32\xf3\xad\x36\xfc\x47\x1d\x3f\ -\xc0\x11\xb9\xd0\xcf\x85\x2f\x6a\x05\x94\xf7\xf8\xe5\x7c\xef\xf1\ -\x08\xa9\x00\xff\x99\x08\x74\xd3\x24\x7e\x23\x05\xc0\xf1\xfa\x95\ -\x4a\x83\x77\x7f\x2a\x41\x4c\x25\x09\x18\xc8\x88\xea\x06\x05\xef\ -\xea\xcb\xa4\x71\xe2\xac\xdf\x56\x0a\x3f\xca\x32\x9d\xd4\xf4\x13\ -\x6a\x08\xfd\x03\xec\x81\x48\x98\x49\x9e\xf7\x83\xc8\x9f\xcb\x2b\ -\x7e\xc3\xa5\xe1\xd6\x92\xc5\xa1\xe2\x39\x05\x19\x32\xa4\x4d\xdf\ -\xbb\x71\x25\x09\xf8\x6b\x35\xc1\x4f\x52\x82\xc0\x4e\x90\x5e\xa0\ -\x0a\xbe\x1b\xa6\xea\x04\x54\x84\x1f\x6c\xf6\x04\xbc\x98\xcc\x4f\ -\xfb\x01\x42\x2e\xec\x85\x50\xd8\x00\x3c\x98\x0f\x8f\xc1\x0d\x4a\ -\x09\x08\x2e\x04\x25\x51\xf8\x07\xa4\xc1\xd6\xb2\xd5\xc1\xe2\xad\ -\xd5\x68\x37\x30\x1a\x2a\x03\x4e\x93\xa8\x20\x2e\x6b\x06\xbe\xe4\ -\x59\xc1\xa7\x02\xdc\x34\x82\xe0\xf8\xf6\x90\x4c\xbe\x43\xa8\x11\ -\xdc\x31\x06\x1e\xb4\x08\x21\x81\xbf\x82\xdd\x10\x04\xeb\x48\xe0\ -\x73\x21\x81\x84\x2d\x10\x05\x2e\x89\x2b\x41\x5b\x14\xfe\x13\x69\ -\x87\x4f\x2d\x5d\x1e\x2e\x9e\xd4\xb0\x86\xce\x17\x50\x22\x15\xc4\ -\x21\x4d\x3f\x49\x30\xdf\x08\xa2\xf7\x72\xe1\xdd\x19\x75\xf0\x0d\ -\xef\x0e\xc8\x97\x85\xe8\x64\x1d\xb9\x64\x99\x39\x90\xf4\x8f\xb0\ -\xab\xa3\xfc\xb9\x5f\x44\x68\x2b\x0d\xb5\x96\xef\x0f\x20\xee\x52\ -\x7e\x45\x4b\x83\xf6\xea\x80\xb3\xc5\x15\x44\x52\x51\xcc\x70\xd7\ -\x00\x6f\x2a\x40\x6a\x0b\x08\xa4\x6f\xcd\xab\x35\xb9\xeb\xbf\x14\ -\xbe\x5d\x45\x93\x6f\xbc\x34\xd0\x3a\xb2\x41\x84\x78\x90\x69\x0a\ -\xad\x1b\x68\xca\x03\x8e\x13\x57\x10\x7f\x6d\x09\x85\xa1\xdd\x41\ -\x10\x61\x0b\x7c\x64\x20\x53\x08\xf0\xa1\xd9\x28\xc8\xa8\x31\xfc\ -\xe1\x84\xf2\x61\xde\xdd\xd2\x30\xeb\xe0\x0e\x21\xe2\xd9\xbe\x77\ -\x64\x18\xc0\x9e\xe2\x0a\xe2\xc5\x4e\x90\x49\x4b\x81\x9c\x86\xe0\ -\x43\xdf\x5e\x52\x7d\x08\xac\x36\xfc\x09\x84\xf2\x19\x3e\xb7\xfe\ -\x57\x9b\x2c\x4a\x05\xf8\x7e\x22\xd0\xd2\xa0\x4c\x9f\xd4\xe2\x97\ -\x1b\x83\x80\x0a\x10\xdb\x09\x32\xc8\xbf\x94\xd2\xb7\x38\xa2\x37\ -\x44\xfc\x43\x00\x3d\x51\xf8\x8f\x7e\xc4\x34\x74\xa9\x00\xff\x3b\ -\x09\x70\x38\xa9\xcd\x53\x01\x28\x45\x8a\x10\x44\xdf\x62\x31\x07\ -\x5e\xc8\x4d\x81\x92\x8a\xf0\x3b\x8a\xc2\x7f\xf7\x9f\xce\x32\x92\ -\x52\x4b\xf7\x08\x22\xaf\xdf\xe5\x38\xa4\x42\x68\x00\x48\xea\x02\ -\x18\x6c\x0c\xa9\xa2\xb7\x48\xc7\x0e\x0c\x21\x40\x14\x7e\xbf\x8a\ -\x99\xbd\x4e\xd2\x00\x7f\xc2\x5d\xc2\xd8\x9d\x41\xcd\xea\x03\x9e\ -\xef\x00\xf8\x9c\x53\xfe\x18\x20\x6d\x3c\xa1\x5e\x6f\x12\xbc\x02\ -\x54\x5a\xdd\x2a\xe5\x27\xdc\x26\x4e\xbc\x1a\x26\x9d\x96\x06\x6e\ -\x24\xf0\x25\xe4\x6d\xf6\xa3\x4b\xa4\x98\x8a\x4a\x9f\x82\x34\xbc\ -\x9f\x7c\x9f\x40\xf1\x80\x12\x3d\x7c\x21\x5d\xbc\xb8\x81\x72\x50\ -\x5a\xe3\xff\x17\x9f\x18\x22\x45\x2a\x80\x94\xef\xcc\xff\x01\x19\ -\x47\x8e\x78\xd3\x1b\x66\xf0\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ -\x42\x60\x82\ -\x00\x00\x05\x24\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ -\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ -\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x06\xec\x00\x00\x06\xec\ -\x01\x1e\x75\x38\x35\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ -\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ -\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x00\x13\x74\x45\ -\x58\x74\x41\x75\x74\x68\x6f\x72\x00\x52\x6f\x64\x6e\x65\x79\x20\ -\x44\x61\x77\x65\x73\x0e\xd8\x7e\x1d\x00\x00\x04\x82\x49\x44\x41\ -\x54\x48\x89\x8d\x96\x6d\x88\x54\x55\x18\xc7\x7f\xe7\xbe\xcd\xdc\ -\xb9\xb3\xb3\x33\xfb\x1a\xeb\xbe\xe9\x6e\xad\x2f\x69\xea\x4a\x68\ -\x8a\x22\x24\x94\x58\x7e\xaa\x48\x23\x30\x41\xa1\xec\x5b\x51\x41\ -\xf8\x31\xa3\x3e\x44\x94\x44\x92\x09\xa5\x46\x54\x84\x21\x59\x44\ -\x18\x96\x19\xe8\xb2\x5a\x9b\xba\xea\xa4\xfb\x66\xee\xce\xce\xec\ -\xec\xcc\xdc\xbd\xf3\x76\xef\xe9\x43\xb3\x63\xe6\xbe\x3d\xf0\x7c\ -\xfb\x9f\xff\xef\x3c\xcf\x3d\xf7\x39\x47\x48\x29\x99\x2d\x36\xbf\ -\x27\x7c\x6a\x40\x7f\xc0\x6f\xaa\x8f\x02\x64\x1d\xf7\x84\x3b\x51\ -\xb8\xf0\xed\x8b\x32\x37\xdb\x5a\x31\x1d\x60\xf7\x01\xa1\x67\x2a\ -\x42\x6f\x37\xd7\x76\xac\xaf\x0a\xd6\xd5\x07\x2c\x7f\xb5\x69\x05\ -\x7c\x52\xba\x38\xb6\x93\xb3\xed\x6c\x62\xdc\x8e\xdd\xea\x8b\x5d\ -\x39\x15\x4c\xa7\x5e\xfe\x70\x97\x2c\xcc\x19\xb0\xf5\xa0\xb1\xa2\ -\xb9\x7e\xfe\xa1\x65\x1d\xab\x96\x7a\x5a\x56\xc9\xbb\x0e\x92\x3b\ -\x75\x02\x81\xa1\x9a\x50\x50\xbd\x9e\x2b\xe7\xff\xe8\x1f\xbe\xb1\ -\xe3\xd8\xce\x7c\xf7\xac\x80\x6d\x9f\x87\xf7\x2d\x69\x59\xf9\x5c\ -\x5d\x7d\x4d\x9d\xe3\xa6\x67\xeb\x00\x00\x01\x35\x44\x7c\x78\x74\ -\xe4\x42\x5f\xd7\xc7\x47\x9f\x4a\xbe\x36\x2d\xe0\x89\xc3\xe6\xb3\ -\x6b\x96\xae\xdf\x6f\x04\xf5\xa0\x27\xdd\x39\x99\x87\xb4\x7a\xaa\ -\xb4\x05\xa4\x8a\x43\x8c\x24\xaf\x65\xce\xf4\xfc\xfa\xc2\x17\xcf\ -\x38\x9f\xdc\x05\x78\xfc\x80\xa8\x59\xdc\xb6\xfc\xb7\xc6\xd6\x79\ -\x6d\x73\x33\x17\x6c\xa8\xdc\x43\x83\xb1\x0c\x9f\x08\x72\xc9\xf9\ -\x9e\x73\x99\x4f\x19\xb8\xd1\x17\xbd\x18\xed\x59\xfd\xcd\x2e\x39\ -\x0a\xa0\x4c\xca\xab\xab\xea\x8e\x34\x35\x37\xcc\xd1\x1c\x56\x5a\ -\x4f\xd2\xea\x5b\x8d\x5f\x54\x10\x2b\x46\x39\x97\x39\x8c\x2b\x5d\ -\x5a\x9a\x5b\xdb\xaa\xab\x6a\x8f\x4c\xea\x14\xf8\xf7\xa3\xde\xd7\ -\xd2\xb1\xd6\x15\x73\x33\xaf\xd1\xdb\xb9\xd7\xdc\x88\x82\x4a\xda\ -\x1d\xe1\x97\xd4\x07\xb8\xb2\x08\x40\x51\xb8\xb4\x37\xcf\x5f\xbb\ -\xf5\xa0\xb1\xa2\x0c\xf0\xfb\xd4\x2d\x56\xd0\xb4\x40\x20\xc4\x54\ -\xa9\x94\x53\x53\xfc\xac\x0b\xed\x22\xa0\x86\xc9\x4b\x87\x4b\xce\ -\x09\xd2\xee\x2d\x14\xa1\xa2\x08\x0d\x21\x54\x02\x96\xdf\xf2\xfb\ -\xd4\x2d\x00\x1a\x80\x65\x55\x76\xaa\x86\x86\xbc\xdd\xb1\x52\x97\ -\xc5\x5d\xbb\x5f\x57\xb1\x9b\x88\xd6\x02\xc0\xad\x42\x0f\x97\xb3\ -\x3f\xa2\x08\xed\x0e\x8d\x66\xe8\x58\x66\xa8\xb3\x0c\x08\x9b\x91\ -\x26\x4f\x4a\x54\x45\x9d\xd1\x7c\x81\xff\x21\x1a\x7d\xcb\xf1\x3c\ -\x8f\x91\x42\x2f\xc7\xe3\x7b\x31\xd5\x10\x86\x12\xb8\x43\x57\x14\ -\x0a\xe1\x40\xa8\xa9\x0c\x30\x0d\xb3\x5a\x11\x3a\x0a\x0a\x4c\x61\ -\x0c\x10\x50\x23\x2c\x31\x1f\x43\xb8\x3a\x49\xf7\x26\xc7\xe3\x7b\ -\x89\xe5\xa3\x00\x58\x6a\x15\x11\xa3\x11\x53\x0d\x23\x10\xe4\xbd\ -\x1c\xa6\xe1\xab\x2e\x03\x9c\xfc\x44\x5c\x57\x8c\x96\xff\x9a\x77\ -\x98\x9b\x08\x28\x61\xba\xed\x2f\xf1\x64\x91\x4e\x73\x1b\x16\xb5\ -\x14\x65\x8e\x0b\xf6\x57\x0c\xe5\xcf\x97\xb5\xb6\x9b\xc0\x76\x12\ -\xf8\x14\x8b\xb0\xde\x48\xde\x4b\xe1\xe4\xb3\xf1\x32\x20\x69\x8f\ -\x0d\x08\xe9\xad\x14\xc2\x00\x60\x51\xe0\x11\x96\x04\x36\x23\x50\ -\x18\x2b\x0c\x52\x70\x73\xdc\xa3\x2d\x06\xa0\x3f\x77\x96\x53\xa9\ -\xfd\x53\x56\x99\xf3\x6c\x86\x73\xbd\x54\xa9\xb5\x24\x27\xc6\x07\ -\xca\x00\xdb\x49\x75\x25\x9d\xa1\xad\xe8\x3a\xa6\x5a\x89\x81\x85\ -\xf0\x34\x3c\xcf\xa3\xcd\xd8\x80\x2e\x4c\x14\xa1\x13\x2b\x5c\xe5\ -\x58\xe2\x55\x60\xe6\x09\xac\xba\x3a\xb6\x93\xe9\x2a\x03\xb2\x39\ -\xf7\x78\x2e\x53\x7c\x65\x3c\xd0\x67\x49\x3c\xbc\xa2\xa4\xb5\x76\ -\x0d\x9a\xf0\x53\x55\x3a\x31\xb6\x1b\xe7\x64\xea\x1d\x26\xbc\xc4\ -\x8c\xe6\x02\x85\x82\x2d\xed\x6c\xce\x3d\x0e\xa5\xff\xe0\xd8\xce\ -\x7c\x77\x74\xa0\xff\x74\x8d\xd2\x00\xc0\x60\xbe\x9b\x44\xb1\xbf\ -\xbc\xc8\x93\x2e\x97\x9c\xef\xb8\xea\xfc\x34\xa3\x39\x40\x8d\x52\ -\x47\x74\x70\xf0\xf4\xe4\x64\x2d\x1f\xfc\x78\x22\xb6\x3d\x31\xe4\ -\x44\x4d\xc5\xc2\xc3\xe5\x77\xfb\x6b\x3c\x59\xa4\x20\x1d\x06\xf2\ -\xe7\xf8\x21\xf9\xe6\xac\xe6\xa6\x12\x20\x31\xe4\x44\xe3\x89\xf8\ -\xf6\x72\x45\xff\x9f\xa6\x9d\x0b\xef\x7f\x3f\xe9\xff\xbb\xc2\x95\ -\x1e\x0b\xcd\x87\xc9\xca\x34\xd7\xb3\x67\x98\xad\xef\x9a\xd0\x08\ -\x3b\xb5\xe9\xae\xde\xde\x3d\x53\x4e\xd3\xc9\xd8\xf6\x59\x78\x5f\ -\xc7\xbc\x96\x1d\x6a\x4d\xa1\x3e\xe9\xc6\x67\xdd\x35\x40\x58\x8d\ -\xe0\x8e\x6a\xc3\xbd\x43\xfd\x87\x8e\x3e\x9d\x9a\xfe\x3e\x98\x8c\ -\x4d\x6f\x19\xab\xdb\xda\xeb\x3f\x5a\xd0\x5e\xb7\x28\xad\x24\x94\ -\xac\x9c\xfa\x46\xf3\x0b\x3f\x15\x6e\xd8\xfb\xeb\xda\xc8\xe5\x8b\ -\x5d\xb1\xe7\x7f\xde\x57\x3c\x2d\x65\x69\xea\x4d\x05\x10\x42\xa8\ -\x40\x35\x10\x36\x2b\xa8\xdd\xf8\x7a\xf0\xa5\x96\xe6\xea\x65\xa1\ -\x50\x20\x12\x08\x2b\x41\xa3\x42\xd1\x01\x72\x29\xaf\x38\x91\x2c\ -\x66\xd2\xa9\x6c\xf2\xfa\xf5\xd8\x9f\x27\xdf\x98\x78\x37\x67\x33\ -\x0a\x8c\x01\x49\x29\x65\x72\xda\x0a\x84\x10\x16\x10\x01\x2a\x4b\ -\x59\x61\x04\x89\x34\x3c\xa8\x2c\x6c\x5a\xa5\xad\x90\x12\x65\xe0\ -\x6c\xf1\xfc\xcd\xb3\x5e\xb4\x60\x33\x06\x64\x80\x14\x30\x5e\xca\ -\x84\x94\xb7\x1f\x00\xd3\xbe\x2a\x4a\x30\x1d\x30\x00\x5f\x29\x75\ -\x40\x05\x8a\x40\x0e\xc8\x03\x59\xa0\x20\xe5\xd4\x37\xd5\x3f\x13\ -\x05\x02\x8c\xec\xcf\x7e\xae\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ -\x42\x60\x82\ -\x00\x00\x05\x64\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ -\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ -\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\ -\x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ -\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ -\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x04\xe1\x49\x44\ -\x41\x54\x48\x89\xb5\x95\x4b\x6c\x54\x55\x18\xc7\x7f\xe7\x9c\x3b\ -\xd3\x99\x32\xb5\xf4\x41\x1f\x38\x02\xa5\x8d\x4c\x54\xb0\x25\x48\ -\xa2\x12\x62\x14\x17\x46\x12\x64\x81\x26\xb0\x31\x18\x12\x36\x04\ -\xd2\x45\xe9\xca\x84\x5d\x29\x1a\x43\x80\x6e\x0c\x2b\xdc\x88\x09\ -\xa0\x21\x04\x35\x18\xa2\x26\x3e\xa3\x62\xa8\x4f\x7c\x61\x95\x81\ -\xb6\x4c\xcb\xbc\x67\xee\x39\x9f\x8b\xdb\xde\x4e\x2b\x10\x37\xde\ -\xe4\x4b\xee\xf3\xf7\xff\xfe\xdf\xf9\xee\x77\x94\x88\xf0\x7f\x1e\ -\xde\xdd\x1e\x9e\x53\xaa\x23\x6a\xcc\x33\xf1\x58\xec\xc9\xc6\xae\ -\x15\x2b\xeb\x97\x76\x2e\xb5\xc5\x62\x29\xfb\xd7\x5f\x63\xb9\x6b\ -\xd7\x7f\x2c\x55\x2a\xe7\x2b\xd6\x7e\xb0\x59\xa4\x70\x27\x86\xba\ -\xad\x03\xa5\xd4\x87\xb1\xd8\xcb\xc9\xc7\x1e\xdd\xdd\xd9\xde\xd6\ -\x11\xab\x8b\xa3\x4a\x45\xc8\xe5\xc1\x18\x48\x2c\xc2\x79\x86\x5c\ -\x21\xef\x7e\x1f\xbd\x7c\x65\xe2\xa7\x5f\xf7\x3c\x59\xad\xbe\xf7\ -\x9f\x04\xce\x2b\xb5\xa2\x75\x69\xe7\x1b\xa9\x0d\x8f\xaf\x4f\x54\ -\x6d\x84\x52\x29\x7c\x26\xc0\xec\xfb\x32\x13\x34\x24\xb8\x91\x9f\ -\x9e\xfa\xe5\xe3\x4f\xdf\xb6\xb7\xb2\xbb\x9f\x10\x29\xd5\xf2\xe6\ -\x09\xbc\xa7\x54\x6a\xc5\xda\xde\xf7\x7b\xba\xbb\x93\x3a\x57\x98\ -\x83\x2c\x80\x2e\x14\x11\xcf\xc3\xc6\x23\xf2\xdd\xa7\x9f\x7d\x91\ -\x1f\xbb\xf6\xf8\x13\x22\xfe\x2c\x53\xcf\x9e\x1c\x50\x4a\xb7\x2e\ -\x5f\x76\xa2\x67\xf9\xb2\x24\xd9\x3c\x4e\x24\x0c\x0b\xd8\x9a\x6b\ -\x07\xc1\x3d\xc0\x01\xce\xf7\x21\x5b\x54\x3d\x0f\xaf\x5e\xa7\xeb\ -\xeb\x5f\xab\x75\x10\x0a\x6c\x8a\xc7\x87\xee\x7f\xe8\x81\x3e\x29\ -\x56\x02\x80\x08\x95\xed\xdb\xa9\x6e\xd9\x32\x27\x52\x03\xb5\x9e\ -\x87\x1d\x18\xc0\xef\xed\x0d\xc5\x8d\x55\x3a\xd9\xf7\xe0\x0b\xef\ -\x46\xa3\xeb\x67\xb9\x1e\xc0\x05\xa5\xee\x5f\xf9\xd8\xfa\x17\xeb\ -\x7c\x67\x1c\x0a\x01\xec\x8e\x1d\xf8\xcf\x3d\x17\x64\xa1\x14\xfa\ -\xf4\xe9\xb0\x3c\x2e\x12\x81\xfd\xfb\xa1\xb7\x17\xfa\xfa\x70\x43\ -\x43\xf0\xf5\xd7\x88\x15\x9a\x1b\x9b\x96\x34\x25\xdb\x5f\x47\xa9\ -\x5e\x44\x44\x03\x78\x9e\xf7\x52\xc7\xe2\xa6\x25\x0e\x15\x64\xe9\ -\x79\xd8\x9e\x9e\xd0\xa6\xdb\xbe\x1d\x7f\xeb\xd6\x20\xd3\x68\x14\ -\x06\x07\x51\x7d\x7d\x28\xa5\x50\x91\x08\x74\x77\x63\x95\xc2\x29\ -\x85\xad\x38\x5a\xdb\x5a\x97\x9d\x85\x9e\xd0\xc1\xa2\xd6\x96\x07\ -\x11\x70\x5a\x07\x59\x3a\x07\x43\x43\xa8\xc1\x41\x58\xbd\x3a\x50\ -\xd9\xb1\x03\x17\x89\xa0\x52\x29\xd4\x9a\x35\xa1\xb8\x3d\x79\x12\ -\xff\xcc\x19\xc4\x18\x10\x41\xb4\xa6\x2e\x16\x5b\x1c\xd1\xfa\x29\ -\xe0\x67\x0d\x10\x6b\x6d\xba\xcf\x39\xc1\x69\x3d\x17\xd6\xe2\x86\ -\x87\x91\xcb\x97\xe7\x16\xec\xf9\xe7\x43\xb8\x88\xe0\xbf\xf9\x26\ -\xd5\x53\xa7\x82\xc4\xb4\xc6\x79\x1e\x62\x0c\x26\x12\xa5\x2e\x11\ -\xdf\x00\xa0\xdf\x52\x2a\x51\xb7\x28\xde\xee\xbc\xc8\x7c\x01\xad\ -\x71\xce\xe1\xbf\xf2\x0a\x32\x3a\x3a\x57\x2e\xe7\xf0\x7d\x9f\xe2\ -\xa1\x43\x64\x07\x06\x28\x5f\xb9\x82\x2d\x95\x70\xc6\x04\xdf\x18\ -\x83\xad\x5a\xe2\x4b\x9a\x97\x87\x25\xc2\x98\x60\xe1\x6a\x7b\x7b\ -\x06\x28\xc6\x20\x4a\x81\x13\x9c\xb8\xf0\x1f\x70\x80\xb5\x16\x3f\ -\x9d\x46\xd2\x69\x74\x4b\x0b\x5e\x32\x89\x6e\x6e\xc6\x96\xcb\x88\ -\xb8\xa0\x8b\xb6\x89\xe4\xbe\x79\x68\xd5\x75\xb9\xd7\xb4\x8b\x95\ -\x00\xae\x54\x00\x8f\x44\x88\xec\xdb\x07\xa9\x14\xd6\xd9\xda\xf6\ -\x26\xd6\xdf\x8f\x03\x0a\x87\x0f\x07\x5d\x37\x31\x41\x65\x7c\x1c\ -\xe2\x71\xbc\xce\x26\xf2\x13\x53\x7f\x84\xff\x41\x71\x32\xf3\xa7\ -\x3f\x39\x81\xd3\x26\xb4\xea\x97\xca\xe8\x5d\xbb\x90\x55\xab\x70\ -\x2e\xc8\xa6\xf0\xea\xab\x54\x3f\xfa\x28\x14\xa9\xef\xef\x27\xb6\ -\x77\x2f\x56\x24\x08\xc0\x2f\x14\xa8\x7a\x9a\x72\xae\xf8\x71\x58\ -\xa2\xc2\x44\x66\xb4\x9c\xcd\x3c\xeb\x46\x7f\x40\x25\x12\x38\xdf\ -\x27\x3e\x32\x82\x5e\xbb\x36\x84\xe5\x87\x87\x29\x1c\x3b\x86\x44\ -\xa3\x34\x1e\x3f\x4e\xdd\xc6\x8d\x00\x24\xfa\xfb\x71\xbe\x4f\xf6\ -\xc8\x91\xb0\xb4\x3e\x4c\x39\xe7\x2e\x84\x0e\xf0\xfd\xe3\x19\xe7\ -\xc6\x2d\x8e\xca\xf8\x38\xd5\x4c\x06\xff\xd2\xa5\x10\x9e\x3b\x78\ -\x90\xdc\xd1\xa3\x41\x96\xe5\x32\x93\x3b\x77\x52\xba\x78\x31\x28\ -\x63\xb9\x4c\xe9\xdb\x6f\xf1\x67\x5c\xe8\xf6\x26\xa6\x27\x32\x57\ -\x37\xc3\x15\xa8\x19\x76\x17\xea\xeb\x87\xbb\xfa\x52\xfd\xf6\xf2\ -\x2f\x46\x24\x58\x8b\xc4\xe0\x20\xf6\xd6\x2d\x72\x23\x23\x41\x8f\ -\xd7\x36\x41\x34\x4a\xeb\xc8\x08\xd9\x13\x27\x28\x5c\xbc\x18\x0c\ -\xc4\xa8\x87\xac\xec\x18\xff\xfb\xab\x9f\x36\x6f\x16\xf9\x7c\x9e\ -\xc0\x01\xa5\xf4\xa6\x64\xfb\x67\x1d\x0d\x0d\xeb\x2a\x63\xd7\x91\ -\x05\xc0\xd9\xde\x9f\x27\x52\x73\x0d\x50\x97\xba\xcf\xa5\xbf\xff\ -\x7d\xe4\xe9\x42\x69\xcf\xac\xfb\x79\xe3\xfa\xac\x52\xa9\xb6\x54\ -\xd7\xfb\x8d\xc6\x24\x2b\x7f\xa4\xff\x05\x58\x08\x0d\x47\xb7\x67\ -\x88\x76\x77\xca\x74\xfa\xe6\x17\x92\x9e\x9c\x37\xae\x6f\xbb\xe1\ -\xc4\xda\x9a\xdf\x58\xb2\x62\xe9\x23\xfe\x6f\xd7\xa2\x36\x5f\x9c\ -\x2f\xb0\x40\xc4\x6b\x6b\x44\x9a\x13\x99\xc9\x1f\xc7\xde\x89\x15\ -\x4a\x77\xdf\x70\x00\x94\x52\x0d\x31\x68\x39\x12\x31\x03\xa9\x55\ -\xcb\xb7\x35\x18\xd3\xaa\x7c\x8b\x9d\xce\x53\x9d\xce\x83\xd1\x98\ -\xc5\x09\x74\x43\x0c\xab\x95\x4c\x66\x72\x63\x17\xae\x5e\x3f\x78\ -\x08\xde\x05\xa6\x80\x29\xb9\x93\x03\xa5\x54\x04\x68\x01\x16\x03\ -\x4d\xbd\xd0\xb5\x49\xeb\x8d\x5d\x9e\xe9\x6b\x69\x69\x68\x6b\x6c\ -\x5c\xd4\x58\xf5\xad\xbd\x99\xc9\x4d\xdf\x98\xce\x5f\x1b\xf5\xed\ -\x97\xe7\xe0\x93\x71\x48\xcf\xc2\x81\x8c\x88\x64\xef\xe8\x60\x46\ -\xa8\x1e\xb8\x67\x26\xea\x81\x38\x50\x07\x18\x40\x11\xec\x3b\x15\ -\xa0\x0c\xe4\x81\x1c\x70\x0b\xc8\xca\xec\x8c\x98\x39\xfe\x01\x76\ -\x95\xba\xf1\x06\x3a\xff\x81\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ -\x42\x60\x82\ -" - -qt_resource_name = "\ -\x00\x06\ -\x07\x03\x7d\xc3\ -\x00\x69\ -\x00\x6d\x00\x61\x00\x67\x00\x65\x00\x73\ -\x00\x0e\ -\x05\xcd\xf4\xe7\ -\x00\x63\ -\x00\x6f\x00\x6e\x00\x6e\x00\x5f\x00\x65\x00\x72\x00\x72\x00\x6f\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x13\ -\x09\xd2\x6c\x67\ -\x00\x45\ -\x00\x6d\x00\x62\x00\x6c\x00\x65\x00\x6d\x00\x2d\x00\x71\x00\x75\x00\x65\x00\x73\x00\x74\x00\x69\x00\x6f\x00\x6e\x00\x2e\x00\x70\ -\x00\x6e\x00\x67\ -\x00\x12\ -\x04\xe4\x91\x47\ -\x00\x63\ -\x00\x6f\x00\x6e\x00\x6e\x00\x5f\x00\x63\x00\x6f\x00\x6e\x00\x6e\x00\x65\x00\x63\x00\x74\x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\ -\x00\x67\ -\x00\x13\ -\x0d\x76\x37\xc7\ -\x00\x63\ -\x00\x6f\x00\x6e\x00\x6e\x00\x5f\x00\x63\x00\x6f\x00\x6e\x00\x6e\x00\x65\x00\x63\x00\x74\x00\x69\x00\x6e\x00\x67\x00\x2e\x00\x70\ -\x00\x6e\x00\x67\ -\x00\x14\ -\x00\xe9\x23\x87\ -\x00\x6c\ -\x00\x65\x00\x61\x00\x70\x00\x2d\x00\x63\x00\x6f\x00\x6c\x00\x6f\x00\x72\x00\x2d\x00\x73\x00\x6d\x00\x61\x00\x6c\x00\x6c\x00\x2e\ -\x00\x70\x00\x6e\x00\x67\ -\x00\x11\ -\x06\x1a\x44\xa7\ -\x00\x44\ -\x00\x69\x00\x61\x00\x6c\x00\x6f\x00\x67\x00\x2d\x00\x61\x00\x63\x00\x63\x00\x65\x00\x70\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\ -\x00\x10\ -\x0f\xc3\x90\x67\ -\x00\x44\ -\x00\x69\x00\x61\x00\x6c\x00\x6f\x00\x67\x00\x2d\x00\x65\x00\x72\x00\x72\x00\x6f\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ -" - -qt_resource_struct = "\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x07\x00\x00\x00\x02\ -\x00\x00\x00\xb6\x00\x00\x00\x00\x00\x01\x00\x00\x0f\x03\ -\x00\x00\x00\x60\x00\x00\x00\x00\x00\x01\x00\x00\x0a\x89\ -\x00\x00\x00\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x00\xe4\x00\x00\x00\x00\x00\x01\x00\x00\x36\x7b\ -\x00\x00\x00\x34\x00\x00\x00\x00\x00\x01\x00\x00\x05\x99\ -\x00\x00\x00\x8a\x00\x00\x00\x00\x00\x01\x00\x00\x0c\x37\ -\x00\x00\x01\x0c\x00\x00\x00\x00\x00\x01\x00\x00\x3b\xa3\ -" - -def qInitResources(): - QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) - -def qCleanupResources(): - QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) - -qInitResources() diff --git a/src/leap/gui/progress.py b/src/leap/gui/progress.py deleted file mode 100644 index ca4f6cc3..00000000 --- a/src/leap/gui/progress.py +++ /dev/null @@ -1,488 +0,0 @@ -""" -classes used in progress pages -from first run wizard -""" -try: - from collections import OrderedDict -except ImportError: # pragma: no cover - # We must be in 2.6 - from leap.util.dicts import OrderedDict - -import logging - -from PyQt4 import QtCore -from PyQt4 import QtGui - -from leap.gui.threads import FunThread - -from leap.gui import mainwindow_rc - -ICON_CHECKMARK = ":/images/Dialog-accept.png" -ICON_FAILED = ":/images/Dialog-error.png" -ICON_WAITING = ":/images/Emblem-question.png" - -logger = logging.getLogger(__name__) - - -class ImgWidget(QtGui.QWidget): - - # XXX move to widgets - - def __init__(self, parent=None, img=None): - super(ImgWidget, self).__init__(parent) - self.pic = QtGui.QPixmap(img) - - def paintEvent(self, event): - painter = QtGui.QPainter(self) - painter.drawPixmap(0, 0, self.pic) - - -class ProgressStep(object): - """ - Data model for sequential steps - to be used in a progress page in - connection wizard - """ - NAME = 0 - DONE = 1 - - def __init__(self, stepname, done, index=None): - """ - @param step: the name of the step - @type step: str - @param done: whether is completed or not - @type done: bool - """ - self.index = int(index) if index else 0 - self.name = unicode(stepname) - self.done = bool(done) - - @classmethod - def columns(self): - return ('name', 'done') - - -class ProgressStepContainer(object): - """ - a container for ProgressSteps objects - access data in the internal dict - """ - - def __init__(self): - self.dirty = False - self.steps = {} - - def step(self, identity): - return self.steps.get(identity, None) - - def addStep(self, step): - self.steps[step.index] = step - - def removeStep(self, step): - if step and self.steps.get(step.index, None): - del self.steps[step.index] - del step - self.dirty = True - - def removeAllSteps(self): - for item in iter(self): - self.removeStep(item) - - @property - def columns(self): - return ProgressStep.columns() - - def __len__(self): - return len(self.steps) - - def __iter__(self): - for step in self.steps.values(): - yield step - - -class StepsTableWidget(QtGui.QTableWidget): - """ - initializes a TableWidget - suitable for our display purposes, like removing - header info and grid display - """ - - def __init__(self, parent=None): - super(StepsTableWidget, self).__init__(parent=parent) - - # remove headers and all edit/select behavior - self.horizontalHeader().hide() - self.verticalHeader().hide() - self.setEditTriggers( - QtGui.QAbstractItemView.NoEditTriggers) - self.setSelectionMode( - QtGui.QAbstractItemView.NoSelection) - width = self.width() - - # WTF? Here init width is 100... - # but on populating is 456... :( - #logger.debug('init table. width=%s' % width) - - # XXX do we need this initial? - self.horizontalHeader().resizeSection(0, width * 0.7) - - # this disables the table grid. - # we should add alignment to the ImgWidget (it's top-left now) - self.setShowGrid(False) - self.setFocusPolicy(QtCore.Qt.NoFocus) - #self.setStyleSheet("QTableView{outline: 0;}") - - # XXX change image for done to rc - - # Note about the "done" status painting: - # - # XXX currently we are setting the CellWidget - # for the whole table on a per-row basis - # (on add_status_line method on ValidationPage). - # However, a more generic solution might be - # to implement a custom Delegate that overwrites - # the paint method (so it paints a checked tickmark if - # done is True and some other thing if checking or false). - # What we have now is quick and works because - # I'm supposing that on first fail we will - # go back to previous wizard page to signal the failure. - # A more generic solution could be used for - # some failing tests if they are not critical. - - -class WithStepsMixIn(object): - """ - This Class is a mixin that can be inherited - by InlineValidation pages (which will display - a progress steps widget in the same page as the form) - or by Validation Pages (which will only display - the progress steps in the page, below a progress bar widget) - """ - STEPS_TIMER_MS = 100 - - # - # methods related to worker threads - # launched for individual checks - # - - def setupStepsProcessingQueue(self): - """ - should be called from the init method - of the derived classes - """ - self.steps_queue = Queue.Queue() - self.stepscheck_timer = QtCore.QTimer() - self.stepscheck_timer.timeout.connect(self.processStepsQueue) - self.stepscheck_timer.start(self.STEPS_TIMER_MS) - # we need to keep a reference to child threads - self.threads = [] - - def do_checks(self): - """ - main entry point for checks. - it calls _do_checks in derived classes, - and it expects it to be a generator - yielding a tuple in the form (("message", progress_int), checkfunction) - """ - - # yo dawg, I heard you like checks - # so I put a __do_checks in your do_checks - # for calling others' _do_checks - - def __do_checks(fun=None, queue=None): - - for checkcase in fun(): # pragma: no cover - checkmsg, checkfun = checkcase - - queue.put(checkmsg) - if checkfun() is False: - queue.put("failed") - break - - t = FunThread(fun=partial( - __do_checks, - fun=self._do_checks, - queue=self.steps_queue)) - if hasattr(self, 'on_checks_validation_ready'): - t.finished.connect(self.on_checks_validation_ready) - t.begin() - self.threads.append(t) - - def processStepsQueue(self): - """ - consume steps queue - and pass messages - to the ui updater functions - """ - while self.steps_queue.qsize(): - try: - status = self.steps_queue.get(0) - if status == "failed": - self.set_failed_icon() - else: - self.onStepStatusChanged(*status) - except Queue.Empty: # pragma: no cover - pass - - def fail(self, err=None): - """ - return failed state - and send error notification as - a nice side effect. this function is called from - the _do_checks check functions returned in the - generator. - """ - wizard = self.wizard() - senderr = lambda err: wizard.set_validation_error( - self.current_page, err) - self.set_undone() - if err: - senderr(err) - return False - - @QtCore.pyqtSlot() - def launch_checks(self): - self.do_checks() - - # (gui) presentation stuff begins ##################### - - # slot - #@QtCore.pyqtSlot(str, int) - def onStepStatusChanged(self, status, progress=None): - status = unicode(status) - if status not in ("head_sentinel", "end_sentinel"): - self.add_status_line(status) - if status in ("end_sentinel"): - #self.checks_finished = True - self.set_checked_icon() - if progress and hasattr(self, 'progress'): - self.progress.setValue(progress) - self.progress.update() - - def setupSteps(self): - self.steps = ProgressStepContainer() - # steps table widget - if isinstance(self, QtCore.QObject): - parent = self - else: - parent = None - self.stepsTableWidget = StepsTableWidget(parent=parent) - zeros = (0, 0, 0, 0) - self.stepsTableWidget.setContentsMargins(*zeros) - self.errors = OrderedDict() - - def set_error(self, name, error): - self.errors[name] = error - - def pop_first_error(self): - errkey, errval = list(reversed(self.errors.items())).pop() - del self.errors[errkey] - return errkey, errval - - def clean_errors(self): - self.errors = OrderedDict() - - def clean_wizard_errors(self, pagename=None): - if pagename is None: # pragma: no cover - pagename = getattr(self, 'prev_page', None) - if pagename is None: # pragma: no cover - return - #logger.debug('cleaning wizard errors for %s' % pagename) - self.wizard().set_validation_error(pagename, None) - - def populateStepsTable(self): - # from examples, - # but I guess it's not needed to re-populate - # the whole table. - table = self.stepsTableWidget - table.setRowCount(len(self.steps)) - columns = self.steps.columns - table.setColumnCount(len(columns)) - - for row, step in enumerate(self.steps): - item = QtGui.QTableWidgetItem(step.name) - item.setData(QtCore.Qt.UserRole, - long(id(step))) - table.setItem(row, columns.index('name'), item) - table.setItem(row, columns.index('done'), - QtGui.QTableWidgetItem(step.done)) - self.resizeTable() - self.update() - - def clearTable(self): - # ??? -- not sure what's the difference - #self.stepsTableWidget.clear() - self.stepsTableWidget.clearContents() - - def resizeTable(self): - # resize first column to ~80% - table = self.stepsTableWidget - FIRST_COLUMN_PERCENT = 0.70 - width = table.width() - #logger.debug('populate table. width=%s' % width) - table.horizontalHeader().resizeSection(0, width * FIRST_COLUMN_PERCENT) - - def set_item_icon(self, img=ICON_CHECKMARK, current=True): - """ - mark the last item - as done - """ - # setting cell widget. - # see note on StepsTableWidget about plans to - # change this for a better solution. - if not hasattr(self, 'steps'): - return - index = len(self.steps) - table = self.stepsTableWidget - _index = index - 1 if current else index - 2 - table.setCellWidget( - _index, - ProgressStep.DONE, - ImgWidget(img=img)) - table.update() - - def set_failed_icon(self): - self.set_item_icon(img=ICON_FAILED, current=True) - - def set_checking_icon(self): - self.set_item_icon(img=ICON_WAITING, current=True) - - def set_checked_icon(self, current=True): - self.set_item_icon(current=current) - - def add_status_line(self, message): - """ - adds a new status line - and mark the next-to-last item - as done - """ - index = len(self.steps) - step = ProgressStep(message, False, index=index) - self.steps.addStep(step) - self.populateStepsTable() - self.set_checking_icon() - self.set_checked_icon(current=False) - - # Sets/unsets done flag - # for isComplete checks - - def set_done(self): - self.done = True - self.completeChanged.emit() - - def set_undone(self): - self.done = False - self.completeChanged.emit() - - def is_done(self): - return self.done - - # convenience for going back and forth - # in the wizard pages. - - def go_back(self): - self.wizard().back() - - def go_next(self): - self.wizard().next() - - -""" -We will use one base class for the intermediate pages -and another one for the in-page validations, both sharing the creation -of the tablewidgets. -The logic of this split comes from where I was trying to solve -the ui update using signals, but now that it's working well with -queues I could join them again. -""" - -import Queue -from functools import partial - - -class InlineValidationPage(QtGui.QWizardPage, WithStepsMixIn): - - def __init__(self, parent=None): - super(InlineValidationPage, self).__init__(parent) - self.setupStepsProcessingQueue() - self.done = False - - # slot - - @QtCore.pyqtSlot() - def showStepsFrame(self): - self.valFrame.show() - self.update() - - # progress frame - - def setupValidationFrame(self): - qframe = QtGui.QFrame - valFrame = qframe() - valFrame.setFrameStyle(qframe.NoFrame) - valframeLayout = QtGui.QVBoxLayout() - zeros = (0, 0, 0, 0) - valframeLayout.setContentsMargins(*zeros) - - valframeLayout.addWidget(self.stepsTableWidget) - valFrame.setLayout(valframeLayout) - self.valFrame = valFrame - - -class ValidationPage(QtGui.QWizardPage, WithStepsMixIn): - """ - class to be used as an intermediate - between two pages in a wizard. - shows feedback to the user and goes back if errors, - goes forward if ok. - initializePage triggers a one shot timer - that calls do_checks. - Derived classes should implement - _do_checks and - _do_validation - """ - - # signals - stepChanged = QtCore.pyqtSignal([str, int]) - - def __init__(self, parent=None): - super(ValidationPage, self).__init__(parent) - self.setupSteps() - #self.connect_step_status() - - layout = QtGui.QVBoxLayout() - self.progress = QtGui.QProgressBar(self) - layout.addWidget(self.progress) - layout.addWidget(self.stepsTableWidget) - - self.setLayout(layout) - self.layout = layout - - self.timer = QtCore.QTimer() - self.done = False - - self.setupStepsProcessingQueue() - - def isComplete(self): - return self.is_done() - - ######################## - - def show_progress(self): - self.progress.show() - self.stepsTableWidget.show() - - def hide_progress(self): - self.progress.hide() - self.stepsTableWidget.hide() - - # pagewizard methods. - # if overriden, child classes should call super. - - def initializePage(self): - self.clean_errors() - self.clean_wizard_errors() - self.steps.removeAllSteps() - self.clearTable() - self.resizeTable() - self.timer.singleShot(0, self.do_checks) diff --git a/src/leap/gui/styles.py b/src/leap/gui/styles.py deleted file mode 100644 index b482922e..00000000 --- a/src/leap/gui/styles.py +++ /dev/null @@ -1,16 +0,0 @@ -GreenLineEdit = "QLabel {color: green; font-weight: bold}" -ErrorLabelStyleSheet = """QLabel { color: red; font-weight: bold }""" -ErrorLineEdit = """QLineEdit { border: 1px solid red; }""" - - -# XXX this is bad. -# and you should feel bad for it. -# The original style has a sort of box color -# white/beige left-top/right-bottom or something like -# that. - -RegularLineEdit = """ -QLineEdit { - border: 1px solid black; -} -""" diff --git a/src/leap/gui/tests/__init__.py b/src/leap/gui/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/leap/gui/tests/integration/fake_user_signup.py b/src/leap/gui/tests/integration/fake_user_signup.py deleted file mode 100644 index 78873749..00000000 --- a/src/leap/gui/tests/integration/fake_user_signup.py +++ /dev/null @@ -1,84 +0,0 @@ -""" -simple server to test registration and -authentication - -To test: - -curl -d login=python_test_user -d password_salt=54321\ - -d password_verifier=12341234 \ - http://localhost:8000/users.json - -""" -from BaseHTTPServer import HTTPServer -from BaseHTTPServer import BaseHTTPRequestHandler -import cgi -import json -import urlparse - -HOST = "localhost" -PORT = 8000 - -LOGIN_ERROR = """{"errors":{"login":["has already been taken"]}}""" - -from leap.base.tests.test_providers import EXPECTED_DEFAULT_CONFIG - - -class request_handler(BaseHTTPRequestHandler): - responses = { - '/': ['ok\n'], - '/users.json': ['ok\n'], - '/timeout': ['ok\n'], - '/provider.json': ['%s\n' % json.dumps(EXPECTED_DEFAULT_CONFIG)] - } - - def do_GET(self): - path = urlparse.urlparse(self.path) - message = '\n'.join( - self.responses.get( - path.path, None)) - self.send_response(200) - self.end_headers() - self.wfile.write(message) - - def do_POST(self): - form = cgi.FieldStorage( - fp=self.rfile, - headers=self.headers, - environ={'REQUEST_METHOD': 'POST', - 'CONTENT_TYPE': self.headers['Content-Type'], - }) - data = dict( - (key, form[key].value) for key in form.keys()) - path = urlparse.urlparse(self.path) - message = '\n'.join( - self.responses.get( - path.path, '')) - - login = data.get('login', None) - #password_salt = data.get('password_salt', None) - #password_verifier = data.get('password_verifier', None) - - if path.geturl() == "/timeout": - print 'timeout' - self.send_response(200) - self.end_headers() - self.wfile.write(message) - import time - time.sleep(10) - return - - ok = True if (login == "python_test_user") else False - if ok: - self.send_response(200) - self.end_headers() - self.wfile.write(message) - - else: - self.send_response(500) - self.end_headers() - self.wfile.write(LOGIN_ERROR) - - -if __name__ == "__main__": - server = HTTPServer((HOST, PORT), request_handler) - server.serve_forever() diff --git a/src/leap/gui/tests/test_firstrun_login.py b/src/leap/gui/tests/test_firstrun_login.py deleted file mode 100644 index 6c45b8ef..00000000 --- a/src/leap/gui/tests/test_firstrun_login.py +++ /dev/null @@ -1,212 +0,0 @@ -import sys -import unittest - -import mock - -from leap.testing import qunittest -#from leap.testing import pyqt - -from PyQt4 import QtGui -#from PyQt4 import QtCore -#import PyQt4.QtCore # some weirdness with mock module - -from PyQt4.QtTest import QTest -from PyQt4.QtCore import Qt - -from leap.gui import firstrun - -try: - from collections import OrderedDict -except ImportError: - # We must be in 2.6 - from leap.util.dicts import OrderedDict - - -class TestPage(firstrun.login.LogInPage): - pass - - -class LogInPageLogicTestCase(qunittest.TestCase): - - # XXX can spy on signal connections - __name__ = "register user page logic tests" - - def setUp(self): - self.app = QtGui.QApplication(sys.argv) - QtGui.qApp = self.app - self.page = TestPage(None) - self.page.wizard = mock.MagicMock() - - def tearDown(self): - QtGui.qApp = None - self.app = None - self.page = None - - def test__do_checks(self): - eq = self.assertEqual - - self.page.userNameLineEdit.setText('testuser@domain') - self.page.userPasswordLineEdit.setText('testpassword') - - # fake register process - with mock.patch('leap.base.auth.LeapSRPRegister') as mockAuth: - mockSignup = mock.MagicMock() - - reqMockup = mock.Mock() - # XXX should inject bad json to get error - reqMockup.content = '{"errors": null}' - mockSignup.register_user.return_value = (True, reqMockup) - mockAuth.return_value = mockSignup - checks = [x for x in self.page._do_checks()] - - eq(len(checks), 4) - labels = [str(x) for (x, y), z in checks] - eq(labels, ['head_sentinel', - 'Resolving domain name', - 'Validating credentials', - 'end_sentinel']) - progress = [y for (x, y), z in checks] - eq(progress, [0, 20, 60, 100]) - - # normal run, ie, no exceptions - - checkfuns = [z for (x, y), z in checks] - checkusername, resolvedomain, valcreds = checkfuns[:-1] - - self.assertTrue(checkusername()) - #self.mocknetchecker.check_name_resolution.assert_called_with( - #'test_provider1') - - self.assertTrue(resolvedomain()) - #self.mockpcertchecker.is_https_working.assert_called_with( - #"https://test_provider1", verify=True) - - self.assertTrue(valcreds()) - - # XXX missing: inject failing exceptions - # XXX TODO make it break - - -class RegisterUserPageUITestCase(qunittest.TestCase): - - # XXX can spy on signal connections - __name__ = "Register User Page UI tests" - - def setUp(self): - self.app = QtGui.QApplication(sys.argv) - QtGui.qApp = self.app - - self.pagename = "signup" - pages = OrderedDict(( - (self.pagename, TestPage), - ('providersetupvalidation', - firstrun.connect.ConnectionPage))) - self.wizard = firstrun.wizard.FirstRunWizard(None, pages_dict=pages) - self.page = self.wizard.page(self.wizard.get_page_index(self.pagename)) - - self.page.do_checks = mock.Mock() - - # wizard would do this for us - self.page.initializePage() - - def tearDown(self): - QtGui.qApp = None - self.app = None - self.wizard = None - - # XXX refactor out - def fill_field(self, field, text): - """ - fills a field (line edit) that is passed along - :param field: the qLineEdit - :param text: the text to be filled - :type field: QLineEdit widget - :type text: str - """ - keyp = QTest.keyPress - field.setFocus(True) - for c in text: - keyp(field, c) - self.assertEqual(field.text(), text) - - def del_field(self, field): - """ - deletes entried text in - field line edit - :param field: the QLineEdit - :type field: QLineEdit widget - """ - keyp = QTest.keyPress - for c in range(len(field.text())): - keyp(field, Qt.Key_Backspace) - self.assertEqual(field.text(), "") - - def test_buttons_disabled_until_textentry(self): - # it's a commit button this time - nextbutton = self.wizard.button(QtGui.QWizard.CommitButton) - - self.assertFalse(nextbutton.isEnabled()) - - f_username = self.page.userNameLineEdit - f_password = self.page.userPasswordLineEdit - - self.fill_field(f_username, "testuser") - self.fill_field(f_password, "testpassword") - - # commit should be enabled - # XXX Need a workaround here - # because the isComplete is not being evaluated... - # (no event loop running??) - #import ipdb;ipdb.set_trace() - #self.assertTrue(nextbutton.isEnabled()) - self.assertTrue(self.page.isComplete()) - - self.del_field(f_username) - self.del_field(f_password) - - # after rm fields commit button - # should be disabled again - #self.assertFalse(nextbutton.isEnabled()) - self.assertFalse(self.page.isComplete()) - - def test_validate_page(self): - self.assertFalse(self.page.validatePage()) - # XXX TODO MOAR CASES... - # add errors, False - # change done, False - # not done, do_checks called - # click confirm, True - # done and do_confirm, True - - def test_next_id(self): - self.assertEqual(self.page.nextId(), 1) - - def test_paint_event(self): - self.page.populateErrors = mock.Mock() - self.page.paintEvent(None) - self.page.populateErrors.assert_called_with() - - def test_validation_ready(self): - f_username = self.page.userNameLineEdit - f_password = self.page.userPasswordLineEdit - - self.fill_field(f_username, "testuser") - self.fill_field(f_password, "testpassword") - - self.page.done = True - self.page.on_checks_validation_ready() - self.assertFalse(f_username.isEnabled()) - self.assertFalse(f_password.isEnabled()) - - self.assertEqual(self.page.validationMsg.text(), - "Credentials validated.") - self.assertEqual(self.page.do_confirm_next, True) - - def test_regex(self): - # XXX enter invalid username with key presses - # check text is not updated - pass - - -if __name__ == "__main__": - unittest.main() diff --git a/src/leap/gui/tests/test_firstrun_providerselect.py b/src/leap/gui/tests/test_firstrun_providerselect.py deleted file mode 100644 index 18d89010..00000000 --- a/src/leap/gui/tests/test_firstrun_providerselect.py +++ /dev/null @@ -1,203 +0,0 @@ -import sys -import unittest - -import mock - -from leap.testing import qunittest -#from leap.testing import pyqt - -from PyQt4 import QtGui -#from PyQt4 import QtCore -#import PyQt4.QtCore # some weirdness with mock module - -from PyQt4.QtTest import QTest -from PyQt4.QtCore import Qt - -from leap.gui import firstrun - -try: - from collections import OrderedDict -except ImportError: - # We must be in 2.6 - from leap.util.dicts import OrderedDict - - -class TestPage(firstrun.providerselect.SelectProviderPage): - pass - - -class SelectProviderPageLogicTestCase(qunittest.TestCase): - - # XXX can spy on signal connections - - def setUp(self): - self.app = QtGui.QApplication(sys.argv) - QtGui.qApp = self.app - self.page = TestPage(None) - self.page.wizard = mock.MagicMock() - - mocknetchecker = mock.Mock() - self.page.wizard().netchecker.return_value = mocknetchecker - self.mocknetchecker = mocknetchecker - - mockpcertchecker = mock.Mock() - self.page.wizard().providercertchecker.return_value = mockpcertchecker - self.mockpcertchecker = mockpcertchecker - - mockeipconfchecker = mock.Mock() - self.page.wizard().eipconfigchecker.return_value = mockeipconfchecker - self.mockeipconfchecker = mockeipconfchecker - - def tearDown(self): - QtGui.qApp = None - self.app = None - self.page = None - - def test__do_checks(self): - eq = self.assertEqual - - self.page.providerNameEdit.setText('test_provider1') - - checks = [x for x in self.page._do_checks()] - eq(len(checks), 5) - labels = [str(x) for (x, y), z in checks] - eq(labels, ['head_sentinel', - 'Checking if it is a valid provider', - 'Checking for a secure connection', - 'Getting info from the provider', - 'end_sentinel']) - progress = [y for (x, y), z in checks] - eq(progress, [0, 20, 40, 80, 100]) - - # normal run, ie, no exceptions - - checkfuns = [z for (x, y), z in checks] - namecheck, httpscheck, fetchinfo = checkfuns[1:-1] - - self.assertTrue(namecheck()) - self.mocknetchecker.check_name_resolution.assert_called_with( - 'test_provider1') - - self.assertTrue(httpscheck()) - self.mockpcertchecker.is_https_working.assert_called_with( - "https://test_provider1", verify=True) - - self.assertTrue(fetchinfo()) - self.mockeipconfchecker.fetch_definition.assert_called_with( - domain="test_provider1") - - # XXX missing: inject failing exceptions - # XXX TODO make it break - - -class SelectProviderPageUITestCase(qunittest.TestCase): - - # XXX can spy on signal connections - __name__ = "Select Provider Page UI tests" - - def setUp(self): - self.app = QtGui.QApplication(sys.argv) - QtGui.qApp = self.app - - self.pagename = "providerselection" - pages = OrderedDict(( - (self.pagename, TestPage), - ('providerinfo', - firstrun.providerinfo.ProviderInfoPage))) - self.wizard = firstrun.wizard.FirstRunWizard(None, pages_dict=pages) - self.page = self.wizard.page(self.wizard.get_page_index(self.pagename)) - - self.page.do_checks = mock.Mock() - - # wizard would do this for us - self.page.initializePage() - - def tearDown(self): - QtGui.qApp = None - self.app = None - self.wizard = None - - def fill_provider(self): - """ - fills provider line edit - """ - keyp = QTest.keyPress - pedit = self.page.providerNameEdit - pedit.setFocus(True) - for c in "testprovider": - keyp(pedit, c) - self.assertEqual(pedit.text(), "testprovider") - - def del_provider(self): - """ - deletes entried provider in - line edit - """ - keyp = QTest.keyPress - pedit = self.page.providerNameEdit - for c in range(len("testprovider")): - keyp(pedit, Qt.Key_Backspace) - self.assertEqual(pedit.text(), "") - - def test_buttons_disabled_until_textentry(self): - nextbutton = self.wizard.button(QtGui.QWizard.NextButton) - checkbutton = self.page.providerCheckButton - - self.assertFalse(nextbutton.isEnabled()) - self.assertFalse(checkbutton.isEnabled()) - - self.fill_provider() - # checkbutton should be enabled - self.assertTrue(checkbutton.isEnabled()) - self.assertFalse(nextbutton.isEnabled()) - - self.del_provider() - # after rm provider checkbutton disabled again - self.assertFalse(checkbutton.isEnabled()) - self.assertFalse(nextbutton.isEnabled()) - - def test_check_button_triggers_tests(self): - checkbutton = self.page.providerCheckButton - self.assertFalse(checkbutton.isEnabled()) - self.assertFalse(self.page.do_checks.called) - - self.fill_provider() - - self.assertTrue(checkbutton.isEnabled()) - mclick = QTest.mouseClick - # click! - mclick(checkbutton, Qt.LeftButton) - self.waitFor(seconds=0.1) - self.assertTrue(self.page.do_checks.called) - - # XXX - # can play with different side_effects for do_checks mock... - # so we can see what happens with errors and so on - - def test_page_completed_after_checks(self): - nextbutton = self.wizard.button(QtGui.QWizard.NextButton) - self.assertFalse(nextbutton.isEnabled()) - - self.assertFalse(self.page.isComplete()) - self.fill_provider() - # simulate checks done - self.page.done = True - self.page.on_checks_validation_ready() - self.assertTrue(self.page.isComplete()) - # cannot test for nexbutton enabled - # cause it's the the wizard loop - # that would do that I think - - def test_validate_page(self): - self.assertTrue(self.page.validatePage()) - - def test_next_id(self): - self.assertEqual(self.page.nextId(), 1) - - def test_paint_event(self): - self.page.populateErrors = mock.Mock() - self.page.paintEvent(None) - self.page.populateErrors.assert_called_with() - -if __name__ == "__main__": - unittest.main() diff --git a/src/leap/gui/tests/test_firstrun_register.py b/src/leap/gui/tests/test_firstrun_register.py deleted file mode 100644 index 9d62f808..00000000 --- a/src/leap/gui/tests/test_firstrun_register.py +++ /dev/null @@ -1,244 +0,0 @@ -import sys -import unittest - -import mock - -from leap.testing import qunittest -#from leap.testing import pyqt - -from PyQt4 import QtGui -#from PyQt4 import QtCore -#import PyQt4.QtCore # some weirdness with mock module - -from PyQt4.QtTest import QTest -from PyQt4.QtCore import Qt - -from leap.gui import firstrun - -try: - from collections import OrderedDict -except ImportError: - # We must be in 2.6 - from leap.util.dicts import OrderedDict - - -class TestPage(firstrun.register.RegisterUserPage): - - def field(self, field): - if field == "provider_domain": - return "testprovider" - - -class RegisterUserPageLogicTestCase(qunittest.TestCase): - - # XXX can spy on signal connections - __name__ = "register user page logic tests" - - def setUp(self): - self.app = QtGui.QApplication(sys.argv) - QtGui.qApp = self.app - self.page = TestPage(None) - self.page.wizard = mock.MagicMock() - - #mocknetchecker = mock.Mock() - #self.page.wizard().netchecker.return_value = mocknetchecker - #self.mocknetchecker = mocknetchecker -# - #mockpcertchecker = mock.Mock() - #self.page.wizard().providercertchecker.return_value = mockpcertchecker - #self.mockpcertchecker = mockpcertchecker -# - #mockeipconfchecker = mock.Mock() - #self.page.wizard().eipconfigchecker.return_value = mockeipconfchecker - #self.mockeipconfchecker = mockeipconfchecker - - def tearDown(self): - QtGui.qApp = None - self.app = None - self.page = None - - def test__do_checks(self): - eq = self.assertEqual - - self.page.userNameLineEdit.setText('testuser') - self.page.userPasswordLineEdit.setText('testpassword') - self.page.userPassword2LineEdit.setText('testpassword') - - # fake register process - with mock.patch('leap.base.auth.LeapSRPRegister') as mockAuth: - mockSignup = mock.MagicMock() - - reqMockup = mock.Mock() - # XXX should inject bad json to get error - reqMockup.content = '{"errors": null}' - mockSignup.register_user.return_value = (True, reqMockup) - mockAuth.return_value = mockSignup - checks = [x for x in self.page._do_checks()] - - eq(len(checks), 3) - labels = [str(x) for (x, y), z in checks] - eq(labels, ['head_sentinel', - 'Registering username', - 'end_sentinel']) - progress = [y for (x, y), z in checks] - eq(progress, [0, 40, 100]) - - # normal run, ie, no exceptions - - checkfuns = [z for (x, y), z in checks] - passcheck, register = checkfuns[:-1] - - self.assertTrue(passcheck()) - #self.mocknetchecker.check_name_resolution.assert_called_with( - #'test_provider1') - - self.assertTrue(register()) - #self.mockpcertchecker.is_https_working.assert_called_with( - #"https://test_provider1", verify=True) - - # XXX missing: inject failing exceptions - # XXX TODO make it break - - -class RegisterUserPageUITestCase(qunittest.TestCase): - - # XXX can spy on signal connections - __name__ = "Register User Page UI tests" - - def setUp(self): - self.app = QtGui.QApplication(sys.argv) - QtGui.qApp = self.app - - self.pagename = "signup" - pages = OrderedDict(( - (self.pagename, TestPage), - ('connect', - firstrun.connect.ConnectionPage))) - self.wizard = firstrun.wizard.FirstRunWizard(None, pages_dict=pages) - self.page = self.wizard.page(self.wizard.get_page_index(self.pagename)) - - self.page.do_checks = mock.Mock() - - # wizard would do this for us - self.page.initializePage() - - def tearDown(self): - QtGui.qApp = None - self.app = None - self.wizard = None - - def fill_field(self, field, text): - """ - fills a field (line edit) that is passed along - :param field: the qLineEdit - :param text: the text to be filled - :type field: QLineEdit widget - :type text: str - """ - keyp = QTest.keyPress - field.setFocus(True) - for c in text: - keyp(field, c) - self.assertEqual(field.text(), text) - - def del_field(self, field): - """ - deletes entried text in - field line edit - :param field: the QLineEdit - :type field: QLineEdit widget - """ - keyp = QTest.keyPress - for c in range(len(field.text())): - keyp(field, Qt.Key_Backspace) - self.assertEqual(field.text(), "") - - def test_buttons_disabled_until_textentry(self): - # it's a commit button this time - nextbutton = self.wizard.button(QtGui.QWizard.CommitButton) - - self.assertFalse(nextbutton.isEnabled()) - - f_username = self.page.userNameLineEdit - f_password = self.page.userPasswordLineEdit - f_passwor2 = self.page.userPassword2LineEdit - - self.fill_field(f_username, "testuser") - self.fill_field(f_password, "testpassword") - self.fill_field(f_passwor2, "testpassword") - - # commit should be enabled - # XXX Need a workaround here - # because the isComplete is not being evaluated... - # (no event loop running??) - #import ipdb;ipdb.set_trace() - #self.assertTrue(nextbutton.isEnabled()) - self.assertTrue(self.page.isComplete()) - - self.del_field(f_username) - self.del_field(f_password) - self.del_field(f_passwor2) - - # after rm fields commit button - # should be disabled again - #self.assertFalse(nextbutton.isEnabled()) - self.assertFalse(self.page.isComplete()) - - @unittest.skip - def test_check_button_triggers_tests(self): - checkbutton = self.page.providerCheckButton - self.assertFalse(checkbutton.isEnabled()) - self.assertFalse(self.page.do_checks.called) - - self.fill_provider() - - self.assertTrue(checkbutton.isEnabled()) - mclick = QTest.mouseClick - # click! - mclick(checkbutton, Qt.LeftButton) - self.waitFor(seconds=0.1) - self.assertTrue(self.page.do_checks.called) - - # XXX - # can play with different side_effects for do_checks mock... - # so we can see what happens with errors and so on - - def test_validate_page(self): - self.assertFalse(self.page.validatePage()) - # XXX TODO MOAR CASES... - # add errors, False - # change done, False - # not done, do_checks called - # click confirm, True - # done and do_confirm, True - - def test_next_id(self): - self.assertEqual(self.page.nextId(), 1) - - def test_paint_event(self): - self.page.populateErrors = mock.Mock() - self.page.paintEvent(None) - self.page.populateErrors.assert_called_with() - - def test_validation_ready(self): - f_username = self.page.userNameLineEdit - f_password = self.page.userPasswordLineEdit - f_passwor2 = self.page.userPassword2LineEdit - - self.fill_field(f_username, "testuser") - self.fill_field(f_password, "testpassword") - self.fill_field(f_passwor2, "testpassword") - - self.page.done = True - self.page.on_checks_validation_ready() - self.assertFalse(f_username.isEnabled()) - self.assertFalse(f_password.isEnabled()) - self.assertFalse(f_passwor2.isEnabled()) - - self.assertEqual(self.page.validationMsg.text(), - "Registration succeeded!") - self.assertEqual(self.page.do_confirm_next, True) - - -if __name__ == "__main__": - unittest.main() diff --git a/src/leap/gui/tests/test_firstrun_wizard.py b/src/leap/gui/tests/test_firstrun_wizard.py deleted file mode 100644 index 395604d3..00000000 --- a/src/leap/gui/tests/test_firstrun_wizard.py +++ /dev/null @@ -1,137 +0,0 @@ -import sys -import unittest - -import mock - -from leap.testing import qunittest -from leap.testing import pyqt - -from PyQt4 import QtGui -#from PyQt4 import QtCore -import PyQt4.QtCore # some weirdness with mock module - -from PyQt4.QtTest import QTest -#from PyQt4.QtCore import Qt - -from leap.gui import firstrun - - -class TestWizard(firstrun.wizard.FirstRunWizard): - pass - - -PAGES_DICT = dict(( - ('intro', firstrun.intro.IntroPage), - ('providerselection', - firstrun.providerselect.SelectProviderPage), - ('login', firstrun.login.LogInPage), - ('providerinfo', firstrun.providerinfo.ProviderInfoPage), - ('providersetupvalidation', - firstrun.providersetup.ProviderSetupValidationPage), - ('signup', firstrun.register.RegisterUserPage), - ('connect', - firstrun.connect.ConnectionPage), - ('lastpage', firstrun.last.LastPage) -)) - - -mockQSettings = mock.MagicMock() -mockQSettings().setValue.return_value = True - -#PyQt4.QtCore.QSettings = mockQSettings - - -class FirstRunWizardTestCase(qunittest.TestCase): - - # XXX can spy on signal connections - - def setUp(self): - self.app = QtGui.QApplication(sys.argv) - QtGui.qApp = self.app - self.wizard = TestWizard(None) - - def tearDown(self): - QtGui.qApp = None - self.app = None - self.wizard = None - - def test_defaults(self): - self.assertEqual(self.wizard.pages_dict, PAGES_DICT) - - @mock.patch('PyQt4.QtCore.QSettings', mockQSettings) - def test_accept(self): - """ - test the main accept method - that gets called when user has gone - thru all the wizard and click on finish button - """ - - self.wizard.success_cb = mock.Mock() - self.wizard.success_cb.return_value = True - - # dummy values; we inject them in the field - # mocks (where wizard gets them) and then - # we check that they are passed to QSettings.setValue - field_returns = ["testuser", "1234", "testprovider", True] - - def field_side_effects(*args): - return field_returns.pop(0) - - self.wizard.field = mock.Mock(side_effect=field_side_effects) - self.wizard.get_random_str = mock.Mock() - RANDOMSTR = "thisisarandomstringTM" - self.wizard.get_random_str.return_value = RANDOMSTR - - # mocked settings (see decorator on this method) - mqs = PyQt4.QtCore.QSettings - - # go! call accept... - self.wizard.accept() - - # did settings().setValue get called with the proper - # arguments? - call = mock.call - calls = [call("FirstRunWizardDone", True), - call("provider_domain", "testprovider"), - call("remember_user_and_pass", True), - call("username", "testuser@testprovider"), - call("testprovider_seed", RANDOMSTR)] - mqs().setValue.assert_has_calls(calls, any_order=True) - - # assert success callback is success oh boy - self.wizard.success_cb.assert_called_with() - - def test_random_str(self): - r = self.wizard.get_random_str(42) - self.assertTrue(len(r) == 42) - - def test_page_index(self): - """ - we test both the get_page_index function - and the correct ordering of names - """ - # remember it's implemented as an ordered dict - - pagenames = ('intro', 'providerselection', 'login', 'providerinfo', - 'providersetupvalidation', 'signup', 'connect', - 'lastpage') - eq = self.assertEqual - w = self.wizard - for index, name in enumerate(pagenames): - eq(w.get_page_index(name), index) - - def test_validation_errors(self): - """ - tests getters and setters for validation errors - """ - page = "testpage" - eq = self.assertEqual - w = self.wizard - eq(w.get_validation_error(page), None) - w.set_validation_error(page, "error") - eq(w.get_validation_error(page), "error") - w.clean_validation_error(page) - eq(w.get_validation_error(page), None) - -if __name__ == "__main__": - unittest.main() diff --git a/src/leap/gui/tests/test_mainwindow_rc.py b/src/leap/gui/tests/test_mainwindow_rc.py deleted file mode 100644 index 9f5172f7..00000000 --- a/src/leap/gui/tests/test_mainwindow_rc.py +++ /dev/null @@ -1,32 +0,0 @@ -import unittest -import hashlib - -try: - import sip - sip.setapi('QVariant', 2) -except ValueError: - pass - -from leap.gui import mainwindow_rc - -# I have to admit that there's something -# perverse in testing this. -# Even though, I still think that it _is_ a good idea -# to put a check to avoid non-updated resources files. - -# so, if you came here because an updated resource -# did break a test, what you have to do is getting -# the md5 hash of your qt_resource_data and change it here. - -# annoying? yep. try making a script for that :P - - -class MainWindowResourcesTest(unittest.TestCase): - - def test_mainwindow_resources_hash(self): - self.assertEqual( - hashlib.md5(mainwindow_rc.qt_resource_data).hexdigest(), - 'e04cb467985ba38b9eb91e7689f9458f') - -if __name__ == "__main__": - unittest.main() diff --git a/src/leap/gui/tests/test_progress.py b/src/leap/gui/tests/test_progress.py deleted file mode 100644 index 1f9f9e38..00000000 --- a/src/leap/gui/tests/test_progress.py +++ /dev/null @@ -1,449 +0,0 @@ -from collections import namedtuple -import sys -import unittest -import Queue - -import mock - -from leap.testing import qunittest -from leap.testing import pyqt - -from PyQt4 import QtGui -from PyQt4 import QtCore -from PyQt4.QtTest import QTest -from PyQt4.QtCore import Qt - -from leap.gui import progress - - -class ProgressStepTestCase(unittest.TestCase): - - def test_step_attrs(self): - ps = progress.ProgressStep - step = ps('test', False, 1) - # instance - self.assertEqual(step.index, 1) - self.assertEqual(step.name, "test") - self.assertEqual(step.done, False) - step = ps('test2', True, 2) - self.assertEqual(step.index, 2) - self.assertEqual(step.name, "test2") - self.assertEqual(step.done, True) - - # class methods and attrs - self.assertEqual(ps.columns(), ('name', 'done')) - self.assertEqual(ps.NAME, 0) - self.assertEqual(ps.DONE, 1) - - -class ProgressStepContainerTestCase(unittest.TestCase): - def setUp(self): - self.psc = progress.ProgressStepContainer() - - def addSteps(self, number): - Step = progress.ProgressStep - for n in range(number): - self.psc.addStep(Step("%s" % n, False, n)) - - def test_attrs(self): - self.assertEqual(self.psc.columns, - ('name', 'done')) - - def test_add_steps(self): - Step = progress.ProgressStep - self.assertTrue(len(self.psc) == 0) - self.psc.addStep(Step('one', False, 0)) - self.assertTrue(len(self.psc) == 1) - self.psc.addStep(Step('two', False, 1)) - self.assertTrue(len(self.psc) == 2) - - def test_del_all_steps(self): - self.assertTrue(len(self.psc) == 0) - self.addSteps(5) - self.assertTrue(len(self.psc) == 5) - self.psc.removeAllSteps() - self.assertTrue(len(self.psc) == 0) - - def test_del_step(self): - Step = progress.ProgressStep - self.addSteps(5) - self.assertTrue(len(self.psc) == 5) - self.psc.removeStep(self.psc.step(4)) - self.assertTrue(len(self.psc) == 4) - self.psc.removeStep(self.psc.step(4)) - self.psc.removeStep(Step('none', False, 5)) - self.psc.removeStep(self.psc.step(4)) - - def test_iter(self): - self.addSteps(10) - self.assertEqual( - [x.index for x in self.psc], - [x for x in range(10)]) - - -class StepsTableWidgetTestCase(unittest.TestCase): - - def setUp(self): - self.app = QtGui.QApplication(sys.argv) - QtGui.qApp = self.app - self.stw = progress.StepsTableWidget() - - def tearDown(self): - QtGui.qApp = None - self.app = None - - def test_defaults(self): - self.assertTrue(isinstance(self.stw, QtGui.QTableWidget)) - self.assertEqual(self.stw.focusPolicy(), 0) - - -class TestWithStepsClass(QtGui.QWidget, progress.WithStepsMixIn): - - def __init__(self, parent=None): - super(TestWithStepsClass, self).__init__(parent=parent) - self.setupStepsProcessingQueue() - self.statuses = [] - self.current_page = "testpage" - - def onStepStatusChanged(self, *args): - """ - blank out this gui method - that will add status lines - """ - self.statuses.append(args) - - -class WithStepsMixInTestCase(qunittest.TestCase): - - TIMER_WAIT = 2 * progress.WithStepsMixIn.STEPS_TIMER_MS / 1000.0 - - # XXX can spy on signal connections - - def setUp(self): - self.app = QtGui.QApplication(sys.argv) - QtGui.qApp = self.app - self.stepy = TestWithStepsClass() - #self.connects = [] - #pyqt.enableSignalDebugging( - #connectCall=lambda *args: self.connects.append(args)) - #self.assertEqual(self.connects, []) - #self.stepy.stepscheck_timer.timeout.disconnect( - #self.stepy.processStepsQueue) - - def tearDown(self): - QtGui.qApp = None - self.app = None - - def test_has_queue(self): - s = self.stepy - self.assertTrue(hasattr(s, 'steps_queue')) - self.assertTrue(isinstance(s.steps_queue, Queue.Queue)) - self.assertTrue(isinstance(s.stepscheck_timer, QtCore.QTimer)) - - def test_do_checks_delegation(self): - s = self.stepy - - _do_checks = mock.Mock() - _do_checks.return_value = ( - (("test", 0), lambda: None), - (("test", 0), lambda: None)) - s._do_checks = _do_checks - s.do_checks() - self.waitFor(seconds=self.TIMER_WAIT) - _do_checks.assert_called_with() - self.assertEqual(len(s.statuses), 2) - - # test that a failed test interrupts the run - - s.statuses = [] - _do_checks = mock.Mock() - _do_checks.return_value = ( - (("test", 0), lambda: None), - (("test", 0), lambda: False), - (("test", 0), lambda: None)) - s._do_checks = _do_checks - s.do_checks() - self.waitFor(seconds=self.TIMER_WAIT) - _do_checks.assert_called_with() - self.assertEqual(len(s.statuses), 2) - - def test_process_queue(self): - s = self.stepy - q = s.steps_queue - s.set_failed_icon = mock.MagicMock() - with self.assertRaises(AssertionError): - q.put('foo') - self.waitFor(seconds=self.TIMER_WAIT) - s.set_failed_icon.assert_called_with() - q.put("failed") - self.waitFor(seconds=self.TIMER_WAIT) - s.set_failed_icon.assert_called_with() - - def test_on_checks_validation_ready_called(self): - s = self.stepy - s.on_checks_validation_ready = mock.MagicMock() - - _do_checks = mock.Mock() - _do_checks.return_value = ( - (("test", 0), lambda: None),) - s._do_checks = _do_checks - s.do_checks() - - self.waitFor(seconds=self.TIMER_WAIT) - s.on_checks_validation_ready.assert_called_with() - - def test_fail(self): - s = self.stepy - - s.wizard = mock.Mock() - wizard = s.wizard.return_value - wizard.set_validation_error.return_value = True - s.completeChanged = mock.Mock() - s.completeChanged.emit.return_value = True - - self.assertFalse(s.fail(err="foo")) - self.waitFor(seconds=self.TIMER_WAIT) - wizard.set_validation_error.assert_called_with('testpage', 'foo') - s.completeChanged.emit.assert_called_with() - - # with no args - s.wizard = mock.Mock() - wizard = s.wizard.return_value - wizard.set_validation_error.return_value = True - s.completeChanged = mock.Mock() - s.completeChanged.emit.return_value = True - - self.assertFalse(s.fail()) - self.waitFor(seconds=self.TIMER_WAIT) - with self.assertRaises(AssertionError): - wizard.set_validation_error.assert_called_with() - s.completeChanged.emit.assert_called_with() - - def test_done(self): - s = self.stepy - s.done = False - - s.completeChanged = mock.Mock() - s.completeChanged.emit.return_value = True - - self.assertFalse(s.is_done()) - s.set_done() - self.assertTrue(s.is_done()) - s.completeChanged.emit.assert_called_with() - - s.completeChanged = mock.Mock() - s.completeChanged.emit.return_value = True - s.set_undone() - self.assertFalse(s.is_done()) - - def test_back_and_next(self): - s = self.stepy - s.wizard = mock.Mock() - wizard = s.wizard.return_value - wizard.back.return_value = True - wizard.next.return_value = True - s.go_back() - wizard.back.assert_called_with() - s.go_next() - wizard.next.assert_called_with() - - def test_on_step_statuschanged_slot(self): - s = self.stepy - s.onStepStatusChanged = progress.WithStepsMixIn.onStepStatusChanged - s.add_status_line = mock.Mock() - s.set_checked_icon = mock.Mock() - s.progress = mock.Mock() - s.progress.setValue.return_value = True - s.progress.update.return_value = True - - s.onStepStatusChanged(s, "end_sentinel") - s.set_checked_icon.assert_called_with() - - s.onStepStatusChanged(s, "foo") - s.add_status_line.assert_called_with("foo") - - s.onStepStatusChanged(s, "bar", 42) - s.progress.setValue.assert_called_with(42) - s.progress.update.assert_called_with() - - def test_steps_and_errors(self): - s = self.stepy - s.setupSteps() - self.assertTrue(isinstance(s.steps, progress.ProgressStepContainer)) - self.assertEqual(s.errors, {}) - s.set_error('fooerror', 'barerror') - self.assertEqual(s.errors, {'fooerror': 'barerror'}) - s.set_error('2', 42) - self.assertEqual(s.errors, {'fooerror': 'barerror', '2': 42}) - fe = s.pop_first_error() - self.assertEqual(fe, ('fooerror', 'barerror')) - self.assertEqual(s.errors, {'2': 42}) - s.clean_errors() - self.assertEqual(s.errors, {}) - - def test_launch_chechs_slot(self): - s = self.stepy - s.do_checks = mock.Mock() - s.launch_checks() - s.do_checks.assert_called_with() - - def test_clean_wizard_errors(self): - s = self.stepy - s.wizard = mock.Mock() - wizard = s.wizard.return_value - wizard.set_validation_error.return_value = True - s.clean_wizard_errors(pagename="foopage") - wizard.set_validation_error.assert_called_with("foopage", None) - - def test_clear_table(self): - s = self.stepy - s.stepsTableWidget = mock.Mock() - s.stepsTableWidget.clearContents.return_value = True - s.clearTable() - s.stepsTableWidget.clearContents.assert_called_with() - - def test_populate_steps_table(self): - s = self.stepy - Step = namedtuple('Step', ['name', 'done']) - - class Steps(object): - columns = ("name", "done") - _items = (Step('step1', False), Step('step2', False)) - - def __len__(self): - return 2 - - def __iter__(self): - for i in self._items: - yield i - - s.steps = Steps() - - s.stepsTableWidget = mock.Mock() - s.stepsTableWidget.setItem.return_value = True - s.resizeTable = mock.Mock() - s.update = mock.Mock() - s.populateStepsTable() - s.update.assert_called_with() - s.resizeTable.assert_called_with() - - # assert stepsTableWidget.setItem called ... - # we do not want to get into the actual - # - call_list = s.stepsTableWidget.setItem.call_args_list - indexes = [(y, z) for y, z, xx in [x[0] for x in call_list]] - self.assertEqual(indexes, - [(0, 0), (0, 1), (1, 0), (1, 1)]) - - def test_add_status_line(self): - s = self.stepy - s.steps = progress.ProgressStepContainer() - s.stepsTableWidget = mock.Mock() - s.stepsTableWidget.width.return_value = 100 - s.set_item = mock.Mock() - s.set_item_icon = mock.Mock() - s.add_status_line("new status") - s.set_item_icon.assert_called_with(current=False) - - def test_set_item_icon(self): - s = self.stepy - s.steps = progress.ProgressStepContainer() - s.stepsTableWidget = mock.Mock() - s.stepsTableWidget.setCellWidget.return_value = True - s.stepsTableWidget.width.return_value = 100 - #s.set_item = mock.Mock() - #s.set_item_icon = mock.Mock() - s.add_status_line("new status") - s.add_status_line("new 2 status") - s.add_status_line("new 3 status") - call_list = s.stepsTableWidget.setCellWidget.call_args_list - indexes = [(y, z) for y, z, xx in [x[0] for x in call_list]] - self.assertEqual( - indexes, - [(0, 1), (-1, 1), (1, 1), (0, 1), (2, 1), (1, 1)]) - - -class TestInlineValidationPage(progress.InlineValidationPage): - pass - - -class InlineValidationPageTestCase(unittest.TestCase): - - def setUp(self): - self.app = QtGui.QApplication(sys.argv) - QtGui.qApp = self.app - self.page = TestInlineValidationPage() - - def tearDown(self): - QtGui.qApp = None - self.app = None - - def test_defaults(self): - self.assertFalse(self.page.done) - # if setupProcessingQueue was called - self.assertTrue(isinstance(self.page.stepscheck_timer, QtCore.QTimer)) - self.assertTrue(isinstance(self.page.steps_queue, Queue.Queue)) - - def test_validation_frame(self): - # test frame creation - self.page.stepsTableWidget = progress.StepsTableWidget( - parent=self.page) - self.page.setupValidationFrame() - self.assertTrue(isinstance(self.page.valFrame, QtGui.QFrame)) - - # test show steps calls frame.show - self.page.valFrame = mock.Mock() - self.page.valFrame.show.return_value = True - self.page.showStepsFrame() - self.page.valFrame.show.assert_called_with() - - -class TestValidationPage(progress.ValidationPage): - pass - - -class ValidationPageTestCase(unittest.TestCase): - - def setUp(self): - self.app = QtGui.QApplication(sys.argv) - QtGui.qApp = self.app - self.page = TestValidationPage() - - def tearDown(self): - QtGui.qApp = None - self.app = None - - def test_defaults(self): - self.assertFalse(self.page.done) - # if setupProcessingQueue was called - self.assertTrue(isinstance(self.page.timer, QtCore.QTimer)) - self.assertTrue(isinstance(self.page.stepscheck_timer, QtCore.QTimer)) - self.assertTrue(isinstance(self.page.steps_queue, Queue.Queue)) - - def test_is_complete(self): - self.assertFalse(self.page.isComplete()) - self.page.done = True - self.assertTrue(self.page.isComplete()) - self.page.done = False - self.assertFalse(self.page.isComplete()) - - def test_show_hide_progress(self): - p = self.page - p.progress = mock.Mock() - p.progress.show.return_code = True - p.show_progress() - p.progress.show.assert_called_with() - p.progress.hide.return_code = True - p.hide_progress() - p.progress.hide.assert_called_with() - - def test_initialize_page(self): - p = self.page - p.timer = mock.Mock() - p.timer.singleShot.return_code = True - p.initializePage() - p.timer.singleShot.assert_called_with(0, p.do_checks) - - -if __name__ == "__main__": - unittest.main() diff --git a/src/leap/gui/tests/test_threads.py b/src/leap/gui/tests/test_threads.py deleted file mode 100644 index 06c19606..00000000 --- a/src/leap/gui/tests/test_threads.py +++ /dev/null @@ -1,27 +0,0 @@ -import unittest - -import mock -from leap.gui import threads - - -class FunThreadTestCase(unittest.TestCase): - - def setUp(self): - self.fun = mock.MagicMock() - self.fun.return_value = "foo" - self.t = threads.FunThread(fun=self.fun) - - def test_thread(self): - self.t.begin() - self.t.wait() - self.fun.assert_called() - del self.t - - def test_run(self): - # this is called by PyQt - self.t.run() - del self.t - self.fun.assert_called() - -if __name__ == "__main__": - unittest.main() diff --git a/src/leap/gui/threads.py b/src/leap/gui/threads.py deleted file mode 100644 index 8aad8866..00000000 --- a/src/leap/gui/threads.py +++ /dev/null @@ -1,21 +0,0 @@ -from PyQt4 import QtCore - - -class FunThread(QtCore.QThread): - - def __init__(self, fun=None, parent=None): - - QtCore.QThread.__init__(self, parent) - self.exiting = False - self.fun = fun - - def __del__(self): - self.exiting = True - self.wait() - - def run(self): - if self.fun: - self.fun() - - def begin(self): - self.start() diff --git a/src/leap/gui/utils.py b/src/leap/gui/utils.py deleted file mode 100644 index f91ac3ef..00000000 --- a/src/leap/gui/utils.py +++ /dev/null @@ -1,34 +0,0 @@ -""" -utility functions to work with gui objects -""" -from PyQt4 import QtCore - - -def layout_widgets(layout): - """ - return a generator with all widgets in a layout - """ - return (layout.itemAt(i) for i in range(layout.count())) - - -DELAY_MSECS = 50 - - -def delay(obj, method_str=None, call_args=None): - """ - Triggers a function or slot with a small delay. - this is a mainly a hack to get responsiveness in the ui - in cases in which the event loop freezes and the task - is not heavy enough to setup a processing queue. - """ - if callable(obj) and not method_str: - fun = lambda: obj() - - if method_str: - invoke = QtCore.QMetaObject.invokeMethod - if call_args: - fun = lambda: invoke(obj, method_str, call_args) - else: - fun = lambda: invoke(obj, method_str) - - QtCore.QTimer().singleShot(DELAY_MSECS, fun) -- cgit v1.2.3 From 361a18b0e727a68d6e0d1e9d03273630b9c14692 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 6 Mar 2013 15:43:36 -0300 Subject: Add UI merging all the code Also add resources --- src/leap/gui/__init__.py | 0 src/leap/gui/mainwindow.py | 600 +++++++++++++++++++++++++++++++ src/leap/gui/ui/mainwindow.ui | 377 ++++++++++++++++++++ src/leap/gui/ui/wizard.ui | 800 ++++++++++++++++++++++++++++++++++++++++++ src/leap/gui/wizard.py | 403 +++++++++++++++++++++ src/leap/gui/wizardpage.py | 39 ++ 6 files changed, 2219 insertions(+) create mode 100644 src/leap/gui/__init__.py create mode 100644 src/leap/gui/mainwindow.py create mode 100644 src/leap/gui/ui/mainwindow.ui create mode 100644 src/leap/gui/ui/wizard.ui create mode 100644 src/leap/gui/wizard.py create mode 100644 src/leap/gui/wizardpage.py (limited to 'src/leap/gui') diff --git a/src/leap/gui/__init__.py b/src/leap/gui/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py new file mode 100644 index 00000000..1821e4a6 --- /dev/null +++ b/src/leap/gui/mainwindow.py @@ -0,0 +1,600 @@ +# -*- coding: utf-8 -*- +# mainwindow.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 . + +""" +Main window for the leap client +""" +import os +import logging + +from PySide import QtCore, QtGui + +from ui_mainwindow import Ui_MainWindow +from leap.config.providerconfig import ProviderConfig +from leap.crypto.srpauth import SRPAuth +from leap.services.eip.vpn import VPN +from leap.services.eip.providerbootstrapper import ProviderBootstrapper +from leap.services.eip.eipbootstrapper import EIPBootstrapper +from leap.services.eip.eipconfig import EIPConfig +from leap.gui.wizard import Wizard + +logger = logging.getLogger(__name__) + + +class MainWindow(QtGui.QMainWindow): + """ + Main window for login and presenting status updates to the user + """ + + # StackedWidget indexes + LOGIN_INDEX = 0 + EIP_STATUS_INDEX = 1 + + def __init__(self): + QtGui.QMainWindow.__init__(self) + + self.CONNECTING_ICON = QtGui.QPixmap(":/images/conn_connecting.png") + self.CONNECTED_ICON = QtGui.QPixmap(":/images/conn_connected.png") + self.ERROR_ICON = QtGui.QPixmap(":/images/conn_error.png") + + self.ui = Ui_MainWindow() + self.ui.setupUi(self) + + self.ui.lnPassword.setEchoMode(QtGui.QLineEdit.Password) + + self.ui.btnLogin.clicked.connect(self._login) + self.ui.lnUser.returnPressed.connect(self._focus_password) + self.ui.lnPassword.returnPressed.connect(self._login) + + self.ui.stackedWidget.setCurrentIndex(self.LOGIN_INDEX) + + # This is loaded only once, there's a bug when doing that more + # than once + self._provider_config = ProviderConfig() + self._eip_config = EIPConfig() + # This is created once we have a valid provider config + self._srp_auth = None + + # This thread is always running, although it's quite + # lightweight when it's done setting up provider + # configuration and certificate. + self._provider_bootstrapper = ProviderBootstrapper() + + # TODO: add sigint handler + + # Intermediate stages, only do something if there was an error + self._provider_bootstrapper.name_resolution.connect( + self._intermediate_stage) + self._provider_bootstrapper.https_connection.connect( + self._intermediate_stage) + self._provider_bootstrapper.download_ca_cert.connect( + self._intermediate_stage) + + # Important stages, loads the provider config and checks + # certificates + self._provider_bootstrapper.download_provider_info.connect( + self._load_provider_config) + self._provider_bootstrapper.check_api_certificate.connect( + self._provider_config_loaded) + + # This thread is similar to the provider bootstrapper + self._eip_bootstrapper = EIPBootstrapper() + + self._eip_bootstrapper.download_config.connect( + self._intermediate_stage) + self._eip_bootstrapper.download_client_certificate.connect( + self._start_eip) + + self._vpn = VPN() + self._vpn.state_changed.connect(self._update_vpn_state) + self._vpn.status_changed.connect(self._update_vpn_status) + + QtCore.QCoreApplication.instance().connect( + QtCore.QCoreApplication.instance(), + QtCore.SIGNAL("aboutToQuit()"), + self._vpn.set_should_quit) + QtCore.QCoreApplication.instance().connect( + QtCore.QCoreApplication.instance(), + QtCore.SIGNAL("aboutToQuit()"), + self._provider_bootstrapper.set_should_quit) + QtCore.QCoreApplication.instance().connect( + QtCore.QCoreApplication.instance(), + QtCore.SIGNAL("aboutToQuit()"), + self._eip_bootstrapper.set_should_quit) + + 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) + self.ui.action_quit.triggered.connect(self.quit) + self.ui.action_wizard.triggered.connect(self._launch_wizard) + + # Used to differentiate between real quits and close to tray + self._really_quit = False + + self._systray = None + self._action_visible = QtGui.QAction("Hide", self) + self._action_visible.triggered.connect(self._toggle_visible) + + self._center_window() + self._wizard = None + if self._first_run(): + self._wizard = Wizard() + # Give this window time to finish init and then show the wizard + QtCore.QTimer.singleShot(1, self._launch_wizard) + self._wizard.finished.connect(self._finish_init) + else: + self._finish_init() + + def _launch_wizard(self): + if self._wizard is None: + self._wizard = Wizard() + self._wizard.exec_() + + def _finish_init(self): + self.ui.cmbProviders.addItems(self._configured_providers()) + self._show_systray() + self.show() + if self._wizard: + possible_username = self._wizard.get_username() + if possible_username is not None: + self.ui.lnUser.setText(possible_username) + self._focus_password() + self._wizard = None + + def _show_systray(self): + """ + Sets up the systray icon + """ + systrayMenu = QtGui.QMenu(self) + systrayMenu.addAction(self._action_visible) + systrayMenu.addAction(self.ui.action_sign_out) + systrayMenu.addSeparator() + systrayMenu.addAction(self.ui.action_quit) + 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) + + def _toggle_visible(self): + """ + SLOT + TRIGGER: self._systray.activated + + Toggles the window visibility + """ + self.setVisible(not self.isVisible()) + action_visible_text = "Hide" + if not self.isVisible(): + action_visible_text = "Show" + self._action_visible.setText(action_visible_text) + + def _center_window(self): + """ + Centers the mainwindow based on the desktop geometry + """ + app = QtGui.QApplication.instance() + width = app.desktop().width() + height = app.desktop().height() + window_width = self.size().width() + window_height = self.size().height() + x = (width / 2.0) - (window_width / 2.0) + y = (height / 2.0) - (window_height / 2.0) + self.move(x, y) + + def _about(self): + """ + Display the About LEAP dialog + """ + QtGui.QMessageBox.about(self, "About LEAP", + "LEAP is a non-profit dedicated to giving " + "all internet users access to secure " + "communication. Our focus is on adapting " + "encryption technology to make it easy to use " + "and widely available. " + "More about LEAP" + "") + + def quit(self): + self._really_quit = True + if self._wizard: + self._wizard.accept() + self.close() + + def changeEvent(self, e): + """ + Reimplements the changeEvent method to minimize to tray + """ + if QtGui.QSystemTrayIcon.isSystemTrayAvailable() and \ + e.type() == QtCore.QEvent.WindowStateChange and \ + self.isMinimized(): + self._toggle_visible() + e.accept() + return + QtGui.QMainWindow.changeEvent(self, e) + + def closeEvent(self, e): + """ + Reimplementation of closeEvent to close to tray + """ + if QtGui.QSystemTrayIcon.isSystemTrayAvailable() and \ + not self._really_quit: + self._toggle_visible() + e.ignore() + return + QtGui.QMainWindow.closeEvent(self, e) + + def _configured_providers(self): + """ + Returns the available providers based on the file structure + + @rtype: list + """ + providers = os.listdir( + os.path.join(self._provider_config.get_path_prefix(), + "leap", + "providers")) + return providers + + def _first_run(self): + """ + Returns True if there are no configured providers. False otherwise + + @rtype: bool + """ + return len(self._configured_providers()) == 0 + + def _focus_password(self): + """ + Focuses in the password lineedit + """ + self.ui.lnPassword.setFocus() + + def _set_status(self, status): + """ + Sets the status label at the login stage to status + + @param status: status message + @type status: str + """ + self.ui.lblStatus.setText(status) + + def _set_eip_status(self, status): + """ + Sets the status label at the VPN stage to status + + @param status: status message + @type status: str + """ + 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) + 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() + + self._provider_bootstrapper.start() + self._provider_bootstrapper.run_provider_select_checks( + provider, + download_if_needed=True) + + def _load_provider_config(self, data): + """ + SLOT + TRIGGER: self._provider_bootstrapper.download_provider_info + + Once the provider config has been downloaded, this loads the + self._provider_config instance with it and starts the second + part of the bootstrapping sequence + + @param data: result from the last stage of the + run_provider_select_checks + @type data: dict + """ + if data[self._provider_bootstrapper.PASSED_KEY]: + provider = self.ui.cmbProviders.currentText() + if self._provider_config.loaded() or \ + self._provider_config.load(os.path.join("leap", + "providers", + provider, + "provider.json")): + self._provider_bootstrapper.run_provider_setup_checks( + self._provider_config, + download_if_needed=True) + else: + self._set_status("Could not load provider configuration") + self._login_set_enabled(True) + else: + self._set_status(data[self._provider_bootstrapper.ERROR_KEY]) + self._login_set_enabled(True) + + def _login(self): + """ + SLOT + TRIGGERS: + self.ui.btnLogin.clicked + self.ui.lnPassword.returnPressed + + Starts the login sequence. Which involves bootstrapping the + selected provider if the selection is valid (not empty), then + start the SRP authentication, and as the last step + bootstrapping the EIP service + """ + assert self._provider_config, "We need a provider config" + + username = self.ui.lnUser.text() + password = self.ui.lnPassword.text() + provider = self.ui.cmbProviders.currentText() + + if len(provider) == 0: + self._set_status("Please select a valid provider") + return + + if len(username) == 0: + self._set_status("Please provide a valid username") + return + + if len(password) == 0: + self._set_status("Please provide a valid Password") + return + + self._set_status("Logging in...") + self._login_set_enabled(False) + + self._download_provider_config() + + def _provider_config_loaded(self, data): + """ + SLOT + TRIGGER: self._provider_bootstrapper.check_api_certificate + + Once the provider configuration is loaded, this starts the SRP + authentication + """ + assert self._provider_config, "We need a provider config!" + + self._provider_bootstrapper.set_should_quit() + + if data[self._provider_bootstrapper.PASSED_KEY]: + username = self.ui.lnUser.text() + password = self.ui.lnPassword.text() + + if self._srp_auth is None: + self._srp_auth = SRPAuth(self._provider_config) + self._srp_auth.authentication_finished.connect( + self._authentication_finished) + self._srp_auth.logout_finished.connect( + self._done_logging_out) + + self._srp_auth.authenticate(username, password) + else: + self._set_status(data[self._provider_bootstrapper.ERROR_KEY]) + self._login_set_enabled(True) + + def _authentication_finished(self, ok, message): + """ + SLOT + TRIGGER: self._srp_auth.authentication_finished + + Once the user is properly authenticated, try starting the EIP + service + """ + self._set_status(message) + if ok: + self.ui.action_sign_out.setEnabled(True) + # We leave a bit of room for the user to see the + # "Succeeded" message and then we switch to the EIP status + # panel + QtCore.QTimer.singleShot(1000, self._switch_to_status) + else: + self._login_set_enabled(True) + + def _switch_to_status(self): + """ + Changes the stackedWidget index to the EIP status one and + triggers the eip bootstrapping + """ + self.ui.stackedWidget.setCurrentIndex(self.EIP_STATUS_INDEX) + self._download_eip_config() + + def _download_eip_config(self): + """ + Starts the EIP bootstrapping sequence + """ + assert self._eip_bootstrapper, "We need an eip bootstrapper!" + assert self._provider_config, "We need a provider config" + + self._set_eip_status("Checking configuration, please wait...") + + if self._provider_config.provides_eip(): + self._eip_bootstrapper.start() + self._eip_bootstrapper.run_eip_setup_checks( + self._provider_config, + download_if_needed=True) + else: + self._set_eip_status("%s does not support EIP" % + (self._provider_config.get_domain(),)) + + def _set_eip_status_icon(self, status): + """ + Given a status step from the VPN thread, set the icon properly + + @param status: status step + @type status: str + """ + selected_pixmap = self.ERROR_ICON + if status in ("AUTH", "GET_CONFIG"): + selected_pixmap = self.CONNECTING_ICON + elif status in ("CONNECTED"): + selected_pixmap = self.CONNECTED_ICON + + self.ui.lblVPNStatusIcon.setPixmap(selected_pixmap) + self._systray.setIcon(QtGui.QIcon(selected_pixmap)) + + def _update_vpn_state(self, data): + """ + SLOT + TRIGGER: self._vpn.state_changed + + Updates the displayed VPN state based on the data provided by + the VPN thread + """ + status = data[self._vpn.STATUS_STEP_KEY] + self._set_eip_status_icon(status) + if status == "AUTH": + self._set_eip_status("VPN: Authenticating...") + elif status == "GET_CONFIG": + self._set_eip_status("VPN: Retrieving configuration...") + elif status == "CONNECTED": + self._set_eip_status("VPN: Connected!") + else: + self._set_eip_status(status) + + def _update_vpn_status(self, data): + """ + SLOT + TRIGGER: self._vpn.status_changed + + Updates the download/upload labels based on the data provided + by the VPN thread + """ + upload = float(data[self._vpn.TUNTAP_WRITE_KEY]) + upload = upload / 1000.0 + self.ui.lblUpload.setText("%s Kb" % (upload,)) + download = float(data[self._vpn.TUNTAP_READ_KEY]) + download = download / 1000.0 + self.ui.lblDownload.setText("%s Kb" % (download,)) + + def _start_eip(self, data): + """ + SLOT + TRIGGER: self._eip_bootstrapper.download_client_certificate + + Starts the VPN thread if the eip configuration is properly + loaded + """ + assert self._eip_config, "We need an eip config!" + assert self._provider_config, "We need a provider config!" + + self._eip_bootstrapper.set_should_quit() + if self._eip_config.loaded() or \ + self._eip_config.load(os.path.join("leap", + "providers", + self._provider_config + .get_domain(), + "eip-service.json")): + self._vpn.start(eipconfig=self._eip_config, + providerconfig=self._provider_config, + socket_host="/home/chiiph/vpnsock", + socket_port="unix") + # TODO: display a message if the EIP configuration cannot be + # loaded + + def _logout(self): + """ + SLOT + TRIGGER: self.ui.action_sign_out.triggered + + Starts the logout sequence + """ + self._set_eip_status_icon("error") + self._set_eip_status("Signing out...") + self._srp_auth.logout() + + def _done_logging_out(self, ok, message): + """ + SLOT + TRIGGER: self._srp_auth.logout_finished + + Switches the stackedWidget back to the login stage after + logging out + """ + self._set_status(message) + 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._vpn.set_should_quit() + + def _intermediate_stage(self, data): + """ + SLOT + TRIGGERS: + self._provider_bootstrapper.name_resolution + self._provider_bootstrapper.https_connection + self._provider_bootstrapper.download_ca_cert + self._eip_bootstrapper.download_config + + If there was a problem, displays it, otherwise it does nothing. + This is used for intermediate bootstrapping stages, in case + they fail. + """ + passed = data[self._provider_bootstrapper.PASSED_KEY] + if not passed: + self._login_set_enabled(True) + self._set_status(data[self._provider_bootstrapper.ERROR_KEY]) + +if __name__ == "__main__": + import signal + from functools import partial + + def sigint_handler(*args, **kwargs): + logger.debug('SIGINT catched. shutting down...') + mainwindow = args[0] + mainwindow.quit() + + import sys + + logger = logging.getLogger(name='leap') + logger.setLevel(logging.DEBUG) + console = logging.StreamHandler() + console.setLevel(logging.DEBUG) + formatter = logging.Formatter( + '%(asctime)s ' + '- %(name)s - %(levelname)s - %(message)s') + console.setFormatter(formatter) + logger.addHandler(console) + + app = QtGui.QApplication(sys.argv) + mainwindow = MainWindow() + mainwindow.show() + + timer = QtCore.QTimer() + timer.start(500) + timer.timeout.connect(lambda: None) + + sigint = partial(sigint_handler, mainwindow) + signal.signal(signal.SIGINT, sigint) + + sys.exit(app.exec_()) diff --git a/src/leap/gui/ui/mainwindow.ui b/src/leap/gui/ui/mainwindow.ui new file mode 100644 index 00000000..a527eaf6 --- /dev/null +++ b/src/leap/gui/ui/mainwindow.ui @@ -0,0 +1,377 @@ + + + MainWindow + + + + 0 + 0 + 405 + 579 + + + + LEAP + + + + :/images/leap-color-big.png:/images/leap-color-big.png + + + Qt::ImhHiddenText + + + + 128 + 128 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + 1 + + + + + + + + + + Remember + + + + + + + Login + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <b>Provider:</b> + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + <b>Password:</b> + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + + + + + + <b>User:</b> + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Qt::AlignCenter + + + + + + + + + + + Disconnected + + + Qt::AlignCenter + + + + + + + + + + :/images/conn_error.png + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 70 + 0 + + + + 0.0 Kb + + + + + + + + 70 + 0 + + + + 0.0 Kb + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + :/images/Arrow-Up-32.png + + + + + + + + + + :/images/Arrow-Down-32.png + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + + + + :/images/leap-color-big.png + + + Qt::AlignCenter + + + + + + + + + 0 + 0 + 405 + 25 + + + + + &Session + + + + + + + + Help + + + + + + + + S&ettings + + + + + + + + + + + &Sign out + + + + + &Quit + + + + + About &LEAP + + + + + &Help + + + + + &Wizard + + + + + lnUser + lnPassword + chkRemember + btnLogin + cmbProviders + + + + + + diff --git a/src/leap/gui/ui/wizard.ui b/src/leap/gui/ui/wizard.ui new file mode 100644 index 00000000..86f8d458 --- /dev/null +++ b/src/leap/gui/ui/wizard.ui @@ -0,0 +1,800 @@ + + + Wizard + + + + 0 + 0 + 536 + 452 + + + + LEAP First run + + + + :/images/leap-color-big.png:/images/leap-color-big.png + + + true + + + QWizard::ModernStyle + + + QWizard::IndependentPages + + + + Welcome + + + This is the LEAP Client first run wizard + + + 0 + + + + + + Log In with my credentials + + + + + + + <html><head/><body><p>New we will guide you through some configuration that is needed before you can connect for the first time.</p><p>If you ever need to modify these options again, you can find the wizard in the <span style=" font-style:italic;">'Settings'</span> menu from the main window.</p><p>Do you want to <span style=" font-weight:600;">sign up</span> for a new account, or <span style=" font-weight:600;">log in</span> with an already existing username?</p></body></html> + + + Qt::RichText + + + true + + + + + + + Sign up for a new account + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Provider selection + + + Please enter the domain of the provider you want to user for your connection + + + 1 + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 60 + + + + + + + + https:// + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Checking provider + + + + + + Download provider information + + + + + + + HTTPS Connection + + + + + + + + 0 + 0 + + + + + + + :/images/Emblem-question.png + + + + + + + + 0 + 0 + + + + + + + :/images/Emblem-question.png + + + + + + + + 0 + 0 + + + + + + + :/images/Emblem-question.png + + + + + + + Name resolution + + + + + + + Qt::Horizontal + + + + 40 + 0 + + + + + + + + + + + Check + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Provider Information + + + Services offered by this provider + + + 2 + + + + + + Qt::Horizontal + + + + 40 + 0 + + + + + + + + <b>Enrollment policy:</b> + + + + + + + URL + + + + + + + Name + + + + + + + Qt::Horizontal + + + + 40 + 0 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + policy + + + + + + + Desc + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Provider setup + + + Gathering configuration options for this provider + + + 3 + + + + + + Checking provider + + + + + + + 0 + 0 + + + + + + + :/images/Emblem-question.png + + + + + + + + 0 + 0 + + + + + + + :/images/Emblem-question.png + + + + + + + Download CA Certificate + + + + + + + Check CA Certificate Fingerprint + + + + + + + Check API Certificate + + + + + + + + 0 + 0 + + + + + + + :/images/Emblem-question.png + + + + + + + Qt::Horizontal + + + + 40 + 0 + + + + + + + + + + + Qt::Vertical + + + + 20 + 60 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Register new user + + + Register a new user with provider + + + 4 + + + + + + + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + <b>User:</b> + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + <b>Password:</b> + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + + + + + <b>Re-enter password:</b> + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Register + + + + + + + Qt::Vertical + + + + 20 + 60 + + + + + + + + + EIP Setup + + + Setting up Encrypted Internet + + + 5 + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Checking EIP + + + + + + Download EIP configuration + + + + + + + Download client certificate + + + + + + + + 0 + 0 + + + + + + + :/images/Emblem-question.png + + + + + + + + 0 + 0 + + + + + + + :/images/Emblem-question.png + + + + + + + Qt::Horizontal + + + + 40 + 0 + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Congratulations! + + + You have successfully configured the LEAP client. + + + 6 + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + :/images/leap-color-big.png + + + + + + + + 0 + 0 + + + + + + + :/images/Globe.png + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + WizardPage + QWizardPage +
wizardpage.h
+ 1 +
+
+ + lblUser + lblPassword + lblPassword2 + btnRegister + rdoRegister + rdoLogin + lnProvider + btnCheck + + + + + +
diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py new file mode 100644 index 00000000..7dcc8dd6 --- /dev/null +++ b/src/leap/gui/wizard.py @@ -0,0 +1,403 @@ +# -*- coding: utf-8 -*- +# wizard.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 . + +""" +First run wizard +""" +import os +import logging + +from PySide import QtCore, QtGui + +from ui_wizard import Ui_Wizard +from leap.config.providerconfig import ProviderConfig +from leap.crypto.srpregister import SRPRegister +from leap.services.eip.providerbootstrapper import ProviderBootstrapper +from leap.services.eip.eipbootstrapper import EIPBootstrapper + +logger = logging.getLogger(__name__) + + +class Wizard(QtGui.QWizard): + """ + First run wizard to register a user and setup a provider + """ + + INTRO_PAGE = 0 + SELECT_PROVIDER_PAGE = 1 + PRESENT_PROVIDER_PAGE = 2 + SETUP_PROVIDER_PAGE = 3 + REGISTER_USER_PAGE = 4 + SETUP_EIP_PAGE = 5 + FINISH_PATH = 6 + + WEAK_PASSWORDS = ("1234", "12345", "123456", + "password") + + def __init__(self): + QtGui.QWizard.__init__(self) + + self.ui = Ui_Wizard() + self.ui.setupUi(self) + + self.QUESTION_ICON = QtGui.QPixmap(":/images/Emblem-question.png") + self.ERROR_ICON = QtGui.QPixmap(":/images/Dialog-error.png") + self.OK_ICON = QtGui.QPixmap(":/images/Dialog-accept.png") + + self._show_register = False + + self.ui.grpCheckProvider.setVisible(False) + self.ui.btnCheck.clicked.connect(self._check_provider) + self.ui.lnProvider.returnPressed.connect(self._check_provider) + + self._provider_bootstrapper = ProviderBootstrapper() + self._provider_bootstrapper.name_resolution.connect( + self._name_resolution) + self._provider_bootstrapper.https_connection.connect( + self._https_connection) + self._provider_bootstrapper.download_provider_info.connect( + self._download_provider_info) + + self._provider_bootstrapper.download_ca_cert.connect( + self._download_ca_cert) + self._provider_bootstrapper.check_ca_fingerprint.connect( + self._check_ca_fingerprint) + self._provider_bootstrapper.check_api_certificate.connect( + self._check_api_certificate) + + self._eip_bootstrapper = EIPBootstrapper() + + self._eip_bootstrapper.download_config.connect( + self._download_eip_config) + self._eip_bootstrapper.download_client_certificate.connect( + self._download_client_certificate) + + self._domain = None + self._provider_config = ProviderConfig() + + self.currentIdChanged.connect(self._current_id_changed) + + self.ui.lblPassword.setEchoMode(QtGui.QLineEdit.Password) + self.ui.lblPassword2.setEchoMode(QtGui.QLineEdit.Password) + + self.ui.lblUser.returnPressed.connect( + self._focus_password) + self.ui.lblPassword.returnPressed.connect( + self._focus_second_password) + self.ui.lblPassword2.returnPressed.connect( + self._register) + self.ui.btnRegister.clicked.connect( + self._register) + + self._username = None + + def __del__(self): + self._provider_bootstrapper.set_should_quit() + self._eip_bootstrapper.set_should_quit() + self._provider_bootstrapper.wait() + self._eip_bootstrapper.wait() + + def get_username(self): + return self._username + + def _focus_password(self): + """ + Focuses at the password lineedit for the registration page + """ + self.ui.lblPassword.setFocus() + + def _focus_second_password(self): + """ + Focuses at the second password lineedit for the registration page + """ + self.ui.lblPassword2.setFocus() + + def _basic_password_checks(self, username, password, password2): + """ + Performs basic password checks to avoid really easy passwords. + + @param username: username provided at the registrarion form + @type username: str + @param password: password from the registration form + @type password: str + @param password2: second password from the registration form + @type password: str + + @return: returns True if all the checks pass, False otherwise + @rtype: bool + """ + message = None + + try: + username.encode("ascii") + password.encode("ascii") + except: + message = u"Refrain from using non ASCII áñ characters" + + if message is not None and password != password2: + message = "Passwords don't match" + + if message is not None and len(password) < 4: + message = "Password too short" + + if message is not None and password in self.WEAK_PASSWORDS: + message = "Password too easy" + + if message is not None and username == password: + message = "Password equal to username" + + if message is not None: + self._set_register_status(message) + self._focus_password() + return False + + return True + + def _register(self): + """ + Performs the registration based on the values provided in the form + """ + self.ui.btnRegister.setEnabled(False) + # See the disabled button + while QtGui.QApplication.instance().hasPendingEvents(): + QtGui.QApplication.instance().processEvents() + self.button(QtGui.QWizard.NextButton).setFocus() + + username = self.ui.lblUser.text() + password = self.ui.lblPassword.text() + password2 = self.ui.lblPassword2.text() + + if self._basic_password_checks(username, password, password2): + register = SRPRegister(provider_config=self._provider_config) + ok, req = register.register_user(username, password) + if ok: + self._set_register_status("User registration OK") + self._username = username + self.ui.lblPassword2.clearFocus() + # Detach this call to allow UI updates briefly + QtCore.QTimer.singleShot(1, + self.page(self.REGISTER_USER_PAGE) + .set_completed) + else: + print req.content + error_msg = "Unknown error" + try: + error_msg = req.json().get("errors").get("login")[0] + except: + logger.error("Unknown error: %r" % (req.content,)) + self._set_register_status(error_msg) + self.ui.btnRegister.setEnabled(True) + else: + self.ui.btnRegister.setEnabled(True) + + def _set_register_status(self, status): + """ + Sets the status label in the registration page to status + + @param status: status message to display, can be HTML + @type status: str + """ + self.ui.lblRegisterStatus.setText(status) + + def _check_provider(self): + """ + SLOT + TRIGGERS: + self.ui.btnCheck.clicked + self.ui.lnProvider.returnPressed + + Starts the checks for a given provider + """ + self.ui.grpCheckProvider.setVisible(True) + self.ui.btnCheck.setEnabled(False) + self._domain = self.ui.lnProvider.text() + + self._provider_bootstrapper.start() + self._provider_bootstrapper.run_provider_select_checks(self._domain) + + def _complete_task(self, data, label, complete=False, complete_page=-1): + """ + Checks a task and completes a page if specified + + @param data: data as it comes from the bootstrapper thread for + a specific check + @type data: dict + @param label: label that displays the status icon for a + specific check that corresponds to the data + @type label: QtGui.QLabel + @param complete: if True, it completes the page specified, + which must be of type WizardPage + @type complete: bool + @param complete_page: page id to complete + @type complete_page: int + """ + passed = data[self._provider_bootstrapper.PASSED_KEY] + error = data[self._provider_bootstrapper.ERROR_KEY] + if passed: + label.setPixmap(self.OK_ICON) + if complete: + self.page(complete_page).set_completed() + self.button(QtGui.QWizard.NextButton).setFocus() + else: + label.setPixmap(self.ERROR_ICON) + logger.error(error) + + def _name_resolution(self, data): + """ + SLOT + TRIGGER: self._provider_bootstrapper.name_resolution + + Sets the status for the name resolution check + """ + self._complete_task(data, self.ui.lblNameResolution) + + def _https_connection(self, data): + """ + SLOT + TRIGGER: self._provider_bootstrapper.https_connection + + Sets the status for the https connection check + """ + self._complete_task(data, self.ui.lblHTTPS) + + def _download_provider_info(self, data): + """ + SLOT + TRIGGER: self._provider_bootstrapper.download_provider_info + + Sets the status for the provider information download + check. Since this check is the last of this set, it also + completes the page if passed + """ + if self._provider_config.load(os.path.join("leap", + "providers", + self._domain, + "provider.json")): + self._complete_task(data, self.ui.lblProviderInfo, + True, self.SELECT_PROVIDER_PAGE) + else: + new_data = { + self._provider_bootstrapper.PASSED_KEY: False, + self._provider_bootstrapper.ERROR_KEY: + "Unable to load provider configuration" + } + self._complete_task(new_data, self.ui.lblProviderInfo) + + self.ui.btnCheck.setEnabled(True) + + def _download_ca_cert(self, data): + """ + SLOT + TRIGGER: self._provider_bootstrapper.download_ca_cert + + Sets the status for the download of the CA certificate check + """ + self._complete_task(data, self.ui.lblDownloadCaCert) + + def _check_ca_fingerprint(self, data): + """ + SLOT + TRIGGER: self._provider_bootstrapper.check_ca_fingerprint + + Sets the status for the CA fingerprint check + """ + self._complete_task(data, self.ui.lblCheckCaFpr) + + def _check_api_certificate(self, data): + """ + SLOT + TRIGGER: self._provider_bootstrapper.check_api_certificate + + Sets the status for the API certificate check. Also finishes + the provider bootstrapper thread since it's not needed anymore + from this point on, unless the whole check chain is restarted + """ + self._complete_task(data, self.ui.lblCheckApiCert, + True, self.SETUP_PROVIDER_PAGE) + self._provider_bootstrapper.set_should_quit() + + def _download_eip_config(self, data): + """ + SLOT + TRIGGER: self._eip_bootstrapper.download_config + + Sets the status for the EIP config downloading check + """ + self._complete_task(data, self.ui.lblDownloadEIPConfig) + + def _download_client_certificate(self, data): + """ + SLOT + TRIGGER: self._provider_bootstrapper.download_client_certificate + + Sets the status for the download client certificate check and + completes the page if passed. Also stops the eip bootstrapper + thread since it's not needed from this point on unless the + check chain is restarted + """ + self._complete_task(data, self.ui.lblDownloadClientCert, + True, self.SETUP_EIP_PAGE) + self._eip_bootstrapper.set_should_quit() + + def _current_id_changed(self, pageId): + """ + SLOT + TRIGGER: self.currentIdChanged + + Prepares the pages when they appear + """ + if pageId == self.SELECT_PROVIDER_PAGE: + self.ui.grpCheckProvider.setVisible(False) + self.ui.lblNameResolution.setPixmap(self.QUESTION_ICON) + self.ui.lblHTTPS.setPixmap(self.QUESTION_ICON) + self.ui.lblProviderInfo.setPixmap(self.QUESTION_ICON) + + if pageId == self.SETUP_PROVIDER_PAGE: + self._provider_bootstrapper.\ + run_provider_setup_checks(self._provider_config) + + if pageId == self.SETUP_EIP_PAGE: + self._eip_bootstrapper.start() + self._eip_bootstrapper.run_eip_setup_checks(self._provider_config) + + if pageId == self.PRESENT_PROVIDER_PAGE: + # TODO: get the right lang for these + self.ui.lblProviderName.setText( + "%s" % + (self._provider_config.get_name(),)) + self.ui.lblProviderURL.setText(self._provider_config.get_domain()) + self.ui.lblProviderDesc.setText( + "%s" % + (self._provider_config.get_description(),)) + self.ui.lblProviderPolicy.setText(self._provider_config + .get_enrollment_policy()) + + def nextId(self): + """ + Sets the next page id for the wizard based on wether the user + wants to register a new identity or uses an existing one + """ + if self.currentPage() == self.page(self.INTRO_PAGE): + self._show_register = self.ui.rdoRegister.isChecked() + + if self.currentPage() == self.page(self.SETUP_PROVIDER_PAGE): + if self._show_register: + return self.REGISTER_USER_PAGE + else: + return self.SETUP_EIP_PAGE + + return QtGui.QWizard.nextId(self) diff --git a/src/leap/gui/wizardpage.py b/src/leap/gui/wizardpage.py new file mode 100644 index 00000000..2138ac7b --- /dev/null +++ b/src/leap/gui/wizardpage.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# wizardpage.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 . + +from PySide import QtGui + + +class WizardPage(QtGui.QWizardPage): + """ + Simple wizard page helper + """ + + def __init__(self): + QtGui.QWizardPage.__init__(self) + self._completed = False + + def set_completed(self): + self._completed = True + self.completeChanged.emit() + + def isComplete(self): + return self._completed + + def cleanupPage(self): + self._completed = False + QtGui.QWizardPage.cleanupPage(self) -- cgit v1.2.3 From 751638b4eb8208e1eaa1beaaed284da6b412bca7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Thu, 7 Mar 2013 19:05:11 -0300 Subject: Change asserts for a custom leap_assert method Also: - Make SRPAuth and the Bootstrappers be a QObject instead of a QThread so we can use them inside another more generic thread - Add a generic CheckerThread that runs checks or whatever operation as long as it returns a boolean value - Closes the whole application if the wizard is rejected at the first run - Do not fail when the config directory doesn't exist - Set the wizard pixmap logo as LEAP's logo - Improve wizard checks - Make SRPRegister play nice with the CheckerThread --- src/leap/gui/mainwindow.py | 91 ++++++++++++++++++++++++++++++---------------- src/leap/gui/ui/wizard.ui | 5 ++- src/leap/gui/wizard.py | 91 ++++++++++++++++++++++++++-------------------- 3 files changed, 115 insertions(+), 72 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 1821e4a6..50a03fb9 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -31,6 +31,8 @@ from leap.services.eip.providerbootstrapper import ProviderBootstrapper from leap.services.eip.eipbootstrapper import EIPBootstrapper from leap.services.eip.eipconfig import EIPConfig from leap.gui.wizard import Wizard +from leap.util.check import leap_assert +from leap.util.checkerthread import CheckerThread logger = logging.getLogger(__name__) @@ -44,6 +46,9 @@ class MainWindow(QtGui.QMainWindow): LOGIN_INDEX = 0 EIP_STATUS_INDEX = 1 + GEOMETRY_KEY = "Geometry" + WINDOWSTATE_KEY = "WindowState" + def __init__(self): QtGui.QMainWindow.__init__(self) @@ -69,6 +74,9 @@ class MainWindow(QtGui.QMainWindow): # This is created once we have a valid provider config self._srp_auth = None + self._checker_thread = CheckerThread() + self._checker_thread.start() + # This thread is always running, although it's quite # lightweight when it's done setting up provider # configuration and certificate. @@ -110,11 +118,7 @@ class MainWindow(QtGui.QMainWindow): QtCore.QCoreApplication.instance().connect( QtCore.QCoreApplication.instance(), QtCore.SIGNAL("aboutToQuit()"), - self._provider_bootstrapper.set_should_quit) - QtCore.QCoreApplication.instance().connect( - QtCore.QCoreApplication.instance(), - QtCore.SIGNAL("aboutToQuit()"), - self._eip_bootstrapper.set_should_quit) + self._checker_thread.set_should_quit) self.ui.action_sign_out.setEnabled(False) self.ui.action_sign_out.triggered.connect(self._logout) @@ -131,17 +135,26 @@ class MainWindow(QtGui.QMainWindow): self._center_window() self._wizard = None + self._wizard_firstrun = False if self._first_run(): - self._wizard = Wizard() + self._wizard_firstrun = True + self._wizard = Wizard(self._checker_thread) # Give this window time to finish init and then show the wizard QtCore.QTimer.singleShot(1, self._launch_wizard) - self._wizard.finished.connect(self._finish_init) + self._wizard.accepted.connect(self._finish_init) + self._wizard.rejected.connect(self._rejected_wizard) + else: + self._finish_init() + + def _rejected_wizard(self): + if self._wizard_firstrun: + self.quit() else: self._finish_init() def _launch_wizard(self): if self._wizard is None: - self._wizard = Wizard() + self._wizard = Wizard(self._checker_thread) self._wizard.exec_() def _finish_init(self): @@ -187,14 +200,23 @@ class MainWindow(QtGui.QMainWindow): """ Centers the mainwindow based on the desktop geometry """ - app = QtGui.QApplication.instance() - width = app.desktop().width() - height = app.desktop().height() - window_width = self.size().width() - window_height = self.size().height() - x = (width / 2.0) - (window_width / 2.0) - y = (height / 2.0) - (window_height / 2.0) - self.move(x, y) + settings = QtCore.QSettings() + geometry = settings.value(self.GEOMETRY_KEY, None) + state = settings.value(self.WINDOWSTATE_KEY, None) + if geometry is None: + app = QtGui.QApplication.instance() + width = app.desktop().width() + height = app.desktop().height() + window_width = self.size().width() + window_height = self.size().height() + x = (width / 2.0) - (window_width / 2.0) + y = (height / 2.0) - (window_height / 2.0) + self.move(x, y) + else: + self.restoreGeometry(geometry) + + if state is not None: + self.restoreState(state) def _about(self): """ @@ -236,6 +258,9 @@ class MainWindow(QtGui.QMainWindow): self._toggle_visible() e.ignore() return + settings = QtCore.QSettings() + settings.setValue(self.GEOMETRY_KEY, self.saveGeometry()) + settings.setValue(self.WINDOWSTATE_KEY, self.saveState()) QtGui.QMainWindow.closeEvent(self, e) def _configured_providers(self): @@ -244,10 +269,16 @@ class MainWindow(QtGui.QMainWindow): @rtype: list """ - providers = os.listdir( - os.path.join(self._provider_config.get_path_prefix(), - "leap", - "providers")) + providers = [] + try: + providers = os.listdir( + os.path.join(self._provider_config.get_path_prefix(), + "leap", + "providers")) + except Exception as e: + logger.debug("Error listing providers, assume there are none. %r" + % (e,)) + return providers def _first_run(self): @@ -303,8 +334,8 @@ class MainWindow(QtGui.QMainWindow): """ provider = self.ui.cmbProviders.currentText() - self._provider_bootstrapper.start() self._provider_bootstrapper.run_provider_select_checks( + self._checker_thread, provider, download_if_needed=True) @@ -329,6 +360,7 @@ class MainWindow(QtGui.QMainWindow): provider, "provider.json")): self._provider_bootstrapper.run_provider_setup_checks( + self._checker_thread, self._provider_config, download_if_needed=True) else: @@ -350,7 +382,7 @@ class MainWindow(QtGui.QMainWindow): start the SRP authentication, and as the last step bootstrapping the EIP service """ - assert self._provider_config, "We need a provider config" + leap_assert(self._provider_config, "We need a provider config") username = self.ui.lnUser.text() password = self.ui.lnPassword.text() @@ -381,9 +413,7 @@ class MainWindow(QtGui.QMainWindow): Once the provider configuration is loaded, this starts the SRP authentication """ - assert self._provider_config, "We need a provider config!" - - self._provider_bootstrapper.set_should_quit() + leap_assert(self._provider_config, "We need a provider config!") if data[self._provider_bootstrapper.PASSED_KEY]: username = self.ui.lnUser.text() @@ -431,14 +461,14 @@ class MainWindow(QtGui.QMainWindow): """ Starts the EIP bootstrapping sequence """ - assert self._eip_bootstrapper, "We need an eip bootstrapper!" - assert self._provider_config, "We need a provider config" + leap_assert(self._eip_bootstrapper, "We need an eip bootstrapper!") + leap_assert(self._provider_config, "We need a provider config") self._set_eip_status("Checking configuration, please wait...") if self._provider_config.provides_eip(): - self._eip_bootstrapper.start() self._eip_bootstrapper.run_eip_setup_checks( + self._checker_thread, self._provider_config, download_if_needed=True) else: @@ -503,10 +533,9 @@ class MainWindow(QtGui.QMainWindow): Starts the VPN thread if the eip configuration is properly loaded """ - assert self._eip_config, "We need an eip config!" - assert self._provider_config, "We need a provider config!" + leap_assert(self._eip_config, "We need an eip config!") + leap_assert(self._provider_config, "We need a provider config!") - self._eip_bootstrapper.set_should_quit() if self._eip_config.loaded() or \ self._eip_config.load(os.path.join("leap", "providers", diff --git a/src/leap/gui/ui/wizard.ui b/src/leap/gui/ui/wizard.ui index 86f8d458..2d9cb441 100644 --- a/src/leap/gui/ui/wizard.ui +++ b/src/leap/gui/ui/wizard.ui @@ -47,7 +47,7 @@ - <html><head/><body><p>New we will guide you through some configuration that is needed before you can connect for the first time.</p><p>If you ever need to modify these options again, you can find the wizard in the <span style=" font-style:italic;">'Settings'</span> menu from the main window.</p><p>Do you want to <span style=" font-weight:600;">sign up</span> for a new account, or <span style=" font-weight:600;">log in</span> with an already existing username?</p></body></html> + <html><head/><body><p>Now we will guide you through some configuration that is needed before you can connect for the first time.</p><p>If you ever need to modify these options again, you can find the wizard in the <span style=" font-style:italic;">'Settings'</span> menu from the main window.</p><p>Do you want to <span style=" font-weight:600;">sign up</span> for a new account, or <span style=" font-weight:600;">log in</span> with an already existing username?</p></body></html> Qt::RichText @@ -497,6 +497,9 @@ + + Qt::AutoText + Qt::AlignCenter diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index 7dcc8dd6..bac74d1d 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -21,7 +21,8 @@ First run wizard import os import logging -from PySide import QtCore, QtGui +from PySide import QtGui +from functools import partial from ui_wizard import Ui_Wizard from leap.config.providerconfig import ProviderConfig @@ -45,15 +46,18 @@ class Wizard(QtGui.QWizard): SETUP_EIP_PAGE = 5 FINISH_PATH = 6 - WEAK_PASSWORDS = ("1234", "12345", "123456", + WEAK_PASSWORDS = ("123456", "qweasd", "qwerty", "password") - def __init__(self): + def __init__(self, checker): QtGui.QWizard.__init__(self) self.ui = Ui_Wizard() self.ui.setupUi(self) + self.setPixmap(QtGui.QWizard.LogoPixmap, + QtGui.QPixmap(":/images/leap-color-small.png")) + self.QUESTION_ICON = QtGui.QPixmap(":/images/Emblem-question.png") self.ERROR_ICON = QtGui.QPixmap(":/images/Dialog-error.png") self.OK_ICON = QtGui.QPixmap(":/images/Dialog-accept.png") @@ -94,6 +98,9 @@ class Wizard(QtGui.QWizard): self.ui.lblPassword.setEchoMode(QtGui.QLineEdit.Password) self.ui.lblPassword2.setEchoMode(QtGui.QLineEdit.Password) + self.ui.lnProvider.textChanged.connect( + self._enable_check) + self.ui.lblUser.returnPressed.connect( self._focus_password) self.ui.lblPassword.returnPressed.connect( @@ -105,15 +112,14 @@ class Wizard(QtGui.QWizard): self._username = None - def __del__(self): - self._provider_bootstrapper.set_should_quit() - self._eip_bootstrapper.set_should_quit() - self._provider_bootstrapper.wait() - self._eip_bootstrapper.wait() + self._checker_thread = checker def get_username(self): return self._username + def _enable_check(self, text): + self.ui.btnCheck.setEnabled(len(self.ui.lnProvider.text()) != 0) + def _focus_password(self): """ Focuses at the password lineedit for the registration page @@ -151,13 +157,13 @@ class Wizard(QtGui.QWizard): if message is not None and password != password2: message = "Passwords don't match" - if message is not None and len(password) < 4: + if message is None and len(password) < 6: message = "Password too short" - if message is not None and password in self.WEAK_PASSWORDS: + if message is None and password in self.WEAK_PASSWORDS: message = "Password too easy" - if message is not None and username == password: + if message is None and username == password: message = "Password equal to username" if message is not None: @@ -172,10 +178,6 @@ class Wizard(QtGui.QWizard): Performs the registration based on the values provided in the form """ self.ui.btnRegister.setEnabled(False) - # See the disabled button - while QtGui.QApplication.instance().hasPendingEvents(): - QtGui.QApplication.instance().processEvents() - self.button(QtGui.QWizard.NextButton).setFocus() username = self.ui.lblUser.text() password = self.ui.lblPassword.text() @@ -183,27 +185,32 @@ class Wizard(QtGui.QWizard): if self._basic_password_checks(username, password, password2): register = SRPRegister(provider_config=self._provider_config) - ok, req = register.register_user(username, password) - if ok: - self._set_register_status("User registration OK") - self._username = username - self.ui.lblPassword2.clearFocus() - # Detach this call to allow UI updates briefly - QtCore.QTimer.singleShot(1, - self.page(self.REGISTER_USER_PAGE) - .set_completed) - else: - print req.content - error_msg = "Unknown error" - try: - error_msg = req.json().get("errors").get("login")[0] - except: - logger.error("Unknown error: %r" % (req.content,)) - self._set_register_status(error_msg) - self.ui.btnRegister.setEnabled(True) + register.registration_finished.connect( + self._registration_finished) + self._checker_thread.add_checks( + [partial(register.register_user, username, password)]) + self._username = username + self._set_register_status("Starting registration...") else: self.ui.btnRegister.setEnabled(True) + def _registration_finished(self, ok, req): + if ok: + self._set_register_status("" + "User registration OK") + self.ui.lblPassword2.clearFocus() + self.page(self.REGISTER_USER_PAGE).set_completed() + self.button(QtGui.QWizard.BackButton).setEnabled(False) + else: + self._username = None + error_msg = "Unknown error" + try: + error_msg = req.json().get("errors").get("login")[0] + except: + logger.error("Unknown error: %r" % (req.content,)) + self._set_register_status(error_msg) + self.ui.btnRegister.setEnabled(True) + def _set_register_status(self, status): """ Sets the status label in the registration page to status @@ -222,12 +229,16 @@ class Wizard(QtGui.QWizard): Starts the checks for a given provider """ + if len(self.ui.lnProvider.text()) == 0: + return + self.ui.grpCheckProvider.setVisible(True) self.ui.btnCheck.setEnabled(False) self._domain = self.ui.lnProvider.text() - self._provider_bootstrapper.start() - self._provider_bootstrapper.run_provider_select_checks(self._domain) + self._provider_bootstrapper.run_provider_select_checks( + self._checker_thread, + self._domain) def _complete_task(self, data, label, complete=False, complete_page=-1): """ @@ -328,7 +339,6 @@ class Wizard(QtGui.QWizard): """ self._complete_task(data, self.ui.lblCheckApiCert, True, self.SETUP_PROVIDER_PAGE) - self._provider_bootstrapper.set_should_quit() def _download_eip_config(self, data): """ @@ -351,7 +361,6 @@ class Wizard(QtGui.QWizard): """ self._complete_task(data, self.ui.lblDownloadClientCert, True, self.SETUP_EIP_PAGE) - self._eip_bootstrapper.set_should_quit() def _current_id_changed(self, pageId): """ @@ -365,14 +374,16 @@ class Wizard(QtGui.QWizard): self.ui.lblNameResolution.setPixmap(self.QUESTION_ICON) self.ui.lblHTTPS.setPixmap(self.QUESTION_ICON) self.ui.lblProviderInfo.setPixmap(self.QUESTION_ICON) + self._enable_check("") if pageId == self.SETUP_PROVIDER_PAGE: self._provider_bootstrapper.\ - run_provider_setup_checks(self._provider_config) + run_provider_setup_checks(self._checker_thread, + self._provider_config) if pageId == self.SETUP_EIP_PAGE: - self._eip_bootstrapper.start() - self._eip_bootstrapper.run_eip_setup_checks(self._provider_config) + self._eip_bootstrapper.run_eip_setup_checks(self._checker_thread, + self._provider_config) if pageId == self.PRESENT_PROVIDER_PAGE: # TODO: get the right lang for these -- cgit v1.2.3 From 01a7faa2033ef3ce85bc5a346eca3601f0f4f7c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Fri, 8 Mar 2013 11:09:23 -0300 Subject: Migrate VPN process to QProcess Also: - Add a new tray icon for the whole app and a VPN specific one - Add a way to start/stop EIP independently - Improve reaction to the process dying --- src/leap/gui/mainwindow.py | 98 ++++++++++++++++++++++++++++++++++++++----- src/leap/gui/ui/mainwindow.ui | 63 +++++++++++++++------------- 2 files changed, 123 insertions(+), 38 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 50a03fb9..df21a2bb 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -56,6 +56,9 @@ class MainWindow(QtGui.QMainWindow): self.CONNECTED_ICON = QtGui.QPixmap(":/images/conn_connected.png") self.ERROR_ICON = QtGui.QPixmap(":/images/conn_error.png") + self.LOGGED_OUT_ICON = QtGui.QPixmap(":/images/leap-gray-big.png") + self.LOGGED_IN_ICON = QtGui.QPixmap(":/images/leap-color-big.png") + self.ui = Ui_MainWindow() self.ui.setupUi(self) @@ -67,6 +70,10 @@ class MainWindow(QtGui.QMainWindow): self.ui.stackedWidget.setCurrentIndex(self.LOGIN_INDEX) + self.ui.btnEipStartStop.setEnabled(False) + self.ui.btnEipStartStop.clicked.connect( + self._stop_eip) + # This is loaded only once, there's a bug when doing that more # than once self._provider_config = ProviderConfig() @@ -105,11 +112,13 @@ class MainWindow(QtGui.QMainWindow): self._eip_bootstrapper.download_config.connect( self._intermediate_stage) self._eip_bootstrapper.download_client_certificate.connect( - self._start_eip) + self._finish_eip_bootstrap) self._vpn = VPN() self._vpn.state_changed.connect(self._update_vpn_state) self._vpn.status_changed.connect(self._update_vpn_status) + self._vpn.process_finished.connect( + self._eip_finished) QtCore.QCoreApplication.instance().connect( QtCore.QCoreApplication.instance(), @@ -130,6 +139,22 @@ class MainWindow(QtGui.QMainWindow): self._really_quit = False self._systray = None + self._vpn_systray = None + + self._action_eip_status = QtGui.QAction("Encryption is OFF", self) + self._action_eip_status.setEnabled(False) + self._action_eip_stop = QtGui.QAction("Stop", self) + self._action_eip_stop.triggered.connect( + self._stop_eip) + self._action_eip_write = QtGui.QAction( + QtGui.QIcon(":/images/Arrow-Up-32.png"), + "0.0 Kb", self) + self._action_eip_write.setEnabled(False) + self._action_eip_read = QtGui.QAction( + QtGui.QIcon(":/images/Arrow-Down-32.png"), + "0.0 Kb", self) + self._action_eip_read.setEnabled(False) + self._action_visible = QtGui.QAction("Hide", self) self._action_visible.triggered.connect(self._toggle_visible) @@ -179,10 +204,20 @@ class MainWindow(QtGui.QMainWindow): systrayMenu.addAction(self.ui.action_quit) self._systray = QtGui.QSystemTrayIcon(self) self._systray.setContextMenu(systrayMenu) - self._systray.setIcon(QtGui.QIcon(self.ERROR_ICON)) + self._systray.setIcon(QtGui.QIcon(self.LOGGED_OUT_ICON)) self._systray.setVisible(True) self._systray.activated.connect(self._toggle_visible) + vpn_systrayMenu = QtGui.QMenu(self) + vpn_systrayMenu.addAction(self._action_eip_status) + vpn_systrayMenu.addAction(self._action_eip_stop) + vpn_systrayMenu.addAction(self._action_eip_read) + vpn_systrayMenu.addAction(self._action_eip_write) + self._vpn_systray = QtGui.QSystemTrayIcon(self) + self._vpn_systray.setContextMenu(vpn_systrayMenu) + self._vpn_systray.setIcon(QtGui.QIcon(self.ERROR_ICON)) + self._vpn_systray.setVisible(False) + def _toggle_visible(self): """ SLOT @@ -455,8 +490,33 @@ class MainWindow(QtGui.QMainWindow): triggers the eip bootstrapping """ self.ui.stackedWidget.setCurrentIndex(self.EIP_STATUS_INDEX) + self._systray.setIcon(self.LOGGED_IN_ICON) self._download_eip_config() + def _start_eip(self): + self._vpn.start(eipconfig=self._eip_config, + providerconfig=self._provider_config, + socket_host="/home/chiiph/vpnsock", + socket_port="unix") + self._vpn_systray.setVisible(True) + self.ui.btnEipStartStop.setEnabled(True) + self.ui.btnEipStartStop.setText("Stop EIP") + self.ui.btnEipStartStop.clicked.disconnect( + self._start_eip) + self.ui.btnEipStartStop.clicked.connect( + self._stop_eip) + + def _stop_eip(self): + self._vpn.set_should_quit() + self._vpn_systray.setVisible(False) + self._set_eip_status("EIP has stopped") + self._set_eip_status_icon("error") + self.ui.btnEipStartStop.setText("Start EIP") + self.ui.btnEipStartStop.clicked.disconnect( + self._stop_eip) + self.ui.btnEipStartStop.clicked.connect( + self._start_eip) + def _download_eip_config(self): """ Starts the EIP bootstrapping sequence @@ -483,13 +543,16 @@ class MainWindow(QtGui.QMainWindow): @type status: str """ selected_pixmap = self.ERROR_ICON + tray_message = "Encryption is OFF" if status in ("AUTH", "GET_CONFIG"): selected_pixmap = self.CONNECTING_ICON elif status in ("CONNECTED"): + tray_message = "Encryption is ON" selected_pixmap = self.CONNECTED_ICON self.ui.lblVPNStatusIcon.setPixmap(selected_pixmap) - self._systray.setIcon(QtGui.QIcon(selected_pixmap)) + self._vpn_systray.setIcon(QtGui.QIcon(selected_pixmap)) + self._action_eip_status.setText(tray_message) def _update_vpn_state(self, data): """ @@ -520,12 +583,16 @@ class MainWindow(QtGui.QMainWindow): """ upload = float(data[self._vpn.TUNTAP_WRITE_KEY]) upload = upload / 1000.0 - self.ui.lblUpload.setText("%s Kb" % (upload,)) + upload_str = "%s Kb" % (upload,) + self.ui.lblUpload.setText(upload_str) + self._action_eip_write.setText(upload_str) download = float(data[self._vpn.TUNTAP_READ_KEY]) download = download / 1000.0 - self.ui.lblDownload.setText("%s Kb" % (download,)) + download_str = "%s Kb" % (download,) + self.ui.lblDownload.setText(download_str) + self._action_eip_read.setText(download_str) - def _start_eip(self, data): + def _finish_eip_bootstrap(self, data): """ SLOT TRIGGER: self._eip_bootstrapper.download_client_certificate @@ -542,10 +609,7 @@ class MainWindow(QtGui.QMainWindow): self._provider_config .get_domain(), "eip-service.json")): - self._vpn.start(eipconfig=self._eip_config, - providerconfig=self._provider_config, - socket_host="/home/chiiph/vpnsock", - socket_port="unix") + self._start_eip() # TODO: display a message if the EIP configuration cannot be # loaded @@ -569,12 +633,14 @@ class MainWindow(QtGui.QMainWindow): logging out """ self._set_status(message) + self._vpn_systray.setIcon(self.LOGGED_OUT_ICON) 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._vpn.set_should_quit() + self._vpn_systray.setVisible(False) def _intermediate_stage(self, data): """ @@ -594,6 +660,18 @@ class MainWindow(QtGui.QMainWindow): self._login_set_enabled(True) self._set_status(data[self._provider_bootstrapper.ERROR_KEY]) + def _eip_finished(self, exitCode): + """ + SLOT + TRIGGERS: + self._vpn.process_finished + + Triggered when the EIP/VPN process finishes to set the UI + accordingly + """ + logger.debug("Finished VPN with exitCode %s" % (exitCode,)) + self._stop_eip() + if __name__ == "__main__": import signal from functools import partial diff --git a/src/leap/gui/ui/mainwindow.ui b/src/leap/gui/ui/mainwindow.ui index a527eaf6..d8a6d37d 100644 --- a/src/leap/gui/ui/mainwindow.ui +++ b/src/leap/gui/ui/mainwindow.ui @@ -171,16 +171,6 @@ - - - - Disconnected - - - Qt::AlignCenter - - - @@ -194,20 +184,7 @@ - - - - Qt::Horizontal - - - - 40 - 20 - - - - - + @@ -220,7 +197,7 @@ - + @@ -233,7 +210,7 @@ - + Qt::Horizontal @@ -246,7 +223,7 @@ - + @@ -256,7 +233,7 @@ - + @@ -266,6 +243,36 @@ + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Disconnected + + + Qt::AlignCenter + + + + + + + Start EIP + + + -- cgit v1.2.3 From 926575bc811e8382100695a3396da7191fb43eb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Fri, 8 Mar 2013 13:15:38 -0300 Subject: Add translation support Also: - Make OpenVPN use a random port every time - Logout in parallel so the UI doesn't block - Add the WAIT status from OpenVPN to the mainwindow displays - Support non-unix sockets in the LinuxVPNLauncher --- src/leap/gui/mainwindow.py | 72 +++++++++++++++++++++++++--------------------- src/leap/gui/ui/wizard.ui | 1 + src/leap/gui/wizard.py | 28 +++++++++--------- 3 files changed, 55 insertions(+), 46 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index df21a2bb..d66ddbb5 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -20,6 +20,7 @@ Main window for the leap client """ import os import logging +import random from PySide import QtCore, QtGui @@ -141,9 +142,10 @@ class MainWindow(QtGui.QMainWindow): self._systray = None self._vpn_systray = None - self._action_eip_status = QtGui.QAction("Encryption is OFF", self) + self._action_eip_status = QtGui.QAction(self.tr("Encryption is OFF"), + self) self._action_eip_status.setEnabled(False) - self._action_eip_stop = QtGui.QAction("Stop", self) + self._action_eip_stop = QtGui.QAction(self.tr("Stop"), self) self._action_eip_stop.triggered.connect( self._stop_eip) self._action_eip_write = QtGui.QAction( @@ -155,7 +157,7 @@ class MainWindow(QtGui.QMainWindow): "0.0 Kb", self) self._action_eip_read.setEnabled(False) - self._action_visible = QtGui.QAction("Hide", self) + self._action_visible = QtGui.QAction(self.tr("Hide"), self) self._action_visible.triggered.connect(self._toggle_visible) self._center_window() @@ -226,9 +228,9 @@ class MainWindow(QtGui.QMainWindow): Toggles the window visibility """ self.setVisible(not self.isVisible()) - action_visible_text = "Hide" + action_visible_text = self.tr("Hide") if not self.isVisible(): - action_visible_text = "Show" + action_visible_text = self.tr("Show") self._action_visible.setText(action_visible_text) def _center_window(self): @@ -257,14 +259,15 @@ class MainWindow(QtGui.QMainWindow): """ Display the About LEAP dialog """ - QtGui.QMessageBox.about(self, "About LEAP", - "LEAP is a non-profit dedicated to giving " - "all internet users access to secure " - "communication. Our focus is on adapting " - "encryption technology to make it easy to use " - "and widely available. " - "More about LEAP" - "") + QtGui.QMessageBox.about( + self, self.tr("About LEAP"), + self.tr("LEAP is a non-profit dedicated to giving " + "all internet users access to secure " + "communication. Our focus is on adapting " + "encryption technology to make it easy to use " + "and widely available. " + "More about LEAP" + "")) def quit(self): self._really_quit = True @@ -399,7 +402,8 @@ class MainWindow(QtGui.QMainWindow): self._provider_config, download_if_needed=True) else: - self._set_status("Could not load provider configuration") + self._set_status( + self.tr("Could not load provider configuration")) self._login_set_enabled(True) else: self._set_status(data[self._provider_bootstrapper.ERROR_KEY]) @@ -424,18 +428,18 @@ class MainWindow(QtGui.QMainWindow): provider = self.ui.cmbProviders.currentText() if len(provider) == 0: - self._set_status("Please select a valid provider") + self._set_status(self.tr("Please select a valid provider")) return if len(username) == 0: - self._set_status("Please provide a valid username") + self._set_status(self.tr("Please provide a valid username")) return if len(password) == 0: - self._set_status("Please provide a valid Password") + self._set_status(self.tr("Please provide a valid Password")) return - self._set_status("Logging in...") + self._set_status(self.tr("Logging in...")) self._login_set_enabled(False) self._download_provider_config() @@ -496,11 +500,11 @@ class MainWindow(QtGui.QMainWindow): def _start_eip(self): self._vpn.start(eipconfig=self._eip_config, providerconfig=self._provider_config, - socket_host="/home/chiiph/vpnsock", - socket_port="unix") + socket_host="localhost", + socket_port=str(random.randint(1000, 9999))) self._vpn_systray.setVisible(True) self.ui.btnEipStartStop.setEnabled(True) - self.ui.btnEipStartStop.setText("Stop EIP") + self.ui.btnEipStartStop.setText(self.tr("Stop EIP")) self.ui.btnEipStartStop.clicked.disconnect( self._start_eip) self.ui.btnEipStartStop.clicked.connect( @@ -509,9 +513,9 @@ class MainWindow(QtGui.QMainWindow): def _stop_eip(self): self._vpn.set_should_quit() self._vpn_systray.setVisible(False) - self._set_eip_status("EIP has stopped") + self._set_eip_status(self.tr("EIP has stopped")) self._set_eip_status_icon("error") - self.ui.btnEipStartStop.setText("Start EIP") + self.ui.btnEipStartStop.setText(self.tr("Start EIP")) self.ui.btnEipStartStop.clicked.disconnect( self._stop_eip) self.ui.btnEipStartStop.clicked.connect( @@ -524,7 +528,7 @@ class MainWindow(QtGui.QMainWindow): leap_assert(self._eip_bootstrapper, "We need an eip bootstrapper!") leap_assert(self._provider_config, "We need a provider config") - self._set_eip_status("Checking configuration, please wait...") + self._set_eip_status(self.tr("Checking configuration, please wait...")) if self._provider_config.provides_eip(): self._eip_bootstrapper.run_eip_setup_checks( @@ -532,7 +536,7 @@ class MainWindow(QtGui.QMainWindow): self._provider_config, download_if_needed=True) else: - self._set_eip_status("%s does not support EIP" % + self._set_eip_status(self.tr("%s does not support EIP") % (self._provider_config.get_domain(),)) def _set_eip_status_icon(self, status): @@ -543,11 +547,11 @@ class MainWindow(QtGui.QMainWindow): @type status: str """ selected_pixmap = self.ERROR_ICON - tray_message = "Encryption is OFF" - if status in ("AUTH", "GET_CONFIG"): + tray_message = self.tr("Encryption is OFF") + if status in ("WAIT", "AUTH", "GET_CONFIG"): selected_pixmap = self.CONNECTING_ICON elif status in ("CONNECTED"): - tray_message = "Encryption is ON" + tray_message = self.tr("Encryption is ON") selected_pixmap = self.CONNECTED_ICON self.ui.lblVPNStatusIcon.setPixmap(selected_pixmap) @@ -565,11 +569,13 @@ class MainWindow(QtGui.QMainWindow): status = data[self._vpn.STATUS_STEP_KEY] self._set_eip_status_icon(status) if status == "AUTH": - self._set_eip_status("VPN: Authenticating...") + self._set_eip_status(self.tr("VPN: Authenticating...")) elif status == "GET_CONFIG": - self._set_eip_status("VPN: Retrieving configuration...") + self._set_eip_status(self.tr("VPN: Retrieving configuration...")) elif status == "CONNECTED": - self._set_eip_status("VPN: Connected!") + self._set_eip_status(self.tr("VPN: Connected!")) + elif status == "WAIT": + self._set_eip_status(self.tr("VPN: Waiting to start...")) else: self._set_eip_status(status) @@ -621,8 +627,8 @@ class MainWindow(QtGui.QMainWindow): Starts the logout sequence """ self._set_eip_status_icon("error") - self._set_eip_status("Signing out...") - self._srp_auth.logout() + self._set_eip_status(self.tr("Signing out...")) + self._checker_thread.add_checks([self._srp_auth.logout]) def _done_logging_out(self, ok, message): """ diff --git a/src/leap/gui/ui/wizard.ui b/src/leap/gui/ui/wizard.ui index 2d9cb441..a7198c5f 100644 --- a/src/leap/gui/ui/wizard.ui +++ b/src/leap/gui/ui/wizard.ui @@ -798,6 +798,7 @@ + diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index bac74d1d..fa705645 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -21,7 +21,7 @@ First run wizard import os import logging -from PySide import QtGui +from PySide import QtCore, QtGui from functools import partial from ui_wizard import Ui_Wizard @@ -152,19 +152,20 @@ class Wizard(QtGui.QWizard): username.encode("ascii") password.encode("ascii") except: - message = u"Refrain from using non ASCII áñ characters" + message = self.tr(u"Refrain from using non " + u"ASCII characters like á, ñ, æ") if message is not None and password != password2: - message = "Passwords don't match" + message = self.tr("Passwords don't match") if message is None and len(password) < 6: - message = "Password too short" + message = self.tr("Password too short") if message is None and password in self.WEAK_PASSWORDS: - message = "Password too easy" + message = self.tr("Password too easy") if message is None and username == password: - message = "Password equal to username" + message = self.tr("Password equal to username") if message is not None: self._set_register_status(message) @@ -190,20 +191,20 @@ class Wizard(QtGui.QWizard): self._checker_thread.add_checks( [partial(register.register_user, username, password)]) self._username = username - self._set_register_status("Starting registration...") + self._set_register_status(self.tr("Starting registration...")) else: self.ui.btnRegister.setEnabled(True) def _registration_finished(self, ok, req): if ok: - self._set_register_status("" - "User registration OK") + self._set_register_status(self.tr("" + "User registration OK")) self.ui.lblPassword2.clearFocus() self.page(self.REGISTER_USER_PAGE).set_completed() self.button(QtGui.QWizard.BackButton).setEnabled(False) else: self._username = None - error_msg = "Unknown error" + error_msg = self.tr("Unknown error") try: error_msg = req.json().get("errors").get("login")[0] except: @@ -304,7 +305,7 @@ class Wizard(QtGui.QWizard): new_data = { self._provider_bootstrapper.PASSED_KEY: False, self._provider_bootstrapper.ERROR_KEY: - "Unable to load provider configuration" + self.tr("Unable to load provider configuration") } self._complete_task(new_data, self.ui.lblProviderInfo) @@ -387,13 +388,14 @@ class Wizard(QtGui.QWizard): if pageId == self.PRESENT_PROVIDER_PAGE: # TODO: get the right lang for these + lang = QtCore.QLocale.system().name() self.ui.lblProviderName.setText( "%s" % - (self._provider_config.get_name(),)) + (self._provider_config.get_name(lang=lang),)) self.ui.lblProviderURL.setText(self._provider_config.get_domain()) self.ui.lblProviderDesc.setText( "%s" % - (self._provider_config.get_description(),)) + (self._provider_config.get_description(lang=lang),)) self.ui.lblProviderPolicy.setText(self._provider_config .get_enrollment_policy()) -- cgit v1.2.3 From e4e5f35c3fc7ff02bc20a6ef7eaffae09f485061 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Fri, 8 Mar 2013 14:24:05 -0300 Subject: Add keyring and username/password saving capabilities Also: - Fix translations in SRPAuth - Support non-ascii passwords - Make the server check if the characters are allowed, just check for easy passwords --- src/leap/gui/mainwindow.py | 33 +++++++++++++++++++++++++++++++-- src/leap/gui/wizard.py | 11 +++-------- 2 files changed, 34 insertions(+), 10 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index d66ddbb5..68c73e4d 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -21,6 +21,7 @@ Main window for the leap client import os import logging import random +import keyring from PySide import QtCore, QtGui @@ -49,6 +50,9 @@ class MainWindow(QtGui.QMainWindow): GEOMETRY_KEY = "Geometry" WINDOWSTATE_KEY = "WindowState" + USER_KEY = "User" + + KEYRING_KEY = "leap_client" def __init__(self): QtGui.QMainWindow.__init__(self) @@ -183,6 +187,7 @@ class MainWindow(QtGui.QMainWindow): if self._wizard is None: self._wizard = Wizard(self._checker_thread) self._wizard.exec_() + self._wizard = None def _finish_init(self): self.ui.cmbProviders.addItems(self._configured_providers()) @@ -194,6 +199,17 @@ class MainWindow(QtGui.QMainWindow): self.ui.lnUser.setText(possible_username) self._focus_password() self._wizard = None + else: + settings = QtCore.QSettings() + saved_user = settings.value(self.USER_KEY, None) + + if saved_user is not None: + self.ui.lnUser.setText(saved_user) + self.ui.chkRemember.setChecked(True) + saved_password = keyring.get_password(self.KEYRING_KEY, + saved_user + .encode("utf8")) + self.ui.lnPassword.setText(saved_password.decode("utf8")) def _show_systray(self): """ @@ -442,6 +458,19 @@ class MainWindow(QtGui.QMainWindow): self._set_status(self.tr("Logging in...")) self._login_set_enabled(False) + if self.ui.chkRemember.isChecked(): + try: + keyring.set_password(self.KEYRING_KEY, + username.encode("utf8"), + password.encode("utf8")) + # Only save the username if it was saved correctly in + # the keyring + settings = QtCore.QSettings() + settings.setValue(self.USER_KEY, username) + except Exception as e: + logger.error("Problem saving data to keyring. %r" + % (e,)) + self._download_provider_config() def _provider_config_loaded(self, data): @@ -455,8 +484,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() - password = self.ui.lnPassword.text() + username = self.ui.lnUser.text().encode("utf8") + password = self.ui.lnPassword.text().encode("utf8") if self._srp_auth is None: self._srp_auth = SRPAuth(self._provider_config) diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index fa705645..72e791b3 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -148,13 +148,6 @@ class Wizard(QtGui.QWizard): """ message = None - try: - username.encode("ascii") - password.encode("ascii") - except: - message = self.tr(u"Refrain from using non " - u"ASCII characters like á, ñ, æ") - if message is not None and password != password2: message = self.tr("Passwords don't match") @@ -189,7 +182,9 @@ class Wizard(QtGui.QWizard): register.registration_finished.connect( self._registration_finished) self._checker_thread.add_checks( - [partial(register.register_user, username, password)]) + [partial(register.register_user, + username.encode("utf8"), + password.encode("utf8"))]) self._username = username self._set_register_status(self.tr("Starting registration...")) else: -- cgit v1.2.3 From 1e3631c9a21f99e08bbb4bace61590b1221bc9f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Mon, 11 Mar 2013 10:24:25 -0300 Subject: Add AutoLogin checkbox --- src/leap/gui/mainwindow.py | 17 +++++++++++++++-- src/leap/gui/ui/mainwindow.ui | 38 ++++++++++++++++++++++++-------------- 2 files changed, 39 insertions(+), 16 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 68c73e4d..399a7cb4 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -48,10 +48,13 @@ class MainWindow(QtGui.QMainWindow): LOGIN_INDEX = 0 EIP_STATUS_INDEX = 1 + # Settings GEOMETRY_KEY = "Geometry" WINDOWSTATE_KEY = "WindowState" USER_KEY = "User" + AUTOLOGIN_KEY = "AutoLogin" + # Keyring KEYRING_KEY = "leap_client" def __init__(self): @@ -202,15 +205,22 @@ class MainWindow(QtGui.QMainWindow): else: settings = QtCore.QSettings() saved_user = settings.value(self.USER_KEY, None) + auto_login = settings.value(self.AUTOLOGIN_KEY, None) if saved_user is not None: self.ui.lnUser.setText(saved_user) self.ui.chkRemember.setChecked(True) + self.ui.chkAutoLogin.setEnabled(True) saved_password = keyring.get_password(self.KEYRING_KEY, saved_user .encode("utf8")) self.ui.lnPassword.setText(saved_password.decode("utf8")) + # Only automatically login if there is a saved user + if auto_login is not None: + self.ui.chkAutoLogin.setChecked(True) + self._login() + def _show_systray(self): """ Sets up the systray icon @@ -315,6 +325,7 @@ class MainWindow(QtGui.QMainWindow): settings = QtCore.QSettings() settings.setValue(self.GEOMETRY_KEY, self.saveGeometry()) settings.setValue(self.WINDOWSTATE_KEY, self.saveState()) + settings.setValue(self.AUTOLOGIN_KEY, self.ui.chkAutoLogin.isChecked()) QtGui.QMainWindow.closeEvent(self, e) def _configured_providers(self): @@ -378,6 +389,7 @@ class MainWindow(QtGui.QMainWindow): self.ui.lnPassword.setEnabled(enabled) self.ui.btnLogin.setEnabled(enabled) self.ui.chkRemember.setEnabled(enabled) + self.ui.chkAutoLogin.setEnabled(enabled) self.ui.cmbProviders.setEnabled(enabled) def _download_provider_config(self): @@ -458,6 +470,8 @@ class MainWindow(QtGui.QMainWindow): self._set_status(self.tr("Logging in...")) self._login_set_enabled(False) + settings = QtCore.QSettings() + if self.ui.chkRemember.isChecked(): try: keyring.set_password(self.KEYRING_KEY, @@ -465,7 +479,6 @@ class MainWindow(QtGui.QMainWindow): password.encode("utf8")) # Only save the username if it was saved correctly in # the keyring - settings = QtCore.QSettings() settings.setValue(self.USER_KEY, username) except Exception as e: logger.error("Problem saving data to keyring. %r" @@ -577,7 +590,7 @@ class MainWindow(QtGui.QMainWindow): """ selected_pixmap = self.ERROR_ICON tray_message = self.tr("Encryption is OFF") - if status in ("WAIT", "AUTH", "GET_CONFIG"): + if status in ("WAIT", "AUTH", "GET_CONFIG", "RECONNECTING"): selected_pixmap = self.CONNECTING_ICON elif status in ("CONNECTED"): tray_message = self.tr("Encryption is ON") diff --git a/src/leap/gui/ui/mainwindow.ui b/src/leap/gui/ui/mainwindow.ui index d8a6d37d..d723114c 100644 --- a/src/leap/gui/ui/mainwindow.ui +++ b/src/leap/gui/ui/mainwindow.ui @@ -70,13 +70,10 @@ - 1 + 0 - - - @@ -84,15 +81,11 @@ - - - - Login - - + + - - + + Qt::Horizontal @@ -104,8 +97,15 @@ - - + + + + Login + + + + + Qt::Horizontal @@ -167,6 +167,16 @@ + + + + false + + + Automatically login + + + -- cgit v1.2.3 From eaec4a9fff67134284f45eb850f0d7e0f7f275f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Mon, 11 Mar 2013 13:37:14 -0300 Subject: Finish up translation support Also: - Add spanish translations - Fix an issue when disabling autologin --- src/leap/gui/mainwindow.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 399a7cb4..4e6bb5fe 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -205,7 +205,7 @@ class MainWindow(QtGui.QMainWindow): else: settings = QtCore.QSettings() saved_user = settings.value(self.USER_KEY, None) - auto_login = settings.value(self.AUTOLOGIN_KEY, None) + auto_login = settings.value(self.AUTOLOGIN_KEY, "false") != "false" if saved_user is not None: self.ui.lnUser.setText(saved_user) @@ -217,8 +217,8 @@ class MainWindow(QtGui.QMainWindow): self.ui.lnPassword.setText(saved_password.decode("utf8")) # Only automatically login if there is a saved user - if auto_login is not None: - self.ui.chkAutoLogin.setChecked(True) + self.ui.chkAutoLogin.setChecked(auto_login) + if auto_login: self._login() def _show_systray(self): -- cgit v1.2.3 From 1580197229ce01ebdc6a655d2f5b67ca4b32da88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Mon, 11 Mar 2013 16:07:13 -0300 Subject: Auto login after the first run wizard if registered a new user Also add pyxdg as a dep --- src/leap/gui/mainwindow.py | 8 +++++++- src/leap/gui/wizard.py | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 4e6bb5fe..78ff9101 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -198,9 +198,14 @@ class MainWindow(QtGui.QMainWindow): self.show() if self._wizard: possible_username = self._wizard.get_username() + possible_password = self._wizard.get_password() if possible_username is not None: self.ui.lnUser.setText(possible_username) self._focus_password() + if possible_password is not None: + self.ui.lnPassword.setText(possible_password) + self.ui.chkRemember.setChecked(True) + self._login() self._wizard = None else: settings = QtCore.QSettings() @@ -214,7 +219,8 @@ class MainWindow(QtGui.QMainWindow): saved_password = keyring.get_password(self.KEYRING_KEY, saved_user .encode("utf8")) - self.ui.lnPassword.setText(saved_password.decode("utf8")) + if saved_password is not None: + self.ui.lnPassword.setText(saved_password.decode("utf8")) # Only automatically login if there is a saved user self.ui.chkAutoLogin.setChecked(auto_login) diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index 72e791b3..f8a1f833 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -111,12 +111,16 @@ class Wizard(QtGui.QWizard): self._register) self._username = None + self._password = None self._checker_thread = checker def get_username(self): return self._username + def get_password(self): + return self._password + def _enable_check(self, text): self.ui.btnCheck.setEnabled(len(self.ui.lnProvider.text()) != 0) @@ -186,6 +190,7 @@ class Wizard(QtGui.QWizard): username.encode("utf8"), password.encode("utf8"))]) self._username = username + self._password = password self._set_register_status(self.tr("Starting registration...")) else: self.ui.btnRegister.setEnabled(True) @@ -199,6 +204,7 @@ class Wizard(QtGui.QWizard): self.button(QtGui.QWizard.BackButton).setEnabled(False) else: self._username = None + self._password = None error_msg = self.tr("Unknown error") try: error_msg = req.json().get("errors").get("login")[0] -- cgit v1.2.3 From 060984df444dbf407e9078de638ef7486ef7b0a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Mon, 11 Mar 2013 16:42:23 -0300 Subject: Notify if openvpn is not found --- src/leap/gui/mainwindow.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 78ff9101..8464d313 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -29,6 +29,7 @@ from ui_mainwindow import Ui_MainWindow from leap.config.providerconfig import ProviderConfig from leap.crypto.srpauth import SRPAuth from leap.services.eip.vpn import VPN +from leap.services.eip.vpnlaunchers import VPNLauncherException from leap.services.eip.providerbootstrapper import ProviderBootstrapper from leap.services.eip.eipbootstrapper import EIPBootstrapper from leap.services.eip.eipconfig import EIPConfig @@ -546,17 +547,20 @@ class MainWindow(QtGui.QMainWindow): self._download_eip_config() def _start_eip(self): - self._vpn.start(eipconfig=self._eip_config, - providerconfig=self._provider_config, - socket_host="localhost", - socket_port=str(random.randint(1000, 9999))) - self._vpn_systray.setVisible(True) + try: + self._vpn.start(eipconfig=self._eip_config, + providerconfig=self._provider_config, + socket_host="localhost", + socket_port=str(random.randint(1000, 9999))) + self._vpn_systray.setVisible(True) + self.ui.btnEipStartStop.setText(self.tr("Stop EIP")) + self.ui.btnEipStartStop.clicked.disconnect( + self._start_eip) + self.ui.btnEipStartStop.clicked.connect( + self._stop_eip) + except VPNLauncherException as e: + self._set_eip_status("%s" % (e,)) self.ui.btnEipStartStop.setEnabled(True) - self.ui.btnEipStartStop.setText(self.tr("Stop EIP")) - self.ui.btnEipStartStop.clicked.disconnect( - self._start_eip) - self.ui.btnEipStartStop.clicked.connect( - self._stop_eip) def _stop_eip(self): self._vpn.set_should_quit() -- cgit v1.2.3 From 815315bdfb26aaee16a8482f1708bf3868019fd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Mon, 11 Mar 2013 17:03:00 -0300 Subject: Add user regex validator --- src/leap/gui/wizard.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/leap/gui') diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index f8a1f833..ef4eafdb 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -49,6 +49,8 @@ class Wizard(QtGui.QWizard): WEAK_PASSWORDS = ("123456", "qweasd", "qwerty", "password") + BARE_USERNAME_REGEX = r"^[A-Za-z\d_]+$" + def __init__(self, checker): QtGui.QWizard.__init__(self) @@ -110,6 +112,10 @@ class Wizard(QtGui.QWizard): self.ui.btnRegister.clicked.connect( self._register) + usernameRe = QtCore.QRegExp(self.BARE_USERNAME_REGEX) + self.ui.lblUser.setValidator( + QtGui.QRegExpValidator(usernameRe, self)) + self._username = None self._password = None -- cgit v1.2.3 From 1371b450a3e4cd8bc1d8b50133eb4b7a8f1d9aad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Mon, 11 Mar 2013 17:23:08 -0300 Subject: First run checks for a complete valid run of the wizard Not just the files on disk --- src/leap/gui/mainwindow.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 8464d313..cf795a8e 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -54,6 +54,7 @@ class MainWindow(QtGui.QMainWindow): WINDOWSTATE_KEY = "WindowState" USER_KEY = "User" AUTOLOGIN_KEY = "AutoLogin" + PROPER_PROVIDER = "ProperProvider" # Keyring KEYRING_KEY = "leap_client" @@ -183,6 +184,8 @@ class MainWindow(QtGui.QMainWindow): def _rejected_wizard(self): if self._wizard_firstrun: + settings = QtCore.QSettings() + settings.setValue(self.PROPER_PROVIDER, False) self.quit() else: self._finish_init() @@ -194,9 +197,11 @@ class MainWindow(QtGui.QMainWindow): self._wizard = None def _finish_init(self): + settings = QtCore.QSettings() self.ui.cmbProviders.addItems(self._configured_providers()) self._show_systray() self.show() + if self._wizard: possible_username = self._wizard.get_username() possible_password = self._wizard.get_password() @@ -208,8 +213,8 @@ class MainWindow(QtGui.QMainWindow): self.ui.chkRemember.setChecked(True) self._login() self._wizard = None + settings.setValue(self.PROPER_PROVIDER, True) else: - settings = QtCore.QSettings() saved_user = settings.value(self.USER_KEY, None) auto_login = settings.value(self.AUTOLOGIN_KEY, "false") != "false" @@ -305,7 +310,7 @@ class MainWindow(QtGui.QMainWindow): def quit(self): self._really_quit = True if self._wizard: - self._wizard.accept() + self._wizard.close() self.close() def changeEvent(self, e): @@ -359,7 +364,11 @@ class MainWindow(QtGui.QMainWindow): @rtype: bool """ - return len(self._configured_providers()) == 0 + settings = QtCore.QSettings() + has_provider_on_disk = len(self._configured_providers()) != 0 + is_proper_provider = settings.value(self.PROPER_PROVIDER, + "false") != "false" + return not (has_provider_on_disk and is_proper_provider) def _focus_password(self): """ -- cgit v1.2.3 From b7325ef4527b90ed007a3f3ba7c61ff680076957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Mon, 11 Mar 2013 17:36:28 -0300 Subject: Provide more meaningful messages in the provider select page --- src/leap/gui/ui/wizard.ui | 47 +++++++++++++++++++++++++++-------------------- src/leap/gui/wizard.py | 15 +++++++++++++++ 2 files changed, 42 insertions(+), 20 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/ui/wizard.ui b/src/leap/gui/ui/wizard.ui index a7198c5f..160d628d 100644 --- a/src/leap/gui/ui/wizard.ui +++ b/src/leap/gui/ui/wizard.ui @@ -122,6 +122,29 @@ + + + + + + + Check + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -132,10 +155,7 @@ - - - - + Checking provider @@ -226,26 +246,13 @@ - - + + - Check + - - - - Qt::Vertical - - - - 20 - 40 - - - - diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index ef4eafdb..82deab0d 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -283,6 +283,11 @@ class Wizard(QtGui.QWizard): Sets the status for the name resolution check """ self._complete_task(data, self.ui.lblNameResolution) + status = "" + if not data[self._provider_bootstrapper.PASSED_KEY]: + status = self.tr("Non-existent " + "provider") + self.ui.lblProviderSelectStatus.setText(status) def _https_connection(self, data): """ @@ -292,6 +297,11 @@ class Wizard(QtGui.QWizard): Sets the status for the https connection check """ self._complete_task(data, self.ui.lblHTTPS) + status = "" + if not data[self._provider_bootstrapper.PASSED_KEY]: + status = self.tr("Provider does not " + "support HTTPS") + self.ui.lblProviderSelectStatus.setText(status) def _download_provider_info(self, data): """ @@ -316,6 +326,11 @@ class Wizard(QtGui.QWizard): } self._complete_task(new_data, self.ui.lblProviderInfo) + status = "" + if not data[self._provider_bootstrapper.PASSED_KEY]: + status = self.tr("Not a valid provider" + "") + self.ui.lblProviderSelectStatus.setText(status) self.ui.btnCheck.setEnabled(True) def _download_ca_cert(self, data): -- cgit v1.2.3 From 77c7e96e2aad4db223e4c7af53ec300d5472829b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Mon, 11 Mar 2013 18:13:55 -0300 Subject: Instantiate provider name in the wizard once its set --- src/leap/gui/wizard.py | 37 ++++++++++++++++++++++++++++++++++--- src/leap/gui/wizardpage.py | 7 ++++--- 2 files changed, 38 insertions(+), 6 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index 82deab0d..70028581 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -129,6 +129,7 @@ class Wizard(QtGui.QWizard): def _enable_check(self, text): self.ui.btnCheck.setEnabled(len(self.ui.lnProvider.text()) != 0) + self._reset_provider_check() def _focus_password(self): """ @@ -228,6 +229,14 @@ class Wizard(QtGui.QWizard): """ self.ui.lblRegisterStatus.setText(status) + def _reset_provider_check(self): + self.ui.lblNameResolution.setPixmap(self.QUESTION_ICON) + self.ui.lblHTTPS.setPixmap(self.QUESTION_ICON) + self.ui.lblProviderInfo.setPixmap(self.QUESTION_ICON) + self._domain = None + self.button(QtGui.QWizard.NextButton).setEnabled(False) + self.page(self.SELECT_PROVIDER_PAGE).set_completed(False) + def _check_provider(self): """ SLOT @@ -242,6 +251,8 @@ class Wizard(QtGui.QWizard): self.ui.grpCheckProvider.setVisible(True) self.ui.btnCheck.setEnabled(False) + self.ui.lnProvider.setEnabled(False) + self.button(QtGui.QWizard.BackButton).clearFocus() self._domain = self.ui.lnProvider.text() self._provider_bootstrapper.run_provider_select_checks( @@ -284,10 +295,13 @@ class Wizard(QtGui.QWizard): """ self._complete_task(data, self.ui.lblNameResolution) status = "" - if not data[self._provider_bootstrapper.PASSED_KEY]: + passed = data[self._provider_bootstrapper.PASSED_KEY] + if not passed: status = self.tr("Non-existent " "provider") self.ui.lblProviderSelectStatus.setText(status) + self.ui.btnCheck.setEnabled(not passed) + self.ui.lnProvider.setEnabled(not passed) def _https_connection(self, data): """ @@ -298,10 +312,13 @@ class Wizard(QtGui.QWizard): """ self._complete_task(data, self.ui.lblHTTPS) status = "" - if not data[self._provider_bootstrapper.PASSED_KEY]: + passed = data[self._provider_bootstrapper.PASSED_KEY] + if not passed: status = self.tr("Provider does not " "support HTTPS") self.ui.lblProviderSelectStatus.setText(status) + self.ui.btnCheck.setEnabled(not passed) + self.ui.lnProvider.setEnabled(not passed) def _download_provider_info(self, data): """ @@ -332,6 +349,7 @@ class Wizard(QtGui.QWizard): "") self.ui.lblProviderSelectStatus.setText(status) self.ui.btnCheck.setEnabled(True) + self.ui.lnProvider.setEnabled(True) def _download_ca_cert(self, data): """ @@ -400,6 +418,10 @@ class Wizard(QtGui.QWizard): self._enable_check("") if pageId == self.SETUP_PROVIDER_PAGE: + self.page(pageId).setSubTitle(self.tr("Gathering configuration " + "options for %s") % + (self._provider_config + .get_name(),)) self._provider_bootstrapper.\ run_provider_setup_checks(self._checker_thread, self._provider_config) @@ -409,7 +431,10 @@ class Wizard(QtGui.QWizard): self._provider_config) if pageId == self.PRESENT_PROVIDER_PAGE: - # TODO: get the right lang for these + self.page(pageId).setSubTitle(self.tr("Services offered by %s") % + (self._provider_config + .get_name(),)) + lang = QtCore.QLocale.system().name() self.ui.lblProviderName.setText( "%s" % @@ -421,6 +446,12 @@ class Wizard(QtGui.QWizard): self.ui.lblProviderPolicy.setText(self._provider_config .get_enrollment_policy()) + if pageId == self.REGISTER_USER_PAGE: + self.page(pageId).setSubTitle(self.tr("Register a new user with " + "%s") % + (self._provider_config + .get_name(),)) + def nextId(self): """ Sets the next page id for the wizard based on wether the user diff --git a/src/leap/gui/wizardpage.py b/src/leap/gui/wizardpage.py index 2138ac7b..b2a00028 100644 --- a/src/leap/gui/wizardpage.py +++ b/src/leap/gui/wizardpage.py @@ -27,9 +27,10 @@ class WizardPage(QtGui.QWizardPage): QtGui.QWizardPage.__init__(self) self._completed = False - def set_completed(self): - self._completed = True - self.completeChanged.emit() + def set_completed(self, val=True): + self._completed = val + if val: + self.completeChanged.emit() def isComplete(self): return self._completed -- cgit v1.2.3 From 453a4abc87f957a96fcf68fa0ad625d27c5e73e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Mon, 11 Mar 2013 18:22:19 -0300 Subject: Prepend username if the error message doesn't start with uppercase --- src/leap/gui/wizard.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index 70028581..86e0dcfd 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -210,14 +210,17 @@ class Wizard(QtGui.QWizard): self.page(self.REGISTER_USER_PAGE).set_completed() self.button(QtGui.QWizard.BackButton).setEnabled(False) else: + old_username = self._username self._username = None self._password = None error_msg = self.tr("Unknown error") try: error_msg = req.json().get("errors").get("login")[0] + if not error_msg.istitle(): + error_msg = "%s %s" % (old_username, error_msg) + self._set_register_status(error_msg) except: logger.error("Unknown error: %r" % (req.content,)) - self._set_register_status(error_msg) self.ui.btnRegister.setEnabled(True) def _set_register_status(self, status): -- cgit v1.2.3 From 0a295be5fb396e67d7ce6800a647adbc69acee8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Mon, 11 Mar 2013 18:29:49 -0300 Subject: Set register page as commit page Also, document reset_provider_checks method, and add a reset for provider setup --- src/leap/gui/wizard.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'src/leap/gui') diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index 86e0dcfd..90ff7f1b 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -116,6 +116,8 @@ class Wizard(QtGui.QWizard): self.ui.lblUser.setValidator( QtGui.QRegExpValidator(usernameRe, self)) + self.page(self.REGISTER_USER_PAGE).setCommitPage(True) + self._username = None self._password = None @@ -233,6 +235,10 @@ class Wizard(QtGui.QWizard): self.ui.lblRegisterStatus.setText(status) def _reset_provider_check(self): + """ + Resets the UI for checking a provider. Also resets the domain + in this object. + """ self.ui.lblNameResolution.setPixmap(self.QUESTION_ICON) self.ui.lblHTTPS.setPixmap(self.QUESTION_ICON) self.ui.lblProviderInfo.setPixmap(self.QUESTION_ICON) @@ -240,6 +246,14 @@ class Wizard(QtGui.QWizard): self.button(QtGui.QWizard.NextButton).setEnabled(False) self.page(self.SELECT_PROVIDER_PAGE).set_completed(False) + def _reset_provider_setup(self): + """ + Resets the UI for setting up a provider. + """ + self.ui.lblDownloadCaCert.setPixmap(self.QUESTION_ICON) + self.ui.lblCheckCaFpr.setPixmap(self.QUESTION_ICON) + self.ui.lblCheckApiCert.setPixmap(self.QUESTION_ICON) + def _check_provider(self): """ SLOT @@ -421,6 +435,7 @@ class Wizard(QtGui.QWizard): self._enable_check("") if pageId == self.SETUP_PROVIDER_PAGE: + self._reset_provider_setup() self.page(pageId).setSubTitle(self.tr("Gathering configuration " "options for %s") % (self._provider_config -- cgit v1.2.3 From 461721850ee668d1a5e612de6af4cf1d2eeeca74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Mon, 11 Mar 2013 18:43:02 -0300 Subject: Disable remember checkbox if no keyring is available Also set properly the enabledness of chkAutoLogin --- src/leap/gui/mainwindow.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index cf795a8e..7b03eabb 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -139,6 +139,10 @@ class MainWindow(QtGui.QMainWindow): QtCore.SIGNAL("aboutToQuit()"), self._checker_thread.set_should_quit) + 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) @@ -196,6 +200,9 @@ class MainWindow(QtGui.QMainWindow): self._wizard.exec_() self._wizard = None + def _remember_state_changed(self, state): + self.ui.chkAutoLogin.setEnabled(state == QtCore.Qt.Checked) + def _finish_init(self): settings = QtCore.QSettings() self.ui.cmbProviders.addItems(self._configured_providers()) @@ -221,7 +228,8 @@ class MainWindow(QtGui.QMainWindow): if saved_user is not None: self.ui.lnUser.setText(saved_user) self.ui.chkRemember.setChecked(True) - self.ui.chkAutoLogin.setEnabled(True) + self.ui.chkAutoLogin.setEnabled(self.ui.chkRemember + .isEnabled()) saved_password = keyring.get_password(self.KEYRING_KEY, saved_user .encode("utf8")) @@ -405,7 +413,8 @@ class MainWindow(QtGui.QMainWindow): self.ui.lnPassword.setEnabled(enabled) self.ui.btnLogin.setEnabled(enabled) self.ui.chkRemember.setEnabled(enabled) - self.ui.chkAutoLogin.setEnabled(enabled) + if not enabled: + self.ui.chkAutoLogin.setEnabled(False) self.ui.cmbProviders.setEnabled(enabled) def _download_provider_config(self): -- cgit v1.2.3 From d28912e0a364ba5ed4201e87614ec94b29bd68bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Mon, 11 Mar 2013 18:48:08 -0300 Subject: Explicitly disable watermark pixmap in wizard --- src/leap/gui/wizard.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/leap/gui') diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index 90ff7f1b..3a604157 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -60,6 +60,8 @@ class Wizard(QtGui.QWizard): self.setPixmap(QtGui.QWizard.LogoPixmap, QtGui.QPixmap(":/images/leap-color-small.png")) + self.setPixmap(QtGui.QWizard.WatermarkPixmap, None) + self.QUESTION_ICON = QtGui.QPixmap(":/images/Emblem-question.png") self.ERROR_ICON = QtGui.QPixmap(":/images/Dialog-error.png") self.OK_ICON = QtGui.QPixmap(":/images/Dialog-accept.png") -- cgit v1.2.3 From c0e411564a2e7c3af75e2f7c894fa46c3ca8da00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Mon, 11 Mar 2013 18:51:50 -0300 Subject: Display error messages in red and bold --- src/leap/gui/mainwindow.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 7b03eabb..08b8f08b 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -384,13 +384,15 @@ class MainWindow(QtGui.QMainWindow): """ self.ui.lnPassword.setFocus() - def _set_status(self, status): + 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 = "%s" % (status,) self.ui.lblStatus.setText(status) def _set_eip_status(self, status): @@ -492,7 +494,7 @@ class MainWindow(QtGui.QMainWindow): self._set_status(self.tr("Please provide a valid Password")) return - self._set_status(self.tr("Logging in...")) + self._set_status(self.tr("Logging in..."), error=False) self._login_set_enabled(False) settings = QtCore.QSettings() @@ -545,7 +547,7 @@ class MainWindow(QtGui.QMainWindow): Once the user is properly authenticated, try starting the EIP service """ - self._set_status(message) + self._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 @@ -708,7 +710,6 @@ class MainWindow(QtGui.QMainWindow): Switches the stackedWidget back to the login stage after logging out """ - self._set_status(message) self._vpn_systray.setIcon(self.LOGGED_OUT_ICON) self.ui.action_sign_out.setEnabled(False) self.ui.stackedWidget.setCurrentIndex(self.LOGIN_INDEX) -- cgit v1.2.3 From 7752eb5fc11d6f5973c4aa7d575cabd0dbc979b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Mon, 11 Mar 2013 18:54:47 -0300 Subject: Don't display a ? icon when we are checking things in the wizard --- src/leap/gui/ui/wizard.ui | 48 +++++++++++++++++++++++++++++++++++++++++++++++ src/leap/gui/wizard.py | 2 +- 2 files changed, 49 insertions(+), 1 deletion(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/ui/wizard.ui b/src/leap/gui/ui/wizard.ui index 160d628d..d44e8224 100644 --- a/src/leap/gui/ui/wizard.ui +++ b/src/leap/gui/ui/wizard.ui @@ -183,6 +183,12 @@ 0 + + + 24 + 24 + + @@ -199,6 +205,12 @@ 0 + + + 24 + 24 + + @@ -215,6 +227,12 @@ 0 + + + 24 + 24 + + @@ -383,6 +401,12 @@ 0 + + + 24 + 24 + + @@ -399,6 +423,12 @@ 0 + + + 24 + 24 + + @@ -436,6 +466,12 @@ 0 + + + 24 + 24 + + @@ -638,6 +674,12 @@ 0 + + + 24 + 24 + + @@ -654,6 +696,12 @@ 0 + + + 24 + 24 + + diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index 3a604157..1aa0738b 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -62,7 +62,7 @@ class Wizard(QtGui.QWizard): self.setPixmap(QtGui.QWizard.WatermarkPixmap, None) - self.QUESTION_ICON = QtGui.QPixmap(":/images/Emblem-question.png") + self.QUESTION_ICON = None self.ERROR_ICON = QtGui.QPixmap(":/images/Dialog-error.png") self.OK_ICON = QtGui.QPixmap(":/images/Dialog-accept.png") -- cgit v1.2.3 From 908a726f0f10722ff8a875f24325ea1be6f293a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Tue, 12 Mar 2013 07:49:57 -0300 Subject: Fix passwords are equal check for the register page --- src/leap/gui/wizard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index 1aa0738b..ff257601 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -163,7 +163,7 @@ class Wizard(QtGui.QWizard): """ message = None - if message is not None and password != password2: + if message is None and password != password2: message = self.tr("Passwords don't match") if message is None and len(password) < 6: -- cgit v1.2.3 From 2da60cd0f78378fdcb8f6364a798720281b34b4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Tue, 12 Mar 2013 09:56:05 -0300 Subject: Check and try to fix certificate permissions --- src/leap/gui/mainwindow.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 08b8f08b..aa9b4ffd 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -395,13 +395,15 @@ class MainWindow(QtGui.QMainWindow): status = "%s" % (status,) self.ui.lblStatus.setText(status) - def _set_eip_status(self, status): + def _set_eip_status(self, status, error=False): """ Sets the status label at the VPN stage to status @param status: status message @type status: str """ + if error: + status = "%s" % (status,) self.ui.lblEIPStatus.setText(status) def _login_set_enabled(self, enabled=False): @@ -579,7 +581,7 @@ class MainWindow(QtGui.QMainWindow): self.ui.btnEipStartStop.clicked.connect( self._stop_eip) except VPNLauncherException as e: - self._set_eip_status("%s" % (e,)) + self._set_eip_status("%s" % (e,), error=True) self.ui.btnEipStartStop.setEnabled(True) def _stop_eip(self): @@ -609,7 +611,8 @@ class MainWindow(QtGui.QMainWindow): download_if_needed=True) else: self._set_eip_status(self.tr("%s does not support EIP") % - (self._provider_config.get_domain(),)) + (self._provider_config.get_domain(),), + error=True) def _set_eip_status_icon(self, status): """ @@ -681,15 +684,21 @@ class MainWindow(QtGui.QMainWindow): leap_assert(self._eip_config, "We need an eip config!") leap_assert(self._provider_config, "We need a provider config!") - if self._eip_config.loaded() or \ - self._eip_config.load(os.path.join("leap", - "providers", - self._provider_config - .get_domain(), - "eip-service.json")): + if data[self._eip_bootstrapper.PASSED_KEY] and \ + (self._eip_config.loaded() or + self._eip_config.load(os.path.join("leap", + "providers", + self._provider_config + .get_domain(), + "eip-service.json"))): self._start_eip() - # TODO: display a message if the EIP configuration cannot be - # loaded + else: + if data[self._eip_bootstrapper.PASSED_KEY]: + self._set_eip_status(self.tr("Could not load EIP " + "Configuration"), error=True) + else: + self._set_eip_status(data[self._eip_bootstrapper.ERROR_KEY], + error=True) def _logout(self): """ -- cgit v1.2.3 From 295b7ed9982094b6a4aa1951561e34bfb16e16c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Tue, 12 Mar 2013 10:08:58 -0300 Subject: Gray out the systray icon after logging out --- src/leap/gui/mainwindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index aa9b4ffd..7209861f 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -719,7 +719,7 @@ class MainWindow(QtGui.QMainWindow): Switches the stackedWidget back to the login stage after logging out """ - self._vpn_systray.setIcon(self.LOGGED_OUT_ICON) + self._systray.setIcon(self.LOGGED_OUT_ICON) self.ui.action_sign_out.setEnabled(False) self.ui.stackedWidget.setCurrentIndex(self.LOGIN_INDEX) self.ui.lnPassword.setText("") -- cgit v1.2.3 From f3d8bf14688227cfae1efcbec606639991885c33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Tue, 12 Mar 2013 10:34:40 -0300 Subject: Set vpn systray tooltip to the latest status --- src/leap/gui/mainwindow.py | 1 + 1 file changed, 1 insertion(+) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 7209861f..65ab6632 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -402,6 +402,7 @@ class MainWindow(QtGui.QMainWindow): @param status: status message @type status: str """ + self._vpn_systray.setToolTip(status) if error: status = "%s" % (status,) self.ui.lblEIPStatus.setText(status) -- cgit v1.2.3 From 731ecfaf60d7bd0c6a52f3fd1676cfbd71696493 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Tue, 12 Mar 2013 10:34:57 -0300 Subject: Fix disconnect problem with start/stop EIP button --- src/leap/gui/mainwindow.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 65ab6632..2a72d4d6 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -82,7 +82,7 @@ class MainWindow(QtGui.QMainWindow): self.ui.btnEipStartStop.setEnabled(False) self.ui.btnEipStartStop.clicked.connect( - self._stop_eip) + self._start_eip) # This is loaded only once, there's a bug when doing that more # than once @@ -577,8 +577,7 @@ class MainWindow(QtGui.QMainWindow): socket_port=str(random.randint(1000, 9999))) self._vpn_systray.setVisible(True) self.ui.btnEipStartStop.setText(self.tr("Stop EIP")) - self.ui.btnEipStartStop.clicked.disconnect( - self._start_eip) + self.ui.btnEipStartStop.disconnect(self) self.ui.btnEipStartStop.clicked.connect( self._stop_eip) except VPNLauncherException as e: @@ -591,8 +590,7 @@ class MainWindow(QtGui.QMainWindow): self._set_eip_status(self.tr("EIP has stopped")) self._set_eip_status_icon("error") self.ui.btnEipStartStop.setText(self.tr("Start EIP")) - self.ui.btnEipStartStop.clicked.disconnect( - self._stop_eip) + self.ui.btnEipStartStop.disconnect(self) self.ui.btnEipStartStop.clicked.connect( self._start_eip) -- cgit v1.2.3 From e18eb96f09069afe538508c30acd3d95e8359a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Tue, 12 Mar 2013 11:53:11 -0300 Subject: Add version to about --- src/leap/gui/mainwindow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 2a72d4d6..882a5a99 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -36,6 +36,7 @@ from leap.services.eip.eipconfig import EIPConfig from leap.gui.wizard import Wizard from leap.util.check import leap_assert from leap.util.checkerthread import CheckerThread +from leap import __version__ as VERSION logger = logging.getLogger(__name__) @@ -306,7 +307,7 @@ class MainWindow(QtGui.QMainWindow): Display the About LEAP dialog """ QtGui.QMessageBox.about( - self, self.tr("About LEAP"), + self, self.tr("About LEAP - %s") % (VERSION,), self.tr("LEAP is a non-profit dedicated to giving " "all internet users access to secure " "communication. Our focus is on adapting " -- cgit v1.2.3 From f838e880b7d694eee09c360ab737103a68e613aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Tue, 12 Mar 2013 11:55:04 -0300 Subject: Display ? icon for the test being run at the time Also, change button texts --- src/leap/gui/wizard.py | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index ff257601..5a66adbc 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -44,7 +44,7 @@ class Wizard(QtGui.QWizard): SETUP_PROVIDER_PAGE = 3 REGISTER_USER_PAGE = 4 SETUP_EIP_PAGE = 5 - FINISH_PATH = 6 + FINISH_PAGE = 6 WEAK_PASSWORDS = ("123456", "qweasd", "qwerty", "password") @@ -62,7 +62,7 @@ class Wizard(QtGui.QWizard): self.setPixmap(QtGui.QWizard.WatermarkPixmap, None) - self.QUESTION_ICON = None + self.QUESTION_ICON = QtGui.QPixmap(":/images/Emblem-question.png") self.ERROR_ICON = QtGui.QPixmap(":/images/Dialog-error.png") self.OK_ICON = QtGui.QPixmap(":/images/Dialog-accept.png") @@ -125,6 +125,11 @@ class Wizard(QtGui.QWizard): self._checker_thread = checker + self.page(self.REGISTER_USER_PAGE).setButtonText( + QtGui.QWizard.CommitButton, self.tr("&Next >")) + self.page(self.FINISH_PAGE).setButtonText( + QtGui.QWizard.FinishButton, self.tr("Connect")) + def get_username(self): return self._username @@ -241,9 +246,9 @@ class Wizard(QtGui.QWizard): Resets the UI for checking a provider. Also resets the domain in this object. """ - self.ui.lblNameResolution.setPixmap(self.QUESTION_ICON) - self.ui.lblHTTPS.setPixmap(self.QUESTION_ICON) - self.ui.lblProviderInfo.setPixmap(self.QUESTION_ICON) + self.ui.lblNameResolution.setPixmap(None) + self.ui.lblHTTPS.setPixmap(None) + self.ui.lblProviderInfo.setPixmap(None) self._domain = None self.button(QtGui.QWizard.NextButton).setEnabled(False) self.page(self.SELECT_PROVIDER_PAGE).set_completed(False) @@ -252,9 +257,16 @@ class Wizard(QtGui.QWizard): """ Resets the UI for setting up a provider. """ - self.ui.lblDownloadCaCert.setPixmap(self.QUESTION_ICON) - self.ui.lblCheckCaFpr.setPixmap(self.QUESTION_ICON) - self.ui.lblCheckApiCert.setPixmap(self.QUESTION_ICON) + self.ui.lblDownloadCaCert.setPixmap(None) + self.ui.lblCheckCaFpr.setPixmap(None) + self.ui.lblCheckApiCert.setPixmap(None) + + def _reset_eip_check(self): + """ + Resets the UI for the EIP check + """ + self.ui.lblDownloadEIPConfig.setPixmap(None) + self.ui.lblDownloadClientCert.setPixmap(None) def _check_provider(self): """ @@ -274,6 +286,7 @@ class Wizard(QtGui.QWizard): self.button(QtGui.QWizard.BackButton).clearFocus() self._domain = self.ui.lnProvider.text() + self.ui.lblNameResolution.setPixmap(self.QUESTION_ICON) self._provider_bootstrapper.run_provider_select_checks( self._checker_thread, self._domain) @@ -321,6 +334,7 @@ class Wizard(QtGui.QWizard): self.ui.lblProviderSelectStatus.setText(status) self.ui.btnCheck.setEnabled(not passed) self.ui.lnProvider.setEnabled(not passed) + self.ui.lblHTTPS.setPixmap(self.QUESTION_ICON) def _https_connection(self, data): """ @@ -338,6 +352,7 @@ class Wizard(QtGui.QWizard): self.ui.lblProviderSelectStatus.setText(status) self.ui.btnCheck.setEnabled(not passed) self.ui.lnProvider.setEnabled(not passed) + self.ui.lblProviderInfo.setPixmap(self.QUESTION_ICON) def _download_provider_info(self, data): """ @@ -378,6 +393,7 @@ class Wizard(QtGui.QWizard): Sets the status for the download of the CA certificate check """ self._complete_task(data, self.ui.lblDownloadCaCert) + self.ui.lblCheckCaFpr.setPixmap(self.QUESTION_ICON) def _check_ca_fingerprint(self, data): """ @@ -387,6 +403,7 @@ class Wizard(QtGui.QWizard): Sets the status for the CA fingerprint check """ self._complete_task(data, self.ui.lblCheckCaFpr) + self.ui.lblCheckApiCert.setPixmap(self.QUESTION_ICON) def _check_api_certificate(self, data): """ @@ -408,6 +425,7 @@ class Wizard(QtGui.QWizard): Sets the status for the EIP config downloading check """ self._complete_task(data, self.ui.lblDownloadEIPConfig) + self.ui.lblDownloadClientCert.setPixmap(self.QUESTION_ICON) def _download_client_certificate(self, data): """ @@ -430,10 +448,7 @@ class Wizard(QtGui.QWizard): Prepares the pages when they appear """ if pageId == self.SELECT_PROVIDER_PAGE: - self.ui.grpCheckProvider.setVisible(False) - self.ui.lblNameResolution.setPixmap(self.QUESTION_ICON) - self.ui.lblHTTPS.setPixmap(self.QUESTION_ICON) - self.ui.lblProviderInfo.setPixmap(self.QUESTION_ICON) + self._reset_provider_check() self._enable_check("") if pageId == self.SETUP_PROVIDER_PAGE: @@ -442,11 +457,14 @@ class Wizard(QtGui.QWizard): "options for %s") % (self._provider_config .get_name(),)) + self.ui.lblDownloadCaCert.setPixmap(self.QUESTION_ICON) self._provider_bootstrapper.\ run_provider_setup_checks(self._checker_thread, self._provider_config) if pageId == self.SETUP_EIP_PAGE: + self._reset_eip_check() + self.ui.lblDownloadEIPConfig.setPixmap(self.QUESTION_ICON) self._eip_bootstrapper.run_eip_setup_checks(self._checker_thread, self._provider_config) -- cgit v1.2.3 From 68c18135659351aac1c10aefadce431240ee6366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Tue, 12 Mar 2013 12:06:56 -0300 Subject: Reset error message when the provider changes --- src/leap/gui/wizard.py | 1 + 1 file changed, 1 insertion(+) (limited to 'src/leap/gui') diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index 5a66adbc..08e9cc6b 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -249,6 +249,7 @@ class Wizard(QtGui.QWizard): self.ui.lblNameResolution.setPixmap(None) self.ui.lblHTTPS.setPixmap(None) self.ui.lblProviderInfo.setPixmap(None) + self.ui.lblProviderSelectStatus.setText("") self._domain = None self.button(QtGui.QWizard.NextButton).setEnabled(False) self.page(self.SELECT_PROVIDER_PAGE).set_completed(False) -- cgit v1.2.3 From 605a456ffdb196211d239b6bdb56914b260c468f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Tue, 12 Mar 2013 12:11:34 -0300 Subject: Add watermark image --- src/leap/gui/wizard.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index 08e9cc6b..202c6dc2 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -60,7 +60,8 @@ class Wizard(QtGui.QWizard): self.setPixmap(QtGui.QWizard.LogoPixmap, QtGui.QPixmap(":/images/leap-color-small.png")) - self.setPixmap(QtGui.QWizard.WatermarkPixmap, None) + self.setPixmap(QtGui.QWizard.WatermarkPixmap, + QtGui.QPixmap(":/images/watermark.png")) self.QUESTION_ICON = QtGui.QPixmap(":/images/Emblem-question.png") self.ERROR_ICON = QtGui.QPixmap(":/images/Dialog-error.png") -- cgit v1.2.3 From 70ab05982ab778b6d2e949b186b9932b90115a8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Tue, 12 Mar 2013 12:54:49 -0300 Subject: Prepend https:// to the provider domain --- src/leap/gui/wizard.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index 202c6dc2..de988153 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -479,7 +479,8 @@ class Wizard(QtGui.QWizard): self.ui.lblProviderName.setText( "%s" % (self._provider_config.get_name(lang=lang),)) - self.ui.lblProviderURL.setText(self._provider_config.get_domain()) + self.ui.lblProviderURL.setText( + "https://%s" % (self._provider_config.get_domain(),)) self.ui.lblProviderDesc.setText( "%s" % (self._provider_config.get_description(lang=lang),)) -- cgit v1.2.3 From cabfa92593d0c21f63f218842af8ed2bfba77cde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Tue, 12 Mar 2013 14:08:47 -0300 Subject: Make login in parallel --- src/leap/gui/mainwindow.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 882a5a99..3af87366 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -24,6 +24,7 @@ import random import keyring from PySide import QtCore, QtGui +from functools import partial from ui_mainwindow import Ui_MainWindow from leap.config.providerconfig import ProviderConfig @@ -538,7 +539,10 @@ class MainWindow(QtGui.QMainWindow): self._srp_auth.logout_finished.connect( self._done_logging_out) - self._srp_auth.authenticate(username, password) + auth_partial = partial(self._srp_auth.authenticate, + username, + password) + self._checker_thread.add_checks([auth_partial]) else: self._set_status(data[self._provider_bootstrapper.ERROR_KEY]) self._login_set_enabled(True) @@ -760,7 +764,6 @@ class MainWindow(QtGui.QMainWindow): if __name__ == "__main__": import signal - from functools import partial def sigint_handler(*args, **kwargs): logger.debug('SIGINT catched. shutting down...') -- cgit v1.2.3 From 66464e6a2ea48d3dbafaa7c11b14478e7d34ab4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Tue, 12 Mar 2013 14:11:56 -0300 Subject: Login after register Also, mark error in red and bold in the register form --- src/leap/gui/ui/wizard.ui | 84 ++++++++++++++++++++++++++--------------------- src/leap/gui/wizard.py | 44 ++++++++++++++++++++----- 2 files changed, 83 insertions(+), 45 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/ui/wizard.ui b/src/leap/gui/ui/wizard.ui index d44e8224..ad4c0500 100644 --- a/src/leap/gui/ui/wizard.ui +++ b/src/leap/gui/ui/wizard.ui @@ -535,42 +535,6 @@ 4 - - - - - - - Qt::AutoText - - - Qt::AlignCenter - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - <b>User:</b> - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - @@ -600,7 +564,7 @@ - + Register @@ -620,6 +584,52 @@ + + + + + + + Qt::AutoText + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + <b>User:</b> + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + false + + + Remember my username and password + + + diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index de988153..1f0bc877 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -27,6 +27,7 @@ from functools import partial from ui_wizard import Ui_Wizard from leap.config.providerconfig import ProviderConfig from leap.crypto.srpregister import SRPRegister +from leap.crypto.srpauth import SRPAuth from leap.services.eip.providerbootstrapper import ProviderBootstrapper from leap.services.eip.eipbootstrapper import EIPBootstrapper @@ -60,9 +61,6 @@ class Wizard(QtGui.QWizard): self.setPixmap(QtGui.QWizard.LogoPixmap, QtGui.QPixmap(":/images/leap-color-small.png")) - self.setPixmap(QtGui.QWizard.WatermarkPixmap, - QtGui.QPixmap(":/images/watermark.png")) - self.QUESTION_ICON = QtGui.QPixmap(":/images/Emblem-question.png") self.ERROR_ICON = QtGui.QPixmap(":/images/Dialog-error.png") self.OK_ICON = QtGui.QPixmap(":/images/Dialog-accept.png") @@ -182,7 +180,7 @@ class Wizard(QtGui.QWizard): message = self.tr("Password equal to username") if message is not None: - self._set_register_status(message) + self._set_register_status(message, error=True) self._focus_password() return False @@ -215,10 +213,20 @@ class Wizard(QtGui.QWizard): def _registration_finished(self, ok, req): if ok: self._set_register_status(self.tr("" - "User registration OK")) + "User registration OK. " + "Logging in...")) self.ui.lblPassword2.clearFocus() - self.page(self.REGISTER_USER_PAGE).set_completed() - self.button(QtGui.QWizard.BackButton).setEnabled(False) + + srp_auth = SRPAuth(self._provider_config) + srp_auth.authentication_finished.connect( + self._authentication_finished) + + auth_partial = partial(srp_auth.authenticate, + self._username, + self._password) + self._checker_thread.add_checks([auth_partial]) + + self.ui.chkRemember.setEnabled(True) else: old_username = self._username self._username = None @@ -233,13 +241,33 @@ class Wizard(QtGui.QWizard): logger.error("Unknown error: %r" % (req.content,)) self.ui.btnRegister.setEnabled(True) - def _set_register_status(self, status): + def _authentication_finished(self, ok, message): + """ + SLOT + TRIGGER: srp_auth.authentication_finished + + Finish the authentication process as it comes from the + register form + """ + if ok: + self._set_register_status(self.tr("" + "Login succeeded!" + "")) + self.page(self.REGISTER_USER_PAGE).set_completed() + else: + self._set_register_status(message) + self.ui.btnRegister.setEnabled(True) + self.ui.chkRemember.setEnabled(False) + + def _set_register_status(self, status, error=False): """ Sets the status label in the registration page to status @param status: status message to display, can be HTML @type status: str """ + if error: + status = "%s" % (status,) self.ui.lblRegisterStatus.setText(status) def _reset_provider_check(self): -- cgit v1.2.3 From 364c6d5e9882f47ff5938f731641bc7509e32fc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Tue, 12 Mar 2013 17:12:20 -0300 Subject: Make the wizard provider setup only Also: - Add a way to enable/disable services for a provider - Run EIP only if it is enabled --- src/leap/gui/mainwindow.py | 25 +++++++-- src/leap/gui/ui/mainwindow.ui | 3 +- src/leap/gui/ui/wizard.ui | 106 ++-------------------------------- src/leap/gui/wizard.py | 128 +++++++++++++++++++----------------------- 4 files changed, 88 insertions(+), 174 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 3af87366..f8ca4c35 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -175,6 +175,8 @@ class MainWindow(QtGui.QMainWindow): self._action_visible = QtGui.QAction(self.tr("Hide"), self) self._action_visible.triggered.connect(self._toggle_visible) + self._enabled_services = [] + self._center_window() self._wizard = None self._wizard_firstrun = False @@ -214,6 +216,11 @@ class MainWindow(QtGui.QMainWindow): if self._wizard: possible_username = self._wizard.get_username() possible_password = self._wizard.get_password() + self.ui.chkRemember.setChecked(self._wizard.get_remember()) + self._enabled_services = list(self._wizard.get_services()) + settings.setValue("%s/Services" % + (self.ui.cmbProviders.currentText(),), + self._enabled_services) if possible_username is not None: self.ui.lnUser.setText(possible_username) self._focus_password() @@ -487,6 +494,11 @@ class MainWindow(QtGui.QMainWindow): password = self.ui.lnPassword.text() provider = self.ui.cmbProviders.currentText() + settings = QtCore.QSettings() + self._enabled_services = settings.value( + "%s/Services" % + (self.ui.cmbProviders.currentText(),), "").split(",") + if len(provider) == 0: self._set_status(self.tr("Please select a valid provider")) return @@ -608,15 +620,20 @@ class MainWindow(QtGui.QMainWindow): self._set_eip_status(self.tr("Checking configuration, please wait...")) - if self._provider_config.provides_eip(): + if self._provider_config.provides_eip() and \ + self._enabled_services.count("openvpn") > 0: self._eip_bootstrapper.run_eip_setup_checks( self._checker_thread, self._provider_config, download_if_needed=True) else: - self._set_eip_status(self.tr("%s does not support EIP") % - (self._provider_config.get_domain(),), - error=True) + if self._enabled_services.count("openvpn") > 0: + self._set_eip_status(self.tr("%s does not support EIP") % + (self._provider_config.get_domain(),), + error=True) + else: + self._set_eip_status(self.tr("EIP is disabled")) + self.ui.btnEipStartStop.setEnabled(False) def _set_eip_status_icon(self, status): """ diff --git a/src/leap/gui/ui/mainwindow.ui b/src/leap/gui/ui/mainwindow.ui index d723114c..a8a8aa59 100644 --- a/src/leap/gui/ui/mainwindow.ui +++ b/src/leap/gui/ui/mainwindow.ui @@ -70,7 +70,7 @@ - 0 + 1 @@ -389,6 +389,7 @@ + diff --git a/src/leap/gui/ui/wizard.ui b/src/leap/gui/ui/wizard.ui index ad4c0500..b8fed183 100644 --- a/src/leap/gui/ui/wizard.ui +++ b/src/leap/gui/ui/wizard.ui @@ -632,123 +632,29 @@ - + - EIP Setup + Service selection - Setting up Encrypted Internet + Please select the services you would like to have 5 - - - Qt::Vertical - - - - 20 - 40 - - - - - - + - Checking EIP + Services by PROVIDER - - - - Download EIP configuration - - - - - - - Download client certificate - - - - - - - - 0 - 0 - - - - - 24 - 24 - - - - - - - :/images/Emblem-question.png - - - - - - - - 0 - 0 - - - - - 24 - 24 - - - - - - - :/images/Emblem-question.png - - - - - - Qt::Horizontal - - - - 40 - 0 - - - + - - - - Qt::Vertical - - - - 20 - 40 - - - - diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index 1f0bc877..83f64020 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -27,9 +27,7 @@ from functools import partial from ui_wizard import Ui_Wizard from leap.config.providerconfig import ProviderConfig from leap.crypto.srpregister import SRPRegister -from leap.crypto.srpauth import SRPAuth from leap.services.eip.providerbootstrapper import ProviderBootstrapper -from leap.services.eip.eipbootstrapper import EIPBootstrapper logger = logging.getLogger(__name__) @@ -44,7 +42,7 @@ class Wizard(QtGui.QWizard): PRESENT_PROVIDER_PAGE = 2 SETUP_PROVIDER_PAGE = 3 REGISTER_USER_PAGE = 4 - SETUP_EIP_PAGE = 5 + SERVICES_PAGE = 5 FINISH_PAGE = 6 WEAK_PASSWORDS = ("123456", "qweasd", "qwerty", @@ -65,6 +63,16 @@ class Wizard(QtGui.QWizard): self.ERROR_ICON = QtGui.QPixmap(":/images/Dialog-error.png") self.OK_ICON = QtGui.QPixmap(":/images/Dialog-accept.png") + # Correspondence for services and their name to display + self.SERVICE_DISPLAY = [ + self.tr("Encrypted Internet") + ] + self.SERVICE_CONFIG = [ + "openvpn" + ] + + self._selected_services = set() + self._show_register = False self.ui.grpCheckProvider.setVisible(False) @@ -86,13 +94,6 @@ class Wizard(QtGui.QWizard): self._provider_bootstrapper.check_api_certificate.connect( self._check_api_certificate) - self._eip_bootstrapper = EIPBootstrapper() - - self._eip_bootstrapper.download_config.connect( - self._download_eip_config) - self._eip_bootstrapper.download_client_certificate.connect( - self._download_client_certificate) - self._domain = None self._provider_config = ProviderConfig() @@ -135,6 +136,12 @@ class Wizard(QtGui.QWizard): def get_password(self): return self._password + def get_remember(self): + return self.ui.chkRemember.isChecked() + + def get_services(self): + return self._selected_services + def _enable_check(self, text): self.ui.btnCheck.setEnabled(len(self.ui.lnProvider.text()) != 0) self._reset_provider_check() @@ -214,19 +221,11 @@ class Wizard(QtGui.QWizard): if ok: self._set_register_status(self.tr("" "User registration OK. " - "Logging in...")) + "")) self.ui.lblPassword2.clearFocus() - - srp_auth = SRPAuth(self._provider_config) - srp_auth.authentication_finished.connect( - self._authentication_finished) - - auth_partial = partial(srp_auth.authenticate, - self._username, - self._password) - self._checker_thread.add_checks([auth_partial]) - self.ui.chkRemember.setEnabled(True) + + self.page(self.REGISTER_USER_PAGE).set_completed() else: old_username = self._username self._username = None @@ -241,24 +240,6 @@ class Wizard(QtGui.QWizard): logger.error("Unknown error: %r" % (req.content,)) self.ui.btnRegister.setEnabled(True) - def _authentication_finished(self, ok, message): - """ - SLOT - TRIGGER: srp_auth.authentication_finished - - Finish the authentication process as it comes from the - register form - """ - if ok: - self._set_register_status(self.tr("" - "Login succeeded!" - "")) - self.page(self.REGISTER_USER_PAGE).set_completed() - else: - self._set_register_status(message) - self.ui.btnRegister.setEnabled(True) - self.ui.chkRemember.setEnabled(False) - def _set_register_status(self, status, error=False): """ Sets the status label in the registration page to status @@ -291,13 +272,6 @@ class Wizard(QtGui.QWizard): self.ui.lblCheckCaFpr.setPixmap(None) self.ui.lblCheckApiCert.setPixmap(None) - def _reset_eip_check(self): - """ - Resets the UI for the EIP check - """ - self.ui.lblDownloadEIPConfig.setPixmap(None) - self.ui.lblDownloadClientCert.setPixmap(None) - def _check_provider(self): """ SLOT @@ -447,28 +421,47 @@ class Wizard(QtGui.QWizard): self._complete_task(data, self.ui.lblCheckApiCert, True, self.SETUP_PROVIDER_PAGE) - def _download_eip_config(self, data): + def _service_selection_changed(self, service, state): """ SLOT - TRIGGER: self._eip_bootstrapper.download_config + TRIGGER: service_checkbox.stateChanged + Adds the service to the state if the state is checked, removes + it otherwise + + @param service: service to handle + @type service: str + @param state: state of the checkbox + @type state: int + """ + if state == QtCore.Qt.Checked: + self._selected_services = \ + self._selected_services.union({service}) + else: + self._selected_services = \ + self._selected_services.difference({service}) - Sets the status for the EIP config downloading check + def _populate_services(self): """ - self._complete_task(data, self.ui.lblDownloadEIPConfig) - self.ui.lblDownloadClientCert.setPixmap(self.QUESTION_ICON) - - def _download_client_certificate(self, data): + Loads the services that the provider provides into the UI for + the user to enable or disable """ - SLOT - TRIGGER: self._provider_bootstrapper.download_client_certificate + self.ui.grpServices.setTitle( + self.tr("Services by %s") % + (self._provider_config.get_name(),)) - Sets the status for the download client certificate check and - completes the page if passed. Also stops the eip bootstrapper - thread since it's not needed from this point on unless the - check chain is restarted - """ - self._complete_task(data, self.ui.lblDownloadClientCert, - True, self.SETUP_EIP_PAGE) + for service in self._provider_config.get_services(): + try: + checkbox = QtGui.QCheckBox(self) + service_index = self.SERVICE_CONFIG.index(service) + checkbox.setText(self.SERVICE_DISPLAY[service_index]) + self.ui.serviceListLayout.addWidget(checkbox) + checkbox.stateChanged.connect( + partial(self._service_selection_changed, service)) + checkbox.setChecked(True) + except ValueError: + logger.error( + self.tr("Something went wrong while trying to " + "load service %s" % (service,))) def _current_id_changed(self, pageId): """ @@ -492,12 +485,6 @@ class Wizard(QtGui.QWizard): run_provider_setup_checks(self._checker_thread, self._provider_config) - if pageId == self.SETUP_EIP_PAGE: - self._reset_eip_check() - self.ui.lblDownloadEIPConfig.setPixmap(self.QUESTION_ICON) - self._eip_bootstrapper.run_eip_setup_checks(self._checker_thread, - self._provider_config) - if pageId == self.PRESENT_PROVIDER_PAGE: self.page(pageId).setSubTitle(self.tr("Services offered by %s") % (self._provider_config @@ -521,6 +508,9 @@ class Wizard(QtGui.QWizard): (self._provider_config .get_name(),)) + if pageId == self.SERVICES_PAGE: + self._populate_services() + def nextId(self): """ Sets the next page id for the wizard based on wether the user @@ -533,6 +523,6 @@ class Wizard(QtGui.QWizard): if self._show_register: return self.REGISTER_USER_PAGE else: - return self.SETUP_EIP_PAGE + return self.SERVICES_PAGE return QtGui.QWizard.nextId(self) -- cgit v1.2.3 From 70c402fe170ca4e01159b03739b7cacda7b0dfd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 13 Mar 2013 09:43:13 -0300 Subject: Add mtime check for existing json definitions before download Also, wait for threads to finish when quitting --- src/leap/gui/mainwindow.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index f8ca4c35..6f7f646e 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -136,10 +136,18 @@ class MainWindow(QtGui.QMainWindow): QtCore.QCoreApplication.instance(), QtCore.SIGNAL("aboutToQuit()"), self._vpn.set_should_quit) + QtCore.QCoreApplication.instance().connect( + QtCore.QCoreApplication.instance(), + QtCore.SIGNAL("aboutToQuit()"), + self._vpn.wait) QtCore.QCoreApplication.instance().connect( QtCore.QCoreApplication.instance(), QtCore.SIGNAL("aboutToQuit()"), self._checker_thread.set_should_quit) + QtCore.QCoreApplication.instance().connect( + QtCore.QCoreApplication.instance(), + QtCore.SIGNAL("aboutToQuit()"), + self._checker_thread.wait) self.ui.chkRemember.stateChanged.connect( self._remember_state_changed) -- cgit v1.2.3 From 4359515dafe572398262ce91bf88d4f122042981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 13 Mar 2013 13:39:00 -0300 Subject: Add vpn already running checks --- src/leap/gui/mainwindow.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 6f7f646e..9b4a70a5 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -680,6 +680,14 @@ class MainWindow(QtGui.QMainWindow): self._set_eip_status(self.tr("VPN: Connected!")) elif status == "WAIT": self._set_eip_status(self.tr("VPN: Waiting to start...")) + elif status == "ALREADYRUNNING": + # Put the following calls in Qt's event queue, otherwise + # the UI won't update properly + QtCore.QTimer.singleShot(0, self._stop_eip) + QtCore.QTimer.singleShot(0, partial(self._set_eip_status, + self.tr("Unable to start VPN, " + "it's already " + "running."))) else: self._set_eip_status(status) -- cgit v1.2.3 From ef43dff37c6db915757184ad51bc017d45e70c98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 13 Mar 2013 14:02:13 -0300 Subject: Notify the user of pkexec/polkit/openvpn checks --- src/leap/gui/mainwindow.py | 22 +++++++++++++++++++++- src/leap/gui/ui/mainwindow.ui | 3 +++ 2 files changed, 24 insertions(+), 1 deletion(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 9b4a70a5..9589bea0 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -30,7 +30,10 @@ from ui_mainwindow import Ui_MainWindow from leap.config.providerconfig import ProviderConfig from leap.crypto.srpauth import SRPAuth from leap.services.eip.vpn import VPN -from leap.services.eip.vpnlaunchers import VPNLauncherException +from leap.services.eip.vpnlaunchers import (VPNLauncherException, + OpenVPNNotFoundException, + EIPNoPkexecAvailable, + EIPNoPolkitAuthAgentAvailable) from leap.services.eip.providerbootstrapper import ProviderBootstrapper from leap.services.eip.eipbootstrapper import EIPBootstrapper from leap.services.eip.eipconfig import EIPConfig @@ -605,8 +608,25 @@ class MainWindow(QtGui.QMainWindow): self.ui.btnEipStartStop.disconnect(self) self.ui.btnEipStartStop.clicked.connect( self._stop_eip) + except EIPNoPolkitAuthAgentAvailable: + self._set_eip_status(self.tr("We could not find any " + "authentication " + "agent in your system.
" + "Make sure you have " + "polkit-gnome-authentication-" + "agent-1 " + "running and try again."), + error=True) + except EIPNoPkexecAvailable: + self._set_eip_status(self.tr("We could not find pkexec " + "in your system."), + error=True) + except OpenVPNNotFoundException: + self._set_eip_status(self.tr("We couldn't find openvpn"), + error=True) except VPNLauncherException as e: self._set_eip_status("%s" % (e,), error=True) + self.ui.btnEipStartStop.setEnabled(True) def _stop_eip(self): diff --git a/src/leap/gui/ui/mainwindow.ui b/src/leap/gui/ui/mainwindow.ui index a8a8aa59..ca3db95c 100644 --- a/src/leap/gui/ui/mainwindow.ui +++ b/src/leap/gui/ui/mainwindow.ui @@ -274,6 +274,9 @@ Qt::AlignCenter + + true +
-- cgit v1.2.3 From fdc1d749a859cefd325e1de712f90eba79d3f678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 13 Mar 2013 14:15:02 -0300 Subject: Display a more related error message for https wizard checks Differentiate between SSLError and other type of errors, so to its clear when the certificate fails and when the connection fails or other kind of problems --- src/leap/gui/wizard.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index 83f64020..4e811fb9 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -351,8 +351,8 @@ class Wizard(QtGui.QWizard): status = "" passed = data[self._provider_bootstrapper.PASSED_KEY] if not passed: - status = self.tr("Provider does not " - "support HTTPS") + status = self.tr("%s") \ + % (data[self._provider_bootstrapper.ERROR_KEY]) self.ui.lblProviderSelectStatus.setText(status) self.ui.btnCheck.setEnabled(not passed) self.ui.lnProvider.setEnabled(not passed) -- cgit v1.2.3 From 715b50b872736952d01b2c8cac9003da1e634c05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 13 Mar 2013 14:24:05 -0300 Subject: Make sure we always end up with the proper list of services --- src/leap/gui/mainwindow.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 9589bea0..af905fc0 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -508,7 +508,10 @@ class MainWindow(QtGui.QMainWindow): settings = QtCore.QSettings() self._enabled_services = settings.value( "%s/Services" % - (self.ui.cmbProviders.currentText(),), "").split(",") + (self.ui.cmbProviders.currentText(),), "") + + if isinstance(self._enabled_services, (str, unicode)): + self._enabled_services = self._enabled_services.split(",") if len(provider) == 0: self._set_status(self.tr("Please select a valid provider")) -- cgit v1.2.3 From 3ff6a5ebadca8cf6066e9db39fcbfcb3d8283430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 13 Mar 2013 14:42:52 -0300 Subject: Add a TODO comment to not forget improve the configured providers check --- src/leap/gui/mainwindow.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index af905fc0..6aaf0a38 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -374,6 +374,9 @@ class MainWindow(QtGui.QMainWindow): @rtype: list """ + + # TODO: check which providers have a valid certificate among + # other things, not just the directories providers = [] try: providers = os.listdir( -- cgit v1.2.3 From 4e3b2f09f0e5fba35aef3edf8ad2b23c64a5d995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 13 Mar 2013 15:46:50 -0300 Subject: Use a unix socket every time for now Will need a special case for windows --- src/leap/gui/mainwindow.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 6aaf0a38..c2c37485 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -20,7 +20,7 @@ Main window for the leap client """ import os import logging -import random +import tempfile import keyring from PySide import QtCore, QtGui @@ -603,12 +603,27 @@ class MainWindow(QtGui.QMainWindow): self._systray.setIcon(self.LOGGED_IN_ICON) self._download_eip_config() + def _get_socket_host(self): + """ + Returns the socket and port to be used for VPN + + @rtype: tuple (str, str) (host, port) + """ + + # TODO: make this properly multiplatform + host = os.path.join(tempfile.mkdtemp(prefix="leap-tmp"), + 'openvpn.socket') + port = "unix" + + return host, port + def _start_eip(self): try: + host, port = self._get_socket_host() self._vpn.start(eipconfig=self._eip_config, providerconfig=self._provider_config, - socket_host="localhost", - socket_port=str(random.randint(1000, 9999))) + socket_host=host, + socket_port=port) self._vpn_systray.setVisible(True) self.ui.btnEipStartStop.setText(self.tr("Stop EIP")) self.ui.btnEipStartStop.disconnect(self) -- cgit v1.2.3 From 9bd4a1394cfbdd555b9cb42e2aeba3dc4957ae88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 13 Mar 2013 16:08:42 -0300 Subject: Always show the eip icon if the service is available and enabled --- src/leap/gui/mainwindow.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index c2c37485..4db74c2d 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -104,8 +104,6 @@ class MainWindow(QtGui.QMainWindow): # configuration and certificate. self._provider_bootstrapper = ProviderBootstrapper() - # TODO: add sigint handler - # Intermediate stages, only do something if there was an error self._provider_bootstrapper.name_resolution.connect( self._intermediate_stage) @@ -171,8 +169,8 @@ class MainWindow(QtGui.QMainWindow): self._action_eip_status = QtGui.QAction(self.tr("Encryption is OFF"), self) self._action_eip_status.setEnabled(False) - self._action_eip_stop = QtGui.QAction(self.tr("Stop"), self) - self._action_eip_stop.triggered.connect( + self._action_eip_startstop = QtGui.QAction(self.tr("Stop"), self) + self._action_eip_startstop.triggered.connect( self._stop_eip) self._action_eip_write = QtGui.QAction( QtGui.QIcon(":/images/Arrow-Up-32.png"), @@ -278,7 +276,7 @@ class MainWindow(QtGui.QMainWindow): vpn_systrayMenu = QtGui.QMenu(self) vpn_systrayMenu.addAction(self._action_eip_status) - vpn_systrayMenu.addAction(self._action_eip_stop) + vpn_systrayMenu.addAction(self._action_eip_startstop) vpn_systrayMenu.addAction(self._action_eip_read) vpn_systrayMenu.addAction(self._action_eip_write) self._vpn_systray = QtGui.QSystemTrayIcon(self) @@ -624,11 +622,14 @@ class MainWindow(QtGui.QMainWindow): providerconfig=self._provider_config, socket_host=host, socket_port=port) - self._vpn_systray.setVisible(True) self.ui.btnEipStartStop.setText(self.tr("Stop EIP")) self.ui.btnEipStartStop.disconnect(self) self.ui.btnEipStartStop.clicked.connect( self._stop_eip) + self._action_eip_startstop.setText(self.tr("Stop")) + self._action_eip_startstop.disconnect(self) + self._action_eip_startstop.triggered.connect( + self._stop_eip) except EIPNoPolkitAuthAgentAvailable: self._set_eip_status(self.tr("We could not find any " "authentication " @@ -652,13 +653,16 @@ class MainWindow(QtGui.QMainWindow): def _stop_eip(self): self._vpn.set_should_quit() - self._vpn_systray.setVisible(False) self._set_eip_status(self.tr("EIP has stopped")) self._set_eip_status_icon("error") self.ui.btnEipStartStop.setText(self.tr("Start EIP")) self.ui.btnEipStartStop.disconnect(self) self.ui.btnEipStartStop.clicked.connect( self._start_eip) + self._action_eip_startstop.setText(self.tr("Start")) + self._action_eip_startstop.disconnect(self) + self._action_eip_startstop.triggered.connect( + self._start_eip) def _download_eip_config(self): """ @@ -671,6 +675,7 @@ class MainWindow(QtGui.QMainWindow): if self._provider_config.provides_eip() and \ self._enabled_services.count("openvpn") > 0: + self._vpn_systray.setVisible(True) self._eip_bootstrapper.run_eip_setup_checks( self._checker_thread, self._provider_config, @@ -804,7 +809,6 @@ class MainWindow(QtGui.QMainWindow): self._login_set_enabled(True) self._set_status("") self._vpn.set_should_quit() - self._vpn_systray.setVisible(False) def _intermediate_stage(self, data): """ -- cgit v1.2.3 From d0dfad6ac2af360de6421ce74a6831b5b81ad019 Mon Sep 17 00:00:00 2001 From: kali Date: Thu, 14 Mar 2013 07:08:31 +0900 Subject: namespace leap + leap.common split leap is a namespace package from here on. common folder will be deleted and moved to leap_pycommon repository. --- src/leap/gui/mainwindow.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 4db74c2d..a93b2e3c 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -27,6 +27,7 @@ from PySide import QtCore, QtGui from functools import partial from ui_mainwindow import Ui_MainWindow +from leap.common.check import leap_assert from leap.config.providerconfig import ProviderConfig from leap.crypto.srpauth import SRPAuth from leap.services.eip.vpn import VPN @@ -38,9 +39,8 @@ from leap.services.eip.providerbootstrapper import ProviderBootstrapper from leap.services.eip.eipbootstrapper import EIPBootstrapper from leap.services.eip.eipconfig import EIPConfig from leap.gui.wizard import Wizard -from leap.util.check import leap_assert from leap.util.checkerthread import CheckerThread -from leap import __version__ as VERSION +from leap.util import __version__ as VERSION logger = logging.getLogger(__name__) @@ -325,13 +325,14 @@ class MainWindow(QtGui.QMainWindow): """ QtGui.QMessageBox.about( self, self.tr("About LEAP - %s") % (VERSION,), - self.tr("LEAP is a non-profit dedicated to giving " + self.tr("version: %s
" + "LEAP is a non-profit dedicated to giving " "all internet users access to secure " "communication. Our focus is on adapting " "encryption technology to make it easy to use " "and widely available. " "More about LEAP" - "")) + "") % (VERSION,)) def quit(self): self._really_quit = True -- cgit v1.2.3 From ddbad58fc2b3f44a293eeac7790a06f13b56944a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 20 Mar 2013 16:44:00 -0300 Subject: Display ASSIGN_IP state correctly in the mainwindow --- src/leap/gui/mainwindow.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index a93b2e3c..eabdfe57 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -699,7 +699,8 @@ class MainWindow(QtGui.QMainWindow): """ selected_pixmap = self.ERROR_ICON tray_message = self.tr("Encryption is OFF") - if status in ("WAIT", "AUTH", "GET_CONFIG", "RECONNECTING"): + if status in ("WAIT", "AUTH", "GET_CONFIG", + "RECONNECTING", "ASSIGN_IP"): selected_pixmap = self.CONNECTING_ICON elif status in ("CONNECTED"): tray_message = self.tr("Encryption is ON") @@ -727,6 +728,8 @@ class MainWindow(QtGui.QMainWindow): self._set_eip_status(self.tr("VPN: Connected!")) elif status == "WAIT": self._set_eip_status(self.tr("VPN: Waiting to start...")) + elif status == "ASSIGN_IP": + self._set_eip_status(self.tr("VPN: Assigning IP")) elif status == "ALREADYRUNNING": # Put the following calls in Qt's event queue, otherwise # the UI won't update properly -- cgit v1.2.3 From 74600d1c73cfc2f958f2c64d57ea8fb85d348424 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 20 Mar 2013 21:06:59 +0900 Subject: syntax fix for python 2.6 --- src/leap/gui/wizard.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index 4e811fb9..dee3b230 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -435,10 +435,10 @@ class Wizard(QtGui.QWizard): """ if state == QtCore.Qt.Checked: self._selected_services = \ - self._selected_services.union({service}) + self._selected_services.union(set([service])) else: self._selected_services = \ - self._selected_services.difference({service}) + self._selected_services.difference(set([service])) def _populate_services(self): """ -- cgit v1.2.3 From 8f54774f6c3f779527718a0158ebd0efc4aab588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Fri, 15 Mar 2013 13:30:01 -0300 Subject: Handle configuration and paths in a standalone way Also, abstracts QSettings under LeapSettings and adds a way to define the VPN env in a platform dependant way. --- src/leap/gui/mainwindow.py | 69 +++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 37 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index eabdfe57..703d1e26 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -29,6 +29,7 @@ from functools import partial from ui_mainwindow import Ui_MainWindow from leap.common.check import leap_assert from leap.config.providerconfig import ProviderConfig +from leap.config.leapsettings import LeapSettings from leap.crypto.srpauth import SRPAuth from leap.services.eip.vpn import VPN from leap.services.eip.vpnlaunchers import (VPNLauncherException, @@ -54,17 +55,17 @@ class MainWindow(QtGui.QMainWindow): LOGIN_INDEX = 0 EIP_STATUS_INDEX = 1 - # Settings - GEOMETRY_KEY = "Geometry" - WINDOWSTATE_KEY = "WindowState" - USER_KEY = "User" - AUTOLOGIN_KEY = "AutoLogin" - PROPER_PROVIDER = "ProperProvider" - # Keyring KEYRING_KEY = "leap_client" - def __init__(self): + def __init__(self, standalone=False): + """ + Constructor for the client main window + + @param standalone: Set to true if the app should use configs + inside its pwd + @type standalone: bool + """ QtGui.QMainWindow.__init__(self) self.CONNECTING_ICON = QtGui.QPixmap(":/images/conn_connecting.png") @@ -91,6 +92,9 @@ class MainWindow(QtGui.QMainWindow): # This is loaded only once, there's a bug when doing that more # than once + ProviderConfig.standalone = standalone + EIPConfig.standalone = standalone + self._standalone = standalone self._provider_config = ProviderConfig() self._eip_config = EIPConfig() # This is created once we have a valid provider config @@ -185,6 +189,7 @@ class MainWindow(QtGui.QMainWindow): self._action_visible.triggered.connect(self._toggle_visible) self._enabled_services = [] + self._settings = LeapSettings(standalone) self._center_window() self._wizard = None @@ -201,8 +206,7 @@ class MainWindow(QtGui.QMainWindow): def _rejected_wizard(self): if self._wizard_firstrun: - settings = QtCore.QSettings() - settings.setValue(self.PROPER_PROVIDER, False) + self._settings.set_properprovider(False) self.quit() else: self._finish_init() @@ -217,7 +221,6 @@ class MainWindow(QtGui.QMainWindow): self.ui.chkAutoLogin.setEnabled(state == QtCore.Qt.Checked) def _finish_init(self): - settings = QtCore.QSettings() self.ui.cmbProviders.addItems(self._configured_providers()) self._show_systray() self.show() @@ -227,9 +230,9 @@ class MainWindow(QtGui.QMainWindow): possible_password = self._wizard.get_password() self.ui.chkRemember.setChecked(self._wizard.get_remember()) self._enabled_services = list(self._wizard.get_services()) - settings.setValue("%s/Services" % - (self.ui.cmbProviders.currentText(),), - self._enabled_services) + self._settings.set_enabled_services( + self.ui.cmbProviders.currentText(), + self._enabled_services) if possible_username is not None: self.ui.lnUser.setText(possible_username) self._focus_password() @@ -238,10 +241,10 @@ class MainWindow(QtGui.QMainWindow): self.ui.chkRemember.setChecked(True) self._login() self._wizard = None - settings.setValue(self.PROPER_PROVIDER, True) + self._settings.set_properprovider(True) else: - saved_user = settings.value(self.USER_KEY, None) - auto_login = settings.value(self.AUTOLOGIN_KEY, "false") != "false" + saved_user = self._settings.get_user() + auto_login = self._settings.get_autologin() if saved_user is not None: self.ui.lnUser.setText(saved_user) @@ -301,9 +304,9 @@ class MainWindow(QtGui.QMainWindow): """ Centers the mainwindow based on the desktop geometry """ - settings = QtCore.QSettings() - geometry = settings.value(self.GEOMETRY_KEY, None) - state = settings.value(self.WINDOWSTATE_KEY, None) + geometry = self._settings.get_geometry() + state = self._settings.get_windowstate() + if geometry is None: app = QtGui.QApplication.instance() width = app.desktop().width() @@ -361,10 +364,11 @@ class MainWindow(QtGui.QMainWindow): self._toggle_visible() e.ignore() return - settings = QtCore.QSettings() - settings.setValue(self.GEOMETRY_KEY, self.saveGeometry()) - settings.setValue(self.WINDOWSTATE_KEY, self.saveState()) - settings.setValue(self.AUTOLOGIN_KEY, self.ui.chkAutoLogin.isChecked()) + + 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) def _configured_providers(self): @@ -394,10 +398,8 @@ class MainWindow(QtGui.QMainWindow): @rtype: bool """ - settings = QtCore.QSettings() has_provider_on_disk = len(self._configured_providers()) != 0 - is_proper_provider = settings.value(self.PROPER_PROVIDER, - "false") != "false" + is_proper_provider = self._settings.get_properprovider() return not (has_provider_on_disk and is_proper_provider) def _focus_password(self): @@ -507,13 +509,8 @@ class MainWindow(QtGui.QMainWindow): password = self.ui.lnPassword.text() provider = self.ui.cmbProviders.currentText() - settings = QtCore.QSettings() - self._enabled_services = settings.value( - "%s/Services" % - (self.ui.cmbProviders.currentText(),), "") - - if isinstance(self._enabled_services, (str, unicode)): - self._enabled_services = self._enabled_services.split(",") + self._enabled_services = self._settings.get_enabled_services( + self.ui.cmbProviders.currentText()) if len(provider) == 0: self._set_status(self.tr("Please select a valid provider")) @@ -530,8 +527,6 @@ class MainWindow(QtGui.QMainWindow): self._set_status(self.tr("Logging in..."), error=False) self._login_set_enabled(False) - settings = QtCore.QSettings() - if self.ui.chkRemember.isChecked(): try: keyring.set_password(self.KEYRING_KEY, @@ -539,7 +534,7 @@ class MainWindow(QtGui.QMainWindow): password.encode("utf8")) # Only save the username if it was saved correctly in # the keyring - settings.setValue(self.USER_KEY, username) + self._settings.set_user(username) except Exception as e: logger.error("Problem saving data to keyring. %r" % (e,)) -- cgit v1.2.3 From bdc3e1b840c383f0d9236ad8d4df66633e87baf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Thu, 21 Mar 2013 10:38:48 -0300 Subject: Fixes as per review --- src/leap/gui/mainwindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 703d1e26..3f29f957 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -28,8 +28,8 @@ from functools import partial from ui_mainwindow import Ui_MainWindow from leap.common.check import leap_assert -from leap.config.providerconfig import ProviderConfig from leap.config.leapsettings import LeapSettings +from leap.config.providerconfig import ProviderConfig from leap.crypto.srpauth import SRPAuth from leap.services.eip.vpn import VPN from leap.services.eip.vpnlaunchers import (VPNLauncherException, -- cgit v1.2.3 From 30912fed29fd652f69de5a518cf70e6fe04b04f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Thu, 21 Mar 2013 13:40:18 -0300 Subject: Make Windows use a regular socket --- src/leap/gui/mainwindow.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 3f29f957..974476a3 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -20,6 +20,7 @@ Main window for the leap client """ import os import logging +import platform import tempfile import keyring @@ -605,9 +606,14 @@ class MainWindow(QtGui.QMainWindow): """ # TODO: make this properly multiplatform - host = os.path.join(tempfile.mkdtemp(prefix="leap-tmp"), - 'openvpn.socket') - port = "unix" + + if platform.system() == "Windows": + host = "localhost" + port = "9876" + else: + host = os.path.join(tempfile.mkdtemp(prefix="leap-tmp"), + 'openvpn.socket') + port = "unix" return host, port -- cgit v1.2.3 From e267ebd09c57c869dc0ab8d57baaed6d63fb2f13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Fri, 22 Mar 2013 16:12:26 -0300 Subject: Improve systray behavior with context menu and activation --- src/leap/gui/mainwindow.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 3f29f957..e039fdf1 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -287,18 +287,19 @@ class MainWindow(QtGui.QMainWindow): self._vpn_systray.setIcon(QtGui.QIcon(self.ERROR_ICON)) self._vpn_systray.setVisible(False) - def _toggle_visible(self): + def _toggle_visible(self, reason=None): """ SLOT TRIGGER: self._systray.activated Toggles the window visibility """ - self.setVisible(not self.isVisible()) - action_visible_text = self.tr("Hide") - if not self.isVisible(): - action_visible_text = self.tr("Show") - self._action_visible.setText(action_visible_text) + if reason != QtGui.QSystemTrayIcon.Context: + self.setVisible(not self.isVisible()) + action_visible_text = self.tr("Hide") + if not self.isVisible(): + action_visible_text = self.tr("Show") + self._action_visible.setText(action_visible_text) def _center_window(self): """ -- cgit v1.2.3 From 2c30ffe4ab8a12712735b7f8fef27cd7700eaaae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Mon, 25 Mar 2013 12:00:49 -0300 Subject: Add windows platform initializer --- src/leap/gui/mainwindow.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 3f29f957..ca6f1a8b 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -31,6 +31,7 @@ from leap.common.check import leap_assert from leap.config.leapsettings import LeapSettings from leap.config.providerconfig import ProviderConfig from leap.crypto.srpauth import SRPAuth +from leap.platform_init.initializers import init_platform from leap.services.eip.vpn import VPN from leap.services.eip.vpnlaunchers import (VPNLauncherException, OpenVPNNotFoundException, @@ -192,6 +193,9 @@ class MainWindow(QtGui.QMainWindow): self._settings = LeapSettings(standalone) self._center_window() + + init_platform() + self._wizard = None self._wizard_firstrun = False if self._first_run(): -- cgit v1.2.3 From f6fa3ad1f1c7c4ed9d069da031bfb6dfc6511590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Mon, 25 Mar 2013 12:55:06 -0300 Subject: Reorder imports based on rope's recommendations --- src/leap/gui/mainwindow.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 974476a3..aa29e843 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -18,31 +18,32 @@ """ Main window for the leap client """ -import os import logging +import os import platform import tempfile -import keyring - -from PySide import QtCore, QtGui from functools import partial -from ui_mainwindow import Ui_MainWindow +import keyring +from PySide import QtCore, QtGui from leap.common.check import leap_assert from leap.config.leapsettings import LeapSettings from leap.config.providerconfig import ProviderConfig from leap.crypto.srpauth import SRPAuth +from leap.gui.wizard import Wizard +from leap.services.eip.eipbootstrapper import EIPBootstrapper +from leap.services.eip.eipconfig import EIPConfig +from leap.services.eip.providerbootstrapper import ProviderBootstrapper from leap.services.eip.vpn import VPN from leap.services.eip.vpnlaunchers import (VPNLauncherException, OpenVPNNotFoundException, EIPNoPkexecAvailable, EIPNoPolkitAuthAgentAvailable) -from leap.services.eip.providerbootstrapper import ProviderBootstrapper -from leap.services.eip.eipbootstrapper import EIPBootstrapper -from leap.services.eip.eipconfig import EIPConfig -from leap.gui.wizard import Wizard -from leap.util.checkerthread import CheckerThread from leap.util import __version__ as VERSION +from leap.util.checkerthread import CheckerThread + +from ui_mainwindow import Ui_MainWindow + logger = logging.getLogger(__name__) -- cgit v1.2.3 From 466ccbafb38cf8fd767ed2b88d93c547a4eca9a2 Mon Sep 17 00:00:00 2001 From: kali Date: Wed, 3 Apr 2013 03:22:08 +0900 Subject: Do not populate user/pass when deselecting "remember" Closes: #2059 --- src/leap/gui/mainwindow.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 863640ef..e5efdbed 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -225,6 +225,7 @@ class MainWindow(QtGui.QMainWindow): def _remember_state_changed(self, state): self.ui.chkAutoLogin.setEnabled(state == QtCore.Qt.Checked) + self._settings.set_remember(bool(state)) def _finish_init(self): self.ui.cmbProviders.addItems(self._configured_providers()) @@ -249,6 +250,10 @@ class MainWindow(QtGui.QMainWindow): self._wizard = None self._settings.set_properprovider(True) else: + if not self._settings.get_remember(): + # nothing to do here + return + saved_user = self._settings.get_user() auto_login = self._settings.get_autologin() -- cgit v1.2.3 From 005b6ca29c75e0e0f583327b8afea601a90ed6e7 Mon Sep 17 00:00:00 2001 From: kali Date: Wed, 3 Apr 2013 04:37:11 +0900 Subject: Makes traffic indicators display fixed precision. Closes:#2114 --- src/leap/gui/mainwindow.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 863640ef..3fa3aad3 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -181,11 +181,11 @@ class MainWindow(QtGui.QMainWindow): self._stop_eip) self._action_eip_write = QtGui.QAction( QtGui.QIcon(":/images/Arrow-Up-32.png"), - "0.0 Kb", self) + "%12.2f Kb" % (0.0,), self) self._action_eip_write.setEnabled(False) self._action_eip_read = QtGui.QAction( QtGui.QIcon(":/images/Arrow-Down-32.png"), - "0.0 Kb", self) + "%12.2f Kb" % (0.0,), self) self._action_eip_read.setEnabled(False) self._action_visible = QtGui.QAction(self.tr("Hide"), self) @@ -758,12 +758,12 @@ class MainWindow(QtGui.QMainWindow): """ upload = float(data[self._vpn.TUNTAP_WRITE_KEY]) upload = upload / 1000.0 - upload_str = "%s Kb" % (upload,) + upload_str = "%12.2f Kb" % (upload,) self.ui.lblUpload.setText(upload_str) self._action_eip_write.setText(upload_str) download = float(data[self._vpn.TUNTAP_READ_KEY]) download = download / 1000.0 - download_str = "%s Kb" % (download,) + download_str = "%12.2f Kb" % (download,) self.ui.lblDownload.setText(download_str) self._action_eip_read.setText(download_str) -- cgit v1.2.3 From 22342664951ac32756ceb7ade59ada90f92c8793 Mon Sep 17 00:00:00 2001 From: kali Date: Thu, 4 Apr 2013 01:47:12 +0900 Subject: Several fixes in wizard Closes:#2061 o Rewording of setup steps in wizard, to make them more meaningful to the non-technical user. Closes: #2061 o Fix typo in wizard o Fix multiple drawing of services if going back o Make registration errors show in red o Add a warning if EIP service needs admin password. Addresses part of #2062 --- src/leap/gui/mainwindow.py | 2 +- src/leap/gui/ui/wizard.ui | 265 +++++++++++++++++++++++++++++++++++---------- src/leap/gui/wizard.py | 49 +++++++-- 3 files changed, 248 insertions(+), 68 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 863640ef..7f529e2f 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -202,7 +202,7 @@ class MainWindow(QtGui.QMainWindow): self._wizard_firstrun = False if self._first_run(): self._wizard_firstrun = True - self._wizard = Wizard(self._checker_thread) + self._wizard = Wizard(self._checker_thread, standalone=standalone) # Give this window time to finish init and then show the wizard QtCore.QTimer.singleShot(1, self._launch_wizard) self._wizard.accepted.connect(self._finish_init) diff --git a/src/leap/gui/ui/wizard.ui b/src/leap/gui/ui/wizard.ui index b8fed183..141418db 100644 --- a/src/leap/gui/ui/wizard.ui +++ b/src/leap/gui/ui/wizard.ui @@ -100,7 +100,7 @@ Provider selection - Please enter the domain of the provider you want to user for your connection + Please enter the domain of the provider you want to use for your connection 1 @@ -158,20 +158,20 @@ - Checking provider + Checking for a valid provider - Download provider information + Getting provider information - HTTPS Connection + Can we stablish a secure connection? @@ -244,7 +244,7 @@ - Name resolution + Can we reach this provider? @@ -278,39 +278,117 @@ Provider Information - Services offered by this provider + Description of services offered by this provider 2 - - + + - Qt::Horizontal + Qt::Vertical - 40 - 0 + 20 + 40 - + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + <b>Enrollment policy:</b> - - + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + - URL + policy + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + @@ -318,20 +396,27 @@ - - + + - Qt::Horizontal + Qt::Vertical - 40 - 0 + 20 + 40 - + + + + <b>Services offered:</b> + + + + Qt::Vertical @@ -344,36 +429,81 @@ - - + + + + Qt::Vertical + + + + 20 + 40 + + + + + + - policy + URL - - - - Desc + + + + Qt::Horizontal - - true + + + 40 + 20 + + + + + + + + services - - + + - Qt::Vertical + Qt::Horizontal - 20 - 40 + 40 + 20 + + + + + 0 + 0 + + + + + 200 + 0 + + + + Desc + + + true + + + @@ -386,11 +516,47 @@ 3 - - + + + + + Qt::Vertical + + + + 20 + 60 + + + + + + + + We are downloading some bits that we need to stablish a secure connection with the provider for the first time. + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + - Checking provider + Setting up provider @@ -440,21 +606,21 @@ - Download CA Certificate + Getting info from the Certificate Authority - Check CA Certificate Fingerprint + Do we trust this Certificate Authority? - Check API Certificate + Establishing a trust relationship with this provider @@ -496,20 +662,7 @@ - - - - Qt::Vertical - - - - 20 - 60 - - - - - + Qt::Vertical @@ -662,7 +815,7 @@ Congratulations! - You have successfully configured the LEAP client. + You have successfully configured the LEAP Client. 6 diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index dee3b230..5742dfef 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -27,6 +27,7 @@ from functools import partial from ui_wizard import Ui_Wizard from leap.config.providerconfig import ProviderConfig from leap.crypto.srpregister import SRPRegister +from leap.util.privilege_policies import is_missing_policy_permissions from leap.services.eip.providerbootstrapper import ProviderBootstrapper logger = logging.getLogger(__name__) @@ -50,9 +51,11 @@ class Wizard(QtGui.QWizard): BARE_USERNAME_REGEX = r"^[A-Za-z\d_]+$" - def __init__(self, checker): + def __init__(self, checker, standalone=False): QtGui.QWizard.__init__(self) + self.standalone = standalone + self.ui = Ui_Wizard() self.ui.setupUi(self) @@ -64,14 +67,23 @@ class Wizard(QtGui.QWizard): self.OK_ICON = QtGui.QPixmap(":/images/Dialog-accept.png") # Correspondence for services and their name to display + # XXX need to add a note about "requires admin pass" if + # no polkit found. + EIP_LABEL = self.tr("Encrypted Internet") + + if self._is_need_eip_password_warning(): + EIP_LABEL += " " + self.tr( + "(will need admin pass to start)") + self.SERVICE_DISPLAY = [ - self.tr("Encrypted Internet") + EIP_LABEL ] self.SERVICE_CONFIG = [ "openvpn" ] self._selected_services = set() + self._shown_services = set() self._show_register = False @@ -235,7 +247,7 @@ class Wizard(QtGui.QWizard): error_msg = req.json().get("errors").get("login")[0] if not error_msg.istitle(): error_msg = "%s %s" % (old_username, error_msg) - self._set_register_status(error_msg) + self._set_register_status(error_msg, error=True) except: logger.error("Unknown error: %r" % (req.content,)) self.ui.btnRegister.setEnabled(True) @@ -451,13 +463,15 @@ class Wizard(QtGui.QWizard): for service in self._provider_config.get_services(): try: - checkbox = QtGui.QCheckBox(self) - service_index = self.SERVICE_CONFIG.index(service) - checkbox.setText(self.SERVICE_DISPLAY[service_index]) - self.ui.serviceListLayout.addWidget(checkbox) - checkbox.stateChanged.connect( - partial(self._service_selection_changed, service)) - checkbox.setChecked(True) + if service not in self._shown_services: + checkbox = QtGui.QCheckBox(self) + service_index = self.SERVICE_CONFIG.index(service) + checkbox.setText(self.SERVICE_DISPLAY[service_index]) + self.ui.serviceListLayout.addWidget(checkbox) + checkbox.stateChanged.connect( + partial(self._service_selection_changed, service)) + checkbox.setChecked(True) + self._shown_services.add(service) except ValueError: logger.error( self.tr("Something went wrong while trying to " @@ -486,7 +500,8 @@ class Wizard(QtGui.QWizard): self._provider_config) if pageId == self.PRESENT_PROVIDER_PAGE: - self.page(pageId).setSubTitle(self.tr("Services offered by %s") % + self.page(pageId).setSubTitle(self.tr("Description of services " + "offered by %s") % (self._provider_config .get_name(),)) @@ -499,6 +514,9 @@ class Wizard(QtGui.QWizard): self.ui.lblProviderDesc.setText( "%s" % (self._provider_config.get_description(lang=lang),)) + + self.ui.lblServicesOffered.setText(self._provider_config + .get_services_string()) self.ui.lblProviderPolicy.setText(self._provider_config .get_enrollment_policy()) @@ -511,6 +529,15 @@ class Wizard(QtGui.QWizard): if pageId == self.SERVICES_PAGE: self._populate_services() + def _is_need_eip_password_warning(self): + """ + Returns True if we need to add a warning about eip needing + administrative permissions to start. That can be either + because we are running in standalone mode, or because we could + not find the needed privilege escalation mechanisms being operative. + """ + return self.standalone or is_missing_policy_permissions() + def nextId(self): """ Sets the next page id for the wizard based on wether the user -- cgit v1.2.3 From e75fe6ffd58b067b3fc32196ad245d26a1287e99 Mon Sep 17 00:00:00 2001 From: kali Date: Tue, 9 Apr 2013 00:43:13 +0900 Subject: fixes as per review --- src/leap/gui/ui/wizard.ui | 184 +++++++++++++++++++++++----------------------- src/leap/gui/wizard.py | 13 +++- 2 files changed, 102 insertions(+), 95 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/ui/wizard.ui b/src/leap/gui/ui/wizard.ui index 141418db..ed7fe37c 100644 --- a/src/leap/gui/ui/wizard.ui +++ b/src/leap/gui/ui/wizard.ui @@ -284,8 +284,8 @@ 2 - - + + Qt::Vertical @@ -297,8 +297,8 @@ - - + + Qt::Vertical @@ -310,8 +310,8 @@ - - + + Qt::Vertical @@ -323,15 +323,8 @@ - - - - <b>Enrollment policy:</b> - - - - - + + Qt::Vertical @@ -343,8 +336,8 @@ - - + + Qt::Vertical @@ -356,15 +349,8 @@ - - - - policy - - - - - + + Qt::Vertical @@ -376,7 +362,21 @@ - + + + + Name + + + + + + + URL + + + + Qt::Horizontal @@ -390,66 +390,55 @@ - - - Name - - - - - + - Qt::Vertical + Qt::Horizontal - 20 - 40 + 40 + 20 - - - - <b>Services offered:</b> - - - - - - - Qt::Vertical - - + + + - 20 - 40 + 0 + 0 - - - - - - Qt::Vertical - - + - 20 - 40 + 200 + 0 - + + Desc + + + true + + - - + + - URL + <b>Services offered:</b> + + + + + + + services - + Qt::Horizontal @@ -462,47 +451,58 @@ - - + + - services + <b>Enrollment policy:</b> - - + + + + policy + + + + + - Qt::Horizontal + Qt::Vertical - 40 - 20 + 20 + 40 - - - - - 0 - 0 - + + + + Qt::Vertical - + - 200 - 0 + 20 + 40 - - Desc + + + + + + Qt::Vertical - - true + + + 20 + 40 + - + @@ -533,7 +533,7 @@ - We are downloading some bits that we need to stablish a secure connection with the provider for the first time. + We are downloading some bits that we need to establish a secure connection with the provider for the first time. true diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index 5742dfef..cc00039b 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -52,6 +52,15 @@ class Wizard(QtGui.QWizard): BARE_USERNAME_REGEX = r"^[A-Za-z\d_]+$" def __init__(self, checker, standalone=False): + """ + Constructor for the main Wizard. + + @param checker: Checker thread that the wizard should use. + @type checker: CheckerThread + @param standalone: If True, the application is running as standalone + and the wizard should display some messages according to this. + @type standalone: bool + """ QtGui.QWizard.__init__(self) self.standalone = standalone @@ -67,13 +76,11 @@ class Wizard(QtGui.QWizard): self.OK_ICON = QtGui.QPixmap(":/images/Dialog-accept.png") # Correspondence for services and their name to display - # XXX need to add a note about "requires admin pass" if - # no polkit found. EIP_LABEL = self.tr("Encrypted Internet") if self._is_need_eip_password_warning(): EIP_LABEL += " " + self.tr( - "(will need admin pass to start)") + "(
will need admin password to start
)") self.SERVICE_DISPLAY = [ EIP_LABEL -- cgit v1.2.3 From 785bdfaceb9f3d392f162dd818fea9098d9375a4 Mon Sep 17 00:00:00 2001 From: kali Date: Tue, 9 Apr 2013 20:17:03 +0900 Subject: fix markup typo s/br/b --- src/leap/gui/wizard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index cc00039b..7759b98d 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -80,7 +80,7 @@ class Wizard(QtGui.QWizard): if self._is_need_eip_password_warning(): EIP_LABEL += " " + self.tr( - "(
will need admin password to start
)") + "(will need admin password to start)") self.SERVICE_DISPLAY = [ EIP_LABEL -- cgit v1.2.3 From bcfdc13f5450e3df695c61c44bf637b18386f31a Mon Sep 17 00:00:00 2001 From: kali Date: Tue, 9 Apr 2013 20:48:29 +0900 Subject: make state switch more readable --- src/leap/gui/mainwindow.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index e5efdbed..3c256bea 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -224,8 +224,9 @@ class MainWindow(QtGui.QMainWindow): self._wizard = None def _remember_state_changed(self, state): - self.ui.chkAutoLogin.setEnabled(state == QtCore.Qt.Checked) - self._settings.set_remember(bool(state)) + enable = True if state == QtCore.Qt.Checked else False + self.ui.chkAutoLogin.setEnabled(enable) + self._settings.set_remember(enable) def _finish_init(self): self.ui.cmbProviders.addItems(self._configured_providers()) -- cgit v1.2.3 From e1d8032f4a0c4e8e8d46c017af3ffb91ea98946c Mon Sep 17 00:00:00 2001 From: Tomas Touceda Date: Tue, 9 Apr 2013 10:27:42 -0300 Subject: Display a simple message when updates are available --- src/leap/gui/mainwindow.py | 65 +++++++++++++++++++++++ src/leap/gui/ui/mainwindow.ui | 119 +++++++++++++++++++++++++++++++++--------- 2 files changed, 159 insertions(+), 25 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 6a73fb76..71dff1c5 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -42,6 +42,11 @@ from leap.services.eip.vpnlaunchers import (VPNLauncherException, EIPNoPolkitAuthAgentAvailable) from leap.util import __version__ as VERSION from leap.util.checkerthread import CheckerThread +from leap.common.events import server +from leap.common.events import ( + register, + events_pb2 as proto, +) from ui_mainwindow import Ui_MainWindow @@ -61,6 +66,8 @@ class MainWindow(QtGui.QMainWindow): # Keyring KEYRING_KEY = "leap_client" + new_updates = QtCore.Signal(object) + def __init__(self, standalone=False): """ Constructor for the client main window @@ -71,6 +78,12 @@ class MainWindow(QtGui.QMainWindow): """ QtGui.QMainWindow.__init__(self) + server.ensure_server(port=8090) + + register(signal=proto.UPDATER_NEW_UPDATES, + callback=self._new_updates_available) + self._updates_content = "" + self.CONNECTING_ICON = QtGui.QPixmap(":/images/conn_connecting.png") self.CONNECTED_ICON = QtGui.QPixmap(":/images/conn_connected.png") self.ERROR_ICON = QtGui.QPixmap(":/images/conn_error.png") @@ -196,6 +209,11 @@ class MainWindow(QtGui.QMainWindow): self._center_window() + self.ui.lblNewUpdates.setVisible(False) + self.ui.btnMore.setVisible(False) + self.ui.btnMore.clicked.connect(self._updates_details) + self.new_updates.connect(self._react_to_new_updates) + init_platform() self._wizard = None @@ -228,6 +246,53 @@ class MainWindow(QtGui.QMainWindow): self.ui.chkAutoLogin.setEnabled(enable) self._settings.set_remember(enable) + def _new_updates_available(self, req): + """ + Callback for the new updates event + + @param req: Request type + @type req: leap.common.events.events_pb2.SignalRequest + """ + self.new_updates.emit(req) + + def _react_to_new_updates(self, req): + """ + SLOT + TRIGGER: self._new_updates_available + + Displays the new updates label and sets the updates_content + """ + self.moveToThread(QtCore.QCoreApplication.instance().thread()) + self.ui.lblNewUpdates.setVisible(True) + self.ui.btnMore.setVisible(True) + self._updates_content = req.content + + def _updates_details(self): + """ + Parses and displays the updates details + """ + msg = "" + if len(self._updates_content) == 0: + # We assume that if there is nothing in the contents, then + # the LEAPClient bundle is what needs updating. + msg = self.tr("The LEAPClient app is ready to update, please" + " restart the application.") + else: + files = self._updates_content.split(", ") + files_str = "" + for f in files: + final_name = f.replace("/data/", "") + final_name = final_name.replace(".thp", "") + files_str += final_name + files_str += "\n" + msg = self.tr("The LEAPClient app is ready to update, please" + " restart the application so the following " + "components get updated:\n%s") % (files_str,) + + QtGui.QMessageBox.information(self, + self.tr("Updates available"), + msg) + def _finish_init(self): self.ui.cmbProviders.addItems(self._configured_providers()) self._show_systray() diff --git a/src/leap/gui/ui/mainwindow.ui b/src/leap/gui/ui/mainwindow.ui index ca3db95c..b42ff180 100644 --- a/src/leap/gui/ui/mainwindow.ui +++ b/src/leap/gui/ui/mainwindow.ui @@ -6,7 +6,7 @@ 0 0 - 405 + 415 579
@@ -28,7 +28,20 @@ - + + + + Qt::Vertical + + + + 20 + 40 + + + + + Qt::Vertical @@ -41,8 +54,8 @@ - - + + Qt::Horizontal @@ -54,20 +67,20 @@ - - + + - Qt::Vertical + Qt::Horizontal - 20 - 40 + 40 + 20 - + 1 @@ -290,20 +303,7 @@
- - - - Qt::Horizontal - - - - 40 - 20 - - - - - + false @@ -319,6 +319,75 @@ + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 0 + + + + + + + + There are new updates available, please restart. + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + More... + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + +
@@ -326,7 +395,7 @@ 0 0 - 405 + 415 25 -- cgit v1.2.3 From 0cbff90910fa35a9489a5542d093211c61d2267f Mon Sep 17 00:00:00 2001 From: Tomas Touceda Date: Wed, 10 Apr 2013 09:27:14 -0300 Subject: Move ensure server to the very beginning Also, fix documentation for a slot and mini-refactor a method for improved translations. --- src/leap/gui/mainwindow.py | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 71dff1c5..0d59e24b 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -42,7 +42,6 @@ from leap.services.eip.vpnlaunchers import (VPNLauncherException, EIPNoPolkitAuthAgentAvailable) from leap.util import __version__ as VERSION from leap.util.checkerthread import CheckerThread -from leap.common.events import server from leap.common.events import ( register, events_pb2 as proto, @@ -78,8 +77,6 @@ class MainWindow(QtGui.QMainWindow): """ QtGui.QMainWindow.__init__(self) - server.ensure_server(port=8090) - register(signal=proto.UPDATER_NEW_UPDATES, callback=self._new_updates_available) self._updates_content = "" @@ -269,15 +266,17 @@ class MainWindow(QtGui.QMainWindow): def _updates_details(self): """ + SLOT + TRIGGER: self.ui.btnMore.clicked + Parses and displays the updates details """ - msg = "" - if len(self._updates_content) == 0: - # We assume that if there is nothing in the contents, then - # the LEAPClient bundle is what needs updating. - msg = self.tr("The LEAPClient app is ready to update, please" - " restart the application.") - else: + msg = self.tr("The LEAPClient app is ready to update, please" + " restart the application.") + + # We assume that if there is nothing in the contents, then + # the LEAPClient bundle is what needs updating. + if len(self._updates_content) > 0: files = self._updates_content.split(", ") files_str = "" for f in files: @@ -285,9 +284,8 @@ class MainWindow(QtGui.QMainWindow): final_name = final_name.replace(".thp", "") files_str += final_name files_str += "\n" - msg = self.tr("The LEAPClient app is ready to update, please" - " restart the application so the following " - "components get updated:\n%s") % (files_str,) + msg += self.tr(" The following components will be updated:\n%s") \ + % (files_str,) QtGui.QMessageBox.information(self, self.tr("Updates available"), -- cgit v1.2.3 From 816104f2983e0f6dcded621c02c4c520e2e36da6 Mon Sep 17 00:00:00 2001 From: kali Date: Thu, 4 Apr 2013 05:16:36 +0900 Subject: detection of multiple instances using flock. sending RAISE_WINDOW leap event if we are not the main instance. --- src/leap/gui/mainwindow.py | 54 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 5 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 0d59e24b..e362663a 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -26,7 +26,10 @@ from functools import partial import keyring from PySide import QtCore, QtGui + from leap.common.check import leap_assert +from leap.common.events import register +from leap.common.events import events_pb2 as proto from leap.config.leapsettings import LeapSettings from leap.config.providerconfig import ProviderConfig from leap.crypto.srpauth import SRPAuth @@ -34,6 +37,7 @@ from leap.gui.wizard import Wizard from leap.services.eip.eipbootstrapper import EIPBootstrapper from leap.services.eip.eipconfig import EIPConfig from leap.services.eip.providerbootstrapper import ProviderBootstrapper +from leap.platform_init import IS_WIN from leap.platform_init.initializers import init_platform from leap.services.eip.vpn import VPN from leap.services.eip.vpnlaunchers import (VPNLauncherException, @@ -42,14 +46,9 @@ from leap.services.eip.vpnlaunchers import (VPNLauncherException, EIPNoPolkitAuthAgentAvailable) from leap.util import __version__ as VERSION from leap.util.checkerthread import CheckerThread -from leap.common.events import ( - register, - events_pb2 as proto, -) from ui_mainwindow import Ui_MainWindow - logger = logging.getLogger(__name__) @@ -65,7 +64,9 @@ class MainWindow(QtGui.QMainWindow): # Keyring KEYRING_KEY = "leap_client" + # Signals new_updates = QtCore.Signal(object) + raise_window = QtCore.Signal([]) def __init__(self, standalone=False): """ @@ -77,8 +78,12 @@ class MainWindow(QtGui.QMainWindow): """ QtGui.QMainWindow.__init__(self) + # register leap events register(signal=proto.UPDATER_NEW_UPDATES, callback=self._new_updates_available) + register(signal=proto.RAISE_WINDOW, + callback=self._on_raise_window_event) + self._updates_content = "" self.CONNECTING_ICON = QtGui.QPixmap(":/images/conn_connecting.png") @@ -110,6 +115,7 @@ class MainWindow(QtGui.QMainWindow): self._standalone = standalone self._provider_config = ProviderConfig() self._eip_config = EIPConfig() + # This is created once we have a valid provider config self._srp_auth = None @@ -166,6 +172,10 @@ class MainWindow(QtGui.QMainWindow): QtCore.QCoreApplication.instance(), QtCore.SIGNAL("aboutToQuit()"), self._checker_thread.wait) + QtCore.QCoreApplication.instance().connect( + QtCore.QCoreApplication.instance(), + QtCore.SIGNAL("aboutToQuit()"), + self._cleanup_pidfiles) self.ui.chkRemember.stateChanged.connect( self._remember_state_changed) @@ -176,6 +186,7 @@ class MainWindow(QtGui.QMainWindow): self.ui.action_about_leap.triggered.connect(self._about) self.ui.action_quit.triggered.connect(self.quit) self.ui.action_wizard.triggered.connect(self._launch_wizard) + self.raise_window.connect(self._do_raise_mainwindow) # Used to differentiate between real quits and close to tray self._really_quit = False @@ -920,6 +931,39 @@ class MainWindow(QtGui.QMainWindow): logger.debug("Finished VPN with exitCode %s" % (exitCode,)) self._stop_eip() + def _on_raise_window_event(self, req): + """ + Callback for the raise window event + """ + self.raise_window.emit() + + def _do_raise_mainwindow(self): + """ + SLOT + TRIGGERS: + self._on_raise_window_event + + Triggered when we receive a RAISE_WINDOW event. + """ + TOPFLAG = QtCore.Qt.WindowStaysOnTopHint + self.setWindowFlags(self.windowFlags() | TOPFLAG) + self.show() + self.setWindowFlags(self.windowFlags() & ~TOPFLAG) + self.show() + + def _cleanup_pidfiles(self): + """ + SLOT + TRIGGERS: + self.aboutToQuit + + Triggered on about to quit signal, removes lockfiles on a clean + shutdown + """ + if IS_WIN: + lockfile = WindowsLock() + lockfile.release_lock() + if __name__ == "__main__": import signal -- cgit v1.2.3 From 92a2fb893b02d1880aac3509b0550fb04d5bf876 Mon Sep 17 00:00:00 2001 From: kali Date: Thu, 11 Apr 2013 20:36:56 +0900 Subject: Use dark icons (light theme) for OSX Closes:#2130 --- src/leap/gui/mainwindow.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 0d59e24b..d9e8e3f6 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -34,6 +34,7 @@ from leap.gui.wizard import Wizard from leap.services.eip.eipbootstrapper import EIPBootstrapper from leap.services.eip.eipconfig import EIPConfig from leap.services.eip.providerbootstrapper import ProviderBootstrapper +from leap.platform_init import IS_MAC from leap.platform_init.initializers import init_platform from leap.services.eip.vpn import VPN from leap.services.eip.vpnlaunchers import (VPNLauncherException, @@ -81,9 +82,20 @@ class MainWindow(QtGui.QMainWindow): callback=self._new_updates_available) self._updates_content = "" - self.CONNECTING_ICON = QtGui.QPixmap(":/images/conn_connecting.png") - self.CONNECTED_ICON = QtGui.QPixmap(":/images/conn_connected.png") - self.ERROR_ICON = QtGui.QPixmap(":/images/conn_error.png") + if IS_MAC: + EIP_ICONS = ( + ":/images/conn_connecting-light.png" + ":/images/conn_connected-light.png", + ":/images/conn_error-light.png") + else: + EIP_ICONS = ( + ":/images/conn_connecting.png" + ":/images/conn_connected.png", + ":/images/conn_error.png") + + self.CONNECTING_ICON = QtGui.QPixmap(EIP_ICONS[0]) + self.CONNECTED_ICON = QtGui.QPixmap(EIP_ICONS[1]) + self.ERROR_ICON = QtGui.QPixmap(EIP_ICONS[2]) self.LOGGED_OUT_ICON = QtGui.QPixmap(":/images/leap-gray-big.png") self.LOGGED_IN_ICON = QtGui.QPixmap(":/images/leap-color-big.png") -- cgit v1.2.3 From b585cefa15a1d7a03dd8a6c5651220ac3fd94fd1 Mon Sep 17 00:00:00 2001 From: kali Date: Thu, 11 Apr 2013 22:04:00 +0900 Subject: missing comma in tuple --- src/leap/gui/mainwindow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index d9e8e3f6..f84cb00c 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -84,12 +84,12 @@ class MainWindow(QtGui.QMainWindow): if IS_MAC: EIP_ICONS = ( - ":/images/conn_connecting-light.png" + ":/images/conn_connecting-light.png", ":/images/conn_connected-light.png", ":/images/conn_error-light.png") else: EIP_ICONS = ( - ":/images/conn_connecting.png" + ":/images/conn_connecting.png", ":/images/conn_connected.png", ":/images/conn_error.png") -- cgit v1.2.3 From 4e4268af42e6d0bbf38e6027ca36d1b9f1bdda68 Mon Sep 17 00:00:00 2001 From: Tomas Touceda Date: Fri, 12 Apr 2013 14:05:29 -0300 Subject: Fix UI which was missbehaving - Remove the wrapping on checkboxes, since they don't understand HTML - Change the display of provider since providers with big names like dev.bitmask.net make the UI look bad. --- src/leap/gui/ui/wizard.ui | 173 +++++++++++----------------------------------- src/leap/gui/wizard.py | 2 +- 2 files changed, 42 insertions(+), 133 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/ui/wizard.ui b/src/leap/gui/ui/wizard.ui index ed7fe37c..87d04194 100644 --- a/src/leap/gui/ui/wizard.ui +++ b/src/leap/gui/ui/wizard.ui @@ -284,125 +284,27 @@ 2
- - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - + Name - - - - URL - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - + + - Qt::Horizontal + Qt::Vertical - 40 - 20 + 20 + 40 - + @@ -424,21 +326,24 @@ - + <b>Services offered:</b> + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + - + services - + Qt::Horizontal @@ -451,22 +356,25 @@ - + <b>Enrollment policy:</b> + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + - + policy - - + + Qt::Vertical @@ -478,31 +386,32 @@ - - - - Qt::Vertical + + + + <b>URL:</b> - - - 20 - 40 - + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + - - - - Qt::Vertical + + + + URL - - - 20 - 40 - + + + + + + <b>Description:</b> - + + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing + + diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index 7759b98d..4da4c815 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -80,7 +80,7 @@ class Wizard(QtGui.QWizard): if self._is_need_eip_password_warning(): EIP_LABEL += " " + self.tr( - "(will need admin password to start)") + "(will need admin password to start)") self.SERVICE_DISPLAY = [ EIP_LABEL -- cgit v1.2.3 From 1fbf6db1276c5bca41c4cfbcc90818d9605c1938 Mon Sep 17 00:00:00 2001 From: Tomas Touceda Date: Fri, 12 Apr 2013 14:07:15 -0300 Subject: Add --danger option to not validate the first hop of certificates This is intended to be used while testing, not in production --- src/leap/gui/mainwindow.py | 17 +++++++++++++---- src/leap/gui/wizard.py | 7 +++++-- 2 files changed, 18 insertions(+), 6 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index bf8491d0..c9743f95 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -68,13 +68,16 @@ class MainWindow(QtGui.QMainWindow): new_updates = QtCore.Signal(object) raise_window = QtCore.Signal([]) - def __init__(self, standalone=False): + def __init__(self, standalone=False, bypass_checks=False): """ Constructor for the client main window @param standalone: Set to true if the app should use configs inside its pwd @type standalone: bool + @param bypass_checks: Set to true if the app should bypass + first round of checks for CA certificates at bootstrap + @type bypass_checks: bool """ QtGui.QMainWindow.__init__(self) @@ -136,7 +139,7 @@ class MainWindow(QtGui.QMainWindow): # This thread is always running, although it's quite # lightweight when it's done setting up provider # configuration and certificate. - self._provider_bootstrapper = ProviderBootstrapper() + self._provider_bootstrapper = ProviderBootstrapper(bypass_checks) # Intermediate stages, only do something if there was an error self._provider_bootstrapper.name_resolution.connect( @@ -237,9 +240,14 @@ class MainWindow(QtGui.QMainWindow): self._wizard = None self._wizard_firstrun = False + + self._bypass_checks = bypass_checks + if self._first_run(): self._wizard_firstrun = True - self._wizard = Wizard(self._checker_thread, standalone=standalone) + self._wizard = Wizard(self._checker_thread, + standalone=standalone, + bypass_checks=bypass_checks) # Give this window time to finish init and then show the wizard QtCore.QTimer.singleShot(1, self._launch_wizard) self._wizard.accepted.connect(self._finish_init) @@ -256,7 +264,8 @@ class MainWindow(QtGui.QMainWindow): def _launch_wizard(self): if self._wizard is None: - self._wizard = Wizard(self._checker_thread) + self._wizard = Wizard(self._checker_thread, + bypass_checks=self._bypass_checks) self._wizard.exec_() self._wizard = None diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index 4da4c815..33c3ed0c 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -51,7 +51,7 @@ class Wizard(QtGui.QWizard): BARE_USERNAME_REGEX = r"^[A-Za-z\d_]+$" - def __init__(self, checker, standalone=False): + def __init__(self, checker, standalone=False, bypass_checks=False): """ Constructor for the main Wizard. @@ -60,6 +60,9 @@ class Wizard(QtGui.QWizard): @param standalone: If True, the application is running as standalone and the wizard should display some messages according to this. @type standalone: bool + @param bypass_checks: Set to true if the app should bypass + first round of checks for CA certificates at bootstrap + @type bypass_checks: bool """ QtGui.QWizard.__init__(self) @@ -98,7 +101,7 @@ class Wizard(QtGui.QWizard): self.ui.btnCheck.clicked.connect(self._check_provider) self.ui.lnProvider.returnPressed.connect(self._check_provider) - self._provider_bootstrapper = ProviderBootstrapper() + self._provider_bootstrapper = ProviderBootstrapper(bypass_checks) self._provider_bootstrapper.name_resolution.connect( self._name_resolution) self._provider_bootstrapper.https_connection.connect( -- cgit v1.2.3 From 2dae2703fb8c2ae7e721ce83020c0dd10ff9ca33 Mon Sep 17 00:00:00 2001 From: kali Date: Fri, 3 May 2013 02:59:22 +0900 Subject: updated documentation * documentation reviewed after rewrite, ready for 0.2.1 * updated docstrings format to fit sphinx autodoc --- src/leap/gui/mainwindow.py | 38 +++++++++++++++---------------- src/leap/gui/wizard.py | 56 +++++++++++++++++++++++----------------------- 2 files changed, 47 insertions(+), 47 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index c9743f95..ccf97672 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -72,12 +72,12 @@ class MainWindow(QtGui.QMainWindow): """ Constructor for the client main window - @param standalone: Set to true if the app should use configs + :param standalone: Set to true if the app should use configs inside its pwd - @type standalone: bool - @param bypass_checks: Set to true if the app should bypass + :type standalone: bool + :param bypass_checks: Set to true if the app should bypass first round of checks for CA certificates at bootstrap - @type bypass_checks: bool + :type bypass_checks: bool """ QtGui.QMainWindow.__init__(self) @@ -278,8 +278,8 @@ class MainWindow(QtGui.QMainWindow): """ Callback for the new updates event - @param req: Request type - @type req: leap.common.events.events_pb2.SignalRequest + :param req: Request type + :type req: leap.common.events.events_pb2.SignalRequest """ self.new_updates.emit(req) @@ -482,7 +482,7 @@ class MainWindow(QtGui.QMainWindow): """ Returns the available providers based on the file structure - @rtype: list + :rtype: list """ # TODO: check which providers have a valid certificate among @@ -503,7 +503,7 @@ class MainWindow(QtGui.QMainWindow): """ Returns True if there are no configured providers. False otherwise - @rtype: bool + :rtype: bool """ has_provider_on_disk = len(self._configured_providers()) != 0 is_proper_provider = self._settings.get_properprovider() @@ -519,8 +519,8 @@ class MainWindow(QtGui.QMainWindow): """ Sets the status label at the login stage to status - @param status: status message - @type status: str + :param status: status message + :type status: str """ if error: status = "%s" % (status,) @@ -530,8 +530,8 @@ class MainWindow(QtGui.QMainWindow): """ Sets the status label at the VPN stage to status - @param status: status message - @type status: str + :param status: status message + :type status: str """ self._vpn_systray.setToolTip(status) if error: @@ -542,8 +542,8 @@ class MainWindow(QtGui.QMainWindow): """ Enables or disables all the login widgets - @param enabled: wether they should be enabled or not - @type enabled: bool + :param enabled: wether they should be enabled or not + :type enabled: bool """ self.ui.lnUser.setEnabled(enabled) self.ui.lnPassword.setEnabled(enabled) @@ -575,9 +575,9 @@ class MainWindow(QtGui.QMainWindow): self._provider_config instance with it and starts the second part of the bootstrapping sequence - @param data: result from the last stage of the + :param data: result from the last stage of the run_provider_select_checks - @type data: dict + :type data: dict """ if data[self._provider_bootstrapper.PASSED_KEY]: provider = self.ui.cmbProviders.currentText() @@ -708,7 +708,7 @@ class MainWindow(QtGui.QMainWindow): """ Returns the socket and port to be used for VPN - @rtype: tuple (str, str) (host, port) + :rtype: tuple (str, str) (host, port) """ # TODO: make this properly multiplatform @@ -801,8 +801,8 @@ class MainWindow(QtGui.QMainWindow): """ Given a status step from the VPN thread, set the icon properly - @param status: status step - @type status: str + :param status: status step + :type status: str """ selected_pixmap = self.ERROR_ICON tray_message = self.tr("Encryption is OFF") diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index 33c3ed0c..ad45dd8c 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -55,14 +55,14 @@ class Wizard(QtGui.QWizard): """ Constructor for the main Wizard. - @param checker: Checker thread that the wizard should use. - @type checker: CheckerThread - @param standalone: If True, the application is running as standalone + :param checker: Checker thread that the wizard should use. + :type checker: CheckerThread + :param standalone: If True, the application is running as standalone and the wizard should display some messages according to this. - @type standalone: bool - @param bypass_checks: Set to true if the app should bypass + :type standalone: bool + :param bypass_checks: Set to true if the app should bypass first round of checks for CA certificates at bootstrap - @type bypass_checks: bool + :type bypass_checks: bool """ QtGui.QWizard.__init__(self) @@ -184,15 +184,15 @@ class Wizard(QtGui.QWizard): """ Performs basic password checks to avoid really easy passwords. - @param username: username provided at the registrarion form - @type username: str - @param password: password from the registration form - @type password: str - @param password2: second password from the registration form - @type password: str + :param username: username provided at the registrarion form + :type username: str + :param password: password from the registration form + :type password: str + :param password2: second password from the registration form + :type password: str - @return: returns True if all the checks pass, False otherwise - @rtype: bool + :return: returns True if all the checks pass, False otherwise + :rtype: bool """ message = None @@ -266,8 +266,8 @@ class Wizard(QtGui.QWizard): """ Sets the status label in the registration page to status - @param status: status message to display, can be HTML - @type status: str + :param status: status message to display, can be HTML + :type status: str """ if error: status = "%s" % (status,) @@ -321,17 +321,17 @@ class Wizard(QtGui.QWizard): """ Checks a task and completes a page if specified - @param data: data as it comes from the bootstrapper thread for + :param data: data as it comes from the bootstrapper thread for a specific check - @type data: dict - @param label: label that displays the status icon for a + :type data: dict + :param label: label that displays the status icon for a specific check that corresponds to the data - @type label: QtGui.QLabel - @param complete: if True, it completes the page specified, + :type label: QtGui.QLabel + :param complete: if True, it completes the page specified, which must be of type WizardPage - @type complete: bool - @param complete_page: page id to complete - @type complete_page: int + :type complete: bool + :param complete_page: page id to complete + :type complete_page: int """ passed = data[self._provider_bootstrapper.PASSED_KEY] error = data[self._provider_bootstrapper.ERROR_KEY] @@ -450,10 +450,10 @@ class Wizard(QtGui.QWizard): Adds the service to the state if the state is checked, removes it otherwise - @param service: service to handle - @type service: str - @param state: state of the checkbox - @type state: int + :param service: service to handle + :type service: str + :param state: state of the checkbox + :type state: int """ if state == QtCore.Qt.Checked: self._selected_services = \ -- cgit v1.2.3 From 4e201329042d43c8d281c5737d3d5f6f8e2bf67f Mon Sep 17 00:00:00 2001 From: Tomas Touceda Date: Fri, 10 May 2013 17:01:11 -0300 Subject: Add support for requests<1.0.0 --- src/leap/gui/wizard.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index ad45dd8c..713383a6 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -20,6 +20,7 @@ First run wizard """ import os import logging +import json from PySide import QtCore, QtGui from functools import partial @@ -28,6 +29,7 @@ from ui_wizard import Ui_Wizard from leap.config.providerconfig import ProviderConfig from leap.crypto.srpregister import SRPRegister from leap.util.privilege_policies import is_missing_policy_permissions +from leap.util.request_helpers import get_content from leap.services.eip.providerbootstrapper import ProviderBootstrapper logger = logging.getLogger(__name__) @@ -254,7 +256,9 @@ class Wizard(QtGui.QWizard): self._password = None error_msg = self.tr("Unknown error") try: - error_msg = req.json().get("errors").get("login")[0] + content, _ = get_content(req) + json_content = json.loads(content) + error_msg = json_content.get("errors").get("login")[0] if not error_msg.istitle(): error_msg = "%s %s" % (old_username, error_msg) self._set_register_status(error_msg, error=True) -- cgit v1.2.3 From 9a1a657593c3ee18d35936a85077ae2f7e51c392 Mon Sep 17 00:00:00 2001 From: Tomas Touceda Date: Fri, 10 May 2013 12:09:02 -0300 Subject: Fixup one instance on Windows --- src/leap/gui/mainwindow.py | 1 + 1 file changed, 1 insertion(+) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index ccf97672..358b7a41 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -38,6 +38,7 @@ from leap.services.eip.eipbootstrapper import EIPBootstrapper from leap.services.eip.eipconfig import EIPConfig from leap.services.eip.providerbootstrapper import ProviderBootstrapper from leap.platform_init import IS_MAC, IS_WIN +from leap.platform_init.locks import WindowsLock from leap.platform_init.initializers import init_platform from leap.services.eip.vpn import VPN from leap.services.eip.vpnlaunchers import (VPNLauncherException, -- cgit v1.2.3 From 6c66167ddce1682e88fae0ae106f44befb121c4d Mon Sep 17 00:00:00 2001 From: Tomas Touceda Date: Fri, 10 May 2013 16:32:48 -0300 Subject: Make the import of WindowsLock conditional --- src/leap/gui/mainwindow.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 358b7a41..b3ab56d3 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -38,7 +38,6 @@ from leap.services.eip.eipbootstrapper import EIPBootstrapper from leap.services.eip.eipconfig import EIPConfig from leap.services.eip.providerbootstrapper import ProviderBootstrapper from leap.platform_init import IS_MAC, IS_WIN -from leap.platform_init.locks import WindowsLock from leap.platform_init.initializers import init_platform from leap.services.eip.vpn import VPN from leap.services.eip.vpnlaunchers import (VPNLauncherException, @@ -48,6 +47,9 @@ from leap.services.eip.vpnlaunchers import (VPNLauncherException, from leap.util import __version__ as VERSION from leap.util.checkerthread import CheckerThread +if IS_WIN: + from leap.platform_init.locks import WindowsLock + from ui_mainwindow import Ui_MainWindow logger = logging.getLogger(__name__) -- cgit v1.2.3 From 1cb931e83522746da668f9a8bb5943aca1882086 Mon Sep 17 00:00:00 2001 From: kali Date: Thu, 16 May 2013 04:26:00 +0900 Subject: use qtreactor so twisted is driven by qt main loop aboutToQuit signal is not raised anymore with the qt4reactor. So we are calling all cleanup callbacks from the quit function. --- src/leap/gui/mainwindow.py | 76 ++++++++++++++++++++++++-------------------- src/leap/gui/twisted_main.py | 49 ++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 35 deletions(-) create mode 100644 src/leap/gui/twisted_main.py (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index b3ab56d3..fdf84766 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -71,15 +71,22 @@ class MainWindow(QtGui.QMainWindow): new_updates = QtCore.Signal(object) raise_window = QtCore.Signal([]) - def __init__(self, standalone=False, bypass_checks=False): + def __init__(self, quit_callback, + standalone=False, bypass_checks=False): """ Constructor for the client main window + :param quit_callback: Function to be called when closing + the application. + :type quit_callback: callable + :param standalone: Set to true if the app should use configs - inside its pwd + inside its pwd :type standalone: bool + :param bypass_checks: Set to true if the app should bypass - first round of checks for CA certificates at bootstrap + first round of checks for CA + certificates at bootstrap :type bypass_checks: bool """ QtGui.QMainWindow.__init__(self) @@ -89,6 +96,7 @@ class MainWindow(QtGui.QMainWindow): callback=self._new_updates_available) register(signal=proto.RAISE_WINDOW, callback=self._on_raise_window_event) + self._quit_callback = quit_callback self._updates_content = "" @@ -173,27 +181,6 @@ class MainWindow(QtGui.QMainWindow): self._vpn.process_finished.connect( self._eip_finished) - QtCore.QCoreApplication.instance().connect( - QtCore.QCoreApplication.instance(), - QtCore.SIGNAL("aboutToQuit()"), - self._vpn.set_should_quit) - QtCore.QCoreApplication.instance().connect( - QtCore.QCoreApplication.instance(), - QtCore.SIGNAL("aboutToQuit()"), - self._vpn.wait) - QtCore.QCoreApplication.instance().connect( - QtCore.QCoreApplication.instance(), - QtCore.SIGNAL("aboutToQuit()"), - self._checker_thread.set_should_quit) - QtCore.QCoreApplication.instance().connect( - QtCore.QCoreApplication.instance(), - QtCore.SIGNAL("aboutToQuit()"), - self._checker_thread.wait) - QtCore.QCoreApplication.instance().connect( - QtCore.QCoreApplication.instance(), - QtCore.SIGNAL("aboutToQuit()"), - self._cleanup_pidfiles) - self.ui.chkRemember.stateChanged.connect( self._remember_state_changed) self.ui.chkRemember.setEnabled(keyring.get_keyring() is not None) @@ -447,12 +434,6 @@ class MainWindow(QtGui.QMainWindow): "More about LEAP" "") % (VERSION,)) - def quit(self): - self._really_quit = True - if self._wizard: - self._wizard.close() - self.close() - def changeEvent(self, e): """ Reimplements the changeEvent method to minimize to tray @@ -976,17 +957,42 @@ class MainWindow(QtGui.QMainWindow): def _cleanup_pidfiles(self): """ - SLOT - TRIGGERS: - self.aboutToQuit + Removes lockfiles on a clean shutdown. - Triggered on about to quit signal, removes lockfiles on a clean - shutdown + Triggered after aboutToQuit signal. """ if IS_WIN: lockfile = WindowsLock() lockfile.release_lock() + def _cleanup_and_quit(self): + """ + Call all the cleanup actions in a serialized way. + Should be called from the quit function. + """ + logger.debug('About to quit, doing cleanup...') + self._vpn.set_should_quit() + self._vpn.wait() + self._checker_thread.set_should_quit() + self._checker_thread.wait() + self._cleanup_pidfiles() + + def quit(self): + """ + Cleanup and tidely close the main window before quitting. + """ + self._cleanup_and_quit() + + self._really_quit = True + if self._wizard: + self._wizard.close() + self.close() + + if self._quit_callback: + self._quit_callback() + logger.debug('Bye.') + + if __name__ == "__main__": import signal diff --git a/src/leap/gui/twisted_main.py b/src/leap/gui/twisted_main.py new file mode 100644 index 00000000..44f532a4 --- /dev/null +++ b/src/leap/gui/twisted_main.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# twisted_main.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 . +""" +Main functions for integration of twisted reactor +""" +import logging + +# Resist the temptation of putting the import reactor here, +# it will raise an "reactor already imported" error. + +logger = logging.getLogger(__name__) + + +def start(app): + """ + Start the mainloop. + + :param app: the main qt QApplication instance. + :type app: QtCore.QApplication + """ + from twisted.internet import reactor + logger.debug('starting twisted reactor') + reactor.run() + + +def quit(app): + """ + Stop the mainloop. + + :param app: the main qt QApplication instance. + :type app: QtCore.QApplication + """ + from twisted.internet import reactor + logger.debug('stopping twisted reactor') + reactor.stop() -- cgit v1.2.3 From 212102f05bbc09b0b6cc6fa250eaafc8f80b6824 Mon Sep 17 00:00:00 2001 From: kali Date: Fri, 17 May 2013 00:45:06 +0900 Subject: catch reactor not running error --- src/leap/gui/twisted_main.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/twisted_main.py b/src/leap/gui/twisted_main.py index 44f532a4..871af577 100644 --- a/src/leap/gui/twisted_main.py +++ b/src/leap/gui/twisted_main.py @@ -19,6 +19,8 @@ Main functions for integration of twisted reactor """ import logging +from twisted.internet import error + # Resist the temptation of putting the import reactor here, # it will raise an "reactor already imported" error. @@ -46,4 +48,7 @@ def quit(app): """ from twisted.internet import reactor logger.debug('stopping twisted reactor') - reactor.stop() + try: + reactor.stop() + except error.ReactorNotRunning: + logger.debug('reactor not running') -- cgit v1.2.3 From 319e1d55a2f8e9c521450f60c571f24a907553ee Mon Sep 17 00:00:00 2001 From: kali Date: Fri, 17 May 2013 02:00:35 +0900 Subject: fix a segfault when stopping the reactor this particular way of invoking the run method in the reactor was giving trouble under some conditions. switching to runReturn makes it work. --- src/leap/gui/twisted_main.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/twisted_main.py b/src/leap/gui/twisted_main.py index 871af577..c7add3ee 100644 --- a/src/leap/gui/twisted_main.py +++ b/src/leap/gui/twisted_main.py @@ -36,7 +36,13 @@ def start(app): """ from twisted.internet import reactor logger.debug('starting twisted reactor') - reactor.run() + + # this seems to be troublesome under some + # unidentified settings. + #reactor.run() + + reactor.runReturn() + app.exec_() def quit(app): -- cgit v1.2.3 From b0abf507bb8eb570328172b659ab072bc4b08634 Mon Sep 17 00:00:00 2001 From: Tomas Touceda Date: Wed, 15 May 2013 16:16:32 -0300 Subject: Integrate soledad and keymanager in the client --- src/leap/gui/mainwindow.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index fdf84766..7eb956cb 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -37,6 +37,7 @@ from leap.gui.wizard import Wizard from leap.services.eip.eipbootstrapper import EIPBootstrapper from leap.services.eip.eipconfig import EIPConfig from leap.services.eip.providerbootstrapper import ProviderBootstrapper +from leap.services.soledad.soledadbootstrapper import SoledadBootstrapper from leap.platform_init import IS_MAC, IS_WIN from leap.platform_init.initializers import init_platform from leap.services.eip.vpn import VPN @@ -175,6 +176,10 @@ class MainWindow(QtGui.QMainWindow): self._eip_bootstrapper.download_client_certificate.connect( self._finish_eip_bootstrap) + self._soledad_bootstrapper = SoledadBootstrapper() + self._soledad_bootstrapper.download_config.connect( + self._soledad_bootstrapped_stage) + self._vpn = VPN() self._vpn.state_changed.connect(self._update_vpn_state) self._vpn.status_changed.connect(self._update_vpn_status) @@ -233,6 +238,8 @@ class MainWindow(QtGui.QMainWindow): self._bypass_checks = bypass_checks + self._soledad = None + if self._first_run(): self._wizard_firstrun = True self._wizard = Wizard(self._checker_thread, @@ -686,8 +693,35 @@ class MainWindow(QtGui.QMainWindow): """ self.ui.stackedWidget.setCurrentIndex(self.EIP_STATUS_INDEX) self._systray.setIcon(self.LOGGED_IN_ICON) + + self._soledad_bootstrapper.run_soledad_setup_checks( + self._checker_thread, + self._provider_config, + self.ui.lnUser.text(), + self.ui.lnPassword.text(), + download_if_needed=True) + self._download_eip_config() + def _soledad_bootstrapped_stage(self, data): + """ + SLOT + TRIGGERS: + self._soledad_bootstrapper.download_config + + If there was a problem, displays it, otherwise it does nothing. + This is used for intermediate bootstrapping stages, in case + they fail. + + :param data: result from the bootstrapping stage for Soledad + :type data: dict + """ + passed = data[self._soledad_bootstrapper.PASSED_KEY] + if not passed: + logger.error(data[self._soledad_bootstrapper.ERROR_KEY]) + else: + logger.debug("Done bootstrapping Soledad") + def _get_socket_host(self): """ Returns the socket and port to be used for VPN -- cgit v1.2.3 From c2a0900868eecda3958185bf355a15b046d4e4af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Sat, 18 May 2013 12:38:29 -0300 Subject: Integrate SMTP --- src/leap/gui/mainwindow.py | 69 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 2 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 7eb956cb..d51ec3c8 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -25,7 +25,9 @@ import tempfile from functools import partial import keyring + from PySide import QtCore, QtGui +from mock import Mock from leap.common.check import leap_assert from leap.common.events import register @@ -38,6 +40,7 @@ from leap.services.eip.eipbootstrapper import EIPBootstrapper from leap.services.eip.eipconfig import EIPConfig from leap.services.eip.providerbootstrapper import ProviderBootstrapper from leap.services.soledad.soledadbootstrapper import SoledadBootstrapper +from leap.services.mail.smtpbootstrapper import SMTPBootstrapper from leap.platform_init import IS_MAC, IS_WIN from leap.platform_init.initializers import init_platform from leap.services.eip.vpn import VPN @@ -48,6 +51,8 @@ from leap.services.eip.vpnlaunchers import (VPNLauncherException, from leap.util import __version__ as VERSION from leap.util.checkerthread import CheckerThread +from leap.services.mail.smtpconfig import SMTPConfig + if IS_WIN: from leap.platform_init.locks import WindowsLock @@ -68,6 +73,10 @@ class MainWindow(QtGui.QMainWindow): # Keyring KEYRING_KEY = "leap_client" + # SMTP + PORT_KEY = "port" + IP_KEY = "ip_address" + # Signals new_updates = QtCore.Signal(object) raise_window = QtCore.Signal([]) @@ -177,9 +186,13 @@ class MainWindow(QtGui.QMainWindow): self._finish_eip_bootstrap) self._soledad_bootstrapper = SoledadBootstrapper() - self._soledad_bootstrapper.download_config.connect( + self._soledad_bootstrapper.gen_key.connect( self._soledad_bootstrapped_stage) + self._smtp_bootstrapper = SMTPBootstrapper() + self._smtp_bootstrapper.download_config.connect( + self._smtp_bootstrapped_stage) + self._vpn = VPN() self._vpn.state_changed.connect(self._update_vpn_state) self._vpn.status_changed.connect(self._update_vpn_status) @@ -239,6 +252,9 @@ class MainWindow(QtGui.QMainWindow): self._bypass_checks = bypass_checks self._soledad = None + self._keymanager = None + + self._smtp_config = SMTPConfig() if self._first_run(): self._wizard_firstrun = True @@ -707,7 +723,7 @@ class MainWindow(QtGui.QMainWindow): """ SLOT TRIGGERS: - self._soledad_bootstrapper.download_config + self._soledad_bootstrapper.gen_key If there was a problem, displays it, otherwise it does nothing. This is used for intermediate bootstrapping stages, in case @@ -722,6 +738,55 @@ class MainWindow(QtGui.QMainWindow): else: logger.debug("Done bootstrapping Soledad") + self._soledad = data[self._soledad_bootstrapper.SOLEDAD_KEY] + self._keymanager = data[self._soledad_bootstrapper.KEYMANAGER_KEY] + + self._smtp_bootstrapper.run_smtp_setup_checks( + self._checker_thread, + self._provider_config, + self._smtp_config, + True) + + def _smtp_bootstrapped_stage(self, data): + """ + SLOT + TRIGGERS: + self._smtp_bootstrapper.download_config + + If there was a problem, displays it, otherwise it does nothing. + This is used for intermediate bootstrapping stages, in case + they fail. + + :param data: result from the bootstrapping stage for Soledad + :type data: dict + """ + passed = data[self._smtp_bootstrapper.PASSED_KEY] + if not passed: + logger.error(data[self._smtp_bootstrapper.ERROR_KEY]) + else: + logger.debug("Done bootstrapping SMTP") + + hosts = self._smtp_config.get_hosts() + # TODO: handle more than one host and define how to choose + if len(hosts) > 0: + hostname = hosts.keys()[0] + logger.debug("Using hostname %s for SMTP" % (hostname,)) + host = hosts[hostname][self.IP_KEY].encode("utf-8") + port = hosts[hostname][self.PORT_KEY] + # TODO: pick local smtp port in a better way + # TODO: Make the encrypted_only configurable + + # TODO: Remove mocking!!! + self._keymanager.fetch_keys_from_server = Mock(return_value=[]) + from leap.mail.smtp import setup_smtp_relay + setup_smtp_relay(port=1234, + keymanager=self._keymanager, + smtp_host=host, + smtp_port=port, + smtp_username=".", + smtp_password=".", + encrypted_only=False) + def _get_socket_host(self): """ Returns the socket and port to be used for VPN -- cgit v1.2.3 From 8c54836c146ac1870996b0d00e116332b9b3d0a5 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 22 May 2013 11:13:31 -0300 Subject: Handle 'Incorrect Password' exception. closes issue #2640. don not automatic login if there is no password add changes file --- src/leap/gui/mainwindow.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 7eb956cb..dad88362 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -354,15 +354,22 @@ class MainWindow(QtGui.QMainWindow): self.ui.chkRemember.setChecked(True) self.ui.chkAutoLogin.setEnabled(self.ui.chkRemember .isEnabled()) - saved_password = keyring.get_password(self.KEYRING_KEY, - saved_user - .encode("utf8")) + + saved_password = None + try: + saved_password = keyring.get_password(self.KEYRING_KEY, + saved_user + .encode("utf8")) + except ValueError, e: + 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: + if auto_login and saved_password: self._login() def _show_systray(self): -- cgit v1.2.3 From 9516f7e34cf265f87abb67e40b9fa76682012c84 Mon Sep 17 00:00:00 2001 From: kali Date: Thu, 23 May 2013 22:47:18 +0900 Subject: fix restore from minimized. Closes #2574 --- src/leap/gui/mainwindow.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 3e8a1c83..a8aa1b1c 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -420,12 +420,21 @@ class MainWindow(QtGui.QMainWindow): Toggles the window visibility """ + get_action = lambda visible: ( + self.tr("Show"), + self.tr("Hide"))[int(visible)] + + minimized = self.isMinimized() + if reason != QtGui.QSystemTrayIcon.Context: + # do show + if minimized: + self.showNormal() self.setVisible(not self.isVisible()) - action_visible_text = self.tr("Hide") - if not self.isVisible(): - action_visible_text = self.tr("Show") - self._action_visible.setText(action_visible_text) + + # set labels + visible = self.isVisible() + self._action_visible.setText(get_action(visible)) def _center_window(self): """ -- cgit v1.2.3 From ef00c4ec75446c60dea8f790ae86109e0d8b6ca3 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 27 May 2013 15:08:44 -0300 Subject: Add logger window connected with the leap_handler --- src/leap/gui/loggerwindow.py | 124 ++++++++++++++++++++++++++++++++ src/leap/gui/ui/loggerwindow.ui | 155 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 279 insertions(+) create mode 100644 src/leap/gui/loggerwindow.py create mode 100644 src/leap/gui/ui/loggerwindow.ui (limited to 'src/leap/gui') diff --git a/src/leap/gui/loggerwindow.py b/src/leap/gui/loggerwindow.py new file mode 100644 index 00000000..dd724ac7 --- /dev/null +++ b/src/leap/gui/loggerwindow.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- +# loggerwindow.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 . + +""" +History log window +""" +import logging + +from PySide import QtGui +from ui_loggerwindow import Ui_LoggerWindow +from leap.common.check import leap_assert, leap_assert_type +from leap.util.leap_log_handler import LeapLogHandler + +logger = logging.getLogger(__name__) + + +class LoggerWindow(QtGui.QWidget): + """ + Window that displays a history of the logged messages in the app. + """ + def __init__(self, handler): + """ + Initialize the widget with the custom handler. + + :param handler: Custom handler that supports history and signal. + :type handler: LeapLogHandler. + """ + QtGui.QWidget.__init__(self) + leap_assert(handler, "We need a handler for the logger window") + leap_assert_type(handler, LeapLogHandler) + + # Load UI + self.ui = Ui_LoggerWindow() + self.ui.setupUi(self) + + # Make connections + self.ui.btnSave.clicked.connect(self._save_log_to_file) + self.ui.btnDebug.toggled.connect(self._load_history), + self.ui.btnInfo.toggled.connect(self._load_history), + self.ui.btnWarning.toggled.connect(self._load_history), + self.ui.btnError.toggled.connect(self._load_history), + self.ui.btnCritical.toggled.connect(self._load_history) + + # Load logging history and connect logger with the widget + self._logging_handler = handler + self._connect_to_handler() + self._load_history() + + def _connect_to_handler(self): + """ + This method connects the loggerwindow with the handler through a + signal communicate the logger events. + """ + self._logging_handler.new_log.connect(self._add_log_line) + + def _add_log_line(self, log): + """ + Adds a line to the history, only if it's in the desired levels to show. + + :param log: a log record to be inserted in the widget + :type log: a dict with RECORD_KEY and MESSAGE_KEY. + the record contains the LogRecord of the logging module, + the message contains the formatted message for the log. + """ + level = log[LeapLogHandler.RECORD_KEY].levelname + message = log[LeapLogHandler.MESSAGE_KEY] + + if self._logs_to_display[level]: + self.ui.txtLogHistory.append(message) + + def _load_history(self): + """ + Load the previous logged messages in the widget. + They are stored in the custom handler. + """ + self._set_logs_to_display() + self.ui.txtLogHistory.clear() + history = self._logging_handler.log_history + for line in history: + self._add_log_line(line) + + def _set_logs_to_display(self): + """ + Sets the logs_to_display dict getting the toggled options from the ui + """ + self._logs_to_display = { + 'DEBUG': self.ui.btnDebug.isChecked(), + 'INFO': self.ui.btnInfo.isChecked(), + 'WARNING': self.ui.btnWarning.isChecked(), + 'ERROR': self.ui.btnError.isChecked(), + 'CRITICAL': self.ui.btnCritical.isChecked() + } + + def _save_log_to_file(self): + """ + Lets the user save the current log to a file + """ + fileName, filtr = QtGui.QFileDialog.getSaveFileName( + self, self.tr("Save As")) + + if fileName: + try: + with open(fileName, 'w') as output: + output.write(self.ui.txtLogHistory.toPlainText()) + output.write('\n') + logger.debug('Log saved in %s' % (fileName, )) + except IOError, e: + logger.error("Error saving log file: %r" % (e, )) + else: + logger.debug('Log not saved!') diff --git a/src/leap/gui/ui/loggerwindow.ui b/src/leap/gui/ui/loggerwindow.ui new file mode 100644 index 00000000..28325cdf --- /dev/null +++ b/src/leap/gui/ui/loggerwindow.ui @@ -0,0 +1,155 @@ + + + LoggerWindow + + + + 0 + 0 + 648 + 469 + + + + Logs + + + + :/images/leap-color-big.png:/images/leap-color-big.png + + + + + + + + + + + Debug + + + + :/images/oxygen-icons/script-error.png:/images/oxygen-icons/script-error.png + + + true + + + true + + + true + + + + + + + Info + + + + :/images/oxygen-icons/dialog-information.png:/images/oxygen-icons/dialog-information.png + + + true + + + true + + + true + + + + + + + Warning + + + + :/images/oxygen-icons/dialog-warning.png:/images/oxygen-icons/dialog-warning.png + + + true + + + true + + + true + + + + + + + Error + + + + :/images/oxygen-icons/dialog-error.png:/images/oxygen-icons/dialog-error.png + + + true + + + true + + + true + + + + + + + Critical + + + + :/images/oxygen-icons/edit-bomb.png:/images/oxygen-icons/edit-bomb.png + + + true + + + true + + + true + + + + + + + Save to file + + + + :/images/oxygen-icons/document-save-as.png:/images/oxygen-icons/document-save-as.png + + + + + + + + + btnDebug + btnInfo + btnWarning + btnError + btnCritical + btnSave + txtLogHistory + + + + + + + -- cgit v1.2.3 From c928400ff02adf6c23c5341c2139e95f2c49976d Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 27 May 2013 15:10:49 -0300 Subject: Integrate LoggerWindow with client --- src/leap/gui/mainwindow.py | 37 +++++++++++++++++++++++++++++++++++++ src/leap/gui/ui/mainwindow.ui | 10 ++++++++-- 2 files changed, 45 insertions(+), 2 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index a8aa1b1c..12187f51 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -35,6 +35,7 @@ from leap.common.events import events_pb2 as proto from leap.config.leapsettings import LeapSettings 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.services.eip.eipbootstrapper import EIPBootstrapper from leap.services.eip.eipconfig import EIPConfig @@ -208,6 +209,7 @@ class MainWindow(QtGui.QMainWindow): self.ui.action_about_leap.triggered.connect(self._about) self.ui.action_quit.triggered.connect(self.quit) self.ui.action_wizard.triggered.connect(self._launch_wizard) + self.ui.action_show_logs.triggered.connect(self._show_logger_window) self.raise_window.connect(self._do_raise_mainwindow) # Used to differentiate between real quits and close to tray @@ -249,6 +251,8 @@ class MainWindow(QtGui.QMainWindow): self._wizard = None self._wizard_firstrun = False + self._logger_window = None + self._bypass_checks = bypass_checks self._soledad = None @@ -282,6 +286,35 @@ class MainWindow(QtGui.QMainWindow): self._wizard.exec_() self._wizard = None + def _get_leap_logging_handler(self): + """ + Gets the leap handler from the top level logger + + :return: a logging handler or None + :rtype: LeapLogHandler or None + """ + from leap.util.leap_log_handler import LeapLogHandler + leap_logger = logging.getLogger('leap') + for h in leap_logger.handlers: + if isinstance(h, LeapLogHandler): + return h + return None + + def _show_logger_window(self): + """ + Displays the window with the history of messages logged until now + and displays the new ones on arrival. + """ + if self._logger_window is None: + leap_log_handler = self._get_leap_logging_handler() + if leap_log_handler is None: + logger.error('Leap logger handler not found') + else: + self._logger_window = LoggerWindow(handler=leap_log_handler) + self._logger_window.show() + 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) @@ -1101,6 +1134,10 @@ class MainWindow(QtGui.QMainWindow): self._really_quit = True if self._wizard: self._wizard.close() + + if self._logger_window: + self._logger_window.close() + self.close() if self._quit_callback: diff --git a/src/leap/gui/ui/mainwindow.ui b/src/leap/gui/ui/mainwindow.ui index b42ff180..fdf5c167 100644 --- a/src/leap/gui/ui/mainwindow.ui +++ b/src/leap/gui/ui/mainwindow.ui @@ -396,7 +396,7 @@ 0 0 415 - 25 + 21 @@ -417,9 +417,10 @@ - S&ettings + &Utils + @@ -451,6 +452,11 @@ &Wizard + + + Show &logs + + lnUser -- cgit v1.2.3 From c4ba840c7c0df81fa928bab2edd1f2a2e3379952 Mon Sep 17 00:00:00 2001 From: kali Date: Mon, 27 May 2013 21:42:10 +0900 Subject: adapt test suite to latest client code * fix run_tests * add tox.ini * cleanup/update requirements * fix coverage reports * add pypi badge --- src/leap/gui/__init__.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'src/leap/gui') diff --git a/src/leap/gui/__init__.py b/src/leap/gui/__init__.py index e69de29b..d31dac64 100644 --- a/src/leap/gui/__init__.py +++ b/src/leap/gui/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# __init__.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 . +""" +init file for leap.gui +""" +from .. import app +__all__ = [app] -- cgit v1.2.3 From f59db5457489606a6e12843c9e71911a1dbc54ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Fri, 31 May 2013 16:32:27 -0300 Subject: Fix relative import --- src/leap/gui/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/__init__.py b/src/leap/gui/__init__.py index d31dac64..4b289442 100644 --- a/src/leap/gui/__init__.py +++ b/src/leap/gui/__init__.py @@ -17,5 +17,5 @@ """ init file for leap.gui """ -from .. import app +app = __import__("app", globals(), locals(), [], 2) __all__ = [app] -- cgit v1.2.3 From 4c726c1531abfe288604eaa4c1d347e85bed81eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Mon, 3 Jun 2013 15:02:41 -0300 Subject: Use Twisted's deferToThread and Deferreds to handle parallel tasks This removes CheckerThread --- src/leap/gui/mainwindow.py | 78 ++++++++++++++++++++++++++++++---------------- src/leap/gui/wizard.py | 28 ++++++++--------- 2 files changed, 66 insertions(+), 40 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 12187f51..25478aa1 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -27,7 +27,7 @@ from functools import partial import keyring from PySide import QtCore, QtGui -from mock import Mock +from twisted.internet import threads from leap.common.check import leap_assert from leap.common.events import register @@ -50,7 +50,6 @@ from leap.services.eip.vpnlaunchers import (VPNLauncherException, EIPNoPkexecAvailable, EIPNoPolkitAuthAgentAvailable) from leap.util import __version__ as VERSION -from leap.util.checkerthread import CheckerThread from leap.services.mail.smtpconfig import SMTPConfig @@ -78,6 +77,9 @@ class MainWindow(QtGui.QMainWindow): PORT_KEY = "port" IP_KEY = "ip_address" + OPENVPN_SERVICE = "openvpn" + MX_SERVICE = "mx" + # Signals new_updates = QtCore.Signal(object) raise_window = QtCore.Signal([]) @@ -155,9 +157,6 @@ class MainWindow(QtGui.QMainWindow): # This is created once we have a valid provider config self._srp_auth = None - self._checker_thread = CheckerThread() - self._checker_thread.start() - # This thread is always running, although it's quite # lightweight when it's done setting up provider # configuration and certificate. @@ -187,6 +186,8 @@ class MainWindow(QtGui.QMainWindow): self._finish_eip_bootstrap) self._soledad_bootstrapper = SoledadBootstrapper() + self._soledad_bootstrapper.download_config.connect( + self._soledad_intermediate_stage) self._soledad_bootstrapper.gen_key.connect( self._soledad_bootstrapped_stage) @@ -262,8 +263,7 @@ class MainWindow(QtGui.QMainWindow): if self._first_run(): self._wizard_firstrun = True - self._wizard = Wizard(self._checker_thread, - standalone=standalone, + self._wizard = Wizard(standalone=standalone, bypass_checks=bypass_checks) # Give this window time to finish init and then show the wizard QtCore.QTimer.singleShot(1, self._launch_wizard) @@ -281,8 +281,8 @@ class MainWindow(QtGui.QMainWindow): def _launch_wizard(self): if self._wizard is None: - self._wizard = Wizard(self._checker_thread, - bypass_checks=self._bypass_checks) + self._wizard = Wizard(bypass_checks=self._bypass_checks) + self._wizard.accepted.connect(self._finish_init) self._wizard.exec_() self._wizard = None @@ -369,6 +369,7 @@ class MainWindow(QtGui.QMainWindow): msg) def _finish_init(self): + self.ui.cmbProviders.clear() self.ui.cmbProviders.addItems(self._configured_providers()) self._show_systray() self.show() @@ -425,6 +426,9 @@ class MainWindow(QtGui.QMainWindow): """ Sets up the systray icon """ + if self._systray is not None: + self._systray.setVisible(True) + return systrayMenu = QtGui.QMenu(self) systrayMenu.addAction(self._action_visible) systrayMenu.addAction(self.ui.action_sign_out) @@ -618,7 +622,6 @@ class MainWindow(QtGui.QMainWindow): provider = self.ui.cmbProviders.currentText() self._provider_bootstrapper.run_provider_select_checks( - self._checker_thread, provider, download_if_needed=True) @@ -643,7 +646,6 @@ class MainWindow(QtGui.QMainWindow): provider, "provider.json")): self._provider_bootstrapper.run_provider_setup_checks( - self._checker_thread, self._provider_config, download_if_needed=True) else: @@ -728,7 +730,7 @@ class MainWindow(QtGui.QMainWindow): auth_partial = partial(self._srp_auth.authenticate, username, password) - self._checker_thread.add_checks([auth_partial]) + threads.deferToThread(auth_partial) else: self._set_status(data[self._provider_bootstrapper.ERROR_KEY]) self._login_set_enabled(True) @@ -760,7 +762,6 @@ class MainWindow(QtGui.QMainWindow): self._systray.setIcon(self.LOGGED_IN_ICON) self._soledad_bootstrapper.run_soledad_setup_checks( - self._checker_thread, self._provider_config, self.ui.lnUser.text(), self.ui.lnPassword.text(), @@ -768,6 +769,22 @@ class MainWindow(QtGui.QMainWindow): self._download_eip_config() + def _soledad_intermediate_stage(self, data): + """ + SLOT + TRIGGERS: + self._soledad_bootstrapper.download_config + + If there was a problem, displays it, otherwise it does nothing. + This is used for intermediate bootstrapping stages, in case + they fail. + """ + passed = data[self._soledad_bootstrapper.PASSED_KEY] + if not passed: + # TODO: display in the GUI + logger.error("Soledad failed to start: %s" % + (data[self._soledad_bootstrapper.ERROR_KEY],)) + def _soledad_bootstrapped_stage(self, data): """ SLOT @@ -787,14 +804,24 @@ class MainWindow(QtGui.QMainWindow): else: logger.debug("Done bootstrapping Soledad") - self._soledad = data[self._soledad_bootstrapper.SOLEDAD_KEY] - self._keymanager = data[self._soledad_bootstrapper.KEYMANAGER_KEY] + self._soledad = self._soledad_bootstrapper.soledad + self._keymanager = self._soledad_bootstrapper.keymanager - self._smtp_bootstrapper.run_smtp_setup_checks( - self._checker_thread, - self._provider_config, - self._smtp_config, - True) + if self._provider_config.provides_mx() and \ + self._enabled_services.count(self.MX_SERVICE) > 0: + self._smtp_bootstrapper.run_smtp_setup_checks( + self._provider_config, + self._smtp_config, + True) + else: + if self._enabled_services.count(self.MX_SERVICE) > 0: + pass # TODO: show MX status + #self._set_eip_status(self.tr("%s does not support MX") % + # (self._provider_config.get_domain(),), + # error=True) + else: + pass # TODO: show MX status + #self._set_eip_status(self.tr("MX is disabled")) def _smtp_bootstrapped_stage(self, data): """ @@ -914,14 +941,13 @@ class MainWindow(QtGui.QMainWindow): self._set_eip_status(self.tr("Checking configuration, please wait...")) if self._provider_config.provides_eip() and \ - self._enabled_services.count("openvpn") > 0: + self._enabled_services.count(self.OPENVPN_SERVICE) > 0: self._vpn_systray.setVisible(True) self._eip_bootstrapper.run_eip_setup_checks( - self._checker_thread, self._provider_config, download_if_needed=True) else: - if self._enabled_services.count("openvpn") > 0: + if self._enabled_services.count(self.OPENVPN_SERVICE) > 0: self._set_eip_status(self.tr("%s does not support EIP") % (self._provider_config.get_domain(),), error=True) @@ -1035,7 +1061,9 @@ class MainWindow(QtGui.QMainWindow): """ self._set_eip_status_icon("error") self._set_eip_status(self.tr("Signing out...")) - self._checker_thread.add_checks([self._srp_auth.logout]) + # XXX: If other defers are doing authenticated stuff, this + # might conflict with those. CHECK! + threads.deferToThread(self._srp_auth.logout) def _done_logging_out(self, ok, message): """ @@ -1121,8 +1149,6 @@ class MainWindow(QtGui.QMainWindow): logger.debug('About to quit, doing cleanup...') self._vpn.set_should_quit() self._vpn.wait() - self._checker_thread.set_should_quit() - self._checker_thread.wait() self._cleanup_pidfiles() def quit(self): diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index 713383a6..552ace50 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -24,6 +24,7 @@ import json from PySide import QtCore, QtGui from functools import partial +from twisted.internet import threads from ui_wizard import Ui_Wizard from leap.config.providerconfig import ProviderConfig @@ -53,12 +54,10 @@ class Wizard(QtGui.QWizard): BARE_USERNAME_REGEX = r"^[A-Za-z\d_]+$" - def __init__(self, checker, standalone=False, bypass_checks=False): + def __init__(self, standalone=False, bypass_checks=False): """ Constructor for the main Wizard. - :param checker: Checker thread that the wizard should use. - :type checker: CheckerThread :param standalone: If True, the application is running as standalone and the wizard should display some messages according to this. :type standalone: bool @@ -82,16 +81,19 @@ class Wizard(QtGui.QWizard): # Correspondence for services and their name to display EIP_LABEL = self.tr("Encrypted Internet") + MX_LABEL = self.tr("Encrypted Mail") if self._is_need_eip_password_warning(): EIP_LABEL += " " + self.tr( "(will need admin password to start)") self.SERVICE_DISPLAY = [ - EIP_LABEL + EIP_LABEL, + MX_LABEL ] self.SERVICE_CONFIG = [ - "openvpn" + "openvpn", + "mx" ] self._selected_services = set() @@ -147,8 +149,6 @@ class Wizard(QtGui.QWizard): self._username = None self._password = None - self._checker_thread = checker - self.page(self.REGISTER_USER_PAGE).setButtonText( QtGui.QWizard.CommitButton, self.tr("&Next >")) self.page(self.FINISH_PAGE).setButtonText( @@ -231,10 +231,12 @@ class Wizard(QtGui.QWizard): register = SRPRegister(provider_config=self._provider_config) register.registration_finished.connect( self._registration_finished) - self._checker_thread.add_checks( - [partial(register.register_user, - username.encode("utf8"), - password.encode("utf8"))]) + + threads.deferToThread( + partial(register.register_user, + username.encode("utf8"), + password.encode("utf8"))) + self._username = username self._password = password self._set_register_status(self.tr("Starting registration...")) @@ -318,7 +320,6 @@ class Wizard(QtGui.QWizard): self.ui.lblNameResolution.setPixmap(self.QUESTION_ICON) self._provider_bootstrapper.run_provider_select_checks( - self._checker_thread, self._domain) def _complete_task(self, data, label, complete=False, complete_page=-1): @@ -510,8 +511,7 @@ class Wizard(QtGui.QWizard): .get_name(),)) self.ui.lblDownloadCaCert.setPixmap(self.QUESTION_ICON) self._provider_bootstrapper.\ - run_provider_setup_checks(self._checker_thread, - self._provider_config) + run_provider_setup_checks(self._provider_config) if pageId == self.PRESENT_PROVIDER_PAGE: self.page(pageId).setSubTitle(self.tr("Description of services " -- cgit v1.2.3 From 655cec1fec89eb30fc17bdc0a5f527e5a91ba5b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Tue, 4 Jun 2013 12:56:17 -0300 Subject: Remove CheckerThread from SRPAuth Also, some pep8 fixes --- src/leap/gui/mainwindow.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 25478aa1..89f06a1c 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -815,12 +815,12 @@ class MainWindow(QtGui.QMainWindow): True) else: if self._enabled_services.count(self.MX_SERVICE) > 0: - pass # TODO: show MX status + pass # TODO: show MX status #self._set_eip_status(self.tr("%s does not support MX") % - # (self._provider_config.get_domain(),), + # (self._provider_config.get_domain(),), # error=True) else: - pass # TODO: show MX status + pass # TODO: show MX status #self._set_eip_status(self.tr("MX is disabled")) def _smtp_bootstrapped_stage(self, data): -- cgit v1.2.3 From cc247f63441a8d434bc509ea2764f7969ad47fec Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 5 Jun 2013 10:12:22 -0300 Subject: Improve wizard for registration process --- src/leap/gui/ui/wizard.ui | 32 +++++++++++++++++++------------- src/leap/gui/wizard.py | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 49 insertions(+), 16 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/ui/wizard.ui b/src/leap/gui/ui/wizard.ui index 87d04194..96cf4621 100644 --- a/src/leap/gui/ui/wizard.ui +++ b/src/leap/gui/ui/wizard.ui @@ -597,6 +597,12 @@ 4 + + QLayout::SetDefaultConstraint + + + 4 + @@ -646,19 +652,6 @@ - - - - - - - Qt::AutoText - - - Qt::AlignCenter - - - @@ -692,6 +685,19 @@ + + + + + + + Qt::AutoText + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index 552ace50..405752ef 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -243,15 +243,42 @@ class Wizard(QtGui.QWizard): else: self.ui.btnRegister.setEnabled(True) + def _set_registration_fields_visibility(self, visible): + """ + This method hides the username and password labels and inputboxes. + + :param visible: sets the visibility of the widgets + True: widgets are visible or False: are not + :type visible: bool + """ + # username and password inputs + self.ui.lblUser.setVisible(visible) + self.ui.lblPassword.setVisible(visible) + self.ui.lblPassword2.setVisible(visible) + + # username and password labels + self.ui.label_15.setVisible(visible) + self.ui.label_16.setVisible(visible) + self.ui.label_17.setVisible(visible) + + # register button + self.ui.btnRegister.setVisible(visible) + def _registration_finished(self, ok, req): if ok: - self._set_register_status(self.tr("" - "User registration OK. " - "")) + user_domain = self._username + "@" + self._domain + message = "

" + message += self.tr("User %s successfully registered.") % ( + user_domain, ) + message += "

" + self._set_register_status(message) + self.ui.lblPassword2.clearFocus() + self._set_registration_fields_visibility(False) self.ui.chkRemember.setEnabled(True) self.page(self.REGISTER_USER_PAGE).set_completed() + self.button(QtGui.QWizard.BackButton).setEnabled(False) else: old_username = self._username self._username = None -- cgit v1.2.3 From 5b5eef87e8eb41916f2f5657287ac1ad8f0bbfeb Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 5 Jun 2013 11:52:37 -0300 Subject: Select the configured domain in the providers box --- src/leap/gui/mainwindow.py | 6 ++++++ src/leap/gui/wizard.py | 3 +++ 2 files changed, 9 insertions(+) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 89f06a1c..f9e088a7 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -377,6 +377,12 @@ class MainWindow(QtGui.QMainWindow): if self._wizard: possible_username = self._wizard.get_username() possible_password = self._wizard.get_password() + + # 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.ui.chkRemember.setChecked(self._wizard.get_remember()) self._enabled_services = list(self._wizard.get_services()) self._settings.set_enabled_services( diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index 405752ef..ea4474ae 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -154,6 +154,9 @@ class Wizard(QtGui.QWizard): self.page(self.FINISH_PAGE).setButtonText( QtGui.QWizard.FinishButton, self.tr("Connect")) + def get_domain(self): + return self._domain + def get_username(self): return self._username -- cgit v1.2.3 From 2456b456ae72c7c56c03aaf131aef7a2e7ad011b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 5 Jun 2013 11:58:17 -0300 Subject: Only show question mark when the previous check passed --- src/leap/gui/wizard.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index 405752ef..3cf1765b 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -389,10 +389,11 @@ class Wizard(QtGui.QWizard): if not passed: status = self.tr("Non-existent " "provider") + else: + self.ui.lblHTTPS.setPixmap(self.QUESTION_ICON) self.ui.lblProviderSelectStatus.setText(status) self.ui.btnCheck.setEnabled(not passed) self.ui.lnProvider.setEnabled(not passed) - self.ui.lblHTTPS.setPixmap(self.QUESTION_ICON) def _https_connection(self, data): """ @@ -408,9 +409,10 @@ class Wizard(QtGui.QWizard): status = self.tr("%s") \ % (data[self._provider_bootstrapper.ERROR_KEY]) self.ui.lblProviderSelectStatus.setText(status) + else: + self.ui.lblProviderInfo.setPixmap(self.QUESTION_ICON) self.ui.btnCheck.setEnabled(not passed) self.ui.lnProvider.setEnabled(not passed) - self.ui.lblProviderInfo.setPixmap(self.QUESTION_ICON) def _download_provider_info(self, data): """ @@ -451,7 +453,9 @@ class Wizard(QtGui.QWizard): Sets the status for the download of the CA certificate check """ self._complete_task(data, self.ui.lblDownloadCaCert) - self.ui.lblCheckCaFpr.setPixmap(self.QUESTION_ICON) + passed = data[self._provider_bootstrapper.PASSED_KEY] + if passed: + self.ui.lblCheckCaFpr.setPixmap(self.QUESTION_ICON) def _check_ca_fingerprint(self, data): """ @@ -461,7 +465,9 @@ class Wizard(QtGui.QWizard): Sets the status for the CA fingerprint check """ self._complete_task(data, self.ui.lblCheckCaFpr) - self.ui.lblCheckApiCert.setPixmap(self.QUESTION_ICON) + passed = data[self._provider_bootstrapper.PASSED_KEY] + if passed: + self.ui.lblCheckApiCert.setPixmap(self.QUESTION_ICON) def _check_api_certificate(self, data): """ -- cgit v1.2.3 From 40c1190ad556aee33d1b90a9c234b36ad0759861 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Thu, 6 Jun 2013 11:39:57 -0300 Subject: Make the login process more granular with defers --- src/leap/gui/mainwindow.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index f9e088a7..c892cadd 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -259,6 +259,8 @@ class MainWindow(QtGui.QMainWindow): self._soledad = None self._keymanager = None + self._login_defer = None + self._smtp_config = SMTPConfig() if self._first_run(): @@ -733,10 +735,7 @@ class MainWindow(QtGui.QMainWindow): self._srp_auth.logout_finished.connect( self._done_logging_out) - auth_partial = partial(self._srp_auth.authenticate, - username, - password) - threads.deferToThread(auth_partial) + self._login_defer = self._srp_auth.authenticate(username,password) else: self._set_status(data[self._provider_bootstrapper.ERROR_KEY]) self._login_set_enabled(True) @@ -756,6 +755,7 @@ class MainWindow(QtGui.QMainWindow): # "Succeeded" message and then we switch to the EIP status # panel QtCore.QTimer.singleShot(1000, self._switch_to_status) + self._login_defer = None else: self._login_set_enabled(True) @@ -1172,6 +1172,9 @@ class MainWindow(QtGui.QMainWindow): self.close() + if self._login_defer: + self._login_defer.cancel() + if self._quit_callback: self._quit_callback() logger.debug('Bye.') -- cgit v1.2.3 From 3e4ccef05a02c7b717b392acbb6201b2f8858a91 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 7 Jun 2013 12:10:08 -0300 Subject: Remember provider along with username and password --- src/leap/gui/mainwindow.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index f9e088a7..96c50201 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -405,8 +405,21 @@ class MainWindow(QtGui.QMainWindow): saved_user = self._settings.get_user() auto_login = self._settings.get_autologin() + try: + username, domain = saved_user.split('@') + except (ValueError, AttributeError) as e: + # if the saved_user does not contain an '@' or its None + logger.error('Username@provider malformed. %r' % (e, )) + saved_user = None + if saved_user is not None: - self.ui.lnUser.setText(saved_user) + # fill the username + self.ui.lnUser.setText(username) + + # select the configured provider in the combo box + provider_index = self.ui.cmbProviders.findText(domain) + self.ui.cmbProviders.setCurrentIndex(provider_index) + self.ui.chkRemember.setChecked(True) self.ui.chkAutoLogin.setEnabled(self.ui.chkRemember .isEnabled()) @@ -699,13 +712,16 @@ class MainWindow(QtGui.QMainWindow): self._login_set_enabled(False) if self.ui.chkRemember.isChecked(): + # in the keyring and in the settings + # we store the value 'usename@provider' + username_domain = (username + '@' + provider).encode("utf8") try: keyring.set_password(self.KEYRING_KEY, - username.encode("utf8"), + username_domain, password.encode("utf8")) # Only save the username if it was saved correctly in # the keyring - self._settings.set_user(username) + self._settings.set_user(username_domain) except Exception as e: logger.error("Problem saving data to keyring. %r" % (e,)) -- cgit v1.2.3 From 6a01913bffc10a96c6c3acfa6b8b410d6eb27661 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 7 Jun 2013 15:05:01 -0300 Subject: Wizard ui improvements Hide 'remember' checkbox until the registration succeeds. Use word wrapping to the successful registration message, to be more long-username-friendly. --- src/leap/gui/ui/wizard.ui | 3 +++ src/leap/gui/wizard.py | 4 ++++ 2 files changed, 7 insertions(+) (limited to 'src/leap/gui') diff --git a/src/leap/gui/ui/wizard.ui b/src/leap/gui/ui/wizard.ui index 96cf4621..4b9cab1c 100644 --- a/src/leap/gui/ui/wizard.ui +++ b/src/leap/gui/ui/wizard.ui @@ -696,6 +696,9 @@ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + true +
diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index 8c5ea2a0..d03427db 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -278,6 +278,9 @@ class Wizard(QtGui.QWizard): self.ui.lblPassword2.clearFocus() self._set_registration_fields_visibility(False) + + # Allow the user to remember his password + self.ui.chkRemember.setVisible(True) self.ui.chkRemember.setEnabled(True) self.page(self.REGISTER_USER_PAGE).set_completed() @@ -575,6 +578,7 @@ class Wizard(QtGui.QWizard): "%s") % (self._provider_config .get_name(),)) + self.ui.chkRemember.setVisible(False) if pageId == self.SERVICES_PAGE: self._populate_services() -- cgit v1.2.3 From 20c78001df4cabccd062004e1a6a4f323ca594b8 Mon Sep 17 00:00:00 2001 From: kali Date: Tue, 11 Jun 2013 01:17:13 +0900 Subject: pep8 --- src/leap/gui/mainwindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index c892cadd..f8175b7f 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -735,7 +735,7 @@ class MainWindow(QtGui.QMainWindow): self._srp_auth.logout_finished.connect( self._done_logging_out) - self._login_defer = self._srp_auth.authenticate(username,password) + self._login_defer = self._srp_auth.authenticate(username, password) else: self._set_status(data[self._provider_bootstrapper.ERROR_KEY]) self._login_set_enabled(True) -- cgit v1.2.3 From 029b4c2ac07fab41dbed9ab90e04e477938c3c05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Tue, 11 Jun 2013 12:09:30 -0300 Subject: Merge systray icons Also, catch a possible problem with the login answer from the webapp and display a proper message --- src/leap/gui/mainwindow.py | 38 ++++++++++++++------------------------ 1 file changed, 14 insertions(+), 24 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index f9e088a7..fa49c55d 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -217,12 +217,11 @@ class MainWindow(QtGui.QMainWindow): self._really_quit = False self._systray = None - self._vpn_systray = None - self._action_eip_status = QtGui.QAction(self.tr("Encryption is OFF"), + self._action_eip_status = QtGui.QAction(self.tr("Encrypted internet is OFF"), self) self._action_eip_status.setEnabled(False) - self._action_eip_startstop = QtGui.QAction(self.tr("Stop"), self) + self._action_eip_startstop = QtGui.QAction(self.tr("Turn encryption ON"), self) self._action_eip_startstop.triggered.connect( self._stop_eip) self._action_eip_write = QtGui.QAction( @@ -234,7 +233,7 @@ class MainWindow(QtGui.QMainWindow): "%12.2f Kb" % (0.0,), self) self._action_eip_read.setEnabled(False) - self._action_visible = QtGui.QAction(self.tr("Hide"), self) + self._action_visible = QtGui.QAction(self.tr("Hide Main Window"), self) self._action_visible.triggered.connect(self._toggle_visible) self._enabled_services = [] @@ -440,22 +439,15 @@ class MainWindow(QtGui.QMainWindow): systrayMenu.addAction(self.ui.action_sign_out) systrayMenu.addSeparator() systrayMenu.addAction(self.ui.action_quit) + systrayMenu.addSeparator() + 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.LOGGED_OUT_ICON)) + self._systray.setIcon(QtGui.QIcon(self.ERROR_ICON)) self._systray.setVisible(True) self._systray.activated.connect(self._toggle_visible) - vpn_systrayMenu = QtGui.QMenu(self) - vpn_systrayMenu.addAction(self._action_eip_status) - vpn_systrayMenu.addAction(self._action_eip_startstop) - vpn_systrayMenu.addAction(self._action_eip_read) - vpn_systrayMenu.addAction(self._action_eip_write) - self._vpn_systray = QtGui.QSystemTrayIcon(self) - self._vpn_systray.setContextMenu(vpn_systrayMenu) - self._vpn_systray.setIcon(QtGui.QIcon(self.ERROR_ICON)) - self._vpn_systray.setVisible(False) - def _toggle_visible(self, reason=None): """ SLOT @@ -599,7 +591,7 @@ class MainWindow(QtGui.QMainWindow): :param status: status message :type status: str """ - self._vpn_systray.setToolTip(status) + self._systray.setToolTip(status) if error: status = "%s" % (status,) self.ui.lblEIPStatus.setText(status) @@ -765,7 +757,6 @@ class MainWindow(QtGui.QMainWindow): triggers the eip bootstrapping """ self.ui.stackedWidget.setCurrentIndex(self.EIP_STATUS_INDEX) - self._systray.setIcon(self.LOGGED_IN_ICON) self._soledad_bootstrapper.run_soledad_setup_checks( self._provider_config, @@ -895,11 +886,11 @@ class MainWindow(QtGui.QMainWindow): providerconfig=self._provider_config, socket_host=host, socket_port=port) - self.ui.btnEipStartStop.setText(self.tr("Stop EIP")) + self.ui.btnEipStartStop.setText(self.tr("Turn Encryption OFF")) self.ui.btnEipStartStop.disconnect(self) self.ui.btnEipStartStop.clicked.connect( self._stop_eip) - self._action_eip_startstop.setText(self.tr("Stop")) + self._action_eip_startstop.setText(self.tr("Turn Encryption OFF")) self._action_eip_startstop.disconnect(self) self._action_eip_startstop.triggered.connect( self._stop_eip) @@ -928,11 +919,11 @@ class MainWindow(QtGui.QMainWindow): self._vpn.set_should_quit() self._set_eip_status(self.tr("EIP has stopped")) self._set_eip_status_icon("error") - self.ui.btnEipStartStop.setText(self.tr("Start EIP")) + self.ui.btnEipStartStop.setText(self.tr("Turn Encryption ON")) self.ui.btnEipStartStop.disconnect(self) self.ui.btnEipStartStop.clicked.connect( self._start_eip) - self._action_eip_startstop.setText(self.tr("Start")) + self._action_eip_startstop.setText(self.tr("Turn Encryption ON")) self._action_eip_startstop.disconnect(self) self._action_eip_startstop.triggered.connect( self._start_eip) @@ -948,7 +939,6 @@ class MainWindow(QtGui.QMainWindow): if self._provider_config.provides_eip() and \ self._enabled_services.count(self.OPENVPN_SERVICE) > 0: - self._vpn_systray.setVisible(True) self._eip_bootstrapper.run_eip_setup_checks( self._provider_config, download_if_needed=True) @@ -973,12 +963,13 @@ class MainWindow(QtGui.QMainWindow): if status in ("WAIT", "AUTH", "GET_CONFIG", "RECONNECTING", "ASSIGN_IP"): selected_pixmap = self.CONNECTING_ICON + tray_message = self.tr("Turning Encryption ON") elif status in ("CONNECTED"): tray_message = self.tr("Encryption is ON") selected_pixmap = self.CONNECTED_ICON self.ui.lblVPNStatusIcon.setPixmap(selected_pixmap) - self._vpn_systray.setIcon(QtGui.QIcon(selected_pixmap)) + self._systray.setIcon(QtGui.QIcon(selected_pixmap)) self._action_eip_status.setText(tray_message) def _update_vpn_state(self, data): @@ -1079,7 +1070,6 @@ class MainWindow(QtGui.QMainWindow): Switches the stackedWidget back to the login stage after logging out """ - self._systray.setIcon(self.LOGGED_OUT_ICON) self.ui.action_sign_out.setEnabled(False) self.ui.stackedWidget.setCurrentIndex(self.LOGIN_INDEX) self.ui.lnPassword.setText("") -- cgit v1.2.3 From 56f90d03b40e2a138a8b653de408f207ad562a09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Tue, 11 Jun 2013 12:37:29 -0300 Subject: Save the provider used for EIP as the default provider. --- src/leap/gui/mainwindow.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index f9e088a7..fdcacb56 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -889,12 +889,24 @@ class MainWindow(QtGui.QMainWindow): return host, port def _start_eip(self): + """ + SLOT + TRIGGERS: + self.ui.btnEipStartStop.clicked + self._action_eip_startstop.triggered + or called from _finish_eip_bootstrap + + Starts EIP + """ try: host, port = self._get_socket_host() self._vpn.start(eipconfig=self._eip_config, providerconfig=self._provider_config, socket_host=host, socket_port=port) + + self._settings.set_defaultprovider(self._provider_config.get_domain()) + self.ui.btnEipStartStop.setText(self.tr("Stop EIP")) self.ui.btnEipStartStop.disconnect(self) self.ui.btnEipStartStop.clicked.connect( -- cgit v1.2.3 From dbb873016042b213dd9cd84a59aec0c0a2383691 Mon Sep 17 00:00:00 2001 From: kali Date: Wed, 5 Jun 2013 05:18:39 +0900 Subject: use twisted processProtocol instead of QProcess to drive openvpn --- src/leap/gui/mainwindow.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 89f06a1c..2cad6df3 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -44,7 +44,8 @@ from leap.services.soledad.soledadbootstrapper import SoledadBootstrapper from leap.services.mail.smtpbootstrapper import SMTPBootstrapper from leap.platform_init import IS_MAC, IS_WIN from leap.platform_init.initializers import init_platform -from leap.services.eip.vpn import VPN +from leap.services.eip.vpnprocess import VPN, VPNManager + from leap.services.eip.vpnlaunchers import (VPNLauncherException, OpenVPNNotFoundException, EIPNoPkexecAvailable, @@ -196,9 +197,9 @@ class MainWindow(QtGui.QMainWindow): self._smtp_bootstrapped_stage) self._vpn = VPN() - self._vpn.state_changed.connect(self._update_vpn_state) - self._vpn.status_changed.connect(self._update_vpn_status) - self._vpn.process_finished.connect( + self._vpn.qtsigs.state_changed.connect(self._update_vpn_state) + self._vpn.qtsigs.status_changed.connect(self._update_vpn_status) + self._vpn.qtsigs.process_finished.connect( self._eip_finished) self.ui.chkRemember.stateChanged.connect( @@ -816,8 +817,9 @@ class MainWindow(QtGui.QMainWindow): else: if self._enabled_services.count(self.MX_SERVICE) > 0: pass # TODO: show MX status - #self._set_eip_status(self.tr("%s does not support MX") % - # (self._provider_config.get_domain(),), + #self._set_eip_status( + # self.tr("%s does not support MX") % + # (self._provider_config.get_domain(),), # error=True) else: pass # TODO: show MX status @@ -852,8 +854,6 @@ class MainWindow(QtGui.QMainWindow): # TODO: pick local smtp port in a better way # TODO: Make the encrypted_only configurable - # TODO: Remove mocking!!! - self._keymanager.fetch_keys_from_server = Mock(return_value=[]) from leap.mail.smtp import setup_smtp_relay setup_smtp_relay(port=1234, keymanager=self._keymanager, @@ -919,7 +919,7 @@ class MainWindow(QtGui.QMainWindow): self.ui.btnEipStartStop.setEnabled(True) def _stop_eip(self): - self._vpn.set_should_quit() + self._vpn.terminate() self._set_eip_status(self.tr("EIP has stopped")) self._set_eip_status_icon("error") self.ui.btnEipStartStop.setText(self.tr("Start EIP")) @@ -983,7 +983,7 @@ class MainWindow(QtGui.QMainWindow): Updates the displayed VPN state based on the data provided by the VPN thread """ - status = data[self._vpn.STATUS_STEP_KEY] + status = data[VPNManager.STATUS_STEP_KEY] self._set_eip_status_icon(status) if status == "AUTH": self._set_eip_status(self.tr("VPN: Authenticating...")) @@ -1014,12 +1014,12 @@ class MainWindow(QtGui.QMainWindow): Updates the download/upload labels based on the data provided by the VPN thread """ - upload = float(data[self._vpn.TUNTAP_WRITE_KEY]) + upload = float(data[VPNManager.TUNTAP_WRITE_KEY]) upload = upload / 1000.0 upload_str = "%12.2f Kb" % (upload,) self.ui.lblUpload.setText(upload_str) self._action_eip_write.setText(upload_str) - download = float(data[self._vpn.TUNTAP_READ_KEY]) + download = float(data[VPNManager.TUNTAP_READ_KEY]) download = download / 1000.0 download_str = "%12.2f Kb" % (download,) self.ui.lblDownload.setText(download_str) @@ -1079,7 +1079,7 @@ class MainWindow(QtGui.QMainWindow): self.ui.lnPassword.setText("") self._login_set_enabled(True) self._set_status("") - self._vpn.set_should_quit() + self._vpn.terminate() def _intermediate_stage(self, data): """ @@ -1147,8 +1147,11 @@ class MainWindow(QtGui.QMainWindow): Should be called from the quit function. """ logger.debug('About to quit, doing cleanup...') - self._vpn.set_should_quit() - self._vpn.wait() + + logger.debug('Killing vpn') + self._vpn.terminate() + + logger.debug('Cleaning pidfiles') self._cleanup_pidfiles() def quit(self): -- cgit v1.2.3 From b2653dcf8f9619c73e4fc792a2d952dd2fdfd3b4 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 11 Jun 2013 17:00:22 -0300 Subject: Remove lines disconnecting vpn on logout --- src/leap/gui/mainwindow.py | 3 --- 1 file changed, 3 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index a7d88aee..afa7e947 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -1084,8 +1084,6 @@ class MainWindow(QtGui.QMainWindow): Starts the logout sequence """ - self._set_eip_status_icon("error") - self._set_eip_status(self.tr("Signing out...")) # XXX: If other defers are doing authenticated stuff, this # might conflict with those. CHECK! threads.deferToThread(self._srp_auth.logout) @@ -1103,7 +1101,6 @@ class MainWindow(QtGui.QMainWindow): self.ui.lnPassword.setText("") self._login_set_enabled(True) self._set_status("") - self._vpn.terminate() def _intermediate_stage(self, data): """ -- cgit v1.2.3 From da018dfadc4f3c6cbd1613b45b339279aebb0589 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 12 Jun 2013 10:56:57 -0300 Subject: Autostart EIP whenever possible --- src/leap/gui/mainwindow.py | 94 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 79 insertions(+), 15 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index afa7e947..65f5fe3d 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -153,8 +153,12 @@ class MainWindow(QtGui.QMainWindow): EIPConfig.standalone = standalone self._standalone = standalone self._provider_config = ProviderConfig() + # Used for automatic start of EIP + self._provisional_provider_config = ProviderConfig() self._eip_config = EIPConfig() + self._already_started_eip = False + # This is created once we have a valid provider config self._srp_auth = None @@ -219,10 +223,11 @@ class MainWindow(QtGui.QMainWindow): self._systray = None - self._action_eip_status = QtGui.QAction(self.tr("Encrypted internet is OFF"), - self) + self._action_eip_status = QtGui.QAction( + 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) + self._action_eip_startstop = QtGui.QAction( + self.tr("Turn encryption ON"), self) self._action_eip_startstop.triggered.connect( self._stop_eip) self._action_eip_write = QtGui.QAction( @@ -400,6 +405,7 @@ class MainWindow(QtGui.QMainWindow): self._wizard = None self._settings.set_properprovider(True) else: + self._try_autostart_eip() if not self._settings.get_remember(): # nothing to do here return @@ -443,6 +449,31 @@ class MainWindow(QtGui.QMainWindow): if auto_login and saved_password: self._login() + def _try_autostart_eip(self): + """ + Tries to autostart EIP + """ + default_provider = self._settings.get_defaultprovider() + + if default_provider is None: + logger.info("Cannot autostart EIP because there's no default " + "provider configured") + return + + self._enabled_services = self._settings.get_enabled_services( + default_provider) + + if self._provisional_provider_config.load( + os.path.join("leap", + "providers", + default_provider, + "provider.json")): + self._download_eip_config() + else: + # XXX: Display a proper message to the user + logger.error("Unable to load %s config, cannot autostart." % + (default_provider,)) + def _show_systray(self): """ Sets up the systray icon @@ -744,6 +775,7 @@ class MainWindow(QtGui.QMainWindow): self._srp_auth.logout_finished.connect( self._done_logging_out) + # TODO: Add errback! self._login_defer = self._srp_auth.authenticate(username, password) else: self._set_status(data[self._provider_bootstrapper.ERROR_KEY]) @@ -905,15 +937,17 @@ class MainWindow(QtGui.QMainWindow): Starts EIP """ + provider_config = self._get_best_provider_config() + try: host, port = self._get_socket_host() self._vpn.start(eipconfig=self._eip_config, - providerconfig=self._provider_config, + providerconfig=provider_config, socket_host=host, socket_port=port) self._settings.set_defaultprovider( - self._provider_config.get_domain()) + provider_config.get_domain()) self.ui.btnEipStartStop.setText(self.tr("Turn Encryption OFF")) self.ui.btnEipStartStop.disconnect(self) self.ui.btnEipStartStop.clicked.connect( @@ -955,25 +989,53 @@ class MainWindow(QtGui.QMainWindow): self._action_eip_startstop.disconnect(self) self._action_eip_startstop.triggered.connect( self._start_eip) + self._already_started_eip = False + + def _get_best_provider_config(self): + """ + Returns the best ProviderConfig to use at a moment. We may + have to use self._provider_config or + self._provisional_provider_config depending on the start + status. + + :rtype: ProviderConfig + """ + leap_assert(self._provider_config is not None or + self._provisional_provider_config is not None, + "We need a provider config") + + provider_config = None + if self._provider_config.loaded(): + provider_config = self._provider_config + elif self._provisional_provider_config.loaded(): + provider_config = self._provisional_provider_config + else: + leap_assert(False, "We couldn't find any usable ProviderConfig") + + return provider_config def _download_eip_config(self): """ Starts the EIP bootstrapping sequence """ leap_assert(self._eip_bootstrapper, "We need an eip bootstrapper!") - leap_assert(self._provider_config, "We need a provider config") - self._set_eip_status(self.tr("Checking configuration, please wait...")) + provider_config = self._get_best_provider_config() - if self._provider_config.provides_eip() and \ - self._enabled_services.count(self.OPENVPN_SERVICE) > 0: + if provider_config.provides_eip() and \ + self._enabled_services.count(self.OPENVPN_SERVICE) > 0 and \ + not self._already_started_eip: + + self._set_eip_status( + self.tr("Checking configuration, please wait...")) self._eip_bootstrapper.run_eip_setup_checks( - self._provider_config, + provider_config, download_if_needed=True) - else: + self._already_started_eip = True + elif not self._already_started_eip: if self._enabled_services.count(self.OPENVPN_SERVICE) > 0: self._set_eip_status(self.tr("%s does not support EIP") % - (self._provider_config.get_domain(),), + (provider_config.get_domain(),), error=True) else: self._set_eip_status(self.tr("EIP is disabled")) @@ -1059,14 +1121,16 @@ class MainWindow(QtGui.QMainWindow): loaded """ leap_assert(self._eip_config, "We need an eip config!") - leap_assert(self._provider_config, "We need a provider config!") + + provider_config = self._get_best_provider_config() + + domain = provider_config.get_domain() if data[self._eip_bootstrapper.PASSED_KEY] and \ (self._eip_config.loaded() or self._eip_config.load(os.path.join("leap", "providers", - self._provider_config - .get_domain(), + domain, "eip-service.json"))): self._start_eip() else: -- cgit v1.2.3 From ec1c3fb89b7eaf79dde1b0a7cd4b2dcb277653ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 12 Jun 2013 21:11:19 -0300 Subject: Fix wording in mainwindow --- src/leap/gui/ui/mainwindow.ui | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/ui/mainwindow.ui b/src/leap/gui/ui/mainwindow.ui index fdf5c167..ae6fa5bd 100644 --- a/src/leap/gui/ui/mainwindow.ui +++ b/src/leap/gui/ui/mainwindow.ui @@ -6,7 +6,7 @@ 0 0 - 415 + 429 579 @@ -295,7 +295,7 @@ - Start EIP + Turn Encryption ON @@ -395,8 +395,8 @@ 0 0 - 415 - 21 + 429 + 25 -- cgit v1.2.3 From 521c4b41515fa9491f744274548e1714eb8695f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 12 Jun 2013 21:31:32 -0300 Subject: Display the default provider in the systray menu --- src/leap/gui/mainwindow.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 65f5fe3d..ddcf1a4c 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -223,6 +223,9 @@ 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._action_eip_status.setEnabled(False) @@ -460,6 +463,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,6 +492,7 @@ 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) @@ -948,6 +954,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( -- cgit v1.2.3 From cd11784b8fdf0cb45783e8d6a8e9b5288f34820d Mon Sep 17 00:00:00 2001 From: kali Date: Thu, 13 Jun 2013 22:48:29 +0900 Subject: pep8 --- src/leap/gui/mainwindow.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index afa7e947..23084102 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -219,10 +219,12 @@ class MainWindow(QtGui.QMainWindow): self._systray = None - self._action_eip_status = QtGui.QAction(self.tr("Encrypted internet is OFF"), - self) + self._action_eip_status = QtGui.QAction( + 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) + self._action_eip_startstop = QtGui.QAction( + self.tr("Turn encryption ON"), self) self._action_eip_startstop.triggered.connect( self._stop_eip) self._action_eip_write = QtGui.QAction( -- cgit v1.2.3 From 09f1399db76a5c64cc38d45510b18cecd3a3d457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Thu, 13 Jun 2013 10:49:21 -0300 Subject: If eip fails to autostart, mark it as such --- src/leap/gui/mainwindow.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 65f5fe3d..752f5ef1 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -186,7 +186,7 @@ class MainWindow(QtGui.QMainWindow): self._eip_bootstrapper = EIPBootstrapper() self._eip_bootstrapper.download_config.connect( - self._intermediate_stage) + self._eip_intermediate_stage) self._eip_bootstrapper.download_client_certificate.connect( self._finish_eip_bootstrap) @@ -1140,6 +1140,7 @@ class MainWindow(QtGui.QMainWindow): else: self._set_eip_status(data[self._eip_bootstrapper.ERROR_KEY], error=True) + self._already_started_eip = False def _logout(self): """ @@ -1184,6 +1185,21 @@ class MainWindow(QtGui.QMainWindow): self._login_set_enabled(True) self._set_status(data[self._provider_bootstrapper.ERROR_KEY]) + def _eip_intermediate_stage(self, data): + """ + SLOT + TRIGGERS: + self._eip_bootstrapper.download_config + + If there was a problem, displays it, otherwise it does nothing. + This is used for intermediate bootstrapping stages, in case + they fail. + """ + passed = data[self._provider_bootstrapper.PASSED_KEY] + if not passed: + self._set_status(data[self._provider_bootstrapper.ERROR_KEY]) + self._already_started_eip = False + def _eip_finished(self, exitCode): """ SLOT -- cgit v1.2.3 From b2f2831d6fc090a508437a073267d5a9e2bd5e14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 12 Jun 2013 14:22:16 -0300 Subject: Refactor login to its own widget and remove Utils menu --- src/leap/gui/login.py | 213 +++++++++++++++++++++++++++++++++++++++++ src/leap/gui/mainwindow.py | 199 +++++++++++++++++++-------------------- src/leap/gui/ui/login.ui | 129 +++++++++++++++++++++++++ src/leap/gui/ui/mainwindow.ui | 214 ++++++++++++++++-------------------------- 4 files changed, 519 insertions(+), 236 deletions(-) create mode 100644 src/leap/gui/login.py create mode 100644 src/leap/gui/ui/login.ui (limited to 'src/leap/gui') diff --git a/src/leap/gui/login.py b/src/leap/gui/login.py new file mode 100644 index 00000000..cbd10fd0 --- /dev/null +++ b/src/leap/gui/login.py @@ -0,0 +1,213 @@ +# -*- 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 . + +""" +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 = "%s" % (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 0e388e64..61eb8ca4 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._login_widget.login.connect(self._login) + self._login_widget.show_wizard.connect( + self._launch_wizard) - self.ui.btnLogin.clicked.connect(self._login) - self.ui.lnUser.returnPressed.connect(self._focus_password) - self.ui.lnPassword.returnPressed.connect(self._login) + 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) @@ -244,7 +248,6 @@ class MainWindow(QtGui.QMainWindow): self._action_visible.triggered.connect(self._toggle_visible) self._enabled_services = [] - self._settings = LeapSettings(standalone) self._center_window() @@ -281,6 +284,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() @@ -288,11 +298,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): """ @@ -310,6 +333,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. """ @@ -319,14 +347,11 @@ 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): """ @@ -377,8 +402,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() @@ -388,20 +426,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) @@ -412,7 +448,6 @@ class MainWindow(QtGui.QMainWindow): return saved_user = self._settings.get_user() - auto_login = self._settings.get_autologin() try: username, domain = saved_user.split('@') @@ -423,15 +458,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: @@ -442,12 +474,7 @@ 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): @@ -543,6 +570,9 @@ class MainWindow(QtGui.QMainWindow): def _about(self): """ + SLOT + TRIGGERS: self.ui.action_about_leap.triggered + Display the About LEAP dialog """ QtGui.QMessageBox.about( @@ -580,7 +610,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) @@ -615,23 +644,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 = "%s" % (status,) - self.ui.lblStatus.setText(status) - def _set_eip_status(self, status, error=False): """ Sets the status label at the VPN stage to status @@ -644,28 +656,13 @@ class MainWindow(QtGui.QMainWindow): status = "%s" % (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, @@ -685,7 +682,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", @@ -695,12 +692,12 @@ 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): """ @@ -716,29 +713,29 @@ 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") @@ -766,8 +763,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) @@ -779,8 +776,8 @@ 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): """ @@ -790,7 +787,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 @@ -799,7 +796,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): """ @@ -810,8 +807,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() @@ -1163,9 +1160,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 +1179,8 @@ 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_finished(self, exitCode): """ 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 @@ + + + LoginWidget + + + + 0 + 0 + 356 + 219 + + + + Form + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Create a new account + + + + + + + <b>Provider:</b> + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + + + + + + Remember username and password + + + + + + + <b>Username:</b> + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + <b>Password:</b> + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Log In + + + + + + + + + + Qt::AlignCenter + + + + + + + cmbProviders + lnUser + lnPassword + chkRemember + btnLogin + btnCreateAccount + + + + diff --git a/src/leap/gui/ui/mainwindow.ui b/src/leap/gui/ui/mainwindow.ui index fdf5c167..ae895dce 100644 --- a/src/leap/gui/ui/mainwindow.ui +++ b/src/leap/gui/ui/mainwindow.ui @@ -6,7 +6,7 @@ 0 0 - 415 + 429 579 @@ -28,19 +28,6 @@ - - - - Qt::Vertical - - - - 20 - 40 - - - - @@ -54,18 +41,21 @@ - - - - Qt::Horizontal + + + + false - - - 40 - 20 - + + - + + :/images/leap-color-big.png + + + Qt::AlignCenter + +
@@ -80,25 +70,18 @@ - + - 1 + 0 - + - - - - Remember - - - - - + + - - + + Qt::Horizontal @@ -110,15 +93,8 @@ - - - - Login - - - - - + + Qt::Horizontal @@ -130,66 +106,6 @@ - - - - <b>Provider:</b> - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - <b>Password:</b> - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - - - - - - <b>User:</b> - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - Qt::AlignCenter - - - - - - - false - - - Automatically login - - - @@ -303,21 +219,31 @@ - - - - false + + + + Qt::Horizontal - - + + + 40 + 20 + - - :/images/leap-color-big.png + + + + + + Qt::Vertical - - Qt::AlignCenter + + + 20 + 40 + - + @@ -388,6 +314,39 @@ + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Show Log + + + true + + + false + + + true + + + + + @@ -395,8 +354,8 @@ 0 0 - 415 - 21 + 429 + 25 @@ -415,15 +374,7 @@ - - - &Utils - - - - - @@ -458,13 +409,6 @@ - - lnUser - lnPassword - chkRemember - btnLogin - cmbProviders - -- cgit v1.2.3 From 5e8feb737d0a0fb8065cda583c88498385ff7b9f Mon Sep 17 00:00:00 2001 From: kali Date: Thu, 13 Jun 2013 23:37:13 +0900 Subject: pep8 --- src/leap/gui/login.py | 1 - src/leap/gui/mainwindow.py | 24 ++++++++++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/login.py b/src/leap/gui/login.py index cbd10fd0..c367b2fb 100644 --- a/src/leap/gui/login.py +++ b/src/leap/gui/login.py @@ -210,4 +210,3 @@ class LoginWidget(QtGui.QWidget): 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 61eb8ca4..01707554 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -347,7 +347,8 @@ class MainWindow(QtGui.QMainWindow): logger.error('Leap logger handler not found') else: self._logger_window = LoggerWindow(handler=leap_log_handler) - self._logger_window.setVisible(not self._logger_window.isVisible()) + self._logger_window.setVisible( + not self._logger_window.isVisible()) self.ui.btnShowLog.setChecked(self._logger_window.isVisible()) else: self._logger_window.setVisible(not self._logger_window.isVisible()) @@ -474,7 +475,8 @@ class MainWindow(QtGui.QMainWindow): logger.debug("Incorrect Password. %r." % (e,)) if saved_password is not None: - self._login_widget.set_password(saved_password.decode("utf8")) + self._login_widget.set_password( + saved_password.decode("utf8")) self._login() def _try_autostart_eip(self): @@ -696,7 +698,8 @@ class MainWindow(QtGui.QMainWindow): self.tr("Could not load provider configuration")) self._login_widget.set_enabled(True) else: - self._login_widget.set_status(data[self._provider_bootstrapper.ERROR_KEY]) + self._login_widget.set_status( + data[self._provider_bootstrapper.ERROR_KEY]) self._login_widget.set_enabled(True) def _login(self): @@ -721,15 +724,18 @@ class MainWindow(QtGui.QMainWindow): self._login_widget.get_selected_provider()) if len(provider) == 0: - self._login_widget.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._login_widget.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._login_widget.set_status(self.tr("Please provide a valid Password")) + self._login_widget.set_status( + self.tr("Please provide a valid Password")) return self._login_widget.set_status(self.tr("Logging in..."), error=False) @@ -776,7 +782,8 @@ class MainWindow(QtGui.QMainWindow): # TODO: Add errback! self._login_defer = self._srp_auth.authenticate(username, password) else: - self._login_widget.set_status(data[self._provider_bootstrapper.ERROR_KEY]) + self._login_widget.set_status( + data[self._provider_bootstrapper.ERROR_KEY]) self._login_widget.set_enabled(True) def _authentication_finished(self, ok, message): @@ -1180,7 +1187,8 @@ class MainWindow(QtGui.QMainWindow): passed = data[self._provider_bootstrapper.PASSED_KEY] if not passed: self._login_widget.set_enabled(True) - self._login_widget.set_status(data[self._provider_bootstrapper.ERROR_KEY]) + self._login_widget.set_status( + data[self._provider_bootstrapper.ERROR_KEY]) def _eip_finished(self, exitCode): """ -- cgit v1.2.3 From b8a83ca21a0a44487097dbf41735e3bad176d5e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 12 Jun 2013 11:22:08 -0300 Subject: Always show context menu when clicking the systray --- src/leap/gui/mainwindow.py | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 01707554..3c6c579c 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -523,31 +523,41 @@ class MainWindow(QtGui.QMainWindow): 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 -- cgit v1.2.3 From c3fa54bc8628ba1618aba7b09a0daf458e798d3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Thu, 13 Jun 2013 15:39:21 -0300 Subject: Don't autostart EIP if the user explicitly stops the service --- src/leap/gui/mainwindow.py | 1 + 1 file changed, 1 insertion(+) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index e135c798..381dbf19 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -1012,6 +1012,7 @@ class MainWindow(QtGui.QMainWindow): self._action_eip_startstop.triggered.connect( self._start_eip) self._already_started_eip = False + self._settings.set_defaultprovider(None) def _get_best_provider_config(self): """ -- cgit v1.2.3 From cfd60d6e47a351e1ce52709a497e5a0b03319dcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Thu, 13 Jun 2013 15:14:15 -0300 Subject: Refactor the status bits out of the MainWindow to StatusPanelWidget --- src/leap/gui/mainwindow.py | 238 +++++++++++--------------------- src/leap/gui/statuspanel.py | 216 +++++++++++++++++++++++++++++ src/leap/gui/ui/mainwindow.ui | 304 ++++++++++++++--------------------------- src/leap/gui/ui/statuspanel.ui | 248 +++++++++++++++++++++++++++++++++ 4 files changed, 646 insertions(+), 360 deletions(-) create mode 100644 src/leap/gui/statuspanel.py create mode 100644 src/leap/gui/ui/statuspanel.ui (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index e135c798..67fe34e3 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -38,14 +38,15 @@ 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.gui.statuspanel import StatusPanelWidget from leap.services.eip.eipbootstrapper import EIPBootstrapper from leap.services.eip.eipconfig import EIPConfig from leap.services.eip.providerbootstrapper import ProviderBootstrapper from leap.services.soledad.soledadbootstrapper import SoledadBootstrapper from leap.services.mail.smtpbootstrapper import SMTPBootstrapper -from leap.platform_init import IS_MAC, IS_WIN +from leap.platform_init import IS_WIN from leap.platform_init.initializers import init_platform -from leap.services.eip.vpnprocess import VPN, VPNManager +from leap.services.eip.vpnprocess import VPN from leap.services.eip.vpnlaunchers import (VPNLauncherException, OpenVPNNotFoundException, @@ -115,24 +116,6 @@ class MainWindow(QtGui.QMainWindow): self._updates_content = "" - if IS_MAC: - EIP_ICONS = ( - ":/images/conn_connecting-light.png", - ":/images/conn_connected-light.png", - ":/images/conn_error-light.png") - else: - EIP_ICONS = ( - ":/images/conn_connecting.png", - ":/images/conn_connected.png", - ":/images/conn_error.png") - - self.CONNECTING_ICON = QtGui.QPixmap(EIP_ICONS[0]) - self.CONNECTED_ICON = QtGui.QPixmap(EIP_ICONS[1]) - self.ERROR_ICON = QtGui.QPixmap(EIP_ICONS[2]) - - self.LOGGED_OUT_ICON = QtGui.QPixmap(":/images/leap-gray-big.png") - self.LOGGED_IN_ICON = QtGui.QPixmap(":/images/leap-color-big.png") - self.ui = Ui_MainWindow() self.ui.setupUi(self) @@ -149,11 +132,14 @@ class MainWindow(QtGui.QMainWindow): self.ui.btnShowLog.clicked.connect(self._show_logger_window) + self._status_panel = StatusPanelWidget( + self.ui.stackedWidget.widget(self.EIP_STATUS_INDEX)) + self.ui.statusLayout.addWidget(self._status_panel) + self.ui.stackedWidget.setCurrentIndex(self.LOGIN_INDEX) - self.ui.btnEipStartStop.setEnabled(False) - self.ui.btnEipStartStop.clicked.connect( - self._start_eip) + self._status_panel.start_eip.connect(self._start_eip) + self._status_panel.stop_eip.connect(self._stop_eip) # This is loaded only once, there's a bug when doing that more # than once @@ -169,6 +155,7 @@ class MainWindow(QtGui.QMainWindow): # This is created once we have a valid provider config self._srp_auth = None + self._logged_user = None # This thread is always running, although it's quite # lightweight when it's done setting up provider @@ -209,8 +196,10 @@ class MainWindow(QtGui.QMainWindow): self._smtp_bootstrapped_stage) self._vpn = VPN() - self._vpn.qtsigs.state_changed.connect(self._update_vpn_state) - self._vpn.qtsigs.status_changed.connect(self._update_vpn_status) + self._vpn.qtsigs.state_changed.connect( + self._status_panel.update_vpn_state) + self._vpn.qtsigs.status_changed.connect( + self._status_panel.update_vpn_status) self._vpn.qtsigs.process_finished.connect( self._eip_finished) @@ -234,18 +223,14 @@ class MainWindow(QtGui.QMainWindow): self.tr("Encrypted internet is OFF"), self) self._action_eip_status.setEnabled(False) + + self._status_panel.set_action_eip_status( + self._action_eip_status) + self._action_eip_startstop = QtGui.QAction( - self.tr("Turn encryption ON"), self) + self.tr("Turn ON"), self) self._action_eip_startstop.triggered.connect( self._stop_eip) - self._action_eip_write = QtGui.QAction( - QtGui.QIcon(":/images/Arrow-Up-32.png"), - "%12.2f Kb" % (0.0,), self) - self._action_eip_write.setEnabled(False) - self._action_eip_read = QtGui.QAction( - QtGui.QIcon(":/images/Arrow-Down-32.png"), - "%12.2f Kb" % (0.0,), self) - self._action_eip_read.setEnabled(False) self._action_visible = QtGui.QAction(self.tr("Hide Main Window"), self) self._action_visible.triggered.connect(self._toggle_visible) @@ -527,10 +512,12 @@ class MainWindow(QtGui.QMainWindow): 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.setIcon(self._status_panel.ERROR_ICON) self._systray.setVisible(True) self._systray.activated.connect(self._tray_activated) + self._status_panel.set_systray(self._systray) + def _tray_activated(self, reason=None): """ SLOT @@ -662,18 +649,6 @@ class MainWindow(QtGui.QMainWindow): is_proper_provider = self._settings.get_properprovider() return not (has_provider_on_disk and is_proper_provider) - def _set_eip_status(self, status, error=False): - """ - Sets the status label at the VPN stage to status - - :param status: status message - :type status: str - """ - self._systray.setToolTip(status) - if error: - status = "%s" % (status,) - self.ui.lblEIPStatus.setText(status) - def _download_provider_config(self): """ Starts the bootstrapping sequence. It will download the @@ -812,6 +787,7 @@ class MainWindow(QtGui.QMainWindow): """ self._login_widget.set_status(message, error=not ok) if ok: + self._logged_user = self._login_widget.get_user() self.ui.action_sign_out.setEnabled(True) # We leave a bit of room for the user to see the # "Succeeded" message and then we switch to the EIP status @@ -826,6 +802,11 @@ class MainWindow(QtGui.QMainWindow): Changes the stackedWidget index to the EIP status one and triggers the eip bootstrapping """ + if not self._already_started_eip: + self._status_panel.set_provider( + "%s@%s" % (self._login_widget.get_user(), + self._provider_config.get_domain())) + self.ui.stackedWidget.setCurrentIndex(self.EIP_STATUS_INDEX) self._soledad_bootstrapper.run_soledad_setup_checks( @@ -883,13 +864,14 @@ class MainWindow(QtGui.QMainWindow): else: if self._enabled_services.count(self.MX_SERVICE) > 0: pass # TODO: show MX status - #self._set_eip_status( + #self._status_panel.set_eip_status( # self.tr("%s does not support MX") % # (self._provider_config.get_domain(),), # error=True) else: pass # TODO: show MX status - #self._set_eip_status(self.tr("MX is disabled")) + #self._status_panel.set_eip_status( + # self.tr("MX is disabled")) def _smtp_bootstrapped_stage(self, data): """ @@ -969,45 +951,55 @@ class MainWindow(QtGui.QMainWindow): self._settings.set_defaultprovider( provider_config.get_domain()) + + provider = self._provider_config.get_domain() + if self._logged_user is not None: + provider = "%s@%s" % (self._logged_user, provider) + + self._status_panel.set_provider(provider) + 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( - self._stop_eip) - self._action_eip_startstop.setText(self.tr("Turn Encryption OFF")) + + self._status_panel.eip_started() + + self._action_eip_startstop.setText(self.tr("Turn OFF")) self._action_eip_startstop.disconnect(self) self._action_eip_startstop.triggered.connect( self._stop_eip) except EIPNoPolkitAuthAgentAvailable: - self._set_eip_status(self.tr("We could not find any " - "authentication " - "agent in your system.
" - "Make sure you have " - "polkit-gnome-authentication-" - "agent-1 " - "running and try again."), - error=True) + self._status_panel.set_eip_status( + self.tr("We could not find any " + "authentication " + "agent in your system.
" + "Make sure you have " + "polkit-gnome-authentication-" + "agent-1 " + "running and try again."), + error=True) except EIPNoPkexecAvailable: - self._set_eip_status(self.tr("We could not find pkexec " - "in your system."), - error=True) + self._status_panel.set_eip_status( + self.tr("We could not find pkexec " + "in your system."), + error=True) except OpenVPNNotFoundException: - self._set_eip_status(self.tr("We couldn't find openvpn"), - error=True) + self._status_panel.set_eip_status( + self.tr("We couldn't find openvpn"), + error=True) except VPNLauncherException as e: - self._set_eip_status("%s" % (e,), error=True) + self._status_panel.set_eip_status("%s" % (e,), error=True) + else: + self._already_started_eip = True - self.ui.btnEipStartStop.setEnabled(True) + self._status_panel.set_startstop_enabled(True) def _stop_eip(self): self._vpn.terminate() - self._set_eip_status(self.tr("EIP has stopped")) - self._set_eip_status_icon("error") - self.ui.btnEipStartStop.setText(self.tr("Turn Encryption ON")) - self.ui.btnEipStartStop.disconnect(self) - self.ui.btnEipStartStop.clicked.connect( - self._start_eip) - self._action_eip_startstop.setText(self.tr("Turn Encryption ON")) + self._status_panel.set_eip_status(self.tr("Off")) + self._status_panel.set_eip_status_icon("error") + + self._status_panel.eip_stopped() + + self._action_eip_startstop.setText(self.tr("Turn ON")) self._action_eip_startstop.disconnect(self) self._action_eip_startstop.triggered.connect( self._start_eip) @@ -1048,92 +1040,21 @@ class MainWindow(QtGui.QMainWindow): self._enabled_services.count(self.OPENVPN_SERVICE) > 0 and \ not self._already_started_eip: - self._set_eip_status( - self.tr("Checking configuration, please wait...")) + self._status_panel.set_eip_status( + self.tr("Starting...")) self._eip_bootstrapper.run_eip_setup_checks( provider_config, download_if_needed=True) self._already_started_eip = True elif not self._already_started_eip: if self._enabled_services.count(self.OPENVPN_SERVICE) > 0: - self._set_eip_status(self.tr("%s does not support EIP") % - (provider_config.get_domain(),), - error=True) + self._status_panel.set_eip_status( + self.tr("Not supported"), + error=True) else: - self._set_eip_status(self.tr("EIP is disabled")) + self._status_panel.set_eip_status(self.tr("Disabled")) self.ui.btnEipStartStop.setEnabled(False) - def _set_eip_status_icon(self, status): - """ - Given a status step from the VPN thread, set the icon properly - - :param status: status step - :type status: str - """ - selected_pixmap = self.ERROR_ICON - tray_message = self.tr("Encryption is OFF") - if status in ("WAIT", "AUTH", "GET_CONFIG", - "RECONNECTING", "ASSIGN_IP"): - selected_pixmap = self.CONNECTING_ICON - tray_message = self.tr("Turning Encryption ON") - elif status in ("CONNECTED"): - tray_message = self.tr("Encryption is ON") - selected_pixmap = self.CONNECTED_ICON - - self.ui.lblVPNStatusIcon.setPixmap(selected_pixmap) - self._systray.setIcon(QtGui.QIcon(selected_pixmap)) - self._action_eip_status.setText(tray_message) - - def _update_vpn_state(self, data): - """ - SLOT - TRIGGER: self._vpn.state_changed - - Updates the displayed VPN state based on the data provided by - the VPN thread - """ - status = data[VPNManager.STATUS_STEP_KEY] - self._set_eip_status_icon(status) - if status == "AUTH": - self._set_eip_status(self.tr("VPN: Authenticating...")) - elif status == "GET_CONFIG": - self._set_eip_status(self.tr("VPN: Retrieving configuration...")) - elif status == "CONNECTED": - self._set_eip_status(self.tr("VPN: Connected!")) - elif status == "WAIT": - self._set_eip_status(self.tr("VPN: Waiting to start...")) - elif status == "ASSIGN_IP": - self._set_eip_status(self.tr("VPN: Assigning IP")) - elif status == "ALREADYRUNNING": - # Put the following calls in Qt's event queue, otherwise - # the UI won't update properly - QtCore.QTimer.singleShot(0, self._stop_eip) - QtCore.QTimer.singleShot(0, partial(self._set_eip_status, - self.tr("Unable to start VPN, " - "it's already " - "running."))) - else: - self._set_eip_status(status) - - def _update_vpn_status(self, data): - """ - SLOT - TRIGGER: self._vpn.status_changed - - Updates the download/upload labels based on the data provided - by the VPN thread - """ - upload = float(data[VPNManager.TUNTAP_WRITE_KEY]) - upload = upload / 1000.0 - upload_str = "%12.2f Kb" % (upload,) - self.ui.lblUpload.setText(upload_str) - self._action_eip_write.setText(upload_str) - download = float(data[VPNManager.TUNTAP_READ_KEY]) - download = download / 1000.0 - download_str = "%12.2f Kb" % (download,) - self.ui.lblDownload.setText(download_str) - self._action_eip_read.setText(download_str) - def _finish_eip_bootstrap(self, data): """ SLOT @@ -1157,11 +1078,13 @@ class MainWindow(QtGui.QMainWindow): self._start_eip() else: if data[self._eip_bootstrapper.PASSED_KEY]: - self._set_eip_status(self.tr("Could not load EIP " - "Configuration"), error=True) + self._status_panel.set_eip_status( + self.tr("Could not load EIP Configuration"), + error=True) else: - self._set_eip_status(data[self._eip_bootstrapper.ERROR_KEY], - error=True) + self._status_panel.set_eip_status( + data[self._eip_bootstrapper.ERROR_KEY], + error=True) self._already_started_eip = False def _logout(self): @@ -1183,6 +1106,7 @@ class MainWindow(QtGui.QMainWindow): Switches the stackedWidget back to the login stage after logging out """ + self._logged_user = None self.ui.action_sign_out.setEnabled(False) self.ui.stackedWidget.setCurrentIndex(self.LOGIN_INDEX) self._login_widget.set_password("") diff --git a/src/leap/gui/statuspanel.py b/src/leap/gui/statuspanel.py new file mode 100644 index 00000000..53c19e86 --- /dev/null +++ b/src/leap/gui/statuspanel.py @@ -0,0 +1,216 @@ +# -*- coding: utf-8 -*- +# statuspanel.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 . + +""" +Status Panel widget implementation +""" +import logging + +from functools import partial +from PySide import QtCore, QtGui + +from ui_statuspanel import Ui_StatusPanel +from leap.services.eip.vpnprocess import VPNManager +from leap.platform_init import IS_MAC +from leap.common.check import leap_assert_type + +logger = logging.getLogger(__name__) + + +class StatusPanelWidget(QtGui.QWidget): + """ + Status widget that displays the current state of the LEAP services + """ + + start_eip = QtCore.Signal() + stop_eip = QtCore.Signal() + + def __init__(self, parent=None): + QtGui.QWidget.__init__(self, parent) + + self._systray = None + self._action_eip_status = None + + self.ui = Ui_StatusPanel() + self.ui.setupUi(self) + + self.ui.btnEipStartStop.setEnabled(False) + self.ui.btnEipStartStop.clicked.connect( + self.start_eip) + + if IS_MAC: + EIP_ICONS = ( + ":/images/conn_connecting-light.png", + ":/images/conn_connected-light.png", + ":/images/conn_error-light.png") + else: + EIP_ICONS = ( + ":/images/conn_connecting.png", + ":/images/conn_connected.png", + ":/images/conn_error.png") + + self.CONNECTING_ICON = QtGui.QPixmap(EIP_ICONS[0]) + self.CONNECTED_ICON = QtGui.QPixmap(EIP_ICONS[1]) + self.ERROR_ICON = QtGui.QPixmap(EIP_ICONS[2]) + + def set_systray(self, systray): + """ + Sets the systray object to use + + :param systray: Systray object + :type systray: QtGui.QSystemTrayIcon + """ + leap_assert_type(systray, QtGui.QSystemTrayIcon) + self._systray = systray + + def set_action_eip_status(self, action_eip_status): + """ + Sets the action_eip_status to use + + :param action_eip_status: action_eip_status to be used + :type action_eip_status: QtGui.QAction + """ + leap_assert_type(action_eip_status, QtGui.QAction) + self._action_eip_status = action_eip_status + + def set_eip_status(self, status, error=False): + """ + Sets the status label at the VPN stage to status + + :param status: status message + :type status: str or unicode + :param error: if the status is an erroneous one, then set this + to True + :type error: bool + """ + leap_assert_type(error, bool) + + self._systray.setToolTip(status) + if error: + status = "%s" % (status,) + self.ui.lblEIPStatus.setText(status) + + def set_startstop_enabled(self, value): + """ + Enable or disable btnEipStartStop based on value + + :param value: True for enabled, False otherwise + :type value: bool + """ + leap_assert_type(value, bool) + self.ui.btnEipStartStop.setEnabled(value) + + def eip_started(self): + """ + Sets the state of the widget to how it should look after EIP + has started + """ + self.ui.btnEipStartStop.setText(self.tr("Turn OFF")) + self.ui.btnEipStartStop.disconnect(self) + self.ui.btnEipStartStop.clicked.connect( + self.stop_eip) + + def eip_stopped(self): + """ + Sets the state of the widget to how it should look after EIP + has stopped + """ + self.ui.btnEipStartStop.setText(self.tr("Turn ON")) + self.ui.btnEipStartStop.disconnect(self) + self.ui.btnEipStartStop.clicked.connect( + self.start_eip) + + def set_icon(self, icon): + """ + Sets the icon to display for EIP + + :param icon: icon to display + :type icon: QPixmap + """ + self.ui.lblVPNStatusIcon.setPixmap(icon) + + def update_vpn_status(self, data): + """ + SLOT + TRIGGER: VPN.status_changed + + Updates the download/upload labels based on the data provided + by the VPN thread + """ + upload = float(data[VPNManager.TUNTAP_WRITE_KEY]) + upload = upload / 1000.0 + upload_str = "%12.2f Kb" % (upload,) + self.ui.lblUpload.setText(upload_str) + download = float(data[VPNManager.TUNTAP_READ_KEY]) + download = download / 1000.0 + download_str = "%12.2f Kb" % (download,) + self.ui.lblDownload.setText(download_str) + + def update_vpn_state(self, data): + """ + SLOT + TRIGGER: VPN.state_changed + + Updates the displayed VPN state based on the data provided by + the VPN thread + """ + status = data[VPNManager.STATUS_STEP_KEY] + self.set_eip_status_icon(status) + if status == "AUTH": + self.set_eip_status(self.tr("Authenticating...")) + elif status == "GET_CONFIG": + self.set_eip_status(self.tr("Retrieving configuration...")) + elif status == "CONNECTED": + self.set_eip_status(self.tr("On")) + elif status == "WAIT": + self.set_eip_status(self.tr("Waiting to start...")) + elif status == "ASSIGN_IP": + self.set_eip_status(self.tr("Assigning IP")) + elif status == "ALREADYRUNNING": + # Put the following calls in Qt's event queue, otherwise + # the UI won't update properly + QtCore.QTimer.singleShot(0, self.stop_eip) + QtCore.QTimer.singleShot(0, partial(self.set_eip_status, + self.tr("Unable to start VPN, " + "it's already " + "running."))) + else: + self._set_eip_status(status) + + def set_eip_status_icon(self, status): + """ + Given a status step from the VPN thread, set the icon properly + + :param status: status step + :type status: str + """ + selected_pixmap = self.ERROR_ICON + tray_message = self.tr("Encryption is OFF") + if status in ("WAIT", "AUTH", "GET_CONFIG", + "RECONNECTING", "ASSIGN_IP"): + selected_pixmap = self.CONNECTING_ICON + tray_message = self.tr("Turning ON") + elif status in ("CONNECTED"): + tray_message = self.tr("Encryption is ON") + selected_pixmap = self.CONNECTED_ICON + + self.set_icon(selected_pixmap) + self._systray.setIcon(QtGui.QIcon(selected_pixmap)) + self._action_eip_status.setText(tray_message) + + def set_provider(self, provider): + self.ui.lblProvider.setText(provider) diff --git a/src/leap/gui/ui/mainwindow.ui b/src/leap/gui/ui/mainwindow.ui index 4874a324..58827fe0 100644 --- a/src/leap/gui/ui/mainwindow.ui +++ b/src/leap/gui/ui/mainwindow.ui @@ -28,6 +28,75 @@ + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 0 + + + + + + + + There are new updates available, please restart. + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + More... + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + @@ -41,39 +110,10 @@ - - - - false - - - - - - :/images/leap-color-big.png - - - Qt::AlignCenter - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - 0 + 1 @@ -110,115 +150,29 @@ - - - - - - - :/images/conn_error.png - - - Qt::AlignCenter - - - - - - - - 70 - 0 - - - - 0.0 Kb - - - - - - - - 70 - 0 - - - - 0.0 Kb - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - :/images/Arrow-Up-32.png - - - - - - - - - - :/images/Arrow-Down-32.png - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Disconnected - - - Qt::AlignCenter - - - true - - - - - - - Turn Encryption ON - - + + + + + + false + + + + + + :/images/leap-color-big.png + + + Qt::AlignCenter + + + @@ -245,76 +199,20 @@ - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Horizontal - - - - 40 - 0 - - - - - - - - There are new updates available, please restart. - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - More... - - - true - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - + + + + Qt::Horizontal + + + + 40 + 20 + + + - + diff --git a/src/leap/gui/ui/statuspanel.ui b/src/leap/gui/ui/statuspanel.ui new file mode 100644 index 00000000..67f5f669 --- /dev/null +++ b/src/leap/gui/ui/statuspanel.ui @@ -0,0 +1,248 @@ + + + StatusPanel + + + + 0 + 0 + 506 + 403 + + + + Form + + + + + + font: bold; + + + user@domain.org + + + true + + + + + + + + + + + 12 + + + 16 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 0 + + + + + + 64 + 64 + + + + + + + :/images/light/64/network-eip-down.png + + + Qt::AlignCenter + + + + + + + 0 + + + + + + + Encrypted Internet: + + + + + + + font: bold; + + + Off + + + Qt::AutoText + + + Qt::AlignCenter + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Turn On + + + + + + + + + 4 + + + QLayout::SetDefaultConstraint + + + + + + + + :/images/light/16/down-arrow.png + + + + + + + 0.0 Kb + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + + + :/images/light/16/up-arrow.png + + + + + + + 0.0 Kb + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Preferred + + + + 0 + 11 + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + -- cgit v1.2.3 From c752f90f2e242b96a6a63aa56466d22d8db8ceb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Thu, 13 Jun 2013 16:16:47 -0300 Subject: Always use best provider config Also, set the expected user@provider after stopping EIP, in case it was automatically started with another provider --- src/leap/gui/mainwindow.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 67fe34e3..aa9154e2 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -805,7 +805,7 @@ class MainWindow(QtGui.QMainWindow): if not self._already_started_eip: self._status_panel.set_provider( "%s@%s" % (self._login_widget.get_user(), - self._provider_config.get_domain())) + self._get_best_provider_config().get_domain())) self.ui.stackedWidget.setCurrentIndex(self.EIP_STATUS_INDEX) @@ -952,7 +952,7 @@ class MainWindow(QtGui.QMainWindow): self._settings.set_defaultprovider( provider_config.get_domain()) - provider = self._provider_config.get_domain() + provider = provider_config.get_domain() if self._logged_user is not None: provider = "%s@%s" % (self._logged_user, provider) @@ -1005,6 +1005,11 @@ class MainWindow(QtGui.QMainWindow): self._start_eip) self._already_started_eip = False + if self._logged_user: + self._status_panel.set_provider( + "%s@%s" % (self._logged_user, + self._get_best_provider_config().get_domain())) + def _get_best_provider_config(self): """ Returns the best ProviderConfig to use at a moment. We may -- cgit v1.2.3 From a573f046165aa8f28498021939b40286ab96befe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Fri, 14 Jun 2013 10:06:46 -0300 Subject: Fix set_eip_status wrong call --- src/leap/gui/statuspanel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/statuspanel.py b/src/leap/gui/statuspanel.py index 53c19e86..554903d8 100644 --- a/src/leap/gui/statuspanel.py +++ b/src/leap/gui/statuspanel.py @@ -189,7 +189,7 @@ class StatusPanelWidget(QtGui.QWidget): "it's already " "running."))) else: - self._set_eip_status(status) + self.set_eip_status(status) def set_eip_status_icon(self, status): """ -- cgit v1.2.3 From d8ccb2845ec34d0d00ef5c1799822da7c1281bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Fri, 14 Jun 2013 10:49:41 -0300 Subject: Fix wrong set_status call after refactor --- src/leap/gui/mainwindow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 90e74823..a5905679 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -1150,7 +1150,8 @@ class MainWindow(QtGui.QMainWindow): """ passed = data[self._provider_bootstrapper.PASSED_KEY] if not passed: - self._set_status(data[self._provider_bootstrapper.ERROR_KEY]) + self._login_widget.set_status( + data[self._provider_bootstrapper.ERROR_KEY]) self._already_started_eip = False def _eip_finished(self, exitCode): -- cgit v1.2.3 From 699eb99b1092335ee681dacdc06e6bf9c89827ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Fri, 14 Jun 2013 11:19:50 -0300 Subject: Make both clicks behave the same in the systray --- src/leap/gui/mainwindow.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 90e74823..c58cd4e3 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -529,18 +529,17 @@ class MainWindow(QtGui.QMainWindow): self.tr("Show Main Window"), self.tr("Hide Main Window"))[int(visible)] - if reason != QtGui.QSystemTrayIcon.Context: - # 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()) + # 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): """ -- cgit v1.2.3 From 4494b396385d1f9f84a43201cf25c3afd87a823d Mon Sep 17 00:00:00 2001 From: kali Date: Fri, 14 Jun 2013 04:06:43 +0900 Subject: disable soledad bootstrapper --- src/leap/gui/mainwindow.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index a5905679..42148836 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -185,11 +185,11 @@ class MainWindow(QtGui.QMainWindow): self._eip_bootstrapper.download_client_certificate.connect( self._finish_eip_bootstrap) - self._soledad_bootstrapper = SoledadBootstrapper() - self._soledad_bootstrapper.download_config.connect( - self._soledad_intermediate_stage) - self._soledad_bootstrapper.gen_key.connect( - self._soledad_bootstrapped_stage) + #self._soledad_bootstrapper = SoledadBootstrapper() + #self._soledad_bootstrapper.download_config.connect( + #self._soledad_intermediate_stage) + #self._soledad_bootstrapper.gen_key.connect( + #self._soledad_bootstrapped_stage) self._smtp_bootstrapper = SMTPBootstrapper() self._smtp_bootstrapper.download_config.connect( @@ -809,11 +809,12 @@ class MainWindow(QtGui.QMainWindow): self.ui.stackedWidget.setCurrentIndex(self.EIP_STATUS_INDEX) - self._soledad_bootstrapper.run_soledad_setup_checks( - self._provider_config, - self._login_widget.get_user(), - self._login_widget.get_password(), - download_if_needed=True) + # XXX disabling soledad for now + #self._soledad_bootstrapper.run_soledad_setup_checks( + #self._provider_config, + #self._login_widget.get_user(), + #self._login_widget.get_password(), + #download_if_needed=True) self._download_eip_config() -- cgit v1.2.3 From fdda95c92d22b4ede61bfb7587320ddb36da5cd7 Mon Sep 17 00:00:00 2001 From: kali Date: Sat, 15 Jun 2013 01:17:35 +0900 Subject: working openvpn termination: SIGTERM, then SIGKILL. Closes: #2753 --- src/leap/gui/mainwindow.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 42148836..dd9fb148 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -994,19 +994,21 @@ class MainWindow(QtGui.QMainWindow): self._status_panel.set_startstop_enabled(True) def _stop_eip(self): + """ + Stops vpn process and makes gui adjustments to reflect + the change of state. + """ self._vpn.terminate() + self._status_panel.set_eip_status(self.tr("Off")) self._status_panel.set_eip_status_icon("error") - self._status_panel.eip_stopped() - self._action_eip_startstop.setText(self.tr("Turn ON")) self._action_eip_startstop.disconnect(self) self._action_eip_startstop.triggered.connect( self._start_eip) self._already_started_eip = False self._settings.set_defaultprovider(None) - if self._logged_user: self._status_panel.set_provider( "%s@%s" % (self._logged_user, @@ -1204,12 +1206,12 @@ class MainWindow(QtGui.QMainWindow): """ logger.debug('About to quit, doing cleanup...') - logger.debug('Killing vpn') - self._vpn.terminate() - logger.debug('Cleaning pidfiles') self._cleanup_pidfiles() + logger.debug('Terminating vpn') + self._vpn.terminate() + def quit(self): """ Cleanup and tidely close the main window before quitting. @@ -1223,13 +1225,14 @@ class MainWindow(QtGui.QMainWindow): if self._logger_window: self._logger_window.close() - self.close() - if self._login_defer: self._login_defer.cancel() + self.close() + if self._quit_callback: self._quit_callback() + logger.debug('Bye.') -- cgit v1.2.3 From 8fe9b31ba750ca6e0396b6330640efeccacb4ced Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 14 Jun 2013 16:24:01 -0300 Subject: Set EIP different status icons depending on OS. Closes bug #2643 --- src/leap/gui/statuspanel.py | 47 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 9 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/statuspanel.py b/src/leap/gui/statuspanel.py index 554903d8..19f84d4c 100644 --- a/src/leap/gui/statuspanel.py +++ b/src/leap/gui/statuspanel.py @@ -25,7 +25,7 @@ from PySide import QtCore, QtGui from ui_statuspanel import Ui_StatusPanel from leap.services.eip.vpnprocess import VPNManager -from leap.platform_init import IS_MAC +from leap.platform_init import IS_WIN, IS_LINUX from leap.common.check import leap_assert_type logger = logging.getLogger(__name__) @@ -52,13 +52,35 @@ class StatusPanelWidget(QtGui.QWidget): self.ui.btnEipStartStop.clicked.connect( self.start_eip) - if IS_MAC: - EIP_ICONS = ( - ":/images/conn_connecting-light.png", - ":/images/conn_connected-light.png", - ":/images/conn_error-light.png") - else: - EIP_ICONS = ( + # Set the EIP status icons + self.CONNECTING_ICON = None + self.CONNECTED_ICON = None + self.ERROR_ICON = None + self.CONNECTING_ICON_TRAY = None + self.CONNECTED_ICON_TRAY = None + self.ERROR_ICON_TRAY = None + self._set_eip_icons() + + def _set_eip_icons(self): + """ + Sets the EIP status icons for the main window and for the tray + + MAC : dark icons + LINUX : dark icons in window, light icons in tray + WIN : light icons + """ + EIP_ICONS = EIP_ICONS_TRAY = ( + ":/images/conn_connecting-light.png", + ":/images/conn_connected-light.png", + ":/images/conn_error-light.png") + + if IS_LINUX: + EIP_ICONS_TRAY = ( + ":/images/conn_connecting.png", + ":/images/conn_connected.png", + ":/images/conn_error.png") + elif IS_WIN: + EIP_ICONS = EIP_ICONS_TRAY = ( ":/images/conn_connecting.png", ":/images/conn_connected.png", ":/images/conn_error.png") @@ -67,6 +89,10 @@ class StatusPanelWidget(QtGui.QWidget): self.CONNECTED_ICON = QtGui.QPixmap(EIP_ICONS[1]) self.ERROR_ICON = QtGui.QPixmap(EIP_ICONS[2]) + self.CONNECTING_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[0]) + self.CONNECTED_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[1]) + self.ERROR_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[2]) + def set_systray(self, systray): """ Sets the systray object to use @@ -199,17 +225,20 @@ class StatusPanelWidget(QtGui.QWidget): :type status: str """ selected_pixmap = self.ERROR_ICON + selected_pixmap_tray = self.ERROR_ICON_TRAY tray_message = self.tr("Encryption is OFF") if status in ("WAIT", "AUTH", "GET_CONFIG", "RECONNECTING", "ASSIGN_IP"): selected_pixmap = self.CONNECTING_ICON + selected_pixmap_tray = self.CONNECTING_ICON_TRAY tray_message = self.tr("Turning ON") elif status in ("CONNECTED"): tray_message = self.tr("Encryption is ON") selected_pixmap = self.CONNECTED_ICON + selected_pixmap_tray = self.CONNECTED_ICON_TRAY self.set_icon(selected_pixmap) - self._systray.setIcon(QtGui.QIcon(selected_pixmap)) + self._systray.setIcon(QtGui.QIcon(selected_pixmap_tray)) self._action_eip_status.setText(tray_message) def set_provider(self, provider): -- cgit v1.2.3 From 0f43f3db34dd0262e298dd4431b1e971c77dfbdd Mon Sep 17 00:00:00 2001 From: kali Date: Mon, 17 Jun 2013 05:37:04 +0900 Subject: Remove temporal folder during shutdown Closes: #2891 This is the temporal folder that is used under osx and linux for the unix socket to connect to the management interface. --- src/leap/gui/mainwindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 51b96463..9fd543bf 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -1209,7 +1209,7 @@ class MainWindow(QtGui.QMainWindow): self._cleanup_pidfiles() logger.debug('Terminating vpn') - self._vpn.terminate() + self._vpn.terminate(shutdown=True) def quit(self): """ -- cgit v1.2.3 From 1d23bb2d8f5715031bc45f506ac32ce2afa5bfce Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 18 Jun 2013 13:03:10 -0300 Subject: Close the app on rejected wizard. Closes bug #2905 --- src/leap/gui/mainwindow.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 51b96463..bf6aefe9 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -299,9 +299,12 @@ class MainWindow(QtGui.QMainWindow): """ if self._wizard is None: self._wizard = Wizard(bypass_checks=self._bypass_checks) - self._wizard.accepted.connect(self._finish_init) + self._wizard.accepted.connect(self._finish_init) + self.setVisible(False) self._wizard.exec_() + # We need this to process any wizard related event + QtCore.QCoreApplication.processEvents() self._wizard = None self.setVisible(True) -- cgit v1.2.3 From 8c9e05eb498f24f9e580d6dd13cb6834612f9530 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 19 Jun 2013 11:04:03 -0300 Subject: Temporary removal of enrollment policy in UI. Only hide the components. Closes issue #2922. --- src/leap/gui/wizard.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/leap/gui') diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index d03427db..897bf94f 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -154,6 +154,11 @@ class Wizard(QtGui.QWizard): self.page(self.FINISH_PAGE).setButtonText( QtGui.QWizard.FinishButton, self.tr("Connect")) + # XXX: Temporary removal for enrollment policy + # https://leap.se/code/issues/2922 + self.ui.label_12.setVisible(False) + self.ui.lblProviderPolicy.setVisible(False) + def get_domain(self): return self._domain -- cgit v1.2.3 From 22afa7da7befa83f23eb039418edb39b0f5c2856 Mon Sep 17 00:00:00 2001 From: kali Date: Sat, 15 Jun 2013 23:43:50 +0900 Subject: Improve labels in the app * Clarify use of EIP * Closes #2695 --- src/leap/gui/mainwindow.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 51b96463..d5979395 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -474,8 +474,8 @@ class MainWindow(QtGui.QMainWindow): default_provider = self._settings.get_defaultprovider() if default_provider is None: - logger.info("Cannot autostart EIP because there's no default " - "provider configured") + logger.info("Cannot autostart Encrypted Internet because there is " + "no default provider configured") return self._action_eip_provider.setText(default_provider) @@ -983,7 +983,7 @@ class MainWindow(QtGui.QMainWindow): error=True) except OpenVPNNotFoundException: self._status_panel.set_eip_status( - self.tr("We couldn't find openvpn"), + self.tr("We couldn't find openvpn binary"), error=True) except VPNLauncherException as e: self._status_panel.set_eip_status("%s" % (e,), error=True) @@ -1087,7 +1087,7 @@ class MainWindow(QtGui.QMainWindow): else: if data[self._eip_bootstrapper.PASSED_KEY]: self._status_panel.set_eip_status( - self.tr("Could not load EIP Configuration"), + self.tr("Could not load Encrypted Internet Configuration"), error=True) else: self._status_panel.set_eip_status( -- cgit v1.2.3 From e699db4377d1b79c0c48eb3c753d373e44cceb13 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 19 Jun 2013 14:53:00 -0300 Subject: Comment out soledad temporarily. Closes #2932. --- src/leap/gui/mainwindow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index cf20c6eb..a5d022e9 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -42,7 +42,8 @@ from leap.gui.statuspanel import StatusPanelWidget from leap.services.eip.eipbootstrapper import EIPBootstrapper from leap.services.eip.eipconfig import EIPConfig from leap.services.eip.providerbootstrapper import ProviderBootstrapper -from leap.services.soledad.soledadbootstrapper import SoledadBootstrapper +# XXX: comment out soledad temporarily to avoid problem in Windows, issue #2932 +# from leap.services.soledad.soledadbootstrapper import SoledadBootstrapper from leap.services.mail.smtpbootstrapper import SMTPBootstrapper from leap.platform_init import IS_WIN from leap.platform_init.initializers import init_platform -- cgit v1.2.3 From 9a725220c393f28c3be5bb0f894b9eb7cf9af2ad Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 20 Jun 2013 03:19:57 +0900 Subject: fix for empty data --- src/leap/gui/statuspanel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/statuspanel.py b/src/leap/gui/statuspanel.py index 19f84d4c..62a22725 100644 --- a/src/leap/gui/statuspanel.py +++ b/src/leap/gui/statuspanel.py @@ -177,11 +177,11 @@ class StatusPanelWidget(QtGui.QWidget): Updates the download/upload labels based on the data provided by the VPN thread """ - upload = float(data[VPNManager.TUNTAP_WRITE_KEY]) + upload = float(data[VPNManager.TUNTAP_WRITE_KEY] or "0") upload = upload / 1000.0 upload_str = "%12.2f Kb" % (upload,) self.ui.lblUpload.setText(upload_str) - download = float(data[VPNManager.TUNTAP_READ_KEY]) + download = float(data[VPNManager.TUNTAP_READ_KEY] or "0") download = download / 1000.0 download_str = "%12.2f Kb" % (download,) self.ui.lblDownload.setText(download_str) -- cgit v1.2.3 From 6cb47a3627286dc64f781b5412ec66b6ab92560c Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 20 Jun 2013 07:57:51 +0900 Subject: do not apply systray menu workaround for osx Closes: #2920 --- src/leap/gui/mainwindow.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index a5d022e9..a3c601c5 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -39,6 +39,7 @@ from leap.gui.loggerwindow import LoggerWindow from leap.gui.wizard import Wizard from leap.gui.login import LoginWidget from leap.gui.statuspanel import StatusPanelWidget +from leap.platform_init import IS_MAC from leap.services.eip.eipbootstrapper import EIPBootstrapper from leap.services.eip.eipconfig import EIPConfig from leap.services.eip.providerbootstrapper import ProviderBootstrapper @@ -538,12 +539,12 @@ class MainWindow(QtGui.QMainWindow): 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()) + if not IS_MAC: + # 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. + context_menu.exec_(self._systray.geometry().center()) def _toggle_visible(self): """ -- cgit v1.2.3 From 4a422c92ae83453807074afca6c2f038823e296f Mon Sep 17 00:00:00 2001 From: kali Date: Thu, 20 Jun 2013 10:26:46 +0900 Subject: hide mx until we integrate it Closes: #2938 --- src/leap/gui/mainwindow.py | 4 ++++ src/leap/gui/wizard.py | 8 ++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index a5d022e9..42ee5418 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -109,10 +109,14 @@ class MainWindow(QtGui.QMainWindow): QtGui.QMainWindow.__init__(self) # register leap events + + # XXX ------- this is blocking ----------- register(signal=proto.UPDATER_NEW_UPDATES, callback=self._new_updates_available) register(signal=proto.RAISE_WINDOW, callback=self._on_raise_window_event) + # XXX ------- this is blocking ----------- + self._quit_callback = quit_callback self._updates_content = "" diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index 897bf94f..066a7db3 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -32,6 +32,7 @@ from leap.crypto.srpregister import SRPRegister from leap.util.privilege_policies import is_missing_policy_permissions from leap.util.request_helpers import get_content from leap.services.eip.providerbootstrapper import ProviderBootstrapper +from leap.services import get_available logger = logging.getLogger(__name__) @@ -514,13 +515,16 @@ class Wizard(QtGui.QWizard): def _populate_services(self): """ Loads the services that the provider provides into the UI for - the user to enable or disable + the user to enable or disable. """ self.ui.grpServices.setTitle( self.tr("Services by %s") % (self._provider_config.get_name(),)) - for service in self._provider_config.get_services(): + services = get_available( + self._provider_config.get_services()) + + for service in services: try: if service not in self._shown_services: checkbox = QtGui.QCheckBox(self) -- cgit v1.2.3 From 440f2036e36985e9536644b28af0e62cea8704eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Fri, 21 Jun 2013 15:31:24 -0300 Subject: Disable remembering when not using a proper keyring backend --- src/leap/gui/login.py | 4 +++- src/leap/gui/mainwindow.py | 7 ++++--- src/leap/gui/wizard.py | 8 +++++--- 3 files changed, 12 insertions(+), 7 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/login.py b/src/leap/gui/login.py index c367b2fb..8fc6ec24 100644 --- a/src/leap/gui/login.py +++ b/src/leap/gui/login.py @@ -24,6 +24,8 @@ import keyring from PySide import QtCore, QtGui from ui_login import Ui_LoginWidget +from leap.util.keyring_helpers import has_keyring + logger = logging.getLogger(__name__) @@ -58,7 +60,7 @@ class LoginWidget(QtGui.QWidget): self.ui.chkRemember.stateChanged.connect( self._remember_state_changed) - self.ui.chkRemember.setEnabled(keyring.get_keyring() is not None) + self.ui.chkRemember.setEnabled(has_keyring()) self.ui.lnPassword.setEchoMode(QtGui.QLineEdit.Password) diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 2ed01917..9dc1e006 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -55,6 +55,7 @@ from leap.services.eip.vpnlaunchers import (VPNLauncherException, EIPNoPkexecAvailable, EIPNoPolkitAuthAgentAvailable) from leap.util import __version__ as VERSION +from leap.util.keyring_helpers import has_keyring from leap.services.mail.smtpconfig import SMTPConfig @@ -435,7 +436,7 @@ class MainWindow(QtGui.QMainWindow): self._login_widget.set_user(possible_username) if possible_password is not None: self._login_widget.set_password(possible_password) - self._login_widget.set_remember(True) + self._login_widget.set_remember(has_keyring()) self._login() self._wizard = None self._settings.set_properprovider(True) @@ -454,7 +455,7 @@ class MainWindow(QtGui.QMainWindow): logger.error('Username@provider malformed. %r' % (e, )) saved_user = None - if saved_user is not None: + if saved_user is not None and has_keyring(): # fill the username self._login_widget.set_user(username) @@ -740,7 +741,7 @@ class MainWindow(QtGui.QMainWindow): self._login_widget.set_status(self.tr("Logging in..."), error=False) self._login_widget.set_enabled(False) - if self._login_widget.get_remember(): + if self._login_widget.get_remember() and has_keyring(): # in the keyring and in the settings # we store the value 'usename@provider' username_domain = (username + '@' + provider).encode("utf8") diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index 066a7db3..34eba84c 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -31,6 +31,7 @@ from leap.config.providerconfig import ProviderConfig from leap.crypto.srpregister import SRPRegister from leap.util.privilege_policies import is_missing_policy_permissions from leap.util.request_helpers import get_content +from leap.util.keyring_helpers import has_keyring from leap.services.eip.providerbootstrapper import ProviderBootstrapper from leap.services import get_available @@ -170,7 +171,7 @@ class Wizard(QtGui.QWizard): return self._password def get_remember(self): - return self.ui.chkRemember.isChecked() + return has_keyring() and self.ui.chkRemember.isChecked() def get_services(self): return self._selected_services @@ -286,8 +287,9 @@ class Wizard(QtGui.QWizard): self._set_registration_fields_visibility(False) # Allow the user to remember his password - self.ui.chkRemember.setVisible(True) - self.ui.chkRemember.setEnabled(True) + if has_keyring(): + self.ui.chkRemember.setVisible(True) + self.ui.chkRemember.setEnabled(True) self.page(self.REGISTER_USER_PAGE).set_completed() self.button(QtGui.QWizard.BackButton).setEnabled(False) -- cgit v1.2.3 From 63bc283c90a80ddc030e24fcc38c533b1e7c551d Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 24 Jun 2013 02:21:34 +0900 Subject: let errors go in their own panel --- src/leap/gui/mainwindow.py | 33 ++-- src/leap/gui/statuspanel.py | 47 +++++- src/leap/gui/ui/statuspanel.ui | 343 ++++++++++++++++++++--------------------- 3 files changed, 233 insertions(+), 190 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 9dc1e006..b0bf8a44 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -695,7 +695,7 @@ class MainWindow(QtGui.QMainWindow): download_if_needed=True) else: self._login_widget.set_status( - self.tr("Could not load provider configuration")) + self.tr("Could not load provider configuration.")) self._login_widget.set_enabled(True) else: self._login_widget.set_status( @@ -950,6 +950,7 @@ class MainWindow(QtGui.QMainWindow): Starts EIP """ + self._status_panel.eip_pre_up() provider_config = self._get_best_provider_config() try: @@ -977,7 +978,9 @@ class MainWindow(QtGui.QMainWindow): self._action_eip_startstop.triggered.connect( self._stop_eip) except EIPNoPolkitAuthAgentAvailable: - self._status_panel.set_eip_status( + self._status_panel.set_global_status( + # XXX this should change to polkit-kde where + # applicable. self.tr("We could not find any " "authentication " "agent in your system.
" @@ -986,20 +989,31 @@ class MainWindow(QtGui.QMainWindow): "agent-1
" "running and try again."), error=True) + self._set_eipstatus_off() except EIPNoPkexecAvailable: - self._status_panel.set_eip_status( + self._status_panel.set_global_status( self.tr("We could not find pkexec " "in your system."), error=True) + self._set_eipstatus_off() except OpenVPNNotFoundException: - self._status_panel.set_eip_status( - self.tr("We couldn't find openvpn binary"), + self._status_panel.set_global_status( + self.tr("We could not find openvpn binary."), error=True) + self._set_eipstatus_off() except VPNLauncherException as e: - self._status_panel.set_eip_status("%s" % (e,), error=True) + self._status_panel.set_gloal_status("%s" % (e,), error=True) + self._set_eipstatus_off() else: self._already_started_eip = True + #self._status_panel.set_startstop_enabled(True) + + def _set_eipstatus_off(self): + """ + Sets eip status to off + """ + self._status_panel.set_eip_status(self.tr("OFF"), error=True) self._status_panel.set_startstop_enabled(True) def _stop_eip(self): @@ -1009,7 +1023,7 @@ class MainWindow(QtGui.QMainWindow): """ self._vpn.terminate() - self._status_panel.set_eip_status(self.tr("Off")) + self._status_panel.set_eip_status(self.tr("OFF")) self._status_panel.set_eip_status_icon("error") self._status_panel.eip_stopped() self._action_eip_startstop.setText(self.tr("Turn ON")) @@ -1042,7 +1056,7 @@ class MainWindow(QtGui.QMainWindow): elif self._provisional_provider_config.loaded(): provider_config = self._provisional_provider_config else: - leap_assert(False, "We couldn't find any usable ProviderConfig") + leap_assert(False, "We could not find any usable ProviderConfig.") return provider_config @@ -1097,7 +1111,8 @@ class MainWindow(QtGui.QMainWindow): else: if data[self._eip_bootstrapper.PASSED_KEY]: self._status_panel.set_eip_status( - self.tr("Could not load Encrypted Internet Configuration"), + self.tr("Could not load Encrypted Internet " + "Configuration."), error=True) else: self._status_panel.set_eip_status( diff --git a/src/leap/gui/statuspanel.py b/src/leap/gui/statuspanel.py index 62a22725..de913f09 100644 --- a/src/leap/gui/statuspanel.py +++ b/src/leap/gui/statuspanel.py @@ -52,6 +52,8 @@ class StatusPanelWidget(QtGui.QWidget): self.ui.btnEipStartStop.clicked.connect( self.start_eip) + self.hide_status_box() + # Set the EIP status icons self.CONNECTING_ICON = None self.CONNECTED_ICON = None @@ -95,7 +97,7 @@ class StatusPanelWidget(QtGui.QWidget): def set_systray(self, systray): """ - Sets the systray object to use + Sets the systray object to use. :param systray: Systray object :type systray: QtGui.QSystemTrayIcon @@ -105,7 +107,7 @@ class StatusPanelWidget(QtGui.QWidget): def set_action_eip_status(self, action_eip_status): """ - Sets the action_eip_status to use + Sets the action_eip_status to use. :param action_eip_status: action_eip_status to be used :type action_eip_status: QtGui.QAction @@ -113,6 +115,28 @@ class StatusPanelWidget(QtGui.QWidget): leap_assert_type(action_eip_status, QtGui.QAction) self._action_eip_status = action_eip_status + def set_global_status(self, status, error=False): + """ + Sets the global status label. + + :param status: status message + :type status: str or unicode + :param error: if the status is an erroneous one, then set this + to True + :type error: bool + """ + leap_assert_type(error, bool) + if error: + status = "%s" % (status,) + self.ui.lblGlobalStatus.setText(status) + self.ui.globalStatusBox.show() + + def hide_status_box(self): + """ + Hide global status box. + """ + self.ui.globalStatusBox.hide() + def set_eip_status(self, status, error=False): """ Sets the status label at the VPN stage to status @@ -140,6 +164,15 @@ class StatusPanelWidget(QtGui.QWidget): leap_assert_type(value, bool) self.ui.btnEipStartStop.setEnabled(value) + def eip_pre_up(self): + """ + Triggered when the app activates eip. + Hides the status box and disables the start/stop button. + """ + self.hide_status_box() + self.set_startstop_enabled(False) + logger.debug('disabling buton.....................') + def eip_started(self): """ Sets the state of the widget to how it should look after EIP @@ -196,12 +229,14 @@ class StatusPanelWidget(QtGui.QWidget): """ status = data[VPNManager.STATUS_STEP_KEY] self.set_eip_status_icon(status) - if status == "AUTH": + if status == "CONNECTED": + self.set_eip_status(self.tr("ON")) + # Only now we can properly enable the button. + self.set_startstop_enabled(True) + elif status == "AUTH": self.set_eip_status(self.tr("Authenticating...")) elif status == "GET_CONFIG": self.set_eip_status(self.tr("Retrieving configuration...")) - elif status == "CONNECTED": - self.set_eip_status(self.tr("On")) elif status == "WAIT": self.set_eip_status(self.tr("Waiting to start...")) elif status == "ASSIGN_IP": @@ -210,7 +245,7 @@ class StatusPanelWidget(QtGui.QWidget): # Put the following calls in Qt's event queue, otherwise # the UI won't update properly QtCore.QTimer.singleShot(0, self.stop_eip) - QtCore.QTimer.singleShot(0, partial(self.set_eip_status, + QtCore.QTimer.singleShot(0, partial(self.set_global_status, self.tr("Unable to start VPN, " "it's already " "running."))) diff --git a/src/leap/gui/ui/statuspanel.ui b/src/leap/gui/ui/statuspanel.ui index 67f5f669..502ba78a 100644 --- a/src/leap/gui/ui/statuspanel.ui +++ b/src/leap/gui/ui/statuspanel.ui @@ -6,8 +6,8 @@ 0 0 - 506 - 403 + 542 + 477 @@ -29,200 +29,190 @@ + + true + - - - 12 - - - 16 - - - 0 - - - 0 - - - 0 - - - - - 0 + + + + + + + Encrypted Internet: + + + + + + + font: bold; + + + Off + + + Qt::AutoText + + + Qt::AlignCenter + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Turn On + + + + + + + + + Qt::Vertical + + + QSizePolicy::Preferred + + + + 0 + 11 + + + + + + + + + 64 + 64 + - - 0 + + + + :/images/light/64/network-eip-down.png + + + Qt::AlignCenter + + + + + + + 4 + + + QLayout::SetDefaultConstraint + + + + + + + + :/images/light/16/down-arrow.png + + + + + + + 0.0 Kb + + + - - + + + Qt::Horizontal + + + QSizePolicy::Fixed + + - 64 - 64 + 20 + 20 + + + + - :/images/light/64/network-eip-down.png + :/images/light/16/up-arrow.png - - Qt::AlignCenter + + + + + + 0.0 Kb - - - 0 - - - - - - - Encrypted Internet: - - - - - - - font: bold; - - - Off - - - Qt::AutoText - - - Qt::AlignCenter - - - false - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Turn On - - - - - - - - - 4 - - - QLayout::SetDefaultConstraint - - - - - - - - :/images/light/16/down-arrow.png - - - - - - - 0.0 Kb - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 20 - 20 - - - - - - - - - - - :/images/light/16/up-arrow.png - - - - - - - 0.0 Kb - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Qt::Vertical - - - QSizePolicy::Preferred - - - - 0 - 11 - - - - - + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + false + + + + + + false + + + ... + + + + + + @@ -240,6 +230,9 @@ + lblProvider + status_rows + globalStatusBox -- cgit v1.2.3 From 19c90e02a65d1d6fe30e6915cd2a9440878aef83 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 24 Jun 2013 02:57:26 +0900 Subject: Improve OpenVPN Process Control * Disable toggle button until we get "connected" status. #2956 * Catch abnormal termination and update UI accordingly. #2890 --- src/leap/gui/mainwindow.py | 54 +++++++++++++++++++++++++++++++++++++----- src/leap/gui/statuspanel.py | 1 - src/leap/gui/ui/statuspanel.ui | 3 +++ 3 files changed, 51 insertions(+), 7 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index b0bf8a44..94343292 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -90,6 +90,9 @@ class MainWindow(QtGui.QMainWindow): new_updates = QtCore.Signal(object) raise_window = QtCore.Signal([]) + # We use this flag to detect abnormal terminations + user_stopped_eip = False + def __init__(self, quit_callback, standalone=False, bypass_checks=False): """ @@ -951,6 +954,7 @@ class MainWindow(QtGui.QMainWindow): Starts EIP """ self._status_panel.eip_pre_up() + self.user_stopped_eip = False provider_config = self._get_best_provider_config() try: @@ -1007,8 +1011,6 @@ class MainWindow(QtGui.QMainWindow): else: self._already_started_eip = True - #self._status_panel.set_startstop_enabled(True) - def _set_eipstatus_off(self): """ Sets eip status to off @@ -1016,11 +1018,16 @@ class MainWindow(QtGui.QMainWindow): self._status_panel.set_eip_status(self.tr("OFF"), error=True) self._status_panel.set_startstop_enabled(True) - def _stop_eip(self): + def _stop_eip(self, abnormal=False): """ Stops vpn process and makes gui adjustments to reflect the change of state. + + :param abnormal: whether this was an abnormal termination. + :type abnormal: bool + "" """ + self.user_stopped_eip = True self._vpn.terminate() self._status_panel.set_eip_status(self.tr("OFF")) @@ -1036,6 +1043,8 @@ class MainWindow(QtGui.QMainWindow): self._status_panel.set_provider( "%s@%s" % (self._logged_user, self._get_best_provider_config().get_domain())) + if abnormal: + self._status_panel.set_startstop_enabled(True) def _get_best_provider_config(self): """ @@ -1188,10 +1197,43 @@ class MainWindow(QtGui.QMainWindow): self._vpn.process_finished Triggered when the EIP/VPN process finishes to set the UI - accordingly + accordingly. """ - logger.debug("Finished VPN with exitCode %s" % (exitCode,)) - self._stop_eip() + logger.info("VPN process finished with exitCode %s..." + % (exitCode,)) + + # Ideally we would have the right exit code here, + # but the use of different wrappers (pkexec, cocoasudo) swallows + # the openvpn exit code so we get zero exit in some cases where we + # shouldn't. As a workaround we just use a flag to indicate + # a purposeful switch off, and mark everything else as unexpected. + + # In the near future we should trigger a native notification from here, + # since the user really really wants to know she is unprotected asap. + # And the right thing to do will be to fail-close. + + # TODO we should have a way of parsing the latest lines in the vpn + # log buffer so we can have a more precise idea of which type + # of error did we have (server side, local problem, etc) + abnormal = True + + # XXX check if these exitCodes are pkexec/cocoasudo specific + if exitCode in (126, 127): + self._status_panel.set_global_status( + self.tr("Encrypted Internet could not be launched " + "because you did not authenticate properly."), + error=True) + self._vpn.killit() + elif exitCode != 0 or not self.user_stopped_eip: + self._status_panel.set_global_status( + self.tr("Encrypted Internet finished in an " + "unexpected manner!"), error=True) + else: + abnormal = False + if exitCode == 0: + # XXX remove this warning after I fix cocoasudo. + logger.warning("The above exit code MIGHT BE WRONG.") + self._stop_eip(abnormal) def _on_raise_window_event(self, req): """ diff --git a/src/leap/gui/statuspanel.py b/src/leap/gui/statuspanel.py index de913f09..3e5a5093 100644 --- a/src/leap/gui/statuspanel.py +++ b/src/leap/gui/statuspanel.py @@ -171,7 +171,6 @@ class StatusPanelWidget(QtGui.QWidget): """ self.hide_status_box() self.set_startstop_enabled(False) - logger.debug('disabling buton.....................') def eip_started(self): """ diff --git a/src/leap/gui/ui/statuspanel.ui b/src/leap/gui/ui/statuspanel.ui index 502ba78a..1a2c77ad 100644 --- a/src/leap/gui/ui/statuspanel.ui +++ b/src/leap/gui/ui/statuspanel.ui @@ -208,6 +208,9 @@ ... + + true + -- cgit v1.2.3 From 8ff98b63c077af25fb58dc73750e7765c35ce2f0 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 28 Jun 2013 10:59:56 -0300 Subject: Bugfix: Update available/supported implementation. Closes bug #3032. --- src/leap/gui/wizard.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py index 34eba84c..b29250c8 100644 --- a/src/leap/gui/wizard.py +++ b/src/leap/gui/wizard.py @@ -33,7 +33,7 @@ from leap.util.privilege_policies import is_missing_policy_permissions from leap.util.request_helpers import get_content from leap.util.keyring_helpers import has_keyring from leap.services.eip.providerbootstrapper import ProviderBootstrapper -from leap.services import get_available +from leap.services import get_supported logger = logging.getLogger(__name__) @@ -523,7 +523,7 @@ class Wizard(QtGui.QWizard): self.tr("Services by %s") % (self._provider_config.get_name(),)) - services = get_available( + services = get_supported( self._provider_config.get_services()) for service in services: -- cgit v1.2.3