Merge pull request #1449 from onionshare/1442_settings_tabs

Switch Settings and Tor Settings dialogs into tabs
This commit is contained in:
Micah Lee 2021-11-11 14:41:49 -08:00 committed by GitHub
commit 6a8766671b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 739 additions and 438 deletions

View File

@ -310,32 +310,15 @@ class Common:
def get_tor_paths(self): def get_tor_paths(self):
if self.platform == "Linux": if self.platform == "Linux":
# Look in resources first tor_path = shutil.which("tor")
base_path = self.get_resource_path("tor") if not tor_path:
if os.path.exists(base_path): raise CannotFindTor()
self.log( obfs4proxy_file_path = shutil.which("obfs4proxy")
"Common", "get_tor_paths", f"using tor binaries in {base_path}" snowflake_file_path = shutil.which("snowflake-client")
) meek_client_file_path = shutil.which("meek-client")
tor_path = os.path.join(base_path, "tor") prefix = os.path.dirname(os.path.dirname(tor_path))
tor_geo_ip_file_path = os.path.join(base_path, "geoip") tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip")
tor_geo_ipv6_file_path = os.path.join(base_path, "geoip6") tor_geo_ipv6_file_path = os.path.join(prefix, "share/tor/geoip6")
obfs4proxy_file_path = os.path.join(base_path, "obfs4proxy")
snowflake_file_path = os.path.join(base_path, "snowflake-client")
meek_client_file_path = os.path.join(base_path, "meek-client")
else:
# Fallback to looking in the path
self.log(
"Common", "get_tor_paths", f"using tor binaries in system path"
)
tor_path = shutil.which("tor")
if not tor_path:
raise CannotFindTor()
obfs4proxy_file_path = shutil.which("obfs4proxy")
snowflake_file_path = shutil.which("snowflake-client")
meek_client_file_path = shutil.which("meek-client")
prefix = os.path.dirname(os.path.dirname(tor_path))
tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip")
tor_geo_ipv6_file_path = os.path.join(prefix, "share/tor/geoip6")
elif self.platform == "Windows": elif self.platform == "Windows":
base_path = self.get_resource_path("tor") base_path = self.get_resource_path("tor")
tor_path = os.path.join(base_path, "Tor", "tor.exe") tor_path = os.path.join(base_path, "Tor", "tor.exe")
@ -345,26 +328,15 @@ class Common:
tor_geo_ip_file_path = os.path.join(base_path, "Data", "Tor", "geoip") tor_geo_ip_file_path = os.path.join(base_path, "Data", "Tor", "geoip")
tor_geo_ipv6_file_path = os.path.join(base_path, "Data", "Tor", "geoip6") tor_geo_ipv6_file_path = os.path.join(base_path, "Data", "Tor", "geoip6")
elif self.platform == "Darwin": elif self.platform == "Darwin":
# Look in resources first tor_path = shutil.which("tor")
base_path = self.get_resource_path("tor") if not tor_path:
if os.path.exists(base_path): raise CannotFindTor()
tor_path = os.path.join(base_path, "tor") obfs4proxy_file_path = shutil.which("obfs4proxy")
tor_geo_ip_file_path = os.path.join(base_path, "geoip") snowflake_file_path = shutil.which("snowflake-client")
tor_geo_ipv6_file_path = os.path.join(base_path, "geoip6") meek_client_file_path = shutil.which("meek-client")
obfs4proxy_file_path = os.path.join(base_path, "obfs4proxy") prefix = os.path.dirname(os.path.dirname(tor_path))
meek_client_file_path = os.path.join(base_path, "meek-client") tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip")
snowflake_file_path = os.path.join(base_path, "snowflake-client") tor_geo_ipv6_file_path = os.path.join(prefix, "share/tor/geoip6")
else:
# Fallback to looking in the path
tor_path = shutil.which("tor")
if not tor_path:
raise CannotFindTor()
obfs4proxy_file_path = shutil.which("obfs4proxy")
snowflake_file_path = shutil.which("snowflake-client")
meek_client_file_path = shutil.which("meek-client")
prefix = os.path.dirname(os.path.dirname(tor_path))
tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip")
tor_geo_ipv6_file_path = os.path.join(prefix, "share/tor/geoip6")
elif self.platform == "BSD": elif self.platform == "BSD":
tor_path = "/usr/local/bin/tor" tor_path = "/usr/local/bin/tor"
tor_geo_ip_file_path = "/usr/local/share/tor/geoip" tor_geo_ip_file_path = "/usr/local/share/tor/geoip"

View File

@ -85,6 +85,10 @@ class Meek(object):
self.common.log("Meek", "start", "Starting meek client") self.common.log("Meek", "start", "Starting meek client")
if self.common.platform == "Windows": if self.common.platform == "Windows":
env = os.environ.copy()
for key in self.meek_env:
env[key] = self.meek_env[key]
# In Windows, hide console window when opening meek-client.exe subprocess # In Windows, hide console window when opening meek-client.exe subprocess
startupinfo = subprocess.STARTUPINFO() startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
@ -100,7 +104,7 @@ class Meek(object):
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
startupinfo=startupinfo, startupinfo=startupinfo,
bufsize=1, bufsize=1,
env=self.meek_env, env=env,
text=True, text=True,
) )
else: else:
@ -129,6 +133,7 @@ class Meek(object):
# read stdout without blocking # read stdout without blocking
try: try:
line = q.get_nowait() line = q.get_nowait()
self.common.log("Meek", "start", line.strip())
except Empty: except Empty:
# no stdout yet? # no stdout yet?
pass pass
@ -136,10 +141,17 @@ class Meek(object):
if "CMETHOD meek socks5" in line: if "CMETHOD meek socks5" in line:
self.meek_host = line.split(" ")[3].split(":")[0] self.meek_host = line.split(" ")[3].split(":")[0]
self.meek_port = line.split(" ")[3].split(":")[1] self.meek_port = line.split(" ")[3].split(":")[1]
self.common.log("Meek", "start", f"Meek host is {self.meek_host}") self.common.log(
self.common.log("Meek", "start", f"Meek port is {self.meek_port}") "Meek",
"start",
f"Meek running on {self.meek_host}:{self.meek_port}",
)
break break
if "CMETHOD-ERROR" in line:
self.cleanup()
raise MeekNotRunning()
if self.meek_port: if self.meek_port:
self.meek_proxies = { self.meek_proxies = {
"http": f"socks5h://{self.meek_host}:{self.meek_port}", "http": f"socks5h://{self.meek_host}:{self.meek_port}",
@ -147,6 +159,7 @@ class Meek(object):
} }
else: else:
self.common.log("Meek", "start", "Could not obtain the meek port") self.common.log("Meek", "start", "Could not obtain the meek port")
self.cleanup()
raise MeekNotRunning() raise MeekNotRunning()
def cleanup(self): def cleanup(self):

View File

@ -200,8 +200,6 @@ class Onion(object):
) )
return return
self.common.log("Onion", "connect")
# Either use settings that are passed in, or use them from common # Either use settings that are passed in, or use them from common
if custom_settings: if custom_settings:
self.settings = custom_settings self.settings = custom_settings
@ -212,6 +210,12 @@ class Onion(object):
self.common.load_settings() self.common.load_settings()
self.settings = self.common.settings self.settings = self.common.settings
self.common.log(
"Onion",
"connect",
f"connection_type={self.settings.get('connection_type')}",
)
# The Tor controller # The Tor controller
self.c = None self.c = None
@ -315,40 +319,39 @@ class Onion(object):
f.write(torrc_template) f.write(torrc_template)
# Bridge support # Bridge support
if self.settings.get("tor_bridges_use_obfs4"): if self.settings.get("bridges_enabled"):
with open( if self.settings.get("bridges_type") == "built-in":
self.common.get_resource_path("torrc_template-obfs4") if self.settings.get("bridges_builtin_pt") == "obfs4":
) as o: with open(
for line in o: self.common.get_resource_path("torrc_template-obfs4")
f.write(line) ) as o:
elif self.settings.get("tor_bridges_use_meek_lite_azure"): f.write(o.read())
with open( elif self.settings.get("bridges_builtin_pt") == "meek-azure":
self.common.get_resource_path("torrc_template-meek_lite_azure") with open(
) as o: self.common.get_resource_path(
for line in o: "torrc_template-meek_lite_azure"
f.write(line) )
elif self.settings.get("tor_bridges_use_snowflake"): ) as o:
with open( f.write(o.read())
self.common.get_resource_path("torrc_template-snowflake") elif self.settings.get("bridges_builtin_pt") == "snowflake":
) as o: with open(
for line in o: self.common.get_resource_path(
f.write(line) "torrc_template-snowflake"
)
) as o:
f.write(o.read())
elif self.settings.get("tor_bridges_use_moat"): elif self.settings.get("bridges_type") == "moat":
for line in self.settings.get("tor_bridges_use_moat_bridges").split( for line in self.settings.get("bridges_moat").split("\n"):
"\n" if line.strip() != "":
): f.write(f"Bridge {line}\n")
if line.strip() != "": f.write("\nUseBridges 1\n")
f.write(f"Bridge {line}\n")
f.write("\nUseBridges 1\n")
elif self.settings.get("tor_bridges_use_custom_bridges"): elif self.settings.get("bridges_type") == "custom":
for line in self.settings.get( for line in self.settings.get("bridges_custom").split("\n"):
"tor_bridges_use_custom_bridges" if line.strip() != "":
).split("\n"): f.write(f"Bridge {line}\n")
if line.strip() != "": f.write("\nUseBridges 1\n")
f.write(f"Bridge {line}\n")
f.write("\nUseBridges 1\n")
# Execute a tor subprocess # Execute a tor subprocess
start_ts = time.time() start_ts = time.time()
@ -421,11 +424,7 @@ class Onion(object):
time.sleep(0.2) time.sleep(0.2)
# If using bridges, it might take a bit longer to connect to Tor # If using bridges, it might take a bit longer to connect to Tor
if ( if self.settings.get("bridges_enabled"):
self.settings.get("tor_bridges_use_custom_bridges")
or self.settings.get("tor_bridges_use_obfs4")
or self.settings.get("tor_bridges_use_meek_lite_azure")
):
# Only override timeout if a custom timeout has not been passed in # Only override timeout if a custom timeout has not been passed in
if connect_timeout == 120: if connect_timeout == 120:
connect_timeout = 150 connect_timeout = 150

View File

@ -105,13 +105,11 @@ class Settings(object):
"auth_password": "", "auth_password": "",
"use_autoupdate": True, "use_autoupdate": True,
"autoupdate_timestamp": None, "autoupdate_timestamp": None,
"no_bridges": True, "bridges_enabled": False,
"tor_bridges_use_obfs4": False, "bridges_type": "built-in", # "built-in", "moat", or "custom"
"tor_bridges_use_meek_lite_azure": False, "bridges_builtin_pt": "obfs4", # "obfs4", "meek-azure", or "snowflake"
"tor_bridges_use_snowflake": False, "bridges_moat": "",
"tor_bridges_use_moat": False, "bridges_custom": "",
"tor_bridges_use_moat_bridges": "",
"tor_bridges_use_custom_bridges": "",
"persistent_tabs": [], "persistent_tabs": [],
"locale": None, # this gets defined in fill_in_defaults() "locale": None, # this gets defined in fill_in_defaults()
"theme": 0, "theme": 0,

View File

@ -29,13 +29,11 @@ class TestSettings:
"auth_password": "", "auth_password": "",
"use_autoupdate": True, "use_autoupdate": True,
"autoupdate_timestamp": None, "autoupdate_timestamp": None,
"no_bridges": True, "bridges_enabled": False,
"tor_bridges_use_obfs4": False, "bridges_type": "built-in",
"tor_bridges_use_meek_lite_azure": False, "bridges_builtin_pt": "obfs4",
"tor_bridges_use_snowflake": False, "bridges_moat": "",
"tor_bridges_use_moat": False, "bridges_custom": "",
"tor_bridges_use_moat_bridges": "",
"tor_bridges_use_custom_bridges": "",
"persistent_tabs": [], "persistent_tabs": [],
"theme": 0, "theme": 0,
} }
@ -96,10 +94,11 @@ class TestSettings:
assert settings_obj.get("use_autoupdate") is True assert settings_obj.get("use_autoupdate") is True
assert settings_obj.get("autoupdate_timestamp") is None assert settings_obj.get("autoupdate_timestamp") is None
assert settings_obj.get("autoupdate_timestamp") is None assert settings_obj.get("autoupdate_timestamp") is None
assert settings_obj.get("no_bridges") is True assert settings_obj.get("bridges_enabled") is False
assert settings_obj.get("tor_bridges_use_obfs4") is False assert settings_obj.get("bridges_type") == "built-in"
assert settings_obj.get("tor_bridges_use_meek_lite_azure") is False assert settings_obj.get("bridges_builtin_pt") == "obfs4"
assert settings_obj.get("tor_bridges_use_custom_bridges") == "" assert settings_obj.get("bridges_moat") == ""
assert settings_obj.get("bridges_custom") == ""
def test_set_version(self, settings_obj): def test_set_version(self, settings_obj):
settings_obj.set("version", "CUSTOM_VERSION") settings_obj.set("version", "CUSTOM_VERSION")
@ -142,10 +141,10 @@ class TestSettings:
def test_set_custom_bridge(self, settings_obj): def test_set_custom_bridge(self, settings_obj):
settings_obj.set( settings_obj.set(
"tor_bridges_use_custom_bridges", "bridges_custom",
"Bridge 45.3.20.65:9050 21300AD88890A49C429A6CB9959CFD44490A8F6E", "Bridge 45.3.20.65:9050 21300AD88890A49C429A6CB9959CFD44490A8F6E",
) )
assert ( assert (
settings_obj._settings["tor_bridges_use_custom_bridges"] settings_obj._settings["bridges_custom"]
== "Bridge 45.3.20.65:9050 21300AD88890A49C429A6CB9959CFD44490A8F6E" == "Bridge 45.3.20.65:9050 21300AD88890A49C429A6CB9959CFD44490A8F6E"
) )

View File

@ -65,7 +65,7 @@ python scripts\get-tor-windows.py
### Compile dependencies ### Compile dependencies
Install Go. The simplest way to make sure everything works is to install Go by following [these instructions](https://golang.org/doc/install). Install Go. The simplest way to make sure everything works is to install Go by following [these instructions](https://golang.org/doc/install). (In Windows, make sure to install the 32-bit version of Go, such as `go1.17.3.windows-386.msi`.)
Download and compile `meek-client`: Download and compile `meek-client`:

View File

@ -28,6 +28,7 @@ import shutil
import os import os
import subprocess import subprocess
import inspect import inspect
import platform
def main(): def main():
@ -46,16 +47,21 @@ def main():
root_path = os.path.dirname( root_path = os.path.dirname(
os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
) )
dist_path = os.path.join(root_path, "src", "onionshare", "resources", "tor") if platform.system() == "Windows":
dist_path = os.path.join(root_path, "src", "onionshare", "resources", "tor", "Tor")
bin_filename = "meek-client.exe"
else:
dist_path = os.path.join(root_path, "src", "onionshare", "resources", "tor")
bin_filename = "meek-client"
bin_path = os.path.expanduser("~/go/bin/meek-client") bin_path = os.path.join(os.path.expanduser("~"), "go", "bin", bin_filename)
shutil.copyfile( shutil.copyfile(
os.path.join(bin_path), os.path.join(bin_path),
os.path.join(dist_path, "meek-client"), os.path.join(dist_path, bin_filename),
) )
os.chmod(os.path.join(dist_path, "meek-client"), 0o755) os.chmod(os.path.join(dist_path, bin_filename), 0o755)
print(f"Installed meek-client in {dist_path}") print(f"Installed {bin_filename} in {dist_path}")
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -34,10 +34,10 @@ import requests
def main(): def main():
tarball_url = "https://dist.torproject.org/torbrowser/11.0a9/tor-browser-linux64-11.0a9_en-US.tar.xz" tarball_url = "https://dist.torproject.org/torbrowser/11.0a10/tor-browser-linux64-11.0a10_en-US.tar.xz"
tarball_filename = "tor-browser-linux64-11.0a9_en-US.tar.xz" tarball_filename = "tor-browser-linux64-11.0a10_en-US.tar.xz"
expected_tarball_sha256 = ( expected_tarball_sha256 = (
"cba4a2120b4f847d1ade637e41e69bd01b2e70b4a13e41fe8e69d0424fcf7ca7" "5d3e2ebc4fb6a10f44624359bc2a5a151a57e8402cbd8563d15f9b2524374f1f"
) )
# Build paths # Build paths

View File

@ -34,10 +34,10 @@ import requests
def main(): def main():
dmg_url = "https://dist.torproject.org/torbrowser/11.0a7/TorBrowser-11.0a7-osx64_en-US.dmg" dmg_url = "https://dist.torproject.org/torbrowser/11.0a10/TorBrowser-11.0a10-osx64_en-US.dmg"
dmg_filename = "TorBrowser-11.0a7-osx64_en-US.dmg" dmg_filename = "TorBrowser-11.0a10-osx64_en-US.dmg"
expected_dmg_sha256 = ( expected_dmg_sha256 = (
"46594cefa29493150d1c0e1933dd656aafcb6b51ef310d44ac059eed2fd1388e" "c6823a28fd28205437564815f93011ff93b7972da2a8ce16919adfc65909e7b9"
) )
# Build paths # Build paths
@ -101,6 +101,14 @@ def main():
os.path.join(dist_path, "obfs4proxy"), os.path.join(dist_path, "obfs4proxy"),
) )
os.chmod(os.path.join(dist_path, "obfs4proxy"), 0o755) os.chmod(os.path.join(dist_path, "obfs4proxy"), 0o755)
# snowflake-client binary
shutil.copyfile(
os.path.join(
dmg_tor_path, "MacOS", "Tor", "PluggableTransports", "snowflake-client"
),
os.path.join(dist_path, "snowflake-client"),
)
os.chmod(os.path.join(dist_path, "snowflake-client"), 0o755)
# Eject dmg # Eject dmg
subprocess.call(["diskutil", "eject", "/Volumes/Tor Browser"]) subprocess.call(["diskutil", "eject", "/Volumes/Tor Browser"])

View File

@ -33,10 +33,10 @@ import requests
def main(): def main():
exe_url = "https://dist.torproject.org/torbrowser/11.0a7/torbrowser-install-11.0a7_en-US.exe" exe_url = "https://dist.torproject.org/torbrowser/11.0a10/torbrowser-install-11.0a10_en-US.exe"
exe_filename = "torbrowser-install-11.0a7_en-US.exe" exe_filename = "torbrowser-install-11.0a10_en-US.exe"
expected_exe_sha256 = ( expected_exe_sha256 = (
"8b2013669d88e3ae8fa9bc17a3495eaac9475f79a849354e826e5132811a860b" "f567dd8368dea0a8d7bbf7c19ece7840f93d493e70662939b92f5058c8dc8d2d"
) )
# Build paths # Build paths
root_path = os.path.dirname( root_path = os.path.dirname(

View File

@ -93,6 +93,7 @@ class GuiCommon:
share_zip_progess_bar_chunk_color = "#4E064F" share_zip_progess_bar_chunk_color = "#4E064F"
history_background_color = "#ffffff" history_background_color = "#ffffff"
history_label_color = "#000000" history_label_color = "#000000"
settings_error_color = "#FF0000"
if color_mode == "dark": if color_mode == "dark":
header_color = "#F2F2F2" header_color = "#F2F2F2"
title_color = "#F2F2F2" title_color = "#F2F2F2"
@ -103,6 +104,7 @@ class GuiCommon:
share_zip_progess_bar_border_color = "#F2F2F2" share_zip_progess_bar_border_color = "#F2F2F2"
history_background_color = "#191919" history_background_color = "#191919"
history_label_color = "#ffffff" history_label_color = "#ffffff"
settings_error_color = "#FF9999"
return { return {
# OnionShareGui styles # OnionShareGui styles
@ -281,6 +283,11 @@ class GuiCommon:
QLabel { QLabel {
color: #cc0000; color: #cc0000;
}""", }""",
"tor_not_connected_label": """
QLabel {
font-size: 16px;
font-style: italic;
}""",
# New tab # New tab
"new_tab_button_image": """ "new_tab_button_image": """
QLabel { QLabel {
@ -392,10 +399,12 @@ class GuiCommon:
QPushButton { QPushButton {
padding: 5px 10px; padding: 5px 10px;
}""", }""",
# Moat dialog # Tor Settings dialogs
"moat_error": """ "tor_settings_error": """
QLabel { QLabel {
color: #990000; color: """
+ settings_error_color
+ """;
} }
""", """,
} }

View File

@ -23,9 +23,7 @@ import time
from PySide2 import QtCore, QtWidgets, QtGui from PySide2 import QtCore, QtWidgets, QtGui
from . import strings from . import strings
from .tor_connection_dialog import TorConnectionDialog from .tor_connection import TorConnectionDialog
from .tor_settings_dialog import TorSettingsDialog
from .settings_dialog import SettingsDialog
from .widgets import Alert from .widgets import Alert
from .update_checker import UpdateThread from .update_checker import UpdateThread
from .tab_widget import TabWidget from .tab_widget import TabWidget
@ -245,21 +243,17 @@ class MainWindow(QtWidgets.QMainWindow):
def open_tor_settings(self): def open_tor_settings(self):
""" """
Open the TorSettingsDialog. Open the TorSettingsTab
""" """
self.common.log("MainWindow", "open_tor_settings") self.common.log("MainWindow", "open_tor_settings")
d = TorSettingsDialog(self.common) self.tabs.open_tor_settings_tab()
d.settings_saved.connect(self.settings_have_changed)
d.exec_()
def open_settings(self): def open_settings(self):
""" """
Open the SettingsDialog. Open the SettingsTab
""" """
self.common.log("MainWindow", "open_settings") self.common.log("MainWindow", "open_settings")
d = SettingsDialog(self.common) self.tabs.open_settings_tab()
d.settings_saved.connect(self.settings_have_changed)
d.exec_()
def settings_have_changed(self): def settings_have_changed(self):
self.common.log("OnionShareGui", "settings_have_changed") self.common.log("OnionShareGui", "settings_have_changed")

View File

@ -26,7 +26,7 @@ import json
from . import strings from . import strings
from .gui_common import GuiCommon from .gui_common import GuiCommon
from onionshare_cli.meek import MeekNotFound from onionshare_cli.meek import MeekNotFound, MeekNotRunning
class MoatDialog(QtWidgets.QDialog): class MoatDialog(QtWidgets.QDialog):
@ -70,7 +70,7 @@ class MoatDialog(QtWidgets.QDialog):
# Error label # Error label
self.error_label = QtWidgets.QLabel() self.error_label = QtWidgets.QLabel()
self.error_label.setStyleSheet(self.common.gui.css["moat_error"]) self.error_label.setStyleSheet(self.common.gui.css["tor_settings_error"])
self.error_label.hide() self.error_label.hide()
# Buttons # Buttons
@ -237,7 +237,13 @@ class MoatThread(QtCore.QThread):
try: try:
self.meek.start() self.meek.start()
except MeekNotFound: except MeekNotFound:
self.common.log("MoatThread", "run", f"Could not find the Meek Client") self.common.log("MoatThread", "run", f"Could not find meek-client")
self.bridgedb_error.emit()
return
except MeekNotRunning:
self.common.log(
"MoatThread", "run", f"Ran meek-client, but there was an error"
)
self.bridgedb_error.emit() self.bridgedb_error.emit()
return return

View File

@ -71,7 +71,8 @@
"gui_settings_bridge_custom_radio_option": "Provide a bridge you learned about from a trusted source", "gui_settings_bridge_custom_radio_option": "Provide a bridge you learned about from a trusted source",
"gui_settings_bridge_custom_placeholder": "type address:port (one per line)", "gui_settings_bridge_custom_placeholder": "type address:port (one per line)",
"gui_settings_moat_bridges_invalid": "You have not requested a bridge from torproject.org yet.", "gui_settings_moat_bridges_invalid": "You have not requested a bridge from torproject.org yet.",
"gui_settings_tor_bridges_invalid": "None of the bridges you added work.\nDouble-check them or add others.", "gui_settings_tor_bridges_invalid": "None of the bridges you added work. Double-check them or add others.",
"gui_settings_stop_active_tabs_label": "There are services running in some of your tabs.\nYou must stop all services to change your Tor settings.",
"gui_settings_button_save": "Save", "gui_settings_button_save": "Save",
"gui_settings_button_cancel": "Cancel", "gui_settings_button_cancel": "Cancel",
"gui_settings_button_help": "Help", "gui_settings_button_help": "Help",
@ -228,5 +229,6 @@
"moat_captcha_reload": "Reload", "moat_captcha_reload": "Reload",
"moat_bridgedb_error": "Error contacting BridgeDB.", "moat_bridgedb_error": "Error contacting BridgeDB.",
"moat_captcha_error": "The solution is not correct. Please try again.", "moat_captcha_error": "The solution is not correct. Please try again.",
"moat_solution_empty_error": "You must enter the characters from the image" "moat_solution_empty_error": "You must enter the characters from the image",
"mode_tor_not_connected_label": "OnionShare is not connected to the Tor network"
} }

View File

@ -19,57 +19,30 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
from PySide2 import QtCore, QtWidgets, QtGui from PySide2 import QtCore, QtWidgets, QtGui
from PySide2.QtCore import Slot, Qt
from PySide2.QtGui import QPalette, QColor
import sys
import platform import platform
import datetime import datetime
import re
import os
from onionshare_cli.settings import Settings from onionshare_cli.settings import Settings
from onionshare_cli.onion import (
Onion,
TorErrorInvalidSetting,
TorErrorAutomatic,
TorErrorSocketPort,
TorErrorSocketFile,
TorErrorMissingPassword,
TorErrorUnreadableCookieFile,
TorErrorAuthError,
TorErrorProtocolError,
BundledTorTimeout,
BundledTorBroken,
TorTooOldEphemeral,
TorTooOldStealth,
PortNotAvailable,
)
from . import strings from . import strings
from .widgets import Alert from .widgets import Alert
from .update_checker import UpdateThread from .update_checker import UpdateThread
from .tor_connection_dialog import TorConnectionDialog
from .gui_common import GuiCommon
class SettingsDialog(QtWidgets.QDialog): class SettingsTab(QtWidgets.QWidget):
""" """
Settings dialog. Settings dialog.
""" """
settings_saved = QtCore.Signal() close_this_tab = QtCore.Signal()
def __init__(self, common): def __init__(self, common, tab_id):
super(SettingsDialog, self).__init__() super(SettingsTab, self).__init__()
self.common = common self.common = common
self.common.log("SettingsTab", "__init__")
self.common.log("SettingsDialog", "__init__")
self.setModal(True)
self.setWindowTitle(strings._("gui_settings_window_title"))
self.setWindowIcon(QtGui.QIcon(GuiCommon.get_resource_path("images/logo.png")))
self.system = platform.system() self.system = platform.system()
self.tab_id = tab_id
# Automatic updates options # Automatic updates options
@ -100,9 +73,16 @@ class SettingsDialog(QtWidgets.QDialog):
) )
autoupdate_group.setLayout(autoupdate_group_layout) autoupdate_group.setLayout(autoupdate_group_layout)
autoupdate_layout = QtWidgets.QHBoxLayout()
autoupdate_layout.addStretch()
autoupdate_layout.addWidget(autoupdate_group)
autoupdate_layout.addStretch()
autoupdate_widget = QtWidgets.QWidget()
autoupdate_widget.setLayout(autoupdate_layout)
# Autoupdate is only available for Windows and Mac (Linux updates using package manager) # Autoupdate is only available for Windows and Mac (Linux updates using package manager)
if self.system != "Windows" and self.system != "Darwin": if self.system != "Windows" and self.system != "Darwin":
autoupdate_group.hide() autoupdate_widget.hide()
# Language settings # Language settings
language_label = QtWidgets.QLabel(strings._("gui_settings_language_label")) language_label = QtWidgets.QLabel(strings._("gui_settings_language_label"))
@ -117,6 +97,7 @@ class SettingsDialog(QtWidgets.QDialog):
locale = language_names_to_locales[language_name] locale = language_names_to_locales[language_name]
self.language_combobox.addItem(language_name, locale) self.language_combobox.addItem(language_name, locale)
language_layout = QtWidgets.QHBoxLayout() language_layout = QtWidgets.QHBoxLayout()
language_layout.addStretch()
language_layout.addWidget(language_label) language_layout.addWidget(language_label)
language_layout.addWidget(self.language_combobox) language_layout.addWidget(self.language_combobox)
language_layout.addStretch() language_layout.addStretch()
@ -131,6 +112,7 @@ class SettingsDialog(QtWidgets.QDialog):
] ]
self.theme_combobox.addItems(theme_choices) self.theme_combobox.addItems(theme_choices)
theme_layout = QtWidgets.QHBoxLayout() theme_layout = QtWidgets.QHBoxLayout()
theme_layout.addStretch()
theme_layout.addWidget(theme_label) theme_layout.addWidget(theme_label)
theme_layout.addWidget(self.theme_combobox) theme_layout.addWidget(self.theme_combobox)
theme_layout.addStretch() theme_layout.addStretch()
@ -139,41 +121,44 @@ class SettingsDialog(QtWidgets.QDialog):
version_label = QtWidgets.QLabel( version_label = QtWidgets.QLabel(
strings._("gui_settings_version_label").format(self.common.version) strings._("gui_settings_version_label").format(self.common.version)
) )
version_label.setAlignment(QtCore.Qt.AlignHCenter)
help_label = QtWidgets.QLabel(strings._("gui_settings_help_label")) help_label = QtWidgets.QLabel(strings._("gui_settings_help_label"))
help_label.setAlignment(QtCore.Qt.AlignHCenter)
help_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) help_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
help_label.setOpenExternalLinks(True) help_label.setOpenExternalLinks(True)
# Buttons # Buttons
self.save_button = QtWidgets.QPushButton(strings._("gui_settings_button_save")) self.save_button = QtWidgets.QPushButton(strings._("gui_settings_button_save"))
self.save_button.clicked.connect(self.save_clicked) self.save_button.clicked.connect(self.save_clicked)
self.cancel_button = QtWidgets.QPushButton(
strings._("gui_settings_button_cancel")
)
self.cancel_button.clicked.connect(self.cancel_clicked)
buttons_layout = QtWidgets.QHBoxLayout() buttons_layout = QtWidgets.QHBoxLayout()
buttons_layout.addStretch() buttons_layout.addStretch()
buttons_layout.addWidget(self.save_button) buttons_layout.addWidget(self.save_button)
buttons_layout.addWidget(self.cancel_button) buttons_layout.addStretch()
# Layout # Layout
layout = QtWidgets.QVBoxLayout() layout = QtWidgets.QVBoxLayout()
layout.addWidget(autoupdate_group) layout.addStretch()
if autoupdate_group.isVisible(): layout.addWidget(autoupdate_widget)
if autoupdate_widget.isVisible():
layout.addSpacing(20) layout.addSpacing(20)
layout.addLayout(language_layout) layout.addLayout(language_layout)
layout.addLayout(theme_layout) layout.addLayout(theme_layout)
layout.addSpacing(20) layout.addSpacing(20)
layout.addStretch()
layout.addWidget(version_label) layout.addWidget(version_label)
layout.addWidget(help_label) layout.addWidget(help_label)
layout.addSpacing(20) layout.addSpacing(20)
layout.addLayout(buttons_layout) layout.addLayout(buttons_layout)
layout.addStretch()
self.setLayout(layout) self.setLayout(layout)
self.cancel_button.setFocus()
self.reload_settings() self.reload_settings()
if self.common.gui.onion.connected_to_tor:
self.tor_is_connected()
else:
self.tor_is_disconnected()
def reload_settings(self): def reload_settings(self):
# Load settings, and fill them in # Load settings, and fill them in
self.old_settings = Settings(self.common) self.old_settings = Settings(self.common)
@ -199,7 +184,7 @@ class SettingsDialog(QtWidgets.QDialog):
""" """
Check for Updates button clicked. Manually force an update check. Check for Updates button clicked. Manually force an update check.
""" """
self.common.log("SettingsDialog", "check_for_updates") self.common.log("SettingsTab", "check_for_updates")
# Disable buttons # Disable buttons
self._disable_buttons() self._disable_buttons()
self.common.gui.qtapp.processEvents() self.common.gui.qtapp.processEvents()
@ -261,7 +246,7 @@ class SettingsDialog(QtWidgets.QDialog):
""" """
Save button clicked. Save current settings to disk. Save button clicked. Save current settings to disk.
""" """
self.common.log("SettingsDialog", "save_clicked") self.common.log("SettingsTab", "save_clicked")
def changed(s1, s2, keys): def changed(s1, s2, keys):
""" """
@ -298,33 +283,14 @@ class SettingsDialog(QtWidgets.QDialog):
# Save the new settings # Save the new settings
settings.save() settings.save()
self.settings_saved.emit() self.close_this_tab.emit()
self.close()
def cancel_clicked(self):
"""
Cancel button clicked.
"""
self.common.log("SettingsDialog", "cancel_clicked")
if (
not self.common.gui.local_only
and not self.common.gui.onion.is_authenticated()
):
Alert(
self.common,
strings._("gui_tor_connection_canceled"),
QtWidgets.QMessageBox.Warning,
)
sys.exit()
else:
self.close()
def help_clicked(self): def help_clicked(self):
""" """
Help button clicked. Help button clicked.
""" """
self.common.log("SettingsDialog", "help_clicked") self.common.log("SettingsTab", "help_clicked")
SettingsDialog.open_help() SettingsTab.open_help()
@staticmethod @staticmethod
def open_help(): def open_help():
@ -335,7 +301,7 @@ class SettingsDialog(QtWidgets.QDialog):
""" """
Return a Settings object that's full of values from the settings dialog. Return a Settings object that's full of values from the settings dialog.
""" """
self.common.log("SettingsDialog", "settings_from_fields") self.common.log("SettingsTab", "settings_from_fields")
settings = Settings(self.common) settings = Settings(self.common)
settings.load() # To get the last update timestamp settings.load() # To get the last update timestamp
@ -350,8 +316,12 @@ class SettingsDialog(QtWidgets.QDialog):
return settings return settings
def settings_have_changed(self):
# Global settings have changed
self.common.log("SettingsTab", "settings_have_changed")
def _update_autoupdate_timestamp(self, autoupdate_timestamp): def _update_autoupdate_timestamp(self, autoupdate_timestamp):
self.common.log("SettingsDialog", "_update_autoupdate_timestamp") self.common.log("SettingsTab", "_update_autoupdate_timestamp")
if autoupdate_timestamp: if autoupdate_timestamp:
dt = datetime.datetime.fromtimestamp(autoupdate_timestamp) dt = datetime.datetime.fromtimestamp(autoupdate_timestamp)
@ -363,18 +333,22 @@ class SettingsDialog(QtWidgets.QDialog):
) )
def _disable_buttons(self): def _disable_buttons(self):
self.common.log("SettingsDialog", "_disable_buttons") self.common.log("SettingsTab", "_disable_buttons")
self.check_for_updates_button.setEnabled(False) self.check_for_updates_button.setEnabled(False)
self.save_button.setEnabled(False) self.save_button.setEnabled(False)
self.cancel_button.setEnabled(False)
def _enable_buttons(self): def _enable_buttons(self):
self.common.log("SettingsDialog", "_enable_buttons") self.common.log("SettingsTab", "_enable_buttons")
# We can't check for updates if we're still not connected to Tor # We can't check for updates if we're still not connected to Tor
if not self.common.gui.onion.connected_to_tor: if not self.common.gui.onion.connected_to_tor:
self.check_for_updates_button.setEnabled(False) self.check_for_updates_button.setEnabled(False)
else: else:
self.check_for_updates_button.setEnabled(True) self.check_for_updates_button.setEnabled(True)
self.save_button.setEnabled(True) self.save_button.setEnabled(True)
self.cancel_button.setEnabled(True)
def tor_is_connected(self):
self.check_for_updates_button.show()
def tor_is_disconnected(self):
self.check_for_updates_button.hide()

View File

@ -28,7 +28,7 @@ from .mode_settings_widget import ModeSettingsWidget
from ..server_status import ServerStatus from ..server_status import ServerStatus
from ... import strings from ... import strings
from ...threads import OnionThread, AutoStartTimer from ...threads import OnionThread, AutoStartTimer
from ...widgets import Alert from ...widgets import Alert, MinimumSizeWidget
class Mode(QtWidgets.QWidget): class Mode(QtWidgets.QWidget):
@ -101,6 +101,38 @@ class Mode(QtWidgets.QWidget):
self.primary_action = QtWidgets.QWidget() self.primary_action = QtWidgets.QWidget()
self.primary_action.setLayout(self.primary_action_layout) self.primary_action.setLayout(self.primary_action_layout)
# It's up to the downstream Mode to add stuff to self.content_layout
# self.content_layout shows the actual content of the mode
# self.tor_not_connected_layout is displayed when Tor isn't connected
self.content_layout = QtWidgets.QVBoxLayout()
self.content_widget = QtWidgets.QWidget()
self.content_widget.setLayout(self.content_layout)
tor_not_connected_label = QtWidgets.QLabel(
strings._("mode_tor_not_connected_label")
)
tor_not_connected_label.setAlignment(QtCore.Qt.AlignHCenter)
tor_not_connected_label.setStyleSheet(
self.common.gui.css["tor_not_connected_label"]
)
self.tor_not_connected_layout = QtWidgets.QVBoxLayout()
self.tor_not_connected_layout.addStretch()
self.tor_not_connected_layout.addWidget(tor_not_connected_label)
self.tor_not_connected_layout.addWidget(MinimumSizeWidget(700, 0))
self.tor_not_connected_layout.addStretch()
self.tor_not_connected_widget = QtWidgets.QWidget()
self.tor_not_connected_widget.setLayout(self.tor_not_connected_layout)
self.wrapper_layout = QtWidgets.QVBoxLayout()
self.wrapper_layout.addWidget(self.content_widget)
self.wrapper_layout.addWidget(self.tor_not_connected_widget)
self.setLayout(self.wrapper_layout)
if self.common.gui.onion.connected_to_tor:
self.tor_connection_started()
else:
self.tor_connection_stopped()
def init(self): def init(self):
""" """
Add custom initialization here. Add custom initialization here.
@ -524,3 +556,21 @@ class Mode(QtWidgets.QWidget):
Used in both Share and Website modes, so implemented here. Used in both Share and Website modes, so implemented here.
""" """
self.history.cancel(event["data"]["id"]) self.history.cancel(event["data"]["id"])
def tor_connection_started(self):
"""
This is called on every Mode when Tor is connected
"""
self.content_widget.show()
self.tor_not_connected_widget.hide()
def tor_connection_stopped(self):
"""
This is called on every Mode when Tor is disconnected
"""
if self.common.gui.local_only:
self.tor_connection_started()
return
self.content_widget.hide()
self.tor_not_connected_widget.show()

View File

@ -98,10 +98,8 @@ class ChatMode(Mode):
self.column_layout.addWidget(self.image) self.column_layout.addWidget(self.image)
self.column_layout.addLayout(self.main_layout) self.column_layout.addLayout(self.main_layout)
# Wrapper layout # Content layout
self.wrapper_layout = QtWidgets.QVBoxLayout() self.content_layout.addLayout(self.column_layout)
self.wrapper_layout.addLayout(self.column_layout)
self.setLayout(self.wrapper_layout)
def get_type(self): def get_type(self):
""" """

View File

@ -198,10 +198,8 @@ class ReceiveMode(Mode):
self.column_layout.addLayout(row_layout) self.column_layout.addLayout(row_layout)
self.column_layout.addWidget(self.history, stretch=1) self.column_layout.addWidget(self.history, stretch=1)
# Wrapper layout # Content layout
self.wrapper_layout = QtWidgets.QVBoxLayout() self.content_layout.addLayout(self.column_layout)
self.wrapper_layout.addLayout(self.column_layout)
self.setLayout(self.wrapper_layout)
def get_type(self): def get_type(self):
""" """

View File

@ -169,10 +169,8 @@ class ShareMode(Mode):
self.column_layout.addLayout(self.main_layout) self.column_layout.addLayout(self.main_layout)
self.column_layout.addWidget(self.history, stretch=1) self.column_layout.addWidget(self.history, stretch=1)
# Wrapper layout # Content layout
self.wrapper_layout = QtWidgets.QVBoxLayout() self.content_layout.addLayout(self.column_layout)
self.wrapper_layout.addLayout(self.column_layout)
self.setLayout(self.wrapper_layout)
# Always start with focus on file selection # Always start with focus on file selection
self.file_selection.setFocus() self.file_selection.setFocus()

View File

@ -167,10 +167,8 @@ class WebsiteMode(Mode):
self.column_layout.addLayout(self.main_layout) self.column_layout.addLayout(self.main_layout)
self.column_layout.addWidget(self.history, stretch=1) self.column_layout.addWidget(self.history, stretch=1)
# Wrapper layout # Content layout
self.wrapper_layout = QtWidgets.QVBoxLayout() self.content_layout.addLayout(self.column_layout)
self.wrapper_layout.addLayout(self.column_layout)
self.setLayout(self.wrapper_layout)
# Always start with focus on file selection # Always start with focus on file selection
self.file_selection.setFocus() self.file_selection.setFocus()

View File

@ -96,7 +96,6 @@ class Tab(QtWidgets.QWidget):
tab_id, tab_id,
system_tray, system_tray,
status_bar, status_bar,
mode_settings=None,
filenames=None, filenames=None,
): ):
super(Tab, self).__init__() super(Tab, self).__init__()

View File

@ -26,6 +26,8 @@ from . import strings
from .tab import Tab from .tab import Tab
from .threads import EventHandlerThread from .threads import EventHandlerThread
from .gui_common import GuiCommon from .gui_common import GuiCommon
from .tor_settings_tab import TorSettingsTab
from .settings_tab import SettingsTab
class TabWidget(QtWidgets.QTabWidget): class TabWidget(QtWidgets.QTabWidget):
@ -43,9 +45,12 @@ class TabWidget(QtWidgets.QTabWidget):
self.system_tray = system_tray self.system_tray = system_tray
self.status_bar = status_bar self.status_bar = status_bar
# Keep track of tabs in a dictionary # Keep track of tabs in a dictionary that maps tab_id to tab.
# Each tab has a unique, auto-incremented id (tab_id). This is different than the
# tab's index, which changes as tabs are re-arranged.
self.tabs = {} self.tabs = {}
self.current_tab_id = 0 # Each tab has a unique id self.current_tab_id = 0 # Each tab has a unique id
self.tor_settings_tab = None
# Define the new tab button # Define the new tab button
self.new_tab_button = QtWidgets.QPushButton("+", parent=self) self.new_tab_button = QtWidgets.QPushButton("+", parent=self)
@ -89,9 +94,12 @@ class TabWidget(QtWidgets.QTabWidget):
self.event_handler_t.wait(50) self.event_handler_t.wait(50)
# Clean up each tab # Clean up each tab
for index in range(self.count()): for tab_id in self.tabs:
tab = self.widget(index) if not (
tab.cleanup() type(self.tabs[tab_id]) is SettingsTab
or type(self.tabs[tab_id]) is TorSettingsTab
):
self.tabs[tab_id].cleanup()
def move_new_tab_button(self): def move_new_tab_button(self):
# Find the width of all tabs # Find the width of all tabs
@ -114,8 +122,28 @@ class TabWidget(QtWidgets.QTabWidget):
def tab_changed(self): def tab_changed(self):
# Active tab was changed # Active tab was changed
tab_id = self.currentIndex() tab = self.widget(self.currentIndex())
if not tab:
self.common.log(
"TabWidget",
"tab_changed",
f"tab at index {self.currentIndex()} does not exist",
)
return
tab_id = tab.tab_id
self.common.log("TabWidget", "tab_changed", f"Tab was changed to {tab_id}") self.common.log("TabWidget", "tab_changed", f"Tab was changed to {tab_id}")
# If it's Settings or Tor Settings, ignore
if (
type(self.tabs[tab_id]) is SettingsTab
or type(self.tabs[tab_id]) is TorSettingsTab
):
# Blank the server status indicator
self.status_bar.server_status_image_label.clear()
self.status_bar.server_status_label.clear()
return
try: try:
mode = self.tabs[tab_id].get_mode() mode = self.tabs[tab_id].get_mode()
if mode: if mode:
@ -158,23 +186,6 @@ class TabWidget(QtWidgets.QTabWidget):
index = self.addTab(tab, strings._("gui_new_tab")) index = self.addTab(tab, strings._("gui_new_tab"))
self.setCurrentIndex(index) self.setCurrentIndex(index)
# In macOS, manually create a close button because tabs don't seem to have them otherwise
if self.common.platform == "Darwin":
def close_tab():
self.tabBar().tabCloseRequested.emit(self.indexOf(tab))
tab.close_button = QtWidgets.QPushButton()
tab.close_button.setFlat(True)
tab.close_button.setFixedWidth(40)
tab.close_button.setIcon(
QtGui.QIcon(GuiCommon.get_resource_path("images/close_tab.png"))
)
tab.close_button.clicked.connect(close_tab)
self.tabBar().setTabButton(
index, QtWidgets.QTabBar.RightSide, tab.close_button
)
tab.init(mode_settings) tab.init(mode_settings)
# Make sure the title is set # Make sure the title is set
@ -187,6 +198,44 @@ class TabWidget(QtWidgets.QTabWidget):
# Bring the window to front, in case this is being added by an event # Bring the window to front, in case this is being added by an event
self.bring_to_front.emit() self.bring_to_front.emit()
def open_settings_tab(self):
self.common.log("TabWidget", "open_settings_tab")
# See if a settings tab is already open, and if so switch to it
for tab_id in self.tabs:
if type(self.tabs[tab_id]) is SettingsTab:
self.setCurrentIndex(self.indexOf(self.tabs[tab_id]))
return
settings_tab = SettingsTab(self.common, self.current_tab_id)
settings_tab.close_this_tab.connect(self.close_settings_tab)
self.tabs[self.current_tab_id] = settings_tab
self.current_tab_id += 1
index = self.addTab(settings_tab, strings._("gui_settings_window_title"))
self.setCurrentIndex(index)
def open_tor_settings_tab(self):
self.common.log("TabWidget", "open_tor_settings_tab")
# See if a settings tab is already open, and if so switch to it
for tab_id in self.tabs:
if type(self.tabs[tab_id]) is TorSettingsTab:
self.setCurrentIndex(self.indexOf(self.tabs[tab_id]))
return
self.tor_settings_tab = TorSettingsTab(
self.common, self.current_tab_id, self.are_tabs_active(), self.status_bar
)
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)
self.tor_settings_tab.tor_is_disconnected.connect(self.tor_is_disconnected)
self.tabs[self.current_tab_id] = self.tor_settings_tab
self.current_tab_id += 1
index = self.addTab(
self.tor_settings_tab, strings._("gui_tor_settings_window_title")
)
self.setCurrentIndex(index)
def change_title(self, tab_id, title): def change_title(self, tab_id, title):
shortened_title = title shortened_title = title
if len(shortened_title) > 11: if len(shortened_title) > 11:
@ -200,6 +249,11 @@ class TabWidget(QtWidgets.QTabWidget):
index = self.indexOf(self.tabs[tab_id]) index = self.indexOf(self.tabs[tab_id])
self.setTabIcon(index, QtGui.QIcon(GuiCommon.get_resource_path(icon_path))) self.setTabIcon(index, QtGui.QIcon(GuiCommon.get_resource_path(icon_path)))
# The icon changes when the server status changes, so if we have an open
# Tor Settings tab, tell it to update
if self.tor_settings_tab:
self.tor_settings_tab.active_tabs_changed(self.are_tabs_active())
def change_persistent(self, tab_id, is_persistent): def change_persistent(self, tab_id, is_persistent):
self.common.log( self.common.log(
"TabWidget", "TabWidget",
@ -223,10 +277,14 @@ class TabWidget(QtWidgets.QTabWidget):
def save_persistent_tabs(self): def save_persistent_tabs(self):
# Figure out the order of persistent tabs to save in settings # Figure out the order of persistent tabs to save in settings
persistent_tabs = [] persistent_tabs = []
for index in range(self.count()): for tab_id in self.tabs:
tab = self.widget(index) if not (
if tab.settings.get("persistent", "enabled"): type(self.tabs[tab_id]) is SettingsTab
persistent_tabs.append(tab.settings.id) or type(self.tabs[tab_id]) is TorSettingsTab
):
tab = self.widget(self.indexOf(self.tabs[tab_id]))
if tab.settings.get("persistent", "enabled"):
persistent_tabs.append(tab.settings.id)
# Only save if tabs have actually moved # Only save if tabs have actually moved
if persistent_tabs != self.common.settings.get("persistent_tabs"): if persistent_tabs != self.common.settings.get("persistent_tabs"):
self.common.settings.set("persistent_tabs", persistent_tabs) self.common.settings.set("persistent_tabs", persistent_tabs)
@ -235,10 +293,16 @@ class TabWidget(QtWidgets.QTabWidget):
def close_tab(self, index): def close_tab(self, index):
self.common.log("TabWidget", "close_tab", f"{index}") self.common.log("TabWidget", "close_tab", f"{index}")
tab = self.widget(index) tab = self.widget(index)
if tab.close_tab(): tab_id = tab.tab_id
# If the tab is persistent, delete the settings file from disk
if tab.settings.get("persistent", "enabled"): if (
tab.settings.delete() type(self.tabs[tab_id]) is SettingsTab
or type(self.tabs[tab_id]) is TorSettingsTab
):
self.common.log("TabWidget", "closing a settings tab")
if type(self.tabs[tab_id]) is TorSettingsTab:
self.tor_settings_tab = None
# Remove the tab # Remove the tab
self.removeTab(index) self.removeTab(index)
@ -248,17 +312,56 @@ class TabWidget(QtWidgets.QTabWidget):
if self.count() == 0: if self.count() == 0:
self.new_tab_clicked() self.new_tab_clicked()
self.save_persistent_tabs() else:
self.common.log("TabWidget", "closing a service tab")
if tab.close_tab():
self.common.log("TabWidget", "user is okay with closing the tab")
# If the tab is persistent, delete the settings file from disk
if tab.settings.get("persistent", "enabled"):
tab.settings.delete()
self.save_persistent_tabs()
# Remove the tab
self.removeTab(index)
del self.tabs[tab.tab_id]
# If the last tab is closed, open a new one
if self.count() == 0:
self.new_tab_clicked()
else:
self.common.log("TabWidget", "user does not want to close the tab")
def close_settings_tab(self):
self.common.log("TabWidget", "close_settings_tab")
for tab_id in self.tabs:
if type(self.tabs[tab_id]) is SettingsTab:
index = self.indexOf(self.tabs[tab_id])
self.close_tab(index)
return
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 TorSettingsTab:
index = self.indexOf(self.tabs[tab_id])
self.close_tab(index)
return
def are_tabs_active(self): def are_tabs_active(self):
""" """
See if there are active servers in any open tabs See if there are active servers in any open tabs
""" """
for tab_id in self.tabs: for tab_id in self.tabs:
mode = self.tabs[tab_id].get_mode() if not (
if mode: type(self.tabs[tab_id]) is SettingsTab
if mode.server_status.status != mode.server_status.STATUS_STOPPED: or type(self.tabs[tab_id]) is TorSettingsTab
return True ):
mode = self.tabs[tab_id].get_mode()
if mode:
if mode.server_status.status != mode.server_status.STATUS_STOPPED:
return True
return False return False
def paintEvent(self, event): def paintEvent(self, event):
@ -273,6 +376,26 @@ class TabWidget(QtWidgets.QTabWidget):
super(TabWidget, self).resizeEvent(event) super(TabWidget, self).resizeEvent(event)
self.move_new_tab_button() self.move_new_tab_button()
def tor_is_connected(self):
for tab_id in self.tabs:
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:
mode = self.tabs[tab_id].get_mode()
if mode:
mode.tor_connection_started()
def tor_is_disconnected(self):
for tab_id in self.tabs:
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:
mode = self.tabs[tab_id].get_mode()
if mode:
mode.tor_connection_stopped()
class TabBar(QtWidgets.QTabBar): class TabBar(QtWidgets.QTabBar):
""" """

View File

@ -117,7 +117,6 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
def _connected_to_tor(self): def _connected_to_tor(self):
self.common.log("TorConnectionDialog", "_connected_to_tor") self.common.log("TorConnectionDialog", "_connected_to_tor")
self.active = False self.active = False
# Close the dialog after connecting # Close the dialog after connecting
self.setValue(self.maximum()) self.setValue(self.maximum())
@ -157,26 +156,136 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
QtCore.QTimer.singleShot(1, self.cancel) QtCore.QTimer.singleShot(1, self.cancel)
class TorConnectionWidget(QtWidgets.QWidget):
"""
Connecting to Tor widget, with a progress bar
"""
open_tor_settings = QtCore.Signal()
success = QtCore.Signal()
fail = QtCore.Signal(str)
def __init__(self, common, status_bar):
super(TorConnectionWidget, self).__init__(None)
self.common = common
self.common.log("TorConnectionWidget", "__init__")
self.status_bar = status_bar
self.label = QtWidgets.QLabel(strings._("connecting_to_tor"))
self.label.setAlignment(QtCore.Qt.AlignHCenter)
self.progress = QtWidgets.QProgressBar()
self.progress.setRange(0, 100)
self.cancel_button = QtWidgets.QPushButton(
strings._("gui_settings_button_cancel")
)
self.cancel_button.clicked.connect(self.cancel_clicked)
progress_layout = QtWidgets.QHBoxLayout()
progress_layout.addWidget(self.progress)
progress_layout.addWidget(self.cancel_button)
inner_layout = QtWidgets.QVBoxLayout()
inner_layout.addWidget(self.label)
inner_layout.addLayout(progress_layout)
layout = QtWidgets.QHBoxLayout()
layout.addStretch()
layout.addLayout(inner_layout)
layout.addStretch()
self.setLayout(layout)
# Start displaying the status at 0
self._tor_status_update(0, "")
def start(self, custom_settings=False, testing_settings=False, onion=None):
self.common.log("TorConnectionWidget", "start")
self.was_canceled = False
self.testing_settings = testing_settings
if custom_settings:
self.settings = custom_settings
else:
self.settings = self.common.settings
if self.testing_settings:
self.onion = onion
else:
self.onion = self.common.gui.onion
t = TorConnectionThread(self.common, self.settings, self)
t.tor_status_update.connect(self._tor_status_update)
t.connected_to_tor.connect(self._connected_to_tor)
t.canceled_connecting_to_tor.connect(self._canceled_connecting_to_tor)
t.error_connecting_to_tor.connect(self._error_connecting_to_tor)
t.start()
# The main thread needs to remain active, and checking for Qt events,
# until the thread is finished. Otherwise it won't be able to handle
# accepting signals.
self.active = True
while self.active:
time.sleep(0.1)
self.common.gui.qtapp.processEvents()
def cancel_clicked(self):
self.was_canceled = True
self.fail.emit("")
def wasCanceled(self):
return self.was_canceled
def _tor_status_update(self, progress, summary):
self.progress.setValue(int(progress))
self.label.setText(
f"<strong>{strings._('connecting_to_tor')}</strong><br>{summary}"
)
def _connected_to_tor(self):
self.common.log("TorConnectionWidget", "_connected_to_tor")
self.active = False
self.status_bar.clearMessage()
# Close the dialog after connecting
self.progress.setValue(self.progress.maximum())
self.success.emit()
def _canceled_connecting_to_tor(self):
self.common.log("TorConnectionWidget", "_canceled_connecting_to_tor")
self.active = False
self.onion.cleanup()
# Cancel connecting to Tor
QtCore.QTimer.singleShot(1, self.cancel_clicked)
def _error_connecting_to_tor(self, msg):
self.common.log("TorConnectionWidget", "_error_connecting_to_tor")
self.active = False
self.fail.emit(msg)
class TorConnectionThread(QtCore.QThread): class TorConnectionThread(QtCore.QThread):
tor_status_update = QtCore.Signal(str, str) tor_status_update = QtCore.Signal(str, str)
connected_to_tor = QtCore.Signal() connected_to_tor = QtCore.Signal()
canceled_connecting_to_tor = QtCore.Signal() canceled_connecting_to_tor = QtCore.Signal()
error_connecting_to_tor = QtCore.Signal(str) error_connecting_to_tor = QtCore.Signal(str)
def __init__(self, common, settings, dialog): def __init__(self, common, settings, parent):
super(TorConnectionThread, self).__init__() super(TorConnectionThread, self).__init__()
self.common = common self.common = common
self.common.log("TorConnectionThread", "__init__") self.common.log("TorConnectionThread", "__init__")
self.settings = settings self.settings = settings
self.dialog = dialog self.parent = parent
def run(self): def run(self):
self.common.log("TorConnectionThread", "run") self.common.log("TorConnectionThread", "run")
# Connect to the Onion # Connect to the Onion
try: try:
self.dialog.onion.connect(self.settings, False, self._tor_status_update) self.parent.onion.connect(self.settings, False, self._tor_status_update)
if self.dialog.onion.connected_to_tor: if self.parent.onion.connected_to_tor:
self.connected_to_tor.emit() self.connected_to_tor.emit()
else: else:
self.canceled_connecting_to_tor.emit() self.canceled_connecting_to_tor.emit()
@ -212,4 +321,4 @@ class TorConnectionThread(QtCore.QThread):
self.tor_status_update.emit(progress, summary) self.tor_status_update.emit(progress, summary)
# Return False if the dialog was canceled # Return False if the dialog was canceled
return not self.dialog.wasCanceled() return not self.parent.wasCanceled()

View File

@ -30,32 +30,30 @@ from onionshare_cli.onion import Onion
from . import strings from . import strings
from .widgets import Alert from .widgets import Alert
from .tor_connection_dialog import TorConnectionDialog from .tor_connection import TorConnectionWidget
from .moat_dialog import MoatDialog from .moat_dialog import MoatDialog
from .gui_common import GuiCommon
class TorSettingsDialog(QtWidgets.QDialog): class TorSettingsTab(QtWidgets.QWidget):
""" """
Settings dialog. Settings dialog.
""" """
settings_saved = QtCore.Signal() close_this_tab = QtCore.Signal()
tor_is_connected = QtCore.Signal()
tor_is_disconnected = QtCore.Signal()
def __init__(self, common): def __init__(self, common, tab_id, are_tabs_active, status_bar):
super(TorSettingsDialog, self).__init__() super(TorSettingsTab, self).__init__()
self.common = common self.common = common
self.common.log("TorSettingsTab", "__init__")
self.common.log("TorSettingsDialog", "__init__") self.status_bar = status_bar
self.meek = Meek(common, get_tor_paths=self.common.gui.get_tor_paths) self.meek = Meek(common, get_tor_paths=self.common.gui.get_tor_paths)
self.setModal(True)
self.setWindowTitle(strings._("gui_tor_settings_window_title"))
self.setWindowIcon(QtGui.QIcon(GuiCommon.get_resource_path("images/logo.png")))
self.system = platform.system() self.system = platform.system()
self.tab_id = tab_id
# Connection type: either automatic, control port, or socket file # Connection type: either automatic, control port, or socket file
@ -299,6 +297,7 @@ class TorSettingsDialog(QtWidgets.QDialog):
connection_type_radio_group_layout.addWidget( connection_type_radio_group_layout.addWidget(
self.connection_type_socket_file_radio self.connection_type_socket_file_radio
) )
connection_type_radio_group_layout.addStretch()
connection_type_radio_group = QtWidgets.QGroupBox( connection_type_radio_group = QtWidgets.QGroupBox(
strings._("gui_settings_connection_type_label") strings._("gui_settings_connection_type_label")
) )
@ -319,6 +318,28 @@ class TorSettingsDialog(QtWidgets.QDialog):
connection_type_layout = QtWidgets.QVBoxLayout() connection_type_layout = QtWidgets.QVBoxLayout()
connection_type_layout.addWidget(self.tor_settings_group) connection_type_layout.addWidget(self.tor_settings_group)
connection_type_layout.addWidget(self.connection_type_bridges_radio_group) connection_type_layout.addWidget(self.connection_type_bridges_radio_group)
connection_type_layout.addStretch()
# Settings are in columns
columns_layout = QtWidgets.QHBoxLayout()
columns_layout.addWidget(connection_type_radio_group)
columns_layout.addSpacing(20)
columns_layout.addLayout(connection_type_layout, stretch=1)
columns_wrapper = QtWidgets.QWidget()
columns_wrapper.setFixedHeight(400)
columns_wrapper.setLayout(columns_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()
self.tor_con_type = None
# Error label
self.error_label = QtWidgets.QLabel()
self.error_label.setStyleSheet(self.common.gui.css["tor_settings_error"])
self.error_label.setWordWrap(True)
# Buttons # Buttons
self.test_tor_button = QtWidgets.QPushButton( self.test_tor_button = QtWidgets.QPushButton(
@ -327,26 +348,42 @@ class TorSettingsDialog(QtWidgets.QDialog):
self.test_tor_button.clicked.connect(self.test_tor_clicked) self.test_tor_button.clicked.connect(self.test_tor_clicked)
self.save_button = QtWidgets.QPushButton(strings._("gui_settings_button_save")) self.save_button = QtWidgets.QPushButton(strings._("gui_settings_button_save"))
self.save_button.clicked.connect(self.save_clicked) self.save_button.clicked.connect(self.save_clicked)
self.cancel_button = QtWidgets.QPushButton(
strings._("gui_settings_button_cancel")
)
self.cancel_button.clicked.connect(self.cancel_clicked)
buttons_layout = QtWidgets.QHBoxLayout() buttons_layout = QtWidgets.QHBoxLayout()
buttons_layout.addWidget(self.error_label, stretch=1)
buttons_layout.addSpacing(20)
buttons_layout.addWidget(self.test_tor_button) buttons_layout.addWidget(self.test_tor_button)
buttons_layout.addStretch()
buttons_layout.addWidget(self.save_button) buttons_layout.addWidget(self.save_button)
buttons_layout.addWidget(self.cancel_button)
# Layout # Main layout
main_layout = QtWidgets.QVBoxLayout()
main_layout.addWidget(columns_wrapper)
main_layout.addStretch()
main_layout.addWidget(self.tor_con)
main_layout.addStretch()
main_layout.addLayout(buttons_layout)
self.main_widget = QtWidgets.QWidget()
self.main_widget.setLayout(main_layout)
# Tabs are active label
active_tabs_label = QtWidgets.QLabel(
strings._("gui_settings_stop_active_tabs_label")
)
active_tabs_label.setAlignment(QtCore.Qt.AlignHCenter)
# Active tabs layout
active_tabs_layout = QtWidgets.QVBoxLayout()
active_tabs_layout.addStretch()
active_tabs_layout.addWidget(active_tabs_label)
active_tabs_layout.addStretch()
self.active_tabs_widget = QtWidgets.QWidget()
self.active_tabs_widget.setLayout(active_tabs_layout)
layout = QtWidgets.QVBoxLayout() layout = QtWidgets.QVBoxLayout()
layout.addWidget(connection_type_radio_group) layout.addWidget(self.main_widget)
layout.addLayout(connection_type_layout) layout.addWidget(self.active_tabs_widget)
layout.addStretch()
layout.addLayout(buttons_layout)
self.setLayout(layout) self.setLayout(layout)
self.cancel_button.setFocus()
self.active_tabs_changed(are_tabs_active)
self.reload_settings() self.reload_settings()
def reload_settings(self): def reload_settings(self):
@ -391,63 +428,68 @@ class TorSettingsDialog(QtWidgets.QDialog):
self.old_settings.get("auth_password") self.old_settings.get("auth_password")
) )
if self.old_settings.get("no_bridges"): if self.old_settings.get("bridges_enabled"):
self.bridge_use_checkbox.setCheckState(QtCore.Qt.Unchecked)
self.bridge_settings.hide()
else:
self.bridge_use_checkbox.setCheckState(QtCore.Qt.Checked) self.bridge_use_checkbox.setCheckState(QtCore.Qt.Checked)
self.bridge_settings.show() self.bridge_settings.show()
builtin_obfs4 = self.old_settings.get("tor_bridges_use_obfs4") bridges_type = self.old_settings.get("bridges_type")
builtin_meek_azure = self.old_settings.get( if bridges_type == "built-in":
"tor_bridges_use_meek_lite_azure"
)
builtin_snowflake = self.old_settings.get("tor_bridges_use_snowflake")
if builtin_obfs4 or builtin_meek_azure or builtin_snowflake:
self.bridge_builtin_radio.setChecked(True) self.bridge_builtin_radio.setChecked(True)
self.bridge_builtin_dropdown.show() self.bridge_builtin_dropdown.show()
if builtin_obfs4: self.bridge_moat_radio.setChecked(False)
self.bridge_moat_textbox_options.hide()
self.bridge_custom_radio.setChecked(False)
self.bridge_custom_textbox_options.hide()
bridges_builtin_pt = self.old_settings.get("bridges_builtin_pt")
if bridges_builtin_pt == "obfs4":
self.bridge_builtin_dropdown.setCurrentText("obfs4") self.bridge_builtin_dropdown.setCurrentText("obfs4")
elif builtin_meek_azure: elif bridges_builtin_pt == "meek-azure":
self.bridge_builtin_dropdown.setCurrentText("meek-azure") self.bridge_builtin_dropdown.setCurrentText("meek-azure")
elif builtin_snowflake: else:
self.bridge_builtin_dropdown.setCurrentText("snowflake") self.bridge_builtin_dropdown.setCurrentText("snowflake")
self.bridge_moat_textbox_options.hide() self.bridge_moat_textbox_options.hide()
self.bridge_custom_textbox_options.hide() self.bridge_custom_textbox_options.hide()
elif bridges_type == "moat":
self.bridge_builtin_radio.setChecked(False)
self.bridge_builtin_dropdown.hide()
self.bridge_moat_radio.setChecked(True)
self.bridge_moat_textbox_options.show()
self.bridge_custom_radio.setChecked(False)
self.bridge_custom_textbox_options.hide()
else: else:
self.bridge_builtin_radio.setChecked(False) self.bridge_builtin_radio.setChecked(False)
self.bridge_builtin_dropdown.hide() self.bridge_builtin_dropdown.hide()
self.bridge_moat_radio.setChecked(False)
self.bridge_moat_textbox_options.hide()
self.bridge_custom_radio.setChecked(True)
self.bridge_custom_textbox_options.show()
use_moat = self.old_settings.get("tor_bridges_use_moat") bridges_moat = self.old_settings.get("bridges_moat")
self.bridge_moat_radio.setChecked(use_moat) self.bridge_moat_textbox.document().setPlainText(bridges_moat)
if use_moat: bridges_custom = self.old_settings.get("bridges_custom")
self.bridge_builtin_dropdown.hide() self.bridge_custom_textbox.document().setPlainText(bridges_custom)
self.bridge_custom_textbox_options.hide()
moat_bridges = self.old_settings.get("tor_bridges_use_moat_bridges") else:
self.bridge_moat_textbox.document().setPlainText(moat_bridges) self.bridge_use_checkbox.setCheckState(QtCore.Qt.Unchecked)
if len(moat_bridges.strip()) > 0: self.bridge_settings.hide()
self.bridge_moat_textbox_options.show()
else:
self.bridge_moat_textbox_options.hide()
custom_bridges = self.old_settings.get("tor_bridges_use_custom_bridges") def active_tabs_changed(self, are_tabs_active):
if len(custom_bridges.strip()) != 0: if are_tabs_active:
self.bridge_custom_radio.setChecked(True) self.main_widget.hide()
self.bridge_custom_textbox.setPlainText(custom_bridges) self.active_tabs_widget.show()
else:
self.bridge_builtin_dropdown.hide() self.main_widget.show()
self.bridge_moat_textbox_options.hide() self.active_tabs_widget.hide()
self.bridge_custom_textbox_options.show()
def connection_type_bundled_toggled(self, checked): def connection_type_bundled_toggled(self, checked):
""" """
Connection type bundled was toggled Connection type bundled was toggled
""" """
self.common.log("TorSettingsDialog", "connection_type_bundled_toggled") self.common.log("TorSettingsTab", "connection_type_bundled_toggled")
if checked: if checked:
self.tor_settings_group.hide() self.tor_settings_group.hide()
self.connection_type_socks.hide() self.connection_type_socks.hide()
@ -479,7 +521,7 @@ class TorSettingsDialog(QtWidgets.QDialog):
""" """
if selection == "meek-azure": if selection == "meek-azure":
# Alert the user about meek's costliness if it looks like they're turning it on # Alert the user about meek's costliness if it looks like they're turning it on
if not self.old_settings.get("tor_bridges_use_meek_lite_azure"): if not self.old_settings.get("bridges_builtin_pt") == "meek-azure":
Alert( Alert(
self.common, self.common,
strings._("gui_settings_meek_lite_expensive_warning"), strings._("gui_settings_meek_lite_expensive_warning"),
@ -499,7 +541,7 @@ class TorSettingsDialog(QtWidgets.QDialog):
""" """
Request new bridge button clicked Request new bridge button clicked
""" """
self.common.log("TorSettingsDialog", "bridge_moat_button_clicked") self.common.log("TorSettingsTab", "bridge_moat_button_clicked")
moat_dialog = MoatDialog(self.common, self.meek) moat_dialog = MoatDialog(self.common, self.meek)
moat_dialog.got_bridges.connect(self.bridge_moat_got_bridges) moat_dialog.got_bridges.connect(self.bridge_moat_got_bridges)
@ -509,7 +551,7 @@ class TorSettingsDialog(QtWidgets.QDialog):
""" """
Got new bridges from moat Got new bridges from moat
""" """
self.common.log("TorSettingsDialog", "bridge_moat_got_bridges") self.common.log("TorSettingsTab", "bridge_moat_got_bridges")
self.bridge_moat_textbox.document().setPlainText(bridges) self.bridge_moat_textbox.document().setPlainText(bridges)
self.bridge_moat_textbox.show() self.bridge_moat_textbox.show()
@ -526,7 +568,7 @@ class TorSettingsDialog(QtWidgets.QDialog):
""" """
Connection type automatic was toggled. If checked, hide authentication fields. Connection type automatic was toggled. If checked, hide authentication fields.
""" """
self.common.log("TorSettingsDialog", "connection_type_automatic_toggled") self.common.log("TorSettingsTab", "connection_type_automatic_toggled")
if checked: if checked:
self.tor_settings_group.hide() self.tor_settings_group.hide()
self.connection_type_socks.hide() self.connection_type_socks.hide()
@ -537,7 +579,7 @@ class TorSettingsDialog(QtWidgets.QDialog):
Connection type control port was toggled. If checked, show extra fields Connection type control port was toggled. If checked, show extra fields
for Tor control address and port. If unchecked, hide those extra fields. for Tor control address and port. If unchecked, hide those extra fields.
""" """
self.common.log("TorSettingsDialog", "connection_type_control_port_toggled") self.common.log("TorSettingsTab", "connection_type_control_port_toggled")
if checked: if checked:
self.tor_settings_group.show() self.tor_settings_group.show()
self.connection_type_control_port_extras.show() self.connection_type_control_port_extras.show()
@ -551,7 +593,7 @@ class TorSettingsDialog(QtWidgets.QDialog):
Connection type socket file was toggled. If checked, show extra fields Connection type socket file was toggled. If checked, show extra fields
for socket file. If unchecked, hide those extra fields. for socket file. If unchecked, hide those extra fields.
""" """
self.common.log("TorSettingsDialog", "connection_type_socket_file_toggled") self.common.log("TorSettingsTab", "connection_type_socket_file_toggled")
if checked: if checked:
self.tor_settings_group.show() self.tor_settings_group.show()
self.connection_type_socket_file_extras.show() self.connection_type_socket_file_extras.show()
@ -564,7 +606,7 @@ class TorSettingsDialog(QtWidgets.QDialog):
""" """
Authentication option no authentication was toggled. Authentication option no authentication was toggled.
""" """
self.common.log("TorSettingsDialog", "authenticate_no_auth_toggled") self.common.log("TorSettingsTab", "authenticate_no_auth_toggled")
if checked: if checked:
self.authenticate_password_extras.hide() self.authenticate_password_extras.hide()
else: else:
@ -575,39 +617,34 @@ class TorSettingsDialog(QtWidgets.QDialog):
Test Tor Settings button clicked. With the given settings, see if we can Test Tor Settings button clicked. With the given settings, see if we can
successfully connect and authenticate to Tor. successfully connect and authenticate to Tor.
""" """
self.common.log("TorSettingsDialog", "test_tor_clicked") self.common.log("TorSettingsTab", "test_tor_clicked")
self.error_label.setText("")
settings = self.settings_from_fields() settings = self.settings_from_fields()
if not settings: if not settings:
return return
onion = Onion( self.test_tor_button.hide()
self.common, use_tmp_dir=True, get_tor_paths=self.common.gui.get_tor_paths self.save_button.hide()
self.test_onion = Onion(
self.common,
use_tmp_dir=True,
get_tor_paths=self.common.gui.get_tor_paths,
) )
tor_con = TorConnectionDialog(self.common, settings, True, onion) self.tor_con_type = "test"
tor_con.start() self.tor_con.show()
self.tor_con.start(settings, True, self.test_onion)
# If Tor settings worked, show results
if onion.connected_to_tor:
Alert(
self.common,
strings._("settings_test_success").format(
onion.tor_version,
onion.supports_ephemeral,
onion.supports_stealth,
onion.supports_v3_onions,
),
title=strings._("gui_settings_connection_type_test_button"),
)
# Clean up
onion.cleanup()
def save_clicked(self): def save_clicked(self):
""" """
Save button clicked. Save current settings to disk. Save button clicked. Save current settings to disk.
""" """
self.common.log("TorSettingsDialog", "save_clicked") self.common.log("TorSettingsTab", "save_clicked")
self.error_label.setText("")
def changed(s1, s2, keys): def changed(s1, s2, keys):
""" """
@ -630,7 +667,7 @@ class TorSettingsDialog(QtWidgets.QDialog):
if not self.common.gui.local_only: if not self.common.gui.local_only:
if self.common.gui.onion.is_authenticated(): if self.common.gui.onion.is_authenticated():
self.common.log( self.common.log(
"TorSettingsDialog", "save_clicked", "Connected to Tor" "TorSettingsTab", "save_clicked", "Connected to Tor"
) )
if changed( if changed(
@ -645,10 +682,11 @@ class TorSettingsDialog(QtWidgets.QDialog):
"socket_file_path", "socket_file_path",
"auth_type", "auth_type",
"auth_password", "auth_password",
"no_bridges", "bridges_enabled",
"tor_bridges_use_obfs4", "bridges_type",
"tor_bridges_use_meek_lite_azure", "bridges_builtin_pt",
"tor_bridges_use_custom_bridges", "bridges_moat",
"bridges_custom",
], ],
): ):
@ -656,65 +694,86 @@ class TorSettingsDialog(QtWidgets.QDialog):
else: else:
self.common.log( self.common.log(
"TorSettingsDialog", "save_clicked", "Not connected to Tor" "TorSettingsTab", "save_clicked", "Not connected to Tor"
) )
# Tor isn't connected, so try connecting # Tor isn't connected, so try connecting
reboot_onion = True reboot_onion = True
# Do we need to reinitialize Tor? # Do we need to reinitialize Tor?
if reboot_onion: if reboot_onion:
# Tell the tabs that Tor is disconnected
self.tor_is_disconnected.emit()
# Reinitialize the Onion object # Reinitialize the Onion object
self.common.log( self.common.log(
"TorSettingsDialog", "save_clicked", "rebooting the Onion" "TorSettingsTab", "save_clicked", "rebooting the Onion"
) )
self.common.gui.onion.cleanup() self.common.gui.onion.cleanup()
tor_con = TorConnectionDialog(self.common, settings) self.test_tor_button.hide()
tor_con.start() self.save_button.hide()
self.common.log(
"TorSettingsDialog",
"save_clicked",
f"Onion done rebooting, connected to Tor: {self.common.gui.onion.connected_to_tor}",
)
if (
self.common.gui.onion.is_authenticated()
and not tor_con.wasCanceled()
):
self.settings_saved.emit()
self.close()
self.tor_con_type = "save"
self.tor_con.show()
self.tor_con.start(settings)
else: else:
self.settings_saved.emit() self.close_this_tab.emit()
self.close()
else: else:
self.settings_saved.emit() self.close_this_tab.emit()
self.close()
def cancel_clicked(self): def tor_con_success(self):
""" """
Cancel button clicked. Finished testing tor connection.
""" """
self.common.log("TorSettingsDialog", "cancel_clicked") self.tor_con.hide()
if ( self.test_tor_button.show()
not self.common.gui.local_only self.save_button.show()
and not self.common.gui.onion.is_authenticated()
): if self.tor_con_type == "test":
Alert( Alert(
self.common, self.common,
strings._("gui_tor_connection_canceled"), strings._("settings_test_success").format(
QtWidgets.QMessageBox.Warning, self.test_onion.tor_version,
self.test_onion.supports_ephemeral,
self.test_onion.supports_stealth,
self.test_onion.supports_v3_onions,
),
title=strings._("gui_settings_connection_type_test_button"),
) )
sys.exit() self.test_onion.cleanup()
else:
self.close() elif self.tor_con_type == "save":
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()
self.tor_con_type = None
def tor_con_fail(self, msg):
"""
Finished testing tor connection.
"""
self.tor_con.hide()
self.test_tor_button.show()
self.save_button.show()
self.error_label.setText(msg)
if self.tor_con_type == "test":
self.test_onion.cleanup()
self.tor_con_type = None
def settings_from_fields(self): def settings_from_fields(self):
""" """
Return a Settings object that's full of values from the settings dialog. Return a Settings object that's full of values from the settings dialog.
""" """
self.common.log("TorSettingsDialog", "settings_from_fields") self.common.log("TorSettingsTab", "settings_from_fields")
settings = Settings(self.common) settings = Settings(self.common)
settings.load() # To get the last update timestamp settings.load() # To get the last update timestamp
@ -751,47 +810,30 @@ class TorSettingsDialog(QtWidgets.QDialog):
# Whether we use bridges # Whether we use bridges
if self.bridge_use_checkbox.checkState() == QtCore.Qt.Checked: if self.bridge_use_checkbox.checkState() == QtCore.Qt.Checked:
settings.set("no_bridges", False) settings.set("bridges_enabled", True)
if self.bridge_builtin_radio.isChecked(): if self.bridge_builtin_radio.isChecked():
selection = self.bridge_builtin_dropdown.currentText() settings.set("bridges_type", "built-in")
if selection == "obfs4":
settings.set("tor_bridges_use_obfs4", True)
settings.set("tor_bridges_use_meek_lite_azure", False)
settings.set("tor_bridges_use_snowflake", False)
elif selection == "meek-azure":
settings.set("tor_bridges_use_obfs4", False)
settings.set("tor_bridges_use_meek_lite_azure", True)
settings.set("tor_bridges_use_snowflake", False)
elif selection == "snowflake":
settings.set("tor_bridges_use_obfs4", False)
settings.set("tor_bridges_use_meek_lite_azure", False)
settings.set("tor_bridges_use_snowflake", True)
settings.set("tor_bridges_use_moat", False) selection = self.bridge_builtin_dropdown.currentText()
settings.set("tor_bridges_use_custom_bridges", "") settings.set("bridges_builtin_pt", selection)
if self.bridge_moat_radio.isChecked(): if self.bridge_moat_radio.isChecked():
settings.set("tor_bridges_use_obfs4", False) settings.set("bridges_type", "moat")
settings.set("tor_bridges_use_meek_lite_azure", False)
settings.set("tor_bridges_use_snowflake", False)
settings.set("tor_bridges_use_moat", True)
moat_bridges = self.bridge_moat_textbox.toPlainText() moat_bridges = self.bridge_moat_textbox.toPlainText()
if moat_bridges.strip() == "": if (
Alert(self.common, strings._("gui_settings_moat_bridges_invalid")) self.connection_type_bundled_radio.isChecked()
and moat_bridges.strip() == ""
):
self.error_label.setText(
strings._("gui_settings_moat_bridges_invalid")
)
return False return False
settings.set("tor_bridges_use_moat_bridges", moat_bridges) settings.set("bridges_moat", moat_bridges)
settings.set("tor_bridges_use_custom_bridges", "")
if self.bridge_custom_radio.isChecked(): if self.bridge_custom_radio.isChecked():
settings.set("tor_bridges_use_obfs4", False) settings.set("bridges_type", "custom")
settings.set("tor_bridges_use_meek_lite_azure", False)
settings.set("tor_bridges_use_snowflake", False)
settings.set("tor_bridges_use_moat", False)
new_bridges = [] new_bridges = []
bridges = self.bridge_custom_textbox.toPlainText().split("\n") bridges = self.bridge_custom_textbox.toPlainText().split("\n")
@ -822,26 +864,32 @@ class TorSettingsDialog(QtWidgets.QDialog):
if bridges_valid: if bridges_valid:
new_bridges = "\n".join(new_bridges) + "\n" new_bridges = "\n".join(new_bridges) + "\n"
settings.set("tor_bridges_use_custom_bridges", new_bridges) settings.set("bridges_custom", new_bridges)
else: else:
Alert(self.common, strings._("gui_settings_tor_bridges_invalid")) self.error_label.setText(
strings._("gui_settings_tor_bridges_invalid")
)
return False return False
else: else:
settings.set("no_bridges", True) settings.set("bridges_enabled", False)
return settings return settings
def closeEvent(self, e): def closeEvent(self, e):
self.common.log("TorSettingsDialog", "closeEvent") self.common.log("TorSettingsTab", "closeEvent")
# On close, if Tor isn't connected, then quit OnionShare altogether # On close, if Tor isn't connected, then quit OnionShare altogether
if not self.common.gui.local_only: if not self.common.gui.local_only:
if not self.common.gui.onion.is_authenticated(): if not self.common.gui.onion.is_authenticated():
self.common.log( self.common.log(
"TorSettingsDialog", "TorSettingsTab",
"closeEvent", "closeEvent",
"Closing while not connected to Tor", "Closing while not connected to Tor",
) )
# Wait 1ms for the event loop to finish, then quit # Wait 1ms for the event loop to finish, then quit
QtCore.QTimer.singleShot(1, self.common.gui.qtapp.quit) QtCore.QTimer.singleShot(1, self.common.gui.qtapp.quit)
def settings_have_changed(self):
# Global settings have changed
self.common.log("TorSettingsTab", "settings_have_changed")