mirror of
https://github.com/onionshare/onionshare.git
synced 2024-12-24 14:59:42 -05:00
Merge branch 'censorship' into develop
This commit is contained in:
commit
d59328d99c
@ -31,6 +31,7 @@ Finalize localization:
|
||||
- [ ] Merge all the translations from weblate
|
||||
- [ ] In `docs` run `poetry run ./check-weblate.py [API_KEY]` to see which translations are >90% in the app and docs
|
||||
- [ ] Edit `cli/onionshare_cli/settings.py`, make sure `self.available_locales` lists only locales that are >90% translated
|
||||
- [ ] From the `desktop` folder in the virtual env, run `./scripts/countries-update-list.py` to make sure the localized country list for censorship circumvention is available in all available languages
|
||||
- [ ] Edit `docs/source/conf.py`, make sure `languages` lists only languages that are >90% translated
|
||||
- [ ] Edit `docs/build.sh` and make sure `LOCALES=` lists the same languages as above, in `docs/source/conf.py`
|
||||
- [ ] Make sure the latest documentation is built and committed:
|
||||
|
@ -22,6 +22,12 @@ import requests
|
||||
from .meek import MeekNotRunning
|
||||
|
||||
|
||||
class CensorshipCircumventionError(Exception):
|
||||
"""
|
||||
There was a problem connecting to the Tor CensorshipCircumvention API.
|
||||
"""
|
||||
|
||||
|
||||
class CensorshipCircumvention(object):
|
||||
"""
|
||||
Connect to the Tor Moat APIs to retrieve censorship
|
||||
@ -47,7 +53,7 @@ class CensorshipCircumvention(object):
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"__init__",
|
||||
"Using Meek with CensorShipCircumvention API",
|
||||
"Using Meek with CensorshipCircumvention API",
|
||||
)
|
||||
self.api_proxies = self.meek.meek_proxies
|
||||
if onion:
|
||||
@ -58,7 +64,7 @@ class CensorshipCircumvention(object):
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"__init__",
|
||||
"Using Tor with CensorShipCircumvention API",
|
||||
"Using Tor with CensorshipCircumvention API",
|
||||
)
|
||||
(socks_address, socks_port) = self.onion.get_tor_socks_port()
|
||||
self.api_proxies = {
|
||||
@ -84,31 +90,34 @@ class CensorshipCircumvention(object):
|
||||
if country:
|
||||
data = {"country": country}
|
||||
|
||||
r = requests.post(
|
||||
endpoint,
|
||||
json=data,
|
||||
headers={"Content-Type": "application/vnd.api+json"},
|
||||
proxies=self.api_proxies,
|
||||
)
|
||||
if r.status_code != 200:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"censorship_obtain_map",
|
||||
f"status_code={r.status_code}",
|
||||
try:
|
||||
r = requests.post(
|
||||
endpoint,
|
||||
json=data,
|
||||
headers={"Content-Type": "application/vnd.api+json"},
|
||||
proxies=self.api_proxies,
|
||||
)
|
||||
return False
|
||||
if r.status_code != 200:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"censorship_obtain_map",
|
||||
f"status_code={r.status_code}",
|
||||
)
|
||||
return False
|
||||
|
||||
result = r.json()
|
||||
result = r.json()
|
||||
|
||||
if "errors" in result:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"censorship_obtain_map",
|
||||
f"errors={result['errors']}",
|
||||
)
|
||||
return False
|
||||
if "errors" in result:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"censorship_obtain_map",
|
||||
f"errors={result['errors']}",
|
||||
)
|
||||
return False
|
||||
|
||||
return result
|
||||
return result
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise CensorshipCircumventionError(e)
|
||||
|
||||
def request_settings(self, country=False, transports=False):
|
||||
"""
|
||||
@ -127,45 +136,53 @@ class CensorshipCircumvention(object):
|
||||
endpoint = "https://bridges.torproject.org/moat/circumvention/settings"
|
||||
data = {}
|
||||
if country:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"censorship_obtain_settings",
|
||||
f"Trying to obtain bridges for country={country}",
|
||||
)
|
||||
data = {"country": country}
|
||||
if transports:
|
||||
data.append({"transports": transports})
|
||||
r = requests.post(
|
||||
endpoint,
|
||||
json=data,
|
||||
headers={"Content-Type": "application/vnd.api+json"},
|
||||
proxies=self.api_proxies,
|
||||
)
|
||||
if r.status_code != 200:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"censorship_obtain_settings",
|
||||
f"status_code={r.status_code}",
|
||||
try:
|
||||
r = requests.post(
|
||||
endpoint,
|
||||
json=data,
|
||||
headers={"Content-Type": "application/vnd.api+json"},
|
||||
proxies=self.api_proxies,
|
||||
)
|
||||
return False
|
||||
if r.status_code != 200:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"censorship_obtain_settings",
|
||||
f"status_code={r.status_code}",
|
||||
)
|
||||
return False
|
||||
|
||||
result = r.json()
|
||||
result = r.json()
|
||||
|
||||
if "errors" in result:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"censorship_obtain_settings",
|
||||
f"errors={result['errors']}",
|
||||
)
|
||||
return False
|
||||
if "errors" in result:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"censorship_obtain_settings",
|
||||
f"errors={result['errors']}",
|
||||
)
|
||||
return False
|
||||
|
||||
# There are no settings - perhaps this country doesn't require censorship circumvention?
|
||||
# This is not really an error, so we can just check if False and assume direct Tor
|
||||
# connection will work.
|
||||
if not "settings" in result:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"censorship_obtain_settings",
|
||||
"No settings found for this country",
|
||||
)
|
||||
return False
|
||||
# There are no settings - perhaps this country doesn't require censorship circumvention?
|
||||
# This is not really an error, so we can just check if False and assume direct Tor
|
||||
# connection will work.
|
||||
if not "settings" in result:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"censorship_obtain_settings",
|
||||
"No settings found for this country",
|
||||
)
|
||||
return False
|
||||
|
||||
return result
|
||||
return result
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise CensorshipCircumventionError(e)
|
||||
|
||||
def request_builtin_bridges(self):
|
||||
"""
|
||||
@ -174,27 +191,103 @@ class CensorshipCircumvention(object):
|
||||
if not self.api_proxies:
|
||||
return False
|
||||
endpoint = "https://bridges.torproject.org/moat/circumvention/builtin"
|
||||
r = requests.post(
|
||||
endpoint,
|
||||
headers={"Content-Type": "application/vnd.api+json"},
|
||||
proxies=self.api_proxies,
|
||||
try:
|
||||
r = requests.post(
|
||||
endpoint,
|
||||
headers={"Content-Type": "application/vnd.api+json"},
|
||||
proxies=self.api_proxies,
|
||||
)
|
||||
if r.status_code != 200:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"censorship_obtain_builtin_bridges",
|
||||
f"status_code={r.status_code}",
|
||||
)
|
||||
return False
|
||||
|
||||
result = r.json()
|
||||
|
||||
if "errors" in result:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"censorship_obtain_builtin_bridges",
|
||||
f"errors={result['errors']}",
|
||||
)
|
||||
return False
|
||||
|
||||
return result
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise CensorshipCircumventionError(e)
|
||||
|
||||
def save_settings(self, settings, bridge_settings):
|
||||
"""
|
||||
Checks the bridges and saves them in settings.
|
||||
"""
|
||||
bridges_ok = False
|
||||
self.settings = settings
|
||||
|
||||
# @TODO there might be several bridge types recommended.
|
||||
# Should we attempt to iterate over each type if one of them fails to connect?
|
||||
# But if so, how to stop it starting 3 separate Tor connection threads?
|
||||
# for bridges in request_bridges["settings"]:
|
||||
bridges = bridge_settings["settings"][0]["bridges"]
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"save_settings",
|
||||
f"Obtained bridges: {bridges}",
|
||||
)
|
||||
if r.status_code != 200:
|
||||
bridge_strings = bridges["bridge_strings"]
|
||||
bridge_type = bridges["type"]
|
||||
bridge_source = bridges["source"]
|
||||
|
||||
# If the recommended bridge source is to use the built-in
|
||||
# bridges, set that in our settings, as if the user had
|
||||
# selected the built-in bridges for a specific PT themselves.
|
||||
#
|
||||
if bridge_source == "builtin":
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"censorship_obtain_builtin_bridges",
|
||||
f"status_code={r.status_code}",
|
||||
"save_settings",
|
||||
"Will be using built-in bridges",
|
||||
)
|
||||
return False
|
||||
|
||||
result = r.json()
|
||||
|
||||
if "errors" in result:
|
||||
self.settings.set("bridges_type", "built-in")
|
||||
if bridge_type == "obfs4":
|
||||
self.settings.set("bridges_builtin_pt", "obfs4")
|
||||
if bridge_type == "snowflake":
|
||||
self.settings.set("bridges_builtin_pt", "snowflake")
|
||||
if bridge_type == "meek":
|
||||
self.settings.set("bridges_builtin_pt", "meek-azure")
|
||||
bridges_ok = True
|
||||
else:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"censorship_obtain_builtin_bridges",
|
||||
f"errors={result['errors']}",
|
||||
"save_settings",
|
||||
"Will be using custom bridges",
|
||||
)
|
||||
# Any other type of bridge we can treat as custom.
|
||||
self.settings.set("bridges_type", "custom")
|
||||
|
||||
# Sanity check the bridges provided from the Tor API before saving
|
||||
bridges_checked = self.common.check_bridges_valid(bridge_strings)
|
||||
|
||||
if bridges_checked:
|
||||
self.settings.set("bridges_custom", "\n".join(bridges_checked))
|
||||
bridges_ok = True
|
||||
|
||||
# If we got any good bridges, save them to settings and return.
|
||||
if bridges_ok:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"save_settings",
|
||||
"Saving settings with automatically-obtained bridges",
|
||||
)
|
||||
self.settings.set("bridges_enabled", True)
|
||||
self.settings.save()
|
||||
return True
|
||||
else:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"save_settings",
|
||||
"Could not use any of the obtained bridges.",
|
||||
)
|
||||
return False
|
||||
|
||||
return result
|
||||
|
@ -28,6 +28,7 @@ import sys
|
||||
import threading
|
||||
import time
|
||||
import shutil
|
||||
import re
|
||||
from pkg_resources import resource_filename
|
||||
|
||||
import colorama
|
||||
@ -312,7 +313,6 @@ class Common:
|
||||
"""
|
||||
Returns the absolute path of a resource
|
||||
"""
|
||||
self.log("Common", "get_resource_path", f"filename={filename}")
|
||||
path = resource_filename("onionshare_cli", os.path.join("resources", filename))
|
||||
self.log("Common", "get_resource_path", f"filename={filename}, path={path}")
|
||||
return path
|
||||
@ -467,6 +467,40 @@ class Common:
|
||||
r = random.SystemRandom()
|
||||
return "-".join(r.choice(wordlist) for _ in range(word_count))
|
||||
|
||||
def check_bridges_valid(self, bridges):
|
||||
"""
|
||||
Does a regex check against a supplied list of bridges, to make sure they
|
||||
are valid strings depending on the bridge type.
|
||||
"""
|
||||
valid_bridges = []
|
||||
self.log("Common", "check_bridges_valid", "Checking bridge syntax")
|
||||
for bridge in bridges:
|
||||
if bridge != "":
|
||||
# Check the syntax of the custom bridge to make sure it looks legitimate
|
||||
ipv4_pattern = re.compile(
|
||||
"(obfs4\s+)?(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]):([0-9]+)(\s+)([A-Z0-9]+)(.+)$"
|
||||
)
|
||||
ipv6_pattern = re.compile(
|
||||
"(obfs4\s+)?\[(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\]:[0-9]+\s+[A-Z0-9]+(.+)$"
|
||||
)
|
||||
meek_lite_pattern = re.compile(
|
||||
"(meek_lite)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)(\s)+url=(.+)(\s)+front=(.+)"
|
||||
)
|
||||
snowflake_pattern = re.compile(
|
||||
"(snowflake)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)"
|
||||
)
|
||||
if (
|
||||
ipv4_pattern.match(bridge)
|
||||
or ipv6_pattern.match(bridge)
|
||||
or meek_lite_pattern.match(bridge)
|
||||
or snowflake_pattern.match(bridge)
|
||||
):
|
||||
valid_bridges.append(bridge)
|
||||
if valid_bridges:
|
||||
return valid_bridges
|
||||
else:
|
||||
return False
|
||||
|
||||
def is_flatpak(self):
|
||||
"""
|
||||
Returns True if OnionShare is running in a Flatpak sandbox
|
||||
@ -479,6 +513,7 @@ class Common:
|
||||
"""
|
||||
return os.environ.get("SNAP_INSTANCE_NAME") == "onionshare"
|
||||
|
||||
|
||||
@staticmethod
|
||||
def random_string(num_bytes, output_len=None):
|
||||
"""
|
||||
|
@ -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,
|
||||
|
@ -37,6 +37,7 @@ class TestSettings:
|
||||
"bridges_builtin": {},
|
||||
"persistent_tabs": [],
|
||||
"theme": 0,
|
||||
"auto_connect": False,
|
||||
}
|
||||
for key in settings_obj._settings:
|
||||
# Skip locale, it will not always default to the same thing
|
||||
|
681
desktop/onionshare/connection_tab.py
Normal file
681
desktop/onionshare/connection_tab.py
Normal file
@ -0,0 +1,681 @@
|
||||
# -*- 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/>.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from PySide2 import QtCore, QtWidgets, QtGui
|
||||
|
||||
from onionshare_cli.censorship import (
|
||||
CensorshipCircumvention,
|
||||
CensorshipCircumventionError,
|
||||
)
|
||||
from onionshare_cli.meek import (
|
||||
MeekNotRunning,
|
||||
MeekNotFound,
|
||||
)
|
||||
from onionshare_cli.settings import Settings
|
||||
|
||||
from . import strings
|
||||
from .gui_common import GuiCommon, ToggleCheckbox
|
||||
from .tor_connection import TorConnectionWidget
|
||||
from .update_checker import UpdateThread
|
||||
from .widgets import Alert
|
||||
|
||||
|
||||
class AutoConnectTab(QtWidgets.QWidget):
|
||||
"""
|
||||
Initial Tab that appears in the very beginning to ask user if
|
||||
should auto connect.
|
||||
"""
|
||||
|
||||
close_this_tab = QtCore.Signal()
|
||||
tor_is_connected = QtCore.Signal()
|
||||
tor_is_disconnected = QtCore.Signal()
|
||||
|
||||
def __init__(self, common, tab_id, status_bar, window, parent=None):
|
||||
super(AutoConnectTab, self).__init__()
|
||||
self.common = common
|
||||
self.common.log("AutoConnectTab", "__init__")
|
||||
|
||||
self.status_bar = status_bar
|
||||
self.tab_id = tab_id
|
||||
self.window = window
|
||||
self.parent = parent
|
||||
|
||||
# Was auto connected?
|
||||
self.curr_settings = Settings(common)
|
||||
self.curr_settings.load()
|
||||
self.auto_connect_enabled = self.curr_settings.get("auto_connect")
|
||||
|
||||
# Rocket ship animation images
|
||||
self.anim_stars = AnimStars(self, self.window)
|
||||
self.anim_ship = AnimShip(self, self.window)
|
||||
self.anim_smoke = AnimSmoke(self, self.window)
|
||||
|
||||
# Onionshare logo
|
||||
self.image_label = QtWidgets.QLabel()
|
||||
self.image_label.setPixmap(
|
||||
QtGui.QPixmap.fromImage(
|
||||
QtGui.QImage(
|
||||
GuiCommon.get_resource_path(
|
||||
os.path.join(
|
||||
"images", f"{common.gui.color_mode}_logo_text_bg.png"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
self.image_label.setFixedSize(322, 65)
|
||||
image_layout = QtWidgets.QVBoxLayout()
|
||||
image_layout.addWidget(self.image_label)
|
||||
self.image = QtWidgets.QWidget()
|
||||
self.image.setLayout(image_layout)
|
||||
|
||||
# First launch widget
|
||||
self.first_launch_widget = AutoConnectFirstLaunchWidget(
|
||||
self.common, self.curr_settings
|
||||
)
|
||||
self.first_launch_widget.toggle_auto_connect.connect(self.toggle_auto_connect)
|
||||
self.first_launch_widget.connect_clicked.connect(
|
||||
self.first_launch_widget_connect_clicked
|
||||
)
|
||||
self.first_launch_widget.open_tor_settings.connect(self.open_tor_settings)
|
||||
self.first_launch_widget.show()
|
||||
|
||||
# Use bridge widget
|
||||
self.use_bridge_widget = AutoConnectUseBridgeWidget(self.common)
|
||||
self.use_bridge_widget.connect_clicked.connect(self.use_bridge_connect_clicked)
|
||||
self.use_bridge_widget.try_again_clicked.connect(
|
||||
self.first_launch_widget_connect_clicked
|
||||
)
|
||||
self.use_bridge_widget.open_tor_settings.connect(self.open_tor_settings)
|
||||
self.use_bridge_widget.hide()
|
||||
|
||||
# Tor connection widget
|
||||
self.tor_con = TorConnectionWidget(self.common, self.status_bar)
|
||||
self.tor_con.success.connect(self.tor_con_success)
|
||||
self.tor_con.fail.connect(self.tor_con_fail)
|
||||
self.tor_con.update_progress.connect(self.anim_stars.update)
|
||||
self.tor_con.update_progress.connect(self.anim_ship.update)
|
||||
self.tor_con.update_progress.connect(self.anim_smoke.update)
|
||||
self.tor_con.hide()
|
||||
|
||||
# Layout
|
||||
content_layout = QtWidgets.QVBoxLayout()
|
||||
content_layout.addStretch()
|
||||
content_layout.addWidget(self.image)
|
||||
content_layout.addWidget(self.first_launch_widget)
|
||||
content_layout.addWidget(self.use_bridge_widget)
|
||||
content_layout.addWidget(self.tor_con)
|
||||
content_layout.addStretch()
|
||||
content_layout.setAlignment(QtCore.Qt.AlignCenter)
|
||||
content_widget = QtWidgets.QWidget()
|
||||
content_widget.setLayout(content_layout)
|
||||
|
||||
self.layout = QtWidgets.QHBoxLayout()
|
||||
self.layout.addWidget(content_widget)
|
||||
self.layout.addStretch()
|
||||
|
||||
self.setLayout(self.layout)
|
||||
|
||||
def check_autoconnect(self):
|
||||
"""
|
||||
After rendering, check if autoconnect was clicked, then start connecting
|
||||
"""
|
||||
self.common.log("AutoConnectTab", "autoconnect_checking")
|
||||
if self.auto_connect_enabled:
|
||||
self.first_launch_widget.enable_autoconnect_checkbox.setChecked(True)
|
||||
self.first_launch_widget_connect_clicked()
|
||||
|
||||
def toggle_auto_connect(self):
|
||||
"""
|
||||
Auto connect checkbox clicked
|
||||
"""
|
||||
self.common.log("AutoConnectTab", "autoconnect_checkbox_clicked")
|
||||
self.curr_settings.set(
|
||||
"auto_connect",
|
||||
self.first_launch_widget.enable_autoconnect_checkbox.isChecked(),
|
||||
)
|
||||
self.curr_settings.save()
|
||||
|
||||
def open_tor_settings(self):
|
||||
self.parent.open_settings_tab(from_autoconnect=True, active_tab="tor")
|
||||
|
||||
def first_launch_widget_connect_clicked(self):
|
||||
"""
|
||||
Connect button in first launch widget clicked. Try to connect to tor.
|
||||
"""
|
||||
self.common.log("AutoConnectTab", "first_launch_widget_connect_clicked")
|
||||
self.first_launch_widget.hide_buttons()
|
||||
|
||||
self.tor_con.show()
|
||||
self.tor_con.start(self.curr_settings)
|
||||
|
||||
def _got_bridges(self):
|
||||
self.use_bridge_widget.progress.hide()
|
||||
self.use_bridge_widget.progress_label.hide()
|
||||
# Try and connect again
|
||||
self.common.log(
|
||||
"AutoConnectTab",
|
||||
"_got_bridges",
|
||||
"Got bridges. Trying to reconnect to Tor",
|
||||
)
|
||||
self.tor_con.show()
|
||||
self.tor_con.start(self.curr_settings)
|
||||
|
||||
def _got_no_bridges(self):
|
||||
# If we got no bridges, try connecting again using built-in obfs4 bridges
|
||||
self.curr_settings.set("bridges_type", "built-in")
|
||||
self.curr_settings.set("bridges_builtin_pt", "obfs4")
|
||||
self.curr_settings.set("bridges_enabled", True)
|
||||
self.curr_settings.save()
|
||||
|
||||
self._got_bridges()
|
||||
|
||||
def _censorship_progress_update(self, progress, summary):
|
||||
self.use_bridge_widget.progress.setValue(int(progress))
|
||||
self.use_bridge_widget.progress_label.setText(
|
||||
f"<strong>{strings._('gui_autoconnect_circumventing_censorship')}</strong><br>{summary}"
|
||||
)
|
||||
|
||||
def network_connection_error(self):
|
||||
"""
|
||||
Display an error if there simply seems no network connection.
|
||||
"""
|
||||
self.use_bridge_widget.connection_status_label.setText(
|
||||
strings._("gui_autoconnect_failed_to_connect_to_tor")
|
||||
)
|
||||
self.use_bridge_widget.progress.hide()
|
||||
self.use_bridge_widget.progress_label.hide()
|
||||
self.use_bridge_widget.error_label.show()
|
||||
self.use_bridge_widget.country_combobox.setEnabled(True)
|
||||
self.use_bridge_widget.show_buttons()
|
||||
self.use_bridge_widget.show()
|
||||
|
||||
def use_bridge_connect_clicked(self):
|
||||
"""
|
||||
Connect button in use bridge widget clicked.
|
||||
"""
|
||||
self.common.log(
|
||||
"AutoConnectTab",
|
||||
"use_bridge_connect_clicked",
|
||||
"Trying to automatically obtain bridges",
|
||||
)
|
||||
self.use_bridge_widget.hide_buttons()
|
||||
self.use_bridge_widget.progress.show()
|
||||
self.use_bridge_widget.progress_label.show()
|
||||
|
||||
if self.use_bridge_widget.detect_automatic_radio.isChecked():
|
||||
country = False
|
||||
else:
|
||||
country = self.use_bridge_widget.country_combobox.currentData().lower()
|
||||
|
||||
self._censorship_progress_update(
|
||||
50, strings._("gui_autoconnect_circumventing_censorship_starting_meek")
|
||||
)
|
||||
try:
|
||||
self.common.gui.meek.start()
|
||||
self.censorship_circumvention = CensorshipCircumvention(
|
||||
self.common, self.common.gui.meek
|
||||
)
|
||||
self._censorship_progress_update(
|
||||
75,
|
||||
strings._(
|
||||
"gui_autoconnect_circumventing_censorship_requesting_bridges"
|
||||
),
|
||||
)
|
||||
bridge_settings = self.censorship_circumvention.request_settings(
|
||||
country=country
|
||||
)
|
||||
self.common.gui.meek.cleanup()
|
||||
|
||||
if bridge_settings and self.censorship_circumvention.save_settings(
|
||||
self.curr_settings, bridge_settings
|
||||
):
|
||||
self._censorship_progress_update(
|
||||
100,
|
||||
strings._("gui_autoconnect_circumventing_censorship_got_bridges"),
|
||||
)
|
||||
self._got_bridges()
|
||||
else:
|
||||
self._got_no_bridges()
|
||||
except (
|
||||
MeekNotRunning,
|
||||
MeekNotFound,
|
||||
) as e:
|
||||
self._got_no_bridges()
|
||||
except CensorshipCircumventionError as e:
|
||||
self.common.log(
|
||||
"AutoConnectTab",
|
||||
"use_bridge_connect_clicked",
|
||||
"Request to the Tor Censorship Circumvention API failed. No network connection?",
|
||||
)
|
||||
self.network_connection_error()
|
||||
|
||||
def check_for_updates(self):
|
||||
"""
|
||||
Check for OnionShare updates in a new thread, if enabled.
|
||||
"""
|
||||
if self.common.platform == "Windows" or self.common.platform == "Darwin":
|
||||
if self.common.settings.get("use_autoupdate"):
|
||||
|
||||
def update_available(update_url, installed_version, latest_version):
|
||||
Alert(
|
||||
self.common,
|
||||
strings._("update_available").format(
|
||||
update_url, installed_version, latest_version
|
||||
),
|
||||
)
|
||||
|
||||
self.update_thread = UpdateThread(self.common, self.common.gui.onion)
|
||||
self.update_thread.update_available.connect(update_available)
|
||||
self.update_thread.start()
|
||||
|
||||
def tor_con_success(self):
|
||||
"""
|
||||
Finished testing tor connection.
|
||||
"""
|
||||
self.tor_con.hide()
|
||||
self.first_launch_widget.show_buttons()
|
||||
self.use_bridge_widget.show_buttons()
|
||||
self.use_bridge_widget.progress.hide()
|
||||
self.use_bridge_widget.progress_label.hide()
|
||||
|
||||
if self.common.gui.onion.is_authenticated() and not self.tor_con.wasCanceled():
|
||||
# Tell the tabs that Tor is connected
|
||||
self.tor_is_connected.emit()
|
||||
# After connecting to Tor, check for updates
|
||||
self.check_for_updates()
|
||||
# Close the tab
|
||||
self.close_this_tab.emit()
|
||||
|
||||
def tor_con_fail(self, msg):
|
||||
"""
|
||||
Finished testing tor connection.
|
||||
"""
|
||||
self.tor_con.hide()
|
||||
|
||||
# If we're on first launch, switch to use bridge
|
||||
if self.first_launch_widget.isVisible():
|
||||
self.first_launch_widget.show_buttons()
|
||||
self.first_launch_widget.hide()
|
||||
self.use_bridge_widget.show()
|
||||
else:
|
||||
self.use_bridge_widget.show_buttons()
|
||||
|
||||
def reload_settings(self):
|
||||
"""
|
||||
Reload the latest Tor settings, and reset to show the
|
||||
first-launch widget if it had been hidden.
|
||||
"""
|
||||
self.curr_settings.load()
|
||||
self.auto_connect_enabled = self.curr_settings.get("auto_connect")
|
||||
self.first_launch_widget.enable_autoconnect_checkbox.setChecked(
|
||||
self.auto_connect_enabled
|
||||
)
|
||||
self.use_bridge_widget.hide()
|
||||
self.first_launch_widget.show_buttons()
|
||||
self.first_launch_widget.show()
|
||||
|
||||
|
||||
class Anim(QtWidgets.QLabel):
|
||||
"""
|
||||
Rocket ship animation base class
|
||||
"""
|
||||
|
||||
force_update = QtCore.Signal(int)
|
||||
|
||||
def __init__(self, parent, window, w, h, filename):
|
||||
super(Anim, self).__init__(parent=parent)
|
||||
|
||||
self.window = window
|
||||
self.window.window_resized.connect(self.update_same_percent)
|
||||
|
||||
self.w = w
|
||||
self.h = h
|
||||
self.percent = 0
|
||||
self.used_percentages = []
|
||||
|
||||
self.setPixmap(
|
||||
QtGui.QPixmap.fromImage(
|
||||
QtGui.QImage(
|
||||
GuiCommon.get_resource_path(os.path.join("images", filename))
|
||||
)
|
||||
)
|
||||
)
|
||||
self.setFixedSize(self.w, self.h)
|
||||
self.update(0)
|
||||
|
||||
self.force_update.connect(self.update)
|
||||
|
||||
def update_same_percent(self):
|
||||
self.update(self.percent)
|
||||
|
||||
def update(self, percent):
|
||||
self.percent = percent
|
||||
self.move()
|
||||
self.setGeometry(int(self.x), int(self.y), int(self.w), int(self.h))
|
||||
|
||||
def move(self):
|
||||
# Implement in child
|
||||
pass
|
||||
|
||||
|
||||
class AnimStars(Anim):
|
||||
"""
|
||||
Rocket ship animation part: stars
|
||||
"""
|
||||
|
||||
def __init__(self, parent, window):
|
||||
super(AnimStars, self).__init__(
|
||||
parent, window, 740, 629, "tor-connect-stars.png"
|
||||
)
|
||||
|
||||
def move(self):
|
||||
self.x = self.window.width() - self.w
|
||||
self.y = 0
|
||||
# Stars don't move until 10%, then move down
|
||||
if self.percent >= 10:
|
||||
self.y += self.percent * 6.6
|
||||
|
||||
|
||||
class AnimShip(Anim):
|
||||
"""
|
||||
Rocket ship animation part: ship
|
||||
"""
|
||||
|
||||
def __init__(self, parent, window):
|
||||
super(AnimShip, self).__init__(parent, window, 239, 545, "tor-connect-ship.png")
|
||||
|
||||
def move(self):
|
||||
self.x = self.window.width() - self.w - 150
|
||||
self.y = self.window.height() - self.h - 40
|
||||
# Ship moves up
|
||||
self.y -= self.percent * 6.6
|
||||
|
||||
|
||||
class AnimSmoke(Anim):
|
||||
"""
|
||||
Rocket ship animation part: smoke
|
||||
"""
|
||||
|
||||
def __init__(self, parent, window):
|
||||
super(AnimSmoke, self).__init__(
|
||||
parent, window, 522, 158, "tor-connect-smoke.png"
|
||||
)
|
||||
|
||||
def move(self):
|
||||
self.x = self.window.width() - self.w
|
||||
self.y = self.window.height() - self.h + 50
|
||||
# Smoke moves up until 50%, then moves down
|
||||
self.y -= self.percent * 6.6
|
||||
if self.percent >= 50:
|
||||
self.y += self.percent * 6.7
|
||||
|
||||
|
||||
class AutoConnectFirstLaunchWidget(QtWidgets.QWidget):
|
||||
"""
|
||||
When you first launch OnionShare, this is the widget that is displayed
|
||||
"""
|
||||
|
||||
toggle_auto_connect = QtCore.Signal()
|
||||
connect_clicked = QtCore.Signal()
|
||||
open_tor_settings = QtCore.Signal()
|
||||
|
||||
def __init__(self, common, settings):
|
||||
super(AutoConnectFirstLaunchWidget, self).__init__()
|
||||
self.common = common
|
||||
self.common.log("AutoConnectFirstLaunchWidget", "__init__")
|
||||
|
||||
self.settings = settings
|
||||
|
||||
# Description and checkbox
|
||||
description_label = QtWidgets.QLabel(strings._("gui_autoconnect_description"))
|
||||
self.enable_autoconnect_checkbox = ToggleCheckbox(
|
||||
strings._("gui_enable_autoconnect_checkbox")
|
||||
)
|
||||
self.enable_autoconnect_checkbox.setChecked(self.settings.get("auto_connect"))
|
||||
self.enable_autoconnect_checkbox.clicked.connect(self._toggle_auto_connect)
|
||||
self.enable_autoconnect_checkbox.setFixedWidth(400)
|
||||
self.enable_autoconnect_checkbox.setStyleSheet(
|
||||
common.gui.css["enable_autoconnect"]
|
||||
)
|
||||
description_layout = QtWidgets.QVBoxLayout()
|
||||
description_layout.addWidget(description_label)
|
||||
description_layout.addWidget(self.enable_autoconnect_checkbox)
|
||||
description_widget = QtWidgets.QWidget()
|
||||
description_widget.setLayout(description_layout)
|
||||
|
||||
# Buttons
|
||||
self.connect_button = QtWidgets.QPushButton(strings._("gui_autoconnect_start"))
|
||||
self.connect_button.clicked.connect(self._connect_clicked)
|
||||
self.connect_button.setFixedWidth(150)
|
||||
self.connect_button.setStyleSheet(common.gui.css["autoconnect_start_button"])
|
||||
self.configure_button = QtWidgets.QPushButton(
|
||||
strings._("gui_autoconnect_configure")
|
||||
)
|
||||
self.configure_button.clicked.connect(self._open_tor_settings)
|
||||
self.configure_button.setFlat(True)
|
||||
self.configure_button.setStyleSheet(
|
||||
common.gui.css["autoconnect_configure_button"]
|
||||
)
|
||||
cta_layout = QtWidgets.QHBoxLayout()
|
||||
cta_layout.addWidget(self.connect_button)
|
||||
cta_layout.addWidget(self.configure_button)
|
||||
cta_widget = QtWidgets.QWidget()
|
||||
cta_widget.setLayout(cta_layout)
|
||||
|
||||
# Layout
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
layout.addWidget(description_widget)
|
||||
layout.addWidget(cta_widget)
|
||||
self.setLayout(layout)
|
||||
|
||||
def hide_buttons(self):
|
||||
self.connect_button.hide()
|
||||
self.configure_button.hide()
|
||||
|
||||
def show_buttons(self):
|
||||
self.connect_button.show()
|
||||
self.configure_button.show()
|
||||
|
||||
def _toggle_auto_connect(self):
|
||||
self.toggle_auto_connect.emit()
|
||||
|
||||
def _connect_clicked(self):
|
||||
self.connect_clicked.emit()
|
||||
|
||||
def _open_tor_settings(self):
|
||||
self.open_tor_settings.emit()
|
||||
|
||||
|
||||
class AutoConnectUseBridgeWidget(QtWidgets.QWidget):
|
||||
"""
|
||||
If connecting fails, this is the widget that helps the user bypass censorship
|
||||
"""
|
||||
|
||||
connect_clicked = QtCore.Signal()
|
||||
try_again_clicked = QtCore.Signal()
|
||||
open_tor_settings = QtCore.Signal()
|
||||
|
||||
def __init__(self, common):
|
||||
super(AutoConnectUseBridgeWidget, self).__init__()
|
||||
self.common = common
|
||||
self.common.log("AutoConnectUseBridgeWidget", "__init__")
|
||||
|
||||
# Heading label when we fail to connect to Tor.
|
||||
self.connection_status_label = QtWidgets.QLabel(
|
||||
strings._("gui_autoconnect_failed_to_connect_to_tor")
|
||||
)
|
||||
self.connection_status_label.setTextFormat(QtCore.Qt.RichText)
|
||||
self.connection_status_label.setStyleSheet(
|
||||
common.gui.css["autoconnect_failed_to_connect_label"]
|
||||
)
|
||||
|
||||
# Description
|
||||
self.description_label = QtWidgets.QLabel(
|
||||
strings._("gui_autoconnect_bridge_description")
|
||||
)
|
||||
self.description_label.setTextFormat(QtCore.Qt.RichText)
|
||||
self.description_label.setWordWrap(True)
|
||||
|
||||
# Detection preference
|
||||
self.detect_automatic_radio = QtWidgets.QRadioButton(
|
||||
strings._("gui_autoconnect_bridge_detect_automatic")
|
||||
)
|
||||
self.detect_automatic_radio.toggled.connect(self._detect_automatic_toggled)
|
||||
self.detect_manual_radio = QtWidgets.QRadioButton(
|
||||
strings._("gui_autoconnect_bridge_detect_manual")
|
||||
)
|
||||
self.detect_manual_radio.toggled.connect(self._detect_manual_toggled)
|
||||
detect_layout = QtWidgets.QVBoxLayout()
|
||||
detect_layout.addWidget(self.detect_automatic_radio)
|
||||
detect_layout.addWidget(self.detect_manual_radio)
|
||||
|
||||
# Country list
|
||||
locale = self.common.settings.get("locale")
|
||||
if not locale:
|
||||
locale = "en"
|
||||
|
||||
with open(
|
||||
GuiCommon.get_resource_path(os.path.join("countries", f"{locale}.json"))
|
||||
) as f:
|
||||
countries = json.loads(f.read())
|
||||
|
||||
self.country_combobox = QtWidgets.QComboBox()
|
||||
self.country_combobox.setStyleSheet(
|
||||
common.gui.css["autoconnect_countries_combobox"]
|
||||
)
|
||||
for country_code in countries:
|
||||
self.country_combobox.addItem(countries[country_code], country_code)
|
||||
|
||||
# Task label
|
||||
self.task_label = QtWidgets.QLabel()
|
||||
self.task_label.setStyleSheet(common.gui.css["autoconnect_task_label"])
|
||||
self.task_label.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.task_label.hide()
|
||||
|
||||
# Buttons
|
||||
self.connect_button = QtWidgets.QPushButton(
|
||||
strings._("gui_autoconnect_bridge_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.try_again_button = QtWidgets.QPushButton(
|
||||
strings._("gui_autoconnect_try_again_without_a_bridge")
|
||||
)
|
||||
self.try_again_button.clicked.connect(self._try_again_clicked)
|
||||
self.try_again_button.setStyleSheet(common.gui.css["autoconnect_start_button"])
|
||||
|
||||
self.configure_button = QtWidgets.QPushButton(
|
||||
strings._("gui_autoconnect_configure")
|
||||
)
|
||||
self.configure_button.clicked.connect(self._open_tor_settings)
|
||||
self.configure_button.setFlat(True)
|
||||
self.configure_button.setStyleSheet(
|
||||
common.gui.css["autoconnect_configure_button"]
|
||||
)
|
||||
|
||||
# Error label
|
||||
self.error_label = QtWidgets.QLabel(
|
||||
strings._("gui_autoconnect_could_not_connect_to_tor_api")
|
||||
)
|
||||
self.error_label.setStyleSheet(self.common.gui.css["tor_settings_error"])
|
||||
self.error_label.setWordWrap(True)
|
||||
self.error_label.hide()
|
||||
|
||||
self.progress = QtWidgets.QProgressBar()
|
||||
self.progress.setRange(0, 100)
|
||||
self.progress_label = QtWidgets.QLabel(
|
||||
strings._("gui_autoconnect_circumventing_censorship")
|
||||
)
|
||||
self.progress_label.setAlignment(QtCore.Qt.AlignHCenter)
|
||||
self.progress.hide()
|
||||
self.progress_label.hide()
|
||||
|
||||
cta_layout = QtWidgets.QHBoxLayout()
|
||||
cta_layout.addWidget(self.connect_button)
|
||||
cta_layout.addWidget(self.try_again_button)
|
||||
cta_layout.addWidget(self.configure_button)
|
||||
cta_layout.addStretch()
|
||||
cta_widget = QtWidgets.QWidget()
|
||||
cta_widget.setLayout(cta_layout)
|
||||
|
||||
# Layout
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
layout.addWidget(self.connection_status_label)
|
||||
layout.addWidget(self.description_label)
|
||||
layout.addLayout(detect_layout)
|
||||
layout.addWidget(self.country_combobox)
|
||||
layout.addWidget(self.task_label)
|
||||
layout.addWidget(cta_widget)
|
||||
layout.addWidget(self.progress)
|
||||
layout.addWidget(self.progress_label)
|
||||
layout.addWidget(self.error_label)
|
||||
self.setLayout(layout)
|
||||
|
||||
self.detect_automatic_radio.setChecked(True)
|
||||
|
||||
def hide_buttons(self):
|
||||
self.connect_button.hide()
|
||||
self.try_again_button.hide()
|
||||
self.configure_button.hide()
|
||||
self.description_label.hide()
|
||||
self.error_label.hide()
|
||||
self.detect_automatic_radio.hide()
|
||||
self.detect_manual_radio.hide()
|
||||
|
||||
def show_buttons(self):
|
||||
self.connect_button.show()
|
||||
self.try_again_button.show()
|
||||
self.description_label.show()
|
||||
self.configure_button.show()
|
||||
self.detect_automatic_radio.show()
|
||||
self.detect_manual_radio.show()
|
||||
|
||||
def _detect_automatic_toggled(self):
|
||||
self.country_combobox.setEnabled(False)
|
||||
self.country_combobox.hide()
|
||||
|
||||
def _detect_manual_toggled(self):
|
||||
self.country_combobox.setEnabled(True)
|
||||
self.country_combobox.show()
|
||||
|
||||
def _connect_clicked(self):
|
||||
self.country_combobox.setEnabled(False)
|
||||
self.hide_buttons()
|
||||
self.connection_status_label.setText(
|
||||
strings._("gui_autoconnect_trying_to_connect_to_tor")
|
||||
)
|
||||
self.connect_clicked.emit()
|
||||
|
||||
def _try_again_clicked(self):
|
||||
self.connection_status_label.setText(
|
||||
strings._("gui_autoconnect_trying_to_connect_to_tor")
|
||||
)
|
||||
self.country_combobox.setEnabled(False)
|
||||
self.country_combobox.hide()
|
||||
self.hide_buttons()
|
||||
self.try_again_clicked.emit()
|
||||
|
||||
def _open_tor_settings(self):
|
||||
self.open_tor_settings.emit()
|
@ -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 (
|
||||
@ -39,6 +40,7 @@ from onionshare_cli.onion import (
|
||||
TorTooOldStealth,
|
||||
PortNotAvailable,
|
||||
)
|
||||
from onionshare_cli.meek import Meek
|
||||
|
||||
|
||||
class GuiCommon:
|
||||
@ -77,6 +79,9 @@ class GuiCommon:
|
||||
os.makedirs(self.events_dir, 0o700, True)
|
||||
self.events_filename = os.path.join(self.events_dir, "events")
|
||||
|
||||
# Instantiate Meek, which is used to bypass censorship
|
||||
self.meek = Meek(self.common, get_tor_paths=self.get_tor_paths)
|
||||
|
||||
self.css = self.get_css(qtapp.color_mode)
|
||||
self.color_mode = qtapp.color_mode
|
||||
|
||||
@ -116,6 +121,15 @@ class GuiCommon:
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
}""",
|
||||
"settings_subtab_bar": """
|
||||
QTabBar::tab {
|
||||
background: transparent;
|
||||
}
|
||||
QTabBar::tab:selected {
|
||||
border-bottom: 3px solid;
|
||||
border-color: #4E064F;
|
||||
padding: 3px
|
||||
}""",
|
||||
"mode_new_tab_button": """
|
||||
QPushButton {
|
||||
font-weight: bold;
|
||||
@ -149,6 +163,52 @@ 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;
|
||||
color: #000000;
|
||||
border: 1px solid #DDDBDA;
|
||||
border-radius: 8px;
|
||||
padding: 24px 16px;
|
||||
}
|
||||
QCheckBox::indicator {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}""",
|
||||
"autoconnect_countries_combobox": """
|
||||
QComboBox {
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
QComboBox:disabled {
|
||||
color: #666666;
|
||||
}
|
||||
""",
|
||||
"autoconnect_task_label": """
|
||||
QLabel {
|
||||
font-weight: bold;
|
||||
}
|
||||
""",
|
||||
"autoconnect_failed_to_connect_label": """
|
||||
QLabel {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}""",
|
||||
# Common styles between modes and their child widgets
|
||||
"mode_settings_toggle_advanced": """
|
||||
QPushButton {
|
||||
@ -508,3 +568,50 @@ class GuiCommon:
|
||||
elif type(e) is PortNotAvailable:
|
||||
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 + 16 # 16 is the padding top for the checkbox
|
||||
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()
|
||||
|
@ -23,10 +23,10 @@ import time
|
||||
from PySide2 import QtCore, QtWidgets, QtGui
|
||||
|
||||
from . import strings
|
||||
from .tor_connection import TorConnectionDialog
|
||||
from .widgets import Alert
|
||||
from .update_checker import UpdateThread
|
||||
from .connection_tab import AutoConnectTab
|
||||
from .tab_widget import TabWidget
|
||||
from .settings_tab import SettingsTab
|
||||
from .gui_common import GuiCommon
|
||||
from .threads import OnionCleanupThread
|
||||
|
||||
@ -36,6 +36,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
MainWindow is the OnionShare main window, which contains the GUI elements, including all open tabs
|
||||
"""
|
||||
|
||||
window_resized = QtCore.Signal()
|
||||
|
||||
def __init__(self, common, filenames):
|
||||
super(MainWindow, self).__init__()
|
||||
|
||||
@ -53,7 +55,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.settings_action = menu.addAction(strings._("gui_settings_window_title"))
|
||||
self.settings_action.triggered.connect(self.open_settings)
|
||||
self.help_action = menu.addAction(strings._("gui_settings_button_help"))
|
||||
self.help_action.triggered.connect(lambda: SettingsDialog.help_clicked(self))
|
||||
self.help_action.triggered.connect(lambda: SettingsTab.open_help())
|
||||
exit_action = menu.addAction(strings._("systray_menu_exit"))
|
||||
exit_action.triggered.connect(self.close)
|
||||
|
||||
@ -106,24 +108,6 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
)
|
||||
self.status_bar.addPermanentWidget(self.status_bar.server_status_indicator)
|
||||
|
||||
# Tor settings button
|
||||
self.tor_settings_button = QtWidgets.QPushButton()
|
||||
self.tor_settings_button.setDefault(False)
|
||||
self.tor_settings_button.setFixedSize(40, 50)
|
||||
self.tor_settings_button.setIcon(
|
||||
QtGui.QIcon(
|
||||
GuiCommon.get_resource_path(
|
||||
"images/{}_tor_settings.png".format(self.common.gui.color_mode)
|
||||
)
|
||||
)
|
||||
)
|
||||
self.tor_settings_button.clicked.connect(self.open_tor_settings)
|
||||
self.tor_settings_button.setStyleSheet(self.common.gui.css["settings_button"])
|
||||
self.status_bar.addPermanentWidget(self.tor_settings_button)
|
||||
|
||||
if os.environ.get("ONIONSHARE_HIDE_TOR_SETTINGS") == "1":
|
||||
self.tor_settings_button.hide()
|
||||
|
||||
# Settings button
|
||||
self.settings_button = QtWidgets.QPushButton()
|
||||
self.settings_button.setDefault(False)
|
||||
@ -140,13 +124,16 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.status_bar.addPermanentWidget(self.settings_button)
|
||||
|
||||
# Tabs
|
||||
self.tabs = TabWidget(self.common, self.system_tray, self.status_bar)
|
||||
self.tabs = TabWidget(self.common, self.system_tray, self.status_bar, self)
|
||||
self.tabs.bring_to_front.connect(self.bring_to_front)
|
||||
|
||||
# If we have saved persistent tabs, try opening those
|
||||
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,18 +147,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.success.connect(self.tabs.tor_is_connected)
|
||||
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()
|
||||
@ -186,6 +161,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,15 +224,22 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
"""
|
||||
Open the TorSettingsTab
|
||||
"""
|
||||
self.common.log("MainWindow", "open_tor_settings")
|
||||
self.tabs.open_tor_settings_tab()
|
||||
self._open_settings(active_tab="tor")
|
||||
|
||||
def open_settings(self):
|
||||
"""
|
||||
Open the SettingsTab
|
||||
Open the general SettingsTab
|
||||
"""
|
||||
self.common.log("MainWindow", "open_settings")
|
||||
self.tabs.open_settings_tab()
|
||||
self._open_settings(active_tab="general")
|
||||
|
||||
def _open_settings(self, active_tab):
|
||||
self.common.log("MainWindow", f"open settings with active tab: {active_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_settings_tab(from_autoconnect, active_tab=active_tab)
|
||||
|
||||
def settings_have_changed(self):
|
||||
self.common.log("OnionShareGui", "settings_have_changed")
|
||||
@ -267,25 +252,6 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
tab = self.tabs.widget(index)
|
||||
tab.settings_have_changed()
|
||||
|
||||
def check_for_updates(self):
|
||||
"""
|
||||
Check for updates in a new thread, if enabled.
|
||||
"""
|
||||
if self.common.platform == "Windows" or self.common.platform == "Darwin":
|
||||
if self.common.settings.get("use_autoupdate"):
|
||||
|
||||
def update_available(update_url, installed_version, latest_version):
|
||||
Alert(
|
||||
self.common,
|
||||
strings._("update_available").format(
|
||||
update_url, installed_version, latest_version
|
||||
),
|
||||
)
|
||||
|
||||
self.update_thread = UpdateThread(self.common, self.common.gui.onion)
|
||||
self.update_thread.update_available.connect(update_available)
|
||||
self.update_thread.start()
|
||||
|
||||
def bring_to_front(self):
|
||||
self.common.log("MainWindow", "bring_to_front")
|
||||
self.raise_()
|
||||
@ -355,3 +321,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
|
||||
# Wait 1 second for threads to close gracefully, so tests finally pass
|
||||
time.sleep(1)
|
||||
|
||||
def resizeEvent(self, event):
|
||||
self.window_resized.emit()
|
||||
return super(MainWindow, self).resizeEvent(event)
|
1
desktop/onionshare/resources/countries/ar.json
Normal file
1
desktop/onionshare/resources/countries/ar.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/bn.json
Normal file
1
desktop/onionshare/resources/countries/bn.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/de.json
Normal file
1
desktop/onionshare/resources/countries/de.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/el.json
Normal file
1
desktop/onionshare/resources/countries/el.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/en.json
Normal file
1
desktop/onionshare/resources/countries/en.json
Normal file
@ -0,0 +1 @@
|
||||
{"AF": "Afghanistan", "AX": "\u00c5land Islands", "AL": "Albania", "DZ": "Algeria", "AS": "American Samoa", "AD": "Andorra", "AO": "Angola", "AI": "Anguilla", "AQ": "Antarctica", "AG": "Antigua & Barbuda", "AR": "Argentina", "AM": "Armenia", "AW": "Aruba", "AU": "Australia", "AT": "Austria", "AZ": "Azerbaijan", "BS": "Bahamas", "BH": "Bahrain", "BD": "Bangladesh", "BB": "Barbados", "BY": "Belarus", "BE": "Belgium", "BZ": "Belize", "BJ": "Benin", "BM": "Bermuda", "BT": "Bhutan", "BO": "Bolivia", "BA": "Bosnia & Herzegovina", "BW": "Botswana", "BV": "Bouvet Island", "BR": "Brazil", "IO": "British Indian Ocean Territory", "VG": "British Virgin Islands", "BN": "Brunei", "BG": "Bulgaria", "BF": "Burkina Faso", "BI": "Burundi", "KH": "Cambodia", "CM": "Cameroon", "CA": "Canada", "CV": "Cape Verde", "BQ": "Caribbean Netherlands", "KY": "Cayman Islands", "CF": "Central African Republic", "TD": "Chad", "CL": "Chile", "CN": "China", "CX": "Christmas Island", "CC": "Cocos (Keeling) Islands", "CO": "Colombia", "KM": "Comoros", "CG": "Congo - Brazzaville", "CD": "Congo - Kinshasa", "CK": "Cook Islands", "CR": "Costa Rica", "CI": "C\u00f4te d\u2019Ivoire", "HR": "Croatia", "CU": "Cuba", "CW": "Cura\u00e7ao", "CY": "Cyprus", "CZ": "Czechia", "DK": "Denmark", "DJ": "Djibouti", "DM": "Dominica", "DO": "Dominican Republic", "EC": "Ecuador", "EG": "Egypt", "SV": "El Salvador", "GQ": "Equatorial Guinea", "ER": "Eritrea", "EE": "Estonia", "SZ": "Eswatini", "ET": "Ethiopia", "FK": "Falkland Islands", "FO": "Faroe Islands", "FJ": "Fiji", "FI": "Finland", "FR": "France", "GF": "French Guiana", "PF": "French Polynesia", "TF": "French Southern Territories", "GA": "Gabon", "GM": "Gambia", "GE": "Georgia", "DE": "Germany", "GH": "Ghana", "GI": "Gibraltar", "GR": "Greece", "GL": "Greenland", "GD": "Grenada", "GP": "Guadeloupe", "GU": "Guam", "GT": "Guatemala", "GG": "Guernsey", "GN": "Guinea", "GW": "Guinea-Bissau", "GY": "Guyana", "HT": "Haiti", "HM": "Heard & McDonald Islands", "HN": "Honduras", "HK": "Hong Kong SAR China", "HU": "Hungary", "IS": "Iceland", "IN": "India", "ID": "Indonesia", "IR": "Iran", "IQ": "Iraq", "IE": "Ireland", "IM": "Isle of Man", "IL": "Israel", "IT": "Italy", "JM": "Jamaica", "JP": "Japan", "JO": "Jordan", "KZ": "Kazakhstan", "KE": "Kenya", "KI": "Kiribati", "KW": "Kuwait", "KG": "Kyrgyzstan", "LA": "Laos", "LV": "Latvia", "LB": "Lebanon", "LS": "Lesotho", "LR": "Liberia", "LY": "Libya", "LI": "Liechtenstein", "LT": "Lithuania", "LU": "Luxembourg", "MO": "Macao SAR China", "MG": "Madagascar", "MW": "Malawi", "MY": "Malaysia", "MV": "Maldives", "ML": "Mali", "MT": "Malta", "MQ": "Martinique", "MR": "Mauritania", "MU": "Mauritius", "YT": "Mayotte", "MX": "Mexico", "MD": "Moldova", "MC": "Monaco", "MN": "Mongolia", "ME": "Montenegro", "MS": "Montserrat", "MA": "Morocco", "MZ": "Mozambique", "MM": "Myanmar (Burma)", "NA": "Namibia", "NR": "Nauru", "NP": "Nepal", "NL": "Netherlands", "NC": "New Caledonia", "NZ": "New Zealand", "NI": "Nicaragua", "NE": "Niger", "NG": "Nigeria", "NU": "Niue", "NF": "Norfolk Island", "KP": "North Korea", "MK": "North Macedonia", "NO": "Norway", "OM": "Oman", "PK": "Pakistan", "PW": "Palau", "PA": "Panama", "PG": "Papua New Guinea", "PY": "Paraguay", "PE": "Peru", "PH": "Philippines", "PN": "Pitcairn Islands", "PL": "Poland", "PT": "Portugal", "PR": "Puerto Rico", "QA": "Qatar", "RE": "R\u00e9union", "RO": "Romania", "RU": "Russia", "RW": "Rwanda", "WS": "Samoa", "SM": "San Marino", "ST": "S\u00e3o Tom\u00e9 & Pr\u00edncipe", "SA": "Saudi Arabia", "SN": "Senegal", "RS": "Serbia", "SC": "Seychelles", "SL": "Sierra Leone", "SG": "Singapore", "SX": "Sint Maarten", "SK": "Slovakia", "SI": "Slovenia", "SB": "Solomon Islands", "SO": "Somalia", "ZA": "South Africa", "GS": "South Georgia & South Sandwich Islands", "KR": "South Korea", "SS": "South Sudan", "ES": "Spain", "LK": "Sri Lanka", "BL": "St. Barth\u00e9lemy", "SH": "St. Helena", "KN": "St. Kitts & Nevis", "LC": "St. Lucia", "MF": "St. Martin", "PM": "St. Pierre & Miquelon", "VC": "St. Vincent & Grenadines", "SD": "Sudan", "SR": "Suriname", "SJ": "Svalbard & Jan Mayen", "SE": "Sweden", "CH": "Switzerland", "SY": "Syria", "TW": "Taiwan", "TJ": "Tajikistan", "TZ": "Tanzania", "TH": "Thailand", "TL": "Timor-Leste", "TG": "Togo", "TK": "Tokelau", "TO": "Tonga", "TT": "Trinidad & Tobago", "TN": "Tunisia", "TR": "Turkey", "TM": "Turkmenistan", "TC": "Turks & Caicos Islands", "VI": "U.S. Virgin Islands", "UG": "Uganda", "UA": "Ukraine", "AE": "United Arab Emirates", "GB": "United Kingdom", "US": "United States", "UY": "Uruguay", "UZ": "Uzbekistan", "VU": "Vanuatu", "VA": "Vatican City", "VE": "Venezuela", "VN": "Vietnam", "WF": "Wallis & Futuna", "EH": "Western Sahara", "YE": "Yemen", "ZM": "Zambia", "ZW": "Zimbabwe"}
|
1
desktop/onionshare/resources/countries/es.json
Normal file
1
desktop/onionshare/resources/countries/es.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/fi.json
Normal file
1
desktop/onionshare/resources/countries/fi.json
Normal file
@ -0,0 +1 @@
|
||||
{"AF": "Afganistan", "AX": "Ahvenanmaa", "NL": "Alankomaat", "AL": "Albania", "DZ": "Algeria", "AS": "Amerikan Samoa", "AD": "Andorra", "AO": "Angola", "AI": "Anguilla", "AQ": "Antarktis", "AG": "Antigua ja Barbuda", "AE": "Arabiemiirikunnat", "AR": "Argentiina", "AM": "Armenia", "AW": "Aruba", "AU": "Australia", "AZ": "Azerbaid\u017ean", "BS": "Bahama", "BH": "Bahrain", "BD": "Bangladesh", "BB": "Barbados", "BE": "Belgia", "BZ": "Belize", "BJ": "Benin", "BM": "Bermuda", "BT": "Bhutan", "BO": "Bolivia", "BA": "Bosnia ja Hertsegovina", "BW": "Botswana", "BV": "Bouvet\u2019nsaari", "BR": "Brasilia", "IO": "Brittil\u00e4inen Intian valtameren alue", "VG": "Brittil\u00e4iset Neitsytsaaret", "BN": "Brunei", "BG": "Bulgaria", "BF": "Burkina Faso", "BI": "Burundi", "KY": "Caymansaaret", "CL": "Chile", "CK": "Cookinsaaret", "CR": "Costa Rica", "CW": "Cura\u00e7ao", "DJ": "Djibouti", "DM": "Dominica", "DO": "Dominikaaninen tasavalta", "EC": "Ecuador", "EG": "Egypti", "SV": "El Salvador", "ER": "Eritrea", "ES": "Espanja", "ZA": "Etel\u00e4-Afrikka", "GS": "Etel\u00e4-Georgia ja Etel\u00e4iset Sandwichsaaret", "KR": "Etel\u00e4-Korea", "SS": "Etel\u00e4-Sudan", "ET": "Etiopia", "FK": "Falklandinsaaret", "FJ": "Fid\u017ei", "PH": "Filippiinit", "FO": "F\u00e4rsaaret", "GA": "Gabon", "GM": "Gambia", "GE": "Georgia", "GH": "Ghana", "GI": "Gibraltar", "GD": "Grenada", "GL": "Gr\u00f6nlanti", "GP": "Guadeloupe", "GU": "Guam", "GT": "Guatemala", "GG": "Guernsey", "GN": "Guinea", "GW": "Guinea-Bissau", "GY": "Guyana", "HT": "Haiti", "HM": "Heard ja McDonaldinsaaret", "HN": "Honduras", "HK": "Hongkong \u2013 Kiinan e.h.a.", "SJ": "Huippuvuoret ja Jan Mayen", "ID": "Indonesia", "IN": "Intia", "IQ": "Irak", "IR": "Iran", "IE": "Irlanti", "IS": "Islanti", "GB": "Iso-Britannia", "IL": "Israel", "IT": "Italia", "TL": "It\u00e4-Timor", "AT": "It\u00e4valta", "JM": "Jamaika", "JP": "Japani", "YE": "Jemen", "JO": "Jordania", "CX": "Joulusaari", "KH": "Kambod\u017ea", "CM": "Kamerun", "CA": "Kanada", "CV": "Kap Verde", "BQ": "Karibian Alankomaat", "KZ": "Kazakstan", "KE": "Kenia", "CF": "Keski-Afrikan tasavalta", "CN": "Kiina", "KG": "Kirgisia", "KI": "Kiribati", "CO": "Kolumbia", "KM": "Komorit", "CD": "Kongon demokraattinen tasavalta", "CG": "Kongon tasavalta", "CC": "Kookossaaret (Keelingsaaret)", "GR": "Kreikka", "HR": "Kroatia", "CU": "Kuuba", "KW": "Kuwait", "CY": "Kypros", "LA": "Laos", "LV": "Latvia", "LS": "Lesotho", "LB": "Libanon", "LR": "Liberia", "LY": "Libya", "LI": "Liechtenstein", "LT": "Liettua", "LU": "Luxemburg", "EH": "L\u00e4nsi-Sahara", "MO": "Macao \u2013 Kiinan e.h.a.", "MG": "Madagaskar", "MW": "Malawi", "MV": "Malediivit", "MY": "Malesia", "ML": "Mali", "MT": "Malta", "IM": "Mansaari", "MA": "Marokko", "MQ": "Martinique", "MR": "Mauritania", "MU": "Mauritius", "YT": "Mayotte", "MX": "Meksiko", "MD": "Moldova", "MC": "Monaco", "MN": "Mongolia", "ME": "Montenegro", "MS": "Montserrat", "MZ": "Mosambik", "MM": "Myanmar (Burma)", "NA": "Namibia", "NR": "Nauru", "NP": "Nepal", "NI": "Nicaragua", "NE": "Niger", "NG": "Nigeria", "NU": "Niue", "NF": "Norfolkinsaari", "NO": "Norja", "CI": "Norsunluurannikko", "OM": "Oman", "PK": "Pakistan", "PW": "Palau", "PA": "Panama", "PG": "Papua-Uusi-Guinea", "PY": "Paraguay", "PE": "Peru", "PN": "Pitcairn", "KP": "Pohjois-Korea", "MK": "Pohjois-Makedonia", "PT": "Portugali", "PR": "Puerto Rico", "PL": "Puola", "GQ": "P\u00e4iv\u00e4ntasaajan Guinea", "QA": "Qatar", "FR": "Ranska", "TF": "Ranskan etel\u00e4iset alueet", "GF": "Ranskan Guayana", "PF": "Ranskan Polynesia", "RE": "R\u00e9union", "RO": "Romania", "RW": "Ruanda", "SE": "Ruotsi", "SH": "Saint Helena", "KN": "Saint Kitts ja Nevis", "LC": "Saint Lucia", "VC": "Saint Vincent ja Grenadiinit", "BL": "Saint-Barth\u00e9lemy", "MF": "Saint-Martin", "PM": "Saint-Pierre ja Miquelon", "DE": "Saksa", "SB": "Salomonsaaret", "ZM": "Sambia", "WS": "Samoa", "SM": "San Marino", "ST": "S\u00e3o Tom\u00e9 ja Pr\u00edncipe", "SA": "Saudi-Arabia", "SN": "Senegal", "RS": "Serbia", "SC": "Seychellit", "SL": "Sierra Leone", "SG": "Singapore", "SX": "Sint Maarten", "SK": "Slovakia", "SI": "Slovenia", "SO": "Somalia", "LK": "Sri Lanka", "SD": "Sudan", "FI": "Suomi", "SR": "Suriname", "CH": "Sveitsi", "SZ": "Swazimaa", "SY": "Syyria", "TJ": "Tad\u017eikistan", "TW": "Taiwan", "TZ": "Tansania", "DK": "Tanska", "TH": "Thaimaa", "TG": "Togo", "TK": "Tokelau", "TO": "Tonga", "TT": "Trinidad ja Tobago", "TD": "T\u0161ad", "CZ": "T\u0161ekki", "TN": "Tunisia", "TR": "Turkki", "TM": "Turkmenistan", "TC": "Turks- ja Caicossaaret", "UG": "Uganda", "UA": "Ukraina", "HU": "Unkari", "UY": "Uruguay", "NC": "Uusi-Kaledonia", "NZ": "Uusi-Seelanti", "UZ": "Uzbekistan", "BY": "Valko-Ven\u00e4j\u00e4", "VU": "Vanuatu", "VA": "Vatikaani", "VE": "Venezuela", "RU": "Ven\u00e4j\u00e4", "VN": "Vietnam", "EE": "Viro", "WF": "Wallis ja Futuna", "US": "Yhdysvallat", "VI": "Yhdysvaltain Neitsytsaaret", "ZW": "Zimbabwe"}
|
1
desktop/onionshare/resources/countries/fr.json
Normal file
1
desktop/onionshare/resources/countries/fr.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/gl.json
Normal file
1
desktop/onionshare/resources/countries/gl.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/is.json
Normal file
1
desktop/onionshare/resources/countries/is.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/ja.json
Normal file
1
desktop/onionshare/resources/countries/ja.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/lt.json
Normal file
1
desktop/onionshare/resources/countries/lt.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/nb_NO.json
Normal file
1
desktop/onionshare/resources/countries/nb_NO.json
Normal file
@ -0,0 +1 @@
|
||||
{"AF": "Afghanistan", "AL": "Albania", "DZ": "Algerie", "AS": "Amerikansk Samoa", "AD": "Andorra", "AO": "Angola", "AI": "Anguilla", "AQ": "Antarktis", "AG": "Antigua og Barbuda", "AR": "Argentina", "AM": "Armenia", "AW": "Aruba", "AZ": "Aserbajdsjan", "AU": "Australia", "BS": "Bahamas", "BH": "Bahrain", "BD": "Bangladesh", "BB": "Barbados", "BE": "Belgia", "BZ": "Belize", "BJ": "Benin", "BM": "Bermuda", "BT": "Bhutan", "BO": "Bolivia", "BA": "Bosnia-Hercegovina", "BW": "Botswana", "BV": "Bouvet\u00f8ya", "BR": "Brasil", "BN": "Brunei", "BG": "Bulgaria", "BF": "Burkina Faso", "BI": "Burundi", "CA": "Canada", "KY": "Cayman\u00f8yene", "CL": "Chile", "CX": "Christmas\u00f8ya", "CO": "Colombia", "CK": "Cook\u00f8yene", "CR": "Costa Rica", "CU": "Cuba", "CW": "Cura\u00e7ao", "DK": "Danmark", "VI": "De amerikanske jomfru\u00f8yene", "VG": "De britiske jomfru\u00f8yene", "AE": "De forente arabiske emirater", "TF": "De franske s\u00f8rterritorier", "DO": "Den dominikanske republikk", "CF": "Den sentralafrikanske republikk", "IO": "Det britiske territoriet i Indiahavet", "DJ": "Djibouti", "DM": "Dominica", "EC": "Ecuador", "EG": "Egypt", "GQ": "Ekvatorial-Guinea", "SV": "El Salvador", "CI": "Elfenbenskysten", "ER": "Eritrea", "EE": "Estland", "SZ": "Eswatini", "ET": "Etiopia", "FK": "Falklands\u00f8yene", "FJ": "Fiji", "PH": "Filippinene", "FI": "Finland", "FR": "Frankrike", "GF": "Fransk Guyana", "PF": "Fransk Polynesia", "FO": "F\u00e6r\u00f8yene", "GA": "Gabon", "GM": "Gambia", "GE": "Georgia", "GH": "Ghana", "GI": "Gibraltar", "GD": "Grenada", "GL": "Gr\u00f8nland", "GP": "Guadeloupe", "GU": "Guam", "GT": "Guatemala", "GG": "Guernsey", "GN": "Guinea", "GW": "Guinea-Bissau", "GY": "Guyana", "HT": "Haiti", "HM": "Heard- og McDonald\u00f8yene", "GR": "Hellas", "HN": "Honduras", "HK": "Hongkong S.A.R. Kina", "BY": "Hviterussland", "IN": "India", "ID": "Indonesia", "IQ": "Irak", "IR": "Iran", "IE": "Irland", "IS": "Island", "IL": "Israel", "IT": "Italia", "JM": "Jamaica", "JP": "Japan", "YE": "Jemen", "JO": "Jordan", "KH": "Kambodsja", "CM": "Kamerun", "CV": "Kapp Verde", "BQ": "Karibisk Nederland", "KZ": "Kasakhstan", "KE": "Kenya", "CN": "Kina", "KG": "Kirgisistan", "KI": "Kiribati", "CC": "Kokos\u00f8yene", "KM": "Komorene", "CG": "Kongo-Brazzaville", "CD": "Kongo-Kinshasa", "HR": "Kroatia", "KW": "Kuwait", "CY": "Kypros", "LA": "Laos", "LV": "Latvia", "LS": "Lesotho", "LB": "Libanon", "LR": "Liberia", "LY": "Libya", "LI": "Liechtenstein", "LT": "Litauen", "LU": "Luxemburg", "MO": "Macao S.A.R. Kina", "MG": "Madagaskar", "MW": "Malawi", "MY": "Malaysia", "MV": "Maldivene", "ML": "Mali", "MT": "Malta", "IM": "Man", "MA": "Marokko", "MQ": "Martinique", "MR": "Mauritania", "MU": "Mauritius", "YT": "Mayotte", "MX": "Mexico", "MD": "Moldova", "MC": "Monaco", "MN": "Mongolia", "ME": "Montenegro", "MS": "Montserrat", "MZ": "Mosambik", "MM": "Myanmar (Burma)", "NA": "Namibia", "NR": "Nauru", "NL": "Nederland", "NP": "Nepal", "NZ": "New Zealand", "NI": "Nicaragua", "NE": "Niger", "NG": "Nigeria", "NU": "Niue", "KP": "Nord-Korea", "MK": "Nord-Makedonia", "NF": "Norfolk\u00f8ya", "NO": "Norge", "NC": "Ny-Caledonia", "OM": "Oman", "PK": "Pakistan", "PW": "Palau", "PA": "Panama", "PG": "Papua Ny-Guinea", "PY": "Paraguay", "PE": "Peru", "PN": "Pitcairn\u00f8yene", "PL": "Polen", "PT": "Portugal", "PR": "Puerto Rico", "QA": "Qatar", "RE": "R\u00e9union", "RO": "Romania", "RU": "Russland", "RW": "Rwanda", "KN": "Saint Kitts og Nevis", "BL": "Saint-Barth\u00e9lemy", "MF": "Saint-Martin", "PM": "Saint-Pierre-et-Miquelon", "SB": "Salomon\u00f8yene", "WS": "Samoa", "SM": "San Marino", "ST": "S\u00e3o Tom\u00e9 og Pr\u00edncipe", "SA": "Saudi-Arabia", "SN": "Senegal", "RS": "Serbia", "SC": "Seychellene", "SL": "Sierra Leone", "SG": "Singapore", "SX": "Sint Maarten", "SK": "Slovakia", "SI": "Slovenia", "SO": "Somalia", "ES": "Spania", "LK": "Sri Lanka", "SH": "St. Helena", "LC": "St. Lucia", "VC": "St. Vincent og Grenadinene", "GB": "Storbritannia", "SD": "Sudan", "SR": "Surinam", "SJ": "Svalbard og Jan Mayen", "CH": "Sveits", "SE": "Sverige", "SY": "Syria", "ZA": "S\u00f8r-Afrika", "GS": "S\u00f8r-Georgia og S\u00f8r-Sandwich\u00f8yene", "KR": "S\u00f8r-Korea", "SS": "S\u00f8r-Sudan", "TJ": "Tadsjikistan", "TW": "Taiwan", "TZ": "Tanzania", "TH": "Thailand", "TG": "Togo", "TK": "Tokelau", "TO": "Tonga", "TT": "Trinidad og Tobago", "TD": "Tsjad", "CZ": "Tsjekkia", "TN": "Tunisia", "TM": "Turkmenistan", "TC": "Turks- og Caicos\u00f8yene", "TR": "Tyrkia", "DE": "Tyskland", "UG": "Uganda", "UA": "Ukraina", "HU": "Ungarn", "UY": "Uruguay", "US": "USA", "UZ": "Usbekistan", "VU": "Vanuatu", "VA": "Vatikanstaten", "VE": "Venezuela", "EH": "Vest-Sahara", "VN": "Vietnam", "WF": "Wallis og Futuna", "ZM": "Zambia", "ZW": "Zimbabwe", "TL": "\u00d8st-Timor", "AT": "\u00d8sterrike", "AX": "\u00c5land"}
|
1
desktop/onionshare/resources/countries/pl.json
Normal file
1
desktop/onionshare/resources/countries/pl.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/pt_BR.json
Normal file
1
desktop/onionshare/resources/countries/pt_BR.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/pt_PT.json
Normal file
1
desktop/onionshare/resources/countries/pt_PT.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/ru.json
Normal file
1
desktop/onionshare/resources/countries/ru.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/sv.json
Normal file
1
desktop/onionshare/resources/countries/sv.json
Normal file
@ -0,0 +1 @@
|
||||
{"AF": "Afghanistan", "AL": "Albanien", "DZ": "Algeriet", "VI": "Amerikanska Jungfru\u00f6arna", "AS": "Amerikanska Samoa", "AD": "Andorra", "AO": "Angola", "AI": "Anguilla", "AQ": "Antarktis", "AG": "Antigua och Barbuda", "AR": "Argentina", "AM": "Armenien", "AW": "Aruba", "AU": "Australien", "AZ": "Azerbajdzjan", "BS": "Bahamas", "BH": "Bahrain", "BD": "Bangladesh", "BB": "Barbados", "BE": "Belgien", "BZ": "Belize", "BJ": "Benin", "BM": "Bermuda", "BT": "Bhutan", "BO": "Bolivia", "BA": "Bosnien och Hercegovina", "BW": "Botswana", "BV": "Bouvet\u00f6n", "BR": "Brasilien", "VG": "Brittiska Jungfru\u00f6arna", "IO": "Brittiska territoriet i Indiska oceanen", "BN": "Brunei", "BG": "Bulgarien", "BF": "Burkina Faso", "BI": "Burundi", "KY": "Cayman\u00f6arna", "CF": "Centralafrikanska republiken", "CL": "Chile", "CO": "Colombia", "CK": "Cook\u00f6arna", "CR": "Costa Rica", "CW": "Cura\u00e7ao", "CY": "Cypern", "CI": "C\u00f4te d\u2019Ivoire", "DK": "Danmark", "DJ": "Djibouti", "DM": "Dominica", "DO": "Dominikanska republiken", "EC": "Ecuador", "EG": "Egypten", "GQ": "Ekvatorialguinea", "SV": "El Salvador", "ER": "Eritrea", "EE": "Estland", "ET": "Etiopien", "FK": "Falklands\u00f6arna", "FJ": "Fiji", "PH": "Filippinerna", "FI": "Finland", "FR": "Frankrike", "GF": "Franska Guyana", "PF": "Franska Polynesien", "TF": "Franska sydterritorierna", "FO": "F\u00e4r\u00f6arna", "AE": "F\u00f6renade Arabemiraten", "GA": "Gabon", "GM": "Gambia", "GE": "Georgien", "GH": "Ghana", "GI": "Gibraltar", "GR": "Grekland", "GD": "Grenada", "GL": "Gr\u00f6nland", "GP": "Guadeloupe", "GU": "Guam", "GT": "Guatemala", "GG": "Guernsey", "GN": "Guinea", "GW": "Guinea-Bissau", "GY": "Guyana", "HT": "Haiti", "HM": "Heard\u00f6n och McDonald\u00f6arna", "HN": "Honduras", "HK": "Hongkong", "IN": "Indien", "ID": "Indonesien", "IQ": "Irak", "IR": "Iran", "IE": "Irland", "IS": "Island", "IM": "Isle of Man", "IL": "Israel", "IT": "Italien", "JM": "Jamaica", "JP": "Japan", "YE": "Jemen", "JO": "Jordanien", "CX": "Jul\u00f6n", "KH": "Kambodja", "CM": "Kamerun", "CA": "Kanada", "CV": "Kap Verde", "BQ": "Karibiska Nederl\u00e4nderna", "KZ": "Kazakstan", "KE": "Kenya", "CN": "Kina", "KG": "Kirgizistan", "KI": "Kiribati", "CC": "Kokos\u00f6arna", "KM": "Komorerna", "CG": "Kongo-Brazzaville", "CD": "Kongo-Kinshasa", "HR": "Kroatien", "CU": "Kuba", "KW": "Kuwait", "LA": "Laos", "LS": "Lesotho", "LV": "Lettland", "LB": "Libanon", "LR": "Liberia", "LY": "Libyen", "LI": "Liechtenstein", "LT": "Litauen", "LU": "Luxemburg", "MO": "Macao", "MG": "Madagaskar", "MW": "Malawi", "MY": "Malaysia", "MV": "Maldiverna", "ML": "Mali", "MT": "Malta", "MA": "Marocko", "MQ": "Martinique", "MR": "Mauretanien", "MU": "Mauritius", "YT": "Mayotte", "MX": "Mexiko", "MZ": "Mo\u00e7ambique", "MD": "Moldavien", "MC": "Monaco", "MN": "Mongoliet", "ME": "Montenegro", "MS": "Montserrat", "MM": "Myanmar (Burma)", "NA": "Namibia", "NR": "Nauru", "NL": "Nederl\u00e4nderna", "NP": "Nepal", "NI": "Nicaragua", "NE": "Niger", "NG": "Nigeria", "NU": "Niue", "KP": "Nordkorea", "MK": "Nordmakedonien", "NF": "Norfolk\u00f6n", "NO": "Norge", "NC": "Nya Kaledonien", "NZ": "Nya Zeeland", "OM": "Oman", "PK": "Pakistan", "PW": "Palau", "PA": "Panama", "PG": "Papua Nya Guinea", "PY": "Paraguay", "PE": "Peru", "PN": "Pitcairn\u00f6arna", "PL": "Polen", "PT": "Portugal", "PR": "Puerto Rico", "QA": "Qatar", "RE": "R\u00e9union", "RO": "Rum\u00e4nien", "RW": "Rwanda", "RU": "Ryssland", "BL": "S:t Barth\u00e9lemy", "SH": "S:t Helena", "KN": "S:t Kitts och Nevis", "LC": "S:t Lucia", "PM": "S:t Pierre och Miquelon", "VC": "S:t Vincent och Grenadinerna", "MF": "Saint-Martin", "SB": "Salomon\u00f6arna", "WS": "Samoa", "SM": "San Marino", "ST": "S\u00e3o Tom\u00e9 och Pr\u00edncipe", "SA": "Saudiarabien", "CH": "Schweiz", "SN": "Senegal", "RS": "Serbien", "SC": "Seychellerna", "SL": "Sierra Leone", "SG": "Singapore", "SX": "Sint Maarten", "SK": "Slovakien", "SI": "Slovenien", "SO": "Somalia", "ES": "Spanien", "LK": "Sri Lanka", "GB": "Storbritannien", "SD": "Sudan", "SR": "Surinam", "SJ": "Svalbard och Jan Mayen", "SE": "Sverige", "SZ": "Swaziland", "ZA": "Sydafrika", "GS": "Sydgeorgien och Sydsandwich\u00f6arna", "KR": "Sydkorea", "SS": "Sydsudan", "SY": "Syrien", "TJ": "Tadzjikistan", "TW": "Taiwan", "TZ": "Tanzania", "TD": "Tchad", "TH": "Thailand", "CZ": "Tjeckien", "TG": "Togo", "TK": "Tokelau", "TO": "Tonga", "TT": "Trinidad och Tobago", "TN": "Tunisien", "TR": "Turkiet", "TM": "Turkmenistan", "TC": "Turks- och Caicos\u00f6arna", "DE": "Tyskland", "UG": "Uganda", "UA": "Ukraina", "HU": "Ungern", "UY": "Uruguay", "US": "USA", "UZ": "Uzbekistan", "VU": "Vanuatu", "VA": "Vatikanstaten", "VE": "Venezuela", "VN": "Vietnam", "BY": "Vitryssland", "EH": "V\u00e4stsahara", "WF": "Wallis- och Futuna\u00f6arna", "ZM": "Zambia", "ZW": "Zimbabwe", "AX": "\u00c5land", "AT": "\u00d6sterrike", "TL": "\u00d6sttimor"}
|
1
desktop/onionshare/resources/countries/tr.json
Normal file
1
desktop/onionshare/resources/countries/tr.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/uk.json
Normal file
1
desktop/onionshare/resources/countries/uk.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/zh_Hans.json
Normal file
1
desktop/onionshare/resources/countries/zh_Hans.json
Normal file
File diff suppressed because one or more lines are too long
BIN
desktop/onionshare/resources/images/dark_logo_text_bg.png
Normal file
BIN
desktop/onionshare/resources/images/dark_logo_text_bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.7 KiB |
BIN
desktop/onionshare/resources/images/light_logo_text_bg.png
Normal file
BIN
desktop/onionshare/resources/images/light_logo_text_bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
desktop/onionshare/resources/images/tor-connect-ship.png
Normal file
BIN
desktop/onionshare/resources/images/tor-connect-ship.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
BIN
desktop/onionshare/resources/images/tor-connect-smoke.png
Normal file
BIN
desktop/onionshare/resources/images/tor-connect-smoke.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
BIN
desktop/onionshare/resources/images/tor-connect-stars.png
Normal file
BIN
desktop/onionshare/resources/images/tor-connect-stars.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
@ -42,7 +42,25 @@
|
||||
"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": "Connect to Tor automatically",
|
||||
"gui_autoconnect_failed_to_connect_to_tor": "Failed to Connect to Tor",
|
||||
"gui_autoconnect_trying_to_connect_to_tor": "Trying to Connect to Tor...",
|
||||
"gui_autoconnect_bridge_description": "Are you connected to the internet?<br><br>It's also possible that your internet is being censored. You might be able to bypass this using a bridge.",
|
||||
"gui_autoconnect_bridge_detect_automatic": "Automatically determine my country from my IP address",
|
||||
"gui_autoconnect_bridge_detect_manual": "Manually select my country",
|
||||
"gui_autoconnect_start": "Connect to Tor",
|
||||
"gui_autoconnect_configure": "Network Settings",
|
||||
"gui_autoconnect_bridge_start": "Use a Bridge",
|
||||
"gui_autoconnect_try_again_without_a_bridge": "Try again without a Bridge",
|
||||
"gui_autoconnect_circumventing_censorship": "Trying to resolve connectivity issues",
|
||||
"gui_autoconnect_circumventing_censorship_starting_circumvention": "Starting censorship circumvention process",
|
||||
"gui_autoconnect_circumventing_censorship_starting_meek": "Starting Meek for domain-fronting",
|
||||
"gui_autoconnect_circumventing_censorship_requesting_bridges": "Requesting bridges from the Tor Censorship Circumvention API",
|
||||
"gui_autoconnect_circumventing_censorship_got_bridges": "Got bridges! Trying to reconnect to Tor",
|
||||
"gui_autoconnect_could_not_connect_to_tor_api": "Could not connect to the Tor API. Make sure you are connected to the internet before trying again.",
|
||||
"gui_settings_window_title": "Settings",
|
||||
"gui_general_settings_window_title": "General",
|
||||
"gui_settings_autoupdate_label": "Check for new version",
|
||||
"gui_settings_autoupdate_option": "Notify me when a new version is available",
|
||||
"gui_settings_autoupdate_timestamp": "Last checked: {}",
|
||||
@ -233,4 +251,4 @@
|
||||
"moat_captcha_error": "Incorrect solution. Please try again.",
|
||||
"moat_solution_empty_error": "Enter the characters from the image",
|
||||
"mode_tor_not_connected_label": "OnionShare is not connected to the Tor network"
|
||||
}
|
||||
}
|
72
desktop/onionshare/settings_parent_tab.py
Normal file
72
desktop/onionshare/settings_parent_tab.py
Normal file
@ -0,0 +1,72 @@
|
||||
from PySide2 import QtCore, QtWidgets, QtGui
|
||||
|
||||
from onionshare_cli.mode_settings import ModeSettings
|
||||
|
||||
from . import strings
|
||||
from .tab import Tab
|
||||
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 SettingsParentTab(QtWidgets.QTabWidget):
|
||||
"""
|
||||
The settings tab widget containing the tor settings
|
||||
and app settings subtabs
|
||||
"""
|
||||
|
||||
bring_to_front = QtCore.Signal()
|
||||
close_this_tab = QtCore.Signal()
|
||||
|
||||
def __init__(
|
||||
self, common, tab_id, parent=None, active_tab="general", from_autoconnect=False
|
||||
):
|
||||
super(SettingsParentTab, self).__init__()
|
||||
self.parent = parent
|
||||
self.common = common
|
||||
self.common.log("SettingsParentTab", "__init__")
|
||||
|
||||
# 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 = {
|
||||
"general": 0,
|
||||
"tor": 1,
|
||||
}
|
||||
self.tab_id = tab_id
|
||||
self.current_tab_id = self.tabs[active_tab]
|
||||
|
||||
# Use a custom tab bar
|
||||
tab_bar = TabBar(self.common)
|
||||
self.setTabBar(tab_bar)
|
||||
settings_tab = SettingsTab(self.common, self.tabs["general"], parent=self)
|
||||
self.tor_settings_tab = TorSettingsTab(
|
||||
self.common,
|
||||
self.tabs["tor"],
|
||||
self.parent.are_tabs_active(),
|
||||
self.parent.status_bar,
|
||||
parent=self,
|
||||
from_autoconnect=from_autoconnect,
|
||||
)
|
||||
self.addTab(settings_tab, strings._("gui_general_settings_window_title"))
|
||||
self.addTab(self.tor_settings_tab, strings._("gui_tor_settings_window_title"))
|
||||
|
||||
# Set up the tab widget
|
||||
self.setMovable(False)
|
||||
self.setTabsClosable(False)
|
||||
self.setUsesScrollButtons(False)
|
||||
self.setCurrentIndex(self.current_tab_id)
|
||||
|
||||
|
||||
class TabBar(QtWidgets.QTabBar):
|
||||
"""
|
||||
A custom tab bar
|
||||
"""
|
||||
|
||||
move_new_tab_button = QtCore.Signal()
|
||||
|
||||
def __init__(self, common):
|
||||
super(TabBar, self).__init__()
|
||||
self.setStyleSheet(common.gui.css["settings_subtab_bar"])
|
@ -33,9 +33,7 @@ class SettingsTab(QtWidgets.QWidget):
|
||||
Settings dialog.
|
||||
"""
|
||||
|
||||
close_this_tab = QtCore.Signal()
|
||||
|
||||
def __init__(self, common, tab_id):
|
||||
def __init__(self, common, tab_id, parent=None):
|
||||
super(SettingsTab, self).__init__()
|
||||
|
||||
self.common = common
|
||||
@ -43,6 +41,7 @@ class SettingsTab(QtWidgets.QWidget):
|
||||
|
||||
self.system = platform.system()
|
||||
self.tab_id = tab_id
|
||||
self.parent = parent
|
||||
|
||||
# Automatic updates options
|
||||
|
||||
@ -283,7 +282,7 @@ class SettingsTab(QtWidgets.QWidget):
|
||||
|
||||
# Save the new settings
|
||||
settings.save()
|
||||
self.close_this_tab.emit()
|
||||
self.parent.close_this_tab.emit()
|
||||
|
||||
def help_clicked(self):
|
||||
"""
|
||||
|
@ -26,8 +26,8 @@ from . import strings
|
||||
from .tab import Tab
|
||||
from .threads import EventHandlerThread
|
||||
from .gui_common import GuiCommon
|
||||
from .tor_settings_tab import TorSettingsTab
|
||||
from .settings_tab import SettingsTab
|
||||
from .settings_parent_tab import SettingsParentTab
|
||||
from .connection_tab import AutoConnectTab
|
||||
|
||||
|
||||
class TabWidget(QtWidgets.QTabWidget):
|
||||
@ -37,13 +37,14 @@ class TabWidget(QtWidgets.QTabWidget):
|
||||
|
||||
bring_to_front = QtCore.Signal()
|
||||
|
||||
def __init__(self, common, system_tray, status_bar):
|
||||
def __init__(self, common, system_tray, status_bar, window):
|
||||
super(TabWidget, self).__init__()
|
||||
self.common = common
|
||||
self.common.log("TabWidget", "__init__")
|
||||
|
||||
self.system_tray = system_tray
|
||||
self.status_bar = status_bar
|
||||
self.window = window
|
||||
|
||||
# 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
|
||||
@ -96,8 +97,8 @@ class TabWidget(QtWidgets.QTabWidget):
|
||||
# Clean up each tab
|
||||
for tab_id in self.tabs:
|
||||
if not (
|
||||
type(self.tabs[tab_id]) is SettingsTab
|
||||
or type(self.tabs[tab_id]) is TorSettingsTab
|
||||
type(self.tabs[tab_id]) is SettingsParentTab
|
||||
or type(self.tabs[tab_id]) is AutoConnectTab
|
||||
):
|
||||
self.tabs[tab_id].cleanup()
|
||||
|
||||
@ -136,8 +137,8 @@ class TabWidget(QtWidgets.QTabWidget):
|
||||
|
||||
# If it's Settings or Tor Settings, ignore
|
||||
if (
|
||||
type(self.tabs[tab_id]) is SettingsTab
|
||||
or type(self.tabs[tab_id]) is TorSettingsTab
|
||||
type(self.tabs[tab_id]) is SettingsParentTab
|
||||
or type(self.tabs[tab_id]) is AutoConnectTab
|
||||
):
|
||||
# Blank the server status indicator
|
||||
self.status_bar.server_status_image_label.clear()
|
||||
@ -159,8 +160,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,44 +207,51 @@ 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_settings_tab(self):
|
||||
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, self.window, 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, from_autoconnect=False, active_tab="general"):
|
||||
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:
|
||||
if type(self.tabs[tab_id]) is SettingsParentTab:
|
||||
self.setCurrentIndex(self.indexOf(self.tabs[tab_id]))
|
||||
return
|
||||
|
||||
settings_tab = SettingsTab(self.common, self.current_tab_id)
|
||||
settings_tab = SettingsParentTab(
|
||||
self.common,
|
||||
self.current_tab_id,
|
||||
active_tab=active_tab,
|
||||
parent=self,
|
||||
from_autoconnect=from_autoconnect,
|
||||
)
|
||||
settings_tab.close_this_tab.connect(self.close_settings_tab)
|
||||
self.tor_settings_tab = settings_tab.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] = 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):
|
||||
shortened_title = title
|
||||
if len(shortened_title) > 11:
|
||||
@ -279,8 +295,8 @@ class TabWidget(QtWidgets.QTabWidget):
|
||||
persistent_tabs = []
|
||||
for tab_id in self.tabs:
|
||||
if not (
|
||||
type(self.tabs[tab_id]) is SettingsTab
|
||||
or type(self.tabs[tab_id]) is TorSettingsTab
|
||||
type(self.tabs[tab_id]) is SettingsParentTab
|
||||
or type(self.tabs[tab_id]) is AutoConnectTab
|
||||
):
|
||||
tab = self.widget(self.indexOf(self.tabs[tab_id]))
|
||||
if tab.settings.get("persistent", "enabled"):
|
||||
@ -296,12 +312,12 @@ class TabWidget(QtWidgets.QTabWidget):
|
||||
tab_id = tab.tab_id
|
||||
|
||||
if (
|
||||
type(self.tabs[tab_id]) is SettingsTab
|
||||
or type(self.tabs[tab_id]) is TorSettingsTab
|
||||
type(self.tabs[tab_id]) is SettingsParentTab
|
||||
or type(self.tabs[tab_id]) is AutoConnectTab
|
||||
):
|
||||
self.common.log("TabWidget", "closing a settings tab")
|
||||
|
||||
if type(self.tabs[tab_id]) is TorSettingsTab:
|
||||
if type(self.tabs[tab_id]) is SettingsParentTab:
|
||||
self.tor_settings_tab = None
|
||||
|
||||
# Remove the tab
|
||||
@ -334,18 +350,41 @@ class TabWidget(QtWidgets.QTabWidget):
|
||||
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")
|
||||
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 SettingsTab:
|
||||
if type(self.tabs[tab_id]) is AutoConnectTab:
|
||||
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:
|
||||
def close_settings_tab(self):
|
||||
self.common.log("TabWidget", "close_settings_tab")
|
||||
for tab_id in list(self.tabs):
|
||||
if type(self.tabs[tab_id]) is AutoConnectTab:
|
||||
# If we are being returned to the AutoConnectTab, but
|
||||
# the user has fixed their Tor settings in the TorSettings
|
||||
# tab, *and* they have enabled autoconnect, then
|
||||
# we should close the AutoConnect Tab.
|
||||
if self.common.gui.onion.is_authenticated():
|
||||
self.common.log(
|
||||
"TabWidget",
|
||||
"close_settings_tab",
|
||||
"Tor is connected and we can auto-connect, so closing the tab",
|
||||
)
|
||||
index = self.indexOf(self.tabs[tab_id])
|
||||
self.close_tab(index)
|
||||
else:
|
||||
self.tabs[tab_id].reload_settings()
|
||||
self.common.log(
|
||||
"TabWidget",
|
||||
"close_settings_tab",
|
||||
"Reloading settings in case they changed in the TorSettingsTab. Not auto-connecting",
|
||||
)
|
||||
break
|
||||
# List of indices may have changed due to the above, so we loop over it again as another copy
|
||||
for tab_id in list(self.tabs):
|
||||
if type(self.tabs[tab_id]) is SettingsParentTab:
|
||||
index = self.indexOf(self.tabs[tab_id])
|
||||
self.close_tab(index)
|
||||
return
|
||||
@ -356,8 +395,8 @@ class TabWidget(QtWidgets.QTabWidget):
|
||||
"""
|
||||
for tab_id in self.tabs:
|
||||
if not (
|
||||
type(self.tabs[tab_id]) is SettingsTab
|
||||
or type(self.tabs[tab_id]) is TorSettingsTab
|
||||
type(self.tabs[tab_id]) is SettingsParentTab
|
||||
or type(self.tabs[tab_id]) is AutoConnectTab
|
||||
):
|
||||
mode = self.tabs[tab_id].get_mode()
|
||||
if mode:
|
||||
@ -379,23 +418,23 @@ class TabWidget(QtWidgets.QTabWidget):
|
||||
|
||||
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()
|
||||
if not (
|
||||
type(self.tabs[tab_id]) is SettingsParentTab
|
||||
or type(self.tabs[tab_id]) is AutoConnectTab
|
||||
):
|
||||
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()
|
||||
if not (
|
||||
type(self.tabs[tab_id]) is SettingsParentTab
|
||||
or type(self.tabs[tab_id]) is AutoConnectTab
|
||||
):
|
||||
mode = self.tabs[tab_id].get_mode()
|
||||
if mode:
|
||||
mode.tor_connection_stopped()
|
||||
|
||||
|
||||
class TabBar(QtWidgets.QTabBar):
|
||||
|
@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import time
|
||||
from PySide2 import QtCore, QtWidgets, QtGui
|
||||
from PySide2 import QtCore, QtWidgets
|
||||
|
||||
from onionshare_cli.onion import (
|
||||
BundledTorCanceled,
|
||||
@ -39,122 +39,6 @@ from onionshare_cli.onion import (
|
||||
)
|
||||
|
||||
from . import strings
|
||||
from .gui_common import GuiCommon
|
||||
from .widgets import Alert
|
||||
|
||||
|
||||
class TorConnectionDialog(QtWidgets.QProgressDialog):
|
||||
"""
|
||||
Connecting to Tor dialog.
|
||||
"""
|
||||
|
||||
open_tor_settings = QtCore.Signal()
|
||||
success = QtCore.Signal()
|
||||
|
||||
def __init__(
|
||||
self, common, custom_settings=False, testing_settings=False, onion=None
|
||||
):
|
||||
super(TorConnectionDialog, self).__init__(None)
|
||||
|
||||
self.common = common
|
||||
self.testing_settings = testing_settings
|
||||
|
||||
if custom_settings:
|
||||
self.settings = custom_settings
|
||||
else:
|
||||
self.settings = self.common.settings
|
||||
|
||||
self.common.log("TorConnectionDialog", "__init__")
|
||||
|
||||
if self.testing_settings:
|
||||
self.title = strings._("gui_settings_connection_type_test_button")
|
||||
self.onion = onion
|
||||
else:
|
||||
self.title = "OnionShare"
|
||||
self.onion = self.common.gui.onion
|
||||
|
||||
self.setWindowTitle(self.title)
|
||||
|
||||
self.setWindowIcon(QtGui.QIcon(GuiCommon.get_resource_path("images/logo.png")))
|
||||
self.setModal(True)
|
||||
self.setFixedSize(400, 150)
|
||||
|
||||
# Label
|
||||
self.setLabelText(strings._("connecting_to_tor"))
|
||||
|
||||
# Progress bar ticks from 0 to 100
|
||||
self.setRange(0, 100)
|
||||
# Don't show if connection takes less than 100ms (for non-bundled tor)
|
||||
self.setMinimumDuration(100)
|
||||
|
||||
# Start displaying the status at 0
|
||||
self._tor_status_update(0, "")
|
||||
|
||||
def start(self):
|
||||
self.common.log("TorConnectionDialog", "start")
|
||||
|
||||
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 _tor_status_update(self, progress, summary):
|
||||
self.setValue(int(progress))
|
||||
self.setLabelText(
|
||||
f"<strong>{strings._('connecting_to_tor')}</strong><br>{summary}"
|
||||
)
|
||||
|
||||
def _connected_to_tor(self):
|
||||
self.common.log("TorConnectionDialog", "_connected_to_tor")
|
||||
self.active = False
|
||||
# Close the dialog after connecting
|
||||
self.setValue(self.maximum())
|
||||
self.success.emit()
|
||||
|
||||
def _canceled_connecting_to_tor(self):
|
||||
self.common.log("TorConnectionDialog", "_canceled_connecting_to_tor")
|
||||
self.active = False
|
||||
self.onion.cleanup()
|
||||
|
||||
# Cancel connecting to Tor
|
||||
QtCore.QTimer.singleShot(1, self.cancel)
|
||||
|
||||
def _error_connecting_to_tor(self, msg):
|
||||
self.common.log("TorConnectionDialog", "_error_connecting_to_tor")
|
||||
self.active = False
|
||||
|
||||
if self.testing_settings:
|
||||
# If testing, just display the error but don't open settings
|
||||
def alert():
|
||||
Alert(self.common, msg, QtWidgets.QMessageBox.Warning, title=self.title)
|
||||
|
||||
else:
|
||||
# If not testing, open settings after displaying the error
|
||||
def alert():
|
||||
Alert(
|
||||
self.common,
|
||||
f"{msg}\n\n{strings._('gui_tor_connection_error_settings')}",
|
||||
QtWidgets.QMessageBox.Warning,
|
||||
title=self.title,
|
||||
)
|
||||
|
||||
# Open settings
|
||||
self.open_tor_settings.emit()
|
||||
|
||||
QtCore.QTimer.singleShot(1, alert)
|
||||
|
||||
# Cancel connecting to Tor
|
||||
QtCore.QTimer.singleShot(1, self.cancel)
|
||||
|
||||
|
||||
class TorConnectionWidget(QtWidgets.QWidget):
|
||||
@ -165,6 +49,7 @@ class TorConnectionWidget(QtWidgets.QWidget):
|
||||
open_tor_settings = QtCore.Signal()
|
||||
success = QtCore.Signal()
|
||||
fail = QtCore.Signal(str)
|
||||
update_progress = QtCore.Signal(int)
|
||||
|
||||
def __init__(self, common, status_bar):
|
||||
super(TorConnectionWidget, self).__init__(None)
|
||||
@ -186,14 +71,10 @@ class TorConnectionWidget(QtWidgets.QWidget):
|
||||
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.QVBoxLayout()
|
||||
layout.addWidget(self.label)
|
||||
layout.addLayout(progress_layout)
|
||||
|
||||
layout = QtWidgets.QHBoxLayout()
|
||||
layout.addStretch()
|
||||
layout.addLayout(inner_layout)
|
||||
layout.addStretch()
|
||||
self.setLayout(layout)
|
||||
|
||||
# Start displaying the status at 0
|
||||
@ -233,12 +114,14 @@ class TorConnectionWidget(QtWidgets.QWidget):
|
||||
def cancel_clicked(self):
|
||||
self.was_canceled = True
|
||||
self.fail.emit("")
|
||||
self._reset()
|
||||
|
||||
def wasCanceled(self):
|
||||
return self.was_canceled
|
||||
|
||||
def _tor_status_update(self, progress, summary):
|
||||
self.progress.setValue(int(progress))
|
||||
self.update_progress.emit(int(progress))
|
||||
self.label.setText(
|
||||
f"<strong>{strings._('connecting_to_tor')}</strong><br>{summary}"
|
||||
)
|
||||
@ -250,8 +133,10 @@ class TorConnectionWidget(QtWidgets.QWidget):
|
||||
|
||||
# Close the dialog after connecting
|
||||
self.progress.setValue(self.progress.maximum())
|
||||
self.update_progress.emit(int(self.progress.maximum()))
|
||||
|
||||
self.success.emit()
|
||||
self._reset()
|
||||
|
||||
def _canceled_connecting_to_tor(self):
|
||||
self.common.log("TorConnectionWidget", "_canceled_connecting_to_tor")
|
||||
@ -260,11 +145,18 @@ class TorConnectionWidget(QtWidgets.QWidget):
|
||||
|
||||
# Cancel connecting to Tor
|
||||
QtCore.QTimer.singleShot(1, self.cancel_clicked)
|
||||
self._reset()
|
||||
|
||||
def _error_connecting_to_tor(self, msg):
|
||||
self.common.log("TorConnectionWidget", "_error_connecting_to_tor")
|
||||
self.active = False
|
||||
self.fail.emit(msg)
|
||||
self._reset()
|
||||
|
||||
def _reset(self):
|
||||
self.label.setText("")
|
||||
self.progress.setValue(0)
|
||||
self.update_progress.emit(0)
|
||||
|
||||
|
||||
class TorConnectionThread(QtCore.QThread):
|
||||
|
@ -21,7 +21,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
from PySide2 import QtCore, QtWidgets, QtGui
|
||||
import sys
|
||||
import platform
|
||||
import re
|
||||
import os
|
||||
|
||||
from onionshare_cli.meek import Meek
|
||||
@ -43,7 +42,15 @@ 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,
|
||||
parent=None,
|
||||
):
|
||||
super(TorSettingsTab, self).__init__()
|
||||
|
||||
self.common = common
|
||||
@ -54,6 +61,8 @@ class TorSettingsTab(QtWidgets.QWidget):
|
||||
|
||||
self.system = platform.system()
|
||||
self.tab_id = tab_id
|
||||
self.parent = parent
|
||||
self.from_autoconnect = from_autoconnect
|
||||
|
||||
# Connection type: either automatic, control port, or socket file
|
||||
|
||||
@ -303,6 +312,21 @@ 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 +346,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 +415,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 +505,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 +698,9 @@ 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 and not settings.get("auto_connect")
|
||||
):
|
||||
if self.common.gui.onion.is_authenticated():
|
||||
self.common.log(
|
||||
"TorSettingsTab", "save_clicked", "Connected to Tor"
|
||||
@ -717,9 +753,9 @@ class TorSettingsTab(QtWidgets.QWidget):
|
||||
self.tor_con.show()
|
||||
self.tor_con.start(settings)
|
||||
else:
|
||||
self.close_this_tab.emit()
|
||||
self.parent.close_this_tab.emit()
|
||||
else:
|
||||
self.close_this_tab.emit()
|
||||
self.parent.close_this_tab.emit()
|
||||
|
||||
def tor_con_success(self):
|
||||
"""
|
||||
@ -750,7 +786,7 @@ class TorSettingsTab(QtWidgets.QWidget):
|
||||
# Tell the tabs that Tor is connected
|
||||
self.tor_is_connected.emit()
|
||||
# Close the tab
|
||||
self.close_this_tab.emit()
|
||||
self.parent.close_this_tab.emit()
|
||||
|
||||
self.tor_con_type = None
|
||||
|
||||
@ -777,6 +813,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")
|
||||
@ -835,35 +874,10 @@ class TorSettingsTab(QtWidgets.QWidget):
|
||||
if self.bridge_custom_radio.isChecked():
|
||||
settings.set("bridges_type", "custom")
|
||||
|
||||
new_bridges = []
|
||||
bridges = self.bridge_custom_textbox.toPlainText().split("\n")
|
||||
bridges_valid = False
|
||||
for bridge in bridges:
|
||||
if bridge != "":
|
||||
# Check the syntax of the custom bridge to make sure it looks legitimate
|
||||
ipv4_pattern = re.compile(
|
||||
"(obfs4\s+)?(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]):([0-9]+)(\s+)([A-Z0-9]+)(.+)$"
|
||||
)
|
||||
ipv6_pattern = re.compile(
|
||||
"(obfs4\s+)?\[(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\]:[0-9]+\s+[A-Z0-9]+(.+)$"
|
||||
)
|
||||
meek_lite_pattern = re.compile(
|
||||
"(meek_lite)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)(\s)+url=(.+)(\s)+front=(.+)"
|
||||
)
|
||||
snowflake_pattern = re.compile(
|
||||
"(snowflake)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)"
|
||||
)
|
||||
if (
|
||||
ipv4_pattern.match(bridge)
|
||||
or ipv6_pattern.match(bridge)
|
||||
or meek_lite_pattern.match(bridge)
|
||||
or snowflake_pattern.match(bridge)
|
||||
):
|
||||
new_bridges.append(bridge)
|
||||
bridges_valid = True
|
||||
|
||||
bridges_valid = self.common.check_bridges_valid(bridges)
|
||||
if bridges_valid:
|
||||
new_bridges = "\n".join(new_bridges) + "\n"
|
||||
new_bridges = "\n".join(bridges_valid) + "\n"
|
||||
settings.set("bridges_custom", new_bridges)
|
||||
else:
|
||||
self.error_label.setText(
|
||||
|
42
desktop/scripts/countries-update-list.py
Executable file
42
desktop/scripts/countries-update-list.py
Executable file
@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import tempfile
|
||||
import subprocess
|
||||
import json
|
||||
|
||||
import onionshare_cli
|
||||
|
||||
|
||||
def main():
|
||||
# Clone the country-list repo
|
||||
tmp_dir = tempfile.TemporaryDirectory()
|
||||
subprocess.run(
|
||||
["git", "clone", "https://github.com/umpirsky/country-list.git"],
|
||||
cwd=tmp_dir.name,
|
||||
)
|
||||
repo_dir = os.path.join(tmp_dir.name, "country-list")
|
||||
|
||||
# Get the list of enabled languages
|
||||
common = onionshare_cli.common.Common()
|
||||
settings = onionshare_cli.settings.Settings(common)
|
||||
available_locales = list(settings.available_locales)
|
||||
|
||||
# Make a dictionary that makes a language's ISO 3166-1 to its name in all enabled languages
|
||||
os.makedirs(os.path.join("onionshare", "resources", "countries"), exist_ok=True)
|
||||
for locale in available_locales:
|
||||
with open(os.path.join(repo_dir, "data", locale, "country.json")) as f:
|
||||
countries = json.loads(f.read())
|
||||
|
||||
# Remove countries we don't have images for
|
||||
for key in ["JE", "MH", "FM", "MP", "PS", "TV", "UM"]:
|
||||
del countries[key]
|
||||
|
||||
with open(
|
||||
os.path.join("onionshare", "resources", "countries", f"{locale}.json"),
|
||||
"w",
|
||||
) as f:
|
||||
f.write(json.dumps(countries))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in New Issue
Block a user