diff --git a/src/hotkey.cpp b/src/hotkey.cpp index cf010579..0612700e 100644 --- a/src/hotkey.cpp +++ b/src/hotkey.cpp @@ -4,6 +4,119 @@ #include #include +std::vector> Hotkey::_registeredHotkeys = {}; +uint32_t Hotkey::_hotkeyCounter = 1; + +std::shared_ptr Hotkey::GetHotkey(const std::string &description, + bool ignoreExistingHotkeys) +{ + // Clean up expired hotkeys + auto it = _registeredHotkeys.begin(); + while (it != _registeredHotkeys.end()) { + if (it->expired()) { + it = _registeredHotkeys.erase(it); + } else { + it++; + } + } + + // Check for existing hotkey with same description + for (const auto &h : _registeredHotkeys) { + auto hotkey = h.lock(); + if (!hotkey) { + continue; + } + if (hotkey->_description == description) { + hotkey->_ignoreExistingHotkeys = ignoreExistingHotkeys; + return hotkey; + } + } + + // Create new hotkey + auto hotkey = std::make_shared(description); + _registeredHotkeys.emplace_back(hotkey); + hotkey->_ignoreExistingHotkeys = ignoreExistingHotkeys; + return hotkey; +} + +Hotkey::Hotkey(const std::string &description) : _description(description) +{ + std::string name = + "macro_condition_hotkey_" + std::to_string(_hotkeyCounter); + _hotkeyID = obs_hotkey_register_frontend( + name.c_str(), _description.c_str(), Callback, this); + _hotkeyCounter++; +} + +bool Hotkey::Save(obs_data_t *obj) const +{ + obs_data_set_string(obj, "desc", _description.c_str()); + obs_data_array_t *hotkeyData = obs_hotkey_save(_hotkeyID); + obs_data_set_array(obj, "keyBind", hotkeyData); + obs_data_array_release(hotkeyData); + return true; +} + +bool Hotkey::Load(obs_data_t *obj) +{ + auto description = obs_data_get_string(obj, "desc"); + if (!DescriptionAvailable(description)) { + return false; + } + _description = description; + obs_data_array_t *hotkeyData = obs_data_get_array(obj, "keyBind"); + obs_hotkey_load(_hotkeyID, hotkeyData); + obs_data_array_release(hotkeyData); + obs_hotkey_set_description(_hotkeyID, _description.c_str()); + _ignoreExistingHotkeys = false; + return true; +} + +Hotkey::~Hotkey() +{ + obs_hotkey_unregister(_hotkeyID); +} + +bool Hotkey::UpdateDescription(const std::string &descritpion) +{ + if (!DescriptionAvailable(descritpion)) { + return false; + } + _description = descritpion; + obs_hotkey_set_description(_hotkeyID, descritpion.c_str()); + return true; +} + +bool Hotkey::DescriptionAvailable(const std::string &descritpion) +{ + for (const auto &hotkey : _registeredHotkeys) { + auto h = hotkey.lock(); + if (!h) { + continue; + } + if (!h->_ignoreExistingHotkeys && + h->_description == descritpion) { + return false; + } + } + return true; +} + +void Hotkey::Callback(void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) +{ + auto hotkey = static_cast(data); + if (pressed) { + hotkey->_lastPressed = + std::chrono::high_resolution_clock::now(); + } + hotkey->_pressed = pressed; +} + +void Hotkey::ClearAllHotkeys() +{ + _registeredHotkeys.clear(); +} + void startHotkeyFunc(void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { if (pressed) { diff --git a/src/hotkey.hpp b/src/hotkey.hpp index f337f9b0..5a9f260e 100644 --- a/src/hotkey.hpp +++ b/src/hotkey.hpp @@ -1,7 +1,46 @@ #pragma once +#include +#include +#include +#include +#include extern bool canSimulateKeyPresses; +class Hotkey { +public: + Hotkey(const std::string &description); + ~Hotkey(); + + static std::shared_ptr + GetHotkey(const std::string &description, + bool ignoreExistingHotkeys = false); + static void ClearAllHotkeys(); + + bool Save(obs_data_t *obj) const; + bool Load(obs_data_t *obj); + + bool GetPressed() const { return _pressed; } + auto GetLastPressed() const { return _lastPressed; } + std::string GetDescription() const { return _description; } + bool UpdateDescription(const std::string &); + +private: + static bool DescriptionAvailable(const std::string &); + static void Callback(void *data, obs_hotkey_id, obs_hotkey_t *, + bool pressed); + + static std::vector> _registeredHotkeys; + static uint32_t _hotkeyCounter; + + std::string _description; + obs_hotkey_id _hotkeyID = OBS_INVALID_HOTKEY_ID; + bool _pressed = false; + std::chrono::high_resolution_clock::time_point _lastPressed{}; + // When set will not attempt to share settings with existing hotkey + bool _ignoreExistingHotkeys = false; +}; + enum class HotkeyType { Key_NoKey = 0, diff --git a/src/macro-core/macro-condition-hotkey.cpp b/src/macro-core/macro-condition-hotkey.cpp index 0aed7239..dccbee23 100644 --- a/src/macro-core/macro-condition-hotkey.cpp +++ b/src/macro-core/macro-condition-hotkey.cpp @@ -10,63 +10,41 @@ bool MacroConditionHotkey::_registered = MacroConditionFactory::Register( {MacroConditionHotkey::Create, MacroConditionHotkeyEdit::Create, "AdvSceneSwitcher.condition.hotkey", false}); -static void hotkeyCB(void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) -{ - auto hotkeyCondition = static_cast(data); - auto macro = hotkeyCondition->GetMacro(); - if (macro) { - hotkeyCondition->SetPressed(pressed && !macro->Paused()); - } else { - hotkeyCondition->SetPressed(pressed); - } -} - static uint32_t count = 1; MacroConditionHotkey::MacroConditionHotkey(Macro *m) : MacroCondition(m) { - if (_hotkeyID != OBS_INVALID_HOTKEY_ID) { - obs_hotkey_unregister(_hotkeyID); - } - - std::string hotkeyName = - "macro_condition_hotkey_" + std::to_string(count); - - _name = obs_module_text("AdvSceneSwitcher.condition.hotkey.name") + - std::string(" ") + std::to_string(count); - _hotkeyID = obs_hotkey_register_frontend(hotkeyName.c_str(), - _name.c_str(), hotkeyCB, this); + auto name = obs_module_text("AdvSceneSwitcher.condition.hotkey.name") + + std::string(" ") + std::to_string(count); + _hotkey = Hotkey::GetHotkey(name, true); count++; } -MacroConditionHotkey::~MacroConditionHotkey() -{ - obs_hotkey_unregister(_hotkeyID); -} - bool MacroConditionHotkey::CheckCondition() { - return _pressed; + bool ret = _hotkey->GetPressed() || + _hotkey->GetLastPressed() > _lastCheck; + _lastCheck = std::chrono::high_resolution_clock::now(); + return ret; } bool MacroConditionHotkey::Save(obs_data_t *obj) { MacroCondition::Save(obj); - obs_data_set_string(obj, "desc", _name.c_str()); - obs_data_array_t *pauseHotkey = obs_hotkey_save(_hotkeyID); - obs_data_set_array(obj, "keyBind", pauseHotkey); - obs_data_array_release(pauseHotkey); + _hotkey->Save(obj); return true; } bool MacroConditionHotkey::Load(obs_data_t *obj) { MacroCondition::Load(obj); - _name = obs_data_get_string(obj, "desc"); - obs_data_array_t *pauseHotkey = obs_data_get_array(obj, "keyBind"); - obs_hotkey_load(_hotkeyID, pauseHotkey); - obs_data_array_release(pauseHotkey); - obs_hotkey_set_description(_hotkeyID, _name.c_str()); + if (!_hotkey->Load(obj)) { + auto description = obs_data_get_string(obj, "desc"); + _hotkey = Hotkey::GetHotkey(description); + vblog(LOG_WARNING, + "hotkey name conflict for \"%s\" - using previous key bind", + description); + } return true; } @@ -109,9 +87,18 @@ void MacroConditionHotkeyEdit::NameChanged() } std::lock_guard lock(switcher->m); - _entryData->_name = _name->text().toStdString(); - obs_hotkey_set_description(_entryData->_hotkeyID, - _entryData->_name.c_str()); + const auto name = _name->text().toStdString(); + // In case a hotkey is used by multiple conditions create a new hotkey + // with the new description or get an existing hotkey matching this + // description. + // If the hotkey is only used by this single condition instance try to + // update the description. + // If updating the description runs into a conflict with an existing + // hotkey use its settings instead. + if (_entryData->_hotkey.use_count() > 1 || + !_entryData->_hotkey->UpdateDescription(name)) { + _entryData->_hotkey = Hotkey::GetHotkey(name); + } } void MacroConditionHotkeyEdit::UpdateEntryData() @@ -120,5 +107,6 @@ void MacroConditionHotkeyEdit::UpdateEntryData() return; } - _name->setText(QString::fromStdString(_entryData->_name)); + _name->setText( + QString::fromStdString(_entryData->_hotkey->GetDescription())); } diff --git a/src/macro-core/macro-condition-hotkey.hpp b/src/macro-core/macro-condition-hotkey.hpp index d13f8d96..52f61c9b 100644 --- a/src/macro-core/macro-condition-hotkey.hpp +++ b/src/macro-core/macro-condition-hotkey.hpp @@ -1,12 +1,13 @@ #pragma once #include "macro.hpp" +#include "hotkey.hpp" + #include #include class MacroConditionHotkey : public MacroCondition { public: MacroConditionHotkey(Macro *m); - ~MacroConditionHotkey(); bool CheckCondition(); bool Save(obs_data_t *obj); bool Load(obs_data_t *obj); @@ -15,13 +16,12 @@ public: { return std::make_shared(m); } - void SetPressed(bool value) { _pressed = value; } - std::string _name; - obs_hotkey_id _hotkeyID = OBS_INVALID_HOTKEY_ID; + std::shared_ptr _hotkey; private: - bool _pressed = false; + std::chrono::high_resolution_clock::time_point _lastCheck{}; + static bool _registered; static const std::string id; }; diff --git a/src/macro-core/macro.cpp b/src/macro-core/macro.cpp index 6332f6e2..6774dd93 100644 --- a/src/macro-core/macro.cpp +++ b/src/macro-core/macro.cpp @@ -594,6 +594,7 @@ void SwitcherData::saveMacros(obs_data_t *obj) void SwitcherData::loadMacros(obs_data_t *obj) { + Hotkey::ClearAllHotkeys(); switcher->macroProperties.Load(obj); macros.clear();