Compare commits

..

1 Commits

Author SHA1 Message Date
WarmUpTill
21a00d47cc Add "Recording saved" option to Recording condition 2023-11-26 01:44:59 +01:00
84 changed files with 990 additions and 2555 deletions

View File

@@ -7,8 +7,3 @@ package 'ninja-build', bin: 'ninja'
package 'pkg-config'
package 'clang'
package 'clang-format-13'
package 'libxtst-dev'
package 'libxss-dev'
package 'libopencv-dev'
package 'libtesseract-dev'
package 'libprocps-dev'

View File

@@ -34,6 +34,13 @@ if (( ! (${skips[(Ie)all]} + ${skips[(Ie)deps]}) )) {
log_info 'Installing obs plugin dependencies...'
sudo apt-get install -y \
libxtst-dev \
libxss-dev \
libopencv-dev \
libtesseract-dev \
libprocps-dev
local -a _qt_packages=()
if (( QT_VERSION == 5 )) {

View File

@@ -93,8 +93,6 @@ target_sources(
${LIB_NAME}
PRIVATE src/macro-core/macro-action-audio.cpp
src/macro-core/macro-action-audio.hpp
src/macro-core/macro-action-clipboard.cpp
src/macro-core/macro-action-clipboard.hpp
src/macro-core/macro-action-edit.cpp
src/macro-core/macro-action-edit.hpp
src/macro-core/macro-action-file.cpp
@@ -256,8 +254,6 @@ target_sources(
src/utils/connection-manager.hpp
src/utils/curl-helper.cpp
src/utils/curl-helper.hpp
src/utils/duration.cpp
src/utils/duration.hpp
src/utils/duration-control.cpp
src/utils/duration-control.hpp
src/utils/export-symbol-helper.hpp
@@ -291,8 +287,6 @@ target_sources(
src/utils/obs-module-helper.hpp
src/utils/osc-helpers.cpp
src/utils/osc-helpers.hpp
src/utils/plugin-state-helper.cpp
src/utils/plugin-state-helper.hpp
src/utils/priority-helper.cpp
src/utils/priority-helper.hpp
src/utils/process-config.cpp
@@ -464,24 +458,27 @@ else()
find_path(PROCPS_INCLUDE_DIR NAMES proc/procps.h)
find_path(PROCPS2_INCLUDE_DIR NAMES libproc2/pids.h)
if(PROCPS_INCLUDE_DIR)
find_library(PROCPS_LIBRARY NAMES procps)
find_library(PROCPS2_LIBRARY NAMES proc2)
if(PROCPS_INCLUDE_DIR AND PROCPS_LIBRARY)
message(STATUS "${PROJECT_NAME} using procps")
set(PROC_INCLUDE_DIR "${PROCPS_INCLUDE_DIR}")
target_compile_definitions(${LIB_NAME} PRIVATE PROCPS_AVAILABLE)
set(PROCESS_CONDITION_SUPPORTED 1)
endif()
if(PROCPS2_INCLUDE_DIR)
set(PROC_LIBRARY "${PROCPS_LIBRARY}")
target_compile_definitions(${LIB_NAME} PRIVATE USE_PROCPS)
elseif(PROCPS2_INCLUDE_DIR AND PROCPS2_LIBRARY)
message(STATUS "${PROJECT_NAME} using libproc2")
set(PROC_INCLUDE_DIR "${PROCPS2_INCLUDE_DIR}")
target_compile_definitions(${LIB_NAME} PRIVATE PROCPS2_AVAILABLE)
set(PROCESS_CONDITION_SUPPORTED 1)
endif()
if(NOT DEFINED PROCESS_CONDITION_SUPPORTED)
set(PROC_LIBRARY "${PROCPS2_LIBRARY}")
else()
message(
WARNING
"found neither procps nor libproc2! Process condition will not be functional!"
)
FATAL_ERROR
"found neither procps nor libproc2!
please either set ...
PROCPS_INCLUDE_DIR and PROCPS_LIBRARY
... or ...
PROCPS2_INCLUDE_DIR and PROCPS2_LIBRARY")
endif()
target_link_libraries(${LIB_NAME} PRIVATE "${PROC_LIBRARY}")
target_include_directories(${LIB_NAME} PRIVATE "${PROC_INCLUDE_DIR}")
find_package(CURL)

View File

@@ -221,8 +221,9 @@ AdvSceneSwitcher.condition.record="Aufnahme"
AdvSceneSwitcher.condition.record.state.start="Aufnahme läuft"
AdvSceneSwitcher.condition.record.state.pause="Aufnahme pausiert"
AdvSceneSwitcher.condition.record.state.stop="Aufnahme gestoppt"
AdvSceneSwitcher.condition.record.entry="{{recordState}}"
AdvSceneSwitcher.condition.process="Prozess"
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}läuft{{focused}}und ist fokusiert"
AdvSceneSwitcher.condition.process.entry="{{processes}} läuft {{focused}} und ist fokusiert"
AdvSceneSwitcher.condition.process.entry.focus="Aktueller Vordergrundprozess: {{focusProcess}}"
AdvSceneSwitcher.condition.idle="Leerlauf"
AdvSceneSwitcher.condition.idle.entry="Keine Tastatur- oder Mauseingaben für {{duration}}"

View File

@@ -79,7 +79,6 @@ AdvSceneSwitcher.macroTab.edit.action="Action type:"
AdvSceneSwitcher.macroTab.add="Add new macro"
AdvSceneSwitcher.macroTab.name="Name:"
AdvSceneSwitcher.macroTab.run="Run macro"
AdvSceneSwitcher.macroTab.run.tooltip="Run all macro actions regardless of condition.\nIf else actions are available, hold Shift and click the button to execute them instead."
AdvSceneSwitcher.macroTab.runElse="Run macro (else)"
AdvSceneSwitcher.macroTab.runFail="Running \"%1\" failed!\nEither one of the actions failed or the macro is running already."
AdvSceneSwitcher.macroTab.runInParallel="Run macro in parallel to other macros"
@@ -126,8 +125,6 @@ AdvSceneSwitcher.macroTab.currentDockButtonText.unpause="Unpause button text:"
AdvSceneSwitcher.macroTab.currentDockStatusText.true="Conditions true text:"
AdvSceneSwitcher.macroTab.currentDockStatusText.false="Conditions false text:"
AdvSceneSwitcher.macroTab.currentDockHighlightIfExecuted="Highlight dock if macro actions were recently executed"
AdvSceneSwitcher.macroTab.toggleElseActions.hide.tooltip="Hide else actions"
AdvSceneSwitcher.macroTab.toggleElseActions.show.tooltip="Show else actions"
AdvSceneSwitcher.macroDock.pause="Pause"
AdvSceneSwitcher.macroDock.unpause="Unpause"
@@ -298,10 +295,10 @@ AdvSceneSwitcher.condition.record="Recording"
AdvSceneSwitcher.condition.record.state.start="Recording running"
AdvSceneSwitcher.condition.record.state.pause="Recording paused"
AdvSceneSwitcher.condition.record.state.stop="Recording stopped"
AdvSceneSwitcher.condition.record.state.duration="Recording duration is longer than"
AdvSceneSwitcher.condition.record.entry="{{condition}}{{duration}}"
AdvSceneSwitcher.condition.record.state.saveDone="Recording saved"
AdvSceneSwitcher.condition.record.entry="{{recordState}}"
AdvSceneSwitcher.condition.process="Process"
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}is running{{focused}}and is focused"
AdvSceneSwitcher.condition.process.entry="{{processes}}is running{{focused}}and is focused"
AdvSceneSwitcher.condition.process.entry.focus="Current foreground process:{{focusProcess}}"
AdvSceneSwitcher.condition.idle="Idle"
AdvSceneSwitcher.condition.idle.entry="No keyboard or mouse inputs for{{duration}}"
@@ -330,7 +327,6 @@ AdvSceneSwitcher.condition.macro.type.state="Macro condition state"
AdvSceneSwitcher.condition.macro.type.multiState="Multiple macro condition state"
AdvSceneSwitcher.condition.macro.type.actionDisabled="Macro action disabled"
AdvSceneSwitcher.condition.macro.type.actionEnabled="Macro action enabled"
AdvSceneSwitcher.condition.macro.type.paused="Macro is paused"
AdvSceneSwitcher.condition.macro.type.selection="{{types}}"
AdvSceneSwitcher.condition.macro.count.type.below="Less than"
AdvSceneSwitcher.condition.macro.count.type.above="More than"
@@ -346,7 +342,6 @@ AdvSceneSwitcher.condition.macro.count.entry.line1="{{macros}}was executed{{cond
AdvSceneSwitcher.condition.macro.count.entry.line2="Current count:{{currentCount}}{{resetCount}}"
AdvSceneSwitcher.condition.macro.actionState.disabled.entry="Action{{actionIndex}}of{{macros}}is disabled"
AdvSceneSwitcher.condition.macro.actionState.enabled.entry="Action{{actionIndex}}of{{macros}}is enabled"
AdvSceneSwitcher.condition.macro.paused.entry="Macro{{macros}}is paused"
AdvSceneSwitcher.condition.source="Source"
AdvSceneSwitcher.condition.source.type.active="Is active"
AdvSceneSwitcher.condition.source.type.showing="Is showing"
@@ -858,15 +853,15 @@ AdvSceneSwitcher.action.sceneLock.type.unlock="unlock"
AdvSceneSwitcher.action.sceneLock.type.toggle="toggle lock of"
AdvSceneSwitcher.action.sceneLock.entry="On{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.twitch="Twitch"
AdvSceneSwitcher.action.twitch.type.channel.info.title.set="Set stream title"
AdvSceneSwitcher.action.twitch.type.channel.info.category.set="Set stream category"
AdvSceneSwitcher.action.twitch.type.raid.start="Start raid"
AdvSceneSwitcher.action.twitch.type.commercial.start="Start commercial"
AdvSceneSwitcher.action.twitch.type.marker.create="Create stream marker"
AdvSceneSwitcher.action.twitch.type.clip.create="Create stream clip"
AdvSceneSwitcher.action.twitch.type.chat.announcement.send="Send chat announcement"
AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.enable="Enable chat's emote-only mode"
AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.disable="Disable chat's emote-only mode"
AdvSceneSwitcher.action.twitch.type.title="Set stream title"
AdvSceneSwitcher.action.twitch.type.category="Set stream category"
AdvSceneSwitcher.action.twitch.type.marker="Create stream marker"
AdvSceneSwitcher.action.twitch.type.clip="Create stream clip"
AdvSceneSwitcher.action.twitch.type.commercial="Start commercial with duration"
AdvSceneSwitcher.action.twitch.type.announcement="Send chat announcement"
AdvSceneSwitcher.action.twitch.type.emoteOnlyEnable="Enable chat's emote-only mode"
AdvSceneSwitcher.action.twitch.type.emoteOnlyDisable="Disable chat's emote-only mode"
AdvSceneSwitcher.action.twitch.type.raid="Raid channel"
AdvSceneSwitcher.action.twitch.type.sendChatMessage="Send chat message"
AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Cannot select category without selecting a Twitch account first!"
AdvSceneSwitcher.action.twitch.entry.default="On{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}"
@@ -880,12 +875,6 @@ AdvSceneSwitcher.action.twitch.announcement.blue="Blue"
AdvSceneSwitcher.action.twitch.announcement.green="Green"
AdvSceneSwitcher.action.twitch.announcement.orange="Orange"
AdvSceneSwitcher.action.twitch.announcement.purple="Purple"
AdvSceneSwitcher.action.clipboard="Clipboard"
AdvSceneSwitcher.action.clipboard.type.copy.text="Copy text"
AdvSceneSwitcher.action.clipboard.type.copy.image="Copy image"
AdvSceneSwitcher.action.clipboard.copy.text.text.placeholder="Enter text"
AdvSceneSwitcher.action.clipboard.copy.image.url.placeholder="Enter direct image URL"
AdvSceneSwitcher.action.clipboard.copy.image.url.tooltip="Currently supported formats: PNG, JPG/JPEG, BMP, GIF."
; Hotkey
AdvSceneSwitcher.hotkey.startSwitcherHotkey="Start the Advanced Scene Switcher"
@@ -1039,10 +1028,14 @@ AdvSceneSwitcher.twitchToken.request.notSet="Account is not connected!"
AdvSceneSwitcher.twitchToken.permissions="Token permissions:"
AdvSceneSwitcher.twitchToken.permissionsInsufficient="Permissions of selected token are insufficient to perform selected action!"
AdvSceneSwitcher.twitchToken.channel.broadcast.manage="Manage a channels broadcast configuration, including updating channel configuration and managing stream markers and stream tags."
AdvSceneSwitcher.twitchToken.channel.commercial.edit="Run commercials on a channel."
AdvSceneSwitcher.twitchToken.clips.edit="Create clips from channel's broadcasts."
AdvSceneSwitcher.twitchToken.moderator.announcements.manage="Manage channel's chat announcements."
AdvSceneSwitcher.twitchToken.moderator.chat.settings.manage="Manage channel's chat settings, such as emote-only or slow mode."
AdvSceneSwitcher.twitchToken.channel.raid="Manage channel's raids."
AdvSceneSwitcher.twitchToken.channel.subscriptions.read="Read channel's subscribers list and info."
AdvSceneSwitcher.twitchToken.moderator.followers.read="Read the followers of a broadcaster."
AdvSceneSwitcher.twitchToken.bits.read="Read channel's Bits information."
AdvSceneSwitcher.twitchToken.channel.raids.manage="Manage channel's raids."
AdvSceneSwitcher.twitchToken.moderator.shoutouts.read="Read channel's shoutouts."
AdvSceneSwitcher.twitchToken.moderator.shoutouts.manage="Manage channel's shoutouts."
AdvSceneSwitcher.twitchToken.channel.polls.read="Read channel's polls' state."
@@ -1057,17 +1050,7 @@ AdvSceneSwitcher.twitchToken.moderator.shieldMode.manage="Manage channel's Shiel
AdvSceneSwitcher.twitchToken.channel.redemptions.read="Read channel's point rewards and their redemptions."
AdvSceneSwitcher.twitchToken.channel.redemptions.manage="Manage channel's point rewards and their redemptions."
AdvSceneSwitcher.twitchToken.channel.moderate="Moderate the channel."
AdvSceneSwitcher.twitchToken.moderator.bannedUsers.manage="Manage channel's banned users."
AdvSceneSwitcher.twitchToken.user.blockedUsers.manage="Manage user's blocked users."
AdvSceneSwitcher.twitchToken.moderation.read="Read channel's moderation data."
AdvSceneSwitcher.twitchToken.channel.moderators.manage="Manage channel's moderators."
AdvSceneSwitcher.twitchToken.channel.vips.manage="Manage channel's VIPs."
AdvSceneSwitcher.twitchToken.channel.commercial.edit="Run commercials on a channel."
AdvSceneSwitcher.twitchToken.channel.ads.manage="Manage channel's commercials."
AdvSceneSwitcher.twitchToken.clips.edit="Create clips from channel's broadcasts."
AdvSceneSwitcher.twitchToken.moderator.announcements.manage="Manage channel's chat announcements."
AdvSceneSwitcher.twitchToken.moderator.chat.settings.manage="Manage channel's chat settings, such as emote-only or slow mode."
AdvSceneSwitcher.twitchToken.user.whispers.manage="Manage user's whispers."
AdvSceneSwitcher.twitchToken.channel.moderation.read="Read channel's moderation data."
AdvSceneSwitcher.twitchToken.chat.read="View live stream chat messages."
AdvSceneSwitcher.twitchToken.chat.edit="Send live stream chat messages."
@@ -1430,20 +1413,6 @@ AdvSceneSwitcher.tempVar.twitch.chatter="User login"
AdvSceneSwitcher.tempVar.twitch.chatter.description="The user login of the person who sent the chat message."
AdvSceneSwitcher.tempVar.twitch.chat_message="Chat message"
AdvSceneSwitcher.tempVar.twitch.from_broadcaster_user_id.raid="Raid creator user ID"
AdvSceneSwitcher.tempVar.twitch.from_broadcaster_user_id.raid.description="The broadcaster ID that created the raid."
AdvSceneSwitcher.tempVar.twitch.from_broadcaster_user_login.raid="Raid creator user login"
AdvSceneSwitcher.tempVar.twitch.from_broadcaster_user_login.raid.description="The broadcaster login that created the raid."
AdvSceneSwitcher.tempVar.twitch.from_broadcaster_user_name.raid="Raid creator user name"
AdvSceneSwitcher.tempVar.twitch.from_broadcaster_user_name.raid.description="The broadcaster display name that created the raid."
AdvSceneSwitcher.tempVar.twitch.to_broadcaster_user_id.raid="Raid receiver user ID"
AdvSceneSwitcher.tempVar.twitch.to_broadcaster_user_id.raid.description="The broadcaster ID that created the raid."
AdvSceneSwitcher.tempVar.twitch.to_broadcaster_user_login.raid="Raid receiver user login"
AdvSceneSwitcher.tempVar.twitch.to_broadcaster_user_login.raid.description="The broadcaster login that created the raid."
AdvSceneSwitcher.tempVar.twitch.to_broadcaster_user_name.raid="Raid receiver user name"
AdvSceneSwitcher.tempVar.twitch.to_broadcaster_user_name.raid.description="The broadcaster display name that created the raid."
AdvSceneSwitcher.tempVar.twitch.viewers.raid="Raid participants"
AdvSceneSwitcher.tempVar.twitch.viewers.raid.description="The number of viewers in the raid."
AdvSceneSwitcher.tempVar.audio.output_volume="Output volume"
AdvSceneSwitcher.tempVar.audio.output_volume.description="The volume the audio source is outputting."
@@ -1454,11 +1423,6 @@ AdvSceneSwitcher.tempVar.audio.sync_offset="Source audio sync offset"
AdvSceneSwitcher.tempVar.audio.monitor="Source audio monitor type"
AdvSceneSwitcher.tempVar.audio.balance="Source audio balance"
AdvSceneSwitcher.tempVar.clipboard.mimeType.primary="Primary clipboard item MIME type"
AdvSceneSwitcher.tempVar.clipboard.mimeType.primary.description="Highest priority MIME type of the current item stored in clipboard, if available."
AdvSceneSwitcher.tempVar.clipboard.mimeType.all="All clipboard item MIME types"
AdvSceneSwitcher.tempVar.clipboard.mimeType.all.description="All MIME types of the current item stored in clipboard separated with space, if available."
AdvSceneSwitcher.tempVar.scene.current="Current scene"
AdvSceneSwitcher.tempVar.scene.previous="Previous scene"
AdvSceneSwitcher.tempVar.scene.preview="Preview scene"
@@ -1479,33 +1443,6 @@ AdvSceneSwitcher.tempVar.date.minute="Minute"
AdvSceneSwitcher.tempVar.date.second="Second"
AdvSceneSwitcher.tempVar.date.dayOfWeek="Day of week"
AdvSceneSwitcher.tempVar.midi.type="Message type"
AdvSceneSwitcher.tempVar.midi.channel="Channel"
AdvSceneSwitcher.tempVar.midi.note="Note"
AdvSceneSwitcher.tempVar.midi.value1="Value 1"
AdvSceneSwitcher.tempVar.midi.value2="Value 2"
AdvSceneSwitcher.tempVar.macro.runCount="Run count"
AdvSceneSwitcher.tempVar.macro.runCount.description="The number of times a macro was executed."
AdvSceneSwitcher.tempVar.macro.matchedCount="Matched count"
AdvSceneSwitcher.tempVar.macro.matchedCount.description="The number of macros of which the condition state was true."
AdvSceneSwitcher.tempVar.process.name="Process name"
AdvSceneSwitcher.tempVar.recording.durationSeconds="Recording duration"
AdvSceneSwitcher.tempVar.recording.durationSeconds.description="Recording duration in seconds.\nThis value does not change while the recording is paused and will be reset to zero if the recording is stopped."
AdvSceneSwitcher.tempVar.video.patternCount="Pattern count"
AdvSceneSwitcher.tempVar.video.patternCount.description="The number of times the given pattern has been found in a given video input frame."
AdvSceneSwitcher.tempVar.video.objectCount="Object count"
AdvSceneSwitcher.tempVar.video.objectCount.description="The number of objects the given model has identified in a given video input frame."
AdvSceneSwitcher.tempVar.video.brightness="Average brightness"
AdvSceneSwitcher.tempVar.video.brightness.description="The average brightness in a given video input frame in a range from 0 to 1 (dark to bright)."
AdvSceneSwitcher.tempVar.video.text="OCR text"
AdvSceneSwitcher.tempVar.video.text.description="The text detected in a given video input frame."
AdvSceneSwitcher.tempVar.video.color="Average color"
AdvSceneSwitcher.tempVar.video.color.description="The average RGB color in a given video input frame in HexArgb format."
AdvSceneSwitcher.selectScene="--select scene--"
AdvSceneSwitcher.selectPreviousScene="Previous Scene"
AdvSceneSwitcher.selectCurrentScene="Current Scene"

View File

@@ -182,8 +182,9 @@ AdvSceneSwitcher.condition.record="Grabación"
AdvSceneSwitcher.condition.record.state.start="Grabación en ejecución"
AdvSceneSwitcher.condition.record.state.pause="Grabación en pausa"
AdvSceneSwitcher.condition.record.state.stop="Grabación detenida"
AdvSceneSwitcher.condition.record.entry="{{recordState}}"
AdvSceneSwitcher.condition.process="Proceso"
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}se está ejecutando{{focused}}y está enfocado"
AdvSceneSwitcher.condition.process.entry="{{processes}} se está ejecutando {{focused}} y está enfocado"
AdvSceneSwitcher.condition.idle="Inactivo"
AdvSceneSwitcher.condition.idle.entry="No hay entradas de teclado o ratón durante {{duration}}"
AdvSceneSwitcher.condition.pluginState="Estado del complemento"

View File

@@ -286,7 +286,7 @@ AdvSceneSwitcher.condition.record.state.start="Enregistrement en cours"
AdvSceneSwitcher.condition.record.state.pause="Enregistrement en pause"
AdvSceneSwitcher.condition.record.state.stop="Arrêt de l'enregistrement"
AdvSceneSwitcher.condition.process="Processus"
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}en cours d'exécution{{focused}}et est au premier plan"
AdvSceneSwitcher.condition.process.entry="{{processes}}en cours d'exécution{{focused}}et est au premier plan"
AdvSceneSwitcher.condition.process.entry.focus="Processus au premier plan actuel :{{focusProcess}}"
AdvSceneSwitcher.condition.idle="Inactif"
AdvSceneSwitcher.condition.idle.entry="Aucune entrée de clavier ou de souris pendant{{duration}}"
@@ -736,9 +736,9 @@ AdvSceneSwitcher.action.sceneLock.type.unlock="déverrouiller"
AdvSceneSwitcher.action.sceneLock.type.toggle="basculer le verrouillage de"
AdvSceneSwitcher.action.sceneLock.entry="Sur{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.twitch="Twitch"
AdvSceneSwitcher.action.twitch.type.channel.info.title.set="Définir le titre du flux"
AdvSceneSwitcher.action.twitch.type.channel.info.category.set="Définir la catégorie du flux"
AdvSceneSwitcher.action.twitch.type.commercial.start="Démarrer une publicité d'une durée de"
AdvSceneSwitcher.action.twitch.type.title="Définir le titre du flux"
AdvSceneSwitcher.action.twitch.type.category="Définir la catégorie du flux"
AdvSceneSwitcher.action.twitch.type.commercial="Démarrer une publicité d'une durée de"
AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Impossible de sélectionner une catégorie sans avoir d'abord sélectionné un compte Twitch !"
AdvSceneSwitcher.action.twitch.entry.line1="Sur{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}"

View File

@@ -107,7 +107,7 @@ AdvSceneSwitcher.condition.record.state.start="Запись запущена"
AdvSceneSwitcher.condition.record.state.pause="Запись приостановлена"
AdvSceneSwitcher.condition.record.state.stop="Запись остановлена"
AdvSceneSwitcher.condition.process="Процесс"
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}запущен{{focused}}и сфокусирован"
AdvSceneSwitcher.condition.process.entry="{{processes}} запущен {{focused}} и сфокусирован"
AdvSceneSwitcher.condition.idle="Простой"
AdvSceneSwitcher.condition.idle.entry="Не было ни клавиатуры, ни мыши в течении{{duration}}"
AdvSceneSwitcher.condition.pluginState="Состояние плагина"

View File

@@ -159,8 +159,9 @@ AdvSceneSwitcher.condition.record="Kayıt"
AdvSceneSwitcher.condition.record.state.start="Kayıt Çalışıyor"
AdvSceneSwitcher.condition.record.state.pause="Kayıt durakladı"
AdvSceneSwitcher.condition.record.state.stop="Kayıt durdu"
AdvSceneSwitcher.condition.record.entry="{{recordState}}"
AdvSceneSwitcher.condition.process="İşlem"
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}çalışıyor{{focused}}ve odaklandı"
AdvSceneSwitcher.condition.process.entry="{{processes}} çalışıyor {{focused}} ve odaklandı"
AdvSceneSwitcher.condition.idle="Boşta"
AdvSceneSwitcher.condition.idle.entry="...için klavye veya fare girişi yok {{duration}}"
AdvSceneSwitcher.condition.pluginState="Eklenti durumu"

View File

@@ -267,8 +267,9 @@ AdvSceneSwitcher.condition.record="录制"
AdvSceneSwitcher.condition.record.state.start="录制运行中"
AdvSceneSwitcher.condition.record.state.pause="录制暂停"
AdvSceneSwitcher.condition.record.state.stop="录制停止"
AdvSceneSwitcher.condition.record.entry="{{recordState}}"
AdvSceneSwitcher.condition.process="进程"
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}是正在运行{{focused}}并且是获得焦点"
AdvSceneSwitcher.condition.process.entry="{{processes}} 是正在运行 {{focused}} 并且是获得焦点"
AdvSceneSwitcher.condition.process.entry.focus="当前焦点进程: {{focusProcess}}"
AdvSceneSwitcher.condition.idle="闲置检测"
AdvSceneSwitcher.condition.idle.entry="没有键盘或鼠标输入{{duration}}"

View File

@@ -1,11 +0,0 @@
<?xml version="1.0"?>
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet" version="1.1">
<!-- Created with SVG-edit - https://github.com/SVG-Edit/svgedit-->
<g class="layer">
<title>Layer 1</title>
<rect fill="#fefefe" height="2.25" id="svg_1" stroke="#fefefe" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" transform="matrix(1, 0, 0, 1, 0, 0)" width="13" x="1.5" y="4"/>
<rect fill="#fefefe" height="2.25" id="svg_2" stroke="#fefefe" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" transform="matrix(1, 0, 0, 1, 0, 0)" width="13" x="1.5" y="10"/>
<rect fill="#fefefe" height="1" id="svg_4" stroke="#fefefe" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" transform="matrix(0.341951, -0.926734, 0.939718, 0.337226, -1.948, 13.2448)" width="14" x="1" y="7"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 931 B

View File

@@ -1,11 +0,0 @@
<?xml version="1.0"?>
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet" version="1.1">
<!-- Created with SVG-edit - https://github.com/SVG-Edit/svgedit-->
<g class="layer">
<title>Layer 1</title>
<rect fill="#202020" height="2.25" id="svg_1" stroke="#202020" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" transform="matrix(1, 0, 0, 1, 0, 0)" width="13" x="1.5" y="4"/>
<rect fill="#202020" height="2.25" id="svg_2" stroke="#202020" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" transform="matrix(1, 0, 0, 1, 0, 0)" width="13" x="1.5" y="10"/>
<rect fill="#202020" height="1" id="svg_4" stroke="#202020" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" transform="matrix(0.341951, -0.926734, 0.939718, 0.337226, -1.948, 13.2448)" width="14" x="1" y="7"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 931 B

View File

@@ -119,7 +119,7 @@
<string notr="true">ms</string>
</property>
<property name="minimum">
<number>10</number>
<number>50</number>
</property>
<property name="maximum">
<number>20000</number>
@@ -1275,22 +1275,6 @@
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="toggleElseActions">
<property name="maximumSize">
<size>
<width>22</width>
<height>16777215</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>

View File

@@ -10,7 +10,6 @@
#include <QMainWindow>
#include <QAction>
#include <QFileDialog>
#include <QTextStream>
#include <QDirIterator>
#include <regex>
#include <filesystem>
@@ -116,27 +115,10 @@ void AdvSceneSwitcher::LoadUI()
loading = false;
}
bool AdvSceneSwitcher::eventFilter(QObject *obj, QEvent *event)
{
auto eventType = event->type();
if (obj == ui->macroElseActions && eventType == QEvent::Resize) {
QResizeEvent *resizeEvent = static_cast<QResizeEvent *>(event);
if (resizeEvent->size().height() == 0) {
SetElseActionsStateToHidden();
return QDialog::eventFilter(obj, event);
}
SetElseActionsStateToVisible();
}
return QDialog::eventFilter(obj, event);
}
/******************************************************************************
* Saving and loading
******************************************************************************/
static void AskForBackup(const QString &json);
static void AskForBackup(obs_data_t *obj);
static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *)
{
@@ -157,25 +139,16 @@ static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *)
switcher->Stop();
switcher->m.lock();
OBSDataAutoRelease obj =
obs_data_t *obj =
obs_data_get_obj(save_data, "advanced-scene-switcher");
if (!obj) {
obj = obs_data_create();
}
if (switcher->VersionChanged(obj, g_GIT_SHA1)) {
auto json = obs_data_get_json(obj);
static QString jsonQString = json ? json : "";
std::thread t([]() {
obs_queue_task(
OBS_TASK_UI,
[](void *) {
AskForBackup(jsonQString);
},
nullptr, false);
});
t.detach();
AskForBackup(obj);
}
switcher->LoadSettings(obj);
obs_data_release(obj);
switcher->m.unlock();
if (!switcher->stop) {
@@ -184,12 +157,12 @@ static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *)
}
}
static void AskForBackup(const QString &json)
static void AskForBackup(obs_data_t *obj)
{
const bool backupWasConfirmed = DisplayMessage(
obs_module_text("AdvSceneSwitcher.askBackup"), true, false);
bool backupSettings = DisplayMessage(
obs_module_text("AdvSceneSwitcher.askBackup"), true);
if (!backupWasConfirmed) {
if (!backupSettings) {
return;
}
@@ -208,8 +181,8 @@ static void AskForBackup(const QString &json)
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
return;
}
auto out = QTextStream(&file);
out << json;
obs_data_save_json(obj, file.fileName().toUtf8().constData());
}
/******************************************************************************
@@ -357,8 +330,12 @@ void SwitcherData::SetPreconditions()
InvalidateMacroTempVarValues();
}
void ClearWebsocketMessages();
void SwitcherData::ResetForNextInterval()
{
// Core reset functions
ClearWebsocketMessages();
// Plugin reset functions
for (const auto &func : resetIntervalSteps) {
func();
@@ -782,9 +759,6 @@ void OpenSettingsWindow()
}
}
void SetupConnectionManager();
void SetupWebsocketHelpers();
extern "C" void InitSceneSwitcher(obs_module_t *module, translateFunc translate)
{
blog(LOG_INFO, "version: %s", g_GIT_TAG);
@@ -795,8 +769,6 @@ extern "C" void InitSceneSwitcher(obs_module_t *module, translateFunc translate)
PlatformInit();
LoadPlugins();
SetupDock();
SetupConnectionManager();
SetupWebsocketHelpers();
obs_frontend_add_save_callback(SaveSceneSwitcher, nullptr);
obs_frontend_add_event_callback(OBSEvent, switcher);

View File

@@ -39,9 +39,6 @@ public:
void RestoreWindowGeo();
void CheckFirstTimeSetup();
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
/* --- Begin of general tab section --- */
public:
void SetupGeneralTab();
@@ -118,7 +115,6 @@ public slots:
void on_actionUp_clicked();
void on_actionDown_clicked();
void on_actionBottom_clicked();
void on_toggleElseActions_clicked();
void on_elseActionAdd_clicked();
void on_elseActionRemove_clicked();
void on_elseActionTop_clicked();
@@ -150,8 +146,6 @@ public slots:
void MaximizeElseActions();
void MinimizeConditions();
void MaximizeConditions();
void SetElseActionsStateToHidden();
void SetElseActionsStateToVisible();
void MacroActionSelectionChanged(int idx);
void MacroActionReorder(int to, int target);
void AddMacroAction(int idx);
@@ -340,7 +334,7 @@ public slots:
void on_timeUp_clicked();
void on_timeDown_clicked();
// Audio tab
// Video tab
public:
void SetupAudioTab();
public slots:

View File

@@ -33,7 +33,7 @@ void AdvSceneSwitcher::on_noMatchDontSwitch_clicked()
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->switchIfNotMatching = NoMatchBehavior::NO_SWITCH;
switcher->switchIfNotMatching = SwitcherData::NoMatch::NO_SWITCH;
ui->noMatchSwitchScene->setEnabled(false);
ui->randomDisabledWarning->setVisible(true);
}
@@ -45,7 +45,7 @@ void AdvSceneSwitcher::on_noMatchSwitch_clicked()
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->switchIfNotMatching = NoMatchBehavior::SWITCH;
switcher->switchIfNotMatching = SwitcherData::NoMatch::SWITCH;
ui->noMatchSwitchScene->setEnabled(true);
UpdateNonMatchingScene(ui->noMatchSwitchScene->currentText());
ui->randomDisabledWarning->setVisible(true);
@@ -58,7 +58,7 @@ void AdvSceneSwitcher::on_noMatchRandomSwitch_clicked()
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->switchIfNotMatching = NoMatchBehavior::RANDOM_SWITCH;
switcher->switchIfNotMatching = SwitcherData::NoMatch::RANDOM_SWITCH;
ui->noMatchSwitchScene->setEnabled(false);
ui->randomDisabledWarning->setVisible(false);
}
@@ -506,6 +506,7 @@ void SwitcherData::LoadSettings(obs_data_t *obj)
// selections to be available.
loadSceneGroups(obj);
LoadVariables(obj);
LoadConnections(obj);
for (const auto &func : loadSteps) {
func(obj);
@@ -548,6 +549,7 @@ void SwitcherData::SaveSettings(obs_data_t *obj)
saveSceneGroups(obj);
SaveMacros(obj);
SaveConnections(obj);
SaveVariables(obj);
saveWindowTitleSwitches(obj);
saveScreenRegionSwitches(obj);
@@ -622,9 +624,9 @@ void SwitcherData::LoadGeneralSettings(obs_data_t *obj)
interval = obs_data_get_int(obj, "interval");
obs_data_set_default_int(obj, "switch_if_not_matching",
static_cast<int>(NoMatchBehavior::NO_SWITCH));
switchIfNotMatching = static_cast<NoMatchBehavior>(
obs_data_get_int(obj, "switch_if_not_matching"));
static_cast<int>(NoMatch::NO_SWITCH));
switchIfNotMatching =
(NoMatch)obs_data_get_int(obj, "switch_if_not_matching");
std::string nonMatchingSceneName =
obs_data_get_string(obj, "non_matching_scene");
nonMatchingScene = GetWeakSourceByName(nonMatchingSceneName.c_str());
@@ -798,13 +800,12 @@ void SwitcherData::CheckNoMatchSwitch(bool &match, OBSWeakSource &scene,
return;
}
if (switchIfNotMatching == NoMatchBehavior::SWITCH &&
nonMatchingScene) {
if (switchIfNotMatching == NoMatch::SWITCH && nonMatchingScene) {
match = true;
scene = nonMatchingScene;
transition = nullptr;
}
if (switchIfNotMatching == NoMatchBehavior::RANDOM_SWITCH) {
if (switchIfNotMatching == NoMatch::RANDOM_SWITCH) {
match = checkRandom(scene, transition, sleep);
}
}
@@ -955,11 +956,11 @@ void AdvSceneSwitcher::SetupGeneralTab()
{
PopulateSceneSelection(ui->noMatchSwitchScene, false);
if (switcher->switchIfNotMatching == NoMatchBehavior::SWITCH) {
if (switcher->switchIfNotMatching == SwitcherData::NoMatch::SWITCH) {
ui->noMatchSwitch->setChecked(true);
ui->noMatchSwitchScene->setEnabled(true);
} else if (switcher->switchIfNotMatching ==
NoMatchBehavior::NO_SWITCH) {
SwitcherData::NoMatch::NO_SWITCH) {
ui->noMatchDontSwitch->setChecked(true);
ui->noMatchSwitchScene->setEnabled(false);
} else {

View File

@@ -143,7 +143,8 @@ void AdvSceneSwitcher::SetupRandomTab()
border-radius: 7px; \
border-color: rgb(0,0,0,0) \
}");
if (switcher->switchIfNotMatching != NoMatchBehavior::RANDOM_SWITCH) {
if (switcher->switchIfNotMatching !=
SwitcherData::NoMatch::RANDOM_SWITCH) {
if (!switcher->disableHints) {
PulseWidget(ui->randomDisabledWarning, QColor(Qt::red),
QColor(0, 0, 0, 0));

View File

@@ -1,6 +1,5 @@
#include "platform-funcs.hpp"
#include "hotkey.hpp"
#include "log-helper.hpp"
#include <X11/Xlib.h>
#include <X11/Xatom.h>
@@ -26,10 +25,9 @@
#include <QStringList>
#include <QRegularExpression>
#include <QLibrary>
#ifdef PROCPS_AVAILABLE
#ifdef USE_PROCPS
#include <proc/readproc.h>
#endif
#ifdef PROCPS2_AVAILABLE
#else
#include <libproc2/pids.h>
#endif
#include <fstream>
@@ -55,31 +53,6 @@ std::chrono::high_resolution_clock::time_point lastMouseLeftClickTime{};
std::chrono::high_resolution_clock::time_point lastMouseMiddleClickTime{};
std::chrono::high_resolution_clock::time_point lastMouseRightClickTime{};
static QLibrary *libprocps = nullptr;
#ifdef PROCPS_AVAILABLE
typedef PROCTAB *(*openproc_func)(int flags);
typedef void (*closeproc_func)(PROCTAB *PT);
typedef proc_t *(*readproc_func)(PROCTAB *__restrict const PT,
proc_t *__restrict p);
static openproc_func openproc_ = nullptr;
static closeproc_func closeproc_ = nullptr;
static readproc_func readproc_ = nullptr;
#endif
static bool libprocpsSupported = false;
static QLibrary *libproc2 = nullptr;
#ifdef PROCPS2_AVAILABLE
typedef int (*procps_pids_new_func)(struct pids_info **info,
enum pids_item *items, int numitems);
typedef struct pids_stack *(*procps_pids_get_func)(struct pids_info *info,
enum pids_fetch_type which);
typedef int (*procps_pids_unref_func)(struct pids_info **info);
static procps_pids_new_func procps_pids_new_ = nullptr;
static procps_pids_get_func procps_pids_get_ = nullptr;
static procps_pids_unref_func procps_pids_unref_ = nullptr;
#endif
static bool libprocps2Supported = false;
Display *disp()
{
if (!xdisplay) {
@@ -365,57 +338,46 @@ std::optional<std::string> GetTextInWindow(const std::string &)
return {};
}
static void getProcessListProcps(QStringList &processes)
#ifdef USE_PROCPS
void GetProcessList(QStringList &processes)
{
#ifdef PROCPS_AVAILABLE
PROCTAB *proc = openproc_(PROC_FILLSTAT);
processes.clear();
PROCTAB *proc = openproc(PROC_FILLSTAT);
proc_t proc_info;
memset(&proc_info, 0, sizeof(proc_info));
while (readproc_(proc, &proc_info) != NULL) {
while (readproc(proc, &proc_info) != NULL) {
QString procName(proc_info.cmd);
if (!procName.isEmpty() && !processes.contains(procName)) {
processes << procName;
}
}
closeproc_(proc);
#endif
closeproc(proc);
}
static void getProcessListProcps2(QStringList &processes)
#else
void GetProcessList(QStringList &processes)
{
#ifdef PROCPS2_AVAILABLE
processes.clear();
struct pids_info *info = NULL;
struct pids_stack *stack;
enum pids_item Items[] = {
PIDS_CMD,
};
if (procps_pids_new_(&info, Items, sizeof(Items) / sizeof(Items[0])) <
if (procps_pids_new(&info, Items, sizeof(Items) / sizeof(Items[0])) <
0) {
return;
}
while ((stack = procps_pids_get_(info, PIDS_FETCH_TASKS_ONLY))) {
while ((stack = procps_pids_get(info, PIDS_FETCH_TASKS_ONLY))) {
auto cmd = PIDS_VAL(0, str, stack, info);
QString procName(cmd);
if (!procName.isEmpty() && !processes.contains(procName)) {
processes << procName;
}
}
procps_pids_unref_(&info);
procps_pids_unref(&info);
}
#endif
}
void GetProcessList(QStringList &processes)
{
processes.clear();
if (libprocpsSupported) {
getProcessListProcps(processes);
return;
}
if (libprocps2Supported) {
getProcessListProcps2(processes);
}
}
long getForegroundProcessPid()
{
@@ -671,58 +633,6 @@ void PressKeys(const std::vector<HotkeyType> keys, int duration)
XFlush(display);
}
static void initXtst()
{
libXtstHandle = new QLibrary("libXtst", nullptr);
pressFunc = (keyPressFunc)libXtstHandle->resolve("XTestFakeKeyEvent");
int _;
canSimulateKeyPresses = pressFunc &&
XQueryExtension(disp(), "XTEST", &_, &_, &_);
}
static void initXss()
{
libXssHandle = new QLibrary("libXss", nullptr);
allocSSFunc = (XScreenSaverAllocInfoFunc)libXssHandle->resolve(
"XScreenSaverAllocInfo");
querySSFunc = (XScreenSaverQueryInfoFunc)libXssHandle->resolve(
"XScreenSaverQueryInfo");
int _;
canGetIdleTime = allocSSFunc && querySSFunc &&
XQueryExtension(disp(), ScreenSaverName, &_, &_, &_);
}
static void initProcps()
{
#ifdef PROCPS_AVAILABLE
libprocps = new QLibrary("libprocps", nullptr);
openproc_ = (openproc_func)libprocps->resolve("openproc");
closeproc_ = (closeproc_func)libprocps->resolve("closeproc");
readproc_ = (readproc_func)libprocps->resolve("readproc");
if (openproc_ && closeproc_ && readproc_) {
libprocpsSupported = true;
blog(LOG_INFO, "libprocps symbols resolved successfully!");
}
#endif
}
static void initProc2()
{
#ifdef PROCPS2_AVAILABLE
libproc2 = new QLibrary("libproc2", nullptr);
procps_pids_new_ =
(procps_pids_new_func)libproc2->resolve("procps_pids_new");
procps_pids_get_ =
(procps_pids_get_func)libproc2->resolve("procps_pids_get");
procps_pids_unref_ =
(procps_pids_unref_func)libproc2->resolve("procps_pids_unref");
if (procps_pids_new_ && procps_pids_get_ && procps_pids_unref_) {
libprocps2Supported = true;
blog(LOG_INFO, "libproc2 symbols resolved successfully!");
}
#endif
}
void PlatformInit()
{
auto display = disp();
@@ -730,26 +640,31 @@ void PlatformInit()
return;
}
initXtst();
initXss();
initProcps();
initProc2();
}
libXtstHandle = new QLibrary("libXtst", nullptr);
pressFunc = (keyPressFunc)libXtstHandle->resolve("XTestFakeKeyEvent");
int _;
canSimulateKeyPresses = pressFunc &&
XQueryExtension(disp(), "XTEST", &_, &_, &_);
static void cleanupHelper(QLibrary *lib)
{
if (lib) {
delete lib;
lib = nullptr;
}
libXssHandle = new QLibrary("libXss", nullptr);
allocSSFunc = (XScreenSaverAllocInfoFunc)libXssHandle->resolve(
"XScreenSaverAllocInfo");
querySSFunc = (XScreenSaverQueryInfoFunc)libXssHandle->resolve(
"XScreenSaverQueryInfo");
canGetIdleTime = allocSSFunc && querySSFunc &&
XQueryExtension(disp(), ScreenSaverName, &_, &_, &_);
}
void PlatformCleanup()
{
cleanupHelper(libXtstHandle);
cleanupHelper(libXssHandle);
cleanupHelper(libprocps);
cleanupHelper(libproc2);
if (libXtstHandle) {
delete libXtstHandle;
libXtstHandle = nullptr;
}
if (libXssHandle) {
delete libXssHandle;
libXssHandle = nullptr;
}
cleanupDisplay();
}

View File

@@ -1,257 +0,0 @@
#include "macro-action-clipboard.hpp"
#include "curl-helper.hpp"
#include "switcher-data.hpp"
#include <QApplication>
#include <QClipboard>
#include <QImage>
#include <QMimeData>
namespace advss {
const std::string MacroActionClipboard::id = "clipboard";
bool MacroActionClipboard::_registered = MacroActionFactory::Register(
MacroActionClipboard::id,
{MacroActionClipboard::Create, MacroActionClipboardEdit::Create,
"AdvSceneSwitcher.action.clipboard"});
const static std::map<MacroActionClipboard::Action, std::string> actionTypes = {
{MacroActionClipboard::Action::COPY_TEXT,
"AdvSceneSwitcher.action.clipboard.type.copy.text"},
{MacroActionClipboard::Action::COPY_IMAGE,
"AdvSceneSwitcher.action.clipboard.type.copy.image"},
};
static size_t writeCallback(void *ptr, size_t size, size_t nmemb,
QByteArray *buffer)
{
buffer->append((char *)ptr, nmemb);
return size * nmemb;
}
static std::optional<QImage> getImageFromUrl(const char *url)
{
QByteArray response;
switcher->curl.SetOpt(CURLOPT_URL, url);
switcher->curl.SetOpt(CURLOPT_HTTPGET, 1L);
switcher->curl.SetOpt(CURLOPT_TIMEOUT_MS, 30000);
switcher->curl.SetOpt(CURLOPT_WRITEFUNCTION, writeCallback);
switcher->curl.SetOpt(CURLOPT_WRITEDATA, &response);
auto code = switcher->curl.Perform();
if (code != CURLE_OK) {
blog(LOG_WARNING,
"Retrieving image failed in %s with error: %s", __func__,
switcher->curl.GetError(code));
return {};
}
return QImage::fromData(response);
}
static void setMimeTypeParams(ClipboardQueueParams *params,
QClipboard *clipboard)
{
auto mimeData = clipboard->mimeData();
if (!mimeData) {
return;
}
auto mimeTypeList = mimeData->formats();
if (mimeTypeList.empty()) {
return;
}
params->mimeTypePrimary = mimeTypeList.first().toStdString();
params->mimeTypeAll = mimeTypeList.join(" ").toStdString();
}
static void copyText(void *param)
{
auto params = static_cast<ClipboardTextQueueParams *>(param);
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(params->text.c_str());
setMimeTypeParams(params, clipboard);
}
static void copyImageFromUrl(void *param)
{
auto params = static_cast<ClipboardImageQueueParams *>(param);
auto url = params->url.c_str();
auto image = getImageFromUrl(url);
if (!image || (*image).isNull()) {
blog(LOG_WARNING, "Failed to convert %s URL to image!", url);
return;
}
QClipboard *clipboard = QApplication::clipboard();
clipboard->setImage(*image);
setMimeTypeParams(params, clipboard);
}
bool MacroActionClipboard::PerformAction()
{
switch (_action) {
case Action::COPY_TEXT: {
ClipboardTextQueueParams params{"", "", _text};
obs_queue_task(OBS_TASK_UI, copyText, &params, true);
SetTempVarValues(params);
break;
}
case Action::COPY_IMAGE: {
ClipboardImageQueueParams params{"", "", _url};
obs_queue_task(OBS_TASK_UI, copyImageFromUrl, &params, true);
SetTempVarValues(params);
break;
}
default:
break;
}
return true;
}
void MacroActionClipboard::SetupTempVars()
{
MacroAction::SetupTempVars();
AddTempvar(
"mimeType.primary",
obs_module_text(
"AdvSceneSwitcher.tempVar.clipboard.mimeType.primary"),
obs_module_text(
"AdvSceneSwitcher.tempVar.clipboard.mimeType.primary.description"));
AddTempvar(
"mimeType.all",
obs_module_text(
"AdvSceneSwitcher.tempVar.clipboard.mimeType.all"),
obs_module_text(
"AdvSceneSwitcher.tempVar.clipboard.mimeType.all.description"));
}
void MacroActionClipboard::SetTempVarValues(const ClipboardQueueParams &params)
{
SetTempVarValue("mimeType.primary", params.mimeTypePrimary);
SetTempVarValue("mimeType.all", params.mimeTypeAll);
}
bool MacroActionClipboard::Save(obs_data_t *obj) const
{
MacroAction::Save(obj);
obs_data_set_int(obj, "action", static_cast<int>(_action));
_text.Save(obj, "text");
_url.Save(obj, "url");
return true;
}
bool MacroActionClipboard::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
_text.Load(obj, "text");
_url.Load(obj, "url");
return true;
}
static inline void populateActionSelection(QComboBox *list)
{
for (const auto &[action, name] : actionTypes) {
list->addItem(obs_module_text(name.c_str()),
static_cast<int>(action));
}
}
MacroActionClipboardEdit::MacroActionClipboardEdit(
QWidget *parent, std::shared_ptr<MacroActionClipboard> entryData)
: QWidget(parent),
_actions(new FilterComboBox(this)),
_text(new VariableLineEdit(this)),
_url(new VariableLineEdit(this))
{
populateActionSelection(_actions);
_url->setToolTip(obs_module_text(
"AdvSceneSwitcher.action.clipboard.copy.image.url.tooltip"));
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QWidget::connect(_text, SIGNAL(editingFinished()), this,
SLOT(TextChanged()));
QWidget::connect(_url, SIGNAL(editingFinished()), this,
SLOT(UrlChanged()));
auto mainLayout = new QHBoxLayout();
mainLayout->addWidget(_actions);
mainLayout->addWidget(_text);
mainLayout->addWidget(_url);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroActionClipboardEdit::ActionChanged(int index)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_action = static_cast<MacroActionClipboard::Action>(
_actions->itemData(index).toInt());
SetWidgetVisibility();
}
void MacroActionClipboardEdit::TextChanged()
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_text = _text->text().toStdString();
}
void MacroActionClipboardEdit::UrlChanged()
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_url = _url->text().toStdString();
}
void MacroActionClipboardEdit::SetWidgetVisibility()
{
_text->setVisible(_entryData->_action ==
MacroActionClipboard::Action::COPY_TEXT);
_url->setVisible(_entryData->_action ==
MacroActionClipboard::Action::COPY_IMAGE);
adjustSize();
updateGeometry();
}
void MacroActionClipboardEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_actions->setCurrentIndex(
_actions->findData(static_cast<int>(_entryData->_action)));
_text->setText(_entryData->_text);
_url->setText(_entryData->_url);
SetWidgetVisibility();
}
} // namespace advss

View File

@@ -1,89 +0,0 @@
#pragma once
#include "macro-action-edit.hpp"
#include "filter-combo-box.hpp"
#include <variable-line-edit.hpp>
namespace advss {
struct ClipboardQueueParams {
std::string mimeTypePrimary;
std::string mimeTypeAll;
};
struct ClipboardTextQueueParams : ClipboardQueueParams {
StringVariable text;
};
struct ClipboardImageQueueParams : ClipboardQueueParams {
StringVariable url;
};
class MacroActionClipboard : public MacroAction {
public:
MacroActionClipboard(Macro *m) : MacroAction(m) {}
static std::shared_ptr<MacroAction> Create(Macro *m)
{
return std::make_shared<MacroActionClipboard>(m);
}
std::string GetId() const { return id; };
bool PerformAction();
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
enum class Action {
COPY_TEXT,
COPY_IMAGE,
};
Action _action = Action::COPY_TEXT;
StringVariable _text = obs_module_text(
"AdvSceneSwitcher.action.clipboard.copy.text.text.placeholder");
StringVariable _url = obs_module_text(
"AdvSceneSwitcher.action.clipboard.copy.image.url.placeholder");
private:
void SetupTempVars();
void SetTempVarValues(const ClipboardQueueParams &params);
static bool _registered;
static const std::string id;
};
class MacroActionClipboardEdit : public QWidget {
Q_OBJECT
public:
MacroActionClipboardEdit(
QWidget *parent,
std::shared_ptr<MacroActionClipboard> entryData = nullptr);
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> action)
{
return new MacroActionClipboardEdit(
parent, std::dynamic_pointer_cast<MacroActionClipboard>(
action));
}
void UpdateEntryData();
protected:
std::shared_ptr<MacroActionClipboard> _entryData;
private slots:
void ActionChanged(int index);
void TextChanged();
void UrlChanged();
private:
void SetWidgetVisibility();
bool _loading = true;
FilterComboBox *_actions;
VariableLineEdit *_text;
VariableLineEdit *_url;
};
} // namespace advss

View File

@@ -424,16 +424,11 @@ void MacroActionFilterEdit::SetWidgetVisibility()
_entryData->_settingsInputMethod ==
MacroActionFilter::SettingsInputMethod::
INDIVIDUAL_TEMPVAR);
if (_entryData->_action == MacroActionFilter::Action::SETTINGS &&
_entryData->_settingsInputMethod ==
MacroActionFilter::SettingsInputMethod::INDIVIDUAL_MANUAL) {
RemoveStretchIfPresent(_settingsLayout);
_manualSettingValue->show();
} else {
AddStretchIfNecessary(_settingsLayout);
_manualSettingValue->hide();
}
_manualSettingValue->setVisible(
_entryData->_action == MacroActionFilter::Action::SETTINGS &&
_entryData->_settingsInputMethod ==
MacroActionFilter::SettingsInputMethod::
INDIVIDUAL_MANUAL);
adjustSize();
updateGeometry();

View File

@@ -1,9 +1,8 @@
#include "macro-action-plugin-state.hpp"
#include "plugin-state-helper.hpp"
#include "switcher-data.hpp"
#include "utility.hpp"
#include <thread>
#include <condition_variable>
#include <QMainWindow>
using namespace std::chrono_literals;
@@ -28,38 +27,40 @@ const static std::map<PluginStateAction, std::string> actionTypes = {
"AdvSceneSwitcher.action.pluginState.type.terminate"},
};
const static std::map<NoMatchBehavior, std::string> noMatchValues = {
{NoMatchBehavior::NO_SWITCH,
const static std::map<SwitcherData::NoMatch, std::string> noMatchValues = {
{SwitcherData::NoMatch::NO_SWITCH,
"AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.dontSwitch"},
{NoMatchBehavior::SWITCH,
{SwitcherData::NoMatch::SWITCH,
"AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchTo"},
{NoMatchBehavior::RANDOM_SWITCH,
{SwitcherData::NoMatch::RANDOM_SWITCH,
"AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchToRandom"},
};
static void stopPlugin()
{
std::thread t([]() { StopPlugin(); });
std::thread t([]() { switcher->Stop(); });
t.detach();
}
static void importSettings(const std::string &path)
{
if (SettingsWindowIsOpened()) {
if (switcher->settingsWindowOpened) {
return;
}
OBSDataAutoRelease obj = obs_data_create_from_json_file(path.c_str());
obs_data_t *obj = obs_data_create_from_json_file(path.c_str());
if (!obj) {
return;
}
LoadPluginSettings(obj);
switcher->LoadSettings(obj);
obs_data_release(obj);
}
static void setNoMatchBehaviour(int value, OBSWeakSource &scene)
{
SetPluginNoMatchBehavior(static_cast<NoMatchBehavior>(value));
if (GetPluginNoMatchBehavior() == NoMatchBehavior::SWITCH) {
SetNoMatchScene(scene);
switcher->switchIfNotMatching =
static_cast<SwitcherData::NoMatch>(value);
if (switcher->switchIfNotMatching == SwitcherData::NoMatch::SWITCH) {
switcher->nonMatchingScene = scene;
}
}
@@ -341,8 +342,8 @@ void MacroActionPluginStateEdit::SetWidgetVisibility()
break;
case PluginStateAction::NO_MATCH_BEHAVIOUR:
_values->show();
if (static_cast<NoMatchBehavior>(_entryData->_value) ==
NoMatchBehavior::SWITCH) {
if (static_cast<SwitcherData::NoMatch>(_entryData->_value) ==
SwitcherData::NoMatch::SWITCH) {
_scenes->show();
}
break;

View File

@@ -1,5 +1,5 @@
#include "macro-action-scene-collection.hpp"
#include "plugin-state-helper.hpp"
#include "switcher-data.hpp"
#include "utility.hpp"
namespace advss {
@@ -17,7 +17,7 @@ bool MacroActionSceneCollection::PerformAction()
// Changing the scene collection will also reload the settings of the
// scene switcher, so to avoid issues of not being able to change
// settings ignore this action if the settings dialog is opened.
if (SettingsWindowIsOpened()) {
if (switcher->settingsWindowOpened) {
return false;
}
obs_frontend_set_current_scene_collection(_sceneCollection.c_str());

View File

@@ -145,8 +145,7 @@ bool MacroActionSwitchScene::PerformAction()
auto transition = _transition.GetTransition();
SwitchScene({scene, transition, (int)(_duration.Milliseconds())},
obs_frontend_preview_program_mode_active());
if (_blockUntilTransitionDone && scene &&
scene != switcher->currentScene) {
if (_blockUntilTransitionDone && scene) {
return WaitForTransition(scene, transition);
}
return true;

View File

@@ -32,7 +32,7 @@ void MacroActionScreenshot::CustomScreenshot(OBSWeakSource &source)
}
auto s = obs_weak_source_get_source(source);
_screenshot.~ScreenshotHelper();
new (&_screenshot) ScreenshotHelper(s, QRect(), false, 0, true, _path);
new (&_screenshot) ScreenshotHelper(s, false, 0, true, _path);
obs_source_release(s);
}

View File

@@ -601,17 +601,11 @@ void MacroActionSourceEdit::SetWidgetVisibility()
_entryData->_settingsInputMethod ==
MacroActionSource::SettingsInputMethod::
INDIVIDUAL_TEMPVAR);
if (_entryData->_action == MacroActionSource::Action::SETTINGS &&
_entryData->_settingsInputMethod ==
MacroActionSource::SettingsInputMethod::INDIVIDUAL_MANUAL) {
RemoveStretchIfPresent(_settingsLayout);
_manualSettingValue->show();
} else {
AddStretchIfNecessary(_settingsLayout);
_manualSettingValue->hide();
}
_manualSettingValue->setVisible(
_entryData->_action == MacroActionSource::Action::SETTINGS &&
_entryData->_settingsInputMethod ==
MacroActionSource::SettingsInputMethod::
INDIVIDUAL_MANUAL);
const bool showWarning =
_entryData->_action == MacroActionSource::Action::ENABLE ||
_entryData->_action == MacroActionSource::Action::DISABLE;

View File

@@ -192,7 +192,7 @@ void MacroActionWebsocketEdit::CheckForSettingsConflict()
updateGeometry();
}
void MacroActionWebsocketEdit::SetWidgetVisibility()
void MacroActionWebsocketEdit::SetupWidgetVisibility()
{
_messageType->setVisible(_entryData->_api ==
MacroActionWebsocket::API::SCENE_SWITCHER);
@@ -283,7 +283,7 @@ void MacroActionWebsocketEdit::UpdateEntryData()
_message->setPlainText(_entryData->_message);
_connection->SetConnection(_entryData->_connection);
SetWidgetVisibility();
SetupWidgetVisibility();
}
void MacroActionWebsocketEdit::APITypeChanged(int index)
@@ -294,7 +294,7 @@ void MacroActionWebsocketEdit::APITypeChanged(int index)
auto lock = LockContext();
_entryData->_api = static_cast<MacroActionWebsocket::API>(index);
SetWidgetVisibility();
SetupWidgetVisibility();
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
@@ -308,7 +308,7 @@ void MacroActionWebsocketEdit::MessageTypeChanged(int index)
auto lock = LockContext();
_entryData->_type =
static_cast<MacroActionWebsocket::MessageType>(index);
SetWidgetVisibility();
SetupWidgetVisibility();
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}

View File

@@ -76,7 +76,7 @@ protected:
private:
void CheckForSettingsConflict();
void SetWidgetVisibility();
void SetupWidgetVisibility();
void SetupRequestEdit();
void SetupEventEdit();
void SetupGenericEdit();

View File

@@ -62,17 +62,17 @@ MacroConditionAudio::~MacroConditionAudio()
bool MacroConditionAudio::CheckOutputCondition()
{
bool ret = false;
OBSSourceAutoRelease source =
obs_weak_source_get_source(_audioSource.GetSource());
auto s = obs_weak_source_get_source(_audioSource.GetSource());
// peak will have a value from -60 db to 0 db so we need to scale it
double curVolume = ((double)_peak + 60) * 1.7;
switch (_outputCondition) {
case OutputCondition::ABOVE:
// peak will have a value from -60 db to 0 db
ret = curVolume > _volume;
break;
case OutputCondition::BELOW:
// peak will have a value from -60 db to 0 db
ret = curVolume < _volume;
break;
default:
@@ -84,21 +84,21 @@ bool MacroConditionAudio::CheckOutputCondition()
// Reset for next check
_peak = -std::numeric_limits<float>::infinity();
obs_source_release(s);
if (_audioSource.GetType() == SourceSelection::Type::VARIABLE) {
ResetVolmeter();
}
return ret && source;
return ret;
}
bool MacroConditionAudio::CheckVolumeCondition()
{
bool ret = false;
OBSSourceAutoRelease source =
obs_weak_source_get_source(_audioSource.GetSource());
auto s = obs_weak_source_get_source(_audioSource.GetSource());
float curVolume = obs_source_get_volume(source);
bool muted = obs_source_muted(source);
float curVolume = obs_source_get_volume(s);
bool muted = obs_source_muted(s);
switch (_volumeCondition) {
case VolumeCondition::ABOVE:
@@ -126,9 +126,10 @@ bool MacroConditionAudio::CheckVolumeCondition()
}
SetTempVarValue("configured_volume", std::to_string(curVolume));
SetTempVarValue("muted", (source && muted) ? "true" : "false");
SetTempVarValue("muted", muted ? "true" : "false");
return ret && source;
obs_source_release(s);
return ret;
}
bool MacroConditionAudio::CheckSyncOffset()
@@ -138,9 +139,8 @@ bool MacroConditionAudio::CheckSyncOffset()
}
bool ret = false;
OBSSourceAutoRelease source =
obs_weak_source_get_source(_audioSource.GetSource());
auto curOffset = obs_source_get_sync_offset(source) / nsPerMs;
auto s = obs_weak_source_get_source(_audioSource.GetSource());
auto curOffset = obs_source_get_sync_offset(s) / nsPerMs;
if (_outputCondition == OutputCondition::ABOVE) {
ret = curOffset > _syncOffset;
} else {
@@ -148,7 +148,8 @@ bool MacroConditionAudio::CheckSyncOffset()
}
SetVariableValue(std::to_string(curOffset));
SetTempVarValue("sync_offset", std::to_string(curOffset));
return ret && source;
obs_source_release(s);
return ret;
}
bool MacroConditionAudio::CheckMonitor()
@@ -158,13 +159,13 @@ bool MacroConditionAudio::CheckMonitor()
}
bool ret = false;
OBSSourceAutoRelease source =
obs_weak_source_get_source(_audioSource.GetSource());
ret = obs_source_get_monitoring_type(source) == _monitorType;
auto s = obs_weak_source_get_source(_audioSource.GetSource());
ret = obs_source_get_monitoring_type(s) == _monitorType;
SetVariableValue("");
SetTempVarValue("monitor",
std::to_string(obs_source_get_monitoring_type(source)));
return ret && source;
std::to_string(obs_source_get_monitoring_type(s)));
obs_source_release(s);
return ret;
}
bool MacroConditionAudio::CheckBalance()
@@ -174,9 +175,8 @@ bool MacroConditionAudio::CheckBalance()
}
bool ret = false;
OBSSourceAutoRelease source =
obs_weak_source_get_source(_audioSource.GetSource());
auto curBalance = obs_source_get_balance_value(source);
auto s = obs_weak_source_get_source(_audioSource.GetSource());
auto curBalance = obs_source_get_balance_value(s);
if (_outputCondition == OutputCondition::ABOVE) {
ret = curBalance > _balance;
} else {
@@ -184,7 +184,8 @@ bool MacroConditionAudio::CheckBalance()
}
SetVariableValue(std::to_string(curBalance));
SetTempVarValue("balance", std::to_string(curBalance));
return ret && source;
obs_source_release(s);
return ret;
}
bool MacroConditionAudio::CheckCondition()
@@ -238,12 +239,13 @@ obs_volmeter_t *AddVolmeterToSource(MacroConditionAudio *entry,
obs_volmeter_t *volmeter = obs_volmeter_create(OBS_FADER_LOG);
obs_volmeter_add_callback(volmeter, MacroConditionAudio::SetVolumeLevel,
entry);
OBSSourceAutoRelease audioSource = obs_weak_source_get_source(source);
if (!obs_volmeter_attach_source(volmeter, audioSource)) {
const char *name = obs_source_get_name(audioSource);
obs_source_t *as = obs_weak_source_get_source(source);
if (!obs_volmeter_attach_source(volmeter, as)) {
const char *name = obs_source_get_name(as);
blog(LOG_WARNING, "failed to attach volmeter to source %s",
name);
}
obs_source_release(as);
return volmeter;
}
@@ -453,9 +455,10 @@ MacroConditionAudioEdit::MacroConditionAudioEdit(
void MacroConditionAudioEdit::UpdateVolmeterSource()
{
delete _volMeter;
OBSSourceAutoRelease soruce = obs_weak_source_get_source(
obs_source_t *soruce = obs_weak_source_get_source(
_entryData->_audioSource.GetSource());
_volMeter = new VolControl(soruce.Get());
_volMeter = new VolControl(soruce);
obs_source_release(soruce);
QLayout *layout = this->layout();
layout->addWidget(_volMeter);

View File

@@ -24,8 +24,6 @@ const static std::map<MacroConditionMacro::Type, std::string>
"AdvSceneSwitcher.condition.macro.type.actionDisabled"},
{MacroConditionMacro::Type::ACTION_ENABLED,
"AdvSceneSwitcher.condition.macro.type.actionEnabled"},
{MacroConditionMacro::Type::PAUSED,
"AdvSceneSwitcher.condition.macro.type.paused"},
};
const static std::map<MacroConditionMacro::CounterCondition, std::string>
@@ -77,8 +75,6 @@ bool MacroConditionMacro::CheckMultiStateCondition()
}
}
SetTempVarValue("matchedCount", std::to_string(matchedCount));
switch (_multiSateCondition) {
case MacroConditionMacro::MultiStateCondition::BELOW:
return matchedCount < _multiSateCount;
@@ -111,16 +107,6 @@ bool MacroConditionMacro::CheckActionStateCondition()
return false;
}
bool MacroConditionMacro::CheckPauseState()
{
auto macro = _macro.GetMacro();
if (!macro) {
return false;
}
return macro->Paused();
}
bool MacroConditionMacro::CheckCountCondition()
{
auto macro = _macro.GetMacro();
@@ -128,8 +114,6 @@ bool MacroConditionMacro::CheckCountCondition()
return false;
}
SetTempVarValue("runCount", std::to_string(macro->RunCount()));
switch (_counterCondition) {
case CounterCondition::BELOW:
return macro->RunCount() < _count;
@@ -156,8 +140,6 @@ bool MacroConditionMacro::CheckCondition()
case Type::ACTION_DISABLED:
case Type::ACTION_ENABLED:
return CheckActionStateCondition();
case Type::PAUSED:
return CheckPauseState();
default:
break;
}
@@ -211,7 +193,7 @@ bool MacroConditionMacro::Load(obs_data_t *obj)
bool MacroConditionMacro::PostLoad()
{
return MacroCondition::PostLoad() && MacroRefCondition::PostLoad() &&
return MacroRefCondition::PostLoad() &&
MultiMacroRefCondtition::PostLoad();
}
@@ -220,45 +202,6 @@ std::string MacroConditionMacro::GetShortDesc() const
return _macro.Name();
}
void MacroConditionMacro::SetType(Type type)
{
_type = type;
SetupTempVars();
}
void MacroConditionMacro::SetupTempVars()
{
MacroCondition::SetupTempVars();
switch (_type) {
case Type::COUNT:
AddTempvar(
"runCount",
obs_module_text(
"AdvSceneSwitcher.tempVar.macro.runCount"),
obs_module_text(
"AdvSceneSwitcher.tempVar.macro.runCount.description"));
break;
case Type::STATE:
break;
case Type::MULTI_STATE:
AddTempvar(
"matchedCount",
obs_module_text(
"AdvSceneSwitcher.tempVar.macro.matchedCount"),
obs_module_text(
"AdvSceneSwitcher.tempVar.macro.matchedCount.description"));
break;
case Type::ACTION_DISABLED:
break;
case Type::ACTION_ENABLED:
break;
case Type::PAUSED:
break;
default:
break;
}
}
static inline void populateTypeSelection(QComboBox *list)
{
for (auto entry : macroConditionTypes) {
@@ -392,10 +335,7 @@ void MacroConditionMacroEdit::ClearLayouts()
void MacroConditionMacroEdit::SetupWidgets()
{
SetWidgetVisibility();
ClearLayouts();
switch (_entryData->GetType()) {
switch (_entryData->_type) {
case MacroConditionMacro::Type::COUNT:
SetupCountWidgets();
break;
@@ -411,9 +351,6 @@ void MacroConditionMacroEdit::SetupWidgets()
case MacroConditionMacro::Type::ACTION_ENABLED:
SetupActionStateWidgets(true);
break;
case MacroConditionMacro::Type::PAUSED:
SetupPauseWidgets();
break;
default:
break;
}
@@ -421,24 +358,38 @@ void MacroConditionMacroEdit::SetupWidgets()
void MacroConditionMacroEdit::SetupStateWidgets()
{
SetWidgetVisibility();
ClearLayouts();
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{macros}}", _macros},
};
PlaceWidgets(
obs_module_text("AdvSceneSwitcher.condition.macro.state.entry"),
_settingsLine1, {{"{{macros}}", _macros}});
_settingsLine1, widgetPlaceholders);
}
void MacroConditionMacroEdit::SetupMultiStateWidgets()
{
SetWidgetVisibility();
ClearLayouts();
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{multiStateConditions}}", _multiStateConditions},
{"{{multiStateCount}}", _multiStateCount},
};
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.macro.multistate.entry"),
_settingsLine1,
{{"{{multiStateConditions}}", _multiStateConditions},
{"{{multiStateCount}}", _multiStateCount}});
_settingsLine1, widgetPlaceholders);
}
void MacroConditionMacroEdit::SetupCountWidgets()
{
const std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
SetWidgetVisibility();
ClearLayouts();
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{macros}}", _macros},
{"{{conditions}}", _counterConditions},
{"{{count}}", _count},
@@ -457,6 +408,9 @@ void MacroConditionMacroEdit::SetupCountWidgets()
void MacroConditionMacroEdit::SetupActionStateWidgets(bool enable)
{
SetWidgetVisibility();
ClearLayouts();
PlaceWidgets(
obs_module_text(
enable ? "AdvSceneSwitcher.condition.macro.actionState.enabled.entry"
@@ -465,49 +419,35 @@ void MacroConditionMacroEdit::SetupActionStateWidgets(bool enable)
{{"{{macros}}", _macros}, {"{{actionIndex}}", _actionIndex}});
}
void MacroConditionMacroEdit::SetupPauseWidgets()
{
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.condition.macro.paused.entry"),
_settingsLine1, {{"{{macros}}", _macros}});
}
void MacroConditionMacroEdit::SetWidgetVisibility()
{
_macros->setVisible(
_entryData->GetType() == MacroConditionMacro::Type::COUNT ||
_entryData->GetType() == MacroConditionMacro::Type::STATE ||
_entryData->GetType() ==
_entryData->_type == MacroConditionMacro::Type::COUNT ||
_entryData->_type == MacroConditionMacro::Type::STATE ||
_entryData->_type ==
MacroConditionMacro::Type::ACTION_DISABLED ||
_entryData->GetType() ==
MacroConditionMacro::Type::ACTION_ENABLED ||
_entryData->GetType() == MacroConditionMacro::Type::PAUSED);
_counterConditions->setVisible(_entryData->GetType() ==
_entryData->_type == MacroConditionMacro::Type::ACTION_ENABLED);
_counterConditions->setVisible(_entryData->_type ==
MacroConditionMacro::Type::COUNT);
_count->setVisible(_entryData->GetType() ==
_count->setVisible(_entryData->_type ==
MacroConditionMacro::Type::COUNT);
_currentCount->setVisible(_entryData->GetType() ==
_currentCount->setVisible(_entryData->_type ==
MacroConditionMacro::Type::COUNT);
_resetCount->setVisible(_entryData->GetType() ==
_resetCount->setVisible(_entryData->_type ==
MacroConditionMacro::Type::COUNT);
_macroList->setVisible(_entryData->GetType() ==
_macroList->setVisible(_entryData->_type ==
MacroConditionMacro::Type::MULTI_STATE);
_multiStateConditions->setVisible(
_entryData->GetType() ==
MacroConditionMacro::Type::MULTI_STATE);
_multiStateCount->setVisible(_entryData->GetType() ==
_entryData->_type == MacroConditionMacro::Type::MULTI_STATE);
_multiStateCount->setVisible(_entryData->_type ==
MacroConditionMacro::Type::MULTI_STATE);
_actionIndex->setVisible(
_entryData->GetType() ==
_entryData->_type ==
MacroConditionMacro::Type::ACTION_DISABLED ||
_entryData->GetType() ==
MacroConditionMacro::Type::ACTION_ENABLED);
if (_entryData->GetType() == MacroConditionMacro::Type::MULTI_STATE ||
_entryData->GetType() ==
MacroConditionMacro::Type::ACTION_DISABLED ||
_entryData->GetType() ==
MacroConditionMacro::Type::ACTION_ENABLED ||
_entryData->GetType() == MacroConditionMacro::Type::PAUSED) {
_entryData->_type == MacroConditionMacro::Type::ACTION_ENABLED);
if (_entryData->_type == MacroConditionMacro::Type::MULTI_STATE ||
_entryData->_type == MacroConditionMacro::Type::ACTION_DISABLED ||
_entryData->_type == MacroConditionMacro::Type::ACTION_ENABLED) {
_pausedWarning->hide();
}
@@ -523,7 +463,7 @@ void MacroConditionMacroEdit::UpdateEntryData()
SetupWidgets();
_macros->SetCurrentMacro(_entryData->_macro);
_types->setCurrentIndex(static_cast<int>(_entryData->GetType()));
_types->setCurrentIndex(static_cast<int>(_entryData->_type));
_counterConditions->setCurrentIndex(
static_cast<int>(_entryData->_counterCondition));
_count->SetValue(_entryData->_count);
@@ -595,7 +535,7 @@ void MacroConditionMacroEdit::TypeChanged(int type)
}
auto lock = LockContext();
_entryData->SetType(static_cast<MacroConditionMacro::Type>(type));
_entryData->_type = static_cast<MacroConditionMacro::Type>(type);
SetupWidgets();
}
@@ -631,8 +571,7 @@ void MacroConditionMacroEdit::UpdatePaused()
auto macro = _entryData->_macro.GetMacro();
_pausedWarning->setVisible(
_entryData &&
_entryData->GetType() !=
MacroConditionMacro::Type::MULTI_STATE &&
_entryData->_type != MacroConditionMacro::Type::MULTI_STATE &&
macro && macro->Paused());
adjustSize();
updateGeometry();

View File

@@ -39,10 +39,8 @@ public:
MULTI_STATE,
ACTION_DISABLED,
ACTION_ENABLED,
PAUSED,
};
void SetType(Type);
Type GetType() const { return _type; }
Type _type = Type::STATE;
enum class CounterCondition {
BELOW,
@@ -66,11 +64,7 @@ private:
bool CheckStateCondition();
bool CheckMultiStateCondition();
bool CheckActionStateCondition();
bool CheckPauseState();
void SetupTempVars();
Type _type = Type::STATE;
static bool _registered;
static const std::string id;
};
@@ -134,7 +128,6 @@ private:
void SetupMultiStateWidgets();
void SetupCountWidgets();
void SetupActionStateWidgets(bool enable);
void SetupPauseWidgets();
void SetWidgetVisibility();
bool _loading = true;
};

View File

@@ -19,43 +19,25 @@ bool MacroConditionProcess::CheckCondition()
QStringList runningProcesses;
QString proc = QString::fromStdString(_process);
GetProcessList(runningProcesses);
std::string foregroundProcessName;
GetForegroundProcessName(foregroundProcessName);
SetVariableValue(foregroundProcessName);
bool equals = runningProcesses.contains(proc);
bool matches = runningProcesses.indexOf(QRegularExpression(proc)) != -1;
bool focus = !_focus || IsInFocus(proc);
if (!_regex.Enabled()) {
if (runningProcesses.contains(proc) &&
(!_checkFocus || IsInFocus(proc))) {
SetTempVarValue("name", proc.toStdString());
return true;
}
return false;
if (IsReferencedInVars()) {
std::string name;
GetForegroundProcessName(name);
SetVariableValue(name);
}
auto matchIndex = runningProcesses.indexOf(QRegularExpression(proc));
if (matchIndex == -1) {
return false;
}
if (!_checkFocus) {
SetTempVarValue("name",
runningProcesses.at(matchIndex).toStdString());
return true;
}
if (!IsInFocus(proc)) {
return false;
}
SetTempVarValue("name", foregroundProcessName);
return true;
return (equals || matches) && focus;
}
bool MacroConditionProcess::Save(obs_data_t *obj) const
{
MacroCondition::Save(obj);
obs_data_set_string(obj, "process", _process.c_str());
obs_data_set_bool(obj, "focus", _checkFocus);
_regex.Save(obj);
obs_data_set_int(obj, "version", 1);
obs_data_set_bool(obj, "focus", _focus);
return true;
}
@@ -63,13 +45,7 @@ bool MacroConditionProcess::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_process = obs_data_get_string(obj, "process");
_checkFocus = obs_data_get_bool(obj, "focus");
// Fall back to partial match regex as default for old version
if (!obs_data_has_user_value(obj, "version")) {
_regex.SetEnabled(true);
} else {
_regex.Load(obj);
}
_focus = obs_data_get_bool(obj, "focus");
return true;
}
@@ -78,18 +54,10 @@ std::string MacroConditionProcess::GetShortDesc() const
return _process;
}
void MacroConditionProcess::SetupTempVars()
{
MacroCondition::SetupTempVars();
AddTempvar("name",
obs_module_text("AdvSceneSwitcher.tempVar.process.name"));
}
MacroConditionProcessEdit::MacroConditionProcessEdit(
QWidget *parent, std::shared_ptr<MacroConditionProcess> entryData)
: QWidget(parent),
_processSelection(new QComboBox()),
_regex(new RegexConfigWidget(this)),
_focused(new QCheckBox()),
_focusProcess(new QLabel()),
_focusLayout(new QHBoxLayout())
@@ -100,8 +68,6 @@ MacroConditionProcessEdit::MacroConditionProcessEdit(
QWidget::connect(_processSelection,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(ProcessChanged(const QString &)));
QWidget::connect(_regex, SIGNAL(RegexConfigChanged(RegexConfig)), this,
SLOT(RegexChanged(RegexConfig)));
QWidget::connect(_focused, SIGNAL(stateChanged(int)), this,
SLOT(FocusChanged(int)));
QWidget::connect(&_timer, SIGNAL(timeout()), this,
@@ -111,7 +77,6 @@ MacroConditionProcessEdit::MacroConditionProcessEdit(
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{processes}}", _processSelection},
{"{{regex}}", _regex},
{"{{focused}}", _focused},
{"{{focusProcess}}", _focusProcess},
};
@@ -147,18 +112,6 @@ void MacroConditionProcessEdit::ProcessChanged(const QString &text)
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroConditionProcessEdit::RegexChanged(RegexConfig conf)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_regex = conf;
adjustSize();
updateGeometry();
}
void MacroConditionProcessEdit::FocusChanged(int state)
{
if (_loading || !_entryData) {
@@ -166,7 +119,7 @@ void MacroConditionProcessEdit::FocusChanged(int state)
}
auto lock = LockContext();
_entryData->_checkFocus = state;
_entryData->_focus = state;
SetWidgetVisibility();
}
@@ -181,7 +134,7 @@ void MacroConditionProcessEdit::SetWidgetVisibility()
if (!_entryData) {
return;
}
SetLayoutVisible(_focusLayout, _entryData->_checkFocus);
SetLayoutVisible(_focusLayout, _entryData->_focus);
adjustSize();
}
@@ -192,8 +145,7 @@ void MacroConditionProcessEdit::UpdateEntryData()
}
_processSelection->setCurrentText(_entryData->_process.c_str());
_regex->SetRegexConfig(_entryData->_regex);
_focused->setChecked(_entryData->_checkFocus);
_focused->setChecked(_entryData->_focus);
SetWidgetVisibility();
}

View File

@@ -1,6 +1,5 @@
#pragma once
#include "macro-condition-edit.hpp"
#include "regex-config.hpp"
#include <QComboBox>
#include <QCheckBox>
@@ -21,12 +20,9 @@ public:
}
std::string _process;
bool _checkFocus = true;
RegexConfig _regex = RegexConfig::PartialMatchRegexConfig();
bool _focus = true;
private:
void SetupTempVars();
static bool _registered;
static const std::string id;
};
@@ -49,22 +45,22 @@ public:
private slots:
void ProcessChanged(const QString &text);
void RegexChanged(RegexConfig);
void FocusChanged(int state);
void UpdateFocusProcess();
signals:
void HeaderInfoChanged(const QString &);
private:
void SetWidgetVisibility();
protected:
QComboBox *_processSelection;
RegexConfigWidget *_regex;
QCheckBox *_focused;
QLabel *_focusProcess;
QHBoxLayout *_focusLayout;
QTimer _timer;
std::shared_ptr<MacroConditionProcess> _entryData;
private:
void SetWidgetVisibility();
bool _loading = true;
};

View File

@@ -1,8 +1,7 @@
#include "macro-condition-recording.hpp"
#include "macro.hpp"
#include "utility.hpp"
#include <thread>
namespace advss {
const std::string MacroConditionRecord::id = "recording";
@@ -20,13 +19,29 @@ const static std::map<MacroConditionRecord::Condition, std::string>
"AdvSceneSwitcher.condition.record.state.pause"},
{MacroConditionRecord::Condition::START,
"AdvSceneSwitcher.condition.record.state.start"},
{MacroConditionRecord::Condition::DURATION,
"AdvSceneSwitcher.condition.record.state.duration"},
{MacroConditionRecord::Condition::SAVE_DONE,
"AdvSceneSwitcher.condition.record.state.saveDone"},
};
static bool SetupRecordingTimer();
static bool recordingTimerIsSetup = SetupRecordingTimer();
static int currentRecordingDurationInSeconds = 0;
MacroConditionRecord::MacroConditionRecord(Macro *m) : MacroCondition(m)
{
OBSOutputAutoRelease output = obs_frontend_get_recording_output();
if (!output) {
return;
}
auto sh = obs_output_get_signal_handler(output);
signal_handler_connect(sh, "stop", StopSignal, this);
}
MacroConditionRecord::~MacroConditionRecord()
{
OBSOutputAutoRelease output = obs_frontend_get_recording_output();
if (!output) {
return;
}
auto sh = obs_output_get_signal_handler(output);
signal_handler_disconnect(sh, "stop", StopSignal, this);
}
bool MacroConditionRecord::CheckCondition()
{
@@ -37,11 +52,12 @@ bool MacroConditionRecord::CheckCondition()
return obs_frontend_recording_paused();
case Condition::START:
return obs_frontend_recording_active();
case Condition::DURATION:
SetTempVarValue(
"durationSeconds",
std::to_string(currentRecordingDurationInSeconds));
return currentRecordingDurationInSeconds > _duration.Seconds();
case Condition::SAVE_DONE:
if (_recordingSaveDone) {
_recordingSaveDone = false;
return true;
}
return false;
default:
break;
}
@@ -52,7 +68,6 @@ bool MacroConditionRecord::Save(obs_data_t *obj) const
{
MacroCondition::Save(obj);
obs_data_set_int(obj, "state", static_cast<int>(_condition));
_duration.Save(obj);
return true;
}
@@ -60,30 +75,24 @@ bool MacroConditionRecord::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_condition = static_cast<Condition>(obs_data_get_int(obj, "state"));
_duration.Load(obj);
return true;
}
void MacroConditionRecord::SetCondition(Condition condition)
void MacroConditionRecord::StopSignal(void *c, calldata_t *data)
{
_condition = condition;
SetupTempVars();
}
auto condition = static_cast<MacroConditionRecord *>(c);
const auto macro = condition->GetMacro();
if (macro && macro->Paused()) {
return;
}
void MacroConditionRecord::SetupTempVars()
{
MacroCondition::SetupTempVars();
switch (_condition) {
case Condition::DURATION:
AddTempvar(
"durationSeconds",
obs_module_text(
"AdvSceneSwitcher.tempVar.recording.durationSeconds"),
obs_module_text(
"AdvSceneSwitcher.tempVar.recording.durationSeconds.description"));
break;
default:
break;
long long code = 0;
if (!calldata_get_int(data, "code", &code)) {
condition->_recordingSaveDone = false;
}
if (code == OBS_OUTPUT_SUCCESS) {
condition->_recordingSaveDone = true;
}
}
@@ -96,39 +105,39 @@ static inline void populateStateSelection(QComboBox *list)
MacroConditionRecordEdit::MacroConditionRecordEdit(
QWidget *parent, std::shared_ptr<MacroConditionRecord> entryData)
: QWidget(parent),
_condition(new QComboBox(this)),
_duration(new DurationSelection(this))
: QWidget(parent)
{
populateStateSelection(_condition);
_recordState = new QComboBox();
QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
QWidget::connect(_duration, SIGNAL(DurationChanged(const Duration &)),
this, SLOT(DurationChanged(const Duration &)));
QWidget::connect(_recordState, SIGNAL(currentIndexChanged(int)), this,
SLOT(StateChanged(int)));
populateStateSelection(_recordState);
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{recordState}}", _recordState},
};
auto layout = new QHBoxLayout;
PlaceWidgets(obs_module_text("AdvSceneSwitcher.condition.record.entry"),
layout,
{{"{{condition}}", _condition},
{"{{duration}}", _duration}});
setLayout(layout);
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroConditionRecordEdit::ConditionChanged(int value)
void MacroConditionRecordEdit::StateChanged(int value)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->SetCondition(
static_cast<MacroConditionRecord::Condition>(value));
SetWidgetVisibility();
_entryData->_condition =
static_cast<MacroConditionRecord::Condition>(value);
}
void MacroConditionRecordEdit::UpdateEntryData()
@@ -137,65 +146,7 @@ void MacroConditionRecordEdit::UpdateEntryData()
return;
}
_condition->setCurrentIndex(
static_cast<int>(_entryData->GetCondition()));
_duration->SetDuration(_entryData->_duration);
SetWidgetVisibility();
}
void MacroConditionRecordEdit::DurationChanged(const Duration &duration)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_duration = duration;
}
void MacroConditionRecordEdit::SetWidgetVisibility()
{
_duration->setVisible(_entryData->GetCondition() ==
MacroConditionRecord::Condition::DURATION);
}
bool SetupRecordingTimer()
{
static std::atomic_bool recordingStopped = {true};
static std::atomic_bool recordingPaused = {false};
static auto handleRecordingEvents = [](enum obs_frontend_event event,
void *) {
switch (event) {
case OBS_FRONTEND_EVENT_RECORDING_STOPPED:
recordingStopped = true;
break;
case OBS_FRONTEND_EVENT_RECORDING_STARTED:
recordingStopped = false;
break;
case OBS_FRONTEND_EVENT_RECORDING_PAUSED:
recordingPaused = true;
break;
case OBS_FRONTEND_EVENT_RECORDING_UNPAUSED:
recordingPaused = false;
break;
};
};
obs_frontend_add_event_callback(handleRecordingEvents, nullptr);
std::thread thread([]() {
while (true) {
std::this_thread::sleep_for(std::chrono::seconds(1));
if (recordingStopped) {
currentRecordingDurationInSeconds = 0;
continue;
}
if (recordingPaused) {
continue;
}
++currentRecordingDurationInSeconds;
}
});
thread.detach();
return true;
_recordState->setCurrentIndex(static_cast<int>(_entryData->_condition));
}
} // namespace advss

View File

@@ -8,7 +8,8 @@ namespace advss {
class MacroConditionRecord : public MacroCondition {
public:
MacroConditionRecord(Macro *m) : MacroCondition(m) {}
MacroConditionRecord(Macro *m);
~MacroConditionRecord();
bool CheckCondition();
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
@@ -22,17 +23,14 @@ public:
STOP,
PAUSE,
START,
DURATION,
SAVE_DONE,
};
void SetCondition(Condition);
Condition GetCondition() const { return _condition; }
Duration _duration;
Condition _condition = Condition::STOP;
private:
void SetupTempVars();
static void StopSignal(void *condition, calldata_t *);
bool _recordingSaveDone = false;
Condition _condition = Condition::STOP;
static bool _registered;
static const std::string id;
};
@@ -54,15 +52,13 @@ public:
}
private slots:
void ConditionChanged(int value);
void DurationChanged(const Duration &);
void StateChanged(int value);
protected:
QComboBox *_recordState;
std::shared_ptr<MacroConditionRecord> _entryData;
private:
void SetWidgetVisibility();
QComboBox *_condition;
DurationSelection *_duration;
std::shared_ptr<MacroConditionRecord> _entryData;
bool _loading = true;
};

View File

@@ -34,24 +34,28 @@ static bool matchRegex(const RegexConfig &conf, const std::string &msg,
bool MacroConditionWebsocket::CheckCondition()
{
std::vector<std::string> messages;
const std::vector<std::string> *messages = nullptr;
switch (_type) {
case MacroConditionWebsocket::Type::REQUEST:
messages = GetWebsocketMessages();
messages = &switcher->websocketMessages;
break;
case MacroConditionWebsocket::Type::EVENT: {
auto connection = _connection.lock();
if (!connection) {
return false;
}
messages = connection->Events();
messages = &connection->Events();
break;
}
default:
break;
}
for (const auto &msg : messages) {
if (!messages) {
return false;
}
for (const auto &msg : *messages) {
if (_regex.Enabled()) {
if (matchRegex(_regex, msg, _message)) {
SetVariableValue(msg);

View File

@@ -176,7 +176,6 @@ std::string MacroConditionWindow::GetShortDesc() const
void MacroConditionWindow::SetupTempVars()
{
MacroCondition::SetupTempVars();
AddTempvar(
"window",
obs_module_text("AdvSceneSwitcher.tempVar.window.window"),

View File

@@ -146,6 +146,7 @@ void MacroSegment::InvalidateTempVarValues()
std::optional<const TempVariable>
MacroSegment::GetTempVar(const std::string &id) const
{
TempVariable *result = nullptr;
for (auto &var : _tempVariables) {
if (var.ID() == id) {
return var;

View File

@@ -235,18 +235,21 @@ void AdvSceneSwitcher::ExportMacros()
}
}
OBSDataAutoRelease data = obs_data_create();
OBSDataArrayAutoRelease macroArray = obs_data_array_create();
auto data = obs_data_create();
auto macroArray = obs_data_array_create();
for (const auto &macro : macros) {
OBSDataAutoRelease obj = obs_data_create();
obs_data_t *obj = obs_data_create();
macro->Save(obj);
obs_data_array_push_back(macroArray, obj);
obs_data_release(obj);
}
obs_data_set_array(data, "macros", macroArray);
switcher->SaveVariables(data);
obs_data_array_release(macroArray);
obs_data_set_string(data, "version", g_GIT_TAG);
auto json = obs_data_get_json(data);
QString exportString(json);
obs_data_release(data);
MacroExportImportDialog::ExportMacros(exportString);
}
@@ -265,9 +268,8 @@ void AdvSceneSwitcher::SetupMacroSegmentSelection(MacroSection type, int idx)
return;
}
MacroSegmentList *setList = nullptr, *resetList1 = nullptr,
*resetList2 = nullptr;
int *setIdx = nullptr, *resetIdx1 = nullptr, *resetIdx2 = nullptr;
MacroSegmentList *setList, *resetList1, *resetList2;
int *setIdx, *resetIdx1, *resetIdx2;
std::deque<std::shared_ptr<MacroSegment>> segements;
switch (type) {
@@ -392,8 +394,7 @@ void AdvSceneSwitcher::ImportMacros()
if (!MacroExportImportDialog::ImportMacros(json)) {
return;
}
OBSDataAutoRelease data =
obs_data_create_from_json(json.toStdString().c_str());
auto data = obs_data_create_from_json(json.toStdString().c_str());
if (!data) {
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.macroTab.import.invalid"));
@@ -409,26 +410,27 @@ void AdvSceneSwitcher::ImportMacros()
version);
}
OBSDataArrayAutoRelease array = obs_data_get_array(data, "macros");
auto array = obs_data_get_array(data, "macros");
size_t count = obs_data_array_count(array);
int groupSize = 0;
std::shared_ptr<Macro> group;
std::vector<std::shared_ptr<Macro>> importedMacros;
auto lock = LockContext();
for (size_t i = 0; i < count; i++) {
OBSDataAutoRelease array_obj = obs_data_array_item(array, i);
obs_data_t *array_obj = obs_data_array_item(array, i);
auto macro = std::make_shared<Macro>();
macro->Load(array_obj);
macro->PostLoad();
if (macroNameExists(macro->Name()) &&
!ResolveMacroImportNameConflict(macro)) {
obs_data_release(array_obj);
groupSize--;
continue;
}
importedMacros.emplace_back(macro);
switcher->macros.emplace_back(macro);
if (groupSize > 0 && !macro->IsGroup()) {
Macro::PrepareMoveToGroup(group, macro);
@@ -443,11 +445,11 @@ void AdvSceneSwitcher::ImportMacros()
// to the group as they come up.
macro->ResetGroupSize();
}
}
for (const auto &macro : importedMacros) {
macro->PostLoad();
obs_data_release(array_obj);
}
obs_data_array_release(array);
obs_data_release(data);
ui->macros->Reset(switcher->macros,
switcher->macroProperties._highlightExecuted);
@@ -698,7 +700,8 @@ void AdvSceneSwitcher::MacroSelectionAboutToChange()
// actions nor elseActions being visible when the condition <-> action
// splitter is moved
if (elsePos[0] == 0 && elsePos[1] == 0) {
maximizeFirstSplitterEntry(ui->macroElseActionSplitter);
macro->SetElseActionSplitterPosition(QList<int>()
<< 999999 << 0);
return;
}
macro->SetElseActionSplitterPosition(
@@ -787,8 +790,6 @@ bool shouldRestoreSplitter(const QList<int> &pos)
void AdvSceneSwitcher::SetupMacroTab()
{
ui->macroElseActions->installEventFilter(this);
if (switcher->macros.size() == 0 && !switcher->disableHints) {
addPulse = PulseWidget(ui->macroAdd, QColor(Qt::green));
}
@@ -861,8 +862,6 @@ void AdvSceneSwitcher::SetupMacroTab()
SetButtonIcon(ui->conditionTop, (pathPrefix + "DoubleUp.svg").c_str());
SetButtonIcon(ui->conditionBottom,
(pathPrefix + "DoubleDown.svg").c_str());
SetButtonIcon(ui->toggleElseActions,
(pathPrefix + "NotEqual.svg").c_str());
// Reserve more space for macro edit area than for the macro list
ui->macroListMacroEditSplitter->setStretchFactor(0, 1);
@@ -989,12 +988,13 @@ void AdvSceneSwitcher::CopyMacro()
return;
}
OBSDataAutoRelease data = obs_data_create();
obs_data_t *data = obs_data_create();
macro->Save(data);
newMacro->Load(data);
newMacro->PostLoad();
newMacro->SetName(name);
Macro::PrepareMoveToGroup(macro->Parent(), newMacro);
obs_data_release(data);
ui->macros->Add(newMacro, macro);
ui->macroAdd->disconnect(addPulse);
@@ -1100,31 +1100,6 @@ void AdvSceneSwitcher::MaximizeConditions()
MinimizeActions();
}
void AdvSceneSwitcher::on_toggleElseActions_clicked()
{
auto elsePosition = ui->macroElseActionSplitter->sizes();
if (elsePosition[1] == 0) {
centerSplitterPosition(ui->macroElseActionSplitter);
return;
}
maximizeFirstSplitterEntry(ui->macroElseActionSplitter);
}
void AdvSceneSwitcher::SetElseActionsStateToHidden()
{
ui->toggleElseActions->setToolTip(obs_module_text(
"AdvSceneSwitcher.macroTab.toggleElseActions.show.tooltip"));
ui->toggleElseActions->setChecked(false);
}
void AdvSceneSwitcher::SetElseActionsStateToVisible()
{
ui->toggleElseActions->setToolTip(obs_module_text(
"AdvSceneSwitcher.macroTab.toggleElseActions.hide.tooltip"));
ui->toggleElseActions->setChecked(true);
}
bool AdvSceneSwitcher::MacroTabIsInFocus()
{
return isActiveWindow() && isAncestorOf(focusWidget()) &&

View File

@@ -149,7 +149,6 @@ bool Macro::CeckMatch()
}
break;
case LogicType::OR_NOT:
_matched = _matched || !cond;
if (!cond) {
c->SetHighlight();
}

View File

@@ -19,7 +19,8 @@ bool MacroConditionMidi::CheckCondition()
for (const auto &m : *messages) {
if (m.Matches(_message)) {
SetVariableValues(m);
SetVariableValue(std::to_string(m.Note()) + " " +
std::to_string(m.Value()));
return true;
}
}
@@ -48,36 +49,6 @@ std::string MacroConditionMidi::GetShortDesc() const
return _device.Name();
}
void MacroConditionMidi::SetupTempVars()
{
MacroCondition::SetupTempVars();
AddTempvar("type",
obs_module_text("AdvSceneSwitcher.tempVar.midi.type"));
AddTempvar("channel",
obs_module_text("AdvSceneSwitcher.tempVar.midi.channel"));
AddTempvar("note",
obs_module_text("AdvSceneSwitcher.tempVar.midi.note"));
AddTempvar("value1",
obs_module_text("AdvSceneSwitcher.tempVar.midi.value1"));
AddTempvar("value2",
obs_module_text("AdvSceneSwitcher.tempVar.midi.value2"));
}
void MacroConditionMidi::SetVariableValues(const MidiMessage &m)
{
SetVariableValue(std::to_string(m.Note()) + " " +
std::to_string(m.Value()));
SetTempVarValue("type", m.MidiTypeToString(m.Type()));
SetTempVarValue("channel", std::to_string(m.Channel()));
try {
SetTempVarValue("note",
GetAllNotes().at(m.Note()).toStdString());
} catch (...) {
}
SetTempVarValue("value1", std::to_string(m.Note()));
SetTempVarValue("value2", std::to_string(m.Value()));
}
MacroConditionMidiEdit::MacroConditionMidiEdit(
QWidget *parent, std::shared_ptr<MacroConditionMidi> entryData)
: QWidget(parent),

View File

@@ -24,9 +24,6 @@ public:
MidiMessage _message;
private:
void SetupTempVars();
void SetVariableValues(const MidiMessage &);
static bool _registered;
static const std::string id;
};

View File

@@ -1,17 +1,15 @@
#include "midi-helpers.hpp"
#include <utility.hpp>
#include <log-helper.hpp>
#include <obs-module-helper.hpp>
#include <plugin-state-helper.hpp>
#include <sync-helper.hpp>
#include <switcher-data.hpp>
namespace advss {
static std::map<std::pair<MidiDeviceType, int>, MidiDeviceInstance *>
SetupMidiMessageVector()
{
AddIntervalResetStep(
GetSwitcher()->AddIntervalResetStep(
MidiDeviceInstance::ClearMessageBuffersOfAllDevices);
return {};
}

View File

@@ -190,7 +190,7 @@ void CategoryGrabber::Search(const std::string &)
std::string cursor;
httplib::Params params = {
{"first", "100"}, {"after", cursor}, {"query", _searchString}};
auto response = SendGetRequest(*_token, uri, path, params);
auto response = SendGetRequest(uri, path, *_token, params);
while (response.status == 200 && !_stop) {
cursor = ParseReply(response.data);
@@ -200,7 +200,7 @@ void CategoryGrabber::Search(const std::string &)
params = {{"first", "100"},
{"after", cursor},
{"query", _searchString}};
response = SendGetRequest(*_token, uri, path, params);
response = SendGetRequest(uri, path, *_token, params);
emit CategoryCountUpdated(_categoryMap.size() - startCount);
}
}
@@ -214,7 +214,7 @@ void CategoryGrabber::GetAll()
static std::string cursor;
httplib::Params params = {{"first", "100"}, {"after", cursor}};
auto response = SendGetRequest(*_token, uri, path, params);
auto response = SendGetRequest(uri, path, *_token, params);
while (response.status == 200 && !_stop) {
cursor = ParseReply(response.data);
@@ -222,7 +222,7 @@ void CategoryGrabber::GetAll()
break; // End of category list
}
params = {{"first", "100"}, {"after", cursor}};
response = SendGetRequest(*_token, uri, path, params);
response = SendGetRequest(uri, path, *_token, params);
emit CategoryCountUpdated(_categoryMap.size());
}
}

View File

@@ -35,8 +35,8 @@ std::string TwitchChannel::GetUserID(const TwitchToken &token) const
return it->second;
}
auto res = SendGetRequest(token, "https://api.twitch.tv",
"/helix/users", {{"login", _name}});
auto res = SendGetRequest("https://api.twitch.tv", "/helix/users",
token, {{"login", _name}});
if (res.status == 400) {
userIDCache[_name] = "invalid";
@@ -112,8 +112,8 @@ TwitchChannel::GetLiveInfo(const TwitchToken &token)
httplib::Params params = {
{"first", "1"}, {"after", ""}, {"user_id", id}};
auto result = SendGetRequest(token, "https://api.twitch.tv",
"/helix/streams", params, true);
auto result = SendGetRequest("https://api.twitch.tv", "/helix/streams",
token, params, true);
if (result.status != 200) {
return {};
}
@@ -153,8 +153,8 @@ std::optional<ChannelInfo> TwitchChannel::GetInfo(const TwitchToken &token)
httplib::Params params = {
{"first", "1"}, {"after", ""}, {"broadcaster_id", id}};
auto result = SendGetRequest(token, "https://api.twitch.tv",
"/helix/channels", params, true);
auto result = SendGetRequest("https://api.twitch.tv", "/helix/channels",
token, params, true);
if (result.status != 200) {
return {};
}

View File

@@ -3,7 +3,7 @@
#include "twitch-helpers.hpp"
#include <log-helper.hpp>
#include <plugin-state-helper.hpp>
#include <switcher-data.hpp>
namespace advss {
@@ -364,7 +364,8 @@ TwitchChatConnection::~TwitchChatConnection()
static bool setupChatMessageClear()
{
AddIntervalResetStep(&TwitchChatConnection::ClearAllMessages);
GetSwitcher()->AddIntervalResetStep(
&TwitchChatConnection::ClearAllMessages);
return true;
}
@@ -431,8 +432,8 @@ void TwitchChatConnection::ConnectThread()
_client.reset();
_connected = true;
websocketpp::lib::error_code ec;
websocketpp::client<websocketpp::config::asio_tls_client>::
connection_ptr con = _client.get_connection(_url, ec);
EventSubWSClient::connection_ptr con =
_client.get_connection(_url, ec);
if (ec) {
blog(LOG_INFO, "Twitch TwitchChatConnection failed: %s",
ec.message().c_str());
@@ -633,10 +634,8 @@ void TwitchChatConnection::OnOpen(connection_hdl)
Authenticate();
}
void TwitchChatConnection::OnMessage(
connection_hdl,
websocketpp::client<websocketpp::config::asio_tls_client>::message_ptr
message)
void TwitchChatConnection::OnMessage(connection_hdl,
EventSubWSClient::message_ptr message)
{
constexpr std::string_view authOKCommand = "001";
constexpr std::string_view pingCommand = "PING";
@@ -680,8 +679,7 @@ void TwitchChatConnection::OnMessage(
void TwitchChatConnection::OnClose(connection_hdl hdl)
{
websocketpp::client<websocketpp::config::asio_tls_client>::connection_ptr
con = _client.get_con_from_hdl(hdl);
EventSubWSClient::connection_ptr con = _client.get_con_from_hdl(hdl);
auto msg = con->get_ec().message();
blog(LOG_INFO, "Twitch chat connection closed: %s", msg.c_str());
_connected = false;
@@ -689,8 +687,7 @@ void TwitchChatConnection::OnClose(connection_hdl hdl)
void TwitchChatConnection::OnFail(connection_hdl hdl)
{
websocketpp::client<websocketpp::config::asio_tls_client>::connection_ptr
con = _client.get_con_from_hdl(hdl);
EventSubWSClient::connection_ptr con = _client.get_con_from_hdl(hdl);
auto msg = con->get_ec().message();
blog(LOG_INFO, "Twitch chat connection failed: %s", msg.c_str());
_connected = false;

View File

@@ -3,7 +3,7 @@
#include "twitch-helpers.hpp"
#include <log-helper.hpp>
#include <plugin-state-helper.hpp>
#include <switcher-data.hpp>
namespace advss {
@@ -58,7 +58,7 @@ EventSub::~EventSub()
static bool setupEventSubMessageClear()
{
AddIntervalResetStep(&EventSub::ClearAllEvents);
GetSwitcher()->AddIntervalResetStep(&EventSub::ClearAllEvents);
return true;
}
@@ -230,16 +230,14 @@ std::string EventSub::AddEventSubscribtion(std::shared_ptr<TwitchToken> token,
OBSDataAutoRelease postData = copyData(subscription.data);
setTransportData(postData.Get(), eventSub->_sessionID);
auto result = SendPostRequest(*token, registerSubscriptionURL.data(),
registerSubscriptionPath.data(), {},
auto result = SendPostRequest(registerSubscriptionURL.data(),
registerSubscriptionPath.data(), *token,
postData.Get());
if (result.status != 202) {
vblog(LOG_INFO, "failed to register Twitch EventSub (%d)",
result.status);
return "";
}
OBSDataArrayAutoRelease replyArray =
obs_data_get_array(result.data, "data");
OBSDataAutoRelease replyData = obs_data_array_item(replyArray, 0);

View File

@@ -19,24 +19,24 @@ std::string MacroActionTwitch::GetShortDesc() const
}
const static std::map<MacroActionTwitch::Action, std::string> actionTypes = {
{MacroActionTwitch::Action::CHANNEL_INFO_TITLE_SET,
"AdvSceneSwitcher.action.twitch.type.channel.info.title.set"},
{MacroActionTwitch::Action::CHANNEL_INFO_CATEGORY_SET,
"AdvSceneSwitcher.action.twitch.type.channel.info.category.set"},
{MacroActionTwitch::Action::RAID_START,
"AdvSceneSwitcher.action.twitch.type.raid.start"},
{MacroActionTwitch::Action::COMMERCIAL_START,
"AdvSceneSwitcher.action.twitch.type.commercial.start"},
{MacroActionTwitch::Action::MARKER_CREATE,
"AdvSceneSwitcher.action.twitch.type.marker.create"},
{MacroActionTwitch::Action::CLIP_CREATE,
"AdvSceneSwitcher.action.twitch.type.clip.create"},
{MacroActionTwitch::Action::CHAT_ANNOUNCEMENT_SEND,
"AdvSceneSwitcher.action.twitch.type.chat.announcement.send"},
{MacroActionTwitch::Action::CHAT_EMOTE_ONLY_ENABLE,
"AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.enable"},
{MacroActionTwitch::Action::CHAT_EMOTE_ONLY_DISABLE,
"AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.disable"},
{MacroActionTwitch::Action::TITLE,
"AdvSceneSwitcher.action.twitch.type.title"},
{MacroActionTwitch::Action::CATEGORY,
"AdvSceneSwitcher.action.twitch.type.category"},
{MacroActionTwitch::Action::MARKER,
"AdvSceneSwitcher.action.twitch.type.marker"},
{MacroActionTwitch::Action::CLIP,
"AdvSceneSwitcher.action.twitch.type.clip"},
{MacroActionTwitch::Action::COMMERCIAL,
"AdvSceneSwitcher.action.twitch.type.commercial"},
{MacroActionTwitch::Action::ANNOUNCEMENT,
"AdvSceneSwitcher.action.twitch.type.announcement"},
{MacroActionTwitch::Action::ENABLE_EMOTE_ONLY,
"AdvSceneSwitcher.action.twitch.type.emoteOnlyEnable"},
{MacroActionTwitch::Action::DISABLE_EMOTE_ONLY,
"AdvSceneSwitcher.action.twitch.type.emoteOnlyDisable"},
{MacroActionTwitch::Action::RAID,
"AdvSceneSwitcher.action.twitch.type.raid"},
{MacroActionTwitch::Action::SEND_CHAT_MESSAGE,
"AdvSceneSwitcher.action.twitch.type.sendChatMessage"},
};
@@ -64,12 +64,6 @@ const static std::map<MacroActionTwitch::AnnouncementColor, std::string>
{MacroActionTwitch::AnnouncementColor::PURPLE, "purple"},
};
const static std::map<MacroActionTwitch::RedemptionStatus, std::string>
redemptionStatusesTwitch = {
{MacroActionTwitch::RedemptionStatus::CANCELED, "CANCELED"},
{MacroActionTwitch::RedemptionStatus::FULFILLED, "FULFILLED"},
};
void MacroActionTwitch::SetStreamTitle(
const std::shared_ptr<TwitchToken> &token) const
{
@@ -79,10 +73,11 @@ void MacroActionTwitch::SetStreamTitle(
OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "title", _streamTitle.c_str());
auto result = SendPatchRequest(*token, "https://api.twitch.tv",
"/helix/channels",
{{"broadcaster_id", token->GetUserID()}},
data.Get());
auto result = SendPatchRequest(
"https://api.twitch.tv",
std::string("/helix/channels?broadcaster_id=") +
token->GetUserID(),
*token, data.Get());
if (result.status != 204) {
blog(LOG_INFO, "Failed to set stream title! (%d)",
@@ -100,11 +95,11 @@ void MacroActionTwitch::SetStreamCategory(
OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "game_id",
std::to_string(_category.id).c_str());
auto result = SendPatchRequest(*token, "https://api.twitch.tv",
"/helix/channels",
{{"broadcaster_id", token->GetUserID()}},
data.Get());
auto result = SendPatchRequest(
"https://api.twitch.tv",
std::string("/helix/channels?broadcaster_id=") +
token->GetUserID(),
*token, data.Get());
if (result.status != 204) {
blog(LOG_INFO, "Failed to set stream category! (%d)",
result.status);
@@ -122,8 +117,9 @@ void MacroActionTwitch::CreateStreamMarker(
_markerDescription.c_str());
}
auto result = SendPostRequest(*token, "https://api.twitch.tv",
"/helix/streams/markers", {}, data.Get());
auto result = SendPostRequest("https://api.twitch.tv",
"/helix/streams/markers", *token,
data.Get());
if (result.status != 200) {
blog(LOG_INFO, "Failed to create marker! (%d)", result.status);
@@ -133,12 +129,14 @@ void MacroActionTwitch::CreateStreamMarker(
void MacroActionTwitch::CreateStreamClip(
const std::shared_ptr<TwitchToken> &token) const
{
OBSDataAutoRelease data = obs_data_create();
auto hasDelay = _clipHasDelay ? "true" : "false";
auto result = SendPostRequest(*token, "https://api.twitch.tv",
"/helix/clips",
{{"broadcaster_id", token->GetUserID()},
{"has_delay", hasDelay}});
auto result = SendPostRequest(
"https://api.twitch.tv",
"/helix/clips?broadcaster_id=" + token->GetUserID() +
"&has_delay=" + hasDelay,
*token, data.Get());
if (result.status != 202) {
blog(LOG_INFO, "Failed to create clip! (%d)", result.status);
@@ -151,10 +149,9 @@ void MacroActionTwitch::StartCommercial(
OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "broadcaster_id", token->GetUserID().c_str());
obs_data_set_int(data, "length", _duration.Seconds());
auto result = SendPostRequest(*token, "https://api.twitch.tv",
"/helix/channels/commercial", {},
auto result = SendPostRequest("https://api.twitch.tv",
"/helix/channels/commercial", *token,
data.Get());
if (result.status != 200) {
OBSDataArrayAutoRelease replyArray =
obs_data_get_array(result.data, "data");
@@ -181,10 +178,11 @@ void MacroActionTwitch::SendChatAnnouncement(
announcementColorsTwitch.at(_announcementColor).c_str());
auto userId = token->GetUserID();
auto result = SendPostRequest(
*token, "https://api.twitch.tv", "/helix/chat/announcements",
{{"broadcaster_id", userId}, {"moderator_id", userId}},
data.Get());
auto result =
SendPostRequest("https://api.twitch.tv",
"/helix/chat/announcements?broadcaster_id=" +
userId + "&moderator_id=" + userId,
*token, data.Get());
if (result.status != 204) {
blog(LOG_INFO, "Failed to send chat announcement! (%d)",
@@ -199,10 +197,11 @@ void MacroActionTwitch::SetChatEmoteOnlyMode(
obs_data_set_bool(data, "emote_mode", enable);
auto userId = token->GetUserID();
auto result = SendPatchRequest(
*token, "https://api.twitch.tv", "/helix/chat/settings",
{{"broadcaster_id", userId}, {"moderator_id", userId}},
data.Get());
auto result =
SendPatchRequest("https://api.twitch.tv",
"/helix/chat/settings?broadcaster_id=" +
userId + "&moderator_id=" + userId,
*token, data.Get());
if (result.status != 200) {
blog(LOG_INFO, "Failed to %s chat's emote-only mode! (%d)",
@@ -217,9 +216,8 @@ void MacroActionTwitch::StartRaid(const std::shared_ptr<TwitchToken> &token)
token->GetUserID().c_str());
obs_data_set_string(data, "to_broadcaster_id",
_channel.GetUserID(*token).c_str());
auto result = SendPostRequest(*token, "https://api.twitch.tv",
"/helix/raids", {}, data.Get());
auto result = SendPostRequest("https://api.twitch.tv", "/helix/raids",
*token, data.Get());
if (result.status != 200) {
blog(LOG_INFO, "Failed to start raid! (%d)\n", result.status);
}
@@ -245,32 +243,33 @@ bool MacroActionTwitch::PerformAction()
}
switch (_action) {
case Action::CHANNEL_INFO_TITLE_SET:
case MacroActionTwitch::Action::TITLE:
SetStreamTitle(token);
break;
case Action::CHANNEL_INFO_CATEGORY_SET:
case MacroActionTwitch::Action::CATEGORY:
SetStreamCategory(token);
break;
case Action::RAID_START:
StartRaid(token);
break;
case Action::COMMERCIAL_START:
StartCommercial(token);
break;
case Action::MARKER_CREATE:
case MacroActionTwitch::Action::MARKER:
CreateStreamMarker(token);
break;
case Action::CLIP_CREATE:
case MacroActionTwitch::Action::CLIP:
CreateStreamClip(token);
break;
case Action::CHAT_ANNOUNCEMENT_SEND:
case MacroActionTwitch::Action::COMMERCIAL:
StartCommercial(token);
break;
case MacroActionTwitch::Action::ANNOUNCEMENT:
SendChatAnnouncement(token);
break;
case Action::CHAT_EMOTE_ONLY_ENABLE:
case MacroActionTwitch::Action::ENABLE_EMOTE_ONLY:
SetChatEmoteOnlyMode(token, true);
break;
case Action::CHAT_EMOTE_ONLY_DISABLE:
case MacroActionTwitch::Action::DISABLE_EMOTE_ONLY:
SetChatEmoteOnlyMode(token, false);
break;
case MacroActionTwitch::Action::RAID:
StartRaid(token);
break;
case MacroActionTwitch::Action::SEND_CHAT_MESSAGE:
SendChatMessage(token);
break;
@@ -339,111 +338,27 @@ bool MacroActionTwitch::Load(obs_data_t *obj)
bool MacroActionTwitch::ActionIsSupportedByToken()
{
static const std::unordered_map<Action, TokenOption> requiredOption = {
{Action::CHANNEL_INFO_TITLE_SET, {"channel:manage:broadcast"}},
{Action::CHANNEL_INFO_CATEGORY_SET,
{"channel:manage:broadcast"}},
{Action::CHANNEL_INFO_LANGUAGE_SET,
{"channel:manage:broadcast"}},
{Action::CHANNEL_INFO_DELAY_SET, {"channel:manage:broadcast"}},
{Action::CHANNEL_INFO_BRANDED_CONTENT_ENABLE,
{"channel:manage:broadcast"}},
{Action::CHANNEL_INFO_BRANDED_CONTENT_DISABLE,
{"channel:manage:broadcast"}},
{Action::RAID_START, {"channel:manage:raids"}},
{Action::RAID_END, {"channel:manage:raids"}},
{Action::SHOUTOUT_SEND, {"moderator:manage:shoutouts"}},
{Action::POLL_END, {"channel:manage:polls"}},
{Action::PREDICTION_END, {"channel:manage:predictions"}},
{Action::SHIELD_MODE_START, {"moderator:manage:shield_mode"}},
{Action::SHIELD_MODE_END, {"moderator:manage:shield_mode"}},
{Action::POINTS_REWARD_ENABLE, {"channel:manage:redemptions"}},
{Action::POINTS_REWARD_DISABLE, {"channel:manage:redemptions"}},
{Action::POINTS_REWARD_PAUSE, {"channel:manage:redemptions"}},
{Action::POINTS_REWARD_UNPAUSE, {"channel:manage:redemptions"}},
{Action::POINTS_REWARD_TITLE_SET,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_PROMPT_SET,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_COST_SET,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_USER_INPUT_REQUIRE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_USER_INPUT_UNREQUIRE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_COOLDOWN_ENABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_COOLDOWN_DISABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_QUEUE_SKIP_ENABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_QUEUE_SKIP_DISABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_MAX_PER_STREAM_ENABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_MAX_PER_STREAM_DISABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_MAX_PER_USER_ENABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_MAX_PER_USER_DISABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_DELETE, {"channel:manage:redemptions"}},
{Action::POINTS_REWARD_REDEMPTION_CANCEL,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_REDEMPTION_FULFILL,
{"channel:manage:redemptions"}},
{Action::USER_BAN, {"moderator:manage:banned_users"}},
{Action::USER_UNBAN, {"moderator:manage:banned_users"}},
{Action::USER_BLOCK, {"user:manage:blocked_users"}},
{Action::USER_UNBLOCK, {"user:manage:blocked_users"}},
{Action::USER_MODERATOR_ADD, {"channel:manage:moderators"}},
{Action::USER_MODERATOR_DELETE, {"channel:manage:moderators"}},
{Action::USER_VIP_ADD, {"channel:manage:vips"}},
{Action::USER_VIP_DELETE, {"channel:manage:vips"}},
{Action::COMMERCIAL_START, {"channel:edit:commercial"}},
{Action::COMMERCIAL_SNOOZE, {"channel:manage:ads"}},
{Action::MARKER_CREATE, {"channel:manage:broadcast"}},
{Action::CLIP_CREATE, {"clips:edit"}},
{Action::CHAT_ANNOUNCEMENT_SEND,
{"moderator:manage:announcements"}},
{Action::CHAT_EMOTE_ONLY_ENABLE,
{Action::TITLE, {"channel:manage:broadcast"}},
{Action::CATEGORY, {"channel:manage:broadcast"}},
{Action::MARKER, {"channel:manage:broadcast"}},
{Action::CLIP, {"clips:edit"}},
{Action::COMMERCIAL, {"channel:edit:commercial"}},
{Action::ANNOUNCEMENT, {"moderator:manage:announcements"}},
{Action::ENABLE_EMOTE_ONLY, {"moderator:manage:chat_settings"}},
{Action::DISABLE_EMOTE_ONLY,
{"moderator:manage:chat_settings"}},
{Action::CHAT_EMOTE_ONLY_DISABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_FOLLOWER_ONLY_ENABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_FOLLOWER_ONLY_DISABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_SUBSCRIBER_ONLY_ENABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_SUBSCRIBER_ONLY_DISABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_SLOW_MODE_ENABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_SLOW_MODE_DISABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_NON_MODERATOR_DELAY_ENABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_NON_MODERATOR_DELAY_DISABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_UNIQUE_MODE_ENABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_UNIQUE_MODE_DISABLE,
{"moderator:manage:chat_settings"}},
{Action::WHISPER_SEND, {"user:manage:whispers"}},
{Action::RAID, {"channel:manage:raids"}},
{Action::SEND_CHAT_MESSAGE, {"chat:edit"}},
};
auto token = _token.lock();
if (!token) {
return false;
}
auto option = requiredOption.find(_action);
assert(option != requiredOption.end());
if (option == requiredOption.end()) {
return false;
}
return token->OptionIsEnabled(option->second);
}
@@ -486,8 +401,49 @@ MacroActionTwitchEdit::MacroActionTwitchEdit(
_channel(new TwitchChannelSelection(this)),
_chatMessage(new VariableTextEdit(this))
{
SetWidgetProperties();
SetWidgetSignalConnections();
_streamTitle->setSizePolicy(QSizePolicy::MinimumExpanding,
QSizePolicy::Preferred);
_streamTitle->setMaxLength(140);
_markerDescription->setSizePolicy(QSizePolicy::MinimumExpanding,
QSizePolicy::Preferred);
_markerDescription->setMaxLength(140);
_announcementMessage->setSizePolicy(QSizePolicy::MinimumExpanding,
QSizePolicy::Preferred);
_announcementMessage->setMaxLength(500);
auto spinBox = _duration->SpinBox();
spinBox->setSuffix("s");
spinBox->setMaximum(180);
populateActionSelection(_actions);
populateAnnouncementColorSelection(_announcementColor);
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QWidget::connect(_tokens, SIGNAL(SelectionChanged(const QString &)),
this, SLOT(TwitchTokenChanged(const QString &)));
QWidget::connect(&_tokenPermissionCheckTimer, SIGNAL(timeout()), this,
SLOT(CheckTokenPermissions()));
QWidget::connect(_streamTitle, SIGNAL(editingFinished()), this,
SLOT(StreamTitleChanged()));
QWidget::connect(_category,
SIGNAL(CategoreyChanged(const TwitchCategory &)), this,
SLOT(CategoreyChanged(const TwitchCategory &)));
QWidget::connect(_markerDescription, SIGNAL(editingFinished()), this,
SLOT(MarkerDescriptionChanged()));
QObject::connect(_clipHasDelay, SIGNAL(stateChanged(int)), this,
SLOT(ClipHasDelayChanged(int)));
QObject::connect(_duration, SIGNAL(DurationChanged(const Duration &)),
this, SLOT(DurationChanged(const Duration &)));
QWidget::connect(_announcementMessage, SIGNAL(textChanged()), this,
SLOT(AnnouncementMessageChanged()));
QWidget::connect(_announcementColor, SIGNAL(currentIndexChanged(int)),
this, SLOT(AnnouncementColorChanged(int)));
QWidget::connect(_channel,
SIGNAL(ChannelChanged(const TwitchChannel &)), this,
SLOT(ChannelChanged(const TwitchChannel &)));
QWidget::connect(_chatMessage, SIGNAL(textChanged()), this,
SLOT(ChatMessageChanged()));
_entryData = entryData;
SetWidgetLayout();
@@ -521,8 +477,8 @@ void MacroActionTwitchEdit::ActionChanged(int idx)
auto lock = LockContext();
_entryData->_action = static_cast<MacroActionTwitch::Action>(
_actions->itemData(idx).toInt());
SetupWidgetVisibility();
SetWidgetLayout();
SetWidgetVisibility();
}
void MacroActionTwitchEdit::TwitchTokenChanged(const QString &token)
@@ -536,7 +492,7 @@ void MacroActionTwitchEdit::TwitchTokenChanged(const QString &token)
_category->SetToken(_entryData->_token);
_channel->SetToken(_entryData->_token);
SetWidgetVisibility();
SetupWidgetVisibility();
emit(HeaderInfoChanged(token));
}
@@ -624,86 +580,30 @@ void MacroActionTwitchEdit::AnnouncementColorChanged(int index)
static_cast<MacroActionTwitch::AnnouncementColor>(index);
}
void MacroActionTwitchEdit::SetWidgetProperties()
void MacroActionTwitchEdit::SetupWidgetVisibility()
{
_streamTitle->setSizePolicy(QSizePolicy::MinimumExpanding,
QSizePolicy::Preferred);
_streamTitle->setMaxLength(140);
_markerDescription->setSizePolicy(QSizePolicy::MinimumExpanding,
QSizePolicy::Preferred);
_markerDescription->setMaxLength(140);
_announcementMessage->setMaxLength(500);
auto durationSpinBox = _duration->SpinBox();
durationSpinBox->setMaximum(180);
durationSpinBox->setSuffix("s");
populateActionSelection(_actions);
populateAnnouncementColorSelection(_announcementColor);
}
void MacroActionTwitchEdit::SetWidgetSignalConnections()
{
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QWidget::connect(_tokens, SIGNAL(SelectionChanged(const QString &)),
this, SLOT(TwitchTokenChanged(const QString &)));
QWidget::connect(&_tokenPermissionCheckTimer, SIGNAL(timeout()), this,
SLOT(CheckTokenPermissions()));
QWidget::connect(_streamTitle, SIGNAL(editingFinished()), this,
SLOT(StreamTitleChanged()));
QWidget::connect(_category,
SIGNAL(CategoreyChanged(const TwitchCategory &)), this,
SLOT(CategoreyChanged(const TwitchCategory &)));
QWidget::connect(_markerDescription, SIGNAL(editingFinished()), this,
SLOT(MarkerDescriptionChanged()));
QObject::connect(_clipHasDelay, SIGNAL(stateChanged(int)), this,
SLOT(ClipHasDelayChanged(int)));
QObject::connect(_duration, SIGNAL(DurationChanged(const Duration &)),
this, SLOT(DurationChanged(const Duration &)));
QWidget::connect(_announcementMessage, SIGNAL(textChanged()), this,
SLOT(AnnouncementMessageChanged()));
QWidget::connect(_announcementColor, SIGNAL(currentIndexChanged(int)),
this, SLOT(AnnouncementColorChanged(int)));
QWidget::connect(_channel,
SIGNAL(ChannelChanged(const TwitchChannel &)), this,
SLOT(ChannelChanged(const TwitchChannel &)));
QWidget::connect(_chatMessage, SIGNAL(textChanged()), this,
SLOT(ChatMessageChanged()));
}
void MacroActionTwitchEdit::SetWidgetVisibility()
{
_streamTitle->setVisible(
_entryData->_action ==
MacroActionTwitch::Action::CHANNEL_INFO_TITLE_SET);
_category->setVisible(
_entryData->_action ==
MacroActionTwitch::Action::CHANNEL_INFO_CATEGORY_SET);
_streamTitle->setVisible(_entryData->_action ==
MacroActionTwitch::Action::TITLE);
_category->setVisible(_entryData->_action ==
MacroActionTwitch::Action::CATEGORY);
_markerDescription->setVisible(_entryData->_action ==
MacroActionTwitch::Action::MARKER);
_clipHasDelay->setVisible(_entryData->_action ==
MacroActionTwitch::Action::CLIP);
_duration->setVisible(_entryData->_action ==
MacroActionTwitch::Action::COMMERCIAL);
_announcementMessage->setVisible(
_entryData->_action == MacroActionTwitch::Action::ANNOUNCEMENT);
_announcementColor->setVisible(_entryData->_action ==
MacroActionTwitch::Action::ANNOUNCEMENT);
_channel->setVisible(
_entryData->_action == MacroActionTwitch::Action::RAID_START ||
_entryData->_action == MacroActionTwitch::Action::RAID_END ||
_entryData->_action == MacroActionTwitch::Action::RAID ||
_entryData->_action ==
MacroActionTwitch::Action::SEND_CHAT_MESSAGE);
_duration->setVisible(_entryData->_action ==
MacroActionTwitch::Action::COMMERCIAL_START);
_markerDescription->setVisible(
_entryData->_action ==
MacroActionTwitch::Action::MARKER_CREATE);
_clipHasDelay->setVisible(_entryData->_action ==
MacroActionTwitch::Action::CLIP_CREATE);
_announcementMessage->setVisible(
_entryData->_action ==
MacroActionTwitch::Action::CHAT_ANNOUNCEMENT_SEND);
_announcementColor->setVisible(
_entryData->_action ==
MacroActionTwitch::Action::CHAT_ANNOUNCEMENT_SEND);
_chatMessage->setVisible(_entryData->_action ==
MacroActionTwitch::Action::SEND_CHAT_MESSAGE);
if (_entryData->_action ==
MacroActionTwitch::Action::CHANNEL_INFO_TITLE_SET ||
_entryData->_action == MacroActionTwitch::Action::MARKER_CREATE) {
if (_entryData->_action == MacroActionTwitch::Action::TITLE ||
_entryData->_action == MacroActionTwitch::Action::MARKER) {
RemoveStretchIfPresent(_layout);
} else {
AddStretchIfNecessary(_layout);
@@ -741,7 +641,7 @@ void MacroActionTwitchEdit::ChatMessageChanged()
void MacroActionTwitchEdit::SetWidgetLayout()
{
const std::vector<QWidget *> widgets{
static const std::vector<QWidget *> widgets{
_tokens, _actions, _streamTitle,
_category, _markerDescription, _clipHasDelay,
_duration, _announcementColor, _channel};
@@ -792,7 +692,7 @@ void MacroActionTwitchEdit::UpdateEntryData()
_channel->SetChannel(_entryData->_channel);
_chatMessage->setPlainText(_entryData->_chatMessage);
SetWidgetVisibility();
SetupWidgetVisibility();
}
} // namespace advss

View File

@@ -4,7 +4,6 @@
#include "category-selection.hpp"
#include "channel-selection.hpp"
#include "chat-connection.hpp"
#include "points-reward-selection.hpp"
#include <variable-line-edit.hpp>
#include <variable-text-edit.hpp>
@@ -23,118 +22,16 @@ public:
std::string GetShortDesc() const;
enum class Action {
// Channel info update
CHANNEL_INFO_TITLE_SET = 10,
CHANNEL_INFO_CATEGORY_SET = 20,
CHANNEL_INFO_LANGUAGE_SET = 30,
CHANNEL_INFO_DELAY_SET = 40,
CHANNEL_INFO_TAGS_SET = 50, // TODO
CHANNEL_INFO_CONTENT_LABELS_SET = 60, // TODO
CHANNEL_INFO_BRANDED_CONTENT_ENABLE = 70,
CHANNEL_INFO_BRANDED_CONTENT_DISABLE = 71,
// Raid
RAID_START = 110,
RAID_END = 120,
// Shoutout
SHOUTOUT_SEND = 200,
// Poll
POLL_START = 300, // TODO
POLL_END = 310,
// Prediction
PREDICTION_START = 400, // TODO
PREDICTION_END = 410,
// Shield mode
SHIELD_MODE_START = 500,
SHIELD_MODE_END = 501,
// Reward add
POINTS_REWARD_ADD = 600, // TODO
// Reward update
POINTS_REWARD_ENABLE = 710,
POINTS_REWARD_DISABLE = 711,
POINTS_REWARD_PAUSE = 712,
POINTS_REWARD_UNPAUSE = 713,
POINTS_REWARD_TITLE_SET = 720,
POINTS_REWARD_PROMPT_SET = 730,
POINTS_REWARD_COST_SET = 740,
POINTS_REWARD_BACKGROUND_COLOR_SET = 750, // TODO
POINTS_REWARD_USER_INPUT_REQUIRE = 760,
POINTS_REWARD_USER_INPUT_UNREQUIRE = 761,
POINTS_REWARD_COOLDOWN_ENABLE = 770,
POINTS_REWARD_COOLDOWN_DISABLE = 771,
POINTS_REWARD_QUEUE_SKIP_ENABLE = 780,
POINTS_REWARD_QUEUE_SKIP_DISABLE = 781,
POINTS_REWARD_MAX_PER_STREAM_ENABLE = 790,
POINTS_REWARD_MAX_PER_STREAM_DISABLE = 791,
POINTS_REWARD_MAX_PER_USER_ENABLE = 792,
POINTS_REWARD_MAX_PER_USER_DISABLE = 793,
// Reward delete
POINTS_REWARD_DELETE = 800,
// Reward redemption update
POINTS_REWARD_REDEMPTION_FULFILL = 900,
POINTS_REWARD_REDEMPTION_CANCEL = 910,
// User state
USER_BAN = 1000,
USER_UNBAN = 1100,
USER_BLOCK = 1200,
USER_UNBLOCK = 1300,
USER_MODERATOR_ADD = 1400,
USER_MODERATOR_DELETE = 1500,
USER_VIP_ADD = 1600,
USER_VIP_DELETE = 1700,
USER_CHAT_COLOR_UPDATE = 1800, // TODO
// Commercial
COMMERCIAL_START = 1900,
COMMERCIAL_SNOOZE = 2000,
// Marker
MARKER_CREATE = 2100,
// Clips
CLIP_CREATE = 2200,
// Videos
VIDEOS_DELETE = 2300, // TODO
// Chat
CHAT_MESSAGES_DELETE = 2400, // TODO
CHAT_ANNOUNCEMENT_SEND = 2500,
CHAT_EMOTE_ONLY_ENABLE = 2600,
CHAT_EMOTE_ONLY_DISABLE = 2601,
CHAT_FOLLOWER_ONLY_ENABLE = 2602,
CHAT_FOLLOWER_ONLY_DISABLE = 2603,
CHAT_SUBSCRIBER_ONLY_ENABLE = 2604,
CHAT_SUBSCRIBER_ONLY_DISABLE = 2605,
CHAT_SLOW_MODE_ENABLE = 2640,
CHAT_SLOW_MODE_DISABLE = 2641,
CHAT_NON_MODERATOR_DELAY_ENABLE = 2650,
CHAT_NON_MODERATOR_DELAY_DISABLE = 2651,
CHAT_UNIQUE_MODE_ENABLE = 2960,
CHAT_UNIQUE_MODE_DISABLE = 2661,
CHAT_AUTOMOD_UPDATE = 2700, // TODO
CHAT_AUTOMOD_MESSAGE_ALLOW = 2800, // TODO
CHAT_AUTOMOD_MESSAGE_DENY = 2801, // TODO
CHAT_BLOCKED_TERM_ADD = 2900, // TODO
CHAT_BLOCKED_TERM_DELETE = 3000, // TODO
WHISPER_SEND = 3100,
// Schedule
CHANNEL_SCHEDULE_UPDATE = 3200, // TODO
CHANNEL_SCHEDULE_SEGMENT_ADD = 3300, // TODO
CHANNEL_SCHEDULE_SEGMENT_UPDATE = 3400, // TODO
CHANNEL_SCHEDULE_SEGMENT_DELETE = 3500, // TODO
SEND_CHAT_MESSAGE = 5000,
TITLE,
CATEGORY = 10,
MARKER = 20,
CLIP = 30,
COMMERCIAL = 40,
ANNOUNCEMENT = 50,
ENABLE_EMOTE_ONLY = 60,
DISABLE_EMOTE_ONLY = 70,
RAID = 80,
SEND_CHAT_MESSAGE = 90,
};
enum class AnnouncementColor {
@@ -145,8 +42,6 @@ public:
PURPLE,
};
enum class RedemptionStatus { CANCELED, FULFILLED };
bool PerformAction();
void LogAction() const;
bool Save(obs_data_t *obj) const;
@@ -154,7 +49,7 @@ public:
bool ActionIsSupportedByToken();
void ResetChatConnection();
Action _action = Action::CHANNEL_INFO_TITLE_SET;
Action _action = Action::TITLE;
std::weak_ptr<TwitchToken> _token;
StringVariable _streamTitle =
obs_module_text("AdvSceneSwitcher.action.twitch.title.title");
@@ -224,10 +119,8 @@ protected:
std::shared_ptr<MacroActionTwitch> _entryData;
private:
void SetWidgetProperties();
void SetWidgetSignalConnections();
void SetupWidgetVisibility();
void SetWidgetLayout();
void SetWidgetVisibility();
QHBoxLayout *_layout;
FilterComboBox *_actions;

View File

@@ -866,9 +866,7 @@ void MacroConditionTwitch::SetupTempVars()
AddTempvar(id, name.empty() ? id : name, description);
};
if (_condition != Condition::CHAT_MESSAGE_RECEIVED &&
_condition != Condition::RAID_INBOUND_EVENT &&
_condition != Condition::RAID_OUTBOUND_EVENT) {
if (_condition != Condition::CHAT_MESSAGE_RECEIVED) {
setupTempVarHelper("broadcaster_user_id");
setupTempVarHelper("broadcaster_user_login");
setupTempVarHelper("broadcaster_user_name");
@@ -1143,16 +1141,7 @@ void MacroConditionTwitch::SetupTempVars()
setupTempVarHelper("user_name", ".follow");
setupTempVarHelper("followed_at");
break;
case Condition::RAID_INBOUND_EVENT:
case Condition::RAID_OUTBOUND_EVENT:
setupTempVarHelper("from_broadcaster_user_id", ".raid");
setupTempVarHelper("from_broadcaster_user_login", ".raid");
setupTempVarHelper("from_broadcaster_user_name", ".raid");
setupTempVarHelper("to_broadcaster_user_id", ".raid");
setupTempVarHelper("to_broadcaster_user_login", ".raid");
setupTempVarHelper("to_broadcaster_user_name", ".raid");
setupTempVarHelper("viewers", ".raid");
break;
case Condition::LIVE_POLLING:
setupTempVarHelper("id", ".stream.online");
setupTempVarHelper("game_id");
@@ -1291,7 +1280,7 @@ void MacroConditionTwitchEdit::ConditionChanged(int idx)
auto lock = LockContext();
_entryData->SetCondition(static_cast<MacroConditionTwitch::Condition>(
_conditions->itemData(idx).toInt()));
SetWidgetVisibility();
SetupWidgetVisibility();
emit TempVarsChanged();
}
@@ -1308,7 +1297,7 @@ void MacroConditionTwitchEdit::TwitchTokenChanged(const QString &token)
_pointsReward->SetToken(_entryData->_token);
_entryData->ResetChatConnection();
SetWidgetVisibility();
SetupWidgetVisibility();
emit(HeaderInfoChanged(token));
}
@@ -1399,7 +1388,7 @@ void MacroConditionTwitchEdit::CategoreyChanged(const TwitchCategory &category)
_entryData->_category = category;
}
void MacroConditionTwitchEdit::SetWidgetVisibility()
void MacroConditionTwitchEdit::SetupWidgetVisibility()
{
auto condition = _entryData->GetCondition();
@@ -1459,7 +1448,7 @@ void MacroConditionTwitchEdit::UpdateEntryData()
_category->SetToken(_entryData->_token);
_category->SetCategory(_entryData->_category);
SetWidgetVisibility();
SetupWidgetVisibility();
}
} // namespace advss

View File

@@ -23,13 +23,8 @@ public:
std::string GetShortDesc() const;
enum class Condition {
// Event based
// Generic event based
STREAM_ONLINE_EVENT = 0,
STREAM_ONLINE_LIVE_EVENT = 1,
STREAM_ONLINE_PLAYLIST_EVENT = 2,
STREAM_ONLINE_WATCHPARTY_EVENT = 3,
STREAM_ONLINE_PREMIERE_EVENT = 4,
STREAM_ONLINE_RERUN_EVENT = 5,
STREAM_OFFLINE_EVENT = 100,
CHANNEL_INFO_UPDATE_EVENT = 200,
FOLLOW_EVENT = 300,
@@ -71,6 +66,13 @@ public:
USER_MODERATOR_ADDITION_EVENT = 3900,
USER_MODERATOR_DELETION_EVENT = 4000,
// Event based with custom logic
STREAM_ONLINE_LIVE_EVENT = 1,
STREAM_ONLINE_PLAYLIST_EVENT = 2,
STREAM_ONLINE_WATCHPARTY_EVENT = 3,
STREAM_ONLINE_PREMIERE_EVENT = 4,
STREAM_ONLINE_RERUN_EVENT = 5,
// Chat
CHAT_MESSAGE_RECEIVED = 500000,
@@ -168,7 +170,7 @@ protected:
std::shared_ptr<MacroConditionTwitch> _entryData;
private:
void SetWidgetVisibility();
void SetupWidgetVisibility();
QHBoxLayout *_layout;
FilterComboBox *_conditions;

View File

@@ -134,9 +134,9 @@ TwitchPointsRewardSelection::GetPointsRewards(
httplib::Params params = {
{"broadcaster_id", channel.GetUserID(*token)}};
auto response = SendGetRequest(*token, "https://api.twitch.tv",
auto response = SendGetRequest("https://api.twitch.tv",
"/helix/channel_points/custom_rewards",
params);
*token, params);
if (response.status != 200) {
blog(LOG_WARNING,

View File

@@ -1,24 +1,26 @@
#include "token.hpp"
#include "twitch-helpers.hpp"
#include <plugin-state-helper.hpp>
#include <log-helper.hpp>
#include <obs-module-helper.hpp>
#include <switcher-data.hpp>
#include <utility.hpp>
#include <QScrollArea>
#include <QDesktopServices>
#include <QGroupBox>
#include <QUrl>
namespace advss {
static const int tokenGrabberPort = 8080;
static std::deque<std::shared_ptr<Item>> twitchTokens;
const std::unordered_map<std::string, std::string> TokenOption::_apiIdToLocale{
{"channel:manage:broadcast",
"AdvSceneSwitcher.twitchToken.channel.broadcast.manage"},
{"clips:edit", "AdvSceneSwitcher.twitchToken.clips.edit"},
{"channel:edit:commercial",
"AdvSceneSwitcher.twitchToken.channel.commercial.edit"},
{"moderator:manage:announcements",
"AdvSceneSwitcher.twitchToken.moderator.announcements.manage"},
{"moderator:manage:chat_settings",
"AdvSceneSwitcher.twitchToken.moderator.chat.settings.manage"},
{"channel:manage:raids", "AdvSceneSwitcher.twitchToken.channel.raid"},
{"moderator:read:followers",
"AdvSceneSwitcher.twitchToken.moderator.followers.read"},
{"channel:read:subscriptions",
@@ -26,8 +28,6 @@ const std::unordered_map<std::string, std::string> TokenOption::_apiIdToLocale{
{"bits:read", "AdvSceneSwitcher.twitchToken.bits.read"},
{"moderator:read:shoutouts",
"AdvSceneSwitcher.twitchToken.moderator.shoutouts.read"},
{"channel:manage:raids",
"AdvSceneSwitcher.twitchToken.channel.raids.manage"},
{"moderator:manage:shoutouts",
"AdvSceneSwitcher.twitchToken.moderator.shoutouts.manage"},
{"channel:read:polls",
@@ -53,26 +53,8 @@ const std::unordered_map<std::string, std::string> TokenOption::_apiIdToLocale{
{"channel:manage:redemptions",
"AdvSceneSwitcher.twitchToken.channel.redemptions.manage"},
{"channel:moderate", "AdvSceneSwitcher.twitchToken.channel.moderate"},
{"moderator:manage:banned_users",
"AdvSceneSwitcher.twitchToken.moderator.bannedUsers.manage"},
{"user:manage:blocked_users",
"AdvSceneSwitcher.twitchToken.user.blockedUsers.manage"},
{"moderation:read", "AdvSceneSwitcher.twitchToken.moderation.read"},
{"channel:manage:moderators",
"AdvSceneSwitcher.twitchToken.channel.moderators.manage"},
{"channel:manage:vips",
"AdvSceneSwitcher.twitchToken.channel.vips.manage"},
{"channel:edit:commercial",
"AdvSceneSwitcher.twitchToken.channel.commercial.edit"},
{"channel:manage:ads",
"AdvSceneSwitcher.twitchToken.channel.ads.manage"},
{"clips:edit", "AdvSceneSwitcher.twitchToken.clips.edit"},
{"moderator:manage:announcements",
"AdvSceneSwitcher.twitchToken.moderator.announcements.manage"},
{"moderator:manage:chat_settings",
"AdvSceneSwitcher.twitchToken.moderator.chat.settings.manage"},
{"user:manage:whispers",
"AdvSceneSwitcher.twitchToken.user.whispers.manage"},
{"moderation:read",
"AdvSceneSwitcher.twitchToken.channel.moderation.read"},
{"chat:read", "AdvSceneSwitcher.twitchToken.chat.read"},
{"chat:edit", "AdvSceneSwitcher.twitchToken.chat.edit"},
};
@@ -82,8 +64,8 @@ static void loadConnections(obs_data_t *obj);
bool setupTwitchTokenSupport()
{
AddSaveStep(saveConnections);
AddLoadStep(loadConnections);
GetSwitcher()->AddSaveStep(saveConnections);
GetSwitcher()->AddLoadStep(loadConnections);
return true;
}
@@ -212,7 +194,7 @@ void TwitchToken::SetToken(const std::string &value)
{
_token = value;
auto res =
SendGetRequest(*this, "https://api.twitch.tv", "/helix/users");
SendGetRequest("https://api.twitch.tv", "/helix/users", *this);
if (res.status != 200) {
blog(LOG_WARNING, "failed to get Twitch user id from token!");
_userID = -1;
@@ -611,7 +593,7 @@ static std::string generateScopeString(const std::set<TokenOption> &options)
static std::string getHtml(const QString &redirect)
{
QString html = R"(
const char *html = R"(
<html>
<head>
<title>Advanced scene switcher</title>
@@ -623,18 +605,17 @@ static std::string getHtml(const QString &redirect)
if (document.location.hash && document.location.hash != '') {
var parsedHash = new URLSearchParams(window.location.hash.slice(1));
if (parsedHash.get('access_token')) {
window.location.replace(`http://localhost:%1/token?access_token=${parsedHash.get('access_token')}&state=${parsedHash.get('state')}`);
window.location.replace(`http://localhost:8080/token?access_token=${parsedHash.get('access_token')}&state=${parsedHash.get('state')}`);
output.textContent = 'It is safe to close this window';
}
} else {
window.location.replace('%2');
window.location.replace('%1');
}
</script>
</body>
</html>)";
return html.arg(QString::number(tokenGrabberPort), redirect)
.toStdString();
return QString(html).arg(redirect).toStdString();
}
void TwitchTokenSettingsDialog::RequestToken()
@@ -729,16 +710,13 @@ void TokenGrabberThread::run()
// Generate URI to request token
auto state = generateStateString();
QString getTokenURI =
("https://id.twitch.tv/oauth2/authorize"
"?response_type=token"
"&client_id=" +
QString(GetClientID()) +
"&redirect_uri=http://localhost:%1"
"/auth"
"&scope=" +
_scope + "&state=" + QString::fromStdString(state))
.arg(QString::number(tokenGrabberPort));
auto getTokenURI = "https://id.twitch.tv/oauth2/authorize"
"?response_type=token"
"&client_id=" +
QString(GetClientID()) +
"&redirect_uri=http://localhost:8080/auth"
"&scope=" +
_scope + "&state=" + QString::fromStdString(state);
// Setup server receiving token string
auto html = getHtml(getTokenURI);
@@ -797,19 +775,8 @@ void TokenGrabberThread::run()
// Start the server and wait
std::unique_lock<std::mutex> lock(_mutex);
_serverThread = std::thread([this]() {
if (!_server.bind_to_port("localhost", tokenGrabberPort, 0)) {
blog(LOG_WARNING,
"Failed to bind token server to localhost %d!",
tokenGrabberPort);
return;
}
if (!_server.listen_after_bind()) {
blog(LOG_WARNING, "Token server failed to listen()!");
return;
}
vblog(LOG_INFO, "Token server stopped!");
});
_serverThread =
std::thread([this]() { _server.listen("localhost", 8080); });
auto time = std::chrono::high_resolution_clock::now() +
std::chrono::seconds(_timeout);
while (!_stopWaiting) {

View File

@@ -1,18 +1,11 @@
#include "twitch-helpers.hpp"
#include "token.hpp"
#include <log-helper.hpp>
namespace advss {
static constexpr std::string_view clientID = "ds5tt4ogliifsqc04mz3d3etnck3e5";
static const int cacheTimeoutSeconds = 10;
const char *GetClientID()
{
return clientID.data();
}
class Args {
public:
Args(const std::string &uri, const std::string &path,
@@ -50,7 +43,7 @@ struct CacheEntry {
std::chrono::system_clock::now();
};
static bool cacheIsTooOld(const CacheEntry &cache)
static bool chacheIsTooOld(const CacheEntry &cache)
{
auto currentTime = std::chrono::system_clock::now();
auto diff = currentTime - cache.cacheTime;
@@ -61,7 +54,7 @@ static bool cacheIsValid(const std::map<Args, CacheEntry> &cache,
const Args &args)
{
auto it = cache.find(args);
return it != cache.end() && !cacheIsTooOld(it->second);
return it != cache.end() && !chacheIsTooOld(it->second);
}
static httplib::Headers getTokenRequestHeaders(const std::string &token)
@@ -72,262 +65,166 @@ static httplib::Headers getTokenRequestHeaders(const std::string &token)
};
}
static std::string getRequestBody(const OBSData &data)
{
auto json = obs_data_get_json(data);
return json ? json : "";
}
static RequestResult processResult(const httplib::Result &response,
const char *funcName)
{
if (!response) {
auto err = response.error();
blog(LOG_WARNING, "Twitch request failed in %s with error: %s",
funcName, httplib::to_string(err).c_str());
return {};
}
RequestResult result;
result.status = response->status;
if (response->body.empty()) {
return result;
}
OBSDataAutoRelease replyData =
obs_data_create_from_json(response->body.c_str());
result.data = replyData;
return result;
}
RequestResult SendGetRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
RequestResult SendGetRequest(const std::string &uri, const std::string &path,
const TwitchToken &token,
const httplib::Params &params)
{
httplib::Client cli(uri);
auto tokenStr = token.GetToken();
if (!tokenStr) {
return {};
}
auto url = httplib::append_query_params(uri + path, params);
vblog(LOG_INFO, "Twitch GET request to %s began", url.c_str());
auto headers = getTokenRequestHeaders(*tokenStr);
auto response = cli.Get(path, params, headers);
return processResult(response, __func__);
if (!response) {
auto err = response.error();
blog(LOG_INFO, "%s failed - %s", __func__,
httplib::to_string(err).c_str());
return {};
}
RequestResult result;
result.status = response->status;
if (response->body.empty()) {
return result;
}
OBSDataAutoRelease replyData =
obs_data_create_from_json(response->body.c_str());
result.data = replyData;
return result;
}
RequestResult SendGetRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
RequestResult SendGetRequest(const std::string &uri, const std::string &path,
const TwitchToken &token,
const httplib::Params &params, bool useCache)
{
static std::map<Args, CacheEntry> cache;
static std::mutex mtx;
std::lock_guard<std::mutex> lock(mtx);
auto tokenStr = token.GetToken();
if (!tokenStr) {
return {};
}
auto headers = getTokenRequestHeaders(*tokenStr);
Args args(uri, path, "", params, headers);
if (useCache && cacheIsValid(cache, args)) {
auto it = cache.find(args);
return it->second.result;
}
auto result = SendGetRequest(token, uri, path, params);
auto result = SendGetRequest(uri, path, token, params);
cache[args] = {result};
return result;
}
RequestResult SendPostRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params,
const OBSData &data)
RequestResult SendPostRequest(const std::string &uri, const std::string &path,
const TwitchToken &token, const OBSData &data)
{
httplib::Client cli(uri);
auto tokenStr = token.GetToken();
if (!tokenStr) {
return {};
}
auto pathWithParams = httplib::append_query_params(path, params);
auto url = uri + pathWithParams;
vblog(LOG_INFO, "Twitch POST request to %s began", url.c_str());
auto headers = getTokenRequestHeaders(*tokenStr);
auto body = getRequestBody(data);
auto response =
cli.Post(pathWithParams, headers, body, "application/json");
return processResult(response, __func__);
auto json = obs_data_get_json(data);
std::string body = json ? json : "";
auto response = cli.Post(path, headers, body, "application/json");
if (!response) {
auto err = response.error();
blog(LOG_INFO, "%s failed - %s", __func__,
httplib::to_string(err).c_str());
return {};
}
RequestResult result;
result.status = response->status;
if (response->body.empty()) {
return result;
}
OBSDataAutoRelease replyData =
obs_data_create_from_json(response->body.c_str());
result.data = replyData;
return result;
}
RequestResult SendPostRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params,
const OBSData &data, bool useCache)
RequestResult SendPostRequest(const std::string &uri, const std::string &path,
const TwitchToken &token, const OBSData &data,
bool useCache)
{
static std::map<Args, CacheEntry> cache;
static std::mutex mtx;
std::lock_guard<std::mutex> lock(mtx);
auto tokenStr = token.GetToken();
if (!tokenStr) {
return {};
}
auto headers = getTokenRequestHeaders(*tokenStr);
auto body = getRequestBody(data);
Args args(uri, path, body, params, headers);
auto jsonCstr = obs_data_get_json(data);
Args args(uri, path, jsonCstr ? jsonCstr : "", {}, headers);
if (useCache && cacheIsValid(cache, args)) {
auto it = cache.find(args);
return it->second.result;
}
auto result = SendPostRequest(token, uri, path, params, data);
auto result = SendPostRequest(uri, path, token, data);
cache[args] = {result};
return result;
}
RequestResult SendPutRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params, const OBSData &data)
RequestResult SendPatchRequest(const std::string &uri, const std::string &path,
const TwitchToken &token, const OBSData &data)
{
httplib::Client cli(uri);
auto tokenStr = token.GetToken();
if (!tokenStr) {
return {};
}
auto pathWithParams = httplib::append_query_params(path, params);
auto url = uri + pathWithParams;
vblog(LOG_INFO, "Twitch PUT request to %s began", url.c_str());
auto headers = getTokenRequestHeaders(*tokenStr);
auto body = getRequestBody(data);
auto response =
cli.Put(pathWithParams, headers, body, "application/json");
return processResult(response, __func__);
auto json = obs_data_get_json(data);
std::string body = json ? json : "";
auto response = cli.Patch(path, headers, body, "application/json");
if (!response) {
auto err = response.error();
blog(LOG_INFO, "%s failed - %s", __func__,
httplib::to_string(err).c_str());
return {};
}
RequestResult result;
result.status = response->status;
if (response->body.empty()) {
return result;
}
OBSDataAutoRelease replyData =
obs_data_create_from_json(response->body.c_str());
result.data = replyData;
return result;
}
RequestResult SendPutRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params, const OBSData &data,
bool useCache)
RequestResult SendPatchRequest(const std::string &uri, const std::string &path,
const TwitchToken &token, const OBSData &data,
bool useCache)
{
static std::map<Args, CacheEntry> cache;
static std::mutex mtx;
std::lock_guard<std::mutex> lock(mtx);
auto tokenStr = token.GetToken();
if (!tokenStr) {
return {};
}
auto headers = getTokenRequestHeaders(*tokenStr);
auto body = getRequestBody(data);
Args args(uri, path, body, params, headers);
auto jsonCstr = obs_data_get_json(data);
Args args(uri, path, jsonCstr ? jsonCstr : "", {}, headers);
if (useCache && cacheIsValid(cache, args)) {
auto it = cache.find(args);
return it->second.result;
}
auto result = SendPutRequest(token, uri, path, params, data);
auto result = SendPatchRequest(uri, path, token, data);
cache[args] = {result};
return result;
}
RequestResult SendPatchRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params,
const OBSData &data)
const char *GetClientID()
{
httplib::Client cli(uri);
auto tokenStr = token.GetToken();
if (!tokenStr) {
return {};
}
auto pathWithParams = httplib::append_query_params(path, params);
auto url = uri + pathWithParams;
vblog(LOG_INFO, "Twitch PATCH request to %s began", url.c_str());
auto headers = getTokenRequestHeaders(*tokenStr);
auto body = getRequestBody(data);
auto response =
cli.Patch(pathWithParams, headers, body, "application/json");
return processResult(response, __func__);
}
RequestResult SendPatchRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params,
const OBSData &data, bool useCache)
{
static std::map<Args, CacheEntry> cache;
static std::mutex mtx;
std::lock_guard<std::mutex> lock(mtx);
auto tokenStr = token.GetToken();
if (!tokenStr) {
return {};
}
auto headers = getTokenRequestHeaders(*tokenStr);
auto body = getRequestBody(data);
Args args(uri, path, body, params, headers);
if (useCache && cacheIsValid(cache, args)) {
auto it = cache.find(args);
return it->second.result;
}
auto result = SendPatchRequest(token, uri, path, params, data);
cache[args] = {result};
return result;
}
RequestResult SendDeleteRequest(const TwitchToken &token,
const std::string &uri, const std::string &path,
const httplib::Params &params)
{
httplib::Client cli(uri);
auto tokenStr = token.GetToken();
if (!tokenStr) {
return {};
}
auto pathWithParams = httplib::append_query_params(path, params);
auto url = uri + pathWithParams;
vblog(LOG_INFO, "Twitch DELETE request to %s began", url.c_str());
auto headers = getTokenRequestHeaders(*tokenStr);
auto response =
cli.Delete(pathWithParams, headers, "", "application/json");
return processResult(response, __func__);
return clientID.data();
}
} // namespace advss

View File

@@ -13,44 +13,29 @@ struct RequestResult {
OBSData data = nullptr;
};
const char *GetClientID();
// These functions do *not* use or create RequestResult cache entries
RequestResult SendGetRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params = {});
RequestResult SendPostRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params = {},
const OBSData &data = nullptr);
RequestResult SendPutRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params = {},
const OBSData &data = nullptr);
RequestResult SendPatchRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params = {},
const OBSData &data = nullptr);
RequestResult SendDeleteRequest(const TwitchToken &token,
const std::string &uri, const std::string &path,
const httplib::Params &params = {});
RequestResult SendGetRequest(const std::string &uri, const std::string &path,
const TwitchToken &token,
const httplib::Params & = {});
RequestResult SendPostRequest(const std::string &uri, const std::string &path,
const TwitchToken &token, const OBSData &data);
RequestResult SendPatchRequest(const std::string &uri, const std::string &path,
const TwitchToken &token, const OBSData &data);
// These functions will cache the RequestResult for 10s
// Note that the cache will be reported as a "memory leak" on OBS shutdown
RequestResult SendGetRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params, bool useCache);
RequestResult SendPostRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params,
const OBSData &data, bool useCache);
RequestResult SendPutRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params, const OBSData &data,
RequestResult SendGetRequest(const std::string &uri, const std::string &path,
const TwitchToken &token, const httplib::Params &,
bool useCache);
RequestResult SendPatchRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params,
const OBSData &data, bool useCache);
RequestResult SendPostRequest(const std::string &uri, const std::string &path,
const TwitchToken &token, const OBSData &data,
bool useCache);
RequestResult SendPatchRequest(const std::string &uri, const std::string &path,
const TwitchToken &token, const OBSData &data,
bool useCache);
const char *GetClientID();
} // namespace advss

View File

@@ -177,8 +177,8 @@ bool MacroConditionVideo::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_video.Load(obj);
SetCondition(static_cast<VideoCondition>(
obs_data_get_int(obj, "condition")));
_condition =
static_cast<VideoCondition>(obs_data_get_int(obj, "condition"));
_file = obs_data_get_string(obj, "filePath");
_blockUntilScreenshotDone =
obs_data_get_bool(obj, "blockUntilScreenshotDone");
@@ -215,15 +215,8 @@ void MacroConditionVideo::GetScreenshot(bool blocking)
{
auto source = obs_weak_source_get_source(_video.GetVideo());
_screenshotData.~ScreenshotHelper();
QRect screenshotArea;
if (_areaParameters.enable && _condition != VideoCondition::NO_IMAGE) {
screenshotArea.setRect(_areaParameters.area.x,
_areaParameters.area.y,
_areaParameters.area.width,
_areaParameters.area.height);
}
new (&_screenshotData) ScreenshotHelper(
source, screenshotArea, blocking, GetSwitcher()->interval);
new (&_screenshotData)
ScreenshotHelper(source, blocking, GetSwitcher()->interval);
obs_source_release(source);
_getNextScreenshot = false;
}
@@ -268,12 +261,6 @@ bool MacroConditionVideo::SetLanguage(const std::string &language)
return _ocrParameters.SetLanguageCode(language);
}
void MacroConditionVideo::SetCondition(VideoCondition condition)
{
_condition = condition;
SetupTempVars();
}
bool MacroConditionVideo::ScreenshotContainsPattern()
{
cv::Mat result;
@@ -282,12 +269,9 @@ bool MacroConditionVideo::ScreenshotContainsPattern()
_patternMatchParameters.useAlphaAsMask,
_patternMatchParameters.matchMode);
if (result.total() == 0) {
SetTempVarValue("patternCount", "0");
return false;
}
const auto count = countNonZero(result);
SetTempVarValue("patternCount", std::to_string(count));
return count > 0;
return countNonZero(result) > 0;
}
bool MacroConditionVideo::OutputChanged()
@@ -316,15 +300,12 @@ bool MacroConditionVideo::ScreenshotContainsObject()
_objMatchParameters.minNeighbors,
_objMatchParameters.minSize.CV(),
_objMatchParameters.maxSize.CV());
const auto count = objects.size();
SetTempVarValue("objectCount", std::to_string(count));
return count > 0;
return objects.size() > 0;
}
bool MacroConditionVideo::CheckBrightnessThreshold()
{
_currentBrightness = GetAvgBrightness(_screenshotData.image) / 255.;
SetTempVarValue("brightness", std::to_string(_currentBrightness));
return _currentBrightness > _brightnessThreshold;
}
@@ -337,7 +318,6 @@ bool MacroConditionVideo::CheckOCR()
auto text = RunOCR(_ocrParameters.GetOCR(), _screenshotData.image,
_ocrParameters.color, _ocrParameters.colorThreshold);
SetVariableValue(text);
SetTempVarValue("text", text);
if (!_ocrParameters.regex.Enabled()) {
return text == std::string(_ocrParameters.text);
}
@@ -352,22 +332,21 @@ bool MacroConditionVideo::CheckOCR()
bool MacroConditionVideo::CheckColor()
{
const bool ret = ContainsPixelsInColorRange(
_screenshotData.image, _colorParameters.color,
_colorParameters.colorThreshold,
_colorParameters.matchThreshold);
// Way too slow for now
//SetTempVarValue("dominantColor", GetDominantColor(_screenshotData.image, 3)
// .name(QColor::HexArgb)
// .toStdString());
SetTempVarValue("color", GetAverageColor(_screenshotData.image)
.name(QColor::HexArgb)
.toStdString());
return ret;
return ContainsPixelsInColorRange(_screenshotData.image,
_colorParameters.color,
_colorParameters.colorThreshold,
_colorParameters.matchThreshold);
}
bool MacroConditionVideo::Compare()
{
if (_areaParameters.enable && _condition != VideoCondition::NO_IMAGE) {
_screenshotData.image = _screenshotData.image.copy(
_areaParameters.area.x, _areaParameters.area.y,
_areaParameters.area.width,
_areaParameters.area.height);
}
if (_condition != VideoCondition::OCR) {
SetVariableValue("");
}
@@ -399,58 +378,6 @@ bool MacroConditionVideo::Compare()
return false;
}
void MacroConditionVideo::SetupTempVars()
{
MacroCondition::SetupTempVars();
switch (_condition) {
case VideoCondition::PATTERN:
AddTempvar(
"patternCount",
obs_module_text(
"AdvSceneSwitcher.tempVar.video.patternCount"),
obs_module_text(
"AdvSceneSwitcher.tempVar.video.patternCount.description"));
break;
case VideoCondition::OBJECT:
AddTempvar(
"objectCount",
obs_module_text(
"AdvSceneSwitcher.tempVar.video.objectCount"),
obs_module_text(
"AdvSceneSwitcher.tempVar.video.objectCount.description"));
break;
case VideoCondition::BRIGHTNESS:
AddTempvar(
"brightness",
obs_module_text(
"AdvSceneSwitcher.tempVar.video.brightness"),
obs_module_text(
"AdvSceneSwitcher.tempVar.video.brightness.description"));
break;
case VideoCondition::OCR:
AddTempvar(
"text",
obs_module_text("AdvSceneSwitcher.tempVar.video.text"),
obs_module_text(
"AdvSceneSwitcher.tempVar.video.text.description"));
break;
case VideoCondition::COLOR:
AddTempvar(
"color",
obs_module_text("AdvSceneSwitcher.tempVar.video.color"),
obs_module_text(
"AdvSceneSwitcher.tempVar.video.color.description"));
break;
case VideoCondition::MATCH:
case VideoCondition::DIFFER:
case VideoCondition::HAS_NOT_CHANGED:
case VideoCondition::HAS_CHANGED:
case VideoCondition::NO_IMAGE:
default:
break;
}
}
static inline void populateVideoInputSelection(QComboBox *list)
{
for (const auto &[_, name] : videoInputTypes) {
@@ -1228,7 +1155,7 @@ void MacroConditionVideoEdit::UpdatePreviewTooltip()
return;
}
if (!requiresFileInput(_entryData->GetCondition())) {
if (!requiresFileInput(_entryData->_condition)) {
this->setToolTip("");
return;
}
@@ -1297,7 +1224,7 @@ void MacroConditionVideoEdit::ConditionChanged(int cond)
}
auto lock = LockContext();
_entryData->SetCondition(static_cast<VideoCondition>(cond));
_entryData->_condition = static_cast<VideoCondition>(cond);
_entryData->ResetLastMatch();
SetWidgetVisibility();
@@ -1312,7 +1239,7 @@ void MacroConditionVideoEdit::ConditionChanged(int cond)
_previewDialog.PatternMatchParametersChanged(
_entryData->_patternMatchParameters);
if (_entryData->GetCondition() == VideoCondition::OBJECT) {
if (_entryData->_condition == VideoCondition::OBJECT) {
auto path = _entryData->GetModelDataPath();
_entryData->_objMatchParameters.cascade =
initObjectCascade(path);
@@ -1555,28 +1482,27 @@ void MacroConditionVideoEdit::SetWidgetVisibility()
_sources->setVisible(_entryData->_video.type ==
VideoInput::Type::SOURCE);
_scenes->setVisible(_entryData->_video.type == VideoInput::Type::SCENE);
_imagePath->setVisible(requiresFileInput(_entryData->GetCondition()));
_imagePath->setVisible(requiresFileInput(_entryData->_condition));
_usePatternForChangedCheck->setVisible(
patternControlIsOptional(_entryData->GetCondition()));
_patternThreshold->setVisible(
needsThreshold(_entryData->GetCondition()));
_useAlphaAsMask->setVisible(_entryData->GetCondition() ==
patternControlIsOptional(_entryData->_condition));
_patternThreshold->setVisible(needsThreshold(_entryData->_condition));
_useAlphaAsMask->setVisible(_entryData->_condition ==
VideoCondition::PATTERN);
SetLayoutVisible(_patternMatchModeLayout,
_entryData->GetCondition() == VideoCondition::PATTERN);
_brightness->setVisible(_entryData->GetCondition() ==
_entryData->_condition == VideoCondition::PATTERN);
_brightness->setVisible(_entryData->_condition ==
VideoCondition::BRIGHTNESS);
_showMatch->setVisible(needsShowMatch(_entryData->GetCondition()));
_ocr->setVisible(_entryData->GetCondition() == VideoCondition::OCR);
_objectDetect->setVisible(_entryData->GetCondition() ==
_showMatch->setVisible(needsShowMatch(_entryData->_condition));
_ocr->setVisible(_entryData->_condition == VideoCondition::OCR);
_objectDetect->setVisible(_entryData->_condition ==
VideoCondition::OBJECT);
_color->setVisible(_entryData->GetCondition() == VideoCondition::COLOR);
_color->setVisible(_entryData->_condition == VideoCondition::COLOR);
SetLayoutVisible(_throttleControlLayout,
needsThrottleControls(_entryData->GetCondition()));
_area->setVisible(needsAreaControls(_entryData->GetCondition()));
needsThrottleControls(_entryData->_condition));
_area->setVisible(needsAreaControls(_entryData->_condition));
if (_entryData->GetCondition() == VideoCondition::HAS_CHANGED ||
_entryData->GetCondition() == VideoCondition::HAS_NOT_CHANGED) {
if (_entryData->_condition == VideoCondition::HAS_CHANGED ||
_entryData->_condition == VideoCondition::HAS_NOT_CHANGED) {
_patternThreshold->setVisible(
_entryData->_patternMatchParameters.useForChangedCheck);
SetLayoutVisible(
@@ -1602,7 +1528,7 @@ void MacroConditionVideoEdit::SetupPreviewDialogParams()
_previewDialog.VideoSelectionChanged(_entryData->_video);
_previewDialog.AreaParametersChanged(_entryData->_areaParameters);
_previewDialog.ConditionChanged(
static_cast<int>(_entryData->GetCondition()));
static_cast<int>(_entryData->_condition));
}
void MacroConditionVideoEdit::UpdateEntryData()
@@ -1615,8 +1541,7 @@ void MacroConditionVideoEdit::UpdateEntryData()
static_cast<int>(_entryData->_video.type));
_scenes->SetScene(_entryData->_video.scene);
_sources->SetSource(_entryData->_video.source);
_condition->setCurrentIndex(
static_cast<int>(_entryData->GetCondition()));
_condition->setCurrentIndex(static_cast<int>(_entryData->_condition));
_reduceLatency->setChecked(_entryData->_blockUntilScreenshotDone);
_imagePath->SetPath(QString::fromStdString(_entryData->_file));
_usePatternForChangedCheck->setChecked(

View File

@@ -45,10 +45,8 @@ public:
void SetPageSegMode(tesseract::PageSegMode);
bool SetLanguage(const std::string &);
void SetCondition(VideoCondition);
VideoCondition GetCondition() const { return _condition; }
VideoInput _video;
VideoCondition _condition = VideoCondition::MATCH;
std::string _file = obs_module_text("AdvSceneSwitcher.enterPath");
// Enabling this will reduce matching latency, but slow down the
// the condition checks of all macros overall.
@@ -76,10 +74,6 @@ private:
bool Compare();
bool CheckShouldBeSkipped();
void SetupTempVars();
VideoCondition _condition = VideoCondition::MATCH;
bool _getNextScreenshot = true;
ScreenshotHelper _screenshotData;
QImage _matchImage;

View File

@@ -225,61 +225,6 @@ bool ContainsPixelsInColorRange(const QImage &image, const QColor &color,
return matchPercentage >= totalPixelMatchThreshold;
}
QColor GetAverageColor(const QImage &img)
{
if (img.isNull()) {
return QColor();
}
auto image = QImageToMat(img);
cv::Scalar meanColor = cv::mean(image);
int averageBlue = cvRound(meanColor[0]);
int averageGreen = cvRound(meanColor[1]);
int averageRed = cvRound(meanColor[2]);
return QColor(averageRed, averageGreen, averageBlue);
}
QColor GetDominantColor(const QImage &img, int k)
{
if (img.isNull()) {
return QColor();
}
auto image = QImageToMat(img);
cv::Mat reshapedImage = image.reshape(1, image.rows * image.cols);
reshapedImage.convertTo(reshapedImage, CV_32F);
cv::mean(reshapedImage);
// Apply k-means clustering to group similar colors
cv::TermCriteria criteria(
cv::TermCriteria::EPS + cv::TermCriteria::MAX_ITER, 100, 0.2);
cv::Mat labels, centers;
cv::kmeans(reshapedImage, k, labels, criteria, 1,
cv::KMEANS_RANDOM_CENTERS, centers);
// Find the dominant color
// Center of the cluster with the largest number of pixels
cv::Mat counts = cv::Mat::zeros(1, k, CV_32SC1);
for (int i = 0; i < labels.rows; i++) {
counts.at<int>(0, labels.at<int>(i))++;
}
cv::Point max_loc;
cv::minMaxLoc(counts, nullptr, nullptr, nullptr, &max_loc);
try {
cv::Scalar dominantColor = centers.at<cv::Scalar>(max_loc.y);
const int blue = cv::saturate_cast<int>(dominantColor.val[0]);
const int green = cv::saturate_cast<int>(dominantColor.val[1]);
const int red = cv::saturate_cast<int>(dominantColor.val[2]);
const int alpha = cv::saturate_cast<int>(dominantColor.val[3]);
return QColor(red, green, blue, alpha);
} catch (...) {
}
return QColor();
}
// Assumption is that QImage uses Format_RGBA8888.
// Conversion from: https://github.com/dbzhang800/QtOpenCV
cv::Mat QImageToMat(const QImage &img)

View File

@@ -66,8 +66,6 @@ std::string RunOCR(tesseract::TessBaseAPI *, const QImage &, const QColor &,
bool ContainsPixelsInColorRange(const QImage &image, const QColor &color,
double colorDeviationThreshold,
double totalPixelMatchThreshold);
QColor GetAverageColor(const QImage &img);
QColor GetDominantColor(const QImage &image, int k);
cv::Mat QImageToMat(const QImage &img);
QImage MatToQImage(const cv::Mat &mat);

View File

@@ -255,13 +255,7 @@ void PreviewImage::CreateImage(const VideoInput &video, PreviewType type,
VideoCondition condition)
{
auto source = obs_weak_source_get_source(video.GetVideo());
QRect screenshotArea;
if (areaParams.enable && type == PreviewType::SHOW_MATCH) {
screenshotArea.setRect(areaParams.area.x, areaParams.area.y,
areaParams.area.width,
areaParams.area.height);
}
ScreenshotHelper screenshot(source, screenshotArea, true);
ScreenshotHelper screenshot(source, true);
obs_source_release(source);
if (!video.ValidSelection() || !screenshot.done) {
@@ -280,6 +274,11 @@ void PreviewImage::CreateImage(const VideoInput &video, PreviewType type,
if (type == PreviewType::SHOW_MATCH) {
std::unique_lock<std::mutex> lock(_mtx);
if (areaParams.enable) {
screenshot.image = screenshot.image.copy(
areaParams.area.x, areaParams.area.y,
areaParams.area.width, areaParams.area.height);
}
// Will emit status label update
MarkMatch(screenshot.image, patternMatchParams,
patternImageData, objDetectParams, ocrParams,

View File

@@ -45,7 +45,7 @@ void SwitcherData::Prune()
}
if (nonMatchingScene && !WeakSourceValid(nonMatchingScene)) {
switchIfNotMatching = NoMatchBehavior::NO_SWITCH;
switchIfNotMatching = NoMatch::NO_SWITCH;
nonMatchingScene = nullptr;
}

View File

@@ -21,7 +21,6 @@
#include "curl-helper.hpp"
#include "priority-helper.hpp"
#include "log-helper.hpp"
#include "plugin-state-helper.hpp"
#include <condition_variable>
#include <vector>
@@ -81,6 +80,7 @@ public:
void SaveSettings(obs_data_t *obj);
void SaveMacros(obs_data_t *obj);
void SaveConnections(obs_data_t *obj);
void SaveVariables(obs_data_t *obj);
void SaveGeneralSettings(obs_data_t *obj);
void SaveHotkeys(obs_data_t *obj);
@@ -89,6 +89,7 @@ public:
void LoadSettings(obs_data_t *obj);
void LoadMacros(obs_data_t *obj);
void LoadConnections(obs_data_t *obj);
void LoadVariables(obs_data_t *obj);
void LoadGeneralSettings(obs_data_t *obj);
void LoadHotkeys(obs_data_t *obj);
@@ -142,7 +143,8 @@ public:
int interval = default_interval;
OBSWeakSource nonMatchingScene;
NoMatchBehavior switchIfNotMatching = NoMatchBehavior::NO_SWITCH;
enum class NoMatch { NO_SWITCH = 0, SWITCH = 1, RANDOM_SWITCH = 2 };
NoMatch switchIfNotMatching = NoMatch::NO_SWITCH;
Duration noMatchDelay;
enum class StartupBehavior { PERSIST = 0, START = 1, STOP = 2 };
StartupBehavior startupBehavior = StartupBehavior::PERSIST;
@@ -173,6 +175,8 @@ public:
bool macroSceneSwitched = false;
Curlhelper curl;
std::deque<std::shared_ptr<Item>> connections;
std::vector<std::string> websocketMessages;
std::deque<std::shared_ptr<Item>> variables;
std::string lastTitle;

View File

@@ -1,33 +1,18 @@
#include "connection-manager.hpp"
#include "utility.hpp"
#include "obs-module-helper.hpp"
#include "plugin-state-helper.hpp"
#include "name-dialog.hpp"
#include "switcher-data.hpp"
#include <algorithm>
#include <QAction>
#include <QMenu>
#include <obs-module.h>
Q_DECLARE_METATYPE(advss::Connection *);
namespace advss {
static std::deque<std::shared_ptr<Item>> connections;
static void saveConnections(obs_data_t *obj);
static void loadConnections(obs_data_t *obj);
void SetupConnectionManager()
{
static bool done = false;
if (done) {
return;
}
AddSaveStep(saveConnections);
AddLoadStep(loadConnections);
done = true;
}
static void saveConnections(obs_data_t *obj)
void SwitcherData::SaveConnections(obs_data_t *obj)
{
obs_data_array_t *connectionArray = obs_data_array_create();
for (const auto &c : connections) {
@@ -40,7 +25,7 @@ static void saveConnections(obs_data_t *obj)
obs_data_array_release(connectionArray);
}
static void loadConnections(obs_data_t *obj)
void SwitcherData::LoadConnections(obs_data_t *obj)
{
connections.clear();
@@ -207,7 +192,7 @@ Connection *GetConnectionByName(const QString &name)
Connection *GetConnectionByName(const std::string &name)
{
for (auto &con : connections) {
for (auto &con : switcher->connections) {
if (con->Name() == name) {
return dynamic_cast<Connection *>(con.get());
}
@@ -217,7 +202,7 @@ Connection *GetConnectionByName(const std::string &name)
std::weak_ptr<Connection> GetWeakConnectionByName(const std::string &name)
{
for (const auto &c : connections) {
for (const auto &c : switcher->connections) {
if (c->Name() == name) {
std::weak_ptr<Connection> wp =
std::dynamic_pointer_cast<Connection>(c);
@@ -241,11 +226,6 @@ std::string GetWeakConnectionName(std::weak_ptr<Connection> connection)
return con->Name();
}
std::deque<std::shared_ptr<Item>> &GetConnections()
{
return connections;
}
static bool ConnectionNameAvailable(const QString &name)
{
return !GetConnectionByName(name);
@@ -264,7 +244,8 @@ static bool AskForSettingsWrapper(QWidget *parent, Item &settings)
}
ConnectionSelection::ConnectionSelection(QWidget *parent)
: ItemSelection(connections, Connection::Create, AskForSettingsWrapper,
: ItemSelection(switcher->connections, Connection::Create,
AskForSettingsWrapper,
"AdvSceneSwitcher.connection.select",
"AdvSceneSwitcher.connection.add",
"AdvSceneSwitcher.item.nameNotAvailable",
@@ -315,7 +296,7 @@ void ConnectionSelection::SetConnection(
ConnectionSettingsDialog::ConnectionSettingsDialog(QWidget *parent,
const Connection &settings)
: ItemSettingsDialog(settings, connections,
: ItemSettingsDialog(settings, switcher->connections,
"AdvSceneSwitcher.connection.select",
"AdvSceneSwitcher.connection.add",
"AdvSceneSwitcher.item.nameNotAvailable", parent),

View File

@@ -1,6 +1,5 @@
#pragma once
#include "item-selection-helpers.hpp"
#include "websocket-helpers.hpp"
#include <QComboBox>
#include <QPushButton>
@@ -15,11 +14,10 @@
#include <QGridLayout>
#include <deque>
#include <obs.hpp>
#include <websocket-helpers.hpp>
namespace advss {
void SetupConnectionManager();
class ConnectionSelection;
class ConnectionSettingsDialog;
@@ -71,7 +69,6 @@ Connection *GetConnectionByName(const std::string &);
std::weak_ptr<Connection> GetWeakConnectionByName(const std::string &name);
std::weak_ptr<Connection> GetWeakConnectionByQString(const QString &name);
std::string GetWeakConnectionName(std::weak_ptr<Connection>);
std::deque<std::shared_ptr<Item>> &GetConnections();
class ConnectionSettingsDialog : public ItemSettingsDialog {
Q_OBJECT

View File

@@ -50,15 +50,6 @@ CURLcode Curlhelper::Perform()
return _perform(_curl);
}
char *Curlhelper::GetError(CURLcode code)
{
if (!_initialized) {
return (char *)"CURL initialization failed";
}
return _error(code);
}
bool Curlhelper::LoadLib()
{
_lib = new QLibrary(curl_library_name, nullptr);
@@ -110,10 +101,8 @@ bool Curlhelper::Resolve()
_slistAppend = (slistAppendFunction)_lib->resolve("curl_slist_append");
_perform = (performFunction)_lib->resolve("curl_easy_perform");
_cleanup = (cleanupFunction)_lib->resolve("curl_easy_cleanup");
_error = (errorFunction)_lib->resolve("curl_easy_strerror");
if (_init && _setopt && _slistAppend && _perform && _cleanup &&
_error) {
if (_init && _setopt && _slistAppend && _perform && _cleanup) {
blog(LOG_INFO, "[adv-ss] curl loaded successfully");
return true;
}

View File

@@ -5,25 +5,22 @@
namespace advss {
typedef CURL *(*initFunction)(void);
typedef void (*cleanupFunction)(CURL *);
typedef CURLcode (*setOptFunction)(CURL *, CURLoption, ...);
typedef struct curl_slist *(*slistAppendFunction)(struct curl_slist *list,
const char *string);
typedef CURLcode (*performFunction)(CURL *);
typedef void (*cleanupFunction)(CURL *);
typedef char *(*errorFunction)(CURLcode);
class Curlhelper {
public:
Curlhelper();
~Curlhelper();
bool Initialized() { return _initialized; }
template<typename... Args> CURLcode SetOpt(CURLoption, Args...);
struct curl_slist *SlistAppend(struct curl_slist *list,
const char *string);
CURLcode Perform();
char *GetError(CURLcode code);
bool Initialized() { return _initialized; }
private:
bool LoadLib();
@@ -34,7 +31,6 @@ private:
slistAppendFunction _slistAppend = nullptr;
performFunction _perform = nullptr;
cleanupFunction _cleanup = nullptr;
errorFunction _error = nullptr;
CURL *_curl = nullptr;
QLibrary *_lib;
bool _initialized = false;

View File

@@ -1,12 +1,168 @@
#include "duration-control.hpp"
#include "mouse-wheel-guard.hpp"
#include "obs-module-helper.hpp"
#include "obs-module.h"
#include "utility.hpp"
#include <sstream>
#include <iomanip>
#include <QHBoxLayout>
namespace advss {
Duration::Duration(double initialValueInSeconds) : _value(initialValueInSeconds)
{
}
void Duration::Save(obs_data_t *obj, const char *name) const
{
auto data = obs_data_create();
_value.Save(data, "value");
obs_data_set_int(data, "unit", static_cast<int>(_unit));
obs_data_set_int(data, "version", 1);
obs_data_set_obj(obj, name, data);
obs_data_release(data);
}
static int durationUnitToMultiplier(Duration::Unit u)
{
switch (u) {
case Duration::Unit::SECONDS:
return 1;
case Duration::Unit::MINUTES:
return 60;
case Duration::Unit::HOURS:
return 3600;
default:
break;
}
return 0;
}
void Duration::Load(obs_data_t *obj, const char *name)
{
auto data = obs_data_get_obj(obj, name);
// TODO: remove this fallback
if (!data || !obs_data_has_user_value(data, "version") ||
obs_data_get_int(data, "version") != 1) {
bool usingDefaultArgs = strcmp("duration", name) == 0;
if (usingDefaultArgs) {
_value = obs_data_get_double(obj, "seconds");
}
if (_value.GetValue() == 0.0) {
_value = obs_data_get_double(obj, name);
}
if (usingDefaultArgs) {
_unit = static_cast<Duration::Unit>(
obs_data_get_int(obj, "displayUnit"));
} else if (_value.GetValue() >= 86400) {
_unit = (_value.GetValue() / 60 >= 86400)
? Duration::Unit::HOURS
: Duration::Unit::MINUTES;
} else {
_unit = Duration::Unit::SECONDS;
}
_value = _value / durationUnitToMultiplier(_unit);
obs_data_release(data);
return;
}
_value.Load(data, "value");
_unit = static_cast<Duration::Unit>(obs_data_get_int(data, "unit"));
obs_data_release(data);
}
bool Duration::DurationReached()
{
if (IsReset()) {
_startTime = std::chrono::high_resolution_clock::now();
}
auto runTime = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now() - _startTime);
return runTime.count() >= Milliseconds();
}
bool Duration::IsReset() const
{
return _startTime.time_since_epoch().count() == 0;
}
double Duration::Seconds() const
{
return _value.GetValue() * durationUnitToMultiplier(_unit);
}
double Duration::Milliseconds() const
{
return Seconds() * 1000.0;
}
double Duration::TimeRemaining() const
{
if (IsReset()) {
return Seconds();
}
auto runTime = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now() - _startTime);
if (runTime.count() >= Milliseconds()) {
return 0;
}
return (Milliseconds() - runTime.count()) / 1000.0;
}
void Duration::SetTimeRemaining(double remaining)
{
long long msPassed = (Seconds() - remaining) * 1000;
_startTime = std::chrono::high_resolution_clock::now() -
std::chrono::milliseconds(msPassed);
}
void Duration::Reset()
{
_startTime = {};
}
static std::string durationUnitToString(Duration::Unit u)
{
switch (u) {
case Duration::Unit::SECONDS:
return obs_module_text("AdvSceneSwitcher.unit.secends");
case Duration::Unit::MINUTES:
return obs_module_text("AdvSceneSwitcher.unit.minutes");
case Duration::Unit::HOURS:
return obs_module_text("AdvSceneSwitcher.unit.hours");
default:
break;
}
return "";
}
std::string Duration::ToString() const
{
std::ostringstream ss;
ss << std::fixed << std::setprecision(2) << _value.GetValue() << " "
<< durationUnitToString(_unit);
if (!_value.IsFixedType()) {
ss << " [" << GetWeakVariableName(_value.GetVariable()) << "]";
}
return ss.str();
}
void Duration::SetUnit(Unit u)
{
double prevMultiplier = durationUnitToMultiplier(_unit);
double newMultiplier = durationUnitToMultiplier(u);
_unit = u;
_value = _value * (prevMultiplier / newMultiplier);
}
static void populateUnits(QComboBox *list)
{
list->addItem(obs_module_text("AdvSceneSwitcher.unit.secends"));
@@ -58,28 +214,13 @@ void DurationSelection::_DurationChanged(const NumberVariable<double> &value)
emit DurationChanged(_current);
}
static int durationUnitToMultiplier(Duration::Unit u)
{
switch (u) {
case Duration::Unit::SECONDS:
return 1;
case Duration::Unit::MINUTES:
return 60;
case Duration::Unit::HOURS:
return 3600;
default:
break;
}
return 0;
}
void DurationSelection::_UnitChanged(int idx)
{
Duration::Unit unit = static_cast<Duration::Unit>(idx);
double prevMultiplier = durationUnitToMultiplier(_current._unit);
double newMultiplier = durationUnitToMultiplier(unit);
_current._unit = unit;
const QSignalBlocker b(_duration);
_duration->SetFixedValue(_duration->Value() *
(prevMultiplier / newMultiplier));
emit DurationChanged(_current);

View File

@@ -1,14 +1,48 @@
#pragma once
#include "duration.hpp"
#include "variable-spinbox.hpp"
#include "obs-data.h"
#include <QWidget>
#include <QDoubleSpinBox>
#include <QComboBox>
#include <QPushButton>
#include <chrono>
namespace advss {
class Duration {
public:
Duration() = default;
Duration(double initialValueInSeconds);
void Save(obs_data_t *obj, const char *name = "duration") const;
void Load(obs_data_t *obj, const char *name = "duration");
bool DurationReached();
bool IsReset() const;
double Seconds() const;
double Milliseconds() const;
double TimeRemaining() const;
void SetTimeRemaining(double);
void Reset();
std::string ToString() const;
enum class Unit {
SECONDS,
MINUTES,
HOURS,
};
Unit GetUnit() const { return _unit; }
void SetUnit(Unit u);
private:
NumberVariable<double> _value = 0.;
Unit _unit = Unit::SECONDS;
std::chrono::high_resolution_clock::time_point _startTime;
friend class DurationSelection;
};
class DurationSelection : public QWidget {
Q_OBJECT
public:

View File

@@ -1,169 +0,0 @@
#include "duration.hpp"
#include "obs-module-helper.hpp"
#include "utility.hpp"
#include <sstream>
#include <iomanip>
namespace advss {
Duration::Duration(double initialValueInSeconds) : _value(initialValueInSeconds)
{
}
void Duration::Save(obs_data_t *obj, const char *name) const
{
auto data = obs_data_create();
_value.Save(data, "value");
obs_data_set_int(data, "unit", static_cast<int>(_unit));
obs_data_set_int(data, "version", 1);
obs_data_set_obj(obj, name, data);
obs_data_release(data);
}
static int durationUnitToMultiplier(Duration::Unit u);
void Duration::Load(obs_data_t *obj, const char *name)
{
auto data = obs_data_get_obj(obj, name);
// TODO: remove this fallback
if (!data || !obs_data_has_user_value(data, "version") ||
obs_data_get_int(data, "version") != 1) {
bool usingDefaultArgs = strcmp("duration", name) == 0;
if (usingDefaultArgs) {
_value = obs_data_get_double(obj, "seconds");
}
if (_value.GetValue() == 0.0) {
_value = obs_data_get_double(obj, name);
}
if (usingDefaultArgs) {
_unit = static_cast<Duration::Unit>(
obs_data_get_int(obj, "displayUnit"));
} else if (_value.GetValue() >= 86400) {
_unit = (_value.GetValue() / 60 >= 86400)
? Duration::Unit::HOURS
: Duration::Unit::MINUTES;
} else {
_unit = Duration::Unit::SECONDS;
}
_value = _value / durationUnitToMultiplier(_unit);
obs_data_release(data);
return;
}
_value.Load(data, "value");
_unit = static_cast<Duration::Unit>(obs_data_get_int(data, "unit"));
obs_data_release(data);
}
bool Duration::DurationReached()
{
if (IsReset()) {
_startTime = std::chrono::high_resolution_clock::now();
}
auto runTime = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now() - _startTime);
return runTime.count() >= Milliseconds();
}
bool Duration::IsReset() const
{
return _startTime.time_since_epoch().count() == 0;
}
double Duration::Seconds() const
{
return _value.GetValue() * durationUnitToMultiplier(_unit);
}
double Duration::Milliseconds() const
{
return Seconds() * 1000.0;
}
double Duration::TimeRemaining() const
{
if (IsReset()) {
return Seconds();
}
auto runTime = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now() - _startTime);
if (runTime.count() >= Milliseconds()) {
return 0;
}
return (Milliseconds() - runTime.count()) / 1000.0;
}
void Duration::SetTimeRemaining(double remaining)
{
long long msPassed = (Seconds() - remaining) * 1000;
_startTime = std::chrono::high_resolution_clock::now() -
std::chrono::milliseconds(msPassed);
}
void Duration::Reset()
{
_startTime = {};
}
static std::string durationUnitToString(Duration::Unit u)
{
switch (u) {
case Duration::Unit::SECONDS:
return obs_module_text("AdvSceneSwitcher.unit.secends");
case Duration::Unit::MINUTES:
return obs_module_text("AdvSceneSwitcher.unit.minutes");
case Duration::Unit::HOURS:
return obs_module_text("AdvSceneSwitcher.unit.hours");
default:
break;
}
return "";
}
std::string Duration::ToString() const
{
std::ostringstream ss;
ss << std::fixed << std::setprecision(2) << _value.GetValue() << " "
<< durationUnitToString(_unit);
if (!_value.IsFixedType()) {
ss << " [" << GetWeakVariableName(_value.GetVariable()) << "]";
}
return ss.str();
}
// TODO: Remove the code below
// Only used for backwards compatibility
static int durationUnitToMultiplier(Duration::Unit u)
{
switch (u) {
case Duration::Unit::SECONDS:
return 1;
case Duration::Unit::MINUTES:
return 60;
case Duration::Unit::HOURS:
return 3600;
default:
break;
}
return 0;
}
void Duration::SetUnit(Unit u)
{
double prevMultiplier = durationUnitToMultiplier(_unit);
double newMultiplier = durationUnitToMultiplier(u);
_unit = u;
_value = _value * (prevMultiplier / newMultiplier);
}
} // namespace advss

View File

@@ -1,45 +0,0 @@
#pragma once
#include "variable-number.hpp"
#include "obs-data.h"
#include <chrono>
namespace advss {
class Duration {
public:
Duration() = default;
Duration(double initialValueInSeconds);
void Save(obs_data_t *obj, const char *name = "duration") const;
void Load(obs_data_t *obj, const char *name = "duration");
bool DurationReached();
bool IsReset() const;
double Seconds() const;
double Milliseconds() const;
double TimeRemaining() const;
void SetTimeRemaining(double);
void Reset();
std::string ToString() const;
enum class Unit {
SECONDS,
MINUTES,
HOURS,
};
Unit GetUnit() const { return _unit; }
// TODO: Remove
// Only use this function if you intend to convert old settings formats
void SetUnit(Unit u);
private:
NumberVariable<double> _value = 0.;
Unit _unit = Unit::SECONDS;
std::chrono::high_resolution_clock::time_point _startTime;
friend class DurationSelection;
};
} // namespace advss

View File

@@ -10,26 +10,19 @@ namespace advss {
MacroRunButton::MacroRunButton(QWidget *parent) : QPushButton(parent)
{
installEventFilter(this);
auto parentWindow = window();
if (parentWindow) {
parentWindow->installEventFilter(this);
if (window()) {
window()->installEventFilter(this);
}
setToolTip(obs_module_text("AdvSceneSwitcher.macroTab.run.tooltip"));
QWidget::connect(this, SIGNAL(pressed()), this, SLOT(Pressed()));
}
void MacroRunButton::SetMacroTree(MacroTree *macros)
{
_macros = macros;
QWidget::connect(macros, SIGNAL(MacroSelectionChanged()), this,
SLOT(MacroSelectionChanged()));
QWidget::connect(&_timer, &QTimer::timeout, this,
[this]() { MacroSelectionChanged(); });
_timer.start(1000);
}
@@ -40,53 +33,35 @@ void MacroRunButton::MacroSelectionChanged()
_macroHasElseActions = false;
return;
}
_macroHasElseActions = macro->ElseActions().size() > 0;
}
bool MacroRunButton::eventFilter(QObject *obj, QEvent *event)
{
if (!_macroHasElseActions) {
DeactivateElseState();
setText(obs_module_text("AdvSceneSwitcher.macroTab.run"));
_runElseActionsKeyHeld = false;
return QPushButton::eventFilter(obj, event);
}
auto eventType = event->type();
if (eventType == QEvent::KeyPress) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Shift) {
if (underMouse()) {
ActivateElseState();
}
_shiftHeld = true;
if (keyEvent->key() == Qt::Key_Control) {
setText(obs_module_text(
"AdvSceneSwitcher.macroTab.runElse"));
_runElseActionsKeyHeld = true;
}
} else if (eventType == QEvent::KeyRelease) {
} else if (event->type() == QEvent::KeyRelease) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Shift) {
DeactivateElseState();
_shiftHeld = false;
if (keyEvent->key() == Qt::Key_Control) {
setText(obs_module_text(
"AdvSceneSwitcher.macroTab.run"));
_runElseActionsKeyHeld = false;
}
} else if (_shiftHeld && obj == this && eventType == QEvent::Enter) {
ActivateElseState();
} else if (obj == this && eventType == QEvent::Leave) {
DeactivateElseState();
}
return QPushButton::eventFilter(obj, event);
}
void MacroRunButton::ActivateElseState()
{
setText(obs_module_text("AdvSceneSwitcher.macroTab.runElse"));
_elseStateActive = true;
}
void MacroRunButton::DeactivateElseState()
{
setText(obs_module_text("AdvSceneSwitcher.macroTab.run"));
_elseStateActive = false;
}
void MacroRunButton::Pressed()
{
auto macro = _macros->GetCurrentMacro();
@@ -94,9 +69,9 @@ void MacroRunButton::Pressed()
return;
}
bool ret = _elseStateActive ? macro->PerformActions(false, true, true)
: macro->PerformActions(true, true, true);
bool ret = _runElseActionsKeyHeld
? macro->PerformActions(false, true, true)
: macro->PerformActions(true, true, true);
if (!ret) {
QString err =
obs_module_text("AdvSceneSwitcher.macroTab.runFail");

View File

@@ -20,13 +20,9 @@ private slots:
void Pressed();
private:
void ActivateElseState();
void DeactivateElseState();
bool _macroHasElseActions = false;
bool _elseStateActive = false;
bool _shiftHeld = false;
MacroTree *_macros = nullptr;
bool _macroHasElseActions = false;
bool _runElseActionsKeyHeld = false;
QTimer _timer;
};

View File

@@ -1,35 +1,32 @@
#include "math-helpers.hpp"
#include "obs-module-helper.hpp"
#include <exprtk.hpp>
#include <random>
#ifdef UNIT_TEST
const char *obs_module_text(const char *text)
{
return text;
}
#else
#include <obs-module.h>
#endif
typedef exprtk::expression<double> expression_t;
typedef exprtk::parser<double> parser_t;
namespace advss {
std::variant<double, std::string> EvalMathExpression(const std::string &expr)
{
static bool setupDone = false;
static exprtk::symbol_table<double> symbolTable;
static std::random_device rd;
static std::mt19937 gen(rd());
static std::uniform_real_distribution<double> dis(0.0, 1.0);
static auto randomFunc = []() { return dis(gen); };
expression_t expression;
if (!setupDone) {
symbolTable.add_function("random", randomFunc);
setupDone = true;
}
exprtk::expression<double> expression;
expression.register_symbol_table(symbolTable);
exprtk::parser<double> parser;
parser_t parser;
if (parser.compile(expr, expression)) {
return expression.value();
}
return std::string(obs_module_text(
"AdvSceneSwitcher.math.expressionFail")) +
" \"" + expr + "\"";
" \"" + expr;
}
bool IsValidNumber(const std::string &str)

View File

@@ -1,9 +1,6 @@
#include "obs-module-helper.hpp"
#ifndef UNIT_TEST
#include "switcher-data.hpp"
#endif
#ifndef UNIT_TEST
const char *obs_module_text(const char *text)
{
if (!advss::switcher) {
@@ -19,15 +16,3 @@ obs_module_t *obs_current_module()
}
return advss::switcher->GetModule();
}
#else
const char *obs_module_text(const char *text)
{
return text;
};
obs_module_t *obs_current_module()
{
return nullptr;
}
#endif

View File

@@ -1,9 +1,5 @@
#pragma once
#ifndef UNIT_TEST
#include <obs-module.h>
#else
typedef struct obs_module obs_module_t;
#endif
const char *obs_module_text(const char *text);
obs_module_t *obs_current_module();

View File

@@ -1,61 +0,0 @@
#include "plugin-state-helper.hpp"
#include "switcher-data.hpp"
namespace advss {
void LoadPluginSettings(obs_data_t *obj)
{
GetSwitcher()->LoadSettings(obj);
}
void AddSaveStep(std::function<void(obs_data_t *)> step)
{
GetSwitcher()->AddSaveStep(step);
}
void AddLoadStep(std::function<void(obs_data_t *)> step)
{
GetSwitcher()->AddLoadStep(step);
}
void AddPostLoadStep(std::function<void()> step)
{
GetSwitcher()->AddPostLoadStep(step);
}
void AddIntervalResetStep(std::function<void()> step)
{
GetSwitcher()->AddIntervalResetStep(step);
}
void StopPlugin()
{
GetSwitcher()->Stop();
}
void StartPlugin()
{
GetSwitcher()->Start();
}
void SetPluginNoMatchBehavior(NoMatchBehavior behavior)
{
GetSwitcher()->switchIfNotMatching = behavior;
}
void SetNoMatchScene(const OBSWeakSource &scene)
{
GetSwitcher()->nonMatchingScene = scene;
}
NoMatchBehavior GetPluginNoMatchBehavior()
{
return GetSwitcher()->switchIfNotMatching;
}
bool SettingsWindowIsOpened()
{
return GetSwitcher()->settingsWindowOpened;
}
} // namespace advss

View File

@@ -1,24 +0,0 @@
#pragma once
#include <functional>
#include <obs.hpp>
namespace advss {
void LoadPluginSettings(obs_data_t *);
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()>);
void StopPlugin();
void StartPlugin();
enum class NoMatchBehavior { NO_SWITCH = 0, SWITCH = 1, RANDOM_SWITCH = 2 };
void SetPluginNoMatchBehavior(NoMatchBehavior);
NoMatchBehavior GetPluginNoMatchBehavior();
void SetNoMatchScene(const OBSWeakSource &);
bool SettingsWindowIsOpened();
} // namespace advss

View File

@@ -7,11 +7,10 @@ namespace advss {
static void ScreenshotTick(void *param, float);
ScreenshotHelper::ScreenshotHelper(obs_source_t *source, const QRect &subarea,
bool blocking, int timeout, bool saveToFile,
ScreenshotHelper::ScreenshotHelper(obs_source_t *source, bool blocking,
int timeout, bool saveToFile,
std::string path)
: weakSource(OBSGetWeakRef(source)),
_subarea(subarea),
_blocking(blocking),
_saveToFile(saveToFile),
_path(path)
@@ -63,12 +62,7 @@ void ScreenshotHelper::Screenshot()
cy = ovi.base_height;
}
QRect renderArea(0, 0, cx, cy);
if (!_subarea.isEmpty()) {
renderArea &= _subarea;
}
if (renderArea.isEmpty()) {
if (!cx || !cy) {
vblog(LOG_WARNING,
"Cannot screenshot \"%s\", invalid target size",
obs_source_get_name(source));
@@ -77,24 +71,16 @@ void ScreenshotHelper::Screenshot()
return;
}
cx = renderArea.width();
cy = renderArea.height();
texrender = gs_texrender_create(GS_RGBA, GS_ZS_NONE);
stagesurf = gs_stagesurface_create(renderArea.width(),
renderArea.height(), GS_RGBA);
stagesurf = gs_stagesurface_create(cx, cy, GS_RGBA);
gs_texrender_reset(texrender);
if (gs_texrender_begin(texrender, renderArea.width(),
renderArea.height())) {
if (gs_texrender_begin(texrender, cx, cy)) {
vec4 zero;
vec4_zero(&zero);
gs_clear(GS_CLEAR_COLOR, &zero, 0.0f, 0);
gs_ortho((float)(renderArea.left()),
(float)(renderArea.right() + 1),
(float)(renderArea.top()),
(float)(renderArea.bottom() + 1), -100.0f, 100.0f);
gs_ortho(0.0f, (float)cx, 0.0f, (float)cy, -100.0f, 100.0f);
gs_blend_state_push();
gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO);

View File

@@ -12,9 +12,9 @@ namespace advss {
class ScreenshotHelper {
public:
ScreenshotHelper() = default;
ScreenshotHelper(obs_source_t *source, const QRect &subarea = QRect(),
bool blocking = false, int timeout = 1000,
bool saveToFile = false, std::string path = "");
ScreenshotHelper(obs_source_t *source, bool blocking = false,
int timeout = 1000, bool saveToFile = false,
std::string path = "");
ScreenshotHelper &operator=(const ScreenshotHelper &) = delete;
ScreenshotHelper(const ScreenshotHelper &) = delete;
~ScreenshotHelper();
@@ -39,7 +39,6 @@ public:
private:
std::atomic_bool _initDone = false;
QRect _subarea = QRect();
bool _blocking = false;
std::thread _saveThread;
bool _saveToFile = false;

View File

@@ -50,17 +50,11 @@ std::vector<SourceSetting> GetSoruceSettings(obs_source_t *source)
if (!it) {
continue;
}
auto name = obs_property_name(it);
if (!name) {
continue;
}
auto description = obs_property_description(it);
if (!description) {
continue;
}
auto longDescription = obs_property_long_description(it);
SourceSetting setting(name, description,
longDescription ? longDescription : "");
SourceSetting setting(
obs_property_name(it), obs_property_description(it),
obs_property_long_description(it)
? obs_property_long_description(it)
: "");
settings.emplace_back(setting);
} while (obs_property_next(&it));
obs_properties_destroy(properties);

View File

@@ -2,7 +2,7 @@
#include "advanced-scene-switcher.hpp"
#include "macro.hpp"
#include "macro-segment.hpp"
#include "plugin-state-helper.hpp"
#include "switcher-data.hpp"
#include "utility.hpp"
#include <QVariant>
@@ -170,7 +170,7 @@ void TempVariableRef::Load(obs_data_t *obj, Macro *macro, const char *name)
_id = obs_data_get_string(data, "id");
const auto type =
static_cast<SegmentType>(obs_data_get_int(data, "type"));
AddPostLoadStep([this, idx, type, macro]() {
switcher->AddPostLoadStep([this, idx, type, macro]() {
this->PostLoad(idx, type, macro);
});
}
@@ -427,7 +427,7 @@ void NotifyUIAboutTempVarChange()
obs_queue_task(
OBS_TASK_UI,
[](void *) {
if (!SettingsWindowIsOpened()) {
if (!GetSwitcher()->settingsWindowOpened) {
return;
}
AdvSceneSwitcher::window->SegmentTempVarsChanged();

View File

@@ -1,8 +1,6 @@
#include "websocket-helpers.hpp"
#include "connection-manager.hpp"
#include "log-helper.hpp"
#include "plugin-state-helper.hpp"
#include "sync-helper.hpp"
#include "switcher-data.hpp"
#include <QCryptographicHash>
#include <obs-websocket-api.h>
@@ -15,38 +13,16 @@ using websocketpp::lib::bind;
#define RPC_VERSION 1
constexpr char VendorName[] = "AdvancedSceneSwitcher";
constexpr char VendorRequest[] = "AdvancedSceneSwitcherMessage";
constexpr char VendorEvent[] = "AdvancedSceneSwitcherEvent";
obs_websocket_vendor vendor;
static void clearWebsocketMessages();
static std::vector<std::string> websocketMessages;
void SetupWebsocketHelpers()
void ClearWebsocketMessages()
{
static bool done = false;
if (done) {
return;
}
AddIntervalResetStep(clearWebsocketMessages);
done = true;
}
std::vector<std::string> &GetWebsocketMessages()
{
return websocketMessages;
}
static void clearWebsocketMessages()
{
websocketMessages.clear();
for (auto &connection : GetConnections()) {
auto c = dynamic_cast<Connection *>(connection.get());
if (!c) {
continue;
switcher->websocketMessages.clear();
for (auto &connection : switcher->connections) {
Connection *c = dynamic_cast<Connection *>(connection.get());
if (c) {
c->Events().clear();
}
c->Events().clear();
}
}
@@ -68,8 +44,8 @@ static void receiveWebsocketMessage(obs_data_t *request_data, obs_data_t *,
}
auto msg = obs_data_get_string(request_data, "message");
auto lock = LockContext();
websocketMessages.emplace_back(msg);
std::lock_guard<std::mutex> lock(switcher->m);
switcher->websocketMessages.emplace_back(msg);
vblog(LOG_INFO, "received message: %s", msg);
}
@@ -318,7 +294,7 @@ void WSConnection::HandleEvent(obs_data_t *msg)
return;
}
auto eventDataNested = obs_data_get_obj(eventData, "eventData");
auto lock = LockContext();
std::lock_guard<std::mutex> lock(switcher->m);
_messages.emplace_back(obs_data_get_string(eventDataNested, "message"));
vblog(LOG_INFO, "received event msg \"%s\"",
obs_data_get_string(eventDataNested, "message"));
@@ -350,7 +326,7 @@ void WSConnection::OnGenericMessage(connection_hdl, client::message_ptr message)
return;
}
auto lock = LockContext();
std::lock_guard<std::mutex> lock(switcher->m);
const auto payload = message->get_payload();
_messages.emplace_back(payload);
vblog(LOG_INFO, "received event msg \"%s\"", payload.c_str());

View File

@@ -21,17 +21,18 @@
namespace advss {
using websocketpp::connection_hdl;
typedef websocketpp::server<websocketpp::config::asio> server;
typedef websocketpp::client<websocketpp::config::asio_client> client;
void SetupWebsocketHelpers();
constexpr char VendorName[] = "AdvancedSceneSwitcher";
constexpr char VendorRequest[] = "AdvancedSceneSwitcherMessage";
constexpr char VendorEvent[] = "AdvancedSceneSwitcherEvent";
void ClearWebsocketMessages();
void SendWebsocketEvent(const std::string &);
std::string ConstructVendorRequestMessage(const std::string &message);
std::vector<std::string> &GetWebsocketMessages();
class WSConnection : public QObject {
using server = websocketpp::server<websocketpp::config::asio>;
using client = websocketpp::client<websocketpp::config::asio_client>;
public:
explicit WSConnection(bool useOBSProtocol = true);
virtual ~WSConnection();

View File

@@ -5,17 +5,13 @@ get_target_property(ADVSS_SOURCE_DIR advanced-scene-switcher-lib SOURCE_DIR)
add_executable(${PROJECT_NAME})
target_compile_definitions(${PROJECT_NAME} PRIVATE UNIT_TEST)
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)
target_sources(
${PROJECT_NAME}
PRIVATE tests.cpp ${ADVSS_SOURCE_DIR}/src/utils/math-helpers.cpp
${ADVSS_SOURCE_DIR}/src/utils/obs-module-helper.cpp)
target_sources(${PROJECT_NAME}
PRIVATE tests.cpp ${ADVSS_SOURCE_DIR}/src/utils/math-helpers.cpp)
target_include_directories(
${PROJECT_NAME}
PRIVATE "${ADVSS_SOURCE_DIR}/src" "${ADVSS_SOURCE_DIR}/src/legacy"
"${ADVSS_SOURCE_DIR}/src/macro-core" "${ADVSS_SOURCE_DIR}/src/utils"
"${ADVSS_SOURCE_DIR}/forms" "${ADVSS_SOURCE_DIR}/deps/exprtk")
if(MSVC)
target_compile_options(${PROJECT_NAME} PUBLIC /MP /d2FH4- /wd4267 /wd4267
/bigobj)