diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 0d9ece06..0b69c742 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -12,3 +12,6 @@ OBSWebsocket.NotifyDisconnect.Title="WebSocket client disconnected" OBSWebsocket.NotifyDisconnect.Message="Client %1 disconnected" OBSWebsocket.Server.StartFailed.Title="WebSocket Server failure" OBSWebsocket.Server.StartFailed.Message="The obs-websocket server failed to start, maybe because:\n - TCP port %1 may currently be in use elsewhere on this system, possibly by another application. Try setting a different TCP port in the WebSocket server settings, or stop any application that could be using this port.\n - An unknown network error happened on your system. Try again by changing settings, restarting OBS or restarting your system." +OBSWebsocket.ProfileChanged.Started="WebSockets server enabled in this profile. Server started." +OBSWebsocket.ProfileChanged.Stopped="WebSockets server disabled in this profile. Server stopped." +OBSWebsocket.ProfileChanged.Restarted="WebSockets server port changed in this profile. Server restarted." \ No newline at end of file diff --git a/src/Config.cpp b/src/Config.cpp index a3746838..9ff7a653 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -17,10 +17,10 @@ with this program. If not, see */ #include -#include #include #include +#include #define SECTION_NAME "WebsocketAPI" #define PARAM_ENABLE "ServerEnabled" @@ -31,8 +31,10 @@ with this program. If not, see #define PARAM_SECRET "AuthSecret" #define PARAM_SALT "AuthSalt" -#include "Config.h" #include "Utils.h" +#include "WSServer.h" + +#include "Config.h" #define QT_TO_UTF8(str) str.toUtf8().constData() @@ -55,8 +57,55 @@ Config::Config() : { qsrand(QTime::currentTime().msec()); + SetDefaults(); + SessionChallenge = GenerateSalt(); + + obs_frontend_add_event_callback(OnFrontendEvent, this); +} + +Config::~Config() +{ + obs_frontend_remove_event_callback(OnFrontendEvent, this); +} + +void Config::Load() +{ + config_t* obsConfig = GetConfigStore(); + + ServerEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_ENABLE); + ServerPort = config_get_uint(obsConfig, SECTION_NAME, PARAM_PORT); + + DebugEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_DEBUG); + AlertsEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_ALERT); + + AuthRequired = config_get_bool(obsConfig, SECTION_NAME, PARAM_AUTHREQUIRED); + Secret = config_get_string(obsConfig, SECTION_NAME, PARAM_SECRET); + Salt = config_get_string(obsConfig, SECTION_NAME, PARAM_SALT); +} + +void Config::Save() +{ + config_t* obsConfig = GetConfigStore(); + + config_set_bool(obsConfig, SECTION_NAME, PARAM_ENABLE, ServerEnabled); + config_set_uint(obsConfig, SECTION_NAME, PARAM_PORT, ServerPort); + + config_set_bool(obsConfig, SECTION_NAME, PARAM_DEBUG, DebugEnabled); + config_set_bool(obsConfig, SECTION_NAME, PARAM_ALERT, AlertsEnabled); + + config_set_bool(obsConfig, SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired); + config_set_string(obsConfig, SECTION_NAME, PARAM_SECRET, + QT_TO_UTF8(Secret)); + config_set_string(obsConfig, SECTION_NAME, PARAM_SALT, + QT_TO_UTF8(Salt)); + + config_save(obsConfig); +} + +void Config::SetDefaults() +{ // OBS Config defaults - config_t* obsConfig = obs_frontend_get_global_config(); + config_t* obsConfig = GetConfigStore(); if (obsConfig) { config_set_default_bool(obsConfig, SECTION_NAME, PARAM_ENABLE, ServerEnabled); @@ -75,46 +124,11 @@ Config::Config() : config_set_default_string(obsConfig, SECTION_NAME, PARAM_SALT, QT_TO_UTF8(Salt)); } - - SessionChallenge = GenerateSalt(); } -Config::~Config() +config_t* Config::GetConfigStore() { -} - -void Config::Load() -{ - config_t* obsConfig = obs_frontend_get_global_config(); - - ServerEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_ENABLE); - ServerPort = config_get_uint(obsConfig, SECTION_NAME, PARAM_PORT); - - DebugEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_DEBUG); - AlertsEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_ALERT); - - AuthRequired = config_get_bool(obsConfig, SECTION_NAME, PARAM_AUTHREQUIRED); - Secret = config_get_string(obsConfig, SECTION_NAME, PARAM_SECRET); - Salt = config_get_string(obsConfig, SECTION_NAME, PARAM_SALT); -} - -void Config::Save() -{ - config_t* obsConfig = obs_frontend_get_global_config(); - - config_set_bool(obsConfig, SECTION_NAME, PARAM_ENABLE, ServerEnabled); - config_set_uint(obsConfig, SECTION_NAME, PARAM_PORT, ServerPort); - - config_set_bool(obsConfig, SECTION_NAME, PARAM_DEBUG, DebugEnabled); - config_set_bool(obsConfig, SECTION_NAME, PARAM_ALERT, AlertsEnabled); - - config_set_bool(obsConfig, SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired); - config_set_string(obsConfig, SECTION_NAME, PARAM_SECRET, - QT_TO_UTF8(Secret)); - config_set_string(obsConfig, SECTION_NAME, PARAM_SALT, - QT_TO_UTF8(Salt)); - - config_save(obsConfig); + return obs_frontend_get_profile_config(); } QString Config::GenerateSalt() @@ -184,3 +198,96 @@ bool Config::CheckAuth(QString response) return authSuccess; } + +void Config::OnFrontendEvent(enum obs_frontend_event event, void* param) +{ + auto config = reinterpret_cast(param); + + if (event == OBS_FRONTEND_EVENT_PROFILE_CHANGED) { + obs_frontend_push_ui_translation(obs_module_get_string); + QString startMessage = QObject::tr("OBSWebsocket.ProfileChanged.Started"); + QString stopMessage = QObject::tr("OBSWebsocket.ProfileChanged.Stopped"); + QString restartMessage = QObject::tr("OBSWebsocket.ProfileChanged.Restarted"); + obs_frontend_pop_ui_translation(); + + bool previousEnabled = config->ServerEnabled; + uint64_t previousPort = config->ServerPort; + + config->SetDefaults(); + config->Load(); + + if (config->ServerEnabled != previousEnabled || config->ServerPort != previousPort) { + auto server = WSServer::Current(); + server->stop(); + + if (config->ServerEnabled) { + server->start(config->ServerPort); + + if (previousEnabled != config->ServerEnabled) { + Utils::SysTrayNotify(startMessage, QSystemTrayIcon::MessageIcon::Information); + } else { + Utils::SysTrayNotify(restartMessage, QSystemTrayIcon::MessageIcon::Information); + } + } else { + Utils::SysTrayNotify(stopMessage, QSystemTrayIcon::MessageIcon::Information); + } + } + } +} + +void Config::MigrateFromGlobalSettings() +{ + config_t* source = obs_frontend_get_global_config(); + config_t* destination = obs_frontend_get_profile_config(); + + if(config_has_user_value(source, SECTION_NAME, PARAM_ENABLE)) { + bool value = config_get_bool(source, SECTION_NAME, PARAM_ENABLE); + config_set_bool(destination, SECTION_NAME, PARAM_ENABLE, value); + + config_remove_value(source, SECTION_NAME, PARAM_ENABLE); + } + + if(config_has_user_value(source, SECTION_NAME, PARAM_PORT)) { + uint64_t value = config_get_uint(source, SECTION_NAME, PARAM_PORT); + config_set_uint(destination, SECTION_NAME, PARAM_PORT, value); + + config_remove_value(source, SECTION_NAME, PARAM_PORT); + } + + if(config_has_user_value(source, SECTION_NAME, PARAM_DEBUG)) { + bool value = config_get_bool(source, SECTION_NAME, PARAM_DEBUG); + config_set_bool(destination, SECTION_NAME, PARAM_DEBUG, value); + + config_remove_value(source, SECTION_NAME, PARAM_DEBUG); + } + + if(config_has_user_value(source, SECTION_NAME, PARAM_ALERT)) { + bool value = config_get_bool(source, SECTION_NAME, PARAM_ALERT); + config_set_bool(destination, SECTION_NAME, PARAM_ALERT, value); + + config_remove_value(source, SECTION_NAME, PARAM_ALERT); + } + + if(config_has_user_value(source, SECTION_NAME, PARAM_AUTHREQUIRED)) { + bool value = config_get_bool(source, SECTION_NAME, PARAM_AUTHREQUIRED); + config_set_bool(destination, SECTION_NAME, PARAM_AUTHREQUIRED, value); + + config_remove_value(source, SECTION_NAME, PARAM_AUTHREQUIRED); + } + + if(config_has_user_value(source, SECTION_NAME, PARAM_SECRET)) { + const char* value = config_get_string(source, SECTION_NAME, PARAM_SECRET); + config_set_string(destination, SECTION_NAME, PARAM_SECRET, value); + + config_remove_value(source, SECTION_NAME, PARAM_SECRET); + } + + if(config_has_user_value(source, SECTION_NAME, PARAM_SALT)) { + const char* value = config_get_string(source, SECTION_NAME, PARAM_SALT); + config_set_string(destination, SECTION_NAME, PARAM_SALT, value); + + config_remove_value(source, SECTION_NAME, PARAM_SALT); + } + + config_save(destination); +} diff --git a/src/Config.h b/src/Config.h index 4ff8d393..73f04521 100644 --- a/src/Config.h +++ b/src/Config.h @@ -18,6 +18,8 @@ with this program. If not, see #pragma once +#include +#include #include #include @@ -32,6 +34,10 @@ class Config { ~Config(); void Load(); void Save(); + void SetDefaults(); + config_t* GetConfigStore(); + + void MigrateFromGlobalSettings(); void SetPassword(QString password); bool CheckAuth(QString userChallenge); @@ -52,5 +58,6 @@ class Config { bool SettingsLoaded; private: + static void OnFrontendEvent(enum obs_frontend_event event, void* param); static ConfigPtr _instance; }; diff --git a/src/Utils.cpp b/src/Utils.cpp index 19b0a75b..7cf67723 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -390,7 +390,7 @@ QSystemTrayIcon* Utils::GetTrayIcon() { return main->findChildren().first(); } -void Utils::SysTrayNotify(QString &text, +void Utils::SysTrayNotify(QString text, QSystemTrayIcon::MessageIcon icon, QString title) { if (!Config::Current()->AlertsEnabled || !QSystemTrayIcon::isSystemTrayAvailable() || diff --git a/src/Utils.h b/src/Utils.h index 42a90f73..39e428dd 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -71,7 +71,7 @@ class Utils { static QSystemTrayIcon* GetTrayIcon(); static void SysTrayNotify( - QString &text, + QString text, QSystemTrayIcon::MessageIcon n, QString title = QString("obs-websocket")); diff --git a/src/WSServer.cpp b/src/WSServer.cpp index 2a74ee01..bec085b0 100644 --- a/src/WSServer.cpp +++ b/src/WSServer.cpp @@ -16,6 +16,9 @@ You should have received a copy of the GNU General Public License along with this program. If not, see */ +#include +#include + #include #include #include @@ -55,6 +58,7 @@ WSServer::WSServer() _clMutex(QMutex::Recursive) { _server.init_asio(); + _server.set_reuse_addr(true); _server.set_open_handler(bind(&WSServer::onOpen, this, ::_1)); _server.set_close_handler(bind(&WSServer::onClose, this, ::_1)); @@ -77,12 +81,17 @@ void WSServer::start(quint16 port) stop(); } + _server.reset(); + _serverPort = port; websocketpp::lib::error_code errorCode; _server.listen(_serverPort, errorCode); if (errorCode) { + std::string errorCodeMessage = errorCode.message(); + blog(LOG_INFO, "server: listen failed: %s", errorCodeMessage.c_str()); + obs_frontend_push_ui_translation(obs_module_get_string); QString errorTitle = tr("OBSWebsocket.Server.StartFailed.Title"); QString errorMessage = tr("OBSWebsocket.Server.StartFailed.Message").arg(_serverPort); @@ -97,7 +106,9 @@ void WSServer::start(quint16 port) _server.start_accept(); QtConcurrent::run([=]() { + blog(LOG_INFO, "io thread started"); _server.run(); + blog(LOG_INFO, "io thread exited"); }); blog(LOG_INFO, "server started successfully on port %d", _serverPort); @@ -110,7 +121,16 @@ void WSServer::stop() } _server.stop_listening(); - _server.stop(); + for (connection_hdl hdl : _connections) { + _server.close(hdl, websocketpp::close::status::going_away, "Server stopping"); + } + _connections.clear(); + _connectionProperties.clear(); + + while (!_server.stopped()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + blog(LOG_INFO, "server stopped successfully"); } @@ -173,9 +193,14 @@ void WSServer::onClose(connection_hdl hdl) _connectionProperties.erase(hdl); locker.unlock(); - QString clientIp = getRemoteEndpoint(hdl); - notifyDisconnection(clientIp); - blog(LOG_INFO, "client %s disconnected", clientIp.toUtf8().constData()); + auto conn = _server.get_con_from_hdl(hdl); + auto localCloseCode = conn->get_local_close_code(); + + if (localCloseCode != websocketpp::close::status::going_away) { + QString clientIp = getRemoteEndpoint(hdl); + notifyDisconnection(clientIp); + blog(LOG_INFO, "client %s disconnected", clientIp.toUtf8().constData()); + } } QString WSServer::getRemoteEndpoint(connection_hdl hdl) diff --git a/src/obs-websocket.cpp b/src/obs-websocket.cpp index f3a52e69..615d0782 100644 --- a/src/obs-websocket.cpp +++ b/src/obs-websocket.cpp @@ -47,6 +47,7 @@ bool obs_module_load(void) { // Core setup auto config = Config::Current(); + config->MigrateFromGlobalSettings(); // TODO remove this on the next minor jump config->Load(); WSEvents::ResetCurrent(WSServer::Current());