From c52166bfece009d80b89c7d8943ec537721808ec Mon Sep 17 00:00:00 2001 From: WarmUpTill Date: Tue, 30 May 2023 13:14:19 +0200 Subject: [PATCH] Add option to trigger OBS hotkeys directly Main use case would be to interact with other plugins which offer hotkeys. The benefit of this method is that it does not require a key to be bound to the particular hotkey to trigger the functionality. --- data/locale/en-US.ini | 11 +- src/macro-core/macro-action-hotkey.cpp | 358 +++++++++++++++++++++++-- src/macro-core/macro-action-hotkey.hpp | 26 +- 3 files changed, 362 insertions(+), 33 deletions(-) diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index f1fe89ba..93502dc1 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -554,6 +554,13 @@ AdvSceneSwitcher.action.virtualCamera.type.stop="Stop virtual camera" AdvSceneSwitcher.action.virtualCamera.type.start="Start virtual camera" AdvSceneSwitcher.action.virtualCamera.entry="{{actions}}" AdvSceneSwitcher.action.hotkey="Hotkey" +AdvSceneSwitcher.action.hotkey.type.obs="OBS hotkey" +AdvSceneSwitcher.action.hotkey.type.obs.type.frontend="Frontend" +AdvSceneSwitcher.action.hotkey.type.obs.type.soruce="Source" +AdvSceneSwitcher.action.hotkey.type.obs.type.output="Output" +AdvSceneSwitcher.action.hotkey.type.obs.type.encoder="Encoder" +AdvSceneSwitcher.action.hotkey.type.obs.type.service="Service" +AdvSceneSwitcher.action.hotkey.type.custom="Custom hotkey" AdvSceneSwitcher.action.hotkey.leftShift="Left Shift" AdvSceneSwitcher.action.hotkey.rightShift="Right Shift" AdvSceneSwitcher.action.hotkey.leftCtrl="Left Ctrl" @@ -564,7 +571,8 @@ AdvSceneSwitcher.action.hotkey.leftMeta="Left Meta" AdvSceneSwitcher.action.hotkey.rightMeta="Right Meta" AdvSceneSwitcher.action.hotkey.onlyOBS="Send keypress only to OBS" AdvSceneSwitcher.action.hotkey.disabled="Cannot simulate global key presses - functionality limited to sending key press to OBS!" -AdvSceneSwitcher.action.hotkey.entry="Press {{keys}} for {{duration}}" +AdvSceneSwitcher.action.hotkey.entry.custom="Press{{actionType}}{{keys}}for{{duration}}" +AdvSceneSwitcher.action.hotkey.entry.obs="Press{{actionType}}{{hotkeyType}}{{obsHotkeys}}" AdvSceneSwitcher.action.sceneOrder="Scene item order" AdvSceneSwitcher.action.sceneOrder.type.moveUp="Move up" AdvSceneSwitcher.action.sceneOrder.type.moveDown="Move down" @@ -1059,6 +1067,7 @@ AdvSceneSwitcher.selectSceneCollection="--select scene collection--" AdvSceneSwitcher.enterPath="--enter path--" AdvSceneSwitcher.enterText="--enter text--" AdvSceneSwitcher.enterURL="--enter URL--" +AdvSceneSwitcher.selectHotkey="--select hotkey--" AdvSceneSwitcher.invaildEntriesWillNotBeSaved="invalid entries will not be saved" AdvSceneSwitcher.selectWindowTip="Use \"OBS\" to specify OBS window\nUse \"Task Switching\"to specify ALT + TAB" AdvSceneSwitcher.sceneItemSelection.all="All" diff --git a/src/macro-core/macro-action-hotkey.cpp b/src/macro-core/macro-action-hotkey.cpp index c8d15c55..ff8d45f3 100644 --- a/src/macro-core/macro-action-hotkey.cpp +++ b/src/macro-core/macro-action-hotkey.cpp @@ -13,6 +13,26 @@ bool MacroActionHotkey::_registered = MacroActionFactory::Register( {MacroActionHotkey::Create, MacroActionHotkeyEdit::Create, "AdvSceneSwitcher.action.hotkey"}); +const static std::map actionTypes = { + {MacroActionHotkey::Action::OBS_HOTKEY, + "AdvSceneSwitcher.action.hotkey.type.obs"}, + {MacroActionHotkey::Action::CUSTOM, + "AdvSceneSwitcher.action.hotkey.type.custom"}, +}; + +const static std::map hotkeyTypes = { + {OBS_HOTKEY_REGISTERER_FRONTEND, + "AdvSceneSwitcher.action.hotkey.type.obs.type.frontend"}, + {OBS_HOTKEY_REGISTERER_SOURCE, + "AdvSceneSwitcher.action.hotkey.type.obs.type.soruce"}, + {OBS_HOTKEY_REGISTERER_OUTPUT, + "AdvSceneSwitcher.action.hotkey.type.obs.type.output"}, + {OBS_HOTKEY_REGISTERER_ENCODER, + "AdvSceneSwitcher.action.hotkey.type.obs.type.encoder"}, + {OBS_HOTKEY_REGISTERER_SERVICE, + "AdvSceneSwitcher.action.hotkey.type.obs.type.service"}, +}; + static std::unordered_map keyTable = { // Chars {HotkeyType::Key_A, OBS_KEY_A}, @@ -196,7 +216,92 @@ static void InjectKeys(const std::vector &keys, int duration) obs_hotkey_inject_event(combo, false); } -bool MacroActionHotkey::PerformAction() +static void addNamePrefix(std::string &name, obs_hotkey_t *hotkey) +{ + const auto type = obs_hotkey_get_registerer_type(hotkey); + std::string prefix; + if (type == OBS_HOTKEY_REGISTERER_SOURCE) { + auto source = reinterpret_cast( + obs_hotkey_get_registerer(hotkey)); + prefix = "[" + GetWeakSourceName(source) + "] "; + } else if (type == OBS_HOTKEY_REGISTERER_OUTPUT) { + auto weakOutput = reinterpret_cast( + obs_hotkey_get_registerer(hotkey)); + std::string name; + auto output = obs_weak_output_get_output(weakOutput); + if (output) { + name = obs_output_get_name(output); + obs_output_release(output); + } + prefix = "[" + name + "] "; + } else if (type == OBS_HOTKEY_REGISTERER_ENCODER) { + auto weakEncoder = reinterpret_cast( + obs_hotkey_get_registerer(hotkey)); + std::string name; + auto encoder = obs_weak_encoder_get_encoder(weakEncoder); + if (encoder) { + name = obs_encoder_get_name(encoder); + obs_encoder_release(encoder); + } + prefix = "[" + name + "] "; + } else if (type == OBS_HOTKEY_REGISTERER_SERVICE) { + auto weakService = reinterpret_cast( + obs_hotkey_get_registerer(hotkey)); + std::string name; + auto service = obs_weak_service_get_service(weakService); + if (service) { + name = obs_service_get_name(service); + obs_service_release(service); + } + prefix = "[" + name + "] "; + } + name = prefix + name; +} + +static obs_hotkey_id getHotkeyIdByName(const std::string &name) +{ + struct Parameters { + std::string name; + obs_hotkey_id id = OBS_INVALID_HOTKEY_ID; + } params; + + auto func = [](void *param, obs_hotkey_id id, obs_hotkey_t *hotkey) { + auto params = static_cast(param); + std::string name = obs_hotkey_get_name(hotkey); + addNamePrefix(name, hotkey); + if (name == params->name) { + params->id = id; + return false; + } + return true; + }; + + params.name = name; + obs_enum_hotkeys(func, ¶ms); + return params.id; +} + +void MacroActionHotkey::SendOBSHotkey() +{ + auto id = getHotkeyIdByName(_hotkeyName); + if (id == OBS_INVALID_HOTKEY_ID) { + blog(LOG_WARNING, + "failed to get hotkey id for \"%s\" - key will not be pressed", + _hotkeyName.c_str()); + return; + } + obs_queue_task( + OBS_TASK_UI, + [](void *param) { + auto id = (size_t *)param; + obs_hotkey_trigger_routed_callback(*id, false); + obs_hotkey_trigger_routed_callback(*id, true); + obs_hotkey_trigger_routed_callback(*id, false); + }, + &id, true); +} + +void MacroActionHotkey::SendCustomHotkey() { std::vector keys; if (_leftShift) { @@ -237,18 +342,35 @@ bool MacroActionHotkey::PerformAction() t.detach(); } } +} + +bool MacroActionHotkey::PerformAction() +{ + switch (_action) { + case advss::MacroActionHotkey::Action::OBS_HOTKEY: + SendOBSHotkey(); + break; + case advss::MacroActionHotkey::Action::CUSTOM: + SendCustomHotkey(); + break; + default: + break; + } return true; } void MacroActionHotkey::LogAction() const { - vblog(LOG_INFO, "sent hotkey"); + vblog(LOG_INFO, "sent hotkey type %d", static_cast(_action)); } bool MacroActionHotkey::Save(obs_data_t *obj) const { MacroAction::Save(obj); + obs_data_set_int(obj, "action", static_cast(_action)); + obs_data_set_int(obj, "hotkeyType", _hotkeyType); + obs_data_set_string(obj, "hotkeyName", _hotkeyName.c_str()); obs_data_set_int(obj, "key", static_cast(_key)); obs_data_set_bool(obj, "left_shift", _leftShift); obs_data_set_bool(obj, "right_shift", _rightShift); @@ -260,13 +382,22 @@ bool MacroActionHotkey::Save(obs_data_t *obj) const obs_data_set_bool(obj, "right_meta", _rightMeta); _duration.Save(obj); obs_data_set_bool(obj, "onlyOBS", _onlySendToObs); - obs_data_set_int(obj, "version", 1); + obs_data_set_int(obj, "version", 2); return true; } bool MacroActionHotkey::Load(obs_data_t *obj) { MacroAction::Load(obj); + int version = obs_data_get_int(obj, "version"); + if (version != 2) { + _action = Action::CUSTOM; + } else { + _action = static_cast(obs_data_get_int(obj, "action")); + } + _hotkeyType = static_cast( + obs_data_get_int(obj, "hotkeyType")); + _hotkeyName = obs_data_get_string(obj, "hotkeyName"); _key = static_cast(obs_data_get_int(obj, "key")); _leftShift = obs_data_get_bool(obj, "left_shift"); _rightShift = obs_data_get_bool(obj, "right_shift"); @@ -276,7 +407,7 @@ bool MacroActionHotkey::Load(obs_data_t *obj) _rightAlt = obs_data_get_bool(obj, "right_alt"); _leftMeta = obs_data_get_bool(obj, "left_meta"); _rightMeta = obs_data_get_bool(obj, "right_meta"); - if (!obs_data_has_user_value(obj, "version")) { + if (version == 0) { _duration = obs_data_get_int(obj, "duration") / 1000.0; } else { _duration.Load(obj); @@ -395,9 +526,65 @@ static inline void populateKeySelection(QComboBox *list) list->addItem("NumpadEnter"); } +static void populateActionSelection(QComboBox *list) +{ + for (const auto &[_, value] : actionTypes) { + list->addItem(obs_module_text(value.c_str())); + } +} +static void populateHotkeyTypeSelection(QComboBox *list) +{ + for (const auto &[_, value] : hotkeyTypes) { + list->addItem(obs_module_text(value.c_str())); + } +} + +static void addNamePrefix(QString &name, obs_hotkey_t *hotkey) +{ + auto temp = name.toStdString(); + addNamePrefix(temp, hotkey); + name = QString::fromStdString(temp); +} + +static void populateHotkeyNames(QComboBox *list, + obs_hotkey_registerer_type type) +{ + struct Parameters { + QStringList names; + QStringList descriptions; + obs_hotkey_registerer_type type; + } params; + + auto func = [](void *param, obs_hotkey_id, obs_hotkey_t *hotkey) { + auto params = static_cast(param); + if (obs_hotkey_get_registerer_type(hotkey) == params->type) { + QString description(obs_hotkey_get_description(hotkey)); + addNamePrefix(description, hotkey); + params->descriptions.append(description); + QString name(obs_hotkey_get_name(hotkey)); + addNamePrefix(name, hotkey); + params->names.append(name); + } + return true; + }; + + params.type = type; + obs_enum_hotkeys(func, ¶ms); + + list->clear(); + for (int i = 0; i < params.names.size(); i++) { + list->addItem(params.descriptions.at(i), params.names.at(i)); + } + AddSelectionEntry(list, + obs_module_text("AdvSceneSwitcher.selectHotkey")); +} + MacroActionHotkeyEdit::MacroActionHotkeyEdit( QWidget *parent, std::shared_ptr entryData) : QWidget(parent), + _actionType(new QComboBox()), + _hotkeyType(new QComboBox()), + _obsHotkeys(new QComboBox()), _keys(new QComboBox()), _leftShift(new QCheckBox( obs_module_text("AdvSceneSwitcher.action.hotkey.leftShift"))), @@ -419,10 +606,20 @@ MacroActionHotkeyEdit::MacroActionHotkeyEdit( _onlySendToOBS(new QCheckBox( obs_module_text("AdvSceneSwitcher.action.hotkey.onlyOBS"))), _noKeyPressSimulationWarning(new QLabel( - obs_module_text("AdvSceneSwitcher.action.hotkey.disabled"))) + obs_module_text("AdvSceneSwitcher.action.hotkey.disabled"))), + _entryLayout(new QHBoxLayout()), + _keyConfigLayout(new QHBoxLayout()) { populateKeySelection(_keys); + populateActionSelection(_actionType); + populateHotkeyTypeSelection(_hotkeyType); + QWidget::connect(_actionType, SIGNAL(currentIndexChanged(int)), this, + SLOT(ActionChanged(int))); + QWidget::connect(_hotkeyType, SIGNAL(currentIndexChanged(int)), this, + SLOT(HotkeyTypeChanged(int))); + QWidget::connect(_obsHotkeys, SIGNAL(currentIndexChanged(int)), this, + SLOT(OBSHotkeyChanged(int))); QWidget::connect(_keys, SIGNAL(currentIndexChanged(int)), this, SLOT(KeyChanged(int))); QWidget::connect(_leftShift, SIGNAL(stateChanged(int)), this, @@ -446,28 +643,21 @@ MacroActionHotkeyEdit::MacroActionHotkeyEdit( QWidget::connect(_onlySendToOBS, SIGNAL(stateChanged(int)), this, SLOT(OnlySendToOBSChanged(int))); - QHBoxLayout *line1Layout = new QHBoxLayout; - std::unordered_map widgetPlaceholders = { - {"{{keys}}", _keys}, - {"{{duration}}", _duration}, - }; - PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.hotkey.entry"), - line1Layout, widgetPlaceholders); + _entryLayout->setContentsMargins(0, 0, 0, 0); + _keyConfigLayout->setContentsMargins(0, 0, 0, 0); + _keyConfigLayout->addWidget(_leftShift); + _keyConfigLayout->addWidget(_rightShift); + _keyConfigLayout->addWidget(_leftCtrl); + _keyConfigLayout->addWidget(_rightCtrl); + _keyConfigLayout->addWidget(_leftAlt); + _keyConfigLayout->addWidget(_rightAlt); + _keyConfigLayout->addWidget(_leftMeta); + _keyConfigLayout->addWidget(_rightMeta); + _keyConfigLayout->addStretch(); - QHBoxLayout *line2Layout = new QHBoxLayout; - line2Layout->addWidget(_leftShift); - line2Layout->addWidget(_rightShift); - line2Layout->addWidget(_leftCtrl); - line2Layout->addWidget(_rightCtrl); - line2Layout->addWidget(_leftAlt); - line2Layout->addWidget(_rightAlt); - line2Layout->addWidget(_leftMeta); - line2Layout->addWidget(_rightMeta); - line2Layout->addStretch(); - - QVBoxLayout *mainLayout = new QVBoxLayout; - mainLayout->addLayout(line1Layout); - mainLayout->addLayout(line2Layout); + auto mainLayout = new QVBoxLayout; + mainLayout->addLayout(_entryLayout); + mainLayout->addLayout(_keyConfigLayout); mainLayout->addWidget(_onlySendToOBS); mainLayout->addWidget(_noKeyPressSimulationWarning); setLayout(mainLayout); @@ -479,10 +669,76 @@ MacroActionHotkeyEdit::MacroActionHotkeyEdit( _loading = false; } -void MacroActionHotkeyEdit::SetWarningVisibility() +void MacroActionHotkeyEdit::RepopulateOBSHotkeySelection() { + populateHotkeyNames(_obsHotkeys, _entryData->_hotkeyType); +} + +void MacroActionHotkeyEdit::SetWidgetVisibility() +{ + _entryLayout->removeWidget(_actionType); + _entryLayout->removeWidget(_hotkeyType); + _entryLayout->removeWidget(_obsHotkeys); + _entryLayout->removeWidget(_keys); + _entryLayout->removeWidget(_duration); + ClearLayout(_entryLayout); + PlaceWidgets( + obs_module_text( + _entryData->_action == + MacroActionHotkey::Action::OBS_HOTKEY + ? "AdvSceneSwitcher.action.hotkey.entry.obs" + : "AdvSceneSwitcher.action.hotkey.entry.custom"), + _entryLayout, + { + {"{{actionType}}", _actionType}, + {"{{hotkeyType}}", _hotkeyType}, + {"{{obsHotkeys}}", _obsHotkeys}, + {"{{keys}}", _keys}, + {"{{duration}}", _duration}, + }); + _noKeyPressSimulationWarning->setVisible(!_entryData->_onlySendToObs && !canSimulateKeyPresses); + SetLayoutVisible(_keyConfigLayout, + _entryData->_action == + MacroActionHotkey::Action::CUSTOM); + _duration->setVisible(_entryData->_action == + MacroActionHotkey::Action::CUSTOM); + _keys->setVisible(_entryData->_action == + MacroActionHotkey::Action::CUSTOM); + _onlySendToOBS->setVisible(_entryData->_action == + MacroActionHotkey::Action::CUSTOM); + _hotkeyType->setVisible(_entryData->_action == + MacroActionHotkey::Action::OBS_HOTKEY); + _obsHotkeys->setVisible(_entryData->_action == + MacroActionHotkey::Action::OBS_HOTKEY); + adjustSize(); + updateGeometry(); +} + +static QString getHotkeyDescriptionByName(const std::string &name) +{ + struct Parameters { + std::string name; + QString description = ""; + } params; + + auto func = [](void *param, obs_hotkey_id id, obs_hotkey_t *hotkey) { + auto params = static_cast(param); + std::string name = obs_hotkey_get_name(hotkey); + addNamePrefix(name, hotkey); + if (name == params->name) { + params->description = + obs_hotkey_get_description(hotkey); + addNamePrefix(params->description, hotkey); + return false; + } + return true; + }; + + params.name = name; + obs_enum_hotkeys(func, ¶ms); + return params.description; } void MacroActionHotkeyEdit::UpdateEntryData() @@ -491,6 +747,11 @@ void MacroActionHotkeyEdit::UpdateEntryData() return; } + _actionType->setCurrentIndex(static_cast(_entryData->_action)); + _hotkeyType->setCurrentIndex(static_cast(_entryData->_hotkeyType)); + RepopulateOBSHotkeySelection(); + _obsHotkeys->setCurrentText( + getHotkeyDescriptionByName(_entryData->_hotkeyName)); _keys->setCurrentIndex(static_cast(_entryData->_key)); _leftShift->setChecked(_entryData->_leftShift); _rightShift->setChecked(_entryData->_rightShift); @@ -503,7 +764,7 @@ void MacroActionHotkeyEdit::UpdateEntryData() _duration->SetDuration(_entryData->_duration); _onlySendToOBS->setChecked(_entryData->_onlySendToObs || !canSimulateKeyPresses); - SetWarningVisibility(); + SetWidgetVisibility(); } void MacroActionHotkeyEdit::LShiftChanged(int state) @@ -604,7 +865,7 @@ void MacroActionHotkeyEdit::OnlySendToOBSChanged(int state) auto lock = LockContext(); _entryData->_onlySendToObs = state; - SetWarningVisibility(); + SetWidgetVisibility(); } void MacroActionHotkeyEdit::KeyChanged(int key) @@ -617,4 +878,43 @@ void MacroActionHotkeyEdit::KeyChanged(int key) _entryData->_key = static_cast(key); } +void MacroActionHotkeyEdit::ActionChanged(int value) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_action = static_cast(value); + SetWidgetVisibility(); +} + +void MacroActionHotkeyEdit::HotkeyTypeChanged(int value) +{ + if (_loading || !_entryData) { + return; + } + { + auto lock = LockContext(); + _entryData->_hotkeyType = + static_cast(value); + } + RepopulateOBSHotkeySelection(); +} + +void MacroActionHotkeyEdit::OBSHotkeyChanged(int idx) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + if (idx == -1) { + _entryData->_hotkeyName = ""; + return; + } + _entryData->_hotkeyName = + _obsHotkeys->itemData(idx).toString().toStdString(); +} + } // namespace advss diff --git a/src/macro-core/macro-action-hotkey.hpp b/src/macro-core/macro-action-hotkey.hpp index 92470e93..59b8117f 100644 --- a/src/macro-core/macro-action-hotkey.hpp +++ b/src/macro-core/macro-action-hotkey.hpp @@ -23,7 +23,15 @@ public: return std::make_shared(m); } - OBSWeakSource _HotkeySource; + enum class Action { + OBS_HOTKEY, + CUSTOM, + }; + Action _action = Action::OBS_HOTKEY; + + obs_hotkey_registerer_type _hotkeyType = OBS_HOTKEY_REGISTERER_FRONTEND; + std::string _hotkeyName; + HotkeyType _key = HotkeyType::Key_NoKey; bool _leftShift = false; bool _rightShift = false; @@ -41,6 +49,9 @@ public: #endif private: + void SendOBSHotkey(); + void SendCustomHotkey(); + static bool _registered; static const std::string id; }; @@ -62,6 +73,9 @@ public: } private slots: + void ActionChanged(int key); + void HotkeyTypeChanged(int key); + void OBSHotkeyChanged(int); void KeyChanged(int key); void LShiftChanged(int state); void RShiftChanged(int state); @@ -75,6 +89,9 @@ private slots: void OnlySendToOBSChanged(int state); protected: + QComboBox *_actionType; + QComboBox *_hotkeyType; + QComboBox *_obsHotkeys; QComboBox *_keys; QCheckBox *_leftShift; QCheckBox *_rightShift; @@ -91,8 +108,11 @@ protected: std::shared_ptr _entryData; private: - void SetWarningVisibility(); - QHBoxLayout *_mainLayout; + void RepopulateOBSHotkeySelection(); + void SetWidgetVisibility(); + + QHBoxLayout *_entryLayout; + QHBoxLayout *_keyConfigLayout; bool _loading = true; };