diff options
author | Micah Lee <micah@micahflee.com> | 2020-07-05 21:54:29 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-05 21:54:29 -0700 |
commit | 0b4821c905ecfefdf438e4c98e5e79592b390ced (patch) | |
tree | e36ff23c5e3f129651d3904bfacc17d21082dcc7 /onionshare_gui | |
parent | 0e16d3b69e8d6f0ee4b10291a49b67ff347e7dd7 (diff) | |
parent | 7ce0f18f360f8543af0e4723c65d9175977935d8 (diff) | |
download | onionshare-0b4821c905ecfefdf438e4c98e5e79592b390ced.tar.gz onionshare-0b4821c905ecfefdf438e4c98e5e79592b390ced.zip |
Merge pull request #1139 from micahflee/910_flatpak
Flatpak support
Diffstat (limited to 'onionshare_gui')
-rw-r--r-- | onionshare_gui/__init__.py | 46 | ||||
-rw-r--r-- | onionshare_gui/event_handler.py | 89 | ||||
-rw-r--r-- | onionshare_gui/gui_common.py | 8 | ||||
-rw-r--r-- | onionshare_gui/settings_dialog.py | 25 | ||||
-rw-r--r-- | onionshare_gui/tab/mode/history.py | 4 | ||||
-rw-r--r-- | onionshare_gui/tab/mode/receive_mode/__init__.py | 9 | ||||
-rw-r--r-- | onionshare_gui/tab_widget.py | 18 | ||||
-rw-r--r-- | onionshare_gui/threads.py | 84 | ||||
-rw-r--r-- | onionshare_gui/tor_connection_dialog.py | 6 |
9 files changed, 147 insertions, 142 deletions
diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py index f0186a18..d083f741 100644 --- a/onionshare_gui/__init__.py +++ b/onionshare_gui/__init__.py @@ -24,7 +24,6 @@ import platform import argparse import signal import json -import psutil from PyQt5 import QtCore, QtWidgets from onionshare.common import Common @@ -65,10 +64,6 @@ def main(): # Display OnionShare banner print(f"OnionShare {common.version} | https://onionshare.org/") - # Allow Ctrl-C to smoothly quit the program instead of throwing an exception - # https://stackoverflow.com/questions/42814093/how-to-handle-ctrlc-in-python-app-with-pyqt - signal.signal(signal.SIGINT, signal.SIG_DFL) - # Start the Qt app global qtapp qtapp = Application(common) @@ -126,27 +121,11 @@ def main(): sys.exit() # Is there another onionshare-gui running? - existing_pid = None - for proc in psutil.process_iter(attrs=["pid", "name", "cmdline"]): - if proc.info["pid"] == os.getpid(): - continue - - if proc.info["name"] == "onionshare-gui" and proc.status() != "zombie": - existing_pid = proc.info["pid"] - break - else: - # Dev mode onionshare? - if proc.info["cmdline"] and len(proc.info["cmdline"]) >= 2: - if ( - os.path.basename(proc.info["cmdline"][0]).lower() == "python" - and os.path.basename(proc.info["cmdline"][1]) == "onionshare-gui" - and proc.status() != "zombie" - ): - existing_pid = proc.info["pid"] - break - - if existing_pid: - print(f"Opening tab in existing OnionShare window (pid {proc.info['pid']})") + if os.path.exists(common.gui.lock_filename): + with open(common.gui.lock_filename, "r") as f: + existing_pid = int(f.read()) + + print(f"Opening tab in existing OnionShare window (pid {existing_pid})") # Make an event for the existing OnionShare window if filenames: @@ -159,6 +138,20 @@ def main(): f.write(json.dumps(obj) + "\n") return + else: + # Write the lock file + with open(common.gui.lock_filename, "w") as f: + f.write(f"{os.getpid()}\n") + + # Allow Ctrl-C to smoothly quit the program instead of throwing an exception + def signal_handler(s, frame): + print("\nCtrl-C pressed, quitting") + if os.path.exists(common.gui.lock_filename): + os.remove(common.gui.lock_filename) + sys.exit(0) + + signal.signal(signal.SIGINT, signal_handler) + # Launch the gui main_window = MainWindow(common, filenames) @@ -169,6 +162,7 @@ def main(): # Clean up when app quits def shutdown(): main_window.cleanup() + os.remove(common.gui.lock_filename) qtapp.aboutToQuit.connect(shutdown) diff --git a/onionshare_gui/event_handler.py b/onionshare_gui/event_handler.py deleted file mode 100644 index f4d10c24..00000000 --- a/onionshare_gui/event_handler.py +++ /dev/null @@ -1,89 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee <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 watchdog.observers import Observer -from watchdog.events import FileSystemEventHandler, FileModifiedEvent -from PyQt5 import QtCore - - -class EventHandler(FileSystemEventHandler, QtCore.QObject): - """ - To trigger an event, write a JSON line to the events file. When that file changes, - each line will be handled as an event. Valid events are: - {"type": "new_tab"} - {"type": "new_share_tab", "filenames": ["file1", "file2"]} - """ - - new_tab = QtCore.pyqtSignal() - new_share_tab = QtCore.pyqtSignal(list) - - def __init__(self, common): - super(EventHandler, self).__init__() - self.common = common - - def on_modified(self, event): - if ( - type(event) == FileModifiedEvent - and event.src_path == self.common.gui.events_filename - ): - # Read all the lines in the events, then delete it - with open(self.common.gui.events_filename, "r") as f: - lines = f.readlines() - os.remove(self.common.gui.events_filename) - - self.common.log( - "EventHandler", "on_modified", f"processing {len(lines)} lines" - ) - for line in lines: - try: - obj = json.loads(line) - if "type" not in obj: - self.common.log( - "EventHandler", - "on_modified", - f"event does not have a type: {obj}", - ) - continue - except json.decoder.JSONDecodeError: - self.common.log( - "EventHandler", "on_modified", f"ignoring invalid line: {line}" - ) - continue - - if obj["type"] == "new_tab": - self.common.log("EventHandler", "on_modified", "new_tab event") - self.new_tab.emit() - - elif obj["type"] == "new_share_tab": - if "filenames" in obj and type(obj["filenames"]) is list: - self.new_share_tab.emit(obj["filenames"]) - else: - self.common.log( - "EventHandler", - "on_modified", - f"invalid new_share_tab event: {obj}", - ) - - else: - self.common.log( - "EventHandler", "on_modified", f"invalid event type: {obj}" - ) - diff --git a/onionshare_gui/gui_common.py b/onionshare_gui/gui_common.py index 40c912bb..eb259301 100644 --- a/onionshare_gui/gui_common.py +++ b/onionshare_gui/gui_common.py @@ -37,6 +37,9 @@ class GuiCommon: self.qtapp = qtapp self.local_only = local_only + # Are we running in a flatpak package? + self.is_flatpak = os.path.exists("/.flatpak-info") + # Load settings self.common.load_settings() @@ -46,7 +49,10 @@ class GuiCommon: # Start the Onion self.onion = Onion(common) - # Directory to watch for events + # Lock filename + self.lock_filename = os.path.join(self.common.build_data_dir(), "lock") + + # Events filenames self.events_dir = os.path.join(self.common.build_data_dir(), "events") if not os.path.exists(self.events_dir): os.makedirs(self.events_dir, 0o700, True) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 45eef270..f8cb4204 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -142,7 +142,9 @@ class SettingsDialog(QtWidgets.QDialog): self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path, ) = self.common.get_tor_paths() - if not os.path.isfile(self.obfs4proxy_file_path): + if not self.obfs4proxy_file_path or not os.path.isfile( + self.obfs4proxy_file_path + ): self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton( strings._("gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy") ) @@ -163,7 +165,9 @@ class SettingsDialog(QtWidgets.QDialog): self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path, ) = self.common.get_tor_paths() - if not os.path.isfile(self.obfs4proxy_file_path): + if not self.obfs4proxy_file_path or not os.path.isfile( + self.obfs4proxy_file_path + ): self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton( strings._( "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy" @@ -662,8 +666,7 @@ class SettingsDialog(QtWidgets.QDialog): onion = Onion(self.common, use_tmp_dir=True) onion.connect( - custom_settings=settings, - tor_status_update_func=tor_status_update_func, + custom_settings=settings, tor_status_update_func=tor_status_update_func, ) # If an exception hasn't been raised yet, the Tor settings work @@ -750,9 +753,7 @@ class SettingsDialog(QtWidgets.QDialog): ) close_forced_update_thread() - forced_update_thread = UpdateThread( - self.common, self.onion, force=True - ) + forced_update_thread = UpdateThread(self.common, self.onion, force=True) forced_update_thread.update_available.connect(update_available) forced_update_thread.update_not_available.connect(update_not_available) forced_update_thread.update_error.connect(update_error) @@ -850,7 +851,10 @@ class SettingsDialog(QtWidgets.QDialog): f"Onion done rebooting, connected to Tor: {self.common.gui.onion.connected_to_tor}", ) - if self.common.gui.onion.is_authenticated() and not tor_con.wasCanceled(): + if ( + self.common.gui.onion.is_authenticated() + and not tor_con.wasCanceled() + ): self.settings_saved.emit() self.close() @@ -866,7 +870,10 @@ class SettingsDialog(QtWidgets.QDialog): Cancel button clicked. """ self.common.log("SettingsDialog", "cancel_clicked") - if not self.common.gui.local_only and not self.common.gui.onion.is_authenticated(): + if ( + not self.common.gui.local_only + and not self.common.gui.onion.is_authenticated() + ): Alert( self.common, strings._("gui_tor_connection_canceled"), diff --git a/onionshare_gui/tab/mode/history.py b/onionshare_gui/tab/mode/history.py index f445efa6..c1a2d54d 100644 --- a/onionshare_gui/tab/mode/history.py +++ b/onionshare_gui/tab/mode/history.py @@ -250,11 +250,11 @@ class ReceiveHistoryItemFile(QtWidgets.QWidget): if self.common.platform == "Linux" or self.common.platform == "BSD": try: # If nautilus is available, open it - subprocess.Popen(["nautilus", abs_filename]) + subprocess.Popen(["xdg-open", self.dir]) except: Alert( self.common, - strings._("gui_open_folder_error_nautilus").format(abs_filename), + strings._("gui_open_folder_error").format(abs_filename), ) # macOS diff --git a/onionshare_gui/tab/mode/receive_mode/__init__.py b/onionshare_gui/tab/mode/receive_mode/__init__.py index ba1fc8c4..92cd17fa 100644 --- a/onionshare_gui/tab/mode/receive_mode/__init__.py +++ b/onionshare_gui/tab/mode/receive_mode/__init__.py @@ -17,6 +17,7 @@ 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 from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings @@ -24,7 +25,7 @@ from onionshare.web import Web from ..history import History, ToggleHistory, ReceiveHistoryItem from .. import Mode -from ....widgets import MinimumWidthWidget +from ....widgets import MinimumWidthWidget, Alert class ReceiveMode(Mode): @@ -135,6 +136,12 @@ class ReceiveMode(Mode): ) if selected_dir: + # If we're running inside a flatpak package, the data dir must be inside ~/OnionShare + if self.common.gui.is_flatpak: + if not selected_dir.startswith(os.path.expanduser("~/OnionShare")): + Alert(self.common, strings._("gui_receive_flatpak_data_dir")) + return + self.common.log( "ReceiveMode", "data_dir_button_clicked", diff --git a/onionshare_gui/tab_widget.py b/onionshare_gui/tab_widget.py index df4072f3..1ef82cad 100644 --- a/onionshare_gui/tab_widget.py +++ b/onionshare_gui/tab_widget.py @@ -18,13 +18,12 @@ You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. """ from PyQt5 import QtCore, QtWidgets, QtGui -from watchdog.observers import Observer from onionshare import strings from onionshare.mode_settings import ModeSettings from .tab import Tab -from .event_handler import EventHandler +from .threads import EventHandlerThread class TabWidget(QtWidgets.QTabWidget): @@ -73,17 +72,16 @@ class TabWidget(QtWidgets.QTabWidget): self.move_new_tab_button() # Watch the events file for changes - self.event_handler = EventHandler(common) - self.event_handler.new_tab.connect(self.add_tab) - self.event_handler.new_share_tab.connect(self.new_share_tab) - self.observer = Observer() - self.observer.schedule(self.event_handler, self.common.gui.events_dir) - self.observer.start() + self.event_handler_t = EventHandlerThread(common) + self.event_handler_t.new_tab.connect(self.add_tab) + self.event_handler_t.new_share_tab.connect(self.new_share_tab) + self.event_handler_t.start() def cleanup(self): # Stop the event thread - self.observer.stop() - self.observer.join() + self.event_handler_t.should_quit = True + self.event_handler_t.quit() + self.event_handler_t.wait(50) # Clean up each tab for index in range(self.count()): diff --git a/onionshare_gui/threads.py b/onionshare_gui/threads.py index 785e6ece..29554039 100644 --- a/onionshare_gui/threads.py +++ b/onionshare_gui/threads.py @@ -18,6 +18,8 @@ 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 time +import json +import os from PyQt5 import QtCore from onionshare import strings @@ -166,3 +168,85 @@ class AutoStartTimer(QtCore.QThread): except ValueError as e: self.error.emit(e.args[0]) return + + +class EventHandlerThread(QtCore.QThread): + """ + To trigger an event, write a JSON line to the events file. When that file changes, + each line will be handled as an event. Valid events are: + {"type": "new_tab"} + {"type": "new_share_tab", "filenames": ["file1", "file2"]} + """ + + new_tab = QtCore.pyqtSignal() + new_share_tab = QtCore.pyqtSignal(list) + + def __init__(self, common): + super(EventHandlerThread, self).__init__() + self.common = common + self.common.log("EventHandlerThread", "__init__") + self.should_quit = False + + def run(self): + self.common.log("EventHandlerThread", "run") + + mtime = 0 + while True: + if os.path.exists(self.common.gui.events_filename): + # Events file exists + if os.stat(self.common.gui.events_filename).st_mtime != mtime: + # Events file has been modified, load events + try: + with open(self.common.gui.events_filename, "r") as f: + lines = f.readlines() + os.remove(self.common.gui.events_filename) + + self.common.log( + "EventHandler", "run", f"processing {len(lines)} lines" + ) + for line in lines: + try: + obj = json.loads(line) + if "type" not in obj: + self.common.log( + "EventHandler", + "run", + f"event does not have a type: {obj}", + ) + continue + except json.decoder.JSONDecodeError: + self.common.log( + "EventHandler", + "run", + f"ignoring invalid line: {line}", + ) + continue + + if obj["type"] == "new_tab": + self.common.log("EventHandler", "run", "new_tab event") + self.new_tab.emit() + + elif obj["type"] == "new_share_tab": + if ( + "filenames" in obj + and type(obj["filenames"]) is list + ): + self.new_share_tab.emit(obj["filenames"]) + else: + self.common.log( + "EventHandler", + "run", + f"invalid new_share_tab event: {obj}", + ) + + else: + self.common.log( + "EventHandler", "run", f"invalid event type: {obj}" + ) + + except: + pass + + if self.should_quit: + break + time.sleep(0.2) diff --git a/onionshare_gui/tor_connection_dialog.py b/onionshare_gui/tor_connection_dialog.py index 37c0ebc3..9c17e65c 100644 --- a/onionshare_gui/tor_connection_dialog.py +++ b/onionshare_gui/tor_connection_dialog.py @@ -157,10 +157,8 @@ class TorConnectionThread(QtCore.QThread): self.canceled_connecting_to_tor.emit() except Exception as e: - self.common.log( - "TorConnectionThread", "run", f"caught exception: {e.args[0]}" - ) - self.error_connecting_to_tor.emit(str(e.args[0])) + self.common.log("TorConnectionThread", "run", f"caught exception: {e}") + self.error_connecting_to_tor.emit(str(e)) def _tor_status_update(self, progress, summary): self.tor_status_update.emit(progress, summary) |