mirror of
https://github.com/onionshare/onionshare.git
synced 2025-01-01 02:36:14 -05:00
Merge pull request #823 from micahflee/406_osx_sandbox
Enable macOS sandbox
This commit is contained in:
commit
80becc73fc
10
CHANGELOG.md
10
CHANGELOG.md
@ -1,5 +1,15 @@
|
|||||||
# OnionShare Changelog
|
# OnionShare Changelog
|
||||||
|
|
||||||
|
## 2.0
|
||||||
|
|
||||||
|
* New feature: Receiver mode allows you to receive files with OnionShare, instead of only sending files
|
||||||
|
* New feature: macOS sandbox is enabled
|
||||||
|
* New feature: Support for next generation onion services (TODO waiting on Tor release)
|
||||||
|
* New feature: If you're sharing a single file, don't zip it up
|
||||||
|
* New feature: Allow selecting your language from a dropdown
|
||||||
|
* New translations: (TODO fill in for final release)
|
||||||
|
* Several bugfixes
|
||||||
|
|
||||||
## 1.3.1
|
## 1.3.1
|
||||||
|
|
||||||
* Updated Tor to 0.2.3.10
|
* Updated Tor to 0.2.3.10
|
||||||
|
@ -23,9 +23,12 @@ if [ "$1" = "--release" ]; then
|
|||||||
PKG_PATH="$ROOT/dist/OnionShare.pkg"
|
PKG_PATH="$ROOT/dist/OnionShare.pkg"
|
||||||
IDENTITY_NAME_APPLICATION="Developer ID Application: Micah Lee"
|
IDENTITY_NAME_APPLICATION="Developer ID Application: Micah Lee"
|
||||||
IDENTITY_NAME_INSTALLER="Developer ID Installer: Micah Lee"
|
IDENTITY_NAME_INSTALLER="Developer ID Installer: Micah Lee"
|
||||||
|
ENTITLEMENTS_CHILD_PATH="$ROOT/install/macos_sandbox/child.plist"
|
||||||
|
ENTITLEMENTS_PARENT_PATH="$ROOT/install/macos_sandbox/parent.plist"
|
||||||
|
|
||||||
echo "Codesigning the app bundle"
|
echo "Codesigning the app bundle"
|
||||||
codesign --deep -s "$IDENTITY_NAME_APPLICATION" "$APP_PATH"
|
codesign --deep -s "$IDENTITY_NAME_APPLICATION" -f --entitlements "$ENTITLEMENTS_CHILD_PATH" "$APP_PATH"
|
||||||
|
codesign -s "$IDENTITY_NAME_APPLICATION" -f --entitlements "$ENTITLEMENTS_PARENT_PATH" "$APP_PATH"
|
||||||
|
|
||||||
echo "Creating an installer"
|
echo "Creating an installer"
|
||||||
productbuild --sign "$IDENTITY_NAME_INSTALLER" --component "$APP_PATH" /Applications "$PKG_PATH"
|
productbuild --sign "$IDENTITY_NAME_INSTALLER" --component "$APP_PATH" /Applications "$PKG_PATH"
|
||||||
|
10
install/macos_sandbox/child.plist
Normal file
10
install/macos_sandbox/child.plist
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.app-sandbox</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.inherit</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
39
install/macos_sandbox/parent.plist
Normal file
39
install/macos_sandbox/parent.plist
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<!-- Enable app sandbox -->
|
||||||
|
<key>com.apple.security.app-sandbox</key>
|
||||||
|
<true/>
|
||||||
|
|
||||||
|
<!-- Both OnionShare and Tor need network server and client -->
|
||||||
|
<key>com.apple.security.network.server</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.network.client</key>
|
||||||
|
<true/>
|
||||||
|
|
||||||
|
<!-- In share mode, users need to be able to select files, and in receive mode,
|
||||||
|
users need to be able to choose a folder to save files to -->
|
||||||
|
<key>com.apple.security.files.user-selected.read-write</key>
|
||||||
|
<true/>
|
||||||
|
|
||||||
|
<!-- Flask needs to read this mime.types file when starting an HTTP server -->
|
||||||
|
<key>com.apple.security.temporary-exception.files.absolute-path.read-only</key>
|
||||||
|
<array>
|
||||||
|
<string>/private/etc/apache2/mime.types</string>
|
||||||
|
</array>
|
||||||
|
|
||||||
|
<!-- For OnionShare to be able to connect to Tor Browser's tor control port,
|
||||||
|
it needs to read it's control_auth_cookie file -->
|
||||||
|
<key>com.apple.security.temporary-exception.files.home-relative-path.read-only</key>
|
||||||
|
<array>
|
||||||
|
<string>/Library/Application Support/TorBrowser-Data/Tor/control_auth_cookie</string>
|
||||||
|
</array>
|
||||||
|
|
||||||
|
<!-- In receive mode, OnionShare needs to be able to write to ~/OnionShare -->
|
||||||
|
<key>com.apple.security.temporary-exception.files.home-relative-path.read-write</key>
|
||||||
|
<array>
|
||||||
|
<string>/OnionShare/</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@ -3,10 +3,10 @@
|
|||||||
!define ABOUTURL "https:\\onionshare.org\"
|
!define ABOUTURL "https:\\onionshare.org\"
|
||||||
|
|
||||||
# change these with each release
|
# change these with each release
|
||||||
!define INSTALLSIZE 66537
|
!define INSTALLSIZE 115186
|
||||||
!define VERSIONMAJOR 1
|
!define VERSIONMAJOR 2
|
||||||
!define VERSIONMINOR 3
|
!define VERSIONMINOR 0
|
||||||
!define VERSIONSTRING "1.3.1"
|
!define VERSIONSTRING "2.0"
|
||||||
|
|
||||||
RequestExecutionLevel admin
|
RequestExecutionLevel admin
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@ a = Analysis(
|
|||||||
('../share/torrc_template', 'share'),
|
('../share/torrc_template', 'share'),
|
||||||
('../share/torrc_template-obfs4', 'share'),
|
('../share/torrc_template-obfs4', 'share'),
|
||||||
('../share/torrc_template-meek_lite_azure', 'share'),
|
('../share/torrc_template-meek_lite_azure', 'share'),
|
||||||
('../share/torrc_template-windows', 'share'),
|
|
||||||
('../share/images/*', 'share/images'),
|
('../share/images/*', 'share/images'),
|
||||||
('../share/locale/*', 'share/locale'),
|
('../share/locale/*', 'share/locale'),
|
||||||
('../share/static/*', 'share/static'),
|
('../share/static/*', 'share/static'),
|
||||||
|
@ -123,6 +123,23 @@ class Common(object):
|
|||||||
|
|
||||||
return (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path)
|
return (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path)
|
||||||
|
|
||||||
|
def build_data_dir(self):
|
||||||
|
"""
|
||||||
|
Returns the path of the OnionShare data directory.
|
||||||
|
"""
|
||||||
|
if self.platform == 'Windows':
|
||||||
|
try:
|
||||||
|
appdata = os.environ['APPDATA']
|
||||||
|
return '{}\\OnionShare'.format(appdata)
|
||||||
|
except:
|
||||||
|
# If for some reason we don't have the 'APPDATA' environment variable
|
||||||
|
# (like running tests in Linux while pretending to be in Windows)
|
||||||
|
return os.path.expanduser('~/.config/onionshare')
|
||||||
|
elif self.platform == 'Darwin':
|
||||||
|
return os.path.expanduser('~/Library/Application Support/OnionShare')
|
||||||
|
else:
|
||||||
|
return os.path.expanduser('~/.config/onionshare')
|
||||||
|
|
||||||
def build_slug(self):
|
def build_slug(self):
|
||||||
"""
|
"""
|
||||||
Returns a random string made from two words from the wordlist, such as "deter-trig".
|
Returns a random string made from two words from the wordlist, such as "deter-trig".
|
||||||
|
@ -169,34 +169,35 @@ class Onion(object):
|
|||||||
raise BundledTorNotSupported(strings._('settings_error_bundled_tor_not_supported'))
|
raise BundledTorNotSupported(strings._('settings_error_bundled_tor_not_supported'))
|
||||||
|
|
||||||
# Create a torrc for this session
|
# Create a torrc for this session
|
||||||
self.tor_data_directory = tempfile.TemporaryDirectory()
|
self.tor_data_directory = tempfile.TemporaryDirectory(dir=self.common.build_data_dir())
|
||||||
|
self.common.log('Onion', 'connect', 'tor_data_directory={}'.format(self.tor_data_directory.name))
|
||||||
|
|
||||||
if self.common.platform == 'Windows':
|
# Create the torrc
|
||||||
# Windows needs to use network ports, doesn't support unix sockets
|
with open(self.common.get_resource_path('torrc_template')) as f:
|
||||||
torrc_template = open(self.common.get_resource_path('torrc_template-windows')).read()
|
torrc_template = f.read()
|
||||||
|
self.tor_cookie_auth_file = os.path.join(self.tor_data_directory.name, 'cookie')
|
||||||
|
try:
|
||||||
|
self.tor_socks_port = self.common.get_available_port(1000, 65535)
|
||||||
|
except:
|
||||||
|
raise OSError(strings._('no_available_port'))
|
||||||
|
self.tor_torrc = os.path.join(self.tor_data_directory.name, 'torrc')
|
||||||
|
|
||||||
|
if self.common.platform == 'Windows' or self.common.platform == "Darwin":
|
||||||
|
# Windows doesn't support unix sockets, so it must use a network port.
|
||||||
|
# macOS can't use unix sockets either because socket filenames are limited to
|
||||||
|
# 100 chars, and the macOS sandbox forces us to put the socket file in a place
|
||||||
|
# with a really long path.
|
||||||
|
torrc_template += 'ControlPort {{control_port}}\n'
|
||||||
try:
|
try:
|
||||||
self.tor_control_port = self.common.get_available_port(1000, 65535)
|
self.tor_control_port = self.common.get_available_port(1000, 65535)
|
||||||
except:
|
except:
|
||||||
raise OSError(strings._('no_available_port'))
|
raise OSError(strings._('no_available_port'))
|
||||||
self.tor_control_socket = None
|
self.tor_control_socket = None
|
||||||
self.tor_cookie_auth_file = os.path.join(self.tor_data_directory.name, 'cookie')
|
|
||||||
try:
|
|
||||||
self.tor_socks_port = self.common.get_available_port(1000, 65535)
|
|
||||||
except:
|
|
||||||
raise OSError(strings._('no_available_port'))
|
|
||||||
self.tor_torrc = os.path.join(self.tor_data_directory.name, 'torrc')
|
|
||||||
else:
|
else:
|
||||||
# Linux, Mac and BSD can use unix sockets
|
# Linux and BSD can use unix sockets
|
||||||
with open(self.common.get_resource_path('torrc_template')) as f:
|
torrc_template += 'ControlSocket {{control_socket}}\n'
|
||||||
torrc_template = f.read()
|
|
||||||
self.tor_control_port = None
|
self.tor_control_port = None
|
||||||
self.tor_control_socket = os.path.join(self.tor_data_directory.name, 'control_socket')
|
self.tor_control_socket = os.path.join(self.tor_data_directory.name, 'control_socket')
|
||||||
self.tor_cookie_auth_file = os.path.join(self.tor_data_directory.name, 'cookie')
|
|
||||||
try:
|
|
||||||
self.tor_socks_port = self.common.get_available_port(1000, 65535)
|
|
||||||
except:
|
|
||||||
raise OSError(strings._('no_available_port'))
|
|
||||||
self.tor_torrc = os.path.join(self.tor_data_directory.name, 'torrc')
|
|
||||||
|
|
||||||
torrc_template = torrc_template.replace('{{data_directory}}', self.tor_data_directory.name)
|
torrc_template = torrc_template.replace('{{data_directory}}', self.tor_data_directory.name)
|
||||||
torrc_template = torrc_template.replace('{{control_port}}', str(self.tor_control_port))
|
torrc_template = torrc_template.replace('{{control_port}}', str(self.tor_control_port))
|
||||||
@ -205,6 +206,7 @@ class Onion(object):
|
|||||||
torrc_template = torrc_template.replace('{{geo_ip_file}}', self.tor_geo_ip_file_path)
|
torrc_template = torrc_template.replace('{{geo_ip_file}}', self.tor_geo_ip_file_path)
|
||||||
torrc_template = torrc_template.replace('{{geo_ipv6_file}}', self.tor_geo_ipv6_file_path)
|
torrc_template = torrc_template.replace('{{geo_ipv6_file}}', self.tor_geo_ipv6_file_path)
|
||||||
torrc_template = torrc_template.replace('{{socks_port}}', str(self.tor_socks_port))
|
torrc_template = torrc_template.replace('{{socks_port}}', str(self.tor_socks_port))
|
||||||
|
|
||||||
with open(self.tor_torrc, 'w') as f:
|
with open(self.tor_torrc, 'w') as f:
|
||||||
f.write(torrc_template)
|
f.write(torrc_template)
|
||||||
|
|
||||||
@ -243,7 +245,7 @@ class Onion(object):
|
|||||||
|
|
||||||
# Connect to the controller
|
# Connect to the controller
|
||||||
try:
|
try:
|
||||||
if self.common.platform == 'Windows':
|
if self.common.platform == 'Windows' or self.common.platform == "Darwin":
|
||||||
self.c = Controller.from_port(port=self.tor_control_port)
|
self.c = Controller.from_port(port=self.tor_control_port)
|
||||||
self.c.authenticate()
|
self.c.authenticate()
|
||||||
else:
|
else:
|
||||||
|
@ -23,6 +23,12 @@ import os
|
|||||||
import platform
|
import platform
|
||||||
import locale
|
import locale
|
||||||
|
|
||||||
|
try:
|
||||||
|
# We only need pwd module in macOS, and it's not available in Windows
|
||||||
|
import pwd
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
from . import strings
|
from . import strings
|
||||||
|
|
||||||
|
|
||||||
@ -132,30 +138,24 @@ class Settings(object):
|
|||||||
"""
|
"""
|
||||||
Returns the path of the settings file.
|
Returns the path of the settings file.
|
||||||
"""
|
"""
|
||||||
p = platform.system()
|
return os.path.join(self.common.build_data_dir(), 'onionshare.json')
|
||||||
if p == 'Windows':
|
|
||||||
try:
|
|
||||||
appdata = os.environ['APPDATA']
|
|
||||||
return '{}\\OnionShare\\onionshare.json'.format(appdata)
|
|
||||||
except:
|
|
||||||
# If for some reason we don't have the 'APPDATA' environment variable
|
|
||||||
# (like running tests in Linux while pretending to be in Windows)
|
|
||||||
return os.path.expanduser('~/.config/onionshare/onionshare.json')
|
|
||||||
elif p == 'Darwin':
|
|
||||||
return os.path.expanduser('~/Library/Application Support/OnionShare/onionshare.json')
|
|
||||||
else:
|
|
||||||
return os.path.expanduser('~/.config/onionshare/onionshare.json')
|
|
||||||
|
|
||||||
def build_default_downloads_dir(self):
|
def build_default_downloads_dir(self):
|
||||||
"""
|
"""
|
||||||
Returns the path of the default Downloads directory for receive mode.
|
Returns the path of the default Downloads directory for receive mode.
|
||||||
"""
|
"""
|
||||||
# On Windows, os.path.expanduser() needs to use backslash, or else it
|
|
||||||
# retains the forward slash, which breaks opening the folder in explorer.
|
if self.common.platform == "Darwin":
|
||||||
p = platform.system()
|
# We can't use os.path.expanduser() in macOS because in the sandbox it
|
||||||
if p == 'Windows':
|
# returns the path to the sandboxed homedir
|
||||||
|
real_homedir = pwd.getpwuid(os.getuid()).pw_dir
|
||||||
|
return os.path.join(real_homedir, 'OnionShare')
|
||||||
|
elif self.common.platform == "Windows":
|
||||||
|
# On Windows, os.path.expanduser() needs to use backslash, or else it
|
||||||
|
# retains the forward slash, which breaks opening the folder in explorer.
|
||||||
return os.path.expanduser('~\OnionShare')
|
return os.path.expanduser('~\OnionShare')
|
||||||
else:
|
else:
|
||||||
|
# All other OSes
|
||||||
return os.path.expanduser('~/OnionShare')
|
return os.path.expanduser('~/OnionShare')
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
@ -174,16 +174,18 @@ class Settings(object):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Make sure downloads_dir exists
|
||||||
|
try:
|
||||||
|
os.makedirs(self.get('downloads_dir'), exist_ok=True)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
"""
|
"""
|
||||||
Save settings to file.
|
Save settings to file.
|
||||||
"""
|
"""
|
||||||
self.common.log('Settings', 'save')
|
self.common.log('Settings', 'save')
|
||||||
|
os.makedirs(os.path.dirname(self.filename), exist_ok=True)
|
||||||
try:
|
|
||||||
os.makedirs(os.path.dirname(self.filename))
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
open(self.filename, 'w').write(json.dumps(self._settings))
|
open(self.filename, 'w').write(json.dumps(self._settings))
|
||||||
self.common.log('Settings', 'save', 'Settings saved in {}'.format(self.filename))
|
self.common.log('Settings', 'save', 'Settings saved in {}'.format(self.filename))
|
||||||
|
|
||||||
|
@ -184,19 +184,7 @@ class Web(object):
|
|||||||
"""
|
"""
|
||||||
Turn on debugging mode, which will log flask errors to a debug file.
|
Turn on debugging mode, which will log flask errors to a debug file.
|
||||||
"""
|
"""
|
||||||
if self.common.platform == 'Windows':
|
flask_debug_filename = os.path.join(self.common.build_data_dir(), 'flask_debug.log')
|
||||||
try:
|
|
||||||
appdata = os.environ['APPDATA']
|
|
||||||
flask_debug_filename = '{}\\OnionShare\\flask_debug.log'.format(appdata)
|
|
||||||
except:
|
|
||||||
# If for some reason we don't have the 'APPDATA' environment variable
|
|
||||||
# (like running tests in Linux while pretending to be in Windows)
|
|
||||||
flask_debug_filename = os.path.expanduser('~/.config/onionshare/flask_debug.log')
|
|
||||||
elif self.common.platform == 'Darwin':
|
|
||||||
flask_debug_filename = os.path.expanduser('~/Library/Application Support/OnionShare/flask_debug.log')
|
|
||||||
else:
|
|
||||||
flask_debug_filename = os.path.expanduser('~/.config/onionshare/flask_debug.log')
|
|
||||||
|
|
||||||
log_handler = logging.FileHandler(flask_debug_filename)
|
log_handler = logging.FileHandler(flask_debug_filename)
|
||||||
log_handler.setLevel(logging.WARNING)
|
log_handler.setLevel(logging.WARNING)
|
||||||
self.app.logger.addHandler(log_handler)
|
self.app.logger.addHandler(log_handler)
|
||||||
|
@ -47,7 +47,7 @@ class ShareMode(Mode):
|
|||||||
self.web = Web(self.common, True, 'share')
|
self.web = Web(self.common, True, 'share')
|
||||||
|
|
||||||
# File selection
|
# File selection
|
||||||
self.file_selection = FileSelection(self.common)
|
self.file_selection = FileSelection(self.common, self)
|
||||||
if self.filenames:
|
if self.filenames:
|
||||||
for filename in self.filenames:
|
for filename in self.filenames:
|
||||||
self.file_selection.file_list.add_file(filename)
|
self.file_selection.file_list.add_file(filename)
|
||||||
|
@ -288,10 +288,11 @@ class FileSelection(QtWidgets.QVBoxLayout):
|
|||||||
The list of files and folders in the GUI, as well as buttons to add and
|
The list of files and folders in the GUI, as well as buttons to add and
|
||||||
delete the files and folders.
|
delete the files and folders.
|
||||||
"""
|
"""
|
||||||
def __init__(self, common):
|
def __init__(self, common, parent):
|
||||||
super(FileSelection, self).__init__()
|
super(FileSelection, self).__init__()
|
||||||
|
|
||||||
self.common = common
|
self.common = common
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
self.server_on = False
|
self.server_on = False
|
||||||
|
|
||||||
@ -302,13 +303,25 @@ class FileSelection(QtWidgets.QVBoxLayout):
|
|||||||
self.file_list.files_updated.connect(self.update)
|
self.file_list.files_updated.connect(self.update)
|
||||||
|
|
||||||
# Buttons
|
# Buttons
|
||||||
self.add_button = QtWidgets.QPushButton(strings._('gui_add'))
|
if self.common.platform == 'Darwin':
|
||||||
self.add_button.clicked.connect(self.add)
|
# The macOS sandbox makes it so the Mac version needs separate add files
|
||||||
|
# and folders buttons, in order to use native file selection dialogs
|
||||||
|
self.add_files_button = QtWidgets.QPushButton(strings._('gui_add_files'))
|
||||||
|
self.add_files_button.clicked.connect(self.add_files)
|
||||||
|
self.add_folder_button = QtWidgets.QPushButton(strings._('gui_add_folder'))
|
||||||
|
self.add_folder_button.clicked.connect(self.add_folder)
|
||||||
|
else:
|
||||||
|
self.add_button = QtWidgets.QPushButton(strings._('gui_add'))
|
||||||
|
self.add_button.clicked.connect(self.add)
|
||||||
self.delete_button = QtWidgets.QPushButton(strings._('gui_delete'))
|
self.delete_button = QtWidgets.QPushButton(strings._('gui_delete'))
|
||||||
self.delete_button.clicked.connect(self.delete)
|
self.delete_button.clicked.connect(self.delete)
|
||||||
button_layout = QtWidgets.QHBoxLayout()
|
button_layout = QtWidgets.QHBoxLayout()
|
||||||
button_layout.addStretch()
|
button_layout.addStretch()
|
||||||
button_layout.addWidget(self.add_button)
|
if self.common.platform == 'Darwin':
|
||||||
|
button_layout.addWidget(self.add_files_button)
|
||||||
|
button_layout.addWidget(self.add_folder_button)
|
||||||
|
else:
|
||||||
|
button_layout.addWidget(self.add_button)
|
||||||
button_layout.addWidget(self.delete_button)
|
button_layout.addWidget(self.delete_button)
|
||||||
|
|
||||||
# Add the widgets
|
# Add the widgets
|
||||||
@ -323,10 +336,18 @@ class FileSelection(QtWidgets.QVBoxLayout):
|
|||||||
"""
|
"""
|
||||||
# All buttons should be hidden if the server is on
|
# All buttons should be hidden if the server is on
|
||||||
if self.server_on:
|
if self.server_on:
|
||||||
self.add_button.hide()
|
if self.common.platform == 'Darwin':
|
||||||
|
self.add_files_button.hide()
|
||||||
|
self.add_folder_button.hide()
|
||||||
|
else:
|
||||||
|
self.add_button.hide()
|
||||||
self.delete_button.hide()
|
self.delete_button.hide()
|
||||||
else:
|
else:
|
||||||
self.add_button.show()
|
if self.common.platform == 'Darwin':
|
||||||
|
self.add_files_button.show()
|
||||||
|
self.add_folder_button.show()
|
||||||
|
else:
|
||||||
|
self.add_button.show()
|
||||||
|
|
||||||
# Delete button should be hidden if item isn't selected
|
# Delete button should be hidden if item isn't selected
|
||||||
if len(self.file_list.selectedItems()) == 0:
|
if len(self.file_list.selectedItems()) == 0:
|
||||||
@ -349,6 +370,24 @@ class FileSelection(QtWidgets.QVBoxLayout):
|
|||||||
self.file_list.setCurrentItem(None)
|
self.file_list.setCurrentItem(None)
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
|
def add_files(self):
|
||||||
|
"""
|
||||||
|
Add files button clicked.
|
||||||
|
"""
|
||||||
|
files = QtWidgets.QFileDialog.getOpenFileNames(self.parent, caption=strings._('gui_choose_items'))
|
||||||
|
filenames = files[0]
|
||||||
|
for filename in filenames:
|
||||||
|
self.file_list.add_file(filename)
|
||||||
|
|
||||||
|
def add_folder(self):
|
||||||
|
"""
|
||||||
|
Add folder button clicked.
|
||||||
|
"""
|
||||||
|
filename = QtWidgets.QFileDialog.getExistingDirectory(self.parent,
|
||||||
|
caption=strings._('gui_choose_items'),
|
||||||
|
options=QtWidgets.QFileDialog.ShowDirsOnly)
|
||||||
|
self.file_list.add_file(filename)
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
"""
|
"""
|
||||||
Delete button clicked
|
Delete button clicked
|
||||||
|
@ -44,6 +44,10 @@ class AddFileDialog(QtWidgets.QFileDialog):
|
|||||||
"""
|
"""
|
||||||
Overridden version of QFileDialog which allows us to select folders as well
|
Overridden version of QFileDialog which allows us to select folders as well
|
||||||
as, or instead of, files. For adding files/folders to share.
|
as, or instead of, files. For adding files/folders to share.
|
||||||
|
|
||||||
|
Note that this dialog can't be used in macOS, only in Windows, Linux, and BSD.
|
||||||
|
This is because the macOS sandbox requires native dialogs, and this is a Qt5
|
||||||
|
dialog.
|
||||||
"""
|
"""
|
||||||
def __init__(self, common, *args, **kwargs):
|
def __init__(self, common, *args, **kwargs):
|
||||||
QtWidgets.QFileDialog.__init__(self, *args, **kwargs)
|
QtWidgets.QFileDialog.__init__(self, *args, **kwargs)
|
||||||
|
@ -34,6 +34,8 @@
|
|||||||
"help_config": "Custom JSON config file location (optional)",
|
"help_config": "Custom JSON config file location (optional)",
|
||||||
"gui_drag_and_drop": "Drag and drop files and folders\nto start sharing",
|
"gui_drag_and_drop": "Drag and drop files and folders\nto start sharing",
|
||||||
"gui_add": "Add",
|
"gui_add": "Add",
|
||||||
|
"gui_add_files": "Add Files",
|
||||||
|
"gui_add_folder": "Add Folder",
|
||||||
"gui_delete": "Delete",
|
"gui_delete": "Delete",
|
||||||
"gui_choose_items": "Choose",
|
"gui_choose_items": "Choose",
|
||||||
"gui_share_start_server": "Start sharing",
|
"gui_share_start_server": "Start sharing",
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
DataDirectory {{data_directory}}
|
DataDirectory {{data_directory}}
|
||||||
SocksPort {{socks_port}}
|
SocksPort {{socks_port}}
|
||||||
ControlSocket {{control_socket}}
|
|
||||||
CookieAuthentication 1
|
CookieAuthentication 1
|
||||||
CookieAuthFile {{cookie_auth_file}}
|
CookieAuthFile {{cookie_auth_file}}
|
||||||
AvoidDiskWrites 1
|
AvoidDiskWrites 1
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
DataDirectory {{data_directory}}
|
|
||||||
SocksPort {{socks_port}}
|
|
||||||
ControlPort {{control_port}}
|
|
||||||
CookieAuthentication 1
|
|
||||||
CookieAuthFile {{cookie_auth_file}}
|
|
||||||
AvoidDiskWrites 1
|
|
||||||
Log notice stdout
|
|
||||||
GeoIPFile {{geo_ip_file}}
|
|
||||||
GeoIPv6File {{geo_ipv6_file}}
|
|
@ -1 +1 @@
|
|||||||
2.0.dev
|
2.0.dev1
|
||||||
|
@ -175,7 +175,7 @@ class TestSettings:
|
|||||||
platform_windows):
|
platform_windows):
|
||||||
monkeypatch.setenv('APPDATA', 'C:')
|
monkeypatch.setenv('APPDATA', 'C:')
|
||||||
obj = settings.Settings(common.Common())
|
obj = settings.Settings(common.Common())
|
||||||
assert obj.filename == 'C:\\OnionShare\\onionshare.json'
|
assert obj.filename.replace('/', '\\') == 'C:\\OnionShare\\onionshare.json'
|
||||||
|
|
||||||
def test_set_custom_bridge(self, settings_obj):
|
def test_set_custom_bridge(self, settings_obj):
|
||||||
settings_obj.set('tor_bridges_use_custom_bridges', 'Bridge 45.3.20.65:9050 21300AD88890A49C429A6CB9959CFD44490A8F6E')
|
settings_obj.set('tor_bridges_use_custom_bridges', 'Bridge 45.3.20.65:9050 21300AD88890A49C429A6CB9959CFD44490A8F6E')
|
||||||
|
Loading…
Reference in New Issue
Block a user