Merge pull request #1439 from SaptakS/auto-connect-ui

Implementation of the enable quickstart screen
This commit is contained in:
Saptak Sengupta 2021-12-10 21:25:50 +05:30 committed by GitHub
commit 9d86b943b2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 375 additions and 22 deletions

View file

@ -103,6 +103,7 @@ class Settings(object):
"socket_file_path": "/var/run/tor/control",
"auth_type": "no_auth",
"auth_password": "",
"auto_connect": False,
"use_autoupdate": True,
"autoupdate_timestamp": None,
"bridges_enabled": False,

View file

@ -36,6 +36,7 @@ class TestSettings:
"bridges_custom": "",
"persistent_tabs": [],
"theme": 0,
"auto_connect": False,
}
for key in settings_obj._settings:
# Skip locale, it will not always default to the same thing

View file

@ -0,0 +1,198 @@
# -*- coding: utf-8 -*-
"""
OnionShare | https://onionshare.org/
Copyright (C) 2014-2021 Micah Lee, et al. <micah@micahflee.com>
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/>.
"""
from PySide2 import QtCore, QtWidgets, QtGui
from onionshare_cli.settings import Settings
from . import strings
from .gui_common import GuiCommon, ToggleCheckbox
from .tor_connection import TorConnectionWidget
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, 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.parent = parent
# Was auto connected?
self.curr_settings = Settings(common)
self.curr_settings.load()
self.auto_connect_enabled = self.curr_settings.get("auto_connect")
# Onionshare logo
self.image_label = QtWidgets.QLabel()
self.image_label.setPixmap(
QtGui.QPixmap.fromImage(
QtGui.QImage(
GuiCommon.get_resource_path(
"images/{}_logo_text_bg.png".format(common.gui.color_mode)
)
)
)
)
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)
# 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.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)
# 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.hide()
# Error label
self.error_label = QtWidgets.QLabel()
self.error_label.setStyleSheet(self.common.gui.css["tor_settings_error"])
self.error_label.setWordWrap(True)
# CTA 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
content_layout = QtWidgets.QVBoxLayout()
content_layout.addStretch()
content_layout.addWidget(self.image)
content_layout.addWidget(description_widget)
content_layout.addWidget(self.tor_con)
content_layout.addWidget(cta_widget)
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.enable_autoconnect_checkbox.setChecked(True)
self.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.enable_autoconnect_checkbox.isChecked()
)
self.curr_settings.save()
def open_tor_settings(self):
self.parent.open_tor_settings_tab(from_autoconnect=True)
def connect_clicked(self):
"""
Connect button clicked. Try to connect to tor.
"""
self.common.log("AutoConnectTab", "connect_clicked")
self.error_label.setText("")
self.connect_button.hide()
self.configure_button.hide()
self.tor_con.show()
self.tor_con.start(self.curr_settings)
def tor_con_success(self):
"""
Finished testing tor connection.
"""
self.tor_con.hide()
self.connect_button.show()
self.configure_button.show()
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()
# Close the tab
self.close_this_tab.emit()
def tor_con_fail(self, msg):
"""
Finished testing tor connection.
"""
self.tor_con.hide()
self.connect_button.show()
self.configure_button.show()
self.error_label.setText(msg)
def reload_settings(self):
self.curr_settings.load()
self.auto_connect_enabled = self.curr_settings.get("auto_connect")
self.enable_autoconnect_checkbox.setChecked(self.auto_connect_enabled)

View file

@ -21,6 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import shutil
from pkg_resources import resource_filename
from PySide2 import QtCore, QtWidgets, QtGui
from . import strings
from onionshare_cli.onion import (
@ -149,6 +150,32 @@ class GuiCommon:
QStatusBar::item {
border: 0px;
}""",
"autoconnect_start_button": """
QPushButton {
background-color: #5fa416;
color: #ffffff;
padding: 10px;
border: 0;
border-radius: 5px;
}""",
"autoconnect_configure_button": """
QPushButton {
padding: 9px 29px;
color: #3f7fcf;
text-align: left;
}""",
"enable_autoconnect": """
QCheckBox {
margin-top: 30px;
background: #FCFCFC;
border: 1px solid #DDDBDA;
border-radius: 8px;
padding: 24px 16px;
}
QCheckBox::indicator {
width: 0;
height: 0;
}""",
# Common styles between modes and their child widgets
"mode_settings_toggle_advanced": """
QPushButton {
@ -509,3 +536,44 @@ class GuiCommon:
return strings._("error_port_not_available")
return None
class ToggleCheckbox(QtWidgets.QCheckBox):
def __init__(self, text):
super(ToggleCheckbox, self).__init__(text)
# Set default parameters
self.setCursor(QtCore.Qt.PointingHandCursor)
self.w = 50
self.h = 24
self.bg_color = "#D4D4D4"
self.circle_color = "#BDBDBD"
self.active_color = "#4E0D4E"
self.inactive_color = ""
def hitButton(self, pos):
return self.toggleRect.contains(pos)
def paintEvent(self, e):
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.setPen(QtCore.Qt.NoPen)
opt = QtWidgets.QStyleOptionButton()
opt.init(self)
self.initStyleOption(opt)
s = self.style()
s.drawControl(QtWidgets.QStyle.CE_CheckBox, opt, painter, self)
rect = QtCore.QRect(s.subElementRect(QtWidgets.QStyle.SE_CheckBoxContents, opt, self))
x = rect.width() - rect.x() - self.w + 20 # 20 is the padding between text and toggle
y = self.height() / 2 - self.h / 2 + self.y() / 2
self.toggleRect = QtCore.QRect(x, y, self.w, self.h)
painter.setBrush(QtGui.QColor(self.bg_color))
painter.drawRoundedRect(x, y, self.w, self.h, self.h / 2, self.h / 2)
if not self.isChecked():
painter.setBrush(QtGui.QColor(self.circle_color))
painter.drawEllipse(x, y - 3, self.h + 6, self.h + 6)
else:
painter.setBrush(QtGui.QColor(self.active_color))
painter.drawEllipse(x + self.w - (self.h + 6), y - 3, self.h + 6, self.h + 6)
painter.end()

View file

@ -23,8 +23,8 @@ import time
from PySide2 import QtCore, QtWidgets, QtGui
from . import strings
from .tor_connection import TorConnectionDialog
from .widgets import Alert
from .connection_tab import AutoConnectTab
from .update_checker import UpdateThread
from .tab_widget import TabWidget
from .gui_common import GuiCommon
@ -147,6 +147,9 @@ class MainWindow(QtWidgets.QMainWindow):
if len(self.common.settings.get("persistent_tabs")) > 0:
for mode_settings_id in self.common.settings.get("persistent_tabs"):
self.tabs.load_tab(mode_settings_id)
# If not connected to tor in beginning, show autoconnect tab
if not self.common.gui.onion.connected_to_tor:
self.tabs.new_tab_clicked()
else:
# Start with opening the first tab
self.tabs.new_tab_clicked()
@ -160,17 +163,6 @@ class MainWindow(QtWidgets.QMainWindow):
self.setCentralWidget(central_widget)
self.show()
# Start the "Connecting to Tor" dialog, which calls onion.connect()
tor_con = TorConnectionDialog(self.common)
tor_con.canceled.connect(self.tor_connection_canceled)
tor_con.open_tor_settings.connect(self.tor_connection_open_tor_settings)
if not self.common.gui.local_only:
tor_con.start()
self.settings_have_changed()
# After connecting to Tor, check for updates
self.check_for_updates()
# Create the close warning dialog -- the dialog widget needs to be in the constructor
# in order to test it
self.close_dialog = QtWidgets.QMessageBox()
@ -185,6 +177,9 @@ class MainWindow(QtWidgets.QMainWindow):
)
self.close_dialog.setDefaultButton(self.close_dialog.reject_button)
# Check for autoconnect
self.tabs.check_autoconnect_tab()
def tor_connection_canceled(self):
"""
If the user cancels before Tor finishes connecting, ask if they want to
@ -246,7 +241,12 @@ class MainWindow(QtWidgets.QMainWindow):
Open the TorSettingsTab
"""
self.common.log("MainWindow", "open_tor_settings")
self.tabs.open_tor_settings_tab()
from_autoconnect = False
for tab_id in self.tabs.tabs:
if type(self.tabs.tabs[tab_id]) is AutoConnectTab:
from_autoconnect = True
break
self.tabs.open_tor_settings_tab(from_autoconnect)
def open_settings(self):
"""

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -42,6 +42,10 @@
"gui_please_wait": "Starting… Click to cancel.",
"zip_progress_bar_format": "Compressing: %p%",
"gui_tor_settings_window_title": "Tor Settings",
"gui_autoconnect_description": "OnionShare relies on the Tor Network, run by thousands of volunteers around the world.",
"gui_enable_autoconnect_checkbox": "Enable automatically connecting to Tor",
"gui_autoconnect_start": "Connect to Tor",
"gui_autoconnect_configure": "Network Settings",
"gui_settings_window_title": "Settings",
"gui_settings_autoupdate_label": "Check for new version",
"gui_settings_autoupdate_option": "Notify me when a new version is available",

View file

@ -28,6 +28,7 @@ from .threads import EventHandlerThread
from .gui_common import GuiCommon
from .tor_settings_tab import TorSettingsTab
from .settings_tab import SettingsTab
from .connection_tab import AutoConnectTab
class TabWidget(QtWidgets.QTabWidget):
@ -98,6 +99,7 @@ class TabWidget(QtWidgets.QTabWidget):
if not (
type(self.tabs[tab_id]) is SettingsTab
or type(self.tabs[tab_id]) is TorSettingsTab
or type(self.tabs[tab_id]) is AutoConnectTab
):
self.tabs[tab_id].cleanup()
@ -138,6 +140,7 @@ class TabWidget(QtWidgets.QTabWidget):
if (
type(self.tabs[tab_id]) is SettingsTab
or type(self.tabs[tab_id]) is TorSettingsTab
or type(self.tabs[tab_id]) is AutoConnectTab
):
# Blank the server status indicator
self.status_bar.server_status_image_label.clear()
@ -159,8 +162,16 @@ class TabWidget(QtWidgets.QTabWidget):
pass
def new_tab_clicked(self):
# Create a new tab
self.add_tab()
# if already connected to tor or local only, create a new tab
# Else open the initial connection tab
if self.common.gui.local_only or self.common.gui.onion.is_authenticated():
self.add_tab()
else:
self.open_connection_tab()
def check_autoconnect_tab(self):
if type(self.tabs[0]) is AutoConnectTab:
self.tabs[0].check_autoconnect()
def load_tab(self, mode_settings_id):
# Load the tab's mode settings
@ -198,6 +209,24 @@ class TabWidget(QtWidgets.QTabWidget):
# Bring the window to front, in case this is being added by an event
self.bring_to_front.emit()
def open_connection_tab(self):
self.common.log("TabWidget", "open_connection_tab")
# See if a connection tab is already open, and if so switch to it
for tab_id in self.tabs:
if type(self.tabs[tab_id]) is AutoConnectTab:
self.setCurrentIndex(self.indexOf(self.tabs[tab_id]))
return
connection_tab = AutoConnectTab(self.common, self.current_tab_id, self.status_bar, parent=self)
connection_tab.close_this_tab.connect(self.close_connection_tab)
connection_tab.tor_is_connected.connect(self.tor_is_connected)
connection_tab.tor_is_disconnected.connect(self.tor_is_disconnected)
self.tabs[self.current_tab_id] = connection_tab
self.current_tab_id += 1
index = self.addTab(connection_tab, strings._("gui_autoconnect_start"))
self.setCurrentIndex(index)
def open_settings_tab(self):
self.common.log("TabWidget", "open_settings_tab")
@ -214,7 +243,7 @@ class TabWidget(QtWidgets.QTabWidget):
index = self.addTab(settings_tab, strings._("gui_settings_window_title"))
self.setCurrentIndex(index)
def open_tor_settings_tab(self):
def open_tor_settings_tab(self, from_autoconnect=False):
self.common.log("TabWidget", "open_tor_settings_tab")
# See if a settings tab is already open, and if so switch to it
@ -224,7 +253,7 @@ class TabWidget(QtWidgets.QTabWidget):
return
self.tor_settings_tab = TorSettingsTab(
self.common, self.current_tab_id, self.are_tabs_active(), self.status_bar
self.common, self.current_tab_id, self.are_tabs_active(), self.status_bar, from_autoconnect
)
self.tor_settings_tab.close_this_tab.connect(self.close_tor_settings_tab)
self.tor_settings_tab.tor_is_connected.connect(self.tor_is_connected)
@ -281,6 +310,7 @@ class TabWidget(QtWidgets.QTabWidget):
if not (
type(self.tabs[tab_id]) is SettingsTab
or type(self.tabs[tab_id]) is TorSettingsTab
or type(self.tabs[tab_id]) is AutoConnectTab
):
tab = self.widget(self.indexOf(self.tabs[tab_id]))
if tab.settings.get("persistent", "enabled"):
@ -298,6 +328,7 @@ class TabWidget(QtWidgets.QTabWidget):
if (
type(self.tabs[tab_id]) is SettingsTab
or type(self.tabs[tab_id]) is TorSettingsTab
or type(self.tabs[tab_id]) is AutoConnectTab
):
self.common.log("TabWidget", "closing a settings tab")
@ -333,6 +364,14 @@ class TabWidget(QtWidgets.QTabWidget):
else:
self.common.log("TabWidget", "user does not want to close the tab")
def close_connection_tab(self):
self.common.log("TabWidget", "close_connection_tab")
for tab_id in self.tabs:
if type(self.tabs[tab_id]) is AutoConnectTab:
index = self.indexOf(self.tabs[tab_id])
self.close_tab(index)
return
def close_settings_tab(self):
self.common.log("TabWidget", "close_settings_tab")
for tab_id in self.tabs:
@ -343,6 +382,9 @@ class TabWidget(QtWidgets.QTabWidget):
def close_tor_settings_tab(self):
self.common.log("TabWidget", "close_tor_settings_tab")
for tab_id in self.tabs:
if type(self.tabs[tab_id]) is AutoConnectTab:
self.tabs[tab_id].reload_settings()
for tab_id in self.tabs:
if type(self.tabs[tab_id]) is TorSettingsTab:
index = self.indexOf(self.tabs[tab_id])
@ -357,6 +399,7 @@ class TabWidget(QtWidgets.QTabWidget):
if not (
type(self.tabs[tab_id]) is SettingsTab
or type(self.tabs[tab_id]) is TorSettingsTab
or type(self.tabs[tab_id]) is AutoConnectTab
):
mode = self.tabs[tab_id].get_mode()
if mode:
@ -381,7 +424,10 @@ class TabWidget(QtWidgets.QTabWidget):
if type(self.tabs[tab_id]) is SettingsTab:
self.tabs[tab_id].tor_is_connected()
else:
if not type(self.tabs[tab_id]) is TorSettingsTab:
if not (
type(self.tabs[tab_id]) is TorSettingsTab
or type(self.tabs[tab_id]) is AutoConnectTab
):
mode = self.tabs[tab_id].get_mode()
if mode:
mode.tor_connection_started()
@ -391,7 +437,10 @@ class TabWidget(QtWidgets.QTabWidget):
if type(self.tabs[tab_id]) is SettingsTab:
self.tabs[tab_id].tor_is_disconnected()
else:
if not type(self.tabs[tab_id]) is TorSettingsTab:
if not (
type(self.tabs[tab_id]) is TorSettingsTab
or type(self.tabs[tab_id]) is AutoConnectTab
):
mode = self.tabs[tab_id].get_mode()
if mode:
mode.tor_connection_stopped()

View file

@ -43,7 +43,7 @@ class TorSettingsTab(QtWidgets.QWidget):
tor_is_connected = QtCore.Signal()
tor_is_disconnected = QtCore.Signal()
def __init__(self, common, tab_id, are_tabs_active, status_bar):
def __init__(self, common, tab_id, are_tabs_active, status_bar, from_autoconnect=False):
super(TorSettingsTab, self).__init__()
self.common = common
@ -54,6 +54,7 @@ class TorSettingsTab(QtWidgets.QWidget):
self.system = platform.system()
self.tab_id = tab_id
self.from_autoconnect = from_autoconnect
# Connection type: either automatic, control port, or socket file
@ -303,6 +304,24 @@ class TorSettingsTab(QtWidgets.QWidget):
)
connection_type_radio_group.setLayout(connection_type_radio_group_layout)
# Quickstart settings
self.autoconnect_checkbox = QtWidgets.QCheckBox(
strings._("gui_enable_autoconnect_checkbox")
)
self.autoconnect_checkbox.toggled.connect(
self.autoconnect_toggled
)
left_column_settings = QtWidgets.QVBoxLayout()
connection_type_radio_group.setFixedHeight(300)
left_column_settings.addWidget(connection_type_radio_group)
left_column_settings.addSpacing(20)
left_column_settings.addWidget(self.autoconnect_checkbox)
left_column_settings.addStretch()
left_column_settings.setContentsMargins(0, 0, 0, 0)
left_column_setting_widget = QtWidgets.QWidget()
left_column_setting_widget.setLayout(left_column_settings)
# The Bridges options are not exclusive (enabling Bridges offers obfs4 or custom bridges)
connection_type_bridges_radio_group_layout = QtWidgets.QVBoxLayout()
connection_type_bridges_radio_group_layout.addWidget(self.bridges)
@ -322,7 +341,7 @@ class TorSettingsTab(QtWidgets.QWidget):
# Settings are in columns
columns_layout = QtWidgets.QHBoxLayout()
columns_layout.addWidget(connection_type_radio_group)
columns_layout.addWidget(left_column_setting_widget)
columns_layout.addSpacing(20)
columns_layout.addLayout(connection_type_layout, stretch=1)
columns_wrapper = QtWidgets.QWidget()
@ -391,6 +410,10 @@ class TorSettingsTab(QtWidgets.QWidget):
self.old_settings = Settings(self.common)
self.old_settings.load()
# Check if autoconnect was enabled
if self.old_settings.get("auto_connect"):
self.autoconnect_checkbox.setCheckState(QtCore.Qt.Checked)
connection_type = self.old_settings.get("connection_type")
if connection_type == "bundled":
if self.connection_type_bundled_radio.isEnabled():
@ -477,6 +500,12 @@ class TorSettingsTab(QtWidgets.QWidget):
self.bridge_use_checkbox.setCheckState(QtCore.Qt.Unchecked)
self.bridge_settings.hide()
def autoconnect_toggled(self):
"""
Auto connect checkbox clicked
"""
self.common.log("TorSettingsTab", "autoconnect_checkbox_clicked")
def active_tabs_changed(self, are_tabs_active):
if are_tabs_active:
self.main_widget.hide()
@ -664,7 +693,7 @@ class TorSettingsTab(QtWidgets.QWidget):
# If Tor isn't connected, or if Tor settings have changed, Reinitialize
# the Onion object
reboot_onion = False
if not self.common.gui.local_only:
if not self.common.gui.local_only and not self.from_autoconnect:
if self.common.gui.onion.is_authenticated():
self.common.log(
"TorSettingsTab", "save_clicked", "Connected to Tor"
@ -777,6 +806,9 @@ class TorSettingsTab(QtWidgets.QWidget):
settings = Settings(self.common)
settings.load() # To get the last update timestamp
# autoconnect
settings.set("auto_connect", self.autoconnect_checkbox.isChecked())
# Tor connection
if self.connection_type_bundled_radio.isChecked():
settings.set("connection_type", "bundled")