mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-28 08:52:19 -07:00
Better single-instance handoff, url intent harded
copy game link context-menu Polish for installers Took 35 minutes Took 8 seconds
This commit is contained in:
parent
a9077aa65b
commit
0a1a80eadd
27 changed files with 257 additions and 84 deletions
|
|
@ -115,4 +115,4 @@
|
||||||
</array>
|
</array>
|
||||||
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
||||||
|
|
@ -416,9 +416,18 @@ 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"
|
; Only remove the file/protocol associations if we registered them (i.e. the
|
||||||
DeleteRegKey HKCR "cockatrice"
|
; install was not portable) and .cod is still owned by Cockatrice, so we don't
|
||||||
|
; clobber a .cod association installed by another application.
|
||||||
|
${If} Not ${FileExists} "$INSTDIR\portable.dat"
|
||||||
|
ReadRegStr $0 HKCR ".cod" ""
|
||||||
|
${If} $0 == "Cockatrice"
|
||||||
|
DeleteRegKey HKCR ".cod"
|
||||||
|
DeleteRegKey HKCR "Cockatrice"
|
||||||
|
DeleteRegKey HKCR "cockatrice"
|
||||||
|
${EndIf}
|
||||||
|
${EndIf}
|
||||||
SectionEnd
|
SectionEnd
|
||||||
|
|
||||||
; unselected because it is /o
|
; unselected because it is /o
|
||||||
|
|
|
||||||
|
|
@ -530,6 +530,22 @@ if(UNIX)
|
||||||
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})
|
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/cockatrice-cod.xml DESTINATION ${MIMEDIR})
|
||||||
|
|
||||||
|
# Refresh the freedesktop databases so the file associations and scheme
|
||||||
|
# handler register without requiring the user to run them manually. The
|
||||||
|
# tools may be missing on minimal systems; that is fine, packaging systems
|
||||||
|
# usually refresh these databases through their own triggers.
|
||||||
|
find_program(UPDATE_MIME_DATABASE update-mime-database)
|
||||||
|
if(UPDATE_MIME_DATABASE)
|
||||||
|
install(CODE "execute_process(COMMAND \"${UPDATE_MIME_DATABASE}\" \"${CMAKE_INSTALL_PREFIX}/share/mime\")")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
find_program(UPDATE_DESKTOP_DATABASE update-desktop-database)
|
||||||
|
if(UPDATE_DESKTOP_DATABASE)
|
||||||
|
install(
|
||||||
|
CODE "execute_process(COMMAND \"${UPDATE_DESKTOP_DATABASE}\" \"${CMAKE_INSTALL_PREFIX}/share/applications\")"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
elseif(WIN32)
|
elseif(WIN32)
|
||||||
install(TARGETS cockatrice RUNTIME DESTINATION ./)
|
install(TARGETS cockatrice RUNTIME DESTINATION ./)
|
||||||
|
|
|
||||||
|
|
@ -4,4 +4,4 @@
|
||||||
<comment>Cockatrice Deck File</comment>
|
<comment>Cockatrice Deck File</comment>
|
||||||
<glob pattern="*.cod"/>
|
<glob pattern="*.cod"/>
|
||||||
</mime-type>
|
</mime-type>
|
||||||
</mime-info>
|
</mime-info>
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,4 @@ Exec=cockatrice %U
|
||||||
Icon=cockatrice
|
Icon=cockatrice
|
||||||
Categories=Game;CardGame;
|
Categories=Game;CardGame;
|
||||||
MimeType=application/x-cockatrice;
|
MimeType=application/x-cockatrice;
|
||||||
X-Scheme-Handler/cockatrice=true
|
X-Scheme-Handler/cockatrice=true
|
||||||
|
|
|
||||||
|
|
@ -5,29 +5,33 @@
|
||||||
#include <QFileOpenEvent>
|
#include <QFileOpenEvent>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Event filter that catches QFileOpenEvent URLs matching a scheme
|
* @brief Event filter that catches QFileOpenEvent URLs matching a scheme and
|
||||||
* prefix and re-emits them as urlReceived().
|
* re-emits them as urlReceived().
|
||||||
*
|
*
|
||||||
* On macOS, when the application is registered as a URL scheme handler, the
|
* On macOS, when the application is registered as a URL scheme handler, the
|
||||||
* OS delivers incoming URLs via QFileOpenEvent on the QApplication object.
|
* OS delivers incoming URLs via QFileOpenEvent on the QApplication object.
|
||||||
* Install this filter on QApplication to intercept them:
|
* Install this filter on QApplication to intercept them:
|
||||||
*
|
*
|
||||||
* @code
|
* @code
|
||||||
* UrlSchemeEventFilter filter(QStringLiteral("cockatrice://"));
|
* UrlSchemeEventFilter filter(QStringList{QStringLiteral("cockatrice")});
|
||||||
* QObject::connect(&filter, &UrlSchemeEventFilter::urlReceived,
|
* QObject::connect(&filter, &UrlSchemeEventFilter::urlReceived,
|
||||||
* &mainWindow, &MainWindow::handleUrl);
|
* &mainWindow, &MainWindow::handleUrl);
|
||||||
* app.installEventFilter(&filter);
|
* app.installEventFilter(&filter);
|
||||||
* @endcode
|
* @endcode
|
||||||
|
*
|
||||||
|
* Note: the strings are compared against QUrl::scheme(), so they must be
|
||||||
|
* written without the "://" suffix (e.g. "cockatrice", not "cockatrice://").
|
||||||
*/
|
*/
|
||||||
class UrlSchemeEventFilter : public QObject
|
class UrlSchemeEventFilter : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit UrlSchemeEventFilter(const QStringList &schemePrefix, QObject *parent = nullptr)
|
explicit UrlSchemeEventFilter(const QStringList &schemes, QObject *parent = nullptr)
|
||||||
: QObject(parent), m_prefixes(schemePrefix)
|
: QObject(parent), prefixes(schemes)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,7 +46,7 @@ public:
|
||||||
|
|
||||||
const QUrl url = fileEvent->url();
|
const QUrl url = fileEvent->url();
|
||||||
|
|
||||||
for (auto prefix : m_prefixes) {
|
for (const auto &prefix : prefixes) {
|
||||||
if (url.scheme() == prefix) {
|
if (url.scheme() == prefix) {
|
||||||
emit urlReceived(url.toString());
|
emit urlReceived(url.toString());
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -59,7 +63,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QStringList m_prefixes;
|
QStringList prefixes;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // COCKATRICE_URL_SCHEME_EVENT_FILTER_H
|
#endif // COCKATRICE_URL_SCHEME_EVENT_FILTER_H
|
||||||
|
|
|
||||||
|
|
@ -30,3 +30,19 @@ void Intent::runDependency(Intent *dependency)
|
||||||
|
|
||||||
dependency->execute();
|
dependency->execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Intent::emitFinished()
|
||||||
|
{
|
||||||
|
if (!completed) {
|
||||||
|
completed = true;
|
||||||
|
emit finished();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Intent::emitFailed(const QString &reason)
|
||||||
|
{
|
||||||
|
if (!completed) {
|
||||||
|
completed = true;
|
||||||
|
emit failed(reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,13 @@ protected:
|
||||||
|
|
||||||
// Helper to chain another intent
|
// Helper to chain another intent
|
||||||
void runDependency(Intent *dependency);
|
void runDependency(Intent *dependency);
|
||||||
|
|
||||||
|
// Emit the outcome exactly once; ignore late signals after the intent is done.
|
||||||
|
void emitFinished();
|
||||||
|
void emitFailed(const QString &reason);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool completed = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // COCKATRICE_INTENT_H
|
#endif // COCKATRICE_INTENT_H
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
#include "intent_disconnect_from_server.h"
|
#include "intent_disconnect_from_server.h"
|
||||||
|
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
IntentConnectToServer::IntentConnectToServer(RemoteClient *_remoteClient, ContextConnectToServer *_context)
|
IntentConnectToServer::IntentConnectToServer(RemoteClient *_remoteClient, ContextConnectToServer *_context)
|
||||||
: Intent(), remoteClient(_remoteClient), context(_context)
|
: Intent(), remoteClient(_remoteClient), context(_context)
|
||||||
{
|
{
|
||||||
|
|
@ -16,10 +18,14 @@ void IntentConnectToServer::onPreconditionSatisfied()
|
||||||
{
|
{
|
||||||
remoteClient->connectToServer(context->hostname, context->port.toUInt(), context->username, context->password);
|
remoteClient->connectToServer(context->hostname, context->port.toUInt(), context->username, context->password);
|
||||||
connect(remoteClient, &RemoteClient::statusChanged, this, &IntentConnectToServer::onStatusChanged);
|
connect(remoteClient, &RemoteClient::statusChanged, this, &IntentConnectToServer::onStatusChanged);
|
||||||
connect(remoteClient, &RemoteClient::loginError, this,
|
connect(remoteClient, &RemoteClient::socketError, this, &IntentConnectToServer::onSocketError);
|
||||||
[this](Response::ResponseCode, const QString &reason, quint32, const QList<QString> &) {
|
connect(
|
||||||
emit failed(reason);
|
remoteClient, &RemoteClient::loginError, this,
|
||||||
});
|
[this](Response::ResponseCode, const QString &reason, quint32, const QList<QString> &) { emitFailed(reason); });
|
||||||
|
|
||||||
|
QTimer::singleShot(15000, this, [this]() {
|
||||||
|
emitFailed(tr("Timed out while connecting to %1:%2").arg(context->hostname, context->port));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void IntentConnectToServer::onPreconditionNotSatisfied()
|
void IntentConnectToServer::onPreconditionNotSatisfied()
|
||||||
|
|
@ -30,6 +36,11 @@ void IntentConnectToServer::onPreconditionNotSatisfied()
|
||||||
void IntentConnectToServer::onStatusChanged(ClientStatus status)
|
void IntentConnectToServer::onStatusChanged(ClientStatus status)
|
||||||
{
|
{
|
||||||
if (status == ClientStatus::StatusLoggedIn) {
|
if (status == ClientStatus::StatusLoggedIn) {
|
||||||
emit finished();
|
emitFinished();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IntentConnectToServer::onSocketError(const QString &errorString)
|
||||||
|
{
|
||||||
|
emitFailed(tr("Failed to connect to %1:%2: %3").arg(context->hostname, context->port, errorString));
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ private:
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onStatusChanged(ClientStatus status);
|
void onStatusChanged(ClientStatus status);
|
||||||
|
void onSocketError(const QString &errorString);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // COCKATRICE_INTENT_CONNECT_TO_SERVER_H
|
#endif // COCKATRICE_INTENT_CONNECT_TO_SERVER_H
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ bool IntentDisconnectFromServer::checkPrecondition() const
|
||||||
|
|
||||||
void IntentDisconnectFromServer::onPreconditionSatisfied()
|
void IntentDisconnectFromServer::onPreconditionSatisfied()
|
||||||
{
|
{
|
||||||
emit finished();
|
emitFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
void IntentDisconnectFromServer::onPreconditionNotSatisfied()
|
void IntentDisconnectFromServer::onPreconditionNotSatisfied()
|
||||||
|
|
@ -24,6 +24,6 @@ void IntentDisconnectFromServer::onPreconditionNotSatisfied()
|
||||||
void IntentDisconnectFromServer::onStatusChanged(ClientStatus status)
|
void IntentDisconnectFromServer::onStatusChanged(ClientStatus status)
|
||||||
{
|
{
|
||||||
if (status == ClientStatus::StatusDisconnected) {
|
if (status == ClientStatus::StatusDisconnected) {
|
||||||
emit finished();
|
emitFinished();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@
|
||||||
|
|
||||||
IntentJoinServerGame::IntentJoinServerGame(TabSupervisor *_tabSupervisor,
|
IntentJoinServerGame::IntentJoinServerGame(TabSupervisor *_tabSupervisor,
|
||||||
RemoteClient *_remoteClient,
|
RemoteClient *_remoteClient,
|
||||||
ContextJoinGame *_context)
|
std::unique_ptr<ContextJoinGame> _context)
|
||||||
: Intent(), tabSupervisor(_tabSupervisor), remoteClient(_remoteClient), context(_context)
|
: Intent(), tabSupervisor(_tabSupervisor), remoteClient(_remoteClient), context(_context.release())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -25,6 +25,9 @@ bool IntentJoinServerGame::checkPrecondition() const
|
||||||
if (remoteClient->peerName() != context->roomContext.serverContext.hostname) {
|
if (remoteClient->peerName() != context->roomContext.serverContext.hostname) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (QString::number(remoteClient->peerPort()) != context->roomContext.serverContext.port) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!tabSupervisor->getRoomTabs().contains(context->roomContext.roomId)) {
|
if (!tabSupervisor->getRoomTabs().contains(context->roomContext.roomId)) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -53,8 +56,7 @@ bool IntentJoinServerGame::tryJoinGame(TabRoom *room)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (room->getGameSelector()->joinGameById(context->gameId)) {
|
if (room->getGameSelector()->joinGameById(context->gameId)) {
|
||||||
joined = true;
|
emitFinished();
|
||||||
emit finished();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,9 +72,5 @@ void IntentJoinServerGame::waitForGame(TabRoom *room)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
QTimer::singleShot(15000, this, [this]() {
|
QTimer::singleShot(15000, this, [this]() { emitFailed(tr("Game %1 not found in the room").arg(context->gameId)); });
|
||||||
if (!joined) {
|
|
||||||
emit failed(tr("Game %1 not found in the room").arg(context->gameId));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
#include "remote_client.h"
|
#include "remote_client.h"
|
||||||
|
|
||||||
#include <QScopedPointer>
|
#include <QScopedPointer>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
class TabRoom;
|
class TabRoom;
|
||||||
class TabSupervisor;
|
class TabSupervisor;
|
||||||
|
|
@ -15,7 +16,9 @@ class IntentJoinServerGame : public Intent
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
IntentJoinServerGame(TabSupervisor *_tabSupervisor, RemoteClient *_remoteClient, ContextJoinGame *_context);
|
IntentJoinServerGame(TabSupervisor *_tabSupervisor,
|
||||||
|
RemoteClient *_remoteClient,
|
||||||
|
std::unique_ptr<ContextJoinGame> _context);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool checkPrecondition() const override;
|
bool checkPrecondition() const override;
|
||||||
|
|
@ -29,7 +32,6 @@ private:
|
||||||
TabSupervisor *tabSupervisor;
|
TabSupervisor *tabSupervisor;
|
||||||
RemoteClient *remoteClient;
|
RemoteClient *remoteClient;
|
||||||
QScopedPointer<ContextJoinGame> context;
|
QScopedPointer<ContextJoinGame> context;
|
||||||
bool joined = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // COCKATRICE_INTENT_JOIN_SERVER_GAME_H
|
#endif // COCKATRICE_INTENT_JOIN_SERVER_GAME_H
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@
|
||||||
#include "../widgets/tabs/tab_supervisor.h"
|
#include "../widgets/tabs/tab_supervisor.h"
|
||||||
#include "intent_connect_to_server.h"
|
#include "intent_connect_to_server.h"
|
||||||
|
|
||||||
|
#include <QTimer>
|
||||||
|
#include <libcockatrice/protocol/pb/serverinfo_room.pb.h>
|
||||||
|
|
||||||
IntentJoinServerRoom::IntentJoinServerRoom(TabSupervisor *_tabSupervisor,
|
IntentJoinServerRoom::IntentJoinServerRoom(TabSupervisor *_tabSupervisor,
|
||||||
RemoteClient *_remoteClient,
|
RemoteClient *_remoteClient,
|
||||||
ContextJoinRoom *_context)
|
ContextJoinRoom *_context)
|
||||||
|
|
@ -23,6 +26,9 @@ bool IntentJoinServerRoom::checkPrecondition() const
|
||||||
if (remoteClient->peerName() != context->serverContext.hostname) {
|
if (remoteClient->peerName() != context->serverContext.hostname) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (QString::number(remoteClient->peerPort()) != context->serverContext.port) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -31,7 +37,7 @@ void IntentJoinServerRoom::onPreconditionSatisfied()
|
||||||
{
|
{
|
||||||
if (tabSupervisor->getRoomTabs().contains(context->roomId)) {
|
if (tabSupervisor->getRoomTabs().contains(context->roomId)) {
|
||||||
tabSupervisor->setCurrentWidget(tabSupervisor->getRoomTabs().value(context->roomId));
|
tabSupervisor->setCurrentWidget(tabSupervisor->getRoomTabs().value(context->roomId));
|
||||||
emit finished();
|
emitFinished();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,12 +47,25 @@ void IntentJoinServerRoom::onPreconditionSatisfied()
|
||||||
tabServer = tabSupervisor->getTabServer();
|
tabServer = tabSupervisor->getTabServer();
|
||||||
}
|
}
|
||||||
if (!tabServer) {
|
if (!tabServer) {
|
||||||
emit failed(tr("No server tab available"));
|
emitFailed(tr("No server tab available"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tabServer->joinRoom(context->roomId, true);
|
const int roomId = context->roomId;
|
||||||
connect(tabServer, &TabServer::roomJoined, this, &IntentJoinServerRoom::finished);
|
tabServer->joinRoom(roomId, true);
|
||||||
|
connect(tabServer, &TabServer::roomJoined, this, [this, roomId](const ServerInfo_Room &info, bool) {
|
||||||
|
if (info.room_id() == roomId) {
|
||||||
|
emitFinished();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(tabServer, &TabServer::roomJoinFailed, this, [this, roomId](int failedRoomId) {
|
||||||
|
if (failedRoomId == roomId) {
|
||||||
|
emitFailed(tr("Failed to join the server room %1").arg(roomId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
QTimer::singleShot(15000, this,
|
||||||
|
[this, roomId]() { emitFailed(tr("Timed out while joining the server room %1").arg(roomId)); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void IntentJoinServerRoom::onPreconditionNotSatisfied()
|
void IntentJoinServerRoom::onPreconditionNotSatisfied()
|
||||||
|
|
|
||||||
|
|
@ -21,13 +21,13 @@ void IntentGetLoginCredentials::onPreconditionSatisfied()
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
context->username = servers.getValue(QString("username%1").arg(index), "server", "server_details").toString();
|
context->username = servers.getValue(QString("username%1").arg(index), "server", "server_details").toString();
|
||||||
context->password = servers.getValue(QString("password%1").arg(index), "server", "server_details").toString();
|
context->password = servers.getValue(QString("password%1").arg(index), "server", "server_details").toString();
|
||||||
emit finished();
|
emitFinished();
|
||||||
} else {
|
} else {
|
||||||
emit failed(tr("No saved credentials for this server"));
|
emitFailed(tr("No saved credentials for this server"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void IntentGetLoginCredentials::onPreconditionNotSatisfied()
|
void IntentGetLoginCredentials::onPreconditionNotSatisfied()
|
||||||
{
|
{
|
||||||
emit failed(tr("No saved credentials for this server"));
|
emitFailed(tr("No saved credentials for this server"));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,10 @@ void IntentOpenLocalDeck::onPreconditionSatisfied()
|
||||||
std::optional<LoadedDeck> deckOpt = DeckLoader::loadFromFile(file, DeckFileFormat::getFormatFromName(file), true);
|
std::optional<LoadedDeck> deckOpt = DeckLoader::loadFromFile(file, DeckFileFormat::getFormatFromName(file), true);
|
||||||
if (deckOpt) {
|
if (deckOpt) {
|
||||||
tabSupervisor->openDeckInNewTab(deckOpt.value());
|
tabSupervisor->openDeckInNewTab(deckOpt.value());
|
||||||
|
emitFinished();
|
||||||
|
} else {
|
||||||
|
emitFailed(tr("Unable to load deck file %1").arg(file));
|
||||||
}
|
}
|
||||||
emit finished();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void IntentOpenLocalDeck::onPreconditionNotSatisfied()
|
void IntentOpenLocalDeck::onPreconditionNotSatisfied()
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,11 @@ bool IntentWaitForDatabaseLoad::checkPrecondition() const
|
||||||
|
|
||||||
void IntentWaitForDatabaseLoad::onPreconditionSatisfied()
|
void IntentWaitForDatabaseLoad::onPreconditionSatisfied()
|
||||||
{
|
{
|
||||||
emit finished();
|
emitFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
void IntentWaitForDatabaseLoad::onPreconditionNotSatisfied()
|
void IntentWaitForDatabaseLoad::onPreconditionNotSatisfied()
|
||||||
{
|
{
|
||||||
connect(CardDatabaseManager::getInstance(), &CardDatabase::cardDatabaseLoadingFinished, this,
|
connect(CardDatabaseManager::getInstance(), &CardDatabase::cardDatabaseLoadingFinished, this,
|
||||||
[this]() { emit finished(); });
|
[this]() { emitFinished(); });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,10 @@
|
||||||
#include "intent_login.h"
|
#include "intent_login.h"
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QMessageBox>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QUrlQuery>
|
#include <QUrlQuery>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
IntentUrlParser::IntentUrlParser(QObject *parent, MainWindow *_mainWindow) : QObject(parent), mainWindow(_mainWindow)
|
IntentUrlParser::IntentUrlParser(QObject *parent, MainWindow *_mainWindow) : QObject(parent), mainWindow(_mainWindow)
|
||||||
{
|
{
|
||||||
|
|
@ -35,17 +37,29 @@ void IntentUrlParser::handle(const QString &urlStr)
|
||||||
|
|
||||||
void IntentUrlParser::handleJoinGame(const QUrlQuery &query)
|
void IntentUrlParser::handleJoinGame(const QUrlQuery &query)
|
||||||
{
|
{
|
||||||
auto ctx = new ContextJoinGame();
|
auto showError = [this](const QString &message) { QMessageBox::warning(mainWindow, tr("Open game"), message); };
|
||||||
|
|
||||||
|
auto ctx = std::make_unique<ContextJoinGame>();
|
||||||
|
|
||||||
ctx->roomContext.serverContext.hostname = query.queryItemValue("hostname");
|
ctx->roomContext.serverContext.hostname = query.queryItemValue("hostname");
|
||||||
ctx->roomContext.serverContext.port = query.queryItemValue("port");
|
ctx->roomContext.serverContext.port = query.queryItemValue("port");
|
||||||
|
|
||||||
|
if (ctx->roomContext.serverContext.hostname.isEmpty()) {
|
||||||
|
showError(tr("Missing or empty hostname in the game link"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
|
ctx->roomContext.serverContext.port.toUShort(&ok);
|
||||||
|
if (!ok) {
|
||||||
|
showError(tr("Invalid or missing port in the game link"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ctx->roomContext.roomId = query.queryItemValue("roomid").toInt(&ok);
|
ctx->roomContext.roomId = query.queryItemValue("roomid").toInt(&ok);
|
||||||
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
qWarning() << "Invalid or missing roomId";
|
showError(tr("Invalid or missing room id in the game link"));
|
||||||
delete ctx;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,21 +67,23 @@ void IntentUrlParser::handleJoinGame(const QUrlQuery &query)
|
||||||
ctx->gameId = query.queryItemValue("gameid").toInt(&ok);
|
ctx->gameId = query.queryItemValue("gameid").toInt(&ok);
|
||||||
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
qWarning() << "Invalid or missing gameId";
|
showError(tr("Invalid or missing game id in the game link"));
|
||||||
delete ctx;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The join game intent owns the context and the credential lookup; once the
|
// The join game intent owns the context and the credential lookup; once the
|
||||||
// chain finishes (or fails) it deletes the whole tree.
|
// chain finishes (or fails) it deletes the whole tree.
|
||||||
auto joinGameIntent = new IntentJoinServerGame(mainWindow->getTabSupervisor(), mainWindow->getRemoteClient(), ctx);
|
ContextConnectToServer *serverContext = &ctx->roomContext.serverContext;
|
||||||
|
auto joinGameIntent =
|
||||||
|
new IntentJoinServerGame(mainWindow->getTabSupervisor(), mainWindow->getRemoteClient(), std::move(ctx));
|
||||||
joinGameIntent->setParent(this);
|
joinGameIntent->setParent(this);
|
||||||
|
|
||||||
auto getLoginCredentialsIntent = new IntentGetLoginCredentials(&ctx->roomContext.serverContext);
|
auto getLoginCredentialsIntent = new IntentGetLoginCredentials(serverContext);
|
||||||
getLoginCredentialsIntent->setParent(joinGameIntent);
|
getLoginCredentialsIntent->setParent(joinGameIntent);
|
||||||
|
|
||||||
connect(getLoginCredentialsIntent, &Intent::finished, joinGameIntent, &Intent::execute);
|
connect(getLoginCredentialsIntent, &Intent::finished, joinGameIntent, &Intent::execute);
|
||||||
connect(getLoginCredentialsIntent, &Intent::failed, joinGameIntent, &Intent::failed);
|
connect(getLoginCredentialsIntent, &Intent::failed, joinGameIntent, &Intent::failed);
|
||||||
|
connect(joinGameIntent, &Intent::failed, this, [showError](const QString &reason) { showError(reason); });
|
||||||
|
|
||||||
getLoginCredentialsIntent->execute();
|
getLoginCredentialsIntent->execute();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,6 @@ public:
|
||||||
void handle(const QString &urlStr);
|
void handle(const QString &urlStr);
|
||||||
void handleJoinGame(const QUrlQuery &query);
|
void handleJoinGame(const QUrlQuery &query);
|
||||||
|
|
||||||
void parse(QString url);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
MainWindow *mainWindow;
|
MainWindow *mainWindow;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,16 @@
|
||||||
#include "games_model.h"
|
#include "games_model.h"
|
||||||
#include "user/user_list_manager.h"
|
#include "user/user_list_manager.h"
|
||||||
|
|
||||||
|
#include <QClipboard>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QGuiApplication>
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QHeaderView>
|
#include <QHeaderView>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QTreeView>
|
#include <QTreeView>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QUrlQuery>
|
||||||
#include <libcockatrice/network/client/abstract/abstract_client.h>
|
#include <libcockatrice/network/client/abstract/abstract_client.h>
|
||||||
#include <libcockatrice/protocol/pb/response.pb.h>
|
#include <libcockatrice/protocol/pb/response.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/room_commands.pb.h>
|
#include <libcockatrice/protocol/pb/room_commands.pb.h>
|
||||||
|
|
@ -315,6 +319,21 @@ void GameSelector::customContextMenu(const QPoint &point)
|
||||||
dlg.exec();
|
dlg.exec();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
QAction copyLink(tr("Copy Game Link"));
|
||||||
|
connect(©Link, &QAction::triggered, this, [=, this]() {
|
||||||
|
const ServerInfo_Game &gameInfo = gameListModel->getGame(index.data(Qt::UserRole).toInt());
|
||||||
|
QUrl url;
|
||||||
|
url.setScheme("cockatrice");
|
||||||
|
url.setHost("joingame");
|
||||||
|
QUrlQuery query;
|
||||||
|
query.addQueryItem("hostname", client->serverName());
|
||||||
|
query.addQueryItem("port", QString::number(client->serverPort()));
|
||||||
|
query.addQueryItem("roomid", QString::number(gameInfo.room_id()));
|
||||||
|
query.addQueryItem("gameid", QString::number(gameInfo.game_id()));
|
||||||
|
url.setQuery(query);
|
||||||
|
QGuiApplication::clipboard()->setText(url.toString(QUrl::FullyEncoded));
|
||||||
|
});
|
||||||
|
|
||||||
QMenu menu;
|
QMenu menu;
|
||||||
menu.addAction(&joinGame);
|
menu.addAction(&joinGame);
|
||||||
|
|
||||||
|
|
@ -332,6 +351,11 @@ void GameSelector::customContextMenu(const QPoint &point)
|
||||||
|
|
||||||
menu.addAction(&spectateGame);
|
menu.addAction(&spectateGame);
|
||||||
menu.addAction(&getGameInfo);
|
menu.addAction(&getGameInfo);
|
||||||
|
|
||||||
|
if (!client->serverName().isEmpty()) {
|
||||||
|
menu.addAction(©Link);
|
||||||
|
}
|
||||||
|
|
||||||
menu.exec(gameListView->mapToGlobal(point));
|
menu.exec(gameListView->mapToGlobal(point));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -191,7 +191,10 @@ void TabServer::joinRoom(int id, bool setCurrent)
|
||||||
|
|
||||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||||
pend->setExtraData(setCurrent);
|
pend->setExtraData(setCurrent);
|
||||||
connect(pend, &PendingCommand::finished, this, &TabServer::joinRoomFinished);
|
connect(pend, &PendingCommand::finished, this,
|
||||||
|
[this, id](const Response &r, const CommandContainer &c, const QVariant &v) {
|
||||||
|
joinRoomFinished(r, c, v, id);
|
||||||
|
});
|
||||||
|
|
||||||
client->sendCommand(pend);
|
client->sendCommand(pend);
|
||||||
|
|
||||||
|
|
@ -205,7 +208,8 @@ void TabServer::joinRoom(int id, bool setCurrent)
|
||||||
|
|
||||||
void TabServer::joinRoomFinished(const Response &r,
|
void TabServer::joinRoomFinished(const Response &r,
|
||||||
const CommandContainer & /*commandContainer*/,
|
const CommandContainer & /*commandContainer*/,
|
||||||
const QVariant &extraData)
|
const QVariant &extraData,
|
||||||
|
int roomId)
|
||||||
{
|
{
|
||||||
switch (r.response_code()) {
|
switch (r.response_code()) {
|
||||||
case Response::RespOk:
|
case Response::RespOk:
|
||||||
|
|
@ -213,21 +217,25 @@ void TabServer::joinRoomFinished(const Response &r,
|
||||||
case Response::RespNameNotFound:
|
case Response::RespNameNotFound:
|
||||||
QMessageBox::critical(this, tr("Error"),
|
QMessageBox::critical(this, tr("Error"),
|
||||||
tr("Failed to join the server room: it doesn't exist on the server."));
|
tr("Failed to join the server room: it doesn't exist on the server."));
|
||||||
|
emit roomJoinFailed(roomId);
|
||||||
return;
|
return;
|
||||||
case Response::RespContextError:
|
case Response::RespContextError:
|
||||||
QMessageBox::critical(
|
QMessageBox::critical(
|
||||||
this, tr("Error"),
|
this, tr("Error"),
|
||||||
tr("The server thinks you are in the server room but your client is unable to display it. "
|
tr("The server thinks you are in the server room but your client is unable to display it. "
|
||||||
"Try restarting your client."));
|
"Try restarting your client."));
|
||||||
|
emit roomJoinFailed(roomId);
|
||||||
return;
|
return;
|
||||||
case Response::RespUserLevelTooLow:
|
case Response::RespUserLevelTooLow:
|
||||||
QMessageBox::critical(this, tr("Error"),
|
QMessageBox::critical(this, tr("Error"),
|
||||||
tr("You do not have the required permission to join this server room."));
|
tr("You do not have the required permission to join this server room."));
|
||||||
|
emit roomJoinFailed(roomId);
|
||||||
return;
|
return;
|
||||||
default:
|
default:
|
||||||
QMessageBox::critical(
|
QMessageBox::critical(
|
||||||
this, tr("Error"),
|
this, tr("Error"),
|
||||||
tr("Failed to join the server room due to an unknown error: %1.").arg(r.response_code()));
|
tr("Failed to join the server room due to an unknown error: %1.").arg(r.response_code()));
|
||||||
|
emit roomJoinFailed(roomId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,9 +49,13 @@ class TabServer : public Tab
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
signals:
|
signals:
|
||||||
void roomJoined(const ServerInfo_Room &info, bool setCurrent);
|
void roomJoined(const ServerInfo_Room &info, bool setCurrent);
|
||||||
|
void roomJoinFailed(int roomId);
|
||||||
private slots:
|
private slots:
|
||||||
void processServerMessageEvent(const Event_ServerMessage &event);
|
void processServerMessageEvent(const Event_ServerMessage &event);
|
||||||
void joinRoomFinished(const Response &resp, const CommandContainer &commandContainer, const QVariant &extraData);
|
void joinRoomFinished(const Response &resp,
|
||||||
|
const CommandContainer &commandContainer,
|
||||||
|
const QVariant &extraData,
|
||||||
|
int roomId);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AbstractClient *client;
|
AbstractClient *client;
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QLibraryInfo>
|
#include <QLibraryInfo>
|
||||||
#include <QLocale>
|
#include <QLocale>
|
||||||
|
#include <QMessageBox>
|
||||||
#include <QSystemTrayIcon>
|
#include <QSystemTrayIcon>
|
||||||
#include <QTranslator>
|
#include <QTranslator>
|
||||||
#include <libcockatrice/card/database/card_database_manager.h>
|
#include <libcockatrice/card/database/card_database_manager.h>
|
||||||
|
|
@ -263,6 +264,35 @@ int main(int argc, char *argv[])
|
||||||
Logger::getInstance().logToFile(true);
|
Logger::getInstance().logToFile(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Handle files or URLs passed at startup ---
|
||||||
|
// Only positional arguments are treated as files/URLs, so options like
|
||||||
|
// --connect are never handed off to another instance.
|
||||||
|
const QStringList startupFiles = parser.positionalArguments();
|
||||||
|
const bool hasActivationFiles = !startupFiles.isEmpty();
|
||||||
|
|
||||||
|
SingleInstanceManager instance;
|
||||||
|
|
||||||
|
if (hasActivationFiles) {
|
||||||
|
// Activation launch: hand off to the primary instance if one is
|
||||||
|
// running, otherwise become the primary ourselves. Do this before
|
||||||
|
// constructing the main window so a hand-off exits cheaply.
|
||||||
|
if (!instance.tryRun(startupFiles)) {
|
||||||
|
// Sent successfully → exit
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// No primary instance → become server
|
||||||
|
qInfo() << "No existing instance found, becoming primary instance";
|
||||||
|
} else {
|
||||||
|
// Plain launch: if another instance is running, run independently
|
||||||
|
// instead of handing off and exiting.
|
||||||
|
if (!instance.tryRun(QStringList())) {
|
||||||
|
// Another instance is already running → just run independently
|
||||||
|
qInfo() << "Another instance exists, running independently";
|
||||||
|
} else {
|
||||||
|
qInfo() << "No existing instance found, starting server";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rng = new RNG_SFMT;
|
rng = new RNG_SFMT;
|
||||||
themeManager = new ThemeManager;
|
themeManager = new ThemeManager;
|
||||||
soundEngine = new SoundEngine;
|
soundEngine = new SoundEngine;
|
||||||
|
|
@ -301,6 +331,9 @@ int main(int argc, char *argv[])
|
||||||
urlParser->handle(file);
|
urlParser->handle(file);
|
||||||
} else if (QFileInfo(file).exists()) {
|
} else if (QFileInfo(file).exists()) {
|
||||||
auto openDeckIntent = new IntentOpenLocalDeck(ui.getTabSupervisor(), file);
|
auto openDeckIntent = new IntentOpenLocalDeck(ui.getTabSupervisor(), file);
|
||||||
|
QObject::connect(openDeckIntent, &Intent::failed, &ui, [&ui](const QString &reason) {
|
||||||
|
QMessageBox::warning(&ui, QObject::tr("Open deck"), reason);
|
||||||
|
});
|
||||||
openDeckIntent->execute();
|
openDeckIntent->execute();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -326,35 +359,6 @@ 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 ---
|
|
||||||
QStringList startupFiles;
|
|
||||||
for (int i = 1; i < argc; ++i) {
|
|
||||||
startupFiles.append(QString::fromLocal8Bit(argv[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hasActivationFiles = !startupFiles.isEmpty();
|
|
||||||
|
|
||||||
SingleInstanceManager instance;
|
|
||||||
|
|
||||||
if (hasActivationFiles) {
|
|
||||||
// Activation launch: try to forward
|
|
||||||
if (!instance.tryRun(startupFiles)) {
|
|
||||||
// Sent successfully → exit
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
// No primary instance → become server
|
|
||||||
qInfo() << "No existing instance found, becoming primary instance";
|
|
||||||
} else {
|
|
||||||
// Plain launch: if another instance is running, run independently
|
|
||||||
// instead of handing off and exiting.
|
|
||||||
if (!instance.tryRun(QStringList())) {
|
|
||||||
// Another instance is already running → just run independently
|
|
||||||
qInfo() << "Another instance exists, running independently";
|
|
||||||
} else {
|
|
||||||
qInfo() << "No existing instance found, starting server";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.show();
|
ui.show();
|
||||||
qCInfo(MainLog) << "ui.show() finished";
|
qCInfo(MainLog) << "ui.show() finished";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,24 @@
|
||||||
#include "single_instance_manager.h"
|
#include "single_instance_manager.h"
|
||||||
|
|
||||||
|
#include <QDir>
|
||||||
|
|
||||||
SingleInstanceManager::SingleInstanceManager(QObject *parent) : QObject(parent)
|
SingleInstanceManager::SingleInstanceManager(QObject *parent) : QObject(parent)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SingleInstanceManager::tryRun(const QStringList &filesToSend)
|
bool SingleInstanceManager::tryRun(const QStringList &filesToSend)
|
||||||
{
|
{
|
||||||
serverName = "CockatriceSingleInstance";
|
// Scope the socket name to the current user. On Linux the default abstract
|
||||||
|
// namespace is system-wide, so a plain name would let one user's instance
|
||||||
|
// hijack another user's session.
|
||||||
|
QString userName = qEnvironmentVariable("USER");
|
||||||
|
if (userName.isEmpty()) {
|
||||||
|
userName = qEnvironmentVariable("USERNAME");
|
||||||
|
}
|
||||||
|
if (userName.isEmpty()) {
|
||||||
|
userName = QDir::home().dirName();
|
||||||
|
}
|
||||||
|
serverName = QStringLiteral("CockatriceSingleInstance-%1").arg(userName);
|
||||||
|
|
||||||
// Hand off to an already-running primary instance if one exists.
|
// Hand off to an already-running primary instance if one exists.
|
||||||
if (forwardToPrimary(filesToSend)) {
|
if (forwardToPrimary(filesToSend)) {
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,20 @@ public:
|
||||||
return userName;
|
return userName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the server address configured for the current connection.
|
||||||
|
*
|
||||||
|
* May be empty for clients that have no server counterpart (e.g. local test clients).
|
||||||
|
*/
|
||||||
|
virtual QString serverName() const
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
virtual quint16 serverPort() const
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static PendingCommand *prepareSessionCommand(const ::google::protobuf::Message &cmd);
|
static PendingCommand *prepareSessionCommand(const ::google::protobuf::Message &cmd);
|
||||||
static PendingCommand *prepareRoomCommand(const ::google::protobuf::Message &cmd, int roomId);
|
static PendingCommand *prepareRoomCommand(const ::google::protobuf::Message &cmd, int roomId);
|
||||||
static PendingCommand *prepareModeratorCommand(const ::google::protobuf::Message &cmd);
|
static PendingCommand *prepareModeratorCommand(const ::google::protobuf::Message &cmd);
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,14 @@ public:
|
||||||
return socket->peerPort();
|
return socket->peerPort();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
QString serverName() const override
|
||||||
|
{
|
||||||
|
return lastHostname;
|
||||||
|
}
|
||||||
|
quint16 serverPort() const override
|
||||||
|
{
|
||||||
|
return static_cast<quint16>(lastPort);
|
||||||
|
}
|
||||||
void
|
void
|
||||||
connectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password);
|
connectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password);
|
||||||
void registerToServer(const QString &hostname,
|
void registerToServer(const QString &hostname,
|
||||||
|
|
|
||||||
|
|
@ -337,4 +337,4 @@ bool ServersSettings::hasCredentials(const QString &host, const QString &port) c
|
||||||
bool ServersSettings::hasLoginData(const QString &host, const QString &port) const
|
bool ServersSettings::hasLoginData(const QString &host, const QString &port) const
|
||||||
{
|
{
|
||||||
return hasUsername(host, port) && hasCredentials(host, port);
|
return hasUsername(host, port) && hasCredentials(host, port);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue