mirror of
https://git.outfoxxed.me/quickshell/quickshell.git
synced 2026-04-10 06:11:54 +10:00
Compare commits
2 commits
783b97152a
...
5eb6f51f4a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5eb6f51f4a | ||
|
|
d03c59768c |
23 changed files with 614 additions and 60 deletions
|
|
@ -1,6 +1,8 @@
|
||||||
cmake_minimum_required(VERSION 3.20)
|
cmake_minimum_required(VERSION 3.20)
|
||||||
project(quickshell VERSION "0.2.1" LANGUAGES CXX C)
|
project(quickshell VERSION "0.2.1" LANGUAGES CXX C)
|
||||||
|
|
||||||
|
set(UNRELEASED_FEATURES)
|
||||||
|
|
||||||
set(QT_MIN_VERSION "6.6.0")
|
set(QT_MIN_VERSION "6.6.0")
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ set shell id.
|
||||||
- Added pipewire audio peak detection.
|
- Added pipewire audio peak detection.
|
||||||
- Added initial support for network management.
|
- Added initial support for network management.
|
||||||
- Added support for grabbing focus from popup windows.
|
- Added support for grabbing focus from popup windows.
|
||||||
|
- Added support for IPC signal listeners.
|
||||||
|
- Added Quickshell version checking and version gated preprocessing.
|
||||||
|
|
||||||
## Other Changes
|
## Other Changes
|
||||||
|
|
||||||
|
|
@ -40,6 +42,7 @@ set shell id.
|
||||||
- Fixed missing signals for system tray item title and description updates.
|
- Fixed missing signals for system tray item title and description updates.
|
||||||
- Fixed asynchronous loaders not working after reload.
|
- Fixed asynchronous loaders not working after reload.
|
||||||
- Fixed asynchronous loaders not working before window creation.
|
- Fixed asynchronous loaders not working before window creation.
|
||||||
|
- Fixed memory leak in IPC handlers.
|
||||||
|
|
||||||
## Packaging Changes
|
## Packaging Changes
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
// NOLINTBEGIN
|
// NOLINTBEGIN
|
||||||
|
#define QS_VERSION "@quickshell_VERSION@"
|
||||||
|
#define QS_VERSION_MAJOR @quickshell_VERSION_MAJOR@
|
||||||
|
#define QS_VERSION_MINOR @quickshell_VERSION_MINOR@
|
||||||
|
#define QS_VERSION_PATCH @quickshell_VERSION_PATCH@
|
||||||
|
#define QS_UNRELEASED_FEATURES "@UNRELEASED_FEATURES@"
|
||||||
#define GIT_REVISION "@GIT_REVISION@"
|
#define GIT_REVISION "@GIT_REVISION@"
|
||||||
#define DISTRIBUTOR "@DISTRIBUTOR@"
|
#define DISTRIBUTOR "@DISTRIBUTOR@"
|
||||||
#define DISTRIBUTOR_DEBUGINFO_AVAILABLE @DEBUGINFO_AVAILABLE@
|
#define DISTRIBUTOR_DEBUGINFO_AVAILABLE @DEBUGINFO_AVAILABLE@
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ qt_add_library(quickshell-core STATIC
|
||||||
singleton.cpp
|
singleton.cpp
|
||||||
generation.cpp
|
generation.cpp
|
||||||
scan.cpp
|
scan.cpp
|
||||||
|
scanenv.cpp
|
||||||
qsintercept.cpp
|
qsintercept.cpp
|
||||||
incubator.cpp
|
incubator.cpp
|
||||||
lazyloader.cpp
|
lazyloader.cpp
|
||||||
|
|
@ -51,7 +52,7 @@ qt_add_qml_module(quickshell-core
|
||||||
|
|
||||||
install_qml_module(quickshell-core)
|
install_qml_module(quickshell-core)
|
||||||
|
|
||||||
target_link_libraries(quickshell-core PRIVATE Qt::Quick Qt::QuickPrivate Qt::Widgets)
|
target_link_libraries(quickshell-core PRIVATE Qt::Quick Qt::QuickPrivate Qt::Widgets quickshell-build)
|
||||||
|
|
||||||
qs_module_pch(quickshell-core SET large)
|
qs_module_pch(quickshell-core SET large)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@
|
||||||
#include "paths.hpp"
|
#include "paths.hpp"
|
||||||
#include "qmlscreen.hpp"
|
#include "qmlscreen.hpp"
|
||||||
#include "rootwrapper.hpp"
|
#include "rootwrapper.hpp"
|
||||||
|
#include "scanenv.hpp"
|
||||||
|
|
||||||
QuickshellSettings::QuickshellSettings() {
|
QuickshellSettings::QuickshellSettings() {
|
||||||
QObject::connect(
|
QObject::connect(
|
||||||
|
|
@ -313,6 +314,14 @@ QString QuickshellGlobal::iconPath(const QString& icon, const QString& fallback)
|
||||||
return IconImageProvider::requestString(icon, "", fallback);
|
return IconImageProvider::requestString(icon, "", fallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool QuickshellGlobal::hasVersion(qint32 major, qint32 minor, const QStringList& features) {
|
||||||
|
return qs::scan::env::PreprocEnv::hasVersion(major, minor, features);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QuickshellGlobal::hasVersion(qint32 major, qint32 minor) {
|
||||||
|
return QuickshellGlobal::hasVersion(major, minor, QStringList());
|
||||||
|
}
|
||||||
|
|
||||||
QuickshellGlobal* QuickshellGlobal::create(QQmlEngine* engine, QJSEngine* /*unused*/) {
|
QuickshellGlobal* QuickshellGlobal::create(QQmlEngine* engine, QJSEngine* /*unused*/) {
|
||||||
auto* qsg = new QuickshellGlobal();
|
auto* qsg = new QuickshellGlobal();
|
||||||
auto* generation = EngineGeneration::findEngineGeneration(engine);
|
auto* generation = EngineGeneration::findEngineGeneration(engine);
|
||||||
|
|
|
||||||
|
|
@ -217,6 +217,21 @@ public:
|
||||||
///
|
///
|
||||||
/// The popup can also be blocked by setting `QS_NO_RELOAD_POPUP=1`.
|
/// The popup can also be blocked by setting `QS_NO_RELOAD_POPUP=1`.
|
||||||
Q_INVOKABLE void inhibitReloadPopup() { this->mInhibitReloadPopup = true; }
|
Q_INVOKABLE void inhibitReloadPopup() { this->mInhibitReloadPopup = true; }
|
||||||
|
/// Check if Quickshell's version is at least `major.minor` and the listed
|
||||||
|
/// unreleased features are available. If Quickshell is newer than the given version
|
||||||
|
/// it is assumed that all unreleased features are present. The unreleased feature list
|
||||||
|
/// may be omitted.
|
||||||
|
///
|
||||||
|
/// > [!NOTE] You can feature gate code blocks using Quickshell's preprocessor which
|
||||||
|
/// > has the same function available.
|
||||||
|
/// >
|
||||||
|
/// > ```qml
|
||||||
|
/// > //@ if hasVersion(0, 3, ["feature"])
|
||||||
|
/// > ...
|
||||||
|
/// > //@ endif
|
||||||
|
/// > ```
|
||||||
|
Q_INVOKABLE static bool hasVersion(qint32 major, qint32 minor, const QStringList& features);
|
||||||
|
Q_INVOKABLE static bool hasVersion(qint32 major, qint32 minor);
|
||||||
|
|
||||||
void clearReloadPopupInhibit() { this->mInhibitReloadPopup = false; }
|
void clearReloadPopupInhibit() { this->mInhibitReloadPopup = false; }
|
||||||
[[nodiscard]] bool isReloadPopupInhibited() const { return this->mInhibitReloadPopup; }
|
[[nodiscard]] bool isReloadPopupInhibited() const { return this->mInhibitReloadPopup; }
|
||||||
|
|
|
||||||
|
|
@ -63,9 +63,6 @@ void RootWrapper::reloadGraph(bool hard) {
|
||||||
qs::core::QmlToolingSupport::updateTooling(rootPath, scanner);
|
qs::core::QmlToolingSupport::updateTooling(rootPath, scanner);
|
||||||
this->configDirWatcher.addPath(rootPath.path());
|
this->configDirWatcher.addPath(rootPath.path());
|
||||||
|
|
||||||
auto* generation = new EngineGeneration(rootPath, std::move(scanner));
|
|
||||||
generation->wrapper = this;
|
|
||||||
|
|
||||||
// todo: move into EngineGeneration
|
// todo: move into EngineGeneration
|
||||||
if (this->generation != nullptr) {
|
if (this->generation != nullptr) {
|
||||||
qInfo() << "Reloading configuration...";
|
qInfo() << "Reloading configuration...";
|
||||||
|
|
@ -74,6 +71,33 @@ void RootWrapper::reloadGraph(bool hard) {
|
||||||
|
|
||||||
QDir::setCurrent(this->originalWorkingDirectory);
|
QDir::setCurrent(this->originalWorkingDirectory);
|
||||||
|
|
||||||
|
if (!scanner.scanErrors.isEmpty()) {
|
||||||
|
qCritical() << "Failed to load configuration";
|
||||||
|
QString errorString = "Failed to load configuration";
|
||||||
|
for (auto& error: scanner.scanErrors) {
|
||||||
|
const auto& file = error.file;
|
||||||
|
QString rel;
|
||||||
|
if (file.startsWith(rootPath.path() % '/')) {
|
||||||
|
rel = '@' % file.sliced(rootPath.path().length() + 1);
|
||||||
|
} else {
|
||||||
|
rel = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto msg = " error in " % rel % '[' % QString::number(error.line) % ":0]: " % error.message;
|
||||||
|
errorString += '\n' % msg;
|
||||||
|
qCritical().noquote() << msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->generation != nullptr && this->generation->qsgInstance != nullptr) {
|
||||||
|
emit this->generation->qsgInstance->reloadFailed(errorString);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* generation = new EngineGeneration(rootPath, std::move(scanner));
|
||||||
|
generation->wrapper = this;
|
||||||
|
|
||||||
QUrl url;
|
QUrl url;
|
||||||
url.setScheme("qs");
|
url.setScheme("qs");
|
||||||
url.setPath("@/qs/" % rootFile.fileName());
|
url.setPath("@/qs/" % rootFile.fileName());
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
#include "scan.hpp"
|
#include "scan.hpp"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
#include <qdir.h>
|
#include <qdir.h>
|
||||||
#include <qfileinfo.h>
|
#include <qfileinfo.h>
|
||||||
|
#include <qjsengine.h>
|
||||||
#include <qjsonarray.h>
|
#include <qjsonarray.h>
|
||||||
#include <qjsondocument.h>
|
#include <qjsondocument.h>
|
||||||
#include <qjsonobject.h>
|
#include <qjsonobject.h>
|
||||||
|
|
@ -15,6 +17,7 @@
|
||||||
#include <qtextstream.h>
|
#include <qtextstream.h>
|
||||||
|
|
||||||
#include "logcat.hpp"
|
#include "logcat.hpp"
|
||||||
|
#include "scanenv.hpp"
|
||||||
|
|
||||||
QS_LOGGING_CATEGORY(logQmlScanner, "quickshell.qmlscanner", QtWarningMsg);
|
QS_LOGGING_CATEGORY(logQmlScanner, "quickshell.qmlscanner", QtWarningMsg);
|
||||||
|
|
||||||
|
|
@ -115,51 +118,113 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
|
||||||
auto stream = QTextStream(&file);
|
auto stream = QTextStream(&file);
|
||||||
auto imports = QVector<QString>();
|
auto imports = QVector<QString>();
|
||||||
|
|
||||||
while (!stream.atEnd()) {
|
bool inHeader = false;
|
||||||
auto line = stream.readLine().trimmed();
|
auto ifScopes = QVector<bool>();
|
||||||
if (!singleton && line == "pragma Singleton") {
|
bool sourceMasked = false;
|
||||||
singleton = true;
|
int lineNum = 0;
|
||||||
} else if (!internal && line == "//@ pragma Internal") {
|
QString overrideText;
|
||||||
internal = true;
|
bool isOverridden = false;
|
||||||
} else if (line.startsWith("import")) {
|
|
||||||
// we dont care about "import qs" as we always load the root folder
|
|
||||||
if (auto importCursor = line.indexOf(" qs."); importCursor != -1) {
|
|
||||||
importCursor += 4;
|
|
||||||
QString path;
|
|
||||||
|
|
||||||
while (importCursor != line.length()) {
|
auto pragmaEngine = QJSEngine();
|
||||||
auto c = line.at(importCursor);
|
pragmaEngine.globalObject().setPrototype(
|
||||||
if (c == '.') c = '/';
|
pragmaEngine.newQObject(new qs::scan::env::PreprocEnv())
|
||||||
else if (c == ' ') break;
|
);
|
||||||
else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
|
|
||||||
|| c == '_')
|
auto postError = [&, this](QString error) {
|
||||||
{
|
this->scanErrors.append({.file = path, .message = std::move(error), .line = lineNum});
|
||||||
} else {
|
};
|
||||||
qCWarning(logQmlScanner) << "Import line contains invalid characters: " << line;
|
|
||||||
goto next;
|
while (!stream.atEnd()) {
|
||||||
|
++lineNum;
|
||||||
|
bool hideMask = false;
|
||||||
|
auto rawLine = stream.readLine();
|
||||||
|
auto line = rawLine.trimmed();
|
||||||
|
if (!sourceMasked && inHeader) {
|
||||||
|
if (!singleton && line == "pragma Singleton") {
|
||||||
|
singleton = true;
|
||||||
|
} else if (line.startsWith("import")) {
|
||||||
|
// we dont care about "import qs" as we always load the root folder
|
||||||
|
if (auto importCursor = line.indexOf(" qs."); importCursor != -1) {
|
||||||
|
importCursor += 4;
|
||||||
|
QString path;
|
||||||
|
|
||||||
|
while (importCursor != line.length()) {
|
||||||
|
auto c = line.at(importCursor);
|
||||||
|
if (c == '.') c = '/';
|
||||||
|
else if (c == ' ') break;
|
||||||
|
else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
|
||||||
|
|| c == '_')
|
||||||
|
{
|
||||||
|
} else {
|
||||||
|
qCWarning(logQmlScanner) << "Import line contains invalid characters: " << line;
|
||||||
|
goto next;
|
||||||
|
}
|
||||||
|
|
||||||
|
path.append(c);
|
||||||
|
importCursor += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
path.append(c);
|
imports.append(this->rootPath.filePath(path));
|
||||||
importCursor += 1;
|
} else if (auto startQuot = line.indexOf('"');
|
||||||
|
startQuot != -1 && line.length() >= startQuot + 3)
|
||||||
|
{
|
||||||
|
auto endQuot = line.indexOf('"', startQuot + 1);
|
||||||
|
if (endQuot == -1) continue;
|
||||||
|
|
||||||
|
auto name = line.sliced(startQuot + 1, endQuot - startQuot - 1);
|
||||||
|
imports.push_back(name);
|
||||||
}
|
}
|
||||||
|
} else if (!internal && line == "//@ pragma Internal") {
|
||||||
imports.append(this->rootPath.filePath(path));
|
internal = true;
|
||||||
} else if (auto startQuot = line.indexOf('"');
|
} else if (line.contains('{')) {
|
||||||
startQuot != -1 && line.length() >= startQuot + 3)
|
inHeader = true;
|
||||||
{
|
|
||||||
auto endQuot = line.indexOf('"', startQuot + 1);
|
|
||||||
if (endQuot == -1) continue;
|
|
||||||
|
|
||||||
auto name = line.sliced(startQuot + 1, endQuot - startQuot - 1);
|
|
||||||
imports.push_back(name);
|
|
||||||
}
|
}
|
||||||
} else if (line.contains('{')) break;
|
}
|
||||||
|
|
||||||
|
if (line.startsWith("//@ if ")) {
|
||||||
|
auto code = line.sliced(7);
|
||||||
|
auto value = pragmaEngine.evaluate(code, path, 1234);
|
||||||
|
bool mask = true;
|
||||||
|
|
||||||
|
if (value.isError()) {
|
||||||
|
postError(QString("Evaluating if: %0").arg(value.toString()));
|
||||||
|
} else if (!value.isBool()) {
|
||||||
|
postError(QString("If expression \"%0\" is not a boolean").arg(value.toString()));
|
||||||
|
} else if (value.toBool()) {
|
||||||
|
mask = false;
|
||||||
|
}
|
||||||
|
if (!sourceMasked && mask) hideMask = true;
|
||||||
|
mask = sourceMasked || mask; // cant unmask if a nested if passes
|
||||||
|
ifScopes.append(mask);
|
||||||
|
if (mask) isOverridden = true;
|
||||||
|
sourceMasked = mask;
|
||||||
|
} else if (line.startsWith("//@ endif")) {
|
||||||
|
if (ifScopes.isEmpty()) {
|
||||||
|
postError("endif without matching if");
|
||||||
|
} else {
|
||||||
|
ifScopes.pop_back();
|
||||||
|
|
||||||
|
if (ifScopes.isEmpty()) sourceMasked = false;
|
||||||
|
else sourceMasked = ifScopes.last();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hideMask && sourceMasked) overrideText.append("// MASKED: " % rawLine % '\n');
|
||||||
|
else overrideText.append(rawLine % '\n');
|
||||||
|
|
||||||
next:;
|
next:;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!ifScopes.isEmpty()) {
|
||||||
|
postError("unclosed preprocessor if block");
|
||||||
|
}
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
|
if (isOverridden) {
|
||||||
|
this->fileIntercepts.insert(path, overrideText);
|
||||||
|
}
|
||||||
|
|
||||||
if (logQmlScanner().isDebugEnabled() && !imports.isEmpty()) {
|
if (logQmlScanner().isDebugEnabled() && !imports.isEmpty()) {
|
||||||
qCDebug(logQmlScanner) << "Found imports" << imports;
|
qCDebug(logQmlScanner) << "Found imports" << imports;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,14 @@ public:
|
||||||
QVector<QString> scannedFiles;
|
QVector<QString> scannedFiles;
|
||||||
QHash<QString, QString> fileIntercepts;
|
QHash<QString, QString> fileIntercepts;
|
||||||
|
|
||||||
|
struct ScanError {
|
||||||
|
QString file;
|
||||||
|
QString message;
|
||||||
|
int line;
|
||||||
|
};
|
||||||
|
|
||||||
|
QVector<ScanError> scanErrors;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QDir rootPath;
|
QDir rootPath;
|
||||||
|
|
||||||
|
|
|
||||||
22
src/core/scanenv.cpp
Normal file
22
src/core/scanenv.cpp
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
#include "scanenv.hpp"
|
||||||
|
|
||||||
|
#include <qcontainerfwd.h>
|
||||||
|
|
||||||
|
#include "build.hpp"
|
||||||
|
|
||||||
|
namespace qs::scan::env {
|
||||||
|
|
||||||
|
bool PreprocEnv::hasVersion(int major, int minor, const QStringList& features) {
|
||||||
|
if (QS_VERSION_MAJOR > major) return true;
|
||||||
|
if (QS_VERSION_MAJOR == major && QS_VERSION_MINOR > minor) return true;
|
||||||
|
|
||||||
|
auto availFeatures = QString(QS_UNRELEASED_FEATURES).split(';');
|
||||||
|
|
||||||
|
for (const auto& feature: features) {
|
||||||
|
if (!availFeatures.contains(feature)) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return QS_VERSION_MAJOR == major && QS_VERSION_MINOR == minor;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace qs::scan::env
|
||||||
17
src/core/scanenv.hpp
Normal file
17
src/core/scanenv.hpp
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <qcontainerfwd.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
|
namespace qs::scan::env {
|
||||||
|
|
||||||
|
class PreprocEnv: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Q_INVOKABLE static bool
|
||||||
|
hasVersion(int major, int minor, const QStringList& features = QStringList());
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace qs::scan::env
|
||||||
|
|
@ -24,7 +24,7 @@ qt_add_qml_module(quickshell-io
|
||||||
qs_add_module_deps_light(quickshell-io Quickshell)
|
qs_add_module_deps_light(quickshell-io Quickshell)
|
||||||
install_qml_module(quickshell-io)
|
install_qml_module(quickshell-io)
|
||||||
|
|
||||||
target_link_libraries(quickshell-io PRIVATE Qt::Quick)
|
target_link_libraries(quickshell-io PRIVATE Qt::Quick quickshell-ipc)
|
||||||
target_link_libraries(quickshell PRIVATE quickshell-ioplugin)
|
target_link_libraries(quickshell PRIVATE quickshell-ioplugin)
|
||||||
|
|
||||||
qs_module_pch(quickshell-io)
|
qs_module_pch(quickshell-io)
|
||||||
|
|
|
||||||
|
|
@ -190,6 +190,14 @@ QString WirePropertyDefinition::toString() const {
|
||||||
return "property " % this->name % ": " % this->type;
|
return "property " % this->name % ": " % this->type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString WireSignalDefinition::toString() const {
|
||||||
|
if (this->rettype.isEmpty()) {
|
||||||
|
return "signal " % this->name % "()";
|
||||||
|
} else {
|
||||||
|
return "signal " % this->name % "(" % this->retname % ": " % this->rettype % ')';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QString WireTargetDefinition::toString() const {
|
QString WireTargetDefinition::toString() const {
|
||||||
QString accum = "target " % this->name;
|
QString accum = "target " % this->name;
|
||||||
|
|
||||||
|
|
@ -201,6 +209,10 @@ QString WireTargetDefinition::toString() const {
|
||||||
accum += "\n " % prop.toString();
|
accum += "\n " % prop.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const auto& sig: this->signalFunctions) {
|
||||||
|
accum += "\n " % sig.toString();
|
||||||
|
}
|
||||||
|
|
||||||
return accum;
|
return accum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -146,14 +146,31 @@ struct WirePropertyDefinition {
|
||||||
|
|
||||||
DEFINE_SIMPLE_DATASTREAM_OPS(WirePropertyDefinition, data.name, data.type);
|
DEFINE_SIMPLE_DATASTREAM_OPS(WirePropertyDefinition, data.name, data.type);
|
||||||
|
|
||||||
struct WireTargetDefinition {
|
struct WireSignalDefinition {
|
||||||
QString name;
|
QString name;
|
||||||
QVector<WireFunctionDefinition> functions;
|
QString retname;
|
||||||
QVector<WirePropertyDefinition> properties;
|
QString rettype;
|
||||||
|
|
||||||
[[nodiscard]] QString toString() const;
|
[[nodiscard]] QString toString() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
DEFINE_SIMPLE_DATASTREAM_OPS(WireTargetDefinition, data.name, data.functions, data.properties);
|
DEFINE_SIMPLE_DATASTREAM_OPS(WireSignalDefinition, data.name, data.retname, data.rettype);
|
||||||
|
|
||||||
|
struct WireTargetDefinition {
|
||||||
|
QString name;
|
||||||
|
QVector<WireFunctionDefinition> functions;
|
||||||
|
QVector<WirePropertyDefinition> properties;
|
||||||
|
QVector<WireSignalDefinition> signalFunctions;
|
||||||
|
|
||||||
|
[[nodiscard]] QString toString() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
DEFINE_SIMPLE_DATASTREAM_OPS(
|
||||||
|
WireTargetDefinition,
|
||||||
|
data.name,
|
||||||
|
data.functions,
|
||||||
|
data.properties,
|
||||||
|
data.signalFunctions
|
||||||
|
);
|
||||||
|
|
||||||
} // namespace qs::io::ipc
|
} // namespace qs::io::ipc
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
#include "ipccomm.hpp"
|
#include "ipccomm.hpp"
|
||||||
|
#include <utility>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
#include <qlogging.h>
|
#include <qlogging.h>
|
||||||
#include <qloggingcategory.h>
|
#include <qloggingcategory.h>
|
||||||
|
#include <qobject.h>
|
||||||
#include <qtextstream.h>
|
#include <qtextstream.h>
|
||||||
#include <qtypes.h>
|
#include <qtypes.h>
|
||||||
|
|
||||||
|
|
@ -18,10 +20,6 @@ using namespace qs::ipc;
|
||||||
|
|
||||||
namespace qs::io::ipc::comm {
|
namespace qs::io::ipc::comm {
|
||||||
|
|
||||||
struct NoCurrentGeneration: std::monostate {};
|
|
||||||
struct TargetNotFound: std::monostate {};
|
|
||||||
struct EntryNotFound: std::monostate {};
|
|
||||||
|
|
||||||
using QueryResponse = std::variant<
|
using QueryResponse = std::variant<
|
||||||
std::monostate,
|
std::monostate,
|
||||||
NoCurrentGeneration,
|
NoCurrentGeneration,
|
||||||
|
|
@ -313,4 +311,106 @@ int getProperty(IpcClient* client, const QString& target, const QString& propert
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int listenToSignal(IpcClient* client, const QString& target, const QString& signal, bool once) {
|
||||||
|
if (target.isEmpty()) {
|
||||||
|
qCCritical(logBare) << "Target required to listen for signals.";
|
||||||
|
return -1;
|
||||||
|
} else if (signal.isEmpty()) {
|
||||||
|
qCCritical(logBare) << "Signal required to listen.";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
client->sendMessage(IpcCommand(SignalListenCommand {.target = target, .signal = signal}));
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
SignalListenResponse slot;
|
||||||
|
if (!client->waitForResponse(slot)) return -1;
|
||||||
|
|
||||||
|
if (std::holds_alternative<SignalResponse>(slot)) {
|
||||||
|
auto& result = std::get<SignalResponse>(slot);
|
||||||
|
QTextStream(stdout) << result.response << Qt::endl;
|
||||||
|
if (once) return 0;
|
||||||
|
else continue;
|
||||||
|
} else if (std::holds_alternative<TargetNotFound>(slot)) {
|
||||||
|
qCCritical(logBare) << "Target not found.";
|
||||||
|
} else if (std::holds_alternative<EntryNotFound>(slot)) {
|
||||||
|
qCCritical(logBare) << "Signal not found.";
|
||||||
|
} else if (std::holds_alternative<NoCurrentGeneration>(slot)) {
|
||||||
|
qCCritical(logBare) << "Not ready to accept queries yet.";
|
||||||
|
} else {
|
||||||
|
qCCritical(logIpc) << "Received invalid IPC response from" << client;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SignalListenCommand::exec(qs::ipc::IpcServerConnection* conn) {
|
||||||
|
auto resp = conn->responseStream<SignalListenResponse>();
|
||||||
|
|
||||||
|
if (auto* generation = EngineGeneration::currentGeneration()) {
|
||||||
|
auto* registry = IpcHandlerRegistry::forGeneration(generation);
|
||||||
|
|
||||||
|
auto* handler = registry->findHandler(this->target);
|
||||||
|
if (!handler) {
|
||||||
|
resp << TargetNotFound();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* signal = handler->findSignal(this->signal);
|
||||||
|
if (!signal) {
|
||||||
|
resp << EntryNotFound();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
new RemoteSignalListener(conn, *this);
|
||||||
|
} else {
|
||||||
|
conn->respond(SignalListenResponse(NoCurrentGeneration()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteSignalListener::RemoteSignalListener(
|
||||||
|
qs::ipc::IpcServerConnection* conn,
|
||||||
|
SignalListenCommand command
|
||||||
|
)
|
||||||
|
: conn(conn)
|
||||||
|
, command(std::move(command)) {
|
||||||
|
conn->setParent(this);
|
||||||
|
|
||||||
|
QObject::connect(
|
||||||
|
IpcSignalRemoteListener::instance(),
|
||||||
|
&IpcSignalRemoteListener::triggered,
|
||||||
|
this,
|
||||||
|
&RemoteSignalListener::onSignal
|
||||||
|
);
|
||||||
|
|
||||||
|
QObject::connect(
|
||||||
|
conn,
|
||||||
|
&qs::ipc::IpcServerConnection::destroyed,
|
||||||
|
this,
|
||||||
|
&RemoteSignalListener::onConnDestroyed
|
||||||
|
);
|
||||||
|
|
||||||
|
qCDebug(logIpc) << "Remote listener created for" << this->command.target << this->command.signal
|
||||||
|
<< ":" << this;
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteSignalListener::~RemoteSignalListener() {
|
||||||
|
qCDebug(logIpc) << "Destroying remote listener" << this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteSignalListener::onSignal(
|
||||||
|
const QString& target,
|
||||||
|
const QString& signal,
|
||||||
|
const QString& value
|
||||||
|
) {
|
||||||
|
if (target != this->command.target || signal != this->command.signal) return;
|
||||||
|
qCDebug(logIpc) << "Remote signal" << signal << "triggered on" << target << "with value" << value;
|
||||||
|
|
||||||
|
this->conn->respond(SignalListenResponse(SignalResponse {.response = value}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteSignalListener::onConnDestroyed() { this->deleteLater(); }
|
||||||
|
|
||||||
} // namespace qs::io::ipc::comm
|
} // namespace qs::io::ipc::comm
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
#include <qflags.h>
|
#include <qflags.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qtmetamacros.h>
|
||||||
#include <qtypes.h>
|
#include <qtypes.h>
|
||||||
|
|
||||||
#include "../ipc/ipc.hpp"
|
#include "../ipc/ipc.hpp"
|
||||||
|
|
@ -48,4 +50,52 @@ DEFINE_SIMPLE_DATASTREAM_OPS(StringPropReadCommand, data.target, data.property);
|
||||||
|
|
||||||
int getProperty(qs::ipc::IpcClient* client, const QString& target, const QString& property);
|
int getProperty(qs::ipc::IpcClient* client, const QString& target, const QString& property);
|
||||||
|
|
||||||
|
struct SignalListenCommand {
|
||||||
|
QString target;
|
||||||
|
QString signal;
|
||||||
|
|
||||||
|
void exec(qs::ipc::IpcServerConnection* conn);
|
||||||
|
};
|
||||||
|
|
||||||
|
DEFINE_SIMPLE_DATASTREAM_OPS(SignalListenCommand, data.target, data.signal);
|
||||||
|
|
||||||
|
int listenToSignal(
|
||||||
|
qs::ipc::IpcClient* client,
|
||||||
|
const QString& target,
|
||||||
|
const QString& signal,
|
||||||
|
bool once
|
||||||
|
);
|
||||||
|
|
||||||
|
struct NoCurrentGeneration: std::monostate {};
|
||||||
|
struct TargetNotFound: std::monostate {};
|
||||||
|
struct EntryNotFound: std::monostate {};
|
||||||
|
|
||||||
|
struct SignalResponse {
|
||||||
|
QString response;
|
||||||
|
};
|
||||||
|
|
||||||
|
DEFINE_SIMPLE_DATASTREAM_OPS(SignalResponse, data.response);
|
||||||
|
|
||||||
|
using SignalListenResponse = std::
|
||||||
|
variant<std::monostate, NoCurrentGeneration, TargetNotFound, EntryNotFound, SignalResponse>;
|
||||||
|
|
||||||
|
class RemoteSignalListener: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit RemoteSignalListener(qs::ipc::IpcServerConnection* conn, SignalListenCommand command);
|
||||||
|
|
||||||
|
~RemoteSignalListener() override;
|
||||||
|
|
||||||
|
Q_DISABLE_COPY_MOVE(RemoteSignalListener);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onSignal(const QString& target, const QString& signal, const QString& value);
|
||||||
|
void onConnDestroyed();
|
||||||
|
|
||||||
|
private:
|
||||||
|
qs::ipc::IpcServerConnection* conn;
|
||||||
|
SignalListenCommand command;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace qs::io::ipc::comm
|
} // namespace qs::io::ipc::comm
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
#include "ipchandler.hpp"
|
#include "ipchandler.hpp"
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <memory>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
#include <qdebug.h>
|
#include <qdebug.h>
|
||||||
|
|
@ -139,6 +141,75 @@ WirePropertyDefinition IpcProperty::wireDef() const {
|
||||||
return wire;
|
return wire;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WireSignalDefinition IpcSignal::wireDef() const {
|
||||||
|
WireSignalDefinition wire;
|
||||||
|
wire.name = this->signal.name();
|
||||||
|
if (this->targetSlot != IpcSignalListener::SLOT_VOID) {
|
||||||
|
wire.retname = this->signal.parameterNames().value(0);
|
||||||
|
if (this->targetSlot == IpcSignalListener::SLOT_STRING) wire.rettype = "string";
|
||||||
|
else if (this->targetSlot == IpcSignalListener::SLOT_INT) wire.rettype = "int";
|
||||||
|
else if (this->targetSlot == IpcSignalListener::SLOT_BOOL) wire.rettype = "bool";
|
||||||
|
else if (this->targetSlot == IpcSignalListener::SLOT_REAL) wire.rettype = "real";
|
||||||
|
else if (this->targetSlot == IpcSignalListener::SLOT_COLOR) wire.rettype = "color";
|
||||||
|
}
|
||||||
|
return wire;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOLINTBEGIN (cppcoreguidelines-interfaces-global-init)
|
||||||
|
// clang-format off
|
||||||
|
const int IpcSignalListener::SLOT_VOID = IpcSignalListener::staticMetaObject.indexOfSlot("invokeVoid()");
|
||||||
|
const int IpcSignalListener::SLOT_STRING = IpcSignalListener::staticMetaObject.indexOfSlot("invokeString(QString)");
|
||||||
|
const int IpcSignalListener::SLOT_INT = IpcSignalListener::staticMetaObject.indexOfSlot("invokeInt(int)");
|
||||||
|
const int IpcSignalListener::SLOT_BOOL = IpcSignalListener::staticMetaObject.indexOfSlot("invokeBool(bool)");
|
||||||
|
const int IpcSignalListener::SLOT_REAL = IpcSignalListener::staticMetaObject.indexOfSlot("invokeReal(double)");
|
||||||
|
const int IpcSignalListener::SLOT_COLOR = IpcSignalListener::staticMetaObject.indexOfSlot("invokeColor(QColor)");
|
||||||
|
// clang-format on
|
||||||
|
// NOLINTEND
|
||||||
|
|
||||||
|
bool IpcSignal::resolve(QString& error) {
|
||||||
|
if (this->signal.parameterCount() > 1) {
|
||||||
|
error = "Due to technical limitations, IPC signals can have at most one argument.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto slot = IpcSignalListener::SLOT_VOID;
|
||||||
|
|
||||||
|
if (this->signal.parameterCount() == 1) {
|
||||||
|
auto paramType = this->signal.parameterType(0);
|
||||||
|
if (paramType == QMetaType::QString) slot = IpcSignalListener::SLOT_STRING;
|
||||||
|
else if (paramType == QMetaType::Int) slot = IpcSignalListener::SLOT_INT;
|
||||||
|
else if (paramType == QMetaType::Bool) slot = IpcSignalListener::SLOT_BOOL;
|
||||||
|
else if (paramType == QMetaType::Double) slot = IpcSignalListener::SLOT_REAL;
|
||||||
|
else if (paramType == QMetaType::QColor) slot = IpcSignalListener::SLOT_COLOR;
|
||||||
|
else {
|
||||||
|
error = QString("Type of argument (%2: %3) cannot be used across IPC.")
|
||||||
|
.arg(this->signal.parameterNames().value(0))
|
||||||
|
.arg(QMetaType(paramType).name());
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->targetSlot = slot;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IpcSignal::connectListener(IpcHandler* handler) {
|
||||||
|
if (this->targetSlot == -1) {
|
||||||
|
qFatal() << "Tried to connect unresolved IPC signal";
|
||||||
|
}
|
||||||
|
|
||||||
|
this->listener = std::make_shared<IpcSignalListener>(this->signal.name());
|
||||||
|
QMetaObject::connect(handler, this->signal.methodIndex(), this->listener.get(), this->targetSlot);
|
||||||
|
|
||||||
|
QObject::connect(
|
||||||
|
this->listener.get(),
|
||||||
|
&IpcSignalListener::triggered,
|
||||||
|
handler,
|
||||||
|
&IpcHandler::onSignalTriggered
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
IpcCallStorage::IpcCallStorage(const IpcFunction& function): returnSlot(function.returnType) {
|
IpcCallStorage::IpcCallStorage(const IpcFunction& function): returnSlot(function.returnType) {
|
||||||
for (const auto& arg: function.argumentTypes) {
|
for (const auto& arg: function.argumentTypes) {
|
||||||
this->argumentSlots.emplace_back(arg);
|
this->argumentSlots.emplace_back(arg);
|
||||||
|
|
@ -172,16 +243,28 @@ void IpcHandler::onPostReload() {
|
||||||
// which should handle inheritance on the qml side.
|
// which should handle inheritance on the qml side.
|
||||||
for (auto i = smeta.methodCount(); i != meta->methodCount(); i++) {
|
for (auto i = smeta.methodCount(); i != meta->methodCount(); i++) {
|
||||||
const auto& method = meta->method(i);
|
const auto& method = meta->method(i);
|
||||||
if (method.methodType() != QMetaMethod::Slot) continue;
|
if (method.methodType() == QMetaMethod::Slot) {
|
||||||
|
auto ipcFunc = IpcFunction(method);
|
||||||
|
QString error;
|
||||||
|
|
||||||
auto ipcFunc = IpcFunction(method);
|
if (!ipcFunc.resolve(error)) {
|
||||||
QString error;
|
qmlWarning(this).nospace().noquote()
|
||||||
|
<< "Error parsing function \"" << method.name() << "\": " << error;
|
||||||
|
} else {
|
||||||
|
this->functionMap.insert(method.name(), ipcFunc);
|
||||||
|
}
|
||||||
|
} else if (method.methodType() == QMetaMethod::Signal) {
|
||||||
|
qmlDebug(this) << "Signal detected: " << method.name();
|
||||||
|
auto ipcSig = IpcSignal(method);
|
||||||
|
QString error;
|
||||||
|
|
||||||
if (!ipcFunc.resolve(error)) {
|
if (!ipcSig.resolve(error)) {
|
||||||
qmlWarning(this).nospace().noquote()
|
qmlWarning(this).nospace().noquote()
|
||||||
<< "Error parsing function \"" << method.name() << "\": " << error;
|
<< "Error parsing signal \"" << method.name() << "\": " << error;
|
||||||
} else {
|
} else {
|
||||||
this->functionMap.insert(method.name(), ipcFunc);
|
ipcSig.connectListener(this);
|
||||||
|
this->signalMap.emplace(method.name(), std::move(ipcSig));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -222,6 +305,11 @@ IpcHandlerRegistry* IpcHandlerRegistry::forGeneration(EngineGeneration* generati
|
||||||
return dynamic_cast<IpcHandlerRegistry*>(ext);
|
return dynamic_cast<IpcHandlerRegistry*>(ext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IpcHandler::onSignalTriggered(const QString& signal, const QString& value) const {
|
||||||
|
emit IpcSignalRemoteListener::instance()
|
||||||
|
-> triggered(this->registeredState.target, signal, value);
|
||||||
|
}
|
||||||
|
|
||||||
void IpcHandler::updateRegistration(bool destroying) {
|
void IpcHandler::updateRegistration(bool destroying) {
|
||||||
if (!this->complete) return;
|
if (!this->complete) return;
|
||||||
|
|
||||||
|
|
@ -324,6 +412,10 @@ WireTargetDefinition IpcHandler::wireDef() const {
|
||||||
wire.properties += prop.wireDef();
|
wire.properties += prop.wireDef();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const auto& sig: this->signalMap.values()) {
|
||||||
|
wire.signalFunctions += sig.wireDef();
|
||||||
|
}
|
||||||
|
|
||||||
return wire;
|
return wire;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -368,6 +460,13 @@ IpcProperty* IpcHandler::findProperty(const QString& name) {
|
||||||
else return &*itr;
|
else return &*itr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IpcSignal* IpcHandler::findSignal(const QString& name) {
|
||||||
|
auto itr = this->signalMap.find(name);
|
||||||
|
|
||||||
|
if (itr == this->signalMap.end()) return nullptr;
|
||||||
|
else return &*itr;
|
||||||
|
}
|
||||||
|
|
||||||
IpcHandler* IpcHandlerRegistry::findHandler(const QString& target) {
|
IpcHandler* IpcHandlerRegistry::findHandler(const QString& target) {
|
||||||
return this->handlers.value(target);
|
return this->handlers.value(target);
|
||||||
}
|
}
|
||||||
|
|
@ -382,4 +481,9 @@ QVector<WireTargetDefinition> IpcHandlerRegistry::wireTargets() const {
|
||||||
return wire;
|
return wire;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IpcSignalRemoteListener* IpcSignalRemoteListener::instance() {
|
||||||
|
static auto* instance = new IpcSignalRemoteListener();
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace qs::io::ipc
|
} // namespace qs::io::ipc
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include <qcolor.h>
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
#include <qdebug.h>
|
#include <qdebug.h>
|
||||||
#include <qhash.h>
|
#include <qhash.h>
|
||||||
|
|
@ -67,6 +69,54 @@ public:
|
||||||
const IpcType* type = nullptr;
|
const IpcType* type = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class IpcSignalListener: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
IpcSignalListener(QString signal): signal(std::move(signal)) {}
|
||||||
|
|
||||||
|
static const int SLOT_VOID;
|
||||||
|
static const int SLOT_STRING;
|
||||||
|
static const int SLOT_INT;
|
||||||
|
static const int SLOT_BOOL;
|
||||||
|
static const int SLOT_REAL;
|
||||||
|
static const int SLOT_COLOR;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void triggered(const QString& signal, const QString& value);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void invokeVoid() { this->triggered(this->signal, "void"); }
|
||||||
|
void invokeString(const QString& value) { this->triggered(this->signal, value); }
|
||||||
|
void invokeInt(int value) { this->triggered(this->signal, QString::number(value)); }
|
||||||
|
void invokeBool(bool value) { this->triggered(this->signal, value ? "true" : "false"); }
|
||||||
|
void invokeReal(double value) { this->triggered(this->signal, QString::number(value)); }
|
||||||
|
void invokeColor(QColor value) { this->triggered(this->signal, value.name(QColor::HexArgb)); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString signal;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IpcHandler;
|
||||||
|
|
||||||
|
class IpcSignal {
|
||||||
|
public:
|
||||||
|
explicit IpcSignal(QMetaMethod signal): signal(signal) {}
|
||||||
|
|
||||||
|
bool resolve(QString& error);
|
||||||
|
|
||||||
|
[[nodiscard]] WireSignalDefinition wireDef() const;
|
||||||
|
|
||||||
|
QMetaMethod signal;
|
||||||
|
int targetSlot = -1;
|
||||||
|
|
||||||
|
void connectListener(IpcHandler* handler);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void connectListener(QObject* handler, IpcSignalListener* listener) const;
|
||||||
|
std::shared_ptr<IpcSignalListener> listener;
|
||||||
|
};
|
||||||
|
|
||||||
class IpcHandlerRegistry;
|
class IpcHandlerRegistry;
|
||||||
|
|
||||||
///! Handler for IPC message calls.
|
///! Handler for IPC message calls.
|
||||||
|
|
@ -100,6 +150,11 @@ class IpcHandlerRegistry;
|
||||||
/// - `real` will be converted to a string and returned.
|
/// - `real` will be converted to a string and returned.
|
||||||
/// - `color` will be converted to a hex string in the form `#AARRGGBB` and returned.
|
/// - `color` will be converted to a hex string in the form `#AARRGGBB` and returned.
|
||||||
///
|
///
|
||||||
|
/// #### Signals
|
||||||
|
/// IPC handler signals can be observed remotely using `qs ipc wait` (one call)
|
||||||
|
/// and `qs ipc listen` (many calls). IPC signals may have zero or one argument, where
|
||||||
|
/// the argument is one of the types listed above, or no arguments for void.
|
||||||
|
///
|
||||||
/// #### Example
|
/// #### Example
|
||||||
/// The following example creates ipc functions to control and retrieve the appearance
|
/// The following example creates ipc functions to control and retrieve the appearance
|
||||||
/// of a Rectangle.
|
/// of a Rectangle.
|
||||||
|
|
@ -119,10 +174,18 @@ class IpcHandlerRegistry;
|
||||||
///
|
///
|
||||||
/// function setColor(color: color): void { rect.color = color; }
|
/// function setColor(color: color): void { rect.color = color; }
|
||||||
/// function getColor(): color { return rect.color; }
|
/// function getColor(): color { return rect.color; }
|
||||||
|
///
|
||||||
/// function setAngle(angle: real): void { rect.rotation = angle; }
|
/// function setAngle(angle: real): void { rect.rotation = angle; }
|
||||||
/// function getAngle(): real { return rect.rotation; }
|
/// function getAngle(): real { return rect.rotation; }
|
||||||
/// function setRadius(radius: int): void { rect.radius = radius; }
|
///
|
||||||
|
/// function setRadius(radius: int): void {
|
||||||
|
/// rect.radius = radius;
|
||||||
|
/// this.radiusChanged(radius);
|
||||||
|
/// }
|
||||||
|
///
|
||||||
/// function getRadius(): int { return rect.radius; }
|
/// function getRadius(): int { return rect.radius; }
|
||||||
|
///
|
||||||
|
/// signal radiusChanged(newRadius: int);
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
@ -136,6 +199,7 @@ class IpcHandlerRegistry;
|
||||||
/// function getAngle(): real
|
/// function getAngle(): real
|
||||||
/// function setRadius(radius: int): void
|
/// function setRadius(radius: int): void
|
||||||
/// function getRadius(): int
|
/// function getRadius(): int
|
||||||
|
/// signal radiusChanged(newRadius: int)
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// and then invoked using `qs ipc call`.
|
/// and then invoked using `qs ipc call`.
|
||||||
|
|
@ -179,14 +243,15 @@ public:
|
||||||
QString listMembers(qsizetype indent);
|
QString listMembers(qsizetype indent);
|
||||||
[[nodiscard]] IpcFunction* findFunction(const QString& name);
|
[[nodiscard]] IpcFunction* findFunction(const QString& name);
|
||||||
[[nodiscard]] IpcProperty* findProperty(const QString& name);
|
[[nodiscard]] IpcProperty* findProperty(const QString& name);
|
||||||
|
[[nodiscard]] IpcSignal* findSignal(const QString& name);
|
||||||
[[nodiscard]] WireTargetDefinition wireDef() const;
|
[[nodiscard]] WireTargetDefinition wireDef() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void enabledChanged();
|
void enabledChanged();
|
||||||
void targetChanged();
|
void targetChanged();
|
||||||
|
|
||||||
private slots:
|
public slots:
|
||||||
//void handleIpcPropertyChange();
|
void onSignalTriggered(const QString& signal, const QString& value) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void updateRegistration(bool destroying = false);
|
void updateRegistration(bool destroying = false);
|
||||||
|
|
@ -204,6 +269,7 @@ private:
|
||||||
|
|
||||||
QHash<QString, IpcFunction> functionMap;
|
QHash<QString, IpcFunction> functionMap;
|
||||||
QHash<QString, IpcProperty> propertyMap;
|
QHash<QString, IpcProperty> propertyMap;
|
||||||
|
QHash<QString, IpcSignal> signalMap;
|
||||||
|
|
||||||
friend class IpcHandlerRegistry;
|
friend class IpcHandlerRegistry;
|
||||||
};
|
};
|
||||||
|
|
@ -227,4 +293,14 @@ private:
|
||||||
QHash<QString, QVector<IpcHandler*>> knownHandlers;
|
QHash<QString, QVector<IpcHandler*>> knownHandlers;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class IpcSignalRemoteListener: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static IpcSignalRemoteListener* instance();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void triggered(const QString& target, const QString& signal, const QString& value);
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace qs::io::ipc
|
} // namespace qs::io::ipc
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ IpcServerConnection::IpcServerConnection(QLocalSocket* socket, IpcServer* server
|
||||||
|
|
||||||
void IpcServerConnection::onDisconnected() {
|
void IpcServerConnection::onDisconnected() {
|
||||||
qCInfo(logIpc) << "IPC connection disconnected" << this;
|
qCInfo(logIpc) << "IPC connection disconnected" << this;
|
||||||
|
delete this;
|
||||||
}
|
}
|
||||||
|
|
||||||
void IpcServerConnection::onReadyRead() {
|
void IpcServerConnection::onReadyRead() {
|
||||||
|
|
@ -84,6 +85,11 @@ void IpcServerConnection::onReadyRead() {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!this->stream.commitTransaction()) return;
|
if (!this->stream.commitTransaction()) return;
|
||||||
|
|
||||||
|
// async connections reparent
|
||||||
|
if (dynamic_cast<IpcServer*>(this->parent()) != nullptr) {
|
||||||
|
delete this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IpcClient::IpcClient(const QString& path) {
|
IpcClient::IpcClient(const QString& path) {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ using IpcCommand = std::variant<
|
||||||
IpcKillCommand,
|
IpcKillCommand,
|
||||||
qs::io::ipc::comm::QueryMetadataCommand,
|
qs::io::ipc::comm::QueryMetadataCommand,
|
||||||
qs::io::ipc::comm::StringCallCommand,
|
qs::io::ipc::comm::StringCallCommand,
|
||||||
|
qs::io::ipc::comm::SignalListenCommand,
|
||||||
qs::io::ipc::comm::StringPropReadCommand>;
|
qs::io::ipc::comm::StringPropReadCommand>;
|
||||||
|
|
||||||
} // namespace qs::ipc
|
} // namespace qs::ipc
|
||||||
|
|
|
||||||
|
|
@ -411,6 +411,10 @@ int ipcCommand(CommandState& cmd) {
|
||||||
return qs::io::ipc::comm::queryMetadata(&client, *cmd.ipc.target, *cmd.ipc.name);
|
return qs::io::ipc::comm::queryMetadata(&client, *cmd.ipc.target, *cmd.ipc.name);
|
||||||
} else if (*cmd.ipc.getprop) {
|
} else if (*cmd.ipc.getprop) {
|
||||||
return qs::io::ipc::comm::getProperty(&client, *cmd.ipc.target, *cmd.ipc.name);
|
return qs::io::ipc::comm::getProperty(&client, *cmd.ipc.target, *cmd.ipc.name);
|
||||||
|
} else if (*cmd.ipc.wait) {
|
||||||
|
return qs::io::ipc::comm::listenToSignal(&client, *cmd.ipc.target, *cmd.ipc.name, true);
|
||||||
|
} else if (*cmd.ipc.listen) {
|
||||||
|
return qs::io::ipc::comm::listenToSignal(&client, *cmd.ipc.target, *cmd.ipc.name, false);
|
||||||
} else {
|
} else {
|
||||||
QVector<QString> arguments;
|
QVector<QString> arguments;
|
||||||
for (auto& arg: cmd.ipc.arguments) {
|
for (auto& arg: cmd.ipc.arguments) {
|
||||||
|
|
@ -515,8 +519,8 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.misc.printVersion) {
|
if (state.misc.printVersion) {
|
||||||
qCInfo(logBare).noquote().nospace()
|
qCInfo(logBare).noquote().nospace() << "quickshell " << QS_VERSION << ", revision "
|
||||||
<< "quickshell 0.2.1, revision " << GIT_REVISION << ", distributed by: " << DISTRIBUTOR;
|
<< GIT_REVISION << ", distributed by: " << DISTRIBUTOR;
|
||||||
|
|
||||||
if (state.log.verbosity > 1) {
|
if (state.log.verbosity > 1) {
|
||||||
qCInfo(logBare).noquote() << "\nBuildtime Qt Version:" << QT_VERSION_STR;
|
qCInfo(logBare).noquote() << "\nBuildtime Qt Version:" << QT_VERSION_STR;
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,8 @@ struct CommandState {
|
||||||
CLI::App* show = nullptr;
|
CLI::App* show = nullptr;
|
||||||
CLI::App* call = nullptr;
|
CLI::App* call = nullptr;
|
||||||
CLI::App* getprop = nullptr;
|
CLI::App* getprop = nullptr;
|
||||||
|
CLI::App* wait = nullptr;
|
||||||
|
CLI::App* listen = nullptr;
|
||||||
bool showOld = false;
|
bool showOld = false;
|
||||||
QStringOption target;
|
QStringOption target;
|
||||||
QStringOption name;
|
QStringOption name;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include <CLI/App.hpp>
|
#include <CLI/App.hpp>
|
||||||
#include <CLI/CLI.hpp> // NOLINT: Need to include this for impls of some CLI11 classes
|
#include <CLI/CLI.hpp> // NOLINT: Need to include this for impls of some CLI11 classes
|
||||||
|
|
@ -226,6 +227,16 @@ int parseCommand(int argc, char** argv, CommandState& state) {
|
||||||
->allow_extra_args();
|
->allow_extra_args();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto signalCmd = [&](std::string cmd, std::string desc) {
|
||||||
|
auto* scmd = sub->add_subcommand(std::move(cmd), std::move(desc));
|
||||||
|
scmd->add_option("target", state.ipc.target, "The target to listen on.");
|
||||||
|
scmd->add_option("signal", state.ipc.name, "The signal to listen for.");
|
||||||
|
return scmd;
|
||||||
|
};
|
||||||
|
|
||||||
|
state.ipc.wait = signalCmd("wait", "Wait for one IpcHandler signal.");
|
||||||
|
state.ipc.listen = signalCmd("listen", "Listen for IpcHandler signals.");
|
||||||
|
|
||||||
{
|
{
|
||||||
auto* prop =
|
auto* prop =
|
||||||
sub->add_subcommand("prop", "Manipulate IpcHandler properties.")->require_subcommand();
|
sub->add_subcommand("prop", "Manipulate IpcHandler properties.")->require_subcommand();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue