From 8c317c13968381de96aa27d1e6e17f925bcecbef Mon Sep 17 00:00:00 2001 From: WarmUpTill Date: Tue, 25 Apr 2023 23:28:01 +0200 Subject: [PATCH] Add option to check for and set keyframe interval settings --- data/locale/de-DE.ini | 4 +- data/locale/en-US.ini | 6 +- data/locale/es-ES.ini | 4 +- data/locale/ru-RU.ini | 2 +- data/locale/tr-TR.ini | 4 +- data/locale/zh-CN.ini | 4 +- src/macro-core/macro-action-streaming.cpp | 82 ++++++++++--- src/macro-core/macro-action-streaming.hpp | 19 ++- src/macro-core/macro-condition-streaming.cpp | 115 +++++++++++++------ src/macro-core/macro-condition-streaming.hpp | 24 ++-- src/utils/utility.cpp | 8 ++ src/utils/utility.hpp | 1 + 12 files changed, 203 insertions(+), 70 deletions(-) diff --git a/data/locale/de-DE.ini b/data/locale/de-DE.ini index 7817db87..b3195275 100644 --- a/data/locale/de-DE.ini +++ b/data/locale/de-DE.ini @@ -219,7 +219,7 @@ AdvSceneSwitcher.condition.stream.state.start="Stream läuft" AdvSceneSwitcher.condition.stream.state.stop="Stream gestoppt" AdvSceneSwitcher.condition.stream.state.starting="Stream wird gestartet" AdvSceneSwitcher.condition.stream.state.stopping="Stream wird gestoppt" -AdvSceneSwitcher.condition.stream.entry="{{streamState}}" +AdvSceneSwitcher.condition.stream.entry="{{streamState}}{{keyFrameInterval}}" AdvSceneSwitcher.condition.record="Aufnahme" AdvSceneSwitcher.condition.record.state.start="Aufnahme läuft" AdvSceneSwitcher.condition.record.state.pause="Aufnahme pausiert" @@ -438,7 +438,7 @@ AdvSceneSwitcher.action.replay.entry="{{actions}}" AdvSceneSwitcher.action.streaming="Stream" AdvSceneSwitcher.action.streaming.type.stop="Stream stoppen" AdvSceneSwitcher.action.streaming.type.start="Stream starten" -AdvSceneSwitcher.action.streaming.entry="{{actions}}" +AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}" AdvSceneSwitcher.action.run="Ausführen" AdvSceneSwitcher.action.run.arguments="Argumente:" AdvSceneSwitcher.action.run.addArgument="Argument hinzufügen" diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index b4bb893c..3d99d039 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -253,7 +253,8 @@ AdvSceneSwitcher.condition.stream.state.start="Stream running" AdvSceneSwitcher.condition.stream.state.stop="Stream stopped" AdvSceneSwitcher.condition.stream.state.starting="Stream starting" AdvSceneSwitcher.condition.stream.state.stopping="Stream stopping" -AdvSceneSwitcher.condition.stream.entry="{{streamState}}" +AdvSceneSwitcher.condition.stream.state.keyFrameInterval="Keyframe interval equals" +AdvSceneSwitcher.condition.stream.entry="{{streamState}}{{keyFrameInterval}}" AdvSceneSwitcher.condition.record="Recording" AdvSceneSwitcher.condition.record.state.start="Recording running" AdvSceneSwitcher.condition.record.state.pause="Recording paused" @@ -477,7 +478,8 @@ AdvSceneSwitcher.action.replay.entry="{{actions}}" AdvSceneSwitcher.action.streaming="Streaming" AdvSceneSwitcher.action.streaming.type.stop="Stop streaming" AdvSceneSwitcher.action.streaming.type.start="Start streaming" -AdvSceneSwitcher.action.streaming.entry="{{actions}}" +AdvSceneSwitcher.action.streaming.type.keyFrameInterval="Set keyframe interval" +AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}" AdvSceneSwitcher.action.run="Run" AdvSceneSwitcher.action.sceneVisibility="Scene item visibility" AdvSceneSwitcher.action.sceneVisibility.type.show="Show" diff --git a/data/locale/es-ES.ini b/data/locale/es-ES.ini index 88c13a16..c338c245 100644 --- a/data/locale/es-ES.ini +++ b/data/locale/es-ES.ini @@ -179,7 +179,7 @@ AdvSceneSwitcher.condition.stream.state.start="Transmisión en ejecución" AdvSceneSwitcher.condition.stream.state.stop="Transmisión detenida" AdvSceneSwitcher.condition.stream.state.starting="Inicio de transmisión" AdvSceneSwitcher.condition.stream.state.stopping="Detener transmisión" -AdvSceneSwitcher.condition.stream.entry="{{streamState}}" +AdvSceneSwitcher.condition.stream.entry="{{streamState}}{{keyFrameInterval}}" AdvSceneSwitcher.condition.record="Grabación" AdvSceneSwitcher.condition.record.state.start="Grabación en ejecución" AdvSceneSwitcher.condition.record.state.pause="Grabación en pausa" @@ -360,7 +360,7 @@ AdvSceneSwitcher.action.replay.entry="{{actions}}" AdvSceneSwitcher.action.streaming="Transmisión" AdvSceneSwitcher.action.streaming.type.stop="Detener transmisión" AdvSceneSwitcher.action.streaming.type.start="Iniciar transmisión" -AdvSceneSwitcher.action.streaming.entry="{{actions}}" +AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}" AdvSceneSwitcher.action.run="Ejecutar" AdvSceneSwitcher.action.run.arguments="Argumentos:" AdvSceneSwitcher.action.run.addArgument="Agregar argumento" diff --git a/data/locale/ru-RU.ini b/data/locale/ru-RU.ini index 09acf888..e570fc8b 100644 --- a/data/locale/ru-RU.ini +++ b/data/locale/ru-RU.ini @@ -144,7 +144,7 @@ AdvSceneSwitcher.action.replay.entry="{{actions}}" AdvSceneSwitcher.action.streaming="Потоковое вещание" AdvSceneSwitcher.action.streaming.type.stop="Остановить потоковое вещание" AdvSceneSwitcher.action.streaming.type.start="Начать потоковое вещание" -AdvSceneSwitcher.action.streaming.entry="{{actions}}" +AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}" AdvSceneSwitcher.action.run="Запустить" diff --git a/data/locale/tr-TR.ini b/data/locale/tr-TR.ini index 0580805d..e281ff8b 100644 --- a/data/locale/tr-TR.ini +++ b/data/locale/tr-TR.ini @@ -156,7 +156,7 @@ AdvSceneSwitcher.condition.stream.state.start="Yayın çalışıyor" AdvSceneSwitcher.condition.stream.state.stop="Yayın durdu" AdvSceneSwitcher.condition.stream.state.starting="Yayın başlıyor" AdvSceneSwitcher.condition.stream.state.stopping="Yayın duruyor" -AdvSceneSwitcher.condition.stream.entry="{{streamState}}" +AdvSceneSwitcher.condition.stream.entry="{{streamState}}{{keyFrameInterval}}" AdvSceneSwitcher.condition.record="Kayıt" AdvSceneSwitcher.condition.record.state.start="Kayıt Çalışıyor" AdvSceneSwitcher.condition.record.state.pause="Kayıt durakladı" @@ -290,7 +290,7 @@ AdvSceneSwitcher.action.replay.entry="{{actions}}" AdvSceneSwitcher.action.streaming="Yayın" AdvSceneSwitcher.action.streaming.type.stop="Yayın durdur" AdvSceneSwitcher.action.streaming.type.start="Yayın başlat" -AdvSceneSwitcher.action.streaming.entry="{{actions}}" +AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}" AdvSceneSwitcher.action.run="Çalıştır" AdvSceneSwitcher.action.run.arguments="Argümanlar:" AdvSceneSwitcher.action.run.addArgument="Argüman ekle" diff --git a/data/locale/zh-CN.ini b/data/locale/zh-CN.ini index 21afd752..3eee24bd 100644 --- a/data/locale/zh-CN.ini +++ b/data/locale/zh-CN.ini @@ -197,7 +197,7 @@ AdvSceneSwitcher.condition.stream.state.start="推流启动" AdvSceneSwitcher.condition.stream.state.stop="推流停止" AdvSceneSwitcher.condition.stream.state.starting="推流启动中" AdvSceneSwitcher.condition.stream.state.stopping="推流停止中" -AdvSceneSwitcher.condition.stream.entry="{{streamState}}" +AdvSceneSwitcher.condition.stream.entry="{{streamState}}{{keyFrameInterval}}" AdvSceneSwitcher.condition.record="录制" AdvSceneSwitcher.condition.record.state.start="录制运行中" AdvSceneSwitcher.condition.record.state.pause="录制暂停" @@ -403,7 +403,7 @@ AdvSceneSwitcher.action.replay.entry="{{actions}}" AdvSceneSwitcher.action.streaming="推流" AdvSceneSwitcher.action.streaming.type.stop="停止推流" AdvSceneSwitcher.action.streaming.type.start="开始推流" -AdvSceneSwitcher.action.streaming.entry="{{actions}}" +AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}" AdvSceneSwitcher.action.run="运行" AdvSceneSwitcher.action.run.arguments="参数:" AdvSceneSwitcher.action.run.addArgument="添加参数" diff --git a/src/macro-core/macro-action-streaming.cpp b/src/macro-core/macro-action-streaming.cpp index 87720783..cd379586 100644 --- a/src/macro-core/macro-action-streaming.cpp +++ b/src/macro-core/macro-action-streaming.cpp @@ -1,6 +1,8 @@ #include "macro-action-streaming.hpp" #include "utility.hpp" +#include + namespace advss { const std::string MacroActionStream::id = "streaming"; @@ -10,15 +12,32 @@ bool MacroActionStream::_registered = MacroActionFactory::Register( {MacroActionStream::Create, MacroActionStreamEdit::Create, "AdvSceneSwitcher.action.streaming"}); -const static std::map actionTypes = { - {StreamAction::STOP, "AdvSceneSwitcher.action.streaming.type.stop"}, - {StreamAction::START, "AdvSceneSwitcher.action.streaming.type.start"}, +const static std::map actionTypes = { + {MacroActionStream::Action::STOP, + "AdvSceneSwitcher.action.streaming.type.stop"}, + {MacroActionStream::Action::START, + "AdvSceneSwitcher.action.streaming.type.start"}, + {MacroActionStream::Action::KEYFRAME_INTERVAL, + "AdvSceneSwitcher.action.streaming.type.keyFrameInterval"}, }; constexpr int streamStartCooldown = 5; std::chrono::high_resolution_clock::time_point MacroActionStream::s_lastAttempt = std::chrono::high_resolution_clock::now(); +void MacroActionStream::SetKeyFrameInterval() +{ + const auto configPath = GetPathInProfileDir("streamEncoder.json"); + obs_data_t *settings = + obs_data_create_from_json_file_safe(configPath.c_str(), "bak"); + obs_data_set_int(settings, "keyint_sec", _keyFrameInterval); + if (settings) { + obs_data_save_json_safe(settings, configPath.c_str(), "tmp", + "bak"); + obs_data_release(settings); + } +} + bool MacroActionStream::CooldownDurationReached() { auto timePassed = std::chrono::duration_cast( @@ -29,12 +48,12 @@ bool MacroActionStream::CooldownDurationReached() bool MacroActionStream::PerformAction() { switch (_action) { - case StreamAction::STOP: + case Action::STOP: if (obs_frontend_streaming_active()) { obs_frontend_streaming_stop(); } break; - case StreamAction::START: + case Action::START: if (!obs_frontend_streaming_active() && CooldownDurationReached()) { obs_frontend_streaming_start(); @@ -42,6 +61,9 @@ bool MacroActionStream::PerformAction() std::chrono::high_resolution_clock::now(); } break; + case Action::KEYFRAME_INTERVAL: + SetKeyFrameInterval(); + break; default: break; } @@ -63,13 +85,15 @@ bool MacroActionStream::Save(obs_data_t *obj) const { MacroAction::Save(obj); obs_data_set_int(obj, "action", static_cast(_action)); + _keyFrameInterval.Save(obj, "keyFrameInterval"); return true; } bool MacroActionStream::Load(obs_data_t *obj) { MacroAction::Load(obj); - _action = static_cast(obs_data_get_int(obj, "action")); + _action = static_cast(obs_data_get_int(obj, "action")); + _keyFrameInterval.Load(obj, "keyFrameInterval"); return true; } @@ -82,21 +106,28 @@ static inline void populateActionSelection(QComboBox *list) MacroActionStreamEdit::MacroActionStreamEdit( QWidget *parent, std::shared_ptr entryData) - : QWidget(parent) + : QWidget(parent), + _actions(new QComboBox()), + _keyFrameInterval(new VariableSpinBox()) { - _actions = new QComboBox(); + _keyFrameInterval->setMinimum(0); + _keyFrameInterval->setMaximum(25); populateActionSelection(_actions); QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this, SLOT(ActionChanged(int))); + QWidget::connect( + _keyFrameInterval, + SIGNAL(NumberVariableChanged(const NumberVariable &)), + this, + SLOT(KeyFrameIntervalChanged(const NumberVariable &))); QHBoxLayout *mainLayout = new QHBoxLayout; - std::unordered_map widgetPlaceholders = { - {"{{actions}}", _actions}, - }; PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.streaming.entry"), - mainLayout, widgetPlaceholders); + mainLayout, + {{"{{actions}}", _actions}, + {"{{keyFrameInterval}}", _keyFrameInterval}}); setLayout(mainLayout); _entryData = entryData; @@ -110,6 +141,30 @@ void MacroActionStreamEdit::UpdateEntryData() return; } _actions->setCurrentIndex(static_cast(_entryData->_action)); + _keyFrameInterval->SetValue(_entryData->_keyFrameInterval); + SetWidgetVisiblity(); +} + +void MacroActionStreamEdit::KeyFrameIntervalChanged( + const NumberVariable &value) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_keyFrameInterval = value; +} + +void MacroActionStreamEdit::SetWidgetVisiblity() +{ + if (!_entryData) { + return; + } + + _keyFrameInterval->setVisible( + _entryData->_action == + MacroActionStream::Action::KEYFRAME_INTERVAL); } void MacroActionStreamEdit::ActionChanged(int value) @@ -119,7 +174,8 @@ void MacroActionStreamEdit::ActionChanged(int value) } auto lock = LockContext(); - _entryData->_action = static_cast(value); + _entryData->_action = static_cast(value); + SetWidgetVisiblity(); } } // namespace advss diff --git a/src/macro-core/macro-action-streaming.hpp b/src/macro-core/macro-action-streaming.hpp index 29f7c38a..d299d8ed 100644 --- a/src/macro-core/macro-action-streaming.hpp +++ b/src/macro-core/macro-action-streaming.hpp @@ -1,5 +1,6 @@ #pragma once #include "macro-action-edit.hpp" +#include "variable-spinbox.hpp" #include #include @@ -8,11 +9,6 @@ namespace advss { -enum class StreamAction { - STOP, - START, -}; - class MacroActionStream : public MacroAction { public: MacroActionStream(Macro *m) : MacroAction(m) {} @@ -26,9 +22,16 @@ public: return std::make_shared(m); } - StreamAction _action = StreamAction::STOP; + enum class Action { + STOP, + START, + KEYFRAME_INTERVAL, + }; + Action _action = Action::STOP; + NumberVariable _keyFrameInterval = 0; private: + void SetKeyFrameInterval(); bool CooldownDurationReached(); static std::chrono::high_resolution_clock::time_point s_lastAttempt; @@ -54,12 +57,16 @@ public: private slots: void ActionChanged(int value); + void KeyFrameIntervalChanged(const NumberVariable &); protected: QComboBox *_actions; + VariableSpinBox *_keyFrameInterval; std::shared_ptr _entryData; private: + void SetWidgetVisiblity(); + QHBoxLayout *_mainLayout; bool _loading = true; }; diff --git a/src/macro-core/macro-condition-streaming.cpp b/src/macro-core/macro-condition-streaming.cpp index d84296bb..60082d08 100644 --- a/src/macro-core/macro-condition-streaming.cpp +++ b/src/macro-core/macro-condition-streaming.cpp @@ -11,36 +11,56 @@ bool MacroConditionStream::_registered = MacroConditionFactory::Register( {MacroConditionStream::Create, MacroConditionStreamEdit::Create, "AdvSceneSwitcher.condition.stream"}); -static std::map streamStates = { - {StreamState::STOP, "AdvSceneSwitcher.condition.stream.state.stop"}, - {StreamState::START, "AdvSceneSwitcher.condition.stream.state.start"}, - {StreamState::STARTING, +static std::map streamStates = { + {MacroConditionStream::Condition::STOP, + "AdvSceneSwitcher.condition.stream.state.stop"}, + {MacroConditionStream::Condition::START, + "AdvSceneSwitcher.condition.stream.state.start"}, + {MacroConditionStream::Condition::STARTING, "AdvSceneSwitcher.condition.stream.state.starting"}, - {StreamState::STOPPING, + {MacroConditionStream::Condition::STOPPING, "AdvSceneSwitcher.condition.stream.state.stopping"}, + {MacroConditionStream::Condition::KEYFRAME_INTERVAL, + "AdvSceneSwitcher.condition.stream.state.keyFrameInterval"}, }; +int MacroConditionStream::GetKeyFrameInterval() +{ + const auto configPath = GetPathInProfileDir("streamEncoder.json"); + obs_data_t *settings = + obs_data_create_from_json_file_safe(configPath.c_str(), "bak"); + if (!settings) { + return -1; + } + int ret = obs_data_get_int(settings, "keyint_sec"); + obs_data_release(settings); + return ret; +} + bool MacroConditionStream::CheckCondition() { - bool stateMatch = false; + bool match = false; bool streamStarting = switcher->lastStreamStartingTime != _lastStreamStartingTime; bool streamStopping = switcher->lastStreamStoppingTime != _lastStreamStoppingTime; - switch (_streamState) { - case StreamState::STOP: - stateMatch = !obs_frontend_streaming_active(); + switch (_condition) { + case Condition::STOP: + match = !obs_frontend_streaming_active(); break; - case StreamState::START: - stateMatch = obs_frontend_streaming_active(); + case Condition::START: + match = obs_frontend_streaming_active(); break; - case StreamState::STARTING: - stateMatch = streamStarting; + case Condition::STARTING: + match = streamStarting; break; - case StreamState::STOPPING: - stateMatch = streamStopping; + case Condition::STOPPING: + match = streamStopping; + break; + case Condition::KEYFRAME_INTERVAL: + match = GetKeyFrameInterval() == _keyFrameInterval; break; default: break; @@ -52,20 +72,22 @@ bool MacroConditionStream::CheckCondition() if (streamStopping) { _lastStreamStoppingTime = switcher->lastStreamStoppingTime; } - return stateMatch; + return match; } bool MacroConditionStream::Save(obs_data_t *obj) const { MacroCondition::Save(obj); - obs_data_set_int(obj, "state", static_cast(_streamState)); + obs_data_set_int(obj, "state", static_cast(_condition)); + _keyFrameInterval.Save(obj, "keyFrameInterval"); return true; } bool MacroConditionStream::Load(obs_data_t *obj) { MacroCondition::Load(obj); - _streamState = static_cast(obs_data_get_int(obj, "state")); + _condition = static_cast(obs_data_get_int(obj, "state")); + _keyFrameInterval.Load(obj, "keyFrameInterval"); return true; } @@ -78,23 +100,28 @@ static inline void populateStateSelection(QComboBox *list) MacroConditionStreamEdit::MacroConditionStreamEdit( QWidget *parent, std::shared_ptr entryData) - : QWidget(parent) + : QWidget(parent), + _streamState(new QComboBox()), + _keyFrameInterval(new VariableSpinBox()) { - _streamState = new QComboBox(); - - QWidget::connect(_streamState, SIGNAL(currentIndexChanged(int)), this, - SLOT(StateChanged(int))); + _keyFrameInterval->setMinimum(0); + _keyFrameInterval->setMaximum(25); populateStateSelection(_streamState); + QWidget::connect(_streamState, SIGNAL(currentIndexChanged(int)), this, + SLOT(StateChanged(int))); + QWidget::connect( + _keyFrameInterval, + SIGNAL(NumberVariableChanged(const NumberVariable &)), + this, + SLOT(KeyFrameIntervalChanged(const NumberVariable &))); + QHBoxLayout *mainLayout = new QHBoxLayout; - - std::unordered_map widgetPlaceholders = { - {"{{streamState}}", _streamState}, - }; - PlaceWidgets(obs_module_text("AdvSceneSwitcher.condition.stream.entry"), - mainLayout, widgetPlaceholders); + mainLayout, + {{"{{streamState}}", _streamState}, + {"{{keyFrameInterval}}", _keyFrameInterval}}); setLayout(mainLayout); _entryData = entryData; @@ -109,7 +136,20 @@ void MacroConditionStreamEdit::StateChanged(int value) } auto lock = LockContext(); - _entryData->_streamState = static_cast(value); + _entryData->_condition = + static_cast(value); + SetWidgetVisiblity(); +} + +void MacroConditionStreamEdit::KeyFrameIntervalChanged( + const NumberVariable &value) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_keyFrameInterval = value; } void MacroConditionStreamEdit::UpdateEntryData() @@ -118,8 +158,19 @@ void MacroConditionStreamEdit::UpdateEntryData() return; } - _streamState->setCurrentIndex( - static_cast(_entryData->_streamState)); + _streamState->setCurrentIndex(static_cast(_entryData->_condition)); + _keyFrameInterval->SetValue(_entryData->_keyFrameInterval); + SetWidgetVisiblity(); +} + +void MacroConditionStreamEdit::SetWidgetVisiblity() +{ + if (!_entryData) { + return; + } + _keyFrameInterval->setVisible( + _entryData->_condition == + MacroConditionStream::Condition::KEYFRAME_INTERVAL); } } // namespace advss diff --git a/src/macro-core/macro-condition-streaming.hpp b/src/macro-core/macro-condition-streaming.hpp index cc2d7db5..d2801f21 100644 --- a/src/macro-core/macro-condition-streaming.hpp +++ b/src/macro-core/macro-condition-streaming.hpp @@ -1,18 +1,12 @@ #pragma once #include "macro-condition-edit.hpp" +#include "variable-spinbox.hpp" #include #include namespace advss { -enum class StreamState { - STOP, - START, - STARTING, - STOPPING, -}; - class MacroConditionStream : public MacroCondition { public: MacroConditionStream(Macro *m) : MacroCondition(m) {} @@ -25,9 +19,19 @@ public: return std::make_shared(m); } - StreamState _streamState = StreamState::STOP; + enum class Condition { + STOP, + START, + STARTING, + STOPPING, + KEYFRAME_INTERVAL, + }; + Condition _condition = Condition::STOP; + NumberVariable _keyFrameInterval = 0; private: + int GetKeyFrameInterval(); + std::chrono::high_resolution_clock::time_point _lastStreamStartingTime{}; std::chrono::high_resolution_clock::time_point _lastStreamStoppingTime{}; @@ -53,12 +57,16 @@ public: private slots: void StateChanged(int value); + void KeyFrameIntervalChanged(const NumberVariable &); protected: QComboBox *_streamState; + VariableSpinBox *_keyFrameInterval; std::shared_ptr _entryData; private: + void SetWidgetVisiblity(); + bool _loading = true; }; diff --git a/src/utils/utility.cpp b/src/utils/utility.cpp index 5282b6d5..3434d78a 100644 --- a/src/utils/utility.cpp +++ b/src/utils/utility.cpp @@ -1120,4 +1120,12 @@ QString GetDefaultSettingsSaveLocation() timestamp.toString("yyyy.MM.dd.hh.mm.ss"); } +std::string GetPathInProfileDir(const char *filePath) +{ + auto path = obs_frontend_get_current_profile_path(); + std::string result(path); + bfree(path); + return result + "/" + filePath; +} + } // namespace advss diff --git a/src/utils/utility.hpp b/src/utils/utility.hpp index b087a093..b46f0176 100644 --- a/src/utils/utility.hpp +++ b/src/utils/utility.hpp @@ -117,6 +117,7 @@ std::pair GetCursorPos(); void ReplaceAll(std::string &str, const std::string &from, const std::string &to); QString GetDefaultSettingsSaveLocation(); +std::string GetPathInProfileDir(const char *filePath); /* Legacy helpers */