Add API to register new macro condition and action types

This commit is contained in:
WarmUpTill 2024-07-13 20:38:41 +02:00 committed by WarmUpTill
parent 685e28d161
commit 36201cbfb4
20 changed files with 4670 additions and 40 deletions

1
.gitignore vendored
View File

@ -7,6 +7,7 @@
!/cmake
!/data
!/deps
!/scripting
!/forms
!/lib
!/module

View File

@ -96,6 +96,8 @@ target_sources(
lib/macro/macro-action-macro.hpp
lib/macro/macro-action-queue.cpp
lib/macro/macro-action-queue.hpp
lib/macro/macro-action-script.cpp
lib/macro/macro-action-script.hpp
lib/macro/macro-action-variable.cpp
lib/macro/macro-action-variable.hpp
lib/macro/macro-action.cpp
@ -108,6 +110,8 @@ target_sources(
lib/macro/macro-condition-macro.hpp
lib/macro/macro-condition-queue.cpp
lib/macro/macro-condition-queue.hpp
lib/macro/macro-condition-script.cpp
lib/macro/macro-condition-script.hpp
lib/macro/macro-condition-tempvar.cpp
lib/macro/macro-condition-tempvar.hpp
lib/macro/macro-condition-variable.cpp
@ -128,10 +132,14 @@ target_sources(
lib/macro/macro-ref.hpp
lib/macro/macro-run-button.cpp
lib/macro/macro-run-button.hpp
lib/macro/macro-script-handler.cpp
lib/macro/macro-script-handler.hpp
lib/macro/macro-segment-copy-paste.cpp
lib/macro/macro-segment-copy-paste.hpp
lib/macro/macro-segment-list.cpp
lib/macro/macro-segment-list.hpp
lib/macro/macro-segment-script.cpp
lib/macro/macro-segment-script.hpp
lib/macro/macro-segment-selection.cpp
lib/macro/macro-segment-selection.hpp
lib/macro/macro-segment.cpp
@ -205,6 +213,9 @@ target_sources(
lib/utils/plugin-state-helpers.hpp
lib/utils/priority-helper.cpp
lib/utils/priority-helper.hpp
lib/utils/properties-view.cpp
lib/utils/properties-view.hpp
lib/utils/properties-view.moc.hpp
lib/utils/regex-config.cpp
lib/utils/regex-config.hpp
lib/utils/resizing-text-edit.cpp

View File

@ -1,7 +1,7 @@
AdvSceneSwitcher.pluginName="Advanced Scene Switcher"
AdvSceneSwitcher.windowTitle="Advanced Scene Switcher"
; General Tab
# General Tab
AdvSceneSwitcher.generalTab.title="General"
AdvSceneSwitcher.generalTab.status="Status"
AdvSceneSwitcher.generalTab.status.hotkeytips="Hotkeys can be defined in the OBS settings"
@ -70,7 +70,7 @@ AdvSceneSwitcher.generalTab.transitionOverride="Set transition overrides"
AdvSceneSwitcher.generalTab.adjustActiveTransitionType="Change active transition type"
AdvSceneSwitcher.generalTab.transitionBehaviorSelectionError="At least one option must be enabled:\n\n - Use transition overrides\n\n - Change active transition type"
; Variables Tab
# Variables Tab
AdvSceneSwitcher.variableTab.title="Variables"
AdvSceneSwitcher.variableTab.help="Variables can be used in many places throughout the plugin.\n\nClick on the highlighted plus symbol to add a new variable."
AdvSceneSwitcher.variableTab.variableAddButton.tooltip="Add new variable"
@ -89,7 +89,7 @@ AdvSceneSwitcher.variableTab.lastChanged.text="%1 seconds ago"
AdvSceneSwitcher.variableTab.lastChanged.text.none="No change since launch"
AdvSceneSwitcher.variableTab.lastChanged.tooltip="Times changed: %1\n\nPrevious value: %2"
; Action Queue Tab
# Action Queue Tab
AdvSceneSwitcher.actionQueueTab.title="Action Queues"
AdvSceneSwitcher.actionQueueTab.help="Action queues are executed sequentially but in parallel to the reset of the macro system.\nThe first action added to the queue will be the first one to be processed.\n\nClick on the highlighted plus symbol to add a new queue."
AdvSceneSwitcher.actionQueueTab.queueAddButton.tooltip="Add new action queue"
@ -103,7 +103,7 @@ AdvSceneSwitcher.actionQueueTab.no="No"
AdvSceneSwitcher.actionQueueTab.removeSingleQueuePopup.text="Are you sure you want to remove \"%1\"?"
AdvSceneSwitcher.actionQueueTab.removeMultipleQueuesPopup.text="Are you sure you want to remove %1 action queues?"
; Websocket Connections Tab
# Websocket Connections Tab
AdvSceneSwitcher.websocketConnectionTab.title="Websocket Connections"
AdvSceneSwitcher.websocketConnectionTab.help="Websocket connections can be used to communicate with other OBS instances or programs.\n\nClick on the highlighted plus symbol to add a new connection."
AdvSceneSwitcher.websocketConnectionTab.websocketConnectionAddButton.tooltip="Add new webscoket connection"
@ -117,7 +117,7 @@ AdvSceneSwitcher.websocketConnectionTab.protocol.no="No"
AdvSceneSwitcher.websocketConnectionTab.removeSingleConnectionPopup.text="Are you sure you want to remove \"%1\"?"
AdvSceneSwitcher.websocketConnectionTab.removeMultipleConnectionsPopup.text="Are you sure you want to remove %1 connections?"
; Twitch Connections Tab
# Twitch Connections Tab
AdvSceneSwitcher.twitchConnectionTab.title="Twitch Connections"
AdvSceneSwitcher.twitchConnectionTab.help="Twitch connections can be used to use Twitch events as triggers to execute actions or perform actions on the linked Twitch account.\n\nClick on the highlighted plus symbol to add a new connection."
AdvSceneSwitcher.twitchConnectionTab.twitchConnectionAddButton.tooltip.tooltip="Add new Twitch connection"
@ -130,7 +130,7 @@ AdvSceneSwitcher.twitchConnectionTab.no="No"
AdvSceneSwitcher.twitchConnectionTab.removeSingleConnectionPopup.text="Are you sure you want to remove \"%1\"?"
AdvSceneSwitcher.twitchConnectionTab.removeMultipleConnectionsPopup.text="Are you sure you want to remove %1 Twitch connections?"
; Macro Tab
# Macro Tab
AdvSceneSwitcher.macroTab.title="Macro"
AdvSceneSwitcher.macroTab.macros="Macros"
AdvSceneSwitcher.macroTab.priorityWarning="Note: It is recommended to configure macros to be the highest priority functionality.\nThis setting can be changed on the General tab."
@ -239,11 +239,11 @@ AdvSceneSwitcher.macroDock.run="Run"
AdvSceneSwitcher.macroDock.statusLabel.true="Conditions are true."
AdvSceneSwitcher.macroDock.statusLabel.false="Conditions are false."
; Macro List
# Macro List
AdvSceneSwitcher.macroList.deleted="deleted"
AdvSceneSwitcher.macroList.duplicate="\"%1\" is alreay selected!"
; Macro Logic
# Macro Logic
AdvSceneSwitcher.logic.none="Ignore entry"
AdvSceneSwitcher.logic.and="And"
AdvSceneSwitcher.logic.or="Or"
@ -252,7 +252,8 @@ AdvSceneSwitcher.logic.orNot="Or not"
AdvSceneSwitcher.logic.rootNone="If"
AdvSceneSwitcher.logic.not="If not"
; Macro Conditions
# Macro Conditions
AdvSceneSwitcher.condition.unknown="Unknown condition"
AdvSceneSwitcher.condition.audio="Audio"
AdvSceneSwitcher.condition.audio.state.below="below"
AdvSceneSwitcher.condition.audio.state.exact="exactly"
@ -724,7 +725,8 @@ AdvSceneSwitcher.condition.usb.productName="Product Name:"
AdvSceneSwitcher.condition.usb.serialNumber="Serial Number:"
AdvSceneSwitcher.condition.noDevicesFoundWarning="No USB devices detected!\nThe plugin might not have the required permissions to check for USB devices."
; Macro Actions
# Macro Actions
AdvSceneSwitcher.action.unknown="Unknown action"
AdvSceneSwitcher.action.scene="Switch scene"
AdvSceneSwitcher.action.scene.type.program="Program"
AdvSceneSwitcher.action.scene.type.preview="Preview"
@ -1114,7 +1116,7 @@ AdvSceneSwitcher.action.log="Log"
AdvSceneSwitcher.action.log.placeholder="My log message!"
AdvSceneSwitcher.action.log.entry="Write to OBS log:{{logMessage}}"
; Hotkey
# Hotkey
AdvSceneSwitcher.hotkey.startSwitcherHotkey="Start the Advanced Scene Switcher"
AdvSceneSwitcher.hotkey.stopSwitcherHotkey="Stop the Advanced Scene Switcher"
AdvSceneSwitcher.hotkey.startStopToggleSwitcherHotkey="Toggle Start/Stop for the Advanced Scene Switcher"
@ -1918,8 +1920,44 @@ AdvSceneSwitcher.noSettingsButtons="No buttons found!"
AdvSceneSwitcher.clearBufferOnMatch="Clear message buffer when matching message was found"
; Legacy tabs below - please don't waste your time adding translations for these :)
; Transition Tab
AdvSceneSwitcher.script.settings="Settings"
AdvSceneSwitcher.script.timeout="Script timeout:{{timeout}}"
# This secion is copied from the OBS locale files
# commonly shared locale
Browse="Browse"
Show="Show"
Hide="Hide"
# properties window
Basic.PropertiesWindow="Properties for '%1'"
Basic.PropertiesWindow.AutoSelectFormat="%1 (autoselect: %2)"
Basic.PropertiesWindow.SelectColor="Select color"
Basic.PropertiesWindow.SelectFont="Select font"
Basic.PropertiesWindow.SelectFont.WindowTitle="Pick a Font"
Basic.PropertiesWindow.ConfirmTitle="Settings Changed"
Basic.PropertiesWindow.Confirm="There are unsaved changes. Do you want to keep them?"
Basic.PropertiesWindow.NoProperties="No properties available"
Basic.PropertiesWindow.AddFiles="Add Files"
Basic.PropertiesWindow.AddDir="Add Directory"
Basic.PropertiesWindow.AddURL="Add Path/URL"
Basic.PropertiesWindow.AddEditableListDir="Add directory to '%1'"
Basic.PropertiesWindow.AddEditableListFiles="Add files to '%1'"
Basic.PropertiesWindow.AddEditableListEntry="Add entry to '%1'"
Basic.PropertiesWindow.EditEditableListEntry="Edit entry from '%1'"
# properties view
Basic.PropertiesView.FPS.Simple="Simple FPS Values"
Basic.PropertiesView.FPS.Rational="Rational FPS Values"
Basic.PropertiesView.FPS.ValidFPSRanges="Valid FPS Ranges:"
Basic.PropertiesView.UrlButton.Text="Open this link in your default web browser?"
Basic.PropertiesView.UrlButton.Text.Url="URL: %1"
Basic.PropertiesView.UrlButton.OpenUrl="Open URL"
# Legacy tabs below - please don't waste your time adding translations for these :)
# Transition Tab
AdvSceneSwitcher.transitionTab.title="Transition"
AdvSceneSwitcher.transitionTab.transitionForAToB="Use transition for automated scene switch from scene A to scene B"
AdvSceneSwitcher.transitionTab.transitionsHelp="<html><head/><body><p>These settings <span style=\"font-style:italic;\">only</span> affect transitions caused by the scene switcher - Check out <a href=\"https://obsproject.com/forum/resources/transition-table.1174/\"><span style=\" text-decoration: underline; color:#268bd2;\">Transition Table</span></a> if you want to configure this for manual scene changes.<br/>Settings defined here take priority over transition settings configured elsewhere in the scene switcher.<br/><br/>Click the plus symbol below to add a new entry.</p></body></html>"
@ -1930,7 +1968,7 @@ AdvSceneSwitcher.transitionTab.defaultTransitionsHelp="Click on the plus symbol
AdvSceneSwitcher.transitionTab.defaultTransition.delay="Switch transition{{defTransitionDelay}}after scene change."
AdvSceneSwitcher.transitionTab.defaultTransition.delay.help="The delay is used to avoid cancelled scene switches, which can happen if the transition type is changed while a transition is still ongoing."
; Pause Scenes Tab
# Pause Scenes Tab
AdvSceneSwitcher.pauseTab.title="Pause"
AdvSceneSwitcher.pauseTab.pauseOnScene="Pause the Scene Switcher on scene"
AdvSceneSwitcher.pauseTab.pauseInFocus1="Pause the Scene Switcher when "
@ -1941,7 +1979,7 @@ AdvSceneSwitcher.pauseTab.pauseTargetAll="all"
AdvSceneSwitcher.pauseTab.pauseEntry="Pause{{pauseTargets}}checks when{{pauseTypes}}{{scenes}}{{windows}}"
AdvSceneSwitcher.pauseTab.help="On this tab you can configure to pause individual switching methods if a scene is active or window is in focus.\n\nClick on the highlighted plus symbol to continue."
; Window Title Tab
# Window Title Tab
AdvSceneSwitcher.windowTitleTab.title="Title"
AdvSceneSwitcher.windowTitleTab.regexrDescription="<html><head/><body><p>Enter either direct window titles or valid regex. You can check syntax and matches for regular expressions using <a href=\"https://regexr.com\"><span style=\" text-decoration: underline; color:#268bd2;\">RegExr</span></a></p></body></html>"
AdvSceneSwitcher.windowTitleTab.stayInFocus1="Ignore this window name"
@ -1953,14 +1991,14 @@ AdvSceneSwitcher.windowTitleTab.entry="{{windows}}{{scenes}}{{transitions}}{{ful
AdvSceneSwitcher.windowTitleTab.windowsHelp="Switch scenes based on the window title of running applications.\nThe following additional conditions can be selected:\nThe window is Fullscreen\nThe window is maximized\nThe window is focused\n\nClick on the highlighted plus symbol to continue."
AdvSceneSwitcher.windowTitleTab.ignoreWindowsHelp="If a window title is ignored the scene switcher will act as if the previously selected window is still in focus.\nThis will allow you to avoid scene switches, if you frequently switch to a different window, which shall not trigger a scene change.\n\nChoose a window or enter a window title above and click on the plus symbol below to add it to the list."
; Executable Tab
# Executable Tab
AdvSceneSwitcher.executableTab.title="Executable"
AdvSceneSwitcher.executableTab.implemented="Implemented by dasOven"
AdvSceneSwitcher.executableTab.requiresFocus="only if focused"
AdvSceneSwitcher.executableTab.entry="When{{processes}}is running switch to{{scenes}}using{{transitions}}{{requiresFocus}}"
AdvSceneSwitcher.executableTab.help="This tab will allow you to automatically switch scenes if a process is running.\nThis can be useful in situations where the window name could change or is not known.\n\nClick on the highlighted plus symbol to continue."
; Screen Region Tab
# Screen Region Tab
AdvSceneSwitcher.screenRegionTab.title="Region"
AdvSceneSwitcher.screenRegionTab.currentPosition="Cursor is currently at:"
AdvSceneSwitcher.screenRegionTab.showGuideFrames="Show guide frames"
@ -1969,7 +2007,7 @@ AdvSceneSwitcher.screenRegionTab.excludeScenes.None="No selection"
AdvSceneSwitcher.screenRegionTab.entry="If cursor is in{{minX}}{{minY}}x{{maxX}}{{maxY}}switch to{{scenes}}using{{transitions}}unless in{{excludeScenes}}"
AdvSceneSwitcher.screenRegionTab.help="This tab will allow you to automatically switch scenes based on the current position of your mouse cursor.\n\nClick on the highlighted plus symbol to continue."
; Media Tab
# Media Tab
AdvSceneSwitcher.mediaTab.title="Media"
AdvSceneSwitcher.mediaTab.implemented="Implemented by Exeldro"
AdvSceneSwitcher.mediaTab.states.none="None"
@ -1990,7 +2028,7 @@ AdvSceneSwitcher.mediaTab.timeRestriction.remainLonger="Time remaining longer"
AdvSceneSwitcher.mediaTab.entry="When{{mediaSources}}state is{{states}}and{{timeRestrictions}}{{time}}switch to{{scenes}}using{{transitions}}"
AdvSceneSwitcher.mediaTab.help="This tab will allow you to switch scenes based on the states of media sources.\nFor example, you can automatically switch back to the previous scene once the selected media sourced ended its playback.\n\nClick on the highlighted plus symbol to continue."
; File Tab
# File Tab
AdvSceneSwitcher.fileTab.title="File"
AdvSceneSwitcher.fileTab.readWriteSceneFile="Read / write scene from / to file"
AdvSceneSwitcher.fileTab.currentSceneOutputFile="Write the name of the current scene to this file:"
@ -2015,13 +2053,13 @@ AdvSceneSwitcher.fileTab.entry2="{{matchText}}"
AdvSceneSwitcher.fileTab.entry3="{{useRegex}}{{checkModificationDate}}{{checkFileContent}}"
AdvSceneSwitcher.fileTab.help="This tab will allow you to automatically switch scenes based on the content of remote or local files.\n\nClick on the highlighted plus symbol to continue."
; Random Tab
# Random Tab
AdvSceneSwitcher.randomTab.title="Random"
AdvSceneSwitcher.randomTab.randomDisabledWarning="Functionality disabled - To activate select \"If no switch condition is met switch to any scene in Random tab\" on General tab"
AdvSceneSwitcher.randomTab.entry="If no switch condition is met switch to{{scenes}}using{{transitions}}for{{delay}}"
AdvSceneSwitcher.randomTab.help="The scene switcher will randomly choose an entry on this tab to switch to for the configured time.\nNote that the same entry will not be chosen twice in a row.\n\nClick on the highlighted plus symbol to continue."
; Time Tab
# Time Tab
AdvSceneSwitcher.timeTab.title="Time"
AdvSceneSwitcher.timeTab.anyDay="On any day"
AdvSceneSwitcher.timeTab.mondays="Mondays"
@ -2036,14 +2074,14 @@ AdvSceneSwitcher.timeTab.afterstart.tip="The time relative to the start of strea
AdvSceneSwitcher.timeTab.entry="{{triggers}}at{{time}}switch to{{scenes}}using{{transitions}}"
AdvSceneSwitcher.timeTab.help="This tab will allow you to automatically switch to a different scene based on the current local time.\n\nNote that the scene switcher will only switch scenes at the exact time you specified.\nMake sure you have configured the priority settings on the General tab to your liking so the selected time point will not be missed due to other switching methods having a higher priority.\n\nClick on the highlighted plus symbol to continue."
; Idle Tab
# Idle Tab
AdvSceneSwitcher.idleTab.title="Idle"
AdvSceneSwitcher.idleTab.enable="Enable Idle Detection"
AdvSceneSwitcher.idleTab.idleswitch="After{{duration}}of no keyboard or mouse inputs switch to scene{{scenes}}using the{{transitions}}"
AdvSceneSwitcher.idleTab.dontSwitchIfFocus1="Do not switch if"
AdvSceneSwitcher.idleTab.dontSwitchIfFocus2="is in focus"
; Scene Sequence Tab
# Scene Sequence Tab
AdvSceneSwitcher.sceneSequenceTab.title="Sequence"
AdvSceneSwitcher.sceneSequenceTab.description="A sequence of automatic scene switches can be cancelled by either pausing/stopping the scene switcher or manually switching to a different scene"
AdvSceneSwitcher.sceneSequenceTab.save="Save scene sequences to file"
@ -2060,7 +2098,7 @@ AdvSceneSwitcher.sceneSequenceTab.extendEdit="Extend Sequence"
AdvSceneSwitcher.sceneSequenceTab.extendEntry="After{{delay}}switch to{{scenes}}using{{transitions}}"
AdvSceneSwitcher.sceneSequenceTab.help="This tab will allow you to automatically switch to a different scene if a scene was active for a configured period of time.\nFor example, you could automatically cycle back and forth between two scenes automatically.\n\nClick on the highlighted plus symbol to continue."
; Audio Tab
# Audio Tab
AdvSceneSwitcher.audioTab.title="Audio"
AdvSceneSwitcher.audioTab.condition.above="above"
AdvSceneSwitcher.audioTab.condition.below="below"
@ -2070,7 +2108,7 @@ AdvSceneSwitcher.audioTab.multiMatchfallbackCondition="If multiple entries match
AdvSceneSwitcher.audioTab.multiMatchfallback="... for{{duration}}seconds switch to{{scenes}}using{{transitions}}"
AdvSceneSwitcher.audioTab.help="This tab will allow you to switch scenes based on the volume of sources.\nFor example, you could automatically switch to a different scene if the volume of your microphone reaches a certain threshold.\n\nClick on the highlighted plus symbol to continue."
; Video Tab
# Video Tab
AdvSceneSwitcher.videoTab.title="Video"
AdvSceneSwitcher.videoTab.getScreenshot="Get screenshot for selected entry"
AdvSceneSwitcher.videoTab.getScreenshotHelp="Get Screenshot of the currently selected entry's video source and automatically set it as the target image"
@ -2083,7 +2121,7 @@ AdvSceneSwitcher.videoTab.ignoreInactiveSource="unless source is inactive"
AdvSceneSwitcher.videoTab.entry="When{{videoSources}}{{condition}}{{filePath}}{{browseButton}}for{{duration}}switch to{{scenes}}using{{transitions}}{{ignoreInactiveSource}}"
AdvSceneSwitcher.videoTab.help="<html><head/><body><p>This tab will allow you to switch scenes based on the current video output of selected sources.<br/>Make sure to check out <a href=\"https://obsproject.com/forum/resources/pixel-match-switcher.1202/\"><span style=\" text-decoration: underline; color:#268bd2;\">Pixel Match Switcher</span></a> for an even better implementation of this functionality.<br/><br/> Click on the highlighted plus symbol to continue.</p></body></html>"
; Network Tab
# Network Tab
AdvSceneSwitcher.networkTab.title="Network"
AdvSceneSwitcher.networkTab.description="This tab will allow you to remotely control the active scene of another OBS instance.\nPlease note that the scene names have to match exactly on all OBS instances."
AdvSceneSwitcher.networkTab.warning="Running the server outside of a local network will allow third parties to read the active scene."
@ -2108,7 +2146,7 @@ AdvSceneSwitcher.networkTab.client.status.connecting="Connecting"
AdvSceneSwitcher.networkTab.client.status.connected="Connected"
AdvSceneSwitcher.networkTab.client.reconnect="Force reconnect"
; Scene Group Tab
# Scene Group Tab
AdvSceneSwitcher.sceneGroupTab.title="Scene Group"
AdvSceneSwitcher.sceneGroupTab.list="Scene Groups"
AdvSceneSwitcher.sceneGroupTab.edit="Edit Scene Groups"
@ -2128,7 +2166,7 @@ AdvSceneSwitcher.sceneGroupTab.exists="Scene Group or Scene name exists already"
AdvSceneSwitcher.sceneGroupTab.help="Scene Groups can be selected as a target just like a regular scene.\n\nAs the name suggests a scene group is a collection of multiple scenes.\nThe scene group will advance through the list of its assigned scenes depending on the configured settings, which can be found on the right side.\n\nYou can configure the scene group to advance to the next scene in the list:\nAfter a number of times the scene group is selected as a target.\nAfter a certain amount of time has passed.\nOr randomly.\n\nFor example, a scene group containing the scenes ...\nScene 1\nScene 2\nScene 3 \n... will activate \"Scene 1\" the first time it is selected as a target.\nThe second time it will activate \"Scene 2\".\nThe remaining times \"Scene 3\" will be activated.\n\nClick the highlighted plus symbol below to add a new scene group."
AdvSceneSwitcher.sceneGroupTab.scenes.help="Select the scene group you want to modify on the left.\n\nSelect a scene to add to this scene group by selecting the scene above and clicking the plus symbol below.\n\nA scene can be added multiple times to the same scene group."
; Scene Trigger Tab
# Scene Trigger Tab
AdvSceneSwitcher.sceneTriggerTab.title="Scene Triggers"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.none="--select trigger--"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneActive="is active"

View File

@ -1,7 +1,28 @@
#include "macro-action-factory.hpp"
#include <mutex>
namespace advss {
namespace {
class MacroActionUnknown : public MacroAction {
public:
MacroActionUnknown(Macro *m) : MacroAction(m) {}
std::shared_ptr<MacroAction> Copy() const
{
return std::make_shared<MacroActionUnknown>(GetMacro());
}
bool PerformAction() { return true; };
bool Save(obs_data_t *obj) const { return MacroAction::Save(obj); };
bool Load(obs_data_t *obj) { return MacroAction::Load(obj); };
std::string GetId() const { return "unknown"; }
};
} // namespace
static std::mutex mutex;
std::map<std::string, MacroActionInfo> &MacroActionFactory::GetMap()
{
static std::map<std::string, MacroActionInfo> _methods;
@ -10,6 +31,7 @@ std::map<std::string, MacroActionInfo> &MacroActionFactory::GetMap()
bool MacroActionFactory::Register(const std::string &id, MacroActionInfo info)
{
std::lock_guard<std::mutex> lock(mutex);
if (auto it = GetMap().find(id); it == GetMap().end()) {
GetMap()[id] = info;
return true;
@ -17,35 +39,61 @@ bool MacroActionFactory::Register(const std::string &id, MacroActionInfo info)
return false;
}
bool MacroActionFactory::Deregister(const std::string &id)
{
std::lock_guard<std::mutex> lock(mutex);
if (GetMap().count(id) == 0) {
return false;
}
GetMap().erase(id);
return true;
}
static std::shared_ptr<MacroAction> createUnknownAction(Macro *m)
{
return std::make_shared<MacroActionUnknown>(m);
}
std::shared_ptr<MacroAction> MacroActionFactory::Create(const std::string &id,
Macro *m)
{
if (auto it = GetMap().find(id); it != GetMap().end())
std::lock_guard<std::mutex> lock(mutex);
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._create(m);
}
return nullptr;
return createUnknownAction(m);
}
static QWidget *createUnknownActionWidget()
{
return new QLabel(obs_module_text("AdvSceneSwitcher.action.unknown"));
}
QWidget *MacroActionFactory::CreateWidget(const std::string &id,
QWidget *parent,
std::shared_ptr<MacroAction> action)
{
if (auto it = GetMap().find(id); it != GetMap().end())
std::lock_guard<std::mutex> lock(mutex);
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._createWidget(parent, action);
}
return nullptr;
return createUnknownActionWidget();
}
std::string MacroActionFactory::GetActionName(const std::string &id)
{
std::lock_guard<std::mutex> lock(mutex);
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._name;
}
return "unknown action";
return obs_module_text("AdvSceneSwitcher.action.unknown");
}
std::string MacroActionFactory::GetIdByName(const QString &name)
{
std::lock_guard<std::mutex> lock(mutex);
for (auto it : GetMap()) {
if (name == obs_module_text(it.second._name.c_str())) {
return it.first;

View File

@ -6,10 +6,10 @@
namespace advss {
struct MacroActionInfo {
using CreateAction = std::shared_ptr<MacroAction> (*)(Macro *m);
using CreateActionWidget = QWidget *(*)(QWidget *parent,
std::shared_ptr<MacroAction>);
CreateAction _create = nullptr;
std::function<std::shared_ptr<MacroAction>(Macro *m)> _create = nullptr;
CreateActionWidget _createWidget = nullptr;
std::string _name;
};
@ -19,6 +19,7 @@ public:
MacroActionFactory() = delete;
EXPORT static bool Register(const std::string &id, MacroActionInfo);
static bool Deregister(const std::string &id);
static std::shared_ptr<MacroAction> Create(const std::string &id,
Macro *m);
static QWidget *CreateWidget(const std::string &id, QWidget *parent,

View File

@ -0,0 +1,92 @@
#include "macro-action-script.hpp"
#include "layout-helpers.hpp"
#include "macro-helpers.hpp"
#include "properties-view.hpp"
#include "sync-helpers.hpp"
namespace advss {
MacroActionScript::MacroActionScript(Macro *m, const std::string &id,
const OBSData &defaultSettings,
const std::string &propertiesSignalName,
const std::string &triggerSignal,
const std::string &completionSignal)
: MacroAction(m),
MacroSegmentScript(defaultSettings, propertiesSignalName,
triggerSignal, completionSignal),
_id(id)
{
}
MacroActionScript::MacroActionScript(const advss::MacroActionScript &other)
: MacroAction(other.GetMacro()),
MacroSegmentScript(other),
_id(other._id)
{
}
bool MacroActionScript::PerformAction()
{
if (!ScriptHandler::ActionIdIsValid(_id)) {
blog(LOG_WARNING, "skipping unknown script action \"%s\"",
_id.c_str());
return true;
}
(void)SendTriggerSignal();
return true;
}
void MacroActionScript::LogAction() const
{
ablog(LOG_INFO, "performing script action \"%s\"", GetId().c_str());
}
bool MacroActionScript::Save(obs_data_t *obj) const
{
MacroAction::Save(obj);
MacroSegmentScript::Save(obj);
return true;
}
bool MacroActionScript::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
MacroSegmentScript::Load(obj);
return true;
}
std::shared_ptr<MacroAction> MacroActionScript::Copy() const
{
return std::make_shared<MacroActionScript>(*this);
}
void MacroActionScript::WaitForCompletion() const
{
using namespace std::chrono_literals;
auto start = std::chrono::high_resolution_clock::now();
auto timePassed = std::chrono::duration_cast<std::chrono::milliseconds>(
start - start);
const auto timeoutMs = GetTimeoutSeconds() * 1000.0;
std::unique_lock<std::mutex> lock(*GetMutex());
while (!TriggerIsCompleted()) {
if (MacroWaitShouldAbort() || MacroIsStopped(GetMacro())) {
break;
}
if ((double)timePassed.count() > timeoutMs) {
blog(LOG_INFO, "script action timeout (%s)",
_id.c_str());
break;
}
GetMacroWaitCV().wait_for(lock, 10ms);
const auto now = std::chrono::high_resolution_clock::now();
timePassed =
std::chrono::duration_cast<std::chrono::milliseconds>(
now - start);
}
}
} // namespace advss

View File

@ -0,0 +1,29 @@
#pragma once
#include "macro-action-edit.hpp"
#include "macro-script-handler.hpp"
#include "macro-segment-script.hpp"
namespace advss {
class MacroActionScript : public MacroAction, public MacroSegmentScript {
public:
MacroActionScript(Macro *m, const std::string &id,
const OBSData &defaultSettings,
const std::string &propertiesSignalName,
const std::string &triggerSignal,
const std::string &signalComplete);
MacroActionScript(const advss::MacroActionScript &);
bool PerformAction();
void LogAction() const;
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
std::string GetId() const { return _id; };
std::shared_ptr<MacroAction> Copy() const;
private:
void WaitForCompletion() const;
std::string _id = "";
};
} // namespace advss

View File

@ -1,7 +1,24 @@
#include "macro-condition-factory.hpp"
#include <mutex>
namespace advss {
namespace {
class MacroConditionUnknown : public MacroCondition {
public:
MacroConditionUnknown(Macro *m) : MacroCondition(m) {}
bool CheckCondition() { return false; }
bool Save(obs_data_t *obj) const { return MacroCondition::Save(obj); };
bool Load(obs_data_t *obj) { return MacroCondition::Load(obj); };
std::string GetId() const { return "unknown"; }
};
} // namespace
static std::mutex mutex;
std::map<std::string, MacroConditionInfo> &MacroConditionFactory::GetMap()
{
static std::map<std::string, MacroConditionInfo> _methods;
@ -11,6 +28,7 @@ std::map<std::string, MacroConditionInfo> &MacroConditionFactory::GetMap()
bool MacroConditionFactory::Register(const std::string &id,
MacroConditionInfo info)
{
std::lock_guard<std::mutex> lock(mutex);
if (auto it = GetMap().find(id); it == GetMap().end()) {
GetMap()[id] = info;
return true;
@ -18,35 +36,60 @@ bool MacroConditionFactory::Register(const std::string &id,
return false;
}
bool MacroConditionFactory::Deregister(const std::string &id)
{
std::lock_guard<std::mutex> lock(mutex);
if (GetMap().count(id) == 0) {
return false;
}
GetMap().erase(id);
return true;
}
static std::shared_ptr<MacroCondition> createUnknownCondition(Macro *m)
{
return std::make_shared<MacroConditionUnknown>(m);
}
std::shared_ptr<MacroCondition>
MacroConditionFactory::Create(const std::string &id, Macro *m)
{
std::lock_guard<std::mutex> lock(mutex);
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._create(m);
}
return nullptr;
return createUnknownCondition(m);
}
static QWidget *createUnknownConditionWidget()
{
return new QLabel(
obs_module_text("AdvSceneSwitcher.condition.unknown"));
}
QWidget *
MacroConditionFactory::CreateWidget(const std::string &id, QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
std::lock_guard<std::mutex> lock(mutex);
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._createWidget(parent, cond);
}
return nullptr;
return createUnknownConditionWidget();
}
std::string MacroConditionFactory::GetConditionName(const std::string &id)
{
std::lock_guard<std::mutex> lock(mutex);
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._name;
}
return "unknown condition";
return obs_module_text("AdvSceneSwitcher.condition.unknown");
}
std::string MacroConditionFactory::GetIdByName(const QString &name)
{
std::lock_guard<std::mutex> lock(mutex);
for (auto it : GetMap()) {
if (name == obs_module_text(it.second._name.c_str())) {
return it.first;
@ -57,6 +100,7 @@ std::string MacroConditionFactory::GetIdByName(const QString &name)
bool MacroConditionFactory::UsesDurationModifier(const std::string &id)
{
std::lock_guard<std::mutex> lock(mutex);
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._useDurationModifier;
}

View File

@ -6,10 +6,11 @@
namespace advss {
struct MacroConditionInfo {
using CreateCondition = std::shared_ptr<MacroCondition> (*)(Macro *m);
using CreateConditionWidget =
QWidget *(*)(QWidget *parent, std::shared_ptr<MacroCondition>);
CreateCondition _create = nullptr;
std::function<std::shared_ptr<MacroCondition>(Macro *m)> _create =
nullptr;
CreateConditionWidget _createWidget = nullptr;
std::string _name;
bool _useDurationModifier = true;
@ -19,6 +20,7 @@ class MacroConditionFactory {
public:
MacroConditionFactory() = delete;
EXPORT static bool Register(const std::string &, MacroConditionInfo);
static bool Deregister(const std::string &);
static std::shared_ptr<MacroCondition> Create(const std::string &,
Macro *m);
static QWidget *CreateWidget(const std::string &id, QWidget *parent,

View File

@ -0,0 +1,79 @@
#include "macro-condition-script.hpp"
#include "layout-helpers.hpp"
#include "macro-helpers.hpp"
#include "sync-helpers.hpp"
namespace advss {
MacroConditionScript::MacroConditionScript(
Macro *m, const std::string &id, const OBSData &defaultSettings,
const std::string &propertiesSignalName,
const std::string &triggerSignal, const std::string &completionSignal)
: MacroCondition(m),
MacroSegmentScript(defaultSettings, propertiesSignalName,
triggerSignal, completionSignal),
_id(id)
{
}
MacroConditionScript::MacroConditionScript(
const advss::MacroConditionScript &other)
: MacroCondition(other.GetMacro()),
MacroSegmentScript(other),
_id(other._id)
{
}
bool MacroConditionScript::CheckCondition()
{
if (!ScriptHandler::ConditionIdIsValid(_id)) {
blog(LOG_WARNING, "skipping unknown script condition \"%s\"",
_id.c_str());
return false;
}
return SendTriggerSignal();
}
bool MacroConditionScript::Save(obs_data_t *obj) const
{
MacroCondition::Save(obj);
MacroSegmentScript::Save(obj);
return true;
}
bool MacroConditionScript::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
MacroSegmentScript::Load(obj);
return true;
}
void MacroConditionScript::WaitForCompletion() const
{
using namespace std::chrono_literals;
auto start = std::chrono::high_resolution_clock::now();
auto timePassed = std::chrono::duration_cast<std::chrono::milliseconds>(
start - start);
const auto timeoutMs = GetTimeoutSeconds() * 1000.0;
while (!TriggerIsCompleted()) {
if (MacroWaitShouldAbort() || MacroIsStopped(GetMacro())) {
break;
}
if ((double)timePassed.count() > timeoutMs) {
blog(LOG_INFO, "script condition timeout (%s)",
_id.c_str());
break;
}
std::this_thread::sleep_for(10ms);
const auto now = std::chrono::high_resolution_clock::now();
timePassed =
std::chrono::duration_cast<std::chrono::milliseconds>(
now - start);
}
}
} // namespace advss

View File

@ -0,0 +1,27 @@
#pragma once
#include "macro-condition-edit.hpp"
#include "macro-script-handler.hpp"
#include "macro-segment-script.hpp"
namespace advss {
class MacroConditionScript : public MacroCondition, public MacroSegmentScript {
public:
MacroConditionScript(Macro *m, const std::string &id,
const OBSData &defaultSettings,
const std::string &propertiesSignalName,
const std::string &triggerSignal,
const std::string &signalComplete);
MacroConditionScript(const advss::MacroConditionScript &);
bool CheckCondition();
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
std::string GetId() const { return _id; };
private:
void WaitForCompletion() const;
std::string _id = "";
};
} // namespace advss

View File

@ -0,0 +1,447 @@
#include "macro-script-handler.hpp"
#include "macro-action-script.hpp"
#include "macro-condition-script.hpp"
#include "plugin-state-helpers.hpp"
#include "log-helper.hpp"
#include "variable.hpp"
#include <obs-properties.h>
namespace advss {
std::mutex ScriptHandler::_mutex = {};
std::unordered_map<std::string, ScriptSegmentType> ScriptHandler::_actions = {};
std::unordered_map<std::string, ScriptSegmentType> ScriptHandler::_conditions =
{};
/* Procedure handler helpers */
#define RETURN_STATUS(status) \
{ \
calldata_set_bool(data, "success", status); \
return; \
}
#define RETURN_SUCCESS() RETURN_STATUS(true);
#define RETURN_FAILURE() RETURN_STATUS(false);
static constexpr std::string_view nameParam = "name";
static constexpr std::string_view defaultSettingsParam = "default_settings";
static constexpr std::string_view propertiesSignalParam =
"properties_signal_name";
static constexpr std::string_view triggerSignalParam = "trigger_signal_name";
static std::string getRegisterScriptSegmentDeclString(const char *funcName)
{
return std::string("bool ") + funcName + "(in string " +
nameParam.data() + ", in ptr " + defaultSettingsParam.data() +
", out string " + propertiesSignalParam.data() +
", out string " + triggerSignalParam.data() + ")";
}
static std::string getDeregisterScriptSegmentDeclString(const char *funcName)
{
return std::string("bool ") + funcName + "(in string " +
nameParam.data() + ")";
}
/* Script actions */
static constexpr std::string_view registerActionFuncName =
"advss_register_script_action";
static constexpr std::string_view deregisterActionFuncName =
"advss_deregister_script_action";
static const std::string registerScriptActionDeclString =
getRegisterScriptSegmentDeclString(registerActionFuncName.data());
static const std::string deregisterScriptActionDeclString =
getDeregisterScriptSegmentDeclString(deregisterActionFuncName.data());
/* Script conditions */
static constexpr std::string_view registerConditionFuncName =
"advss_register_script_condition";
static constexpr std::string_view deregisterConditionFuncName =
"advss_deregister_script_condition";
static const std::string registerScriptConditionDeclString =
getRegisterScriptSegmentDeclString(registerConditionFuncName.data());
static const std::string deregisterScriptConditionDeclString =
getDeregisterScriptSegmentDeclString(
deregisterConditionFuncName.data());
/* Script variables */
static constexpr std::string_view valueParam = "value";
static constexpr std::string_view getVariableValueFuncName =
"advss_get_variable_value";
static constexpr std::string_view setVariableValueFuncName =
"advss_set_variable_value";
static const std::string getVariableValueDeclString =
std::string("bool ") + getVariableValueFuncName.data() + "(in string " +
nameParam.data() + ", out string " + valueParam.data() + ")";
static const std::string setVariableValueDeclString =
std::string("bool ") + setVariableValueFuncName.data() + "(in string " +
nameParam.data() + ", in string " + valueParam.data() + ")";
static bool setup();
static bool setupDone = setup();
static bool setup()
{
proc_handler_t *ph = obs_get_proc_handler();
assert(ph != NULL);
proc_handler_add(ph, registerScriptActionDeclString.c_str(),
&ScriptHandler::RegisterScriptAction, nullptr);
proc_handler_add(ph, deregisterScriptActionDeclString.c_str(),
&ScriptHandler::DeregisterScriptAction, nullptr);
proc_handler_add(ph, registerScriptConditionDeclString.c_str(),
&ScriptHandler::RegisterScriptCondition, nullptr);
proc_handler_add(ph, deregisterScriptConditionDeclString.c_str(),
&ScriptHandler::DeregisterScriptCondition, nullptr);
proc_handler_add(ph, getVariableValueDeclString.c_str(),
&ScriptHandler::GetVariableValue, nullptr);
proc_handler_add(ph, setVariableValueDeclString.c_str(),
&ScriptHandler::SetVariableValue, nullptr);
return true;
}
static void replaceWhitespace(std::string &string)
{
std::transform(string.begin(), string.end(), string.begin(), [](char c) {
return std::isspace(static_cast<unsigned char>(c)) ? '_' : c;
});
}
static std::string nameToScriptID(const std::string &name)
{
return std::string("script_") + name;
}
static std::string getTriggerSignal(const std::string &name,
const bool isAction)
{
std::string signal = name;
replaceWhitespace(signal);
signal += "_run";
signal += isAction ? "_action" : "_condition";
return signal;
}
static std::string getCompletionSignal(const std::string &name,
const bool isAction)
{
auto signal = getTriggerSignal(name, isAction);
signal += "_complete";
return signal;
}
static std::string getPropertiesSignal(const std::string &name,
const bool isAction)
{
std::string signal = name;
replaceWhitespace(signal);
signal += isAction ? "_action" : "_condition";
signal += "_get_properties";
return signal;
}
void ScriptHandler::RegisterScriptAction(void *, calldata_t *data)
{
const char *actionName;
if (!calldata_get_string(data, nameParam.data(), &actionName) ||
strlen(actionName) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
registerScriptActionDeclString.data(), nameParam.data());
RETURN_FAILURE();
}
obs_data_t *defaultSettingsPtr = nullptr;
if (!calldata_get_ptr(data, defaultSettingsParam.data(),
&defaultSettingsPtr)) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
registerActionFuncName.data(),
defaultSettingsParam.data());
RETURN_FAILURE();
}
std::lock_guard<std::mutex> lock(_mutex);
OBSData defaultSettings(defaultSettingsPtr);
obs_data_release(defaultSettingsPtr);
if (_actions.count(actionName) > 0) {
blog(LOG_WARNING, "[%s] failed! Action \"%s\" already exists!",
registerActionFuncName.data(), actionName);
RETURN_FAILURE();
}
const std::string id = nameToScriptID(actionName);
auto triggerSignalName = getTriggerSignal(actionName, true);
auto completionSignalName = getCompletionSignal(actionName, true);
auto propertiesSignalName = getPropertiesSignal(actionName, true);
const auto createScriptAction =
[id, defaultSettings, propertiesSignalName, triggerSignalName,
completionSignalName](
Macro *m) -> std::shared_ptr<MacroAction> {
return std::make_shared<MacroActionScript>(
m, id, defaultSettings, propertiesSignalName,
triggerSignalName, completionSignalName);
};
if (!MacroActionFactory::Register(id, {createScriptAction,
MacroSegmentScriptEdit::Create,
actionName})) {
blog(LOG_WARNING,
"[%s] failed! Action id \"%s\" already exists!",
registerActionFuncName.data(), id.c_str());
RETURN_FAILURE();
}
blog(LOG_INFO, "[%s] successful for \"%s\"",
registerActionFuncName.data(), actionName);
calldata_set_string(data, triggerSignalParam.data(),
triggerSignalName.c_str());
calldata_set_string(data, propertiesSignalParam.data(),
propertiesSignalName.c_str());
_actions.emplace(id, ScriptSegmentType(id, propertiesSignalName,
triggerSignalName,
completionSignalName));
RETURN_SUCCESS();
}
void ScriptHandler::DeregisterScriptAction(void *, calldata_t *data)
{
const char *actionName;
if (!calldata_get_string(data, nameParam.data(), &actionName) ||
strlen(actionName) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
deregisterActionFuncName.data(), nameParam.data());
RETURN_FAILURE();
}
const std::string id = nameToScriptID(actionName);
std::lock_guard<std::mutex> lock(_mutex);
if (_actions.count(id) == 0) {
blog(LOG_WARNING,
"[%s] failed! Action \"%s\" was never registered!",
deregisterActionFuncName.data(), id.c_str());
RETURN_FAILURE();
}
if (!MacroActionFactory::Deregister(id)) {
blog(LOG_WARNING,
"[%s] failed! Action id \"%s\" does not exist!",
deregisterActionFuncName.data(), id.c_str());
RETURN_FAILURE();
}
auto it = _actions.find(id);
if (it != _actions.end()) {
_actions.erase(it);
}
RETURN_SUCCESS();
}
void ScriptHandler::RegisterScriptCondition(void *, calldata_t *data)
{
const char *conditionName;
if (!calldata_get_string(data, nameParam.data(), &conditionName) ||
strlen(conditionName) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
registerScriptConditionDeclString.data(),
nameParam.data());
RETURN_FAILURE();
}
obs_data_t *defaultSettingsPtr = nullptr;
if (!calldata_get_ptr(data, defaultSettingsParam.data(),
&defaultSettingsPtr)) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
registerScriptConditionDeclString.data(),
defaultSettingsParam.data());
RETURN_FAILURE();
}
std::lock_guard<std::mutex> lock(_mutex);
OBSData defaultSettings(defaultSettingsPtr);
obs_data_release(defaultSettingsPtr);
if (_conditions.count(conditionName) > 0) {
blog(LOG_WARNING,
"[%s] failed! Condition \"%s\" already exists!",
registerConditionFuncName.data(), conditionName);
RETURN_FAILURE();
}
const std::string id = nameToScriptID(conditionName);
auto triggerSignalName = getTriggerSignal(conditionName, false);
auto completionSignalName = getCompletionSignal(conditionName, false);
auto propertiesSignalName = getPropertiesSignal(conditionName, false);
const auto createScriptCondition =
[id, defaultSettings, propertiesSignalName, triggerSignalName,
completionSignalName](
Macro *m) -> std::shared_ptr<MacroCondition> {
return std::make_shared<MacroConditionScript>(
m, id, defaultSettings, propertiesSignalName,
triggerSignalName, completionSignalName);
};
if (!MacroConditionFactory::Register(
id, {createScriptCondition, MacroSegmentScriptEdit::Create,
conditionName})) {
blog(LOG_WARNING,
"[%s] failed! Condition id \"%s\" already exists!",
registerConditionFuncName.data(), id.c_str());
RETURN_FAILURE();
}
blog(LOG_INFO, "[%s] successful for \"%s\"",
registerConditionFuncName.data(), conditionName);
calldata_set_string(data, triggerSignalParam.data(),
triggerSignalName.c_str());
calldata_set_string(data, propertiesSignalParam.data(),
propertiesSignalName.c_str());
_conditions.emplace(id, ScriptSegmentType(id, propertiesSignalName,
triggerSignalName,
completionSignalName));
RETURN_SUCCESS();
}
void ScriptHandler::DeregisterScriptCondition(void *, calldata_t *data)
{
const char *conditionName;
if (!calldata_get_string(data, nameParam.data(), &conditionName) ||
strlen(conditionName) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
deregisterConditionFuncName.data(), nameParam.data());
RETURN_FAILURE();
}
const std::string id = nameToScriptID(conditionName);
std::lock_guard<std::mutex> lock(_mutex);
if (_conditions.count(id) == 0) {
blog(LOG_WARNING,
"[%s] failed! Condition \"%s\" was never registered!",
deregisterConditionFuncName.data(), id.c_str());
RETURN_FAILURE();
}
if (!MacroConditionFactory::Deregister(id)) {
blog(LOG_WARNING,
"[%s] failed! Condition id \"%s\" does not exist!",
deregisterConditionFuncName.data(), id.c_str());
RETURN_FAILURE();
}
auto it = _conditions.find(id);
if (it != _conditions.end()) {
_conditions.erase(it);
}
RETURN_SUCCESS();
}
void ScriptHandler::GetVariableValue(void *, calldata_t *data)
{
const char *variableName;
if (!calldata_get_string(data, nameParam.data(), &variableName) ||
strlen(variableName) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
getVariableValueFuncName.data(), nameParam.data());
RETURN_FAILURE();
}
auto weakVariable = GetWeakVariableByName(variableName);
auto variable = weakVariable.lock();
if (!variable) {
blog(LOG_WARNING,
"[%s] failed! \"%s\" variable does not exist!",
getVariableValueFuncName.data(), nameParam.data());
RETURN_FAILURE();
}
calldata_set_string(data, valueParam.data(), variable->Value().c_str());
RETURN_SUCCESS();
}
void ScriptHandler::SetVariableValue(void *, calldata_t *data)
{
const char *variableName;
if (!calldata_get_string(data, nameParam.data(), &variableName) ||
strlen(variableName) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
getVariableValueFuncName.data(), nameParam.data());
RETURN_FAILURE();
}
const char *variableValue;
if (!calldata_get_string(data, valueParam.data(), &variableValue) ||
strlen(variableValue) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
getVariableValueFuncName.data(), valueParam.data());
RETURN_FAILURE();
}
auto weakVariable = GetWeakVariableByName(variableName);
auto variable = weakVariable.lock();
if (!variable) {
blog(LOG_WARNING,
"[%s] failed! \"%s\" variable does not exist!",
getVariableValueFuncName.data(), nameParam.data());
RETURN_FAILURE();
}
variable->SetValue(variableValue);
RETURN_SUCCESS();
}
bool ScriptHandler::ActionIdIsValid(const std::string &id)
{
std::lock_guard<std::mutex> lock(_mutex);
return _actions.count(id) > 0;
}
bool ScriptHandler::ConditionIdIsValid(const std::string &id)
{
std::lock_guard<std::mutex> lock(_mutex);
return _conditions.count(id) > 0;
}
static std::string signalNameToTriggerSignalDecl(const std::string &name)
{
return std::string("void ") + name + "()";
}
static std::string signalNameToPropertiesSignalDecl(const std::string &name)
{
return std::string("void ") + name + "(in ptr " +
GetPropertiesSignalParamName().data() + ")";
}
static std::string signalNameToCompletionSignalDecl(const std::string &name)
{
return std::string("bool ") + name + "(in int " +
GeCompletionIdParamName().data() + ")";
}
ScriptSegmentType::ScriptSegmentType(const std::string &id,
const std::string &propertiesSignal,
const std::string &triggerSignal,
const std::string &completionSignal)
: _id(id)
{
signal_handler_add(
obs_get_signal_handler(),
signalNameToPropertiesSignalDecl(propertiesSignal).c_str());
signal_handler_add(
obs_get_signal_handler(),
signalNameToTriggerSignalDecl(triggerSignal).c_str());
signal_handler_add(
obs_get_signal_handler(),
signalNameToCompletionSignalDecl(completionSignal).c_str());
}
} // namespace advss

View File

@ -0,0 +1,59 @@
#pragma once
#include <obs.hpp>
#include <mutex>
#include <string>
#include <unordered_map>
namespace advss {
class ScriptSegmentType {
public:
ScriptSegmentType() = delete;
ScriptSegmentType(const std::string &id,
const std::string &propertiesSignal,
const std::string &triggerSignal,
const std::string &completionSignal);
private:
std::string _id;
};
class ScriptHandler {
public:
ScriptHandler() = delete;
static void RegisterScriptAction(void *ctx, calldata_t *data);
static void DeregisterScriptAction(void *ctx, calldata_t *data);
static void RegisterScriptCondition(void *ctx, calldata_t *data);
static void DeregisterScriptCondition(void *ctx, calldata_t *data);
static void GetVariableValue(void *ctx, calldata_t *data);
static void SetVariableValue(void *ctx, calldata_t *data);
static bool ActionIdIsValid(const std::string &id);
static bool ConditionIdIsValid(const std::string &id);
private:
static std::mutex _mutex;
static std::unordered_map<std::string, ScriptSegmentType> _actions;
static std::unordered_map<std::string, ScriptSegmentType> _conditions;
};
static constexpr std::string_view GetPropertiesSignalParamName()
{
return "properties";
}
static constexpr std::string_view GetActionCompletionSignalParamName()
{
return "completion_signal_name";
}
static constexpr std::string_view GeCompletionIdParamName()
{
return "completion_id";
}
static constexpr std::string_view GeResultSignalParamName()
{
return "result";
}
} // namespace advss

View File

@ -0,0 +1,219 @@
#include "macro-segment-script.hpp"
#include "layout-helpers.hpp"
#include "macro-action.hpp"
#include "macro-condition.hpp"
#include "macro-helpers.hpp"
#include "macro-script-handler.hpp"
#include "obs-module-helper.hpp"
#include "properties-view.hpp"
#include "sync-helpers.hpp"
namespace advss {
static std::atomic_int completionIdCounter = 0;
MacroSegmentScript::MacroSegmentScript(obs_data_t *defaultSettings,
const std::string &propertiesSignalName,
const std::string &triggerSignal,
const std::string &completionSignal)
: _settings(obs_data_get_defaults(defaultSettings)),
_propertiesSignal(propertiesSignalName),
_triggerSignal(triggerSignal),
_completionSignal(completionSignal)
{
signal_handler_connect(obs_get_signal_handler(),
completionSignal.c_str(),
&MacroSegmentScript::CompletionSignalReceived,
this);
}
MacroSegmentScript::MacroSegmentScript(const MacroSegmentScript &other)
: _settings(obs_data_create()),
_propertiesSignal(other._propertiesSignal),
_triggerSignal(other._triggerSignal),
_completionSignal(other._completionSignal)
{
signal_handler_connect(obs_get_signal_handler(),
_completionSignal.c_str(),
&MacroSegmentScript::CompletionSignalReceived,
this);
obs_data_apply(_settings.Get(), other._settings.Get());
}
bool MacroSegmentScript::Save(obs_data_t *obj) const
{
obs_data_set_obj(obj, "settings", _settings.Get());
_timeout.Save(obj);
return true;
}
bool MacroSegmentScript::Load(obs_data_t *obj)
{
OBSDataAutoRelease settings = obs_data_get_obj(obj, "settings");
obs_data_apply(_settings.Get(), settings);
_timeout.Load(obj);
return true;
}
obs_properties_t *MacroSegmentScript::GetProperties() const
{
auto data = calldata_create();
signal_handler_signal(obs_get_signal_handler(),
_propertiesSignal.c_str(), data);
obs_properties_t *properties = nullptr;
if (!calldata_get_ptr(data, GetPropertiesSignalParamName().data(),
&properties)) {
calldata_destroy(data);
return nullptr;
}
calldata_destroy(data);
return properties;
}
void MacroSegmentScript::UpdateSettings(obs_data_t *newSettings) const
{
obs_data_clear(_settings.Get());
obs_data_apply(_settings.Get(), newSettings);
}
bool MacroSegmentScript::SendTriggerSignal()
{
_completionId = ++completionIdCounter;
_triggerIsComplete = false;
_triggerResult = false;
auto data = calldata_create();
calldata_set_string(data, GetActionCompletionSignalParamName().data(),
_completionSignal.c_str());
calldata_set_int(data, GeCompletionIdParamName().data(), _completionId);
calldata_set_string(data, "settings", obs_data_get_json(GetSettings()));
signal_handler_signal(obs_get_signal_handler(), _triggerSignal.c_str(),
data);
calldata_destroy(data);
SetMacroAbortWait(false);
WaitForCompletion();
return _triggerResult;
}
void MacroSegmentScript::CompletionSignalReceived(void *param, calldata_t *data)
{
auto segment = static_cast<MacroSegmentScript *>(param);
long long int id;
if (!calldata_get_int(data, GeCompletionIdParamName().data(), &id)) {
blog(LOG_WARNING,
"received completion signal without \"%s\" parameter",
GeCompletionIdParamName().data());
return;
}
bool result;
if (!calldata_get_bool(data, GeResultSignalParamName().data(),
&result)) {
blog(LOG_WARNING,
"received completion signal without \"%s\" parameter",
GeResultSignalParamName().data());
return;
}
if (id != segment->_completionId) {
return;
}
segment->_triggerIsComplete = true;
segment->_triggerResult = result;
}
obs_properties_t *MacroSegmentScriptEdit::GetProperties(void *obj)
{
auto segmentEdit = reinterpret_cast<MacroSegmentScriptEdit *>(obj);
if (!segmentEdit) {
return nullptr;
}
return segmentEdit->_entryData->GetProperties();
}
void MacroSegmentScriptEdit::UpdateSettings(void *obj, obs_data_t *settings)
{
auto segmentEdit = reinterpret_cast<MacroSegmentScriptEdit *>(obj);
if (!segmentEdit || segmentEdit->_loading || !segmentEdit->_entryData) {
return;
}
auto lock = LockContext();
segmentEdit->_entryData->UpdateSettings(settings);
}
MacroSegmentScriptEdit::MacroSegmentScriptEdit(
QWidget *parent, std::shared_ptr<MacroSegmentScript> entryData)
: QWidget(parent),
_timeout(new DurationSelection(this))
{
QWidget::connect(_timeout, &DurationSelection::DurationChanged, this,
&MacroSegmentScriptEdit::TimeoutChanged);
auto timeoutLayout = new QHBoxLayout();
PlaceWidgets(obs_module_text("AdvSceneSwitcher.script.timeout"),
timeoutLayout, {{"{{timeout}}", _timeout}});
auto layout = new QVBoxLayout();
auto properties = entryData->GetProperties();
if (!!properties) {
obs_properties_destroy(properties);
// We need a separate OBSData object here as we can't risk
// entryData->_settings being modified while it is currently used
OBSDataAutoRelease data = obs_data_create();
obs_data_apply(data, entryData->GetSettings());
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(30, 0, 0)
auto propertiesView =
new OBSPropertiesView(data.Get(), this, GetProperties,
nullptr, UpdateSettings);
layout->addWidget(propertiesView);
connect(propertiesView, &OBSPropertiesView::PropertiesResized,
this, [this]() {
adjustSize();
updateGeometry();
});
#else
layout->addWidget(new QLabel(
"Displaying script properties not supported when compiled for OBS 29!"));
#endif
}
layout->addLayout(timeoutLayout);
setLayout(layout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
adjustSize();
updateGeometry();
}
void MacroSegmentScriptEdit::UpdateEntryData()
{
_timeout->SetDuration(_entryData->_timeout);
}
QWidget *MacroSegmentScriptEdit::Create(QWidget *parent,
std::shared_ptr<MacroAction> segment)
{
return new MacroSegmentScriptEdit(
parent, std::dynamic_pointer_cast<MacroSegmentScript>(segment));
}
QWidget *MacroSegmentScriptEdit::Create(QWidget *parent,
std::shared_ptr<MacroCondition> segment)
{
return new MacroSegmentScriptEdit(
parent, std::dynamic_pointer_cast<MacroSegmentScript>(segment));
}
void MacroSegmentScriptEdit::TimeoutChanged(const Duration &timeout)
{
GUARD_LOADING_AND_LOCK();
_entryData->_timeout = timeout;
}
} // namespace advss

View File

@ -0,0 +1,78 @@
#pragma once
#include "duration-control.hpp"
#include "macro-script-handler.hpp"
#include <atomic>
#include <obs-data.h>
namespace advss {
class Macro;
class MacroAction;
class MacroCondition;
class MacroSegmentScript {
public:
MacroSegmentScript(obs_data_t *defaultSettings,
const std::string &propertiesSignalName,
const std::string &triggerSignal,
const std::string &completionSignal);
MacroSegmentScript(const advss::MacroSegmentScript &);
protected:
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
obs_properties_t *GetProperties() const;
OBSData GetSettings() const { return _settings.Get(); }
void UpdateSettings(obs_data_t *newSettings) const;
bool SendTriggerSignal();
double GetTimeoutSeconds() const { return _timeout.Seconds(); };
bool TriggerIsCompleted() const { return _triggerIsComplete; }
private:
virtual void WaitForCompletion() const = 0;
static void CompletionSignalReceived(void *param, calldata_t *data);
OBSDataAutoRelease _settings;
std::string _propertiesSignal = "";
std::string _triggerSignal = "";
std::string _completionSignal = "";
std::atomic_bool _triggerIsComplete = {false};
bool _triggerResult = false;
int64_t _completionId = 0;
Duration _timeout = Duration(10.0);
friend class MacroSegmentScriptEdit;
};
class MacroSegmentScriptEdit : public QWidget {
Q_OBJECT
public:
MacroSegmentScriptEdit(
QWidget *parent,
std::shared_ptr<MacroSegmentScript> entryData = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> segment);
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> segment);
private slots:
void TimeoutChanged(const Duration &);
private:
static obs_properties_t *GetProperties(void *obj);
static void UpdateSettings(void *obj, obs_data_t *settings);
DurationSelection *_timeout;
std::shared_ptr<MacroSegmentScript> _entryData;
bool _loading = true;
};
} // namespace advss

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,264 @@
#pragma once
#include <obs-data.h>
#include <obs.hpp>
#include <qtimer.h>
#include <QPointer>
#include <vector>
#include <memory>
#include <QScrollArea>
#include <QPlainTextEdit>
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(30, 0, 0)
class QFormLayout;
class QLabel;
class QResizeEvent;
typedef obs_properties_t *(*PropertiesReloadCallback)(void *obj);
typedef void (*PropertiesUpdateCallback)(void *obj, obs_data_t *old_settings,
obs_data_t *new_settings);
typedef void (*PropertiesVisualUpdateCb)(void *obj, obs_data_t *settings);
namespace advss {
class OBSPropertiesView;
/* ------------------------------------------------------------------------- */
class VScrollArea : public QScrollArea {
Q_OBJECT
public:
VScrollArea(QWidget *parent = nullptr);
protected:
virtual void resizeEvent(QResizeEvent *event) override;
};
/* ------------------------------------------------------------------------- */
class SpinBoxIgnoreScroll : public QSpinBox {
Q_OBJECT
public:
SpinBoxIgnoreScroll(QWidget *parent = nullptr);
protected:
virtual void wheelEvent(QWheelEvent *event) override;
};
/* ------------------------------------------------------------------------- */
class OBSPlainTextEdit : public QPlainTextEdit {
Q_OBJECT
public:
explicit OBSPlainTextEdit(QWidget *parent = nullptr,
bool monospace = true);
};
/* ------------------------------------------------------------------------- */
class WidgetInfo : public QObject {
Q_OBJECT
friend class OBSPropertiesView;
private:
OBSPropertiesView *view;
obs_property_t *property;
QWidget *widget;
QPointer<QTimer> update_timer;
bool recently_updated = false;
OBSData old_settings_cache;
void BoolChanged(const char *setting);
void IntChanged(const char *setting);
void FloatChanged(const char *setting);
void TextChanged(const char *setting);
bool PathChanged(const char *setting);
void ListChanged(const char *setting);
bool ColorChangedInternal(const char *setting, bool supportAlpha);
bool ColorChanged(const char *setting);
bool ColorAlphaChanged(const char *setting);
bool FontChanged(const char *setting);
void GroupChanged(const char *setting);
void EditableListChanged();
void ButtonClicked();
void TogglePasswordText(bool checked);
public:
inline WidgetInfo(OBSPropertiesView *view_, obs_property_t *prop,
QWidget *widget_)
: view(view_),
property(prop),
widget(widget_)
{
}
~WidgetInfo()
{
if (update_timer) {
update_timer->stop();
QMetaObject::invokeMethod(update_timer, "timeout");
update_timer->deleteLater();
}
}
public slots:
void ControlChanged();
/* editable list */
void EditListAdd();
void EditListAddText();
void EditListAddFiles();
void EditListAddDir();
void EditListRemove();
void EditListEdit();
void EditListUp();
void EditListDown();
};
/* ------------------------------------------------------------------------- */
class OBSPropertiesView : public VScrollArea {
Q_OBJECT
friend class WidgetInfo;
using properties_delete_t = decltype(&obs_properties_destroy);
using properties_t =
std::unique_ptr<obs_properties_t, properties_delete_t>;
private:
QWidget *widget = nullptr;
properties_t properties;
OBSData settings;
OBSWeakObjectAutoRelease weakObj;
void *rawObj = nullptr;
std::string type;
PropertiesReloadCallback reloadCallback;
PropertiesUpdateCallback callback = nullptr;
PropertiesVisualUpdateCb visUpdateCb = nullptr;
int minSize;
std::vector<std::unique_ptr<WidgetInfo>> children;
std::string lastFocused;
QWidget *lastWidget = nullptr;
bool deferUpdate;
bool enableDefer = true;
template<typename Sender, typename SenderParent, typename... Args>
QWidget *NewWidget(obs_property_t *prop, Sender *widget,
void (SenderParent::*signal)(Args...));
QWidget *AddCheckbox(obs_property_t *prop);
QWidget *AddText(obs_property_t *prop, QFormLayout *layout,
QLabel *&label);
void AddPath(obs_property_t *prop, QFormLayout *layout, QLabel **label);
void AddInt(obs_property_t *prop, QFormLayout *layout, QLabel **label);
void AddFloat(obs_property_t *prop, QFormLayout *layout,
QLabel **label);
QWidget *AddList(obs_property_t *prop, bool &warning);
void AddEditableList(obs_property_t *prop, QFormLayout *layout,
QLabel *&label);
QWidget *AddButton(obs_property_t *prop);
void AddColorInternal(obs_property_t *prop, QFormLayout *layout,
QLabel *&label, bool supportAlpha);
void AddColor(obs_property_t *prop, QFormLayout *layout,
QLabel *&label);
void AddColorAlpha(obs_property_t *prop, QFormLayout *layout,
QLabel *&label);
void AddFont(obs_property_t *prop, QFormLayout *layout, QLabel *&label);
void AddFrameRate(obs_property_t *prop, bool &warning,
QFormLayout *layout, QLabel *&label);
void AddGroup(obs_property_t *prop, QFormLayout *layout);
void AddProperty(obs_property_t *property, QFormLayout *layout);
void resizeEvent(QResizeEvent *event) override;
void GetScrollPos(int &h, int &v, int &hend, int &vend);
void SetScrollPos(int h, int v, int old_hend, int old_vend);
private slots:
void RefreshProperties();
public slots:
void ReloadProperties();
void SignalChanged();
signals:
void PropertiesResized();
void Changed();
void PropertiesRefreshed();
public:
OBSPropertiesView(OBSData settings, obs_object_t *obj,
PropertiesReloadCallback reloadCallback,
PropertiesUpdateCallback callback,
PropertiesVisualUpdateCb cb = nullptr,
int minSize = 0);
OBSPropertiesView(OBSData settings, void *obj,
PropertiesReloadCallback reloadCallback,
PropertiesUpdateCallback callback,
PropertiesVisualUpdateCb cb = nullptr,
int minSize = 0);
OBSPropertiesView(OBSData settings, const char *type,
PropertiesReloadCallback reloadCallback,
int minSize = 0);
#define obj_constructor(type) \
inline OBSPropertiesView(OBSData settings, obs_##type##_t *type, \
PropertiesReloadCallback reloadCallback, \
PropertiesUpdateCallback callback, \
PropertiesVisualUpdateCb cb = nullptr, \
int minSize = 0) \
: OBSPropertiesView(settings, (obs_object_t *)type, \
reloadCallback, callback, cb, minSize) \
{ \
}
obj_constructor(source);
obj_constructor(output);
obj_constructor(encoder);
obj_constructor(service);
#undef obj_constructor
inline obs_data_t *GetSettings() const { return settings; }
inline void UpdateSettings()
{
if (callback)
callback(OBSGetStrongRef(weakObj), nullptr, settings);
else if (visUpdateCb)
visUpdateCb(OBSGetStrongRef(weakObj), settings);
}
inline bool DeferUpdate() const { return deferUpdate; }
inline void SetDeferrable(bool deferrable) { enableDefer = deferrable; }
inline OBSObject GetObject() const { return OBSGetStrongRef(weakObj); }
#define Def_IsObject(type) \
inline bool IsObject(obs_##type##_t *type) const \
{ \
OBSObject obj = OBSGetStrongRef(weakObj); \
return obj.Get() == (obs_object_t *)type; \
}
/* clang-format off */
Def_IsObject(source)
Def_IsObject(output)
Def_IsObject(encoder)
Def_IsObject(service)
/* clang-format on */
#undef Def_IsObject
};
} // namespace advss
#endif

View File

@ -0,0 +1,63 @@
#pragma once
#include <QComboBox>
#include <QLabel>
#include <QSpinBox>
#include <QStackedWidget>
#include <QWidget>
#include <obs.h>
#include <media-io/frame-rate.h>
#include <vector>
#ifdef _MSC_VER
#pragma warning(disable : 4505)
#endif
static bool operator!=(const media_frames_per_second &a,
const media_frames_per_second &b)
{
return a.numerator != b.numerator || a.denominator != b.denominator;
}
static bool operator==(const media_frames_per_second &a,
const media_frames_per_second &b)
{
return !(a != b);
}
using frame_rate_range_t =
std::pair<media_frames_per_second, media_frames_per_second>;
using frame_rate_ranges_t = std::vector<frame_rate_range_t>;
class OBSFrameRatePropertyWidget : public QWidget {
Q_OBJECT
public:
frame_rate_ranges_t fps_ranges;
QComboBox *modeSelect = nullptr;
QStackedWidget *modeDisplay = nullptr;
QWidget *labels = nullptr;
QLabel *currentFPS = nullptr;
QLabel *timePerFrame = nullptr;
QLabel *minLabel = nullptr;
QLabel *maxLabel = nullptr;
QComboBox *simpleFPS = nullptr;
QComboBox *fpsRange = nullptr;
QSpinBox *numEdit = nullptr;
QSpinBox *denEdit = nullptr;
bool updating = false;
const char *name = nullptr;
obs_data_t *settings = nullptr;
QLabel *warningLabel = nullptr;
OBSFrameRatePropertyWidget() = default;
};

283
scripting/examples.lua Normal file
View File

@ -0,0 +1,283 @@
-------------------------------------------------------------------------------
-- Since lua does not support threads natively, you will have to be careful to
-- to not block the main OBS script thread with long running actions or
-- conditions.
--
-- Also note that due to this limitation only one instance of the actions and
-- conditions defined in this script can be executed at a time.
-- If multiple actions or conditions would have been executed at the same time,
-- they will be executed sequentially instead.
--
-- Consider switching to python instead, if those limitations are a problem.
-------------------------------------------------------------------------------
obs = obslua
-------------------------------------------------------------------------------
-- Simple action callback example
function my_lua_action(data)
obs.script_log(obs.LOG_WARNING, "hello from lua!")
end
-------------------------------------------------------------------------------
-- Action showcasing how to provide configurable settings
function get_action_properties()
local props = obs.obs_properties_create()
obs.obs_properties_add_text(props, "name", "Name", obs.OBS_TEXT_DEFAULT)
return props
end
function get_action_defaults()
local default_settings = obs.obs_data_create()
obs.obs_data_set_default_string(default_settings, "name", "John")
return default_settings
end
function my_lua_settings_action(data)
local name = obs.obs_data_get_string(data, "name")
obs.script_log(obs.LOG_WARNING, string.format("hello %s from lua", name))
end
-------------------------------------------------------------------------------
-- Action callback demonstrating the interacting with variables
counter = 0
function variable_lua_action(data)
local value = advss_get_variable_value("variable")
if value ~= nil then
obs.script_log(obs.LOG_WARNING, string.format("variable has value: %s", value))
end
counter = counter + 1
advss_set_variable_value("variable", counter)
end
-------------------------------------------------------------------------------
-- Example condition randomly returning true or false based on user configured
-- probability value
math.randomseed(os.time())
function get_condition_properties()
local props = obs.obs_properties_create()
obs.obs_properties_add_float(props, "probability", "Probability of returning true", 0, 100, 0.1)
return props
end
function get_condition_defaults()
local default_settings = obs.obs_data_create()
obs.obs_data_set_default_double(default_settings, "probability", 33.3)
return default_settings
end
function my_lua_condition(data)
local target = obs.obs_data_get_double(data, "probability")
local value = math.random(0, 100)
return value <= target
end
-------------------------------------------------------------------------------
function script_load(settings)
-- Register an example action
advss_register_action("My simple Lua action", my_lua_action, nil, nil)
-- Register an example action with settings
advss_register_action("My Lua action with settings", my_lua_settings_action, get_action_properties,
get_action_defaults())
-- This example action demonstrates how to interact with variables
advss_register_action("My variable Lua action", variable_lua_action, nil, nil)
-- Register an example condition
advss_register_condition("My Lua condition", my_lua_condition, get_condition_properties, get_condition_defaults())
end
function script_unload()
-- Deregistering is useful if you plan on reloading the script files
advss_deregister_action("My simple Lua action")
advss_deregister_action("My Lua action with settings")
advss_deregister_action("My variable Lua action")
advss_deregister_condition("My Lua condition")
end
-------------------------------------------------------------------------------
-- Advanced Scene Switcher helper functions below:
-- Usually you should not have to modify this code.
-- Simply copy paste it into your scripts.
-------------------------------------------------------------------------------
-- Actions
-------------------------------------------------------------------------------
-- The advss_register_action() function is used to register custom actions
-- It takes the following arguments:
-- 1. The name of the new action type.
-- 2. The function callback which should run when the action is executed.
-- 3. The optional function callback which return the properties to display the
-- settings of this action type.
-- 4. The optional default_settings pointer used to set the default settings of
-- newly created actions.
-- The pointer must not be freed within this script.
function advss_register_action(name, callback, get_properties, default_settings)
advss_register_segment_type(true, name, callback, get_properties, default_settings)
end
function advss_deregister_action(name)
advss_deregister_segment(true, name)
end
-------------------------------------------------------------------------------
-- Conditions
-------------------------------------------------------------------------------
-- The advss_register_condition() function is used to register custom conditions
-- It takes the following arguments:
-- 1. The name of the new condition type.
-- 2. The function callback which should run when the condition is executed.
-- 3. The optional function callback which return the properties to display the
-- settings of this condition type.
-- 4. The optional default_settings pointer used to set the default settings of
-- newly created condition.
-- The pointer must not be freed within this script.
function advss_register_condition(name, callback, get_properties, default_settings)
advss_register_segment_type(false, name, callback, get_properties, default_settings)
end
function advss_deregister_condition(name)
advss_deregister_segment(false, name)
end
-------------------------------------------------------------------------------
-- (De)register helpers
-------------------------------------------------------------------------------
function advss_register_segment_type(is_action, name, callback, get_properties, default_settings)
local proc_handler = obs.obs_get_proc_handler()
local data = obs.calldata_create()
obs.calldata_set_string(data, "name", name)
obs.calldata_set_ptr(data, "default_settings", default_settings)
local register_proc = is_action and "advss_register_script_action" or "advss_register_script_condition"
obs.proc_handler_call(proc_handler, register_proc, data)
local success = obs.calldata_bool(data, "success")
if success == false then
local segment_type = is_action and "action" or "condition"
obs.script_log(obs.LOG_WARNING, string.format("failed to register custom %s \"%s\"", segment_type, name))
obs.calldata_destroy(data)
return
end
-- Lua does not support threads natively.
-- So, we will call the provided callback function directly.
local run_helper = (function(data)
local completion_signal_name = obs.calldata_string(data, "completion_signal_name")
local id = obs.calldata_int(data, "completion_id")
local settings = obs.obs_data_create_from_json(obs.calldata_string(data, "settings"))
local callback_result = callback(settings)
if is_action then
callback_result = true
end
obs.obs_data_release(settings)
local reply_data = obs.calldata_create()
obs.calldata_set_int(reply_data, "completion_id", id)
obs.calldata_set_bool(reply_data, "result", callback_result)
local signal_handler = obs.obs_get_signal_handler()
obs.signal_handler_signal(signal_handler, completion_signal_name, reply_data)
obs.calldata_destroy(reply_data)
end)
local properties_helper = (function(data)
if get_properties ~= nil then
obs.calldata_set_ptr(data, "properties", get_properties())
else
obs.calldata_set_ptr(data, "properties", nil)
end
end)
local signal_name = obs.calldata_string(data, "trigger_signal_name")
local property_signal_name = obs.calldata_string(data, "properties_signal_name")
local signal_handler = obs.obs_get_signal_handler()
obs.signal_handler_connect(signal_handler, signal_name, run_helper)
obs.signal_handler_connect(signal_handler, property_signal_name, properties_helper)
obs.calldata_destroy(data)
end
function advss_deregister_segment(is_action, name)
local proc_handler = obs.obs_get_proc_handler()
local data = obs.calldata_create()
obs.calldata_set_string(data, "name", name)
local deregister_proc = is_action and "advss_deregister_script_action" or "advss_deregister_script_condition"
obs.proc_handler_call(proc_handler, deregister_proc, data)
local success = obs.calldata_bool(data, "success")
if success == false then
local segment_type = is_action and "action" or "condition"
obs.script_log(obs.LOG_WARNING, string.format("failed to deregister custom %s \"%s\"", segment_type, name))
end
obs.calldata_destroy(data)
end
-------------------------------------------------------------------------------
-- Variables
-------------------------------------------------------------------------------
-- The advss_get_variable_value() function can be used to query the value of a
-- variable with a given name.
-- nil is returned in case the variable does not exist.
function advss_get_variable_value(name)
local proc_handler = obs.obs_get_proc_handler()
local data = obs.calldata_create()
obs.calldata_set_string(data, "name", name)
obs.proc_handler_call(proc_handler, "advss_get_variable_value", data)
local success = obs.calldata_bool(data, "success")
if success == false then
obs.script_log(obs.LOG_WARNING, string.format("failed to get value for variable \"%s\"", name))
obs.calldata_destroy(data)
return nil
end
local value = obs.calldata_string(data, "value")
obs.calldata_destroy(data)
return value
end
-- The advss_set_variable_value() function can be used to set the value of a
-- variable with a given name.
-- True is returned if the operation was successful.
function advss_set_variable_value(name, value)
local proc_handler = obs.obs_get_proc_handler()
local data = obs.calldata_create()
obs.calldata_set_string(data, "name", name)
obs.calldata_set_string(data, "value", value)
obs.proc_handler_call(proc_handler, "advss_set_variable_value", data)
local success = obs.calldata_bool(data, "success")
if success == false then
obs.script_log(obs.LOG_WARNING, string.format("failed to set value for variable \"%s\"", name))
end
obs.calldata_destroy(data)
return success
end

305
scripting/examples.py Normal file
View File

@ -0,0 +1,305 @@
import obspython as obs
import threading # Required by advss helpers
import random # Required for example condition
###############################################################################
# Simple action callback example
def my_python_action(data):
obs.script_log(obs.LOG_WARNING, "hello from python!")
###############################################################################
# Action showcasing how to provide configurable settings
def get_action_properties():
props = obs.obs_properties_create()
obs.obs_properties_add_text(props, "name", "Name", obs.OBS_TEXT_DEFAULT)
return props
def get_action_defaults():
default_settings = obs.obs_data_create()
obs.obs_data_set_default_string(default_settings, "name", "John")
return default_settings
def my_python_settings_action(data):
name = obs.obs_data_get_string(data, "name")
obs.script_log(obs.LOG_WARNING, f"hello {name} from python!")
###############################################################################
# Action callback demonstrating the interacting with variables
counter = 0
def variable_python_action(data):
value = advss_get_variable_value("variable")
if value is not None:
obs.script_log(obs.LOG_WARNING, f"variable has value: {value}")
global counter
counter += 1
advss_set_variable_value("variable", str(counter))
###############################################################################
# Example condition randomly returning true or false based on user configured
# probability value
def get_condition_properties():
props = obs.obs_properties_create()
obs.obs_properties_add_float(
props, "probability", "Probability of returning true", 0, 100, 0.1
)
return props
def get_condition_defaults():
default_settings = obs.obs_data_create()
obs.obs_data_set_default_double(default_settings, "probability", 33.3)
return default_settings
def my_python_condition(data):
target = obs.obs_data_get_double(data, "probability")
value = random.uniform(0, 100)
return value <= target
###############################################################################
def script_load(settings):
# Register an example action
advss_register_action("My simple Python action", my_python_action)
# Register an example action with settings
advss_register_action(
"My Python action with settings",
my_python_settings_action,
get_action_properties,
get_action_defaults(),
)
# This example action demonstrates how to interact with variables
advss_register_action("My variable Python action", variable_python_action)
# Register an example condition
advss_register_condition(
"My Python condition",
my_python_condition,
get_condition_properties,
get_condition_defaults(),
)
def script_unload():
# Deregistering is useful if you plan on reloading the script files
advss_deregister_action("My simple Python action")
advss_deregister_action("My Python action with settings")
advss_deregister_action("My variable Python action")
advss_deregister_condition("My Python condition")
###############################################################################
# Advanced Scene Switcher helper functions below:
# Usually you should not have to modify this code.
# Simply copy paste it into your scripts.
###############################################################################
# Actions
###############################################################################
# The advss_register_action() function is used to register custom actions
# It takes the following arguments:
# 1. The name of the new action type.
# 2. The function callback which should run when the action is executed.
# 3. The optional function callback which return the properties to display the
# settings of this action type.
# 4. The optional default_settings pointer used to set the default settings of
# newly created actions.
# The pointer must not be freed within this script.
def advss_register_action(name, callback, get_properties=None, default_settings=None):
advss_register_segment_type(True, name, callback, get_properties, default_settings)
def advss_deregister_action(name):
advss_deregister_segment(True, name)
###############################################################################
# Conditions
###############################################################################
# The advss_register_condition() function is used to register custom conditions
# It takes the following arguments:
# 1. The name of the new condition type.
# 2. The function callback which should run when the condition is executed.
# 3. The optional function callback which return the properties to display the
# settings of this condition type.
# 4. The optional default_settings pointer used to set the default settings of
# newly created condition.
# The pointer must not be freed within this script.
def advss_register_condition(
name, callback, get_properties=None, default_settings=None
):
advss_register_segment_type(False, name, callback, get_properties, default_settings)
def advss_deregister_condition(name):
advss_deregister_segment(False, name)
###############################################################################
# (De)register helpers
###############################################################################
def advss_register_segment_type(
is_action, name, callback, get_properties, default_settings
):
proc_handler = obs.obs_get_proc_handler()
data = obs.calldata_create()
obs.calldata_set_string(data, "name", name)
obs.calldata_set_ptr(data, "default_settings", default_settings)
register_proc = (
"advss_register_script_action"
if is_action
else "advss_register_script_condition"
)
obs.proc_handler_call(proc_handler, register_proc, data)
success = obs.calldata_bool(data, "success")
if success == False:
segment_type = "action" if is_action else "condition"
log_msg = f'failed to register custom {segment_type} "{name}"'
obs.script_log(obs.LOG_WARNING, log_msg)
obs.calldata_destroy(data)
return
# Run in separate thread to avoid blocking main OBS signal handler.
# Operation completion will be indicated via signal completion_signal_name.
def run_helper(data):
completion_signal_name = obs.calldata_string(data, "completion_signal_name")
id = obs.calldata_int(data, "completion_id")
def thread_func(settings):
settings = obs.obs_data_create_from_json(
obs.calldata_string(data, "settings")
)
callback_result = callback(settings)
if is_action:
callback_result = True
reply_data = obs.calldata_create()
obs.calldata_set_int(reply_data, "completion_id", id)
obs.calldata_set_bool(reply_data, "result", callback_result)
signal_handler = obs.obs_get_signal_handler()
obs.signal_handler_signal(
signal_handler, completion_signal_name, reply_data
)
obs.obs_data_release(settings)
obs.calldata_destroy(reply_data)
threading.Thread(target=thread_func, args={data}).start()
def properties_helper(data):
if get_properties is not None:
properties = get_properties()
else:
properties = None
obs.calldata_set_ptr(data, "properties", properties)
trigger_signal_name = obs.calldata_string(data, "trigger_signal_name")
property_signal_name = obs.calldata_string(data, "properties_signal_name")
signal_handler = obs.obs_get_signal_handler()
obs.signal_handler_connect(signal_handler, trigger_signal_name, run_helper)
obs.signal_handler_connect(signal_handler, property_signal_name, properties_helper)
obs.calldata_destroy(data)
def advss_deregister_segment(is_action, name):
proc_handler = obs.obs_get_proc_handler()
data = obs.calldata_create()
obs.calldata_set_string(data, "name", name)
deregister_proc = (
"advss_deregister_script_action"
if is_action
else "advss_deregister_script_condition"
)
obs.proc_handler_call(proc_handler, deregister_proc, data)
success = obs.calldata_bool(data, "success")
if success == False:
segment_type = "action" if is_action else "condition"
log_msg = f'failed to deregister custom {segment_type} "{name}"'
obs.script_log(obs.LOG_WARNING, log_msg)
obs.calldata_destroy(data)
###############################################################################
# Variables
###############################################################################
# The advss_get_variable_value() function can be used to query the value of a
# variable with a given name.
# None is returned in case the variable does not exist.
def advss_get_variable_value(name):
proc_handler = obs.obs_get_proc_handler()
data = obs.calldata_create()
obs.calldata_set_string(data, "name", name)
obs.proc_handler_call(proc_handler, "advss_get_variable_value", data)
success = obs.calldata_bool(data, "success")
if success == False:
obs.script_log(obs.LOG_WARNING, f'failed to get value for variable "{name}"')
obs.calldata_destroy(data)
return None
value = obs.calldata_string(data, "value")
obs.calldata_destroy(data)
return value
# The advss_set_variable_value() function can be used to set the value of a
# variable with a given name.
# True is returned if the operation was successful.
def advss_set_variable_value(name, value):
proc_handler = obs.obs_get_proc_handler()
data = obs.calldata_create()
obs.calldata_set_string(data, "name", name)
obs.calldata_set_string(data, "value", value)
obs.proc_handler_call(proc_handler, "advss_set_variable_value", data)
success = obs.calldata_bool(data, "success")
if success == False:
obs.script_log(obs.LOG_WARNING, f'failed to set value for variable "{name}"')
obs.calldata_destroy(data)
return success