aboutsummaryrefslogtreecommitdiff
path: root/onionshare_gui
diff options
context:
space:
mode:
authorMicah Lee <micah@micahflee.com>2020-07-05 21:54:29 -0700
committerGitHub <noreply@github.com>2020-07-05 21:54:29 -0700
commit0b4821c905ecfefdf438e4c98e5e79592b390ced (patch)
treee36ff23c5e3f129651d3904bfacc17d21082dcc7 /onionshare_gui
parent0e16d3b69e8d6f0ee4b10291a49b67ff347e7dd7 (diff)
parent7ce0f18f360f8543af0e4723c65d9175977935d8 (diff)
downloadonionshare-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__.py46
-rw-r--r--onionshare_gui/event_handler.py89
-rw-r--r--onionshare_gui/gui_common.py8
-rw-r--r--onionshare_gui/settings_dialog.py25
-rw-r--r--onionshare_gui/tab/mode/history.py4
-rw-r--r--onionshare_gui/tab/mode/receive_mode/__init__.py9
-rw-r--r--onionshare_gui/tab_widget.py18
-rw-r--r--onionshare_gui/threads.py84
-rw-r--r--onionshare_gui/tor_connection_dialog.py6
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)