implemented UDP mode

This commit is contained in:
MattKC 2024-08-02 10:46:16 -07:00
parent f26d42ce14
commit 6290f18769
13 changed files with 673 additions and 336 deletions

View file

@ -9,6 +9,7 @@ set(CMAKE_AUTORCC ON)
add_executable(vanilla-gui
audiohandler.cpp
backend.cpp
backendinitdialog.cpp
gamepadhandler.cpp
keymap.cpp
inputconfigdialog.cpp

View file

@ -3,7 +3,9 @@
#include <QCoreApplication>
#include <QDir>
#include <QMessageBox>
#include <QNetworkDatagram>
#include <arpa/inet.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
@ -30,216 +32,226 @@ void vanillaEventHandler(void *context, int type, const char *data, size_t dataL
Backend::Backend(QObject *parent) : QObject(parent)
{
m_pipe = nullptr;
}
void Backend::init()
{
emit ready();
}
BackendViaLocalRoot::BackendViaLocalRoot(const QString &wirelessInterface, QObject *parent) : Backend(parent)
{
m_wirelessInterface = wirelessInterface;
}
BackendViaPipe::BackendViaPipe(QObject *parent) : Backend(parent)
{
}
BackendViaNamedPipe::BackendViaNamedPipe(const QString &wirelessInterface, QObject *parent) : BackendViaPipe(parent)
{
m_pipeIn = -1;
m_pipeOut = -1;
m_interrupt = 0;
// If not running as root, use pipe
if ((geteuid() != 0)) {
m_pipeThread = new QThread(this);
m_pipeThread->start();
m_pipe = new BackendPipe();
m_pipe->moveToThread(m_pipeThread);
connect(m_pipe, &BackendPipe::pipesAvailable, this, &Backend::setUpPipes);
QMetaObject::invokeMethod(m_pipe, &BackendPipe::start, Qt::QueuedConnection);
}
m_pipeThread = new QThread(this);
m_pipe = new BackendPipe(wirelessInterface, this);
connect(m_pipe, &BackendPipe::pipesAvailable, this, &BackendViaNamedPipe::setUpPipes);
connect(m_pipe, &BackendPipe::closed, this, &BackendViaNamedPipe::closed);
}
Backend::~Backend()
void BackendViaNamedPipe::init()
{
if (m_pipe) {
m_pipeMutex.lock();
uint8_t cc = VANILLA_PIPE_IN_QUIT;
write(m_pipeOut, &cc, sizeof(cc));
m_pipeMutex.unlock();
m_pipe->deleteLater();
m_pipeThread->quit();
m_pipeThread->wait();
}
m_pipe->setParent(nullptr);
m_pipeThread->start();
m_pipe->moveToThread(m_pipeThread);
QMetaObject::invokeMethod(m_pipe, &BackendPipe::start, Qt::QueuedConnection);
}
void ignoreByte(int pipe)
BackendViaUdp::BackendViaUdp(const QHostAddress &backendAddr, quint16 backendPort, QObject *parent) : BackendViaPipe(parent)
{
uint8_t ignore;
read(pipe, &ignore, sizeof(ignore));
m_socket = new BackendUdpWrapper(backendAddr, backendPort, this);
connect(m_socket, &BackendUdpWrapper::socketReady, this, &BackendViaUdp::socketReady);
connect(m_socket, &BackendUdpWrapper::receivedData, this, &BackendViaUdp::receivedData, Qt::DirectConnection);
connect(m_socket, &BackendUdpWrapper::closed, this, &BackendViaUdp::closed);
connect(m_socket, &BackendUdpWrapper::error, this, &BackendViaUdp::error);
m_socketThread = new QThread(this);
}
void writeByte(int pipe, uint8_t byte)
BackendViaUdp::~BackendViaUdp()
{
write(pipe, &byte, sizeof(byte));
m_socket->deleteLater();
m_socketThread->quit();
m_socketThread->wait();
}
void Backend::interrupt()
void BackendViaUdp::init()
{
if (m_pipe) {
m_pipeMutex.lock();
uint8_t cc = VANILLA_PIPE_IN_INTERRUPT;
write(m_pipeOut, &cc, sizeof(cc));
m_pipeMutex.unlock();
} else {
vanilla_stop();
}
m_socketThread->start();
m_socket->setParent(nullptr);
m_socket->moveToThread(m_socketThread);
QMetaObject::invokeMethod(m_socket, &BackendUdpWrapper::start, Qt::QueuedConnection);
}
void writeNullTermString(int pipe, const QString &s)
void BackendViaPipe::quitPipe()
{
QByteArray sc = s.toUtf8();
write(pipe, sc.constData(), sc.size());
writeByte(pipe, 0);
pipe_control_code cmd;
cmd.code = VANILLA_PIPE_IN_QUIT;
writeToPipe(&cmd, sizeof(cmd));
}
void Backend::requestIDR()
BackendViaNamedPipe::~BackendViaNamedPipe()
{
if (m_pipe) {
m_pipeMutex.lock();
writeByte(m_pipeOut, VANILLA_PIPE_IN_REQ_IDR);
m_pipeMutex.unlock();
} else {
vanilla_request_idr();
}
quitPipe();
m_pipe->deleteLater();
m_pipeThread->quit();
m_pipeThread->wait();
}
void Backend::connectToConsole(const QString &wirelessInterface)
void BackendViaLocalRoot::interrupt()
{
if (m_pipe) {
// Request pipe to connect
m_pipeMutex.lock();
writeByte(m_pipeOut, VANILLA_PIPE_IN_CONNECT);
writeNullTermString(m_pipeOut, wirelessInterface);
m_pipeMutex.unlock();
uint8_t cc;
while (true) {
ssize_t read_size = read(m_pipeIn, &cc, sizeof(cc));
if (read_size == 0) {
continue;
}
if (cc == VANILLA_PIPE_OUT_EOF) {
break;
} else if (cc == VANILLA_PIPE_OUT_DATA) {
// Read event
read(m_pipeIn, &cc, sizeof(cc));
// Read data size
uint64_t data_size;
read(m_pipeIn, &data_size, sizeof(data_size));
void *buf = malloc(data_size);
read(m_pipeIn, buf, data_size);
vanillaEventHandler(this, cc, (const char *) buf, data_size);
free(buf);
}
}
} else {
QByteArray wirelessInterfaceC = wirelessInterface.toUtf8();
vanilla_connect_to_console(wirelessInterfaceC.constData(), vanillaEventHandler, this);
}
vanilla_stop();
}
void Backend::updateTouch(int x, int y)
void BackendViaPipe::interrupt()
{
if (m_pipe) {
m_pipeMutex.lock();
writeByte(m_pipeOut, VANILLA_PIPE_IN_TOUCH);
int32_t touchX = x;
int32_t touchY = y;
write(m_pipeOut, &x, sizeof(x));
write(m_pipeOut, &y, sizeof(y));
m_pipeMutex.unlock();
} else {
vanilla_set_touch(x, y);
}
pipe_control_code cmd;
cmd.code = VANILLA_PIPE_IN_INTERRUPT;
writeToPipe(&cmd, sizeof(cmd));
}
void Backend::setButton(int button, int32_t value)
void BackendViaLocalRoot::requestIDR()
{
if (m_pipe) {
m_pipeMutex.lock();
writeByte(m_pipeOut, VANILLA_PIPE_IN_BUTTON);
int32_t buttonSized = button;
write(m_pipeOut, &buttonSized, sizeof(buttonSized));
write(m_pipeOut, &value, sizeof(value));
m_pipeMutex.unlock();
} else {
vanilla_set_button(button, value);
}
vanilla_request_idr();
}
void Backend::setRegion(int region)
void BackendViaPipe::requestIDR()
{
if (m_pipe) {
m_pipeMutex.lock();
writeByte(m_pipeOut, VANILLA_PIPE_IN_REGION);
int8_t regionSized = region;
write(m_pipeOut, &regionSized, sizeof(regionSized));
m_pipeMutex.unlock();
} else {
vanilla_set_region(region);
}
pipe_control_code cmd;
cmd.code = VANILLA_PIPE_IN_REQ_IDR;
writeToPipe(&cmd, sizeof(cmd));
}
void Backend::setBatteryStatus(int status)
void BackendViaLocalRoot::connectToConsole()
{
if (m_pipe) {
m_pipeMutex.lock();
writeByte(m_pipeOut, VANILLA_PIPE_IN_BATTERY);
int8_t batterySized = status;
write(m_pipeOut, &batterySized, sizeof(batterySized));
m_pipeMutex.unlock();
} else {
vanilla_set_battery_status(status);
}
QByteArray wirelessInterfaceC = m_wirelessInterface.toUtf8();
vanilla_connect_to_console(wirelessInterfaceC.constData(), vanillaEventHandler, this);
}
void Backend::sync(const QString &wirelessInterface, uint16_t code)
void BackendViaPipe::connectToConsole()
{
if (m_pipe) {
// Request pipe to sync
m_pipeMutex.lock();
// Write control code
writeByte(m_pipeOut, VANILLA_PIPE_IN_SYNC);
// Request pipe to connect
pipe_control_code conn_cmd;
conn_cmd.code = VANILLA_PIPE_IN_CONNECT;
writeToPipe(&conn_cmd, sizeof(conn_cmd));
// Write WPS code
write(m_pipeOut, &code, sizeof(code));
// Write wireless interface
writeNullTermString(m_pipeOut, wirelessInterface);
m_pipeMutex.unlock();
// See if pipe accepted our request to sync
uint8_t cc;
read(m_pipeIn, &cc, sizeof(cc));
if (cc != VANILLA_PIPE_ERR_SUCCESS) {
emit syncCompleted(false);
return;
uint8_t cmd[UINT16_MAX];
while (true) {
ssize_t read_size = readFromPipe(cmd, sizeof(cmd));
if (read_size == 0) {
continue;
}
// Wait for sync status
read(m_pipeIn, &cc, sizeof(cc));
if (cc == VANILLA_PIPE_OUT_SYNC_STATE) {
read(m_pipeIn, &cc, sizeof(cc));
emit syncCompleted(cc == VANILLA_SUCCESS);
} else {
emit syncCompleted(false);
pipe_control_code *cc = (pipe_control_code *) cmd;
if (cc->code == VANILLA_PIPE_OUT_EOF) {
break;
} else if (cc->code == VANILLA_PIPE_OUT_DATA) {
pipe_data_command *event = (pipe_data_command *) cmd;
event->data_size = ntohs(event->data_size);
vanillaEventHandler(this, event->event_type, (const char *) event->data, event->data_size);
}
} else {
QByteArray wirelessInterfaceC = wirelessInterface.toUtf8();
int r = vanilla_sync_with_console(wirelessInterfaceC.constData(), code);
emit syncCompleted(r == VANILLA_SUCCESS);
}
}
void Backend::setUpPipes(const QString &in, const QString &out)
void BackendViaLocalRoot::updateTouch(int x, int y)
{
vanilla_set_touch(x, y);
}
void BackendViaPipe::updateTouch(int x, int y)
{
pipe_touch_command cmd;
cmd.base.code = VANILLA_PIPE_IN_TOUCH;
cmd.x = htonl(x);
cmd.y = htonl(y);
writeToPipe(&cmd, sizeof(cmd));
}
void BackendViaLocalRoot::setButton(int button, int32_t value)
{
vanilla_set_button(button, value);
}
void BackendViaPipe::setButton(int button, int32_t value)
{
pipe_button_command cmd;
cmd.base.code = VANILLA_PIPE_IN_BUTTON;
cmd.id = htonl(button);
cmd.value = htonl(value);
writeToPipe(&cmd, sizeof(cmd));
}
void BackendViaLocalRoot::setRegion(int region)
{
vanilla_set_region(region);
}
void BackendViaPipe::setRegion(int region)
{
pipe_region_command cmd;
cmd.base.code = VANILLA_PIPE_IN_REGION;
cmd.region = region;
writeToPipe(&cmd, sizeof(cmd));
}
void BackendViaLocalRoot::setBatteryStatus(int status)
{
vanilla_set_battery_status(status);
}
void BackendViaPipe::setBatteryStatus(int status)
{
pipe_battery_command cmd;
cmd.base.code = VANILLA_PIPE_IN_BATTERY;
cmd.battery = status;
writeToPipe(&cmd, sizeof(cmd));
}
void BackendViaLocalRoot::sync(uint16_t code)
{
QByteArray wirelessInterfaceC = m_wirelessInterface.toUtf8();
int r = vanilla_sync_with_console(wirelessInterfaceC.constData(), code);
emit syncCompleted(r == VANILLA_SUCCESS);
}
void BackendViaPipe::sync(uint16_t code)
{
// Request pipe to sync
pipe_sync_command cmd;
cmd.base.code = VANILLA_PIPE_IN_SYNC;
cmd.code = code;
writeToPipe(&cmd, sizeof(cmd));
// See if pipe accepted our request to sync
uint8_t cc;
readFromPipe(&cc, sizeof(cc));
if (cc != VANILLA_PIPE_ERR_SUCCESS) {
emit syncCompleted(false);
return;
}
// Wait for sync status
readFromPipe(&cc, sizeof(cc));
if (cc == VANILLA_PIPE_OUT_SYNC_STATE) {
readFromPipe(&cc, sizeof(cc));
emit syncCompleted(cc == VANILLA_SUCCESS);
} else {
emit syncCompleted(false);
}
}
void BackendViaNamedPipe::setUpPipes(const QString &in, const QString &out)
{
QByteArray inUtf8 = in.toUtf8();
QByteArray outUtf8 = out.toUtf8();
@ -253,11 +265,13 @@ void Backend::setUpPipes(const QString &in, const QString &out)
}
printf("Established connection with backend\n");
emit ready();
}
BackendPipe::BackendPipe(QObject *parent) : QObject(parent)
BackendPipe::BackendPipe(const QString &wirelessInterface, QObject *parent) : QObject(parent)
{
m_process = nullptr;
m_wirelessInterface = wirelessInterface;
}
BackendPipe::~BackendPipe()
@ -281,12 +295,12 @@ void BackendPipe::start()
m_process = new QProcess(this);
m_process->setReadChannel(QProcess::StandardError);
connect(m_process, &QProcess::readyReadStandardError, this, &BackendPipe::receivedData);
//connect(m_pipe, &QProcess::finished, this, [this](int code){printf("closed??? %i\n", code);});
connect(m_process, &QProcess::finished, this, &BackendPipe::closed);
m_pipeOutFilename = QStringLiteral("/tmp/vanilla-fifo-in-%0").arg(QString::number(QDateTime::currentMSecsSinceEpoch()));
m_pipeInFilename = QStringLiteral("/tmp/vanilla-fifo-out-%0").arg(QString::number(QDateTime::currentMSecsSinceEpoch()));
m_process->start(QStringLiteral("pkexec"), {pipe_bin, QStringLiteral("-pipe"), m_pipeOutFilename, m_pipeInFilename});
m_process->start(QStringLiteral("pkexec"), {pipe_bin, m_wirelessInterface, QStringLiteral("-pipe"), m_pipeOutFilename, m_pipeInFilename});
}
void BackendPipe::receivedData()
@ -299,4 +313,97 @@ void BackendPipe::receivedData()
printf("%s\n", a.constData());
}
}
}
ssize_t BackendViaNamedPipe::readFromPipe(void *data, size_t length)
{
return read(m_pipeIn, data, length);
}
ssize_t BackendViaNamedPipe::writeToPipe(const void *data, size_t length)
{
return write(m_pipeOut, data, length);
}
BackendUdpWrapper::BackendUdpWrapper(const QHostAddress &backendAddr, quint16 backendPort, QObject *parent) : QObject(parent)
{
m_backendAddress = backendAddr;
m_backendPort = backendPort;
m_socket = new QUdpSocket(this);
connect(m_socket, &QUdpSocket::readyRead, this, &BackendUdpWrapper::readPendingDatagrams);
}
void BackendUdpWrapper::start()
{
bool connected = false;
for (m_frontendPort = 10100; m_frontendPort < 10200; m_frontendPort++) {
printf("Trying to bind to port %u\n", m_frontendPort);
if (m_socket->bind(QHostAddress::Any, m_frontendPort)) {
printf("Bound to port %u\n", m_frontendPort);
connected = true;
break;
}
}
if (connected) {
emit socketReady(m_frontendPort);
} else {
printf("Failed to bind to port\n");
emit error(tr("Failed to bind to UDP port"));
emit closed();
}
}
void BackendUdpWrapper::readPendingDatagrams()
{
while (m_socket->hasPendingDatagrams()) {
QNetworkDatagram datagram = m_socket->receiveDatagram();
emit receivedData(datagram.data());
}
}
void BackendUdpWrapper::write(const QByteArray &data)
{
m_socket->writeDatagram(data, m_backendAddress, m_backendPort);
}
ssize_t BackendViaUdp::readFromPipe(void *data, size_t length)
{
m_readMutex.lock();
if (m_buffer.size() < length) {
m_readWaitCond.wait(&m_readMutex);
}
memcpy(data, m_buffer.constData(), length);
m_buffer.remove(0, length);
m_readMutex.unlock();
return length;
}
ssize_t BackendViaUdp::writeToPipe(const void *data, size_t length)
{
QMetaObject::invokeMethod(m_socket, "write", Q_ARG(QByteArray, QByteArray((const char *) data, length)));
return length;
}
void BackendViaUdp::receivedData(const QByteArray &data)
{
m_readMutex.lock();
m_buffer.append(data);
m_readWaitCond.wakeAll();
m_readMutex.unlock();
}
void BackendViaUdp::socketReady(quint16 port)
{
pipe_control_code cmd;
cmd.code = VANILLA_PIPE_IN_BIND;
writeToPipe(&cmd, sizeof(cmd));
uint8_t cc;
readFromPipe(&cc, sizeof(cc));
if (cc == VANILLA_PIPE_OUT_BOUND_SUCCESSFUL) {
emit ready();
}
}

View file

@ -1,16 +1,19 @@
#ifndef BACKEND_H
#define BACKEND_H
#include <QBuffer>
#include <QMutex>
#include <QObject>
#include <QProcess>
#include <QThread>
#include <QUdpSocket>
#include <QWaitCondition>
class BackendPipe : public QObject
{
Q_OBJECT
public:
BackendPipe(QObject *parent = nullptr);
BackendPipe(const QString &wirelessInterface, QObject *parent = nullptr);
virtual ~BackendPipe() override;
@ -22,6 +25,7 @@ public slots:
signals:
void pipesAvailable(const QString &in, const QString &out);
void portAvailable(uint16_t port);
void closed();
private slots:
void receivedData();
@ -32,6 +36,7 @@ private:
QString m_pipeInFilename;
uint16_t m_serverPort;
uint16_t m_clientPort;
QString m_wirelessInterface;
};
@ -41,9 +46,13 @@ class Backend : public QObject
public:
Backend(QObject *parent = nullptr);
virtual ~Backend() override;
void interrupt();
// These are all commands that can be issued to the backend. They are re-entrant and can be called at any time.
virtual void interrupt() = 0;
virtual void updateTouch(int x, int y) = 0;
virtual void setButton(int button, int32_t value) = 0;
virtual void requestIDR() = 0;
virtual void setRegion(int region) = 0;
virtual void setBatteryStatus(int status) = 0;
signals:
void videoAvailable(const QByteArray &packet);
@ -51,27 +60,144 @@ signals:
void vibrate(bool on);
void errorOccurred();
void syncCompleted(bool success);
void ready();
void closed();
void error(const QString &err);
public slots:
void sync(const QString &wirelessInterface, uint16_t code);
void connectToConsole(const QString &wirelessInterface);
void updateTouch(int x, int y);
void setButton(int button, int32_t value);
void requestIDR();
void setRegion(int region);
void setBatteryStatus(int status);
// These slots must be called with Qt::QueuedConnection to start the event loops in the backend's thread
virtual void init();
virtual void sync(uint16_t code) = 0;
virtual void connectToConsole() = 0;
};
class BackendViaLocalRoot : public Backend
{
Q_OBJECT
public:
BackendViaLocalRoot(const QString &wirelessInterface, QObject *parent = nullptr);
virtual void interrupt() override;
virtual void updateTouch(int x, int y) override;
virtual void setButton(int button, int32_t value) override;
virtual void requestIDR() override;
virtual void setRegion(int region) override;
virtual void setBatteryStatus(int status) override;
public slots:
virtual void sync(uint16_t code) override;
virtual void connectToConsole() override;
private:
QString m_wirelessInterface;
};
class BackendViaPipe : public Backend
{
Q_OBJECT
public:
BackendViaPipe(QObject *parent = nullptr);
virtual void interrupt() override;
virtual void updateTouch(int x, int y) override;
virtual void setButton(int button, int32_t value) override;
virtual void requestIDR() override;
virtual void setRegion(int region) override;
virtual void setBatteryStatus(int status) override;
public slots:
virtual void sync(uint16_t code) override;
virtual void connectToConsole() override;
protected slots:
virtual ssize_t readFromPipe(void *data, size_t length) = 0;
virtual ssize_t writeToPipe(const void *data, size_t length) = 0;
void quitPipe();
};
class BackendViaNamedPipe : public BackendViaPipe
{
Q_OBJECT
public:
BackendViaNamedPipe(const QString &wirelessInterface, QObject *parent = nullptr);
virtual ~BackendViaNamedPipe() override;
public slots:
virtual void init() override;
private:
BackendPipe *m_pipe;
QThread *m_pipeThread;
int m_pipeIn;
int m_pipeOut;
QMutex m_pipeMutex;
QAtomicInt m_interrupt;
protected slots:
virtual ssize_t readFromPipe(void *data, size_t length) override;
virtual ssize_t writeToPipe(const void *data, size_t length) override;
private slots:
void setUpPipes(const QString &in, const QString &out);
};
class BackendUdpWrapper : public QObject
{
Q_OBJECT
public:
BackendUdpWrapper(const QHostAddress &backendAddr, quint16 backendPort, QObject *parent = nullptr);
public slots:
void start();
void write(const QByteArray &data);
signals:
void receivedData(const QByteArray &data);
void socketReady(quint16 port);
void closed();
void error(const QString &err);
private:
QUdpSocket *m_socket;
QHostAddress m_backendAddress;
quint16 m_frontendPort;
quint16 m_backendPort;
private slots:
void readPendingDatagrams();
};
class BackendViaUdp : public BackendViaPipe
{
Q_OBJECT
public:
BackendViaUdp(const QHostAddress &backendAddr, quint16 backendPort, QObject *parent = nullptr);
virtual ~BackendViaUdp() override;
public slots:
virtual void init() override;
protected slots:
virtual ssize_t readFromPipe(void *data, size_t length) override;
virtual ssize_t writeToPipe(const void *data, size_t length) override;
private:
BackendUdpWrapper *m_socket;
QThread *m_socketThread;
QByteArray m_buffer;
QMutex m_readMutex;
QWaitCondition m_readWaitCond;
private slots:
void receivedData(const QByteArray &data);
void socketReady(quint16 port);
};
#endif // BACKEND_H

11
app/backendinitdialog.cpp Normal file
View file

@ -0,0 +1,11 @@
#include "backendinitdialog.h"
#include <QLabel>
#include <QVBoxLayout>
BackendInitDialog::BackendInitDialog(QWidget *parent) : QDialog(parent)
{
QVBoxLayout *l = new QVBoxLayout(this);
l->addWidget(new QLabel(tr("Initializing backend...")));
}

13
app/backendinitdialog.h Normal file
View file

@ -0,0 +1,13 @@
#ifndef BACKEND_INIT_DIALOG_H
#define BACKEND_INIT_DIALOG_H
#include <QDialog>
class BackendInitDialog : public QDialog
{
Q_OBJECT
public:
BackendInitDialog(QWidget *parent = nullptr);
};
#endif // BACKEND_INIT_DIALOG_H

View file

@ -25,6 +25,7 @@
#include <linux/wireless.h>
#include <vanilla.h>
#include "backendinitdialog.h"
#include "inputconfigdialog.h"
#include "keymap.h"
#include "syncdialog.h"
@ -35,6 +36,8 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent)
QMessageBox::critical(this, tr("SDL2 Error"), tr("SDL2 failed to initialize. Controller support will be unavailable."));
}
m_backend = nullptr;
qRegisterMetaType<uint16_t>("uint16_t");
QHBoxLayout *layout = new QHBoxLayout(this);
@ -71,6 +74,7 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent)
configLayout->addWidget(new QLabel(tr("Wi-Fi Adapter: "), connectionConfigGroupBox), row, 0);
m_wirelessInterfaceComboBox = new QComboBox(connectionConfigGroupBox);
connect(m_wirelessInterfaceComboBox, &QComboBox::currentIndexChanged, this, &MainWindow::closeBackend);
configLayout->addWidget(m_wirelessInterfaceComboBox, row, 1);
row++;
@ -84,7 +88,6 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent)
m_connectBtn = new QPushButton(connectionConfigGroupBox);
m_connectBtn->setCheckable(true);
//m_connectBtn->setEnabled(vanilla_has_config()); // TODO: Implement this properly through the pipe at some point
m_backend = nullptr;
setConnectedState(false);
connect(m_connectBtn, &QPushButton::clicked, this, &MainWindow::setConnectedState);
configLayout->addWidget(m_connectBtn, row, 0, 1, 2);
@ -209,9 +212,6 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent)
configOuterLayout->addStretch();
m_backend = new Backend();
startObjectOnThread(m_backend);
m_videoDecoder = new VideoDecoder();
connect(m_recordBtn, &QPushButton::clicked, m_videoDecoder, &VideoDecoder::enableRecording);
startObjectOnThread(m_videoDecoder);
@ -224,18 +224,10 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent)
startObjectOnThread(m_audioHandler);
QMetaObject::invokeMethod(m_audioHandler, &AudioHandler::run, Qt::QueuedConnection);
connect(m_backend, &Backend::videoAvailable, m_videoDecoder, &VideoDecoder::sendPacket);
connect(m_backend, &Backend::audioAvailable, m_videoDecoder, &VideoDecoder::sendAudio);
connect(m_backend, &Backend::syncCompleted, this, [this](bool e){if (e) m_connectBtn->setEnabled(true);});
connect(m_videoDecoder, &VideoDecoder::frameReady, m_viewer, &Viewer::setImage);
connect(m_videoDecoder, &VideoDecoder::recordingError, this, &MainWindow::recordingError);
connect(m_videoDecoder, &VideoDecoder::recordingFinished, this, &MainWindow::recordingFinished);
connect(m_videoDecoder, &VideoDecoder::requestIDR, m_backend, &Backend::requestIDR, Qt::DirectConnection);
connect(m_backend, &Backend::audioAvailable, m_audioHandler, &AudioHandler::write);
connect(m_backend, &Backend::vibrate, m_gamepadHandler, &GamepadHandler::vibrate, Qt::DirectConnection);
connect(m_viewer, &Viewer::touch, m_backend, &Backend::updateTouch, Qt::DirectConnection);
connect(m_gamepadHandler, &GamepadHandler::gamepadsChanged, this, &MainWindow::populateControllers);
connect(m_gamepadHandler, &GamepadHandler::buttonStateChanged, m_backend, &Backend::setButton, Qt::DirectConnection);
connect(m_viewer, &Viewer::keyPressed, m_gamepadHandler, &GamepadHandler::keyPressed, Qt::DirectConnection);
connect(m_viewer, &Viewer::keyReleased, m_gamepadHandler, &GamepadHandler::keyReleased, Qt::DirectConnection);
@ -258,8 +250,7 @@ MainWindow::~MainWindow()
m_videoDecoder->deleteLater();
m_backend->interrupt();
m_backend->deleteLater();
closeBackend();
for (QThread *t : m_threadMap) {
t->quit();
@ -305,14 +296,22 @@ void MainWindow::populateWirelessInterfaces()
while (tmp)
{
if (tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_PACKET) {
if (isWireless(tmp->ifa_name, nullptr))
m_wirelessInterfaceComboBox->addItem(tmp->ifa_name);
if (isWireless(tmp->ifa_name, nullptr)) {
QString s = tmp->ifa_name;
m_wirelessInterfaceComboBox->addItem(s, s);
}
}
tmp = tmp->ifa_next;
}
freeifaddrs(addrs);
if (m_wirelessInterfaceComboBox->count() > 0) {
m_wirelessInterfaceComboBox->insertSeparator(m_wirelessInterfaceComboBox->count());
}
m_wirelessInterfaceComboBox->addItem(tr("External Server..."));
}
void MainWindow::populateMicrophones()
@ -340,33 +339,77 @@ void MainWindow::populateControllers()
}
}
template<typename T>
void MainWindow::initBackend(T func)
{
if (!m_backend) {
BackendInitDialog *d = new BackendInitDialog(this);
d->open();
QString localWirelessIntf = m_wirelessInterfaceComboBox->currentData().toString();
if (localWirelessIntf.isEmpty()) {
// TODO: Prompt for UDP server address
m_backend = new BackendViaUdp(QHostAddress::LocalHost, 10200);
} else if ((geteuid() != 0)) {
// If not root, use named pipe
m_backend = new BackendViaNamedPipe(localWirelessIntf);
} else {
// If root, use lib locally
m_backend = new BackendViaLocalRoot(localWirelessIntf);
}
connect(m_backend, &Backend::closed, d, &BackendInitDialog::deleteLater);
connect(m_backend, &Backend::ready, d, &BackendInitDialog::deleteLater);
connect(m_backend, &Backend::closed, this, &MainWindow::closeBackend);
connect(m_backend, &Backend::ready, this, func);
connect(m_backend, &Backend::error, this, &MainWindow::showBackendError);
connect(m_backend, &Backend::videoAvailable, m_videoDecoder, &VideoDecoder::sendPacket);
connect(m_backend, &Backend::audioAvailable, m_videoDecoder, &VideoDecoder::sendAudio);
connect(m_backend, &Backend::syncCompleted, this, [this](bool e){if (e) m_connectBtn->setEnabled(true);});
connect(m_videoDecoder, &VideoDecoder::requestIDR, m_backend, &Backend::requestIDR, Qt::DirectConnection);
connect(m_backend, &Backend::audioAvailable, m_audioHandler, &AudioHandler::write);
connect(m_backend, &Backend::vibrate, m_gamepadHandler, &GamepadHandler::vibrate, Qt::DirectConnection);
connect(m_viewer, &Viewer::touch, m_backend, &Backend::updateTouch, Qt::DirectConnection);
connect(m_gamepadHandler, &GamepadHandler::buttonStateChanged, m_backend, &Backend::setButton, Qt::DirectConnection);
startObjectOnThread(m_backend);
QMetaObject::invokeMethod(m_backend, &Backend::init, Qt::QueuedConnection);
} else {
func();
}
}
void MainWindow::showSyncDialog()
{
SyncDialog *d = new SyncDialog(this);
d->setup(m_backend, m_wirelessInterfaceComboBox->currentText());
connect(d, &SyncDialog::finished, d, &SyncDialog::deleteLater);
d->open();
initBackend([this]{
SyncDialog *d = new SyncDialog(this);
d->setup(m_backend);
connect(d, &SyncDialog::finished, d, &SyncDialog::deleteLater);
d->open();
});
}
void MainWindow::setConnectedState(bool on)
{
m_wirelessInterfaceComboBox->setEnabled(!on);
m_syncBtn->setEnabled(!on);
m_connectBtn->setChecked(on);
m_connectBtn->setText(on ? tr("Disconnect") : tr("Connect"));
if (on) {
m_connectBtn->setText(tr("Disconnect"));
initBackend([this]{
QMetaObject::invokeMethod(m_backend, &Backend::connectToConsole, Qt::QueuedConnection);
QMetaObject::invokeMethod(m_backend, "connectToConsole", Qt::QueuedConnection, Q_ARG(QString, m_wirelessInterfaceComboBox->currentText()));
updateVolumeAxis();
updateRegion();
updateBatteryStatus();
updateVolumeAxis();
updateRegion();
updateBatteryStatus();
});
} else {
if (m_backend) {
m_backend->interrupt();
}
m_connectBtn->setText(tr("Connect"));
m_viewer->setImage(QImage());
}
}
@ -390,7 +433,7 @@ void MainWindow::exitFullScreen()
void MainWindow::updateVolumeAxis()
{
m_backend->setButton(VANILLA_AXIS_VOLUME, m_volumeSlider->value() * 0xFF / m_volumeSlider->maximum());
if (m_backend) m_backend->setButton(VANILLA_AXIS_VOLUME, m_volumeSlider->value() * 0xFF / m_volumeSlider->maximum());
}
void MainWindow::volumeChanged(int v)
@ -471,10 +514,25 @@ void MainWindow::updateRegionFromComboBox()
void MainWindow::updateRegion()
{
m_backend->setRegion(m_regionComboBox->currentData().toInt());
if (m_backend) m_backend->setRegion(m_regionComboBox->currentData().toInt());
}
void MainWindow::updateBatteryStatus()
{
m_backend->setBatteryStatus(m_batteryStatusComboBox->currentData().toInt());
if (m_backend) m_backend->setBatteryStatus(m_batteryStatusComboBox->currentData().toInt());
}
void MainWindow::closeBackend()
{
setConnectedState(false);
if (m_backend) {
m_backend->interrupt();
m_backend->deleteLater();
m_backend = nullptr;
}
}
void MainWindow::showBackendError(const QString &err)
{
QMessageBox::critical(this, tr("Backend Error"), err);
}

View file

@ -32,6 +32,9 @@ private:
void updateVolumeAxis();
template<typename T>
void initBackend(T func);
Viewer *m_viewer;
QComboBox *m_wirelessInterfaceComboBox;
@ -82,6 +85,10 @@ private slots:
void takeScreenshot();
void closeBackend();
void showBackendError(const QString &err);
};
#endif // MAINWINDOW_H

View file

@ -139,14 +139,13 @@ void SyncDialog::launchSync()
code += m_code[i] * intPow(10, g_symbolCount - 1 - i);
}
SyncProgressDialog *progressDialog = new SyncProgressDialog(m_backend, m_wirelessInterface, code, this->parentWidget());
SyncProgressDialog *progressDialog = new SyncProgressDialog(m_backend, code, this->parentWidget());
progressDialog->open();
connect(progressDialog, &SyncProgressDialog::finished, this, &SyncProgressDialog::deleteLater);
this->close();
}
void SyncDialog::setup(Backend *backend, const QString &wirelessInterface)
void SyncDialog::setup(Backend *backend)
{
m_backend = backend;
m_wirelessInterface = wirelessInterface;
}

View file

@ -13,7 +13,7 @@ class SyncDialog : public QDialog
public:
SyncDialog(QWidget *parent = nullptr);
void setup(Backend *backend, const QString &wirelessInterface);
void setup(Backend *backend);
private:
void updateLabels();
@ -28,7 +28,6 @@ private:
QHBoxLayout *m_buttonLayout;
Backend *m_backend;
QString m_wirelessInterface;
private slots:
void buttonClicked();

View file

@ -8,7 +8,7 @@
#include "mainwindow.h"
SyncProgressDialog::SyncProgressDialog(Backend *backend, const QString &wirelessInterface, uint16_t code, QWidget *parent) : QDialog(parent)
SyncProgressDialog::SyncProgressDialog(Backend *backend, uint16_t code, QWidget *parent) : QDialog(parent)
{
QVBoxLayout *layout = new QVBoxLayout(this);
@ -53,7 +53,7 @@ SyncProgressDialog::SyncProgressDialog(Backend *backend, const QString &wireless
m_backend = backend;
connect(m_backend, &Backend::syncCompleted, this, &SyncProgressDialog::syncReturned);
QMetaObject::invokeMethod(m_backend, "sync", Q_ARG(QString, wirelessInterface), Q_ARG(uint16_t, code));
QMetaObject::invokeMethod(m_backend, "sync", Q_ARG(uint16_t, code));
}
void SyncProgressDialog::syncReturned(bool success)

View file

@ -12,7 +12,7 @@ class SyncProgressDialog : public QDialog
{
Q_OBJECT
public:
SyncProgressDialog(Backend *backend, const QString &wirelessInterface, uint16_t code, QWidget *parent = nullptr);
SyncProgressDialog(Backend *backend, uint16_t code, QWidget *parent = nullptr);
protected:
virtual void done(int r) override;
@ -22,7 +22,6 @@ private:
Backend *m_backend;
QLabel *m_headerLabel;
QLabel *m_statusLabel;
QString m_wirelessInterface;
uint16_t m_wpsCode;
bool m_cancelled;

View file

@ -32,84 +32,46 @@ int open_fifo(const char *name, int mode)
return f;
}
uint8_t buffer[1048576];
size_t buffer_pos = 0;
pthread_mutex_t buffer_mutex;
int fd_in = 0, fd_out = 0;
in_addr_t udp_client_addr = 0;
in_port_t udp_client_port = 0;
struct sockaddr_in udp_client = {0};
void buffer_start()
ssize_t write_pipe(const void *buf, size_t size)
{
pthread_mutex_lock(&buffer_mutex);
}
void buffer_write(const void *data, size_t length)
{
memcpy(buffer + buffer_pos, data, length);
buffer_pos += length;
}
void buffer_finish()
{
if (fd_out != 0) {
write(fd_out, buffer, buffer_pos);
}
if (udp_client_addr != 0 && udp_client_port != 0) {
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = udp_client_addr;
address.sin_port = htons(udp_client_port);
sendto(fd_in, buffer, buffer_pos, 0, (const struct sockaddr *) &address, sizeof(address));
}
buffer_pos = 0;
pthread_mutex_unlock(&buffer_mutex);
}
void buffer_write_single(void *data, size_t length)
{
buffer_start();
buffer_write(data, length);
buffer_finish();
sendto(fd_in, buf, size, 0, (const struct sockaddr *) &udp_client, sizeof(udp_client));
}
pthread_mutex_t action_mutex;
int action_ended = 0;
int write_control_code(uint8_t code)
{
buffer_write_single(&code, 1);
struct pipe_control_code cmd;
cmd.code = code;
write_pipe(&cmd, sizeof(cmd));
}
void event_handler(void *context, int event_type, const char *data, size_t data_size)
{
uint8_t event_sized = event_type;
uint64_t data_size_sized = data_size;
uint8_t control_code = VANILLA_PIPE_OUT_DATA;
struct pipe_data_command cmd;
buffer_start();
buffer_write(&control_code, sizeof(control_code));
buffer_write(&event_sized, sizeof(event_sized));
buffer_write(&data_size_sized, sizeof(data_size_sized));
buffer_write(data, data_size);
buffer_finish();
cmd.base.code = VANILLA_PIPE_OUT_DATA;
cmd.event_type = event_type;
cmd.data_size = htons(data_size);
memcpy(cmd.data, data, data_size);
write_pipe(&cmd, sizeof(cmd)-sizeof(cmd.data)+data_size);
}
void write_sync_state(uint8_t success)
{
uint8_t cc = VANILLA_PIPE_OUT_SYNC_STATE;
buffer_start();
buffer_write(&cc, sizeof(cc));
buffer_write(&success, sizeof(success));
buffer_finish();
struct pipe_sync_state_command cmd;
cmd.base.code = VANILLA_PIPE_OUT_SYNC_STATE;
cmd.state = success;
write_pipe(&cmd, sizeof(cmd));
}
#define WIRELESS_INTERFACE_MAX_LEN 100
struct sync_args
{
char wireless_interface[WIRELESS_INTERFACE_MAX_LEN];
const char *wireless_interface;
uint16_t code;
};
void *sync_command(void *a)
@ -128,7 +90,7 @@ void *sync_command(void *a)
struct connect_args
{
char wireless_interface[WIRELESS_INTERFACE_MAX_LEN];
const char *wireless_interface;
};
void *connect_command(void *a)
{
@ -144,16 +106,6 @@ void *connect_command(void *a)
return (void *) (size_t) r;
}
void read_string(int fd, char *buf, size_t max)
{
for (size_t i = 0; i < max; i++) {
read(fd, &buf[i], 1);
if (buf[i] == 0) {
break;
}
}
}
void lib_logger(const char *fmt, va_list args)
{
vfprintf(stderr, fmt, args);
@ -180,18 +132,20 @@ void vapipelog(const char *str, ...)
int main(int argc, char **argv)
{
if (argc < 2) {
if (argc < 3) {
goto show_help;
}
if (!strcmp(argv[1], "-pipe")) {
if (argc < 4) {
const char *wireless_interface = argv[1];
if (!strcmp(argv[2], "-pipe")) {
if (argc < 5) {
printf("-pipe requires <in-fifo> and <out-fifo>\n");
goto show_help;
}
const char *pipe_in_filename = argv[2];
const char *pipe_out_filename = argv[3];
const char *pipe_in_filename = argv[3];
const char *pipe_out_filename = argv[4];
umask(0000);
@ -202,30 +156,18 @@ int main(int argc, char **argv)
if ((fd_out = open_fifo(pipe_out_filename, O_WRONLY)) == -1) return 1;
if ((fd_in = open_fifo(pipe_in_filename, O_RDONLY)) == -1) return 1;
} else if (!strcmp(argv[1], "-udp")) {
if (argc < 5) {
printf("-udp requires <server-port> <client-address> <client-port>\n");
} else if (!strcmp(argv[2], "-udp")) {
if (argc < 4) {
printf("-udp requires <server-port>\n");
goto show_help;
}
uint16_t server_port = atoi(argv[2]);
uint16_t server_port = atoi(argv[3]);
if (server_port == 0) {
printf("UDP port provided was invalid\n");
goto show_help;
}
udp_client_addr = inet_addr(argv[3]);
if (udp_client_addr == 0) {
printf("Client IP provided was invalid\n");
goto show_help;
}
udp_client_port = atoi(argv[4]);
if (udp_client_port == 0) {
printf("Client port provided was invalid\n");
goto show_help;
}
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
@ -239,22 +181,23 @@ int main(int argc, char **argv)
fprintf(stderr, "READY\n");
} else {
printf("Unknown mode '%s'\n", argv[1]);
printf("Unknown mode '%s'\n", argv[2]);
goto show_help;
}
uint8_t control_code;
char read_buffer[2048];
ssize_t read_size;
pthread_t current_action = 0;
int m_quit = 0;
vanilla_install_logger(lib_logger);
pthread_mutex_init(&buffer_mutex, NULL);
pthread_mutex_init(&action_mutex, NULL);
while (!m_quit) {
read_size = read(fd_in, &control_code, 1);
struct sockaddr_in sender_addr;
socklen_t sender_addr_len = sizeof(sender_addr);
read_size = recvfrom(fd_in, read_buffer, sizeof(read_buffer), 0, (struct sockaddr *) &sender_addr, &sender_addr_len);
if (read_size == 0) {
continue;
}
@ -268,16 +211,22 @@ int main(int argc, char **argv)
}
pthread_mutex_unlock(&action_mutex);
switch (control_code) {
struct pipe_control_code *control_code = (struct pipe_control_code *) read_buffer;
switch (control_code->code) {
case VANILLA_PIPE_IN_SYNC:
{
if (current_action != 0) {
write_control_code(VANILLA_PIPE_ERR_BUSY);
} else {
struct pipe_sync_command *sync_cmd = (struct pipe_sync_command *) read_buffer;
sync_cmd->code = ntohs(sync_cmd->code);
struct sync_args *args = (struct sync_args *) malloc(sizeof(struct sync_args));
read(fd_in, &args->code, sizeof(args->code));
read_string(fd_in, args->wireless_interface, sizeof(args->wireless_interface));
args->code = sync_cmd->code;
args->wireless_interface = wireless_interface;
write_control_code(VANILLA_PIPE_ERR_SUCCESS);
pthread_create(&current_action, NULL, sync_command, args);
}
break;
@ -288,7 +237,7 @@ int main(int argc, char **argv)
write_control_code(VANILLA_PIPE_ERR_BUSY);
} else {
struct connect_args *args = (struct connect_args *) malloc(sizeof(struct connect_args));
read_string(fd_in, args->wireless_interface, sizeof(args->wireless_interface));
args->wireless_interface = wireless_interface;
write_control_code(VANILLA_PIPE_ERR_SUCCESS);
pthread_create(&current_action, NULL, connect_command, args);
}
@ -296,20 +245,18 @@ int main(int argc, char **argv)
}
case VANILLA_PIPE_IN_BUTTON:
{
int32_t button_id;
int32_t button_value;
read(fd_in, &button_id, sizeof(button_id));
read(fd_in, &button_value, sizeof(button_value));
vanilla_set_button(button_id, button_value);
struct pipe_button_command *btn_cmd = (struct pipe_button_command *) read_buffer;
btn_cmd->id = ntohl(btn_cmd->id);
btn_cmd->value = ntohl(btn_cmd->value);
vanilla_set_button(btn_cmd->id, btn_cmd->value);
break;
}
case VANILLA_PIPE_IN_TOUCH:
{
int32_t touch_x;
int32_t touch_y;
read(fd_in, &touch_x, sizeof(touch_x));
read(fd_in, &touch_y, sizeof(touch_y));
vanilla_set_touch(touch_x, touch_y);
struct pipe_touch_command *touch_cmd = (struct pipe_touch_command *) read_buffer;
touch_cmd->x = ntohl(touch_cmd->x);
touch_cmd->y = ntohl(touch_cmd->y);
vanilla_set_touch(touch_cmd->x, touch_cmd->y);
break;
}
case VANILLA_PIPE_IN_INTERRUPT:
@ -327,26 +274,39 @@ int main(int argc, char **argv)
}
case VANILLA_PIPE_IN_REGION:
{
int8_t region;
read(fd_in, &region, sizeof(region));
vanilla_set_region(region);
struct pipe_region_command *region_cmd = (struct pipe_region_command *) read_buffer;
vanilla_set_region(region_cmd->region);
break;
}
case VANILLA_PIPE_IN_BATTERY:
{
int8_t battery;
read(fd_in, &battery, sizeof(battery));
vanilla_set_battery_status(battery);
struct pipe_battery_command *battery_cmd = (struct pipe_battery_command *) read_buffer;
vanilla_set_battery_status(battery_cmd->battery);
break;
}
case VANILLA_PIPE_IN_QUIT:
m_quit = 1;
break;
case VANILLA_PIPE_IN_BIND:
{
struct pipe_bind_command *bind_cmd = (struct pipe_bind_command *) read_buffer;
// Send success message back to client
uint8_t cc = VANILLA_PIPE_OUT_BOUND_SUCCESSFUL;
sendto(fd_in, &cc, sizeof(cc), 0, (const struct sockaddr *) &sender_addr, sizeof(sender_addr));
// Add client to list
udp_client = sender_addr;
char addr_buf[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &udp_client.sin_addr.s_addr, addr_buf, INET_ADDRSTRLEN);
printf("BIND %s:%u\n", addr_buf, ntohs(udp_client.sin_port));
break;
}
}
}
pthread_mutex_destroy(&action_mutex);
pthread_mutex_destroy(&buffer_mutex);
close(fd_in);
close(fd_out);
@ -354,7 +314,7 @@ int main(int argc, char **argv)
return 0;
show_help:
printf("Usage: %s <mode> [args]\n\n", argv[0]);
printf("Usage: %s <wireless-interface> <mode> [args]\n\n", argv[0]);
printf("vanilla-pipe provides a way to connect a frontend to Vanilla's backend when they\n");
printf("aren't able to run on the same device or in the same environment (e.g. when the \n");
printf("backend must run as root but the frontend must run as user).\n\n");

View file

@ -2,7 +2,7 @@
#define VANILLA_PIPE_H
// Control codes that our pipe will receive
enum PipeOpCodes
enum pipe_opcodes
{
VANILLA_PIPE_IN_SYNC = 1,
VANILLA_PIPE_IN_CONNECT,
@ -11,12 +11,14 @@ enum PipeOpCodes
VANILLA_PIPE_IN_REQ_IDR,
VANILLA_PIPE_IN_REGION,
VANILLA_PIPE_IN_BATTERY,
VANILLA_PIPE_IN_BIND,
VANILLA_PIPE_IN_INTERRUPT = 0x1E,
VANILLA_PIPE_IN_QUIT = 0x1F,
VANILLA_PIPE_OUT_DATA = 0x20,
VANILLA_PIPE_OUT_SYNC_STATE,
VANILLA_PIPE_OUT_BOUND_SUCCESSFUL,
VANILLA_PIPE_OUT_EOF,
// Errors that our pipe will send
@ -26,4 +28,59 @@ enum PipeOpCodes
VANILLA_PIPE_ERR_BUSY
};
#pragma pack(push, 1)
struct pipe_control_code
{
uint8_t code;
};
struct pipe_sync_command
{
struct pipe_control_code base;
uint16_t code;
};
struct pipe_button_command
{
struct pipe_control_code base;
int32_t id;
int32_t value;
};
struct pipe_touch_command
{
struct pipe_control_code base;
int32_t x;
int32_t y;
};
struct pipe_region_command
{
struct pipe_control_code base;
int8_t region;
};
struct pipe_battery_command
{
struct pipe_control_code base;
int8_t battery;
};
struct pipe_sync_state_command
{
struct pipe_control_code base;
uint8_t state;
};
struct pipe_data_command
{
struct pipe_control_code base;
uint8_t event_type;
uint16_t data_size;
uint8_t data[UINT16_MAX];
};
#pragma pack(pop)
#endif // VANILLA_PIPE_H