#include "EventHandler.h" #include "../plugin-macros.generated.h" std::string GetCalldataString(const calldata_t *data, const char* name) { const char* value = calldata_string(data, name); if (!value) return ""; return value; } EventHandler::EventHandler(WebSocketServerPtr webSocketServer) : _webSocketServer(webSocketServer), _obsLoaded(false) { blog(LOG_INFO, "[EventHandler::EventHandler] Setting up event handlers..."); _cpuUsageInfo = os_cpu_usage_info_start(); obs_frontend_add_event_callback(OnFrontendEvent, this); signal_handler_t* coreSignalHandler = obs_get_signal_handler(); if (coreSignalHandler) { signal_handler_connect(coreSignalHandler, "source_create", SourceCreatedMultiHandler, this); signal_handler_connect(coreSignalHandler, "source_destroy", SourceDestroyedMultiHandler, this); signal_handler_connect(coreSignalHandler, "source_remove", SourceRemovedMultiHandler, this); signal_handler_connect(coreSignalHandler, "source_rename", SourceRenamedMultiHandler, this); } else { blog(LOG_ERROR, "[EventHandler::EventHandler] Unable to get libobs signal handler!"); } blog(LOG_INFO, "[EventHandler::EventHandler] Finished."); } EventHandler::~EventHandler() { blog(LOG_INFO, "[EventHandler::~EventHandler] Removing event handlers..."); os_cpu_usage_info_destroy(_cpuUsageInfo); obs_frontend_remove_event_callback(OnFrontendEvent, this); signal_handler_t* coreSignalHandler = obs_get_signal_handler(); if (coreSignalHandler) { signal_handler_disconnect(coreSignalHandler, "source_create", SourceCreatedMultiHandler, this); signal_handler_disconnect(coreSignalHandler, "source_destroy", SourceDestroyedMultiHandler, this); signal_handler_disconnect(coreSignalHandler, "source_remove", SourceRemovedMultiHandler, this); signal_handler_disconnect(coreSignalHandler, "source_rename", SourceRenamedMultiHandler, this); } else { blog(LOG_ERROR, "[EventHandler::~EventHandler] Unable to get libobs signal handler!"); } blog(LOG_INFO, "[EventHandler::~EventHandler] Finished."); } void EventHandler::ConnectSourceSignals(obs_source_t *source) // These signals are only reliably connected to inputs. { if (!source || obs_source_removed(source)) return; DisconnectSourceSignals(source); signal_handler_t* sh = obs_source_get_signal_handler(source); // Inputs signal_handler_connect(sh, "activate", HandleInputActiveStateChanged, this); signal_handler_connect(sh, "deactivate", HandleInputActiveStateChanged, this); signal_handler_connect(sh, "show", HandleInputShowStateChanged, this); signal_handler_connect(sh, "hide", HandleInputShowStateChanged, this); signal_handler_connect(sh, "mute", HandleInputMuteStateChanged, this); signal_handler_connect(sh, "volume", HandleInputVolumeChanged, this); signal_handler_connect(sh, "audio_sync", HandleInputAudioSyncOffsetChanged, this); signal_handler_connect(sh, "audio_mixers", HandleInputAudioTracksChanged, this); } void EventHandler::DisconnectSourceSignals(obs_source_t *source) { if (!source) return; signal_handler_t* sh = obs_source_get_signal_handler(source); // Inputs signal_handler_disconnect(sh, "activate", HandleInputActiveStateChanged, this); signal_handler_disconnect(sh, "deactivate", HandleInputActiveStateChanged, this); signal_handler_disconnect(sh, "show", HandleInputShowStateChanged, this); signal_handler_disconnect(sh, "hide", HandleInputShowStateChanged, this); signal_handler_disconnect(sh, "mute", HandleInputMuteStateChanged, this); signal_handler_disconnect(sh, "volume", HandleInputVolumeChanged, this); signal_handler_disconnect(sh, "audio_sync", HandleInputAudioSyncOffsetChanged, this); signal_handler_disconnect(sh, "audio_mixers", HandleInputAudioTracksChanged, this); } void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_data) { auto eventHandler = reinterpret_cast(private_data); if (!eventHandler->_obsLoaded.load()) { if (event == OBS_FRONTEND_EVENT_FINISHED_LOADING) { blog(LOG_INFO, "[EventHandler::OnFrontendEvent] OBS has finished loading. Connecting final handlers and enabling events..."); // Connect source signals and enable events only after OBS has fully loaded (to reduce extra logging). eventHandler->_obsLoaded.store(true); // In the case that plugins become hotloadable, this will have to go back into `EventHandler::EventHandler()` obs_enum_sources([](void* param, obs_source_t* source) { auto eventHandler = reinterpret_cast(param); eventHandler->ConnectSourceSignals(source); return true; }, private_data); blog(LOG_INFO, "[EventHandler::OnFrontendEvent] Finished."); } else { return; } } switch (event) { // General case OBS_FRONTEND_EVENT_EXIT: eventHandler->HandleExitStarted(); blog(LOG_INFO, "[EventHandler::OnFrontendEvent] OBS is unloading. Disabling events..."); // Disconnect source signals and disable events when OBS starts unloading (to reduce extra logging). eventHandler->_obsLoaded.store(false); // In the case that plugins become hotloadable, this will have to go back into `EventHandler::~EventHandler()` obs_enum_sources([](void* param, obs_source_t* source) { auto eventHandler = reinterpret_cast(param); eventHandler->DisconnectSourceSignals(source); return true; }, private_data); blog(LOG_INFO, "[EventHandler::OnFrontendEvent] Finished."); break; case OBS_FRONTEND_EVENT_STUDIO_MODE_ENABLED: eventHandler->HandleStudioModeStateChanged(true); break; case OBS_FRONTEND_EVENT_STUDIO_MODE_DISABLED: eventHandler->HandleStudioModeStateChanged(false); break; // Config case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED: eventHandler->HandleCurrentSceneCollectionChanged(); break; case OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED: eventHandler->HandleSceneCollectionListChanged(); break; case OBS_FRONTEND_EVENT_PROFILE_CHANGED: eventHandler->HandleCurrentProfileChanged(); break; case OBS_FRONTEND_EVENT_PROFILE_LIST_CHANGED: eventHandler->HandleProfileListChanged(); break; // Scenes case OBS_FRONTEND_EVENT_SCENE_CHANGED: eventHandler->HandleCurrentSceneChanged(); break; case OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED: eventHandler->HandleCurrentPreviewSceneChanged(); break; case OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED: eventHandler->HandleSceneListReindexed(); break; // Transitions case OBS_FRONTEND_EVENT_TRANSITION_CHANGED: break; case OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED: break; case OBS_FRONTEND_EVENT_TRANSITION_DURATION_CHANGED: break; // Outputs case OBS_FRONTEND_EVENT_STREAMING_STARTING: break; case OBS_FRONTEND_EVENT_STREAMING_STARTED: break; case OBS_FRONTEND_EVENT_STREAMING_STOPPING: break; case OBS_FRONTEND_EVENT_STREAMING_STOPPED: break; case OBS_FRONTEND_EVENT_RECORDING_STARTING: break; case OBS_FRONTEND_EVENT_RECORDING_STARTED: break; case OBS_FRONTEND_EVENT_RECORDING_STOPPING: break; case OBS_FRONTEND_EVENT_RECORDING_STOPPED: break; case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING: break; case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED: break; case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING: break; case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED: break; case OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED: break; case OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED: break; default: break; } } void EventHandler::SourceCreatedMultiHandler(void *param, calldata_t *data) { auto eventHandler = reinterpret_cast(param); if (!eventHandler->_obsLoaded.load()) return; OBSSource source = GetCalldataPointer(data, "source"); if (!source) return; // Connect all signals from the source eventHandler->ConnectSourceSignals(source); switch (obs_source_get_type(source)) { case OBS_SOURCE_TYPE_INPUT: eventHandler->HandleInputCreated(source); break; case OBS_SOURCE_TYPE_FILTER: break; case OBS_SOURCE_TYPE_TRANSITION: break; case OBS_SOURCE_TYPE_SCENE: eventHandler->HandleSceneCreated(source); break; default: break; } } void EventHandler::SourceDestroyedMultiHandler(void *param, calldata_t *data) { auto eventHandler = reinterpret_cast(param); if (!eventHandler->_obsLoaded.load()) return; // We can't use any smart types because releasing the source will cause infinite recursion obs_source_t *source = GetCalldataPointer(data, "source"); if (!source) return; // Disconnect all signals from the source eventHandler->DisconnectSourceSignals(source); switch (obs_source_get_type(source)) { case OBS_SOURCE_TYPE_INPUT: eventHandler->HandleInputRemoved(source); // We have to call `InputRemoved` with source_destroy because source_removed is not called when a source is *only* destroyed break; case OBS_SOURCE_TYPE_FILTER: break; case OBS_SOURCE_TYPE_TRANSITION: break; case OBS_SOURCE_TYPE_SCENE: break; default: break; } } void EventHandler::SourceRemovedMultiHandler(void *param, calldata_t *data) { auto eventHandler = reinterpret_cast(param); if (!eventHandler->_obsLoaded.load()) return; OBSSource source = GetCalldataPointer(data, "source"); if (!source) return; switch (obs_source_get_type(source)) { case OBS_SOURCE_TYPE_INPUT: break; case OBS_SOURCE_TYPE_FILTER: break; case OBS_SOURCE_TYPE_TRANSITION: break; case OBS_SOURCE_TYPE_SCENE: eventHandler->HandleSceneRemoved(source); break; default: break; } } void EventHandler::SourceRenamedMultiHandler(void *param, calldata_t *data) { auto eventHandler = reinterpret_cast(param); if (!eventHandler->_obsLoaded.load()) return; OBSSource source = GetCalldataPointer(data, "source"); if (!source) return; std::string oldSourceName = GetCalldataString(data, "prev_name"); std::string sourceName = GetCalldataString(data, "new_name"); if (oldSourceName.empty() || sourceName.empty()) return; switch (obs_source_get_type(source)) { case OBS_SOURCE_TYPE_INPUT: eventHandler->HandleInputNameChanged(source, oldSourceName, sourceName); break; case OBS_SOURCE_TYPE_FILTER: break; case OBS_SOURCE_TYPE_TRANSITION: break; case OBS_SOURCE_TYPE_SCENE: eventHandler->HandleSceneNameChanged(source, oldSourceName, sourceName); break; default: break; } }