Implement QtSingleApplication by @Hofer-Julian

- Use C-style single app implementation with messaging across instances of the same app.
- Open main window when application already runs.
This commit is contained in:
Hofer-Julian 2019-04-07 09:36:46 +02:00 committed by Manuel Riel
parent b8a3819303
commit ab04d84fc1
3 changed files with 90 additions and 27 deletions

View File

@ -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()

View File

@ -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()

View File

@ -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)