diff --git a/install/onionshare.nsi b/install/onionshare.nsi index 4030636a..f47541cf 100644 --- a/install/onionshare.nsi +++ b/install/onionshare.nsi @@ -207,6 +207,8 @@ Section "install" File "${BINPATH}\share\images\download_completed_none.png" File "${BINPATH}\share\images\download_in_progress.png" File "${BINPATH}\share\images\download_in_progress_none.png" + File "${BINPATH}\share\images\download_window_gray.png" + File "${BINPATH}\share\images\download_window_green.png" File "${BINPATH}\share\images\favicon.ico" File "${BINPATH}\share\images\file_delete.png" File "${BINPATH}\share\images\info.png" @@ -393,6 +395,8 @@ FunctionEnd Delete "$INSTDIR\share\images\download_completed_none.png" Delete "$INSTDIR\share\images\download_in_progress.png" Delete "$INSTDIR\share\images\download_in_progress_none.png" + Delete "$INSTDIR\share\images\download_window_gray.png" + Delete "$INSTDIR\share\images\download_window_green.png" Delete "$INSTDIR\share\images\favicon.ico" Delete "$INSTDIR\share\images\file_delete.png" Delete "$INSTDIR\share\images\info.png" diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py index 13612e77..39269b07 100644 --- a/onionshare_gui/__init__.py +++ b/onionshare_gui/__init__.py @@ -110,8 +110,7 @@ def main(): app = OnionShare(common, onion, local_only, stay_open, shutdown_timeout) # Launch the gui - web.stay_open = stay_open - gui = OnionShareGui(common, web, onion, qtapp, app, filenames, config) + gui = OnionShareGui(onion, qtapp, app, filenames, config, local_only) # Clean up when app quits def shutdown(): diff --git a/onionshare_gui/downloads.py b/onionshare_gui/downloads.py index db82d30a..0e85d33f 100644 --- a/onionshare_gui/downloads.py +++ b/onionshare_gui/downloads.py @@ -19,7 +19,7 @@ along with this program. If not, see . """ import time -from PyQt5 import QtCore, QtWidgets +from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings @@ -103,19 +103,39 @@ class Downloads(QtWidgets.QWidget): self.common = common self.downloads = {} + + self.downloads_container = QtWidgets.QScrollArea() + self.downloads_container.setWidget(self) + self.downloads_container.setWindowTitle(strings._('gui_downloads', True)) + self.downloads_container.setWidgetResizable(True) + self.downloads_container.setMaximumHeight(600) + self.downloads_container.setMinimumHeight(150) + self.downloads_container.setMinimumWidth(350) + self.downloads_container.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) + self.downloads_container.setWindowFlags(QtCore.Qt.Sheet | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.CustomizeWindowHint) + self.downloads_container.vbar = self.downloads_container.verticalScrollBar() + + self.downloads_label = QtWidgets.QLabel(strings._('gui_downloads', True)) + self.downloads_label.setStyleSheet('QLabel { font-weight: bold; font-size 14px; text-align: center; }') + self.no_downloads_label = QtWidgets.QLabel(strings._('gui_no_downloads', True)) + + self.downloads_layout = QtWidgets.QVBoxLayout() + self.layout = QtWidgets.QVBoxLayout() + self.layout.addWidget(self.downloads_label) + self.layout.addWidget(self.no_downloads_label) + self.layout.addLayout(self.downloads_layout) + self.layout.addStretch() self.setLayout(self.layout) def add_download(self, download_id, total_bytes): """ Add a new download progress bar. """ - self.parent().show() - # add it to the list download = Download(self.common, download_id, total_bytes) self.downloads[download_id] = download - self.layout.insertWidget(-1, download.progress_bar) + self.downloads_layout.addWidget(download.progress_bar) def update_download(self, download_id, downloaded_bytes): """ @@ -134,6 +154,6 @@ class Downloads(QtWidgets.QWidget): Reset the downloads back to zero """ for download in self.downloads.values(): - self.layout.removeWidget(download.progress_bar) + self.downloads_layout.removeWidget(download.progress_bar) download.progress_bar.close() self.downloads = {} diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index a52f232a..b5e411f2 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -46,7 +46,7 @@ class OnionShareGui(QtWidgets.QMainWindow): starting_server_step3 = QtCore.pyqtSignal() starting_server_error = QtCore.pyqtSignal(str) - def __init__(self, common, web, onion, qtapp, app, filenames, config=False): + def __init__(self, common, web, onion, qtapp, app, filenames, config=False, local_only=False): super(OnionShareGui, self).__init__() self.common = common @@ -58,6 +58,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.onion = onion self.qtapp = qtapp self.app = app + self.local_only = local_only self.setWindowTitle('OnionShare') self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) @@ -106,14 +107,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.filesize_warning.hide() # Downloads - self.downloads = Downloads(self.common) - self.downloads_container = QtWidgets.QScrollArea() - self.downloads_container.setWidget(self.downloads) - self.downloads_container.setWidgetResizable(True) - self.downloads_container.setMaximumHeight(200) - self.downloads_container.setMinimumHeight(75) - self.vbar = self.downloads_container.verticalScrollBar() - self.downloads_container.hide() # downloads start out hidden + self.downloads = Downloads() self.new_download = False self.downloads_in_progress = 0 self.downloads_completed = 0 @@ -123,6 +117,12 @@ class OnionShareGui(QtWidgets.QMainWindow): self.info_label = QtWidgets.QLabel() self.info_label.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') + self.info_show_downloads = QtWidgets.QToolButton() + self.info_show_downloads.setIcon(QtGui.QIcon(common.get_resource_path('images/download_window_gray.png'))) + self.info_show_downloads.setCheckable(True) + self.info_show_downloads.toggled.connect(self.downloads_toggled) + self.info_show_downloads.setToolTip(strings._('gui_downloads_window_tooltip', True)) + self.info_in_progress_downloads_count = QtWidgets.QLabel() self.info_in_progress_downloads_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') @@ -136,6 +136,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.info_layout.addStretch() self.info_layout.addWidget(self.info_in_progress_downloads_count) self.info_layout.addWidget(self.info_completed_downloads_count) + self.info_layout.addWidget(self.info_show_downloads) self.info_widget = QtWidgets.QWidget() self.info_widget.setLayout(self.info_layout) @@ -193,7 +194,6 @@ class OnionShareGui(QtWidgets.QMainWindow): primary_action_layout = QtWidgets.QVBoxLayout() primary_action_layout.addWidget(self.server_status) primary_action_layout.addWidget(self.filesize_warning) - primary_action_layout.addWidget(self.downloads_container) self.primary_action = QtWidgets.QWidget() self.primary_action.setLayout(primary_action_layout) self.primary_action.hide() @@ -223,7 +223,8 @@ class OnionShareGui(QtWidgets.QMainWindow): tor_con = TorConnectionDialog(self.common, self.qtapp, self.onion) tor_con.canceled.connect(self._tor_connection_canceled) tor_con.open_settings.connect(self._tor_connection_open_settings) - tor_con.start() + if not self.local_only: + tor_con.start() # Start the timer self.timer.start(500) @@ -341,19 +342,21 @@ class OnionShareGui(QtWidgets.QMainWindow): # We might've stopped the main requests timer if a Tor connection failed. # If we've reloaded settings, we probably succeeded in obtaining a new # connection. If so, restart the timer. - if self.onion.is_authenticated(): - if not self.timer.isActive(): - self.timer.start(500) - # If there were some files listed for sharing, we should be ok to - # re-enable the 'Start Sharing' button now. - if self.server_status.file_selection.get_num_files() > 0: - self.server_status.server_button.setEnabled(True) - self.status_bar.clearMessage() + if not self.local_only: + if self.onion.is_authenticated(): + if not self.timer.isActive(): + self.timer.start(500) + # If there were some files listed for sharing, we should be ok to + # re-enable the 'Start Sharing' button now. + if self.server_status.file_selection.get_num_files() > 0: + self.primary_action.show() + self.info_widget.show() + self.status_bar.clearMessage() # If we switched off the shutdown timeout setting, ensure the widget is hidden. if not self.common.settings.get('shutdown_timeout'): self.server_status.shutdown_timeout_container.hide() - d = SettingsDialog(self.common, self.onion, self.qtapp, self.config) + d = SettingsDialog(self.common, self.onion, self.qtapp, self.config, self.local_only) d.settings_saved.connect(reload_settings) d.exec_() @@ -372,7 +375,6 @@ class OnionShareGui(QtWidgets.QMainWindow): self.app.set_stealth(self.common.settings.get('use_stealth')) # Hide and reset the downloads if we have previously shared - self.downloads_container.hide() self.downloads.reset_downloads() self.reset_info_counters() self.status_bar.clearMessage() @@ -413,7 +415,7 @@ class OnionShareGui(QtWidgets.QMainWindow): """ self.common.log('OnionShareGui', 'start_server_step2') - # add progress bar to the status bar, indicating the crunching of files. + # add progress bar to the status bar, indicating the compressing of files. self._zip_progress_bar = ZipProgressBar(0) self.filenames = [] for index in range(self.file_selection.file_list.count()): @@ -549,20 +551,22 @@ class OnionShareGui(QtWidgets.QMainWindow): """ self.update() - # Have we lost connection to Tor somehow? - if not self.onion.is_authenticated(): - self.timer.stop() - if self.server_status.status != self.server_status.STATUS_STOPPED: - self.server_status.stop_server() - self.server_status.server_button.setEnabled(False) - self.status_bar.showMessage(strings._('gui_tor_connection_lost', True)) - if self.systemTray.supportsMessages() and self.common.settings.get('systray_notifications'): - self.systemTray.showMessage(strings._('gui_tor_connection_lost', True), strings._('gui_tor_connection_error_settings', True)) + if not self.local_only: + # Have we lost connection to Tor somehow? + if not self.onion.is_authenticated(): + self.timer.stop() + if self.server_status.status != self.server_status.STATUS_STOPPED: + self.server_status.stop_server() + self.primary_action.hide() + self.info_widget.hide() + self.status_bar.showMessage(strings._('gui_tor_connection_lost', True)) + if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): + self.systemTray.showMessage(strings._('gui_tor_connection_lost', True), strings._('gui_tor_connection_error_settings', True)) # scroll to the bottom of the dl progress bar log pane # if a new download has been added if self.new_download: - self.vbar.setValue(self.vbar.maximum()) + self.downloads.downloads_container.vbar.setValue(self.downloads.downloads_container.vbar.maximum()) self.new_download = False events = [] @@ -580,8 +584,8 @@ class OnionShareGui(QtWidgets.QMainWindow): self.status_bar.showMessage(strings._('download_page_loaded', True)) elif event["type"] == self.web.REQUEST_DOWNLOAD: - self.downloads_container.show() # show the downloads layout - self.downloads.add_download(event["data"]["id"], self.web.zip_filesize) + self.downloads.no_downloads_label.hide() + self.downloads.add_download(event["data"]["id"], web.zip_filesize) self.new_download = True self.downloads_in_progress += 1 self.update_downloads_in_progress(self.downloads_in_progress) @@ -647,6 +651,16 @@ class OnionShareGui(QtWidgets.QMainWindow): self.status_bar.clearMessage() self.server_share_status_label.setText(strings._('timeout_download_still_running', True)) + def downloads_toggled(self, checked): + """ + When the 'Show/hide downloads' button is toggled, show or hide the downloads window. + """ + common.log('OnionShareGui', 'toggle_downloads') + if checked: + self.downloads.downloads_container.show() + else: + self.downloads.downloads_container.hide() + def copy_url(self): """ When the URL gets copied to the clipboard, display this in the status bar. @@ -687,6 +701,9 @@ class OnionShareGui(QtWidgets.QMainWindow): """ self.update_downloads_completed(0) self.update_downloads_in_progress(0) + self.info_show_downloads.setIcon(QtGui.QIcon(common.get_resource_path('images/download_window_gray.png'))) + self.downloads.no_downloads_label.show() + self.downloads.downloads_container.resize(self.downloads.downloads_container.sizeHint()) def update_downloads_completed(self, count): """ @@ -707,6 +724,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.info_in_progress_downloads_image = self.common.get_resource_path('images/download_in_progress_none.png') else: self.info_in_progress_downloads_image = self.common.get_resource_path('images/download_in_progress.png') + self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_green.png'))) self.info_in_progress_downloads_count.setText(' {1:d}'.format(self.info_in_progress_downloads_image, count)) self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(count)) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 192815da..e7449b51 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -34,7 +34,7 @@ class SettingsDialog(QtWidgets.QDialog): """ settings_saved = QtCore.pyqtSignal() - def __init__(self, common, onion, qtapp, config=False): + def __init__(self, common, onion, qtapp, config=False, local_only=False): super(SettingsDialog, self).__init__() self.common = common @@ -44,6 +44,7 @@ class SettingsDialog(QtWidgets.QDialog): self.onion = onion self.qtapp = qtapp self.config = config + self.local_only = local_only self.setModal(True) self.setWindowTitle(strings._('gui_settings_window_title', True)) @@ -499,6 +500,9 @@ class SettingsDialog(QtWidgets.QDialog): """ if checked: self.tor_bridges_use_custom_textbox_options.hide() + # Alert the user about meek's costliness if it looks like they're turning it on + if not self.old_settings.get('tor_bridges_use_meek_lite_amazon'): + Alert(strings._('gui_settings_meek_lite_expensive_warning', True), QtWidgets.QMessageBox.Warning) def tor_bridges_use_meek_lite_azure_radio_toggled(self, checked): """ @@ -506,6 +510,9 @@ class SettingsDialog(QtWidgets.QDialog): """ if checked: self.tor_bridges_use_custom_textbox_options.hide() + # Alert the user about meek's costliness if it looks like they're turning it on + if not self.old_settings.get('tor_bridges_use_meek_lite_azure'): + Alert(strings._('gui_settings_meek_lite_expensive_warning', True), QtWidgets.QMessageBox.Warning) def tor_bridges_use_custom_radio_toggled(self, checked): """ @@ -674,48 +681,52 @@ class SettingsDialog(QtWidgets.QDialog): # If Tor isn't connected, or if Tor settings have changed, Reinitialize # the Onion object reboot_onion = False - if self.onion.is_authenticated(): - self.common.log('SettingsDialog', 'save_clicked', 'Connected to Tor') - def changed(s1, s2, keys): - """ - Compare the Settings objects s1 and s2 and return true if any values - have changed for the given keys. - """ - for key in keys: - if s1.get(key) != s2.get(key): - return True - return False + if not self.local_only: + if self.onion.is_authenticated(): + common.log('SettingsDialog', 'save_clicked', 'Connected to Tor') + def changed(s1, s2, keys): + """ + Compare the Settings objects s1 and s2 and return true if any values + have changed for the given keys. + """ + for key in keys: + if s1.get(key) != s2.get(key): + return True + return False - if changed(settings, self.old_settings, [ - 'connection_type', 'control_port_address', - 'control_port_port', 'socks_address', 'socks_port', - 'socket_file_path', 'auth_type', 'auth_password', - 'no_bridges', 'tor_bridges_use_obfs4', - 'tor_bridges_use_meek_lite_amazon', 'tor_bridges_use_meek_lite_azure', - 'tor_bridges_use_custom_bridges']): + if changed(settings, self.old_settings, [ + 'connection_type', 'control_port_address', + 'control_port_port', 'socks_address', 'socks_port', + 'socket_file_path', 'auth_type', 'auth_password', + 'no_bridges', 'tor_bridges_use_obfs4', + 'tor_bridges_use_meek_lite_amazon', 'tor_bridges_use_meek_lite_azure', + 'tor_bridges_use_custom_bridges']): + reboot_onion = True + + else: + common.log('SettingsDialog', 'save_clicked', 'Not connected to Tor') + # Tor isn't connected, so try connecting reboot_onion = True - else: - self.common.log('SettingsDialog', 'save_clicked', 'Not connected to Tor') - # Tor isn't connected, so try connecting - reboot_onion = True + # Do we need to reinitialize Tor? + if reboot_onion: + # Reinitialize the Onion object + common.log('SettingsDialog', 'save_clicked', 'rebooting the Onion') + self.onion.cleanup() - # Do we need to reinitialize Tor? - if reboot_onion: - # Reinitialize the Onion object - self.common.log('SettingsDialog', 'save_clicked', 'rebooting the Onion') - self.onion.cleanup() + tor_con = TorConnectionDialog(self.qtapp, settings, self.onion) + tor_con.start() - tor_con = TorConnectionDialog(self.common, self.qtapp, self.onion, settings) - tor_con.start() + common.log('SettingsDialog', 'save_clicked', 'Onion done rebooting, connected to Tor: {}'.format(self.onion.connected_to_tor)) - self.common.log('SettingsDialog', 'save_clicked', 'Onion done rebooting, connected to Tor: {}'.format(self.onion.connected_to_tor)) + if self.onion.is_authenticated() and not tor_con.wasCanceled(): + self.settings_saved.emit() + self.close() - if self.onion.is_authenticated() and not tor_con.wasCanceled(): + else: self.settings_saved.emit() self.close() - else: self.settings_saved.emit() self.close() @@ -859,11 +870,12 @@ class SettingsDialog(QtWidgets.QDialog): self.common.log('SettingsDialog', 'closeEvent') # On close, if Tor isn't connected, then quit OnionShare altogether - if not self.onion.is_authenticated(): - self.common.log('SettingsDialog', 'closeEvent', 'Closing while not connected to Tor') + if not self.local_only: + if not self.onion.is_authenticated(): + self.common.log('SettingsDialog', 'closeEvent', 'Closing while not connected to Tor') - # Wait 1ms for the event loop to finish, then quit - QtCore.QTimer.singleShot(1, self.qtapp.quit) + # Wait 1ms for the event loop to finish, then quit + QtCore.QTimer.singleShot(1, self.qtapp.quit) def _update_autoupdate_timestamp(self, autoupdate_timestamp): self.common.log('SettingsDialog', '_update_autoupdate_timestamp') diff --git a/share/images/download_window_gray.png b/share/images/download_window_gray.png new file mode 100644 index 00000000..bf9c168e Binary files /dev/null and b/share/images/download_window_gray.png differ diff --git a/share/images/download_window_green.png b/share/images/download_window_green.png new file mode 100644 index 00000000..8f9a899b Binary files /dev/null and b/share/images/download_window_green.png differ diff --git a/share/locale/en.json b/share/locale/en.json index 214aa32f..6a983f9c 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -48,14 +48,16 @@ "gui_stop_server_shutdown_timeout_tooltip": "Share will stop automatically at {}", "gui_copy_url": "Copy Address", "gui_copy_hidservauth": "Copy HidServAuth", - "gui_downloads": "Downloads:", + "gui_downloads": "Download History", + "gui_downloads_window_tooltip": "Show/hide downloads", + "gui_no_downloads": "No downloads yet.", "gui_canceled": "Canceled", "gui_copied_url_title": "Copied OnionShare address", "gui_copied_url": "The OnionShare address has been copied to clipboard", "gui_copied_hidservauth_title": "Copied HidServAuth", "gui_copied_hidservauth": "The HidServAuth line has been copied to clipboard", "gui_starting_server1": "Starting Tor onion service...", - "gui_starting_server2": "Crunching files...", + "gui_starting_server2": "Compressing files...", "gui_please_wait": "Starting... Click to cancel", "error_hs_dir_cannot_create": "Cannot create onion service dir {0:s}", "error_hs_dir_not_writable": "onion service dir {0:s} is not writable", @@ -69,7 +71,7 @@ "gui_quit_warning_quit": "Quit", "gui_quit_warning_dont_quit": "Cancel", "error_rate_limit": "An attacker might be trying to guess your address. To prevent this, OnionShare has automatically stopped the server. To share the files you must start it again and share the new address.", - "zip_progress_bar_format": "Crunching files: %p%", + "zip_progress_bar_format": "Compressing files: %p%", "error_stealth_not_supported": "To create stealth onion services, you need at least Tor 0.2.9.1-alpha (or Tor Browser 6.5) and at least python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare requires at least Tor 0.2.7.1 and at least python3-stem 1.4.0.", "gui_settings_window_title": "Settings", @@ -108,6 +110,7 @@ "gui_settings_tor_bridges_meek_lite_amazon_radio_option_no_obfs4proxy": "Use built-in meek_lite (Amazon) pluggable transports (requires obfs4proxy)", "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Use built-in meek_lite (Azure) pluggable transports", "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Use built-in meek_lite (Azure) pluggable transports (requires obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Warning: the meek_lite bridges are very costly for the Tor Project to run!

You should only use meek_lite bridges if you are having trouble connecting to Tor directly, via obfs4 transports or other normal bridges.", "gui_settings_tor_bridges_custom_radio_option": "Use custom bridges", "gui_settings_tor_bridges_custom_label": "You can get bridges from https://bridges.torproject.org", "gui_settings_tor_bridges_invalid": "None of the bridges you supplied seem to be valid.\nPlease try again with valid bridges.", diff --git a/share/locale/eo.json b/share/locale/eo.json index 6d904dc0..8060f815 100644 --- a/share/locale/eo.json +++ b/share/locale/eo.json @@ -34,7 +34,7 @@ "gui_copied_url": "URL kopiita en tondujon", "gui_copied_hidservauth": "Copied HidServAuth line to clipboard", "gui_starting_server1": "Startigas Tor onion service...", - "gui_starting_server2": "Crunching files...", + "gui_starting_server2": "Compressing files...", "gui_please_wait": "Bonvolu atendi...", "error_hs_dir_cannot_create": "Ne eblas krei hidden-service-dosierujon {0:s}", "error_hs_dir_not_writable": "ne eblas konservi dosierojn en hidden-service-dosierujo {0:s}", @@ -47,7 +47,7 @@ "gui_quit_warning_quit": "Foriri", "gui_quit_warning_dont_quit": "Ne foriri", "error_rate_limit": "Iu atankanto povas provi diveni vian URL. Por eviti tion, OnionShare aŭtomate haltis la servilon. Por kundividi la dosierojn vi devas starti ĝin denove kaj kundividi la novan URL.", - "zip_progress_bar_format": "Crunching files: %p%", + "zip_progress_bar_format": "Compressing files: %p%", "error_stealth_not_supported": "To create stealth onion services, you need at least Tor 0.2.9.1-alpha (or Tor Browser 6.5) and at least python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare postulas almenaŭ Tor 0.2.7.1 kaj almenaŭ python3-stem 1.4.0.", "gui_menu_file_menu": "&File",