mirror of
https://github.com/redstrate/Astra.git
synced 2025-04-23 04:57:44 +00:00
Add Watchdog
Helps you get through the queue with instant access to your place, as well as giving you notifications on lobby errors and what else. At the moment, only supported under X11 and Linux but will grow in the future assuming the queues are still terrible.
This commit is contained in:
parent
b7e22b428c
commit
63faea21cb
11 changed files with 395 additions and 5 deletions
|
@ -10,7 +10,7 @@ find_package(Qt5 COMPONENTS Core Widgets Network CONFIG REQUIRED)
|
||||||
|
|
||||||
add_subdirectory(external)
|
add_subdirectory(external)
|
||||||
|
|
||||||
add_executable(xivlauncher
|
set(SRC
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
src/launchercore.cpp
|
src/launchercore.cpp
|
||||||
src/sapphirelauncher.cpp
|
src/sapphirelauncher.cpp
|
||||||
|
@ -21,9 +21,29 @@ add_executable(xivlauncher
|
||||||
src/assetupdater.cpp
|
src/assetupdater.cpp
|
||||||
src/assetupdater.h
|
src/assetupdater.h
|
||||||
src/launcherwindow.cpp
|
src/launcherwindow.cpp
|
||||||
src/launcherwindow.h)
|
src/launcherwindow.h
|
||||||
|
src/watchdog.h
|
||||||
|
src/watchdog.cpp)
|
||||||
|
|
||||||
target_link_libraries(xivlauncher Qt5::Core Qt5::Widgets Qt5::Network qt5keychain QuaZip)
|
set(LIBRARIES
|
||||||
|
Qt5::Core Qt5::Widgets Qt5::Network qt5keychain QuaZip)
|
||||||
|
|
||||||
|
if(UNIX)
|
||||||
|
set(SRC ${SRC}
|
||||||
|
src/gameparser.h
|
||||||
|
src/gameparser.cpp)
|
||||||
|
|
||||||
|
set(LIBRARIES ${LIBRARIES}
|
||||||
|
tesseract
|
||||||
|
lept
|
||||||
|
X11
|
||||||
|
Xcomposite
|
||||||
|
Xrender)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_executable(xivlauncher ${SRC})
|
||||||
|
|
||||||
|
target_link_libraries(xivlauncher PUBLIC ${LIBRARIES})
|
||||||
|
|
||||||
# disgusting, thanks qtkeychain and quazip
|
# disgusting, thanks qtkeychain and quazip
|
||||||
target_include_directories(xivlauncher PRIVATE
|
target_include_directories(xivlauncher PRIVATE
|
||||||
|
|
|
@ -4,7 +4,7 @@ Finally, a cross-platform FFXIV launcher. It should run on Windows, macOS and Li
|
||||||
|
|
||||||
Compared to XIVQuickLauncher and the official launcher, this supports
|
Compared to XIVQuickLauncher and the official launcher, this supports
|
||||||
**multiple profiles**, **Dalamud mods**, _and_ **macOS and Linux**! This means you no longer
|
**multiple profiles**, **Dalamud mods**, _and_ **macOS and Linux**! This means you no longer
|
||||||
have to suffer running your FFXIV launcher through Wine.
|
have to suffer running your FFXIV launcher through Wine. Under Linux, you have **Watchdog** available to help you through the login queue.
|
||||||
|
|
||||||
As of this moment, the three missing major features are **game patching**, **external tool launching** and **the news list**.
|
As of this moment, the three missing major features are **game patching**, **external tool launching** and **the news list**.
|
||||||
If you don't use these features then the launcher is still usable.
|
If you don't use these features then the launcher is still usable.
|
||||||
|
@ -23,6 +23,10 @@ More information can be found in the [FAQ](https://github.com/redstrate/xivlaunc
|
||||||
* Saving username and/or password. These are saved per-profile, and are encrypted using your system wallet.
|
* Saving username and/or password. These are saved per-profile, and are encrypted using your system wallet.
|
||||||
* Encrypted game argument support similiar to what XIVQuickLauncher and the official ffxivboot already does.
|
* Encrypted game argument support similiar to what XIVQuickLauncher and the official ffxivboot already does.
|
||||||
* Enable several (Linux) Wine-specific performance enhancements such as enabling Esync.
|
* Enable several (Linux) Wine-specific performance enhancements such as enabling Esync.
|
||||||
|
* You have a **Watchdog** available to help you through queues.
|
||||||
|
* Only works on X11 and Linux (at the moment)
|
||||||
|
* Will send you a notification on any change in the login queue. (moving up, logged in, lobby error, etc)
|
||||||
|
* Can view your spot in the queue easily by using the system tray icon.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
Pre-compiled binaries are not (yet) available, so you must compile from source. Please see the relevant
|
Pre-compiled binaries are not (yet) available, so you must compile from source. Please see the relevant
|
||||||
|
|
83
src/gameparser.cpp
Normal file
83
src/gameparser.cpp
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
#include "gameparser.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QBuffer>
|
||||||
|
|
||||||
|
GameParser::GameParser() {
|
||||||
|
api = new tesseract::TessBaseAPI();
|
||||||
|
|
||||||
|
if (api->Init(nullptr, "eng")) {
|
||||||
|
qDebug() << "Could not initialize tesseract!";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
api->SetPageSegMode(tesseract::PageSegMode::PSM_SINGLE_BLOCK);
|
||||||
|
}
|
||||||
|
|
||||||
|
GameParser::~GameParser() {
|
||||||
|
api->End();
|
||||||
|
delete api;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameParseResult GameParser::parseImage(QImage img) {
|
||||||
|
QBuffer buf;
|
||||||
|
img = img.convertToFormat(QImage::Format_Grayscale8);
|
||||||
|
img.save(&buf, "PNG", 100);
|
||||||
|
|
||||||
|
Pix* image = pixReadMem((const l_uint8 *) buf.data().data(), buf.size());
|
||||||
|
api->SetImage(image);
|
||||||
|
api->SetSourceResolution(300);
|
||||||
|
|
||||||
|
const QString text = api->GetUTF8Text();
|
||||||
|
|
||||||
|
// TODO: clean up these names
|
||||||
|
const bool hasWorldFullText = text.contains("This World is currently full.") || text.contains("Players in queue");
|
||||||
|
const bool hasLobbyErrorText = text.contains("The lobby server connection has encountered an error.");
|
||||||
|
const bool hasCONFIGURATIONText = text.contains("CONFIGURATION") || text.contains("ONLINE");
|
||||||
|
const bool hasConnectingToData = text.contains("Connecting to data center");
|
||||||
|
const bool worldTotallyFull = text.contains("3001");
|
||||||
|
|
||||||
|
if(hasLobbyErrorText) {
|
||||||
|
qDebug() << "LOBBY ERROR";
|
||||||
|
|
||||||
|
return {ScreenState::LobbyError, -1};
|
||||||
|
} else {
|
||||||
|
if(worldTotallyFull) {
|
||||||
|
qDebug() << "TOTALLY FULL WORLD (CLOSED BY SQENIX)";
|
||||||
|
|
||||||
|
return {ScreenState::WorldFull, -1};
|
||||||
|
} else {
|
||||||
|
if(hasConnectingToData) {
|
||||||
|
qDebug() << "CONNECTING TO DATA CENTER";
|
||||||
|
|
||||||
|
return {ScreenState::ConnectingToDataCenter, -1};
|
||||||
|
} else {
|
||||||
|
if(hasWorldFullText) {
|
||||||
|
qDebug() << "FULL WORLD";
|
||||||
|
|
||||||
|
// attempt to extract number of players in queue
|
||||||
|
QRegularExpression exp("(?:Players in queue: )([\\d|,]*)");
|
||||||
|
|
||||||
|
auto match = exp.match(text);
|
||||||
|
if(match.isValid()) {
|
||||||
|
return {ScreenState::InLoginQueue, match.captured(1).remove(',').toInt()};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {ScreenState::InLoginQueue, -1};
|
||||||
|
} else {
|
||||||
|
if(hasCONFIGURATIONText) {
|
||||||
|
qDebug() << "TITLE SCREEN";
|
||||||
|
return {ScreenState::EnteredTitleScreen, -1};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: figure out how to properly clear tesseract data
|
||||||
|
api->Clear();
|
||||||
|
api->ClearAdaptiveClassifier();
|
||||||
|
|
||||||
|
return {ScreenState::Splash, -1};
|
||||||
|
}
|
39
src/gameparser.h
Normal file
39
src/gameparser.h
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QImage>
|
||||||
|
#include <tesseract/baseapi.h>
|
||||||
|
#include <leptonica/allheaders.h>
|
||||||
|
|
||||||
|
enum class ScreenState {
|
||||||
|
Splash,
|
||||||
|
LobbyError,
|
||||||
|
WorldFull,
|
||||||
|
ConnectingToDataCenter,
|
||||||
|
EnteredTitleScreen,
|
||||||
|
InLoginQueue
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GameParseResult {
|
||||||
|
ScreenState state;
|
||||||
|
|
||||||
|
int playersInQueue = -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline bool operator==(const GameParseResult a, const GameParseResult b) {
|
||||||
|
return a.state == b.state && a.playersInQueue == b.playersInQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool operator!=(const GameParseResult a, const GameParseResult b) {
|
||||||
|
return !(a == b);
|
||||||
|
}
|
||||||
|
|
||||||
|
class GameParser {
|
||||||
|
public:
|
||||||
|
GameParser();
|
||||||
|
~GameParser();
|
||||||
|
|
||||||
|
GameParseResult parseImage(QImage image);
|
||||||
|
|
||||||
|
private:
|
||||||
|
tesseract::TessBaseAPI* api;
|
||||||
|
};
|
|
@ -34,6 +34,7 @@
|
||||||
#include "settingswindow.h"
|
#include "settingswindow.h"
|
||||||
#include "blowfish.h"
|
#include "blowfish.h"
|
||||||
#include "assetupdater.h"
|
#include "assetupdater.h"
|
||||||
|
#include "watchdog.h"
|
||||||
|
|
||||||
void LauncherCore::setSSL(QNetworkRequest& request) {
|
void LauncherCore::setSSL(QNetworkRequest& request) {
|
||||||
QSslConfiguration config;
|
QSslConfiguration config;
|
||||||
|
@ -150,7 +151,10 @@ void LauncherCore::launchGame(const ProfileSettings& profile, const LoginAuth au
|
||||||
connect(gameProcess, &QProcess::readyReadStandardOutput, [this, gameProcess, profile] {
|
connect(gameProcess, &QProcess::readyReadStandardOutput, [this, gameProcess, profile] {
|
||||||
QString output = gameProcess->readAllStandardOutput();
|
QString output = gameProcess->readAllStandardOutput();
|
||||||
|
|
||||||
|
qDebug() << "Now launching dalamud...";
|
||||||
|
|
||||||
auto dalamudProcess = new QProcess();
|
auto dalamudProcess = new QProcess();
|
||||||
|
dalamudProcess->setProcessChannelMode(QProcess::ForwardedChannels);
|
||||||
|
|
||||||
QStringList dalamudEnv = gameProcess->environment();
|
QStringList dalamudEnv = gameProcess->environment();
|
||||||
|
|
||||||
|
@ -300,6 +304,7 @@ void LauncherCore::readInitialInformation() {
|
||||||
profile.useGamemode = settings.value("useGamemode", false).toBool();
|
profile.useGamemode = settings.value("useGamemode", false).toBool();
|
||||||
profile.useGamescope = settings.value("useGamescope", false).toBool();
|
profile.useGamescope = settings.value("useGamescope", false).toBool();
|
||||||
profile.enableDXVKhud = settings.value("enableDXVKhud", false).toBool();
|
profile.enableDXVKhud = settings.value("enableDXVKhud", false).toBool();
|
||||||
|
profile.enableWatchdog = settings.value("enableWatchdog", false).toBool();
|
||||||
|
|
||||||
profile.enableDalamud = settings.value("enableDalamud", false).toBool();
|
profile.enableDalamud = settings.value("enableDalamud", false).toBool();
|
||||||
|
|
||||||
|
@ -365,6 +370,7 @@ LauncherCore::LauncherCore() : settings(QSettings::IniFormat, QSettings::UserSco
|
||||||
squareLauncher = new SquareLauncher(*this);
|
squareLauncher = new SquareLauncher(*this);
|
||||||
squareBoot = new SquareBoot(*this, *squareLauncher);
|
squareBoot = new SquareBoot(*this, *squareLauncher);
|
||||||
assetUpdater = new AssetUpdater(*this);
|
assetUpdater = new AssetUpdater(*this);
|
||||||
|
watchdog = new Watchdog(*this);
|
||||||
|
|
||||||
readInitialInformation();
|
readInitialInformation();
|
||||||
|
|
||||||
|
@ -375,6 +381,10 @@ LauncherCore::LauncherCore() : settings(QSettings::IniFormat, QSettings::UserSco
|
||||||
connect(squareLauncher, &SquareLauncher::gateStatusRecieved, this, &LauncherCore::settingsChanged);
|
connect(squareLauncher, &SquareLauncher::gateStatusRecieved, this, &LauncherCore::settingsChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LauncherCore::~LauncherCore() noexcept {
|
||||||
|
delete watchdog;
|
||||||
|
}
|
||||||
|
|
||||||
ProfileSettings LauncherCore::getProfile(int index) const {
|
ProfileSettings LauncherCore::getProfile(int index) const {
|
||||||
return profileSettings[index];
|
return profileSettings[index];
|
||||||
}
|
}
|
||||||
|
@ -462,6 +472,7 @@ void LauncherCore::saveSettings() {
|
||||||
settings.setValue("rememberPassword", profile.rememberPassword);
|
settings.setValue("rememberPassword", profile.rememberPassword);
|
||||||
|
|
||||||
settings.setValue("enableDalamud", profile.enableDalamud);
|
settings.setValue("enableDalamud", profile.enableDalamud);
|
||||||
|
settings.setValue("enableWatchdog", profile.enableWatchdog);
|
||||||
|
|
||||||
settings.endGroup();
|
settings.endGroup();
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ class SapphireLauncher;
|
||||||
class SquareLauncher;
|
class SquareLauncher;
|
||||||
class SquareBoot;
|
class SquareBoot;
|
||||||
class AssetUpdater;
|
class AssetUpdater;
|
||||||
|
class Watchdog;
|
||||||
|
|
||||||
struct ProfileSettings {
|
struct ProfileSettings {
|
||||||
QUuid uuid;
|
QUuid uuid;
|
||||||
|
@ -23,6 +24,7 @@ struct ProfileSettings {
|
||||||
QString bootVersion, gameVersion;
|
QString bootVersion, gameVersion;
|
||||||
int installedMaxExpansion = -1;
|
int installedMaxExpansion = -1;
|
||||||
QList<QString> expansionVersions;
|
QList<QString> expansionVersions;
|
||||||
|
bool enableWatchdog = false;
|
||||||
|
|
||||||
// wine
|
// wine
|
||||||
// 0 = system, 1 = custom, 2 = built-in (mac only)
|
// 0 = system, 1 = custom, 2 = built-in (mac only)
|
||||||
|
@ -63,6 +65,7 @@ class LauncherCore : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
LauncherCore();
|
LauncherCore();
|
||||||
|
~LauncherCore();
|
||||||
|
|
||||||
QNetworkAccessManager* mgr;
|
QNetworkAccessManager* mgr;
|
||||||
|
|
||||||
|
@ -93,6 +96,7 @@ public:
|
||||||
SquareBoot* squareBoot;
|
SquareBoot* squareBoot;
|
||||||
SquareLauncher* squareLauncher;
|
SquareLauncher* squareLauncher;
|
||||||
AssetUpdater* assetUpdater;
|
AssetUpdater* assetUpdater;
|
||||||
|
Watchdog* watchdog;
|
||||||
|
|
||||||
int defaultProfileIndex = 0;
|
int defaultProfileIndex = 0;
|
||||||
signals:
|
signals:
|
||||||
|
|
|
@ -91,6 +91,17 @@ SettingsWindow::SettingsWindow(LauncherWindow& window, LauncherCore& core, QWidg
|
||||||
});
|
});
|
||||||
gameBoxLayout->addWidget(gameDirectoryButton);
|
gameBoxLayout->addWidget(gameDirectoryButton);
|
||||||
|
|
||||||
|
#if defined(Q_OS_LINUX)
|
||||||
|
enableWatchdog = new QCheckBox("Enable Watchdog (X11 only)");
|
||||||
|
gameBoxLayout->addWidget(enableWatchdog);
|
||||||
|
|
||||||
|
connect(enableWatchdog, &QCheckBox::stateChanged, [this](int state) {
|
||||||
|
getCurrentProfile().enableWatchdog = state;
|
||||||
|
|
||||||
|
this->core.saveSettings();
|
||||||
|
});
|
||||||
|
#endif
|
||||||
|
|
||||||
expansionVersionLabel = new QLabel();
|
expansionVersionLabel = new QLabel();
|
||||||
gameBoxLayout->addRow("Version Info", expansionVersionLabel);
|
gameBoxLayout->addRow("Version Info", expansionVersionLabel);
|
||||||
|
|
||||||
|
@ -343,6 +354,7 @@ void SettingsWindow::reloadControls() {
|
||||||
useEsync->setChecked(profile.useEsync);
|
useEsync->setChecked(profile.useEsync);
|
||||||
useGamescope->setChecked(profile.useGamescope);
|
useGamescope->setChecked(profile.useGamescope);
|
||||||
useGamemode->setChecked(profile.useGamemode);
|
useGamemode->setChecked(profile.useGamemode);
|
||||||
|
enableWatchdog->setChecked(profile.enableWatchdog);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// login
|
// login
|
||||||
|
|
|
@ -39,6 +39,7 @@ private:
|
||||||
QLabel* winePrefixDirectory;
|
QLabel* winePrefixDirectory;
|
||||||
|
|
||||||
QCheckBox* useGamescope, *useEsync, *useGamemode;
|
QCheckBox* useGamescope, *useEsync, *useGamemode;
|
||||||
|
QCheckBox* enableWatchdog;
|
||||||
|
|
||||||
// login
|
// login
|
||||||
QCheckBox* encryptArgumentsBox = nullptr;
|
QCheckBox* encryptArgumentsBox = nullptr;
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
|
||||||
#include "launchercore.h"
|
#include "launchercore.h"
|
||||||
|
#include "watchdog.h"
|
||||||
|
|
||||||
SquareLauncher::SquareLauncher(LauncherCore& window) : window(window) {
|
SquareLauncher::SquareLauncher(LauncherCore& window) : window(window) {
|
||||||
|
|
||||||
|
@ -123,7 +124,11 @@ void SquareLauncher::registerSession(const LoginInformation& info) {
|
||||||
if(reply->rawHeaderList().contains("X-Patch-Unique-Id")) {
|
if(reply->rawHeaderList().contains("X-Patch-Unique-Id")) {
|
||||||
auth.SID = reply->rawHeader("X-Patch-Unique-Id");
|
auth.SID = reply->rawHeader("X-Patch-Unique-Id");
|
||||||
|
|
||||||
|
if(info.settings->enableWatchdog) {
|
||||||
|
window.watchdog->launchGame(*info.settings, auth);
|
||||||
|
} else {
|
||||||
window.launchGame(*info.settings, auth);
|
window.launchGame(*info.settings, auth);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
auto messageBox = new QMessageBox(QMessageBox::Icon::Critical, "Failed to Login", "Failed the anti-tamper check. Please restore your game to the original state or update the game.");
|
auto messageBox = new QMessageBox(QMessageBox::Icon::Critical, "Failed to Login", "Failed the anti-tamper check. Please restore your game to the original state or update the game.");
|
||||||
window.addUpdateButtons(*info.settings, *messageBox);
|
window.addUpdateButtons(*info.settings, *messageBox);
|
||||||
|
|
180
src/watchdog.cpp
Normal file
180
src/watchdog.cpp
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
#include "watchdog.h"
|
||||||
|
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QScreen>
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QMenu>
|
||||||
|
|
||||||
|
#if defined(Q_OS_LINUX)
|
||||||
|
#include <X11/X.h>
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <X11/extensions/Xcomposite.h>
|
||||||
|
#include <X11/extensions/Xrender.h>
|
||||||
|
|
||||||
|
// from https://github.com/adobe/webkit/blob/master/Source/WebCore/plugins/qt/QtX11ImageConversion.cpp
|
||||||
|
// code is licensed under GPLv2
|
||||||
|
// Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies)
|
||||||
|
QImage qimageFromXImage(XImage *xi) {
|
||||||
|
QImage::Format format = QImage::Format_ARGB32_Premultiplied;
|
||||||
|
if (xi->depth == 24)
|
||||||
|
format = QImage::Format_RGB32;
|
||||||
|
else if (xi->depth == 16)
|
||||||
|
format = QImage::Format_RGB16;
|
||||||
|
|
||||||
|
QImage image = QImage(reinterpret_cast<uchar *>(xi->data), xi->width, xi->height, xi->bytes_per_line,
|
||||||
|
format).copy();
|
||||||
|
|
||||||
|
// we may have to swap the byte order
|
||||||
|
if ((QSysInfo::ByteOrder == QSysInfo::LittleEndian && xi->byte_order == MSBFirst)
|
||||||
|
|| (QSysInfo::ByteOrder == QSysInfo::BigEndian && xi->byte_order == LSBFirst)) {
|
||||||
|
|
||||||
|
for (int i = 0; i < image.height(); i++) {
|
||||||
|
if (xi->depth == 16) {
|
||||||
|
ushort *p = reinterpret_cast<ushort *>(image.scanLine(i));
|
||||||
|
ushort *end = p + image.width();
|
||||||
|
while (p < end) {
|
||||||
|
*p = ((*p << 8) & 0xff00) | ((*p >> 8) & 0x00ff);
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
uint *p = reinterpret_cast<uint *>(image.scanLine(i));
|
||||||
|
uint *end = p + image.width();
|
||||||
|
while (p < end) {
|
||||||
|
*p = ((*p << 24) & 0xff000000) | ((*p << 8) & 0x00ff0000)
|
||||||
|
| ((*p >> 8) & 0x0000ff00) | ((*p >> 24) & 0x000000ff);
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fix-up alpha channel
|
||||||
|
if (format == QImage::Format_RGB32) {
|
||||||
|
QRgb *p = reinterpret_cast<QRgb *>(image.bits());
|
||||||
|
for (int y = 0; y < xi->height; ++y) {
|
||||||
|
for (int x = 0; x < xi->width; ++x)
|
||||||
|
p[x] |= 0xff000000;
|
||||||
|
p += xi->bytes_per_line / 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void Watchdog::launchGame(const ProfileSettings &settings, LoginAuth auth) {
|
||||||
|
// TODO: stubbed out on other platforms
|
||||||
|
// (you can't actually enable it on other platforms, so this is fine for now.)
|
||||||
|
#if defined(Q_OS_LINUX)
|
||||||
|
if(icon == nullptr) {
|
||||||
|
icon = new QSystemTrayIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
icon->setToolTip("Queue Status");
|
||||||
|
icon->show();
|
||||||
|
icon->showMessage("Watchdog", "Watchdog service has started. Waiting for you to connect to data center...");
|
||||||
|
|
||||||
|
auto timer = new QTimer(this);
|
||||||
|
|
||||||
|
auto menu = new QMenu();
|
||||||
|
|
||||||
|
auto stopAction = menu->addAction("Stop");
|
||||||
|
connect(stopAction, &QAction::triggered, [=] {
|
||||||
|
timer->stop();
|
||||||
|
processWindowId = -1;
|
||||||
|
icon->hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
icon->setContextMenu(menu);
|
||||||
|
|
||||||
|
core.launchGame(settings, auth);
|
||||||
|
|
||||||
|
if(parser == nullptr) {
|
||||||
|
parser = std::make_unique<GameParser>();
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(timer, &QTimer::timeout, [=] {
|
||||||
|
if (processWindowId == -1) {
|
||||||
|
auto xdoProcess = new QProcess();
|
||||||
|
|
||||||
|
connect(xdoProcess, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
|
||||||
|
[=](int, QProcess::ExitStatus) {
|
||||||
|
QString output = xdoProcess->readAllStandardOutput();
|
||||||
|
qDebug() << "Found XIV Window: " << output.toInt();
|
||||||
|
|
||||||
|
processWindowId = output.toInt();
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: don't use xdotool for this, find a better way to
|
||||||
|
xdoProcess->start("bash", {"-c", "xdotool search --name \"FINAL FANTASY XIV\""});
|
||||||
|
} else {
|
||||||
|
Display* display = XOpenDisplay(nullptr);
|
||||||
|
|
||||||
|
XSynchronize(display, True);
|
||||||
|
|
||||||
|
XWindowAttributes attr;
|
||||||
|
Status status = XGetWindowAttributes(display, processWindowId, &attr);
|
||||||
|
if (status == 0) {
|
||||||
|
qDebug() << "Failed to get window attributes! The window is possibly closed now.";
|
||||||
|
processWindowId = -1;
|
||||||
|
timer->stop();
|
||||||
|
icon->hide();
|
||||||
|
} else {
|
||||||
|
XCompositeRedirectWindow(display, processWindowId, CompositeRedirectAutomatic);
|
||||||
|
XCompositeNameWindowPixmap(display, processWindowId);
|
||||||
|
|
||||||
|
XRenderPictFormat* format = XRenderFindVisualFormat(display, attr.visual);
|
||||||
|
|
||||||
|
XRenderPictureAttributes pa;
|
||||||
|
pa.subwindow_mode = IncludeInferiors;
|
||||||
|
|
||||||
|
Picture picture = XRenderCreatePicture(display, processWindowId, format,
|
||||||
|
CPSubwindowMode, &pa);
|
||||||
|
XFlush(display); // TODO: does this actually make a difference?
|
||||||
|
|
||||||
|
XImage* image = XGetImage(display, processWindowId, 0, 0, attr.width, attr.height, AllPlanes, ZPixmap);
|
||||||
|
if (!image) {
|
||||||
|
qDebug() << "Unable to get image...";
|
||||||
|
} else {
|
||||||
|
auto result = parser->parseImage(qimageFromXImage(image));
|
||||||
|
if (result != lastResult) {
|
||||||
|
lastResult = result;
|
||||||
|
|
||||||
|
switch (result.state) {
|
||||||
|
case ScreenState::InLoginQueue: {
|
||||||
|
icon->showMessage("Watchdog",
|
||||||
|
QString("You are now at position %1 (moved %2 spots)").arg(
|
||||||
|
result.playersInQueue).arg(
|
||||||
|
lastResult.playersInQueue - result.playersInQueue));
|
||||||
|
|
||||||
|
icon->setToolTip(QString("Queue Status (%1)").arg(result.playersInQueue));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ScreenState::LobbyError: {
|
||||||
|
// TODO: kill game?
|
||||||
|
icon->showMessage("Watchdog", "You have been disconnected due to a lobby error.");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ScreenState::ConnectingToDataCenter: {
|
||||||
|
icon->showMessage("Watchdog",
|
||||||
|
"You are in the process of being connected to the data center.");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ScreenState::WorldFull: {
|
||||||
|
icon->showMessage("Watchdog", "You have been disconnected due to a lobby error.");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
XFreePixmap(display, picture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
XCompositeUnredirectWindow(display, processWindowId, CompositeRedirectAutomatic);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
timer->start(5000);
|
||||||
|
#endif
|
||||||
|
}
|
31
src/watchdog.h
Normal file
31
src/watchdog.h
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "launchercore.h"
|
||||||
|
|
||||||
|
#if defined(Q_OS_LINUX)
|
||||||
|
#include "gameparser.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <QSystemTrayIcon>
|
||||||
|
|
||||||
|
class Watchdog : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
Watchdog(LauncherCore& core) : core(core) {}
|
||||||
|
|
||||||
|
void launchGame(const ProfileSettings& settings, LoginAuth auth);
|
||||||
|
|
||||||
|
private:
|
||||||
|
LauncherCore& core;
|
||||||
|
QSystemTrayIcon* icon = nullptr;
|
||||||
|
|
||||||
|
int processWindowId = -1;
|
||||||
|
|
||||||
|
#if defined(Q_OS_LINUX)
|
||||||
|
GameParseResult lastResult;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::unique_ptr<GameParser> parser;
|
||||||
|
};
|
Loading…
Add table
Reference in a new issue