mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-26 00:23:55 -07:00
[Application] Add single instance guard and mime types.
Took 2 hours 39 minutes Took 18 minutes Took 5 minutes Took 12 seconds
This commit is contained in:
parent
45d0cedb5c
commit
c413b0627d
8 changed files with 189 additions and 1 deletions
|
|
@ -34,5 +34,31 @@
|
||||||
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
|
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
|
||||||
<key>NSHighResolutionCapable</key>
|
<key>NSHighResolutionCapable</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>CFBundleDocumentTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeExtensions</key>
|
||||||
|
<array>
|
||||||
|
<string>cod</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>Cockatrice</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Default</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleURLName</key>
|
||||||
|
<string>Cockatrice</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>cockatrice</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
||||||
|
|
@ -294,6 +294,20 @@ Section "Application" SecApplication
|
||||||
SetShellVarContext all
|
SetShellVarContext all
|
||||||
SetOutPath "$INSTDIR"
|
SetOutPath "$INSTDIR"
|
||||||
|
|
||||||
|
${If} $PortableMode = 0
|
||||||
|
|
||||||
|
; --- Register .cod file type ---
|
||||||
|
WriteRegStr HKCR ".cod" "" "Cockatrice"
|
||||||
|
WriteRegStr HKCR "Cockatrice" "" "Cockatrice Deck File"
|
||||||
|
WriteRegStr HKCR "Cockatrice\shell\open\command" "" '"$INSTDIR\cockatrice.exe" "%1"'
|
||||||
|
|
||||||
|
; --- Register custom URI protocol ---
|
||||||
|
WriteRegStr HKCR "cockatrice" "" "URL: Cockatrice Protocol"
|
||||||
|
WriteRegStr HKCR "cockatrice" "URL Protocol" ""
|
||||||
|
WriteRegStr HKCR "cockatrice\shell\open\command" "" '"$INSTDIR\cockatrice.exe" "%1"'
|
||||||
|
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
${If} $PortableMode = 1
|
${If} $PortableMode = 1
|
||||||
${AndIf} ${FileExists} "$INSTDIR\portable.dat"
|
${AndIf} ${FileExists} "$INSTDIR\portable.dat"
|
||||||
; upgrade portable mode
|
; upgrade portable mode
|
||||||
|
|
@ -402,6 +416,9 @@ Section "un.Application" UnSecApplication
|
||||||
RMDir "$SMPROGRAMS\Cockatrice"
|
RMDir "$SMPROGRAMS\Cockatrice"
|
||||||
|
|
||||||
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice"
|
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice"
|
||||||
|
DeleteRegKey HKCR ".cod"
|
||||||
|
DeleteRegKey HKCR "Cockatrice"
|
||||||
|
DeleteRegKey HKCR "cockatrice"
|
||||||
SectionEnd
|
SectionEnd
|
||||||
|
|
||||||
; unselected because it is /o
|
; unselected because it is /o
|
||||||
|
|
|
||||||
|
|
@ -283,6 +283,7 @@ set(cockatrice_SOURCES
|
||||||
src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp
|
src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp
|
||||||
src/interface/window_main.cpp
|
src/interface/window_main.cpp
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
|
src/single_instance_manager.cpp
|
||||||
src/interface/widgets/tabs/abstract_tab_deck_editor.cpp
|
src/interface/widgets/tabs/abstract_tab_deck_editor.cpp
|
||||||
src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp
|
src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp
|
||||||
src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.cpp
|
src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.cpp
|
||||||
|
|
@ -406,6 +407,11 @@ set(DESKTOPDIR
|
||||||
CACHE STRING "desktop file destination"
|
CACHE STRING "desktop file destination"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set(MIMEDIR
|
||||||
|
share/mime/packages
|
||||||
|
CACHE STRING "mime file destination"
|
||||||
|
)
|
||||||
|
|
||||||
set(COCKATRICE_MAC_QM_INSTALL_DIR "cockatrice.app/Contents/Resources/translations")
|
set(COCKATRICE_MAC_QM_INSTALL_DIR "cockatrice.app/Contents/Resources/translations")
|
||||||
set(COCKATRICE_UNIX_QM_INSTALL_DIR "share/cockatrice/translations")
|
set(COCKATRICE_UNIX_QM_INSTALL_DIR "share/cockatrice/translations")
|
||||||
set(COCKATRICE_WIN32_QM_INSTALL_DIR "translations")
|
set(COCKATRICE_WIN32_QM_INSTALL_DIR "translations")
|
||||||
|
|
@ -490,6 +496,7 @@ if(UNIX)
|
||||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/resources/cockatrice.png DESTINATION ${ICONDIR}/hicolor/48x48/apps)
|
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/resources/cockatrice.png DESTINATION ${ICONDIR}/hicolor/48x48/apps)
|
||||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/resources/cockatrice.svg DESTINATION ${ICONDIR}/hicolor/scalable/apps)
|
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/resources/cockatrice.svg DESTINATION ${ICONDIR}/hicolor/scalable/apps)
|
||||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/cockatrice.desktop DESTINATION ${DESKTOPDIR})
|
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/cockatrice.desktop DESTINATION ${DESKTOPDIR})
|
||||||
|
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/cockatrice-cod.xml DESTINATION ${MIMEDIR})
|
||||||
endif()
|
endif()
|
||||||
elseif(WIN32)
|
elseif(WIN32)
|
||||||
install(TARGETS cockatrice RUNTIME DESTINATION ./)
|
install(TARGETS cockatrice RUNTIME DESTINATION ./)
|
||||||
|
|
|
||||||
7
cockatrice/cockatrice-cod.xml
Normal file
7
cockatrice/cockatrice-cod.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
|
||||||
|
<mime-type type="application/x-cockatrice">
|
||||||
|
<comment>Cockatrice Deck File</comment>
|
||||||
|
<glob pattern="*.cod"/>
|
||||||
|
</mime-type>
|
||||||
|
</mime-info>
|
||||||
|
|
@ -6,3 +6,5 @@ Name=Cockatrice
|
||||||
Exec=cockatrice
|
Exec=cockatrice
|
||||||
Icon=cockatrice
|
Icon=cockatrice
|
||||||
Categories=Game;CardGame;
|
Categories=Game;CardGame;
|
||||||
|
MimeType=application/x-cockatrice;
|
||||||
|
X-Scheme-Handler/cockatrice=true
|
||||||
|
|
@ -28,7 +28,9 @@
|
||||||
#include "interface/pixel_map_generator.h"
|
#include "interface/pixel_map_generator.h"
|
||||||
#include "interface/theme_manager.h"
|
#include "interface/theme_manager.h"
|
||||||
#include "interface/widgets/dialogs/dlg_settings.h"
|
#include "interface/widgets/dialogs/dlg_settings.h"
|
||||||
|
#include "interface/widgets/tabs/tab_supervisor.h"
|
||||||
#include "interface/window_main.h"
|
#include "interface/window_main.h"
|
||||||
|
#include "single_instance_manager.h"
|
||||||
#include "version_string.h"
|
#include "version_string.h"
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
|
@ -174,6 +176,7 @@ int main(int argc, char *argv[])
|
||||||
SetUnhandledExceptionFilter(CockatriceUnhandledExceptionFilter);
|
SetUnhandledExceptionFilter(CockatriceUnhandledExceptionFilter);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Logging setup
|
||||||
#ifdef Q_OS_APPLE
|
#ifdef Q_OS_APPLE
|
||||||
// <build>/cockatrice/cockatrice.app/Contents/MacOS/cockatrice
|
// <build>/cockatrice/cockatrice.app/Contents/MacOS/cockatrice
|
||||||
const QByteArray configPath = "../../../qtlogging.ini";
|
const QByteArray configPath = "../../../qtlogging.ini";
|
||||||
|
|
@ -191,6 +194,7 @@ int main(int argc, char *argv[])
|
||||||
// Set the QT_LOGGING_CONF environment variable
|
// Set the QT_LOGGING_CONF environment variable
|
||||||
qputenv("QT_LOGGING_CONF", configPath);
|
qputenv("QT_LOGGING_CONF", configPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
qSetMessagePattern(
|
qSetMessagePattern(
|
||||||
"\033[0m[%{time yyyy-MM-dd h:mm:ss.zzz} "
|
"\033[0m[%{time yyyy-MM-dd h:mm:ss.zzz} "
|
||||||
"%{if-debug}\033[36mD%{endif}%{if-info}\033[32mI%{endif}%{if-warning}\033[33mW%{endif}%{if-critical}\033[31mC%{"
|
"%{if-debug}\033[36mD%{endif}%{if-info}\033[32mI%{endif}%{if-warning}\033[33mW%{endif}%{if-critical}\033[31mC%{"
|
||||||
|
|
@ -200,6 +204,7 @@ int main(int argc, char *argv[])
|
||||||
QObject::connect(&app, &QApplication::lastWindowClosed, &app, &QApplication::quit);
|
QObject::connect(&app, &QApplication::lastWindowClosed, &app, &QApplication::quit);
|
||||||
|
|
||||||
qInstallMessageHandler(CockatriceLogger);
|
qInstallMessageHandler(CockatriceLogger);
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
app.addLibraryPath(app.applicationDirPath() + "/plugins");
|
app.addLibraryPath(app.applicationDirPath() + "/plugins");
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -215,6 +220,7 @@ int main(int argc, char *argv[])
|
||||||
qApp->setAttribute(Qt::AA_DontShowIconsInMenus, true);
|
qApp->setAttribute(Qt::AA_DontShowIconsInMenus, true);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Translations
|
||||||
#ifdef Q_OS_MAC
|
#ifdef Q_OS_MAC
|
||||||
translationPath = qApp->applicationDirPath() + "/../Resources/translations";
|
translationPath = qApp->applicationDirPath() + "/../Resources/translations";
|
||||||
#elif defined(Q_OS_WIN)
|
#elif defined(Q_OS_WIN)
|
||||||
|
|
@ -223,6 +229,7 @@ int main(int argc, char *argv[])
|
||||||
translationPath = qApp->applicationDirPath() + "/../share/cockatrice/translations";
|
translationPath = qApp->applicationDirPath() + "/../share/cockatrice/translations";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Command-line parser
|
||||||
QCommandLineParser parser;
|
QCommandLineParser parser;
|
||||||
parser.setApplicationDescription("Cockatrice");
|
parser.setApplicationDescription("Cockatrice");
|
||||||
parser.addHelpOption();
|
parser.addHelpOption();
|
||||||
|
|
@ -271,6 +278,21 @@ int main(int argc, char *argv[])
|
||||||
// then reload the DB. otherwise just reload the DB
|
// then reload the DB. otherwise just reload the DB
|
||||||
SpoilerBackgroundUpdater spoilerBackgroundUpdater;
|
SpoilerBackgroundUpdater spoilerBackgroundUpdater;
|
||||||
|
|
||||||
|
// --- Handle files or URLs passed at startup ---
|
||||||
|
SingleInstanceManager instance;
|
||||||
|
QStringList startupFiles;
|
||||||
|
|
||||||
|
// Collect command-line files/URLs
|
||||||
|
for (int i = 1; i < argc; ++i) {
|
||||||
|
QString arg = QString::fromLocal8Bit(argv[i]);
|
||||||
|
startupFiles.append(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!instance.tryRun(startupFiles)) {
|
||||||
|
// Another instance received our files, exit
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
ui.show();
|
ui.show();
|
||||||
qCInfo(MainLog) << "ui.show() finished";
|
qCInfo(MainLog) << "ui.show() finished";
|
||||||
|
|
||||||
|
|
@ -280,7 +302,47 @@ int main(int argc, char *argv[])
|
||||||
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
|
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
|
||||||
app.setAttribute(Qt::AA_UseHighDpiPixmaps);
|
app.setAttribute(Qt::AA_UseHighDpiPixmaps);
|
||||||
#endif
|
#endif
|
||||||
app.exec();
|
|
||||||
|
for (const QString &file : startupFiles) {
|
||||||
|
if (file.startsWith("cockatrice://")) {
|
||||||
|
// ui.openUrl(QUrl(file));
|
||||||
|
} else if (QFileInfo(file).exists()) {
|
||||||
|
std::optional<LoadedDeck> deckOpt =
|
||||||
|
DeckLoader::loadFromFile(file, DeckFileFormat::getFormatFromName(file), true);
|
||||||
|
if (deckOpt) {
|
||||||
|
ui.getTabSupervisor()->openDeckInNewTab(deckOpt.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to future file/URL events from other instances
|
||||||
|
QObject::connect(&instance, &SingleInstanceManager::filesReceived, [&ui](const QStringList &files) {
|
||||||
|
for (const QString &file : files) {
|
||||||
|
if (file.startsWith("cockatrice://")) {
|
||||||
|
// ui.openUrl(QUrl(file));
|
||||||
|
} else if (QFileInfo(file).exists()) {
|
||||||
|
std::optional<LoadedDeck> deckOpt =
|
||||||
|
DeckLoader::loadFromFile(file, DeckFileFormat::getFormatFromName(file), true);
|
||||||
|
if (deckOpt) {
|
||||||
|
ui.getTabSupervisor()->openDeckInNewTab(deckOpt.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
#ifdef Q_OS_MAC
|
||||||
|
// macOS: handle files opened via Finder after startup
|
||||||
|
QObject::connect(&app, &QApplication::fileOpen, [&ui](const QString &filePath) {
|
||||||
|
qDebug() << "macOS opened file:" << filePath;
|
||||||
|
std::optional<LoadedDeck> deckOpt =
|
||||||
|
DeckLoader::loadFromFile(filePath, DeckFileFormat::getFormatFromName(filePath), true);
|
||||||
|
if (deckOpt) {
|
||||||
|
ui.getTabSupervisor()->openDeckInNewTab(deckOpt.value());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int ret = app.exec();
|
||||||
|
|
||||||
qCInfo(MainLog) << "Event loop finished, terminating...";
|
qCInfo(MainLog) << "Event loop finished, terminating...";
|
||||||
delete rng;
|
delete rng;
|
||||||
|
|
|
||||||
1
cockatrice/src/single_instance_manager.cpp
Normal file
1
cockatrice/src/single_instance_manager.cpp
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
#include "single_instance_manager.h"
|
||||||
66
cockatrice/src/single_instance_manager.h
Normal file
66
cockatrice/src/single_instance_manager.h
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
#ifndef COCKATRICE_SINGLE_INSTANCE_MANAGER_H
|
||||||
|
#define COCKATRICE_SINGLE_INSTANCE_MANAGER_H
|
||||||
|
|
||||||
|
#include <QDataStream>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QLocalServer>
|
||||||
|
#include <QLocalSocket>
|
||||||
|
|
||||||
|
class SingleInstanceManager : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit SingleInstanceManager(QObject *parent = nullptr) : QObject(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool tryRun(const QStringList &initialFiles)
|
||||||
|
{
|
||||||
|
serverName = "CockatriceSingleInstance";
|
||||||
|
|
||||||
|
QLocalSocket socket;
|
||||||
|
socket.connectToServer(serverName);
|
||||||
|
if (socket.waitForConnected(100)) {
|
||||||
|
// Another instance is running, send files/URLs to it
|
||||||
|
QDataStream out(&socket);
|
||||||
|
out << initialFiles;
|
||||||
|
socket.flush();
|
||||||
|
socket.waitForBytesWritten(1000);
|
||||||
|
|
||||||
|
return false; // Do not continue in this process
|
||||||
|
}
|
||||||
|
|
||||||
|
// No other instance, create server
|
||||||
|
server = new QLocalServer(this);
|
||||||
|
connect(server, &QLocalServer::newConnection, this, &SingleInstanceManager::receiveFiles);
|
||||||
|
if (!server->listen(serverName)) {
|
||||||
|
QLocalServer::removeServer(serverName);
|
||||||
|
server->listen(serverName);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void filesReceived(const QStringList &files);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void receiveFiles()
|
||||||
|
{
|
||||||
|
QLocalSocket *clientConnection = server->nextPendingConnection();
|
||||||
|
connect(clientConnection, &QLocalSocket::disconnected, clientConnection, &QLocalSocket::deleteLater);
|
||||||
|
clientConnection->waitForReadyRead(1000);
|
||||||
|
|
||||||
|
QDataStream in(clientConnection);
|
||||||
|
QStringList files;
|
||||||
|
in >> files;
|
||||||
|
|
||||||
|
emit filesReceived(files);
|
||||||
|
clientConnection->disconnectFromServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString serverName;
|
||||||
|
QLocalServer *server = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // COCKATRICE_SINGLE_INSTANCE_MANAGER_H
|
||||||
Loading…
Add table
Add a link
Reference in a new issue