summaryrefslogtreecommitdiff
path: root/src/leap/bitmask
diff options
context:
space:
mode:
Diffstat (limited to 'src/leap/bitmask')
-rw-r--r--src/leap/bitmask/app.py9
-rw-r--r--src/leap/bitmask/backend/api.py6
-rw-r--r--src/leap/bitmask/backend/backend.py40
-rw-r--r--src/leap/bitmask/backend/backend_proxy.py67
-rw-r--r--src/leap/bitmask/backend/components.py7
-rw-r--r--src/leap/bitmask/backend/leapbackend.py10
-rw-r--r--src/leap/bitmask/backend/leapsignaler.py1
-rw-r--r--src/leap/bitmask/backend/utils.py3
-rw-r--r--src/leap/bitmask/backend_app.py5
-rw-r--r--src/leap/bitmask/config/providerconfig.py2
-rw-r--r--src/leap/bitmask/frontend_app.py4
-rw-r--r--src/leap/bitmask/gui/eip_status.py71
-rw-r--r--src/leap/bitmask/gui/loggerwindow.py117
-rw-r--r--src/leap/bitmask/gui/login.py77
-rw-r--r--src/leap/bitmask/gui/mail_status.py12
-rw-r--r--src/leap/bitmask/gui/mainwindow.py473
-rw-r--r--src/leap/bitmask/gui/providers.py114
-rw-r--r--src/leap/bitmask/gui/ui/advanced_key_management.ui4
-rw-r--r--src/leap/bitmask/gui/ui/eip_status.ui55
-rw-r--r--src/leap/bitmask/gui/ui/eippreferences.ui4
-rw-r--r--src/leap/bitmask/gui/ui/loggerwindow.ui4
-rw-r--r--src/leap/bitmask/gui/ui/login.ui87
-rw-r--r--src/leap/bitmask/gui/ui/logout.ui74
-rw-r--r--src/leap/bitmask/gui/ui/mail_status.ui6
-rw-r--r--src/leap/bitmask/gui/ui/mainwindow.ui55
-rw-r--r--src/leap/bitmask/gui/ui/preferences.ui4
-rw-r--r--src/leap/bitmask/gui/ui/wizard.ui126
-rw-r--r--src/leap/bitmask/gui/wizard.py21
-rw-r--r--src/leap/bitmask/platform_init/__init__.py6
-rw-r--r--src/leap/bitmask/platform_init/initializers.py1
-rw-r--r--src/leap/bitmask/services/eip/conductor.py31
-rw-r--r--src/leap/bitmask/services/eip/eipconfig.py2
-rw-r--r--src/leap/bitmask/services/eip/vpnprocess.py31
-rw-r--r--src/leap/bitmask/util/autostart.py70
-rw-r--r--src/leap/bitmask/util/credentials.py4
-rw-r--r--src/leap/bitmask/util/keyring_helpers.py13
36 files changed, 1126 insertions, 490 deletions
diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py
index 88f6bc15..ad886bc4 100644
--- a/src/leap/bitmask/app.py
+++ b/src/leap/bitmask/app.py
@@ -180,12 +180,15 @@ def start_app():
flags_dict = flags_to_dict()
- backend = lambda: run_backend(opts.danger, flags_dict)
+ frontend_pid = os.getpid()
+ backend = lambda: run_backend(opts.danger, flags_dict, frontend_pid)
backend_process = multiprocessing.Process(target=backend, name='Backend')
- backend_process.daemon = True
+ # we don't set the 'daemon mode' since we need to start child processes in
+ # the backend
+ # backend_process.daemon = True
backend_process.start()
- run_frontend(options, flags_dict)
+ run_frontend(options, flags_dict, backend_pid=backend_process.pid)
if __name__ == "__main__":
diff --git a/src/leap/bitmask/backend/api.py b/src/leap/bitmask/backend/api.py
index b8533f36..3f6c0ad1 100644
--- a/src/leap/bitmask/backend/api.py
+++ b/src/leap/bitmask/backend/api.py
@@ -18,10 +18,14 @@
Backend available API and SIGNALS definition.
"""
STOP_REQUEST = "stop"
+PING_REQUEST = "PING"
+
+# XXX this needs documentation. What is it used for?
API = (
STOP_REQUEST, # this method needs to be defined in order to support the
# backend stop action
+ PING_REQUEST,
"eip_can_start",
"eip_cancel_setup",
@@ -54,6 +58,7 @@ API = (
"soledad_close",
"soledad_load_offline",
"tear_fw_down",
+ "bitmask_root_vpn_down",
"user_cancel_login",
"user_change_password",
"user_get_logged_in_status",
@@ -95,6 +100,7 @@ SIGNALS = (
"eip_status_changed",
"eip_stopped",
"eip_tear_fw_down",
+ "eip_bitmask_root_vpn_down",
"eip_uninitialized_provider",
"eip_vpn_launcher_exception",
"imap_stopped",
diff --git a/src/leap/bitmask/backend/backend.py b/src/leap/bitmask/backend/backend.py
index 833f4368..37535f37 100644
--- a/src/leap/bitmask/backend/backend.py
+++ b/src/leap/bitmask/backend/backend.py
@@ -14,16 +14,23 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# FIXME this is missing module documentation. It would be fine to say a couple
+# of lines about the whole backend architecture.
+# TODO use txzmq bindings instead.
+
import json
import threading
import time
+import psutil
+
from twisted.internet import defer, reactor, threads
import zmq
from zmq.auth.thread import ThreadAuthenticator
-from leap.bitmask.backend.api import API
+from leap.bitmask.backend.api import API, PING_REQUEST
from leap.bitmask.backend.utils import get_backend_certificates
from leap.bitmask.backend.signaler import Signaler
@@ -36,15 +43,23 @@ class Backend(object):
Backend server.
Receives signals from backend_proxy and emit signals if needed.
"""
+ # XXX this should not be hardcoded. Make it configurable.
PORT = '5556'
+
+ # XXX we might want to make this configurable per-platform,
+ # and use the most performant socket type on each one.
BIND_ADDR = "tcp://127.0.0.1:%s" % PORT
- def __init__(self):
+ PING_INTERVAL = 2 # secs
+
+ def __init__(self, frontend_pid=None):
"""
Backend constructor, create needed instances.
"""
self._signaler = Signaler()
+ self._frontend_pid = frontend_pid
+
self._do_work = threading.Event() # used to stop the worker thread.
self._zmq_socket = None
@@ -61,6 +76,7 @@ class Backend(object):
# Start an authenticator for this context.
auth = ThreadAuthenticator(context)
auth.start()
+ # XXX do not hardcode this here.
auth.allow('127.0.0.1')
# Tell authenticator to use the certificate in a directory
@@ -81,6 +97,8 @@ class Backend(object):
Note: we use a simple while since is less resource consuming than a
Twisted's LoopingCall.
"""
+ pid = self._frontend_pid
+ check_wait = 0
while self._do_work.is_set():
# Wait for next request from client
try:
@@ -93,6 +111,20 @@ class Backend(object):
raise
time.sleep(0.01)
+ check_wait += 0.01
+ if pid is not None and check_wait > self.PING_INTERVAL:
+ check_wait = 0
+ self._check_frontend_alive()
+
+ def _check_frontend_alive(self):
+ """
+ Check if the frontend is alive and stop the backend if it is not.
+ """
+ pid = self._frontend_pid
+ if pid is not None and not psutil.pid_exists(pid):
+ logger.critical("The frontend is down!")
+ self.stop()
+
def _stop_reactor(self):
"""
Stop the Twisted reactor, but first wait a little for some threads to
@@ -146,6 +178,10 @@ class Backend(object):
:param request_json: a json specification of a request.
:type request_json: str
"""
+ if request_json == PING_REQUEST:
+ # do not process request if it's just a ping
+ return
+
try:
# request = zmq.utils.jsonapi.loads(request_json)
# We use stdlib's json to ensure that we get unicode strings
diff --git a/src/leap/bitmask/backend/backend_proxy.py b/src/leap/bitmask/backend/backend_proxy.py
index f683e465..e2611251 100644
--- a/src/leap/bitmask/backend/backend_proxy.py
+++ b/src/leap/bitmask/backend/backend_proxy.py
@@ -18,6 +18,8 @@
The BackendProxy handles calls from the GUI and forwards (through ZMQ)
to the backend.
"""
+# XXX should document the relationship to the API here.
+
import functools
import Queue
import threading
@@ -25,7 +27,7 @@ import time
import zmq
-from leap.bitmask.backend.api import API, STOP_REQUEST
+from leap.bitmask.backend.api import API, STOP_REQUEST, PING_REQUEST
from leap.bitmask.backend.utils import get_backend_certificates
import logging
@@ -37,9 +39,15 @@ class BackendProxy(object):
The BackendProxy handles calls from the GUI and forwards (through ZMQ)
to the backend.
"""
+
PORT = '5556'
SERVER = "tcp://localhost:%s" % PORT
+ POLL_TIMEOUT = 4000 # ms
+ POLL_TRIES = 3
+
+ PING_INTERVAL = 2 # secs
+
def __init__(self):
self._socket = None
@@ -62,6 +70,9 @@ class BackendProxy(object):
socket.connect(self.SERVER)
self._socket = socket
+ self._ping_at = 0
+ self.online = False
+
self._call_queue = Queue.Queue()
self._worker_caller = threading.Thread(target=self._worker)
self._worker_caller.start()
@@ -82,9 +93,26 @@ class BackendProxy(object):
except Queue.Empty:
pass
time.sleep(0.01)
+ self._ping()
logger.debug("BackendProxy worker stopped.")
+ def _reset_ping(self):
+ """
+ Reset the ping timeout counter.
+ This is called for every ping and request.
+ """
+ self._ping_at = time.time() + self.PING_INTERVAL
+
+ def _ping(self):
+ """
+ Heartbeat helper.
+ Sends a PING request just to know that the server is alive.
+ """
+ if time.time() > self._ping_at:
+ self._send_request(PING_REQUEST)
+ self._reset_ping()
+
def _api_call(self, *args, **kwargs):
"""
Call the `api_method` method in backend (through zmq).
@@ -134,16 +162,35 @@ class BackendProxy(object):
# logger.debug("Sending request to backend: {0}".format(request))
self._socket.send(request)
- try:
- # Get the reply.
- self._socket.recv()
- # response = self._socket.recv()
- # msg = "Received reply for '{0}' -> '{1}'"
- # msg = msg.format(request, response)
- # logger.debug(msg)
- except zmq.error.Again as e:
- msg = "Timeout error contacting backend. {0!r}".format(e)
+ poll = zmq.Poller()
+ poll.register(self._socket, zmq.POLLIN)
+
+ reply = None
+ tries = 0
+
+ while True:
+ socks = dict(poll.poll(self.POLL_TIMEOUT))
+ if socks.get(self._socket) == zmq.POLLIN:
+ reply = self._socket.recv()
+ break
+
+ tries += 1
+ if tries < self.POLL_TRIES:
+ logger.warning('Retrying receive... {0}/{1}'.format(
+ tries, self.POLL_TRIES))
+ else:
+ break
+
+ if reply is None:
+ msg = "Timeout error contacting backend."
logger.critical(msg)
+ self.online = False
+ else:
+ # msg = "Received reply for '{0}' -> '{1}'".format(request, reply)
+ # logger.debug(msg)
+ self.online = True
+ # request received, no ping needed for other interval.
+ self._reset_ping()
def __getattribute__(self, name):
"""
diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py
index b372db89..f721086b 100644
--- a/src/leap/bitmask/backend/components.py
+++ b/src/leap/bitmask/backend/components.py
@@ -69,7 +69,6 @@ class ILEAPComponent(zope.interface.Interface):
"""
Interface that every component for the backend should comply to
"""
-
key = zope.interface.Attribute("Key id for this component")
@@ -552,6 +551,12 @@ class EIP(object):
"""
self._vpn.tear_down_firewall()
+ def bitmask_root_vpn_down(self):
+ """
+ Bring openvpn down, using bitmask-root helper.
+ """
+ self._vpn.bitmask_root_vpn_down()
+
def get_gateways_list(self, domain):
"""
Signal a list of gateways for the given provider.
diff --git a/src/leap/bitmask/backend/leapbackend.py b/src/leap/bitmask/backend/leapbackend.py
index d3c4fcda..3b023563 100644
--- a/src/leap/bitmask/backend/leapbackend.py
+++ b/src/leap/bitmask/backend/leapbackend.py
@@ -36,11 +36,11 @@ class LeapBackend(Backend):
"""
Backend server subclass, used to implement the API methods.
"""
- def __init__(self, bypass_checks=False):
+ def __init__(self, bypass_checks=False, frontend_pid=None):
"""
Constructor for the backend.
"""
- Backend.__init__(self)
+ Backend.__init__(self, frontend_pid)
self._settings = Settings()
@@ -317,6 +317,12 @@ class LeapBackend(Backend):
"""
self._eip.tear_fw_down()
+ def bitmask_root_vpn_down(self):
+ """
+ Signal the need to bring vpn down.
+ """
+ self._eip.bitmask_root_vpn_down()
+
def user_login(self, provider, username, password):
"""
Execute the whole authentication process for a user
diff --git a/src/leap/bitmask/backend/leapsignaler.py b/src/leap/bitmask/backend/leapsignaler.py
index a36e6fdc..c0fdffdc 100644
--- a/src/leap/bitmask/backend/leapsignaler.py
+++ b/src/leap/bitmask/backend/leapsignaler.py
@@ -58,6 +58,7 @@ class LeapSignaler(SignalerQt):
eip_status_changed = QtCore.Signal(dict)
eip_stopped = QtCore.Signal()
eip_tear_fw_down = QtCore.Signal(object)
+ eip_bitmask_root_vpn_down = QtCore.Signal(object)
eip_uninitialized_provider = QtCore.Signal()
eip_vpn_launcher_exception = QtCore.Signal()
diff --git a/src/leap/bitmask/backend/utils.py b/src/leap/bitmask/backend/utils.py
index 54a16fd7..65bf6753 100644
--- a/src/leap/bitmask/backend/utils.py
+++ b/src/leap/bitmask/backend/utils.py
@@ -19,6 +19,7 @@ Backend utilities to handle ZMQ certificates.
"""
import os
import shutil
+import stat
import zmq.auth
@@ -36,6 +37,8 @@ def generate_certificates():
if os.path.exists(KEYS_DIR):
shutil.rmtree(KEYS_DIR)
mkdir_p(KEYS_DIR)
+ # set permissions to: 0700 (U:rwx G:--- O:---)
+ os.chmod(KEYS_DIR, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
# create new keys in certificates dir
# public_file, secret_file = create_certificates(...)
diff --git a/src/leap/bitmask/backend_app.py b/src/leap/bitmask/backend_app.py
index 5c0e4803..716ae4a7 100644
--- a/src/leap/bitmask/backend_app.py
+++ b/src/leap/bitmask/backend_app.py
@@ -44,7 +44,7 @@ def signal_handler(signum, frame):
logger.debug("{0}: SIGNAL #{1} catched.".format(pname, signum))
-def run_backend(bypass_checks, flags_dict):
+def run_backend(bypass_checks, flags_dict, frontend_pid=None):
"""
Run the backend for the application.
@@ -59,5 +59,6 @@ def run_backend(bypass_checks, flags_dict):
dict_to_flags(flags_dict)
- backend = LeapBackend(bypass_checks=bypass_checks)
+ backend = LeapBackend(bypass_checks=bypass_checks,
+ frontend_pid=frontend_pid)
backend.run()
diff --git a/src/leap/bitmask/config/providerconfig.py b/src/leap/bitmask/config/providerconfig.py
index 7b979e61..57bc3a98 100644
--- a/src/leap/bitmask/config/providerconfig.py
+++ b/src/leap/bitmask/config/providerconfig.py
@@ -22,6 +22,7 @@ import logging
import os
from leap.bitmask import provider
+from leap.bitmask.config import flags
from leap.bitmask.config.provider_spec import leap_provider_spec
from leap.bitmask.services import get_service_display_name
from leap.bitmask.util import get_path_prefix
@@ -43,6 +44,7 @@ class ProviderConfig(BaseConfig):
Provider configuration abstraction class
"""
def __init__(self):
+ self.standalone = flags.STANDALONE
BaseConfig.__init__(self)
def get_light_config(self, domain, lang=None):
diff --git a/src/leap/bitmask/frontend_app.py b/src/leap/bitmask/frontend_app.py
index 51607d0b..5ea89fc9 100644
--- a/src/leap/bitmask/frontend_app.py
+++ b/src/leap/bitmask/frontend_app.py
@@ -54,7 +54,7 @@ def signal_handler(window, pid, signum, frame):
window.quit()
-def run_frontend(options, flags_dict):
+def run_frontend(options, flags_dict, backend_pid):
"""
Run the GUI for the application.
@@ -102,7 +102,7 @@ def run_frontend(options, flags_dict):
timer.start(500) # You may change this if you wish.
timer.timeout.connect(lambda: None) # Let the interpreter run each 500 ms.
- window = MainWindow(start_hidden=start_hidden)
+ window = MainWindow(start_hidden=start_hidden, backend_pid=backend_pid)
my_pid = os.getpid()
sig_handler = partial(signal_handler, window, my_pid)
diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py
index a707050a..abd6e2c9 100644
--- a/src/leap/bitmask/gui/eip_status.py
+++ b/src/leap/bitmask/gui/eip_status.py
@@ -70,8 +70,11 @@ class EIPStatusWidget(QtGui.QWidget):
self.ui.eip_bandwidth.hide()
self.hide_fw_down_button()
+ self.hide_eip_cancel_button()
self.ui.btnFwDown.clicked.connect(
self._on_fw_down_button_clicked)
+ self.ui.btnEipCancel.clicked.connect(
+ self._on_eip_cancel_button_clicked)
# Set the EIP status icons
self.CONNECTING_ICON = None
@@ -88,6 +91,7 @@ class EIPStatusWidget(QtGui.QWidget):
self._provider = ""
self.is_restart = False
self.is_cold_start = True
+ self.user_cancelled = False
self.missing_helpers = False
@@ -210,15 +214,15 @@ class EIPStatusWidget(QtGui.QWidget):
WIN : light icons
"""
EIP_ICONS = EIP_ICONS_TRAY = (
- ":/images/black/32/wait.png",
- ":/images/black/32/on.png",
- ":/images/black/32/off.png")
+ ":/images/black/22/wait.png",
+ ":/images/black/22/on.png",
+ ":/images/black/22/off.png")
if IS_LINUX:
EIP_ICONS_TRAY = (
- ":/images/white/32/wait.png",
- ":/images/white/32/on.png",
- ":/images/white/32/off.png")
+ ":/images/white/22/wait.png",
+ ":/images/white/22/on.png",
+ ":/images/white/22/off.png")
self.CONNECTING_ICON = QtGui.QPixmap(EIP_ICONS[0])
self.CONNECTED_ICON = QtGui.QPixmap(EIP_ICONS[1])
@@ -285,6 +289,8 @@ class EIPStatusWidget(QtGui.QWidget):
Triggered when the app activates eip.
Disables the start/stop button.
"""
+ # XXX hack -- we show the cancel button instead.
+ self.ui.btnEipStartStop.hide()
self.set_startstop_enabled(False)
msg = self.tr("Encrypted Internet is starting")
self.set_eip_message(msg)
@@ -295,6 +301,9 @@ class EIPStatusWidget(QtGui.QWidget):
Triggered when a default provider_config has not been found.
Disables the start button and adds instructions to the user.
"""
+ # XXX this name is unfortunate. "disable" is also applied to a
+ # pushbutton being grayed out.
+
logger.debug('Hiding EIP start button')
# you might be tempted to change this for a .setEnabled(False).
# it won't work. it's under the claws of the state machine.
@@ -327,6 +336,7 @@ class EIPStatusWidget(QtGui.QWidget):
"""
# logger.debug('Showing EIP start button')
self.eip_button.show()
+ self.hide_eip_cancel_button()
# Restore the eip action menu
menu = self._systray.contextMenu()
@@ -357,6 +367,7 @@ class EIPStatusWidget(QtGui.QWidget):
leap_assert_type(error, bool)
if error:
logger.error(status)
+ self.hide_eip_cancel_button()
else:
logger.debug(status)
self._eip_status = status
@@ -420,6 +431,28 @@ class EIPStatusWidget(QtGui.QWidget):
self.set_eip_message(msg)
self.set_eip_status("")
+ def hide_eip_cancel_button(self):
+ """
+ Hide eip-cancel button.
+ """
+ self.ui.btnEipCancel.hide()
+
+ def show_eip_cancel_button(self):
+ """
+ Show eip-cancel button.
+ """
+ self.ui.btnEipCancel.show()
+ self.user_cancelled = False
+
+ def _on_eip_cancel_button_clicked(self):
+ """
+ Call backend to kill the openvpn process with root privileges.
+ """
+ self.eip_conductor.cancelled = True
+ self.eip_conductor._backend.bitmask_root_vpn_down()
+ self.user_cancelled = True
+ self.hide_eip_cancel_button()
+
@QtCore.Slot(dict)
def eip_stopped(self, restart=False, failed=False):
"""
@@ -433,6 +466,11 @@ class EIPStatusWidget(QtGui.QWidget):
self._reset_traffic_rates()
self.ui.eip_bandwidth.hide()
+ if self.user_cancelled:
+ self.eip_conductor._backend.tear_fw_down()
+ self.eip_button.show()
+ failed = False
+
# This is assuming the firewall works correctly, but we should test fw
# status positively.
# Or better call it from the conductor...
@@ -447,6 +485,7 @@ class EIPStatusWidget(QtGui.QWidget):
msg = failed_msg
else:
msg = clear_traffic
+
self.set_eip_message(msg)
self.ui.lblEIPStatus.show()
self.show()
@@ -523,16 +562,19 @@ class EIPStatusWidget(QtGui.QWidget):
self.eipconnection.qtsigs.connected_signal.emit()
self._on_eip_connected()
self.is_cold_start = False
+ self.hide_eip_cancel_button()
+ self.eip_button.show()
# XXX should lookup vpn_state map in EIPConnection
elif vpn_state == "AUTH":
self.set_eip_status(self.tr("Authenticating..."))
+
+ # XXX should be handled by a future state machine instead.
+ self.show_eip_cancel_button()
# we wipe up any previous error info in the EIP message
# when we detect vpn authentication is happening
msg = self.tr("Encrypted Internet is starting")
self.set_eip_message(msg)
- # on the first-run path, we hadn't showed the button yet.
- self.eip_button.show()
elif vpn_state == "GET_CONFIG":
self.set_eip_status(self.tr("Retrieving configuration..."))
elif vpn_state == "WAIT":
@@ -682,9 +724,17 @@ class EIPStatusWidget(QtGui.QWidget):
def _on_eip_vpn_launcher_exception(self):
# XXX We should implement again translatable exceptions so
# we can pass a translatable string to the panel (usermessage attr)
- self.set_eip_status("VPN Launcher error.", error=True)
+ # FIXME this logic should belong to the backend, not to this
+ # widget.
self.set_eipstatus_off()
+ st = self.tr("VPN Launcher error. See the logs for more info.")
+ self.set_eip_status(st, error=True)
+
+ msg = self.tr("Encrypted Internet failed to start")
+ self.set_eip_message(msg)
+ self.show_fw_down_button()
+
self.aborted()
def _on_eip_no_polkit_agent_error(self):
@@ -743,6 +793,3 @@ class EIPStatusWidget(QtGui.QWidget):
"""
self.set_eip_status("", error=error)
self.set_eip_status_icon("error")
-
-import eipstatus_rc
-assert(eipstatus_rc)
diff --git a/src/leap/bitmask/gui/loggerwindow.py b/src/leap/bitmask/gui/loggerwindow.py
index 3a8354b1..360dd5f0 100644
--- a/src/leap/bitmask/gui/loggerwindow.py
+++ b/src/leap/bitmask/gui/loggerwindow.py
@@ -18,11 +18,10 @@
"""
History log window
"""
-import logging
import cgi
+import logging
from PySide import QtCore, QtGui
-from twisted.internet import threads
from ui_loggerwindow import Ui_LoggerWindow
@@ -38,17 +37,17 @@ class LoggerWindow(QtGui.QDialog):
"""
Window that displays a history of the logged messages in the app.
"""
- def __init__(self, handler):
+ _paste_ok = QtCore.Signal(object)
+ _paste_error = QtCore.Signal(object)
+
+ def __init__(self, parent, handler):
"""
Initialize the widget with the custom handler.
:param handler: Custom handler that supports history and signal.
:type handler: LeapLogHandler.
"""
- from twisted.internet import reactor
- self.reactor = reactor
-
- QtGui.QDialog.__init__(self)
+ QtGui.QDialog.__init__(self, parent)
leap_assert(handler, "We need a handler for the logger window")
leap_assert_type(handler, LeapLogHandler)
@@ -67,6 +66,9 @@ class LoggerWindow(QtGui.QDialog):
self.ui.cbCaseInsensitive.stateChanged.connect(self._load_history)
self.ui.btnPastebin.clicked.connect(self._pastebin_this)
+ self._paste_ok.connect(self._pastebin_ok)
+ self._paste_error.connect(self._pastebin_err)
+
self._current_filter = ""
self._current_history = ""
@@ -193,6 +195,45 @@ class LoggerWindow(QtGui.QDialog):
self.ui.btnPastebin.setText(self.tr("Send to Pastebin.com"))
self.ui.btnPastebin.setEnabled(True)
+ def _pastebin_ok(self, link):
+ """
+ Handle a successful paste.
+
+ :param link: the recently created pastebin link.
+ :type link: str
+ """
+ self._set_pastebin_sending(False)
+ msg = self.tr("Your pastebin link <a href='{0}'>{0}</a>")
+ msg = msg.format(link)
+
+ # We save the dialog in an instance member to avoid dialog being
+ # deleted right after we exit this method
+ self._msgBox = msgBox = QtGui.QMessageBox(
+ QtGui.QMessageBox.Information, self.tr("Pastebin OK"), msg)
+ msgBox.setWindowModality(QtCore.Qt.NonModal)
+ msgBox.show()
+
+ def _pastebin_err(self, failure):
+ """
+ Handle a failure in paste.
+
+ :param failure: the exception that made the paste fail.
+ :type failure: Exception
+ """
+ self._set_pastebin_sending(False)
+ logger.error(repr(failure))
+
+ msg = self.tr("Sending logs to Pastebin failed!")
+ if isinstance(failure, pastebin.PostLimitError):
+ msg = self.tr('Maximum posts per day reached')
+
+ # We save the dialog in an instance member to avoid dialog being
+ # deleted right after we exit this method
+ self._msgBox = msgBox = QtGui.QMessageBox(
+ QtGui.QMessageBox.Critical, self.tr("Pastebin Error"), msg)
+ msgBox.setWindowModality(QtCore.Qt.NonModal)
+ msgBox.show()
+
def _pastebin_this(self):
"""
Send the current log history to pastebin.com and gives the user a link
@@ -204,55 +245,19 @@ class LoggerWindow(QtGui.QDialog):
"""
content = self._current_history
pb = pastebin.PastebinAPI()
- link = pb.paste(PASTEBIN_API_DEV_KEY, content,
- paste_name="Bitmask log",
- paste_expire_date='1M')
-
- # convert to 'raw' link
- link = "http://pastebin.com/raw.php?i=" + link.split('/')[-1]
-
- return link
-
- def pastebin_ok(link):
- """
- Callback handler for `do_pastebin`.
-
- :param link: the recently created pastebin link.
- :type link: str
- """
- self._set_pastebin_sending(False)
- msg = self.tr("Your pastebin link <a href='{0}'>{0}</a>")
- msg = msg.format(link)
-
- # We save the dialog in an instance member to avoid dialog being
- # deleted right after we exit this method
- self._msgBox = msgBox = QtGui.QMessageBox(
- QtGui.QMessageBox.Information, self.tr("Pastebin OK"), msg)
- msgBox.setWindowModality(QtCore.Qt.NonModal)
- msgBox.show()
-
- def pastebin_err(failure):
- """
- Errback handler for `do_pastebin`.
-
- :param failure: the failure that triggered the errback.
- :type failure: twisted.python.failure.Failure
- """
- self._set_pastebin_sending(False)
- logger.error(repr(failure))
-
- msg = self.tr("Sending logs to Pastebin failed!")
- if failure.check(pastebin.PostLimitError):
- msg = self.tr('Maximum posts per day reached')
+ try:
+ link = pb.paste(PASTEBIN_API_DEV_KEY, content,
+ paste_name="Bitmask log",
+ paste_expire_date='1M')
+ # convert to 'raw' link
+ link = "http://pastebin.com/raw.php?i=" + link.split('/')[-1]
- # We save the dialog in an instance member to avoid dialog being
- # deleted right after we exit this method
- self._msgBox = msgBox = QtGui.QMessageBox(
- QtGui.QMessageBox.Critical, self.tr("Pastebin Error"), msg)
- msgBox.setWindowModality(QtCore.Qt.NonModal)
- msgBox.show()
+ self._paste_ok.emit(link)
+ except Exception as e:
+ self._paste_error.emit(e)
self._set_pastebin_sending(True)
- d = threads.deferToThread(do_pastebin)
- d.addCallback(pastebin_ok)
- d.addErrback(pastebin_err)
+
+ self._paste_thread = QtCore.QThread()
+ self._paste_thread.run = lambda: do_pastebin()
+ self._paste_thread.start()
diff --git a/src/leap/bitmask/gui/login.py b/src/leap/bitmask/gui/login.py
index f66e71d9..2a79fafd 100644
--- a/src/leap/bitmask/gui/login.py
+++ b/src/leap/bitmask/gui/login.py
@@ -43,10 +43,6 @@ class LoginWidget(QtGui.QWidget):
cancel_login = QtCore.Signal()
logout = QtCore.Signal()
- # Emitted when the user selects "Other..." in the provider
- # combobox or click "Create Account"
- show_wizard = QtCore.Signal()
-
MAX_STATUS_WIDTH = 40
# Keyring
@@ -64,7 +60,6 @@ class LoginWidget(QtGui.QWidget):
QtGui.QWidget.__init__(self, parent)
self._settings = settings
- self._selected_provider_index = -1
self.ui = Ui_LoginWidget()
self.ui.setupUi(self)
@@ -80,9 +75,6 @@ class LoginWidget(QtGui.QWidget):
self.ui.lnUser.returnPressed.connect(self._focus_password)
- self.ui.cmbProviders.currentIndexChanged.connect(
- self._current_provider_changed)
-
self.ui.btnLogout.clicked.connect(
self.logout)
@@ -111,35 +103,6 @@ class LoginWidget(QtGui.QWidget):
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 + [self.tr("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
@@ -216,8 +179,7 @@ class LoginWidget(QtGui.QWidget):
"""
self.ui.lnUser.setEnabled(enabled)
self.ui.lnPassword.setEnabled(enabled)
- self.ui.chkRemember.setEnabled(enabled)
- self.ui.cmbProviders.setEnabled(enabled)
+ self.ui.chkRemember.setEnabled(enabled and has_keyring())
self._set_cancel(not enabled)
@@ -258,41 +220,20 @@ class LoginWidget(QtGui.QWidget):
"""
self.ui.lnPassword.setFocus()
- @QtCore.Slot(int)
- def _current_provider_changed(self, idx):
- """
- TRIGGERS:
- self.ui.cmbProviders.currentIndexChanged
-
- :param idx: the index of the new selected item
- :type idx: int
- """
- if idx == (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 = idx
-
- def start_login(self):
+ def start_login(self, provider):
"""
Setups the login widgets for actually performing the login and
performs some basic checks.
+ :param provider: the domain of the current provider
+ :type provider: unicode str
:returns: True if everything's good to go, False otherwise
:rtype: bool
"""
username = self.get_user()
password = self.get_password()
- provider = self.get_selected_provider()
- self._enabled_services = self._settings.get_enabled_services(
- self.get_selected_provider())
+ self._enabled_services = self._settings.get_enabled_services(provider)
if len(provider) == 0:
self.set_status(
@@ -331,14 +272,16 @@ class LoginWidget(QtGui.QWidget):
% (e,))
return True
- def logged_in(self):
+ def logged_in(self, provider):
"""
Sets the widgets to the logged in state
+
+ :param provider: the domain of the current provider
+ :type provider: unicode str
"""
self.ui.login_widget.hide()
self.ui.logged_widget.show()
- self.ui.lblUser.setText(make_address(
- self.get_user(), self.get_selected_provider()))
+ self.ui.lblUser.setText(make_address(self.get_user(), provider))
if flags.OFFLINE is False:
self.logged_in_signal.emit()
diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py
index bb755b5c..d523f449 100644
--- a/src/leap/bitmask/gui/mail_status.py
+++ b/src/leap/bitmask/gui/mail_status.py
@@ -134,15 +134,15 @@ class MailStatusWidget(QtGui.QWidget):
WIN : light icons
"""
EIP_ICONS = EIP_ICONS_TRAY = (
- ":/images/black/32/wait.png",
- ":/images/black/32/on.png",
- ":/images/black/32/off.png")
+ ":/images/black/22/wait.png",
+ ":/images/black/22/on.png",
+ ":/images/black/22/off.png")
if IS_LINUX:
EIP_ICONS_TRAY = (
- ":/images/white/32/wait.png",
- ":/images/white/32/on.png",
- ":/images/white/32/off.png")
+ ":/images/white/22/wait.png",
+ ":/images/white/22/on.png",
+ ":/images/white/22/off.png")
self.CONNECTING_ICON = QtGui.QPixmap(EIP_ICONS[0])
self.CONNECTED_ICON = QtGui.QPixmap(EIP_ICONS[1])
diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py
index 6959650b..8ce7f2fc 100644
--- a/src/leap/bitmask/gui/mainwindow.py
+++ b/src/leap/bitmask/gui/mainwindow.py
@@ -22,6 +22,8 @@ import time
from datetime import datetime
+import psutil
+
from PySide import QtCore, QtGui
from leap.bitmask import __version__ as VERSION
@@ -43,6 +45,7 @@ from leap.bitmask.gui.mail_status import MailStatusWidget
from leap.bitmask.gui.preferenceswindow import PreferencesWindow
from leap.bitmask.gui.systray import SysTray
from leap.bitmask.gui.wizard import Wizard
+from leap.bitmask.gui.providers import Providers
from leap.bitmask.platform_init import IS_WIN, IS_MAC, IS_LINUX
from leap.bitmask.platform_init.initializers import init_platform
@@ -56,7 +59,7 @@ from leap.bitmask.services.mail import conductor as mail_conductor
from leap.bitmask.services import EIP_SERVICE, MX_SERVICE
-from leap.bitmask.util import make_address
+from leap.bitmask.util import autostart, make_address
from leap.bitmask.util.keyring_helpers import has_keyring
from leap.bitmask.logs.leap_log_handler import LeapLogHandler
@@ -95,9 +98,9 @@ class MainWindow(QtGui.QMainWindow):
EIP_START_TIMEOUT = 60000 # in milliseconds
# We give the services some time to a halt before forcing quit.
- SERVICES_STOP_TIMEOUT = 20000 # in milliseconds
+ SERVICES_STOP_TIMEOUT = 3000 # in milliseconds
- def __init__(self, start_hidden=False):
+ def __init__(self, start_hidden=False, backend_pid=None):
"""
Constructor for the client main window
@@ -106,6 +109,7 @@ class MainWindow(QtGui.QMainWindow):
:type start_hidden: bool
"""
QtGui.QMainWindow.__init__(self)
+ autostart.set_autostart(True)
# register leap events ########################################
register(signal=proto.UPDATER_NEW_UPDATES,
@@ -125,6 +129,11 @@ class MainWindow(QtGui.QMainWindow):
self._backend = BackendProxy()
+ # periodically check if the backend is alive
+ self._backend_checker = QtCore.QTimer(self)
+ self._backend_checker.timeout.connect(self._check_backend_status)
+ self._backend_checker.start(2000)
+
self._leap_signaler = LeapSignaler()
self._leap_signaler.start()
@@ -140,14 +149,18 @@ class MainWindow(QtGui.QMainWindow):
self._mail_status = MailStatusWidget(self)
self.ui.mailLayout.addWidget(self._mail_status)
+ # Provider List
+ self._providers = Providers(self.ui.cmbProviders)
+
# Qt Signal Connections #####################################
# TODO separate logic from ui signals.
self._login_widget.login.connect(self._login)
self._login_widget.cancel_login.connect(self._cancel_login)
- self._login_widget.show_wizard.connect(self._launch_wizard)
self._login_widget.logout.connect(self._logout)
+ self._providers.connect_provider_changed(self._on_provider_changed)
+
# EIP Control redux #########################################
self._eip_conductor = eip_conductor.EIPConductor(
self._settings, self._backend, self._leap_signaler)
@@ -158,11 +171,16 @@ class MainWindow(QtGui.QMainWindow):
self._disable_eip_missing_helpers)
self.ui.eipLayout.addWidget(self._eip_status)
+
+ # XXX we should get rid of the circular refs
+ # conductor <-> status, right now keeping state on the widget ifself.
self._eip_conductor.add_eip_widget(self._eip_status)
self._eip_conductor.connect_signals()
self._eip_conductor.qtsigs.connected_signal.connect(
self._on_eip_connection_connected)
+ self._eip_conductor.qtsigs.disconnected_signal.connect(
+ self._on_eip_connection_disconnected)
self._eip_conductor.qtsigs.connected_signal.connect(
self._maybe_run_soledad_setup_checks)
@@ -172,10 +190,10 @@ class MainWindow(QtGui.QMainWindow):
self.eip_needs_login.connect(self._eip_status.disable_eip_start)
self.eip_needs_login.connect(self._disable_eip_start_action)
+ # XXX all this info about state should move to eip conductor too
self._already_started_eip = False
self._trying_to_start_eip = False
- self._already_started_eip = False
self._soledad_started = False
# This is created once we have a valid provider config
@@ -189,6 +207,7 @@ class MainWindow(QtGui.QMainWindow):
# used to know if we are in the final steps of quitting
self._quitting = False
self._finally_quitting = False
+ self._system_quit = False
self._backend_connected_signals = []
self._backend_connect()
@@ -201,8 +220,9 @@ class MainWindow(QtGui.QMainWindow):
self.ui.action_wizard.triggered.connect(self._launch_wizard)
self.ui.action_show_logs.triggered.connect(self._show_logger_window)
self.ui.action_help.triggered.connect(self._help)
+
self.ui.action_create_new_account.triggered.connect(
- self._launch_wizard)
+ self._on_provider_changed)
self.ui.action_advanced_key_management.triggered.connect(
self._show_AKM)
@@ -224,8 +244,8 @@ class MainWindow(QtGui.QMainWindow):
self._action_eip_startstop = QtGui.QAction("", self)
self._eip_status.set_action_eip_startstop(self._action_eip_startstop)
- self._action_visible = QtGui.QAction(self.tr("Hide Main Window"), self)
- self._action_visible.triggered.connect(self._toggle_visible)
+ self._action_visible = QtGui.QAction(self.tr("Show Main Window"), self)
+ self._action_visible.triggered.connect(self._ensure_visible)
# disable buttons for now, may come back later.
# self.ui.btnPreferences.clicked.connect(self._show_preferences)
@@ -264,23 +284,16 @@ class MainWindow(QtGui.QMainWindow):
self._wizard = None
self._wizard_firstrun = False
- self._logger_window = None
-
self._start_hidden = start_hidden
+ self._backend_pid = backend_pid
self._mail_conductor = mail_conductor.MailConductor(self._backend)
self._mail_conductor.connect_mail_signals(self._mail_status)
self.logout.connect(self._mail_conductor.stop_mail_services)
- # Eip machine is a public attribute where the state machine for
- # the eip connection will be available to the different components.
- # Remember that this will not live in the +1600LOC mainwindow for
- # all the eternity, so at some point we will be moving this to
- # the EIPConductor or some other clever component that we will
- # instantiate from here.
+ # start event machines from within the eip and mail conductors
- # start event machines
# TODO should encapsulate all actions into one object
self._eip_conductor.start_eip_machine(
action=self._action_eip_startstop)
@@ -334,6 +347,23 @@ class MainWindow(QtGui.QMainWindow):
logger.error("Bad call to the backend:")
logger.error(data)
+ @QtCore.Slot()
+ def _check_backend_status(self):
+ """
+ TRIGGERS:
+ self._backend_checker.timeout
+
+ Check that the backend is running. Otherwise show an error to the user.
+ """
+ online = self._backend.online
+ if not online:
+ logger.critical("Backend is not online.")
+ QtGui.QMessageBox.critical(
+ self, self.tr("Application error"),
+ self.tr("There is a problem contacting the backend, please "
+ "restart Bitmask."))
+ self._backend_checker.stop()
+
def _backend_connect(self, only_tracked=False):
"""
Connect to backend signals.
@@ -479,7 +509,6 @@ class MainWindow(QtGui.QMainWindow):
def _launch_wizard(self):
"""
TRIGGERS:
- self._login_widget.show_wizard
self.ui.action_wizard.triggered
Also called in first run.
@@ -534,20 +563,16 @@ class MainWindow(QtGui.QMainWindow):
TRIGGERS:
self.ui.action_show_logs.triggered
- Displays the window with the history of messages logged until now
+ Display 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')
- return
- else:
- self._logger_window = LoggerWindow(handler=leap_log_handler)
- self._logger_window.setVisible(
- not self._logger_window.isVisible())
+ leap_log_handler = self._get_leap_logging_handler()
+ if leap_log_handler is None:
+ logger.error('Leap logger handler not found')
+ return
else:
- self._logger_window.setVisible(not self._logger_window.isVisible())
+ lw = LoggerWindow(self, handler=leap_log_handler)
+ lw.show()
@QtCore.Slot()
def _show_AKM(self):
@@ -555,9 +580,9 @@ class MainWindow(QtGui.QMainWindow):
TRIGGERS:
self.ui.action_advanced_key_management.triggered
- Displays the Advanced Key Management dialog.
+ Display the Advanced Key Management dialog.
"""
- domain = self._login_widget.get_selected_provider()
+ domain = self._providers.get_selected_provider()
logged_user = "{0}@{1}".format(self._logged_user, domain)
details = self._provider_details
@@ -577,10 +602,10 @@ class MainWindow(QtGui.QMainWindow):
self.ui.btnPreferences.clicked (disabled for now)
self.ui.action_preferences
- Displays the preferences window.
+ Display the preferences window.
"""
user = self._logged_user
- domain = self._login_widget.get_selected_provider()
+ domain = self._providers.get_selected_provider()
mx_provided = False
if self._provider_details is not None:
mx_provided = MX_SERVICE in self._provider_details['services']
@@ -695,9 +720,9 @@ class MainWindow(QtGui.QMainWindow):
self.ui.btnEIPPreferences.clicked
self.ui.action_eip_preferences (disabled for now)
- Displays the EIP preferences window.
+ Display the EIP preferences window.
"""
- domain = self._login_widget.get_selected_provider()
+ domain = self._providers.get_selected_provider()
pref = EIPPreferencesWindow(self, domain,
self._backend, self._leap_signaler)
pref.show()
@@ -721,7 +746,7 @@ class MainWindow(QtGui.QMainWindow):
TRIGGERS:
self.new_updates
- Displays the new updates label and sets the updates_content
+ Display the new updates label and sets the updates_content
:param req: Request type
:type req: leap.common.events.events_pb2.SignalRequest
@@ -774,7 +799,7 @@ class MainWindow(QtGui.QMainWindow):
# XXX: May be this can be divided into two methods?
providers = self._settings.get_configured_providers()
- self._login_widget.set_providers(providers)
+ self._providers.set_providers(providers)
self._show_systray()
if not self._start_hidden:
@@ -790,12 +815,12 @@ class MainWindow(QtGui.QMainWindow):
# select the configured provider in the combo box
domain = self._wizard.get_domain()
- self._login_widget.select_provider_by_name(domain)
+ self._providers.select_provider_by_name(domain)
self._login_widget.set_remember(self._wizard.get_remember())
self._enabled_services = list(self._wizard.get_services())
self._settings.set_enabled_services(
- self._login_widget.get_selected_provider(),
+ self._providers.get_selected_provider(),
self._enabled_services)
if possible_username is not None:
self._login_widget.set_user(possible_username)
@@ -812,7 +837,7 @@ class MainWindow(QtGui.QMainWindow):
domain = self._settings.get_provider()
if domain is not None:
- self._login_widget.select_provider_by_name(domain)
+ self._providers.select_provider_by_name(domain)
if not self._settings.get_remember():
# nothing to do here
@@ -875,11 +900,7 @@ class MainWindow(QtGui.QMainWindow):
"""
Set the login label to reflect offline status.
"""
- provider = ""
- if not self._logged_in_offline:
- provider = self.ui.lblLoginProvider.text()
-
- self.ui.lblLoginProvider.setText(provider + self.tr(" (offline mode)"))
+ # TODO: figure out what widget to use for this. Maybe the window title?
#
# systray
@@ -936,10 +957,8 @@ class MainWindow(QtGui.QMainWindow):
:param reason: the reason why the tray got activated.
:type reason: int
- Displays the context menu from the tray icon
+ Display the context menu from the tray icon
"""
- self._update_hideshow_menu()
-
context_menu = self._systray.contextMenu()
if not IS_MAC:
# for some reason, context_menu.show()
@@ -948,48 +967,42 @@ class MainWindow(QtGui.QMainWindow):
# this works however.
context_menu.exec_(self._systray.geometry().center())
- def _update_hideshow_menu(self):
- """
- Updates the Hide/Show main window menu text based on the
- visibility of the window.
+ @QtCore.Slot()
+ def _ensure_visible(self):
"""
- get_action = lambda visible: (
- self.tr("Show Main Window"),
- self.tr("Hide Main Window"))[int(visible)]
+ TRIGGERS:
+ self._action_visible.triggered
- # set labels
- visible = self.isVisible() and self.isActiveWindow()
- self._action_visible.setText(get_action(visible))
+ Ensure that the window is visible and raised.
+ """
+ QtGui.QApplication.setQuitOnLastWindowClosed(True)
+ self.show()
+ if IS_LINUX:
+ # On ubuntu, activateWindow doesn't work reliably, so
+ # we do the following as a workaround. See
+ # https://bugreports.qt-project.org/browse/QTBUG-24932
+ # for more details
+ QtGui.QX11Info.setAppUserTime(0)
+ self.activateWindow()
+ self.raise_()
@QtCore.Slot()
- def _toggle_visible(self):
+ def _ensure_invisible(self):
"""
TRIGGERS:
self._action_visible.triggered
- Toggles the window visibility
+ Ensure that the window is hidden.
"""
- visible = self.isVisible() and self.isActiveWindow()
-
- if not visible:
- QtGui.QApplication.setQuitOnLastWindowClosed(True)
- self.show()
- self.activateWindow()
- self.raise_()
- else:
- # We set this in order to avoid dialogs shutting down the
- # app on close, as they will be the only visible window.
- # e.g.: PreferencesWindow, LoggerWindow
- QtGui.QApplication.setQuitOnLastWindowClosed(False)
- self.hide()
-
- # Wait a bit until the window visibility has changed so
- # the menu is set with the correct value.
- QtDelayedCall(500, self._update_hideshow_menu)
+ # We set this in order to avoid dialogs shutting down the
+ # app on close, as they will be the only visible window.
+ # e.g.: PreferencesWindow, LoggerWindow
+ QtGui.QApplication.setQuitOnLastWindowClosed(False)
+ self.hide()
def _center_window(self):
"""
- Centers the mainwindow based on the desktop geometry
+ Center the main window based on the desktop geometry
"""
geometry = self._settings.get_geometry()
state = self._settings.get_windowstate()
@@ -1048,22 +1061,40 @@ class MainWindow(QtGui.QMainWindow):
# TODO: don't hardcode!
smtp_port = 2013
- url = ("<a href='https://addons.mozilla.org/es/thunderbird/"
- "addon/bitmask/'>bitmask addon</a>")
-
- msg = self.tr(
- "<strong>Instructions to use mail:</strong><br>"
- "If you use Thunderbird you can use the Bitmask extension helper. "
- "Search for 'Bitmask' in the add-on manager or download it "
- "from: {0}.<br><br>"
- "You can configure Bitmask manually with these options:<br>"
- "<em>"
- " Incoming -> IMAP, port: {1}<br>"
- " Outgoing -> SMTP, port: {2}<br>"
- " Username -> your bitmask username.<br>"
- " Password -> does not matter, use any text. "
- " Just don't leave it empty and don't use your account's password."
- "</em>").format(url, IMAP_PORT, smtp_port)
+ help_url = "<p><a href='https://{0}'>{0}</a></p>".format(
+ self.tr("bitmask.net/help"))
+
+ lang = QtCore.QLocale.system().name().replace('_', '-')
+ thunderbird_extension_url = \
+ "https://addons.mozilla.org/{0}/" \
+ "thunderbird/addon/bitmask/".format(lang)
+
+ email_quick_reference = self.tr("Email quick reference")
+ thunderbird_text = self.tr(
+ "For Thunderbird, you can use the "
+ "Bitmask extension. Search for \"Bitmask\" in the add-on "
+ "manager or download it from <a href='{0}'>"
+ "addons.mozilla.org</a>.".format(thunderbird_extension_url))
+ manual_text = self.tr(
+ "Alternately, you can manually configure "
+ "your mail client to use Bitmask Email with these options:")
+ manual_imap = self.tr("IMAP: localhost, port {0}".format(IMAP_PORT))
+ manual_smtp = self.tr("SMTP: localhost, port {0}".format(smtp_port))
+ manual_username = self.tr("Username: your full email address")
+ manual_password = self.tr("Password: any non-empty text")
+
+ msg = help_url + self.tr(
+ "<p><strong>{0}</strong></p>"
+ "<p>{1}</p>"
+ "<p>{2}"
+ "<ul>"
+ "<li>&nbsp;{3}</li>"
+ "<li>&nbsp;{4}</li>"
+ "<li>&nbsp;{5}</li>"
+ "<li>&nbsp;{6}</li>"
+ "</ul></p>").format(email_quick_reference, thunderbird_text,
+ manual_text, manual_imap, manual_smtp,
+ manual_username, manual_password)
QtGui.QMessageBox.about(self, self.tr("Bitmask Help"), msg)
def _needs_update(self):
@@ -1089,26 +1120,19 @@ class MainWindow(QtGui.QMainWindow):
"Error: API version incompatible.")
QtGui.QMessageBox.warning(self, self.tr("Incompatible Provider"), msg)
- def changeEvent(self, e):
- """
- Reimplements the changeEvent method to minimize to tray
- """
- if not IS_MAC and \
- 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 not e.spontaneous():
+ # if the system requested the `close` then we should quit.
+ self._system_quit = True
+ self.quit()
+ return
+
if QtGui.QSystemTrayIcon.isSystemTrayAvailable() and \
not self._really_quit:
- self._toggle_visible()
+ self._ensure_invisible()
e.ignore()
return
@@ -1119,7 +1143,7 @@ class MainWindow(QtGui.QMainWindow):
def _first_run(self):
"""
- Returns True if there are no configured providers. False otherwise
+ Return True if there are no configured providers. False otherwise
:rtype: bool
"""
@@ -1128,13 +1152,15 @@ class MainWindow(QtGui.QMainWindow):
skip_first_run = self._settings.get_skip_first_run()
return not (has_provider_on_disk and skip_first_run)
+ @QtCore.Slot()
def _download_provider_config(self):
"""
- Starts the bootstrapping sequence. It will download the
+ Start the bootstrapping sequence. It will download the
provider configuration if it's not present, otherwise will
emit the corresponding signals inmediately
"""
- domain = self._login_widget.get_selected_provider()
+ self._disconnect_scheduled_login()
+ domain = self._providers.get_selected_provider()
self._backend.provider_setup(provider=domain)
@QtCore.Slot(dict)
@@ -1151,7 +1177,7 @@ class MainWindow(QtGui.QMainWindow):
:type data: dict
"""
if data[PASSED_KEY]:
- selected_provider = self._login_widget.get_selected_provider()
+ selected_provider = self._providers.get_selected_provider()
self._backend.provider_bootstrap(provider=selected_provider)
else:
logger.error(data[ERROR_KEY])
@@ -1160,38 +1186,122 @@ class MainWindow(QtGui.QMainWindow):
@QtCore.Slot()
def _login_problem_provider(self):
"""
- Warns the user about a problem with the provider during login.
+ Warn the user about a problem with the provider during login.
"""
+ # XXX triggers?
self._login_widget.set_status(
self.tr("Unable to login: Problem with provider"))
self._login_widget.set_enabled(True)
+ def _schedule_login(self):
+ """
+ Schedule the login sequence to go after the EIP started.
+
+ The login sequence is connected to all finishing status of EIP
+ (connected, disconnected, aborted or died) to continue with the login
+ after EIP.
+ """
+ logger.debug('Login scheduled when eip_connected is triggered')
+ eip_sigs = self._eip_conductor.qtsigs
+ eip_sigs.connected_signal.connect(self._download_provider_config)
+ eip_sigs.disconnected_signal.connect(self._download_provider_config)
+ eip_sigs.connection_aborted_signal.connect(
+ self._download_provider_config)
+ eip_sigs.connection_died_signal.connect(self._download_provider_config)
+
+ def _disconnect_scheduled_login(self):
+ """
+ Disconnect scheduled login signals if exists
+ """
+ try:
+ eip_sigs = self._eip_conductor.qtsigs
+ eip_sigs.connected_signal.disconnect(
+ self._download_provider_config)
+ eip_sigs.disconnected_signal.disconnect(
+ self._download_provider_config)
+ eip_sigs.connection_aborted_signal.disconnect(
+ self._download_provider_config)
+ eip_sigs.connection_died_signal.disconnect(
+ self._download_provider_config)
+ except Exception:
+ # signal not connected
+ pass
+
+ @QtCore.Slot(object)
+ def _on_provider_changed(self, wizard=True):
+ """
+ TRIGGERS:
+ self._providers._provider_changed
+ self.ui.action_create_new_account.triggered
+
+ Ask the user if really wants to change provider since a services stop
+ is required for that action.
+
+ :param wizard: whether the 'other...' option was picked or not.
+ :type wizard: bool
+ """
+ # TODO: we should handle the case that EIP is autostarting since we
+ # won't get a warning until EIP has fully started.
+ # TODO: we need to add a check for the mail status (smtp/imap/soledad)
+ something_runing = (self._logged_user is not None or
+ self._already_started_eip)
+ if not something_runing:
+ if wizard:
+ self._launch_wizard()
+ return
+
+ title = self.tr("Stop services")
+ text = "<b>" + self.tr("Do you want to stop all services?") + "</b>"
+ informative_text = self.tr("In order to change the provider, the "
+ "running services needs to be stopped.")
+
+ msg = QtGui.QMessageBox(self)
+ msg.setWindowTitle(title)
+ msg.setText(text)
+ msg.setInformativeText(informative_text)
+ msg.setStandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)
+ msg.setDefaultButton(QtGui.QMessageBox.No)
+ msg.setIcon(QtGui.QMessageBox.Warning)
+ res = msg.exec_()
+
+ if res == QtGui.QMessageBox.Yes:
+ self._stop_services()
+ self._eip_conductor.qtsigs.do_disconnect_signal.emit()
+ if wizard:
+ self._launch_wizard()
+ else:
+ if not wizard:
+ # if wizard, the widget restores itself
+ self._providers.restore_previous_provider()
+
@QtCore.Slot()
def _login(self):
"""
TRIGGERS:
self._login_widget.login
- Starts the login sequence. Which involves bootstrapping the
+ Start 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
"""
# TODO most of this could ve handled by the login widget,
- # but we'd have to move lblLoginProvider into the widget itself,
- # instead of having it as a top-level attribute.
+ provider = self._providers.get_selected_provider()
if flags.OFFLINE is True:
logger.debug("OFFLINE mode! bypassing remote login")
# TODO reminder, we're not handling logout for offline
# mode.
- self._login_widget.logged_in()
+ self._login_widget.logged_in(provider)
self._logged_in_offline = True
self._set_label_offline()
self.offline_mode_bypass_login.emit()
else:
self.ui.action_create_new_account.setEnabled(False)
- if self._login_widget.start_login():
- self._download_provider_config()
+ if self._login_widget.start_login(provider):
+ if self._trying_to_start_eip:
+ self._schedule_login()
+ else:
+ self._download_provider_config()
@QtCore.Slot(unicode)
def _authentication_error(self, msg):
@@ -1217,11 +1327,16 @@ class MainWindow(QtGui.QMainWindow):
TRIGGERS:
self._login_widget.cancel_login
- Stops the login sequence.
+ Stop the login sequence.
"""
logger.debug("Cancelling log in.")
+ self._disconnect_scheduled_login()
+
self._cancel_ongoing_defers()
+ # Needed in case of EIP starting and login deferer never set
+ self._set_login_cancelled()
+
def _cancel_ongoing_defers(self):
"""
Cancel the running defers to avoid app blocking.
@@ -1241,7 +1356,7 @@ class MainWindow(QtGui.QMainWindow):
Signaler.prov_cancelled_setup fired by
self._backend.provider_cancel_setup()
- This method re-enables the login widget and display a message for
+ Re-enable the login widget and display a message for
the cancelled operation.
"""
self._login_widget.set_status(self.tr("Log in cancelled by the user."))
@@ -1262,7 +1377,7 @@ class MainWindow(QtGui.QMainWindow):
self._show_hide_unsupported_services()
- domain = self._login_widget.get_selected_provider()
+ domain = self._providers.get_selected_provider()
self._backend.user_login(provider=domain,
username=username, password=password)
else:
@@ -1282,7 +1397,7 @@ class MainWindow(QtGui.QMainWindow):
self._logged_user = self._login_widget.get_user()
user = self._logged_user
- domain = self._login_widget.get_selected_provider()
+ domain = self._providers.get_selected_provider()
full_user_id = make_address(user, domain)
self._mail_conductor.userid = full_user_id
self._start_eip_bootstrap()
@@ -1301,13 +1416,12 @@ class MainWindow(QtGui.QMainWindow):
def _start_eip_bootstrap(self):
"""
- Changes the stackedWidget index to the EIP status one and
+ Change the stackedWidget index to the EIP status one and
triggers the eip bootstrapping.
"""
- self._login_widget.logged_in()
- domain = self._login_widget.get_selected_provider()
- self.ui.lblLoginProvider.setText(domain)
+ domain = self._providers.get_selected_provider()
+ self._login_widget.logged_in(domain)
self._enabled_services = self._settings.get_enabled_services(domain)
@@ -1329,7 +1443,7 @@ class MainWindow(QtGui.QMainWindow):
and enabled.
This is triggered right after the provider has been set up.
"""
- domain = self._login_widget.get_selected_provider()
+ domain = self._providers.get_selected_provider()
lang = QtCore.QLocale.system().name()
self._backend.provider_get_details(domain=domain, lang=lang)
@@ -1345,12 +1459,12 @@ class MainWindow(QtGui.QMainWindow):
def _provides_mx_and_enabled(self):
"""
- Defines if the current provider provides mx and if we have it enabled.
+ Define if the current provider provides mx and if we have it enabled.
:returns: True if provides and is enabled, False otherwise
:rtype: bool
"""
- domain = self._login_widget.get_selected_provider()
+ domain = self._providers.get_selected_provider()
enabled_services = self._settings.get_enabled_services(domain)
mx_enabled = MX_SERVICE in enabled_services
@@ -1362,12 +1476,12 @@ class MainWindow(QtGui.QMainWindow):
def _provides_eip_and_enabled(self):
"""
- Defines if the current provider provides eip and if we have it enabled.
+ Define if the current provider provides eip and if we have it enabled.
:returns: True if provides and is enabled, False otherwise
:rtype: bool
"""
- domain = self._login_widget.get_selected_provider()
+ domain = self._providers.get_selected_provider()
enabled_services = self._settings.get_enabled_services(domain)
eip_enabled = EIP_SERVICE in enabled_services
@@ -1388,7 +1502,7 @@ class MainWindow(QtGui.QMainWindow):
username = self._login_widget.get_user()
password = unicode(self._login_widget.get_password())
- provider_domain = self._login_widget.get_selected_provider()
+ provider_domain = self._providers.get_selected_provider()
if flags.OFFLINE:
full_user_id = make_address(username, provider_domain)
@@ -1404,7 +1518,7 @@ class MainWindow(QtGui.QMainWindow):
password=password, uuid=uuid)
else:
if self._logged_user is not None:
- domain = self._login_widget.get_selected_provider()
+ domain = self._providers.get_selected_provider()
self._backend.soledad_bootstrap(username=username,
domain=domain,
password=password)
@@ -1464,14 +1578,14 @@ class MainWindow(QtGui.QMainWindow):
@QtCore.Slot()
def _disable_eip_start_action(self):
"""
- Disables the EIP start action in the systray menu.
+ Disable the EIP start action in the systray menu.
"""
self._action_eip_startstop.setEnabled(False)
@QtCore.Slot()
def _enable_eip_start_action(self):
"""
- Enables the EIP start action in the systray menu.
+ Enable the EIP start action in the systray menu.
"""
self._action_eip_startstop.setEnabled(True)
self._eip_status.enable_eip_start()
@@ -1488,7 +1602,7 @@ class MainWindow(QtGui.QMainWindow):
"""
self._already_started_eip = True
- domain = self._login_widget.get_selected_provider()
+ domain = self._providers.get_selected_provider()
self._settings.set_defaultprovider(domain)
self._backend.eip_get_gateway_country_code(domain=domain)
@@ -1497,6 +1611,16 @@ class MainWindow(QtGui.QMainWindow):
self._backend.eip_check_dns(domain=domain)
@QtCore.Slot()
+ def _on_eip_connection_disconnected(self):
+ """
+ TRIGGERS:
+ self._eip_conductor.qtsigs.disconnected_signal
+
+ Set the eip status to not started.
+ """
+ self._already_started_eip = False
+
+ @QtCore.Slot()
def _set_eip_provider(self, country_code=None):
"""
TRIGGERS:
@@ -1505,7 +1629,7 @@ class MainWindow(QtGui.QMainWindow):
Set the current provider and country code in the eip status widget.
"""
- domain = self._login_widget.get_selected_provider()
+ domain = self._providers.get_selected_provider()
self._eip_status.set_provider(domain, country_code)
@QtCore.Slot()
@@ -1513,7 +1637,7 @@ class MainWindow(QtGui.QMainWindow):
"""
Trigger this if we don't have a working DNS resolver.
"""
- domain = self._login_widget.get_selected_provider()
+ domain = self._providers.get_selected_provider()
msg = self.tr(
"The server at {0} can't be found, because the DNS lookup "
"failed. DNS is the network service that translates a "
@@ -1528,7 +1652,7 @@ class MainWindow(QtGui.QMainWindow):
def _try_autostart_eip(self):
"""
- Tries to autostart EIP
+ Try to autostart EIP.
"""
settings = self._settings
default_provider = settings.get_defaultprovider()
@@ -1548,6 +1672,8 @@ class MainWindow(QtGui.QMainWindow):
:param autostart: we are autostarting EIP when this is True
:type autostart: bool
"""
+ # XXX should move to EIP conductor.
+
# during autostart we assume that the provider provides EIP
if autostart:
should_start = EIP_SERVICE in self._enabled_services
@@ -1567,9 +1693,15 @@ class MainWindow(QtGui.QMainWindow):
self._enable_eip_start_action()
self._eip_status.set_eip_status(
self.tr("Starting..."))
+ self._eip_status.show_eip_cancel_button()
+
+ # We were disabling the button, but now that we have
+ # a cancel button we just hide it. It will be visible
+ # when the connection is completed successfully.
+ self._eip_status.eip_button.hide()
self._eip_status.eip_button.setEnabled(False)
- domain = self._login_widget.get_selected_provider()
+ domain = self._providers.get_selected_provider()
self._backend.eip_setup(provider=domain)
self._already_started_eip = True
@@ -1627,8 +1759,9 @@ class MainWindow(QtGui.QMainWindow):
"""
passed = data[PASSED_KEY]
if not passed:
- self._login_widget.set_status(
- self.tr("Unable to connect: Problem with provider"))
+ self._eip_status.set_eip_status(
+ self.tr("Unable to connect: Problem with provider"),
+ error=True)
logger.error(data[ERROR_KEY])
self._already_started_eip = False
self._eip_status.aborted()
@@ -1641,7 +1774,7 @@ class MainWindow(QtGui.QMainWindow):
TRIGGERS:
self._login_widget.logout
- Starts the logout sequence
+ Start the logout sequence
"""
self._cancel_ongoing_defers()
@@ -1659,7 +1792,6 @@ class MainWindow(QtGui.QMainWindow):
Inform the user about a logout error.
"""
self._login_widget.done_logout()
- self.ui.lblLoginProvider.setText(self.tr("Login"))
self._login_widget.set_status(
self.tr("Something went wrong with the logout."))
@@ -1669,11 +1801,10 @@ class MainWindow(QtGui.QMainWindow):
TRIGGER:
self._srp_auth.logout_ok
- Switches the stackedWidget back to the login stage after
+ Switch the stackedWidget back to the login stage after
logging out
"""
self._login_widget.done_logout()
- self.ui.lblLoginProvider.setText(self.tr("Login"))
self._logged_user = None
self._login_widget.logged_out()
@@ -1690,7 +1821,7 @@ class MainWindow(QtGui.QMainWindow):
self._backend.signaler.prov_https_connection
self._backend.signaler.prov_download_ca_cert
- If there was a problem, displays it, otherwise it does nothing.
+ If there was a problem, display it, otherwise it does nothing.
This is used for intermediate bootstrapping stages, in case
they fail.
"""
@@ -1744,6 +1875,7 @@ class MainWindow(QtGui.QMainWindow):
imap_stopped = lambda: self._remove_service('imap')
self._leap_signaler.imap_stopped.connect(imap_stopped)
+ # XXX change name, already used in conductor.
eip_stopped = lambda: self._remove_service('eip')
self._leap_signaler.eip_stopped.connect(eip_stopped)
@@ -1766,11 +1898,13 @@ class MainWindow(QtGui.QMainWindow):
if self._quitting:
return
+ autostart.set_autostart(False)
+
self._quitting = True
# first thing to do quitting, hide the mainwindow and show tooltip.
self.hide()
- if self._systray is not None:
+ if not self._system_quit and self._systray is not None:
self._systray.showMessage(
self.tr('Quitting...'),
self.tr('Bitmask is quitting, please wait.'))
@@ -1782,21 +1916,39 @@ class MainWindow(QtGui.QMainWindow):
if self._wizard:
self._wizard.close()
- if self._logger_window:
- self._logger_window.close()
-
# Set this in case that the app is hidden
QtGui.QApplication.setQuitOnLastWindowClosed(True)
- self._stop_services()
-
self._really_quit = True
+ if not self._backend.online:
+ self.final_quit()
+ return
+
# call final quit when all the services are stopped
self.all_services_stopped.connect(self.final_quit)
+
+ self._stop_services()
+
+ # we wait and call manually since during the system's logout the
+ # backend process can be killed and we won't get a response.
+ # XXX: also, for some reason the services stop timeout does not work.
+ if self._system_quit:
+ time.sleep(0.5)
+ self.final_quit()
+
# or if we reach the timeout
QtDelayedCall(self.SERVICES_STOP_TIMEOUT, self.final_quit)
+ def _backend_kill(self):
+ """
+ Send a kill signal to the backend process.
+ This is called if the backend does not respond to requests.
+ """
+ if self._backend_pid is not None:
+ logger.debug("Killing backend")
+ psutil.Process(self._backend_pid).kill()
+
@QtCore.Slot()
def _remove_service(self, service):
"""
@@ -1807,6 +1959,7 @@ class MainWindow(QtGui.QMainWindow):
:param service: the service that we want to remove
:type service: str
"""
+ logger.debug("Removing service: {0}".format(service))
self._services_being_stopped.discard(service)
if not self._services_being_stopped:
@@ -1825,16 +1978,22 @@ class MainWindow(QtGui.QMainWindow):
if self._finally_quitting:
return
+ logger.debug('Final quit...')
self._finally_quitting = True
- logger.debug('Closing soledad...')
- self._backend.soledad_close()
- logger.debug('Final quit...')
+ if self._backend.online:
+ logger.debug('Closing soledad...')
+ self._backend.soledad_close()
self._leap_signaler.stop()
+
self._backend.stop()
time.sleep(0.05) # give the thread a little time to finish.
+ if self._system_quit or not self._backend.online:
+ logger.debug("Killing the backend")
+ self._backend_kill()
+
# Remove lockfiles on a clean shutdown.
logger.debug('Cleaning pidfiles')
if IS_WIN:
diff --git a/src/leap/bitmask/gui/providers.py b/src/leap/bitmask/gui/providers.py
new file mode 100644
index 00000000..b3eb8620
--- /dev/null
+++ b/src/leap/bitmask/gui/providers.py
@@ -0,0 +1,114 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2013,2014 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+"""
+An instance of class Providers, held by mainwindow, is responsible for
+managing the current provider and the combobox provider list.
+"""
+
+from collections import deque
+from PySide import QtCore
+
+
+class Providers(QtCore.QObject):
+
+ # Emitted when the user changes the provider combobox index. The object
+ # parameter is actually a boolean value that is True if "Other..." was
+ # selected, False otherwse
+ _provider_changed = QtCore.Signal(object)
+
+ def __init__(self, providers_combo):
+ """
+ :param providers_combo: combo widget that lists providers
+ :type providers_combo: QWidget
+ """
+ QtCore.QObject.__init__(self)
+ self._providers_indexes = deque(maxlen=2) # previous and current
+ self._providers_indexes.append(-1)
+ self._combo = providers_combo
+ self._combo.currentIndexChanged.connect(
+ self._current_provider_changed)
+
+ 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._combo.blockSignals(True)
+ self._combo.clear()
+ self._combo.addItems(provider_list + [self.tr("Other...")])
+ self._combo.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: unicode str
+ """
+ provider_index = self._combo.findText(name)
+ self._providers_indexes.append(provider_index)
+
+ # block the signals during a combobox change since we don't want to
+ # trigger the default signal that makes the UI ask the user for
+ # confirmation
+ self._combo.blockSignals(True)
+ self._combo.setCurrentIndex(provider_index)
+ self._combo.blockSignals(False)
+
+ def get_selected_provider(self):
+ """
+ Returns the selected provider in the combobox
+
+ :rtype: unicode str
+ """
+ return self._combo.currentText()
+
+ def connect_provider_changed(self, callback):
+ """
+ Connects callback to provider_changed signal
+ """
+ self._provider_changed.connect(callback)
+
+ def restore_previous_provider(self):
+ """
+ Set as selected provider the one that was selected previously.
+ """
+ prev_provider = self._providers_indexes.popleft()
+ self._providers_indexes.append(prev_provider)
+ self._combo.blockSignals(True)
+ self._combo.setCurrentIndex(prev_provider)
+ self._combo.blockSignals(False)
+
+ @QtCore.Slot(int)
+ def _current_provider_changed(self, idx):
+ """
+ TRIGGERS:
+ self._combo.currentIndexChanged
+
+ :param idx: the index of the new selected item
+ :type idx: int
+ """
+ self._providers_indexes.append(idx)
+ is_wizard = idx == (self._combo.count() - 1)
+ self._provider_changed.emit(is_wizard)
+ if is_wizard:
+ self.restore_previous_provider()
diff --git a/src/leap/bitmask/gui/ui/advanced_key_management.ui b/src/leap/bitmask/gui/ui/advanced_key_management.ui
index 3b567347..72f70d24 100644
--- a/src/leap/bitmask/gui/ui/advanced_key_management.ui
+++ b/src/leap/bitmask/gui/ui/advanced_key_management.ui
@@ -14,7 +14,7 @@
<string>Advanced Key Management</string>
</property>
<property name="windowIcon">
- <iconset resource="../../../../../data/resources/mainwindow.qrc">
+ <iconset resource="../../../../../data/resources/icons.qrc">
<normaloff>:/images/mask-icon.png</normaloff>:/images/mask-icon.png</iconset>
</property>
<layout class="QGridLayout" name="gridLayout_4">
@@ -184,7 +184,7 @@
</layout>
</widget>
<resources>
- <include location="../../../../../data/resources/mainwindow.qrc"/>
+ <include location="../../../../../data/resources/icons.qrc"/>
</resources>
<connections/>
</ui>
diff --git a/src/leap/bitmask/gui/ui/eip_status.ui b/src/leap/bitmask/gui/ui/eip_status.ui
index 7216bb0a..0fb861bc 100644
--- a/src/leap/bitmask/gui/ui/eip_status.ui
+++ b/src/leap/bitmask/gui/ui/eip_status.ui
@@ -28,13 +28,26 @@
<property name="verticalSpacing">
<number>0</number>
</property>
- <item row="0" column="4">
+ <item row="0" column="5">
<widget class="QPushButton" name="btnEipStartStop">
<property name="text">
<string>Turn On</string>
</property>
</widget>
</item>
+ <item row="1" column="2">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="maximumSize">
@@ -47,7 +60,7 @@
<string/>
</property>
<property name="pixmap">
- <pixmap resource="../../../../../data/resources/mainwindow.qrc">:/images/black/32/earth.png</pixmap>
+ <pixmap resource="../../../../../data/resources/icons.qrc">:/images/black/32/earth.png</pixmap>
</property>
</widget>
</item>
@@ -86,39 +99,26 @@
</property>
</widget>
</item>
- <item row="0" column="5">
+ <item row="0" column="6">
<widget class="QLabel" name="lblVPNStatusIcon">
<property name="maximumSize">
<size>
- <width>24</width>
- <height>24</height>
+ <width>22</width>
+ <height>22</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
- <pixmap resource="../../../../../data/resources/mainwindow.qrc">:/images/black/32/off.png</pixmap>
+ <pixmap resource="../../../../../data/resources/icons.qrc">:/images/black/22/off.png</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</item>
- <item row="1" column="2">
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>0</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="2" column="2" colspan="4">
+ <item row="2" column="2" colspan="5">
<widget class="QWidget" name="eip_bandwidth" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
@@ -162,7 +162,7 @@
<string>0.0 KB/s</string>
</property>
<property name="icon">
- <iconset resource="../../../../../data/resources/mainwindow.qrc">
+ <iconset resource="../../../../../data/resources/icons.qrc">
<normaloff>:/images/black/32/arrow-down.png</normaloff>:/images/black/32/arrow-down.png</iconset>
</property>
<property name="flat">
@@ -213,7 +213,7 @@
<string>0.0 KB/s</string>
</property>
<property name="icon">
- <iconset resource="../../../../../data/resources/mainwindow.qrc">
+ <iconset resource="../../../../../data/resources/icons.qrc">
<normaloff>:/images/black/32/arrow-up.png</normaloff>:/images/black/32/arrow-up.png</iconset>
</property>
<property name="flat">
@@ -239,7 +239,7 @@
</layout>
</widget>
</item>
- <item row="0" column="3">
+ <item row="0" column="4">
<widget class="QPushButton" name="btnFwDown">
<property name="text">
<string>Turn Off</string>
@@ -253,13 +253,20 @@
</property>
</widget>
</item>
+ <item row="0" column="3">
+ <widget class="QPushButton" name="btnEipCancel">
+ <property name="text">
+ <string>Cancel</string>
+ </property>
+ </widget>
+ </item>
</layout>
</item>
</layout>
</widget>
<resources>
- <include location="../../../../../data/resources/mainwindow.qrc"/>
<include location="../../../../../data/resources/icons.qrc"/>
+ <include location="../../../../../data/resources/flags.qrc"/>
</resources>
<connections/>
</ui>
diff --git a/src/leap/bitmask/gui/ui/eippreferences.ui b/src/leap/bitmask/gui/ui/eippreferences.ui
index a3050683..1a5fcd24 100644
--- a/src/leap/bitmask/gui/ui/eippreferences.ui
+++ b/src/leap/bitmask/gui/ui/eippreferences.ui
@@ -14,7 +14,7 @@
<string>Encrypted Internet Preferences</string>
</property>
<property name="windowIcon">
- <iconset resource="../../../../../data/resources/mainwindow.qrc">
+ <iconset resource="../../../../../data/resources/icons.qrc">
<normaloff>:/images/mask-icon.png</normaloff>:/images/mask-icon.png</iconset>
</property>
<layout class="QGridLayout" name="gridLayout_2">
@@ -96,7 +96,7 @@
<tabstop>pbSaveGateway</tabstop>
</tabstops>
<resources>
- <include location="../../../../../data/resources/mainwindow.qrc"/>
+ <include location="../../../../../data/resources/icons.qrc"/>
</resources>
<connections/>
</ui>
diff --git a/src/leap/bitmask/gui/ui/loggerwindow.ui b/src/leap/bitmask/gui/ui/loggerwindow.ui
index b19ed91a..fd8644c9 100644
--- a/src/leap/bitmask/gui/ui/loggerwindow.ui
+++ b/src/leap/bitmask/gui/ui/loggerwindow.ui
@@ -14,7 +14,7 @@
<string>Logs</string>
</property>
<property name="windowIcon">
- <iconset resource="../../../../../data/resources/mainwindow.qrc">
+ <iconset resource="../../../../../data/resources/icons.qrc">
<normaloff>:/images/mask-icon.png</normaloff>:/images/mask-icon.png</iconset>
</property>
<layout class="QGridLayout" name="gridLayout">
@@ -180,7 +180,7 @@
</tabstops>
<resources>
<include location="../../../../../data/resources/loggerwindow.qrc"/>
- <include location="../../../../../data/resources/mainwindow.qrc"/>
+ <include location="../../../../../data/resources/icons.qrc"/>
</resources>
<connections/>
</ui>
diff --git a/src/leap/bitmask/gui/ui/login.ui b/src/leap/bitmask/gui/ui/login.ui
index 216eca9e..bfd5f9c0 100644
--- a/src/leap/bitmask/gui/ui/login.ui
+++ b/src/leap/bitmask/gui/ui/login.ui
@@ -33,7 +33,7 @@
<number>0</number>
</property>
<property name="verticalSpacing">
- <number>-1</number>
+ <number>6</number>
</property>
<item row="2" column="0">
<spacer name="horizontalSpacer_2">
@@ -69,24 +69,20 @@
<property name="rightMargin">
<number>24</number>
</property>
- <item row="0" column="0">
- <widget class="QLabel" name="label_4">
- <property name="text">
- <string>&lt;b&gt;Provider:&lt;/b&gt;</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
- </item>
- <item row="3" column="0">
+ <item row="3" column="1">
<widget class="QPushButton" name="btnLogin">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
<property name="text">
<string>Log In</string>
</property>
</widget>
</item>
- <item row="3" column="1">
+ <item row="2" column="1">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCheckBox" name="chkRemember">
@@ -110,10 +106,7 @@
</item>
</layout>
</item>
- <item row="0" column="1">
- <widget class="QComboBox" name="cmbProviders"/>
- </item>
- <item row="1" column="0">
+ <item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>&lt;b&gt;Username:&lt;/b&gt;</string>
@@ -123,10 +116,10 @@
</property>
</widget>
</item>
- <item row="1" column="1">
+ <item row="0" column="1">
<widget class="QLineEdit" name="lnUser"/>
</item>
- <item row="2" column="0">
+ <item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>&lt;b&gt;Password:&lt;/b&gt;</string>
@@ -136,7 +129,7 @@
</property>
</widget>
</item>
- <item row="2" column="1">
+ <item row="1" column="1">
<widget class="QLineEdit" name="lnPassword">
<property name="inputMask">
<string/>
@@ -164,7 +157,7 @@
<string/>
</property>
<property name="pixmap">
- <pixmap resource="../../../../../data/resources/mainwindow.qrc">:/images/black/32/user.png</pixmap>
+ <pixmap resource="../../../../../data/resources/icons.qrc">:/images/black/32/user.png</pixmap>
</property>
<property name="scaledContents">
<bool>false</bool>
@@ -198,26 +191,17 @@
</item>
<item row="3" column="2" colspan="2">
<widget class="QWidget" name="logged_widget" native="true">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>0</height>
- </size>
- </property>
- <layout class="QGridLayout" name="gridLayout_5">
+ <layout class="QHBoxLayout" name="horizontalLayout">
<property name="topMargin">
<number>0</number>
</property>
+ <property name="rightMargin">
+ <number>25</number>
+ </property>
<property name="bottomMargin">
- <number>12</number>
+ <number>18</number>
</property>
- <item row="0" column="0" colspan="2">
+ <item>
<widget class="QLabel" name="lblUser">
<property name="font">
<font>
@@ -231,22 +215,41 @@
</property>
</widget>
</item>
- <item row="1" column="0">
+ <item>
+ <spacer name="expandingSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
<widget class="QPushButton" name="btnLogout">
<property name="text">
<string>Logout</string>
</property>
</widget>
</item>
- <item row="1" column="1">
- <spacer name="horizontalSpacer">
+ <item>
+ <spacer name="fixedSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
<property name="sizeHint" stdset="0">
<size>
- <width>40</width>
- <height>20</height>
+ <width>24</width>
+ <height>24</height>
</size>
</property>
</spacer>
@@ -283,7 +286,7 @@
<tabstop>chkRemember</tabstop>
</tabstops>
<resources>
- <include location="../../../../../data/resources/mainwindow.qrc"/>
+ <include location="../../../../../data/resources/icons.qrc"/>
</resources>
<connections/>
</ui>
diff --git a/src/leap/bitmask/gui/ui/logout.ui b/src/leap/bitmask/gui/ui/logout.ui
new file mode 100644
index 00000000..3faa93b6
--- /dev/null
+++ b/src/leap/bitmask/gui/ui/logout.ui
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Form</class>
+ <widget class="QWidget" name="Form">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>434</width>
+ <height>202</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="lblUser">
+ <property name="font">
+ <font>
+ <pointsize>15</pointsize>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="expandingSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnLogout">
+ <property name="text">
+ <string>Logout</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="fixedSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/leap/bitmask/gui/ui/mail_status.ui b/src/leap/bitmask/gui/ui/mail_status.ui
index 22976f39..6fd63aec 100644
--- a/src/leap/bitmask/gui/ui/mail_status.ui
+++ b/src/leap/bitmask/gui/ui/mail_status.ui
@@ -60,7 +60,7 @@
<string/>
</property>
<property name="pixmap">
- <pixmap resource="../../../../../data/resources/mainwindow.qrc">:/images/black/32/off.png</pixmap>
+ <pixmap resource="../../../../../data/resources/icons.qrc">:/images/black/22/off.png</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
@@ -104,14 +104,14 @@
<string/>
</property>
<property name="pixmap">
- <pixmap resource="../../../../../data/resources/mainwindow.qrc">:/images/black/32/email.png</pixmap>
+ <pixmap resource="../../../../../data/resources/icons.qrc">:/images/black/32/email.png</pixmap>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
- <include location="../../../../../data/resources/mainwindow.qrc"/>
+ <include location="../../../../../data/resources/icons.qrc"/>
</resources>
<connections/>
</ui>
diff --git a/src/leap/bitmask/gui/ui/mainwindow.ui b/src/leap/bitmask/gui/ui/mainwindow.ui
index d755115a..92c13d15 100644
--- a/src/leap/bitmask/gui/ui/mainwindow.ui
+++ b/src/leap/bitmask/gui/ui/mainwindow.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>524</width>
- <height>722</height>
+ <height>600</height>
</rect>
</property>
<property name="sizePolicy">
@@ -32,7 +32,7 @@
<string>Bitmask</string>
</property>
<property name="windowIcon">
- <iconset resource="../../../../../data/resources/mainwindow.qrc">
+ <iconset resource="../../../../../data/resources/icons.qrc">
<normaloff>:/images/mask-icon.png</normaloff>:/images/mask-icon.png</iconset>
</property>
<property name="inputMethodHints">
@@ -75,7 +75,7 @@
<x>0</x>
<y>0</y>
<width>524</width>
- <height>667</height>
+ <height>540</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
@@ -86,7 +86,7 @@
<number>0</number>
</property>
<item>
- <widget class="QFrame" name="frame">
+ <widget class="QFrame" name="providerFrame">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
@@ -97,29 +97,16 @@
<bool>false</bool>
</property>
<property name="styleSheet">
- <string notr="true">background-color: rgba(0,0,0,20); border-bottom: 1px solid rgba(0,0,0,30);</string>
+ <string notr="true">QFrame#providerFrame {background-color: rgba(0,0,0,20); border-bottom: 1px solid rgba(0,0,0,30);}</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
- <property name="leftMargin">
- <number>24</number>
- </property>
- <property name="rightMargin">
- <number>24</number>
- </property>
<item>
- <widget class="QLabel" name="lblLoginProvider">
+ <widget class="QComboBox" name="cmbProviders">
<property name="font">
<font>
- <weight>75</weight>
- <bold>true</bold>
+ <pointsize>12</pointsize>
</font>
</property>
- <property name="styleSheet">
- <string notr="true">background-color: rgba(255, 255, 255, 0); border: none;</string>
- </property>
- <property name="text">
- <string>Please Log In</string>
- </property>
</widget>
</item>
</layout>
@@ -186,7 +173,7 @@
<item>
<layout class="QVBoxLayout" name="mailLayout">
<property name="spacing">
- <number>-1</number>
+ <number>6</number>
</property>
<property name="margin">
<number>12</number>
@@ -216,6 +203,28 @@
</property>
</spacer>
</item>
+ <item>
+ <widget class="QFrame" name="frame">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>16</height>
+ </size>
+ </property>
+ <property name="autoFillBackground">
+ <bool>false</bool>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">background-color: rgba(0,0,0,20); border-top: 1px solid rgba(0,0,0,30);</string>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
</widget>
@@ -297,7 +306,7 @@
<x>0</x>
<y>0</y>
<width>524</width>
- <height>23</height>
+ <height>25</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
@@ -377,7 +386,7 @@
</action>
</widget>
<resources>
- <include location="../../../../../data/resources/mainwindow.qrc"/>
+ <include location="../../../../../data/resources/icons.qrc"/>
<include location="../../../../../data/resources/locale.qrc"/>
</resources>
<connections/>
diff --git a/src/leap/bitmask/gui/ui/preferences.ui b/src/leap/bitmask/gui/ui/preferences.ui
index e187c016..cd4d3a77 100644
--- a/src/leap/bitmask/gui/ui/preferences.ui
+++ b/src/leap/bitmask/gui/ui/preferences.ui
@@ -14,7 +14,7 @@
<string>Preferences</string>
</property>
<property name="windowIcon">
- <iconset resource="../../../../../data/resources/mainwindow.qrc">
+ <iconset resource="../../../../../data/resources/icons.qrc">
<normaloff>:/images/mask-icon.png</normaloff>:/images/mask-icon.png</iconset>
</property>
<layout class="QGridLayout" name="gridLayout_3">
@@ -174,7 +174,7 @@
</layout>
</widget>
<resources>
- <include location="../../../../../data/resources/mainwindow.qrc"/>
+ <include location="../../../../../data/resources/icons.qrc"/>
</resources>
<connections/>
</ui>
diff --git a/src/leap/bitmask/gui/ui/wizard.ui b/src/leap/bitmask/gui/ui/wizard.ui
index 8c52897d..0e28ecbf 100644
--- a/src/leap/bitmask/gui/ui/wizard.ui
+++ b/src/leap/bitmask/gui/ui/wizard.ui
@@ -23,10 +23,10 @@
</size>
</property>
<property name="windowTitle">
- <string>Bitmask first run</string>
+ <string>Bitmask Provider Setup</string>
</property>
<property name="windowIcon">
- <iconset resource="../../../../../data/resources/mainwindow.qrc">
+ <iconset resource="../../../../../data/resources/icons.qrc">
<normaloff>:/images/mask-icon.png</normaloff>:/images/mask-icon.png</iconset>
</property>
<property name="modal">
@@ -40,10 +40,10 @@
</property>
<widget class="WizardPage" name="introduction_page">
<property name="title">
- <string>Welcome</string>
+ <string>Welcome to Bitmask</string>
</property>
<property name="subTitle">
- <string>This is the Bitmask first run wizard</string>
+ <string> </string>
</property>
<attribute name="pageId">
<string notr="true">0</string>
@@ -59,7 +59,7 @@
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Now we will guide you through some configuration that is needed before you can connect for the first time.&lt;/p&gt;&lt;p&gt;If you ever need to modify these options again, you can find the wizard in the &lt;span style=&quot; font-style:italic;&quot;&gt;'Bitmask -&amp;gt; Create new account...'&lt;/span&gt; menu from the main window.&lt;/p&gt;&lt;p&gt;Do you want to &lt;span style=&quot; font-weight:600;&quot;&gt;sign up&lt;/span&gt; for a new account, or &lt;span style=&quot; font-weight:600;&quot;&gt;log in&lt;/span&gt; with an already existing username?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string></string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
@@ -109,10 +109,10 @@
</widget>
<widget class="WizardPage" name="select_provider_page">
<property name="title">
- <string>Provider selection</string>
+ <string>Choose a provider</string>
</property>
<property name="subTitle">
- <string>Please enter the domain of the provider you want to use for your connection</string>
+ <string> </string>
</property>
<attribute name="pageId">
<string notr="true">1</string>
@@ -156,7 +156,7 @@
<item row="3" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
- <string>Getting provider information</string>
+ <string>Getting provider information.</string>
</property>
</widget>
</item>
@@ -177,15 +177,24 @@
</property>
<property name="minimumSize">
<size>
- <width>24</width>
- <height>24</height>
+ <width>22</width>
+ <height>22</height>
</size>
</property>
+ <property name="maximumSize">
+ <size>
+ <width>22</width>
+ <height>22</height>
+ </size>
+ </property>
+ <property name="scaledContents">
+ <bool>true</bool>
+ </property>
<property name="text">
<string/>
</property>
<property name="pixmap">
- <pixmap resource="../../../../../data/resources/mainwindow.qrc">:/images/Emblem-question.png</pixmap>
+ <pixmap resource="../../../../../data/resources/icons.qrc">:/images/black/22/question.png</pixmap>
</property>
</widget>
</item>
@@ -199,15 +208,24 @@
</property>
<property name="minimumSize">
<size>
- <width>24</width>
- <height>24</height>
+ <width>22</width>
+ <height>22</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>22</width>
+ <height>22</height>
</size>
</property>
+ <property name="scaledContents">
+ <bool>true</bool>
+ </property>
<property name="text">
<string/>
</property>
<property name="pixmap">
- <pixmap resource="../../../../../data/resources/mainwindow.qrc">:/images/Emblem-question.png</pixmap>
+ <pixmap resource="../../../../../data/resources/icons.qrc">:/images/black/22/question.png</pixmap>
</property>
</widget>
</item>
@@ -221,15 +239,24 @@
</property>
<property name="minimumSize">
<size>
- <width>24</width>
- <height>24</height>
+ <width>22</width>
+ <height>22</height>
</size>
</property>
+ <property name="maximumSize">
+ <size>
+ <width>22</width>
+ <height>22</height>
+ </size>
+ </property>
+ <property name="scaledContents">
+ <bool>true</bool>
+ </property>
<property name="text">
<string/>
</property>
<property name="pixmap">
- <pixmap resource="../../../../../data/resources/mainwindow.qrc">:/images/Emblem-question.png</pixmap>
+ <pixmap resource="../../../../../data/resources/icons.qrc">:/images/black/22/question.png</pixmap>
</property>
</widget>
</item>
@@ -350,10 +377,10 @@
</widget>
<widget class="QWizardPage" name="provider_info_page">
<property name="title">
- <string>Provider Information</string>
+ <string>About this provider</string>
</property>
<property name="subTitle">
- <string>Description of services offered by this provider</string>
+ <string> </string>
</property>
<attribute name="pageId">
<string notr="true">2</string>
@@ -495,7 +522,7 @@
<string>Provider setup</string>
</property>
<property name="subTitle">
- <string>Gathering configuration options for this provider</string>
+ <string> </string>
</property>
<attribute name="pageId">
<string notr="true">3</string>
@@ -517,7 +544,7 @@
<item>
<widget class="QLabel" name="lblSetupProviderExpl">
<property name="text">
- <string>We are downloading some bits that we need to establish a secure connection with the provider for the first time.</string>
+ <string>Bitmask is attempting to establish a secure connection with this provider for the first time.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
@@ -553,15 +580,24 @@
</property>
<property name="minimumSize">
<size>
- <width>24</width>
- <height>24</height>
+ <width>22</width>
+ <height>22</height>
</size>
</property>
+ <property name="maximumSize">
+ <size>
+ <width>22</width>
+ <height>22</height>
+ </size>
+ </property>
+ <property name="scaledContents">
+ <bool>true</bool>
+ </property>
<property name="text">
<string/>
</property>
<property name="pixmap">
- <pixmap resource="../../../../../data/resources/mainwindow.qrc">:/images/Emblem-question.png</pixmap>
+ <pixmap resource="../../../../../data/resources/icons.qrc">:/images/black/22/question.png</pixmap>
</property>
</widget>
</item>
@@ -575,36 +611,45 @@
</property>
<property name="minimumSize">
<size>
- <width>24</width>
- <height>24</height>
+ <width>22</width>
+ <height>22</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>22</width>
+ <height>22</height>
</size>
</property>
+ <property name="scaledContents">
+ <bool>true</bool>
+ </property>
<property name="text">
<string/>
</property>
<property name="pixmap">
- <pixmap resource="../../../../../data/resources/mainwindow.qrc">:/images/Emblem-question.png</pixmap>
+ <pixmap resource="../../../../../data/resources/icons.qrc">:/images/black/22/question.png</pixmap>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
- <string>Getting info from the Certificate Authority</string>
+ <string>Fetching provider credentials.</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
- <string>Do we trust this Certificate Authority?</string>
+ <string>Do we trust these credentials?</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
- <string>Establishing a trust relationship with this provider</string>
+ <string>Connecting to provider.</string>
</property>
</widget>
</item>
@@ -618,15 +663,24 @@
</property>
<property name="minimumSize">
<size>
- <width>24</width>
- <height>24</height>
+ <width>22</width>
+ <height>22</height>
</size>
</property>
+ <property name="maximumSize">
+ <size>
+ <width>22</width>
+ <height>22</height>
+ </size>
+ </property>
+ <property name="scaledContents">
+ <bool>true</bool>
+ </property>
<property name="text">
<string/>
</property>
<property name="pixmap">
- <pixmap resource="../../../../../data/resources/mainwindow.qrc">:/images/Emblem-question.png</pixmap>
+ <pixmap resource="../../../../../data/resources/icons.qrc">:/images/black/22/question.png</pixmap>
</property>
</widget>
</item>
@@ -666,7 +720,7 @@
<string>Register new user</string>
</property>
<property name="subTitle">
- <string>Register a new user with provider</string>
+ <string> </string>
</property>
<attribute name="pageId">
<string notr="true">4</string>
@@ -791,7 +845,7 @@
<string>Service selection</string>
</property>
<property name="subTitle">
- <string>Please select the services you would like to have</string>
+ <string> </string>
</property>
<attribute name="pageId">
<string notr="true">5</string>
@@ -829,7 +883,7 @@
<tabstop>rdoLogin</tabstop>
</tabstops>
<resources>
- <include location="../../../../../data/resources/mainwindow.qrc"/>
+ <include location="../../../../../data/resources/icons.qrc"/>
<include location="../../../../../data/resources/locale.qrc"/>
</resources>
<connections>
diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py
index be5bde52..0223c053 100644
--- a/src/leap/bitmask/gui/wizard.py
+++ b/src/leap/bitmask/gui/wizard.py
@@ -70,9 +70,9 @@ class Wizard(QtGui.QWizard):
self.setPixmap(QtGui.QWizard.LogoPixmap,
QtGui.QPixmap(":/images/mask-icon.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")
+ self.QUESTION_ICON = QtGui.QPixmap(":/images/black/22/question.png")
+ self.ERROR_ICON = QtGui.QPixmap(":/images/black/22/off.png")
+ self.OK_ICON = QtGui.QPixmap(":/images/black/22/on.png")
self._selected_services = set()
self._shown_services = set()
@@ -679,7 +679,7 @@ class Wizard(QtGui.QWizard):
the user to enable or disable.
"""
self.ui.grpServices.setTitle(
- self.tr("Services by {0}").format(self._provider_details['name']))
+ self.tr("Services by {0}").format(self._provider_details['domain']))
services = get_supported(self._provider_details['services'])
@@ -723,18 +723,11 @@ class Wizard(QtGui.QWizard):
if pageId == self.SETUP_PROVIDER_PAGE:
if not self._provider_setup_ok:
self._reset_provider_setup()
- sub_title = self.tr("Gathering configuration options for {0}")
- sub_title = sub_title.format(self._provider_details['name'])
- self.page(pageId).setSubTitle(sub_title)
self.ui.lblDownloadCaCert.setPixmap(self.QUESTION_ICON)
self._provider_setup_defer = self._backend.\
provider_bootstrap(provider=self._domain)
if pageId == self.PRESENT_PROVIDER_PAGE:
- sub_title = self.tr("Description of services offered by {0}")
- sub_title = sub_title.format(self._provider_details['name'])
- self.page(pageId).setSubTitle(sub_title)
-
details = self._provider_details
name = "<b>{0}</b>".format(details['name'])
domain = "https://{0}".format(details['domain'])
@@ -746,9 +739,9 @@ class Wizard(QtGui.QWizard):
self.ui.lblProviderPolicy.setText(details['enrollment_policy'])
if pageId == self.REGISTER_USER_PAGE:
- sub_title = self.tr("Register a new user with {0}")
- sub_title = sub_title.format(self._provider_details['name'])
- self.page(pageId).setSubTitle(sub_title)
+ title = self.tr("Register a new user with {0}")
+ title = title.format(self._provider_details['domain'])
+ self.page(pageId).setTitle(title)
self.ui.chkRemember.setVisible(False)
if pageId == self.SERVICES_PAGE:
diff --git a/src/leap/bitmask/platform_init/__init__.py b/src/leap/bitmask/platform_init/__init__.py
index 2a262a30..a9a97af9 100644
--- a/src/leap/bitmask/platform_init/__init__.py
+++ b/src/leap/bitmask/platform_init/__init__.py
@@ -22,7 +22,7 @@ import platform
_system = platform.system()
-IS_WIN = True if _system == "Windows" else False
-IS_MAC = True if _system == "Darwin" else False
-IS_LINUX = True if _system == "Linux" else False
+IS_LINUX = _system == "Linux"
+IS_MAC = _system == "Darwin"
IS_UNIX = IS_MAC or IS_LINUX
+IS_WIN = _system == "Windows"
diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py
index e4d6f9b3..f56b9330 100644
--- a/src/leap/bitmask/platform_init/initializers.py
+++ b/src/leap/bitmask/platform_init/initializers.py
@@ -106,6 +106,7 @@ def get_missing_helpers_dialog():
msg.setStandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)
msg.addButton("No, don't ask again", QtGui.QMessageBox.RejectRole)
msg.setDefaultButton(QtGui.QMessageBox.Yes)
+ msg.setEscapeButton(QtGui.QMessageBox.No)
return msg
diff --git a/src/leap/bitmask/services/eip/conductor.py b/src/leap/bitmask/services/eip/conductor.py
index bb07809a..0ee56628 100644
--- a/src/leap/bitmask/services/eip/conductor.py
+++ b/src/leap/bitmask/services/eip/conductor.py
@@ -16,6 +16,9 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
EIP Conductor module.
+
+This handles Qt Signals and triggers the calls to the backend,
+where the VPNProcess has been initialized.
"""
import logging
@@ -90,7 +93,7 @@ class EIPConductor(object):
def start_eip_machine(self, action):
"""
- Initializes and starts the EIP state machine.
+ Initialize and start the EIP state machine.
Needs the reference to the eip_status widget not to be empty.
:action: QtAction
@@ -124,8 +127,12 @@ class EIPConductor(object):
@QtCore.Slot()
def _start_eip(self):
"""
- Starts EIP.
+ Start EIP.
+
+ This set a couple of status flags and calls the start procedure in the
+ backend.
"""
+ # TODO status should be kept in a singleton in the backend.
st = self._eip_status
is_restart = st and st.is_restart
@@ -137,6 +144,7 @@ class EIPConductor(object):
else:
self._eip_status.eip_pre_up()
self.user_stopped_eip = False
+ self.cancelled = False
self._eip_status.hide_fw_down_button()
# Until we set an option in the preferences window, we'll assume that
@@ -175,6 +183,9 @@ class EIPConductor(object):
:param failed: whether this is the final step of a retry sequence
:type failed: bool
"""
+ # XXX we should NOT keep status in the widget, but we do for a series
+ # of hacks related to restarts. All status should be kept in a backend
+ # object, widgets should be just widgets.
self._eip_status.is_restart = restart
self.user_stopped_eip = not restart and not failed
@@ -267,7 +278,7 @@ class EIPConductor(object):
TRIGGERS:
Signaler.eip_process_finished
- Triggered when the EIP/VPN process finishes to set the UI
+ Triggered when the EIP/VPN process finishes, in order to set the UI
accordingly.
Ideally we would have the right exit code here,
@@ -302,18 +313,26 @@ class EIPConductor(object):
# bitmask-root is masking the exitcode, so we might need
# to fix it on that side.
# if exitCode != 0 and not self.user_stopped_eip:
- if not self.user_stopped_eip:
+
+ if not self.user_stopped_eip and not self.cancelled:
+ error = True
eip_status_label = self._eip_status.tr(
"{0} finished in an unexpected manner!")
eip_status_label = eip_status_label.format(self.eip_name)
- self._eip_status.eip_stopped()
self._eip_status.set_eip_status_icon("error")
self._eip_status.set_eip_status(eip_status_label,
- error=True)
+ error=error)
+ self._eip_status.eip_stopped()
signal = self.qtsigs.connection_died_signal
self._eip_status.show_fw_down_button()
self._eip_status.eip_failed_to_connect()
+ if self.cancelled:
+ signal = self.qtsigs.connection_aborted_signal
+ self._eip_status.set_eip_status_icon("error")
+ self._eip_status.eip_stopped()
+ self._eip_status.set_eip_status("", error=False)
+
if exitCode == 0 and IS_MAC:
# XXX remove this warning after I fix cocoasudo.
logger.warning("The above exit code MIGHT BE WRONG.")
diff --git a/src/leap/bitmask/services/eip/eipconfig.py b/src/leap/bitmask/services/eip/eipconfig.py
index e7419b22..37c0c8ae 100644
--- a/src/leap/bitmask/services/eip/eipconfig.py
+++ b/src/leap/bitmask/services/eip/eipconfig.py
@@ -24,6 +24,7 @@ import time
import ipaddr
+from leap.bitmask.config import flags
from leap.bitmask.config.providerconfig import ProviderConfig
from leap.bitmask.services import ServiceConfig
from leap.bitmask.services.eip.eipspec import get_schema
@@ -220,6 +221,7 @@ class EIPConfig(ServiceConfig):
OPENVPN_CIPHERS_REGEX = re.compile("[A-Z0-9\-]+")
def __init__(self):
+ self.standalone = flags.STANDALONE
ServiceConfig.__init__(self)
self._api_version = None
diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py
index d1a3fdaa..c7159a93 100644
--- a/src/leap/bitmask/services/eip/vpnprocess.py
+++ b/src/leap/bitmask/services/eip/vpnprocess.py
@@ -202,7 +202,24 @@ class VPN(object):
"aborting openvpn launch.")
return
- cmd = vpnproc.getCommand()
+ # FIXME it would be good to document where the
+ # errors here are catched, since we currently handle them
+ # at the frontend layer. This *should* move to be handled entirely
+ # in the backend.
+ # exception is indeed technically catched in backend, then converted
+ # into a signal, that is catched in the eip_status widget in the
+ # frontend, and converted into a signal to abort the connection that is
+ # sent to the backend again.
+
+ # the whole exception catching should be done in the backend, without
+ # the ping-pong to the frontend, and without adding any logical checks
+ # in the frontend. We should just communicate UI changes to frontend,
+ # and abstract us away from anything else.
+ try:
+ cmd = vpnproc.getCommand()
+ except Exception:
+ logger.error("Error while getting vpn command...")
+ raise
env = os.environ
for key, val in vpnproc.vpn_env.items():
env[key] = val
@@ -263,6 +280,18 @@ class VPN(object):
BM_ROOT, "firewall", "stop"])
return True if exitCode is 0 else False
+ def bitmask_root_vpn_down(self):
+ """
+ Bring openvpn down using the privileged wrapper.
+ """
+ if IS_MAC:
+ # We don't support Mac so far
+ return True
+ BM_ROOT = force_eval(linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT)
+ exitCode = subprocess.call(["pkexec",
+ BM_ROOT, "openvpn", "stop"])
+ return True if exitCode is 0 else False
+
def _kill_if_left_alive(self, tries=0):
"""
Check if the process is still alive, and send a
diff --git a/src/leap/bitmask/util/autostart.py b/src/leap/bitmask/util/autostart.py
new file mode 100644
index 00000000..d7a8afb8
--- /dev/null
+++ b/src/leap/bitmask/util/autostart.py
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+# autostart.py
+# Copyright (C) 2013, 2014 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+Helpers to enable/disable bitmask's autostart.
+"""
+import logging
+import os
+
+from leap.bitmask.config import flags
+from leap.bitmask.platform_init import IS_LINUX
+from leap.common.files import mkdir_p
+
+logger = logging.getLogger(__name__)
+
+
+DESKTOP_ENTRY = """\
+[Desktop Entry]
+Version=1.0
+Encoding=UTF-8
+Type=Application
+Name=Bitmask
+Comment=Secure Communication
+Exec=bitmask --start-hidden
+Terminal=false
+Icon=bitmask
+"""
+
+DESKTOP_ENTRY_PATH = os.path.expanduser("~/.config/autostart/")
+DESKTOP_ENTRY_FILE = os.path.join(DESKTOP_ENTRY_PATH, 'bitmask.desktop')
+
+
+def set_autostart(enabled):
+ """
+ Set the autostart mode to enabled or disabled depending on the parameter.
+ If `enabled` is `True`, save the autostart file to its place. Otherwise,
+ remove that file.
+ Right now we support only Linux autostart.
+
+ :param enabled: whether the autostart should be enabled or not.
+ :type enabled: bool
+ """
+ # we don't do autostart for bundle or systems different than Linux
+ if flags.STANDALONE or not IS_LINUX:
+ return
+
+ if enabled:
+ mkdir_p(DESKTOP_ENTRY_PATH)
+ with open(DESKTOP_ENTRY_FILE, 'w') as f:
+ f.write(DESKTOP_ENTRY)
+ else:
+ try:
+ os.remove(DESKTOP_ENTRY_FILE)
+ except OSError: # if the file does not exist
+ pass
+ except Exception as e:
+ logger.error("Problem disabling autostart, {0!r}".format(e))
diff --git a/src/leap/bitmask/util/credentials.py b/src/leap/bitmask/util/credentials.py
index 07ded17b..757ce10c 100644
--- a/src/leap/bitmask/util/credentials.py
+++ b/src/leap/bitmask/util/credentials.py
@@ -21,7 +21,7 @@ Credentials utilities
from PySide import QtCore, QtGui
WEAK_PASSWORDS = ("123456", "qweasd", "qwerty", "password")
-USERNAME_REGEX = r"^[A-Za-z][A-Za-z\d_\-\.]+[A-Za-z\d]$"
+USERNAME_REGEX = r"^[a-z][a-z\d_\-\.]+[a-z\d]$"
USERNAME_VALIDATOR = QtGui.QRegExpValidator(QtCore.QRegExp(USERNAME_REGEX))
@@ -69,7 +69,7 @@ def password_checks(username, password, password2):
if message is None and not password:
message = _tr("You can't use an empty password")
- if message is None and len(password) < 6:
+ if message is None and len(password) < 8:
message = _tr("Password too short")
if message is None and password in WEAK_PASSWORDS:
diff --git a/src/leap/bitmask/util/keyring_helpers.py b/src/leap/bitmask/util/keyring_helpers.py
index ee2d7a1c..2b729b65 100644
--- a/src/leap/bitmask/util/keyring_helpers.py
+++ b/src/leap/bitmask/util/keyring_helpers.py
@@ -46,15 +46,12 @@ def _get_keyring_with_fallback():
This is a workaround for the cases in which the keyring module chooses
an insecure keyring by default (ie, inside a virtualenv).
"""
- if not keyring:
+ if keyring is None:
return None
kr = keyring.get_keyring()
if not canuse(kr):
- try:
- kr_klass = keyring.backends.SecretService
- kr = kr_klass.Keyring()
- except AttributeError:
- logger.warning("Keyring cannot find SecretService Backend")
+ logger.debug("No usable keyring found")
+ return None
logger.debug("Selected keyring: %s" % (kr.__class__,))
if not canuse(kr):
logger.debug("Not using default keyring since it is obsolete")
@@ -67,7 +64,7 @@ def has_keyring():
:rtype: bool
"""
- if not keyring:
+ if keyring is None:
return False
kr = _get_keyring_with_fallback()
return canuse(kr)
@@ -79,7 +76,7 @@ def get_keyring():
:rtype: keyringBackend or None
"""
- if not keyring:
+ if keyring is None:
return False
kr = _get_keyring_with_fallback()
return kr if canuse(kr) else None