diff options
Diffstat (limited to 'src/leap/bitmask/gui/eip_status.py')
-rw-r--r-- | src/leap/bitmask/gui/eip_status.py | 313 |
1 files changed, 287 insertions, 26 deletions
diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index ca28b8bf..8b9f2d44 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -24,7 +24,7 @@ from functools import partial from PySide import QtCore, QtGui -from leap.bitmask.services.eip.connection import EIPConnection +from leap.bitmask.config import flags from leap.bitmask.services import get_service_display_name, EIP_SERVICE from leap.bitmask.platform_init import IS_LINUX from leap.bitmask.util.averages import RateMovingAverage @@ -32,6 +32,7 @@ from leap.common.check import leap_assert_type from ui_eip_status import Ui_EIPStatus +QtDelayedCall = QtCore.QTimer.singleShot logger = logging.getLogger(__name__) @@ -43,9 +44,14 @@ class EIPStatusWidget(QtGui.QWidget): RATE_STR = "%1.2f KB/s" TOTAL_STR = "%1.2f Kb" - eip_connection_connected = QtCore.Signal() + def __init__(self, parent=None, eip_conductor=None): + """ + :param parent: the parent of the widget. + :type parent: QObject - def __init__(self, parent=None): + :param eip_conductor: an EIPConductor object. + :type eip_conductor: EIPConductor + """ QtGui.QWidget.__init__(self, parent) self._systray = None @@ -54,13 +60,17 @@ class EIPStatusWidget(QtGui.QWidget): self.ui = Ui_EIPStatus() self.ui.setupUi(self) - self.eipconnection = EIPConnection() + self.eip_conductor = eip_conductor + self.eipconnection = eip_conductor.eip_connection # set systray tooltip status self._eip_status = "" self._service_name = get_service_display_name(EIP_SERVICE) self.ui.eip_bandwidth.hide() + self.hide_fw_down_button() + self.ui.btnFwDown.clicked.connect( + self._on_fw_down_button_clicked) # Set the EIP status icons self.CONNECTING_ICON = None @@ -75,11 +85,43 @@ class EIPStatusWidget(QtGui.QWidget): self._make_status_clickable() self._provider = "" + self.is_restart = False + self.is_cold_start = True # Action for the systray self._eip_disabled_action = QtGui.QAction( "{0} is {1}".format(self._service_name, self.tr("disabled")), self) + def connect_backend_signals(self): + """ + Connect backend signals. + """ + signaler = self.eip_conductor._backend.signaler + + signaler.eip_openvpn_already_running.connect( + self._on_eip_openvpn_already_running) + signaler.eip_alien_openvpn_already_running.connect( + self._on_eip_alien_openvpn_already_running) + signaler.eip_openvpn_not_found_error.connect( + self._on_eip_openvpn_not_found_error) + signaler.eip_vpn_launcher_exception.connect( + self._on_eip_vpn_launcher_exception) + signaler.eip_no_polkit_agent_error.connect( + self._on_eip_no_polkit_agent_error) + signaler.eip_connection_aborted.connect( + self._on_eip_connection_aborted) + signaler.eip_no_pkexec_error.connect(self._on_eip_no_pkexec_error) + signaler.eip_no_tun_kext_error.connect(self._on_eip_no_tun_kext_error) + + signaler.eip_state_changed.connect(self.update_vpn_state) + signaler.eip_status_changed.connect(self.update_vpn_status) + + # XXX we cannot connect this signal now because + # it interferes with the proper notifications during restarts + # without available network. + #signaler.eip_network_unreachable.connect( + #self._on_eip_network_unreachable) + def _make_status_clickable(self): """ Makes upload and download figures clickable. @@ -208,7 +250,7 @@ class EIPStatusWidget(QtGui.QWidget): def set_action_eip_startstop(self, action_eip_startstop): """ - Sets the action_eip_startstop to use. + Set the action_eip_startstop to use. :param action_eip_startstop: action_eip_status to be used :type action_eip_startstop: QtGui.QAction @@ -238,9 +280,11 @@ class EIPStatusWidget(QtGui.QWidget): def eip_pre_up(self): """ Triggered when the app activates eip. - Hides the status box and disables the start/stop button. + Disables the start/stop button. """ self.set_startstop_enabled(False) + msg = self.tr("Encrypted Internet is starting") + self.set_eip_message(msg) @QtCore.Slot() def disable_eip_start(self): @@ -248,7 +292,7 @@ class EIPStatusWidget(QtGui.QWidget): Triggered when a default provider_config has not been found. Disables the start button and adds instructions to the user. """ - #logger.debug('Hiding EIP start button') + 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. # probably the best thing would be to make a conditional @@ -282,10 +326,19 @@ class EIPStatusWidget(QtGui.QWidget): if self.isVisible(): self._eip_status_menu.menuAction().setVisible(True) - # XXX disable (later) -------------------------- + def set_eip_message(self, message): + """ + Set the EIP Widget main message. + + :param message: the message to set in the widget + :type message: str or unicode + """ + self.ui.lblEIPMessage.setText(message) + self.ui.lblEIPMessage.show() + def set_eip_status(self, status, error=False): """ - Sets the status label at the VPN stage to status + Set the status label at the VPN stage to status. :param status: status message :type status: str or unicode @@ -326,29 +379,80 @@ class EIPStatusWidget(QtGui.QWidget): Sets the state of the widget to how it should look after EIP has started """ - self.ui.btnEipStartStop.setText(self.tr("Turn OFF")) self.ui.btnEipStartStop.disconnect(self) self.ui.btnEipStartStop.clicked.connect( self.eipconnection.qtsigs.do_connect_signal) - # XXX disable ----------------------------- - def eip_stopped(self): + def hide_fw_down_button(self): + """ + Hide firewall-down button. + """ + self.ui.btnFwDown.hide() + + def show_fw_down_button(self): + """ + Enable firewall-down button. """ + retry_msg = self.tr("Retry") + self.ui.btnEipStartStop.setText(retry_msg) + self._action_eip_startstop.setText(retry_msg) + self.ui.btnFwDown.show() + + def _on_fw_down_button_clicked(self): + """ + Raise a signal for tearing down the firewall, and hide the button + afterwards. + """ + self.eip_conductor._backend.tear_fw_down() + QtDelayedCall(50, self.hide_fw_down_button) + + # XXX do actual check + msg = "Traffic is being routed in the clear." + self.ui.btnEipStartStop.setText(self.tr("Turn ON")) + self.set_eip_message(msg) + self.set_eip_status("") + + @QtCore.Slot(dict) + def eip_stopped(self, restart=False, failed=False): + """ + TRIGGERS: + EIPConductor.qtsigs.disconnected_signal + Sets the state of the widget to how it should look after EIP has stopped """ - # XXX should connect this to EIPConnection.disconnected_signal + self.set_country_code("") self._reset_traffic_rates() - # XXX disable ----------------------------- - self.ui.btnEipStartStop.setText(self.tr("Turn ON")) - self.ui.btnEipStartStop.disconnect(self) - self.ui.btnEipStartStop.clicked.connect( - self.eipconnection.qtsigs.do_disconnect_signal) - self.ui.eip_bandwidth.hide() - self.ui.lblEIPMessage.setText( - self.tr("Traffic is being routed in the clear")) + + # This is assuming the firewall works correctly, but we should test fw + # status positively. + # Or better call it from the conductor... + + clear_traffic = self.tr("Traffic is being routed in the clear.") + unreachable_net = self.tr("Network is unreachable.") + failed_msg = self.tr("Error connecting") + + if restart: + msg = unreachable_net + elif failed: + msg = failed_msg + else: + msg = clear_traffic + self.set_eip_message(msg) + self.ui.lblEIPStatus.show() + self.show() + + def eip_failed_to_connect(self): + """ + Update EIP messages with error during (re)connection. + """ + msg = self.tr("Error connecting.") + self.ui.lblEIPMessage.setText(msg) self.ui.lblEIPStatus.show() + self.set_eip_status(self.tr("Bitmask is blocking " + "unencrypted traffic.")) + self.show_fw_down_button() @QtCore.Slot(dict) def update_vpn_status(self, data=None): @@ -407,11 +511,20 @@ class EIPStatusWidget(QtGui.QWidget): self.ui.lblEIPStatus.hide() # XXX should be handled by the state machine too. - self.eip_connection_connected.emit() + # --- is this currently being sent? + self.eipconnection.qtsigs.connected_signal.emit() + self._on_eip_connected() + self.is_cold_start = False # XXX should lookup vpn_state map in EIPConnection elif vpn_state == "AUTH": self.set_eip_status(self.tr("Authenticating...")) + # 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": @@ -423,10 +536,11 @@ class EIPStatusWidget(QtGui.QWidget): elif vpn_state == "ALREADYRUNNING": # Put the following calls in Qt's event queue, otherwise # the UI won't update properly - QtCore.QTimer.singleShot( - 0, self.eipconnection.qtsigs.do_disconnect_signal) + #self.send_disconnect_signal() + QtDelayedCall( + 0, self.eipconnection.qtsigns.do_disconnect_signal.emit) msg = self.tr("Unable to start VPN, it's already running.") - QtCore.QTimer.singleShot(0, partial(self.set_eip_status, msg)) + QtDelayedCall(0, partial(self.set_eip_status, msg)) else: self.set_eip_status(vpn_state) @@ -468,5 +582,152 @@ class EIPStatusWidget(QtGui.QWidget): def set_provider(self, provider): self._provider = provider + self.ui.lblEIPMessage.setText( - self.tr("Route traffic through: {0}").format(self._provider)) + self.tr("Routing traffic through: <b>{0}</b>").format( + provider)) + + ccode = flags.CURRENT_VPN_COUNTRY + if ccode is not None: + self.set_country_code(ccode) + + def set_country_code(self, code): + """ + Set the pixmap of the given country code + + :param code: the country code + :type code: str + """ + if code is not None and len(code) == 2: + img = ":/images/countries/%s.png" % (code.lower(),) + else: + img = None + cc = self.ui.lblGatewayCountryCode + cc.setPixmap(QtGui.QPixmap(img)) + cc.setToolTip(code) + + def aborted(self): + """ + Notify the state machine that EIP was aborted for some reason. + """ + # signal connection_aborted to state machine: + qtsigs = self.eipconnection.qtsigs + qtsigs.connection_aborted_signal.emit() + + # + # Slots for signals + # + + @QtCore.Slot() + def _on_eip_connection_aborted(self): + """ + TRIGGERS: + Signaler.eip_connection_aborted + """ + # TODO this name is very misleading, since there's a generic signal + # that's called connection_aborted / connection_died... + # should rename to something more specific about missing config. + logger.error("Tried to start EIP but cannot find any " + "available provider!") + + eip_status_label = self.tr("Could not load {0} configuration.") + eip_status_label = eip_status_label.format( + self.eip_conductor.eip_name) + self.set_eip_status(eip_status_label, error=True) + + self.aborted() + + def _on_eip_openvpn_already_running(self): + self.set_eip_status( + self.tr("Another openvpn instance is already running, and " + "could not be stopped."), + error=True) + self.set_eipstatus_off() + + self.aborted() + + def _on_eip_alien_openvpn_already_running(self): + self.set_eip_status( + self.tr("Another openvpn instance is already running, and " + "could not be stopped because it was not launched by " + "Bitmask. Please stop it and try again."), + error=True) + self.set_eipstatus_off() + + self.aborted() + + def _on_eip_openvpn_not_found_error(self): + self.set_eip_status( + self.tr("We could not find openvpn binary."), + error=True) + self.set_eipstatus_off() + + self.aborted() + + 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) + self.set_eipstatus_off() + + self.aborted() + + def _on_eip_no_polkit_agent_error(self): + self.set_eip_status( + # XXX this should change to polkit-kde where + # applicable. + self.tr("We could not find any authentication agent in your " + "system.<br/>Make sure you have" + "<b>polkit-gnome-authentication-agent-1</b> running and" + "try again."), + error=True) + self.set_eipstatus_off() + + self.aborted() + + def _on_eip_no_pkexec_error(self): + self.set_eip_status( + self.tr("We could not find <b>pkexec</b> in your system."), + error=True) + self.set_eipstatus_off() + + self.aborted() + + def _on_eip_no_tun_kext_error(self): + self.set_eip_status( + self.tr("{0} cannot be started because the tuntap extension is " + "not installed properly in your " + "system.").format(self.eip_conductor.eip_name)) + self.set_eipstatus_off() + + self.aborted() + + def _on_eip_connected(self): + """ + Reconnect the disconnecting signal when we are just connected, + so that we restore the disconnecting -> stop behaviour. + """ + self.eip_conductor.reconnect_stop_signal() + + @QtCore.Slot() + def _on_eip_network_unreachable(self): + """ + TRIGGERS: + self._eip_connection.qtsigs.network_unreachable + + Displays a "network unreachable" error in the EIP status panel. + """ + self.set_eip_status(self.tr("Network is unreachable"), + error=True) + self.set_eip_status_icon("error") + + def set_eipstatus_off(self, error=True): + # XXX this should be handled by the state machine. + """ + Sets eip status to off + """ + self.set_eip_status("", error=error) + self.set_eip_status_icon("error") + +import eipstatus_rc +assert(eipstatus_rc) |