Prevent multiple instances and center settings dialog

This commit is contained in:
2026-06-01 13:20:41 +08:00
parent 7ffc009307
commit 92483bf6e1
3 changed files with 146 additions and 0 deletions
+78
View File
@@ -1,7 +1,12 @@
#include <QApplication>
#include <QByteArray>
#include <QCoreApplication>
#include <QIcon>
#include <QIODevice>
#include <QLocalServer>
#include <QLocalSocket>
#include <QObject>
#include <QString>
#include "src/config/ConfigManager.h"
#include "src/tray/TrayController.h"
@@ -9,12 +14,41 @@
#include "src/util/Logger.h"
#include "src/util/ResourcePaths.h"
namespace
{
const QString SingleInstanceServerName = QStringLiteral("Make.QtDesktopPet.SingleInstance");
const QByteArray ActivateMessage("activate\n");
constexpr int SingleInstanceConnectTimeoutMs = 500;
constexpr int SingleInstanceWriteTimeoutMs = 500;
bool notifyRunningInstance()
{
QLocalSocket socket;
socket.connectToServer(SingleInstanceServerName, QIODevice::WriteOnly);
if (!socket.waitForConnected(SingleInstanceConnectTimeoutMs))
{
return false;
}
socket.write(ActivateMessage);
socket.flush();
socket.waitForBytesWritten(SingleInstanceWriteTimeoutMs);
socket.disconnectFromServer();
return true;
}
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QApplication::setApplicationName("QtDesktopPet");
QApplication::setOrganizationName("QtDesktopPet");
if (notifyRunningInstance())
{
return 0;
}
const QIcon appIcon(ResourcePaths::appIconPath());
if (!appIcon.isNull())
{
@@ -23,6 +57,19 @@ int main(int argc, char *argv[])
Logger::info(QStringLiteral("Application started."));
QLocalServer singleInstanceServer;
QLocalServer::removeServer(SingleInstanceServerName);
if (!singleInstanceServer.listen(SingleInstanceServerName))
{
if (notifyRunningInstance())
{
return 0;
}
Logger::warning(QStringLiteral("Unable to start single instance server: ")
+ singleInstanceServer.errorString());
}
ConfigManager configManager;
PetWindow window;
window.applyAppConfig(configManager.loadAppConfig());
@@ -31,6 +78,37 @@ int main(int argc, char *argv[])
window.setSettingsFallbackInContextMenuEnabled(!trayController.isAvailable());
trayController.show();
QObject::connect(&singleInstanceServer, &QLocalServer::newConnection, [&singleInstanceServer, &window]() {
while (singleInstanceServer.hasPendingConnections())
{
QLocalSocket *socket = singleInstanceServer.nextPendingConnection();
if (socket == nullptr)
{
continue;
}
const auto handleActivationMessage = [socket, &window]() {
const QByteArray message = socket->readAll().trimmed();
if (message == ActivateMessage.trimmed())
{
window.activateFromExternalInstance();
}
};
QObject::connect(socket, &QLocalSocket::readyRead, handleActivationMessage);
QObject::connect(socket, &QLocalSocket::disconnected, [socket, handleActivationMessage]() {
if (socket->bytesAvailable() > 0)
{
handleActivationMessage();
}
socket->deleteLater();
});
if (socket->bytesAvailable() > 0)
{
handleActivationMessage();
}
}
});
QObject::connect(&app, &QCoreApplication::aboutToQuit, [&configManager, &window]() {
if (!configManager.saveAppConfig(window.currentAppConfig()))
{
+67
View File
@@ -14,6 +14,7 @@
#include "SettingsDialog.h"
#include <QAction>
#include <QApplication>
#include <QContextMenuEvent>
#include <QCursor>
#include <QDialog>
@@ -89,6 +90,46 @@ AppConfig normalizedAppConfig(AppConfig config)
return config;
}
QScreen *screenForPopup(const QWidget *reference)
{
if (reference != nullptr)
{
if (QScreen *screen = QGuiApplication::screenAt(reference->frameGeometry().center()))
{
return screen;
}
}
if (QScreen *screen = QGuiApplication::screenAt(QCursor::pos()))
{
return screen;
}
return QGuiApplication::primaryScreen();
}
void centerDialogOnScreen(QDialog *dialog, const QWidget *reference)
{
if (dialog == nullptr)
{
return;
}
QScreen *screen = screenForPopup(reference);
if (screen == nullptr)
{
return;
}
const QRect availableGeometry = screen->availableGeometry();
const QSize dialogSize = dialog->size().isValid()
? dialog->size()
: dialog->sizeHint();
const int x = availableGeometry.left() + qMax(0, (availableGeometry.width() - dialogSize.width()) / 2);
const int y = availableGeometry.top() + qMax(0, (availableGeometry.height() - dialogSize.height()) / 2);
dialog->move(QPoint(x, y));
}
QString userVisibleErrorMessage(const ChatResponse &response)
{
QString message = response.errorMessage.trimmed();
@@ -284,6 +325,7 @@ void PetWindow::openSettingsDialog()
}, [this]() {
clearConversation();
}, this);
centerDialogOnScreen(&dialog, this);
if (dialog.exec() != QDialog::Accepted)
{
return;
@@ -301,6 +343,31 @@ void PetWindow::openSettingsDialog()
}
}
void PetWindow::activateFromExternalInstance()
{
if (!isVisible())
{
show();
resumeAnimation();
}
if (isMinimized())
{
setWindowState(windowState() & ~Qt::WindowMinimized);
}
if (QWidget *modalWidget = QApplication::activeModalWidget())
{
modalWidget->raise();
modalWidget->activateWindow();
return;
}
raise();
activateWindow();
updateBubblePosition();
}
void PetWindow::setSettingsFallbackInContextMenuEnabled(bool enabled)
{
m_settingsFallbackInContextMenuEnabled = enabled;
+1
View File
@@ -37,6 +37,7 @@ public:
void applyAppConfig(const AppConfig &config);
AppConfig currentAppConfig() const;
void openSettingsDialog();
void activateFromExternalInstance();
void setSettingsFallbackInContextMenuEnabled(bool enabled);
void pauseAnimation();
void resumeAnimation();