diff --git a/src/vorta/__main__.py b/src/vorta/__main__.py index 17941e31..78e9ba9f 100644 --- a/src/vorta/__main__.py +++ b/src/vorta/__main__.py @@ -19,7 +19,6 @@ def main(): # We assume that a frozen binary is a fat single-file binary made with # PyInstaller. These are not compatible with forking into background here: if not (want_foreground or frozen_binary): - print('Forking to background (see system tray).') if os.fork(): sys.exit() diff --git a/src/vorta/application.py b/src/vorta/application.py index a28ca469..c720cb4f 100644 --- a/src/vorta/application.py +++ b/src/vorta/application.py @@ -1,23 +1,22 @@ -import os import sys -import fcntl + +import qdarkstyle +from PyQt5 import QtCore import sip -from PyQt5 import QtCore -from PyQt5.QtWidgets import QApplication -import qdarkstyle - -from .i18n import init_translations, translate -from .tray_menu import TrayMenu -from .scheduler import VortaScheduler -from .models import BackupProfileModel, SettingsModel from .borg.create import BorgCreateThread -from .views.main_window import MainWindow +from .i18n import init_translations, translate +from .models import BackupProfileModel, SettingsModel +from .qt_single_application import QtSingleApplication +from .scheduler import VortaScheduler +from .tray_menu import TrayMenu from .utils import parse_args, set_tray_icon -from vorta.config import SETTINGS_DIR +from .views.main_window import MainWindow + +APP_ID = "vorta" -class VortaApp(QApplication): +class VortaApp(QtSingleApplication): """ All windows and QWidgets are children of this app. @@ -32,20 +31,12 @@ class VortaApp(QApplication): def __init__(self, args_raw, single_app=False): - # Ensure only one app instance is running. - # From https://stackoverflow.com/questions/220525/ - # ensure-a-single-instance-of-an-application-in-linux#221159 - if single_app: - pid_file = os.path.join(SETTINGS_DIR, 'vorta.pid') - lockfile = open(pid_file, 'w+') - try: - fcntl.lockf(lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB) - self.lockfile = lockfile - except OSError: - print('An instance of Vorta is already running.') - sys.exit(1) + super().__init__(APP_ID, args_raw) + if self.isRunning() and single_app: + self.sendMessage("open main window") + print('An instance of Vorta is already running. Opening main window.') + sys.exit() - super().__init__(args_raw) init_translations(self) self.setQuitOnLastWindowClosed(False) @@ -65,6 +56,7 @@ class VortaApp(QApplication): self.backup_started_event.connect(self.backup_started_event_response) self.backup_finished_event.connect(self.backup_finished_event_response) self.backup_cancelled_event.connect(self.backup_cancelled_event_response) + self.message_received_event.connect(self.message_received_event_response) def create_backup_action(self, profile_id=None): if not profile_id: @@ -98,3 +90,7 @@ class VortaApp(QApplication): def backup_cancelled_event_response(self): set_tray_icon(self.tray) + + def message_received_event_response(self, message): + if message == "open main window": + self.open_main_window_action() diff --git a/src/vorta/qt_single_application.py b/src/vorta/qt_single_application.py new file mode 100644 index 00000000..3c584770 --- /dev/null +++ b/src/vorta/qt_single_application.py @@ -0,0 +1,68 @@ +from PyQt5.QtCore import QTextStream +from PyQt5.QtNetwork import QLocalSocket, QLocalServer +from PyQt5.QtWidgets import QApplication +from PyQt5.QtCore import pyqtSignal + + +class QtSingleApplication(QApplication): + """ + Adapted from https://stackoverflow.com/a/12712362/11038610 + + Published by Johan Rade under 2-clause BSD license, opensource.org/licenses/BSD-2-Clause + """ + message_received_event = pyqtSignal(str) + + def __init__(self, id, *argv): + + super().__init__(*argv) + self._id = id + + # Is there another instance running? + self._outSocket = QLocalSocket() + self._outSocket.connectToServer(self._id) + self._isRunning = self._outSocket.waitForConnected() + + if self._isRunning: + # Yes, there is. + self._outStream = QTextStream(self._outSocket) + self._outStream.setCodec('UTF-8') + else: + # No, there isn't. + self._outSocket = None + self._outStream = None + self._inSocket = None + self._inStream = None + self._server = QLocalServer() + self._server.removeServer(self._id) + self._server.listen(self._id) + self._server.newConnection.connect(self._onNewConnection) + + def isRunning(self): + return self._isRunning + + def id(self): + return self._id + + def sendMessage(self, msg): + if not self._outStream: + return False + self._outStream << msg << '\n' + self._outStream.flush() + return self._outSocket.waitForBytesWritten() + + def _onNewConnection(self): + if self._inSocket: + self._inSocket.readyRead.disconnect(self._onReadyRead) + self._inSocket = self._server.nextPendingConnection() + if not self._inSocket: + return + self._inStream = QTextStream(self._inSocket) + self._inStream.setCodec('UTF-8') + self._inSocket.readyRead.connect(self._onReadyRead) + + def _onReadyRead(self): + while True: + msg = self._inStream.readLine() + if not msg: + break + self.message_received_event.emit(msg)