diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 82c9b5b9..5a902d71 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -200,7 +200,7 @@ jobs: QT_VERSION: 5.10.1 CMAKE_GENERATOR: "Visual Studio 16 2019" CMAKE_SYSTEM_VERSION: "10.0.18363.657" - WINDOWS_DEPS_VERSION: '2017' + WINDOWS_DEPS_VERSION: '2019' steps: - name: Add msbuild to PATH uses: microsoft/setup-msbuild@v1.0.2 diff --git a/CMakeLists.txt b/CMakeLists.txt index 95643471..b1e1d8ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,7 +62,6 @@ set(advanced-scene-switcher_HEADERS ${advanced-scene-switcher_HEADERS} src/headers/advanced-scene-switcher.hpp src/headers/switcher-data-structs.hpp - src/headers/utility.hpp src/headers/scene-group.hpp src/headers/scene-trigger.hpp src/headers/switch-audio.hpp @@ -82,32 +81,44 @@ set(advanced-scene-switcher_HEADERS src/headers/switch-generic.hpp src/headers/macro-action-edit.hpp src/headers/macro-action-audio.hpp + src/headers/macro-action-filter.hpp + src/headers/macro-action-macro.hpp + src/headers/macro-action-media.hpp + src/headers/macro-action-plugin-state.hpp src/headers/macro-action-recording.hpp src/headers/macro-action-replay-buffer.hpp src/headers/macro-action-run.hpp + src/headers/macro-action-scene-switch.hpp + src/headers/macro-action-scene-visibility.hpp + src/headers/macro-action-source.hpp src/headers/macro-action-streaming.hpp - src/headers/macro-action-switch-scene.hpp src/headers/macro-action-wait.hpp src/headers/macro-condition-edit.hpp src/headers/macro-condition-audio.hpp + src/headers/macro-condition-counter.hpp src/headers/macro-condition-file.hpp src/headers/macro-condition-idle.hpp + src/headers/macro-condition-interval.hpp src/headers/macro-condition-media.hpp src/headers/macro-condition-plugin-state.hpp src/headers/macro-condition-process.hpp src/headers/macro-condition-recording.hpp src/headers/macro-condition-region.hpp src/headers/macro-condition-scene.hpp + src/headers/macro-condition-source.hpp src/headers/macro-condition-streaming.hpp src/headers/macro-condition-video.hpp src/headers/macro-condition-virt-desktop.hpp src/headers/macro-condition-window.hpp src/headers/macro.hpp + src/headers/macro-selection.hpp src/headers/curl-helper.hpp src/headers/screenshot-helper.hpp src/headers/name-dialog.hpp src/headers/duration-control.hpp src/headers/section.hpp + src/headers/platform-funcs.hpp + src/headers/utility.hpp src/headers/volume-control.hpp src/headers/version.h ) @@ -139,33 +150,44 @@ set(advanced-scene-switcher_SOURCES src/switch-generic.cpp src/macro-action-edit.cpp src/macro-action-audio.cpp + src/macro-action-filter.cpp + src/macro-action-macro.cpp + src/macro-action-media.cpp + src/macro-action-plugin-state.cpp src/macro-action-recording.cpp src/macro-action-replay-buffer.cpp src/macro-action-run.cpp + src/macro-action-scene-switch.cpp + src/macro-action-scene-visibility.cpp + src/macro-action-source.cpp src/macro-action-streaming.cpp - src/macro-action-switch-scene.cpp src/macro-action-wait.cpp src/macro-condition-edit.cpp src/macro-condition-audio.cpp + src/macro-condition-counter.cpp src/macro-condition-file.cpp src/macro-condition-idle.cpp + src/macro-condition-interval.cpp src/macro-condition-media.cpp src/macro-condition-plugin-state.cpp src/macro-condition-process.cpp src/macro-condition-recording.cpp src/macro-condition-region.cpp src/macro-condition-scene.cpp + src/macro-condition-source.cpp src/macro-condition-streaming.cpp src/macro-condition-video.cpp src/macro-condition-virt-desktop.cpp src/macro-condition-window.cpp src/macro.cpp + src/macro-selection.cpp src/macro-tab.cpp src/curl-helper.cpp src/screenshot-helper.cpp src/name-dialog.cpp src/duration-control.cpp src/section.cpp + src/utility.cpp src/volume-control.cpp src/version.cpp ) diff --git a/data/locale/de-DE.ini b/data/locale/de-DE.ini index a04f9f70..e300f5dd 100644 --- a/data/locale/de-DE.ini +++ b/data/locale/de-DE.ini @@ -227,7 +227,9 @@ AdvSceneSwitcher.networkTab.Disabledwarning="Diese Funktionalität musste unter AdvSceneSwitcher.networkTab.server="Server starten (Sendet Szenenwechselnachrichten zu allen verbundenen Clients)" AdvSceneSwitcher.networkTab.server.port="Port" AdvSceneSwitcher.networkTab.server.lockToIPv4="Nur IPv4 verwenden (deaktiviert IPv6)" -AdvSceneSwitcher.networkTab.server.restrictSendToAutomatedSwitches="Sende nur Szenenwechselnachrichten für automatisierte Szenenwechsel" +AdvSceneSwitcher.networkTab.server.sendSceneChange="Sende Nachrichten für Szenenwechsel" +AdvSceneSwitcher.networkTab.server.restrictSendToAutomatedSwitches="Sende nur Szenenwechselnachrichten für automatisierte Szenenwechsel" +AdvSceneSwitcher.networkTab.server.sendPreview="Sende Nachrichten für Vorschau Szenenwechsel im Studio-Modus" AdvSceneSwitcher.networkTab.startFailed.message="Der WebSocket-Server konnte nicht gestartet werden, mögliche Gründe:\n - Der TCP-Port %1 wird möglicherweise gerade von einem anderen Programm verwendet. Versuchen Sie einen anderen Port in den Websocket-Servereinstellungen zu setzen oder alle Programme zu beenden, die den Port möglicherweise verwenden.\n - Fehler: %2" AdvSceneSwitcher.networkTab.server.status.currentStatus="Aktueller Status" AdvSceneSwitcher.networkTab.server.status.notRunning="Nicht gestartet" diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index e0c898d0..78dd1c95 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -57,8 +57,8 @@ AdvSceneSwitcher.generalTab.priority.macro="Macro" AdvSceneSwitcher.macroTab.title="Macro" AdvSceneSwitcher.macroTab.macros="Macros" AdvSceneSwitcher.macroTab.help="Macros allow you to execute a string of actions depending on multiple conditions.\n\nClick on the highlighted plus symbol to add a new Macro." -AdvSceneSwitcher.macroTab.editConditionHelp="This section allows you to define Macro conditions.\n\nSelect an existing or add a new Macro on the left." -AdvSceneSwitcher.macroTab.editActionHelp="This section allows you to define Macro actions.\n\nSelect an existing or add a new Macro on the left." +AdvSceneSwitcher.macroTab.editConditionHelp="This section allows you to define Macro conditions.\n\nSelect an existing or add a new Macro on the left.\nThen click the plus button below to add a new condition." +AdvSceneSwitcher.macroTab.editActionHelp="This section allows you to define Macro actions.\n\nSelect an existing or add a new Macro on the left.\nThen click the plus button below to add a new action." AdvSceneSwitcher.macroTab.edit="Edit macro" AdvSceneSwitcher.macroTab.edit.logic="Logic type:" AdvSceneSwitcher.macroTab.edit.condition="Condition type:" @@ -68,6 +68,7 @@ AdvSceneSwitcher.macroTab.name="Name:" AdvSceneSwitcher.macroTab.defaultname="Macro %1" AdvSceneSwitcher.macroTab.exists="Macro name exists already" AdvSceneSwitcher.macroTab.copy="Create copy" + ; Macro Logic AdvSceneSwitcher.logic.none="Ignore entry" AdvSceneSwitcher.logic.and="And" @@ -76,17 +77,18 @@ AdvSceneSwitcher.logic.andNot="And not" AdvSceneSwitcher.logic.orNot="Or not" AdvSceneSwitcher.logic.rootNone="If" AdvSceneSwitcher.logic.not="If not" + ; Macro Conditions AdvSceneSwitcher.condition.audio="Audio" -AdvSceneSwitcher.ondition.audio.state.below="Below" -AdvSceneSwitcher.ondition.audio.state.above="Above" -AdvSceneSwitcher.condition.audio.entry="Volume of {{audioSources}} is {{condition}} {{volume}} for {{duration}} seconds" +AdvSceneSwitcher.condition.audio.state.below="Below" +AdvSceneSwitcher.condition.audio.state.above="Above" +AdvSceneSwitcher.condition.audio.entry="Volume of {{audioSources}} is {{condition}} {{volume}}" AdvSceneSwitcher.condition.region="Screen region" AdvSceneSwitcher.condition.region.entry="Cursor is in {{minX}} {{minY}} x {{maxX}} {{maxY}}" AdvSceneSwitcher.condition.scene="Scene" AdvSceneSwitcher.condition.scene.type.current="Current" AdvSceneSwitcher.condition.scene.type.previous="Previous" -AdvSceneSwitcher.condition.scene.entry="{{sceneType}} scene is {{scenes}} for {{duration}}" +AdvSceneSwitcher.condition.scene.entry="{{sceneType}} scene is {{scenes}}" AdvSceneSwitcher.condition.window="Window" AdvSceneSwitcher.condition.window.entry.line1="{{windows}} exist and ..." AdvSceneSwitcher.condition.window.entry.line2="... is {{fullscreen}} fullscreen {{maximized}} maximized {{focused}} focused" @@ -105,16 +107,16 @@ AdvSceneSwitcher.condition.video.condition.noImage="has no output" AdvSceneSwitcher.condition.video.askFileAction="Do you want to use an existing file or create a screenshot of the currently selected source?" AdvSceneSwitcher.condition.video.askFileAction.file="Use existing file" AdvSceneSwitcher.condition.video.askFileAction.screenshot="Create screenshot" -AdvSceneSwitcher.condition.video.entry="{{videoSources}} {{condition}} {{filePath}} {{browseButton}} for {{duration}}" +AdvSceneSwitcher.condition.video.entry="{{videoSources}} {{condition}} {{filePath}} {{browseButton}}" AdvSceneSwitcher.condition.stream="Streaming" AdvSceneSwitcher.condition.stream.state.start="Stream running" AdvSceneSwitcher.condition.stream.state.stop="Stream stopped" -AdvSceneSwitcher.condition.stream.entry="{{streamState}} for {{duration}}" +AdvSceneSwitcher.condition.stream.entry="{{streamState}}" 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.entry="{{recordState}} for {{duration}}" +AdvSceneSwitcher.condition.record.entry="{{recordState}}" AdvSceneSwitcher.condition.process="Process" AdvSceneSwitcher.condition.process.entry="{{processes}} is running {{focused}} and is focused" AdvSceneSwitcher.condition.idle="Idle" @@ -126,6 +128,24 @@ AdvSceneSwitcher.condition.virtDesktop="Virtual desktop" AdvSceneSwitcher.condition.virtDesktop.notAvailable="Not availbale" AdvSceneSwitcher.condition.virtDesktop.entry.line1="Active virtual desktop is {{virtDesktops}}" AdvSceneSwitcher.condition.virtDesktop.entry.line2="Current: {{currentDesktop}}" +AdvSceneSwitcher.condition.interval="Interval" +AdvSceneSwitcher.condition.interval.entry="{{duration}} have passed" +AdvSceneSwitcher.condition.counter="Count" +AdvSceneSwitcher.condition.counter.type.below="Less than" +AdvSceneSwitcher.condition.counter.type.above="More than" +AdvSceneSwitcher.condition.counter.type.equal="Exactly" +AdvSceneSwitcher.condition.counter.reset="Reset" +AdvSceneSwitcher.condition.counter.entry.line1="{{macros}} was executed {{conditions}} {{count}} times" +AdvSceneSwitcher.condition.counter.entry.line2="Current count: {{currentCount}} {{resetCount}}" +AdvSceneSwitcher.condition.source="Source" +AdvSceneSwitcher.condition.source.type.active="Is active" +AdvSceneSwitcher.condition.source.type.showing="Is showing" +AdvSceneSwitcher.condition.source.type.settings="Settings match" +AdvSceneSwitcher.condition.source.regex="Use regular expressions" +AdvSceneSwitcher.condition.source.getSettings="Get current settings" +AdvSceneSwitcher.condition.source.entry.line1="{{sources}} {{conditions}}" +AdvSceneSwitcher.condition.source.entry.line2="{{settings}}" +AdvSceneSwitcher.condition.source.entry.line3="{{regex}} {{getSettings}}" ; Macro Actions AdvSceneSwitcher.action.switchScene="Switch scene" @@ -159,7 +179,37 @@ AdvSceneSwitcher.action.streaming.type.start="Start streaming" AdvSceneSwitcher.action.streaming.entry="{{actions}}" AdvSceneSwitcher.action.run="Run" AdvSceneSwitcher.action.run.entry="Run {{filePath}} {{browseButton}}" - +AdvSceneSwitcher.action.sceneVisibility="Scene item visibility" +AdvSceneSwitcher.action.sceneVisibility.type.show="Show" +AdvSceneSwitcher.action.sceneVisibility.type.hide="Hide" +AdvSceneSwitcher.action.sceneVisibility.entry="On {{scenes}} {{actions}} {{sources}}" +AdvSceneSwitcher.action.filter="Filter" +AdvSceneSwitcher.action.filter.type.enable="Enable" +AdvSceneSwitcher.action.filter.type.disable="Disable" +AdvSceneSwitcher.action.filter.entry="On {{sources}} {{actions}} {{filters}}" +AdvSceneSwitcher.action.source="Source" +AdvSceneSwitcher.action.source.type.enable="Enable" +AdvSceneSwitcher.action.source.type.disable="Disable" +AdvSceneSwitcher.action.source.type.settings="Set settings" +AdvSceneSwitcher.action.source.entry="{{actions}} {{sources}}" +AdvSceneSwitcher.action.source.warning="Warning: Enabling and disabling sources globally cannot be controlled by the OBS UI" +AdvSceneSwitcher.action.source.getSettings="Get current settings" +AdvSceneSwitcher.action.media="Media" +AdvSceneSwitcher.action.media.type.play="Play" +AdvSceneSwitcher.action.media.type.pause="Pause" +AdvSceneSwitcher.action.media.type.stop="Stop" +AdvSceneSwitcher.action.media.type.restart="Restart" +AdvSceneSwitcher.action.media.type.next="Next" +AdvSceneSwitcher.action.media.type.previous="Previous" +AdvSceneSwitcher.action.media.entry="{{actions}} {{mediaSources}}" +AdvSceneSwitcher.action.macro="Macro" +AdvSceneSwitcher.action.macro.type.pause="Pause" +AdvSceneSwitcher.action.macro.type.unpause="Unpause" +AdvSceneSwitcher.action.macro.type.resetCounter="Reset counter" +AdvSceneSwitcher.action.macro.entry="{{actions}} {{macros}}" +AdvSceneSwitcher.action.pluginState="Plugin state" +AdvSceneSwitcher.action.pluginState.type.stop="Stop the Advanced Scene Switcher plugin" +AdvSceneSwitcher.action.pluginState.entry="{{actions}}" ; Transition Tab AdvSceneSwitcher.transitionTab.title="Transition" @@ -336,7 +386,9 @@ AdvSceneSwitcher.networkTab.DisabledWarning="This functionality unfortunately ha AdvSceneSwitcher.networkTab.server="Start server (Sends scene switch messages to all connected clients)" AdvSceneSwitcher.networkTab.server.port="Port" AdvSceneSwitcher.networkTab.server.lockToIPv4="Lock server to only using IPv4" +AdvSceneSwitcher.networkTab.server.sendSceneChange="Send messages for scene changes" AdvSceneSwitcher.networkTab.server.restrictSendToAutomatedSwitches="Only send messages for automated scene switches" +AdvSceneSwitcher.networkTab.server.sendPreview="Send messages for preview scene change when running in Studio mode" AdvSceneSwitcher.networkTab.startFailed.message="The WebSockets server failed to start, maybe because:\n - TCP port %1 may currently be in use elsewhere on this system, possibly by another application. Try setting a different TCP port in the WebSocket server settings, or stop any application that could be using this port.\n - Error message: %2" AdvSceneSwitcher.networkTab.server.status.currentStatus="Current status" AdvSceneSwitcher.networkTab.server.status.notRunning="Not running" @@ -398,6 +450,8 @@ AdvSceneSwitcher.sceneTriggerTab.help="This tab allows you to trigger actions on AdvSceneSwitcher.hotkey.startSwitcherHotkey="Start the Advanced Scene Switcher" AdvSceneSwitcher.hotkey.stopSwitcherHotkey="Stop the Advanced Scene Switcher" AdvSceneSwitcher.hotkey.startStopToggleSwitcherHotkey="Toggle Start/Stop for the Advanced Scene Switcher" +AdvSceneSwitcher.hotkey.macro.pause="Pause macro %1" +AdvSceneSwitcher.hotkey.macro.unpause="Unpause macro %1" AdvSceneSwitcher.askBackup="Detected a new version of the Advanced Scene Switcher.\nShould a backup of the old settings be created?" @@ -409,10 +463,14 @@ AdvSceneSwitcher.selectPreviousScene="Previous Scene" AdvSceneSwitcher.currentTransition="Current Transition" AdvSceneSwitcher.selectTransition="--select transition--" AdvSceneSwitcher.selectWindow="--select window--" +AdvSceneSwitcher.selectSource="--select source--" AdvSceneSwitcher.selectAudioSource="--select audio source--" AdvSceneSwitcher.selectVideoSource="--select video source--" AdvSceneSwitcher.selectMediaSource="--select media source--" AdvSceneSwitcher.selectProcess="--select process--" +AdvSceneSwitcher.selectFilter="--select filter--" +AdvSceneSwitcher.selectMacro="--select macro--" +AdvSceneSwitcher.selectItem="--select item--" AdvSceneSwitcher.enterPath="--enter path--" AdvSceneSwitcher.enterText="--enter text--" AdvSceneSwitcher.invaildEntriesWillNotBeSaved="invalid entries will not be saved" @@ -425,3 +483,7 @@ AdvSceneSwitcher.unit.milliseconds="milliseconds" AdvSceneSwitcher.unit.secends="seconds" AdvSceneSwitcher.unit.minutes="minutes" AdvSceneSwitcher.unit.hours="hours" +AdvSceneSwitcher.duration.condition.none="No time constraint" +AdvSceneSwitcher.duration.condition.more="For at least" +AdvSceneSwitcher.duration.condition.equal="For exactly" +AdvSceneSwitcher.duration.condition.less="For at most" diff --git a/data/locale/ru-RU.ini b/data/locale/ru-RU.ini new file mode 100644 index 00000000..f4104ce8 --- /dev/null +++ b/data/locale/ru-RU.ini @@ -0,0 +1,423 @@ +AdvSceneSwitcher.pluginName="Advanced Scene Switcher" +AdvSceneSwitcher.windowTitle="Advanced Scene Switcher" + +; General Tab +AdvSceneSwitcher.generalTab.title="Общие" +AdvSceneSwitcher.generalTab.status="Статус" +AdvSceneSwitcher.generalTab.status.hotkeytips="Горячие клавиши можно определить в настройках OBS" +AdvSceneSwitcher.generalTab.status.currentStatus="Advanced Scene Switcher сейчас:" +AdvSceneSwitcher.generalTab.status.onStartup="При запуске OBS:" +AdvSceneSwitcher.generalTab.status.onStartup.asLastRun="Запустить переключатель сцен, если он был запущен" +AdvSceneSwitcher.generalTab.status.onStartup.alwaysStart="Всегда запускать переключатель сцен" +AdvSceneSwitcher.generalTab.status.onStartup.doNotStart="Не запускать переключатель сцен" +AdvSceneSwitcher.generalTab.status.start="Старт" +AdvSceneSwitcher.generalTab.status.stop="Стоп" +AdvSceneSwitcher.generalTab.status.autoStart="Автоматически запускать переключатель сцен, когда:" +AdvSceneSwitcher.generalTab.status.autoStart.never="Никогда" +AdvSceneSwitcher.generalTab.status.autoStart.recording="Запись" +AdvSceneSwitcher.generalTab.status.autoStart.streaming="Вещание" +AdvSceneSwitcher.generalTab.status.autoStart.recordingAndStreaming="Запись или потоковое вещание" +AdvSceneSwitcher.generalTab.status.checkInterval="Проверять условия переключения каждый раз" +AdvSceneSwitcher.generalTab.generalBehavior="Общее поведение" +AdvSceneSwitcher.generalTab.generalBehavior.onNoMet="Если не выполняется условие переключения для" +AdvSceneSwitcher.generalTab.generalBehavior.onNoMetDelayTooltip="Будет только настолько точным, насколько настроен интервал проверки." +AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.dontSwitch="Не переключаться" +AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchToRandom="Переключиться на любую сцену на вкладке Random" +AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchTo="Переключиться на:" +AdvSceneSwitcher.generalTab.generalBehavior.cooldown="После совпадения не переключаться между сценами в течение" +AdvSceneSwitcher.generalTab.generalBehavior.cooldownHint="В течение этого времени потенциальные совпадения будут игнорироваться!" +AdvSceneSwitcher.generalTab.generalBehavior.verboseLogging="Включить ведение подробного журнала" +AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo="Сохранять положение и размер окна" +AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="Отключить подсказки пользовательского интерфейса" +AdvSceneSwitcher.generalTab.priority="Приоритет" +AdvSceneSwitcher.generalTab.priority.description="Приоритет методов переключения (самый высокий приоритет - вверху)" +AdvSceneSwitcher.generalTab.priority.threadPriority="Использовать приоритет потока" +AdvSceneSwitcher.generalTab.priority.threadPriorityNotice="(Поднимать приоритет выше \"Нормальный\" не рекомендуется)" +AdvSceneSwitcher.generalTab.saveOrLoadsettings="Сохранить / загрузить настройки" +AdvSceneSwitcher.generalTab.saveOrLoadsettings.export="Экспорт" +AdvSceneSwitcher.generalTab.saveOrLoadsettings.import="Импорт" +AdvSceneSwitcher.generalTab.saveOrLoadsettings.exportWindowTitle="Экспортировать настройки расширенного переключателя сцен в файл ..." +AdvSceneSwitcher.generalTab.saveOrLoadsettings.importWindowTitle="Импортировать настройки Advanced Scene Switcher из файла ..." +AdvSceneSwitcher.generalTab.saveOrLoadsettings.textType="Текстовые файлы (*.txt)" +AdvSceneSwitcher.generalTab.saveOrLoadsettings.loadFail="Advanced Scene Switcher не удалось импортировать настройки" +AdvSceneSwitcher.generalTab.saveOrLoadsettings.loadSuccess="Настройки Advanced Scene Switcher импортированы успешно" +AdvSceneSwitcher.generalTab.priority.fileContent="Содержание файла" +AdvSceneSwitcher.generalTab.priority.sceneSequence="Последовательность сцены" +AdvSceneSwitcher.generalTab.priority.idleDetection="Обнаружение простоя" +AdvSceneSwitcher.generalTab.priority.executable="Исполняемый файл" +AdvSceneSwitcher.generalTab.priority.screenRegion="Область экрана" +AdvSceneSwitcher.generalTab.priority.windowTitle="Заголовок окна" +AdvSceneSwitcher.generalTab.priority.media="Медиа" +AdvSceneSwitcher.generalTab.priority.time="Время" +AdvSceneSwitcher.generalTab.priority.audio="Аудио" +AdvSceneSwitcher.generalTab.priority.video="Видео" +AdvSceneSwitcher.generalTab.priority.macro="Макрос" + +; Macro Tab +AdvSceneSwitcher.macroTab.title="Макрос" +AdvSceneSwitcher.macroTab.macros="Макросы" +AdvSceneSwitcher.macroTab.help="Макросы позволяют выполнять ряд действий в зависимости от нескольких условий.\n\nНажмите на выделенный символ плюса, чтобы добавить новый макрос." +AdvSceneSwitcher.macroTab.editConditionHelp="Этот раздел позволяет определить условия макроса.\n\nВыберите существующий или добавьте новый макрос слева." +AdvSceneSwitcher.macroTab.editActionHelp="Этот раздел позволяет определить действия макроса.\n\n\nВыберите существующий или добавьте новый макрос слева." +AdvSceneSwitcher.macroTab.edit="Редактировать макрос" +AdvSceneSwitcher.macroTab.edit.logic="Тип логики:" +AdvSceneSwitcher.macroTab.edit.condition="Тип условия:" +AdvSceneSwitcher.macroTab.edit.action="Тип действия:" +AdvSceneSwitcher.macroTab.add="Добавить новый макрос" +AdvSceneSwitcher.macroTab.name="Имя:" +AdvSceneSwitcher.macroTab.defaultname="Макрос %1" +AdvSceneSwitcher.macroTab.exists="Имя макроса уже существует" +AdvSceneSwitcher.macroTab.copy="Создать копию" +; Macro Logic +AdvSceneSwitcher.logic.none="Игнорировать вход" +AdvSceneSwitcher.logic.and="И" +AdvSceneSwitcher.logic.or="Или" +AdvSceneSwitcher.logic.andNot="И не" +AdvSceneSwitcher.logic.orNot="Или нет" +AdvSceneSwitcher.logic.rootNone="Если" +AdvSceneSwitcher.logic.not="Если нет" +; Macro Conditions +AdvSceneSwitcher.condition.audio="Аудио" +AdvSceneSwitcher.ondition.audio.state.below="Ниже" +AdvSceneSwitcher.ondition.audio.state.above="Выше" +AdvSceneSwitcher.condition.audio.entry="Громкость {{audioSources}} равна {{condition}} {{volume}} в течении {{duration}} секунд" +AdvSceneSwitcher.condition.region="Область экрана" +AdvSceneSwitcher.condition.region.entry="Курсор находится в {{minX}} {{minY}} x {{maxX}} {{maxY}}" +AdvSceneSwitcher.condition.scene="Сцена" +AdvSceneSwitcher.condition.scene.type.current="Текущий" +AdvSceneSwitcher.condition.scene.type.previous="Предыдущий" +AdvSceneSwitcher.condition.scene.entry="{{sceneType}} сцена является {{scenes}} на {{duration}}" +AdvSceneSwitcher.condition.window="Окно" +AdvSceneSwitcher.condition.window.entry.line1="{{windows}} существует и ..." +AdvSceneSwitcher.condition.window.entry.line2="... это {{fullscreen}} полноэкранный {{maximized}} максимизированный {{focused}} сфокусированный" +AdvSceneSwitcher.condition.file="Файл" +AdvSceneSwitcher.condition.file.entry.line1="Содержимое {{fileType}} {{filePath}} {{browseButton}} соответствует:" +AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}" +AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}" +AdvSceneSwitcher.condition.media="Медиа" +AdvSceneSwitcher.condition.media.entry="{{mediaSources}} состояние {{states}} и {{timeRestrictions}} {{time}}" +AdvSceneSwitcher.condition.video="Видео" +AdvSceneSwitcher.condition.video.condition.match="точно соответствует" +AdvSceneSwitcher.condition.video.condition.differ="не совпадает" +AdvSceneSwitcher.condition.video.condition.hasChanged="изменилось" +AdvSceneSwitcher.condition.video.condition.hasNotChanged="не изменилось" +AdvSceneSwitcher.condition.video.condition.noImage="не имеет вывода" +AdvSceneSwitcher.condition.video.askFileAction="Вы хотите использовать существующий файл или создать скриншот текущего выбранного источника?" +AdvSceneSwitcher.condition.video.askFileAction.file="Использовать существующий файл" +AdvSceneSwitcher.condition.video.askFileAction.screenshot="Создать скриншот" +AdvSceneSwitcher.condition.video.entry="{{videoSources}} {{condition}} {{filePath}} {{browseButton}} для {{duration}}" +AdvSceneSwitcher.condition.stream="Потоковое вещание" +AdvSceneSwitcher.condition.stream.state.start="Поток запущен" +AdvSceneSwitcher.condition.stream.state.stop="Поток остановлен" +AdvSceneSwitcher.condition.stream.entry="{{streamState}} для {{duration}}" +AdvSceneSwitcher.condition.record="Запись" +AdvSceneSwitcher.condition.record.state.start="Запись запущена" +AdvSceneSwitcher.condition.record.state.pause="Запись приостановлена" +AdvSceneSwitcher.condition.record.state.stop="Запись остановлена" +AdvSceneSwitcher.condition.record.entry="{{recordState}} для {{duration}}" +AdvSceneSwitcher.condition.process="Процесс" +AdvSceneSwitcher.condition.process.entry="{{processes}} запущен {{focused}} и сфокусирован" +AdvSceneSwitcher.condition.idle="Простой" +AdvSceneSwitcher.condition.idle.entry="Не было ни клавиатуры, ни мыши в течении{{duration}}" +AdvSceneSwitcher.condition.pluginState="Состояние плагина" +AdvSceneSwitcher.condition.pluginState.state.sceneSwitched="Автоматическая смена сцены была запущена в этом интервале" +AdvSceneSwitcher.condition.pluginState.entry="{{condition}}" + +; Macro Actions +AdvSceneSwitcher.action.switchScene="Переключить сцену" +AdvSceneSwitcher.action.scene.entry="Перейти к сцене {{scenes}} используя {{transitions}} с продолжительностью {{duration}} секунд" +AdvSceneSwitcher.action.wait="Подождать" +AdvSceneSwitcher.action.wait.type.fixed="фиксированный" +AdvSceneSwitcher.action.wait.type.random="случайный" +AdvSceneSwitcher.action.wait.entry.fixed="Ожидать {{waitType}} в течении {{duration}}" +AdvSceneSwitcher.action.wait.entry.random="Ожидание {{waitType}} длительностью от {{duration}} до {{duration2}}" +AdvSceneSwitcher.action.audio="Аудио" +AdvSceneSwitcher.action.audio.type.mute="Замьютить" +AdvSceneSwitcher.action.audio.type.unmute="Размьютить" +AdvSceneSwitcher.action.audio.type.sourceVolume="Установить громкость источника" +AdvSceneSwitcher.action.audio.type.masterVolume="Установить главную громкость" +AdvSceneSwitcher.action.audio.entry="{{actions}} {{audioSources}} {{volume}}" +AdvSceneSwitcher.action.recording="Запись" +AdvSceneSwitcher.action.recording.type.stop="Остановить запись" +AdvSceneSwitcher.action.recording.type.start="Начать запись" +AdvSceneSwitcher.action.recording.type.pause="Пауза записи" +AdvSceneSwitcher.action.recording.type.unpause="Снять запись с паузы" +AdvSceneSwitcher.action.recording.pause.hint="Обратите внимание, что в зависимости от настроек записи вы можете не иметь возможности приостановить запись" +AdvSceneSwitcher.action.recording.entry="{{actions}}{{pauseHint}}" +AdvSceneSwitcher.action.replay="Буфер воспроизведения" +AdvSceneSwitcher.action.replay.type.stop="Остановить буфер воспроизведения" +AdvSceneSwitcher.action.replay.type.start="Начать воспроизведение буфера" +AdvSceneSwitcher.action.replay.type.save="Сохранить буфер воспроизведения" +AdvSceneSwitcher.action.replay.entry="{{actions}}" +AdvSceneSwitcher.action.streaming="Потоковое вещание" +AdvSceneSwitcher.action.streaming.type.stop="Остановить потоковое вещание" +AdvSceneSwitcher.action.streaming.type.start="Начать потоковое вещание" +AdvSceneSwitcher.action.streaming.entry="{{actions}}" +AdvSceneSwitcher.action.run="Запустить" +AdvSceneSwitcher.action.run.entry="Запустить {{filePath}} {{browseButton}}" + + +; Transition Tab +AdvSceneSwitcher.transitionTab.title="Переход" +AdvSceneSwitcher.transitionTab.setTransitionBy="При изменении переходов:" +AdvSceneSwitcher.transitionTab.transitionOverride="Установить переопределение переходов" +AdvSceneSwitcher.transitionTab.adjustActiveTransitionType="Изменить тип активного перехода" +AdvSceneSwitcher.transitionTab.transitionBehaviorSelectionError="Должна быть включена хотя бы одна опция:\n\n - Использовать переопределения переходов\n\n - Изменить тип активного перехода" +AdvSceneSwitcher.transitionTab.transitionForAToB="Использовать переход для автоматического переключения сцены со сцены A на сцену B" +AdvSceneSwitcher.transitionTab.transitionsHelp="

Эти настройки только влияют на переходы, вызванные переключателем сцен - Проверьте Transition Table если вы хотите настроить его для ручного изменения сцены.
Настройки, определенные здесь, имеют приоритет над настройками перехода, сконфигурированными в других местах переключателя сцен.

Нажмите на символ плюса ниже, чтобы добавить новую запись.

" +AdvSceneSwitcher.transitionTab.defaultTransition="Изменить переход, если сцена активна" +AdvSceneSwitcher.transitionTab.entry="Переход от {{scenes}} к {{scenes2}} с помощью {{transitions}} с длительностью {{duration}}" +AdvSceneSwitcher.transitionTab.defaultTransitionEntry="Когда сцена {{scenes}} активна, изменить переход сцены по умолчанию на {{transitions}}" +AdvSceneSwitcher.transitionTab.defaultTransitionsHelp="Нажмите на символ плюса, чтобы добавить запись." +AdvSceneSwitcher.transitionTab.defaultTransition.delay="Переключить переход {{defTransitionDelay}} после смены сцены." +AdvSceneSwitcher.transitionTab.defaultTransition.delay.help="Задержка используется для того, чтобы избежать отмены переключения сцены, что может произойти, если тип перехода будет изменен, когда переход еще продолжается." + +; Pause Scenes Tab +AdvSceneSwitcher.pauseTab.title="Пауза" +AdvSceneSwitcher.pauseTab.pauseOnScene="Приостановить Scene Switcher на сцене" +AdvSceneSwitcher.pauseTab.pauseInFocus1="Приостановить Scene Switcher когда " +AdvSceneSwitcher.pauseTab.pauseInFocus2="находится в фокусе" +AdvSceneSwitcher.pauseTab.pauseTypeScene="сцена активна" +AdvSceneSwitcher.pauseTab.pauseTypeWindow="окно в фокусе" +AdvSceneSwitcher.pauseTab.pauseTargetAll="все" +AdvSceneSwitcher.pauseTab.pauseEntry="Пауза {{pauseTargets}} проверяет, когда {{pauseTypes}} {{scenes}} {{windows}}" +AdvSceneSwitcher.pauseTab.help="На этой вкладке вы можете настроить приостановку отдельных методов переключения, если сцена активна или окно находится в фокусе.\n\nНажмите на выделенный символ плюса, чтобы продолжить." + +; Window Title Tab +AdvSceneSwitcher.windowTitleTab.title="Название" +AdvSceneSwitcher.windowTitleTab.regexrDescription="

Введите либо прямые заголовки окон, либо правильное регулярное выражение. Вы можете проверить синтаксис и соответствие регулярных выражений, используя RegExr

" +AdvSceneSwitcher.windowTitleTab.stayInFocus1="Игнорировать имя этого окна" +AdvSceneSwitcher.windowTitleTab.stayInFocus2=" " +AdvSceneSwitcher.windowTitleTab.fullscreen="if fullscreen" +AdvSceneSwitcher.windowTitleTab.maximized="if maximized" +AdvSceneSwitcher.windowTitleTab.focused="if focused" +AdvSceneSwitcher.windowTitleTab.entry="{{windows}} {{scenes}} {{transitions}} {{fullscreen}} {{maximized}} {{focused}}" +AdvSceneSwitcher.windowTitleTab.windowsHelp="Переключение сцен на основе заголовков окон запущенных приложений.\nВы можете выбрать следующие дополнительные условия:\nОкно полноэкранное\nОкно максимизированное\nОкно сфокусированное\n\nНажмите на выделенный символ плюса, чтобы продолжить." +AdvSceneSwitcher.windowTitleTab.ignoreWindowsHelp="Если заголовок окна игнорируется, переключатель сцен будет действовать так, как будто ранее выбранное окно все еще в фокусе.\nЭто позволит вам избежать переключения сцен, если вы часто переключаетесь на другое окно, которое не должно вызывать смену сцены.\n\nВыберите окно или введите заголовок окна выше и нажмите на символ плюса ниже, чтобы добавить его в список." + +; Executable Tab +AdvSceneSwitcher.executableTab.title="Исполняемый" +AdvSceneSwitcher.executableTab.implemented="Implemented by dasOven" +AdvSceneSwitcher.executableTab.requiresFocus="только если сфокусирован" +AdvSceneSwitcher.executableTab.entry="Когда {{processes}} запущены, переключить на {{scenes}} используя {{transitions}} {{requiresFocus}}" +AdvSceneSwitcher.executableTab.help="Эта вкладка позволит вам автоматически переключаться между сценами, если запущен процесс.\nЭто может быть полезно в ситуациях, когда имя окна может измениться или неизвестно.\n\nНажмите на выделенный символ плюса, чтобы продолжить." + +; Screen Region Tab +AdvSceneSwitcher.screenRegionTab.title="Регион" +AdvSceneSwitcher.screenRegionTab.currentPosition="Курсор в данный момент находится в:" +AdvSceneSwitcher.screenRegionTab.showGuideFrames="Показать направляющие кадры" +AdvSceneSwitcher.screenRegionTab.hideGuideFrames="Скрыть направляющие рамки" +AdvSceneSwitcher.screenRegionTab.excludeScenes.None="Нет выбора" +AdvSceneSwitcher.screenRegionTab.entry="Если курсор находится в {{minX}} {{minY}} x {{maxX}} {{maxY}} переключить на {{scenes}} используя {{transitions}} если не указано {{excludeScenes}}" +AdvSceneSwitcher.screenRegionTab.help="Эта вкладка позволит вам автоматически переключать сцены, основываясь на текущем положении курсора мыши.\n\nНажмите на выделенный символ плюса, чтобы продолжить." + +; Media Tab +AdvSceneSwitcher.mediaTab.title="Медиа" +AdvSceneSwitcher.mediaTab.implemented="Implemented by Exeldro" +AdvSceneSwitcher.mediaTab.states.none="Нет" +AdvSceneSwitcher.mediaTab.states.playing="Играет" +AdvSceneSwitcher.mediaTab.states.opening="Открытие" +AdvSceneSwitcher.mediaTab.states.buffering="Буферизация" +AdvSceneSwitcher.mediaTab.states.Paused="Приостановлено" +AdvSceneSwitcher.mediaTab.states.stopped="Остановлено" +AdvSceneSwitcher.mediaTab.states.ended="Закончилось" +AdvSceneSwitcher.mediaTab.states.error="Ошибка" +AdvSceneSwitcher.mediaTab.states.playedToEnd="Воспроизведено до конца" +AdvSceneSwitcher.mediaTab.states.any="Любой" +AdvSceneSwitcher.mediaTab.timeRestriction.none="Нет" +AdvSceneSwitcher.mediaTab.timeRestriction.shorter="Время короче" +AdvSceneSwitcher.mediaTab.timeRestriction.longer="Время больше" +AdvSceneSwitcher.mediaTab.timeRestriction.remainShorter="Оставшееся время короче" +AdvSceneSwitcher.mediaTab.timeRestriction.remainLonger="Время, оставшееся дольше" +AdvSceneSwitcher.mediaTab.entry="Когда состояние {{mediaSources}} равно {{states}} и {{timeRestrictions}} {{time}} переключиться на {{scenes}} используя {{transitions}}" +AdvSceneSwitcher.mediaTab.help="Эта вкладка позволит вам переключать сцены на основе состояний медиаисточников.\nНапример, вы можете автоматически переключиться на предыдущую сцену, когда выбранный медиаисточник закончил свое воспроизведение.\n\nНажмите на выделенный символ плюса, чтобы продолжить." + +; File Tab +AdvSceneSwitcher.fileTab.title="Файл" +AdvSceneSwitcher.fileTab.readWriteSceneFile="Считать / записать сцену из / в файл" +AdvSceneSwitcher.fileTab.currentSceneOutputFile="Записать имя текущей сцены в этот файл:" +AdvSceneSwitcher.fileTab.switchSceneBaseOnFile="Включить переключение сцен на основе ввода файла" +AdvSceneSwitcher.fileTab.switchSceneNameInputFile="Считать имя сцены, на которую нужно переключиться, из этого файла:" +AdvSceneSwitcher.fileTab.switchSceneBaseOnFileContent="Переключать сцену на основе содержимого файла" +AdvSceneSwitcher.fileTab.remoteFileWarning="Обратите внимание, что при выборе опции remote переключатель сцен будет пытаться получить доступ к удаленному местоположению каждые x мс, как указано на вкладке Общие" +AdvSceneSwitcher.fileTab.remoteFileWarning1="Обратите внимание, что переключатель сцены будет пытаться получить доступ к удаленному местоположению каждые " +AdvSceneSwitcher.fileTab.remoteFileWarning2="ms" +AdvSceneSwitcher.fileTab.libcurlWarning="Не удалось загрузить libcurl! Доступ к удаленным файлам невозможен!" +AdvSceneSwitcher.fileTab.selectWrite="Выберите файл для записи в ..." +AdvSceneSwitcher.fileTab.selectRead="Выберите файл для чтения из ..." +AdvSceneSwitcher.fileTab.textFileType="Текстовые файлы (*.txt)" +AdvSceneSwitcher.fileTab.anyFileType="Любые файлы (*.*)" +AdvSceneSwitcher.fileTab.remote="удаленный файл" +AdvSceneSwitcher.fileTab.local="локальный файл" +AdvSceneSwitcher.fileTab.useRegExp="использовать регулярные выражения (сопоставление шаблонов)" +AdvSceneSwitcher.fileTab.checkfileContentTime="если дата модификации изменена" +AdvSceneSwitcher.fileTab.checkfileContent="если содержимое изменено" +AdvSceneSwitcher.fileTab.entry="Переключиться на {{scenes}} используя {{transitions}} если содержимое {{fileType}} {{filePath}} {{browseButton}} совпадает:" +AdvSceneSwitcher.fileTab.entry2="{{matchText}}" +AdvSceneSwitcher.fileTab.entry3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}" +AdvSceneSwitcher.fileTab.help="Эта вкладка позволит вам автоматически переключать сцены на основе содержимого удаленных или локальных файлов.\n\nНажмите на выделенный символ плюса, чтобы продолжить." + +; Random Tab +AdvSceneSwitcher.randomTab.title="Рандом" +AdvSceneSwitcher.randomTab.randomDisabledWarning="Функциональность отключена - для активации выберите \"Если условие переключения не выполнено, переключиться на любую сцену на вкладке Random\" на вкладке Общие" +AdvSceneSwitcher.randomTab.entry="Если условие переключения не выполнено, переключиться на {{scenes}} используя {{transitions}} для {{delay}}" +AdvSceneSwitcher.randomTab.help="Переключатель сцен будет случайным образом выбирать запись на этой вкладке для переключения на заданное время.\nЗаметьте, что одна и та же запись не будет выбрана дважды подряд.\n\nНажмите на выделенный символ плюса, чтобы продолжить." + +; Time Tab +AdvSceneSwitcher.timeTab.title="Время" +AdvSceneSwitcher.timeTab.anyDay="В любой день" +AdvSceneSwitcher.timeTab.mondays="По понедельникам" +AdvSceneSwitcher.timeTab.tuesdays="По вторникам" +AdvSceneSwitcher.timeTab.wednesdays="По средам" +AdvSceneSwitcher.timeTab.thursdays="По четвергам" +AdvSceneSwitcher.timeTab.fridays="По пятницам" +AdvSceneSwitcher.timeTab.saturdays="По субботам" +AdvSceneSwitcher.timeTab.sundays="По воскресеньям" +AdvSceneSwitcher.timeTab.afterstart="После начала потокового вещания/записи" +AdvSceneSwitcher.timeTab.afterstart.tip="Будет использоваться время относительно начала потокового вещания/записи" +AdvSceneSwitcher.timeTab.entry="{{triggers}} в {{time}} переключаются на {{scenes}} используя {{transitions}}" +AdvSceneSwitcher.timeTab.help="Эта вкладка позволит вам автоматически переключаться на другую сцену на основе текущего местного времени.\n\nЗаметьте, что переключатель сцен будет переключаться только в точное время, которое вы указали.\nУбедитесь, что вы настроили параметры приоритета на вкладке Общие по своему вкусу, чтобы выбранная временная точка не была пропущена из-за других методов переключения, имеющих более высокий приоритет.\n\nНажмите на выделенный символ плюса, чтобы продолжить." + +; Idle Tab +AdvSceneSwitcher.idleTab.title="Бездействие" +AdvSceneSwitcher.idleTab.enable="Включить обнаружение простоя" +AdvSceneSwitcher.idleTab.idleswitch="После {{duration}} отсутствия ввода с клавиатуры или мыши переключиться на сцену {{scenes}} используя {{transitions}}" +AdvSceneSwitcher.idleTab.dontSwitchIfFocus1="Не переключаться, если" +AdvSceneSwitcher.idleTab.dontSwitchIfFocus2="находится в фокусе" + +; Scene Sequence Tab +AdvSceneSwitcher.sceneSequenceTab.title="Последовательность" +AdvSceneSwitcher.sceneSequenceTab.description="Последовательность автоматического переключения сцен может быть отменена либо паузой/остановкой переключателя сцен, либо ручным переключением на другую сцену" +AdvSceneSwitcher.sceneSequenceTab.save="Сохранить последовательность сцен в файл" +AdvSceneSwitcher.sceneSequenceTab.load="Загрузить последовательности сцен из файла" +AdvSceneSwitcher.sceneSequenceTab.saveTitle="Сохранить последовательность сцен в файл ..." +AdvSceneSwitcher.sceneSequenceTab.loadTitle="Выберите файл для чтения последовательности сцен из файла ..." +AdvSceneSwitcher.sceneSequenceTab.loadFail="Advanced Scene Switcher не удалось импортировать настройки!" +AdvSceneSwitcher.sceneSequenceTab.loadSuccess="Настройки Advanced Scene Switcher импортированы успешно!" +AdvSceneSwitcher.sceneSequenceTab.fileType="Текстовые файлы (*.txt)" +AdvSceneSwitcher.sceneSequenceTab.interruptible="прервать" +AdvSceneSwitcher.sceneSequenceTab.interruptibleHint="Другие методы переключения разрешены для прерывания этой последовательности сцен" +AdvSceneSwitcher.sceneSequenceTab.entry="Когда {{startScenes}} активен, переключить на {{scenes}} после {{delay}} используя {{transitions}} {{interruptible}}" +AdvSceneSwitcher.sceneSequenceTab.extendEdit="Расширить последовательность" +AdvSceneSwitcher.sceneSequenceTab.extendEntry="После {{delay}} переключиться на {{scenes}} используя {{transitions}}" +AdvSceneSwitcher.sceneSequenceTab.help="Эта вкладка позволит вам автоматически переключаться на другую сцену, если сцена была активна в течение заданного периода времени.\nНапример, вы можете автоматически переключаться между двумя сценами.\n\nНажмите на выделенный символ плюса, чтобы продолжить." + +; Audio Tab +AdvSceneSwitcher.audioTab.title="Аудио" +AdvSceneSwitcher.audioTab.condition.above="выше" +AdvSceneSwitcher.audioTab.condition.below="ниже" +AdvSceneSwitcher.audioTab.ignoreInactiveSource="если источник неактивен" +AdvSceneSwitcher.audioTab.entry="Когда громкость {{audioSources}} равна {{condition}} {{volumeWidget}} в течение {{duration}} секунд переключить на {{scenes}} используя {{transitions}} {{ignoreInactiveSource}}" +AdvSceneSwitcher.audioTab.multiMatchfallbackCondition="Если несколько записей совпадают ..." +AdvSceneSwitcher.audioTab.multiMatchfallback="... в течение {{duration}} секунд переключить на {{scenes}} используя {{transitions}}" +AdvSceneSwitcher.audioTab.help="Эта вкладка позволит вам переключать сцены в зависимости от громкости источников.\nНапример, вы можете автоматически переключиться на другую сцену, если громкость вашего микрофона достигнет определенного порога.\n\nНажмите на выделенный символ плюса, чтобы продолжить." + +; Video Tab +AdvSceneSwitcher.videoTab.title="Видео" +AdvSceneSwitcher.videoTab.getScreenshot="Получить снимок экрана для выбранной записи" +AdvSceneSwitcher.videoTab.getScreenshotHelp="Получить скриншот источника видео текущей выбранной записи и автоматически установить его в качестве целевого изображени" +AdvSceneSwitcher.videoTab.condition.match="точно соответствует" +AdvSceneSwitcher.videoTab.condition.match.tooltip="Точное совпадение требует, чтобы целевое и исходное изображение имели одинаковое разрешение.\nДаже каждый пиксель должен совпадать, поэтому не рекомендуется использовать форматы изображений, которые используют сжатие (например, .JPG)!" +AdvSceneSwitcher.videoTab.condition.differ="не совпадает" +AdvSceneSwitcher.videoTab.condition.hasNotChanged="не изменилось" +AdvSceneSwitcher.videoTab.condition.hasChanged="изменилось" +AdvSceneSwitcher.videoTab.ignoreInactiveSource="если источник неактивен" +AdvSceneSwitcher.videoTab.entry="Когда {{videoSources}} {{condition}} {{filePath}} {{browseButton}} для {{duration}} переключиться на {{scenes}} используя {{transitions}} {{ignoreInactiveSource}}" +AdvSceneSwitcher.videoTab.help="

Эта вкладка позволит вам переключать сцены на основе текущего видеовыхода выбранных источников.
Обязательно проверьте Pixel Match Switcher для еще лучшей реализации этой функциональности.

Нажмите на выделенный символ плюса, чтобы продолжить.

" + +; Network Tab +AdvSceneSwitcher.networkTab.title="Сеть" +AdvSceneSwitcher.networkTab.warning="Запуск сервера вне локальной сети позволит третьим лицам читать активную сцену." +AdvSceneSwitcher.networkTab.DisabledWarning="ту функциональность, к сожалению, пришлось отключить на macOS из-за несовместимости библиотек при параллельном запуске плагина obs-websocket." +AdvSceneSwitcher.networkTab.server="Запустить сервер (отправляет сообщения о переключении сцены всем подключенным клиентам)" +AdvSceneSwitcher.networkTab.server.port="Порт" +AdvSceneSwitcher.networkTab.server.lockToIPv4="Заблокировать сервер на использование только IPv4" +AdvSceneSwitcher.networkTab.server.restrictSendToAutomatedSwitches="Отправлять сообщения только для автоматических переключателей сцен" +AdvSceneSwitcher.networkTab.startFailed.message="Сервер WebSockets не удалось запустить, возможно потому, что:\n - TCP порт %1 может в настоящее время использоваться в другом месте в этой системе, возможно, другим приложением. Попробуйте установить другой TCP порт в настройках сервера WebSocket, или остановите любое приложение, которое может использовать этот порт.\n - Сообщение об ошибке: %2" +AdvSceneSwitcher.networkTab.server.status.currentStatus="Текущий статус" +AdvSceneSwitcher.networkTab.server.status.notRunning="Не запущен" +AdvSceneSwitcher.networkTab.server.status.starting="Запуск" +AdvSceneSwitcher.networkTab.server.status.running="Работает" +AdvSceneSwitcher.networkTab.server.restart="Перезапустить сервер" +AdvSceneSwitcher.networkTab.client="Запустить клиент (Получает сообщения о переключении сцен)" +AdvSceneSwitcher.networkTab.client.address="Имя хоста или IP-адрес" +AdvSceneSwitcher.networkTab.client.port="Порт" +AdvSceneSwitcher.networkTab.client.status.currentStatus="Текущий статус" +AdvSceneSwitcher.networkTab.client.status.disconnected="Отключено" +AdvSceneSwitcher.networkTab.client.status.connecting="Подключение" +AdvSceneSwitcher.networkTab.client.status.connected="Подключено" +AdvSceneSwitcher.networkTab.client.reconnect="Принудительное переподключение" + +; Scene Group Tab +AdvSceneSwitcher.sceneGroupTab.title="Группа сцен" +AdvSceneSwitcher.sceneGroupTab.list="Группы сцен" +AdvSceneSwitcher.sceneGroupTab.edit="Редактировать группы сцен" +AdvSceneSwitcher.sceneGroupTab.edit.name="Имя:" +AdvSceneSwitcher.sceneGroupTab.edit.type="Тип: {{type}}" +AdvSceneSwitcher.sceneGroupTab.type.count="Количество" +AdvSceneSwitcher.sceneGroupTab.type.time="Время" +AdvSceneSwitcher.sceneGroupTab.type.random="Случайно" +AdvSceneSwitcher.sceneGroupTab.edit.count="Переход к следующей сцене в списке после {{count}} совпадений" +AdvSceneSwitcher.sceneGroupTab.edit.time="Переход к следующей сцене в списке по истечении {{time}}" +AdvSceneSwitcher.sceneGroupTab.edit.random="Выбирать следующую сцену в списке случайным образом" +AdvSceneSwitcher.sceneGroupTab.edit.repeat="Начинать с начала, если достигнут конец списка сцен" +AdvSceneSwitcher.sceneGroupTab.edit.addScene="Добавить сцену" +AdvSceneSwitcher.sceneGroupTab.add="Добавить группу сцен" +AdvSceneSwitcher.sceneGroupTab.defaultname="Группа сцен %1" +AdvSceneSwitcher.sceneGroupTab.exists="Группа сцен или название сцены уже существует" +AdvSceneSwitcher.sceneGroupTab.help="Группы сцен могут быть выбраны в качестве цели так же, как и обычные сцены.\n\nКак следует из названия, группа сцен представляет собой набор из нескольких сцен.\nГруппа сцен будет продвигаться по списку назначенных ей сцен в зависимости от настроенных параметров, которые можно найти справа.\n\nВы можете настроить группу сцен на переход к следующей сцене в списке:\nПосле определенного количества раз, когда группа сцен выбрана в качестве цели.\nПо истечении определенного времени.\nИли случайным образом.\n\nНапример, группа сцен, содержащая сцены ...\nScene 1\nScene 2\nScene 3 \n... активирует \"Scene 1\" в первый раз, когда он выбран в качестве цели.\nВо второй раз он активируется \"Scene 2\".\nОставшееся время \"Scene 3\" будет активирован.\n\nНажмите на выделенный символ плюса ниже, чтобы добавить новую группу сцен." +AdvSceneSwitcher.sceneGroupTab.scenes.help="Выберите группу сцен, которую вы хотите изменить слева.\n\nВыберите сцену для добавления в эту группу сцен, выбрав сцену выше и нажав на символ плюса ниже.\n\nСцена может быть добавлена несколько раз в одну и ту же группу сцен." + +; Scene Trigger Tab +AdvSceneSwitcher.sceneTriggerTab.title="Триггеры сцены" +AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.none="--выбрать триггер--" +AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneActive="активен" +AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneInactive="не активен" +AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneLeave="переключился с" +AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.none="--выбрать действие--" +AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startRecording="начать запись" +AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.pauseRecording="приостановить запись" +AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unpauseRecording="отменить паузу записи" +AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopRecording="остановить запись" +AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopStreaming="остановить потоковое вещание" +AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startStreaming="начать потоковое вещание" +AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startReplayBuffer="запустить буфер воспроизведения" +AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopReplayBuffer="остановить буфер воспроизведения" +AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.muteSource="отключить источник" +AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unmuteSource="включить источник" +AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startSwitcher="запустить переключатель сцены" +AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopSwitcher="остановить переключатель сцены" +AdvSceneSwitcher.sceneTriggerTab.entry="Когда {{scenes}} {{triggers}} {{actions}} {{audioSources}} после {{duration}}" +AdvSceneSwitcher.sceneTriggerTab.help="Эта вкладка позволяет запускать действия при изменении сцены, например, остановку записи или потоковой передачи." + +; Hotkey +AdvSceneSwitcher.hotkey.startSwitcherHotkey="Запустить Advanced Scene Switcher" +AdvSceneSwitcher.hotkey.stopSwitcherHotkey="становить Advanced Scene Switcher" +AdvSceneSwitcher.hotkey.startStopToggleSwitcherHotkey="Переключить старт/стоп для Advanced Scene Switcher" + +AdvSceneSwitcher.askBackup="Обнаружена новая версия Advanced Scene Switcher.\nНужно ли создать резервную копию старых настроек?" + +AdvSceneSwitcher.close="Закрыть" +AdvSceneSwitcher.browse="Обзор" + +AdvSceneSwitcher.selectScene="--выбрать сцену--" +AdvSceneSwitcher.selectPreviousScene="Предыдущая сцена" +AdvSceneSwitcher.currentTransition="Текущий переход" +AdvSceneSwitcher.selectTransition="--выбрать переход--" +AdvSceneSwitcher.selectWindow="--выбрать окно--" +AdvSceneSwitcher.selectAudioSource="--выбрать источник звука--" +AdvSceneSwitcher.selectVideoSource="--sвыбрать источник видео--" +AdvSceneSwitcher.selectMediaSource="--выбрать источник мультимедиа--" +AdvSceneSwitcher.selectProcess="--выбрать процесс--" +AdvSceneSwitcher.enterPath="--ввести путь--" +AdvSceneSwitcher.enterText="--ввести текст--" +AdvSceneSwitcher.invaildEntriesWillNotBeSaved="недействительные записи не будут сохранены" +AdvSceneSwitcher.selectWindowTip="Используйте \"OBS\" для указания окна OBS\nИспользуйте \"Переключение задач\"для указания ALT + TAB" + +AdvSceneSwitcher.status.active="Активный" +AdvSceneSwitcher.status.inactive="Неактивен" + +AdvSceneSwitcher.unit.milliseconds="миллисекунды" +AdvSceneSwitcher.unit.secends="секунды" +AdvSceneSwitcher.unit.minutes="минуты" +AdvSceneSwitcher.unit.hours="часы" diff --git a/data/res/time.svg b/data/res/time.svg new file mode 100644 index 00000000..cafc173a --- /dev/null +++ b/data/res/time.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/forms/advanced-scene-switcher.ui b/forms/advanced-scene-switcher.ui index f21ba378..890d39e4 100644 --- a/forms/advanced-scene-switcher.ui +++ b/forms/advanced-scene-switcher.ui @@ -723,7 +723,7 @@ AdvSceneSwitcher.macroTab.edit - + @@ -752,184 +752,204 @@ - - - true + + + Qt::Vertical - - - - 0 - 0 - 767 - 293 - + + + true - - - - - - - false - - - AdvSceneSwitcher.macroTab.editConditionHelp - - - Qt::AlignCenter - - - true - - - - - - - - - - - - - - - 22 - 22 - - - - true - - - addIconSmall - - - - - - - - 22 - 22 - - - - true - - - removeIconSmall - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - + + + + 0 + 0 + 767 + 220 + + + + + QLayout::SetMinAndMaxSize + + + + + QLayout::SetMinAndMaxSize + + + + + false + + + AdvSceneSwitcher.macroTab.editConditionHelp + + + Qt::AlignCenter + + + true + + + + + + + QLayout::SetFixedSize + + + + + + + + + + + + 22 + 22 + + + + true + + + addIconSmall + + + + + + + + 22 + 22 + + + + true + + + removeIconSmall + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + - - - - - - true - - - - - 0 - 0 - 767 - 145 - + + + true - - - - - - - false - - - AdvSceneSwitcher.macroTab.editActionHelp - - - Qt::AlignCenter - - - true - - - - - - - - - - - - - - - 22 - 22 - - - - true - - - addIconSmall - - - - - - - - 22 - 22 - - - - true - - - removeIconSmall - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - + + + + 0 + 0 + 767 + 219 + + + + + QLayout::SetMinAndMaxSize + + + + + + + false + + + AdvSceneSwitcher.macroTab.editActionHelp + + + Qt::AlignCenter + + + true + + + + + + + QLayout::SetMinAndMaxSize + + + + + + + + + + + + 22 + 22 + + + + true + + + addIconSmall + + + + + + + + 22 + 22 + + + + true + + + removeIconSmall + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + @@ -3724,6 +3744,27 @@ + + + + AdvSceneSwitcher.networkTab.server.restrictSendToAutomatedSwitches + + + + + + + AdvSceneSwitcher.networkTab.server.status.currentStatus + + + + + + + AdvSceneSwitcher.networkTab.server.status.notRunning + + + @@ -3751,24 +3792,17 @@ - - + + - AdvSceneSwitcher.networkTab.server.status.currentStatus - - - - - - - AdvSceneSwitcher.networkTab.server.status.notRunning + AdvSceneSwitcher.networkTab.server.sendPreview - + - AdvSceneSwitcher.networkTab.server.restrictSendToAutomatedSwitches + AdvSceneSwitcher.networkTab.server.sendSceneChange diff --git a/src/advanced-scene-switcher.cpp b/src/advanced-scene-switcher.cpp index 96f7b640..f87dea31 100644 --- a/src/advanced-scene-switcher.cpp +++ b/src/advanced-scene-switcher.cpp @@ -1,13 +1,9 @@ #include #include -#include -#include -#include -#include +#include #include #include -#include #include "headers/advanced-scene-switcher.hpp" #include "headers/curl-helper.hpp" @@ -73,331 +69,36 @@ void AdvSceneSwitcher::loadUI() } /****************************************************************************** - * UI helpers + * Saving and loading ******************************************************************************/ -void AdvSceneSwitcher::addSelectionEntry(QComboBox *sel, - const char *description, - bool selectable, const char *tooltip) +void AskBackup(obs_data_t *obj) { - sel->addItem(description); + bool backupSettings = DisplayMessage( + obs_module_text("AdvSceneSwitcher.askBackup"), true); - if (strcmp(tooltip, "") != 0) { - sel->setItemData(0, tooltip, Qt::ToolTipRole); - } - - QStandardItemModel *model = - qobject_cast(sel->model()); - QModelIndex firstIndex = - model->index(0, sel->modelColumn(), sel->rootModelIndex()); - QStandardItem *firstItem = model->itemFromIndex(firstIndex); - if (!selectable) { - firstItem->setSelectable(false); - firstItem->setEnabled(false); - } -} - -void AdvSceneSwitcher::populateSceneSelection(QComboBox *sel, bool addPrevious, - bool addSceneGroup, - bool addSelect, - std::string selectText, - bool selectable) -{ - if (addSelect) { - if (selectText.empty()) { - addSelectionEntry( - sel, - obs_module_text("AdvSceneSwitcher.selectScene"), - selectable, - obs_module_text( - "AdvSceneSwitcher.invaildEntriesWillNotBeSaved")); - } else { - addSelectionEntry(sel, selectText.c_str(), selectable); - } - } - - BPtr scenes = obs_frontend_get_scene_names(); - char **temp = scenes; - while (*temp) { - const char *name = *temp; - sel->addItem(name); - temp++; - } - - if (addPrevious) { - sel->addItem(obs_module_text( - "AdvSceneSwitcher.selectPreviousScene")); - } - - if (addSceneGroup) { - for (auto &sg : switcher->sceneGroups) { - sel->addItem(QString::fromStdString(sg.name)); - } - } -} - -void AdvSceneSwitcher::populateTransitionSelection(QComboBox *sel, - bool addCurrent, - bool addSelect, - bool selectable) -{ - if (addSelect) { - addSelectionEntry( - sel, - obs_module_text("AdvSceneSwitcher.selectTransition"), - selectable); - } - - if (addCurrent) { - sel->addItem( - obs_module_text("AdvSceneSwitcher.currentTransition")); - } - - obs_frontend_source_list *transitions = new obs_frontend_source_list(); - obs_frontend_get_transitions(transitions); - - for (size_t i = 0; i < transitions->sources.num; i++) { - const char *name = - obs_source_get_name(transitions->sources.array[i]); - sel->addItem(name); - } - - obs_frontend_source_list_free(transitions); -} - -void AdvSceneSwitcher::populateWindowSelection(QComboBox *sel, bool addSelect) -{ - if (addSelect) { - addSelectionEntry( - sel, obs_module_text("AdvSceneSwitcher.selectWindow")); - } - - std::vector windows; - GetWindowList(windows); - sort(windows.begin(), windows.end()); - - for (std::string &window : windows) { - sel->addItem(window.c_str()); - } - -#ifdef WIN32 - sel->setItemData(0, obs_module_text("AdvSceneSwitcher.selectWindowTip"), - Qt::ToolTipRole); -#endif -} - -void AdvSceneSwitcher::populateAudioSelection(QComboBox *sel, bool addSelect) -{ - if (addSelect) { - addSelectionEntry( - sel, - obs_module_text("AdvSceneSwitcher.selectAudioSource"), - false, - obs_module_text( - "AdvSceneSwitcher.invaildEntriesWillNotBeSaved")); - } - - auto sourceEnum = [](void *data, obs_source_t *source) -> bool /* -- */ - { - std::vector *list = - reinterpret_cast *>(data); - uint32_t flags = obs_source_get_output_flags(source); - - if ((flags & OBS_SOURCE_AUDIO) != 0) { - list->push_back(obs_source_get_name(source)); - } - return true; - }; - - std::vector audioSources; - obs_enum_sources(sourceEnum, &audioSources); - sort(audioSources.begin(), audioSources.end()); - for (std::string &source : audioSources) { - sel->addItem(source.c_str()); - } -} - -void AdvSceneSwitcher::populateVideoSelection(QComboBox *sel, bool addScenes, - bool addSelect) -{ - if (addSelect) { - addSelectionEntry( - sel, - obs_module_text("AdvSceneSwitcher.selectVideoSource"), - false, - obs_module_text( - "AdvSceneSwitcher.invaildEntriesWillNotBeSaved")); - } - - auto sourceEnum = [](void *data, obs_source_t *source) -> bool /* -- */ - { - std::vector *list = - reinterpret_cast *>(data); - uint32_t flags = obs_source_get_output_flags(source); - std::string test = obs_source_get_name(source); - if ((flags & (OBS_SOURCE_VIDEO | OBS_SOURCE_ASYNC)) != 0) { - list->push_back(obs_source_get_name(source)); - } - return true; - }; - - std::vector videoSources; - obs_enum_sources(sourceEnum, &videoSources); - sort(videoSources.begin(), videoSources.end()); - for (std::string &source : videoSources) { - sel->addItem(source.c_str()); - } - - if (addScenes) { - populateSceneSelection(sel, false, false, false); - } -} - -void AdvSceneSwitcher::populateMediaSelection(QComboBox *sel, bool addSelect) -{ - if (addSelect) { - addSelectionEntry( - sel, - obs_module_text("AdvSceneSwitcher.selectMediaSource"), - false, - obs_module_text( - "AdvSceneSwitcher.invaildEntriesWillNotBeSaved")); - } - - auto sourceEnum = [](void *data, obs_source_t *source) -> bool /* -- */ - { - std::vector *list = - reinterpret_cast *>(data); - std::string sourceId = obs_source_get_id(source); - if (sourceId.compare("ffmpeg_source") == 0 || - sourceId.compare("vlc_source") == 0) { - list->push_back(obs_source_get_name(source)); - } - return true; - }; - - std::vector mediaSources; - obs_enum_sources(sourceEnum, &mediaSources); - sort(mediaSources.begin(), mediaSources.end()); - for (std::string &source : mediaSources) { - sel->addItem(source.c_str()); - } -} - -void AdvSceneSwitcher::populateProcessSelection(QComboBox *sel, bool addSelect) -{ - if (addSelect) { - addSelectionEntry( - sel, obs_module_text("AdvSceneSwitcher.selectProcess")); - } - - QStringList processes; - GetProcessList(processes); - processes.sort(); - for (QString &process : processes) - sel->addItem(process); -} - -void AdvSceneSwitcher::listAddClicked(QListWidget *list, - SwitchWidget *newWidget, - QPushButton *addButton, - QMetaObject::Connection *addHighlight) -{ - if (!list || !newWidget) { - blog(LOG_WARNING, - "listAddClicked called without valid list or widget"); + if (!backupSettings) { return; } - if (addButton && addHighlight) { - addButton->disconnect(*addHighlight); + QString directory = QFileDialog::getSaveFileName( + nullptr, + obs_module_text( + "AdvSceneSwitcher.generalTab.saveOrLoadsettings.importWindowTitle"), + QDir::currentPath(), + obs_module_text( + "AdvSceneSwitcher.generalTab.saveOrLoadsettings.textType")); + if (directory.isEmpty()) { + return; } - QListWidgetItem *item; - item = new QListWidgetItem(list); - list->addItem(item); - item->setSizeHint(newWidget->minimumSizeHint()); - list->setItemWidget(item, newWidget); - - list->scrollToItem(item); -} - -bool AdvSceneSwitcher::listMoveUp(QListWidget *list) -{ - int index = list->currentRow(); - if (index == -1 || index == 0) { - return false; + QFile file(directory); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + return; } - QWidget *row = list->itemWidget(list->currentItem()); - QListWidgetItem *itemN = list->currentItem()->clone(); - - list->insertItem(index - 1, itemN); - list->setItemWidget(itemN, row); - - list->takeItem(index + 1); - list->setCurrentRow(index - 1); - return true; + obs_data_save_json(obj, file.fileName().toUtf8().constData()); } -bool AdvSceneSwitcher::listMoveDown(QListWidget *list) -{ - int index = list->currentRow(); - if (index == -1 || index == list->count() - 1) { - return false; - } - - QWidget *row = list->itemWidget(list->currentItem()); - QListWidgetItem *itemN = list->currentItem()->clone(); - - list->insertItem(index + 2, itemN); - list->setItemWidget(itemN, row); - - list->takeItem(index); - list->setCurrentRow(index + 1); - return true; -} - -QMetaObject::Connection AdvSceneSwitcher::PulseWidget(QWidget *widget, - QColor endColor, - QColor startColor, - QString specifier) -{ - if (switcher->disableHints) { - return QMetaObject::Connection(); - } - - widget->setStyleSheet(specifier + "{ \ - border-style: outset; \ - border-width: 2px; \ - border-radius: 10px; \ - border-color: rgb(0,0,0,0) \ - }"); - - QGraphicsColorizeEffect *eEffect = new QGraphicsColorizeEffect(widget); - widget->setGraphicsEffect(eEffect); - QPropertyAnimation *paAnimation = - new QPropertyAnimation(eEffect, "color"); - paAnimation->setStartValue(startColor); - paAnimation->setEndValue(endColor); - paAnimation->setDuration(1000); - // Play backwards to return to original state on timer end - paAnimation->setDirection(QAbstractAnimation::Backward); - - auto con = QWidget::connect( - paAnimation, &QPropertyAnimation::finished, [paAnimation]() { - QTimer::singleShot(1000, [paAnimation] { - paAnimation->start(); - }); - }); - - paAnimation->start(); - - return con; -} - -/****************************************************************************** - * Saving and loading - ******************************************************************************/ static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *) { if (saving) { @@ -422,7 +123,7 @@ static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *) obj = obs_data_create(); } if (switcher->versionChanged(obj, g_GIT_SHA1)) { - AdvSceneSwitcher::AskBackup(obj); + AskBackup(obj); } switcher->loadSettings(obj); @@ -484,6 +185,7 @@ void SwitcherData::Thread() } vblog(LOG_INFO, "try to sleep for %ld", duration.count()); + setWaitScene(); cv.wait_for(lock, duration); startTime = std::chrono::high_resolution_clock::now(); @@ -510,6 +212,7 @@ void SwitcherData::Thread() vblog(LOG_INFO, "sleep for %ld before switching scene", duration.count()); + setWaitScene(); cv.wait_for(lock, duration); if (stop) { @@ -616,7 +319,7 @@ bool SwitcherData::checkForMatch(OBSWeakSource &scene, return match; } -void switchScene(sceneSwitchInfo sceneSwitch) +void switchScene(const sceneSwitchInfo &sceneSwitch) { if (!sceneSwitch.scene && switcher->verbose) { blog(LOG_INFO, "nothing to switch to"); @@ -640,7 +343,7 @@ void switchScene(sceneSwitchInfo sceneSwitch) blog(LOG_INFO, "switched scene"); } - if (switcher->networkConfig.ServerEnabled) { + if (switcher->networkConfig.ShouldSendSceneChange()) { switcher->server.sendMessage(sceneSwitch); } } @@ -648,6 +351,13 @@ void switchScene(sceneSwitchInfo sceneSwitch) obs_source_release(source); } +void switchPreviewScene(const OBSWeakSource &ws) +{ + auto source = obs_weak_source_get_source(ws); + obs_frontend_set_current_preview_scene(source); + obs_source_release(source); +} + void SwitcherData::Start() { if (!(th && th->isRunning())) { @@ -669,6 +379,13 @@ void SwitcherData::Start() } } +void ResetMacroCounters() +{ + for (auto &m : switcher->macros) { + m.ResetCount(); + } +} + void SwitcherData::Stop() { if (th && th->isRunning()) { @@ -680,12 +397,19 @@ void SwitcherData::Stop() th = nullptr; writeToStatusFile("Advanced Scene Switcher stopped"); + ResetMacroCounters(); } server.stop(); client.disconnect(); } +void SwitcherData::setWaitScene() +{ + waitScene = obs_frontend_get_current_scene(); + obs_source_release(waitScene); +} + bool SwitcherData::sceneChangedDuringWait() { obs_source_t *currentSource = obs_frontend_get_current_scene(); @@ -713,11 +437,11 @@ extern "C" void FreeSceneSwitcher() switcher = nullptr; } -void handleSceneChange(SwitcherData *s) +void handleSceneChange() { // Stop waiting if scene was changed - if (s->sceneChangedDuringWait()) { - s->cv.notify_one(); + if (switcher->sceneChangedDuringWait()) { + switcher->cv.notify_one(); } // Set previous scene @@ -725,42 +449,52 @@ void handleSceneChange(SwitcherData *s) obs_weak_source_t *ws = obs_source_get_weak_source(source); obs_source_release(source); obs_weak_source_release(ws); - if (source && s->previousSceneHelper != ws) { - s->previousScene = s->previousSceneHelper; - s->previousSceneHelper = ws; + if (source && switcher->previousSceneHelper != ws) { + switcher->previousScene = switcher->previousSceneHelper; + switcher->previousSceneHelper = ws; } - s->checkTriggers(); - s->checkDefaultSceneTransitions(); + switcher->checkTriggers(); + switcher->checkDefaultSceneTransitions(); - if (switcher->networkConfig.ServerEnabled && - switcher->networkConfig.SendAll) { + if (switcher->networkConfig.ShouldSendFrontendSceneChange()) { switcher->server.sendMessage({ws, nullptr, 0}); } } -void setLiveTime(SwitcherData *s) +void setLiveTime() { - s->liveTime = QDateTime::currentDateTime(); + switcher->liveTime = QDateTime::currentDateTime(); } -void resetLiveTime(SwitcherData *s) +void resetLiveTime() { - s->liveTime = QDateTime(); + switcher->liveTime = QDateTime(); } -void checkAutoStartRecording(SwitcherData *s) +void checkAutoStartRecording() { - if (s->autoStartEvent == AutoStartEvent::RECORDING || - s->autoStartEvent == AutoStartEvent::RECORINDG_OR_STREAMING) - s->Start(); + if (switcher->autoStartEvent == AutoStartEvent::RECORDING || + switcher->autoStartEvent == AutoStartEvent::RECORINDG_OR_STREAMING) + switcher->Start(); } -void checkAutoStartStreaming(SwitcherData *s) +void checkAutoStartStreaming() { - if (s->autoStartEvent == AutoStartEvent::STREAMING || - s->autoStartEvent == AutoStartEvent::RECORINDG_OR_STREAMING) - s->Start(); + if (switcher->autoStartEvent == AutoStartEvent::STREAMING || + switcher->autoStartEvent == AutoStartEvent::RECORINDG_OR_STREAMING) + switcher->Start(); +} + +void handlePeviewSceneChange() +{ + if (switcher->networkConfig.ShouldSendPrviewSceneChange()) { + auto source = obs_frontend_get_current_preview_scene(); + auto weak = obs_source_get_weak_source(source); + switcher->server.sendMessage({weak, nullptr, 0}, true); + obs_weak_source_release(weak); + obs_source_release(source); + } } // Note to future self: @@ -768,24 +502,31 @@ void checkAutoStartStreaming(SwitcherData *s) // frontend functions such as obs_frontend_set_current_scene() static void OBSEvent(enum obs_frontend_event event, void *switcher) { + if (!switcher) { + return; + } + switch (event) { case OBS_FRONTEND_EVENT_EXIT: FreeSceneSwitcher(); break; case OBS_FRONTEND_EVENT_SCENE_CHANGED: - handleSceneChange((SwitcherData *)switcher); + handleSceneChange(); + break; + case OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED: + handlePeviewSceneChange(); break; case OBS_FRONTEND_EVENT_RECORDING_STARTED: - setLiveTime((SwitcherData *)switcher); - checkAutoStartRecording((SwitcherData *)switcher); + setLiveTime(); + checkAutoStartRecording(); break; case OBS_FRONTEND_EVENT_STREAMING_STARTED: - setLiveTime((SwitcherData *)switcher); - checkAutoStartStreaming((SwitcherData *)switcher); + setLiveTime(); + checkAutoStartStreaming(); break; case OBS_FRONTEND_EVENT_RECORDING_STOPPED: case OBS_FRONTEND_EVENT_STREAMING_STOPPED: - resetLiveTime((SwitcherData *)switcher); + resetLiveTime(); break; default: break; diff --git a/src/duration-control.cpp b/src/duration-control.cpp index 72f28b32..3eee43eb 100644 --- a/src/duration-control.cpp +++ b/src/duration-control.cpp @@ -1,4 +1,5 @@ #include "headers/duration-control.hpp" +#include "headers/utility.hpp" #include "obs-module.h" #include @@ -99,6 +100,8 @@ DurationSelection::DurationSelection(QWidget *parent, bool showUnitSelection) SLOT(_UnitChanged(int))); QHBoxLayout *layout = new QHBoxLayout; + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(11); layout->addWidget(_duration); if (showUnitSelection) { layout->addWidget(_unitSelection); @@ -137,3 +140,131 @@ void DurationSelection::_UnitChanged(int idx) emit UnitChanged(unit); } + +void DurationConstraint::Save(obs_data_t *obj, const char *condName, + const char *secondsName, const char *unitName) +{ + obs_data_set_int(obj, condName, static_cast(_type)); + _dur.Save(obj, secondsName, unitName); +} + +void DurationConstraint::Load(obs_data_t *obj, const char *condName, + const char *secondsName, const char *unitName) +{ + // For backwards compatability check if duration value exist without + // time constraint condition - if so assume DurationCondition::MORE + if (!obs_data_has_user_value(obj, condName) && + obs_data_has_user_value(obj, secondsName)) { + obs_data_set_int(obj, condName, + static_cast(DurationCondition::MORE)); + } + + _type = static_cast(obs_data_get_int(obj, condName)); + _dur.Load(obj, secondsName, unitName); +} + +bool DurationConstraint::DurationReached() +{ + switch (_type) { + case DurationCondition::NONE: + return true; + break; + case DurationCondition::MORE: + return _dur.DurationReached(); + break; + case DurationCondition::EQUAL: + if (_dur.DurationReached() && !_timeReached) { + _timeReached = true; + return true; + } + break; + case DurationCondition::LESS: + return !_dur.DurationReached(); + break; + default: + break; + } + return false; +} + +void DurationConstraint::Reset() +{ + _timeReached = false; + _dur.Reset(); +} + +static void populateConditions(QComboBox *list) +{ + list->addItem( + obs_module_text("AdvSceneSwitcher.duration.condition.none")); + list->addItem( + obs_module_text("AdvSceneSwitcher.duration.condition.more")); + list->addItem( + obs_module_text("AdvSceneSwitcher.duration.condition.equal")); + list->addItem( + obs_module_text("AdvSceneSwitcher.duration.condition.less")); +} + +DurationConstraintEdit::DurationConstraintEdit(QWidget *parent) +{ + _condition = new QComboBox(parent); + _duration = new DurationSelection(parent); + _toggle = new QPushButton(parent); + _toggle->setMaximumSize(22, 22); + _toggle->setIcon( + QIcon(QString::fromStdString(getDataFilePath("res/time.svg")))); + populateConditions(_condition); + QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this, + SLOT(_ConditionChanged(int))); + QObject::connect(_duration, &DurationSelection::DurationChanged, this, + &DurationConstraintEdit::DurationChanged); + QObject::connect(_duration, &DurationSelection::UnitChanged, this, + &DurationConstraintEdit::UnitChanged); + QWidget::connect(_toggle, SIGNAL(clicked()), this, + SLOT(ToggleClicked())); + + QHBoxLayout *layout = new QHBoxLayout; + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(11); + layout->addWidget(_toggle); + layout->addWidget(_condition); + layout->addWidget(_duration); + setLayout(layout); + Collapse(true); +} + +void DurationConstraintEdit::SetValue(DurationConstraint &value) +{ + _duration->SetDuration(value.GetDuration()); + _condition->setCurrentIndex(static_cast(value.GetCondition())); + _duration->setVisible(value.GetCondition() != DurationCondition::NONE); +} + +void DurationConstraintEdit::SetUnit(DurationUnit u) +{ + _duration->SetUnit(u); +} + +void DurationConstraintEdit::SetDuration(const Duration &d) +{ + _duration->SetDuration(d); +} + +void DurationConstraintEdit::_ConditionChanged(int value) +{ + auto cond = static_cast(value); + Collapse(cond == DurationCondition::NONE); + emit ConditionChanged(cond); +} + +void DurationConstraintEdit::ToggleClicked() +{ + Collapse(false); +} + +void DurationConstraintEdit::Collapse(bool collapse) +{ + _toggle->setVisible(collapse); + _duration->setVisible(!collapse); + _condition->setVisible(!collapse); +} diff --git a/src/general.cpp b/src/general.cpp index 161c8519..8e90871c 100644 --- a/src/general.cpp +++ b/src/general.cpp @@ -1,9 +1,9 @@ -#include - #include "headers/advanced-scene-switcher.hpp" #include "headers/utility.hpp" #include "headers/version.h" +#include + QMetaObject::Connection inactivePluse; void AdvSceneSwitcher::on_close_clicked() @@ -156,8 +156,11 @@ void AdvSceneSwitcher::SetStopped() obs_module_text("AdvSceneSwitcher.generalTab.status.start")); ui->pluginRunningText->setText( obs_module_text("AdvSceneSwitcher.status.inactive")); - inactivePluse = PulseWidget(ui->pluginRunningText, QColor(Qt::red), - QColor(0, 0, 0, 0), "QLabel "); + if (!switcher->disableHints) { + inactivePluse = PulseWidget(ui->pluginRunningText, + QColor(Qt::red), QColor(0, 0, 0, 0), + "QLabel "); + } currentStatusActive = false; } @@ -207,34 +210,6 @@ void AdvSceneSwitcher::on_uiHintsDisable_stateChanged(int state) switcher->disableHints = state; } -void AdvSceneSwitcher::AskBackup(obs_data_t *obj) -{ - bool backupSettings = DisplayMessage( - obs_module_text("AdvSceneSwitcher.askBackup"), true); - - if (!backupSettings) { - return; - } - - QString directory = QFileDialog::getSaveFileName( - nullptr, - obs_module_text( - "AdvSceneSwitcher.generalTab.saveOrLoadsettings.importWindowTitle"), - QDir::currentPath(), - obs_module_text( - "AdvSceneSwitcher.generalTab.saveOrLoadsettings.textType")); - if (directory.isEmpty()) { - return; - } - - QFile file(directory); - if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { - return; - } - - obs_data_save_json(obj, file.fileName().toUtf8().constData()); -} - void AdvSceneSwitcher::on_exportSettings_clicked() { QString directory = QFileDialog::getSaveFileName( diff --git a/src/headers/advanced-scene-switcher.hpp b/src/headers/advanced-scene-switcher.hpp index 217acfab..43124e8e 100644 --- a/src/headers/advanced-scene-switcher.hpp +++ b/src/headers/advanced-scene-switcher.hpp @@ -5,6 +5,7 @@ #include "ui_advanced-scene-switcher.h" #endif #include "switcher-data-structs.hpp" +#include "platform-funcs.hpp" #define blog(level, msg, ...) blog(level, "[adv-ss] " msg, ##__VA_ARGS__) #define vblog(level, msg, ...) \ @@ -69,44 +70,10 @@ public: void setTabOrder(); void restoreWindowGeo(); - static void AskBackup(obs_data_t *obj); - - static void addSelectionEntry(QComboBox *sel, const char *description, - bool selectable = false, - const char *tooltip = ""); - static void populateSceneSelection(QComboBox *sel, - bool addPrevious = false, - bool addSceneGroup = false, - bool addSelect = true, - std::string selectText = "", - bool selectable = false); - - static void populateTransitionSelection(QComboBox *sel, - bool addCurrent = true, - bool addSelect = true, - bool selectable = false); - static void populateWindowSelection(QComboBox *sel, - bool addSelect = true); - static void populateAudioSelection(QComboBox *sel, - bool addSelect = true); - static void populateVideoSelection(QComboBox *sel, - bool addScenes = true, - bool addSelect = true); - static void populateMediaSelection(QComboBox *sel, - bool addSelect = true); - static void populateProcessSelection(QComboBox *sel, - bool addSelect = true); - QMetaObject::Connection PulseWidget(QWidget *widget, QColor endColor, - QColor = QColor(0, 0, 0, 0), - QString specifier = "QLabel "); - - void listAddClicked(QListWidget *list, SwitchWidget *newWidget, - QPushButton *addButton = nullptr, - QMetaObject::Connection *addHighlight = nullptr); - bool listMoveUp(QListWidget *list); - bool listMoveDown(QListWidget *list); - signals: + void MacroAdded(const QString &name); + void MacroRemoved(const QString &name); + void MacroRenamed(const QString &oldName, const QString newName); void SceneGroupAdded(const QString &name); void SceneGroupRemoved(const QString &name); void SceneGroupRenamed(const QString &oldName, const QString newName); @@ -137,6 +104,7 @@ public slots: void on_macroDown_clicked(); void on_macroName_editingFinished(); void on_macros_currentRowChanged(int idx); + void on_macros_itemChanged(QListWidgetItem *); void on_conditionAdd_clicked(); void on_conditionRemove_clicked(); void on_actionAdd_clicked(); @@ -243,7 +211,9 @@ public slots: void on_clientSettings_toggled(bool on); void on_clientHostname_textChanged(const QString &text); void on_clientPort_valueChanged(int value); + void on_sendSceneChange_stateChanged(int state); void on_restrictSend_stateChanged(int state); + void on_sendPreview_stateChanged(int state); void on_clientReconnect_clicked(); void updateClientStatus(); @@ -276,42 +246,17 @@ public slots: private: }; -/****************************************************************************** - * Windowtitle helper - ******************************************************************************/ -void GetWindowList(std::vector &windows); -void GetWindowList(QStringList &windows); -void GetCurrentWindowTitle(std::string &title); -bool isFullscreen(std::string &title); -bool isMaximized(std::string &title); -bool GetCurrentVirtualDesktop(long &desktop); -bool GetVirtualDesktopCount(long &ndesktops); - -/****************************************************************************** - * Screenregion helper - ******************************************************************************/ -std::pair getCursorPos(); - -/****************************************************************************** - * Idle detection helper - ******************************************************************************/ -int secondsSinceLastInput(); - -/****************************************************************************** - * Executable helper - ******************************************************************************/ -void GetProcessList(QStringList &processes); -bool isInFocus(const QString &executable); - /****************************************************************************** * Sceneswitch helper ******************************************************************************/ -void setNextTransition(sceneSwitchInfo &ssi, obs_source_t *currentSource, +void setNextTransition(const sceneSwitchInfo &ssi, obs_source_t *currentSource, transitionData &td); -void overwriteTransitionOverride(sceneSwitchInfo ssi, transitionData &td); -void restoreTransitionOverride(obs_source_t *scene, transitionData td); -void switchScene(sceneSwitchInfo ssi); +void overwriteTransitionOverride(const sceneSwitchInfo &ssi, + transitionData &td); +void restoreTransitionOverride(obs_source_t *scene, const transitionData &td); +void switchScene(const sceneSwitchInfo &ssi); +void switchPreviewScene(const OBSWeakSource &ws); /****************************************************************************** * Main SwitcherData diff --git a/src/headers/duration-control.hpp b/src/headers/duration-control.hpp index 32c6a172..3ff1d774 100644 --- a/src/headers/duration-control.hpp +++ b/src/headers/duration-control.hpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include "obs-data.h" @@ -53,3 +54,57 @@ private: double _unitMultiplier; }; + +enum class DurationCondition { + NONE, + MORE, + EQUAL, + LESS, +}; + +class DurationConstraint { +public: + void Save(obs_data_t *obj, const char *condName = "time_constraint", + const char *secondsName = "seconds", + const char *unitName = "displayUnit"); + void Load(obs_data_t *obj, const char *condName = "time_constraint", + const char *secondsName = "seconds", + const char *unitName = "displayUnit"); + void SetCondition(DurationCondition cond) { _type = cond; } + void SetDuration(const Duration &dur) { _dur = dur; } + void SetValue(double value) { _dur.seconds = value; } + void SetUnit(DurationUnit u) { _dur.displayUnit = u; } + DurationCondition GetCondition() { return _type; } + Duration GetDuration() { return _dur; } + bool DurationReached(); + void Reset(); + +private: + DurationCondition _type = DurationCondition::NONE; + Duration _dur; + bool _timeReached = false; +}; + +class DurationConstraintEdit : public QWidget { + Q_OBJECT +public: + DurationConstraintEdit(QWidget *parent = nullptr); + void SetValue(DurationConstraint &value); + void SetUnit(DurationUnit u); + void SetDuration(const Duration &d); + +private slots: + void _ConditionChanged(int value); + void ToggleClicked(); +signals: + void DurationChanged(double value); + void UnitChanged(DurationUnit u); + void ConditionChanged(DurationCondition value); + +private: + void Collapse(bool collapse); + + DurationSelection *_duration; + QComboBox *_condition; + QPushButton *_toggle; +}; diff --git a/src/headers/macro-action-audio.hpp b/src/headers/macro-action-audio.hpp index 7f4d60b1..315be060 100644 --- a/src/headers/macro-action-audio.hpp +++ b/src/headers/macro-action-audio.hpp @@ -15,7 +15,7 @@ public: void LogAction(); bool Save(obs_data_t *obj); bool Load(obs_data_t *obj); - int GetId() { return id; }; + std::string GetId() { return id; }; static std::shared_ptr Create() { return std::make_shared(); @@ -27,7 +27,7 @@ public: private: static bool _registered; - static const int id; + static const std::string id; }; class MacroActionAudioEdit : public QWidget { @@ -37,6 +37,7 @@ public: MacroActionAudioEdit( QWidget *parent, std::shared_ptr entryData = nullptr); + void SetWidgetVisibility(); void UpdateEntryData(); static QWidget *Create(QWidget *parent, std::shared_ptr action) diff --git a/src/headers/macro-action-edit.hpp b/src/headers/macro-action-edit.hpp index 0556cafb..ee045053 100644 --- a/src/headers/macro-action-edit.hpp +++ b/src/headers/macro-action-edit.hpp @@ -20,14 +20,16 @@ struct MacroActionInfo { class MacroActionFactory { public: MacroActionFactory() = delete; - static bool Register(int id, MacroActionInfo); - static std::shared_ptr Create(const int id); - static QWidget *CreateWidget(const int id, QWidget *parent, + static bool Register(const std::string &id, MacroActionInfo); + static std::shared_ptr Create(const std::string &id); + static QWidget *CreateWidget(const std::string &id, QWidget *parent, std::shared_ptr action); static auto GetActionTypes() { return _methods; } + static std::string GetActionName(const std::string &id); + static std::string GetIdByName(const QString &name); private: - static std::map _methods; + static std::map _methods; }; class MacroActionEdit : public QWidget { @@ -35,13 +37,13 @@ class MacroActionEdit : public QWidget { public: MacroActionEdit(QWidget *parent = nullptr, - std::shared_ptr * = nullptr, int type = 0, + std::shared_ptr * = nullptr, + const std::string &id = "scene_switch", bool startCollapsed = false); - void UpdateEntryData(int type); - void Collapse(bool collapsed); + void UpdateEntryData(const std::string &id, bool collapse); private slots: - void ActionSelectionChanged(int idx); + void ActionSelectionChanged(const QString &text); protected: QComboBox *_actionSelection; diff --git a/src/headers/macro-action-filter.hpp b/src/headers/macro-action-filter.hpp new file mode 100644 index 00000000..63d07f3e --- /dev/null +++ b/src/headers/macro-action-filter.hpp @@ -0,0 +1,61 @@ +#pragma once +#include +#include "macro-action-edit.hpp" + +enum class FilterAction { + ENABLE, + DISABLE, +}; + +class MacroActionFilter : public MacroAction { +public: + bool PerformAction(); + void LogAction(); + bool Save(obs_data_t *obj); + bool Load(obs_data_t *obj); + std::string GetId() { return id; }; + static std::shared_ptr Create() + { + return std::make_shared(); + } + + OBSWeakSource _source; + OBSWeakSource _filter; + FilterAction _action = FilterAction::ENABLE; + +private: + static bool _registered; + static const std::string id; +}; + +class MacroActionFilterEdit : public QWidget { + Q_OBJECT + +public: + MacroActionFilterEdit( + QWidget *parent, + std::shared_ptr entryData = nullptr); + void UpdateEntryData(); + static QWidget *Create(QWidget *parent, + std::shared_ptr action) + { + return new MacroActionFilterEdit( + parent, + std::dynamic_pointer_cast(action)); + } + +private slots: + void SourceChanged(const QString &text); + void FilterChanged(const QString &text); + void ActionChanged(int value); + +protected: + QComboBox *_sources; + QComboBox *_filters; + QComboBox *_actions; + std::shared_ptr _entryData; + +private: + QHBoxLayout *_mainLayout; + bool _loading = true; +}; diff --git a/src/headers/macro-action-macro.hpp b/src/headers/macro-action-macro.hpp new file mode 100644 index 00000000..75ae149b --- /dev/null +++ b/src/headers/macro-action-macro.hpp @@ -0,0 +1,60 @@ +#pragma once +#include +#include "macro-action-edit.hpp" +#include "macro-selection.hpp" + +enum class PerformMacroAction { + PAUSE, + UNPAUSE, + RESET_COUNTER, +}; + +class MacroActionMacro : public MacroRefAction { +public: + bool PerformAction(); + void LogAction(); + bool Save(obs_data_t *obj); + bool Load(obs_data_t *obj); + std::string GetId() { return id; }; + static std::shared_ptr Create() + { + return std::make_shared(); + } + + PerformMacroAction _action = PerformMacroAction::PAUSE; + +private: + static bool _registered; + static const std::string id; +}; + +class MacroActionMacroEdit : public QWidget { + Q_OBJECT + +public: + MacroActionMacroEdit( + QWidget *parent, + std::shared_ptr entryData = nullptr); + void UpdateEntryData(); + static QWidget *Create(QWidget *parent, + std::shared_ptr action) + { + return new MacroActionMacroEdit( + parent, + std::dynamic_pointer_cast(action)); + } + +private slots: + void MacroChanged(const QString &text); + void MacroRemove(const QString &name); + void ActionChanged(int value); + +protected: + MacroSelection *_macros; + QComboBox *_actions; + std::shared_ptr _entryData; + +private: + QHBoxLayout *_mainLayout; + bool _loading = true; +}; diff --git a/src/headers/macro-action-media.hpp b/src/headers/macro-action-media.hpp new file mode 100644 index 00000000..1ba797c1 --- /dev/null +++ b/src/headers/macro-action-media.hpp @@ -0,0 +1,62 @@ +#pragma once +#include +#include "macro-action-edit.hpp" + +enum class MediaAction { + PLAY, + PAUSE, + STOP, + RESTART, + NEXT, + PREVIOUS, +}; + +class MacroActionMedia : public MacroAction { +public: + bool PerformAction(); + void LogAction(); + bool Save(obs_data_t *obj); + bool Load(obs_data_t *obj); + std::string GetId() { return id; }; + static std::shared_ptr Create() + { + return std::make_shared(); + } + + OBSWeakSource _mediaSource; + MediaAction _action = MediaAction::PLAY; + +private: + static bool _registered; + static const std::string id; +}; + +class MacroActionMediaEdit : public QWidget { + Q_OBJECT + +public: + MacroActionMediaEdit( + QWidget *parent, + std::shared_ptr entryData = nullptr); + void UpdateEntryData(); + static QWidget *Create(QWidget *parent, + std::shared_ptr action) + { + return new MacroActionMediaEdit( + parent, + std::dynamic_pointer_cast(action)); + } + +private slots: + void SourceChanged(const QString &text); + void ActionChanged(int value); + +protected: + QComboBox *_mediaSources; + QComboBox *_actions; + std::shared_ptr _entryData; + +private: + QHBoxLayout *_mainLayout; + bool _loading = true; +}; diff --git a/src/headers/macro-action-plugin-state.hpp b/src/headers/macro-action-plugin-state.hpp new file mode 100644 index 00000000..fd4dcb7c --- /dev/null +++ b/src/headers/macro-action-plugin-state.hpp @@ -0,0 +1,55 @@ +#pragma once +#include +#include "macro-action-edit.hpp" + +enum class PluginStateAction { + STOP, +}; + +class MacroActionPluginState : public MacroAction { +public: + bool PerformAction(); + void LogAction(); + bool Save(obs_data_t *obj); + bool Load(obs_data_t *obj); + std::string GetId() { return id; }; + static std::shared_ptr Create() + { + return std::make_shared(); + } + + PluginStateAction _action = PluginStateAction::STOP; + +private: + static bool _registered; + static const std::string id; +}; + +class MacroActionPluginStateEdit : public QWidget { + Q_OBJECT + +public: + MacroActionPluginStateEdit( + QWidget *parent, + std::shared_ptr entryData = nullptr); + void UpdateEntryData(); + static QWidget *Create(QWidget *parent, + std::shared_ptr action) + { + return new MacroActionPluginStateEdit( + parent, + std::dynamic_pointer_cast( + action)); + } + +private slots: + void ActionChanged(int value); + +protected: + QComboBox *_actions; + std::shared_ptr _entryData; + +private: + QHBoxLayout *_mainLayout; + bool _loading = true; +}; diff --git a/src/headers/macro-action-recording.hpp b/src/headers/macro-action-recording.hpp index 05c26669..34b29e6f 100644 --- a/src/headers/macro-action-recording.hpp +++ b/src/headers/macro-action-recording.hpp @@ -16,7 +16,7 @@ public: void LogAction(); bool Save(obs_data_t *obj); bool Load(obs_data_t *obj); - int GetId() { return id; }; + std::string GetId() { return id; }; static std::shared_ptr Create() { return std::make_shared(); @@ -26,7 +26,7 @@ public: private: static bool _registered; - static const int id; + static const std::string id; }; class MacroActionRecordEdit : public QWidget { diff --git a/src/headers/macro-action-replay-buffer.hpp b/src/headers/macro-action-replay-buffer.hpp index 26dc7fc7..5d047270 100644 --- a/src/headers/macro-action-replay-buffer.hpp +++ b/src/headers/macro-action-replay-buffer.hpp @@ -15,7 +15,7 @@ public: void LogAction(); bool Save(obs_data_t *obj); bool Load(obs_data_t *obj); - int GetId() { return id; }; + std::string GetId() { return id; }; static std::shared_ptr Create() { return std::make_shared(); @@ -33,7 +33,7 @@ private: Duration _duration; static bool _registered; - static const int id; + static const std::string id; }; class MacroActionReplayBufferEdit : public QWidget { diff --git a/src/headers/macro-action-run.hpp b/src/headers/macro-action-run.hpp index cf276057..414fa532 100644 --- a/src/headers/macro-action-run.hpp +++ b/src/headers/macro-action-run.hpp @@ -10,7 +10,7 @@ public: void LogAction(); bool Save(obs_data_t *obj); bool Load(obs_data_t *obj); - int GetId() { return id; }; + std::string GetId() { return id; }; static std::shared_ptr Create() { return std::make_shared(); @@ -20,7 +20,7 @@ public: private: static bool _registered; - static const int id; + static const std::string id; }; class MacroActionRunEdit : public QWidget { diff --git a/src/headers/macro-action-switch-scene.hpp b/src/headers/macro-action-scene-switch.hpp similarity index 94% rename from src/headers/macro-action-switch-scene.hpp rename to src/headers/macro-action-scene-switch.hpp index 36b8c5ff..967e2bfc 100644 --- a/src/headers/macro-action-switch-scene.hpp +++ b/src/headers/macro-action-scene-switch.hpp @@ -9,7 +9,7 @@ public: void LogAction(); bool Save(obs_data_t *obj); bool Load(obs_data_t *obj); - int GetId() { return id; }; + std::string GetId() { return id; }; static std::shared_ptr Create() { @@ -21,7 +21,7 @@ private: const char *getType() { return "MacroActionSwitchScene"; } static bool _registered; - static const int id; + static const std::string id; }; class MacroActionSwitchSceneEdit : public SwitchWidget { diff --git a/src/headers/macro-action-scene-visibility.hpp b/src/headers/macro-action-scene-visibility.hpp new file mode 100644 index 00000000..223d5c1f --- /dev/null +++ b/src/headers/macro-action-scene-visibility.hpp @@ -0,0 +1,62 @@ +#pragma once +#include +#include "macro-action-edit.hpp" + +enum class SceneVisibilityAction { + SHOW, + HIDE, +}; + +class MacroActionSceneVisibility : public MacroAction { +public: + bool PerformAction(); + void LogAction(); + bool Save(obs_data_t *obj); + bool Load(obs_data_t *obj); + std::string GetId() { return id; }; + static std::shared_ptr Create() + { + return std::make_shared(); + } + + OBSWeakSource _scene; + OBSWeakSource _source; + SceneVisibilityAction _action = SceneVisibilityAction::SHOW; + +private: + static bool _registered; + static const std::string id; +}; + +class MacroActionSceneVisibilityEdit : public QWidget { + Q_OBJECT + +public: + MacroActionSceneVisibilityEdit( + QWidget *parent, + std::shared_ptr entryData = nullptr); + void UpdateEntryData(); + static QWidget *Create(QWidget *parent, + std::shared_ptr action) + { + return new MacroActionSceneVisibilityEdit( + parent, + std::dynamic_pointer_cast( + action)); + } + +private slots: + void SceneChanged(const QString &text); + void SourceChanged(const QString &text); + void ActionChanged(int value); + +protected: + QComboBox *_scenes; + QComboBox *_sources; + QComboBox *_actions; + std::shared_ptr _entryData; + +private: + QHBoxLayout *_mainLayout; + bool _loading = true; +}; diff --git a/src/headers/macro-action-source.hpp b/src/headers/macro-action-source.hpp new file mode 100644 index 00000000..8b0e5178 --- /dev/null +++ b/src/headers/macro-action-source.hpp @@ -0,0 +1,70 @@ +#pragma once +#include "macro-action-edit.hpp" + +#include +#include +#include +#include + +enum class SourceAction { + ENABLE, + DISABLE, + SETTINGS, +}; + +class MacroActionSource : public MacroAction { +public: + bool PerformAction(); + void LogAction(); + bool Save(obs_data_t *obj); + bool Load(obs_data_t *obj); + std::string GetId() { return id; }; + static std::shared_ptr Create() + { + return std::make_shared(); + } + + OBSWeakSource _source; + std::string _settings = ""; + SourceAction _action = SourceAction::ENABLE; + +private: + static bool _registered; + static const std::string id; +}; + +class MacroActionSourceEdit : public QWidget { + Q_OBJECT + +public: + MacroActionSourceEdit( + QWidget *parent, + std::shared_ptr entryData = nullptr); + void UpdateEntryData(); + static QWidget *Create(QWidget *parent, + std::shared_ptr action) + { + return new MacroActionSourceEdit( + parent, + std::dynamic_pointer_cast(action)); + } + +private slots: + void SourceChanged(const QString &text); + void ActionChanged(int value); + void GetSettingsClicked(); + void SettingsChanged(); + +protected: + QComboBox *_sources; + QComboBox *_actions; + QPushButton *_getSettings; + QPlainTextEdit *_settings; + QLabel *_warning; + std::shared_ptr _entryData; + +private: + void SetWidgetVisibility(bool); + QHBoxLayout *_mainLayout; + bool _loading = true; +}; diff --git a/src/headers/macro-action-streaming.hpp b/src/headers/macro-action-streaming.hpp index e0d71003..8d3fc038 100644 --- a/src/headers/macro-action-streaming.hpp +++ b/src/headers/macro-action-streaming.hpp @@ -14,7 +14,7 @@ public: void LogAction(); bool Save(obs_data_t *obj); bool Load(obs_data_t *obj); - int GetId() { return id; }; + std::string GetId() { return id; }; static std::shared_ptr Create() { return std::make_shared(); @@ -28,7 +28,7 @@ private: Duration _retryCooldown; static bool _registered; - static const int id; + static const std::string id; }; class MacroActionStreamEdit : public QWidget { diff --git a/src/headers/macro-action-wait.hpp b/src/headers/macro-action-wait.hpp index 50b555e4..e3b9756d 100644 --- a/src/headers/macro-action-wait.hpp +++ b/src/headers/macro-action-wait.hpp @@ -13,7 +13,7 @@ public: bool PerformAction(); bool Save(obs_data_t *obj); bool Load(obs_data_t *obj); - int GetId() { return id; }; + std::string GetId() { return id; }; static std::shared_ptr Create() { return std::make_shared(); @@ -24,7 +24,7 @@ public: private: static bool _registered; - static const int id; + static const std::string id; }; class MacroActionWaitEdit : public QWidget { diff --git a/src/headers/macro-condition-audio.hpp b/src/headers/macro-condition-audio.hpp index f6843ef2..4f9d5028 100644 --- a/src/headers/macro-condition-audio.hpp +++ b/src/headers/macro-condition-audio.hpp @@ -6,8 +6,6 @@ #include #include -#include "duration-control.hpp" - enum class AudioCondition { ABOVE, BELOW, @@ -19,7 +17,7 @@ public: bool CheckCondition(); bool Save(obs_data_t *obj); bool Load(obs_data_t *obj); - int GetId() { return id; }; + std::string GetId() { return id; }; static std::shared_ptr Create() { return std::make_shared(); @@ -33,13 +31,12 @@ public: OBSWeakSource _audioSource; int _volume = 0; AudioCondition _condition = AudioCondition::ABOVE; - Duration _duration; obs_volmeter_t *_volmeter = nullptr; private: float _peak = -std::numeric_limits::infinity(); static bool _registered; - static const int id; + static const std::string id; }; class MacroConditionAudioEdit : public QWidget { @@ -63,13 +60,11 @@ private slots: void SourceChanged(const QString &text); void VolumeThresholdChanged(int vol); void ConditionChanged(int cond); - void DurationChanged(double seconds); protected: QComboBox *_audioSources; QComboBox *_condition; QSpinBox *_volume; - DurationSelection *_duration; VolControl *_volMeter = nullptr; std::shared_ptr _entryData; diff --git a/src/headers/macro-condition-counter.hpp b/src/headers/macro-condition-counter.hpp new file mode 100644 index 00000000..e02bc646 --- /dev/null +++ b/src/headers/macro-condition-counter.hpp @@ -0,0 +1,70 @@ +#pragma once +#include "macro.hpp" +#include "macro-selection.hpp" +#include +#include +#include +#include + +enum class CounterCondition { + BELOW, + ABOVE, + EQUAL, +}; + +class MacroConditionCounter : public MacroRefCondition { +public: + bool CheckCondition(); + bool Save(obs_data_t *obj); + bool Load(obs_data_t *obj); + std::string GetId() { return id; }; + static std::shared_ptr Create() + { + return std::make_shared(); + } + + CounterCondition _condition = CounterCondition::BELOW; + int _count = 0; + +private: + static bool _registered; + static const std::string id; +}; + +class MacroConditionCounterEdit : public QWidget { + Q_OBJECT + +public: + MacroConditionCounterEdit( + QWidget *parent, + std::shared_ptr cond = nullptr); + void UpdateEntryData(); + static QWidget *Create(QWidget *parent, + std::shared_ptr cond) + { + return new MacroConditionCounterEdit( + parent, + std::dynamic_pointer_cast(cond)); + } + +private slots: + void MacroChanged(const QString &text); + void MacroRemove(const QString &name); + void CountChanged(int value); + void ConditionChanged(int cond); + void ResetClicked(); + void UpdateCount(); + +protected: + MacroSelection *_macros; + QComboBox *_conditions; + QSpinBox *_count; + QLabel *_currentCount; + QPushButton *_resetCount; + std::unique_ptr _timer; + std::shared_ptr _entryData; + +private: + void ResetTimer(); + bool _loading = true; +}; diff --git a/src/headers/macro-condition-edit.hpp b/src/headers/macro-condition-edit.hpp index 3e935ee4..347b6666 100644 --- a/src/headers/macro-condition-edit.hpp +++ b/src/headers/macro-condition-edit.hpp @@ -15,19 +15,23 @@ struct MacroConditionInfo { TCreateMethod _createFunc; TCreateWidgetMethod _createWidgetFunc; std::string _name; + bool _useDurationConstraint = true; }; class MacroConditionFactory { public: MacroConditionFactory() = delete; - static bool Register(int id, MacroConditionInfo); - static std::shared_ptr Create(const int id); - static QWidget *CreateWidget(const int id, QWidget *parent, + static bool Register(const std::string &, MacroConditionInfo); + static std::shared_ptr Create(const std::string &); + static QWidget *CreateWidget(const std::string &id, QWidget *parent, std::shared_ptr); static auto GetConditionTypes() { return _methods; } + static std::string GetConditionName(const std::string &); + static std::string GetIdByName(const QString &name); + static bool UsesDurationConstraint(const std::string &id); private: - static std::map _methods; + static std::map _methods; }; class MacroConditionEdit : public QWidget { @@ -36,20 +40,23 @@ class MacroConditionEdit : public QWidget { public: MacroConditionEdit(QWidget *parent = nullptr, std::shared_ptr * = nullptr, - int type = 0, bool root = true, + const std::string &id = "scene", bool root = true, bool startCollapsed = false); bool IsRootNode(); - void UpdateEntryData(int type); - void Collapse(bool collapsed); + void UpdateEntryData(const std::string &id, bool collapse); private slots: void LogicSelectionChanged(int idx); - void ConditionSelectionChanged(int idx); + void ConditionSelectionChanged(const QString &text); + void DurationChanged(double seconds); + void DurationConditionChanged(DurationCondition cond); + void DurationUnitChanged(DurationUnit unit); protected: QComboBox *_logicSelection; QComboBox *_conditionSelection; Section *_section; + DurationConstraintEdit *_dur; std::shared_ptr *_entryData; diff --git a/src/headers/macro-condition-file.hpp b/src/headers/macro-condition-file.hpp index f5fd34c4..5cbd8a39 100644 --- a/src/headers/macro-condition-file.hpp +++ b/src/headers/macro-condition-file.hpp @@ -18,7 +18,7 @@ public: bool CheckCondition(); bool Save(obs_data_t *obj); bool Load(obs_data_t *obj); - int GetId() { return id; }; + std::string GetId() { return id; }; static std::shared_ptr Create() { return std::make_shared(); @@ -39,7 +39,7 @@ private: QDateTime _lastMod; size_t _lastHash = 0; static bool _registered; - static const int id; + static const std::string id; }; class MacroConditionFileEdit : public QWidget { diff --git a/src/headers/macro-condition-idle.hpp b/src/headers/macro-condition-idle.hpp index 0f1a0e49..32a20187 100644 --- a/src/headers/macro-condition-idle.hpp +++ b/src/headers/macro-condition-idle.hpp @@ -1,15 +1,16 @@ #pragma once #include "macro.hpp" +#include "duration-control.hpp" + #include #include -#include "duration-control.hpp" class MacroConditionIdle : public MacroCondition { public: bool CheckCondition(); bool Save(obs_data_t *obj); bool Load(obs_data_t *obj); - int GetId() { return id; }; + std::string GetId() { return id; }; static std::shared_ptr Create() { return std::make_shared(); @@ -19,7 +20,7 @@ public: private: static bool _registered; - static const int id; + static const std::string id; }; class MacroConditionIdleEdit : public QWidget { diff --git a/src/headers/macro-condition-interval.hpp b/src/headers/macro-condition-interval.hpp new file mode 100644 index 00000000..7d8edfbb --- /dev/null +++ b/src/headers/macro-condition-interval.hpp @@ -0,0 +1,52 @@ +#pragma once +#include "macro.hpp" +#include +#include +#include "duration-control.hpp" + +class MacroConditionInterval : public MacroCondition { +public: + bool CheckCondition(); + bool Save(obs_data_t *obj); + bool Load(obs_data_t *obj); + std::string GetId() { return id; }; + static std::shared_ptr Create() + { + return std::make_shared(); + } + + Duration _duration; + +private: + static bool _registered; + static const std::string id; +}; + +class MacroConditionIntervalEdit : public QWidget { + Q_OBJECT + +public: + MacroConditionIntervalEdit( + QWidget *parent, + std::shared_ptr cond = nullptr); + void UpdateEntryData(); + static QWidget *Create(QWidget *parent, + std::shared_ptr cond) + { + return new MacroConditionIntervalEdit( + parent, + std::dynamic_pointer_cast( + cond)); + } + +private slots: + void DurationChanged(double seconds); + void DurationUnitChanged(DurationUnit unit); + +protected: + DurationSelection *_duration; + std::shared_ptr _entryData; + +private: + bool _loading = true; +}; diff --git a/src/headers/macro-condition-media.hpp b/src/headers/macro-condition-media.hpp index fcf516b4..5c1735f8 100644 --- a/src/headers/macro-condition-media.hpp +++ b/src/headers/macro-condition-media.hpp @@ -39,7 +39,7 @@ public: bool CheckCondition(); bool Save(obs_data_t *obj); bool Load(obs_data_t *obj); - int GetId() { return id; }; + std::string GetId() { return id; }; static std::shared_ptr Create() { return std::make_shared(); @@ -65,7 +65,7 @@ private: bool _playedToEnd = false; static bool _registered; - static const int id; + static const std::string id; }; class MacroConditionMediaEdit : public QWidget { diff --git a/src/headers/macro-condition-plugin-state.hpp b/src/headers/macro-condition-plugin-state.hpp index 1a71df82..bf131e85 100644 --- a/src/headers/macro-condition-plugin-state.hpp +++ b/src/headers/macro-condition-plugin-state.hpp @@ -12,7 +12,7 @@ public: bool CheckCondition(); bool Save(obs_data_t *obj); bool Load(obs_data_t *obj); - int GetId() { return id; }; + std::string GetId() { return id; }; static std::shared_ptr Create() { return std::make_shared(); @@ -22,7 +22,7 @@ public: private: static bool _registered; - static const int id; + static const std::string id; }; class MacroConditionPluginStateEdit : public QWidget { diff --git a/src/headers/macro-condition-process.hpp b/src/headers/macro-condition-process.hpp index a934cc68..3f4e3d58 100644 --- a/src/headers/macro-condition-process.hpp +++ b/src/headers/macro-condition-process.hpp @@ -8,7 +8,7 @@ public: bool CheckCondition(); bool Save(obs_data_t *obj); bool Load(obs_data_t *obj); - int GetId() { return id; }; + std::string GetId() { return id; }; static std::shared_ptr Create() { return std::make_shared(); @@ -19,7 +19,7 @@ public: private: static bool _registered; - static const int id; + static const std::string id; }; class MacroConditionProcessEdit : public QWidget { diff --git a/src/headers/macro-condition-recording.hpp b/src/headers/macro-condition-recording.hpp index c66acdb1..70275e56 100644 --- a/src/headers/macro-condition-recording.hpp +++ b/src/headers/macro-condition-recording.hpp @@ -2,7 +2,6 @@ #include "macro.hpp" #include #include -#include "duration-control.hpp" enum class RecordState { STOP, @@ -15,18 +14,17 @@ public: bool CheckCondition(); bool Save(obs_data_t *obj); bool Load(obs_data_t *obj); - int GetId() { return id; }; + std::string GetId() { return id; }; static std::shared_ptr Create() { return std::make_shared(); } RecordState _recordState; - Duration _duration; private: static bool _registered; - static const int id; + static const std::string id; }; class MacroConditionRecordEdit : public QWidget { @@ -47,12 +45,9 @@ public: private slots: void StateChanged(int value); - void DurationChanged(double seconds); - void DurationUnitChanged(DurationUnit unit); protected: QComboBox *_recordState; - DurationSelection *_duration; std::shared_ptr _entryData; private: diff --git a/src/headers/macro-condition-region.hpp b/src/headers/macro-condition-region.hpp index c998512d..5a5474ad 100644 --- a/src/headers/macro-condition-region.hpp +++ b/src/headers/macro-condition-region.hpp @@ -8,7 +8,7 @@ public: bool CheckCondition(); bool Save(obs_data_t *obj); bool Load(obs_data_t *obj); - int GetId() { return id; }; + std::string GetId() { return id; }; static std::shared_ptr Create() { return std::make_shared(); @@ -18,7 +18,7 @@ public: private: static bool _registered; - static const int id; + static const std::string id; }; class MacroConditionRegionEdit : public QWidget { diff --git a/src/headers/macro-condition-scene.hpp b/src/headers/macro-condition-scene.hpp index 8d4b56d4..cfd0f624 100644 --- a/src/headers/macro-condition-scene.hpp +++ b/src/headers/macro-condition-scene.hpp @@ -2,7 +2,6 @@ #include "macro.hpp" #include #include -#include "duration-control.hpp" enum class SceneType { CURRENT, @@ -14,7 +13,7 @@ public: bool CheckCondition(); bool Save(obs_data_t *obj); bool Load(obs_data_t *obj); - int GetId() { return id; }; + std::string GetId() { return id; }; static std::shared_ptr Create() { return std::make_shared(); @@ -22,11 +21,10 @@ public: OBSWeakSource _scene; SceneType _type; - Duration _duration; private: static bool _registered; - static const int id; + static const std::string id; }; class MacroConditionSceneEdit : public QWidget { @@ -48,13 +46,10 @@ public: private slots: void SceneChanged(const QString &text); void TypeChanged(int value); - void DurationChanged(double seconds); - void DurationUnitChanged(DurationUnit unit); protected: QComboBox *_sceneSelection; QComboBox *_sceneType; - DurationSelection *_duration; std::shared_ptr _entryData; private: diff --git a/src/headers/macro-condition-source.hpp b/src/headers/macro-condition-source.hpp new file mode 100644 index 00000000..60ff4107 --- /dev/null +++ b/src/headers/macro-condition-source.hpp @@ -0,0 +1,68 @@ +#pragma once +#include "macro.hpp" +#include +#include + +enum class SourceCondition { + ACTIVE, + SHOWING, + SETTINGS, +}; + +class MacroConditionSource : public MacroCondition { +public: + bool CheckCondition(); + bool Save(obs_data_t *obj); + bool Load(obs_data_t *obj); + std::string GetId() { return id; }; + static std::shared_ptr Create() + { + return std::make_shared(); + } + + OBSWeakSource _source = nullptr; + SourceCondition _condition = SourceCondition::ACTIVE; + std::string _settings = ""; + bool _regex = false; + +private: + static bool _registered; + static const std::string id; +}; + +class MacroConditionSourceEdit : public QWidget { + Q_OBJECT + +public: + MacroConditionSourceEdit( + QWidget *parent, + std::shared_ptr cond = nullptr); + void UpdateEntryData(); + static QWidget *Create(QWidget *parent, + std::shared_ptr cond) + { + return new MacroConditionSourceEdit( + parent, + std::dynamic_pointer_cast(cond)); + } + +private slots: + void SourceChanged(const QString &text); + void ConditionChanged(int cond); + void GetSettingsClicked(); + void SettingsChanged(); + void RegexChanged(int); + +protected: + QComboBox *_sources; + QComboBox *_conditions; + QPushButton *_getSettings; + QPlainTextEdit *_settings; + QCheckBox *_regex; + + std::shared_ptr _entryData; + +private: + void SetSettingsSelectionVisible(bool visible); + bool _loading = true; +}; diff --git a/src/headers/macro-condition-streaming.hpp b/src/headers/macro-condition-streaming.hpp index 4d959a03..845823f9 100644 --- a/src/headers/macro-condition-streaming.hpp +++ b/src/headers/macro-condition-streaming.hpp @@ -2,7 +2,6 @@ #include "macro.hpp" #include #include -#include "duration-control.hpp" enum class StreamState { STOP, @@ -14,18 +13,17 @@ public: bool CheckCondition(); bool Save(obs_data_t *obj); bool Load(obs_data_t *obj); - int GetId() { return id; }; + std::string GetId() { return id; }; static std::shared_ptr Create() { return std::make_shared(); } StreamState _streamState; - Duration _duration; private: static bool _registered; - static const int id; + static const std::string id; }; class MacroConditionStreamEdit : public QWidget { @@ -46,12 +44,9 @@ public: private slots: void StateChanged(int value); - void DurationChanged(double seconds); - void DurationUnitChanged(DurationUnit unit); protected: QComboBox *_streamState; - DurationSelection *_duration; std::shared_ptr _entryData; private: diff --git a/src/headers/macro-condition-video.hpp b/src/headers/macro-condition-video.hpp index 122e1b18..6fdc57bf 100644 --- a/src/headers/macro-condition-video.hpp +++ b/src/headers/macro-condition-video.hpp @@ -1,10 +1,10 @@ #pragma once #include "macro.hpp" +#include "screenshot-helper.hpp" + #include #include #include -#include "duration-control.hpp" -#include "screenshot-helper.hpp" enum class VideoCondition { MATCH, @@ -19,7 +19,7 @@ public: bool CheckCondition(); bool Save(obs_data_t *obj); bool Load(obs_data_t *obj); - int GetId() { return id; }; + std::string GetId() { return id; }; QImage GetMatchImage() { return _matchImage; }; static std::shared_ptr Create() { @@ -30,7 +30,6 @@ public: OBSWeakSource _videoSource; VideoCondition _condition = VideoCondition::MATCH; - Duration _duration; std::string _file = obs_module_text("AdvSceneSwitcher.enterPath"); private: @@ -39,7 +38,7 @@ private: std::unique_ptr _screenshotData = nullptr; QImage _matchImage; static bool _registered; - static const int id; + static const std::string id; }; class MacroConditionVideoEdit : public QWidget { @@ -66,12 +65,9 @@ private slots: void ConditionChanged(int cond); void FilePathChanged(); void BrowseButtonClicked(); - void DurationChanged(double seconds); - void DurationUnitChanged(DurationUnit unit); protected: QComboBox *_videoSelection; - DurationSelection *_duration; QComboBox *_condition; QLineEdit *_filePath; QPushButton *_browseButton; diff --git a/src/headers/macro-condition-window.hpp b/src/headers/macro-condition-window.hpp index e90921f9..022430e3 100644 --- a/src/headers/macro-condition-window.hpp +++ b/src/headers/macro-condition-window.hpp @@ -8,7 +8,7 @@ public: bool CheckCondition(); bool Save(obs_data_t *obj); bool Load(obs_data_t *obj); - int GetId() { return id; }; + std::string GetId() { return id; }; static std::shared_ptr Create() { return std::make_shared(); @@ -27,7 +27,7 @@ public: private: static bool _registered; - static const int id; + static const std::string id; }; class MacroConditionWindowEdit : public QWidget { diff --git a/src/headers/macro-selection.hpp b/src/headers/macro-selection.hpp new file mode 100644 index 00000000..3fe13b93 --- /dev/null +++ b/src/headers/macro-selection.hpp @@ -0,0 +1,17 @@ +#pragma once +#include + +class Macro; + +class MacroSelection : public QComboBox { + Q_OBJECT + +public: + MacroSelection(QWidget *parent); + void SetCurrentMacro(Macro *); + +private slots: + void MacroAdd(const QString &name); + void MacroRemove(const QString &name); + void MacroRename(const QString &oldName, const QString &newName); +}; diff --git a/src/headers/macro.hpp b/src/headers/macro.hpp index bdc586e2..ca8673df 100644 --- a/src/headers/macro.hpp +++ b/src/headers/macro.hpp @@ -1,10 +1,13 @@ #pragma once +#include "duration-control.hpp" + #include #include #include #include #include #include +#include constexpr auto macro_func = 10; constexpr auto default_priority_10 = macro_func; @@ -38,14 +41,23 @@ public: virtual bool CheckCondition() = 0; virtual bool Save(obs_data_t *obj) = 0; virtual bool Load(obs_data_t *obj) = 0; - virtual int GetId() = 0; + virtual std::string GetId() = 0; + LogicType GetLogicType() { return _logic; } void SetLogicType(LogicType logic) { _logic = logic; } - static const std::map logicTypes; + bool DurationReached() { return _duration.DurationReached(); } + void ResetDuration() { _duration.Reset(); } + DurationConstraint GetDurationConstraint() { return _duration; } + void SetDurationConstraint(const DurationConstraint &dur); + void SetDurationCondition(DurationCondition cond); + void SetDurationUnit(DurationUnit u); + void SetDuration(double seconds); + private: LogicType _logic; + DurationConstraint _duration; }; class MacroAction { @@ -53,21 +65,24 @@ public: virtual bool PerformAction() = 0; virtual bool Save(obs_data_t *obj) = 0; virtual bool Load(obs_data_t *obj) = 0; - virtual int GetId() = 0; + virtual std::string GetId() = 0; virtual void LogAction(); }; class Macro { - public: - Macro(std::string name = ""); + Macro(const std::string &name = ""); virtual ~Macro(); bool CeckMatch(); bool PerformAction(); bool Matched() { return _matched; } std::string Name() { return _name; } - void SetName(std::string name) { _name = name; } + void SetName(const std::string &name); + void SetPaused(bool pause = true) { _paused = pause; } + bool Paused() { return _paused; } + int GetCount() { return _count; }; + void ResetCount() { _count = 0; }; std::deque> &Conditions() { return _conditions; @@ -76,13 +91,56 @@ public: bool Save(obs_data_t *obj); bool Load(obs_data_t *obj); + // Some macros can refer to other macros, which are not yet loaded. + // Use this function to set these references after loading is complete. + void ResolveMacroRef(); // Helper function for plugin state condition regarding scene change bool SwitchesScene(); private: + void SetupHotkeys(); + void ClearHotkeys(); + void SetHotkeysDesc(); + std::string _name = ""; std::deque> _conditions; std::deque> _actions; bool _matched = false; + bool _paused = false; + int _count = 0; + obs_hotkey_id _pauseHotkey = OBS_INVALID_HOTKEY_ID; + obs_hotkey_id _unpauseHotkey = OBS_INVALID_HOTKEY_ID; +}; + +Macro *GetMacroByName(const char *name); +Macro *GetMacroByQString(const QString &name); + +class MacroRef { +public: + MacroRef(){}; + MacroRef(std::string name); + void UpdateRef(); + void UpdateRef(std::string name); + void UpdateRef(QString name); + void Save(obs_data_t *obj); + void Load(obs_data_t *obj); + Macro *get(); + Macro *operator->(); + +private: + std::string _name = ""; + Macro *_ref = nullptr; +}; + +class MacroRefCondition : public MacroCondition { +public: + void ResolveMacroRef(); + MacroRef _macro; +}; + +class MacroRefAction : public MacroAction { +public: + void ResolveMacroRef(); + MacroRef _macro; }; diff --git a/src/headers/platform-funcs.hpp b/src/headers/platform-funcs.hpp new file mode 100644 index 00000000..d6164901 --- /dev/null +++ b/src/headers/platform-funcs.hpp @@ -0,0 +1,11 @@ +#pragma once + +void GetWindowList(std::vector &windows); +void GetWindowList(QStringList &windows); +void GetCurrentWindowTitle(std::string &title); +bool isFullscreen(const std::string &title); +bool isMaximized(const std::string &title); +std::pair getCursorPos(); +int secondsSinceLastInput(); +void GetProcessList(QStringList &processes); +bool isInFocus(const QString &executable); diff --git a/src/headers/scene-group.hpp b/src/headers/scene-group.hpp index b29dc1ae..2e79f3e6 100644 --- a/src/headers/scene-group.hpp +++ b/src/headers/scene-group.hpp @@ -40,9 +40,9 @@ struct SceneGroup { int lastRandomScene = -1; inline SceneGroup(){}; - inline SceneGroup(std::string name_) : name(name_){}; - inline SceneGroup(std::string name_, AdvanceCondition type_, - std::vector scenes_, int count_, + inline SceneGroup(const std::string &name_) : name(name_){}; + inline SceneGroup(const std::string &name_, AdvanceCondition type_, + const std::vector &scenes_, int count_, double time_, bool repeat_) : name(name_), type(type_), diff --git a/src/headers/section.hpp b/src/headers/section.hpp index 8e3154a2..b3a1ef2b 100644 --- a/src/headers/section.hpp +++ b/src/headers/section.hpp @@ -14,18 +14,31 @@ public: explicit Section(const int animationDuration = 300, QWidget *parent = 0); - void SetContent(QWidget *w); + void SetContent(QWidget *w, bool collapsed = true); void AddHeaderWidget(QWidget *); -public slots: - void Collapse(bool collapsed); +protected: + bool eventFilter(QObject *obj, QEvent *event) override; + +private slots: + void AnimationFinished(); + void Collapse(bool collapse); private: + void SetupAnimations(); + void CleanUpPreviousContent(); + QGridLayout *_mainLayout; QHBoxLayout *_headerWidgetLayout; QToolButton *_toggleButton; QFrame *_headerLine; QParallelAnimationGroup *_toggleAnimation = nullptr; + QParallelAnimationGroup *_contentAnimation = nullptr; QScrollArea *_contentArea = nullptr; + QWidget *_content = nullptr; int _animationDuration; + std::atomic_bool _transitioning = {false}; + std::atomic_bool _collapsed = {false}; + int _headerHeight = 0; + int _contentHeight = 0; }; diff --git a/src/headers/switch-network.hpp b/src/headers/switch-network.hpp index 4e52061f..20d75552 100644 --- a/src/headers/switch-network.hpp +++ b/src/headers/switch-network.hpp @@ -33,6 +33,10 @@ public: std::string GetClientUri(); + bool ShouldSendSceneChange(); + bool ShouldSendFrontendSceneChange(); + bool ShouldSendPrviewSceneChange(); + // Server bool ServerEnabled; uint64_t ServerPort; @@ -42,7 +46,9 @@ public: bool ClientEnabled; std::string Address; uint64_t ClientPort; - bool SendAll; + bool SendSceneChange; + bool SendSceneChangeAll; + bool SendPreview; }; class WSServer : public QObject { @@ -53,7 +59,7 @@ public: virtual ~WSServer(); void start(quint16 port, bool lockToIPv4); void stop(); - void sendMessage(sceneSwitchInfo sceneSwitch); + void sendMessage(sceneSwitchInfo sceneSwitch, bool preview = false); QThreadPool *threadPool() { return &_threadPool; } private: diff --git a/src/headers/switcher-data-structs.hpp b/src/headers/switcher-data-structs.hpp index 7fa4864a..a9ac72cb 100644 --- a/src/headers/switcher-data-structs.hpp +++ b/src/headers/switcher-data-structs.hpp @@ -192,6 +192,7 @@ struct SwitcherData { void Start(); void Stop(); + void setWaitScene(); bool sceneChangedDuringWait(); bool prioFuncsValid(); @@ -200,7 +201,7 @@ struct SwitcherData { void resetTabOrder(); void writeSceneInfoToFile(); - void writeToStatusFile(QString status); + void writeToStatusFile(const QString &msg); bool checkForMatch(OBSWeakSource &scene, OBSWeakSource &transition, int &linger, bool &setPreviousSceneAsMatch, @@ -253,7 +254,7 @@ struct SwitcherData { void saveNetworkSwitches(obs_data_t *obj); void saveGeneralSettings(obs_data_t *obj); void saveHotkeys(obs_data_t *obj); - void saveVersion(obs_data_t *obj, std::string currentVersion); + void saveVersion(obs_data_t *obj, const std::string ¤tVersion); void loadSettings(obs_data_t *obj); void loadMacros(obs_data_t *obj); diff --git a/src/headers/utility.hpp b/src/headers/utility.hpp index 726f5c9c..6a54c970 100644 --- a/src/headers/utility.hpp +++ b/src/headers/utility.hpp @@ -1,118 +1,28 @@ #pragma once - #include -#include #include -#include -#include -#include - +#include +#include +#include +#include +#include #include -#include #include +#include +#include +#include "scene-group.hpp" -static inline bool WeakSourceValid(obs_weak_source_t *ws) -{ - obs_source_t *source = obs_weak_source_get_source(ws); - if (source) { - obs_source_release(source); - } - return !!source; -} - -static inline std::string GetWeakSourceName(obs_weak_source_t *weak_source) -{ - std::string name; - - obs_source_t *source = obs_weak_source_get_source(weak_source); - if (source) { - name = obs_source_get_name(source); - obs_source_release(source); - } - - return name; -} - -static inline OBSWeakSource GetWeakSourceByName(const char *name) -{ - OBSWeakSource weak; - obs_source_t *source = obs_get_source_by_name(name); - if (source) { - weak = obs_source_get_weak_source(source); - obs_weak_source_release(weak); - obs_source_release(source); - } - - return weak; -} - -static inline OBSWeakSource GetWeakSourceByQString(const QString &name) -{ - return GetWeakSourceByName(name.toUtf8().constData()); -} - -static inline OBSWeakSource GetWeakTransitionByName(const char *transitionName) -{ - OBSWeakSource weak; - obs_source_t *source = nullptr; - - if (strcmp(transitionName, "Default") == 0) { - source = obs_frontend_get_current_transition(); - weak = obs_source_get_weak_source(source); - obs_source_release(source); - obs_weak_source_release(weak); - return weak; - } - - obs_frontend_source_list *transitions = new obs_frontend_source_list(); - obs_frontend_get_transitions(transitions); - bool match = false; - - for (size_t i = 0; i < transitions->sources.num; i++) { - const char *name = - obs_source_get_name(transitions->sources.array[i]); - if (strcmp(transitionName, name) == 0) { - match = true; - source = transitions->sources.array[i]; - break; - } - } - - if (match) { - weak = obs_source_get_weak_source(source); - obs_weak_source_release(weak); - } - obs_frontend_source_list_free(transitions); - - return weak; -} - -static inline OBSWeakSource GetWeakTransitionByQString(const QString &name) -{ - return GetWeakTransitionByName(name.toUtf8().constData()); -} - -static inline std::string -getNextDelim(std::string text, - std::unordered_map placeholders) -{ - size_t pos = std::string::npos; - std::string res = ""; - - for (const auto &ph : placeholders) { - size_t newPos = text.find(ph.first); - if (newPos <= pos) { - pos = newPos; - res = ph.first; - } - } - - if (pos == std::string::npos) { - return ""; - } - - return res; -} +bool WeakSourceValid(obs_weak_source_t *ws); +std::string GetWeakSourceName(obs_weak_source_t *weak_source); +OBSWeakSource GetWeakSourceByName(const char *name); +OBSWeakSource GetWeakSourceByQString(const QString &name); +OBSWeakSource GetWeakTransitionByName(const char *transitionName); +OBSWeakSource GetWeakTransitionByQString(const QString &name); +OBSWeakSource GetWeakFilterByName(OBSWeakSource source, const char *name); +OBSWeakSource GetWeakFilterByQString(OBSWeakSource source, const QString &name); +bool compareIgnoringLineEnding(QString &s1, QString &s2); +std::string getSourceSettings(OBSWeakSource ws); +std::string getDataFilePath(const std::string &file); /** * Populate layout with labels and widgets based on provided text @@ -122,95 +32,32 @@ getNextDelim(std::string text, * @param placeholders Map containing a mapping of placeholder strings to widgets. * @param addStretch Add addStretch() to layout. */ -static inline void -placeWidgets(std::string text, QBoxLayout *layout, - std::unordered_map placeholders, - bool addStretch = true) -{ - std::vector> labelsWidgetsPairs; - - std::string delim = getNextDelim(text, placeholders); - while (delim != "") { - size_t pos = text.find(delim); - if (pos != std::string::npos) { - labelsWidgetsPairs.emplace_back(text.substr(0, pos), - placeholders[delim]); - text.erase(0, pos + delim.length()); - } - delim = getNextDelim(text, placeholders); - } - - if (text != "") { - labelsWidgetsPairs.emplace_back(text, nullptr); - } - - for (auto &lw : labelsWidgetsPairs) { - if (lw.first != "") { - layout->addWidget(new QLabel(lw.first.c_str())); - } - if (lw.second) { - layout->addWidget(lw.second); - } - } - if (addStretch) { - layout->addStretch(); - } -} - -static inline void clearLayout(QLayout *layout) -{ - QLayoutItem *item; - while ((item = layout->takeAt(0))) { - if (item->layout()) { - clearLayout(item->layout()); - delete item->layout(); - } - if (item->widget()) { - delete item->widget(); - } - delete item; - } -} - -static inline bool compareIgnoringLineEnding(QString &s1, QString &s2) -{ - // Let QT deal with different types of lineendings - QTextStream s1stream(&s1); - QTextStream s2stream(&s2); - - while (!s1stream.atEnd() || !s2stream.atEnd()) { - QString s1s = s1stream.readLine(); - QString s2s = s2stream.readLine(); - if (s1s != s2s) { - return false; - } - } - - if (!s1stream.atEnd() && !s2stream.atEnd()) { - return false; - } - - return true; -} - -static inline bool DisplayMessage(QString msg, bool question = false) -{ - if (question) { - QMessageBox::StandardButton reply; - reply = QMessageBox::question( - nullptr, "Advanced Scene Switcher", msg, - QMessageBox::Yes | QMessageBox::No); - if (reply == QMessageBox::Yes) { - return true; - } else { - return false; - } - } else { - QMessageBox Msgbox; - Msgbox.setWindowTitle("Advanced Scene Switcher"); - Msgbox.setText(msg); - Msgbox.exec(); - } - - return false; -} +void placeWidgets(std::string text, QBoxLayout *layout, + std::unordered_map placeholders, + bool addStretch = true); +void clearLayout(QLayout *layout); +QMetaObject::Connection PulseWidget(QWidget *widget, QColor endColor, + QColor = QColor(0, 0, 0, 0), + QString specifier = "QLabel "); +void listAddClicked(QListWidget *list, QWidget *newWidget, + QPushButton *addButton = nullptr, + QMetaObject::Connection *addHighlight = nullptr); +bool listMoveUp(QListWidget *list); +bool listMoveDown(QListWidget *list); +bool DisplayMessage(const QString &msg, bool question = false); +void addSelectionEntry(QComboBox *sel, const char *description, + bool selectable = false, const char *tooltip = ""); +void populateTransitionSelection(QComboBox *sel, bool addCurrent = true, + bool addSelect = true, + bool selectable = false); +void populateWindowSelection(QComboBox *sel, bool addSelect = true); +void populateAudioSelection(QComboBox *sel, bool addSelect = true); +void populateVideoSelection(QComboBox *sel, bool addSelect = true); +void populateMediaSelection(QComboBox *sel, bool addSelect = true); +void populateProcessSelection(QComboBox *sel, bool addSelect = true); +void populateSourceSelection(QComboBox *list, bool addSelect = true); +void populateSceneSelection(QComboBox *sel, bool addPrevious = false, + bool addSceneGroup = false, + std::deque *sceneGroups = nullptr, + bool addSelect = true, std::string selectText = "", + bool selectable = false); diff --git a/src/linux/advanced-scene-switcher-nix.cpp b/src/linux/advanced-scene-switcher-nix.cpp index ef78c9c9..71580a91 100644 --- a/src/linux/advanced-scene-switcher-nix.cpp +++ b/src/linux/advanced-scene-switcher-nix.cpp @@ -253,7 +253,7 @@ std::pair getCursorPos() return pos; } -bool isMaximized(std::string &title) +bool isMaximized(const std::string &title) { if (!ewmhIsSupported()) return false; @@ -302,7 +302,7 @@ bool isMaximized(std::string &title) return false; } -bool isFullscreen(std::string &title) +bool isFullscreen(const std::string &title) { if (!ewmhIsSupported()) return false; diff --git a/src/macro-action-audio.cpp b/src/macro-action-audio.cpp index a5473845..ca4bf929 100644 --- a/src/macro-action-audio.cpp +++ b/src/macro-action-audio.cpp @@ -2,7 +2,7 @@ #include "headers/advanced-scene-switcher.hpp" #include "headers/utility.hpp" -const int MacroActionAudio::id = 2; +const std::string MacroActionAudio::id = "audio"; bool MacroActionAudio::_registered = MacroActionFactory::Register( MacroActionAudio::id, @@ -94,7 +94,7 @@ MacroActionAudioEdit::MacroActionAudioEdit( _volumePercent->setSuffix("%"); populateActionSelection(_actions); - AdvSceneSwitcher::populateAudioSelection(_audioSources); + populateAudioSelection(_audioSources); QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this, SLOT(ActionChanged(int))); @@ -130,17 +130,8 @@ bool hasSourceControl(AudioAction action) return action != AudioAction::MASTER_VOLUME; } -void MacroActionAudioEdit::UpdateEntryData() +void MacroActionAudioEdit::SetWidgetVisibility() { - if (!_entryData) { - return; - } - - _audioSources->setCurrentText( - GetWeakSourceName(_entryData->_audioSource).c_str()); - _actions->setCurrentIndex(static_cast(_entryData->_action)); - _volumePercent->setValue(_entryData->_volume); - if (hasVolumeControl(_entryData->_action)) { _volumePercent->show(); } else { @@ -154,6 +145,20 @@ void MacroActionAudioEdit::UpdateEntryData() } } +void MacroActionAudioEdit::UpdateEntryData() +{ + if (!_entryData) { + return; + } + + _audioSources->setCurrentText( + GetWeakSourceName(_entryData->_audioSource).c_str()); + _actions->setCurrentIndex(static_cast(_entryData->_action)); + _volumePercent->setValue(_entryData->_volume); + + SetWidgetVisibility(); +} + void MacroActionAudioEdit::SourceChanged(const QString &text) { if (_loading || !_entryData) { @@ -172,7 +177,7 @@ void MacroActionAudioEdit::ActionChanged(int value) std::lock_guard lock(switcher->m); _entryData->_action = static_cast(value); - UpdateEntryData(); + SetWidgetVisibility(); } void MacroActionAudioEdit::VolumeChanged(int value) diff --git a/src/macro-action-edit.cpp b/src/macro-action-edit.cpp index 8cf6eb99..1624a4e2 100644 --- a/src/macro-action-edit.cpp +++ b/src/macro-action-edit.cpp @@ -1,11 +1,11 @@ #include "headers/macro-action-edit.hpp" -#include "headers/macro-action-switch-scene.hpp" +#include "headers/macro-action-scene-switch.hpp" #include "headers/utility.hpp" #include "headers/advanced-scene-switcher.hpp" -std::map MacroActionFactory::_methods; +std::map MacroActionFactory::_methods; -bool MacroActionFactory::Register(int id, MacroActionInfo info) +bool MacroActionFactory::Register(const std::string &id, MacroActionInfo info) { if (auto it = _methods.find(id); it == _methods.end()) { _methods[id] = info; @@ -14,7 +14,7 @@ bool MacroActionFactory::Register(int id, MacroActionInfo info) return false; } -std::shared_ptr MacroActionFactory::Create(const int id) +std::shared_ptr MacroActionFactory::Create(const std::string &id) { if (auto it = _methods.find(id); it != _methods.end()) return it->second._createFunc(); @@ -22,7 +22,8 @@ std::shared_ptr MacroActionFactory::Create(const int id) return nullptr; } -QWidget *MacroActionFactory::CreateWidget(const int id, QWidget *parent, +QWidget *MacroActionFactory::CreateWidget(const std::string &id, + QWidget *parent, std::shared_ptr action) { if (auto it = _methods.find(id); it != _methods.end()) @@ -31,6 +32,24 @@ QWidget *MacroActionFactory::CreateWidget(const int id, QWidget *parent, return nullptr; } +std::string MacroActionFactory::GetActionName(const std::string &id) +{ + if (auto it = _methods.find(id); it != _methods.end()) { + return it->second._name; + } + return "unknown action"; +} + +std::string MacroActionFactory::GetIdByName(const QString &name) +{ + for (auto it : _methods) { + if (name == obs_module_text(it.second._name.c_str())) { + return it.first; + } + } + return ""; +} + static inline void populateActionSelection(QComboBox *list) { for (auto entry : MacroActionFactory::GetActionTypes()) { @@ -40,14 +59,15 @@ static inline void populateActionSelection(QComboBox *list) MacroActionEdit::MacroActionEdit(QWidget *parent, std::shared_ptr *entryData, - int type, bool startCollapsed) + const std::string &id, bool startCollapsed) : QWidget(parent) { _actionSelection = new QComboBox(); _section = new Section(300); - QWidget::connect(_actionSelection, SIGNAL(currentIndexChanged(int)), - this, SLOT(ActionSelectionChanged(int))); + QWidget::connect(_actionSelection, + SIGNAL(currentTextChanged(const QString &)), this, + SLOT(ActionSelectionChanged(const QString &))); populateActionSelection(_actionSelection); @@ -58,38 +78,34 @@ MacroActionEdit::MacroActionEdit(QWidget *parent, setLayout(mainLayout); _entryData = entryData; - UpdateEntryData(type); + UpdateEntryData(id, startCollapsed); _loading = false; - _section->Collapse(startCollapsed); } -void MacroActionEdit::ActionSelectionChanged(int idx) +void MacroActionEdit::ActionSelectionChanged(const QString &text) { if (_loading || !_entryData) { return; } + std::string id = MacroActionFactory::GetIdByName(text); + std::lock_guard lock(switcher->m); _entryData->reset(); - *_entryData = MacroActionFactory::Create(idx); + *_entryData = MacroActionFactory::Create(id); auto widget = - MacroActionFactory::CreateWidget(idx, window(), *_entryData); - _section->SetContent(widget); - _section->Collapse(false); + MacroActionFactory::CreateWidget(id, window(), *_entryData); + _section->SetContent(widget, false); } -void MacroActionEdit::UpdateEntryData(int type) +void MacroActionEdit::UpdateEntryData(const std::string &id, bool collapse) { - _actionSelection->setCurrentIndex(type); + _actionSelection->setCurrentText( + obs_module_text(MacroActionFactory::GetActionName(id).c_str())); auto widget = - MacroActionFactory::CreateWidget(type, window(), *_entryData); - _section->SetContent(widget); -} - -void MacroActionEdit::Collapse(bool collapsed) -{ - _section->Collapse(collapsed); + MacroActionFactory::CreateWidget(id, window(), *_entryData); + _section->SetContent(widget, collapse); } void AdvSceneSwitcher::on_actionAdd_clicked() @@ -98,12 +114,16 @@ void AdvSceneSwitcher::on_actionAdd_clicked() if (!macro) { return; } + + MacroActionSwitchScene temp; + std::string id = temp.GetId(); + std::lock_guard lock(switcher->m); - macro->Actions().emplace_back(MacroActionFactory::Create(0)); - auto newEntry = new MacroActionEdit(this, ¯o->Actions().back(), 0); + macro->Actions().emplace_back(MacroActionFactory::Create(id)); + auto newEntry = + new MacroActionEdit(this, ¯o->Actions().back(), id, false); ui->macroEditActionLayout->addWidget(newEntry); ui->macroEditActionHelp->setVisible(false); - newEntry->Collapse(false); } void AdvSceneSwitcher::on_actionRemove_clicked() diff --git a/src/macro-action-filter.cpp b/src/macro-action-filter.cpp new file mode 100644 index 00000000..81e4f95c --- /dev/null +++ b/src/macro-action-filter.cpp @@ -0,0 +1,203 @@ +#include "headers/macro-action-filter.hpp" +#include "headers/advanced-scene-switcher.hpp" +#include "headers/utility.hpp" + +const std::string MacroActionFilter::id = "filter"; + +bool MacroActionFilter::_registered = MacroActionFactory::Register( + MacroActionFilter::id, + {MacroActionFilter::Create, MacroActionFilterEdit::Create, + "AdvSceneSwitcher.action.filter"}); + +const static std::map actionTypes = { + {FilterAction::ENABLE, "AdvSceneSwitcher.action.filter.type.enable"}, + {FilterAction::DISABLE, "AdvSceneSwitcher.action.filter.type.disable"}, +}; + +bool MacroActionFilter::PerformAction() +{ + auto s = obs_weak_source_get_source(_filter); + switch (_action) { + case FilterAction::ENABLE: + obs_source_set_enabled(s, true); + break; + case FilterAction::DISABLE: + obs_source_set_enabled(s, false); + break; + default: + break; + } + obs_source_release(s); + return true; +} + +void MacroActionFilter::LogAction() +{ + auto it = actionTypes.find(_action); + if (it != actionTypes.end()) { + vblog(LOG_INFO, + "performed action \"%s\" for filter \"%s\" on source \"%s\"", + it->second.c_str(), GetWeakSourceName(_filter).c_str(), + GetWeakSourceName(_source).c_str()); + } else { + blog(LOG_WARNING, "ignored unknown filter action %d", + static_cast(_action)); + } +} + +bool MacroActionFilter::Save(obs_data_t *obj) +{ + MacroAction::Save(obj); + obs_data_set_string(obj, "source", GetWeakSourceName(_source).c_str()); + obs_data_set_string(obj, "filter", GetWeakSourceName(_filter).c_str()); + obs_data_set_int(obj, "action", static_cast(_action)); + return true; +} + +bool MacroActionFilter::Load(obs_data_t *obj) +{ + MacroAction::Load(obj); + const char *sourceName = obs_data_get_string(obj, "source"); + _source = GetWeakSourceByName(sourceName); + const char *filterName = obs_data_get_string(obj, "filter"); + _filter = GetWeakFilterByQString(_source, filterName); + _action = static_cast(obs_data_get_int(obj, "action")); + return true; +} + +static inline void populateActionSelection(QComboBox *list) +{ + for (auto entry : actionTypes) { + list->addItem(obs_module_text(entry.second.c_str())); + } +} + +static inline void populateFilters(QComboBox *list, + OBSWeakSource weakSource = nullptr) +{ + auto enumFilters = [](obs_source_t *, obs_source_t *filter, void *ptr) { + QComboBox *list = reinterpret_cast(ptr); + auto name = obs_source_get_name(filter); + list->addItem(name); + }; + + auto s = obs_weak_source_get_source(weakSource); + obs_source_enum_filters(s, enumFilters, list); + list->model()->sort(0); + addSelectionEntry(list, + obs_module_text("AdvSceneSwitcher.selectFilter")); + obs_source_release(s); + list->setCurrentIndex(0); +} + +static inline void hasFilterEnum(obs_source_t *, obs_source_t *filter, + void *ptr) +{ + if (!filter) { + return; + } + bool *hasFilter = reinterpret_cast(ptr); + *hasFilter = true; +} + +static inline void populateSourcesWithFilter(QComboBox *list) +{ + auto enumSourcesWithFilters = [](void *param, obs_source_t *source) { + if (!source) { + return true; + } + QComboBox *list = reinterpret_cast(param); + bool hasFilter = false; + obs_source_enum_filters(source, hasFilterEnum, &hasFilter); + if (hasFilter) { + list->addItem(obs_source_get_name(source)); + } + return true; + }; + obs_enum_sources(enumSourcesWithFilters, list); + obs_enum_scenes(enumSourcesWithFilters, list); + list->model()->sort(0); + addSelectionEntry(list, + obs_module_text("AdvSceneSwitcher.selectSource")); + list->setCurrentIndex(0); +} + +MacroActionFilterEdit::MacroActionFilterEdit( + QWidget *parent, std::shared_ptr entryData) + : QWidget(parent) +{ + _sources = new QComboBox(); + _filters = new QComboBox(); + _actions = new QComboBox(); + + populateActionSelection(_actions); + populateSourcesWithFilter(_sources); + + QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this, + SLOT(ActionChanged(int))); + QWidget::connect(_sources, SIGNAL(currentTextChanged(const QString &)), + this, SLOT(SourceChanged(const QString &))); + QWidget::connect(_filters, SIGNAL(currentTextChanged(const QString &)), + this, SLOT(FilterChanged(const QString &))); + + QHBoxLayout *mainLayout = new QHBoxLayout; + std::unordered_map widgetPlaceholders = { + {"{{sources}}", _sources}, + {"{{filters}}", _filters}, + {"{{actions}}", _actions}, + }; + placeWidgets(obs_module_text("AdvSceneSwitcher.action.filter.entry"), + mainLayout, widgetPlaceholders); + setLayout(mainLayout); + + _entryData = entryData; + UpdateEntryData(); + _loading = false; +} + +void MacroActionFilterEdit::UpdateEntryData() +{ + if (!_entryData) { + return; + } + + _actions->setCurrentIndex(static_cast(_entryData->_action)); + _sources->setCurrentText( + GetWeakSourceName(_entryData->_source).c_str()); + populateFilters(_filters, _entryData->_source); + _filters->setCurrentText( + GetWeakSourceName(_entryData->_filter).c_str()); +} + +void MacroActionFilterEdit::SourceChanged(const QString &text) +{ + if (_loading || !_entryData) { + return; + } + { + std::lock_guard lock(switcher->m); + _entryData->_source = GetWeakSourceByQString(text); + } + _filters->clear(); + populateFilters(_filters, _entryData->_source); +} + +void MacroActionFilterEdit::FilterChanged(const QString &text) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_filter = GetWeakFilterByQString(_entryData->_source, text); +} + +void MacroActionFilterEdit::ActionChanged(int value) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_action = static_cast(value); +} diff --git a/src/macro-action-macro.cpp b/src/macro-action-macro.cpp new file mode 100644 index 00000000..a5bea1b2 --- /dev/null +++ b/src/macro-action-macro.cpp @@ -0,0 +1,152 @@ +#include "headers/macro-action-macro.hpp" +#include "headers/advanced-scene-switcher.hpp" +#include "headers/utility.hpp" + +const std::string MacroActionMacro::id = "macro"; + +bool MacroActionMacro::_registered = MacroActionFactory::Register( + MacroActionMacro::id, + {MacroActionMacro::Create, MacroActionMacroEdit::Create, + "AdvSceneSwitcher.action.macro"}); + +const static std::map actionTypes = { + {PerformMacroAction::PAUSE, "AdvSceneSwitcher.action.macro.type.pause"}, + {PerformMacroAction::UNPAUSE, + "AdvSceneSwitcher.action.macro.type.unpause"}, + {PerformMacroAction::RESET_COUNTER, + "AdvSceneSwitcher.action.macro.type.resetCounter"}, +}; + +bool MacroActionMacro::PerformAction() +{ + if (!_macro.get()) { + return true; + } + + switch (_action) { + case PerformMacroAction::PAUSE: + _macro->SetPaused(); + break; + case PerformMacroAction::UNPAUSE: + _macro->SetPaused(false); + break; + case PerformMacroAction::RESET_COUNTER: + _macro->ResetCount(); + break; + default: + break; + } + return true; +} + +void MacroActionMacro::LogAction() +{ + if (!_macro.get()) { + return; + } + switch (_action) { + case PerformMacroAction::PAUSE: + vblog(LOG_INFO, "paused \"%s\"", _macro->Name().c_str()); + break; + case PerformMacroAction::UNPAUSE: + vblog(LOG_INFO, "unpaused \"%s\"", _macro->Name().c_str()); + break; + case PerformMacroAction::RESET_COUNTER: + vblog(LOG_INFO, "reset counter for \"%s\"", + _macro->Name().c_str()); + break; + default: + break; + } +} + +bool MacroActionMacro::Save(obs_data_t *obj) +{ + MacroAction::Save(obj); + _macro.Save(obj); + obs_data_set_int(obj, "action", static_cast(_action)); + return true; +} + +bool MacroActionMacro::Load(obs_data_t *obj) +{ + MacroAction::Load(obj); + _macro.Load(obj); + _action = static_cast( + obs_data_get_int(obj, "action")); + return true; +} + +static inline void populateActionSelection(QComboBox *list) +{ + for (auto entry : actionTypes) { + list->addItem(obs_module_text(entry.second.c_str())); + } +} + +MacroActionMacroEdit::MacroActionMacroEdit( + QWidget *parent, std::shared_ptr entryData) + : QWidget(parent) +{ + _macros = new MacroSelection(parent); + _actions = new QComboBox(); + + populateActionSelection(_actions); + + QWidget::connect(_macros, SIGNAL(currentTextChanged(const QString &)), + this, SLOT(MacroChanged(const QString &))); + QWidget::connect(parent, SIGNAL(MacroRemoved(const QString &)), this, + SLOT(MacroRemove(const QString &))); + QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this, + SLOT(ActionChanged(int))); + + QHBoxLayout *mainLayout = new QHBoxLayout; + std::unordered_map widgetPlaceholders = { + {"{{actions}}", _actions}, + {"{{macros}}", _macros}, + }; + placeWidgets(obs_module_text("AdvSceneSwitcher.action.macro.entry"), + mainLayout, widgetPlaceholders); + setLayout(mainLayout); + + _entryData = entryData; + UpdateEntryData(); + _loading = false; +} + +void MacroActionMacroEdit::UpdateEntryData() +{ + if (!_entryData) { + return; + } + _actions->setCurrentIndex(static_cast(_entryData->_action)); + _macros->SetCurrentMacro(_entryData->_macro.get()); +} + +void MacroActionMacroEdit::MacroChanged(const QString &text) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_macro.UpdateRef(text); +} + +void MacroActionMacroEdit::ActionChanged(int value) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_action = static_cast(value); +} + +void MacroActionMacroEdit::MacroRemove(const QString &name) +{ + UNUSED_PARAMETER(name); + if (_entryData) { + _entryData->_macro.UpdateRef(); + } +} diff --git a/src/macro-action-media.cpp b/src/macro-action-media.cpp new file mode 100644 index 00000000..8e0e890d --- /dev/null +++ b/src/macro-action-media.cpp @@ -0,0 +1,153 @@ +#include "headers/macro-action-media.hpp" +#include "headers/advanced-scene-switcher.hpp" +#include "headers/utility.hpp" + +const std::string MacroActionMedia::id = "media"; + +bool MacroActionMedia::_registered = MacroActionFactory::Register( + MacroActionMedia::id, + {MacroActionMedia::Create, MacroActionMediaEdit::Create, + "AdvSceneSwitcher.action.media"}); + +const static std::map actionTypes = { + {MediaAction::PLAY, "AdvSceneSwitcher.action.media.type.play"}, + {MediaAction::PAUSE, "AdvSceneSwitcher.action.media.type.pause"}, + {MediaAction::STOP, "AdvSceneSwitcher.action.media.type.stop"}, + {MediaAction::RESTART, "AdvSceneSwitcher.action.media.type.restart"}, + {MediaAction::NEXT, "AdvSceneSwitcher.action.media.type.next"}, + {MediaAction::PREVIOUS, "AdvSceneSwitcher.action.media.type.previous"}, +}; + +bool MacroActionMedia::PerformAction() +{ + auto source = obs_weak_source_get_source(_mediaSource); + obs_media_state state = obs_source_media_get_state(source); + switch (_action) { + case MediaAction::PLAY: + if (state == OBS_MEDIA_STATE_STOPPED || + state == OBS_MEDIA_STATE_ENDED) { + obs_source_media_restart(source); + } else { + obs_source_media_play_pause(source, false); + } + break; + case MediaAction::PAUSE: + obs_source_media_play_pause(source, true); + break; + case MediaAction::STOP: + obs_source_media_stop(source); + break; + case MediaAction::RESTART: + obs_source_media_restart(source); + break; + case MediaAction::NEXT: + obs_source_media_next(source); + break; + case MediaAction::PREVIOUS: + obs_source_media_previous(source); + break; + default: + break; + } + obs_source_release(source); + return true; +} + +void MacroActionMedia::LogAction() +{ + auto it = actionTypes.find(_action); + if (it != actionTypes.end()) { + vblog(LOG_INFO, "performed action \"%s\" for source \"%s\"", + it->second.c_str(), + GetWeakSourceName(_mediaSource).c_str()); + } else { + blog(LOG_WARNING, "ignored unknown media action %d", + static_cast(_action)); + } +} + +bool MacroActionMedia::Save(obs_data_t *obj) +{ + MacroAction::Save(obj); + obs_data_set_string(obj, "mediaSource", + GetWeakSourceName(_mediaSource).c_str()); + obs_data_set_int(obj, "action", static_cast(_action)); + return true; +} + +bool MacroActionMedia::Load(obs_data_t *obj) +{ + MacroAction::Load(obj); + const char *MediaSourceName = obs_data_get_string(obj, "mediaSource"); + _mediaSource = GetWeakSourceByName(MediaSourceName); + _action = static_cast(obs_data_get_int(obj, "action")); + return true; +} + +static inline void populateActionSelection(QComboBox *list) +{ + for (auto entry : actionTypes) { + list->addItem(obs_module_text(entry.second.c_str())); + } +} + +MacroActionMediaEdit::MacroActionMediaEdit( + QWidget *parent, std::shared_ptr entryData) + : QWidget(parent) +{ + _mediaSources = new QComboBox(); + _actions = new QComboBox(); + + populateActionSelection(_actions); + populateMediaSelection(_mediaSources); + + QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this, + SLOT(ActionChanged(int))); + QWidget::connect(_mediaSources, + SIGNAL(currentTextChanged(const QString &)), this, + SLOT(SourceChanged(const QString &))); + + QHBoxLayout *mainLayout = new QHBoxLayout; + std::unordered_map widgetPlaceholders = { + {"{{mediaSources}}", _mediaSources}, + {"{{actions}}", _actions}, + }; + placeWidgets(obs_module_text("AdvSceneSwitcher.action.media.entry"), + mainLayout, widgetPlaceholders); + setLayout(mainLayout); + + _entryData = entryData; + UpdateEntryData(); + _loading = false; +} + +void MacroActionMediaEdit::UpdateEntryData() +{ + if (!_entryData) { + return; + } + + _mediaSources->setCurrentText( + GetWeakSourceName(_entryData->_mediaSource).c_str()); + _actions->setCurrentIndex(static_cast(_entryData->_action)); +} + +void MacroActionMediaEdit::SourceChanged(const QString &text) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_mediaSource = GetWeakSourceByQString(text); +} + +void MacroActionMediaEdit::ActionChanged(int value) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_action = static_cast(value); +} diff --git a/src/macro-action-plugin-state.cpp b/src/macro-action-plugin-state.cpp new file mode 100644 index 00000000..4f368862 --- /dev/null +++ b/src/macro-action-plugin-state.cpp @@ -0,0 +1,114 @@ +#include "headers/macro-action-plugin-state.hpp" +#include "headers/advanced-scene-switcher.hpp" +#include "headers/utility.hpp" + +#include + +const std::string MacroActionPluginState::id = "plugin_state"; + +bool MacroActionPluginState::_registered = MacroActionFactory::Register( + MacroActionPluginState::id, + {MacroActionPluginState::Create, MacroActionPluginStateEdit::Create, + "AdvSceneSwitcher.action.PluginState"}); + +const static std::map actionTypes = { + {PluginStateAction::STOP, + "AdvSceneSwitcher.action.pluginState.type.stop"}, +}; + +void stopPlugin() +{ + std::thread t([]() { switcher->Stop(); }); + t.detach(); +} + +bool MacroActionPluginState::PerformAction() +{ + switch (_action) { + case PluginStateAction::STOP: + stopPlugin(); + break; + default: + break; + } + return true; +} + +void MacroActionPluginState::LogAction() +{ + auto it = actionTypes.find(_action); + switch (_action) { + case PluginStateAction::STOP: + blog(LOG_INFO, "stop() called by macro", it->second.c_str()); + break; + default: + blog(LOG_WARNING, "ignored unknown pluginState action %d", + static_cast(_action)); + break; + } +} + +bool MacroActionPluginState::Save(obs_data_t *obj) +{ + MacroAction::Save(obj); + obs_data_set_int(obj, "action", static_cast(_action)); + return true; +} + +bool MacroActionPluginState::Load(obs_data_t *obj) +{ + MacroAction::Load(obj); + _action = + static_cast(obs_data_get_int(obj, "action")); + return true; +} + +static inline void populateActionSelection(QComboBox *list) +{ + for (auto entry : actionTypes) { + list->addItem(obs_module_text(entry.second.c_str())); + } +} + +MacroActionPluginStateEdit::MacroActionPluginStateEdit( + QWidget *parent, std::shared_ptr entryData) + : QWidget(parent) +{ + _actions = new QComboBox(); + + populateActionSelection(_actions); + + QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this, + SLOT(ActionChanged(int))); + + QHBoxLayout *mainLayout = new QHBoxLayout; + std::unordered_map widgetPlaceholders = { + {"{{actions}}", _actions}, + }; + placeWidgets( + obs_module_text("AdvSceneSwitcher.action.pluginState.entry"), + mainLayout, widgetPlaceholders); + setLayout(mainLayout); + + _entryData = entryData; + UpdateEntryData(); + _loading = false; +} + +void MacroActionPluginStateEdit::UpdateEntryData() +{ + if (!_entryData) { + return; + } + _actions->setCurrentIndex(static_cast(_entryData->_action)); +} + +void MacroActionPluginStateEdit::ActionChanged(int value) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_action = static_cast(value); +} diff --git a/src/macro-action-recording.cpp b/src/macro-action-recording.cpp index e479b899..527b98b7 100644 --- a/src/macro-action-recording.cpp +++ b/src/macro-action-recording.cpp @@ -2,7 +2,7 @@ #include "headers/advanced-scene-switcher.hpp" #include "headers/utility.hpp" -const int MacroActionRecord::id = 4; +const std::string MacroActionRecord::id = "recording"; bool MacroActionRecord::_registered = MacroActionFactory::Register( MacroActionRecord::id, diff --git a/src/macro-action-replay-buffer.cpp b/src/macro-action-replay-buffer.cpp index 0785bbed..d0351766 100644 --- a/src/macro-action-replay-buffer.cpp +++ b/src/macro-action-replay-buffer.cpp @@ -2,7 +2,7 @@ #include "headers/advanced-scene-switcher.hpp" #include "headers/utility.hpp" -const int MacroActionReplayBuffer::id = 5; +const std::string MacroActionReplayBuffer::id = "replay_buffer"; bool MacroActionReplayBuffer::_registered = MacroActionFactory::Register( MacroActionReplayBuffer::id, diff --git a/src/macro-action-run.cpp b/src/macro-action-run.cpp index 9923e34e..ef01f0ac 100644 --- a/src/macro-action-run.cpp +++ b/src/macro-action-run.cpp @@ -5,7 +5,7 @@ #include #include -const int MacroActionRun::id = 6; +const std::string MacroActionRun::id = "run"; bool MacroActionRun::_registered = MacroActionFactory::Register( MacroActionRun::id, {MacroActionRun::Create, MacroActionRunEdit::Create, diff --git a/src/macro-action-switch-scene.cpp b/src/macro-action-scene-switch.cpp similarity index 94% rename from src/macro-action-switch-scene.cpp rename to src/macro-action-scene-switch.cpp index 2c34bb79..8ab79ff7 100644 --- a/src/macro-action-switch-scene.cpp +++ b/src/macro-action-scene-switch.cpp @@ -1,8 +1,8 @@ -#include "headers/macro-action-switch-scene.hpp" +#include "headers/macro-action-scene-switch.hpp" #include "headers/advanced-scene-switcher.hpp" #include "headers/utility.hpp" -const int MacroActionSwitchScene::id = 0; +const std::string MacroActionSwitchScene::id = "scene_switch"; bool MacroActionSwitchScene::_registered = MacroActionFactory::Register( MacroActionSwitchScene::id, diff --git a/src/macro-action-scene-visibility.cpp b/src/macro-action-scene-visibility.cpp new file mode 100644 index 00000000..ab3d7ff1 --- /dev/null +++ b/src/macro-action-scene-visibility.cpp @@ -0,0 +1,212 @@ +#include "headers/macro-action-scene-visibility.hpp" +#include "headers/advanced-scene-switcher.hpp" +#include "headers/utility.hpp" + +const std::string MacroActionSceneVisibility::id = "scene_visibility"; + +bool MacroActionSceneVisibility::_registered = MacroActionFactory::Register( + MacroActionSceneVisibility::id, + {MacroActionSceneVisibility::Create, + MacroActionSceneVisibilityEdit::Create, + "AdvSceneSwitcher.action.sceneVisibility"}); + +const static std::map actionTypes = { + {SceneVisibilityAction::SHOW, + "AdvSceneSwitcher.action.sceneVisibility.type.show"}, + {SceneVisibilityAction::HIDE, + "AdvSceneSwitcher.action.sceneVisibility.type.hide"}, +}; + +struct VisInfo { + std::string name; + bool visible; +}; + +static bool visibilityEnum(obs_scene_t *, obs_sceneitem_t *item, void *ptr) +{ + VisInfo *vInfo = reinterpret_cast(ptr); + auto sourceName = obs_source_get_name(obs_sceneitem_get_source(item)); + if (vInfo->name == sourceName) { + obs_sceneitem_set_visible(item, vInfo->visible); + } + + if (obs_sceneitem_is_group(item)) { + obs_scene_t *scene = obs_sceneitem_group_get_scene(item); + obs_scene_enum_items(scene, visibilityEnum, ptr); + } + + return true; +} + +bool MacroActionSceneVisibility::PerformAction() +{ + auto s = obs_weak_source_get_source(_scene); + auto scene = obs_scene_from_source(s); + auto sourceName = GetWeakSourceName(_source); + VisInfo vInfo = {sourceName, _action == SceneVisibilityAction::SHOW}; + + switch (_action) { + case SceneVisibilityAction::SHOW: + obs_scene_enum_items(scene, visibilityEnum, &vInfo); + break; + case SceneVisibilityAction::HIDE: + obs_scene_enum_items(scene, visibilityEnum, &vInfo); + break; + default: + break; + } + obs_source_release(s); + return true; +} + +void MacroActionSceneVisibility::LogAction() +{ + auto it = actionTypes.find(_action); + if (it != actionTypes.end()) { + vblog(LOG_INFO, + "performed action \"%s\" for source \"%s\" on scene \"%s\"", + it->second.c_str(), GetWeakSourceName(_scene).c_str(), + GetWeakSourceName(_scene).c_str()); + } else { + blog(LOG_WARNING, "ignored unknown SceneVisibility action %d", + static_cast(_action)); + } +} + +bool MacroActionSceneVisibility::Save(obs_data_t *obj) +{ + MacroAction::Save(obj); + obs_data_set_string(obj, "scene", GetWeakSourceName(_scene).c_str()); + obs_data_set_string(obj, "source", GetWeakSourceName(_source).c_str()); + obs_data_set_int(obj, "action", static_cast(_action)); + return true; +} + +bool MacroActionSceneVisibility::Load(obs_data_t *obj) +{ + MacroAction::Load(obj); + const char *sceneName = obs_data_get_string(obj, "scene"); + _scene = GetWeakSourceByName(sceneName); + const char *sourceName = obs_data_get_string(obj, "source"); + _source = GetWeakSourceByName(sourceName); + _action = static_cast( + obs_data_get_int(obj, "action")); + return true; +} + +static inline void populateActionSelection(QComboBox *list) +{ + for (auto entry : actionTypes) { + list->addItem(obs_module_text(entry.second.c_str())); + } +} + +static bool enumItem(obs_scene_t *, obs_sceneitem_t *item, void *ptr) +{ + std::set *names = reinterpret_cast *>(ptr); + + if (obs_sceneitem_is_group(item)) { + obs_scene_t *scene = obs_sceneitem_group_get_scene(item); + obs_scene_enum_items(scene, enumItem, ptr); + } + auto name = obs_source_get_name(obs_sceneitem_get_source(item)); + names->emplace(name); + return true; +} + +static inline void populateSceneItems(QComboBox *list, + OBSWeakSource sceneWeakSource = nullptr) +{ + std::set names; + auto s = obs_weak_source_get_source(sceneWeakSource); + auto scene = obs_scene_from_source(s); + obs_scene_enum_items(scene, enumItem, &names); + obs_source_release(s); + + for (auto &name : names) { + list->addItem(name); + } + list->model()->sort(0); + addSelectionEntry(list, obs_module_text("AdvSceneSwitcher.selectItem")); + list->setCurrentIndex(0); +} + +MacroActionSceneVisibilityEdit::MacroActionSceneVisibilityEdit( + QWidget *parent, std::shared_ptr entryData) + : QWidget(parent) +{ + _scenes = new QComboBox(); + _sources = new QComboBox(); + _actions = new QComboBox(); + + populateActionSelection(_actions); + populateSceneSelection(_scenes); + + QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this, + SLOT(ActionChanged(int))); + QWidget::connect(_scenes, SIGNAL(currentTextChanged(const QString &)), + this, SLOT(SceneChanged(const QString &))); + QWidget::connect(_sources, SIGNAL(currentTextChanged(const QString &)), + this, SLOT(SourceChanged(const QString &))); + + QHBoxLayout *mainLayout = new QHBoxLayout; + std::unordered_map widgetPlaceholders = { + {"{{scenes}}", _scenes}, + {"{{sources}}", _sources}, + {"{{actions}}", _actions}, + }; + placeWidgets(obs_module_text( + "AdvSceneSwitcher.action.SceneVisibility.entry"), + mainLayout, widgetPlaceholders); + setLayout(mainLayout); + + _entryData = entryData; + UpdateEntryData(); + _loading = false; +} + +void MacroActionSceneVisibilityEdit::UpdateEntryData() +{ + if (!_entryData) { + return; + } + + _actions->setCurrentIndex(static_cast(_entryData->_action)); + _scenes->setCurrentText(GetWeakSourceName(_entryData->_scene).c_str()); + populateSceneItems(_sources, _entryData->_scene); + _sources->setCurrentText( + GetWeakSourceName(_entryData->_source).c_str()); +} + +void MacroActionSceneVisibilityEdit::SceneChanged(const QString &text) +{ + if (_loading || !_entryData) { + return; + } + { + std::lock_guard lock(switcher->m); + _entryData->_scene = GetWeakSourceByQString(text); + } + _sources->clear(); + populateSceneItems(_sources, _entryData->_scene); +} + +void MacroActionSceneVisibilityEdit::SourceChanged(const QString &text) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_source = GetWeakSourceByQString(text); +} + +void MacroActionSceneVisibilityEdit::ActionChanged(int value) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_action = static_cast(value); +} diff --git a/src/macro-action-source.cpp b/src/macro-action-source.cpp new file mode 100644 index 00000000..a3fd8936 --- /dev/null +++ b/src/macro-action-source.cpp @@ -0,0 +1,201 @@ +#include "headers/macro-action-source.hpp" +#include "headers/advanced-scene-switcher.hpp" +#include "headers/utility.hpp" + +const std::string MacroActionSource::id = "source"; + +bool MacroActionSource::_registered = MacroActionFactory::Register( + MacroActionSource::id, + {MacroActionSource::Create, MacroActionSourceEdit::Create, + "AdvSceneSwitcher.action.source"}); + +const static std::map actionTypes = { + {SourceAction::ENABLE, "AdvSceneSwitcher.action.source.type.enable"}, + {SourceAction::DISABLE, "AdvSceneSwitcher.action.source.type.disable"}, + {SourceAction::SETTINGS, + "AdvSceneSwitcher.action.source.type.settings"}, +}; + +void setSourceSettings(obs_source_t *s, const std::string &settings) +{ + if (settings.empty()) { + return; + } + + obs_data_t *data = obs_data_create_from_json(settings.c_str()); + if (!data) { + blog(LOG_WARNING, "invalid source settings provided: \n%s", + settings.c_str()); + return; + } + obs_source_update(s, data); + obs_data_release(data); +} + +bool MacroActionSource::PerformAction() +{ + auto s = obs_weak_source_get_source(_source); + switch (_action) { + case SourceAction::ENABLE: + obs_source_set_enabled(s, true); + break; + case SourceAction::DISABLE: + obs_source_set_enabled(s, false); + break; + case SourceAction::SETTINGS: + setSourceSettings(s, _settings); + break; + default: + break; + } + obs_source_release(s); + return true; +} + +void MacroActionSource::LogAction() +{ + auto it = actionTypes.find(_action); + if (it != actionTypes.end()) { + vblog(LOG_INFO, "performed action \"%s\" for Source \"%s\"", + it->second.c_str(), GetWeakSourceName(_source).c_str()); + } else { + blog(LOG_WARNING, "ignored unknown source action %d", + static_cast(_action)); + } +} + +bool MacroActionSource::Save(obs_data_t *obj) +{ + MacroAction::Save(obj); + obs_data_set_string(obj, "source", GetWeakSourceName(_source).c_str()); + obs_data_set_int(obj, "action", static_cast(_action)); + obs_data_set_string(obj, "settings", _settings.c_str()); + return true; +} + +bool MacroActionSource::Load(obs_data_t *obj) +{ + MacroAction::Load(obj); + const char *sourceName = obs_data_get_string(obj, "source"); + _source = GetWeakSourceByName(sourceName); + _action = static_cast(obs_data_get_int(obj, "action")); + _settings = obs_data_get_string(obj, "settings"); + return true; +} + +static inline void populateActionSelection(QComboBox *list) +{ + for (auto entry : actionTypes) { + list->addItem(obs_module_text(entry.second.c_str())); + } +} + +MacroActionSourceEdit::MacroActionSourceEdit( + QWidget *parent, std::shared_ptr entryData) + : QWidget(parent) +{ + _sources = new QComboBox(); + _actions = new QComboBox(); + _getSettings = new QPushButton( + obs_module_text("AdvSceneSwitcher.action.source.getSettings")); + _settings = new QPlainTextEdit(); + _warning = new QLabel( + obs_module_text("AdvSceneSwitcher.action.source.warning")); + + populateActionSelection(_actions); + populateSourceSelection(_sources); + + QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this, + SLOT(ActionChanged(int))); + QWidget::connect(_sources, SIGNAL(currentTextChanged(const QString &)), + this, SLOT(SourceChanged(const QString &))); + QWidget::connect(_getSettings, SIGNAL(clicked()), this, + SLOT(GetSettingsClicked())); + QWidget::connect(_settings, SIGNAL(textChanged()), this, + SLOT(SettingsChanged())); + + QVBoxLayout *mainLayout = new QVBoxLayout; + QHBoxLayout *entryLayout = new QHBoxLayout; + QHBoxLayout *buttonLayout = new QHBoxLayout; + std::unordered_map widgetPlaceholders = { + {"{{sources}}", _sources}, + {"{{actions}}", _actions}, + {"{{settings}}", _settings}, + {"{{getSettings}}", _getSettings}, + }; + placeWidgets(obs_module_text("AdvSceneSwitcher.action.source.entry"), + entryLayout, widgetPlaceholders); + mainLayout->addLayout(entryLayout); + mainLayout->addWidget(_warning); + mainLayout->addWidget(_settings); + buttonLayout->addWidget(_getSettings); + buttonLayout->addStretch(); + mainLayout->addLayout(buttonLayout); + setLayout(mainLayout); + + _entryData = entryData; + UpdateEntryData(); + _loading = false; +} + +void MacroActionSourceEdit::UpdateEntryData() +{ + if (!_entryData) { + return; + } + + _actions->setCurrentIndex(static_cast(_entryData->_action)); + _sources->setCurrentText( + GetWeakSourceName(_entryData->_source).c_str()); + _settings->setPlainText(QString::fromStdString(_entryData->_settings)); + SetWidgetVisibility(_entryData->_action == SourceAction::SETTINGS); +} + +void MacroActionSourceEdit::SourceChanged(const QString &text) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_source = GetWeakSourceByQString(text); +} + +void MacroActionSourceEdit::ActionChanged(int value) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_action = static_cast(value); + SetWidgetVisibility(_entryData->_action == SourceAction::SETTINGS); +} + +void MacroActionSourceEdit::GetSettingsClicked() +{ + if (_loading || !_entryData || !_entryData->_source) { + return; + } + + _settings->setPlainText( + QString::fromStdString(getSourceSettings(_entryData->_source))); +} + +void MacroActionSourceEdit::SettingsChanged() +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_settings = _settings->toPlainText().toStdString(); +} + +void MacroActionSourceEdit::SetWidgetVisibility(bool showSettings) +{ + _settings->setVisible(showSettings); + _getSettings->setVisible(showSettings); + _warning->setVisible(!showSettings); + adjustSize(); +} diff --git a/src/macro-action-streaming.cpp b/src/macro-action-streaming.cpp index 56dc0c97..b9abda30 100644 --- a/src/macro-action-streaming.cpp +++ b/src/macro-action-streaming.cpp @@ -2,7 +2,7 @@ #include "headers/advanced-scene-switcher.hpp" #include "headers/utility.hpp" -const int MacroActionStream::id = 3; +const std::string MacroActionStream::id = "streaming"; bool MacroActionStream::_registered = MacroActionFactory::Register( MacroActionStream::id, diff --git a/src/macro-action-wait.cpp b/src/macro-action-wait.cpp index f30914d2..024960c4 100644 --- a/src/macro-action-wait.cpp +++ b/src/macro-action-wait.cpp @@ -4,7 +4,7 @@ #include -const int MacroActionWait::id = 1; +const std::string MacroActionWait::id = "wait"; bool MacroActionWait::_registered = MacroActionFactory::Register( MacroActionWait::id, diff --git a/src/macro-condition-audio.cpp b/src/macro-condition-audio.cpp index c25f7b96..7fe0ce9f 100644 --- a/src/macro-condition-audio.cpp +++ b/src/macro-condition-audio.cpp @@ -3,7 +3,7 @@ #include "headers/utility.hpp" #include "headers/advanced-scene-switcher.hpp" -const int MacroConditionAudio::id = 3; +const std::string MacroConditionAudio::id = "audio"; bool MacroConditionAudio::_registered = MacroConditionFactory::Register( MacroConditionAudio::id, @@ -11,8 +11,8 @@ bool MacroConditionAudio::_registered = MacroConditionFactory::Register( "AdvSceneSwitcher.condition.audio"}); static std::map audioConditionTypes = { - {AudioCondition::ABOVE, "AdvSceneSwitcher.ondition.audio.state.above"}, - {AudioCondition::BELOW, "AdvSceneSwitcher.ondition.audio.state.below"}, + {AudioCondition::ABOVE, "AdvSceneSwitcher.condition.audio.state.above"}, + {AudioCondition::BELOW, "AdvSceneSwitcher.condition.audio.state.below"}, }; MacroConditionAudio::~MacroConditionAudio() @@ -35,11 +35,7 @@ bool MacroConditionAudio::CheckCondition() // Reset for next check _peak = -std::numeric_limits::infinity(); - if (!volumeThresholdreached) { - _duration.Reset(); - return false; - } - return _duration.DurationReached(); + return volumeThresholdreached; } bool MacroConditionAudio::Save(obs_data_t *obj) @@ -49,7 +45,6 @@ bool MacroConditionAudio::Save(obs_data_t *obj) GetWeakSourceName(_audioSource).c_str()); obs_data_set_int(obj, "volume", _volume); obs_data_set_int(obj, "condition", static_cast(_condition)); - _duration.Save(obj); return true; } @@ -75,12 +70,9 @@ bool MacroConditionAudio::Load(obs_data_t *obj) MacroCondition::Load(obj); const char *audioSourceName = obs_data_get_string(obj, "audioSource"); _audioSource = GetWeakSourceByName(audioSourceName); - _volume = obs_data_get_int(obj, "volume"); _condition = static_cast(obs_data_get_int(obj, "condition")); - _duration.Load(obj); - _volmeter = AddVolmeterToSource(this, _audioSource); return true; } @@ -123,7 +115,6 @@ MacroConditionAudioEdit::MacroConditionAudioEdit( _audioSources = new QComboBox(); _condition = new QComboBox(); _volume = new QSpinBox(); - _duration = new DurationSelection(parent, false); _volume->setSuffix("%"); _volume->setMaximum(100); @@ -133,13 +124,11 @@ MacroConditionAudioEdit::MacroConditionAudioEdit( SLOT(VolumeThresholdChanged(int))); QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this, SLOT(ConditionChanged(int))); - QWidget::connect(_duration, SIGNAL(DurationChanged(double)), this, - SLOT(DurationChanged(double))); QWidget::connect(_audioSources, SIGNAL(currentTextChanged(const QString &)), this, SLOT(SourceChanged(const QString &))); - AdvSceneSwitcher::populateAudioSelection(_audioSources); + populateAudioSelection(_audioSources); populateConditionSelection(_condition); QHBoxLayout *switchLayout = new QHBoxLayout; @@ -147,7 +136,7 @@ MacroConditionAudioEdit::MacroConditionAudioEdit( {"{{audioSources}}", _audioSources}, {"{{volume}}", _volume}, {"{{condition}}", _condition}, - {"{{duration}}", _duration}}; + }; placeWidgets(obs_module_text("AdvSceneSwitcher.condition.audio.entry"), switchLayout, widgetPlaceholders); @@ -214,16 +203,6 @@ void MacroConditionAudioEdit::ConditionChanged(int cond) _entryData->_condition = static_cast(cond); } -void MacroConditionAudioEdit::DurationChanged(double seconds) -{ - if (_loading || !_entryData) { - return; - } - - std::lock_guard lock(switcher->m); - _entryData->_duration.seconds = seconds; -} - void MacroConditionAudioEdit::UpdateEntryData() { if (!_entryData) { @@ -234,6 +213,5 @@ void MacroConditionAudioEdit::UpdateEntryData() GetWeakSourceName(_entryData->_audioSource).c_str()); _volume->setValue(_entryData->_volume); _condition->setCurrentIndex(static_cast(_entryData->_condition)); - _duration->SetDuration(_entryData->_duration); UpdateVolmeterSource(); } diff --git a/src/macro-condition-counter.cpp b/src/macro-condition-counter.cpp new file mode 100644 index 00000000..b8d18f13 --- /dev/null +++ b/src/macro-condition-counter.cpp @@ -0,0 +1,194 @@ +#include "headers/macro-condition-edit.hpp" +#include "headers/macro-condition-counter.hpp" +#include "headers/utility.hpp" +#include "headers/advanced-scene-switcher.hpp" + +const std::string MacroConditionCounter::id = "counter"; + +bool MacroConditionCounter::_registered = MacroConditionFactory::Register( + MacroConditionCounter::id, + {MacroConditionCounter::Create, MacroConditionCounterEdit::Create, + "AdvSceneSwitcher.condition.counter"}); + +static std::map counterConditionTypes = { + {CounterCondition::BELOW, + "AdvSceneSwitcher.condition.counter.type.below"}, + {CounterCondition::ABOVE, + "AdvSceneSwitcher.condition.counter.type.above"}, + {CounterCondition::EQUAL, + "AdvSceneSwitcher.condition.counter.type.equal"}, +}; + +bool MacroConditionCounter::CheckCondition() +{ + if (!_macro.get()) { + return false; + } + + switch (_condition) { + case CounterCondition::BELOW: + return _macro->GetCount() < _count; + case CounterCondition::ABOVE: + return _macro->GetCount() > _count; + case CounterCondition::EQUAL: + return _macro->GetCount() == _count; + default: + break; + } + + return false; +} + +bool MacroConditionCounter::Save(obs_data_t *obj) +{ + MacroCondition::Save(obj); + _macro.Save(obj); + obs_data_set_int(obj, "condition", static_cast(_condition)); + obs_data_set_int(obj, "count", _count); + return true; +} + +bool MacroConditionCounter::Load(obs_data_t *obj) +{ + MacroCondition::Load(obj); + _macro.Load(obj); + _condition = static_cast( + obs_data_get_int(obj, "condition")); + _count = obs_data_get_int(obj, "count"); + return true; +} + +static inline void populateConditionSelection(QComboBox *list) +{ + for (auto entry : counterConditionTypes) { + list->addItem(obs_module_text(entry.second.c_str())); + } +} + +MacroConditionCounterEdit::MacroConditionCounterEdit( + QWidget *parent, std::shared_ptr entryData) + : QWidget(parent) +{ + _macros = new MacroSelection(parent); + _conditions = new QComboBox(); + _count = new QSpinBox(); + _currentCount = new QLabel(); + _resetCount = new QPushButton( + obs_module_text("AdvSceneSwitcher.condition.counter.reset")); + + _count->setMaximum(10000000); + populateConditionSelection(_conditions); + + QWidget::connect(_macros, SIGNAL(currentTextChanged(const QString &)), + this, SLOT(MacroChanged(const QString &))); + QWidget::connect(parent, SIGNAL(MacroRemoved(const QString &)), this, + SLOT(MacroRemove(const QString &))); + QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this, + SLOT(ConditionChanged(int))); + QWidget::connect(_count, SIGNAL(valueChanged(int)), this, + SLOT(CountChanged(int))); + QWidget::connect(_resetCount, SIGNAL(clicked()), this, + SLOT(ResetClicked())); + + QVBoxLayout *mainLayout = new QVBoxLayout; + QHBoxLayout *line1Layout = new QHBoxLayout; + QHBoxLayout *line2Layout = new QHBoxLayout; + std::unordered_map widgetPlaceholders = { + {"{{macros}}", _macros}, + {"{{conditions}}", _conditions}, + {"{{count}}", _count}, + {"{{currentCount}}", _currentCount}, + {"{{resetCount}}", _resetCount}, + }; + placeWidgets(obs_module_text( + "AdvSceneSwitcher.condition.counter.entry.line1"), + line1Layout, widgetPlaceholders); + placeWidgets(obs_module_text( + "AdvSceneSwitcher.condition.counter.entry.line2"), + line2Layout, widgetPlaceholders); + mainLayout->addLayout(line1Layout); + mainLayout->addLayout(line2Layout); + setLayout(mainLayout); + + _entryData = entryData; + UpdateEntryData(); + _loading = false; +} + +void MacroConditionCounterEdit::UpdateEntryData() +{ + if (!_entryData) { + return; + } + + _macros->SetCurrentMacro(_entryData->_macro.get()); + _conditions->setCurrentIndex(static_cast(_entryData->_condition)); + _count->setValue(_entryData->_count); + ResetTimer(); +} + +void MacroConditionCounterEdit::MacroChanged(const QString &text) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_macro.UpdateRef(text); + ResetTimer(); +} + +void MacroConditionCounterEdit::CountChanged(int value) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_count = value; +} + +void MacroConditionCounterEdit::ConditionChanged(int cond) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_condition = static_cast(cond); +} + +void MacroConditionCounterEdit::MacroRemove(const QString &name) +{ + UNUSED_PARAMETER(name); + if (_entryData) { + _entryData->_macro.UpdateRef(); + } +} + +void MacroConditionCounterEdit::ResetClicked() +{ + if (_loading || !_entryData || !_entryData->_macro.get()) { + return; + } + + _entryData->_macro->ResetCount(); + ResetTimer(); +} + +void MacroConditionCounterEdit::UpdateCount() +{ + if (_entryData && _entryData->_macro.get()) { + _currentCount->setText( + QString::number(_entryData->_macro->GetCount())); + } else { + _currentCount->setText("-"); + } +} + +void MacroConditionCounterEdit::ResetTimer() +{ + _timer.reset(new QTimer(this)); + connect(_timer.get(), SIGNAL(timeout()), this, SLOT(UpdateCount())); + _timer->start(1000); +} diff --git a/src/macro-condition-edit.cpp b/src/macro-condition-edit.cpp index 31b839b5..b47e6b44 100644 --- a/src/macro-condition-edit.cpp +++ b/src/macro-condition-edit.cpp @@ -1,8 +1,10 @@ #include "headers/macro-condition-edit.hpp" +#include "headers/macro-condition-scene.hpp" -std::map MacroConditionFactory::_methods; +std::map MacroConditionFactory::_methods; -bool MacroConditionFactory::Register(int id, MacroConditionInfo info) +bool MacroConditionFactory::Register(const std::string &id, + MacroConditionInfo info) { if (auto it = _methods.find(id); it == _methods.end()) { _methods[id] = info; @@ -11,24 +13,51 @@ bool MacroConditionFactory::Register(int id, MacroConditionInfo info) return false; } -std::shared_ptr MacroConditionFactory::Create(const int id) +std::shared_ptr +MacroConditionFactory::Create(const std::string &id) { - if (auto it = _methods.find(id); it != _methods.end()) + if (auto it = _methods.find(id); it != _methods.end()) { return it->second._createFunc(); - + } return nullptr; } QWidget * -MacroConditionFactory::CreateWidget(const int id, QWidget *parent, +MacroConditionFactory::CreateWidget(const std::string &id, QWidget *parent, std::shared_ptr cond) { - if (auto it = _methods.find(id); it != _methods.end()) + if (auto it = _methods.find(id); it != _methods.end()) { return it->second._createWidgetFunc(parent, cond); - + } return nullptr; } +std::string MacroConditionFactory::GetConditionName(const std::string &id) +{ + if (auto it = _methods.find(id); it != _methods.end()) { + return it->second._name; + } + return "unknown condition"; +} + +std::string MacroConditionFactory::GetIdByName(const QString &name) +{ + for (auto it : _methods) { + if (name == obs_module_text(it.second._name.c_str())) { + return it.first; + } + } + return ""; +} + +bool MacroConditionFactory::UsesDurationConstraint(const std::string &id) +{ + if (auto it = _methods.find(id); it != _methods.end()) { + return it->second._useDurationConstraint; + } + return false; +} + static inline void populateLogicSelection(QComboBox *list, bool root = false) { if (root) { @@ -57,24 +86,34 @@ static inline void populateConditionSelection(QComboBox *list) } MacroConditionEdit::MacroConditionEdit( - QWidget *parent, std::shared_ptr *entryData, int type, - bool root, bool startCollapsed) + QWidget *parent, std::shared_ptr *entryData, + const std::string &id, bool root, bool startCollapsed) : QWidget(parent) { _logicSelection = new QComboBox(); _conditionSelection = new QComboBox(); _section = new Section(300); + _dur = new DurationConstraintEdit(); QWidget::connect(_logicSelection, SIGNAL(currentIndexChanged(int)), this, SLOT(LogicSelectionChanged(int))); - QWidget::connect(_conditionSelection, SIGNAL(currentIndexChanged(int)), - this, SLOT(ConditionSelectionChanged(int))); + QWidget::connect(_conditionSelection, + SIGNAL(currentTextChanged(const QString &)), this, + SLOT(ConditionSelectionChanged(const QString &))); + QWidget::connect(_dur, SIGNAL(DurationChanged(double)), this, + SLOT(DurationChanged(double))); + QWidget::connect(_dur, SIGNAL(UnitChanged(DurationUnit)), this, + SLOT(DurationUnitChanged(DurationUnit))); + QWidget::connect(_dur, SIGNAL(ConditionChanged(DurationCondition)), + this, + SLOT(DurationConditionChanged(DurationCondition))); populateLogicSelection(_logicSelection, root); populateConditionSelection(_conditionSelection); _section->AddHeaderWidget(_logicSelection); _section->AddHeaderWidget(_conditionSelection); + _section->AddHeaderWidget(_dur); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(_section); @@ -82,9 +121,8 @@ MacroConditionEdit::MacroConditionEdit( _entryData = entryData; _isRoot = root; - UpdateEntryData(type); + UpdateEntryData(id, startCollapsed); _loading = false; - _section->Collapse(startCollapsed); } void MacroConditionEdit::LogicSelectionChanged(int idx) @@ -109,11 +147,12 @@ bool MacroConditionEdit::IsRootNode() return _isRoot; } -void MacroConditionEdit::UpdateEntryData(int type) +void MacroConditionEdit::UpdateEntryData(const std::string &id, bool collapse) { - _conditionSelection->setCurrentIndex(type); - auto widget = MacroConditionFactory::CreateWidget(type, window(), - *_entryData); + _conditionSelection->setCurrentText(obs_module_text( + MacroConditionFactory::GetConditionName(id).c_str())); + auto widget = + MacroConditionFactory::CreateWidget(id, window(), *_entryData); auto logic = (*_entryData)->GetLogicType(); if (IsRootNode()) { _logicSelection->setCurrentIndex(static_cast(logic)); @@ -121,29 +160,63 @@ void MacroConditionEdit::UpdateEntryData(int type) _logicSelection->setCurrentIndex(static_cast(logic) - logic_root_offset); } - _section->SetContent(widget); + _section->SetContent(widget, collapse); + + _dur->setVisible(MacroConditionFactory::UsesDurationConstraint(id)); + auto constraint = (*_entryData)->GetDurationConstraint(); + _dur->SetValue(constraint); } -void MacroConditionEdit::Collapse(bool collapsed) +void MacroConditionEdit::ConditionSelectionChanged(const QString &text) { - _section->Collapse(collapsed); + if (_loading || !_entryData) { + return; + } + + std::string id = MacroConditionFactory::GetIdByName(text); + + auto temp = DurationConstraint(); + _dur->SetValue(temp); + + std::lock_guard lock(switcher->m); + auto logic = (*_entryData)->GetLogicType(); + _entryData->reset(); + *_entryData = MacroConditionFactory::Create(id); + (*_entryData)->SetLogicType(logic); + auto widget = + MacroConditionFactory::CreateWidget(id, window(), *_entryData); + _section->SetContent(widget, false); + _dur->setVisible(MacroConditionFactory::UsesDurationConstraint(id)); } -void MacroConditionEdit::ConditionSelectionChanged(int idx) +void MacroConditionEdit::DurationChanged(double seconds) { if (_loading || !_entryData) { return; } std::lock_guard lock(switcher->m); - auto logic = (*_entryData)->GetLogicType(); - _entryData->reset(); - *_entryData = MacroConditionFactory::Create(idx); - (*_entryData)->SetLogicType(logic); - auto widget = - MacroConditionFactory::CreateWidget(idx, window(), *_entryData); - _section->SetContent(widget); - _section->Collapse(false); + (*_entryData)->SetDuration(seconds); +} + +void MacroConditionEdit::DurationConditionChanged(DurationCondition cond) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + (*_entryData)->SetDurationCondition(cond); +} + +void MacroConditionEdit::DurationUnitChanged(DurationUnit unit) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + (*_entryData)->SetDurationUnit(unit); } void AdvSceneSwitcher::on_conditionAdd_clicked() @@ -152,17 +225,18 @@ void AdvSceneSwitcher::on_conditionAdd_clicked() if (!macro) { return; } + MacroConditionScene temp; + std::string id = temp.GetId(); std::lock_guard lock(switcher->m); bool root = macro->Conditions().size() == 0; - macro->Conditions().emplace_back(MacroConditionFactory::Create(0)); + macro->Conditions().emplace_back(MacroConditionFactory::Create(id)); auto logic = root ? LogicType::ROOT_NONE : LogicType::NONE; macro->Conditions().back()->SetLogicType(logic); auto newEntry = new MacroConditionEdit( - this, ¯o->Conditions().back(), 0, root); + this, ¯o->Conditions().back(), id, root, false); ui->macroEditConditionLayout->addWidget(newEntry); ui->macroEditConditionHelp->setVisible(false); - newEntry->Collapse(false); } void AdvSceneSwitcher::on_conditionRemove_clicked() diff --git a/src/macro-condition-file.cpp b/src/macro-condition-file.cpp index 234c4a5d..16a5af4f 100644 --- a/src/macro-condition-file.cpp +++ b/src/macro-condition-file.cpp @@ -7,7 +7,7 @@ #include #include -const int MacroConditionFile::id = 4; +const std::string MacroConditionFile::id = "file"; bool MacroConditionFile::_registered = MacroConditionFactory::Register( MacroConditionFile::id, diff --git a/src/macro-condition-idle.cpp b/src/macro-condition-idle.cpp index 533d62ea..79fdad57 100644 --- a/src/macro-condition-idle.cpp +++ b/src/macro-condition-idle.cpp @@ -3,12 +3,12 @@ #include "headers/utility.hpp" #include "headers/advanced-scene-switcher.hpp" -const int MacroConditionIdle::id = 10; +const std::string MacroConditionIdle::id = "idle"; bool MacroConditionIdle::_registered = MacroConditionFactory::Register( MacroConditionIdle::id, {MacroConditionIdle::Create, MacroConditionIdleEdit::Create, - "AdvSceneSwitcher.condition.idle"}); + "AdvSceneSwitcher.condition.idle", false}); bool MacroConditionIdle::CheckCondition() { diff --git a/src/macro-condition-interval.cpp b/src/macro-condition-interval.cpp new file mode 100644 index 00000000..7783fead --- /dev/null +++ b/src/macro-condition-interval.cpp @@ -0,0 +1,88 @@ +#include "headers/macro-condition-edit.hpp" +#include "headers/macro-condition-interval.hpp" +#include "headers/utility.hpp" +#include "headers/advanced-scene-switcher.hpp" + +const std::string MacroConditionInterval::id = "interval"; + +bool MacroConditionInterval::_registered = MacroConditionFactory::Register( + MacroConditionInterval::id, + {MacroConditionInterval::Create, MacroConditionIntervalEdit::Create, + "AdvSceneSwitcher.condition.interval", false}); + +bool MacroConditionInterval::CheckCondition() +{ + if (_duration.DurationReached()) { + _duration.Reset(); + return true; + } + return false; +} + +bool MacroConditionInterval::Save(obs_data_t *obj) +{ + MacroCondition::Save(obj); + _duration.Save(obj); + return true; +} + +bool MacroConditionInterval::Load(obs_data_t *obj) +{ + MacroCondition::Load(obj); + _duration.Load(obj); + return true; +} + +MacroConditionIntervalEdit::MacroConditionIntervalEdit( + QWidget *parent, std::shared_ptr entryData) + : QWidget(parent) +{ + _duration = new DurationSelection(); + + QWidget::connect(_duration, SIGNAL(DurationChanged(double)), this, + SLOT(DurationChanged(double))); + QWidget::connect(_duration, SIGNAL(UnitChanged(DurationUnit)), this, + SLOT(DurationUnitChanged(DurationUnit))); + + QHBoxLayout *mainLayout = new QHBoxLayout; + std::unordered_map widgetPlaceholders = { + {"{{duration}}", _duration}, + }; + placeWidgets( + obs_module_text("AdvSceneSwitcher.condition.interval.entry"), + mainLayout, widgetPlaceholders); + setLayout(mainLayout); + + _entryData = entryData; + UpdateEntryData(); + _loading = false; +} + +void MacroConditionIntervalEdit::DurationChanged(double seconds) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_duration.seconds = seconds; +} + +void MacroConditionIntervalEdit::DurationUnitChanged(DurationUnit unit) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_duration.displayUnit = unit; +} + +void MacroConditionIntervalEdit::UpdateEntryData() +{ + if (!_entryData) { + return; + } + + _duration->SetDuration(_entryData->_duration); +} diff --git a/src/macro-condition-media.cpp b/src/macro-condition-media.cpp index 828718d5..d05ce8fd 100644 --- a/src/macro-condition-media.cpp +++ b/src/macro-condition-media.cpp @@ -3,7 +3,7 @@ #include "headers/utility.hpp" #include "headers/advanced-scene-switcher.hpp" -const int MacroConditionMedia::id = 5; +const std::string MacroConditionMedia::id = "media"; bool MacroConditionMedia::_registered = MacroConditionFactory::Register( MacroConditionMedia::id, @@ -256,7 +256,7 @@ MacroConditionMediaEdit::MacroConditionMediaEdit( QWidget::connect(_time, SIGNAL(UnitChanged(DurationUnit)), this, SLOT(TimeUnitChanged(DurationUnit))); - AdvSceneSwitcher::populateMediaSelection(_mediaSources); + populateMediaSelection(_mediaSources); populateMediaStates(_states); populateMediaTimeRestrictions(_timeRestrictions); diff --git a/src/macro-condition-plugin-state.cpp b/src/macro-condition-plugin-state.cpp index ac69b8da..0fce3935 100644 --- a/src/macro-condition-plugin-state.cpp +++ b/src/macro-condition-plugin-state.cpp @@ -3,13 +3,13 @@ #include "headers/utility.hpp" #include "headers/advanced-scene-switcher.hpp" -const int MacroConditionPluginState::id = 11; +const std::string MacroConditionPluginState::id = "plugin_state"; bool MacroConditionPluginState::_registered = MacroConditionFactory::Register( MacroConditionPluginState::id, {MacroConditionPluginState::Create, MacroConditionPluginStateEdit::Create, - "AdvSceneSwitcher.condition.pluginState"}); + "AdvSceneSwitcher.condition.pluginState", false}); static std::map pluginStateConditionTypes = { {PluginStateCondition::SCENESWITCHED, diff --git a/src/macro-condition-process.cpp b/src/macro-condition-process.cpp index 992ef67a..a71341de 100644 --- a/src/macro-condition-process.cpp +++ b/src/macro-condition-process.cpp @@ -5,7 +5,7 @@ #include -const int MacroConditionProcess::id = 9; +const std::string MacroConditionProcess::id = "process"; bool MacroConditionProcess::_registered = MacroConditionFactory::Register( MacroConditionProcess::id, @@ -61,7 +61,7 @@ MacroConditionProcessEdit::MacroConditionProcessEdit( QWidget::connect(_focused, SIGNAL(stateChanged(int)), this, SLOT(FocusChanged(int))); - AdvSceneSwitcher::populateProcessSelection(_processSelection); + populateProcessSelection(_processSelection); std::unordered_map widgetPlaceholders = { {"{{processes}}", _processSelection}, diff --git a/src/macro-condition-recording.cpp b/src/macro-condition-recording.cpp index cd23f5ce..38c40a64 100644 --- a/src/macro-condition-recording.cpp +++ b/src/macro-condition-recording.cpp @@ -3,7 +3,7 @@ #include "headers/utility.hpp" #include "headers/advanced-scene-switcher.hpp" -const int MacroConditionRecord::id = 8; +const std::string MacroConditionRecord::id = "recording"; bool MacroConditionRecord::_registered = MacroConditionFactory::Register( MacroConditionRecord::id, @@ -32,20 +32,13 @@ bool MacroConditionRecord::CheckCondition() default: break; } - - if (!stateMatch) { - _duration.Reset(); - return false; - } - - return _duration.DurationReached(); + return stateMatch; } bool MacroConditionRecord::Save(obs_data_t *obj) { MacroCondition::Save(obj); obs_data_set_int(obj, "state", static_cast(_recordState)); - _duration.Save(obj); return true; } @@ -53,7 +46,6 @@ bool MacroConditionRecord::Load(obs_data_t *obj) { MacroCondition::Load(obj); _recordState = static_cast(obs_data_get_int(obj, "state")); - _duration.Load(obj); return true; } @@ -69,14 +61,9 @@ MacroConditionRecordEdit::MacroConditionRecordEdit( : QWidget(parent) { _recordState = new QComboBox(); - _duration = new DurationSelection(); QWidget::connect(_recordState, SIGNAL(currentIndexChanged(int)), this, SLOT(StateChanged(int))); - QWidget::connect(_duration, SIGNAL(DurationChanged(double)), this, - SLOT(DurationChanged(double))); - QWidget::connect(_duration, SIGNAL(UnitChanged(DurationUnit)), this, - SLOT(DurationUnitChanged(DurationUnit))); populateStateSelection(_recordState); @@ -84,7 +71,6 @@ MacroConditionRecordEdit::MacroConditionRecordEdit( std::unordered_map widgetPlaceholders = { {"{{recordState}}", _recordState}, - {"{{duration}}", _duration}, }; placeWidgets(obs_module_text("AdvSceneSwitcher.condition.record.entry"), @@ -106,26 +92,6 @@ void MacroConditionRecordEdit::StateChanged(int value) _entryData->_recordState = static_cast(value); } -void MacroConditionRecordEdit::DurationChanged(double seconds) -{ - if (_loading || !_entryData) { - return; - } - - std::lock_guard lock(switcher->m); - _entryData->_duration.seconds = seconds; -} - -void MacroConditionRecordEdit::DurationUnitChanged(DurationUnit unit) -{ - if (_loading || !_entryData) { - return; - } - - std::lock_guard lock(switcher->m); - _entryData->_duration.displayUnit = unit; -} - void MacroConditionRecordEdit::UpdateEntryData() { if (!_entryData) { @@ -134,5 +100,4 @@ void MacroConditionRecordEdit::UpdateEntryData() _recordState->setCurrentIndex( static_cast(_entryData->_recordState)); - _duration->SetDuration(_entryData->_duration); } diff --git a/src/macro-condition-region.cpp b/src/macro-condition-region.cpp index fdb331c8..00016ce9 100644 --- a/src/macro-condition-region.cpp +++ b/src/macro-condition-region.cpp @@ -3,7 +3,7 @@ #include "headers/utility.hpp" #include "headers/advanced-scene-switcher.hpp" -const int MacroConditionRegion::id = 2; +const std::string MacroConditionRegion::id = "region"; bool MacroConditionRegion::_registered = MacroConditionFactory::Register( MacroConditionRegion::id, diff --git a/src/macro-condition-scene.cpp b/src/macro-condition-scene.cpp index 095bb26d..ba7b3395 100644 --- a/src/macro-condition-scene.cpp +++ b/src/macro-condition-scene.cpp @@ -3,7 +3,7 @@ #include "headers/utility.hpp" #include "headers/advanced-scene-switcher.hpp" -const int MacroConditionScene::id = 0; +const std::string MacroConditionScene::id = "scene"; bool MacroConditionScene::_registered = MacroConditionFactory::Register( MacroConditionScene::id, @@ -29,11 +29,7 @@ bool MacroConditionScene::CheckCondition() sceneMatch = switcher->previousScene == _scene; } - if (!sceneMatch) { - _duration.Reset(); - return false; - } - return _duration.DurationReached(); + return sceneMatch; } bool MacroConditionScene::Save(obs_data_t *obj) @@ -41,7 +37,6 @@ bool MacroConditionScene::Save(obs_data_t *obj) MacroCondition::Save(obj); obs_data_set_string(obj, "scene", GetWeakSourceName(_scene).c_str()); obs_data_set_int(obj, "type", static_cast(_type)); - _duration.Save(obj); return true; } @@ -50,7 +45,6 @@ bool MacroConditionScene::Load(obs_data_t *obj) MacroCondition::Load(obj); _scene = GetWeakSourceByName(obs_data_get_string(obj, "scene")); _type = static_cast(obs_data_get_int(obj, "type")); - _duration.Load(obj); return true; } @@ -67,26 +61,20 @@ MacroConditionSceneEdit::MacroConditionSceneEdit( { _sceneSelection = new QComboBox(); _sceneType = new QComboBox(); - _duration = new DurationSelection(); QWidget::connect(_sceneSelection, SIGNAL(currentTextChanged(const QString &)), this, SLOT(SceneChanged(const QString &))); QWidget::connect(_sceneType, SIGNAL(currentIndexChanged(int)), this, SLOT(TypeChanged(int))); - QWidget::connect(_duration, SIGNAL(DurationChanged(double)), this, - SLOT(DurationChanged(double))); - QWidget::connect(_duration, SIGNAL(UnitChanged(DurationUnit)), this, - SLOT(DurationUnitChanged(DurationUnit))); - AdvSceneSwitcher::populateSceneSelection(_sceneSelection); + populateSceneSelection(_sceneSelection); populateTypeSelection(_sceneType); QHBoxLayout *mainLayout = new QHBoxLayout; std::unordered_map widgetPlaceholders = { {"{{scenes}}", _sceneSelection}, {"{{sceneType}}", _sceneType}, - {"{{duration}}", _duration}, }; placeWidgets(obs_module_text("AdvSceneSwitcher.condition.scene.entry"), mainLayout, widgetPlaceholders); @@ -117,26 +105,6 @@ void MacroConditionSceneEdit::TypeChanged(int value) _entryData->_type = static_cast(value); } -void MacroConditionSceneEdit::DurationChanged(double seconds) -{ - if (_loading || !_entryData) { - return; - } - - std::lock_guard lock(switcher->m); - _entryData->_duration.seconds = seconds; -} - -void MacroConditionSceneEdit::DurationUnitChanged(DurationUnit unit) -{ - if (_loading || !_entryData) { - return; - } - - std::lock_guard lock(switcher->m); - _entryData->_duration.displayUnit = unit; -} - void MacroConditionSceneEdit::UpdateEntryData() { if (!_entryData) { @@ -146,5 +114,4 @@ void MacroConditionSceneEdit::UpdateEntryData() _sceneSelection->setCurrentText( GetWeakSourceName(_entryData->_scene).c_str()); _sceneType->setCurrentIndex(static_cast(_entryData->_type)); - _duration->SetDuration(_entryData->_duration); } diff --git a/src/macro-condition-source.cpp b/src/macro-condition-source.cpp new file mode 100644 index 00000000..0bd58ef3 --- /dev/null +++ b/src/macro-condition-source.cpp @@ -0,0 +1,223 @@ +#include "headers/macro-condition-edit.hpp" +#include "headers/macro-condition-source.hpp" +#include "headers/utility.hpp" +#include "headers/advanced-scene-switcher.hpp" + +#include + +const std::string MacroConditionSource::id = "source"; + +bool MacroConditionSource::_registered = MacroConditionFactory::Register( + MacroConditionSource::id, + {MacroConditionSource::Create, MacroConditionSourceEdit::Create, + "AdvSceneSwitcher.condition.source"}); + +static std::map sourceConditionTypes = { + {SourceCondition::ACTIVE, + "AdvSceneSwitcher.condition.source.type.active"}, + {SourceCondition::SHOWING, + "AdvSceneSwitcher.condition.source.type.showing"}, + {SourceCondition::SETTINGS, + "AdvSceneSwitcher.condition.source.type.settings"}, +}; + +bool checkSettings(const OBSWeakSource &source, const std::string &settings, + bool useRegex) +{ + bool ret = false; + std::string currentSettings = getSourceSettings(source); + if (useRegex) { + try { + std::regex expr(settings); + ret = std::regex_match(currentSettings, expr); + } catch (const std::regex_error &) { + } + } else { + ret = currentSettings == settings; + } + return ret; +} + +bool MacroConditionSource::CheckCondition() +{ + if (!_source) { + return false; + } + + bool ret = false; + auto s = obs_weak_source_get_source(_source); + + switch (_condition) { + case SourceCondition::ACTIVE: + ret = obs_source_active(s); + break; + case SourceCondition::SHOWING: + ret = obs_source_showing(s); + break; + case SourceCondition::SETTINGS: + ret = checkSettings(_source, _settings, _regex); + break; + default: + break; + } + + obs_source_release(s); + + return ret; +} + +bool MacroConditionSource::Save(obs_data_t *obj) +{ + MacroCondition::Save(obj); + obs_data_set_string(obj, "source", GetWeakSourceName(_source).c_str()); + obs_data_set_int(obj, "condition", static_cast(_condition)); + obs_data_set_string(obj, "settings", _settings.c_str()); + return true; +} + +bool MacroConditionSource::Load(obs_data_t *obj) +{ + MacroCondition::Load(obj); + const char *sourceName = obs_data_get_string(obj, "source"); + _source = GetWeakSourceByName(sourceName); + _condition = static_cast( + obs_data_get_int(obj, "condition")); + _settings = obs_data_get_string(obj, "settings"); + return true; +} + +static inline void populateConditionSelection(QComboBox *list) +{ + for (auto entry : sourceConditionTypes) { + list->addItem(obs_module_text(entry.second.c_str())); + } +} + +MacroConditionSourceEdit::MacroConditionSourceEdit( + QWidget *parent, std::shared_ptr entryData) + : QWidget(parent) +{ + _sources = new QComboBox(); + _conditions = new QComboBox(); + _getSettings = new QPushButton(obs_module_text( + "AdvSceneSwitcher.condition.source.getSettings")); + _settings = new QPlainTextEdit(); + _regex = new QCheckBox( + obs_module_text("AdvSceneSwitcher.condition.source.regex")); + + populateConditionSelection(_conditions); + populateSourceSelection(_sources); + + QWidget::connect(_sources, SIGNAL(currentTextChanged(const QString &)), + this, SLOT(SourceChanged(const QString &))); + QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this, + SLOT(ConditionChanged(int))); + QWidget::connect(_getSettings, SIGNAL(clicked()), this, + SLOT(GetSettingsClicked())); + QWidget::connect(_settings, SIGNAL(textChanged()), this, + SLOT(SettingsChanged())); + QWidget::connect(_regex, SIGNAL(stateChanged(int)), this, + SLOT(RegexChanged(int))); + + QVBoxLayout *mainLayout = new QVBoxLayout; + QHBoxLayout *line1Layout = new QHBoxLayout; + QHBoxLayout *line2Layout = new QHBoxLayout; + QHBoxLayout *line3Layout = new QHBoxLayout; + std::unordered_map widgetPlaceholders = { + {"{{sources}}", _sources}, {"{{conditions}}", _conditions}, + {"{{settings}}", _settings}, {"{{getSettings}}", _getSettings}, + {"{{regex}}", _regex}, + }; + placeWidgets(obs_module_text( + "AdvSceneSwitcher.condition.source.entry.line1"), + line1Layout, widgetPlaceholders); + placeWidgets(obs_module_text( + "AdvSceneSwitcher.condition.source.entry.line2"), + line2Layout, widgetPlaceholders, false); + placeWidgets(obs_module_text( + "AdvSceneSwitcher.condition.source.entry.line3"), + line3Layout, widgetPlaceholders); + mainLayout->addLayout(line1Layout); + mainLayout->addLayout(line2Layout); + mainLayout->addLayout(line3Layout); + setLayout(mainLayout); + + _entryData = entryData; + UpdateEntryData(); + _loading = false; +} + +void MacroConditionSourceEdit::SourceChanged(const QString &text) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_source = GetWeakSourceByQString(text); +} + +void MacroConditionSourceEdit::ConditionChanged(int index) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_condition = static_cast(index); + SetSettingsSelectionVisible(_entryData->_condition == + SourceCondition::SETTINGS); +} + +void MacroConditionSourceEdit::GetSettingsClicked() +{ + if (_loading || !_entryData || !_entryData->_source) { + return; + } + + _settings->setPlainText( + QString::fromStdString(getSourceSettings(_entryData->_source))); +} + +void MacroConditionSourceEdit::SettingsChanged() +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_settings = _settings->toPlainText().toStdString(); +} + +void MacroConditionSourceEdit::RegexChanged(int state) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_regex = state; +} + +void MacroConditionSourceEdit::SetSettingsSelectionVisible(bool visible) +{ + _settings->setVisible(visible); + _getSettings->setVisible(visible); + _regex->setVisible(visible); + adjustSize(); +} + +void MacroConditionSourceEdit::UpdateEntryData() +{ + if (!_entryData) { + return; + } + + _sources->setCurrentText( + GetWeakSourceName(_entryData->_source).c_str()); + _conditions->setCurrentIndex(static_cast(_entryData->_condition)); + _settings->setPlainText(QString::fromStdString(_entryData->_settings)); + _regex->setChecked(_entryData->_regex); + SetSettingsSelectionVisible(_entryData->_condition == + SourceCondition::SETTINGS); +} diff --git a/src/macro-condition-streaming.cpp b/src/macro-condition-streaming.cpp index 3ccfed81..a975a33f 100644 --- a/src/macro-condition-streaming.cpp +++ b/src/macro-condition-streaming.cpp @@ -3,7 +3,7 @@ #include "headers/utility.hpp" #include "headers/advanced-scene-switcher.hpp" -const int MacroConditionStream::id = 7; +const std::string MacroConditionStream::id = "streaming"; bool MacroConditionStream::_registered = MacroConditionFactory::Register( MacroConditionStream::id, @@ -28,20 +28,13 @@ bool MacroConditionStream::CheckCondition() default: break; } - - if (!stateMatch) { - _duration.Reset(); - return false; - } - - return _duration.DurationReached(); + return stateMatch; } bool MacroConditionStream::Save(obs_data_t *obj) { MacroCondition::Save(obj); obs_data_set_int(obj, "state", static_cast(_streamState)); - _duration.Save(obj); return true; } @@ -49,7 +42,6 @@ bool MacroConditionStream::Load(obs_data_t *obj) { MacroCondition::Load(obj); _streamState = static_cast(obs_data_get_int(obj, "state")); - _duration.Load(obj); return true; } @@ -65,14 +57,9 @@ MacroConditionStreamEdit::MacroConditionStreamEdit( : QWidget(parent) { _streamState = new QComboBox(); - _duration = new DurationSelection(); QWidget::connect(_streamState, SIGNAL(currentIndexChanged(int)), this, SLOT(StateChanged(int))); - QWidget::connect(_duration, SIGNAL(DurationChanged(double)), this, - SLOT(DurationChanged(double))); - QWidget::connect(_duration, SIGNAL(UnitChanged(DurationUnit)), this, - SLOT(DurationUnitChanged(DurationUnit))); populateStateSelection(_streamState); @@ -80,7 +67,6 @@ MacroConditionStreamEdit::MacroConditionStreamEdit( std::unordered_map widgetPlaceholders = { {"{{streamState}}", _streamState}, - {"{{duration}}", _duration}, }; placeWidgets(obs_module_text("AdvSceneSwitcher.condition.stream.entry"), @@ -102,26 +88,6 @@ void MacroConditionStreamEdit::StateChanged(int value) _entryData->_streamState = static_cast(value); } -void MacroConditionStreamEdit::DurationChanged(double seconds) -{ - if (_loading || !_entryData) { - return; - } - - std::lock_guard lock(switcher->m); - _entryData->_duration.seconds = seconds; -} - -void MacroConditionStreamEdit::DurationUnitChanged(DurationUnit unit) -{ - if (_loading || !_entryData) { - return; - } - - std::lock_guard lock(switcher->m); - _entryData->_duration.displayUnit = unit; -} - void MacroConditionStreamEdit::UpdateEntryData() { if (!_entryData) { @@ -130,5 +96,4 @@ void MacroConditionStreamEdit::UpdateEntryData() _streamState->setCurrentIndex( static_cast(_entryData->_streamState)); - _duration->SetDuration(_entryData->_duration); } diff --git a/src/macro-condition-video.cpp b/src/macro-condition-video.cpp index fa4b40a6..2120a796 100644 --- a/src/macro-condition-video.cpp +++ b/src/macro-condition-video.cpp @@ -8,7 +8,7 @@ #include #include -const int MacroConditionVideo::id = 6; +const std::string MacroConditionVideo::id = "video"; bool MacroConditionVideo::_registered = MacroConditionFactory::Register( MacroConditionVideo::id, @@ -39,15 +39,7 @@ bool MacroConditionVideo::CheckCondition() if (_screenshotData) { if (_screenshotData->done) { - bool imageMatch = Compare(); - - if (!imageMatch) { - _duration.Reset(); - } - - if (imageMatch && _duration.DurationReached()) { - match = true; - } + match = Compare(); if (!requiresFileInput(_condition)) { _matchImage = std::move(_screenshotData->image); @@ -66,7 +58,6 @@ bool MacroConditionVideo::Save(obs_data_t *obj) obs_data_set_string(obj, "videoSource", GetWeakSourceName(_videoSource).c_str()); obs_data_set_int(obj, "condition", static_cast(_condition)); - _duration.Save(obj); obs_data_set_string(obj, "filePath", _file.c_str()); return true; } @@ -77,7 +68,6 @@ bool MacroConditionVideo::Load(obs_data_t *obj) _videoSource = GetWeakSourceByName(videoSourceName); _condition = static_cast(obs_data_get_int(obj, "condition")); - _duration.Load(obj); _file = obs_data_get_string(obj, "filePath"); if (requiresFileInput(_condition)) { @@ -137,7 +127,6 @@ MacroConditionVideoEdit::MacroConditionVideoEdit( { _videoSelection = new QComboBox(); _condition = new QComboBox(); - _duration = new DurationSelection(); _filePath = new QLineEdit(); _browseButton = new QPushButton(obs_module_text("AdvSceneSwitcher.browse")); @@ -151,23 +140,18 @@ MacroConditionVideoEdit::MacroConditionVideoEdit( SLOT(SourceChanged(const QString &))); QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this, SLOT(ConditionChanged(int))); - QWidget::connect(_duration, SIGNAL(DurationChanged(double)), this, - SLOT(DurationChanged(double))); - QWidget::connect(_duration, SIGNAL(UnitChanged(DurationUnit)), this, - SLOT(DurationUnitChanged(DurationUnit))); QWidget::connect(_filePath, SIGNAL(editingFinished()), this, SLOT(FilePathChanged())); QWidget::connect(_browseButton, SIGNAL(clicked()), this, SLOT(BrowseButtonClicked())); - AdvSceneSwitcher::populateVideoSelection(_videoSelection, false); + populateVideoSelection(_videoSelection); populateConditionSelection(_condition); QHBoxLayout *mainLayout = new QHBoxLayout; std::unordered_map widgetPlaceholders = { {"{{videoSources}}", _videoSelection}, {"{{condition}}", _condition}, - {"{{duration}}", _duration}, {"{{filePath}}", _filePath}, {"{{browseButton}}", _browseButton}, }; @@ -324,26 +308,6 @@ void MacroConditionVideoEdit::BrowseButtonClicked() SetFilePath(path); } -void MacroConditionVideoEdit::DurationChanged(double seconds) -{ - if (_loading || !_entryData) { - return; - } - - std::lock_guard lock(switcher->m); - _entryData->_duration.seconds = seconds; -} - -void MacroConditionVideoEdit::DurationUnitChanged(DurationUnit unit) -{ - if (_loading || !_entryData) { - return; - } - - std::lock_guard lock(switcher->m); - _entryData->_duration.displayUnit = unit; -} - void MacroConditionVideoEdit::UpdateEntryData() { if (!_entryData) { @@ -353,7 +317,6 @@ void MacroConditionVideoEdit::UpdateEntryData() _videoSelection->setCurrentText( GetWeakSourceName(_entryData->_videoSource).c_str()); _condition->setCurrentIndex(static_cast(_entryData->_condition)); - _duration->SetDuration(_entryData->_duration); _filePath->setText(QString::fromStdString(_entryData->_file)); if (!requiresFileInput(_entryData->_condition)) { diff --git a/src/macro-condition-window.cpp b/src/macro-condition-window.cpp index ed0923fc..e0289ba6 100644 --- a/src/macro-condition-window.cpp +++ b/src/macro-condition-window.cpp @@ -5,7 +5,7 @@ #include -const int MacroConditionWindow::id = 1; +const std::string MacroConditionWindow::id = "window"; bool MacroConditionWindow::_registered = MacroConditionFactory::Register( MacroConditionWindow::id, @@ -83,12 +83,7 @@ bool MacroConditionWindow::Load(obs_data_t *obj) MacroCondition::Load(obj); _window = obs_data_get_string(obj, "window"); _fullscreen = obs_data_get_bool(obj, "fullscreen"); -#if __APPLE__ - // TODO: Implement maximized check on MacOS - _maximized = false; -#else _maximized = obs_data_get_bool(obj, "maximized"); -#endif _focus = obs_data_get_bool(obj, "focus"); return true; } @@ -115,7 +110,7 @@ MacroConditionWindowEdit::MacroConditionWindowEdit( QWidget::connect(_focused, SIGNAL(stateChanged(int)), this, SLOT(FocusChanged(int))); - AdvSceneSwitcher::populateWindowSelection(_windowSelection); + populateWindowSelection(_windowSelection); std::unordered_map widgetPlaceholders = { {"{{windows}}", _windowSelection}, diff --git a/src/macro-selection.cpp b/src/macro-selection.cpp new file mode 100644 index 00000000..9b610259 --- /dev/null +++ b/src/macro-selection.cpp @@ -0,0 +1,68 @@ +#include "headers/macro-selection.hpp" +#include "headers/advanced-scene-switcher.hpp" + +#include + +MacroSelection::MacroSelection(QWidget *parent) : QComboBox(parent) +{ + addItem(obs_module_text("AdvSceneSwitcher.selectMacro")); + + QStandardItemModel *model = + qobject_cast(this->model()); + QModelIndex firstIndex = + model->index(0, modelColumn(), rootModelIndex()); + QStandardItem *firstItem = model->itemFromIndex(firstIndex); + firstItem->setSelectable(false); + firstItem->setEnabled(false); + + for (auto &m : switcher->macros) { + addItem(QString::fromStdString(m.Name())); + } + + QWidget::connect(parent, SIGNAL(MacroAdded(const QString &)), this, + SLOT(MacroAdd(const QString &))); + QWidget::connect(parent, SIGNAL(MacroRemoved(const QString &)), this, + SLOT(MacroRemove(const QString &))); + QWidget::connect(parent, + SIGNAL(MacroRenamed(const QString &, const QString &)), + this, + SLOT(MacroRename(const QString &, const QString &))); +} + +void MacroSelection::MacroAdd(const QString &name) +{ + addItem(name); +} + +void MacroSelection::SetCurrentMacro(Macro *m) +{ + if (!m) { + this->setCurrentIndex(0); + } else { + this->setCurrentText(QString::fromStdString(m->Name())); + } +} + +void MacroSelection::MacroRemove(const QString &name) +{ + int idx = findText(name); + if (idx == -1) { + return; + } + removeItem(idx); + setCurrentIndex(0); +} + +void MacroSelection::MacroRename(const QString &oldName, const QString &newName) +{ + bool renameSelected = currentText() == oldName; + int idx = findText(oldName); + if (idx == -1) { + return; + } + removeItem(idx); + insertItem(idx, newName); + if (renameSelected) { + setCurrentIndex(findText(newName)); + } +} diff --git a/src/macro-tab.cpp b/src/macro-tab.cpp index 67feda0f..3e81250f 100644 --- a/src/macro-tab.cpp +++ b/src/macro-tab.cpp @@ -9,17 +9,11 @@ static QMetaObject::Connection addPulse; const auto conditionsCollapseThreshold = 4; -const auto actionsCollapseThreshold = 2; +const auto actionsCollapseThreshold = 4; bool macroNameExists(std::string name) { - for (auto &m : switcher->macros) { - if (m.Name() == name) { - return true; - } - } - - return false; + return !!GetMacroByName(name.c_str()); } bool AdvSceneSwitcher::addNewMacro(std::string &name) @@ -69,10 +63,14 @@ void AdvSceneSwitcher::on_macroAdd_clicked() QListWidgetItem *item = new QListWidgetItem(text, ui->macros); item->setData(Qt::UserRole, text); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(Qt::Checked); ui->macros->setCurrentItem(item); ui->macroAdd->disconnect(addPulse); ui->macroHelp->setVisible(false); + + emit MacroAdded(QString::fromStdString(name)); } void AdvSceneSwitcher::on_macroRemove_clicked() @@ -81,9 +79,11 @@ void AdvSceneSwitcher::on_macroRemove_clicked() if (!item) { return; } + QString name; { std::lock_guard lock(switcher->m); int idx = ui->macros->currentRow(); + QString::fromStdString(switcher->macros[idx].Name()); switcher->macros.erase(switcher->macros.begin() + idx); } @@ -92,6 +92,7 @@ void AdvSceneSwitcher::on_macroRemove_clicked() if (ui->macros->count() == 0) { ui->macroHelp->setVisible(true); } + emit MacroRemoved(name); } void AdvSceneSwitcher::on_macroUp_clicked() @@ -147,12 +148,17 @@ void AdvSceneSwitcher::on_macroName_editingFinished() if (nameValid) { macro->SetName(newName.toUtf8().constData()); QListWidgetItem *item = ui->macros->currentItem(); - item->setData(Qt::UserRole, newName); + // Don't trigger itemChanged() + // pause state remains as is + ui->macros->blockSignals(true); item->setText(newName); + ui->macros->blockSignals(false); } else { ui->macroName->setText(oldName); } } + + emit MacroRenamed(oldName, newName); } void AdvSceneSwitcher::SetEditMacro(Macro &m) @@ -196,22 +202,14 @@ void AdvSceneSwitcher::SetEditMacro(Macro &m) Macro *AdvSceneSwitcher::getSelectedMacro() { - Macro *macro = nullptr; QListWidgetItem *item = ui->macros->currentItem(); if (!item) { - return macro; + return nullptr; } - QString name = item->data(Qt::UserRole).toString(); - for (auto &m : switcher->macros) { - if (name.compare(m.Name().c_str()) == 0) { - macro = &m; - break; - } - } - - return macro; + QString name = item->text(); + return GetMacroByQString(name); } void AdvSceneSwitcher::on_macros_currentRowChanged(int idx) @@ -226,13 +224,26 @@ void AdvSceneSwitcher::on_macros_currentRowChanged(int idx) } QListWidgetItem *item = ui->macros->item(idx); - QString macroName = item->data(Qt::UserRole).toString(); + QString macroName = item->text(); - for (auto &m : switcher->macros) { - if (macroName.compare(m.Name().c_str()) == 0) { - SetEditMacro(m); - break; - } + auto macro = GetMacroByQString(macroName); + if (macro) { + SetEditMacro(*macro); + } +} + +void AdvSceneSwitcher::on_macros_itemChanged(QListWidgetItem *item) +{ + if (loading) { + return; + } + + std::lock_guard lock(switcher->m); + QString name = item->text(); + + auto m = GetMacroByQString(name); + if (m) { + m->SetPaused(item->checkState() != Qt::Checked); } } @@ -243,10 +254,18 @@ void AdvSceneSwitcher::setupMacroTab() QListWidgetItem *item = new QListWidgetItem(text, ui->macros); item->setData(Qt::UserRole, text); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + if (m.Paused()) { + item->setCheckState(Qt::Unchecked); + } else { + item->setCheckState(Qt::Checked); + } } if (switcher->macros.size() == 0) { - addPulse = PulseWidget(ui->macroAdd, QColor(Qt::green)); + if (!switcher->disableHints) { + addPulse = PulseWidget(ui->macroAdd, QColor(Qt::green)); + } ui->macroHelp->setVisible(true); } else { ui->macroHelp->setVisible(false); @@ -286,5 +305,7 @@ void AdvSceneSwitcher::copyMacro() QString text = QString::fromStdString(name); QListWidgetItem *item = new QListWidgetItem(text, ui->macros); item->setData(Qt::UserRole, text); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(Qt::Checked); ui->macros->setCurrentItem(item); } diff --git a/src/macro.cpp b/src/macro.cpp index e0dda440..a6d46b4c 100644 --- a/src/macro.cpp +++ b/src/macro.cpp @@ -2,7 +2,10 @@ #include "headers/macro-action-edit.hpp" #include "headers/macro-condition-edit.hpp" -#include "headers/macro-action-switch-scene.hpp" +#include "headers/macro-action-scene-switch.hpp" + +#include +#undef max const std::map MacroCondition::logicTypes = { {LogicType::NONE, {"AdvSceneSwitcher.logic.none"}}, @@ -14,15 +17,26 @@ const std::map MacroCondition::logicTypes = { {LogicType::ROOT_NOT, {"AdvSceneSwitcher.logic.not"}}, }; -Macro::Macro(std::string name) : _name(name) {} +Macro::Macro(const std::string &name) +{ + SetupHotkeys(); + SetName(name); +} -Macro::~Macro() {} +Macro::~Macro() +{ + ClearHotkeys(); +} bool Macro::CeckMatch() { _matched = false; for (auto &c : _conditions) { bool cond = c->CheckCondition(); + if (!cond) { + c->ResetDuration(); + } + cond = cond && c->DurationReached(); switch (c->GetLogicType()) { case LogicType::NONE: @@ -55,6 +69,21 @@ bool Macro::CeckMatch() _name.c_str()); break; } + vblog(LOG_INFO, "condition %s returned %d", c->GetId().c_str(), + cond); + } + + vblog(LOG_INFO, "Macro %s returned %d", _name.c_str(), _matched); + + // Condition checks shall still be run even if macro is paused. + // Otherwise conditions might behave in unexpected ways when resuming. + // + // For example, audio could immediately match after unpause, when it + // matched before it was paused due to timers not being updated. + if (_paused) { + vblog(LOG_INFO, "Macro %s is paused", _name.c_str()); + _matched = false; + return false; } return _matched; @@ -70,13 +99,29 @@ bool Macro::PerformAction() return false; } } - + if (ret && _count != std::numeric_limits::max()) { + _count++; + } return ret; } +void Macro::SetName(const std::string &name) +{ + _name = name; + SetHotkeysDesc(); +} + bool Macro::Save(obs_data_t *obj) { obs_data_set_string(obj, "name", _name.c_str()); + obs_data_set_bool(obj, "pause", _paused); + + obs_data_array_t *pauseHotkey = obs_hotkey_save(_pauseHotkey); + obs_data_set_array(obj, "pauseHotkey", pauseHotkey); + obs_data_array_release(pauseHotkey); + obs_data_array_t *unpauseHotkey = obs_hotkey_save(_unpauseHotkey); + obs_data_set_array(obj, "unpauseHotkey", unpauseHotkey); + obs_data_array_release(unpauseHotkey); obs_data_array_t *conditions = obs_data_array_create(); for (auto &c : _conditions) { @@ -142,15 +187,27 @@ void setValidLogic(MacroCondition *c, bool root, std::string name) bool Macro::Load(obs_data_t *obj) { _name = obs_data_get_string(obj, "name"); - bool root = true; + _paused = obs_data_get_bool(obj, "pause"); + obs_data_array_t *pauseHotkey = obs_data_get_array(obj, "pauseHotkey"); + obs_hotkey_load(_pauseHotkey, pauseHotkey); + obs_data_array_release(pauseHotkey); + + obs_data_array_t *unpauseHotkey = + obs_data_get_array(obj, "unpauseHotkey"); + obs_hotkey_load(_unpauseHotkey, unpauseHotkey); + obs_data_array_release(unpauseHotkey); + + SetHotkeysDesc(); + + bool root = true; obs_data_array_t *conditions = obs_data_get_array(obj, "conditions"); size_t count = obs_data_array_count(conditions); for (size_t i = 0; i < count; i++) { obs_data_t *array_obj = obs_data_array_item(conditions, i); - int id = obs_data_get_int(array_obj, "id"); + std::string id = obs_data_get_string(array_obj, "id"); auto newEntry = MacroConditionFactory::Create(id); if (newEntry) { @@ -160,8 +217,8 @@ bool Macro::Load(obs_data_t *obj) setValidLogic(c, root, _name); } else { blog(LOG_WARNING, - "discarding condition entry with unkown id (%d) for macro %s", - id, _name.c_str()); + "discarding condition entry with unkown id (%s) for macro %s", + id.c_str(), _name.c_str()); } obs_data_release(array_obj); @@ -175,7 +232,7 @@ bool Macro::Load(obs_data_t *obj) for (size_t i = 0; i < count; i++) { obs_data_t *array_obj = obs_data_array_item(actions, i); - int id = obs_data_get_int(array_obj, "id"); + std::string id = obs_data_get_string(array_obj, "id"); auto newEntry = MacroActionFactory::Create(id); if (newEntry) { @@ -183,44 +240,154 @@ bool Macro::Load(obs_data_t *obj) _actions.back()->Load(array_obj); } else { blog(LOG_WARNING, - "discarding action entry with unkown id (%d) for macro %s", - id, _name.c_str()); + "discarding action entry with unkown id (%s) for macro %s", + id.c_str(), _name.c_str()); } obs_data_release(array_obj); } obs_data_array_release(actions); - return true; } +void Macro::ResolveMacroRef() +{ + for (auto &c : _conditions) { + MacroRefCondition *ref = + dynamic_cast(c.get()); + if (ref) { + ref->_macro.UpdateRef(); + } + } + for (auto &a : _actions) { + MacroRefAction *ref = dynamic_cast(a.get()); + if (ref) { + ref->_macro.UpdateRef(); + } + } +} + bool Macro::SwitchesScene() { MacroActionSwitchScene temp; + auto sceneSwitchId = temp.GetId(); for (auto &a : _actions) { - if (a->GetId() == temp.GetId()) { + if (a->GetId() == sceneSwitchId) { return true; } } return false; } +static void pauseCB(void *data, obs_hotkey_id id, obs_hotkey_t *hotkey, + bool pressed) +{ + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(data); + UNUSED_PARAMETER(hotkey); + if (pressed) { + auto m = static_cast(data); + m->SetPaused(true); + } +} + +static void unpauseCB(void *data, obs_hotkey_id id, obs_hotkey_t *hotkey, + bool pressed) +{ + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(data); + UNUSED_PARAMETER(hotkey); + if (pressed) { + auto m = static_cast(data); + m->SetPaused(false); + } +} + +static int macroHotkeyID = 0; + +void Macro::SetupHotkeys() +{ + if (_pauseHotkey != OBS_INVALID_HOTKEY_ID || + _unpauseHotkey != OBS_INVALID_HOTKEY_ID) { + ClearHotkeys(); + } + + macroHotkeyID++; + + std::string hotkeyName = + "macro_pause_hotkey_" + std::to_string(macroHotkeyID); + QString format{obs_module_text("AdvSceneSwitcher.hotkey.macro.pause")}; + QString hotkeyDesc = format.arg(QString::fromStdString(_name)); + _pauseHotkey = obs_hotkey_register_frontend( + hotkeyName.c_str(), hotkeyDesc.toStdString().c_str(), pauseCB, + this); + + macroHotkeyID++; + + hotkeyName = "macro_pause_hotkey_" + _name; + format = {obs_module_text("AdvSceneSwitcher.hotkey.macro.unpause")}; + hotkeyDesc = format.arg(QString::fromStdString(_name)); + _unpauseHotkey = obs_hotkey_register_frontend( + hotkeyName.c_str(), hotkeyDesc.toStdString().c_str(), unpauseCB, + this); +} + +void Macro::ClearHotkeys() +{ + obs_hotkey_unregister(_pauseHotkey); + obs_hotkey_unregister(_unpauseHotkey); +} + +void Macro::SetHotkeysDesc() +{ + QString format{obs_module_text("AdvSceneSwitcher.hotkey.macro.pause")}; + QString hotkeyDesc = format.arg(QString::fromStdString(_name)); + obs_hotkey_set_description(_pauseHotkey, + hotkeyDesc.toStdString().c_str()); + format = {obs_module_text("AdvSceneSwitcher.hotkey.macro.unpause")}; + hotkeyDesc = format.arg(QString::fromStdString(_name)); + obs_hotkey_set_description(_unpauseHotkey, + hotkeyDesc.toStdString().c_str()); +} + bool MacroCondition::Save(obs_data_t *obj) { - obs_data_set_int(obj, "id", GetId()); + obs_data_set_string(obj, "id", GetId().c_str()); obs_data_set_int(obj, "logic", static_cast(_logic)); + _duration.Save(obj); return true; } bool MacroCondition::Load(obs_data_t *obj) { _logic = static_cast(obs_data_get_int(obj, "logic")); + _duration.Load(obj); return true; } +void MacroCondition::SetDurationConstraint(const DurationConstraint &dur) +{ + _duration = dur; +} + +void MacroCondition::SetDurationCondition(DurationCondition cond) +{ + _duration.SetCondition(cond); +} + +void MacroCondition::SetDurationUnit(DurationUnit u) +{ + _duration.SetUnit(u); +} + +void MacroCondition::SetDuration(double seconds) +{ + _duration.SetValue(seconds); +} + bool MacroAction::Save(obs_data_t *obj) { - obs_data_set_int(obj, "id", GetId()); + obs_data_set_string(obj, "id", GetId().c_str()); return true; } @@ -232,7 +399,7 @@ bool MacroAction::Load(obs_data_t *obj) void MacroAction::LogAction() { - blog(LOG_INFO, "performed action %d", GetId()); + vblog(LOG_INFO, "performed action %s", GetId().c_str()); } void SwitcherData::saveMacros(obs_data_t *obj) @@ -250,8 +417,71 @@ void SwitcherData::saveMacros(obs_data_t *obj) obs_data_array_release(macroArray); } +// Temporary helper functions to convert old settings format to new one +static std::unordered_map actionIntToActionString = { + {2, "audio"}, {4, "recording"}, {5, "replay_buffer"}, {6, "run"}, + {3, "streaming"}, {0, "scene_switch"}, {1, "wait"}, +}; + +static void replaceActionIds(obs_data_t *obj) +{ + obs_data_array_t *actions = obs_data_get_array(obj, "actions"); + size_t count = obs_data_array_count(actions); + + for (size_t i = 0; i < count; i++) { + obs_data_t *array_obj = obs_data_array_item(actions, i); + auto oldId = obs_data_get_int(array_obj, "id"); + obs_data_set_string(array_obj, "id", + actionIntToActionString[oldId].c_str()); + obs_data_release(array_obj); + } + obs_data_array_release(actions); +} + +static std::unordered_map conditionIntToConditionString = { + {3, "audio"}, {4, "file"}, {10, "idle"}, {5, "media"}, + {11, "plugin_state"}, {9, "process"}, {8, "recording"}, {2, "region"}, + {0, "scene"}, {7, "streaming"}, {6, "video"}, {1, "window"}, +}; + +static void replaceConditionIds(obs_data_t *obj) +{ + obs_data_array_t *conditions = obs_data_get_array(obj, "conditions"); + size_t count = obs_data_array_count(conditions); + + for (size_t i = 0; i < count; i++) { + obs_data_t *array_obj = obs_data_array_item(conditions, i); + auto oldId = obs_data_get_int(array_obj, "id"); + obs_data_set_string( + array_obj, "id", + conditionIntToConditionString[oldId].c_str()); + obs_data_release(array_obj); + } + obs_data_array_release(conditions); +} + +static void convertOldMacroIdsToString(obs_data_t *obj) +{ + obs_data_array_t *macroArray = obs_data_get_array(obj, "macros"); + size_t count = obs_data_array_count(macroArray); + + for (size_t i = 0; i < count; i++) { + obs_data_t *array_obj = obs_data_array_item(macroArray, i); + replaceActionIds(array_obj); + replaceConditionIds(array_obj); + obs_data_release(array_obj); + } + obs_data_array_release(macroArray); +} + void SwitcherData::loadMacros(obs_data_t *obj) { + // TODO: Remove conversion helper in future version + std::string previousVersion = obs_data_get_string(obj, "version"); + if (previousVersion == "2ce0b35921be892c987c7dbb5fc90db38f15f0a6") { + convertOldMacroIdsToString(obj); + } + macros.clear(); obs_data_array_t *macroArray = obs_data_get_array(obj, "macros"); @@ -264,6 +494,10 @@ void SwitcherData::loadMacros(obs_data_t *obj) obs_data_release(array_obj); } obs_data_array_release(macroArray); + + for (auto &m : macros) { + m.ResolveMacroRef(); + } } bool SwitcherData::checkMacros() @@ -286,7 +520,7 @@ bool SwitcherData::runMacros() { for (auto &m : macros) { if (m.Matched()) { - blog(LOG_INFO, "running macro: %s", m.Name().c_str()); + vblog(LOG_INFO, "running macro: %s", m.Name().c_str()); if (!m.PerformAction()) { blog(LOG_WARNING, "abort macro: %s", m.Name().c_str()); @@ -296,3 +530,69 @@ bool SwitcherData::runMacros() } return true; } + +Macro *GetMacroByName(const char *name) +{ + for (auto &m : switcher->macros) { + if (m.Name() == name) { + return &m; + } + } + + return nullptr; +} + +Macro *GetMacroByQString(const QString &name) +{ + return GetMacroByName(name.toUtf8().constData()); +} + +MacroRef::MacroRef(std::string name) : _name(name) +{ + UpdateRef(); +} +void MacroRef::UpdateRef() +{ + _ref = GetMacroByName(_name.c_str()); +} +void MacroRef::UpdateRef(std::string newName) +{ + _name = newName; + UpdateRef(); +} +void MacroRef::UpdateRef(QString newName) +{ + _name = newName.toStdString(); + UpdateRef(); +} +void MacroRef::Save(obs_data_t *obj) +{ + if (_ref) { + obs_data_set_string(obj, "macro", _ref->Name().c_str()); + } +} +void MacroRef::Load(obs_data_t *obj) +{ + _name = obs_data_get_string(obj, "macro"); + UpdateRef(); +} + +Macro *MacroRef::get() +{ + return _ref; +} + +Macro *MacroRef::operator->() +{ + return _ref; +} + +void MacroRefCondition::ResolveMacroRef() +{ + _macro.UpdateRef(); +} + +void MacroRefAction::ResolveMacroRef() +{ + _macro.UpdateRef(); +} diff --git a/src/osx/advanced-scene-switcher-osx.mm b/src/osx/advanced-scene-switcher-osx.mm index 4f9bccb0..69479042 100644 --- a/src/osx/advanced-scene-switcher-osx.mm +++ b/src/osx/advanced-scene-switcher-osx.mm @@ -17,7 +17,6 @@ void GetWindowList(std::vector &windows) (__bridge NSMutableArray *)CGWindowListCopyWindowInfo( kCGWindowListOptionAll, kCGNullWindowID); for (NSDictionary *app in apps) { - // Construct string from NSString accounting for nil std::string name([[app objectForKey:@"kCGWindowName"] UTF8String], [[app objectForKey:@"kCGWindowName"] @@ -30,53 +29,26 @@ void GetWindowList(std::vector &windows) lengthOfBytesUsingEncoding: NSUTF8StringEncoding]); - // Check if name exists if (!name.empty() && find(windows.begin(), windows.end(), name) == windows.end()) windows.emplace_back(name); - // Check if owner exists - else if (!owner.empty() && - find(windows.begin(), windows.end(), owner) == - windows.end()) + + if (!owner.empty() && + find(windows.begin(), windows.end(), owner) == + windows.end()) windows.emplace_back(owner); } } } -// Overloaded void GetWindowList(QStringList &windows) { windows.clear(); - - @autoreleasepool { - NSMutableArray *apps = - (__bridge NSMutableArray *)CGWindowListCopyWindowInfo( - kCGWindowListOptionAll, kCGNullWindowID); - for (NSDictionary *app in apps) { - // Construct string from NSString accounting for nil - std::string name([[app objectForKey:@"kCGWindowName"] - UTF8String], - [[app objectForKey:@"kCGWindowName"] - lengthOfBytesUsingEncoding: - NSUTF8StringEncoding]); - std::string owner( - [[app objectForKey:@"kCGWindowOwnerName"] - UTF8String], - [[app objectForKey:@"kCGWindowOwnerName"] - lengthOfBytesUsingEncoding: - NSUTF8StringEncoding]); - - // Check if name exists - if (!name.empty() && - !windows.contains(QString::fromStdString(name))) - windows << QString::fromStdString(name); - // Check if owner exists - else if (!owner.empty() && - !windows.contains( - QString::fromStdString(name))) - windows << QString::fromStdString(owner); - } + std::vector temp; + GetWindowList(temp); + for (auto &w : temp) { + windows << QString::fromStdString(w); } } @@ -94,7 +66,6 @@ void GetCurrentWindowTitle(std::string &title) [[app objectForKey:@"kCGWindowLayer"] intValue]; // True if window is frontmost if (layer == 0) { - // Construct string from NSString accounting for nil std::string name( [[app objectForKey:@"kCGWindowName"] UTF8String], @@ -130,23 +101,68 @@ std::pair getCursorPos() return pos; } -// TODO: -// not implemented on MacOS as I cannot test it -bool isMaximized(std::string &title) +bool isWindowOriginOnScreen(NSDictionary *app, NSScreen *screen, + bool fullscreen = false) { - return false; + NSArray *screens = [NSScreen screens]; + NSRect mainScreenFrame = [screens[0] frame]; + NSRect screenFrame; + if (fullscreen) { + screenFrame = [screen frame]; + } else { + screenFrame = [screen visibleFrame]; + } + NSRect windowBounds; + CGRectMakeWithDictionaryRepresentation( + (CFDictionaryRef)[app objectForKey:@"kCGWindowBounds"], + &windowBounds); + + return (windowBounds.origin.x == screenFrame.origin.x && + (mainScreenFrame.size.height - screenFrame.size.height - + windowBounds.origin.y == + screenFrame.origin.y)); } -bool isFullscreen(std::string &title) +bool isWindowMaximizedOnScreen(NSDictionary *app, NSScreen *screen) +{ + double maximizedTolerance = 0.99; + NSRect screenFrame = [screen frame]; + NSRect windowBounds; + CGRectMakeWithDictionaryRepresentation( + (CFDictionaryRef)[app objectForKey:@"kCGWindowBounds"], + &windowBounds); + + int sumX = windowBounds.origin.x + windowBounds.size.width; + int sumY = windowBounds.origin.y + windowBounds.size.height; + + // Return false if window spans over multiple screens + if (sumX > screenFrame.size.width) { + return false; + } + if (sumY > screenFrame.size.height) { + return false; + } + + return ((double)sumX / (double)screenFrame.size.width) > + maximizedTolerance && + ((double)sumY / (double)screenFrame.size.height) > + maximizedTolerance; +} + +bool nameMachesPattern(std::string windowName, std::string pattern) +{ + return QString::fromStdString(windowName) + .contains(QRegularExpression(QString::fromStdString(pattern))); +} + +bool isMaximized(const std::string &title) { - // Check for match @autoreleasepool { NSArray *screens = [NSScreen screens]; NSMutableArray *apps = (__bridge NSMutableArray *)CGWindowListCopyWindowInfo( kCGWindowListOptionAll, kCGNullWindowID); for (NSDictionary *app in apps) { - // Construct string from NSString accounting for nil std::string name([[app objectForKey:@"kCGWindowName"] UTF8String], [[app objectForKey:@"kCGWindowName"] @@ -159,51 +175,69 @@ bool isFullscreen(std::string &title) lengthOfBytesUsingEncoding: NSUTF8StringEncoding]); - // True if switch equals app bool equals = (title == name || title == owner); - // True if switch matches app - bool matches = (QString::fromStdString(name).contains( - QRegularExpression( - QString::fromStdString( - title))) || - QString::fromStdString(owner).contains( - QRegularExpression( - QString::fromStdString( - title)))); - - // If found, check if fullscreen + bool matches = nameMachesPattern(name, title) || + nameMachesPattern(owner, title); if (equals || matches) { - // Get window bounds - NSRect bounds; - CGRectMakeWithDictionaryRepresentation( - (CFDictionaryRef)[app - objectForKey:@"kCGWindowBounds"], - &bounds); - - // Compare to screen bounds for (NSScreen *screen in screens) { - NSRect frame = [screen visibleFrame]; + if (isWindowOriginOnScreen(app, + screen) && + isWindowMaximizedOnScreen(app, + screen)) { + return true; + } + } + } + } + } + return false; +} - // True if flipped window origin equals screen origin - bool origin = - (bounds.origin.x == - frame.origin.x && - ([screens[0] visibleFrame] - .size.height - - frame.size.height - - bounds.origin.y == - frame.origin.y)); - // True if window size equals screen size - bool size = NSEqualSizes(bounds.size, - frame.size); +bool isWindowFullscreenOnScreen(NSDictionary *app, NSScreen *screen) +{ + NSRect screenFrame = [screen frame]; + NSRect windowBounds; + CGRectMakeWithDictionaryRepresentation( + (CFDictionaryRef)[app objectForKey:@"kCGWindowBounds"], + &windowBounds); - if (origin && size) + return NSEqualSizes(windowBounds.size, screenFrame.size); +} + +bool isFullscreen(const std::string &title) +{ + @autoreleasepool { + NSArray *screens = [NSScreen screens]; + NSMutableArray *apps = + (__bridge NSMutableArray *)CGWindowListCopyWindowInfo( + kCGWindowListOptionAll, kCGNullWindowID); + for (NSDictionary *app in apps) { + std::string name([[app objectForKey:@"kCGWindowName"] + UTF8String], + [[app objectForKey:@"kCGWindowName"] + lengthOfBytesUsingEncoding: + NSUTF8StringEncoding]); + std::string owner( + [[app objectForKey:@"kCGWindowOwnerName"] + UTF8String], + [[app objectForKey:@"kCGWindowOwnerName"] + lengthOfBytesUsingEncoding: + NSUTF8StringEncoding]); + + bool equals = (title == name || title == owner); + bool matches = nameMachesPattern(name, title) || + nameMachesPattern(owner, title); + if (equals || matches) { + for (NSScreen *screen in screens) { + if (isWindowOriginOnScreen(app, screen, + true) && + isWindowFullscreenOnScreen(app, + screen)) return true; } } } } - return false; } diff --git a/src/scene-group.cpp b/src/scene-group.cpp index b1c3cb9b..fe18da9d 100644 --- a/src/scene-group.cpp +++ b/src/scene-group.cpp @@ -499,7 +499,10 @@ void AdvSceneSwitcher::setupSceneGroupTab() } if (switcher->sceneGroups.size() == 0) { - addPulse = PulseWidget(ui->sceneGroupAdd, QColor(Qt::green)); + if (!switcher->disableHints) { + addPulse = PulseWidget(ui->sceneGroupAdd, + QColor(Qt::green)); + } ui->sceneGroupHelp->setVisible(true); } else { ui->sceneGroupHelp->setVisible(false); diff --git a/src/scene-trigger.cpp b/src/scene-trigger.cpp index 055d373b..60a434de 100644 --- a/src/scene-trigger.cpp +++ b/src/scene-trigger.cpp @@ -347,7 +347,10 @@ void AdvSceneSwitcher::setupTriggerTab() } if (switcher->sceneTriggers.size() == 0) { - addPulse = PulseWidget(ui->triggerAdd, QColor(Qt::green)); + if (!switcher->disableHints) { + addPulse = + PulseWidget(ui->triggerAdd, QColor(Qt::green)); + } ui->triggerHelp->setVisible(true); } else { ui->triggerHelp->setVisible(false); @@ -381,7 +384,7 @@ void SceneTrigger::load(obs_data_t *obj) static inline void populateTriggers(QComboBox *list) { - AdvSceneSwitcher::addSelectionEntry( + addSelectionEntry( list, obs_module_text( "AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.none")); @@ -396,7 +399,7 @@ static inline void populateTriggers(QComboBox *list) inline void populateActions(QComboBox *list) { - AdvSceneSwitcher::addSelectionEntry( + addSelectionEntry( list, obs_module_text( "AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.none")); @@ -451,7 +454,7 @@ SceneTriggerWidget::SceneTriggerWidget(QWidget *parent, SceneTrigger *s) populateTriggers(triggers); populateActions(actions); - AdvSceneSwitcher::populateAudioSelection(audioSources); + populateAudioSelection(audioSources); if (s) { triggers->setCurrentIndex(static_cast(s->triggerType)); diff --git a/src/section.cpp b/src/section.cpp index 9713950c..f38fa63a 100644 --- a/src/section.cpp +++ b/src/section.cpp @@ -1,7 +1,9 @@ -#include #include "headers/section.hpp" #include "headers/utility.hpp" +#include +#include + Section::Section(const int animationDuration, QWidget *parent) : QWidget(parent), _animationDuration(animationDuration) { @@ -38,45 +40,74 @@ Section::Section(const int animationDuration, QWidget *parent) connect(_toggleButton, &QToolButton::toggled, this, &Section::Collapse); } -void Section::Collapse(bool collapsed) +void Section::Collapse(bool collapse) { - _toggleButton->setChecked(collapsed); - _toggleButton->setArrowType(!collapsed ? Qt::ArrowType::DownArrow - : Qt::ArrowType::RightArrow); - _toggleAnimation->setDirection(!collapsed + _toggleButton->setChecked(collapse); + _toggleButton->setArrowType(!collapse ? Qt::ArrowType::DownArrow + : Qt::ArrowType::RightArrow); + _toggleAnimation->setDirection(!collapse ? QAbstractAnimation::Forward : QAbstractAnimation::Backward); + _transitioning = true; + _collapsed = collapse; _toggleAnimation->start(); } -void Section::SetContent(QWidget *w) +void Section::SetContent(QWidget *w, bool collapsed) { - // Clean up previous content - if (_contentArea) { - auto oldLayout = _contentArea->layout(); - if (oldLayout) { - clearLayout(oldLayout); - delete oldLayout; - } - } - + CleanUpPreviousContent(); delete _contentArea; - delete _toggleAnimation; // Setup contentArea _contentArea = new QScrollArea(this); _contentArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - - // Start out collapsed + _contentArea->setStyleSheet("QScrollArea { border: none; }"); _contentArea->setMaximumHeight(0); _contentArea->setMinimumHeight(0); + w->installEventFilter(this); + _content = w; auto newLayout = new QVBoxLayout(); + newLayout->setContentsMargins(0, 0, 0, 0); newLayout->addWidget(w); _contentArea->setLayout(newLayout); _mainLayout->addWidget(_contentArea, 1, 0, 1, 3); - // Animation Setup + _headerHeight = sizeHint().height() - _contentArea->maximumHeight(); + _contentHeight = newLayout->sizeHint().height(); + + if (collapsed) { + this->setMinimumHeight(_headerHeight); + _contentArea->setMaximumHeight(0); + } else { + this->setMinimumHeight(_headerHeight + _contentHeight); + _contentArea->setMaximumHeight(_contentHeight); + } + SetupAnimations(); + Collapse(collapsed); +} + +void Section::AddHeaderWidget(QWidget *w) +{ + _headerWidgetLayout->addWidget(w); +} + +bool Section::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::Resize && !_transitioning && !_collapsed) { + _contentHeight = _content->sizeHint().height(); + setMaximumHeight(_headerHeight + _contentHeight); + setMinimumHeight(_headerHeight + _contentHeight); + _contentArea->setMaximumHeight(_contentHeight); + SetupAnimations(); + } + return QObject::eventFilter(obj, event); +} + +void Section::SetupAnimations() +{ + delete _toggleAnimation; + _toggleAnimation = new QParallelAnimationGroup(this); _toggleAnimation->addAnimation( new QPropertyAnimation(this, "minimumHeight")); @@ -85,17 +116,13 @@ void Section::SetContent(QWidget *w) _toggleAnimation->addAnimation( new QPropertyAnimation(_contentArea, "maximumHeight")); - const auto collapsedHeight = - sizeHint().height() - _contentArea->maximumHeight(); - auto contentHeight = newLayout->sizeHint().height(); - for (int i = 0; i < _toggleAnimation->animationCount() - 1; ++i) { QPropertyAnimation *SectionAnimation = static_cast( _toggleAnimation->animationAt(i)); SectionAnimation->setDuration(_animationDuration); - SectionAnimation->setStartValue(collapsedHeight); - SectionAnimation->setEndValue(collapsedHeight + contentHeight); + SectionAnimation->setStartValue(_headerHeight); + SectionAnimation->setEndValue(_headerHeight + _contentHeight); } QPropertyAnimation *contentAnimation = @@ -103,10 +130,24 @@ void Section::SetContent(QWidget *w) _toggleAnimation->animationCount() - 1)); contentAnimation->setDuration(_animationDuration); contentAnimation->setStartValue(0); - contentAnimation->setEndValue(contentHeight); + contentAnimation->setEndValue(_contentHeight); + + QWidget::connect(_toggleAnimation, SIGNAL(finished()), this, + SLOT(AnimationFinished())); } -void Section::AddHeaderWidget(QWidget *w) +void Section::CleanUpPreviousContent() { - _headerWidgetLayout->addWidget(w); + if (_contentArea) { + auto oldLayout = _contentArea->layout(); + if (oldLayout) { + clearLayout(oldLayout); + delete oldLayout; + } + } +} + +void Section::AnimationFinished() +{ + _transitioning = false; } diff --git a/src/switch-audio.cpp b/src/switch-audio.cpp index 03ff4197..0473346d 100644 --- a/src/switch-audio.cpp +++ b/src/switch-audio.cpp @@ -224,7 +224,9 @@ void AdvSceneSwitcher::setupAudioTab() } if (switcher->audioSwitches.size() == 0) { - addPulse = PulseWidget(ui->audioAdd, QColor(Qt::green)); + if (!switcher->disableHints) { + addPulse = PulseWidget(ui->audioAdd, QColor(Qt::green)); + } ui->audioHelp->setVisible(true); } else { ui->audioHelp->setVisible(false); @@ -447,7 +449,7 @@ AudioSwitchWidget::AudioSwitchWidget(QWidget *parent, AudioSwitch *s) QWidget::connect(ignoreInactiveSource, SIGNAL(stateChanged(int)), this, SLOT(IgnoreInactiveChanged(int))); - AdvSceneSwitcher::populateAudioSelection(audioSources); + populateAudioSelection(audioSources); populateConditionSelection(condition); if (s) { diff --git a/src/switch-executable.cpp b/src/switch-executable.cpp index 04073ff7..42138f09 100644 --- a/src/switch-executable.cpp +++ b/src/switch-executable.cpp @@ -187,7 +187,10 @@ void AdvSceneSwitcher::setupExecutableTab() } if (switcher->executableSwitches.size() == 0) { - addPulse = PulseWidget(ui->executableAdd, QColor(Qt::green)); + if (!switcher->disableHints) { + addPulse = PulseWidget(ui->executableAdd, + QColor(Qt::green)); + } ui->exeHelp->setVisible(true); } else { ui->exeHelp->setVisible(false); @@ -223,7 +226,7 @@ ExecutableSwitchWidget::ExecutableSwitchWidget(QWidget *parent, QWidget::connect(requiresFocus, SIGNAL(stateChanged(int)), this, SLOT(FocusChanged(int))); - AdvSceneSwitcher::populateProcessSelection(processes); + populateProcessSelection(processes); processes->setEditable(true); processes->setMaxVisibleItems(20); diff --git a/src/switch-file.cpp b/src/switch-file.cpp index da050bb2..f963e79d 100644 --- a/src/switch-file.cpp +++ b/src/switch-file.cpp @@ -101,7 +101,7 @@ void SwitcherData::writeSceneInfoToFile() obs_source_release(currentSource); } -void SwitcherData::writeToStatusFile(QString msg) +void SwitcherData::writeToStatusFile(const QString &msg) { if (!fileIO.writeEnabled || fileIO.writePath.empty()) { return; @@ -415,7 +415,9 @@ void AdvSceneSwitcher::setupFileTab() } if (switcher->fileSwitches.size() == 0) { - addPulse = PulseWidget(ui->fileAdd, QColor(Qt::green)); + if (!switcher->disableHints) { + addPulse = PulseWidget(ui->fileAdd, QColor(Qt::green)); + } ui->fileHelp->setVisible(true); } else { ui->fileHelp->setVisible(false); diff --git a/src/switch-generic.cpp b/src/switch-generic.cpp index f8cb8c5e..ef4863b5 100644 --- a/src/switch-generic.cpp +++ b/src/switch-generic.cpp @@ -228,10 +228,9 @@ SwitchWidget::SwitchWidget(QWidget *parent, SceneSwitcherEntry *s, SIGNAL(SceneGroupRenamed(const QString &, const QString &)), this, SLOT(SceneGroupRename(const QString &, const QString &))); - AdvSceneSwitcher::populateSceneSelection(scenes, usePreviousScene, - addSceneGroup); - AdvSceneSwitcher::populateTransitionSelection(transitions, - addCurrentTransition); + populateSceneSelection(scenes, usePreviousScene, addSceneGroup, + &switcher->sceneGroups); + populateTransitionSelection(transitions, addCurrentTransition); switchData = s; showSwitchData(); diff --git a/src/switch-media.cpp b/src/switch-media.cpp index c83c1627..4849ec1b 100644 --- a/src/switch-media.cpp +++ b/src/switch-media.cpp @@ -244,7 +244,9 @@ void AdvSceneSwitcher::setupMediaTab() } if (switcher->mediaSwitches.size() == 0) { - addPulse = PulseWidget(ui->mediaAdd, QColor(Qt::green)); + if (!switcher->disableHints) { + addPulse = PulseWidget(ui->mediaAdd, QColor(Qt::green)); + } ui->mediaHelp->setVisible(true); } else { ui->mediaHelp->setVisible(false); @@ -458,7 +460,7 @@ MediaSwitchWidget::MediaSwitchWidget(QWidget *parent, MediaSwitch *s) QWidget::connect(time, SIGNAL(valueChanged(int)), this, SLOT(TimeChanged(int))); - AdvSceneSwitcher::populateMediaSelection(mediaSources); + populateMediaSelection(mediaSources); populateMediaStates(states); populateTimeRestrictions(timeRestrictions); diff --git a/src/switch-network.cpp b/src/switch-network.cpp index 865e4bf0..232cb7ee 100644 --- a/src/switch-network.cpp +++ b/src/switch-network.cpp @@ -17,13 +17,16 @@ Most of this code is based on https://github.com/Palakis/obs-websocket #define PARAM_CLIENT_ENABLE "ClientEnabled" #define PARAM_CLIENT_PORT "ClientPort" #define PARAM_ADDRESS "Address" -#define PARAM_CLIENT_SENDALL "SendAll" +#define PARAM_CLIENT_SEND_SCENE_CHANGE "SendSceneChange" +#define PARAM_CLIENT_SEND_SCENE_CHANGE_ALL "SendSceneChangeAll" +#define PARAM_CLIENT_SENDPREVIEW "SendPreview" #define RECONNECT_DELAY 10 #define SCENE_ENTRY "scene" #define TRANSITION_ENTRY "transition" #define TRANSITION_DURATION "duration" +#define SET_PREVIEW "preview" using websocketpp::lib::placeholders::_1; using websocketpp::lib::placeholders::_2; @@ -36,7 +39,9 @@ NetworkConfig::NetworkConfig() ClientEnabled(false), Address(""), ClientPort(55555), - SendAll(true) + SendSceneChange(true), + SendSceneChangeAll(true), + SendPreview(true) { } @@ -51,7 +56,11 @@ void NetworkConfig::Load(obs_data_t *obj) ClientEnabled = obs_data_get_bool(obj, PARAM_CLIENT_ENABLE); Address = obs_data_get_string(obj, PARAM_ADDRESS); ClientPort = obs_data_get_int(obj, PARAM_CLIENT_PORT); - SendAll = obs_data_get_bool(obj, PARAM_CLIENT_SENDALL); + SendSceneChange = + obs_data_get_bool(obj, PARAM_CLIENT_SEND_SCENE_CHANGE); + SendSceneChangeAll = + obs_data_get_bool(obj, PARAM_CLIENT_SEND_SCENE_CHANGE_ALL); + SendPreview = obs_data_get_bool(obj, PARAM_CLIENT_SENDPREVIEW); } void NetworkConfig::Save(obs_data_t *obj) @@ -63,7 +72,10 @@ void NetworkConfig::Save(obs_data_t *obj) obs_data_set_bool(obj, PARAM_CLIENT_ENABLE, ClientEnabled); obs_data_set_string(obj, PARAM_ADDRESS, Address.c_str()); obs_data_set_int(obj, PARAM_CLIENT_PORT, ClientPort); - obs_data_set_bool(obj, PARAM_CLIENT_SENDALL, SendAll); + obs_data_set_bool(obj, PARAM_CLIENT_SEND_SCENE_CHANGE, SendSceneChange); + obs_data_set_bool(obj, PARAM_CLIENT_SEND_SCENE_CHANGE_ALL, + SendSceneChangeAll); + obs_data_set_bool(obj, PARAM_CLIENT_SENDPREVIEW, SendPreview); } void NetworkConfig::SetDefaults(obs_data_t *obj) @@ -75,7 +87,11 @@ void NetworkConfig::SetDefaults(obs_data_t *obj) obs_data_set_default_bool(obj, PARAM_CLIENT_ENABLE, ClientEnabled); obs_data_set_default_string(obj, PARAM_ADDRESS, Address.c_str()); obs_data_set_default_int(obj, PARAM_CLIENT_PORT, ClientPort); - obs_data_set_default_bool(obj, PARAM_CLIENT_SENDALL, SendAll); + obs_data_set_default_bool(obj, PARAM_CLIENT_SEND_SCENE_CHANGE, + SendSceneChange); + obs_data_set_default_bool(obj, PARAM_CLIENT_SEND_SCENE_CHANGE_ALL, + SendSceneChangeAll); + obs_data_set_default_bool(obj, PARAM_CLIENT_SENDPREVIEW, SendPreview); } std::string NetworkConfig::GetClientUri() @@ -83,6 +99,21 @@ std::string NetworkConfig::GetClientUri() return "ws://" + Address + ":" + std::to_string(ClientPort); } +bool NetworkConfig::ShouldSendSceneChange() +{ + return ServerEnabled && SendSceneChange; +} + +bool NetworkConfig::ShouldSendFrontendSceneChange() +{ + return ShouldSendSceneChange() && SendSceneChangeAll; +} + +bool NetworkConfig::ShouldSendPrviewSceneChange() +{ + return ServerEnabled && SendPreview; +} + WSServer::WSServer() : QObject(nullptr), _connections(), _clMutex(QMutex::Recursive) { @@ -192,7 +223,7 @@ void WSServer::stop() blog(LOG_INFO, "server stopped successfully"); } -void WSServer::sendMessage(sceneSwitchInfo sceneSwitch) +void WSServer::sendMessage(sceneSwitchInfo sceneSwitch, bool preview) { if (!sceneSwitch.scene) { return; @@ -204,6 +235,7 @@ void WSServer::sendMessage(sceneSwitchInfo sceneSwitch) obs_data_set_string(data, TRANSITION_ENTRY, GetWeakSourceName(sceneSwitch.transition).c_str()); obs_data_set_int(data, TRANSITION_DURATION, sceneSwitch.duration); + obs_data_set_bool(data, SET_PREVIEW, preview); std::string message = obs_data_get_json(data); obs_data_release(data); @@ -248,7 +280,8 @@ std::string processMessage(std::string payload) if (!obs_data_has_user_value(data, SCENE_ENTRY) || !obs_data_has_user_value(data, TRANSITION_ENTRY) || - !obs_data_has_user_value(data, TRANSITION_DURATION)) { + !obs_data_has_user_value(data, TRANSITION_DURATION) || + !obs_data_has_user_value(data, SET_PREVIEW)) { return "missing request parameters"; } @@ -256,6 +289,7 @@ std::string processMessage(std::string payload) std::string transitionName = obs_data_get_string(data, TRANSITION_ENTRY); int duration = obs_data_get_int(data, TRANSITION_DURATION); + bool preview = obs_data_get_bool(data, SET_PREVIEW); obs_data_release(data); @@ -271,8 +305,11 @@ std::string processMessage(std::string payload) ret += " - ignoring invalid transition: '" + transitionName + "'"; } - - switchScene({scene, transition, duration}); + if (preview) { + switchPreviewScene(scene); + } else { + switchScene({scene, transition, duration}); + } return ret; } @@ -478,7 +515,12 @@ void AdvSceneSwitcher::setupNetworkTab() ui->clientSettings->setChecked(switcher->networkConfig.ClientEnabled); ui->clientHostname->setText(switcher->networkConfig.Address.c_str()); ui->clientPort->setValue(switcher->networkConfig.ClientPort); - ui->restrictSend->setChecked(!switcher->networkConfig.SendAll); + ui->sendSceneChange->setChecked( + switcher->networkConfig.SendSceneChange); + ui->restrictSend->setChecked( + !switcher->networkConfig.SendSceneChangeAll); + ui->sendPreview->setChecked(switcher->networkConfig.SendPreview); + ui->restrictSend->setDisabled(!switcher->networkConfig.SendSceneChange); QTimer *statusTimer = new QTimer(this); connect(statusTimer, SIGNAL(timeout()), this, @@ -592,6 +634,17 @@ void AdvSceneSwitcher::on_clientPort_valueChanged(int value) switcher->networkConfig.ClientPort = value; } +void AdvSceneSwitcher::on_sendSceneChange_stateChanged(int state) +{ + if (loading) { + return; + } + + std::lock_guard lock(switcher->m); + switcher->networkConfig.SendSceneChange = state; + ui->restrictSend->setDisabled(!state); +} + void AdvSceneSwitcher::on_restrictSend_stateChanged(int state) { if (loading) { @@ -599,7 +652,17 @@ void AdvSceneSwitcher::on_restrictSend_stateChanged(int state) } std::lock_guard lock(switcher->m); - switcher->networkConfig.SendAll = !state; + switcher->networkConfig.SendSceneChangeAll = !state; +} + +void AdvSceneSwitcher::on_sendPreview_stateChanged(int state) +{ + if (loading) { + return; + } + + std::lock_guard lock(switcher->m); + switcher->networkConfig.SendPreview = state; } void AdvSceneSwitcher::on_clientReconnect_clicked() diff --git a/src/switch-pause.cpp b/src/switch-pause.cpp index 723a9f03..3a4511eb 100644 --- a/src/switch-pause.cpp +++ b/src/switch-pause.cpp @@ -222,7 +222,6 @@ void SwitcherData::savePauseSwitches(obs_data_t *obj) obs_data_release(array_obj); } obs_data_set_array(obj, "pauseEntries", pauseScenesArray); - obs_data_set_int(obj, "oldPauseValuesImported", 1); obs_data_array_release(pauseScenesArray); } @@ -265,7 +264,9 @@ void AdvSceneSwitcher::setupPauseTab() } if (switcher->pauseEntries.size() == 0) { - addPulse = PulseWidget(ui->pauseAdd, QColor(Qt::green)); + if (!switcher->disableHints) { + addPulse = PulseWidget(ui->pauseAdd, QColor(Qt::green)); + } ui->pauseHelp->setVisible(true); } else { ui->pauseHelp->setVisible(false); @@ -316,7 +317,7 @@ PauseEntryWidget::PauseEntryWidget(QWidget *parent, PauseEntry *s) populatePauseTypes(pauseTypes); populatePauseTargets(pauseTargets); - AdvSceneSwitcher::populateWindowSelection(windows); + populateWindowSelection(windows); windows->setEditable(true); windows->setMaxVisibleItems(20); diff --git a/src/switch-random.cpp b/src/switch-random.cpp index 920613c7..b45f1790 100644 --- a/src/switch-random.cpp +++ b/src/switch-random.cpp @@ -125,15 +125,20 @@ void AdvSceneSwitcher::setupRandomTab() } if (switcher->randomSwitches.size() == 0) { - addPulse = PulseWidget(ui->randomAdd, QColor(Qt::green)); + if (!switcher->disableHints) { + addPulse = + PulseWidget(ui->randomAdd, QColor(Qt::green)); + } ui->randomHelp->setVisible(true); } else { ui->randomHelp->setVisible(false); } if (switcher->switchIfNotMatching != RANDOM_SWITCH) { - PulseWidget(ui->randomDisabledWarning, QColor(Qt::red), - QColor(0, 0, 0, 0), "QLabel "); + if (!switcher->disableHints) { + PulseWidget(ui->randomDisabledWarning, QColor(Qt::red), + QColor(0, 0, 0, 0), "QLabel "); + } } else { ui->randomDisabledWarning->setVisible(false); } diff --git a/src/switch-screen-region.cpp b/src/switch-screen-region.cpp index 7d140f68..99d4874c 100644 --- a/src/switch-screen-region.cpp +++ b/src/switch-screen-region.cpp @@ -245,7 +245,10 @@ void AdvSceneSwitcher::setupRegionTab() } if (switcher->screenRegionSwitches.size() == 0) { - addPulse = PulseWidget(ui->screenRegionAdd, QColor(Qt::green)); + if (!switcher->disableHints) { + addPulse = PulseWidget(ui->screenRegionAdd, + QColor(Qt::green)); + } ui->regionHelp->setVisible(true); } else { ui->regionHelp->setVisible(false); @@ -320,8 +323,8 @@ ScreenRegionWidget::ScreenRegionWidget(QWidget *parent, ScreenRegionSwitch *s) QWidget::connect(maxY, SIGNAL(valueChanged(int)), this, SLOT(MaxYChanged(int))); - AdvSceneSwitcher::populateSceneSelection( - excludeScenes, false, false, true, + populateSceneSelection( + excludeScenes, false, false, nullptr, true, obs_module_text( "AdvSceneSwitcher.screenRegionTab.excludeScenes.None"), true); diff --git a/src/switch-sequence.cpp b/src/switch-sequence.cpp index abdb675a..a9889845 100644 --- a/src/switch-sequence.cpp +++ b/src/switch-sequence.cpp @@ -289,7 +289,10 @@ void AdvSceneSwitcher::setupSequenceTab() } if (switcher->sceneSequenceSwitches.size() == 0) { - addPulse = PulseWidget(ui->sceneSequenceAdd, QColor(Qt::green)); + if (!switcher->disableHints) { + addPulse = PulseWidget(ui->sceneSequenceAdd, + QColor(Qt::green)); + } ui->sequenceHelp->setVisible(true); } else { ui->sequenceHelp->setVisible(false); @@ -440,8 +443,6 @@ void SceneSequenceSwitch::prepareUninterruptibleMatch( { int dur = delay.seconds * 1000; if (dur > 0) { - switcher->waitScene = obs_weak_source_get_source(currentScene); - obs_source_release(switcher->waitScene); linger = dur; } } @@ -588,7 +589,7 @@ SequenceWidget::SequenceWidget(QWidget *parent, SceneSequenceSwitch *s, QWidget::connect(reduce, SIGNAL(clicked()), this, SLOT(ReduceClicked())); - AdvSceneSwitcher::populateSceneSelection(startScenes, false); + populateSceneSelection(startScenes); interruptible->setToolTip(obs_module_text( "AdvSceneSwitcher.sceneSequenceTab.interruptibleHint")); diff --git a/src/switch-time.cpp b/src/switch-time.cpp index 748aa951..1e643ff7 100644 --- a/src/switch-time.cpp +++ b/src/switch-time.cpp @@ -191,7 +191,9 @@ void AdvSceneSwitcher::setupTimeTab() } if (switcher->timeSwitches.size() == 0) { - addPulse = PulseWidget(ui->timeAdd, QColor(Qt::green)); + if (!switcher->disableHints) { + addPulse = PulseWidget(ui->timeAdd, QColor(Qt::green)); + } ui->timeHelp->setVisible(true); } else { ui->timeHelp->setVisible(false); diff --git a/src/switch-transitions.cpp b/src/switch-transitions.cpp index 494804de..05aed606 100644 --- a/src/switch-transitions.cpp +++ b/src/switch-transitions.cpp @@ -240,7 +240,7 @@ std::pair getNextTransition(obs_weak_source_t *scene1, return std::make_pair(ws, duration); } -void overwriteTransitionOverride(sceneSwitchInfo ssi, transitionData &td) +void overwriteTransitionOverride(const sceneSwitchInfo &ssi, transitionData &td) { obs_source_t *scene = obs_weak_source_get_source(ssi.scene); obs_data_t *data = obs_source_get_private_settings(scene); @@ -257,7 +257,7 @@ void overwriteTransitionOverride(sceneSwitchInfo ssi, transitionData &td) obs_source_release(scene); } -void restoreTransitionOverride(obs_source_t *scene, transitionData td) +void restoreTransitionOverride(obs_source_t *scene, const transitionData &td) { obs_data_t *data = obs_source_get_private_settings(scene); @@ -267,7 +267,7 @@ void restoreTransitionOverride(obs_source_t *scene, transitionData td) obs_data_release(data); } -void setNextTransition(sceneSwitchInfo &sceneSwitch, +void setNextTransition(const sceneSwitchInfo &sceneSwitch, obs_source_t *currentSource, transitionData &td) { // Priority: @@ -495,7 +495,7 @@ TransitionSwitchWidget::TransitionSwitchWidget(QWidget *parent, QWidget::connect(duration, SIGNAL(valueChanged(double)), this, SLOT(DurationChanged(double))); - AdvSceneSwitcher::populateSceneSelection(scenes2, false); + populateSceneSelection(scenes2); if (s) { scenes2->setCurrentText(GetWeakSourceName(s->scene2).c_str()); diff --git a/src/switch-video.cpp b/src/switch-video.cpp index 95ef3041..8ec1cd15 100644 --- a/src/switch-video.cpp +++ b/src/switch-video.cpp @@ -194,7 +194,9 @@ void AdvSceneSwitcher::setupVideoTab() } if (switcher->videoSwitches.size() == 0) { - addPulse = PulseWidget(ui->videoAdd, QColor(Qt::green)); + if (!switcher->disableHints) { + addPulse = PulseWidget(ui->videoAdd, QColor(Qt::green)); + } ui->videoHelp->setVisible(true); } else { ui->videoHelp->setVisible(false); @@ -399,10 +401,7 @@ VideoSwitchWidget::VideoSwitchWidget(QWidget *parent, VideoSwitch *s) QWidget::connect(ignoreInactiveSource, SIGNAL(stateChanged(int)), this, SLOT(IgnoreInactiveChanged(int))); - // TODO: - // Figure out why scene do not work for "match exactly". - // Until then do not allow selecting scenes - AdvSceneSwitcher::populateVideoSelection(videoSources, false); + populateVideoSelection(videoSources); populateConditionSelection(condition); if (s) { diff --git a/src/switch-window.cpp b/src/switch-window.cpp index a9ee073c..ebd83322 100644 --- a/src/switch-window.cpp +++ b/src/switch-window.cpp @@ -348,7 +348,10 @@ void AdvSceneSwitcher::setupTitleTab() } if (switcher->windowSwitches.size() == 0) { - addPulse = PulseWidget(ui->windowAdd, QColor(Qt::green)); + if (!switcher->disableHints) { + addPulse = + PulseWidget(ui->windowAdd, QColor(Qt::green)); + } ui->windowHelp->setVisible(true); } else { ui->windowHelp->setVisible(false); @@ -387,13 +390,7 @@ void WindowSwitch::load(obs_data_t *obj) window = obs_data_get_string(obj, "windowTitle"); fullscreen = obs_data_get_bool(obj, "fullscreen"); -#if __APPLE__ - // TODO: - // not implemented on MacOS as I cannot test it - maximized = false; -#else maximized = obs_data_get_bool(obj, "maximized"); -#endif focus = obs_data_get_bool(obj, "focus") || !obs_data_has_user_value(obj, "focus"); } @@ -418,16 +415,10 @@ WindowSwitchWidget::WindowSwitchWidget(QWidget *parent, WindowSwitch *s) QWidget::connect(focused, SIGNAL(stateChanged(int)), this, SLOT(FocusChanged(int))); - AdvSceneSwitcher::populateWindowSelection(windows); + populateWindowSelection(windows); windows->setEditable(true); windows->setMaxVisibleItems(20); -#if __APPLE__ - // TODO: - // not implemented on MacOS as I cannot test it - maximized->setDisabled(true); - maximized->setVisible(false); -#endif if (s) { windows->setCurrentText(s->window.c_str()); diff --git a/src/switcher-data-structs.cpp b/src/switcher-data-structs.cpp index b889bd69..b892494f 100644 --- a/src/switcher-data-structs.cpp +++ b/src/switcher-data-structs.cpp @@ -124,7 +124,8 @@ bool SwitcherData::versionChanged(obs_data_t *obj, std::string currentVersion) return previousVersion != currentVersion; } -void SwitcherData::saveVersion(obs_data_t *obj, std::string currentVersion) +void SwitcherData::saveVersion(obs_data_t *obj, + const std::string ¤tVersion) { obs_data_set_string(obj, "version", currentVersion.c_str()); } diff --git a/src/utility.cpp b/src/utility.cpp new file mode 100644 index 00000000..6a448ca9 --- /dev/null +++ b/src/utility.cpp @@ -0,0 +1,593 @@ +#include "headers/utility.hpp" +#include "headers/platform-funcs.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +bool WeakSourceValid(obs_weak_source_t *ws) +{ + obs_source_t *source = obs_weak_source_get_source(ws); + if (source) { + obs_source_release(source); + } + return !!source; +} + +std::string GetWeakSourceName(obs_weak_source_t *weak_source) +{ + std::string name; + + obs_source_t *source = obs_weak_source_get_source(weak_source); + if (source) { + name = obs_source_get_name(source); + obs_source_release(source); + } + + return name; +} + +OBSWeakSource GetWeakSourceByName(const char *name) +{ + OBSWeakSource weak; + obs_source_t *source = obs_get_source_by_name(name); + if (source) { + weak = obs_source_get_weak_source(source); + obs_weak_source_release(weak); + obs_source_release(source); + } + + return weak; +} + +OBSWeakSource GetWeakSourceByQString(const QString &name) +{ + return GetWeakSourceByName(name.toUtf8().constData()); +} + +OBSWeakSource GetWeakTransitionByName(const char *transitionName) +{ + OBSWeakSource weak; + obs_source_t *source = nullptr; + + if (strcmp(transitionName, "Default") == 0) { + source = obs_frontend_get_current_transition(); + weak = obs_source_get_weak_source(source); + obs_source_release(source); + obs_weak_source_release(weak); + return weak; + } + + obs_frontend_source_list *transitions = new obs_frontend_source_list(); + obs_frontend_get_transitions(transitions); + bool match = false; + + for (size_t i = 0; i < transitions->sources.num; i++) { + const char *name = + obs_source_get_name(transitions->sources.array[i]); + if (strcmp(transitionName, name) == 0) { + match = true; + source = transitions->sources.array[i]; + break; + } + } + + if (match) { + weak = obs_source_get_weak_source(source); + obs_weak_source_release(weak); + } + obs_frontend_source_list_free(transitions); + + return weak; +} + +OBSWeakSource GetWeakTransitionByQString(const QString &name) +{ + return GetWeakTransitionByName(name.toUtf8().constData()); +} + +OBSWeakSource GetWeakFilterByName(OBSWeakSource source, const char *name) +{ + OBSWeakSource weak; + auto s = obs_weak_source_get_source(source); + if (s) { + auto filterSource = obs_source_get_filter_by_name(s, name); + weak = obs_source_get_weak_source(filterSource); + obs_weak_source_release(weak); + obs_source_release(filterSource); + obs_source_release(s); + } + return weak; +} + +OBSWeakSource GetWeakFilterByQString(OBSWeakSource source, const QString &name) +{ + return GetWeakFilterByName(source, name.toUtf8().constData()); +} + +std::string +getNextDelim(const std::string &text, + std::unordered_map placeholders) +{ + size_t pos = std::string::npos; + std::string res = ""; + + for (const auto &ph : placeholders) { + size_t newPos = text.find(ph.first); + if (newPos <= pos) { + pos = newPos; + res = ph.first; + } + } + + if (pos == std::string::npos) { + return ""; + } + + return res; +} + +void placeWidgets(std::string text, QBoxLayout *layout, + std::unordered_map placeholders, + bool addStretch) +{ + std::vector> labelsWidgetsPairs; + + std::string delim = getNextDelim(text, placeholders); + while (delim != "") { + size_t pos = text.find(delim); + if (pos != std::string::npos) { + labelsWidgetsPairs.emplace_back(text.substr(0, pos), + placeholders[delim]); + text.erase(0, pos + delim.length()); + } + delim = getNextDelim(text, placeholders); + } + + if (text != "") { + labelsWidgetsPairs.emplace_back(text, nullptr); + } + + for (auto &lw : labelsWidgetsPairs) { + if (lw.first != "") { + layout->addWidget(new QLabel(lw.first.c_str())); + } + if (lw.second) { + layout->addWidget(lw.second); + } + } + if (addStretch) { + layout->addStretch(); + } +} + +void clearLayout(QLayout *layout) +{ + QLayoutItem *item; + while ((item = layout->takeAt(0))) { + if (item->layout()) { + clearLayout(item->layout()); + delete item->layout(); + } + if (item->widget()) { + delete item->widget(); + } + delete item; + } +} + +bool compareIgnoringLineEnding(QString &s1, QString &s2) +{ + // Let QT deal with different types of lineendings + QTextStream s1stream(&s1); + QTextStream s2stream(&s2); + + while (!s1stream.atEnd() || !s2stream.atEnd()) { + QString s1s = s1stream.readLine(); + QString s2s = s2stream.readLine(); + if (s1s != s2s) { + return false; + } + } + + if (!s1stream.atEnd() && !s2stream.atEnd()) { + return false; + } + + return true; +} + +std::string getSourceSettings(OBSWeakSource ws) +{ + auto s = obs_weak_source_get_source(ws); + obs_data_t *data = obs_source_get_settings(s); + std::string settings = obs_data_get_json(data); + obs_data_release(data); + obs_source_release(s); + + return settings; +} + +std::string getDataFilePath(const std::string &file) +{ + std::string root_path = obs_get_module_data_path(obs_current_module()); + if (!root_path.empty()) { + return root_path + "/" + file; + } + return ""; +} + +bool DisplayMessage(const QString &msg, bool question) +{ + if (question) { + QMessageBox::StandardButton reply; + reply = QMessageBox::question( + nullptr, "Advanced Scene Switcher", msg, + QMessageBox::Yes | QMessageBox::No); + if (reply == QMessageBox::Yes) { + return true; + } else { + return false; + } + } else { + QMessageBox Msgbox; + Msgbox.setWindowTitle("Advanced Scene Switcher"); + Msgbox.setText(msg); + Msgbox.exec(); + } + + return false; +} + +void addSelectionEntry(QComboBox *sel, const char *description, bool selectable, + const char *tooltip) +{ + sel->insertItem(0, description); + + if (strcmp(tooltip, "") != 0) { + sel->setItemData(0, tooltip, Qt::ToolTipRole); + } + + QStandardItemModel *model = + qobject_cast(sel->model()); + QModelIndex firstIndex = + model->index(0, sel->modelColumn(), sel->rootModelIndex()); + QStandardItem *firstItem = model->itemFromIndex(firstIndex); + if (!selectable) { + firstItem->setSelectable(false); + firstItem->setEnabled(false); + } +} + +void populateSourceSelection(QComboBox *list, bool addSelect) +{ + auto enumSourcesWithSources = [](void *param, obs_source_t *source) { + if (!source) { + return true; + } + QComboBox *list = reinterpret_cast(param); + list->addItem(obs_source_get_name(source)); + return true; + }; + + obs_enum_sources(enumSourcesWithSources, list); + + list->model()->sort(0); + if (addSelect) { + addSelectionEntry( + list, obs_module_text("AdvSceneSwitcher.selectSource"), + false); + } + list->setCurrentIndex(0); +} + +void populateTransitionSelection(QComboBox *sel, bool addCurrent, + bool addSelect, bool selectable) +{ + + obs_frontend_source_list *transitions = new obs_frontend_source_list(); + obs_frontend_get_transitions(transitions); + + for (size_t i = 0; i < transitions->sources.num; i++) { + const char *name = + obs_source_get_name(transitions->sources.array[i]); + sel->addItem(name); + } + + obs_frontend_source_list_free(transitions); + + sel->model()->sort(0); + + if (addCurrent) { + sel->insertItem( + 0, + obs_module_text("AdvSceneSwitcher.currentTransition")); + } + + if (addSelect) { + addSelectionEntry( + sel, + obs_module_text("AdvSceneSwitcher.selectTransition"), + selectable); + } + sel->setCurrentIndex(0); +} + +void populateWindowSelection(QComboBox *sel, bool addSelect) +{ + + std::vector windows; + GetWindowList(windows); + + for (std::string &window : windows) { + sel->addItem(window.c_str()); + } + + sel->model()->sort(0); + if (addSelect) { + addSelectionEntry( + sel, obs_module_text("AdvSceneSwitcher.selectWindow")); + } + sel->setCurrentIndex(0); +#ifdef WIN32 + sel->setItemData(0, obs_module_text("AdvSceneSwitcher.selectWindowTip"), + Qt::ToolTipRole); +#endif +} + +void populateAudioSelection(QComboBox *sel, bool addSelect) +{ + + auto sourceEnum = [](void *data, obs_source_t *source) -> bool /* -- */ + { + std::vector *list = + reinterpret_cast *>(data); + uint32_t flags = obs_source_get_output_flags(source); + + if ((flags & OBS_SOURCE_AUDIO) != 0) { + list->push_back(obs_source_get_name(source)); + } + return true; + }; + + std::vector audioSources; + obs_enum_sources(sourceEnum, &audioSources); + + for (std::string &source : audioSources) { + sel->addItem(source.c_str()); + } + + sel->model()->sort(0); + if (addSelect) { + addSelectionEntry( + sel, + obs_module_text("AdvSceneSwitcher.selectAudioSource"), + false, + obs_module_text( + "AdvSceneSwitcher.invaildEntriesWillNotBeSaved")); + } + sel->setCurrentIndex(0); +} + +void populateVideoSelection(QComboBox *sel, bool addSelect) +{ + + auto sourceEnum = [](void *data, obs_source_t *source) -> bool /* -- */ + { + std::vector *list = + reinterpret_cast *>(data); + uint32_t flags = obs_source_get_output_flags(source); + std::string test = obs_source_get_name(source); + if ((flags & (OBS_SOURCE_VIDEO | OBS_SOURCE_ASYNC)) != 0) { + list->push_back(obs_source_get_name(source)); + } + return true; + }; + + std::vector videoSources; + obs_enum_sources(sourceEnum, &videoSources); + sort(videoSources.begin(), videoSources.end()); + for (std::string &source : videoSources) { + sel->addItem(source.c_str()); + } + + sel->model()->sort(0); + if (addSelect) { + addSelectionEntry( + sel, + obs_module_text("AdvSceneSwitcher.selectVideoSource"), + false, + obs_module_text( + "AdvSceneSwitcher.invaildEntriesWillNotBeSaved")); + } + sel->setCurrentIndex(0); +} + +void populateMediaSelection(QComboBox *sel, bool addSelect) +{ + auto sourceEnum = [](void *data, obs_source_t *source) -> bool /* -- */ + { + std::vector *list = + reinterpret_cast *>(data); + std::string sourceId = obs_source_get_id(source); + if (sourceId.compare("ffmpeg_source") == 0 || + sourceId.compare("vlc_source") == 0) { + list->push_back(obs_source_get_name(source)); + } + return true; + }; + + std::vector mediaSources; + obs_enum_sources(sourceEnum, &mediaSources); + for (std::string &source : mediaSources) { + sel->addItem(source.c_str()); + } + + sel->model()->sort(0); + if (addSelect) { + addSelectionEntry( + sel, + obs_module_text("AdvSceneSwitcher.selectMediaSource"), + false, + obs_module_text( + "AdvSceneSwitcher.invaildEntriesWillNotBeSaved")); + } + sel->setCurrentIndex(0); +} + +void populateProcessSelection(QComboBox *sel, bool addSelect) +{ + QStringList processes; + GetProcessList(processes); + processes.sort(); + for (QString &process : processes) { + sel->addItem(process); + } + + sel->model()->sort(0); + if (addSelect) { + addSelectionEntry( + sel, obs_module_text("AdvSceneSwitcher.selectProcess")); + } + sel->setCurrentIndex(0); +} + +void populateSceneSelection(QComboBox *sel, bool addPrevious, + bool addSceneGroup, + std::deque *sceneGroups, bool addSelect, + std::string selectText, bool selectable) +{ + BPtr scenes = obs_frontend_get_scene_names(); + char **temp = scenes; + while (*temp) { + const char *name = *temp; + sel->addItem(name); + temp++; + } + + if (addPrevious) { + sel->addItem(obs_module_text( + "AdvSceneSwitcher.selectPreviousScene")); + } + + if (addSceneGroup && sceneGroups) { + for (auto &sg : *sceneGroups) { + sel->addItem(QString::fromStdString(sg.name)); + } + } + + sel->model()->sort(0); + if (addSelect) { + if (selectText.empty()) { + addSelectionEntry( + sel, + obs_module_text("AdvSceneSwitcher.selectScene"), + selectable, + obs_module_text( + "AdvSceneSwitcher.invaildEntriesWillNotBeSaved")); + } else { + addSelectionEntry(sel, selectText.c_str(), selectable); + } + } + sel->setCurrentIndex(0); +} + +QMetaObject::Connection PulseWidget(QWidget *widget, QColor endColor, + QColor startColor, QString specifier) +{ + widget->setStyleSheet(specifier + "{ \ + border-style: outset; \ + border-width: 2px; \ + border-radius: 10px; \ + border-color: rgb(0,0,0,0) \ + }"); + + QGraphicsColorizeEffect *eEffect = new QGraphicsColorizeEffect(widget); + widget->setGraphicsEffect(eEffect); + QPropertyAnimation *paAnimation = + new QPropertyAnimation(eEffect, "color"); + paAnimation->setStartValue(startColor); + paAnimation->setEndValue(endColor); + paAnimation->setDuration(1000); + // Play backwards to return to original state on timer end + paAnimation->setDirection(QAbstractAnimation::Backward); + + auto con = QWidget::connect( + paAnimation, &QPropertyAnimation::finished, [paAnimation]() { + QTimer::singleShot(1000, [paAnimation] { + paAnimation->start(); + }); + }); + + paAnimation->start(); + + return con; +} + +void listAddClicked(QListWidget *list, QWidget *newWidget, + QPushButton *addButton, + QMetaObject::Connection *addHighlight) +{ + if (!list || !newWidget) { + blog(LOG_WARNING, + "listAddClicked called without valid list or widget"); + return; + } + + if (addButton && addHighlight) { + addButton->disconnect(*addHighlight); + } + + QListWidgetItem *item; + item = new QListWidgetItem(list); + list->addItem(item); + item->setSizeHint(newWidget->minimumSizeHint()); + list->setItemWidget(item, newWidget); + + list->scrollToItem(item); +} + +bool listMoveUp(QListWidget *list) +{ + int index = list->currentRow(); + if (index == -1 || index == 0) { + return false; + } + + QWidget *row = list->itemWidget(list->currentItem()); + QListWidgetItem *itemN = list->currentItem()->clone(); + + list->insertItem(index - 1, itemN); + list->setItemWidget(itemN, row); + + list->takeItem(index + 1); + list->setCurrentRow(index - 1); + return true; +} + +bool listMoveDown(QListWidget *list) +{ + int index = list->currentRow(); + if (index == -1 || index == list->count() - 1) { + return false; + } + + QWidget *row = list->itemWidget(list->currentItem()); + QListWidgetItem *itemN = list->currentItem()->clone(); + + list->insertItem(index + 2, itemN); + list->setItemWidget(itemN, row); + + list->takeItem(index); + list->setCurrentRow(index + 1); + return true; +} diff --git a/src/win/advanced-scene-switcher-win.cpp b/src/win/advanced-scene-switcher-win.cpp index 36e7e6bf..bc045cae 100644 --- a/src/win/advanced-scene-switcher-win.cpp +++ b/src/win/advanced-scene-switcher-win.cpp @@ -145,7 +145,7 @@ HWND getHWNDfromTitle(std::string title) return hwnd; } -bool isMaximized(std::string &title) +bool isMaximized(const std::string &title) { RECT appBounds; MONITORINFO monitorInfo = {0}; @@ -175,7 +175,7 @@ bool isMaximized(std::string &title) return false; } -bool isFullscreen(std::string &title) +bool isFullscreen(const std::string &title) { RECT appBounds; MONITORINFO monitorInfo = {0};