mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-08-29 12:45:28 -05:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa87911b71 | ||
|
|
1b05019acc | ||
|
|
78a5a2629d | ||
|
|
634270a978 | ||
|
|
78ba22e1e4 | ||
|
|
aba5737a60 | ||
|
|
4315f7f621 | ||
|
|
53c535962f | ||
|
|
1e718b78c7 | ||
|
|
b1d2156228 | ||
|
|
9c3c953c6b | ||
|
|
ae74f68db7 | ||
|
|
213f1bba36 | ||
|
|
5a2cb0bd68 | ||
|
|
23b461828b | ||
|
|
9944a1b03b | ||
|
|
eaad4d1bbd |
@@ -33,6 +33,11 @@ add_library(${LIB_NAME} SHARED)
|
||||
include(cmake/common/get_git_revision_description.cmake)
|
||||
get_git_head_revision(GIT_REFSPEC GIT_SHA1)
|
||||
git_describe(GIT_TAG)
|
||||
|
||||
if(${GIT_TAG} STREQUAL "GIT-NOTFOUND")
|
||||
set(GIT_TAG ${PROJECT_VERSION})
|
||||
endif()
|
||||
message(STATUS "${PROJECT_NAME} version: ${GIT_TAG}")
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/common/version.cpp.in"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/lib/version.cpp" @ONLY)
|
||||
|
||||
@@ -298,6 +303,13 @@ setup_obs_lib_dependency(${PROJECT_NAME})
|
||||
find_package(Qt6 REQUIRED COMPONENTS Widgets Core)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Core Qt6::Widgets)
|
||||
target_link_libraries(${LIB_NAME} PRIVATE Qt6::Core Qt6::Widgets)
|
||||
|
||||
# Ignore QCheckBox::stateChanged deprecation warning until minimum supported Qt
|
||||
# version is at least Qt 6.7, which introduces QCheckBox::checkStateChanged
|
||||
if(Qt6_VERSION VERSION_GREATER "6.0.0")
|
||||
target_compile_definitions(${LIB_NAME} PRIVATE QT_NO_DEPRECATED_WARNINGS)
|
||||
endif()
|
||||
|
||||
target_compile_options(
|
||||
${PROJECT_NAME}
|
||||
PRIVATE
|
||||
@@ -431,6 +443,9 @@ else()
|
||||
endif()
|
||||
target_include_directories(${LIB_NAME} PRIVATE "${PROC_INCLUDE_DIR}")
|
||||
target_sources(${LIB_NAME} PRIVATE lib/linux/advanced-scene-switcher-nix.cpp)
|
||||
|
||||
# Don't include irrelevant folders into sources archive
|
||||
list(APPEND CPACK_SOURCE_IGNORE_FILES "\\.deps/.*")
|
||||
endif()
|
||||
|
||||
if(NOT OS_WINDOWS)
|
||||
|
||||
@@ -286,6 +286,12 @@ function(setup_advss_plugin target)
|
||||
find_package(Qt6 REQUIRED COMPONENTS Widgets Core)
|
||||
target_link_libraries(${target} PRIVATE Qt6::Core Qt6::Widgets)
|
||||
|
||||
# Ignore QCheckBox::stateChanged deprecation warning until minimum supported
|
||||
# Qt version is at least Qt 6.7, which introduces QCheckBox::checkStateChanged
|
||||
if(Qt6_VERSION VERSION_GREATER "6.7.0")
|
||||
target_compile_definitions(${target} PRIVATE QT_NO_DEPRECATED_WARNINGS)
|
||||
endif()
|
||||
|
||||
set_target_properties(
|
||||
${target}
|
||||
PROPERTIES AUTOMOC ON
|
||||
|
||||
@@ -526,7 +526,6 @@ AdvSceneSwitcher.action.screenshot.save.default="Standard"
|
||||
AdvSceneSwitcher.action.screenshot.save.custom="Benutzerdefiniert"
|
||||
AdvSceneSwitcher.action.screenshot.type.source="Quelle"
|
||||
AdvSceneSwitcher.action.screenshot.type.scene="Szene"
|
||||
AdvSceneSwitcher.action.screenshot.mainOutput="OBS's Haupt-Ausgabe"
|
||||
AdvSceneSwitcher.action.screenshot.blackscreenNote="Quellen oder Szenen, die nicht immer gerendert werden, können dazu führen, dass einige Teile der Screenshots leer bleiben."
|
||||
AdvSceneSwitcher.action.screenshot.entry="Screenshot{{targetType}}{{sources}}{{scenes}}und speichere in{{saveType}}{{variables}}Pfad"
|
||||
AdvSceneSwitcher.action.profile="Profil"
|
||||
|
||||
@@ -1022,7 +1022,6 @@ AdvSceneSwitcher.action.screenshot.save.custom="Custom file path"
|
||||
AdvSceneSwitcher.action.screenshot.save.variable="Variable (base64 encoded PNG)"
|
||||
AdvSceneSwitcher.action.screenshot.type.source="Source"
|
||||
AdvSceneSwitcher.action.screenshot.type.scene="Scene"
|
||||
AdvSceneSwitcher.action.screenshot.mainOutput="OBS's main output"
|
||||
AdvSceneSwitcher.action.screenshot.blackscreenNote="Sources or scenes, which are not always rendered, may result in some parts of screenshots to remain blank."
|
||||
AdvSceneSwitcher.action.screenshot.entry="Screenshot{{targetType}}{{sources}}{{scenes}}and save to{{saveType}}{{variables}}"
|
||||
AdvSceneSwitcher.action.profile="Profile"
|
||||
@@ -1051,11 +1050,25 @@ AdvSceneSwitcher.action.websocket.entry.sceneSwitcher.request="Send{{api}}of typ
|
||||
AdvSceneSwitcher.action.websocket.entry.sceneSwitcher.event="Send{{api}}of type{{type}}to connected clients"
|
||||
AdvSceneSwitcher.action.websocket.entry.generic="Send{{api}}via{{connection}}"
|
||||
AdvSceneSwitcher.action.http="HTTP"
|
||||
AdvSceneSwitcher.action.http.legacy="HTTP (legacy)"
|
||||
AdvSceneSwitcher.action.http.setHeaders="Set headers"
|
||||
AdvSceneSwitcher.action.http.headers="Headers:"
|
||||
AdvSceneSwitcher.action.http.addHeader="Add header"
|
||||
AdvSceneSwitcher.action.http.addHeader.name="Header name"
|
||||
AdvSceneSwitcher.action.http.addHeader.value="Header value"
|
||||
AdvSceneSwitcher.action.http.setParams="Set parameters"
|
||||
AdvSceneSwitcher.action.http.params="Parameters:"
|
||||
AdvSceneSwitcher.action.http.addParam.name="Parameter name"
|
||||
AdvSceneSwitcher.action.http.addParam.value="Parameter value"
|
||||
AdvSceneSwitcher.action.http.body="Message body:"
|
||||
AdvSceneSwitcher.action.http.type.get="GET"
|
||||
AdvSceneSwitcher.action.http.type.post="POST"
|
||||
AdvSceneSwitcher.action.http.type.put="PUT"
|
||||
AdvSceneSwitcher.action.http.type.patch="PATCH"
|
||||
AdvSceneSwitcher.action.http.type.delete="DELETE"
|
||||
AdvSceneSwitcher.action.http.layout.method="Send{{method}}to URL{{url}}at path{{path}}"
|
||||
AdvSceneSwitcher.action.http.layout.contentType="Content type:{{contentType}}"
|
||||
AdvSceneSwitcher.action.http.layout.timeout="Timeout:{{timeout}}seconds"
|
||||
AdvSceneSwitcher.action.http.entry.line1="Send{{method}}to{{url}}"
|
||||
AdvSceneSwitcher.action.http.entry.line2="Timeout:{{timeout}}seconds"
|
||||
AdvSceneSwitcher.action.variable="Variable"
|
||||
@@ -1113,6 +1126,8 @@ AdvSceneSwitcher.action.variable.entry.userInput.customPrompt="{{useCustomPrompt
|
||||
AdvSceneSwitcher.action.variable.entry.userInput.placeholder="{{useInputPlaceholder}}Fill with placeholder{{inputPlaceholder}}"
|
||||
AdvSceneSwitcher.action.variable.entry.randomNumber="Generate random number in range from{{randomNumberStart}}to{{randomNumberEnd}}"
|
||||
AdvSceneSwitcher.action.projector="Projector"
|
||||
AdvSceneSwitcher.action.projector.action.open="Open"
|
||||
AdvSceneSwitcher.action.projector.action.close="Close"
|
||||
AdvSceneSwitcher.action.projector.type.source="Source"
|
||||
AdvSceneSwitcher.action.projector.type.scene="Scene"
|
||||
AdvSceneSwitcher.action.projector.type.preview="Preview"
|
||||
@@ -1121,8 +1136,9 @@ AdvSceneSwitcher.action.projector.type.multiview="Multiview"
|
||||
AdvSceneSwitcher.action.projector.display="Display"
|
||||
AdvSceneSwitcher.action.projector.windowed="Windowed"
|
||||
AdvSceneSwitcher.action.projector.fullscreen="Fullscreen"
|
||||
AdvSceneSwitcher.action.projector.entry="Open{{windowTypes}}projector of{{types}}{{scenes}}{{sources}}"
|
||||
AdvSceneSwitcher.action.projector.entry.monitor="on{{monitors}}"
|
||||
AdvSceneSwitcher.action.projector.entry.close="{{actions}}projector with name{{projectorWindowName}}{{regex}}"
|
||||
AdvSceneSwitcher.action.projector.entry.open.windowed="{{actions}}{{windowTypes}}projector of{{types}}{{scenes}}{{sources}}"
|
||||
AdvSceneSwitcher.action.projector.entry.open.fullscreen="{{actions}}{{windowTypes}}projector of{{types}}{{scenes}}{{sources}}on{{monitors}}"
|
||||
AdvSceneSwitcher.action.midi="MIDI"
|
||||
AdvSceneSwitcher.action.midi.entry="Send message to{{device}}:"
|
||||
AdvSceneSwitcher.action.midi.entry.listen="Set MIDI message selection to messages incoming on{{listenDevices}}:{{listenButton}}"
|
||||
@@ -2038,6 +2054,11 @@ AdvSceneSwitcher.tempVar.gameCapture.class.description="Window class of the appl
|
||||
AdvSceneSwitcher.tempVar.gameCapture.executable="Executable"
|
||||
AdvSceneSwitcher.tempVar.gameCapture.executable.description="Executable name of the application captured by the source."
|
||||
|
||||
AdvSceneSwitcher.tempVar.http.status="Status code"
|
||||
AdvSceneSwitcher.tempVar.http.body="Message body"
|
||||
AdvSceneSwitcher.tempVar.http.error="Error"
|
||||
AdvSceneSwitcher.tempVar.http.error.description="Empty when no error occurred.\nOther possible values:\n\n * Could not establish connection\n * Failed to bind IP address\n * Failed to read connection\n * Failed to write connection\n * Maximum redirect count exceeded\n * Connection handling canceled\n * SSL connection failed\n * SSL certificate loading failed\n * SSL server verification failed\n * Unsupported HTTP multipart boundary characters\n * Compression failed\n * Connection timed out\n * Proxy connection failed\n * Unknown"
|
||||
|
||||
AdvSceneSwitcher.selectScene="--select scene--"
|
||||
AdvSceneSwitcher.selectPreviousScene="Previous Scene"
|
||||
AdvSceneSwitcher.selectCurrentScene="Current Scene"
|
||||
@@ -2076,7 +2097,6 @@ AdvSceneSwitcher.settings.suffix.type.list=" (List)"
|
||||
AdvSceneSwitcher.settings.suffix.type.color=" (Color)"
|
||||
AdvSceneSwitcher.settings.suffix.type.button=" (Button)"
|
||||
AdvSceneSwitcher.settings.suffix.type.font=" (Font)"
|
||||
AdvSceneSwitcher.settings.suffix.type.font=" (Font)"
|
||||
AdvSceneSwitcher.settings.suffix.type.editableList=" (Editable list)"
|
||||
AdvSceneSwitcher.settings.suffix.type.frameRate=" (Frame rate)"
|
||||
AdvSceneSwitcher.settings.suffix.type.group=" (Group)"
|
||||
|
||||
@@ -438,7 +438,6 @@ AdvSceneSwitcher.action.random="Aleatorio"
|
||||
AdvSceneSwitcher.action.random.entry="Ejecute aleatoriamente cualquiera de las siguientes macros (las macros en pausa se ignoran)"
|
||||
AdvSceneSwitcher.action.systray="Notificación de la bandeja del sistema"
|
||||
AdvSceneSwitcher.action.screenshot="Captura de pantalla"
|
||||
AdvSceneSwitcher.action.screenshot.mainOutput="Salida principal de OBS"
|
||||
AdvSceneSwitcher.action.profile="Perfil"
|
||||
AdvSceneSwitcher.action.profile.entry="Cambiar perfil activo a {{profiles}}"
|
||||
AdvSceneSwitcher.action.sceneCollection="Colección de escenas"
|
||||
|
||||
@@ -647,7 +647,6 @@ AdvSceneSwitcher.action.screenshot.save.default="Par défaut"
|
||||
AdvSceneSwitcher.action.screenshot.save.custom="Personnalisé"
|
||||
AdvSceneSwitcher.action.screenshot.type.source="Source"
|
||||
AdvSceneSwitcher.action.screenshot.type.scene="Scène"
|
||||
AdvSceneSwitcher.action.screenshot.mainOutput="Sortie principale d'OBS"
|
||||
AdvSceneSwitcher.action.screenshot.blackscreenNote="Les sources ou les scènes qui ne sont pas toujours rendues peuvent entraîner des parties de captures d'écran vides."
|
||||
AdvSceneSwitcher.action.screenshot.entry="Capturer{{targetType}}{{sources}}{{scenes}}et enregistrer à l'emplacement{{saveType}}{{variables}}"
|
||||
AdvSceneSwitcher.action.profile="Profil"
|
||||
@@ -718,8 +717,6 @@ AdvSceneSwitcher.action.projector.type.multiview="Multivue"
|
||||
AdvSceneSwitcher.action.projector.display="Affichage"
|
||||
AdvSceneSwitcher.action.projector.windowed="Fenêtré"
|
||||
AdvSceneSwitcher.action.projector.fullscreen="Plein écran"
|
||||
AdvSceneSwitcher.action.projector.entry="Ouvrir le projecteur{{windowTypes}}de{{types}}{{scenes}}{{sources}}"
|
||||
AdvSceneSwitcher.action.projector.entry.monitor="sur{{monitors}}"
|
||||
AdvSceneSwitcher.action.midi="MIDI"
|
||||
AdvSceneSwitcher.action.midi.entry="Envoyer un message à{{device}}:"
|
||||
AdvSceneSwitcher.action.midi.entry.listen="Définir la sélection de messages MIDI sur les messages entrants de{{listenDevices}}:{{listenButton}}"
|
||||
|
||||
@@ -979,7 +979,6 @@ AdvSceneSwitcher.action.screenshot.save.default="デフォルト"
|
||||
AdvSceneSwitcher.action.screenshot.save.custom="カスタム"
|
||||
; AdvSceneSwitcher.action.screenshot.type.source="Source"
|
||||
AdvSceneSwitcher.action.screenshot.type.scene="シーン"
|
||||
AdvSceneSwitcher.action.screenshot.mainOutput="OBSの主な出力"
|
||||
AdvSceneSwitcher.action.screenshot.blackscreenNote="常にレンダリングされるわけではないソースやシーンにより、スクリーンショットの一部が空白のままになる場合があります。"
|
||||
AdvSceneSwitcher.action.screenshot.entry="スクリーンショット{{targetType}}{{sources}}{{scenes}}を作成し、{{saveType}}{{variables}}の場所に保存します"
|
||||
AdvSceneSwitcher.action.profile="プロファイル"
|
||||
@@ -1068,8 +1067,6 @@ AdvSceneSwitcher.action.projector.type.multiview="マルチビュー"
|
||||
AdvSceneSwitcher.action.projector.display="ディスプレイ"
|
||||
AdvSceneSwitcher.action.projector.windowed="ウィンドウ"
|
||||
AdvSceneSwitcher.action.projector.fullscreen="フルスクリーン"
|
||||
AdvSceneSwitcher.action.projector.entry="{{types}}{{scenes}}{{sources}}の{{windowTypes}}プロジェクターを開きます"
|
||||
AdvSceneSwitcher.action.projector.entry.monitor="{{monitors}}上で"
|
||||
; AdvSceneSwitcher.action.midi="MIDI"
|
||||
AdvSceneSwitcher.action.midi.entry="メッセージを{{device}}に送信します:"
|
||||
AdvSceneSwitcher.action.midi.entry.listen="MIDI メッセージの選択を {{listenDevices}} で受信するメッセージに設定します:{{listenButton}}"
|
||||
|
||||
@@ -960,7 +960,6 @@ AdvSceneSwitcher.action.screenshot.save.default="Padrão"
|
||||
AdvSceneSwitcher.action.screenshot.save.custom="Personalizado"
|
||||
AdvSceneSwitcher.action.screenshot.type.source="Fonte"
|
||||
AdvSceneSwitcher.action.screenshot.type.scene="Cena"
|
||||
AdvSceneSwitcher.action.screenshot.mainOutput="Saída principal do OBS"
|
||||
AdvSceneSwitcher.action.screenshot.blackscreenNote="Fontes ou cenas, que nem sempre são renderizadas, podem resultar em algumas partes das capturas de tela permanecendo em branco."
|
||||
AdvSceneSwitcher.action.screenshot.entry="Captura de tela{{targetType}}{{sources}}{{scenes}}e salvar em{{saveType}}{{variables}}localização"
|
||||
AdvSceneSwitcher.action.profile="Perfil"
|
||||
@@ -1049,8 +1048,6 @@ AdvSceneSwitcher.action.projector.type.multiview="Multivisão"
|
||||
AdvSceneSwitcher.action.projector.display="Exibição"
|
||||
AdvSceneSwitcher.action.projector.windowed="Janela"
|
||||
AdvSceneSwitcher.action.projector.fullscreen="Tela cheia"
|
||||
AdvSceneSwitcher.action.projector.entry="Abrir{{windowTypes}}projetor de{{types}}{{scenes}}{{sources}}"
|
||||
AdvSceneSwitcher.action.projector.entry.monitor="em{{monitors}}"
|
||||
AdvSceneSwitcher.action.midi="MIDI"
|
||||
AdvSceneSwitcher.action.midi.entry="Enviar mensagem para{{device}}:"
|
||||
AdvSceneSwitcher.action.midi.entry.listen="Definir seleção de mensagem MIDI para mensagens recebidas em{{listenDevices}}:{{listenButton}}"
|
||||
|
||||
@@ -356,7 +356,6 @@ AdvSceneSwitcher.action.random="Rastgele"
|
||||
AdvSceneSwitcher.action.random.entry="Aşağıdaki makrolardan herhangi birini rastgele çalıştırın (duraklatılmış makrolar yoksayılır)"
|
||||
AdvSceneSwitcher.action.systray="Sistem tepsisi bildirimi"
|
||||
AdvSceneSwitcher.action.screenshot="Ekran görüntüsü"
|
||||
AdvSceneSwitcher.action.screenshot.mainOutput="OBS'nin ana çıkışı"
|
||||
AdvSceneSwitcher.action.profile="Profil"
|
||||
AdvSceneSwitcher.action.profile.entry="Aktif profili şununla değiştir: {{profiles}}"
|
||||
AdvSceneSwitcher.action.sceneCollection="Sahne koleksiyonu"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
2
deps/libremidi
vendored
2
deps/libremidi
vendored
Submodule deps/libremidi updated: d6d6557b5b...73e6ea40de
@@ -1,6 +1,5 @@
|
||||
#include "advanced-scene-switcher.hpp"
|
||||
#include "backup.hpp"
|
||||
#include "curl-helper.hpp"
|
||||
#include "log-helper.hpp"
|
||||
#include "macro-helpers.hpp"
|
||||
#include "obs-module-helper.hpp"
|
||||
@@ -19,10 +18,15 @@
|
||||
#include <obs-frontend-api.h>
|
||||
#include <QAction>
|
||||
#include <QDirIterator>
|
||||
#include <QLibrary>
|
||||
#include <QMainWindow>
|
||||
#include <QTextStream>
|
||||
#include <regex>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
namespace advss {
|
||||
|
||||
AdvSceneSwitcher *AdvSceneSwitcher::window = nullptr;
|
||||
@@ -307,7 +311,7 @@ void SwitcherData::Thread()
|
||||
}
|
||||
}
|
||||
|
||||
ResetForNextInterval();
|
||||
RunIntervalResetSteps();
|
||||
|
||||
if (match) {
|
||||
if (macroMatch) {
|
||||
@@ -357,14 +361,6 @@ void SwitcherData::SetPreconditions()
|
||||
InvalidateMacroTempVarValues();
|
||||
}
|
||||
|
||||
void SwitcherData::ResetForNextInterval()
|
||||
{
|
||||
// Plugin reset functions
|
||||
for (const auto &func : resetIntervalSteps) {
|
||||
func();
|
||||
}
|
||||
}
|
||||
|
||||
bool SwitcherData::CheckForMatch(OBSWeakSource &scene,
|
||||
OBSWeakSource &transition, int &linger,
|
||||
bool &setPrevSceneAfterLinger,
|
||||
@@ -445,7 +441,7 @@ void AutoStartActionQueues();
|
||||
void SwitcherData::Start()
|
||||
{
|
||||
if (!(th && th->isRunning())) {
|
||||
ResetForNextInterval();
|
||||
RunIntervalResetSteps();
|
||||
ResetMacros();
|
||||
AutoStartActionQueues();
|
||||
|
||||
@@ -453,10 +449,7 @@ void SwitcherData::Start()
|
||||
th = new SwitcherThread();
|
||||
th->start((QThread::Priority)threadPriority);
|
||||
|
||||
// Will be overwritten quickly but might be useful
|
||||
writeToStatusFile("Advanced Scene Switcher running");
|
||||
SendWebsocketVendorEvent("AdvancedSceneSwitcherStarted",
|
||||
nullptr);
|
||||
RunStartSteps();
|
||||
}
|
||||
|
||||
if (showSystemTrayNotifications) {
|
||||
@@ -483,11 +476,7 @@ void SwitcherData::Stop()
|
||||
th->wait();
|
||||
delete th;
|
||||
th = nullptr;
|
||||
writeToStatusFile("Advanced Scene Switcher stopped");
|
||||
if (!obsIsShuttingDown) {
|
||||
SendWebsocketVendorEvent("AdvancedSceneSwitcherStopped",
|
||||
nullptr);
|
||||
}
|
||||
RunStopSteps();
|
||||
}
|
||||
|
||||
if (showSystemTrayNotifications) {
|
||||
|
||||
@@ -21,6 +21,29 @@ bool FileSwitch::pause = false;
|
||||
static QObject *addPulse = nullptr;
|
||||
static std::hash<std::string> strHash;
|
||||
|
||||
static void writeToStatusFile(const QString &msg)
|
||||
{
|
||||
if (!GetSwitcher() || !GetSwitcher()->fileIO.writeEnabled ||
|
||||
GetSwitcher()->fileIO.writePath.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QFile file(QString::fromStdString(GetSwitcher()->fileIO.writePath));
|
||||
if (file.open(QIODevice::ReadWrite)) {
|
||||
QTextStream stream(&file);
|
||||
stream << msg << Qt::endl;
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
static bool _ = []() {
|
||||
AddStartStep(
|
||||
[]() { writeToStatusFile("Advanced Scene Switcher running"); });
|
||||
AddStopStep(
|
||||
[]() { writeToStatusFile("Advanced Scene Switcher stopped"); });
|
||||
return true;
|
||||
}();
|
||||
|
||||
void AdvSceneSwitcher::on_browseButton_clicked()
|
||||
{
|
||||
QString path = QFileDialog::getOpenFileName(
|
||||
@@ -113,20 +136,6 @@ void SwitcherData::writeSceneInfoToFile()
|
||||
}
|
||||
}
|
||||
|
||||
void SwitcherData::writeToStatusFile(const QString &msg)
|
||||
{
|
||||
if (!fileIO.writeEnabled || fileIO.writePath.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QFile file(QString::fromStdString(fileIO.writePath));
|
||||
if (file.open(QIODevice::ReadWrite)) {
|
||||
QTextStream stream(&file);
|
||||
stream << msg << Qt::endl;
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
bool SwitcherData::checkSwitchInfoFromFile(OBSWeakSource &scene,
|
||||
OBSWeakSource &transition)
|
||||
{
|
||||
|
||||
@@ -17,6 +17,9 @@ static inline void populateActionSelection(QComboBox *list)
|
||||
QString entry(obs_module_text(action._name.c_str()));
|
||||
if (list->findText(entry) == -1) {
|
||||
list->addItem(entry);
|
||||
qobject_cast<QListView *>(list->view())
|
||||
->setRowHidden(list->count() - 1,
|
||||
action._hidden);
|
||||
} else {
|
||||
blog(LOG_WARNING,
|
||||
"did not insert duplicate action entry with name \"%s\"",
|
||||
|
||||
@@ -12,6 +12,7 @@ struct MacroActionInfo {
|
||||
std::function<std::shared_ptr<MacroAction>(Macro *m)> _create = nullptr;
|
||||
CreateActionWidget _createWidget = nullptr;
|
||||
std::string _name;
|
||||
bool _hidden = false;
|
||||
};
|
||||
|
||||
class MacroActionFactory {
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include <QPushButton>
|
||||
#include <QHBoxLayout>
|
||||
#include <QTimer>
|
||||
#include <QListWidget>
|
||||
|
||||
namespace advss {
|
||||
|
||||
|
||||
@@ -67,9 +67,7 @@ void SetMacroSwitchedScene(bool value)
|
||||
{
|
||||
static bool setupDone = false;
|
||||
if (!setupDone) {
|
||||
// Will always be called with switcher lock already held
|
||||
AddIntervalResetStep([]() { macroSceneSwitched = false; },
|
||||
false);
|
||||
AddIntervalResetStep([]() { macroSceneSwitched = false; });
|
||||
setupDone = true;
|
||||
}
|
||||
macroSceneSwitched = value;
|
||||
|
||||
@@ -108,6 +108,24 @@ static const std::string setTempVarValueDeclString =
|
||||
tempVarIdParam.data() + ", in string " + valueParam.data() +
|
||||
", in int " + GetInstanceIdParamName().data() + ")";
|
||||
|
||||
/* Plugin status */
|
||||
|
||||
static constexpr std::string_view stopSignalName = "advss_plugin_stopped";
|
||||
static constexpr std::string_view startSignalName = "advss_plugin_started";
|
||||
static constexpr std::string_view getRunningStatusFuncName =
|
||||
"advss_plugin_running";
|
||||
static constexpr std::string_view resetIntervalSignalName =
|
||||
"advss_interval_reset";
|
||||
|
||||
static const std::string stopSignalDeclString =
|
||||
std::string("void ") + stopSignalName.data() + "()";
|
||||
static const std::string startSignalDeclString =
|
||||
std::string("void ") + startSignalName.data() + "()";
|
||||
static const std::string resetIntervalDeclString =
|
||||
std::string("void ") + resetIntervalSignalName.data() + "()";
|
||||
static const std::string getRunningStatusDeclString =
|
||||
std::string("bool ") + getRunningStatusFuncName.data() + "()";
|
||||
|
||||
static bool setup();
|
||||
static bool setupDone = setup();
|
||||
|
||||
@@ -134,6 +152,25 @@ static bool setup()
|
||||
&ScriptHandler::DeregisterAllTempVars, nullptr);
|
||||
proc_handler_add(ph, setTempVarValueDeclString.c_str(),
|
||||
&ScriptHandler::SetTempVarValue, nullptr);
|
||||
proc_handler_add(ph, getRunningStatusDeclString.c_str(),
|
||||
&ScriptHandler::GetRunningStatus, nullptr);
|
||||
|
||||
auto sh = obs_get_signal_handler();
|
||||
signal_handler_add(sh, stopSignalDeclString.c_str());
|
||||
signal_handler_add(sh, startSignalDeclString.c_str());
|
||||
signal_handler_add(sh, resetIntervalDeclString.c_str());
|
||||
|
||||
static constexpr auto triggerSignal = [](const std::string_view &name) {
|
||||
auto sh = obs_get_signal_handler();
|
||||
struct calldata data;
|
||||
calldata_init(&data);
|
||||
signal_handler_signal(sh, name.data(), &data);
|
||||
calldata_free(&data);
|
||||
};
|
||||
|
||||
AddIntervalResetStep([]() { triggerSignal(resetIntervalSignalName); });
|
||||
AddStartStep([]() { triggerSignal(startSignalName); });
|
||||
AddStopStep([]() { triggerSignal(stopSignalName); });
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -476,6 +513,11 @@ void ScriptHandler::SetVariableValue(void *, calldata_t *data)
|
||||
RETURN_SUCCESS();
|
||||
}
|
||||
|
||||
void ScriptHandler::GetRunningStatus(void *ctx, calldata_t *data)
|
||||
{
|
||||
calldata_set_bool(data, "is_running", PluginIsRunning());
|
||||
}
|
||||
|
||||
void ScriptHandler::RegisterTempVar(void *, calldata_t *data)
|
||||
{
|
||||
const char *variableId;
|
||||
|
||||
@@ -32,6 +32,7 @@ public:
|
||||
static void DeregisterAllTempVars(void *ctx, calldata_t *data);
|
||||
static void SetTempVarValue(void *ctx, calldata_t *data);
|
||||
static void SetVariableValue(void *ctx, calldata_t *data);
|
||||
static void GetRunningStatus(void *ctx, calldata_t *data);
|
||||
static bool ActionIdIsValid(const std::string &id);
|
||||
static bool ConditionIdIsValid(const std::string &id);
|
||||
|
||||
|
||||
@@ -168,17 +168,6 @@ void SwitcherData::SaveVersion(obs_data_t *obj,
|
||||
obs_data_set_string(obj, "version", currentVersion.c_str());
|
||||
}
|
||||
|
||||
void SwitcherData::AddIntervalResetStep(std::function<void()> function,
|
||||
bool tryLock)
|
||||
{
|
||||
if (!tryLock) {
|
||||
resetIntervalSteps.emplace_back(function);
|
||||
return;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
resetIntervalSteps.emplace_back(function);
|
||||
}
|
||||
|
||||
void SwitcherData::RunPostLoadSteps()
|
||||
{
|
||||
for (const auto &func : postLoadSteps) {
|
||||
|
||||
@@ -57,11 +57,9 @@ public:
|
||||
bool AnySceneTransitionStarted();
|
||||
|
||||
void SetPreconditions();
|
||||
void ResetForNextInterval();
|
||||
void AddSaveStep(std::function<void(obs_data_t *)>);
|
||||
void AddLoadStep(std::function<void(obs_data_t *)>);
|
||||
void AddPostLoadStep(std::function<void()>);
|
||||
void AddIntervalResetStep(std::function<void()>, bool lock = true);
|
||||
void RunPostLoadSteps();
|
||||
bool CheckForMatch(OBSWeakSource &scene, OBSWeakSource &transition,
|
||||
int &linger, bool &setPreviousSceneAsMatch,
|
||||
@@ -100,7 +98,6 @@ public:
|
||||
std::vector<std::function<void(obs_data_t *)>> saveSteps;
|
||||
std::vector<std::function<void(obs_data_t *)>> loadSteps;
|
||||
std::vector<std::function<void()>> postLoadSteps;
|
||||
std::vector<std::function<void()>> resetIntervalSteps;
|
||||
|
||||
bool firstBoot = true;
|
||||
bool transitionActive = false;
|
||||
@@ -233,7 +230,6 @@ public:
|
||||
bool checkPause();
|
||||
void checkDefaultSceneTransitions();
|
||||
void writeSceneInfoToFile();
|
||||
void writeToStatusFile(const QString &msg);
|
||||
void checkSwitchCooldown(bool &match);
|
||||
|
||||
std::deque<WindowSwitch> windowSwitches;
|
||||
|
||||
@@ -32,7 +32,9 @@ void PreventMouseWheelAdjustWithoutFocus(QWidget *w)
|
||||
QString(w->metaObject()->className()) ==
|
||||
"advss::OSCMessageElementEdit" ||
|
||||
QString(w->metaObject()->className()) ==
|
||||
"advss::ChatMessagePropertyEdit") {
|
||||
"advss::ChatMessagePropertyEdit" ||
|
||||
QString(w->metaObject()->className()) ==
|
||||
"advss::KeyValueListContainerWidget") {
|
||||
return;
|
||||
}
|
||||
w->setFocusPolicy(Qt::StrongFocus);
|
||||
|
||||
@@ -67,6 +67,8 @@ NonModalMessageDialog::NonModalMessageDialog(const QString &message, Type type,
|
||||
}
|
||||
case Type::INPUT: {
|
||||
_inputEdit = new ResizingPlainTextEdit(this);
|
||||
_inputEdit->setTabChangesFocus(true);
|
||||
_inputEdit->setFocus();
|
||||
connect(_inputEdit, &ResizingPlainTextEdit::textChanged, this,
|
||||
&NonModalMessageDialog::InputChanged);
|
||||
layout->addWidget(_inputEdit);
|
||||
|
||||
@@ -21,6 +21,24 @@ static std::vector<std::function<void()>> &getPluginCleanupSteps()
|
||||
return steps;
|
||||
}
|
||||
|
||||
static std::vector<std::function<void()>> &getResetIntervalSteps()
|
||||
{
|
||||
static std::vector<std::function<void()>> steps;
|
||||
return steps;
|
||||
}
|
||||
|
||||
static std::vector<std::function<void()>> &getStartSteps()
|
||||
{
|
||||
static std::vector<std::function<void()>> steps;
|
||||
return steps;
|
||||
}
|
||||
|
||||
static std::vector<std::function<void()>> &getStopSteps()
|
||||
{
|
||||
static std::vector<std::function<void()>> steps;
|
||||
return steps;
|
||||
}
|
||||
|
||||
static std::mutex mutex;
|
||||
|
||||
void SavePluginSettings(obs_data_t *obj)
|
||||
@@ -48,9 +66,10 @@ void AddPostLoadStep(std::function<void()> step)
|
||||
GetSwitcher()->AddPostLoadStep(step);
|
||||
}
|
||||
|
||||
void AddIntervalResetStep(std::function<void()> step, bool lock)
|
||||
void AddIntervalResetStep(std::function<void()> step)
|
||||
{
|
||||
GetSwitcher()->AddIntervalResetStep(step, lock);
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
getResetIntervalSteps().emplace_back(step);
|
||||
}
|
||||
|
||||
void RunPostLoadSteps()
|
||||
@@ -100,6 +119,42 @@ void RunPluginCleanupSteps()
|
||||
}
|
||||
}
|
||||
|
||||
void RunIntervalResetSteps()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
for (const auto &step : getResetIntervalSteps()) {
|
||||
step();
|
||||
}
|
||||
}
|
||||
|
||||
void AddStartStep(std::function<void()> step)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
getStartSteps().emplace_back(step);
|
||||
}
|
||||
|
||||
void AddStopStep(std::function<void()> step)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
getStopSteps().emplace_back(step);
|
||||
}
|
||||
|
||||
void RunStartSteps()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
for (const auto &step : getStartSteps()) {
|
||||
step();
|
||||
}
|
||||
}
|
||||
|
||||
void RunStopSteps()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
for (const auto &step : getStopSteps()) {
|
||||
step();
|
||||
}
|
||||
}
|
||||
|
||||
void StopPlugin()
|
||||
{
|
||||
GetSwitcher()->Stop();
|
||||
|
||||
@@ -11,7 +11,7 @@ EXPORT void LoadPluginSettings(obs_data_t *);
|
||||
EXPORT void AddSaveStep(std::function<void(obs_data_t *)>);
|
||||
EXPORT void AddLoadStep(std::function<void(obs_data_t *)>);
|
||||
EXPORT void AddPostLoadStep(std::function<void()>);
|
||||
EXPORT void AddIntervalResetStep(std::function<void()>, bool lock = true);
|
||||
EXPORT void AddIntervalResetStep(std::function<void()>);
|
||||
EXPORT void RunPostLoadSteps();
|
||||
|
||||
EXPORT void AddPluginInitStep(std::function<void()>);
|
||||
@@ -25,6 +25,11 @@ EXPORT void StopPlugin();
|
||||
EXPORT void StartPlugin();
|
||||
EXPORT bool PluginIsRunning();
|
||||
EXPORT int GetIntervalValue();
|
||||
void AddStartStep(std::function<void()>);
|
||||
void AddStopStep(std::function<void()>);
|
||||
void RunStartSteps();
|
||||
void RunStopSteps();
|
||||
void RunIntervalResetSteps();
|
||||
|
||||
enum class NoMatchBehavior { NO_SWITCH = 0, SWITCH = 1, RANDOM_SWITCH = 2 };
|
||||
EXPORT void SetPluginNoMatchBehavior(NoMatchBehavior);
|
||||
|
||||
@@ -23,6 +23,14 @@ static bool setupDone = setup();
|
||||
bool setup()
|
||||
{
|
||||
AddPluginPostLoadStep(registerWebsocketVendor);
|
||||
AddStartStep([]() {
|
||||
SendWebsocketVendorEvent("AdvancedSceneSwitcherStarted",
|
||||
nullptr);
|
||||
});
|
||||
AddStopStep([]() {
|
||||
SendWebsocketVendorEvent("AdvancedSceneSwitcherStopped",
|
||||
nullptr);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -109,6 +117,9 @@ void RegisterWebsocketRequest(
|
||||
|
||||
void SendWebsocketVendorEvent(const std::string &eventName, obs_data_t *data)
|
||||
{
|
||||
if (OBSIsShuttingDown()) {
|
||||
return;
|
||||
}
|
||||
obs_websocket_vendor_emit_event(vendor, eventName.c_str(), data);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ install_advss_plugin_dependency(...)
|
||||
... to install the plugin and its dependencies.
|
||||
#]]
|
||||
|
||||
add_plugin(http)
|
||||
add_plugin(midi)
|
||||
add_plugin(openvr)
|
||||
add_plugin(stream-deck)
|
||||
|
||||
@@ -15,8 +15,8 @@ target_sources(
|
||||
macro-action-filter.hpp
|
||||
macro-action-hotkey.cpp
|
||||
macro-action-hotkey.hpp
|
||||
macro-action-http.cpp
|
||||
macro-action-http.hpp
|
||||
macro-action-http-legacy.cpp
|
||||
macro-action-http-legacy.hpp
|
||||
macro-action-log.cpp
|
||||
macro-action-log.hpp
|
||||
macro-action-media.cpp
|
||||
|
||||
@@ -449,8 +449,11 @@ void MacroActionFilterEdit::SetWidgetVisibility()
|
||||
_entryData->_action == MacroActionFilter::Action::SETTINGS &&
|
||||
_entryData->_settingsInputMethod ==
|
||||
MacroActionFilter::SettingsInputMethod::JSON_STRING);
|
||||
_getSettings->setVisible(_entryData->_action ==
|
||||
MacroActionFilter::Action::SETTINGS);
|
||||
_getSettings->setVisible(
|
||||
_entryData->_action == MacroActionFilter::Action::SETTINGS &&
|
||||
_entryData->_settingsInputMethod !=
|
||||
MacroActionFilter::SettingsInputMethod::
|
||||
INDIVIDUAL_TEMPVAR);
|
||||
_tempVars->setVisible(_entryData->_action ==
|
||||
MacroActionFilter::Action::SETTINGS &&
|
||||
_entryData->_settingsInputMethod ==
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "macro-action-http.hpp"
|
||||
#include "macro-action-http-legacy.hpp"
|
||||
#include "curl-helper.hpp"
|
||||
#include "layout-helpers.hpp"
|
||||
|
||||
@@ -9,7 +9,7 @@ const std::string MacroActionHttp::id = "http";
|
||||
bool MacroActionHttp::_registered = MacroActionFactory::Register(
|
||||
MacroActionHttp::id,
|
||||
{MacroActionHttp::Create, MacroActionHttpEdit::Create,
|
||||
"AdvSceneSwitcher.action.http"});
|
||||
"AdvSceneSwitcher.action.http.legacy", true});
|
||||
|
||||
const static std::map<MacroActionHttp::Method, std::string> methods = {
|
||||
{MacroActionHttp::Method::GET, "AdvSceneSwitcher.action.http.type.get"},
|
||||
@@ -34,11 +34,11 @@ const static std::map<PluginStateAction, std::string> actionTypes = {
|
||||
|
||||
const static std::map<NoMatchBehavior, std::string> noMatchValues = {
|
||||
{NoMatchBehavior::NO_SWITCH,
|
||||
"AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.dontSwitch"},
|
||||
"AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.dontSwitch"},
|
||||
{NoMatchBehavior::SWITCH,
|
||||
"AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchTo"},
|
||||
"AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.switchTo"},
|
||||
{NoMatchBehavior::RANDOM_SWITCH,
|
||||
"AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchToRandom"},
|
||||
"AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.switchToRandom"},
|
||||
};
|
||||
|
||||
static void stopPlugin()
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include "source-helpers.hpp"
|
||||
|
||||
#include <obs-frontend-api.h>
|
||||
#include <QApplication>
|
||||
#include <QWindow>
|
||||
|
||||
namespace advss {
|
||||
|
||||
@@ -28,8 +30,34 @@ const static std::map<MacroActionProjector::Type, std::string> selectionTypes =
|
||||
"AdvSceneSwitcher.action.projector.type.multiview"},
|
||||
};
|
||||
|
||||
static void closeOBSProjectorWindows(const std::string expectedWindowTitle,
|
||||
const RegexConfig ®ex)
|
||||
{
|
||||
for (QWindow *widget : QApplication::allWindows()) {
|
||||
if (!widget->property("isOBSProjectorWindow").toBool()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const windowTitle = widget->title().toStdString();
|
||||
if (!regex.Enabled() && expectedWindowTitle != windowTitle) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!regex.Matches(windowTitle, expectedWindowTitle)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
widget->close();
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionProjector::PerformAction()
|
||||
{
|
||||
if (_action == Action::CLOSE) {
|
||||
closeOBSProjectorWindows(_projectorWindowName, _regex);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string name = "";
|
||||
const char *type = "";
|
||||
|
||||
@@ -78,10 +106,16 @@ bool MacroActionProjector::PerformAction()
|
||||
|
||||
void MacroActionProjector::LogAction() const
|
||||
{
|
||||
if (_action == Action::CLOSE) {
|
||||
ablog(LOG_INFO, "closing projector window \"%s\"",
|
||||
_projectorWindowName.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = selectionTypes.find(_type);
|
||||
if (it != selectionTypes.end()) {
|
||||
ablog(LOG_INFO,
|
||||
"performed projector action \"%s\" with"
|
||||
"open projector \"%s\" with"
|
||||
"source \"%s\","
|
||||
"scene \"%s\","
|
||||
"monitor %d",
|
||||
@@ -96,24 +130,30 @@ void MacroActionProjector::LogAction() const
|
||||
bool MacroActionProjector::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
obs_data_set_int(obj, "action", static_cast<int>(_action));
|
||||
obs_data_set_int(obj, "type", static_cast<int>(_type));
|
||||
obs_data_set_int(obj, "monitor", _monitor);
|
||||
obs_data_set_string(obj, "monitorName", _monitorName.c_str());
|
||||
obs_data_set_bool(obj, "fullscreen", _fullscreen);
|
||||
_scene.Save(obj);
|
||||
_source.Save(obj);
|
||||
_projectorWindowName.Save(obj, "projectorWindowName");
|
||||
_regex.Save(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionProjector::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
|
||||
_type = static_cast<Type>(obs_data_get_int(obj, "type"));
|
||||
_monitor = obs_data_get_int(obj, "monitor");
|
||||
_monitorName = obs_data_get_string(obj, "monitorName");
|
||||
_fullscreen = obs_data_get_bool(obj, "fullscreen");
|
||||
_scene.Load(obj);
|
||||
_source.Load(obj);
|
||||
_projectorWindowName.Load(obj, "projectorWindowName");
|
||||
_regex.Load(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -131,6 +171,7 @@ void MacroActionProjector::ResolveVariablesToFixedValues()
|
||||
{
|
||||
_source.ResolveVariables();
|
||||
_scene.ResolveVariables();
|
||||
_projectorWindowName.ResolveVariables();
|
||||
}
|
||||
|
||||
void MacroActionProjector::SetMonitor(int idx)
|
||||
@@ -166,14 +207,22 @@ bool MacroActionProjector::MonitorSetupChanged() const
|
||||
QString::fromStdString(_monitorName);
|
||||
}
|
||||
|
||||
static inline void populateSelectionTypes(QComboBox *list)
|
||||
static void populateActionSelection(QComboBox *list)
|
||||
{
|
||||
for (auto entry : selectionTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
list->addItem(obs_module_text(
|
||||
"AdvSceneSwitcher.action.projector.action.open"));
|
||||
list->addItem(obs_module_text(
|
||||
"AdvSceneSwitcher.action.projector.action.close"));
|
||||
}
|
||||
|
||||
static void populateSelectionTypes(QComboBox *list)
|
||||
{
|
||||
for (const auto &[_, name] : selectionTypes) {
|
||||
list->addItem(obs_module_text(name.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void populateWindowTypes(QComboBox *list)
|
||||
static void populateWindowTypes(QComboBox *list)
|
||||
{
|
||||
list->addItem(
|
||||
obs_module_text("AdvSceneSwitcher.action.projector.windowed"));
|
||||
@@ -184,14 +233,18 @@ static inline void populateWindowTypes(QComboBox *list)
|
||||
MacroActionProjectorEdit::MacroActionProjectorEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionProjector> entryData)
|
||||
: QWidget(parent),
|
||||
_windowTypes(new QComboBox()),
|
||||
_actions(new QComboBox()),
|
||||
_types(new QComboBox()),
|
||||
_windowTypes(new QComboBox()),
|
||||
_scenes(new SceneSelectionWidget(window(), true, false, true, true,
|
||||
true)),
|
||||
_sources(new SourceSelectionWidget(window(), QStringList(), true)),
|
||||
_monitorSelection(new QHBoxLayout()),
|
||||
_monitors(new QComboBox())
|
||||
_monitors(new QComboBox()),
|
||||
_projectorWindowName(new VariableLineEdit(this)),
|
||||
_regex(new RegexConfigWidget(this)),
|
||||
_layout(new QHBoxLayout(this))
|
||||
{
|
||||
populateActionSelection(_actions);
|
||||
populateWindowTypes(_windowTypes);
|
||||
populateSelectionTypes(_types);
|
||||
auto sources = GetSourceNames();
|
||||
@@ -201,6 +254,8 @@ MacroActionProjectorEdit::MacroActionProjectorEdit(
|
||||
_monitors->setPlaceholderText(
|
||||
obs_module_text("AdvSceneSwitcher.selectDisplay"));
|
||||
|
||||
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ActionChanged(int)));
|
||||
QWidget::connect(_windowTypes, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(WindowTypeChanged(int)));
|
||||
QWidget::connect(_types, SIGNAL(currentIndexChanged(int)), this,
|
||||
@@ -212,24 +267,15 @@ MacroActionProjectorEdit::MacroActionProjectorEdit(
|
||||
SLOT(SourceChanged(const SourceSelection &)));
|
||||
QWidget::connect(_monitors, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(MonitorChanged(int)));
|
||||
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{windowTypes}}", _windowTypes}, {"{{types}}", _types},
|
||||
{"{{scenes}}", _scenes}, {"{{sources}}", _sources},
|
||||
{"{{monitors}}", _monitors},
|
||||
};
|
||||
|
||||
PlaceWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.action.projector.entry.monitor"),
|
||||
_monitorSelection, widgetPlaceholders);
|
||||
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout;
|
||||
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.projector.entry"),
|
||||
mainLayout, widgetPlaceholders);
|
||||
mainLayout->insertLayout(mainLayout->count() - 1, _monitorSelection);
|
||||
setLayout(mainLayout);
|
||||
QWidget::connect(_projectorWindowName, SIGNAL(editingFinished()), this,
|
||||
SLOT(ProjectorWindowNameChanged()));
|
||||
QWidget::connect(_regex,
|
||||
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
|
||||
SLOT(RegexChanged(const RegexConfig &)));
|
||||
|
||||
_entryData = entryData;
|
||||
SetWidgetLayout();
|
||||
setLayout(_layout);
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
@@ -239,11 +285,15 @@ void MacroActionProjectorEdit::UpdateEntryData()
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
|
||||
_windowTypes->setCurrentIndex(_entryData->_fullscreen ? 1 : 0);
|
||||
_types->setCurrentIndex(static_cast<int>(_entryData->_type));
|
||||
_scenes->SetScene(_entryData->_scene);
|
||||
_sources->SetSource(_entryData->_source);
|
||||
_monitors->setCurrentIndex(_entryData->GetMonitor());
|
||||
_projectorWindowName->setText(_entryData->_projectorWindowName);
|
||||
_regex->SetRegexConfig(_entryData->_regex);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
@@ -265,12 +315,26 @@ void MacroActionProjectorEdit::MonitorChanged(int value)
|
||||
_entryData->SetMonitor(value);
|
||||
}
|
||||
|
||||
void MacroActionProjectorEdit::ProjectorWindowNameChanged()
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_projectorWindowName =
|
||||
_projectorWindowName->text().toStdString();
|
||||
}
|
||||
|
||||
void MacroActionProjectorEdit::RegexChanged(const RegexConfig ®ex)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_regex = regex;
|
||||
}
|
||||
|
||||
void MacroActionProjectorEdit::WindowTypeChanged(int)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_fullscreen =
|
||||
_windowTypes->currentText() ==
|
||||
obs_module_text("AdvSceneSwitcher.action.projector.fullscreen");
|
||||
SetWidgetLayout();
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
@@ -281,17 +345,69 @@ void MacroActionProjectorEdit::TypeChanged(int value)
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionProjectorEdit::ActionChanged(int idx)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_action = static_cast<MacroActionProjector::Action>(idx);
|
||||
SetWidgetLayout();
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionProjectorEdit::SetWidgetLayout()
|
||||
{
|
||||
const std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{actions}}", _actions},
|
||||
{"{{windowTypes}}", _windowTypes},
|
||||
{"{{types}}", _types},
|
||||
{"{{scenes}}", _scenes},
|
||||
{"{{sources}}", _sources},
|
||||
{"{{monitors}}", _monitors},
|
||||
{"{{projectorWindowName}}", _projectorWindowName},
|
||||
{"{{regex}}", _regex},
|
||||
};
|
||||
|
||||
for (const auto &[_, widget] : widgetPlaceholders) {
|
||||
_layout->removeWidget(widget);
|
||||
}
|
||||
ClearLayout(_layout);
|
||||
|
||||
const char *layoutText;
|
||||
if (_entryData->_action == MacroActionProjector::Action::CLOSE) {
|
||||
layoutText = "AdvSceneSwitcher.action.projector.entry.close";
|
||||
} else if (_entryData->_fullscreen) {
|
||||
layoutText =
|
||||
"AdvSceneSwitcher.action.projector.entry.open.fullscreen";
|
||||
} else {
|
||||
layoutText =
|
||||
"AdvSceneSwitcher.action.projector.entry.open.windowed";
|
||||
}
|
||||
|
||||
PlaceWidgets(obs_module_text(layoutText), _layout, widgetPlaceholders);
|
||||
}
|
||||
|
||||
void MacroActionProjectorEdit::SetWidgetVisibility()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_scenes->setVisible(_entryData->_type ==
|
||||
MacroActionProjector::Type::SCENE);
|
||||
_sources->setVisible(_entryData->_type ==
|
||||
MacroActionProjector::Type::SOURCE);
|
||||
SetLayoutVisible(_monitorSelection, _entryData->_fullscreen);
|
||||
_projectorWindowName->setVisible(_entryData->_action ==
|
||||
MacroActionProjector::Action::CLOSE);
|
||||
_regex->setVisible(_entryData->_action ==
|
||||
MacroActionProjector::Action::CLOSE);
|
||||
_types->setVisible(_entryData->_action ==
|
||||
MacroActionProjector::Action::OPEN);
|
||||
_windowTypes->setVisible(_entryData->_action ==
|
||||
MacroActionProjector::Action::OPEN);
|
||||
_scenes->setVisible(
|
||||
_entryData->_action == MacroActionProjector::Action::OPEN &&
|
||||
_entryData->_type == MacroActionProjector::Type::SCENE);
|
||||
_sources->setVisible(
|
||||
_entryData->_action == MacroActionProjector::Action::OPEN &&
|
||||
_entryData->_type == MacroActionProjector::Type::SOURCE);
|
||||
_monitors->setVisible(_entryData->_action ==
|
||||
MacroActionProjector::Action::OPEN &&
|
||||
_entryData->_fullscreen);
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "regex-config.hpp"
|
||||
#include "scene-selection.hpp"
|
||||
#include "source-selection.hpp"
|
||||
#include "variable-line-edit.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
@@ -19,6 +21,11 @@ public:
|
||||
void SetMonitor(int);
|
||||
int GetMonitor() const;
|
||||
|
||||
enum class Action {
|
||||
OPEN,
|
||||
CLOSE,
|
||||
};
|
||||
|
||||
enum class Type {
|
||||
SOURCE,
|
||||
SCENE,
|
||||
@@ -27,10 +34,13 @@ public:
|
||||
MULTIVIEW,
|
||||
};
|
||||
|
||||
Action _action = Action::OPEN;
|
||||
Type _type = Type::SCENE;
|
||||
SourceSelection _source;
|
||||
SceneSelection _scene;
|
||||
bool _fullscreen = true;
|
||||
StringVariable _projectorWindowName = "Windowed Projector";
|
||||
RegexConfig _regex = RegexConfig::PartialMatchRegexConfig(true);
|
||||
|
||||
private:
|
||||
bool MonitorSetupChanged() const;
|
||||
@@ -60,21 +70,28 @@ public:
|
||||
}
|
||||
|
||||
private slots:
|
||||
void ActionChanged(int value);
|
||||
void WindowTypeChanged(int value);
|
||||
void TypeChanged(int value);
|
||||
void SceneChanged(const SceneSelection &);
|
||||
void SourceChanged(const SourceSelection &);
|
||||
void MonitorChanged(int value);
|
||||
void ProjectorWindowNameChanged();
|
||||
void RegexChanged(const RegexConfig &);
|
||||
|
||||
private:
|
||||
void SetWidgetLayout();
|
||||
void SetWidgetVisibility();
|
||||
|
||||
QComboBox *_windowTypes;
|
||||
QComboBox *_actions;
|
||||
QComboBox *_types;
|
||||
QComboBox *_windowTypes;
|
||||
SceneSelectionWidget *_scenes;
|
||||
SourceSelectionWidget *_sources;
|
||||
QHBoxLayout *_monitorSelection;
|
||||
QComboBox *_monitors;
|
||||
VariableLineEdit *_projectorWindowName;
|
||||
RegexConfigWidget *_regex;
|
||||
QHBoxLayout *_layout;
|
||||
|
||||
std::shared_ptr<MacroActionProjector> _entryData;
|
||||
bool _loading = true;
|
||||
|
||||
@@ -42,7 +42,7 @@ const static std::map<obs_deinterlace_mode, std::string> deinterlaceModes = {
|
||||
{OBS_DEINTERLACE_MODE_DISABLE,
|
||||
"AdvSceneSwitcher.action.source.deinterlaceMode.disable"},
|
||||
{OBS_DEINTERLACE_MODE_DISCARD,
|
||||
"AdvSceneSwitcher.action.source.deinterlaceMode.disable"},
|
||||
"AdvSceneSwitcher.action.source.deinterlaceMode.discard"},
|
||||
{OBS_DEINTERLACE_MODE_RETRO,
|
||||
"AdvSceneSwitcher.action.source.deinterlaceMode.retro"},
|
||||
{OBS_DEINTERLACE_MODE_BLEND,
|
||||
@@ -526,8 +526,11 @@ void MacroActionSourceEdit::SetWidgetVisibility()
|
||||
_entryData->_action == MacroActionSource::Action::SETTINGS &&
|
||||
_entryData->_settingsInputMethod ==
|
||||
MacroActionSource::SettingsInputMethod::JSON_STRING);
|
||||
_getSettings->setVisible(_entryData->_action ==
|
||||
MacroActionSource::Action::SETTINGS);
|
||||
_getSettings->setVisible(
|
||||
_entryData->_action == MacroActionSource::Action::SETTINGS &&
|
||||
_entryData->_settingsInputMethod !=
|
||||
MacroActionSource::SettingsInputMethod::
|
||||
INDIVIDUAL_TEMPVAR);
|
||||
_tempVars->setVisible(_entryData->_action ==
|
||||
MacroActionSource::Action::SETTINGS &&
|
||||
_entryData->_settingsInputMethod ==
|
||||
|
||||
@@ -442,6 +442,13 @@ void MacroConditionFileEdit::SetWidgetVisibility()
|
||||
_entryData->_onlyMatchIfChanged &&
|
||||
_entryData->GetCondition() ==
|
||||
MacroConditionFile::Condition::MATCH);
|
||||
|
||||
// TODO: Remove remote file support in future version in favor of HTTP
|
||||
// action.
|
||||
// Hide the option for now, if it is not used already.
|
||||
_fileTypes->setVisible(_entryData->_fileType ==
|
||||
MacroConditionFile::FileType::REMOTE);
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
@@ -41,12 +41,12 @@ public:
|
||||
|
||||
StringVariable _file = obs_module_text("AdvSceneSwitcher.enterPath");
|
||||
StringVariable _text = obs_module_text("AdvSceneSwitcher.enterText");
|
||||
FileType _fileType = FileType::LOCAL;
|
||||
RegexConfig _regex;
|
||||
|
||||
// TODO: Remove in future version
|
||||
bool _useTime = false;
|
||||
bool _onlyMatchIfChanged = false;
|
||||
FileType _fileType = FileType::LOCAL;
|
||||
|
||||
private:
|
||||
bool MatchFileContent(QString &filedata);
|
||||
|
||||
77
plugins/http/CMakeLists.txt
Normal file
77
plugins/http/CMakeLists.txt
Normal file
@@ -0,0 +1,77 @@
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(advanced-scene-switcher-http)
|
||||
|
||||
# --- Check requirements ---
|
||||
|
||||
get_target_property(ADVSS_SOURCE_DIR advanced-scene-switcher-lib SOURCE_DIR)
|
||||
set(CPP_HTTPLIB_DIR "${ADVSS_SOURCE_DIR}/deps/cpp-httplib")
|
||||
if(NOT EXISTS "${CPP_HTTPLIB_DIR}/CMakeLists.txt")
|
||||
message(WARNING "cpp-httplib directory \"${CPP_HTTPLIB_DIR}\" not found!\n"
|
||||
"HTTP support will be disabled!")
|
||||
return()
|
||||
endif()
|
||||
if(NOT TARGET httplib)
|
||||
add_subdirectory("${CPP_HTTPLIB_DIR}" "${CPP_HTTPLIB_DIR}/build"
|
||||
EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
if(NOT OPENSSL_INCLUDE_DIR OR NOT OPENSSL_LIBRARIES)
|
||||
find_package(OpenSSL)
|
||||
if(NOT OPENSSL_FOUND)
|
||||
message(WARNING "OpenSSL not found!\n" "HTTP support will be disabled!\n\n")
|
||||
return()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
find_package(ZLIB)
|
||||
if(NOT ZLIB_FOUND)
|
||||
message(WARNING "zlib not found!\n" "HTTP support will be disabled!\n\n")
|
||||
return()
|
||||
endif()
|
||||
|
||||
# --- End of section ---
|
||||
|
||||
add_library(${PROJECT_NAME} MODULE)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT=1)
|
||||
if(OS_MACOS)
|
||||
target_compile_definitions(
|
||||
${PROJECT_NAME} PRIVATE CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN=1)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE "-framework CoreFoundation")
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE "-framework Security")
|
||||
endif()
|
||||
|
||||
target_sources(
|
||||
${PROJECT_NAME} PRIVATE macro-action-http.cpp macro-action-http.hpp
|
||||
key-value-list.cpp key-value-list.hpp)
|
||||
|
||||
setup_advss_plugin(${PROJECT_NAME})
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE "${CPP_HTTPLIB_DIR}/"
|
||||
"${OPENSSL_INCLUDE_DIR}")
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE ${OPENSSL_LIBRARIES} ZLIB::ZLIB)
|
||||
install_advss_plugin(${PROJECT_NAME})
|
||||
if(OS_WINDOWS)
|
||||
# Couldn't really find a better way to install runtime dependencies for
|
||||
# Windows TODO: Clean this up at some point
|
||||
function(FIND_FILES_WITH_PATTERN result pattern dir)
|
||||
execute_process(
|
||||
COMMAND
|
||||
powershell -Command
|
||||
"Get-ChildItem -Path '${dir}' -Recurse -Include ${pattern} |"
|
||||
"Select-Object -First 1 |"
|
||||
"ForEach-Object { $_.FullName -replace '\\\\', '\\\\' }"
|
||||
OUTPUT_VARIABLE files
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
set(${result}
|
||||
${files}
|
||||
PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
set(OPENSSL_DLL_SEARCH_DIR "${OPENSSL_INCLUDE_DIR}/..")
|
||||
find_files_with_pattern(CRYPTO_DLL_FILES "libcrypto*.dll"
|
||||
"${OPENSSL_DLL_SEARCH_DIR}")
|
||||
find_files_with_pattern(SSL_DLL_FILES "libssl*.dll"
|
||||
"${OPENSSL_DLL_SEARCH_DIR}")
|
||||
install_advss_plugin_dependency(TARGET ${PROJECT_NAME} DEPENDENCIES
|
||||
"${CRYPTO_DLL_FILES}" "${SSL_DLL_FILES}")
|
||||
endif()
|
||||
213
plugins/http/key-value-list.cpp
Normal file
213
plugins/http/key-value-list.cpp
Normal file
@@ -0,0 +1,213 @@
|
||||
#include "key-value-list.hpp"
|
||||
#include "name-dialog.hpp"
|
||||
#include "ui-helpers.hpp"
|
||||
|
||||
#include <QLayout>
|
||||
#include <QTimer>
|
||||
|
||||
namespace advss {
|
||||
|
||||
KeyValueListEdit::KeyValueListEdit(QWidget *parent, const QString &addKeyString,
|
||||
const QString &addKeyStringDescription,
|
||||
const QString &addValueString,
|
||||
const QString &addValueStringDescription)
|
||||
: ListEditor(parent),
|
||||
_addKeyString(addKeyString),
|
||||
_addKeyStringDescription(addKeyStringDescription),
|
||||
_addValueString(addValueString),
|
||||
_addValueStringDescription(addValueStringDescription)
|
||||
{
|
||||
_list->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
_list->setAutoScroll(false);
|
||||
}
|
||||
|
||||
void KeyValueListEdit::SetStringList(const StringList &list)
|
||||
{
|
||||
_stringList = list;
|
||||
_list->clear();
|
||||
|
||||
for (int i = 0; i < list.size(); i += 2) {
|
||||
AppendListEntryWidget(
|
||||
list.at(i), i + 1 >= list.size() ? "" : list.at(i + 1));
|
||||
}
|
||||
UpdateListSize();
|
||||
}
|
||||
|
||||
void KeyValueListEdit::Add()
|
||||
{
|
||||
StringVariable key;
|
||||
StringVariable value;
|
||||
bool accepted = AskForKeyValue(key, value);
|
||||
if (!accepted) {
|
||||
return;
|
||||
}
|
||||
|
||||
AppendListEntryWidget(key, value);
|
||||
_stringList << key << value;
|
||||
|
||||
// Delay resizing to make sure the list viewport was already updated
|
||||
QTimer::singleShot(0, this, [this]() { UpdateListSize(); });
|
||||
|
||||
StringListChanged(_stringList);
|
||||
}
|
||||
|
||||
void KeyValueListEdit::Remove()
|
||||
{
|
||||
int idx = _list->currentRow();
|
||||
if (idx == -1) {
|
||||
return;
|
||||
}
|
||||
_stringList.removeAt(idx);
|
||||
|
||||
QListWidgetItem *item = _list->currentItem();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
delete item;
|
||||
|
||||
// Delay resizing to make sure the list viewport was already updated
|
||||
QTimer::singleShot(0, this, [this]() { UpdateListSize(); });
|
||||
|
||||
StringListChanged(_stringList);
|
||||
}
|
||||
|
||||
void KeyValueListEdit::Up()
|
||||
{
|
||||
int idx = _list->currentRow();
|
||||
if (idx <= 0 || idx >= _list->count()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MoveStringListIdxUp(idx);
|
||||
|
||||
auto row = _list->itemWidget(_list->currentItem());
|
||||
auto newItem = _list->currentItem()->clone();
|
||||
|
||||
_list->insertItem(idx - 1, newItem);
|
||||
_list->setItemWidget(newItem, row);
|
||||
|
||||
_list->takeItem(idx + 1);
|
||||
_list->setCurrentRow(idx - 1);
|
||||
|
||||
UpdateListSize();
|
||||
|
||||
StringListChanged(_stringList);
|
||||
}
|
||||
|
||||
void KeyValueListEdit::Down()
|
||||
{
|
||||
int idx = _list->currentRow();
|
||||
if (idx == -1 || idx == _list->count() - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
MoveStringListIdxUp(idx + 1);
|
||||
|
||||
auto row = _list->itemWidget(_list->currentItem());
|
||||
auto newItem = _list->currentItem()->clone();
|
||||
|
||||
_list->insertItem(idx + 2, newItem);
|
||||
_list->setItemWidget(newItem, row);
|
||||
|
||||
_list->takeItem(idx);
|
||||
_list->setCurrentRow(idx + 1);
|
||||
|
||||
UpdateListSize();
|
||||
|
||||
StringListChanged(_stringList);
|
||||
}
|
||||
|
||||
void KeyValueListEdit::Clicked(QListWidgetItem *item)
|
||||
{
|
||||
int idx = _list->currentRow();
|
||||
|
||||
StringVariable key = _stringList[idx * 2];
|
||||
StringVariable value = _stringList[idx * 2 + 1];
|
||||
bool accepted = AskForKeyValue(key, value);
|
||||
if (!accepted) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto container = static_cast<KeyValueListContainerWidget *>(
|
||||
_list->itemWidget(item));
|
||||
container->_key->setText(QString::fromStdString(key.UnresolvedValue()));
|
||||
container->_value->setText(
|
||||
QString::fromStdString(value.UnresolvedValue()));
|
||||
container->adjustSize();
|
||||
container->updateGeometry();
|
||||
|
||||
_stringList[idx * 2] = key;
|
||||
_stringList[idx * 2 + 1] = value;
|
||||
|
||||
// Delay resizing to make sure the list viewport was already updated
|
||||
QTimer::singleShot(0, this, [this]() { UpdateListSize(); });
|
||||
StringListChanged(_stringList);
|
||||
}
|
||||
|
||||
void KeyValueListEdit::MoveStringListIdxUp(int idx)
|
||||
{
|
||||
if (idx <= 0 || idx >= _list->count()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_stringList.move(idx * 2, idx * 2 - 2);
|
||||
_stringList.move(idx * 2 + 1, idx * 2 - 1);
|
||||
}
|
||||
|
||||
bool KeyValueListEdit::AskForKeyValue(StringVariable &keyVariable,
|
||||
StringVariable &valueVariable)
|
||||
{
|
||||
std::string key;
|
||||
bool accepted = NameDialog::AskForName(
|
||||
this, _addKeyString, _addKeyStringDescription, key,
|
||||
QString::fromStdString(keyVariable.UnresolvedValue()), 4096,
|
||||
false);
|
||||
if (!accepted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string value;
|
||||
accepted = NameDialog::AskForName(
|
||||
this, _addValueString, _addValueStringDescription, value,
|
||||
QString::fromStdString(valueVariable.UnresolvedValue()), 4096,
|
||||
false);
|
||||
if (!accepted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
keyVariable = key;
|
||||
valueVariable = value;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void KeyValueListEdit::AppendListEntryWidget(const StringVariable &key,
|
||||
const StringVariable &value)
|
||||
{
|
||||
QListWidgetItem *item = new QListWidgetItem(_list);
|
||||
auto container = new KeyValueListContainerWidget(this, _list->count());
|
||||
container->_key->setText(QString::fromStdString(key.UnresolvedValue()));
|
||||
container->_value->setText(
|
||||
QString::fromStdString(value.UnresolvedValue()));
|
||||
container->adjustSize();
|
||||
container->updateGeometry();
|
||||
_list->addItem(item);
|
||||
_list->setItemWidget(item, container);
|
||||
UpdateListSize();
|
||||
}
|
||||
|
||||
KeyValueListContainerWidget::KeyValueListContainerWidget(QWidget *parent,
|
||||
int index)
|
||||
: QWidget(parent),
|
||||
_key(new QLabel("Key", this)),
|
||||
_value(new QLabel("Value", this)),
|
||||
_index(index)
|
||||
{
|
||||
auto layout = new QHBoxLayout();
|
||||
layout->addWidget(_key);
|
||||
layout->addWidget(_value);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
53
plugins/http/key-value-list.hpp
Normal file
53
plugins/http/key-value-list.hpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
#include "string-list.hpp"
|
||||
#include "variable-line-edit.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
class KeyValueListEdit final : public ListEditor {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
KeyValueListEdit(QWidget *parent, const QString &addKeyString,
|
||||
const QString &addKeyStringDescription,
|
||||
const QString &addValueString,
|
||||
const QString &addValueStringDescription);
|
||||
void SetStringList(const StringList &);
|
||||
|
||||
private slots:
|
||||
void Add();
|
||||
void Remove();
|
||||
void Up();
|
||||
void Down();
|
||||
void Clicked(QListWidgetItem *);
|
||||
signals:
|
||||
void StringListChanged(const StringList &);
|
||||
|
||||
private:
|
||||
void MoveStringListIdxUp(int);
|
||||
bool AskForKeyValue(StringVariable &key, StringVariable &value);
|
||||
void AppendListEntryWidget(const StringVariable &key,
|
||||
const StringVariable &value);
|
||||
|
||||
StringList _stringList;
|
||||
QString _addKeyString;
|
||||
QString _addKeyStringDescription;
|
||||
QString _addValueString;
|
||||
QString _addValueStringDescription;
|
||||
};
|
||||
|
||||
class KeyValueListContainerWidget final : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
KeyValueListContainerWidget(QWidget *parent, int index);
|
||||
|
||||
private:
|
||||
QLabel *_key;
|
||||
QLabel *_value;
|
||||
|
||||
int _index = -1;
|
||||
friend class KeyValueListEdit;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
463
plugins/http/macro-action-http.cpp
Normal file
463
plugins/http/macro-action-http.cpp
Normal file
@@ -0,0 +1,463 @@
|
||||
#include "macro-action-http.hpp"
|
||||
#include "layout-helpers.hpp"
|
||||
|
||||
#include <httplib.h>
|
||||
|
||||
#undef DELETE
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionHttp::id = "http_v2";
|
||||
|
||||
bool MacroActionHttp::_registered = MacroActionFactory::Register(
|
||||
MacroActionHttp::id,
|
||||
{MacroActionHttp::Create, MacroActionHttpEdit::Create,
|
||||
"AdvSceneSwitcher.action.http"});
|
||||
|
||||
static httplib::Headers getHeaders(const StringList &strings)
|
||||
{
|
||||
httplib::Headers headers;
|
||||
for (int i = 0; i < strings.size(); i = i + 2) {
|
||||
const auto pair =
|
||||
i + 1 >= strings.size()
|
||||
? std::make_pair(std::string(strings.at(i)), "")
|
||||
: std::make_pair(
|
||||
std::string(strings.at(i)),
|
||||
std::string(strings.at(i + 1)));
|
||||
headers.emplace(pair);
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
static httplib::Params getParams(const StringList &strings)
|
||||
{
|
||||
httplib::Params params;
|
||||
for (int i = 0; i < strings.size(); i = i + 2) {
|
||||
const auto pair =
|
||||
i + 1 >= strings.size()
|
||||
? std::make_pair(std::string(strings.at(i)), "")
|
||||
: std::make_pair(
|
||||
std::string(strings.at(i)),
|
||||
std::string(strings.at(i + 1)));
|
||||
params.emplace(pair);
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
static void setTimeout(httplib::Client &client, const Duration &timeout)
|
||||
{
|
||||
const time_t seconds = timeout.Seconds();
|
||||
const time_t usecs = timeout.Milliseconds() * 1000;
|
||||
client.set_read_timeout(seconds, usecs);
|
||||
client.set_write_timeout(seconds, usecs);
|
||||
}
|
||||
|
||||
void MacroActionHttp::SetupTempVars()
|
||||
{
|
||||
MacroAction::SetupTempVars();
|
||||
AddTempvar("status",
|
||||
obs_module_text("AdvSceneSwitcher.tempVar.http.status"));
|
||||
AddTempvar("body",
|
||||
obs_module_text("AdvSceneSwitcher.tempVar.http.body"));
|
||||
AddTempvar("error",
|
||||
obs_module_text("AdvSceneSwitcher.tempVar.http.error"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.http.error.description"));
|
||||
}
|
||||
|
||||
bool MacroActionHttp::PerformAction()
|
||||
{
|
||||
httplib::Client cli(_url);
|
||||
setTimeout(cli, _timeout);
|
||||
const auto params = _setParams ? getParams(_params) : httplib::Params();
|
||||
const auto headers = _setHeaders ? getHeaders(_headers)
|
||||
: httplib::Headers();
|
||||
|
||||
httplib::Result response;
|
||||
switch (_method) {
|
||||
case MacroActionHttp::Method::GET:
|
||||
response = cli.Get(_path, params, headers);
|
||||
break;
|
||||
case MacroActionHttp::Method::POST: {
|
||||
const auto path = httplib::append_query_params(_path, params);
|
||||
response = cli.Post(path, headers, _body, _contentType);
|
||||
break;
|
||||
}
|
||||
case MacroActionHttp::Method::PUT: {
|
||||
const auto path = httplib::append_query_params(_path, params);
|
||||
response = cli.Put(path, headers, _body, _contentType);
|
||||
break;
|
||||
}
|
||||
case MacroActionHttp::Method::PATCH: {
|
||||
const auto path = httplib::append_query_params(_path, params);
|
||||
response = cli.Patch(path, headers, _body, _contentType);
|
||||
break;
|
||||
}
|
||||
case MacroActionHttp::Method::DELETE: {
|
||||
const auto path = httplib::append_query_params(_path, params);
|
||||
response = cli.Delete(path, headers, _body, _contentType);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
SetTempVarValue("status",
|
||||
response ? std::to_string(response->status) : "");
|
||||
SetTempVarValue("body", response ? response->body : "");
|
||||
SetTempVarValue("error",
|
||||
response ? "" : httplib::to_string(response.error()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static constexpr std::string_view methodToString(MacroActionHttp::Method method)
|
||||
{
|
||||
switch (method) {
|
||||
case MacroActionHttp::Method::GET:
|
||||
return "GET";
|
||||
case MacroActionHttp::Method::POST:
|
||||
return "POST";
|
||||
case MacroActionHttp::Method::PUT:
|
||||
return "PUT";
|
||||
case MacroActionHttp::Method::PATCH:
|
||||
return "PATCH";
|
||||
case MacroActionHttp::Method::DELETE:
|
||||
return "DELETE";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
static std::string stringListToString(const StringList &list)
|
||||
{
|
||||
if (list.empty()) {
|
||||
return "[]";
|
||||
}
|
||||
std::string result = "[";
|
||||
for (const auto &string : list) {
|
||||
result += std::string(string) + ", ";
|
||||
}
|
||||
result.pop_back();
|
||||
return result + "]";
|
||||
}
|
||||
|
||||
void MacroActionHttp::LogAction() const
|
||||
{
|
||||
ablog(LOG_INFO,
|
||||
"sent HTTP request (%s) "
|
||||
"to URL \"%s\" "
|
||||
"to path \"%s\" "
|
||||
"with content type \"%s\" "
|
||||
"with body \"%s\" "
|
||||
"with headers \"%s\" "
|
||||
"with parameters \"%s\" "
|
||||
"with timeout \"%s\"",
|
||||
methodToString(_method).data(), _url.c_str(), _path.c_str(),
|
||||
_contentType.c_str(), _body.c_str(),
|
||||
_setHeaders ? stringListToString(_headers).c_str() : "-",
|
||||
_setParams ? stringListToString(_params).c_str() : "-",
|
||||
_timeout.ToString().c_str());
|
||||
}
|
||||
|
||||
bool MacroActionHttp::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
_url.Save(obj, "url");
|
||||
_path.Save(obj, "path");
|
||||
_contentType.Save(obj, "contentType");
|
||||
_body.Save(obj, "body");
|
||||
obs_data_set_bool(obj, "setHeaders", _setHeaders);
|
||||
_headers.Save(obj, "headers", "header");
|
||||
obs_data_set_bool(obj, "setParams", _setParams);
|
||||
_params.Save(obj, "params", "param");
|
||||
obs_data_set_int(obj, "method", static_cast<int>(_method));
|
||||
_timeout.Save(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionHttp::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_url.Load(obj, "url");
|
||||
_path.Load(obj, "path");
|
||||
_contentType.Load(obj, "contentType");
|
||||
_body.Load(obj, "body");
|
||||
_setHeaders = obs_data_get_bool(obj, "setHeaders");
|
||||
_headers.Load(obj, "headers", "header");
|
||||
_setParams = obs_data_get_bool(obj, "setParams");
|
||||
_params.Load(obj, "params", "param");
|
||||
_method = static_cast<Method>(obs_data_get_int(obj, "method"));
|
||||
_timeout.Load(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroActionHttp::GetShortDesc() const
|
||||
{
|
||||
return _url.UnresolvedValue();
|
||||
}
|
||||
|
||||
std::shared_ptr<MacroAction> MacroActionHttp::Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionHttp>(m);
|
||||
}
|
||||
|
||||
std::shared_ptr<MacroAction> MacroActionHttp::Copy() const
|
||||
{
|
||||
return std::make_shared<MacroActionHttp>(*this);
|
||||
}
|
||||
|
||||
void MacroActionHttp::ResolveVariablesToFixedValues()
|
||||
{
|
||||
_url.ResolveVariables();
|
||||
_path.ResolveVariables();
|
||||
_contentType.ResolveVariables();
|
||||
_body.ResolveVariables();
|
||||
_headers.ResolveVariables();
|
||||
_params.ResolveVariables();
|
||||
_timeout.ResolveVariables();
|
||||
}
|
||||
|
||||
static inline void populateMethodSelection(QComboBox *list)
|
||||
{
|
||||
const static std::map<MacroActionHttp::Method, std::string> methods = {
|
||||
{MacroActionHttp::Method::GET,
|
||||
"AdvSceneSwitcher.action.http.type.get"},
|
||||
{MacroActionHttp::Method::POST,
|
||||
"AdvSceneSwitcher.action.http.type.post"},
|
||||
{MacroActionHttp::Method::PUT,
|
||||
"AdvSceneSwitcher.action.http.type.put"},
|
||||
{MacroActionHttp::Method::PATCH,
|
||||
"AdvSceneSwitcher.action.http.type.patch"},
|
||||
{MacroActionHttp::Method::DELETE,
|
||||
"AdvSceneSwitcher.action.http.type.delete"},
|
||||
};
|
||||
|
||||
for (const auto &[value, name] : methods) {
|
||||
list->addItem(obs_module_text(name.c_str()),
|
||||
static_cast<int>(value));
|
||||
}
|
||||
}
|
||||
|
||||
MacroActionHttpEdit::MacroActionHttpEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionHttp> entryData)
|
||||
: QWidget(parent),
|
||||
_url(new VariableLineEdit(this)),
|
||||
_path(new VariableLineEdit(this)),
|
||||
_contentType(new VariableLineEdit(this)),
|
||||
_contentTypeLayout(new QHBoxLayout()),
|
||||
_methods(new QComboBox()),
|
||||
_body(new VariableTextEdit(this)),
|
||||
_bodyLayout(new QVBoxLayout()),
|
||||
_setHeaders(new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.action.http.setHeaders"))),
|
||||
_headerList(new KeyValueListEdit(
|
||||
this, obs_module_text("AdvSceneSwitcher.action.http.headers"),
|
||||
obs_module_text("AdvSceneSwitcher.action.http.addHeader.name"),
|
||||
obs_module_text("AdvSceneSwitcher.action.http.headers"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.http.addHeader.value"))),
|
||||
_headerListLayout(new QVBoxLayout()),
|
||||
_setParams(new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.action.http.setParams"))),
|
||||
_paramList(new KeyValueListEdit(
|
||||
this, obs_module_text("AdvSceneSwitcher.action.http.params"),
|
||||
obs_module_text("AdvSceneSwitcher.action.http.addParam.name"),
|
||||
obs_module_text("AdvSceneSwitcher.action.http.params"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.http.addParam.value"))),
|
||||
_paramListLayout(new QVBoxLayout()),
|
||||
_timeout(new DurationSelection(this, false))
|
||||
{
|
||||
populateMethodSelection(_methods);
|
||||
|
||||
SetWidgetSignalConnections();
|
||||
SetWidgetLayout();
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionHttpEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_url->setText(_entryData->_url);
|
||||
_path->setText(_entryData->_path);
|
||||
_contentType->setText(_entryData->_contentType);
|
||||
_body->setPlainText(_entryData->_body);
|
||||
_setHeaders->setChecked(_entryData->_setHeaders);
|
||||
_headerList->SetStringList(_entryData->_headers);
|
||||
_setParams->setChecked(_entryData->_setParams);
|
||||
_paramList->SetStringList(_entryData->_params);
|
||||
_methods->setCurrentIndex(
|
||||
_methods->findData(static_cast<int>(_entryData->_method)));
|
||||
_timeout->SetDuration(_entryData->_timeout);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionHttpEdit::URLChanged()
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_url = _url->text().toStdString();
|
||||
emit(HeaderInfoChanged(_url->text()));
|
||||
}
|
||||
|
||||
void MacroActionHttpEdit::PathChanged()
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_path = _path->text().toStdString();
|
||||
}
|
||||
|
||||
void MacroActionHttpEdit::ContentTypeChanged()
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_contentType = _contentType->text().toStdString();
|
||||
}
|
||||
|
||||
void MacroActionHttpEdit::BodyChanged()
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_body = _body->toPlainText().toUtf8().constData();
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionHttpEdit::MethodChanged(int idx)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_method = static_cast<MacroActionHttp::Method>(
|
||||
_methods->itemData(idx).toInt());
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionHttpEdit::TimeoutChanged(const Duration &dur)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_timeout = dur;
|
||||
}
|
||||
|
||||
void MacroActionHttpEdit::SetHeadersChanged(int value)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_setHeaders = value;
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionHttpEdit::HeadersChanged(const StringList &headers)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_headers = headers;
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionHttpEdit::SetParamsChanged(int value)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_setParams = value;
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionHttpEdit::ParamsChanged(const StringList ¶ms)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_params = params;
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionHttpEdit::SetWidgetSignalConnections()
|
||||
{
|
||||
QWidget::connect(_url, SIGNAL(editingFinished()), this,
|
||||
SLOT(URLChanged()));
|
||||
QWidget::connect(_path, SIGNAL(editingFinished()), this,
|
||||
SLOT(PathChanged()));
|
||||
QWidget::connect(_contentType, SIGNAL(editingFinished()), this,
|
||||
SLOT(ContentTypeChanged()));
|
||||
QWidget::connect(_body, SIGNAL(textChanged()), this,
|
||||
SLOT(BodyChanged()));
|
||||
QWidget::connect(_methods, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(MethodChanged(int)));
|
||||
QWidget::connect(_setHeaders, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(SetHeadersChanged(int)));
|
||||
QWidget::connect(_headerList,
|
||||
SIGNAL(StringListChanged(const StringList &)), this,
|
||||
SLOT(HeadersChanged(const StringList &)));
|
||||
QWidget::connect(_setParams, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(SetParamsChanged(int)));
|
||||
QWidget::connect(_paramList,
|
||||
SIGNAL(StringListChanged(const StringList &)), this,
|
||||
SLOT(ParamsChanged(const StringList &)));
|
||||
QWidget::connect(_timeout, SIGNAL(DurationChanged(const Duration &)),
|
||||
this, SLOT(TimeoutChanged(const Duration &)));
|
||||
}
|
||||
|
||||
void MacroActionHttpEdit::SetWidgetLayout()
|
||||
{
|
||||
const std::unordered_map<std::string, QWidget *> widgets = {
|
||||
{"{{url}}", _url},
|
||||
{"{{path}}", _path},
|
||||
{"{{contentType}}", _contentType},
|
||||
{"{{method}}", _methods},
|
||||
{"{{body}}", _body},
|
||||
{"{{timeout}}", _timeout},
|
||||
};
|
||||
|
||||
auto actionLayout = new QHBoxLayout;
|
||||
PlaceWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.action.http.layout.method"),
|
||||
actionLayout, widgets);
|
||||
|
||||
PlaceWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.action.http.layout.contentType"),
|
||||
_contentTypeLayout, widgets);
|
||||
|
||||
_bodyLayout->addWidget(new QLabel(
|
||||
obs_module_text("AdvSceneSwitcher.action.http.body")));
|
||||
_bodyLayout->addWidget(_body);
|
||||
|
||||
auto timeoutLayout = new QHBoxLayout;
|
||||
PlaceWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.action.http.layout.timeout"),
|
||||
timeoutLayout, widgets);
|
||||
|
||||
_headerListLayout->addWidget(new QLabel(
|
||||
obs_module_text("AdvSceneSwitcher.action.http.headers")));
|
||||
_headerListLayout->addWidget(_headerList);
|
||||
|
||||
_paramListLayout->addWidget(new QLabel(
|
||||
obs_module_text("AdvSceneSwitcher.action.http.params")));
|
||||
_paramListLayout->addWidget(_paramList);
|
||||
|
||||
auto layout = new QVBoxLayout;
|
||||
layout->addLayout(actionLayout);
|
||||
layout->addWidget(_setHeaders);
|
||||
layout->addLayout(_headerListLayout);
|
||||
layout->addWidget(_setParams);
|
||||
layout->addLayout(_paramListLayout);
|
||||
layout->addLayout(_contentTypeLayout);
|
||||
layout->addLayout(_bodyLayout);
|
||||
layout->addLayout(timeoutLayout);
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void MacroActionHttpEdit::SetWidgetVisibility()
|
||||
{
|
||||
SetLayoutVisible(_headerListLayout, _entryData->_setHeaders);
|
||||
SetLayoutVisible(_paramListLayout, _entryData->_setParams);
|
||||
SetLayoutVisible(_contentTypeLayout,
|
||||
_entryData->_method != MacroActionHttp::Method::GET);
|
||||
SetLayoutVisible(_bodyLayout,
|
||||
_entryData->_method != MacroActionHttp::Method::GET);
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
107
plugins/http/macro-action-http.hpp
Normal file
107
plugins/http/macro-action-http.hpp
Normal file
@@ -0,0 +1,107 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "key-value-list.hpp"
|
||||
#include "variable-text-edit.hpp"
|
||||
#include "variable-line-edit.hpp"
|
||||
#include "duration-control.hpp"
|
||||
|
||||
#include <QLineEdit>
|
||||
#include <QComboBox>
|
||||
#include <QCheckBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroActionHttp final : public MacroAction {
|
||||
public:
|
||||
MacroActionHttp(Macro *m) : MacroAction(m, true) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string GetShortDesc() const;
|
||||
std::string GetId() const { return id; };
|
||||
static std::shared_ptr<MacroAction> Create(Macro *m);
|
||||
std::shared_ptr<MacroAction> Copy() const;
|
||||
void ResolveVariablesToFixedValues();
|
||||
|
||||
enum class Method {
|
||||
GET = 0,
|
||||
POST,
|
||||
PUT,
|
||||
PATCH,
|
||||
DELETE,
|
||||
};
|
||||
|
||||
StringVariable _url = "127.0.0.1:8080";
|
||||
StringVariable _path = "/";
|
||||
StringVariable _body = obs_module_text("AdvSceneSwitcher.enterText");
|
||||
StringVariable _contentType = "application/json";
|
||||
bool _setHeaders = false;
|
||||
StringList _headers;
|
||||
bool _setParams = false;
|
||||
StringList _params;
|
||||
Method _method = Method::GET;
|
||||
Duration _timeout = Duration(1.0);
|
||||
|
||||
private:
|
||||
void SetupTempVars();
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionHttpEdit final : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionHttpEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionHttp> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionHttpEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionHttp>(action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void URLChanged();
|
||||
void PathChanged();
|
||||
void BodyChanged();
|
||||
void ContentTypeChanged();
|
||||
void MethodChanged(int);
|
||||
void TimeoutChanged(const Duration &seconds);
|
||||
void SetHeadersChanged(int);
|
||||
void HeadersChanged(const StringList &);
|
||||
void SetParamsChanged(int);
|
||||
void ParamsChanged(const StringList &);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
private:
|
||||
void SetWidgetSignalConnections();
|
||||
void SetWidgetLayout();
|
||||
void SetWidgetVisibility();
|
||||
|
||||
VariableLineEdit *_url;
|
||||
VariableLineEdit *_path;
|
||||
VariableLineEdit *_contentType;
|
||||
QHBoxLayout *_contentTypeLayout;
|
||||
QComboBox *_methods;
|
||||
VariableTextEdit *_body;
|
||||
QVBoxLayout *_bodyLayout;
|
||||
QCheckBox *_setHeaders;
|
||||
KeyValueListEdit *_headerList;
|
||||
QVBoxLayout *_headerListLayout;
|
||||
QCheckBox *_setParams;
|
||||
KeyValueListEdit *_paramList;
|
||||
QVBoxLayout *_paramListLayout;
|
||||
DurationSelection *_timeout;
|
||||
|
||||
std::shared_ptr<MacroActionHttp> _entryData;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
@@ -19,9 +19,22 @@ add_library(${PROJECT_NAME} MODULE)
|
||||
|
||||
if(OS_WINDOWS)
|
||||
if(MSVC)
|
||||
target_compile_options(libremidi PRIVATE /wd4251 /wd4267 /wd4275 /wd4101
|
||||
/wd4244 /wd4018)
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE /wd4101 /wd4244 /wd4018)
|
||||
target_compile_options(
|
||||
libremidi
|
||||
PRIVATE /wd4018
|
||||
/wd4068
|
||||
/wd4100
|
||||
/wd4101
|
||||
/wd4189
|
||||
/wd4244
|
||||
/wd4251
|
||||
/wd4267
|
||||
/wd4275
|
||||
/wd4389
|
||||
/wd4505
|
||||
/wd4702)
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE /wd4017 /wd4068 /wd4100
|
||||
/wd4101 /wd4244)
|
||||
endif()
|
||||
else()
|
||||
target_compile_options(
|
||||
|
||||
@@ -10,8 +10,10 @@ if(NOT EXISTS "${CPP_HTTPLIB_DIR}/CMakeLists.txt")
|
||||
"Twitch support will be disabled!")
|
||||
return()
|
||||
endif()
|
||||
add_subdirectory("${CPP_HTTPLIB_DIR}" "${CPP_HTTPLIB_DIR}/build"
|
||||
EXCLUDE_FROM_ALL)
|
||||
if(NOT TARGET httplib)
|
||||
add_subdirectory("${CPP_HTTPLIB_DIR}" "${CPP_HTTPLIB_DIR}/build"
|
||||
EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
if(NOT OPENSSL_INCLUDE_DIR OR NOT OPENSSL_LIBRARIES)
|
||||
find_package(OpenSSL)
|
||||
|
||||
@@ -826,7 +826,7 @@ MacroActionTwitchEdit::MacroActionTwitchEdit(
|
||||
_userInfoQueryType(new QComboBox(this)),
|
||||
_userLogin(new VariableLineEdit(this)),
|
||||
_userId(new VariableSpinBox(this)),
|
||||
_pointsReward(new TwitchPointsRewardWidget(this)),
|
||||
_pointsReward(new TwitchPointsRewardWidget(this, false)),
|
||||
_rewardVariable(new VariableSelection(this)),
|
||||
_toggleRewardSelection(new QPushButton())
|
||||
{
|
||||
|
||||
@@ -1450,7 +1450,7 @@ MacroConditionTwitchEdit::MacroConditionTwitchEdit(
|
||||
_tokens(new TwitchConnectionSelection()),
|
||||
_tokenWarning(new QLabel()),
|
||||
_channel(new TwitchChannelSelection(this)),
|
||||
_pointsReward(new TwitchPointsRewardWidget(this)),
|
||||
_pointsReward(new TwitchPointsRewardWidget(this, true)),
|
||||
_streamTitle(new VariableLineEdit(this)),
|
||||
_regexTitle(new RegexConfigWidget(parent)),
|
||||
_chatMesageEdit(new ChatMessageEdit(this)),
|
||||
|
||||
@@ -135,9 +135,10 @@ void TwitchPointsRewardSelection::SelectionChanged(int index)
|
||||
emit PointsRewardChanged(pointsReward);
|
||||
}
|
||||
|
||||
TwitchPointsRewardWidget::TwitchPointsRewardWidget(QWidget *parent)
|
||||
TwitchPointsRewardWidget::TwitchPointsRewardWidget(QWidget *parent,
|
||||
bool allowAny)
|
||||
: QWidget(parent),
|
||||
_selection(new TwitchPointsRewardSelection(this)),
|
||||
_selection(new TwitchPointsRewardSelection(this, allowAny)),
|
||||
_refreshButton(new QPushButton(this))
|
||||
{
|
||||
_refreshButton->setMaximumWidth(22);
|
||||
|
||||
@@ -20,7 +20,7 @@ class TwitchPointsRewardSelection : public FilterComboBox {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TwitchPointsRewardSelection(QWidget *parent, bool allowAny = true);
|
||||
TwitchPointsRewardSelection(QWidget *parent, bool allowAny);
|
||||
|
||||
void SetPointsReward(const TwitchPointsReward &pointsReward);
|
||||
void SetChannel(const TwitchChannel &channel);
|
||||
@@ -53,7 +53,7 @@ class TwitchPointsRewardWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TwitchPointsRewardWidget(QWidget *parent);
|
||||
TwitchPointsRewardWidget(QWidget *parent, bool allowAny);
|
||||
|
||||
void SetPointsReward(const TwitchPointsReward &pointsReward);
|
||||
void SetChannel(const TwitchChannel &channel);
|
||||
|
||||
@@ -29,17 +29,23 @@ public:
|
||||
_headers(headers)
|
||||
{
|
||||
}
|
||||
bool operator<(const Args &other) const
|
||||
bool operator==(const Args &other) const
|
||||
{
|
||||
bool ret = true;
|
||||
ret = ret && _uri < other._uri;
|
||||
ret = ret && _path < other._path;
|
||||
ret = ret && _params < other._params;
|
||||
ret = ret && _data < other._data;
|
||||
ret = ret && _headers < other._headers;
|
||||
ret = ret && _uri == other._uri;
|
||||
ret = ret && _path == other._path;
|
||||
ret = ret && _params == other._params;
|
||||
ret = ret && _data == other._data;
|
||||
ret = ret && _headers == other._headers;
|
||||
return ret;
|
||||
}
|
||||
|
||||
const std::string &uri() const { return _uri; }
|
||||
const std::string &path() const { return _path; }
|
||||
const std::string &data() const { return _data; }
|
||||
const httplib::Params ¶ms() const { return _params; }
|
||||
const httplib::Headers &headers() const { return _headers; }
|
||||
|
||||
private:
|
||||
std::string _uri;
|
||||
std::string _path;
|
||||
@@ -48,6 +54,37 @@ private:
|
||||
httplib::Headers _headers;
|
||||
};
|
||||
|
||||
}; // namespace advss
|
||||
|
||||
template<> struct std::hash<advss::Args> {
|
||||
inline std::size_t operator()(const advss::Args &args) const
|
||||
{
|
||||
static constexpr auto hash_combine = [](std::size_t &seed,
|
||||
std::size_t hashValue) {
|
||||
seed ^= hashValue + 0x9e3779b9 + (seed << 6) +
|
||||
(seed >> 2);
|
||||
};
|
||||
|
||||
std::size_t seed = 0;
|
||||
hash_combine(seed, std::hash<std::string>()(args.uri()));
|
||||
hash_combine(seed, std::hash<std::string>()(args.path()));
|
||||
hash_combine(seed, std::hash<std::string>()(args.data()));
|
||||
|
||||
for (const auto &[key, value] : args.params()) {
|
||||
hash_combine(seed, std::hash<std::string>()(key));
|
||||
hash_combine(seed, std::hash<std::string>()(value));
|
||||
}
|
||||
for (const auto &[key, value] : args.headers()) {
|
||||
hash_combine(seed, std::hash<std::string>()(key));
|
||||
hash_combine(seed, std::hash<std::string>()(value));
|
||||
}
|
||||
|
||||
return seed;
|
||||
}
|
||||
};
|
||||
|
||||
namespace advss {
|
||||
|
||||
struct CacheEntry {
|
||||
RequestResult result;
|
||||
std::chrono::system_clock::time_point cacheTime =
|
||||
@@ -61,14 +98,15 @@ static bool cacheIsTooOld(const CacheEntry &cache)
|
||||
return diff >= std::chrono::seconds(cacheTimeoutSeconds);
|
||||
}
|
||||
|
||||
static bool cacheIsValid(const std::map<Args, CacheEntry> &cache,
|
||||
static bool cacheIsValid(const std::unordered_map<Args, CacheEntry> &cache,
|
||||
const Args &args)
|
||||
{
|
||||
auto it = cache.find(args);
|
||||
return it != cache.end() && !cacheIsTooOld(it->second);
|
||||
}
|
||||
|
||||
static void cleanupCache(std::map<Args, CacheEntry> &cache, std::mutex &mtx)
|
||||
static void cleanupCache(std::unordered_map<Args, CacheEntry> &cache,
|
||||
std::mutex &mtx)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
cache.clear();
|
||||
@@ -181,7 +219,7 @@ RequestResult SendGetRequest(const TwitchToken &token, const std::string &uri,
|
||||
return {};
|
||||
}
|
||||
|
||||
static std::map<Args, CacheEntry> cache;
|
||||
static std::unordered_map<Args, CacheEntry> cache;
|
||||
static std::mutex mtx;
|
||||
[[maybe_unused]] static bool _ = []() {
|
||||
AddPluginCleanupStep([]() { cleanupCache(cache, mtx); });
|
||||
@@ -251,7 +289,7 @@ RequestResult SendPostRequest(const TwitchToken &token, const std::string &uri,
|
||||
return {};
|
||||
}
|
||||
|
||||
static std::map<Args, CacheEntry> cache;
|
||||
static std::unordered_map<Args, CacheEntry> cache;
|
||||
static std::mutex mtx;
|
||||
[[maybe_unused]] static bool _ = []() {
|
||||
AddPluginCleanupStep([]() { cleanupCache(cache, mtx); });
|
||||
@@ -322,7 +360,7 @@ RequestResult SendPutRequest(const TwitchToken &token, const std::string &uri,
|
||||
return {};
|
||||
}
|
||||
|
||||
static std::map<Args, CacheEntry> cache;
|
||||
static std::unordered_map<Args, CacheEntry> cache;
|
||||
static std::mutex mtx;
|
||||
[[maybe_unused]] static bool _ = []() {
|
||||
AddPluginCleanupStep([]() { cleanupCache(cache, mtx); });
|
||||
@@ -393,7 +431,7 @@ RequestResult SendPatchRequest(const TwitchToken &token, const std::string &uri,
|
||||
return {};
|
||||
}
|
||||
|
||||
static std::map<Args, CacheEntry> cache;
|
||||
static std::unordered_map<Args, CacheEntry> cache;
|
||||
static std::mutex mtx;
|
||||
[[maybe_unused]] static bool _ = []() {
|
||||
AddPluginCleanupStep([]() { cleanupCache(cache, mtx); });
|
||||
|
||||
@@ -148,6 +148,39 @@ def script_load(settings):
|
||||
],
|
||||
)
|
||||
|
||||
# Other signals and procedures, which might be useful
|
||||
def plugin_stop_handler(data):
|
||||
obs.script_log(obs.LOG_INFO, "Hello from stop handler!")
|
||||
get_running_status()
|
||||
|
||||
def plugin_start_handler(data):
|
||||
obs.script_log(obs.LOG_INFO, "Hello from start handler!")
|
||||
get_running_status()
|
||||
|
||||
def get_running_status():
|
||||
proc_handler = obs.obs_get_proc_handler()
|
||||
data = obs.calldata_create()
|
||||
obs.proc_handler_call(proc_handler, "advss_plugin_running", data)
|
||||
success = obs.calldata_bool(data, "is_running")
|
||||
obs.script_log(
|
||||
obs.LOG_INFO, f"The advanced scene switcher is currently running: {success}"
|
||||
)
|
||||
obs.calldata_destroy(data)
|
||||
|
||||
def interval_reset_handler(data):
|
||||
obs.script_log(obs.LOG_INFO, "Hello from reset handler!")
|
||||
|
||||
signal_handler = obs.obs_get_signal_handler()
|
||||
obs.signal_handler_connect(
|
||||
signal_handler, "advss_plugin_stopped", plugin_stop_handler
|
||||
)
|
||||
obs.signal_handler_connect(
|
||||
signal_handler, "advss_plugin_started", plugin_start_handler
|
||||
)
|
||||
obs.signal_handler_connect(
|
||||
signal_handler, "advss_interval_reset", interval_reset_handler
|
||||
)
|
||||
|
||||
|
||||
def script_unload():
|
||||
# Deregistering is useful if you plan on reloading the script files
|
||||
|
||||
Reference in New Issue
Block a user