From 2f02cd407f4b978108256f62424e638524e8da58 Mon Sep 17 00:00:00 2001 From: AT Date: Fri, 30 Aug 2024 12:11:32 -0400 Subject: [PATCH] Only allow a single instance of program to be run at a time (#2923) Signed-off-by: Adam Treat Signed-off-by: Jared Van Bortel Co-authored-by: Jared Van Bortel --- .gitmodules | 3 ++ gpt4all-chat/CHANGELOG.md | 1 + gpt4all-chat/CMakeLists.txt | 4 ++- gpt4all-chat/deps/SingleApplication | 1 + gpt4all-chat/src/main.cpp | 47 ++++++++++++++++++++++++++--- 5 files changed, 50 insertions(+), 6 deletions(-) create mode 160000 gpt4all-chat/deps/SingleApplication diff --git a/.gitmodules b/.gitmodules index 77533f6a..b59d07fd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,3 +5,6 @@ [submodule "gpt4all-chat/usearch"] path = gpt4all-chat/deps/usearch url = https://github.com/nomic-ai/usearch.git +[submodule "gpt4all-chat/deps/SingleApplication"] + path = gpt4all-chat/deps/SingleApplication + url = https://github.com/nomic-ai/SingleApplication.git diff --git a/gpt4all-chat/CHANGELOG.md b/gpt4all-chat/CHANGELOG.md index 2bea4a7c..6f1457dd 100644 --- a/gpt4all-chat/CHANGELOG.md +++ b/gpt4all-chat/CHANGELOG.md @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Changed - Smaller default window size, dynamic minimum size, and scaling tweaks ([#2904](https://github.com/nomic-ai/gpt4all/pull/2904)) +- Only allow a single instance of program to be run at a time ([#2923](https://github.com/nomic-ai/gpt4all/pull/2923])) ### Fixed - Bring back "Auto" option for Embeddings Device as "Application default," which went missing in v3.1.0 ([#2873](https://github.com/nomic-ai/gpt4all/pull/2873)) diff --git a/gpt4all-chat/CMakeLists.txt b/gpt4all-chat/CMakeLists.txt index d8c92ead..fa70a793 100644 --- a/gpt4all-chat/CMakeLists.txt +++ b/gpt4all-chat/CMakeLists.txt @@ -105,6 +105,8 @@ if (APPLE) list(APPEND CHAT_EXE_RESOURCES "${LOCAL_EMBEDDING_MODEL_PATH}") endif() +set(QAPPLICATION_CLASS QGuiApplication) +add_subdirectory(deps/SingleApplication) add_subdirectory(src) target_sources(chat PRIVATE ${APP_ICON_RESOURCE} ${CHAT_EXE_RESOURCES}) @@ -238,7 +240,7 @@ else() PRIVATE Qt6::Quick Qt6::Svg Qt6::HttpServer Qt6::Sql Qt6::Pdf) endif() target_link_libraries(chat - PRIVATE llmodel) + PRIVATE llmodel SingleApplication) # -- install -- diff --git a/gpt4all-chat/deps/SingleApplication b/gpt4all-chat/deps/SingleApplication new file mode 160000 index 00000000..21bdef01 --- /dev/null +++ b/gpt4all-chat/deps/SingleApplication @@ -0,0 +1 @@ +Subproject commit 21bdef01eddcbd78044eea1d50b9dee08d218ff2 diff --git a/gpt4all-chat/src/main.cpp b/gpt4all-chat/src/main.cpp index 8521f4ea..6aab7b51 100644 --- a/gpt4all-chat/src/main.cpp +++ b/gpt4all-chat/src/main.cpp @@ -9,15 +9,14 @@ #include "network.h" #include +#include #include -#include #include #include -#include +#include #include #include -#include #include #include @@ -25,6 +24,29 @@ # include #endif +#ifdef Q_OS_WINDOWS +# include +#endif + +using namespace Qt::Literals::StringLiterals; + + +static void raiseWindow(QWindow *window) +{ +#ifdef Q_OS_WINDOWS + HWND hwnd = HWND(window->winId()); + + // check if window is minimized to Windows task bar + if (IsIconic(hwnd)) + ShowWindow(hwnd, SW_RESTORE); + + SetForegroundWindow(hwnd); +#else + window->show(); + window->raise(); + window->requestActivate(); +#endif +} int main(int argc, char *argv[]) { @@ -36,7 +58,15 @@ int main(int argc, char *argv[]) Logger::globalInstance(); - QGuiApplication app(argc, argv); + SingleApplication app(argc, argv, true /*allowSecondary*/); + if (app.isSecondary()) { +#ifdef Q_OS_WINDOWS + AllowSetForegroundWindow(DWORD(app.primaryPid())); +#endif + app.sendMessage("RAISE_WINDOW"); + return 0; + } + #ifdef Q_OS_LINUX app.setWindowIcon(QIcon(":/gpt4all/icons/gpt4all.svg")); #endif @@ -77,7 +107,7 @@ int main(int argc, char *argv[]) qmlRegisterSingletonInstance("localdocs", 1, 0, "LocalDocs", LocalDocs::globalInstance()); qmlRegisterUncreatableMetaObject(MySettingsEnums::staticMetaObject, "mysettingsenums", 1, 0, "MySettingsEnums", "Error: only enums"); - const QUrl url(u"qrc:/gpt4all/main.qml"_qs); + const QUrl url(u"qrc:/gpt4all/main.qml"_s); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { @@ -86,6 +116,13 @@ int main(int argc, char *argv[]) }, Qt::QueuedConnection); engine.load(url); + QObject *rootObject = engine.rootObjects().first(); + QQuickWindow *windowObject = qobject_cast(rootObject); + Q_ASSERT(windowObject); + if (windowObject) + QObject::connect(&app, &SingleApplication::receivedMessage, + windowObject, [windowObject] () { raiseWindow(windowObject); } ); + #if 0 QDirIterator it("qrc:", QDirIterator::Subdirectories); while (it.hasNext()) {