Files
video-shader-toys/apps/LoopThroughWithOpenGLCompositing/ControlServer.h
2026-05-03 11:16:56 +10:00

100 lines
3.4 KiB
C++

#pragma once
#include "NativeSockets.h"
#include <winsock2.h>
#include <atomic>
#include <filesystem>
#include <functional>
#include <map>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
class JsonValue;
class ControlServer
{
public:
struct Callbacks
{
std::function<std::string()> getStateJson;
std::function<bool(const std::string&, std::string&)> addLayer;
std::function<bool(const std::string&, std::string&)> removeLayer;
std::function<bool(const std::string&, int, std::string&)> moveLayer;
std::function<bool(const std::string&, std::size_t, std::string&)> moveLayerToIndex;
std::function<bool(const std::string&, bool, std::string&)> setLayerBypass;
std::function<bool(const std::string&, const std::string&, std::string&)> setLayerShader;
std::function<bool(const std::string&, const std::string&, const std::string&, std::string&)> updateLayerParameter;
std::function<bool(const std::string&, std::string&)> resetLayerParameters;
std::function<bool(const std::string&, std::string&)> saveStackPreset;
std::function<bool(const std::string&, std::string&)> loadStackPreset;
std::function<bool(std::string&)> reloadShader;
};
ControlServer();
~ControlServer();
bool Start(const std::filesystem::path& uiRoot, unsigned short preferredPort, const Callbacks& callbacks, std::string& error);
void Stop();
void BroadcastState();
unsigned short GetPort() const { return mPort; }
private:
struct ClientConnection
{
UniqueSocket socket;
bool websocket = false;
};
struct HttpRequest
{
std::string method;
std::string path;
std::map<std::string, std::string> headers;
std::string body;
};
struct HttpResponse
{
std::string status;
std::string contentType;
std::string body;
bool broadcastState = false;
};
void ServerLoop();
bool HandleHttpClient(UniqueSocket clientSocket);
bool TryAcceptClient();
bool SendHttpResponse(SOCKET clientSocket, const HttpResponse& response);
bool SendHttpResponse(SOCKET clientSocket, const std::string& status, const std::string& contentType, const std::string& body);
bool HandleHttpRequest(UniqueSocket clientSocket, const std::string& request);
bool HandleWebSocketUpgrade(UniqueSocket clientSocket, const HttpRequest& request);
HttpResponse RouteHttpRequest(const HttpRequest& request);
HttpResponse ServeGetRequest(const HttpRequest& request) const;
HttpResponse ServeUiAsset(const std::string& relativePath) const;
HttpResponse HandleApiPost(const HttpRequest& request);
bool InvokePostRoute(const std::string& path, const JsonValue& root, std::string& actionError);
bool SendWebSocketText(SOCKET clientSocket, const std::string& payload);
void BroadcastStateLocked();
std::string LoadUiAsset(const std::string& relativePath, std::string& contentType) const;
std::string BuildJsonResponse(bool success, const std::string& error = std::string()) const;
static std::string Base64Encode(const unsigned char* data, DWORD dataLength);
static std::string ComputeWebSocketAcceptKey(const std::string& clientKey);
static std::string GetHeaderValue(const HttpRequest& request, const std::string& headerName);
static bool ParseHttpRequest(const std::string& rawRequest, HttpRequest& request);
private:
std::filesystem::path mUiRoot;
Callbacks mCallbacks;
UniqueSocket mListenSocket;
unsigned short mPort;
std::thread mThread;
std::atomic<bool> mRunning;
mutable std::mutex mMutex;
std::vector<ClientConnection> mClients;
};