diff --git a/Config.cpp b/Config.cpp new file mode 100644 index 00000000..d79f944a --- /dev/null +++ b/Config.cpp @@ -0,0 +1,49 @@ +#include "Config.h" + +Config *Config::_instance = new Config(); + +Config::Config() { + AuthRequired = false; + Challenge = ""; + Salt = ""; + SettingsLoaded = false; +} + +void Config::SetPassword(const char *password) { + +} + +void Config::OBSSaveCallback(obs_data_t *save_data, bool saving, void *private_data) { + Config *conf = static_cast(private_data); + + if (saving) { + obs_data_t *settings = obs_data_create(); + obs_data_set_bool(settings, "auth_required", conf->AuthRequired); + obs_data_set_string(settings, "auth_hash", conf->Challenge); + obs_data_set_string(settings, "auth_salt", conf->Salt); + + obs_data_set_obj(save_data, "obs-websocket", settings); + + obs_data_release(settings); + } + else { + obs_data_t *settings = obs_data_get_obj(save_data, "obs-websocket"); + if (!settings) { + settings = obs_data_create(); + obs_data_set_bool(settings, "auth_required", conf->AuthRequired); + obs_data_set_string(settings, "auth_hash", conf->Challenge); + obs_data_set_string(settings, "auth_salt", conf->Salt); + } + + conf->AuthRequired = obs_data_get_bool(settings, "auth_required"); + conf->Challenge = obs_data_get_string(settings, "auth_hash"); + conf->Salt = obs_data_get_string(settings, "auth_salt"); + conf->SettingsLoaded = true; + + obs_data_release(settings); + } +} + +Config* Config::Current() { + return _instance; +} diff --git a/Config.h b/Config.h new file mode 100644 index 00000000..31d4f76c --- /dev/null +++ b/Config.h @@ -0,0 +1,22 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#include + +class Config { + public: + Config(); + void SetPassword(const char *password); + bool AuthRequired; + const char *Challenge; + const char *Salt; + bool SettingsLoaded; + static void OBSSaveCallback(obs_data_t *save_data, bool saving, void *); + + static Config* Current(); + + private: + static Config *_instance; +}; + +#endif // CONFIG_H \ No newline at end of file diff --git a/WSEvents.cpp b/WSEvents.cpp index f43dc2ab..3760602d 100644 --- a/WSEvents.cpp +++ b/WSEvents.cpp @@ -50,6 +50,7 @@ void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void *private owner->OnRecordingStopped(); } else if (event == OBS_FRONTEND_EVENT_EXIT) { + obs_frontend_save(); owner->OnExit(); } } diff --git a/WSRequestHandler.cpp b/WSRequestHandler.cpp index bd0cdd43..4133bf74 100644 --- a/WSRequestHandler.cpp +++ b/WSRequestHandler.cpp @@ -1,8 +1,14 @@ #include "WSRequestHandler.h" #include "obs-websocket.h" +#include "Config.h" #include "Utils.h" -WSRequestHandler::WSRequestHandler(QWebSocket *client) { +WSRequestHandler::WSRequestHandler(QWebSocket *client) : + _authenticated(false), + _messageId(0), + _requestType(""), + _requestData(nullptr) +{ _client = client; messageMap["GetVersion"] = WSRequestHandler::HandleGetVersion; @@ -21,12 +27,28 @@ WSRequestHandler::WSRequestHandler(QWebSocket *client) { messageMap["ToggleMute"] = WSRequestHandler::ErrNotImplemented; messageMap["GetVolumes"] = WSRequestHandler::ErrNotImplemented; messageMap["SetVolume"] = WSRequestHandler::ErrNotImplemented; + + messageMap["GetTransitionList"] = WSRequestHandler::HandleGetTransitionList; + messageMap["GetCurrentTransition"] = WSRequestHandler::HandleGetCurrentTransition; + messageMap["SetCurrentTransition"] = WSRequestHandler::HandleSetCurrentTransition; + + authNotRequired.insert("GetVersion"); + authNotRequired.insert("GetAuthRequired"); + authNotRequired.insert("Authenticate"); + + blog(LOG_INFO, "[obs-websockets] new client connected from %s:%d", _client->peerAddress().toString().toLocal8Bit(), _client->peerPort()); + + connect(_client, &QWebSocket::textMessageReceived, this, &WSRequestHandler::processTextMessage); + connect(_client, &QWebSocket::disconnected, this, &WSRequestHandler::socketDisconnected); } -void WSRequestHandler::handleMessage(const char *message) { - _requestData = obs_data_create_from_json(message); +void WSRequestHandler::processTextMessage(QString textMessage) { + QByteArray msgData = textMessage.toLocal8Bit(); + const char *msg = msgData; + + _requestData = obs_data_create_from_json(msg); if (!_requestData) { - blog(LOG_ERROR, "[obs-websockets] invalid JSON payload for '%s'", message); + blog(LOG_ERROR, "[obs-websockets] invalid JSON payload for '%s'", msg); SendErrorResponse("invalid JSON payload"); return; } @@ -34,6 +56,14 @@ void WSRequestHandler::handleMessage(const char *message) { _requestType = obs_data_get_string(_requestData, "request-type"); _messageId = obs_data_get_int(_requestData, "message-id"); + if (Config::Current()->AuthRequired + && !_authenticated + && authNotRequired.find(_requestType) == authNotRequired.end()) + { + SendErrorResponse("Not Authenticated"); + return; + } + void (*handlerFunc)(WSRequestHandler*) = (messageMap[_requestType]); if (handlerFunc != NULL) { @@ -44,6 +74,17 @@ void WSRequestHandler::handleMessage(const char *message) { } } +void WSRequestHandler::socketDisconnected() { + blog(LOG_INFO, "[obs-websockets] client %s:%d disconnected", _client->peerAddress().toString().toStdString(), _client->peerPort()); + + _client->deleteLater(); + emit disconnected(); +} + +void WSRequestHandler::sendTextMessage(QString textMessage) { + _client->sendTextMessage(textMessage); +} + WSRequestHandler::~WSRequestHandler() { if (_requestData != NULL) { obs_data_release(_requestData); @@ -78,21 +119,20 @@ void WSRequestHandler::SendErrorResponse(const char *errorMessage) { void WSRequestHandler::HandleGetVersion(WSRequestHandler *owner) { obs_data_t *data = obs_data_create(); obs_data_set_double(data, "version", OBS_WEBSOCKET_VERSION); - owner->SendOKResponse(data); obs_data_release(data); } void WSRequestHandler::HandleGetAuthRequired(WSRequestHandler *owner) { - bool authRequired = false; // Auth isn't implemented yet - + bool authRequired = Config::Current()->AuthRequired; + obs_data_t *data = obs_data_create(); obs_data_set_bool(data, "authRequired", authRequired); + if (authRequired) { - // Just here for protocol doc - obs_data_set_string(data, "challenge", ""); - obs_data_set_string(data, "salt", ""); + obs_data_set_string(data, "challenge", Config::Current()->Challenge); + obs_data_set_string(data, "salt", Config::Current()->Salt); } owner->SendOKResponse(data); @@ -102,11 +142,14 @@ void WSRequestHandler::HandleGetAuthRequired(WSRequestHandler *owner) { void WSRequestHandler::HandleAuthenticate(WSRequestHandler *owner) { const char *auth = obs_data_get_string(owner->_requestData, "auth"); - if (!auth) { + if (!auth || strlen(auth) < 1) { owner->SendErrorResponse("auth not specified!"); return; } + // TODO : Implement auth here + + owner->_authenticated = true; owner->SendOKResponse(); } @@ -203,6 +246,52 @@ void WSRequestHandler::HandleStartStopRecording(WSRequestHandler *owner) { owner->SendOKResponse(); } +void WSRequestHandler::HandleGetTransitionList(WSRequestHandler *owner) { + obs_frontend_source_list transitionList = {}; + obs_frontend_get_transitions(&transitionList); + + obs_data_array_t* transitions = obs_data_array_create(); + for (size_t i = 0; i < (&transitionList)->sources.num; i++) { + obs_source_t* transition = (&transitionList)->sources.array[i]; + + obs_data_t *obj = obs_data_create(); + obs_data_set_string(obj, "name", obs_source_get_name(transition)); + + obs_data_array_push_back(transitions, obj); + } + obs_frontend_source_list_free(&transitionList); + + obs_data_t *response = obs_data_create(); + obs_data_set_string(response, "current-transition", obs_source_get_name(obs_frontend_get_current_transition())); + obs_data_set_array(response, "transitions", transitions); + owner->SendOKResponse(response); + + obs_data_release(response); +} + +void WSRequestHandler::HandleGetCurrentTransition(WSRequestHandler *owner) { + obs_data_t *response = obs_data_create(); + obs_data_set_string(response, "name", obs_source_get_name(obs_frontend_get_current_transition())); + owner->SendOKResponse(response); + + obs_data_release(response); +} + +void WSRequestHandler::HandleSetCurrentTransition(WSRequestHandler *owner) { + const char *name = obs_data_get_string(owner->_requestData, "transition-name"); + obs_source_t *transition = obs_get_source_by_name(name); + + if (transition) { + obs_frontend_set_current_transition(transition); + owner->SendOKResponse(); + + obs_source_release(transition); + } + else { + owner->SendErrorResponse("requested transition does not exist"); + } +} + void WSRequestHandler::ErrNotImplemented(WSRequestHandler *owner) { owner->SendErrorResponse("not implemented"); } diff --git a/WSRequestHandler.h b/WSRequestHandler.h index a01001e9..241d75f4 100644 --- a/WSRequestHandler.h +++ b/WSRequestHandler.h @@ -2,23 +2,35 @@ #define WSREQUESTHANDLER_H #include +#include #include #include -class WSRequestHandler +class WSRequestHandler : public QObject { + Q_OBJECT + public: explicit WSRequestHandler(QWebSocket *client); ~WSRequestHandler(); - void handleMessage(const char* message); + void sendTextMessage(QString textMessage); + + private Q_SLOTS: + void processTextMessage(QString textMessage); + void socketDisconnected(); + + Q_SIGNALS: + void disconnected(); private: QWebSocket *_client; - long _messageId; + bool _authenticated; + unsigned long _messageId; const char *_requestType; obs_data_t *_requestData; std::map messageMap; + std::set authNotRequired; void SendOKResponse(obs_data_t *additionalFields = NULL); void SendErrorResponse(const char *errorMessage); @@ -27,13 +39,19 @@ class WSRequestHandler static void HandleGetVersion(WSRequestHandler *owner); static void HandleGetAuthRequired(WSRequestHandler *owner); static void HandleAuthenticate(WSRequestHandler *owner); + static void HandleSetCurrentScene(WSRequestHandler *owner); static void HandleGetCurrentScene(WSRequestHandler *owner); static void HandleGetSceneList(WSRequestHandler *owner); static void HandleSetSourceRender(WSRequestHandler *owner); + static void HandleGetStreamingStatus(WSRequestHandler *owner); static void HandleStartStopStreaming(WSRequestHandler *owner); static void HandleStartStopRecording(WSRequestHandler *owner); + + static void HandleGetTransitionList(WSRequestHandler *owner); + static void HandleGetCurrentTransition(WSRequestHandler *owner); + static void HandleSetCurrentTransition(WSRequestHandler *owner); }; #endif // WSPROTOCOL_H \ No newline at end of file diff --git a/WSServer.cpp b/WSServer.cpp index 47252dbe..2dcd2fc4 100644 --- a/WSServer.cpp +++ b/WSServer.cpp @@ -31,7 +31,7 @@ WSServer::~WSServer() void WSServer::broadcast(QString message) { - Q_FOREACH(QWebSocket *pClient, _clients) { + Q_FOREACH(WSRequestHandler *pClient, _clients) { pClient->sendTextMessage(message); } } @@ -40,31 +40,17 @@ void WSServer::onNewConnection() { QWebSocket *pSocket = _wsServer->nextPendingConnection(); - blog(LOG_INFO, "[obs-websockets] new client connected from %s:%d", pSocket->peerAddress().toString().toStdString(), pSocket->peerPort()); + if (pSocket) { + WSRequestHandler *pHandler = new WSRequestHandler(pSocket); - connect(pSocket, &QWebSocket::textMessageReceived, this, &WSServer::processTextMessage); - connect(pSocket, &QWebSocket::disconnected, this, &WSServer::socketDisconnected); - - _clients << pSocket; -} - -void WSServer::processTextMessage(QString textMessage) { - QWebSocket *pSender = qobject_cast(sender()); - if (pSender) { - const char *msg = textMessage.toLocal8Bit(); - blog(LOG_INFO, "[obs-websockets] new message : %s", msg); - - WSRequestHandler *handler = new WSRequestHandler(pSender); - handler->handleMessage(msg); - delete handler; + connect(pHandler, &WSRequestHandler::disconnected, this, &WSServer::socketDisconnected); + _clients << pHandler; } } void WSServer::socketDisconnected() { - QWebSocket *pClient = qobject_cast(sender()); - - blog(LOG_INFO, "[obs-websockets] client %s:%d disconnected", pClient->peerAddress().toString().toStdString(), pClient->peerPort()); + WSRequestHandler *pClient = qobject_cast(sender()); if (pClient) { _clients.removeAll(pClient); diff --git a/WSServer.h b/WSServer.h index 211ac25b..f6cd009c 100644 --- a/WSServer.h +++ b/WSServer.h @@ -4,6 +4,7 @@ #include #include #include +#include "WSRequestHandler.h" QT_FORWARD_DECLARE_CLASS(QWebSocketServer) QT_FORWARD_DECLARE_CLASS(QWebSocket) @@ -19,12 +20,11 @@ class WSServer : public QObject private Q_SLOTS: void onNewConnection(); - void processTextMessage(QString textMessage); void socketDisconnected(); private: QWebSocketServer *_wsServer; - QList _clients; + QList _clients; }; #endif // WSSERVER_H \ No newline at end of file diff --git a/obs-websocket.cpp b/obs-websocket.cpp index b73f5e74..78e51c4f 100644 --- a/obs-websocket.cpp +++ b/obs-websocket.cpp @@ -1,8 +1,10 @@ #include #include + #include "obs-websocket.h" #include "WSEvents.h" #include "WSServer.h" +#include "Config.h" OBS_DECLARE_MODULE() OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US") @@ -12,11 +14,13 @@ WSServer *server; bool obs_module_load(void) { - blog(LOG_INFO, "[obs-websockets] you can haz websockets (version %f)", OBS_WEBSOCKET_VERSION); + blog(LOG_INFO, "[obs-websockets] you can haz websockets (version %.2f)", OBS_WEBSOCKET_VERSION); server = new WSServer(4444); eventHandler = new WSEvents(server); + obs_frontend_add_save_callback(Config::OBSSaveCallback, Config::Current()); + return true; }