问题
I'm creating a single instance app, which is minimised to system tray, I want to show the currently running instance, then quit the new one. How can I create this functionality?
main.cpp
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QIcon>
#include <QQuickWidget>
#include <QSystemTrayIcon>
#include <QQmlContext>
#include <QQmlEngine>
#include <QSystemSemaphore>
#include <QSharedMemory>
// Declare a user-defined data type to work with an icon in QML
Q_DECLARE_METATYPE(QSystemTrayIcon::ActivationReason)
Q_DECL_EXPORT int main(int argc, char *argv[])
{
#if defined(Q_OS_WIN)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QApplication app(argc, argv);
QQmlApplicationEngine engine;
QSystemSemaphore semaphore("deploy", 1); // create semaphore
semaphore.acquire(); // Raise the semaphore, barring other instances to work with shared memory
#ifndef Q_OS_WIN32
// in linux / unix shared memory is not freed when the application terminates abnormally,
// so you need to get rid of the garbage
QSharedMemory nix_fix_shared_memory("deploy Shared Memory");
if(nix_fix_shared_memory.attach()){
nix_fix_shared_memory.detach();
}
#endif
QSharedMemory sharedMemory("deploy Shared Memory"); // Create a copy of the shared memory
bool is_running; // variable to test the already running application
if (sharedMemory.attach()){ // We are trying to attach a copy of the shared memory
// To an existing segment
is_running = true; // If successful, it determines that there is already a running instance
}else{
sharedMemory.create(1); // Otherwise allocate 1 byte of memory
is_running = false; // And determines that another instance is not running
}
semaphore.release();
// If you already run one instance of the application, then we inform the user about it
// and complete the current instance of the application
if(is_running){
return -1;
}
// Register QSystemTrayIcon in Qml
qmlRegisterType<QSystemTrayIcon>("QSystemTrayIcon", 1, 0, "QSystemTrayIcon");
// Register in QML the data type of click by tray icon
qRegisterMetaType<QSystemTrayIcon::ActivationReason>("ActivationReason");
// Set icon in the context of the engine
engine.rootContext()->setContextProperty("iconTray", QIcon(":/deploy.png"));
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main.qml
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Controls 1.4 as Tray
import QtQuick.Dialogs 1.2
import QtQuick.Extras 1.2
import QtQuick.Window 2.0
import QSystemTrayIcon 1.0
Window {
visible: true
id: application
width: 640
height: 480
title: qsTr("Test")
// system tray
QSystemTrayIcon {
id: systemTray
// Initial initialization of the system tray
Component.onCompleted: {
icon = iconTray // Set icon
toolTip = "Deploy App"
show();
if(application.visibility === Window.Hidden) {
application.show()
} else {
application.hide()
}
}
/* By clicking on the tray icon define the left or right mouse button click was.
* If left, then hide or open the application window.
* If right, then open the System Tray menu
* */
onActivated: {
if(reason === 1){
trayMenu.popup()
} else {
if(application.visibility === Window.Hidden) {
application.show()
} else {
application.hide()
}
}
}
}
// Menu system tray
Tray.Menu {
id: trayMenu
Tray.MenuItem {
text: qsTr("Show App")
onTriggered: application.show()
}
Tray.MenuItem {
text: qsTr("Quit")
onTriggered: {
systemTray.hide()
Qt.quit()
}
}
}
}
I have tried creating an object, and set its running status to false or true in my main.cpp
and in my main.qml
I check the value and quit the application.
QQmlApplicationEngine engine;
QQmlContext *context = engine.rootContext();
SingleInstance singleInstance;
context->setContextProperty("SingleInstance", &singleInstance);
if (is_running) {
singleInstance.running(true);
In my main.qml
I check if the application is running.
Connections {
target: SingleInstance
}
Component.onCompleted: {
if (SingleInstance.running) {
if(application.visibility === Window.Hidden) {
Qt.quit()
}
}
}
回答1:
What we ended up doing for single instance is to use QLocalSocket which is named pipe on Windows and local domain socket on Unix instead of QSharedMemory
which requires more tweaking to get it right. Basically you do something like this to check if the server is running (appName
must be unique application identifier, you can use e.g. QCoreApplication::applicationName):
bool isSingleInstanceRunning(QString appName) {
QLocalSocket socket;
socket.connectToServer(m_appName);
bool isOpen = socket.isOpen();
socket.close();
return isOpen;
}
If you get false
you will create your own server (keep this instance during lifetime of your application):
QLocalServer* startSingleInstanceServer(QString appName) {
QLocalServer* server = new QLocalServer;
server->setSocketOptions(QLocalServer::WorldAccessOption);
server->listen(appName);
}
You can also pass command line arguments from starting application to the already running instance - in starting instance of your application open the socket and send the command line parameters. On existing instance side just hook to QLocalServer::newConnection signal, open the socket and hook to QLocalSocket::readyRead signal.
回答2:
For this use case QtSingleApplication
has been created, which was adapted to Qt5, too:
https://github.com/qtproject/qt-solutions/tree/master/qtsingleapplication
来源:https://stackoverflow.com/questions/48060989/qt-show-application-if-currently-running