# -*- coding: utf-8 -*- """ OnionShare | https://onionshare.org/ Copyright (C) 2014-2021 Micah Lee, et al. 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 . """ import json import os from PySide6 import QtCore, QtWidgets, QtGui from onionshare_cli.censorship import ( CensorshipCircumvention, CensorshipCircumventionError, ) from onionshare_cli.meek import ( MeekNotRunning, MeekNotFound, ) from onionshare_cli.settings import Settings from . import strings from .gui_common import GuiCommon, ToggleCheckbox from .tor_connection import TorConnectionWidget from .update_checker import UpdateThread from .widgets import Alert class AutoConnectTab(QtWidgets.QWidget): """ Initial Tab that appears in the very beginning to ask user if should auto connect. """ close_this_tab = QtCore.Signal() tor_is_connected = QtCore.Signal() tor_is_disconnected = QtCore.Signal() def __init__(self, common, tab_id, status_bar, window, parent=None): super(AutoConnectTab, self).__init__() self.common = common self.common.log("AutoConnectTab", "__init__") self.status_bar = status_bar self.tab_id = tab_id self.window = window self.parent = parent # Was auto connected? self.curr_settings = Settings(common) self.curr_settings.load() self.auto_connect_enabled = self.curr_settings.get("auto_connect") # Rocket ship animation images self.anim_stars = AnimStars(self, self.window) self.anim_ship = AnimShip(self, self.window) self.anim_smoke = AnimSmoke(self, self.window) # Onionshare logo self.image_label = QtWidgets.QLabel() self.image_label.setPixmap( QtGui.QPixmap.fromImage( QtGui.QImage( GuiCommon.get_resource_path( os.path.join( "images", f"{common.gui.color_mode}_logo_text_bg.png" ) ) ) ) ) self.image_label.setFixedSize(322, 65) image_layout = QtWidgets.QVBoxLayout() image_layout.addWidget(self.image_label) self.image = QtWidgets.QWidget() self.image.setLayout(image_layout) # First launch widget self.first_launch_widget = AutoConnectFirstLaunchWidget( self.common, self.curr_settings ) self.first_launch_widget.toggle_auto_connect.connect(self.toggle_auto_connect) self.first_launch_widget.connect_clicked.connect( self.first_launch_widget_connect_clicked ) self.first_launch_widget.open_tor_settings.connect(self.open_tor_settings) self.first_launch_widget.show() # Use bridge widget self.use_bridge_widget = AutoConnectUseBridgeWidget(self.common) self.use_bridge_widget.connect_clicked.connect(self.use_bridge_connect_clicked) self.use_bridge_widget.try_again_clicked.connect( self.first_launch_widget_connect_clicked ) self.use_bridge_widget.open_tor_settings.connect(self.open_tor_settings) self.use_bridge_widget.hide() # Tor connection widget self.tor_con = TorConnectionWidget(self.common, self.status_bar) self.tor_con.success.connect(self.tor_con_success) self.tor_con.fail.connect(self.tor_con_fail) self.tor_con.update_progress.connect(self.anim_stars.update) self.tor_con.update_progress.connect(self.anim_ship.update) self.tor_con.update_progress.connect(self.anim_smoke.update) self.tor_con.hide() # Layout content_layout = QtWidgets.QVBoxLayout() content_layout.addStretch() content_layout.addWidget(self.image) content_layout.addWidget(self.first_launch_widget) content_layout.addWidget(self.use_bridge_widget) content_layout.addWidget(self.tor_con) content_layout.addStretch() content_layout.setAlignment(QtCore.Qt.AlignCenter) content_widget = QtWidgets.QWidget() content_widget.setLayout(content_layout) self.layout = QtWidgets.QHBoxLayout() self.layout.addWidget(content_widget) self.layout.addStretch() self.setLayout(self.layout) def check_autoconnect(self): """ After rendering, check if autoconnect was clicked, then start connecting """ self.common.log("AutoConnectTab", "autoconnect_checking") if self.auto_connect_enabled: self.first_launch_widget.enable_autoconnect_checkbox.setChecked(True) self.first_launch_widget_connect_clicked() def toggle_auto_connect(self): """ Auto connect checkbox clicked """ self.common.log("AutoConnectTab", "autoconnect_checkbox_clicked") self.curr_settings.set( "auto_connect", self.first_launch_widget.enable_autoconnect_checkbox.isChecked(), ) self.curr_settings.save() def open_tor_settings(self): self.parent.open_settings_tab(from_autoconnect=True, active_tab="tor") def first_launch_widget_connect_clicked(self): """ Connect button in first launch widget clicked. Try to connect to tor. """ self.common.log("AutoConnectTab", "first_launch_widget_connect_clicked") self.first_launch_widget.hide_buttons() self.tor_con.show() self.tor_con.start(self.curr_settings) def _got_bridges(self): self.use_bridge_widget.progress.hide() self.use_bridge_widget.progress_label.hide() # Try and connect again self.common.log( "AutoConnectTab", "_got_bridges", "Got bridges. Trying to reconnect to Tor", ) self.tor_con.show() self.tor_con.start(self.curr_settings) def _got_no_bridges(self): # If we got no bridges, even after trying the default bridges # provided by the Censorship API, try connecting again using # our built-in obfs4 bridges self.curr_settings.set("bridges_type", "built-in") self.curr_settings.set("bridges_builtin_pt", "obfs4") self.curr_settings.set("bridges_enabled", True) self.curr_settings.save() self._got_bridges() def _censorship_progress_update(self, progress, summary): self.use_bridge_widget.progress.setValue(int(progress)) self.use_bridge_widget.progress_label.setText( f"{strings._('gui_autoconnect_circumventing_censorship')}
{summary}" ) def network_connection_error(self): """ Display an error if there simply seems no network connection. """ self.use_bridge_widget.connection_status_label.setText( strings._("gui_autoconnect_failed_to_connect_to_tor") ) self.use_bridge_widget.progress.hide() self.use_bridge_widget.progress_label.hide() self.use_bridge_widget.error_label.show() self.use_bridge_widget.country_combobox.setEnabled(True) self.use_bridge_widget.show_buttons() self.use_bridge_widget.show() def use_bridge_connect_clicked(self): """ Connect button in use bridge widget clicked. """ self.common.log( "AutoConnectTab", "use_bridge_connect_clicked", "Trying to automatically obtain bridges", ) self.use_bridge_widget.hide_buttons() self.use_bridge_widget.progress.show() self.use_bridge_widget.progress_label.show() if self.use_bridge_widget.detect_automatic_radio.isChecked(): country = False else: country = self.use_bridge_widget.country_combobox.currentData().lower() self._censorship_progress_update( 50, strings._("gui_autoconnect_circumventing_censorship_starting_meek") ) try: self.common.gui.meek.start() self.censorship_circumvention = CensorshipCircumvention( self.common, self.common.gui.meek ) self._censorship_progress_update( 75, strings._( "gui_autoconnect_circumventing_censorship_requesting_bridges" ), ) bridge_settings = self.censorship_circumvention.request_settings( country=country ) if not bridge_settings: # Fall back to trying the default bridges from the API self.common.log( "AutoConnectTab", "use_bridge_connect_clicked", "Falling back to trying default bridges provided by the Censorship Circumvention API", ) bridge_settings = ( self.censorship_circumvention.request_default_bridges() ) self.common.gui.meek.cleanup() if bridge_settings and self.censorship_circumvention.save_settings( self.curr_settings, bridge_settings ): self._censorship_progress_update( 100, strings._("gui_autoconnect_circumventing_censorship_got_bridges"), ) self._got_bridges() else: self._got_no_bridges() except ( MeekNotRunning, MeekNotFound, ) as e: self._got_no_bridges() except CensorshipCircumventionError as e: self.common.log( "AutoConnectTab", "use_bridge_connect_clicked", "Request to the Tor Censorship Circumvention API failed. No network connection?", ) self.network_connection_error() def check_for_updates(self): """ Check for OnionShare updates in a new thread, if enabled. """ if self.common.platform == "Windows" or self.common.platform == "Darwin": if self.common.settings.get("use_autoupdate"): def update_available(update_url, installed_version, latest_version): Alert( self.common, strings._("update_available").format( update_url, installed_version, latest_version ), ) self.update_thread = UpdateThread(self.common, self.common.gui.onion) self.update_thread.update_available.connect(update_available) self.update_thread.start() def tor_con_success(self): """ Finished testing tor connection. """ self.tor_con.hide() self.first_launch_widget.show_buttons() self.use_bridge_widget.show_buttons() self.use_bridge_widget.progress.hide() self.use_bridge_widget.progress_label.hide() if self.common.gui.onion.is_authenticated() and not self.tor_con.wasCanceled(): # Tell the tabs that Tor is connected self.tor_is_connected.emit() # After connecting to Tor, check for updates self.check_for_updates() # Close the tab self.close_this_tab.emit() def tor_con_fail(self, msg): """ Finished testing tor connection. """ self.tor_con.hide() # If there is a message, update the text of the bridge widget if msg: self.use_bridge_widget.connection_error_message.setText(msg) # If we're on first launch, check if wasCanceled # If cancelled, stay in first launch widget and show buttons # Else, switch to use bridge if self.first_launch_widget.isVisible(): if self.tor_con.wasCanceled(): self.first_launch_widget.show_buttons() else: self.first_launch_widget.show_buttons() self.first_launch_widget.hide() self.use_bridge_widget.show() else: self.use_bridge_widget.show_buttons() def reload_settings(self): """ Reload the latest Tor settings, and reset to show the first-launch widget if it had been hidden. """ self.curr_settings.load() self.auto_connect_enabled = self.curr_settings.get("auto_connect") self.first_launch_widget.enable_autoconnect_checkbox.setChecked( self.auto_connect_enabled ) self.use_bridge_widget.hide() self.first_launch_widget.show_buttons() self.first_launch_widget.show() class Anim(QtWidgets.QLabel): """ Rocket ship animation base class """ force_update = QtCore.Signal(int) def __init__(self, parent, window, w, h, filename): super(Anim, self).__init__(parent=parent) self.window = window self.window.window_resized.connect(self.update_same_percent) self.w = w self.h = h self.percent = 0 self.used_percentages = [] self.setPixmap( QtGui.QPixmap.fromImage( QtGui.QImage( GuiCommon.get_resource_path(os.path.join("images", filename)) ) ) ) self.setFixedSize(self.w, self.h) self.update(0) self.force_update.connect(self.update) def update_same_percent(self): self.update(self.percent) def update(self, percent): self.percent = percent self.move() self.setGeometry(int(self.x), int(self.y), int(self.w), int(self.h)) def move(self): # Implement in child pass class AnimStars(Anim): """ Rocket ship animation part: stars """ def __init__(self, parent, window): super(AnimStars, self).__init__( parent, window, 740, 629, "tor-connect-stars.png" ) def move(self): self.x = self.window.width() - self.w self.y = 0 # Stars don't move until 10%, then move down if self.percent >= 10: self.y += self.percent * 6.6 class AnimShip(Anim): """ Rocket ship animation part: ship """ def __init__(self, parent, window): super(AnimShip, self).__init__(parent, window, 239, 545, "tor-connect-ship.png") def move(self): self.x = self.window.width() - self.w - 150 self.y = self.window.height() - self.h - 40 # Ship moves up self.y -= self.percent * 6.6 class AnimSmoke(Anim): """ Rocket ship animation part: smoke """ def __init__(self, parent, window): super(AnimSmoke, self).__init__( parent, window, 522, 158, "tor-connect-smoke.png" ) def move(self): self.x = self.window.width() - self.w self.y = self.window.height() - self.h + 50 # Smoke moves up until 50%, then moves down self.y -= self.percent * 6.6 if self.percent >= 50: self.y += self.percent * 6.7 class AutoConnectFirstLaunchWidget(QtWidgets.QWidget): """ When you first launch OnionShare, this is the widget that is displayed """ toggle_auto_connect = QtCore.Signal() connect_clicked = QtCore.Signal() open_tor_settings = QtCore.Signal() def __init__(self, common, settings): super(AutoConnectFirstLaunchWidget, self).__init__() self.common = common self.common.log("AutoConnectFirstLaunchWidget", "__init__") self.settings = settings # Description and checkbox description_label = QtWidgets.QLabel(strings._("gui_autoconnect_description")) self.enable_autoconnect_checkbox = ToggleCheckbox( strings._("gui_enable_autoconnect_checkbox") ) self.enable_autoconnect_checkbox.setChecked(self.settings.get("auto_connect")) self.enable_autoconnect_checkbox.clicked.connect(self._toggle_auto_connect) self.enable_autoconnect_checkbox.setFixedWidth(400) self.enable_autoconnect_checkbox.setStyleSheet( common.gui.css["enable_autoconnect"] ) description_layout = QtWidgets.QVBoxLayout() description_layout.addWidget(description_label) description_layout.addWidget(self.enable_autoconnect_checkbox) description_widget = QtWidgets.QWidget() description_widget.setLayout(description_layout) # Buttons self.connect_button = QtWidgets.QPushButton(strings._("gui_autoconnect_start")) self.connect_button.clicked.connect(self._connect_clicked) self.connect_button.setFixedWidth(150) self.connect_button.setStyleSheet(common.gui.css["autoconnect_start_button"]) self.configure_button = QtWidgets.QPushButton( strings._("gui_autoconnect_configure") ) self.configure_button.clicked.connect(self._open_tor_settings) self.configure_button.setFlat(True) self.configure_button.setStyleSheet( common.gui.css["autoconnect_configure_button"] ) cta_layout = QtWidgets.QHBoxLayout() cta_layout.addWidget(self.connect_button) cta_layout.addWidget(self.configure_button) cta_widget = QtWidgets.QWidget() cta_widget.setLayout(cta_layout) # Layout layout = QtWidgets.QVBoxLayout() layout.addWidget(description_widget) layout.addWidget(cta_widget) self.setLayout(layout) def hide_buttons(self): self.connect_button.hide() self.configure_button.hide() def show_buttons(self): self.connect_button.show() self.configure_button.show() def _toggle_auto_connect(self): self.toggle_auto_connect.emit() def _connect_clicked(self): self.connect_clicked.emit() def _open_tor_settings(self): self.open_tor_settings.emit() class AutoConnectUseBridgeWidget(QtWidgets.QWidget): """ If connecting fails, this is the widget that helps the user bypass censorship """ connect_clicked = QtCore.Signal() try_again_clicked = QtCore.Signal() open_tor_settings = QtCore.Signal() def __init__(self, common): super(AutoConnectUseBridgeWidget, self).__init__() self.common = common self.common.log("AutoConnectUseBridgeWidget", "__init__") # Heading label when we fail to connect to Tor. self.connection_status_label = QtWidgets.QLabel( strings._("gui_autoconnect_failed_to_connect_to_tor") ) self.connection_status_label.setTextFormat(QtCore.Qt.RichText) self.connection_status_label.setStyleSheet( common.gui.css["autoconnect_failed_to_connect_label"] ) # Tor connection error message self.connection_error_message = QtWidgets.QLabel( strings._("gui_autoconnect_connection_error_msg") ) self.connection_error_message.setTextFormat(QtCore.Qt.RichText) self.connection_error_message.setWordWrap(True) self.connection_error_message.setContentsMargins(0, 0, 0, 10) # Description self.description_label = QtWidgets.QLabel( strings._("gui_autoconnect_bridge_description") ) self.description_label.setTextFormat(QtCore.Qt.RichText) self.description_label.setWordWrap(True) self.description_label.setContentsMargins(0, 0, 0, 20) # Detection preference self.use_bridge = True self.no_bridge = QtWidgets.QRadioButton( strings._("gui_autoconnect_no_bridge") ) self.no_bridge.toggled.connect(self._toggle_no_bridge) self.detect_automatic_radio = QtWidgets.QRadioButton( strings._("gui_autoconnect_bridge_detect_automatic") ) self.detect_automatic_radio.toggled.connect(self._detect_automatic_toggled) self.detect_manual_radio = QtWidgets.QRadioButton( strings._("gui_autoconnect_bridge_detect_manual") ) self.detect_manual_radio.toggled.connect(self._detect_manual_toggled) detect_layout = QtWidgets.QVBoxLayout() detect_layout.addWidget(self.no_bridge) detect_layout.addWidget(self.detect_automatic_radio) detect_layout.addWidget(self.detect_manual_radio) bridge_setting_options = QtWidgets.QGroupBox( strings._("gui_autoconnect_bridge_setting_options") ) bridge_setting_options.setLayout(detect_layout) bridge_setting_options.setFlat(True) bridge_setting_options.setStyleSheet( common.gui.css["autoconnect_bridge_setting_options"] ) # Country list locale = self.common.settings.get("locale") if not locale: locale = "en" with open( GuiCommon.get_resource_path(os.path.join("countries", f"{locale}.json")) ) as f: countries = json.loads(f.read()) self.country_combobox = QtWidgets.QComboBox() self.country_combobox.setStyleSheet( common.gui.css["autoconnect_countries_combobox"] ) self.country_combobox.setIconSize(QtCore.QSize(26, 20)) for country_code in countries: icon = QtGui.QIcon( GuiCommon.get_resource_path(os.path.join("images", "countries", f"{country_code.lower()}.png")) ) self.country_combobox.addItem(icon, countries[country_code], country_code) # Task label self.task_label = QtWidgets.QLabel() self.task_label.setStyleSheet(common.gui.css["autoconnect_task_label"]) self.task_label.setAlignment(QtCore.Qt.AlignCenter) self.task_label.hide() # Buttons self.connect_button = QtWidgets.QPushButton( strings._("gui_autoconnect_start") ) self.connect_button.clicked.connect(self._connect_clicked) self.connect_button.setFixedWidth(150) self.connect_button.setStyleSheet(common.gui.css["autoconnect_start_button"]) self.configure_button = QtWidgets.QPushButton( strings._("gui_autoconnect_configure") ) self.configure_button.clicked.connect(self._open_tor_settings) self.configure_button.setFlat(True) self.configure_button.setStyleSheet( common.gui.css["autoconnect_configure_button"] ) # Error label self.error_label = QtWidgets.QLabel( strings._("gui_autoconnect_could_not_connect_to_tor_api") ) self.error_label.setStyleSheet(self.common.gui.css["tor_settings_error"]) self.error_label.setWordWrap(True) self.error_label.hide() self.progress = QtWidgets.QProgressBar() self.progress.setRange(0, 100) self.progress_label = QtWidgets.QLabel( strings._("gui_autoconnect_circumventing_censorship") ) self.progress_label.setAlignment(QtCore.Qt.AlignHCenter) self.progress.hide() self.progress_label.hide() cta_layout = QtWidgets.QHBoxLayout() cta_layout.addWidget(self.connect_button) cta_layout.addWidget(self.configure_button) cta_layout.addStretch() cta_widget = QtWidgets.QWidget() cta_widget.setLayout(cta_layout) # Layout layout = QtWidgets.QVBoxLayout() layout.addWidget(self.connection_status_label) layout.addWidget(self.connection_error_message) layout.addWidget(self.description_label) layout.addWidget(bridge_setting_options) layout.addWidget(self.country_combobox) layout.addWidget(self.task_label) layout.addWidget(cta_widget) layout.addWidget(self.progress) layout.addWidget(self.progress_label) layout.addWidget(self.error_label) self.setLayout(layout) self.detect_automatic_radio.setChecked(True) def hide_buttons(self): self.connect_button.hide() self.configure_button.hide() self.connection_error_message.hide() self.description_label.hide() self.error_label.hide() self.no_bridge.hide() self.detect_automatic_radio.hide() self.detect_manual_radio.hide() def show_buttons(self): self.connect_button.show() self.connection_error_message.show() self.description_label.show() self.configure_button.show() self.no_bridge.show() self.detect_automatic_radio.show() self.detect_manual_radio.show() def _toggle_no_bridge(self): self.use_bridge = not self.use_bridge def _detect_automatic_toggled(self): self.country_combobox.setEnabled(False) self.country_combobox.hide() def _detect_manual_toggled(self): self.country_combobox.setEnabled(True) self.country_combobox.show() def _connect_clicked(self): self.country_combobox.setEnabled(False) self.hide_buttons() self.connection_status_label.setText( strings._("gui_autoconnect_trying_to_connect_to_tor") ) print(self.use_bridge) if not self.use_bridge: self.try_again_clicked.emit() else: self.connect_clicked.emit() def _open_tor_settings(self): self.open_tor_settings.emit()