diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..3e9749f0 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "deps/websocketpp"] + path = deps/websocketpp + url = https://github.com/zaphoyd/websocketpp.git +[submodule "deps/asio"] + path = deps/asio + url = https://github.com/chriskohlhoff/asio.git diff --git a/CI/build-macos.sh b/CI/build-macos.sh index f528ce2e..c41ebaf6 100755 --- a/CI/build-macos.sh +++ b/CI/build-macos.sh @@ -20,6 +20,7 @@ echo "[obs-websocket] Building 'obs-websocket' for macOS." mkdir -p build && cd build cmake .. \ -DQTDIR=/usr/local/opt/qt \ + -DBOOST_ROOT=/usr/local/opt/boost \ -DLIBOBS_INCLUDE_DIR=../../obs-studio/libobs \ -DLIBOBS_LIB=../../obs-studio/libobs \ -DOBS_FRONTEND_LIB="$(pwd)/../../obs-studio/build/UI/obs-frontend-api/libobs-frontend-api.dylib" \ diff --git a/CI/install-dependencies-xenial.sh b/CI/install-dependencies-xenial.sh index b6dda1c6..4b4041bd 100755 --- a/CI/install-dependencies-xenial.sh +++ b/CI/install-dependencies-xenial.sh @@ -11,7 +11,8 @@ apt-get install -y \ checkinstall \ cmake \ obs-studio \ - libqt5websockets5-dev + qtbase5-dev \ + libboost-all-dev # Dirty hack wget -O /usr/include/obs/obs-frontend-api.h https://raw.githubusercontent.com/obsproject/obs-studio/master/UI/obs-frontend-api/obs-frontend-api.h diff --git a/CI/macos/obs-websocket.pkgproj b/CI/macos/obs-websocket.pkgproj index b8159920..766efa15 100644 --- a/CI/macos/obs-websocket.pkgproj +++ b/CI/macos/obs-websocket.pkgproj @@ -12,123 +12,6 @@ CHILDREN - - CHILDREN - - - CHILDREN - - - CHILDREN - - - CHILDREN - - - CHILDREN - - - CHILDREN - - GID - 80 - PATH - ../../build/QtNetwork - PATH_TYPE - 1 - PERMISSIONS - 292 - TYPE - 3 - UID - 0 - - - CHILDREN - - GID - 80 - PATH - ../../build/QtWebSockets - PATH_TYPE - 1 - PERMISSIONS - 292 - TYPE - 3 - UID - 0 - - - GID - 80 - PATH - bin - PATH_TYPE - 0 - PERMISSIONS - 509 - TYPE - 2 - UID - 0 - - - GID - 80 - PATH - Resources - PATH_TYPE - 0 - PERMISSIONS - 509 - TYPE - 2 - UID - 0 - - - GID - 80 - PATH - Contents - PATH_TYPE - 0 - PERMISSIONS - 509 - TYPE - 2 - UID - 0 - - - GID - 80 - PATH - OBS.app - PATH_TYPE - 0 - PERMISSIONS - 509 - TYPE - 2 - UID - 0 - - - GID - 80 - PATH - Applications - PATH_TYPE - 0 - PERMISSIONS - 509 - TYPE - 1 - UID - 0 - CHILDREN diff --git a/CI/package-macos.sh b/CI/package-macos.sh index 62bfb622..7fc2e59b 100755 --- a/CI/package-macos.sh +++ b/CI/package-macos.sh @@ -12,9 +12,6 @@ fi echo "[obs-websocket] Preparing package build" export QT_CELLAR_PREFIX="$(/usr/bin/find /usr/local/Cellar/qt -d 1 | sort -t '.' -k 1,1n -k 2,2n -k 3,3n | tail -n 1)" -export WS_LIB="/usr/local/opt/qt/lib/QtWebSockets.framework/QtWebSockets" -export NET_LIB="/usr/local/opt/qt/lib/QtNetwork.framework/QtNetwork" - export GIT_HASH=$(git rev-parse --short HEAD) export VERSION="$GIT_HASH-$TRAVIS_BRANCH" @@ -27,46 +24,17 @@ fi export FILENAME="obs-websocket-$VERSION.pkg" export LATEST_FILENAME="obs-websocket-latest-$LATEST_VERSION.pkg" -echo "[obs-websocket] Copying Qt dependencies" -if [ ! -f ./build/$(basename $WS_LIB) ]; then cp $WS_LIB ./build; fi -if [ ! -f ./build/$(basename $NET_LIB) ]; then cp $NET_LIB ./build; fi - -chmod +rw ./build/QtWebSockets ./build/QtNetwork - -echo "[obs-websocket] Modifying QtNetwork" -install_name_tool \ - -id @rpath/QtNetwork \ - -change /usr/local/opt/qt/lib/QtNetwork.framework/Versions/5/QtNetwork @rpath/QtNetwork \ - -change $QT_CELLAR_PREFIX/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore \ - ./build/QtNetwork - -echo "[obs-websocket] Modifying QtWebSockets" -install_name_tool \ - -id @rpath/QtWebSockets \ - -change /usr/local/opt/qt/lib/QtWebSockets.framework/Versions/5/QtWebSockets @rpath/QtWebSockets \ - -change $QT_CELLAR_PREFIX/lib/QtNetwork.framework/Versions/5/QtNetwork @rpath/QtNetwork \ - -change $QT_CELLAR_PREFIX/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore \ - ./build/QtWebSockets - echo "[obs-websocket] Modifying obs-websocket.so" install_name_tool \ - -change /usr/local/opt/qt/lib/QtWebSockets.framework/Versions/5/QtWebSockets @rpath/QtWebSockets \ -change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets @rpath/QtWidgets \ - -change /usr/local/opt/qt/lib/QtNetwork.framework/Versions/5/QtNetwork @rpath/QtNetwork \ -change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui @rpath/QtGui \ -change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore \ ./build/obs-websocket.so # Check if replacement worked -echo "[obs-websocket] Dependencies for QtNetwork" -otool -L ./build/QtNetwork -echo "[obs-websocket] Dependencies for QtWebSockets" -otool -L ./build/QtWebSockets echo "[obs-websocket] Dependencies for obs-websocket" otool -L ./build/obs-websocket.so -chmod -w ./build/QtWebSockets ./build/QtNetwork - echo "[obs-websocket] Actual package build" packagesbuild ./CI/macos/obs-websocket.pkgproj diff --git a/CI/package-xenial.sh b/CI/package-xenial.sh index b1dda473..dde64929 100755 --- a/CI/package-xenial.sh +++ b/CI/package-xenial.sh @@ -17,7 +17,7 @@ PAGER=cat checkinstall -y --type=debian --fstrans=no --nodoc \ --backup=no --deldoc=yes --install=no \ --pkgname=obs-websocket --pkgversion="$PKG_VERSION" \ --pkglicense="GPLv2.0" --maintainer="contact@slepin.fr" \ - --requires="libqt5websockets5" --pkggroup="video" \ + --pkggroup="video" \ --pkgsource="https://github.com/Palakis/obs-websocket" \ --pakdir="/package" diff --git a/CMakeLists.txt b/CMakeLists.txt index 50d81520..e8897f28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,14 +6,26 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) if (WIN32 OR APPLE) include(external/FindLibObs.cmake) endif() +add_definitions(-DASIO_STANDALONE) + +if (UNIX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +endif() + +if (WIN32 OR APPLE) + include(external/FindLibObs.cmake) +endif() + find_package(LibObs REQUIRED) find_package(Qt5Core REQUIRED) -find_package(Qt5WebSockets REQUIRED) find_package(Qt5Widgets REQUIRED) +find_package(Boost REQUIRED) set(obs-websocket_SOURCES src/obs-websocket.cpp @@ -52,13 +64,14 @@ add_library(obs-websocket MODULE include_directories( "${LIBOBS_INCLUDE_DIR}/../UI/obs-frontend-api" ${Qt5Core_INCLUDES} - ${Qt5WebSockets_INCLUDES} - ${Qt5Widgets_INCLUDES}) + ${Qt5Widgets_INCLUDES} + ${Boost_INCLUDE_DIRS} + "${CMAKE_SOURCE_DIR}/deps/asio/asio/include" + "${CMAKE_SOURCE_DIR}/deps/websocketpp") target_link_libraries(obs-websocket libobs Qt5::Core - Qt5::WebSockets Qt5::Widgets) # --- End of section --- @@ -70,6 +83,8 @@ if(WIN32) message(FATAL_ERROR "Could not find OBS Frontend API's library !") endif() + add_definitions(-D_WEBSOCKETPP_CPP11_STL_) + if(CMAKE_SIZEOF_VOID_P EQUAL 8) set(ARCH_NAME "64bit") set(OBS_BUILDDIR_ARCH "build64") @@ -102,8 +117,6 @@ if(WIN32) COMMAND if $==1 ("${CMAKE_COMMAND}" -E copy "$" - "${QTDIR}/bin/Qt5WebSockets.dll" - "${QTDIR}/bin/Qt5Network.dll" "${RELEASE_DIR}/obs-plugins/${ARCH_NAME}") # If config is RelWithDebInfo, package release files @@ -118,8 +131,6 @@ if(WIN32) COMMAND if $==1 ("${CMAKE_COMMAND}" -E copy "$" - "${QTDIR}/bin/Qt5WebSockets.dll" - "${QTDIR}/bin/Qt5Network.dll" "${RELEASE_DIR}/obs-plugins/${ARCH_NAME}") COMMAND if $==1 ("${CMAKE_COMMAND}" -E copy @@ -130,8 +141,6 @@ if(WIN32) COMMAND if $==1 ( "${CMAKE_COMMAND}" -E copy "$" - "${QTDIR}/bin/Qt5WebSocketsd.dll" - "${QTDIR}/bin/Qt5Networkd.dll" "${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$/obs-plugins/${ARCH_NAME}") COMMAND if $==1 ( @@ -155,11 +164,8 @@ endif() # --- Linux-specific build settings and tasks --- if(UNIX AND NOT APPLE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") - set_target_properties(obs-websocket PROPERTIES PREFIX "") - target_link_libraries(obs-websocket - obs-frontend-api) + target_link_libraries(obs-websocket obs-frontend-api) file(GLOB locale_files data/locale/*.ini) diff --git a/appveyor.yml b/appveyor.yml index c12a2501..b75bfd83 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -8,6 +8,7 @@ install: - 7z x dependencies2015.zip -odependencies2015 - set DepsPath32=%CD%\dependencies2015\win32 - set DepsPath64=%CD%\dependencies2015\win64 + - set BoostRoot=C:\Libraries\boost_1_67_0 - call C:\projects\obs-websocket\CI\install-setup-qt.cmd - set build_config=RelWithDebInfo - call C:\projects\obs-websocket\CI\install-build-obs.cmd @@ -15,9 +16,9 @@ install: - mkdir build32 - mkdir build64 - cd ./build32 - - cmake -G "Visual Studio 14 2015" -DQTDIR="%QTDIR32%" -DLibObs_DIR="C:\projects\obs-studio\build32\libobs" -DLIBOBS_INCLUDE_DIR="C:\projects\obs-studio\libobs" -DLIBOBS_LIB="C:\projects\obs-studio\build32\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="C:\projects\obs-studio\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" .. + - cmake -G "Visual Studio 14 2015" -DQTDIR="%QTDIR32%" -DBOOST_ROOT="%BoostRoot%" -DLibObs_DIR="C:\projects\obs-studio\build32\libobs" -DLIBOBS_INCLUDE_DIR="C:\projects\obs-studio\libobs" -DLIBOBS_LIB="C:\projects\obs-studio\build32\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="C:\projects\obs-studio\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" .. - cd ../build64 - - cmake -G "Visual Studio 14 2015 Win64" -DQTDIR="%QTDIR64%" -DLibObs_DIR="C:\projects\obs-studio\build64\libobs" -DLIBOBS_INCLUDE_DIR="C:\projects\obs-studio\libobs" -DLIBOBS_LIB="C:\projects\obs-studio\build64\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="C:\projects\obs-studio\build64\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" .. + - cmake -G "Visual Studio 14 2015 Win64" -DQTDIR="%QTDIR64%" -DBOOST_ROOT="%BoostRoot%" -DLibObs_DIR="C:\projects\obs-studio\build64\libobs" -DLIBOBS_INCLUDE_DIR="C:\projects\obs-studio\libobs" -DLIBOBS_LIB="C:\projects\obs-studio\build64\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="C:\projects\obs-studio\build64\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" .. build_script: - call msbuild /m /p:Configuration=%build_config% C:\projects\obs-websocket\build32\obs-websocket.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" diff --git a/deps/asio b/deps/asio new file mode 160000 index 00000000..b73dc1d2 --- /dev/null +++ b/deps/asio @@ -0,0 +1 @@ +Subproject commit b73dc1d2c0ecb9452a87c26544d7f71e24342df6 diff --git a/deps/websocketpp b/deps/websocketpp new file mode 160000 index 00000000..c6d7e295 --- /dev/null +++ b/deps/websocketpp @@ -0,0 +1 @@ +Subproject commit c6d7e295bf5a0ab9b5f896720cc1a0e0fdc397a7 diff --git a/src/Config.cpp b/src/Config.cpp index 43ea8d99..c38bd1ad 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -36,7 +36,12 @@ with this program. If not, see #define QT_TO_UTF8(str) str.toUtf8().constData() -Config* Config::_instance = new Config(); +ConfigPtr Config::_instance = ConfigPtr(new Config()); + +ConfigPtr Config::Current() +{ + return _instance; +} Config::Config() : ServerEnabled(true), @@ -179,8 +184,3 @@ bool Config::CheckAuth(QString response) return authSuccess; } - -Config* Config::Current() -{ - return _instance; -} diff --git a/src/Config.h b/src/Config.h index 80f3c0cd..8966f2d0 100644 --- a/src/Config.h +++ b/src/Config.h @@ -20,9 +20,15 @@ with this program. If not, see #define CONFIG_H #include +#include + +class Config; +typedef QSharedPointer ConfigPtr; class Config { public: + static ConfigPtr Current(); + Config(); ~Config(); void Load(); @@ -46,10 +52,8 @@ class Config { QString SessionChallenge; bool SettingsLoaded; - static Config* Current(); - private: - static Config* _instance; + static ConfigPtr _instance; }; #endif // CONFIG_H diff --git a/src/Utils.cpp b/src/Utils.cpp index b73ed51c..570e676d 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -376,15 +376,6 @@ void Utils::SysTrayNotify(QString &text, trayIcon->showMessage(title, text, icon); } -QString Utils::FormatIPAddress(QHostAddress &addr) { - QRegExp v4regex("(::ffff:)(((\\d).){3})", Qt::CaseInsensitive); - QString addrString = addr.toString(); - if (addrString.contains(v4regex)) { - addrString = QHostAddress(addr.toIPv4Address()).toString(); - } - return addrString; -} - const char* Utils::GetRecordingFolder() { config_t* profile = obs_frontend_get_profile_config(); QString outputMode = config_get_string(profile, "Output", "Mode"); diff --git a/src/Utils.h b/src/Utils.h index ba97d5cf..651991d6 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -26,7 +26,6 @@ with this program. If not, see #include #include #include -#include #include #include @@ -70,8 +69,6 @@ class Utils { QSystemTrayIcon::MessageIcon n, QString title = QString("obs-websocket")); - static QString FormatIPAddress(QHostAddress &addr); - static const char* GetRecordingFolder(); static bool SetRecordingFolder(const char* path); diff --git a/src/WSEvents.cpp b/src/WSEvents.cpp index 7bf30dc7..29aa8428 100644 --- a/src/WSEvents.cpp +++ b/src/WSEvents.cpp @@ -62,9 +62,17 @@ void* calldata_get_ptr(const calldata_t* data, const char* name) { return ptr; } -WSEvents* WSEvents::Instance = nullptr; +WSEventsPtr WSEvents::_instance = WSEventsPtr(nullptr); -WSEvents::WSEvents(WSServer* srv) { +WSEventsPtr WSEvents::Current() { + return _instance; +} + +void WSEvents::ResetCurrent(WSServerPtr srv) { + _instance = WSEventsPtr(new WSEvents(srv)); +} + +WSEvents::WSEvents(WSServerPtr srv) { _srv = srv; obs_frontend_add_event_callback(WSEvents::FrontendEventHandler, this); @@ -214,7 +222,7 @@ void WSEvents::broadcastUpdate(const char* updateType, obs_data_apply(update, additionalFields); QString json = obs_data_get_json(update); - _srv->broadcast(json); + _srv->broadcast(json.toStdString()); if (Config::Current()->DebugEnabled) blog(LOG_DEBUG, "Update << '%s'", json.toUtf8().constData()); diff --git a/src/WSEvents.h b/src/WSEvents.h index 7b7cf95e..97187404 100644 --- a/src/WSEvents.h +++ b/src/WSEvents.h @@ -22,17 +22,27 @@ with this program. If not, see #include #include + #include +#include + #include "WSServer.h" -class WSEvents : public QObject { - Q_OBJECT - public: - explicit WSEvents(WSServer* srv); +class WSEvents; +typedef QSharedPointer WSEventsPtr; + +class WSEvents : public QObject +{ +Q_OBJECT + +public: + static WSEventsPtr Current(); + static void ResetCurrent(WSServerPtr srv); + + explicit WSEvents(WSServerPtr srv); ~WSEvents(); static void FrontendEventHandler( enum obs_frontend_event event, void* privateData); - static WSEvents* Instance; void connectSceneSignals(obs_source_t* scene); void hookTransitionBeginEvent(); @@ -44,14 +54,16 @@ class WSEvents : public QObject { bool HeartbeatIsActive; - private slots: +private slots: void deferredInitOperations(); void StreamStatus(); void Heartbeat(); void TransitionDurationChanged(int ms); - private: - WSServer* _srv; +private: + static WSEventsPtr _instance; + + WSServerPtr _srv; OBSSource currentScene; bool pulse; diff --git a/src/WSRequestHandler.cpp b/src/WSRequestHandler.cpp index 321fa5a6..e9c49138 100644 --- a/src/WSRequestHandler.cpp +++ b/src/WSRequestHandler.cpp @@ -128,26 +128,23 @@ QSet WSRequestHandler::authNotRequired { "Authenticate" }; -WSRequestHandler::WSRequestHandler(QWebSocket* client) : +WSRequestHandler::WSRequestHandler(QVariantHash& connProperties) : _messageId(0), _requestType(""), data(nullptr), - _client(client) + _connProperties(connProperties) { } -void WSRequestHandler::processIncomingMessage(QString textMessage) { - QByteArray msgData = textMessage.toUtf8(); - const char* msg = msgData.constData(); +std::string WSRequestHandler::processIncomingMessage(std::string& textMessage) { + std::string msgContainer(textMessage); + const char* msg = msgContainer.c_str(); data = obs_data_create_from_json(msg); if (!data) { - if (!msg) - msg = ""; - blog(LOG_ERROR, "invalid JSON payload received for '%s'", msg); SendErrorResponse("invalid JSON payload"); - return; + return _response; } if (Config::Current()->DebugEnabled) { @@ -158,18 +155,18 @@ void WSRequestHandler::processIncomingMessage(QString textMessage) { || !hasField("message-id")) { SendErrorResponse("missing request parameters"); - return; + return _response; } _requestType = obs_data_get_string(data, "request-type"); _messageId = obs_data_get_string(data, "message-id"); if (Config::Current()->AuthRequired - && (_client->property(PROP_AUTHENTICATED).toBool() == false) - && (authNotRequired.find(_requestType) == authNotRequired.end())) + && (!authNotRequired.contains(_requestType)) + && (_connProperties.value(PROP_AUTHENTICATED).toBool() == false)) { SendErrorResponse("Not Authenticated"); - return; + return _response; } void (*handlerFunc)(WSRequestHandler*) = (messageMap[_requestType]); @@ -178,6 +175,8 @@ void WSRequestHandler::processIncomingMessage(QString textMessage) { handlerFunc(this); else SendErrorResponse("invalid request type"); + + return _response; } WSRequestHandler::~WSRequestHandler() { @@ -215,11 +214,10 @@ void WSRequestHandler::SendErrorResponse(obs_data_t* additionalFields) { } void WSRequestHandler::SendResponse(obs_data_t* response) { - QString json = obs_data_get_json(response); - _client->sendTextMessage(json); + _response = obs_data_get_json(response); if (Config::Current()->DebugEnabled) - blog(LOG_DEBUG, "Response << '%s'", json.toUtf8().constData()); + blog(LOG_DEBUG, "Response << '%s'", _response.c_str()); } bool WSRequestHandler::hasField(QString name) { diff --git a/src/WSRequestHandler.h b/src/WSRequestHandler.h index cb259827..9fab30e4 100644 --- a/src/WSRequestHandler.h +++ b/src/WSRequestHandler.h @@ -22,8 +22,7 @@ with this program. If not, see #include #include -#include -#include +#include #include #include @@ -34,15 +33,16 @@ class WSRequestHandler : public QObject { Q_OBJECT public: - explicit WSRequestHandler(QWebSocket* client); + explicit WSRequestHandler(QVariantHash& connProperties); ~WSRequestHandler(); - void processIncomingMessage(QString textMessage); + std::string processIncomingMessage(std::string& textMessage); bool hasField(QString name); private: - QWebSocket* _client; const char* _messageId; const char* _requestType; + std::string _response; + QVariantHash& _connProperties; OBSDataAutoRelease data; void SendOKResponse(obs_data_t* additionalFields = NULL); diff --git a/src/WSRequestHandler_General.cpp b/src/WSRequestHandler_General.cpp index 6e98e519..10dd999b 100644 --- a/src/WSRequestHandler_General.cpp +++ b/src/WSRequestHandler_General.cpp @@ -85,20 +85,24 @@ void WSRequestHandler::HandleAuthenticate(WSRequestHandler* req) { return; } + if (req->_connProperties.value(PROP_AUTHENTICATED).toBool() == true) { + req->SendErrorResponse("already authenticated"); + return; + } + QString auth = obs_data_get_string(req->data, "auth"); if (auth.isEmpty()) { req->SendErrorResponse("auth not specified!"); return; } - if ((req->_client->property(PROP_AUTHENTICATED).toBool() == false) - && Config::Current()->CheckAuth(auth)) - { - req->_client->setProperty(PROP_AUTHENTICATED, true); - req->SendOKResponse(); - } else { + if (Config::Current()->CheckAuth(auth) == false) { req->SendErrorResponse("Authentication Failed."); + return; } + + req->_connProperties.insert(PROP_AUTHENTICATED, true); + req->SendOKResponse(); } /** @@ -117,12 +121,12 @@ void WSRequestHandler::HandleAuthenticate(WSRequestHandler* req) { return; } - WSEvents::Instance->HeartbeatIsActive = - obs_data_get_bool(req->data, "enable"); + auto events = WSEvents::Current(); + + events->HeartbeatIsActive = obs_data_get_bool(req->data, "enable"); OBSDataAutoRelease response = obs_data_create(); - obs_data_set_bool(response, "enable", - WSEvents::Instance->HeartbeatIsActive); + obs_data_set_bool(response, "enable", events->HeartbeatIsActive); req->SendOKResponse(response); } diff --git a/src/WSRequestHandler_Streaming.cpp b/src/WSRequestHandler_Streaming.cpp index cca33cee..fc3d660d 100644 --- a/src/WSRequestHandler_Streaming.cpp +++ b/src/WSRequestHandler_Streaming.cpp @@ -21,6 +21,8 @@ * @since 0.3 */ void WSRequestHandler::HandleGetStreamingStatus(WSRequestHandler* req) { + auto events = WSEvents::Current(); + OBSDataAutoRelease data = obs_data_create(); obs_data_set_bool(data, "streaming", obs_frontend_streaming_active()); obs_data_set_bool(data, "recording", obs_frontend_recording_active()); @@ -28,13 +30,13 @@ const char* tc = nullptr; if (obs_frontend_streaming_active()) { - tc = WSEvents::Instance->GetStreamingTimecode(); + tc = events->GetStreamingTimecode(); obs_data_set_string(data, "stream-timecode", tc); bfree((void*)tc); } if (obs_frontend_recording_active()) { - tc = WSEvents::Instance->GetRecordingTimecode(); + tc = events->GetRecordingTimecode(); obs_data_set_string(data, "rec-timecode", tc); bfree((void*)tc); } diff --git a/src/WSServer.cpp b/src/WSServer.cpp index 4ca45823..b3dc834e 100644 --- a/src/WSServer.cpp +++ b/src/WSServer.cpp @@ -16,11 +16,11 @@ You should have received a copy of the GNU General Public License along with this program. If not, see */ -#include #include #include #include #include +#include #include #include "WSServer.h" @@ -30,139 +30,162 @@ with this program. If not, see QT_USE_NAMESPACE -WSServer* WSServer::Instance = nullptr; +using websocketpp::lib::placeholders::_1; +using websocketpp::lib::placeholders::_2; +using websocketpp::lib::bind; -WSServer::WSServer(QObject* parent) - : QObject(parent), - _wsServer(Q_NULLPTR), - _clients(), +WSServerPtr WSServer::_instance = WSServerPtr(nullptr); + +WSServerPtr WSServer::Current() +{ + if (!_instance) { + ResetCurrent(); + } + return _instance; +} + +void WSServer::ResetCurrent() +{ + _instance = WSServerPtr(new WSServer()); +} + +WSServer::WSServer() + : QObject(nullptr), + _connections(), _clMutex(QMutex::Recursive) { - _wsServer = new QWebSocketServer( - QStringLiteral("obs-websocket"), - QWebSocketServer::NonSecureMode); + _server.init_asio(); + + _server.set_open_handler(bind(&WSServer::onOpen, this, ::_1)); + _server.set_close_handler(bind(&WSServer::onClose, this, ::_1)); + _server.set_message_handler(bind(&WSServer::onMessage, this, ::_1, ::_2)); } -WSServer::~WSServer() { - Stop(); +WSServer::~WSServer() +{ + stop(); } -void WSServer::Start(quint16 port) { - if (port == _wsServer->serverPort()) +void WSServer::start(quint16 port) +{ + if (_server.is_listening() && port == _serverPort) { + blog(LOG_INFO, "WebSocketsServer::start: server already on this port. no restart needed"); return; - - if(_wsServer->isListening()) - Stop(); - - bool serverStarted = _wsServer->listen(QHostAddress::Any, port); - if (serverStarted) { - blog(LOG_INFO, "server started successfully on TCP port %d", port); - - connect(_wsServer, SIGNAL(newConnection()), - this, SLOT(onNewConnection())); } - else { - QString errorString = _wsServer->errorString(); - blog(LOG_ERROR, - "error: failed to start server on TCP port %d: %s", - port, errorString.toUtf8().constData()); - QMainWindow* mainWindow = (QMainWindow*)obs_frontend_get_main_window(); - - obs_frontend_push_ui_translation(obs_module_get_string); - QString title = tr("OBSWebsocket.Server.StartFailed.Title"); - QString msg = tr("OBSWebsocket.Server.StartFailed.Message").arg(port); - obs_frontend_pop_ui_translation(); - - QMessageBox::warning(mainWindow, title, msg); + if (_server.is_listening()) { + stop(); } + + _serverPort = port; + + _server.listen(_serverPort); + _server.start_accept(); + + QtConcurrent::run([=]() { + _server.run(); + }); + + blog(LOG_INFO, "server started successfully on port %d", _serverPort); } -void WSServer::Stop() { - QMutexLocker locker(&_clMutex); - for(QWebSocket* pClient : _clients) { - pClient->close(); +void WSServer::stop() +{ + if (!_server.is_listening()) { + return; } - locker.unlock(); - - _wsServer->close(); + _server.stop_listening(); + _server.stop(); blog(LOG_INFO, "server stopped successfully"); } -void WSServer::broadcast(QString message) { +void WSServer::broadcast(std::string message) +{ QMutexLocker locker(&_clMutex); - for(QWebSocket* pClient : _clients) { - if (Config::Current()->AuthRequired - && (pClient->property(PROP_AUTHENTICATED).toBool() == false)) { - // Skip this client if unauthenticated - continue; + for (connection_hdl hdl : _connections) { + if (Config::Current()->AuthRequired) { + bool authenticated = _connectionProperties[hdl].value(PROP_AUTHENTICATED).toBool(); + if (!authenticated) { + continue; + } } - pClient->sendTextMessage(message); + _server.send(hdl, message, websocketpp::frame::opcode::text); } } -void WSServer::onNewConnection() { - QWebSocket* pSocket = _wsServer->nextPendingConnection(); - if (pSocket) { - connect(pSocket, SIGNAL(textMessageReceived(const QString&)), - this, SLOT(onTextMessageReceived(QString))); - connect(pSocket, SIGNAL(disconnected()), - this, SLOT(onSocketDisconnected())); +void WSServer::onOpen(connection_hdl hdl) +{ + QMutexLocker locker(&_clMutex); + _connections.insert(hdl); + locker.unlock(); - pSocket->setProperty(PROP_AUTHENTICATED, false); - - QMutexLocker locker(&_clMutex); - _clients << pSocket; - locker.unlock(); - - QHostAddress clientAddr = pSocket->peerAddress(); - QString clientIp = Utils::FormatIPAddress(clientAddr); - - blog(LOG_INFO, "new client connection from %s:%d", - clientIp.toUtf8().constData(), pSocket->peerPort()); - - obs_frontend_push_ui_translation(obs_module_get_string); - QString title = tr("OBSWebsocket.NotifyConnect.Title"); - QString msg = tr("OBSWebsocket.NotifyConnect.Message") - .arg(Utils::FormatIPAddress(clientAddr)); - obs_frontend_pop_ui_translation(); - - Utils::SysTrayNotify(msg, QSystemTrayIcon::Information, title); - } + QString clientIp = getRemoteEndpoint(hdl); + notifyConnection(clientIp); + blog(LOG_INFO, "new client connection from %s", clientIp.toUtf8().constData()); } -void WSServer::onTextMessageReceived(QString message) { - QWebSocket* pSocket = qobject_cast(sender()); - if (pSocket) { - WSRequestHandler handler(pSocket); - handler.processIncomingMessage(message); +void WSServer::onMessage(connection_hdl hdl, server::message_ptr message) +{ + auto opcode = message->get_opcode(); + if (opcode != websocketpp::frame::opcode::text) { + return; } + + std::string payload = message->get_payload(); + + QMutexLocker locker(&_clMutex); + QVariantHash connProperties = _connectionProperties[hdl]; + locker.unlock(); + + WSRequestHandler handler(connProperties); + std::string response = handler.processIncomingMessage(payload); + + _server.send(hdl, response, websocketpp::frame::opcode::text); + + locker.relock(); + // In multithreaded processing this would be problematic to put back + // a copy of the connection properties, because there might conflicts + // between several simultaneous handlers. + // In our case, it's fine because all messages are processed in one thread. + _connectionProperties[hdl] = connProperties; + locker.unlock(); } -void WSServer::onSocketDisconnected() { - QWebSocket* pSocket = qobject_cast(sender()); - if (pSocket) { - pSocket->setProperty(PROP_AUTHENTICATED, false); +void WSServer::onClose(connection_hdl hdl) +{ + QMutexLocker locker(&_clMutex); + _connections.erase(hdl); + _connectionProperties.erase(hdl); + locker.unlock(); - QMutexLocker locker(&_clMutex); - _clients.removeAll(pSocket); - locker.unlock(); - - pSocket->deleteLater(); - - QHostAddress clientAddr = pSocket->peerAddress(); - QString clientIp = Utils::FormatIPAddress(clientAddr); - - blog(LOG_INFO, "client %s:%d disconnected", - clientIp.toUtf8().constData(), pSocket->peerPort()); - - obs_frontend_push_ui_translation(obs_module_get_string); - QString title = tr("OBSWebsocket.NotifyDisconnect.Title"); - QString msg = tr("OBSWebsocket.NotifyDisconnect.Message") - .arg(Utils::FormatIPAddress(clientAddr)); - obs_frontend_pop_ui_translation(); - - Utils::SysTrayNotify(msg, QSystemTrayIcon::Information, title); - } + QString clientIp = getRemoteEndpoint(hdl); + notifyDisconnection(clientIp); + blog(LOG_INFO, "client %s disconnected", clientIp.toUtf8().constData()); +} + +QString WSServer::getRemoteEndpoint(connection_hdl hdl) +{ + auto conn = _server.get_con_from_hdl(hdl); + return QString::fromStdString(conn->get_remote_endpoint()); +} + +void WSServer::notifyConnection(QString clientIp) +{ + obs_frontend_push_ui_translation(obs_module_get_string); + QString title = tr("OBSWebsocket.NotifyConnect.Title"); + QString msg = tr("OBSWebsocket.NotifyConnect.Message").arg(clientIp); + obs_frontend_pop_ui_translation(); + + Utils::SysTrayNotify(msg, QSystemTrayIcon::Information, title); +} + +void WSServer::notifyDisconnection(QString clientIp) +{ + obs_frontend_push_ui_translation(obs_module_get_string); + QString title = tr("OBSWebsocket.NotifyDisconnect.Title"); + QString msg = tr("OBSWebsocket.NotifyDisconnect.Message").arg(clientIp); + obs_frontend_pop_ui_translation(); + + Utils::SysTrayNotify(msg, QSystemTrayIcon::Information, title); } diff --git a/src/WSServer.h b/src/WSServer.h index 3d33532a..eee056d8 100644 --- a/src/WSServer.h +++ b/src/WSServer.h @@ -20,33 +20,58 @@ with this program. If not, see #define WSSERVER_H #include -#include #include +#include +#include + +#include +#include + +#include +#include #include "WSRequestHandler.h" QT_FORWARD_DECLARE_CLASS(QWebSocketServer) QT_FORWARD_DECLARE_CLASS(QWebSocket) -class WSServer : public QObject { - Q_OBJECT - public: - explicit WSServer(QObject* parent = Q_NULLPTR); - virtual ~WSServer(); - void Start(quint16 port); - void Stop(); - void broadcast(QString message); - static WSServer* Instance; +using websocketpp::connection_hdl; - private slots: - void onNewConnection(); - void onTextMessageReceived(QString message); - void onSocketDisconnected(); +typedef websocketpp::server server; - private: - QWebSocketServer* _wsServer; - QList _clients; - QMutex _clMutex; +class WSServer; +typedef QSharedPointer WSServerPtr; + +class WSServer : public QObject +{ +Q_OBJECT + +public: + static WSServerPtr Current(); + static void ResetCurrent(); + + explicit WSServer(); + virtual ~WSServer(); + void start(quint16 port); + void stop(); + void broadcast(std::string message); + +private: + static WSServerPtr _instance; + + void onOpen(connection_hdl hdl); + void onMessage(connection_hdl hdl, server::message_ptr message); + void onClose(connection_hdl hdl); + + QString getRemoteEndpoint(connection_hdl hdl); + void notifyConnection(QString clientIp); + void notifyDisconnection(QString clientIp); + + server _server; + quint16 _serverPort; + std::set> _connections; + std::map> _connectionProperties; + QMutex _clMutex; }; #endif // WSSERVER_H diff --git a/src/forms/settings-dialog.cpp b/src/forms/settings-dialog.cpp index 9bdaaa39..5778aa4e 100644 --- a/src/forms/settings-dialog.cpp +++ b/src/forms/settings-dialog.cpp @@ -41,7 +41,7 @@ SettingsDialog::SettingsDialog(QWidget* parent) : } void SettingsDialog::showEvent(QShowEvent* event) { - Config* conf = Config::Current(); + auto conf = Config::Current(); ui->serverEnabled->setChecked(conf->ServerEnabled); ui->serverPort->setValue(conf->ServerPort); @@ -68,7 +68,7 @@ void SettingsDialog::AuthCheckboxChanged() { } void SettingsDialog::FormAccepted() { - Config* conf = Config::Current(); + auto conf = Config::Current(); conf->ServerEnabled = ui->serverEnabled->isChecked(); conf->ServerPort = ui->serverPort->value(); @@ -94,9 +94,9 @@ void SettingsDialog::FormAccepted() { conf->Save(); if (conf->ServerEnabled) - WSServer::Instance->Start(conf->ServerPort); + WSServer::Current()->start(conf->ServerPort); else - WSServer::Instance->Stop(); + WSServer::Current()->stop(); } SettingsDialog::~SettingsDialog() { diff --git a/src/obs-websocket.cpp b/src/obs-websocket.cpp index fd18af5d..9939f8da 100644 --- a/src/obs-websocket.cpp +++ b/src/obs-websocket.cpp @@ -45,18 +45,19 @@ bool obs_module_load(void) { QT_VERSION_STR, qVersion()); // Core setup - Config* config = Config::Current(); + auto config = Config::Current(); config->Load(); - WSServer::Instance = new WSServer(); - WSEvents::Instance = new WSEvents(WSServer::Instance); + WSEvents::ResetCurrent(WSServer::Current()); - if (config->ServerEnabled) - WSServer::Instance->Start(config->ServerPort); + if (config->ServerEnabled) { + WSServer::Current()->start(config->ServerPort); + } // UI setup QAction* menu_action = (QAction*)obs_frontend_add_tools_menu_qaction( - obs_module_text("OBSWebsocket.Menu.SettingsItem")); + obs_module_text("OBSWebsocket.Menu.SettingsItem") + ); obs_frontend_push_ui_translation(obs_module_get_string); QMainWindow* main_window = (QMainWindow*)obs_frontend_get_main_window(); @@ -75,6 +76,7 @@ bool obs_module_load(void) { } void obs_module_unload() { + WSServer::Current()->stop(); blog(LOG_INFO, "goodbye!"); }