42#include <QApplication>
48#include <QHostAddress>
49#include <QJsonDocument>
64QObject* findItem(
const QString& name)
67 if(mw ==
nullptr)
return nullptr;
68 QObject* root = mw->qmlRootObject();
69 if(root ==
nullptr)
return nullptr;
70 if(root->objectName() == name)
return root;
71 return root->findChild<QObject*>(name);
74QJsonObject execute(
const QJsonObject& c)
76 const QString cmd = c.value(QStringLiteral(
"cmd")).toString();
77 auto ok = [](QJsonValue r){ QJsonObject o; o[QStringLiteral(
"ok")] =
true; o[QStringLiteral(
"result")] = r;
return o; };
78 auto err = [](
const QString& m){ QJsonObject o; o[QStringLiteral(
"ok")] =
false; o[QStringLiteral(
"error")] = m;
return o; };
80 if(cmd == QStringLiteral(
"ping"))
return ok(QStringLiteral(
"pong"));
83 if(brg ==
nullptr)
return err(QStringLiteral(
"bridge not ready"));
85 if(cmd == QStringLiteral(
"title"))
88 if(cmd == QStringLiteral(
"screen")) {
89 const QString s = c.value(QStringLiteral(
"arg")).toString();
90 if(!
Router::screens.contains(s))
return err(QStringLiteral(
"unknown screen: ") + s);
91 if(s == QStringLiteral(
"pokemonDetails")) {
93 mw->debugOpenPartyDetails(c.value(QStringLiteral(
"index")).toInt(0));
100 if(cmd == QStringLiteral(
"party")) {
102 return ok(mw->debugOpenPartyDetails(c.value(QStringLiteral(
"arg")).toInt(0)));
103 return err(QStringLiteral(
"no window"));
106 if(cmd == QStringLiteral(
"back")) {
111 if(cmd == QStringLiteral(
"home")) {
113 return ok(QStringLiteral(
"home"));
116 if(cmd == QStringLiteral(
"sav")) {
117 brg->
file->
setPath(c.value(QStringLiteral(
"arg")).toString());
122 if(cmd == QStringLiteral(
"shot")) {
124 if(mw ==
nullptr)
return err(QStringLiteral(
"no window"));
125 const QString p = c.value(QStringLiteral(
"arg")).toString();
126 if(p.isEmpty())
return err(QStringLiteral(
"shot needs 'arg' path"));
127 return mw->saveShot(p) ? ok(p) : err(QStringLiteral(
"grab failed"));
130 if(cmd == QStringLiteral(
"get")) {
131 QObject* it = findItem(c.value(QStringLiteral(
"obj")).toString());
132 if(it ==
nullptr)
return err(QStringLiteral(
"no object: ") + c.value(QStringLiteral(
"obj")).toString());
133 const QByteArray prop = c.value(QStringLiteral(
"prop")).toString().toUtf8();
134 return ok(QJsonValue::fromVariant(it->property(prop.constData())));
137 if(cmd == QStringLiteral(
"set")) {
138 QObject* it = findItem(c.value(QStringLiteral(
"obj")).toString());
139 if(it ==
nullptr)
return err(QStringLiteral(
"no object"));
140 const QByteArray prop = c.value(QStringLiteral(
"prop")).toString().toUtf8();
141 const bool okSet = it->setProperty(prop.constData(), c.value(QStringLiteral(
"val")).toVariant());
142 return okSet ? ok(
true) : err(QStringLiteral(
"set failed (no such property?)"));
145 if(cmd == QStringLiteral(
"click")) {
146 QObject* it = findItem(c.value(QStringLiteral(
"obj")).toString());
147 if(it ==
nullptr)
return err(QStringLiteral(
"no object"));
148 const bool okClick = QMetaObject::invokeMethod(it,
"clicked");
149 return okClick ? ok(
true) : err(QStringLiteral(
"no clicked() signal"));
152 if(cmd == QStringLiteral(
"reload")) {
154 return err(QStringLiteral(
"no window"));
157 if(cmd == QStringLiteral(
"list")) {
158 const QString sub = c.value(QStringLiteral(
"arg")).toString();
160 QObject* root = mw ? mw->qmlRootObject() :
nullptr;
161 if(root ==
nullptr)
return err(QStringLiteral(
"no root"));
163 const auto all = root->findChildren<QObject*>();
164 for(QObject* o : all) {
165 const QString n = o->objectName();
166 if(!n.isEmpty() && (sub.isEmpty() || n.contains(sub, Qt::CaseInsensitive)))
172 return err(QStringLiteral(
"unknown cmd: ") + cmd);
179 auto* server =
new QTcpServer(qApp);
180 const quint16 port = 8766;
181 if(!server->listen(QHostAddress::LocalHost, port)) {
182 qWarning() <<
"[debug-server] could NOT listen on 127.0.0.1:" << port
183 <<
"--" << server->errorString();
186 qInfo() <<
"[debug-server] listening on 127.0.0.1:" << port
187 <<
"(newline-delimited JSON)";
189 QObject::connect(server, &QTcpServer::newConnection, qApp, [server]() {
190 while(server->hasPendingConnections()) {
191 QTcpSocket* sock = server->nextPendingConnection();
192 QObject::connect(sock, &QTcpSocket::readyRead, sock, [sock]() {
193 while(sock->canReadLine()) {
194 const QByteArray line = sock->readLine().trimmed();
195 if(line.isEmpty())
continue;
197 const QJsonDocument doc = QJsonDocument::fromJson(line, &pe);
199 if(pe.error != QJsonParseError::NoError || !doc.isObject()) {
200 resp[QStringLiteral(
"ok")] =
false;
201 resp[QStringLiteral(
"error")] = QStringLiteral(
"bad json: ") + pe.errorString();
203 resp = execute(doc.object());
205 sock->write(QJsonDocument(resp).toJson(QJsonDocument::Compact));
210 QObject::connect(sock, &QTcpSocket::disconnected, sock, &QObject::deleteLater);
The single QML<->C++ doorway – everything the UI touches hangs off here.
void setPath(QString path)
Set the active path (backs the path property).
void reopenFile()
Reload the current path from disk, discarding edits.
QString getPath()
Current file path.
static MainWindow * getInstance()
The single MainWindow instance.
static Bridge * bridge
The brg aggregate (created here, injected into QML).
void changeScreen(QString name)
Navigate to the registered screen name.
void closeScreen()
Close the top screen.
static QHash< QString, Screen * > screens
The registry of named screens.