aboutsummaryrefslogtreecommitdiff
path: root/desktop/onionshare/main_window.py
diff options
context:
space:
mode:
Diffstat (limited to 'desktop/onionshare/main_window.py')
-rw-r--r--desktop/onionshare/main_window.py357
1 files changed, 357 insertions, 0 deletions
diff --git a/desktop/onionshare/main_window.py b/desktop/onionshare/main_window.py
new file mode 100644
index 00000000..6e5cd61d
--- /dev/null
+++ b/desktop/onionshare/main_window.py
@@ -0,0 +1,357 @@
+# -*- 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 os
+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 .tab_widget import TabWidget
+from .gui_common import GuiCommon
+from .threads import OnionCleanupThread
+
+
+class MainWindow(QtWidgets.QMainWindow):
+ """
+ MainWindow is the OnionShare main window, which contains the GUI elements, including all open tabs
+ """
+
+ def __init__(self, common, filenames):
+ super(MainWindow, self).__init__()
+
+ self.common = common
+ self.common.log("MainWindow", "__init__")
+
+ # Initialize the window
+ self.setMinimumWidth(1040)
+ self.setMinimumHeight(700)
+ self.setWindowTitle("OnionShare")
+ self.setWindowIcon(QtGui.QIcon(GuiCommon.get_resource_path("images/logo.png")))
+
+ # System tray
+ menu = QtWidgets.QMenu()
+ 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))
+ exit_action = menu.addAction(strings._("systray_menu_exit"))
+ exit_action.triggered.connect(self.close)
+
+ self.system_tray = QtWidgets.QSystemTrayIcon(self)
+
+ # The convention is Mac systray icons are always grayscale
+ if self.common.platform == "Darwin":
+ self.system_tray.setIcon(
+ QtGui.QIcon(GuiCommon.get_resource_path("images/logo_grayscale.png"))
+ )
+ else:
+ self.system_tray.setIcon(
+ QtGui.QIcon(GuiCommon.get_resource_path("images/logo.png"))
+ )
+ self.system_tray.setContextMenu(menu)
+ self.system_tray.show()
+
+ # Status bar
+ self.status_bar = QtWidgets.QStatusBar()
+ self.status_bar.setSizeGripEnabled(False)
+ self.status_bar.setStyleSheet(self.common.gui.css["status_bar"])
+ self.setStatusBar(self.status_bar)
+
+ # Server status indicator icons
+ self.status_bar.server_status_image_stopped = QtGui.QImage(
+ GuiCommon.get_resource_path("images/server_stopped.png")
+ )
+ self.status_bar.server_status_image_working = QtGui.QImage(
+ GuiCommon.get_resource_path("images/server_working.png")
+ )
+ self.status_bar.server_status_image_started = QtGui.QImage(
+ GuiCommon.get_resource_path("images/server_started.png")
+ )
+
+ # Server status indicator on the status bar
+ self.status_bar.server_status_image_label = QtWidgets.QLabel()
+ self.status_bar.server_status_image_label.setFixedWidth(20)
+ self.status_bar.server_status_label = QtWidgets.QLabel("")
+ self.status_bar.server_status_label.setStyleSheet(
+ self.common.gui.css["server_status_indicator_label"]
+ )
+ server_status_indicator_layout = QtWidgets.QHBoxLayout()
+ server_status_indicator_layout.addWidget(
+ self.status_bar.server_status_image_label
+ )
+ server_status_indicator_layout.addWidget(self.status_bar.server_status_label)
+ self.status_bar.server_status_indicator = QtWidgets.QWidget()
+ self.status_bar.server_status_indicator.setLayout(
+ server_status_indicator_layout
+ )
+ 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)
+ self.settings_button.setFixedSize(40, 50)
+ self.settings_button.setIcon(
+ QtGui.QIcon(
+ GuiCommon.get_resource_path(
+ "images/{}_settings.png".format(self.common.gui.color_mode)
+ )
+ )
+ )
+ self.settings_button.clicked.connect(self.open_settings)
+ self.settings_button.setStyleSheet(self.common.gui.css["settings_button"])
+ self.status_bar.addPermanentWidget(self.settings_button)
+
+ # Tabs
+ self.tabs = TabWidget(self.common, self.system_tray, self.status_bar)
+ 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)
+ else:
+ # Start with opening the first tab
+ self.tabs.new_tab_clicked()
+
+ # Layout
+ layout = QtWidgets.QVBoxLayout()
+ layout.addWidget(self.tabs)
+
+ central_widget = QtWidgets.QWidget()
+ central_widget.setLayout(layout)
+ 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()
+ self.close_dialog.setWindowTitle(strings._("gui_quit_warning_title"))
+ self.close_dialog.setText(strings._("gui_quit_warning_description"))
+ self.close_dialog.setIcon(QtWidgets.QMessageBox.Critical)
+ self.close_dialog.accept_button = self.close_dialog.addButton(
+ strings._("gui_quit_warning_quit"), QtWidgets.QMessageBox.AcceptRole
+ )
+ self.close_dialog.reject_button = self.close_dialog.addButton(
+ strings._("gui_quit_warning_cancel"), QtWidgets.QMessageBox.NoRole
+ )
+ self.close_dialog.setDefaultButton(self.close_dialog.reject_button)
+
+ def tor_connection_canceled(self):
+ """
+ If the user cancels before Tor finishes connecting, ask if they want to
+ quit, or open settings.
+ """
+ self.common.log("MainWindow", "tor_connection_canceled")
+
+ def ask():
+ a = Alert(
+ self.common,
+ strings._("gui_tor_connection_ask"),
+ QtWidgets.QMessageBox.Question,
+ buttons=QtWidgets.QMessageBox.NoButton,
+ autostart=False,
+ )
+ settings_button = QtWidgets.QPushButton(
+ strings._("gui_tor_connection_ask_open_settings")
+ )
+ quit_button = QtWidgets.QPushButton(
+ strings._("gui_tor_connection_ask_quit")
+ )
+ a.addButton(settings_button, QtWidgets.QMessageBox.AcceptRole)
+ a.addButton(quit_button, QtWidgets.QMessageBox.RejectRole)
+ a.setDefaultButton(settings_button)
+ a.exec_()
+
+ if a.clickedButton() == settings_button:
+ # Open settings
+ self.common.log(
+ "OnionShareGui",
+ "_tor_connection_canceled",
+ "Settings button clicked",
+ )
+ self.open_tor_settings()
+
+ if a.clickedButton() == quit_button:
+ # Quit
+ self.common.log(
+ "OnionShareGui", "_tor_connection_canceled", "Quit button clicked"
+ )
+
+ # Wait 1ms for the event loop to finish, then quit
+ QtCore.QTimer.singleShot(1, self.common.gui.qtapp.quit)
+
+ # Wait 100ms before asking
+ QtCore.QTimer.singleShot(100, ask)
+
+ def tor_connection_open_tor_settings(self):
+ """
+ The TorConnectionDialog wants to open the Tor Settings dialog
+ """
+ self.common.log("MainWindow", "tor_connection_open_tor_settings")
+
+ # Wait 1ms for the event loop to finish closing the TorConnectionDialog
+ QtCore.QTimer.singleShot(1, self.open_tor_settings)
+
+ def open_tor_settings(self):
+ """
+ Open the TorSettingsTab
+ """
+ self.common.log("MainWindow", "open_tor_settings")
+ self.tabs.open_tor_settings_tab()
+
+ def open_settings(self):
+ """
+ Open the SettingsTab
+ """
+ self.common.log("MainWindow", "open_settings")
+ self.tabs.open_settings_tab()
+
+ def settings_have_changed(self):
+ self.common.log("OnionShareGui", "settings_have_changed")
+
+ if self.common.gui.onion.is_authenticated():
+ self.status_bar.clearMessage()
+
+ # Tell each tab that settings have changed
+ for index in range(self.tabs.count()):
+ 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_()
+ self.activateWindow()
+
+ def closeEvent(self, e):
+ self.common.log("MainWindow", "closeEvent")
+
+ if self.tabs.are_tabs_active():
+ # Open the warning dialog
+ self.common.log("MainWindow", "closeEvent, opening warning dialog")
+ self.close_dialog.exec_()
+
+ # Close
+ if self.close_dialog.clickedButton() == self.close_dialog.accept_button:
+ self.system_tray.hide()
+ e.accept()
+ # Cancel
+ else:
+ e.ignore()
+ return
+
+ self.system_tray.hide()
+ e.accept()
+
+ def event(self, event):
+ # Check if color mode switched while onionshare was open, if so, ask user to restart
+ if event.type() == QtCore.QEvent.Type.ApplicationPaletteChange:
+ QtCore.QTimer.singleShot(1, self.color_mode_warning)
+ return True
+ return QtWidgets.QMainWindow.event(self, event)
+
+ def color_mode_warning(self):
+ """
+ Open the color mode warning alert.
+ """
+ notice = strings._("gui_color_mode_changed_notice")
+ Alert(self.common, notice, QtWidgets.QMessageBox.Information)
+
+ def cleanup(self):
+ self.common.log("MainWindow", "cleanup")
+ self.tabs.cleanup()
+
+ alert = Alert(
+ self.common,
+ strings._("gui_rendezvous_cleanup"),
+ QtWidgets.QMessageBox.Information,
+ buttons=QtWidgets.QMessageBox.NoButton,
+ autostart=False,
+ )
+ quit_early_button = QtWidgets.QPushButton(
+ strings._("gui_rendezvous_cleanup_quit_early")
+ )
+ alert.addButton(quit_early_button, QtWidgets.QMessageBox.RejectRole)
+
+ self.onion_cleanup_thread = OnionCleanupThread(self.common)
+ self.onion_cleanup_thread.finished.connect(alert.accept)
+ self.onion_cleanup_thread.start()
+
+ alert.exec_()
+ if alert.clickedButton() == quit_early_button:
+ self.common.log("MainWindow", "cleanup", "quitting early")
+ if self.onion_cleanup_thread.isRunning():
+ self.onion_cleanup_thread.terminate()
+ self.onion_cleanup_thread.wait()
+ self.common.gui.onion.cleanup(wait=False)
+
+ # Wait 1 second for threads to close gracefully, so tests finally pass
+ time.sleep(1)