Compare commits

..

40 Commits

Author SHA1 Message Date
WarmUpTill
7cc44b4470 Fix folder watch condition filter handling 2024-05-19 03:46:53 +02:00
WarmUpTill
54356d9410 Add tooltip to macro property selection in variable action 2024-05-19 01:44:10 +02:00
WarmUpTill
defbdf8b7a Add folder condition
It allows you to watch for chagnes in a given folder
2024-05-19 01:44:10 +02:00
WarmUpTill
a01af6cfc3 Adapt code to be compatible with older MSVC redistributable versions 2024-05-18 23:31:04 +02:00
WarmUpTill
4d9c7f1054 Fix typo 2024-05-18 23:31:04 +02:00
WarmUpTill
ced36e2b5b Add nodiscard to sync-helpers 2024-05-18 23:31:04 +02:00
WarmUpTill
d2749f29b0 Add note about limitations of the window focus action 2024-05-14 20:23:36 +02:00
WarmUpTill
b78c8bc1de Assume minimum volume when no volume update was received within 250ms
Without this timeout the peak volume update, which was received last,
would be used permanently until the next update arrives.
This might only take place when the source produces audio output again.
2024-05-14 19:12:49 +02:00
WarmUpTill
cc4a3560d8 Fix "Window" actions not being applied to OBS windows 2024-05-12 01:29:06 +02:00
WarmUpTill
3ab2ea9e66 Only update Twitch tab content when switching to it
This should reduce the potential lag introduced when querying the token
status using the Twitch API
2024-05-12 01:28:51 +02:00
WarmUpTill
69b82ee2a3 Fix audio condition incorrectly reporting very low volume levels
OBS might very rarely not update _peak quickly enough when very low
intervals are configured on the General tab.
In that case _peak might be set to negative infinity still, which will
result in unexpected behavior, so we use the previously valid peak value
instead.
2024-05-10 15:11:20 +02:00
WarmUpTill
f14e6bf252 Fix crash when access to midi devices is denied 2024-05-10 15:11:20 +02:00
WarmUpTill
e51ae79b4c Improve window action
* The focus action no longer restores maximized windows
* The focus, maximize, minimize, and close actions will be performed on
  all handles matching the given window title
2024-05-10 15:11:20 +02:00
WarmUpTill
8f55856fd5 Fix macro segment highlighting not being in sync
Each macro segment had its own internal timer to handle highlighting.
This handing was now moved centrally to the macro tab.
2024-05-10 15:11:20 +02:00
WarmUpTill
a360d53419 Enable right-click to select macro segments
This will be used to enable copy-paste via the context menu available
when right-clicking a macro segment
2024-05-10 15:11:20 +02:00
WarmUpTill
d28164d02f Fix initial replay buffer save not triggering condition 2024-05-10 15:11:20 +02:00
WarmUpTill
f723212394 Add copy-paste support for individual macro segments 2024-05-10 15:11:20 +02:00
WarmUpTill
a0cb08d18f Fix potential crash when creating actions 2024-05-10 15:11:20 +02:00
WarmUpTill
d18044b764 Add handling for fading of else-controls 2024-05-10 15:11:20 +02:00
WarmUpTill
406b5d61f5 Add option to conditionally run action of other macros 2024-05-10 15:11:20 +02:00
WarmUpTill
9a4eef4a83 Adjust busy loop sleep to 10ms
This is now in line with the new minimum interval value
2024-05-10 15:11:20 +02:00
WarmUpTill
efaf9a2ef3 Add option to check if hotkey is released
This was already possible previously by inverting the logic with a
"not", however, this should it make it more obvious that this type of
check is possible.
2024-05-09 23:19:08 +02:00
WarmUpTill
edb2952fd6 Add temp vars to file condition 2024-05-09 22:14:43 +02:00
Denilson Sá Maia
088828a351 Fixed typo 2024-05-03 20:04:22 +02:00
WarmUpTill
e359b7fa00 Add Twitch connection tab 2024-05-01 19:44:57 +02:00
WarmUpTill
44fc5177d6 Add action queue tab 2024-05-01 19:44:57 +02:00
WarmUpTill
604a27141f Move files 2024-05-01 19:44:57 +02:00
WarmUpTill
fd65a84d7d Improve buildnumber handling 2024-05-01 19:44:57 +02:00
WarmUpTill
966b389807 Rework variable tab to use tab helpers 2024-05-01 19:44:57 +02:00
WarmUpTill
a6bcce5a63 Add websocket connections tab 2024-05-01 19:44:57 +02:00
WarmUpTill
530fbdd282 Add ResourceTable class
Base class to be used to display items in a table
2024-05-01 19:44:57 +02:00
WarmUpTill
54f1051456 Ease detection of calling obs_module_text() too early 2024-05-01 19:44:57 +02:00
WarmUpTill
d136bf5561 Fix crash when on startup when registering new tab 2024-05-01 19:44:57 +02:00
WarmUpTill
5cba948e02 Decouple plugin init, load, cleanup handling from SwitcherData 2024-05-01 19:44:57 +02:00
WarmUpTill
ab922f6735 Rename and refactor websocket helpers 2024-05-01 19:44:57 +02:00
WarmUpTill
d71c87535f Add RemoveItemsByName()
Helper function to remove Items by name from std::queue of Item
2024-05-01 19:44:57 +02:00
WarmUpTill
ce501bd972 Move / add tabWidget related helper functions 2024-05-01 19:44:57 +02:00
WarmUpTill
ec5944f53c Fix Debian cmake configure issue
The "CI" environment variable was defined but "GITHUB_RUN_ID" was not
resulting in a broken if statement:

if(true AND)

https://github.com/WarmUpTill/SceneSwitcher/issues/1090
2024-04-25 19:31:18 +02:00
WarmUpTill
5e64e0bbaa Disable "psabi" warning for x86 Debian build
https://github.com/WarmUpTill/SceneSwitcher/issues/1091
2024-04-25 19:31:18 +02:00
WarmUpTill
e16f1d732d Fix layout of the "System tray notification" action 2024-04-25 19:30:43 +02:00
80 changed files with 3300 additions and 958 deletions

View File

@@ -128,6 +128,8 @@ target_sources(
lib/macro/macro-ref.hpp
lib/macro/macro-run-button.cpp
lib/macro/macro-run-button.hpp
lib/macro/macro-segment-copy-paste.cpp
lib/macro/macro-segment-copy-paste.hpp
lib/macro/macro-segment-list.cpp
lib/macro/macro-segment-list.hpp
lib/macro/macro-segment-selection.cpp
@@ -145,8 +147,10 @@ target_sources(
# Utility function sources
target_sources(
${LIB_NAME}
PRIVATE lib/utils/action-queue.cpp
lib/utils/action-queue.hpp
PRIVATE lib/queue/action-queue.cpp
lib/queue/action-queue.hpp
lib/queue/action-queue-tab.cpp
lib/queue/action-queue-tab.hpp
lib/utils/backup.cpp
lib/utils/backup.hpp
lib/utils/curl-helper.cpp
@@ -191,6 +195,10 @@ target_sources(
lib/utils/regex-config.hpp
lib/utils/resizing-text-edit.cpp
lib/utils/resizing-text-edit.hpp
lib/utils/resource-table.cpp
lib/utils/resource-table.hpp
lib/utils/resource-table-hotkey-handler.cpp
lib/utils/resource-table-hotkey-handler.hpp
lib/utils/scene-selection.cpp
lib/utils/scene-selection.hpp
lib/utils/scene-switch-helpers.cpp
@@ -217,6 +225,8 @@ target_sources(
lib/utils/switch-button.hpp
lib/utils/sync-helpers.cpp
lib/utils/sync-helpers.hpp
lib/utils/tab-helpers.cpp
lib/utils/tab-helpers.hpp
lib/utils/temp-variable.cpp
lib/utils/temp-variable.hpp
lib/utils/ui-helpers.cpp
@@ -234,6 +244,7 @@ target_sources(
lib/variables/variable-string.cpp
lib/variables/variable-string.hpp
lib/variables/variable-tab.cpp
lib/variables/variable-tab.hpp
lib/variables/variable-text-edit.cpp
lib/variables/variable-text-edit.hpp
lib/variables/variable.cpp
@@ -268,6 +279,7 @@ target_include_directories(
PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/lib"
"${CMAKE_CURRENT_SOURCE_DIR}/lib/legacy"
"${CMAKE_CURRENT_SOURCE_DIR}/lib/macro"
"${CMAKE_CURRENT_SOURCE_DIR}/lib/queue"
"${CMAKE_CURRENT_SOURCE_DIR}/lib/utils"
"${CMAKE_CURRENT_SOURCE_DIR}/lib/variables"
"${CMAKE_CURRENT_BINARY_DIR}/forms")

View File

@@ -12,8 +12,14 @@ if(NOT DEFINED PLUGIN_BUILD_NUMBER AND EXISTS "${_BUILD_NUMBER_CACHE}")
file(READ "${_BUILD_NUMBER_CACHE}" PLUGIN_BUILD_NUMBER)
math(EXPR PLUGIN_BUILD_NUMBER "${PLUGIN_BUILD_NUMBER}+1")
elseif(NOT DEFINED PLUGIN_BUILD_NUMBER)
if($ENV{CI} AND $ENV{GITHUB_RUN_ID})
set(PLUGIN_BUILD_NUMBER "$ENV{GITHUB_RUN_ID}")
if($ENV{CI})
if($ENV{GITHUB_RUN_ID})
set(PLUGIN_BUILD_NUMBER "$ENV{GITHUB_RUN_ID}")
elseif($ENV{GITLAB_RUN_ID})
set(PLUGIN_BUILD_NUMBER "$ENV{GITLAB_RUN_ID}")
else()
set(PLUGIN_BUILD_NUMBER "1")
endif()
else()
set(PLUGIN_BUILD_NUMBER "1")
endif()

View File

@@ -68,9 +68,14 @@ else()
endif()
if(CMAKE_CXX_COMPILER_ID STREQUAL GNU)
# Disable warning for https://github.com/WarmUpTill/SceneSwitcher/issues/1091
add_compile_options(-Wno-error=psabi)
# Disable false-positive warning in GCC 12.1.0 and later
add_compile_options(-Wno-error=maybe-uninitialized)
add_compile_options(-Wno-error=stringop-overflow)
if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 12.1.0)
add_compile_options(-Wno-error=maybe-uninitialized)
add_compile_options(-Wno-error=stringop-overflow)
endif()
# Add warning for infinite recursion (added in GCC 12)
if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 12.0.0)

View File

@@ -287,8 +287,8 @@ AdvSceneSwitcher.condition.sceneOrder.entry="Auf{{scenes}}{{sources}}{{condition
AdvSceneSwitcher.condition.hotkey="Hotkey"
AdvSceneSwitcher.condition.hotkey.name="Makro-Trigger-Hotkey"
AdvSceneSwitcher.condition.hotkey.tip="Hinweis: Die Tastenkombinationen für diesen Hotkey können in den OBS-Einstellungen konfiguriert werden"
AdvSceneSwitcher.condition.hotkey.entry.line1="Hotkey ist gedrückt"
AdvSceneSwitcher.condition.hotkey.entry.line2="Name: {{name}}"
AdvSceneSwitcher.condition.hotkey.entry.keyState="Hotkey ist{{keyState}}"
AdvSceneSwitcher.condition.hotkey.entry.name="Name:{{name}}"
AdvSceneSwitcher.condition.replay="Replay Buffer"
AdvSceneSwitcher.condition.replay.state.stopped="Replay Buffer gestoppt"
AdvSceneSwitcher.condition.replay.state.started="Replay Buffer gestartet"
@@ -469,7 +469,6 @@ AdvSceneSwitcher.action.macro.type.unpause="Nicht mehr pausieren"
AdvSceneSwitcher.action.macro.type.resetCounter="Zähler zurücksetzen"
AdvSceneSwitcher.action.macro.type.run="Aktionen ausführen"
AdvSceneSwitcher.action.macro.type.stop="Aktionen stoppen"
AdvSceneSwitcher.action.macro.entry="{{actions}}{{actionIndex}}{{macros}}"
AdvSceneSwitcher.action.pluginState="Plugin-Status"
AdvSceneSwitcher.action.pluginState.type.stop="Erweiterten Automatischen Szenenwechsler stoppen"
AdvSceneSwitcher.action.pluginState.type.noMatch="Ändern des Nichtübereinstimmungsverhaltens:"

View File

@@ -88,6 +88,47 @@ AdvSceneSwitcher.variableTab.lastChanged.text="%1 seconds ago"
AdvSceneSwitcher.variableTab.lastChanged.text.none="No change since launch"
AdvSceneSwitcher.variableTab.lastChanged.tooltip="Times changed: %1\n\nPrevious value: %2"
; Action Queue Tab
AdvSceneSwitcher.actionQueueTab.title="Action Queues"
AdvSceneSwitcher.actionQueueTab.help="Action queues are executed sequentially but in parallel to the reset of the macro system.\nThe first action added to the queue will be the first one to be processed.\n\nClick on the highlighted plus symbol to add a new queue."
AdvSceneSwitcher.actionQueueTab.queueAddButton.tooltip="Add new action queue"
AdvSceneSwitcher.actionQueueTab.queueRemoveButton.tooltip="Remove selected action queues"
AdvSceneSwitcher.actionQueueTab.name.header="Name"
AdvSceneSwitcher.actionQueueTab.size.header="Size"
AdvSceneSwitcher.actionQueueTab.isRunning.header="Is running"
AdvSceneSwitcher.actionQueueTab.runOnStartup.header="Run on startup"
AdvSceneSwitcher.actionQueueTab.yes="Yes"
AdvSceneSwitcher.actionQueueTab.no="No"
AdvSceneSwitcher.actionQueueTab.removeSingleConnectionPopup.text="Are you sure you want to remove \"%1\"?"
AdvSceneSwitcher.actionQueueTab.removeMultipleConnectionsPopup.text="Are you sure you want to remove %1 action queues?"
; Websocket Connections Tab
AdvSceneSwitcher.websocketConnectionTab.title="Websocket Connections"
AdvSceneSwitcher.websocketConnectionTab.help="Websocket connections can be used to communicate with other OBS instances or programs.\n\nClick on the highlighted plus symbol to add a new connection."
AdvSceneSwitcher.websocketConnectionTab.websocketConnectionAddButton.tooltip="Add new webscoket connection"
AdvSceneSwitcher.websocketConnectionTab.websocketConnectionRemoveButton.tooltip="Remove selected websocket connections"
AdvSceneSwitcher.websocketConnectionTab.name.header="Name"
AdvSceneSwitcher.websocketConnectionTab.address.header="Address"
AdvSceneSwitcher.websocketConnectionTab.port.header="Port"
AdvSceneSwitcher.websocketConnectionTab.protocol.header="Is using OBS protocol?"
AdvSceneSwitcher.websocketConnectionTab.protocol.yes="Yes"
AdvSceneSwitcher.websocketConnectionTab.protocol.no="No"
AdvSceneSwitcher.websocketConnectionTab.removeSingleConnectionPopup.text="Are you sure you want to remove \"%1\"?"
AdvSceneSwitcher.websocketConnectionTab.removeMultipleConnectionsPopup.text="Are you sure you want to remove %1 connections?"
; Twitch Connections Tab
AdvSceneSwitcher.twitchConnectionTab.title="Twitch Connections"
AdvSceneSwitcher.twitchConnectionTab.help="Twitch connections can be used to use Twitch events as triggers to execute actions or perform actions on the linked Twitch account.\n\nClick on the highlighted plus symbol to add a new connection."
AdvSceneSwitcher.twitchConnectionTab.twitchConnectionAddButton.tooltip.tooltip="Add new Twitch connection"
AdvSceneSwitcher.twitchConnectionTab.twitchConnectionRemoveButton.tooltip="Remove selected Twitch connections"
AdvSceneSwitcher.twitchConnectionTab.name.header="Name"
AdvSceneSwitcher.twitchConnectionTab.isValid.header="Is valid?"
AdvSceneSwitcher.twitchConnectionTab.permissionCount.header="Number of permissions"
AdvSceneSwitcher.twitchConnectionTab.yes="Yes"
AdvSceneSwitcher.twitchConnectionTab.no="No"
AdvSceneSwitcher.twitchConnectionTab.removeSingleConnectionPopup.text="Are you sure you want to remove \"%1\"?"
AdvSceneSwitcher.twitchConnectionTab.removeMultipleConnectionsPopup.text="Are you sure you want to remove %1 Twitch connections?"
; Macro Tab
AdvSceneSwitcher.macroTab.title="Macro"
AdvSceneSwitcher.macroTab.macros="Macros"
@@ -136,6 +177,8 @@ AdvSceneSwitcher.macroTab.minimize="Minimize"
AdvSceneSwitcher.macroTab.segment.useCustomLabel="Use custom label"
AdvSceneSwitcher.macroTab.segment.defaultCustomLabel="My label"
AdvSceneSwitcher.macroTab.segment.setCustomLabel="Set label ..."
AdvSceneSwitcher.macroTab.segment.copy="Copy"
AdvSceneSwitcher.macroTab.segment.paste="Paste"
AdvSceneSwitcher.macroTab.highlightSettings="Visual settings"
AdvSceneSwitcher.macroTab.hotkeySettings="Hotkey settings"
AdvSceneSwitcher.macroTab.generalSettings="General settings"
@@ -441,10 +484,12 @@ AdvSceneSwitcher.condition.sceneOrder.type.position="Is at position"
AdvSceneSwitcher.condition.sceneOrder.positionInfo="The position value starts at the bottom with 0 and increases by one for each scene item including the ones in scene groups"
AdvSceneSwitcher.condition.sceneOrder.entry="On{{scenes}}{{sources}}{{conditions}}{{sources2}}{{position}}"
AdvSceneSwitcher.condition.hotkey="Hotkey"
AdvSceneSwitcher.condition.hotkey.pressed="pressed"
AdvSceneSwitcher.condition.hotkey.released="released"
AdvSceneSwitcher.condition.hotkey.name="Macro trigger hotkey"
AdvSceneSwitcher.condition.hotkey.tip="Note: You can configure the keybindings for this hotkey in the OBS settings window"
AdvSceneSwitcher.condition.hotkey.entry.line1="Hotkey is pressed"
AdvSceneSwitcher.condition.hotkey.entry.line2="Name:{{name}}"
AdvSceneSwitcher.condition.hotkey.entry.keyState="Hotkey is{{keyState}}"
AdvSceneSwitcher.condition.hotkey.entry.name="Name:{{name}}"
AdvSceneSwitcher.condition.replay="Replay buffer"
AdvSceneSwitcher.condition.replay.state.stopped="Replay buffer stopped"
AdvSceneSwitcher.condition.replay.state.started="Replay buffer started"
@@ -549,7 +594,7 @@ AdvSceneSwitcher.condition.run="Run"
AdvSceneSwitcher.condition.run.entry="Process exits before timeout of{{timeout}}seconds"
AdvSceneSwitcher.condition.run.entry.exit="{{checkExitCode}}Check for exit code{{exitCode}}"
AdvSceneSwitcher.condition.midi="MIDI"
AdvSceneSwitcher.condition.midi.entry="Mesasge was received from{{device}}which matches:"
AdvSceneSwitcher.condition.midi.entry="Message was received from{{device}}which matches:"
AdvSceneSwitcher.condition.midi.entry.listen="Set MIDI message selection to messages incoming on selected device:{{listenButton}}"
AdvSceneSwitcher.condition.display="Display"
AdvSceneSwitcher.condition.display.type.displayName="Name of connected displays matches"
@@ -638,6 +683,17 @@ AdvSceneSwitcher.condition.clipboard.condition.isImage="Clipboard contains an im
AdvSceneSwitcher.condition.clipboard.condition.isURL="Clipboard contains an URL"
AdvSceneSwitcher.condition.clipboard.condition.matches="Clipboard content matches"
AdvSceneSwitcher.condition.clipboard.condition.entry="{{conditions}}{{regex}}{{urlInfo}}"
AdvSceneSwitcher.condition.folder="Folder watch"
AdvSceneSwitcher.condition.folder.tooltip="This condition type will allow you to monitor the contents of a folder.\nNote that the monitoring will *not* recursively scan for changes in sub directories within directories of the selected folder!\nNote that if there are several changes during a short period of time, some of the changes might not emit this signal.\nHowever, the last change in the sequence of changes always will."
AdvSceneSwitcher.condition.folder.condition.any="Any change happened"
AdvSceneSwitcher.condition.folder.condition.fileAdd="A file was added"
AdvSceneSwitcher.condition.folder.condition.fileChange="A file was modified"
AdvSceneSwitcher.condition.folder.condition.fileRemove="A file was removed"
AdvSceneSwitcher.condition.folder.condition.folderAdd="A directory was added"
AdvSceneSwitcher.condition.folder.condition.folderRemove="A directory was removed"
AdvSceneSwitcher.condition.folder.entry="{{conditions}}in{{folder}}{{tooltip}}"
AdvSceneSwitcher.condition.folder.enableFilter="Only evaluate to true, if the changed path matches a patern"
AdvSceneSwitcher.condition.folder.entry.filter="{{filter}}{{regex}}"
; Macro Actions
AdvSceneSwitcher.action.scene="Switch scene"
@@ -769,12 +825,20 @@ 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.type.run="Run actions"
AdvSceneSwitcher.action.macro.type.run="Run macro"
AdvSceneSwitcher.action.macro.type.run.conditions.ignore="Do not consider condition state"
AdvSceneSwitcher.action.macro.type.run.conditions.true="Only if conditions evaluate to true"
AdvSceneSwitcher.action.macro.type.run.conditions.false="Only if conditions evaluate to false"
AdvSceneSwitcher.action.macro.type.run.actionType.regular="actions"
AdvSceneSwitcher.action.macro.type.run.actionType.else="else-actions"
AdvSceneSwitcher.action.macro.type.run.skipWhenPaused="Skip execution when macro is paused"
AdvSceneSwitcher.action.macro.type.stop="Stop actions"
AdvSceneSwitcher.action.macro.type.disableAction="Disable action"
AdvSceneSwitcher.action.macro.type.enableAction="Enable action"
AdvSceneSwitcher.action.macro.type.toggleAction="Toggle action"
AdvSceneSwitcher.action.macro.entry="{{actions}}{{actionIndex}}{{macros}}"
AdvSceneSwitcher.action.macro.entry.run="{{actions}}{{actionTypes}}of{{macros}}"
AdvSceneSwitcher.action.macro.entry.run.condition="{{conditionBehaviors}}of{{conditionMacros}}"
AdvSceneSwitcher.action.macro.entry.other="{{actions}}{{actionIndex}}{{macros}}"
AdvSceneSwitcher.action.pluginState="Plugin state"
AdvSceneSwitcher.action.pluginState.type.stop="Stop the Advanced Scene Switcher plugin"
AdvSceneSwitcher.action.pluginState.type.noMatch="Change the no-match behaviour:"
@@ -919,6 +983,7 @@ AdvSceneSwitcher.action.variable.type.sceneItemCount="Set to scene item count of
AdvSceneSwitcher.action.variable.type.stringLength="Set to length of string"
AdvSceneSwitcher.action.variable.type.extractJson="Extract json field with name"
AdvSceneSwitcher.action.variable.type.setToTempvar="Set to macro property"
AdvSceneSwitcher.action.variable.type.setToTempvar.help="This action type will allow you to extract values out of segments of the current macro and assign those values to the selected variable.\nFor example, you can get the current scene name from a scene condition and assign this name to a variable."
AdvSceneSwitcher.action.variable.type.sceneItemName="Set to scene item name at index"
AdvSceneSwitcher.action.variable.type.padValue="Pad current value"
AdvSceneSwitcher.action.variable.type.truncateValue="Truncate current value"
@@ -935,7 +1000,7 @@ AdvSceneSwitcher.action.variable.invalidSelection="Invalid selection!"
AdvSceneSwitcher.action.variable.actionNoVariableSupport="Getting variable values from %1 actions is not supported!"
AdvSceneSwitcher.action.variable.conditionNoVariableSupport="Getting variable values from %1 conditions is not supported!"
AdvSceneSwitcher.action.variable.currentSegmentValue="Current value:"
AdvSceneSwitcher.action.variable.entry.other="{{actions}}{{variables}}{{variables2}}{{strValue}}{{numValue}}{{segmentIndex}}{{mathExpression}}{{envVariableName}}{{scenes}}{{tempVars}}{{sceneItemIndex}}{{direction}}{{stringLength}}{{paddingCharSelection}}"
AdvSceneSwitcher.action.variable.entry.other="{{actions}}{{variables}}{{variables2}}{{strValue}}{{numValue}}{{segmentIndex}}{{mathExpression}}{{envVariableName}}{{scenes}}{{tempVars}}{{tempVarsHelp}}{{sceneItemIndex}}{{direction}}{{stringLength}}{{paddingCharSelection}}"
AdvSceneSwitcher.action.variable.entry.pad="{{actions}}of{{variables}}to length{{stringLength}}by adding{{paddingCharSelection}}to the{{direction}}"
AdvSceneSwitcher.action.variable.entry.truncate="{{actions}}of{{variables}}to length{{stringLength}}by removing characters from the{{direction}}"
AdvSceneSwitcher.action.variable.entry.substringIndex="Substring start:{{subStringStart}}Substring size:{{subStringSize}}"
@@ -1001,6 +1066,8 @@ AdvSceneSwitcher.action.queue.entry.add="{{actions}}actions of macro{{macros}}to
AdvSceneSwitcher.action.queue.entry.other="{{actions}}queue{{queues}}"
AdvSceneSwitcher.action.window="Window"
AdvSceneSwitcher.action.window.type.setFocusWindow="Focus window"
AdvSceneSwitcher.action.window.type.setFocusWindow.limitation="The focus action can only be performed under certain circumstances"
AdvSceneSwitcher.action.window.type.setFocusWindow.limitation.details="At least one of the following conditions must be true:\n OBS is the foreground process.\n There is currently no foreground window, and thus no foreground process.\n OBS received the last input event."
AdvSceneSwitcher.action.window.type.maximizeWindow="Maximize window"
AdvSceneSwitcher.action.window.type.minimizeWindow="Minimize window"
AdvSceneSwitcher.action.window.type.closeWindow="Close window"
@@ -1676,6 +1743,15 @@ AdvSceneSwitcher.tempVar.streaming.durationSeconds.description="Seconds passed s
AdvSceneSwitcher.tempVar.clipboard.text="Clipboard text"
AdvSceneSwitcher.tempVar.clipboard.text.description="The text contained in the clipboard.\nWill be empty if the clipboard does not contain text."
AdvSceneSwitcher.tempVar.file.content="File content"
AdvSceneSwitcher.tempVar.file.date="File modification date"
AdvSceneSwitcher.tempVar.folder.newFiles="New files"
AdvSceneSwitcher.tempVar.folder.changedFiles="Changed files"
AdvSceneSwitcher.tempVar.folder.removedFiles="Removed files"
AdvSceneSwitcher.tempVar.folder.newDirs="New directories"
AdvSceneSwitcher.tempVar.folder.removedDirs="Removed directories"
AdvSceneSwitcher.selectScene="--select scene--"
AdvSceneSwitcher.selectPreviousScene="Previous Scene"
AdvSceneSwitcher.selectCurrentScene="Current Scene"

View File

@@ -238,8 +238,7 @@ AdvSceneSwitcher.condition.sceneOrder.entry="En{{scenes}}{{sources}}{{conditions
AdvSceneSwitcher.condition.hotkey="Tecla de acceso rápido"
AdvSceneSwitcher.condition.hotkey.name="Tecla de acceso directo de activación de macro"
AdvSceneSwitcher.condition.hotkey.tip="Nota: puede configurar las combinaciones de teclas para esta tecla de acceso rápido en la ventana de configuración de OBS"
AdvSceneSwitcher.condition.hotkey.entry.line1="Se presiona la tecla de acceso rápido"
AdvSceneSwitcher.condition.hotkey.entry.line2="Nombre: {{name}}"
AdvSceneSwitcher.condition.hotkey.entry.name="Nombre:{{name}}"
AdvSceneSwitcher.condition.replay="Búfer de reproducción"
AdvSceneSwitcher.condition.replay.state.stopped="Búfer de reproducción detenido"
AdvSceneSwitcher.condition.replay.state.started="Búfer de reproducción iniciado"
@@ -387,7 +386,6 @@ AdvSceneSwitcher.action.macro.type.unpause="Reanudar"
AdvSceneSwitcher.action.macro.type.resetCounter="Reiniciar contador"
AdvSceneSwitcher.action.macro.type.run="Ejecutar"
AdvSceneSwitcher.action.macro.type.stop="Detener"
AdvSceneSwitcher.action.macro.entry="{{actions}}{{actionIndex}}{{macros}}"
AdvSceneSwitcher.action.pluginState="Estado del complemento"
AdvSceneSwitcher.action.pluginState.type.stop="Detener el complemento Advanced Scene Switcher"
AdvSceneSwitcher.action.pluginState.type.noMatch="Cambiar el comportamiento de no coincidencia:"

View File

@@ -351,8 +351,7 @@ AdvSceneSwitcher.condition.sceneOrder.entry="Sur{{scenes}}{{sources}}{{condition
AdvSceneSwitcher.condition.hotkey="Raccourci clavier"
AdvSceneSwitcher.condition.hotkey.name="Raccourci clavier de déclenchement de macro"
AdvSceneSwitcher.condition.hotkey.tip="Remarque : Vous pouvez configurer les raccourcis clavier pour ce raccourci dans la fenêtre de paramètres d'OBS"
AdvSceneSwitcher.condition.hotkey.entry.line1="Raccourci clavier est enfoncé"
AdvSceneSwitcher.condition.hotkey.entry.line2="Nom :{{name}}"
AdvSceneSwitcher.condition.hotkey.entry.name="Nom:{{name}}"
AdvSceneSwitcher.condition.replay="Tampon de répétition"
AdvSceneSwitcher.condition.replay.state.stopped="Tampon de répétition arrêté"
AdvSceneSwitcher.condition.replay.state.started="Tampon de répétition démarré"

View File

@@ -315,7 +315,6 @@ AdvSceneSwitcher.action.macro.type.pause="Duraklat"
AdvSceneSwitcher.action.macro.type.unpause="Duraklatma"
AdvSceneSwitcher.action.macro.type.resetCounter="Sayacı sıfırla"
AdvSceneSwitcher.action.macro.type.run="Çalıştır"
AdvSceneSwitcher.action.macro.entry="{{actions}}{{actionIndex}}{{macros}}"
AdvSceneSwitcher.action.pluginState="Eklenti durumu"
AdvSceneSwitcher.action.pluginState.type.stop="Advanced Scene Switcher eklentisini durdurun"
AdvSceneSwitcher.action.pluginState.type.noMatch="Eşleşmeme davranışını değiştirin:"

View File

@@ -332,8 +332,7 @@ AdvSceneSwitcher.condition.sceneOrder.entry="在 {{scenes}} {{sources}} {{condit
AdvSceneSwitcher.condition.hotkey="热键"
AdvSceneSwitcher.condition.hotkey.name="宏触发热键"
AdvSceneSwitcher.condition.hotkey.tip="注意您可以在OBS设置窗口中为此热键配置按键绑定"
AdvSceneSwitcher.condition.hotkey.entry.line1="热键被按下"
AdvSceneSwitcher.condition.hotkey.entry.line2="名称: {{name}}"
AdvSceneSwitcher.condition.hotkey.entry.name="名称:{{name}}"
AdvSceneSwitcher.condition.replay="回放缓存"
AdvSceneSwitcher.condition.replay.state.stopped="回放缓存已停止"
AdvSceneSwitcher.condition.replay.state.started="回放缓存已启动"
@@ -525,7 +524,6 @@ AdvSceneSwitcher.action.macro.type.unpause="取消暂停"
AdvSceneSwitcher.action.macro.type.resetCounter="复位计数器"
AdvSceneSwitcher.action.macro.type.run="运行操作"
AdvSceneSwitcher.action.macro.type.stop="停止操作"
AdvSceneSwitcher.action.macro.entry="{{actions}}{{actionIndex}}{{macros}}"
AdvSceneSwitcher.action.pluginState="插件状态"
AdvSceneSwitcher.action.pluginState.type.stop="停止高级场景切换插件"
AdvSceneSwitcher.action.pluginState.type.noMatch="没有匹配项时:"

View File

@@ -1602,104 +1602,6 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="variableTab">
<attribute name="title">
<string>AdvSceneSwitcher.variableTab.title</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_33">
<item>
<layout class="QGridLayout" name="gridLayout_25">
<item row="0" column="0">
<widget class="QTableWidget" name="variables">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
<property name="cornerButtonEnabled">
<bool>false</bool>
</property>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="variablesHelp">
<property name="text">
<string>AdvSceneSwitcher.variableTab.help</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_30">
<item>
<widget class="QPushButton" name="variableAdd">
<property name="maximumSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="toolTip">
<string>AdvSceneSwitcher.variableTab.variableAddButton.tooltip</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="themeID" stdset="0">
<string notr="true">addIconSmall</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="variableRemove">
<property name="maximumSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="toolTip">
<string>AdvSceneSwitcher.variableTab.variableRemoveButton.tooltip</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="themeID" stdset="0">
<string notr="true">removeIconSmall</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_211">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="networkTab">
<attribute name="title">
<string>AdvSceneSwitcher.networkTab.title</string>

View File

@@ -11,6 +11,7 @@
#include "status-control.hpp"
#include "switcher-data.hpp"
#include "ui-helpers.hpp"
#include "tab-helpers.hpp"
#include "utility.hpp"
#include "version.h"
@@ -44,7 +45,7 @@ AdvSceneSwitcher::~AdvSceneSwitcher()
{
if (switcher) {
switcher->settingsWindowOpened = false;
switcher->lastOpenedTab = ui->tabWidget->currentIndex();
SaveLastOpenedTab(ui->tabWidget);
}
}
@@ -112,11 +113,11 @@ void AdvSceneSwitcher::LoadUI()
SetupSceneGroupTab();
SetupTriggerTab();
SetupMacroTab();
SetupVariableTab();
SetupOtherTabs(ui->tabWidget);
SetDeprecationWarnings();
SetTabOrder();
SetCurrentTab();
SetTabOrder(ui->tabWidget);
SetCurrentTab(ui->tabWidget);
RestoreWindowGeo();
CheckFirstTimeSetup();
@@ -145,12 +146,6 @@ bool AdvSceneSwitcher::eventFilter(QObject *obj, QEvent *event)
} else if (pressedKey == Qt::Key_Delete) {
RemoveSelectedMacros();
}
} else if (obj == ui->variables && ui->variables->isVisible()) {
if (pressedKey == Qt::Key_F2) {
OpenSettingsForSelectedVariable();
} else if (pressedKey == Qt::Key_Delete) {
RemoveSelectedVariables();
}
}
}
@@ -245,7 +240,7 @@ void SwitcherData::Thread()
if (duration.count() < 1) {
blog(LOG_INFO,
"detected busy loop - refusing to sleep less than 1ms");
duration = std::chrono::milliseconds(50);
duration = std::chrono::milliseconds(10);
}
}
@@ -527,9 +522,7 @@ bool SwitcherData::AnySceneTransitionStarted()
extern "C" EXPORT void FreeSceneSwitcher()
{
PlatformCleanup();
for (const auto &cleanupStep : switcher->pluginCleanupSteps) {
cleanupStep();
}
RunPluginCleanupSteps();
delete switcher;
switcher = nullptr;
@@ -759,13 +752,6 @@ QWidget *GetSettingsWindow()
void SetupActionQueues();
extern "C" EXPORT void RunPluginPostLoadSteps()
{
for (const auto &postLoadStep : switcher->pluginPostLoadSteps) {
postLoadStep();
}
}
extern "C" EXPORT void InitSceneSwitcher(obs_module_t *module,
translateFunc translate)
{
@@ -779,9 +765,7 @@ extern "C" EXPORT void InitSceneSwitcher(obs_module_t *module,
SetupDock();
SetupActionQueues();
for (const auto &initStep : switcher->pluginInitSteps) {
initStep();
}
RunPluginInitSteps();
obs_frontend_add_save_callback(SaveSceneSwitcher, nullptr);
obs_frontend_add_event_callback(OBSEvent, switcher);

View File

@@ -12,6 +12,7 @@ class MacroActionEdit;
class MacroConditionEdit;
class Duration;
class SequenceWidget;
enum class LogicType;
struct SceneGroup;
/*******************************************************************************
@@ -34,8 +35,6 @@ public:
void LoadUI();
void SetTabOrder();
void SetCurrentTab();
void RestoreWindowGeo();
void CheckFirstTimeSetup();
@@ -59,7 +58,6 @@ public slots:
void on_autoStartEvent_currentIndexChanged(int index);
void on_noMatchSwitchScene_currentTextChanged(const QString &text);
void on_checkInterval_valueChanged(int value);
void on_tabMoved(int from, int to);
void on_tabWidget_currentChanged(int index);
void on_exportSettings_clicked();
void on_importSettings_clicked();
@@ -155,12 +153,16 @@ public slots:
void SetElseActionsStateToVisible();
void MacroActionSelectionChanged(int idx);
void MacroActionReorder(int to, int target);
void AddMacroAction(Macro *macro, int idx, const std::string &id,
obs_data_t *data);
void AddMacroAction(int idx);
void RemoveMacroAction(int idx);
void MoveMacroActionUp(int idx);
void MoveMacroActionDown(int idx);
void MacroElseActionSelectionChanged(int idx);
void MacroElseActionReorder(int to, int target);
void AddMacroElseAction(Macro *macro, int idx, const std::string &id,
obs_data_t *data);
void AddMacroElseAction(int idx);
void RemoveMacroElseAction(int idx);
void SwapElseActions(Macro *m, int pos1, int pos2);
@@ -169,16 +171,16 @@ public slots:
void MacroConditionSelectionChanged(int idx);
void MacroConditionReorder(int to, int target);
void AddMacroCondition(int idx);
void AddMacroCondition(Macro *macro, int idx, const std::string &id,
obs_data_t *data, LogicType logic);
void RemoveMacroCondition(int idx);
void MoveMacroConditionUp(int idx);
void MoveMacroConditionDown(int idx);
void FadeOutActionControls();
void FadeOutConditionControls();
void ResetOpacityActionControls();
void ResetOpacityConditionControls();
void HighlightControls();
void HighlightOnChange();
void on_macroProperties_clicked();
void CopyMacroSegment();
void PasteMacroSegment();
signals:
void MacroAdded(const QString &name);
@@ -187,9 +189,6 @@ signals:
void MacroSegmentOrderChanged();
void SegmentTempVarsChanged();
void HighlightMacrosChanged(bool value);
void HighlightActionsChanged(bool value);
void HighlightElseActionsChanged(bool value);
void HighlightConditionsChanged(bool value);
void ConnectionAdded(const QString &);
void ConnectionRenamed(const QString &oldName, const QString &newName);
@@ -209,19 +208,6 @@ private:
/* --- End of macro tab section --- */
/* --- Begin of variable tab section --- */
public:
void SetupVariableTab();
public slots:
void on_variableAdd_clicked();
void on_variableRemove_clicked();
void OpenSettingsForSelectedVariable();
void RemoveSelectedVariables();
/* --- End of variable tab section --- */
/* --- Begin of legacy tab section --- */
public:
void ClearFrames(QListWidget *list);

View File

@@ -9,6 +9,7 @@
#include "splitter-helpers.hpp"
#include "status-control.hpp"
#include "switcher-data.hpp"
#include "tab-helpers.hpp"
#include "ui-helpers.hpp"
#include "utility.hpp"
#include "variable.hpp"
@@ -19,17 +20,6 @@
namespace advss {
static constexpr std::array<const char *, 19> tabNames = {
"generalTab", "macroTab", "variableTab",
"windowTitleTab", "executableTab", "screenRegionTab",
"mediaTab", "fileTab", "randomTab",
"timeTab", "idleTab", "sceneSequenceTab",
"audioTab", "videoTab", "networkTab",
"sceneGroupTab", "transitionsTab", "pauseTab",
"sceneTriggerTab"};
static std::vector<int> tabOrder = std::vector<int>(tabNames.size());
void AdvSceneSwitcher::reject()
{
close();
@@ -244,14 +234,7 @@ void AdvSceneSwitcher::on_hideLegacyTabs_stateChanged(int state)
for (int idx = 0; idx < ui->tabWidget->count(); idx++) {
if (isLegacyTab(ui->tabWidget->tabText(idx))) {
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
// TODO: Switch to setTabVisible() once QT 5.15 is more wide spread
ui->tabWidget->setTabEnabled(idx, !state);
ui->tabWidget->setStyleSheet(
"QTabBar::tab::disabled {width: 0; height: 0; margin: 0; padding: 0; border: none;} ");
#else
ui->tabWidget->setTabVisible(idx, !state);
#endif
}
}
}
@@ -363,67 +346,6 @@ void AdvSceneSwitcher::on_importSettings_clicked()
}
}
static int findTabIndex(QTabWidget *tabWidget, int pos)
{
int at = -1;
QString tabName = tabNames.at(pos);
QWidget *page = tabWidget->findChild<QWidget *>(tabName);
if (page) {
at = tabWidget->indexOf(page);
}
if (at == -1) {
blog(LOG_INFO, "failed to find tab %s",
tabName.toUtf8().constData());
}
return at;
}
static bool tabWidgetOrderValid()
{
auto tmp = std::vector<int>(tabNames.size());
std::iota(tmp.begin(), tmp.end(), 0);
for (auto &p : tmp) {
auto it = std::find(tabOrder.begin(), tabOrder.end(), p);
if (it == tabOrder.end()) {
return false;
}
}
return true;
}
static void resetTabWidgetOrder()
{
tabOrder = std::vector<int>(tabNames.size());
std::iota(tabOrder.begin(), tabOrder.end(), 0);
}
void AdvSceneSwitcher::SetTabOrder()
{
if (!tabWidgetOrderValid()) {
resetTabWidgetOrder();
}
QTabBar *bar = ui->tabWidget->tabBar();
for (int i = 0; i < bar->count(); ++i) {
int curPos = findTabIndex(ui->tabWidget, tabOrder[i]);
if (i != curPos && curPos != -1) {
bar->moveTab(curPos, i);
}
}
connect(bar, &QTabBar::tabMoved, this, &AdvSceneSwitcher::on_tabMoved);
}
void AdvSceneSwitcher::SetCurrentTab()
{
if (switcher->lastOpenedTab >= 0) {
ui->tabWidget->setCurrentIndex(switcher->lastOpenedTab);
}
}
static bool windowPosValid(QPoint pos)
{
return !!QGuiApplication::screenAt(pos);
@@ -446,17 +368,12 @@ void AdvSceneSwitcher::CheckFirstTimeSetup()
}
}
void AdvSceneSwitcher::on_tabMoved(int from, int to)
void AdvSceneSwitcher::on_tabWidget_currentChanged(int)
{
if (loading) {
return;
}
std::swap(tabOrder[from], tabOrder[to]);
}
void AdvSceneSwitcher::on_tabWidget_currentChanged(int)
{
switcher->showFrame = false;
ClearFrames(ui->screenRegionSwitches);
SetShowFrames();
@@ -537,7 +454,7 @@ void SwitcherData::LoadSettings(obs_data_t *obj)
RunPostLoadSteps();
// Reset on startup and scene collection change
switcher->lastOpenedTab = -1;
ResetLastOpenedTab();
startupLoadDone = true;
}
@@ -685,13 +602,7 @@ void SwitcherData::LoadGeneralSettings(obs_data_t *obj)
void SwitcherData::SaveUISettings(obs_data_t *obj)
{
OBSDataArrayAutoRelease tabWidgetOrder = obs_data_array_create();
for (size_t i = 0; i < tabNames.size(); i++) {
OBSDataAutoRelease entry = obs_data_create();
obs_data_set_int(entry, tabNames[i], tabOrder[i]);
obs_data_array_push_back(tabWidgetOrder, entry);
}
obs_data_set_array(obj, "tabWidgetOrder", tabWidgetOrder);
SaveTabOrder(obj);
obs_data_set_bool(obj, "saveWindowGeo", saveWindowGeo);
obs_data_set_int(obj, "windowPosX", windowPos.x());
@@ -705,28 +616,7 @@ void SwitcherData::SaveUISettings(obs_data_t *obj)
void SwitcherData::LoadUISettings(obs_data_t *obj)
{
OBSDataArrayAutoRelease defaultTabWidgetOrder = obs_data_array_create();
for (size_t i = 0; i < tabNames.size(); i++) {
OBSDataAutoRelease entry = obs_data_create();
obs_data_set_default_int(entry, tabNames[i], i);
obs_data_array_push_back(defaultTabWidgetOrder, entry);
}
obs_data_set_default_array(obj, "tabWidgetOrder",
defaultTabWidgetOrder);
tabOrder.clear();
OBSDataArrayAutoRelease tabWidgetOrder =
obs_data_get_array(obj, "tabWidgetOrder");
for (size_t i = 0; i < tabNames.size(); i++) {
OBSDataAutoRelease entry =
obs_data_array_item(tabWidgetOrder, i);
tabOrder.emplace_back(
(int)(obs_data_get_int(entry, tabNames[i])));
}
if (!tabWidgetOrderValid()) {
resetTabWidgetOrder();
}
LoadTabOrder(obj);
saveWindowGeo = obs_data_get_bool(obj, "saveWindowGeo");
windowPos = {(int)obs_data_get_int(obj, "windowPosX"),

View File

@@ -29,8 +29,7 @@ static inline void populateActionSelection(QComboBox *list)
MacroActionEdit::MacroActionEdit(QWidget *parent,
std::shared_ptr<MacroAction> *entryData,
const std::string &id)
: MacroSegmentEdit(GetGlobalMacroProperties()._highlightActions,
parent),
: MacroSegmentEdit(parent),
_actionSelection(new FilterComboBox()),
_enable(new SwitchButton()),
_entryData(entryData)
@@ -40,8 +39,6 @@ MacroActionEdit::MacroActionEdit(QWidget *parent,
SLOT(ActionSelectionChanged(const QString &)));
QWidget::connect(_enable, SIGNAL(checked(bool)), this,
SLOT(ActionEnableChanged(bool)));
QWidget::connect(window(), SIGNAL(HighlightActionsChanged(bool)), this,
SLOT(EnableHighlight(bool)));
QWidget::connect(&_actionStateTimer, SIGNAL(timeout()), this,
SLOT(UpdateActionState()));
@@ -161,33 +158,20 @@ std::shared_ptr<MacroSegment> MacroActionEdit::Data() const
return *_entryData;
}
void AdvSceneSwitcher::AddMacroAction(int idx)
void AdvSceneSwitcher::AddMacroAction(Macro *macro, int idx,
const std::string &id, obs_data_t *data)
{
auto macro = GetSelectedMacro();
if (!macro) {
return;
}
if (idx < 0 || idx > (int)macro->Actions().size()) {
assert(false);
return;
}
std::string id;
if (idx - 1 >= 0) {
id = macro->Actions().at(idx - 1)->GetId();
} else {
id = MacroAction::GetDefaultID();
}
{
auto lock = LockContext();
macro->Actions().emplace(
macro->Actions().begin() + idx,
MacroActionFactory::Create(id, macro.get()));
if (idx - 1 >= 0) {
auto data = obs_data_create();
macro->Actions().at(idx - 1)->Save(data);
macro->Actions().emplace(macro->Actions().begin() + idx,
MacroActionFactory::Create(id, macro));
if (data) {
macro->Actions().at(idx)->Load(data);
obs_data_release(data);
}
macro->Actions().at(idx)->PostLoad();
RunPostLoadSteps();
@@ -198,9 +182,37 @@ void AdvSceneSwitcher::AddMacroAction(int idx)
SetActionData(*macro);
}
HighlightAction(idx);
ui->actionsList->SetHelpMsgVisible(false);
emit(MacroSegmentOrderChanged());
}
void AdvSceneSwitcher::AddMacroAction(int idx)
{
auto macro = GetSelectedMacro();
if (!macro) {
return;
}
if (idx < 0 || idx > (int)macro->Actions().size()) {
assert(false);
return;
}
std::string id;
if (idx - 1 >= 0) {
id = macro->Actions().at(idx - 1)->GetId();
} else {
id = MacroAction::GetDefaultID();
}
OBSDataAutoRelease data;
if (idx - 1 >= 0) {
data = obs_data_create();
macro->Actions().at(idx - 1)->Save(data);
}
AddMacroAction(macro.get(), idx, id, data);
}
void AdvSceneSwitcher::on_actionAdd_clicked()
{
auto macro = GetSelectedMacro();
@@ -216,7 +228,6 @@ void AdvSceneSwitcher::on_actionAdd_clicked()
if (currentActionIdx != -1) {
MacroActionSelectionChanged(currentActionIdx + 1);
}
ui->actionsList->SetHelpMsgVisible(false);
}
void AdvSceneSwitcher::RemoveMacroAction(int idx)
@@ -311,7 +322,6 @@ void AdvSceneSwitcher::on_elseActionAdd_clicked()
if (currentElseActionIdx != -1) {
MacroElseActionSelectionChanged(currentElseActionIdx + 1);
}
ui->elseActionsList->SetHelpMsgVisible(false);
}
void AdvSceneSwitcher::on_elseActionRemove_clicked()
@@ -450,31 +460,21 @@ void AdvSceneSwitcher::MacroElseActionReorder(int to, int from)
emit(MacroSegmentOrderChanged());
}
void AdvSceneSwitcher::AddMacroElseAction(int idx)
void AdvSceneSwitcher::AddMacroElseAction(Macro *macro, int idx,
const std::string &id,
obs_data_t *data)
{
auto macro = GetSelectedMacro();
if (!macro) {
return;
}
if (idx < 0 || idx > (int)macro->ElseActions().size()) {
assert(false);
return;
}
std::string id;
if (idx - 1 >= 0) {
id = macro->ElseActions().at(idx - 1)->GetId();
} else {
id = MacroAction::GetDefaultID();
}
{
auto lock = LockContext();
macro->ElseActions().emplace(
macro->ElseActions().begin() + idx,
MacroActionFactory::Create(id, macro.get()));
if (idx - 1 >= 0) {
OBSDataAutoRelease data = obs_data_create();
macro->ElseActions().at(idx - 1)->Save(data);
macro->ElseActions().emplace(macro->ElseActions().begin() + idx,
MacroActionFactory::Create(id,
macro));
if (data) {
macro->ElseActions().at(idx)->Load(data);
}
macro->ElseActions().at(idx)->PostLoad();
@@ -486,9 +486,37 @@ void AdvSceneSwitcher::AddMacroElseAction(int idx)
SetElseActionData(*macro);
}
HighlightElseAction(idx);
ui->elseActionsList->SetHelpMsgVisible(false);
emit(MacroSegmentOrderChanged());
}
void AdvSceneSwitcher::AddMacroElseAction(int idx)
{
auto macro = GetSelectedMacro();
if (!macro) {
return;
}
if (idx < 0 || idx > (int)macro->ElseActions().size()) {
assert(false);
return;
}
std::string id;
if (idx - 1 >= 0) {
id = macro->ElseActions().at(idx - 1)->GetId();
} else {
id = MacroAction::GetDefaultID();
}
OBSDataAutoRelease data;
if (idx - 1 >= 0) {
data = obs_data_create();
macro->ElseActions().at(idx - 1)->Save(data);
}
AddMacroElseAction(macro.get(), idx, id, data);
}
void AdvSceneSwitcher::RemoveMacroElseAction(int idx)
{
auto macro = GetSelectedMacro();
@@ -507,7 +535,7 @@ void AdvSceneSwitcher::RemoveMacroElseAction(int idx)
SetMacroAbortWait(true);
GetMacroWaitCV().notify_all();
macro->UpdateElseActionIndices();
SetActionData(*macro);
SetElseActionData(*macro);
}
MacroElseActionSelectionChanged(-1);
lastInteracted = MacroSection::ELSE_ACTIONS;

View File

@@ -48,9 +48,7 @@ bool MacroActionMacro::PerformAction()
macro->ResetRunCount();
break;
case Action::RUN:
if (!macro->Paused()) {
macro->PerformActions(true, false, true);
}
RunActions(macro.get());
break;
case Action::STOP:
macro->Stop();
@@ -127,6 +125,7 @@ bool MacroActionMacro::Save(obs_data_t *obj) const
_macro.Save(obj);
_actionIndex.Save(obj, "actionIndex");
obs_data_set_int(obj, "action", static_cast<int>(_action));
_runOptions.Save(obj);
return true;
}
@@ -137,6 +136,15 @@ bool MacroActionMacro::Load(obs_data_t *obj)
_actionIndex.Load(obj, "actionIndex");
_action = static_cast<MacroActionMacro::Action>(
obs_data_get_int(obj, "action"));
_runOptions.Load(obj);
return true;
}
bool MacroActionMacro::PostLoad()
{
MacroRefAction::PostLoad();
MacroAction::PostLoad();
_runOptions.macro.PostLoad();
return true;
}
@@ -160,11 +168,55 @@ void MacroActionMacro::ResolveVariablesToFixedValues()
_actionIndex.ResolveVariables();
}
static inline void populateActionSelection(QComboBox *list)
void MacroActionMacro::RunActions(Macro *actionMacro) const
{
for (auto entry : actionTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
if (_runOptions.skipWhenPaused && actionMacro->Paused()) {
return;
}
if (_runOptions.logic == RunOptions::Logic::IGNORE_CONDITIONS) {
actionMacro->PerformActions(!_runOptions.runElseActions, false,
true);
return;
}
auto conditionMacro = _runOptions.macro.GetMacro();
if (!conditionMacro) {
return;
}
if ((_runOptions.logic == RunOptions::Logic::CONDITIONS &&
conditionMacro->Matched()) ||
(_runOptions.logic == RunOptions::Logic::INVERT_CONDITIONS &&
!conditionMacro->Matched())) {
actionMacro->PerformActions(!_runOptions.runElseActions, false,
true);
}
}
static void populateActionSelection(QComboBox *list)
{
for (const auto &[_, name] : actionTypes) {
list->addItem(obs_module_text(name.c_str()));
}
}
static void populateConditionBehaviorSelection(QComboBox *list)
{
list->addItem(obs_module_text(
"AdvSceneSwitcher.action.macro.type.run.conditions.ignore"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.action.macro.type.run.conditions.true"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.action.macro.type.run.conditions.false"));
}
static void populateActionTypeSelection(QComboBox *list)
{
list->addItem(obs_module_text(
"AdvSceneSwitcher.action.macro.type.run.actionType.regular"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.action.macro.type.run.actionType.else"));
}
MacroActionMacroEdit::MacroActionMacroEdit(
@@ -173,9 +225,20 @@ MacroActionMacroEdit::MacroActionMacroEdit(
_macros(new MacroSelection(parent)),
_actionIndex(new MacroSegmentSelection(
this, MacroSegmentSelection::Type::ACTION)),
_actions(new QComboBox())
_actions(new QComboBox()),
_conditionMacros(new MacroSelection(parent)),
_conditionBehaviors(new QComboBox()),
_actionTypes(new QComboBox()),
_skipWhenPaused(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.action.macro.type.run.skipWhenPaused"))),
_entryLayout(new QHBoxLayout()),
_conditionLayout(new QHBoxLayout())
{
populateActionSelection(_actions);
populateConditionBehaviorSelection(_conditionBehaviors);
populateActionTypeSelection(_actionTypes);
_conditionMacros->HideSelectedMacro();
QWidget::connect(_macros, SIGNAL(currentTextChanged(const QString &)),
this, SLOT(MacroChanged(const QString &)));
@@ -184,15 +247,21 @@ MacroActionMacroEdit::MacroActionMacroEdit(
QWidget::connect(_actionIndex,
SIGNAL(SelectionChanged(const IntVariable &)), this,
SLOT(ActionIndexChanged(const IntVariable &)));
QWidget::connect(_conditionMacros,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(ConditionMacroChanged(const QString &)));
QWidget::connect(_conditionBehaviors, SIGNAL(currentIndexChanged(int)),
this, SLOT(ConditionBehaviorChanged(int)));
QWidget::connect(_actionTypes, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionTypeChanged(int)));
QWidget::connect(_skipWhenPaused, SIGNAL(stateChanged(int)), this,
SLOT(SkipWhenPausedChanged(int)));
auto mainLayout = new QHBoxLayout;
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.macro.entry"),
mainLayout,
{{"{{actions}}", _actions},
{"{{actionIndex}}", _actionIndex},
{"{{macros}}", _macros}});
setLayout(mainLayout);
auto layout = new QVBoxLayout();
layout->addLayout(_entryLayout);
layout->addLayout(_conditionLayout);
layout->addWidget(_skipWhenPaused);
setLayout(layout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
@@ -207,6 +276,12 @@ void MacroActionMacroEdit::UpdateEntryData()
_actionIndex->SetValue(_entryData->_actionIndex);
_actionIndex->SetMacro(_entryData->_macro.GetMacro());
_macros->SetCurrentMacro(_entryData->_macro);
_conditionMacros->SetCurrentMacro(_entryData->_runOptions.macro);
_conditionBehaviors->setCurrentIndex(
static_cast<int>(_entryData->_runOptions.logic));
_actionTypes->setCurrentIndex(
_entryData->_runOptions.runElseActions ? 1 : 0);
_skipWhenPaused->setChecked(_entryData->_runOptions.skipWhenPaused);
SetWidgetVisibility();
}
@@ -244,8 +319,88 @@ void MacroActionMacroEdit::ActionIndexChanged(const IntVariable &value)
_entryData->_actionIndex = value;
}
void MacroActionMacroEdit::ConditionMacroChanged(const QString &text)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_runOptions.macro = text;
}
void MacroActionMacroEdit::ConditionBehaviorChanged(int value)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_runOptions.logic =
static_cast<MacroActionMacro::RunOptions::Logic>(value);
SetWidgetVisibility();
}
void MacroActionMacroEdit::ActionTypeChanged(int value)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_runOptions.runElseActions = value;
}
void MacroActionMacroEdit::SkipWhenPausedChanged(int value)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_runOptions.skipWhenPaused = value;
}
void MacroActionMacroEdit::SetWidgetVisibility()
{
_entryLayout->removeWidget(_actions);
_entryLayout->removeWidget(_actionIndex);
_entryLayout->removeWidget(_macros);
_entryLayout->removeWidget(_actionTypes);
_conditionLayout->removeWidget(_conditionBehaviors);
_conditionLayout->removeWidget(_conditionMacros);
ClearLayout(_entryLayout);
ClearLayout(_conditionLayout);
const std::unordered_map<std::string, QWidget *> placeholders = {
{"{{actions}}", _actions},
{"{{actionIndex}}", _actionIndex},
{"{{macros}}", _macros},
{"{{actionTypes}}", _actionTypes},
{"{{conditionBehaviors}}", _conditionBehaviors},
{"{{conditionMacros}}", _conditionMacros},
};
PlaceWidgets(
obs_module_text(
_entryData->_action == MacroActionMacro::Action::RUN
? "AdvSceneSwitcher.action.macro.entry.run"
: "AdvSceneSwitcher.action.macro.entry.other"),
_entryLayout, placeholders);
if (_entryData->_runOptions.logic ==
MacroActionMacro::RunOptions::Logic::IGNORE_CONDITIONS) {
_conditionLayout->addWidget(_conditionBehaviors);
_conditionLayout->addStretch();
} else {
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.action.macro.entry.run.condition"),
_conditionLayout, placeholders);
}
if (_entryData->_action == MacroActionMacro::Action::RUN ||
_entryData->_action == MacroActionMacro::Action::STOP) {
_macros->HideSelectedMacro();
@@ -260,6 +415,42 @@ void MacroActionMacroEdit::SetWidgetVisibility()
MacroActionMacro::Action::ENABLE_ACTION ||
_entryData->_action == MacroActionMacro::Action::TOGGLE_ACTION;
_actionIndex->setVisible(isModifyingActionState);
SetLayoutVisible(_conditionLayout,
_entryData->_action == MacroActionMacro::Action::RUN);
_conditionMacros->setVisible(
_entryData->_action == MacroActionMacro::Action::RUN &&
_entryData->_runOptions.logic !=
MacroActionMacro::RunOptions::Logic::IGNORE_CONDITIONS);
_actionTypes->setVisible(_entryData->_action ==
MacroActionMacro::Action::RUN);
_skipWhenPaused->setVisible(_entryData->_action ==
MacroActionMacro::Action::RUN);
adjustSize();
updateGeometry();
}
void MacroActionMacro::RunOptions::Save(obs_data_t *obj) const
{
OBSDataAutoRelease data = obs_data_create();
obs_data_set_int(data, "logic", static_cast<int>(logic));
obs_data_set_bool(data, "runElseActions", runElseActions);
obs_data_set_bool(data, "skipWhenPaused", skipWhenPaused);
macro.Save(data);
obs_data_set_obj(obj, "runOptions", data);
}
void MacroActionMacro::RunOptions::Load(obs_data_t *obj)
{
if (!obs_data_has_user_value(obj, "runOptions")) {
return;
}
OBSDataAutoRelease data = obs_data_get_obj(obj, "runOptions");
logic = static_cast<Logic>(obs_data_get_int(data, "logic"));
runElseActions = obs_data_get_bool(data, "runElseActions");
skipWhenPaused = obs_data_get_bool(data, "skipWhenPaused");
macro.Load(data);
}
} // namespace advss

View File

@@ -3,6 +3,7 @@
#include "macro-selection.hpp"
#include "macro-segment-selection.hpp"
#include <QCheckBox>
#include <QHBoxLayout>
namespace advss {
@@ -14,12 +15,27 @@ public:
void LogAction() const;
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
bool PostLoad();
std::string GetShortDesc() const;
std::string GetId() const { return id; };
static std::shared_ptr<MacroAction> Create(Macro *m);
std::shared_ptr<MacroAction> Copy() const;
void ResolveVariablesToFixedValues();
struct RunOptions {
void Save(obs_data_t *obj) const;
void Load(obs_data_t *obj);
enum class Logic {
IGNORE_CONDITIONS,
CONDITIONS,
INVERT_CONDITIONS,
};
Logic logic;
bool runElseActions = false;
bool skipWhenPaused = true;
MacroRef macro;
};
enum class Action {
PAUSE,
UNPAUSE,
@@ -32,8 +48,11 @@ public:
};
Action _action = Action::PAUSE;
IntVariable _actionIndex = 1;
RunOptions _runOptions = {};
private:
void RunActions(Macro *actionMacro) const;
static bool _registered;
static const std::string id;
};
@@ -58,18 +77,28 @@ private slots:
void MacroChanged(const QString &text);
void ActionChanged(int value);
void ActionIndexChanged(const IntVariable &value);
void ConditionMacroChanged(const QString &text);
void ConditionBehaviorChanged(int value);
void ActionTypeChanged(int value);
void SkipWhenPausedChanged(int value);
signals:
void HeaderInfoChanged(const QString &);
protected:
MacroSelection *_macros;
MacroSegmentSelection *_actionIndex;
QComboBox *_actions;
std::shared_ptr<MacroActionMacro> _entryData;
private:
void SetWidgetVisibility();
MacroSelection *_macros;
MacroSegmentSelection *_actionIndex;
QComboBox *_actions;
MacroSelection *_conditionMacros;
QComboBox *_conditionBehaviors;
QComboBox *_actionTypes;
QCheckBox *_skipWhenPaused;
QHBoxLayout *_entryLayout;
QHBoxLayout *_conditionLayout;
std::shared_ptr<MacroActionMacro> _entryData;
bool _loading = true;
};

View File

@@ -6,6 +6,7 @@
#include "macro.hpp"
#include "non-modal-dialog.hpp"
#include "source-helpers.hpp"
#include "ui-helpers.hpp"
#include "utility.hpp"
namespace advss {
@@ -630,6 +631,7 @@ MacroActionVariableEdit::MacroActionVariableEdit(
_scenes(new SceneSelectionWidget(this, true, false, true, true,
true)),
_tempVars(new TempVariableSelection(this)),
_tempVarsHelp(new QLabel()),
_sceneItemIndex(new VariableSpinBox()),
_direction(new QComboBox()),
_stringLength(new VariableSpinBox()),
@@ -657,6 +659,15 @@ MacroActionVariableEdit::MacroActionVariableEdit(
populateTypeSelection(_actions);
populateDirectionSelection(_direction);
QString path = GetThemeTypeName() == "Light"
? ":/res/images/help.svg"
: ":/res/images/help_light.svg";
QIcon icon(path);
QPixmap pixmap = icon.pixmap(QSize(16, 16));
_tempVarsHelp->setPixmap(pixmap);
_tempVarsHelp->setToolTip(obs_module_text(
"AdvSceneSwitcher.action.variable.type.setToTempvar.help"));
QWidget::connect(_variables, SIGNAL(SelectionChanged(const QString &)),
this, SLOT(VariableChanged(const QString &)));
QWidget::connect(_variables2, SIGNAL(SelectionChanged(const QString &)),
@@ -742,6 +753,7 @@ MacroActionVariableEdit::MacroActionVariableEdit(
{"{{envVariableName}}", _envVariable},
{"{{scenes}}", _scenes},
{"{{tempVars}}", _tempVars},
{"{{tempVarsHelp}}", _tempVarsHelp},
{"{{sceneItemIndex}}", _sceneItemIndex},
{"{{direction}}", _direction},
{"{{stringLength}}", _stringLength},
@@ -1202,6 +1214,7 @@ void MacroActionVariableEdit::SelectionChanged(const TempVariableRef &var)
auto lock = LockContext();
_entryData->_tempVar = var;
SetWidgetVisibility();
}
void MacroActionVariableEdit::SceneItemIndexChanged(
@@ -1268,6 +1281,7 @@ void MacroActionVariableEdit::SetWidgetVisibility()
{"{{envVariableName}}", _envVariable},
{"{{scenes}}", _scenes},
{"{{tempVars}}", _tempVars},
{"{{tempVarsHelp}}", _tempVarsHelp},
{"{{sceneItemIndex}}", _sceneItemIndex},
{"{{direction}}", _direction},
{"{{stringLength}}", _stringLength},
@@ -1381,6 +1395,10 @@ void MacroActionVariableEdit::SetWidgetVisibility()
MacroActionVariable::Type::SCENE_ITEM_NAME);
_tempVars->setVisible(_entryData->_type ==
MacroActionVariable::Type::SET_TO_TEMPVAR);
_tempVarsHelp->setVisible(
_entryData->_type ==
MacroActionVariable::Type::SET_TO_TEMPVAR &&
!_entryData->_tempVar.HasValidID());
_sceneItemIndex->setVisible(_entryData->_type ==
MacroActionVariable::Type::SCENE_ITEM_NAME);
_direction->setVisible(

View File

@@ -185,6 +185,7 @@ private:
VariableLineEdit *_envVariable;
SceneSelectionWidget *_scenes;
TempVariableSelection *_tempVars;
QLabel *_tempVarsHelp;
VariableSpinBox *_sceneItemIndex;
QComboBox *_direction;
VariableSpinBox *_stringLength;

View File

@@ -121,8 +121,7 @@ void DurationModifierEdit::Collapse(bool collapse)
MacroConditionEdit::MacroConditionEdit(
QWidget *parent, std::shared_ptr<MacroCondition> *entryData,
const std::string &id, bool root)
: MacroSegmentEdit(GetGlobalMacroProperties()._highlightConditions,
parent),
: MacroSegmentEdit(parent),
_logicSelection(new QComboBox()),
_conditionSelection(new FilterComboBox()),
_dur(new DurationModifierEdit()),
@@ -139,8 +138,6 @@ MacroConditionEdit::MacroConditionEdit(
QWidget::connect(_dur, SIGNAL(ModifierChanged(DurationModifier::Type)),
this,
SLOT(DurationModifierChanged(DurationModifier::Type)));
QWidget::connect(window(), SIGNAL(HighlightConditionsChanged(bool)),
this, SLOT(EnableHighlight(bool)));
populateLogicSelection(_logicSelection, root);
populateConditionSelection(_conditionSelection);
@@ -299,6 +296,7 @@ void AdvSceneSwitcher::AddMacroCondition(int idx)
}
if (idx < 0 || idx > (int)macro->Conditions().size()) {
assert(false);
return;
}
@@ -315,16 +313,31 @@ void AdvSceneSwitcher::AddMacroCondition(int idx)
id = MacroCondition::GetDefaultID();
logic = LogicType::ROOT_NONE;
}
OBSDataAutoRelease data;
if (idx - 1 >= 0) {
data = obs_data_create();
macro->Conditions().at(idx - 1)->Save(data);
}
AddMacroCondition(macro.get(), idx, id, data.Get(), logic);
}
void AdvSceneSwitcher::AddMacroCondition(Macro *macro, int idx,
const std::string &id,
obs_data_t *data, LogicType logic)
{
if (idx < 0 || idx > (int)macro->Conditions().size()) {
assert(false);
return;
}
{
auto lock = LockContext();
auto cond = macro->Conditions().emplace(
macro->Conditions().begin() + idx,
MacroConditionFactory::Create(id, macro.get()));
if (idx - 1 >= 0) {
auto data = obs_data_create();
macro->Conditions().at(idx - 1)->Save(data);
MacroConditionFactory::Create(id, macro));
if (data) {
macro->Conditions().at(idx)->Load(data);
obs_data_release(data);
}
macro->Conditions().at(idx)->PostLoad();
RunPostLoadSteps();
@@ -337,6 +350,7 @@ void AdvSceneSwitcher::AddMacroCondition(int idx)
SetConditionData(*macro);
}
HighlightCondition(idx);
ui->conditionsList->SetHelpMsgVisible(false);
emit(MacroSegmentOrderChanged());
}
@@ -355,7 +369,6 @@ void AdvSceneSwitcher::on_conditionAdd_clicked()
if (currentConditionIdx != -1) {
MacroConditionSelectionChanged(currentConditionIdx + 1);
}
ui->conditionsList->SetHelpMsgVisible(false);
}
void AdvSceneSwitcher::RemoveMacroCondition(int idx)

View File

@@ -0,0 +1,104 @@
#include "advanced-scene-switcher.hpp"
#include "macro.hpp"
#include <QShortcut>
namespace advss {
struct MacroSegmentCopyInfo {
enum class Type { NONE, CONDITION, ACTION, ELSE };
Type type = Type::NONE;
std::shared_ptr<MacroSegment> segment;
};
static MacroSegmentCopyInfo copyInfo;
void AdvSceneSwitcher::CopyMacroSegment()
{
copyInfo.segment.reset();
copyInfo.type = MacroSegmentCopyInfo::Type::NONE;
if (currentConditionIdx == -1 && currentActionIdx == -1 &&
currentElseActionIdx == -1) {
return;
}
auto macro = GetSelectedMacro();
if (!macro) {
return;
}
if (currentConditionIdx != -1) {
copyInfo.type = MacroSegmentCopyInfo::Type::CONDITION;
copyInfo.segment = macro->Conditions().at(currentConditionIdx);
} else if (currentActionIdx != -1) {
copyInfo.type = MacroSegmentCopyInfo::Type::ACTION;
copyInfo.segment = macro->Actions().at(currentActionIdx);
} else if (currentElseActionIdx != -1) {
copyInfo.type = MacroSegmentCopyInfo::Type::ELSE;
copyInfo.segment =
macro->ElseActions().at(currentElseActionIdx);
} else {
assert(false);
}
}
void AdvSceneSwitcher::PasteMacroSegment()
{
if (copyInfo.type == MacroSegmentCopyInfo::Type::NONE) {
return;
}
auto macro = GetSelectedMacro();
if (!macro || !copyInfo.segment) {
return;
}
OBSDataAutoRelease data = obs_data_create();
copyInfo.segment->Save(data);
switch (copyInfo.type) {
case MacroSegmentCopyInfo::Type::CONDITION: {
const auto condition = std::static_pointer_cast<MacroCondition>(
copyInfo.segment);
auto logic = condition->GetLogicType();
if (logic > LogicType::ROOT_LAST &&
macro->Conditions().empty()) {
logic = LogicType::ROOT_NONE;
}
if (logic < LogicType::ROOT_LAST &&
!macro->Conditions().empty()) {
logic = LogicType::OR;
}
AddMacroCondition(macro.get(), macro->Conditions().size(),
copyInfo.segment->GetId(), data.Get(), logic);
break;
}
case MacroSegmentCopyInfo::Type::ACTION:
AddMacroAction(macro.get(), macro->Actions().size(),
copyInfo.segment->GetId(), data.Get());
break;
case MacroSegmentCopyInfo::Type::ELSE:
AddMacroElseAction(macro.get(), macro->ElseActions().size(),
copyInfo.segment->GetId(), data.Get());
break;
default:
break;
}
}
bool MacroSegmentIsInClipboard()
{
return copyInfo.type != MacroSegmentCopyInfo::Type::NONE;
}
void SetupSegmentCopyPasteShortcutHandlers(AdvSceneSwitcher *window)
{
auto copyShortcut = new QShortcut(QKeySequence("Ctrl+C"), window);
QWidget::connect(copyShortcut, &QShortcut::activated, window,
&AdvSceneSwitcher::CopyMacroSegment);
auto pasteShortcut = new QShortcut(QKeySequence("Ctrl+V"), window);
QWidget::connect(pasteShortcut, &QShortcut::activated, window,
&AdvSceneSwitcher::PasteMacroSegment);
}
} // namespace advss

View File

@@ -0,0 +1,8 @@
namespace advss {
class AdvSceneSwitcher;
bool MacroSegmentIsInClipboard();
void SetupSegmentCopyPasteShortcutHandlers(AdvSceneSwitcher *window);
} // namespace advss

View File

@@ -176,7 +176,8 @@ bool MacroSegmentList::eventFilter(QObject *object, QEvent *event)
void MacroSegmentList::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
if (event->button() == Qt::LeftButton ||
event->button() == Qt::RightButton) {
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
_dragPosition = GetDragIndex(event->globalPos());
#else

View File

@@ -59,12 +59,12 @@ std::string MacroSegment::GetShortDesc() const
return "";
}
void MacroSegment::SetHighlight()
void MacroSegment::EnableHighlight()
{
_highlight = true;
}
bool MacroSegment::Highlight()
bool MacroSegment::GetHighlightAndReset()
{
if (_highlight) {
_highlight = false;
@@ -194,7 +194,7 @@ void DecrementVariableRef(MacroSegment *segment)
segment->_variableRefs--;
}
MacroSegmentEdit::MacroSegmentEdit(bool highlight, QWidget *parent)
MacroSegmentEdit::MacroSegmentEdit(QWidget *parent)
: QWidget(parent),
_section(new Section(300)),
_headerInfo(new QLabel()),
@@ -203,8 +203,7 @@ MacroSegmentEdit::MacroSegmentEdit(bool highlight, QWidget *parent)
_noBorderframe(new QFrame),
_borderFrame(new QFrame),
_dropLineAbove(new QFrame),
_dropLineBelow(new QFrame),
_showHighlight(highlight)
_dropLineBelow(new QFrame)
{
_dropLineAbove->setLineWidth(3);
_dropLineAbove->setFixedHeight(11);
@@ -282,10 +281,6 @@ MacroSegmentEdit::MacroSegmentEdit(bool highlight, QWidget *parent)
// Enable dragging while clicking on the header text
_headerInfo->installEventFilter(this);
_timer.setInterval(1500);
connect(&_timer, SIGNAL(timeout()), this, SLOT(Highlight()));
_timer.start();
}
bool MacroSegmentEdit::eventFilter(QObject *obj, QEvent *ev)
@@ -330,22 +325,6 @@ void MacroSegmentEdit::Collapsed(bool collapsed)
}
}
void MacroSegmentEdit::Highlight()
{
if (!Data()) {
return;
}
if (_showHighlight && Data()->Highlight()) {
PulseWidget(this, Qt::green, QColor(0, 0, 0, 0), true);
}
}
void MacroSegmentEdit::EnableHighlight(bool value)
{
_showHighlight = value;
}
void MacroSegmentEdit::SetFocusPolicyOfWidgets()
{
QList<QWidget *> widgets = this->findChildren<QWidget *>();

View File

@@ -37,8 +37,8 @@ public:
virtual bool PostLoad();
virtual std::string GetShortDesc() const;
virtual std::string GetId() const = 0;
void SetHighlight();
bool Highlight();
void EnableHighlight();
bool GetHighlightAndReset();
virtual std::string GetVariableValue() const;
protected:
@@ -87,7 +87,7 @@ class MacroSegmentEdit : public QWidget {
Q_OBJECT
public:
MacroSegmentEdit(bool highlight, QWidget *parent = nullptr);
MacroSegmentEdit(QWidget *parent = nullptr);
// Use this function to avoid accidental edits when scrolling through
// list of actions and conditions
void SetFocusPolicyOfWidgets();
@@ -100,8 +100,6 @@ public slots:
protected slots:
void Collapsed(bool);
void Highlight();
void EnableHighlight(bool);
signals:
void MacroAdded(const QString &name);
void MacroRemoved(const QString &name);
@@ -143,9 +141,6 @@ private:
QFrame *_dropLineAbove;
QFrame *_dropLineBelow;
bool _showHighlight;
QTimer _timer;
friend class MacroSegmentList;
};

View File

@@ -4,6 +4,7 @@
#include "macro-condition-edit.hpp"
#include "macro-export-import-dialog.hpp"
#include "macro-properties.hpp"
#include "macro-segment-copy-paste.hpp"
#include "macro-tree.hpp"
#include "macro.hpp"
#include "math-helpers.hpp"
@@ -767,8 +768,6 @@ void AdvSceneSwitcher::on_macroProperties_clicked()
}
GetGlobalMacroProperties() = prop;
emit HighlightMacrosChanged(prop._highlightExecuted);
emit HighlightActionsChanged(prop._highlightActions);
emit HighlightConditionsChanged(prop._highlightConditions);
}
static void moveControlsToSplitter(QSplitter *splitter, int idx,
@@ -783,7 +782,7 @@ static void moveControlsToSplitter(QSplitter *splitter, int idx,
splitter->setStyleSheet("QSplitter::handle {background: transparent;}");
}
bool shouldRestoreSplitter(const QList<int> &pos)
static bool shouldRestoreSplitter(const QList<int> &pos)
{
if (pos.size() == 0) {
return false;
@@ -797,6 +796,36 @@ bool shouldRestoreSplitter(const QList<int> &pos)
return true;
}
static void runSegmentHighligtChecksHelper(MacroSegmentList *list)
{
MacroSegmentEdit *widget = nullptr;
for (int i = 0; (widget = list->WidgetAt(i)); i++) {
if (widget->Data() && widget->Data()->GetHighlightAndReset()) {
list->Highlight(i);
}
}
}
static void runSegmentHighligtChecks(AdvSceneSwitcher *ss)
{
if (!ss || !HighlightUIElementsEnabled()) {
return;
}
auto macro = ss->GetSelectedMacro();
if (!macro) {
return;
}
const auto &properties = GetGlobalMacroProperties();
if (properties._highlightConditions) {
runSegmentHighligtChecksHelper(ss->ui->conditionsList);
}
if (properties._highlightActions) {
runSegmentHighligtChecksHelper(ss->ui->actionsList);
runSegmentHighligtChecksHelper(ss->ui->elseActionsList);
}
}
void AdvSceneSwitcher::SetupMacroTab()
{
ui->macroElseActions->installEventFilter(this);
@@ -891,6 +920,14 @@ void AdvSceneSwitcher::SetupMacroTab()
switcher->macroListMacroEditSplitterPosition);
}
}
SetupSegmentCopyPasteShortcutHandlers(this);
// Macro segment highlight
auto timer = new QTimer(this);
connect(timer, &QTimer::timeout,
[this]() { runSegmentHighligtChecks(this); });
timer->start(1500);
}
void AdvSceneSwitcher::ShowMacroContextMenu(const QPoint &pos)
@@ -978,16 +1015,19 @@ static void setupConextMenu(AdvSceneSwitcher *ss, const QPoint &pos,
MacroSegmentList *list)
{
QMenu menu;
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.expandAll"),
ss, [ss, expand]() { expand(ss); });
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.collapseAll"),
ss, [ss, collapse]() { collapse(ss); });
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.maximize"),
ss, [ss, maximize]() { maximize(ss); });
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.minimize"),
ss, [ss, minimize]() { minimize(ss); });
auto segmentEdit = list->WidgetAt(pos);
auto copy = menu.addAction(
obs_module_text("AdvSceneSwitcher.macroTab.segment.copy"), ss,
[ss]() { ss->CopyMacroSegment(); });
copy->setEnabled(!!segmentEdit);
auto paste = menu.addAction(
obs_module_text("AdvSceneSwitcher.macroTab.segment.paste"), ss,
[ss]() { ss->PasteMacroSegment(); });
paste->setEnabled(MacroSegmentIsInClipboard());
menu.addSeparator();
if (segmentEdit) {
auto customLabel = menu.addAction(obs_module_text(
"AdvSceneSwitcher.macroTab.segment.useCustomLabel"));
@@ -999,6 +1039,21 @@ static void setupConextMenu(AdvSceneSwitcher *ss, const QPoint &pos,
std::bind(handleCustomLabelChange, segmentEdit,
customLabel));
}
menu.addSeparator();
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.expandAll"),
ss, [ss, expand]() { expand(ss); });
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.collapseAll"),
ss, [ss, collapse]() { collapse(ss); });
menu.addSeparator();
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.maximize"),
ss, [ss, maximize]() { maximize(ss); });
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.minimize"),
ss, [ss, minimize]() { minimize(ss); });
menu.exec(list->mapToGlobal(pos));
}
@@ -1343,58 +1398,47 @@ static void fade(QWidget *widget, bool fadeOut)
animation->start(QPropertyAnimation::DeleteWhenStopped);
}
void AdvSceneSwitcher::FadeOutActionControls()
void fadeWidgets(const std::vector<QWidget *> &widgets, bool fadeOut)
{
fade(ui->actionAdd, true);
fade(ui->actionRemove, true);
fade(ui->actionTop, true);
fade(ui->actionUp, true);
fade(ui->actionDown, true);
fade(ui->actionBottom, true);
}
void AdvSceneSwitcher::FadeOutConditionControls()
{
fade(ui->conditionAdd, true);
fade(ui->conditionRemove, true);
fade(ui->conditionTop, true);
fade(ui->conditionUp, true);
fade(ui->conditionDown, true);
fade(ui->conditionBottom, true);
}
void AdvSceneSwitcher::ResetOpacityActionControls()
{
fade(ui->actionAdd, false);
fade(ui->actionRemove, false);
fade(ui->actionTop, false);
fade(ui->actionUp, false);
fade(ui->actionDown, false);
fade(ui->actionBottom, false);
}
void AdvSceneSwitcher::ResetOpacityConditionControls()
{
fade(ui->conditionAdd, false);
fade(ui->conditionRemove, false);
fade(ui->conditionTop, false);
fade(ui->conditionUp, false);
fade(ui->conditionDown, false);
fade(ui->conditionBottom, false);
for (const auto &widget : widgets) {
fade(widget, fadeOut);
}
}
void AdvSceneSwitcher::HighlightControls()
{
if ((currentActionIdx == -1 && currentConditionIdx == -1) ||
(currentActionIdx != -1 && currentConditionIdx != -1)) {
ResetOpacityActionControls();
ResetOpacityConditionControls();
const std::vector<QWidget *> conditionControls{
ui->conditionAdd, ui->conditionRemove, ui->conditionTop,
ui->conditionUp, ui->conditionDown, ui->conditionBottom,
};
const std::vector<QWidget *> actionControls{
ui->actionAdd, ui->actionRemove, ui->actionTop,
ui->actionUp, ui->actionDown, ui->actionBottom,
};
const std::vector<QWidget *> elseActionControls{
ui->elseActionAdd, ui->elseActionRemove, ui->elseActionTop,
ui->elseActionUp, ui->elseActionDown, ui->elseActionBottom,
};
if ((currentActionIdx == -1 && currentConditionIdx == -1 &&
currentElseActionIdx == -1)) {
fadeWidgets(conditionControls, false);
fadeWidgets(actionControls, false);
fadeWidgets(elseActionControls, false);
} else if (currentConditionIdx != -1) {
fadeWidgets(conditionControls, false);
fadeWidgets(actionControls, true);
fadeWidgets(elseActionControls, true);
} else if (currentActionIdx != -1) {
FadeOutConditionControls();
ResetOpacityActionControls();
fadeWidgets(conditionControls, true);
fadeWidgets(actionControls, false);
fadeWidgets(elseActionControls, true);
} else if (currentElseActionIdx != -1) {
fadeWidgets(conditionControls, true);
fadeWidgets(actionControls, true);
fadeWidgets(elseActionControls, false);
} else {
FadeOutActionControls();
ResetOpacityConditionControls();
assert(false);
}
}

View File

@@ -137,37 +137,37 @@ bool Macro::CeckMatch()
case LogicType::AND:
_matched = _matched && cond;
if (cond) {
c->SetHighlight();
c->EnableHighlight();
}
break;
case LogicType::OR:
_matched = _matched || cond;
if (cond) {
c->SetHighlight();
c->EnableHighlight();
}
break;
case LogicType::AND_NOT:
_matched = _matched && !cond;
if (!cond) {
c->SetHighlight();
c->EnableHighlight();
}
break;
case LogicType::OR_NOT:
_matched = _matched || !cond;
if (!cond) {
c->SetHighlight();
c->EnableHighlight();
}
break;
case LogicType::ROOT_NONE:
_matched = cond;
if (cond) {
c->SetHighlight();
c->EnableHighlight();
}
break;
case LogicType::ROOT_NOT:
_matched = !cond;
if (!cond) {
c->SetHighlight();
c->EnableHighlight();
}
break;
default:
@@ -313,7 +313,7 @@ bool Macro::RunActionsHelper(
break;
}
if (action->Enabled()) {
action->SetHighlight();
action->EnableHighlight();
}
}
_done = true;
@@ -819,10 +819,10 @@ void Macro::ResetUIHelpers()
{
_onPreventedActionExecution = false;
for (auto c : _conditions) {
c->Highlight();
c->GetHighlightAndReset();
}
for (auto a : _actions) {
a->Highlight();
a->GetHighlightAndReset();
}
}

View File

@@ -0,0 +1,257 @@
#include "action-queue-tab.hpp"
#include "action-queue.hpp"
#include "log-helper.hpp"
#include "obs-module-helper.hpp"
#include "plugin-state-helpers.hpp"
#include "sync-helpers.hpp"
#include "tab-helpers.hpp"
#include "ui-helpers.hpp"
#include <QTimer>
namespace advss {
static void setupTab(QTabWidget *);
static ActionQueueTable *tabWidget = nullptr;
void RegisterActionQueueTab()
{
AddPluginInitStep([]() {
AddSetupTabCallback("actionQueueTab", ActionQueueTable::Create,
setupTab);
});
}
static void setTabVisible(QTabWidget *tabWidget, bool visible)
{
SetTabVisibleByName(
tabWidget, visible,
obs_module_text("AdvSceneSwitcher.actionQueueTab.title"));
}
ActionQueueTable *ActionQueueTable::Create()
{
tabWidget = new ActionQueueTable();
return tabWidget;
}
void ActionQueueTable::Add()
{
auto newQueue = std::make_shared<ActionQueue>();
auto accepted =
ActionQueueSettingsDialog::AskForSettings(this, *newQueue);
if (!accepted) {
return;
}
{
auto lock = LockContext();
auto &queues = GetActionQueues();
queues.emplace_back(newQueue);
}
ActionQueueSignalManager::Instance()->Add(
QString::fromStdString(newQueue->Name()));
}
static QStringList getCellLabels(ActionQueue *queue, bool addName = true)
{
assert(queue);
auto result = QStringList();
if (addName) {
result << QString::fromStdString(queue->Name());
}
result << QString::number(queue->Size())
<< QString::fromStdString(obs_module_text(
queue->IsRunning()
? "AdvSceneSwitcher.actionQueueTab.yes"
: "AdvSceneSwitcher.actionQueueTab.no"))
<< QString::fromStdString(obs_module_text(
queue->RunsOnStartup()
? "AdvSceneSwitcher.actionQueueTab.yes"
: "AdvSceneSwitcher.actionQueueTab.no"));
return result;
}
static void updateQueueStatus(QTableWidget *table)
{
for (int row = 0; row < table->rowCount(); row++) {
auto item = table->item(row, 0);
if (!item) {
continue;
}
auto weakQueue = GetWeakActionQueueByQString(item->text());
auto queue = weakQueue.lock();
if (!queue) {
continue;
}
UpdateItemTableRow(table, row,
getCellLabels(queue.get(), false));
}
}
static void openSettingsDialog()
{
auto selectedRows =
tabWidget->Table()->selectionModel()->selectedRows();
if (selectedRows.empty()) {
return;
}
auto cell = tabWidget->Table()->item(selectedRows.last().row(), 0);
if (!cell) {
return;
}
auto weakQueue = GetWeakActionQueueByQString(cell->text());
auto queue = weakQueue.lock();
if (!queue) {
return;
}
auto oldName = queue->Name();
bool accepted = ActionQueueSettingsDialog::AskForSettings(
tabWidget->Table(), *queue.get());
if (accepted && oldName != queue->Name()) {
ActionQueueSignalManager::Instance()->Rename(
QString::fromStdString(oldName),
QString::fromStdString(queue->Name()));
}
}
static void removeQueuesWithNames(const QStringList &queueNames)
{
for (const auto &name : queueNames) {
auto queue = GetWeakActionQueueByQString(name).lock();
if (!queue) {
continue;
}
auto &queues = GetActionQueues();
queues.erase(
std::remove_if(
queues.begin(), queues.end(),
[queue](const std::shared_ptr<Item> &item) {
return item == queue;
}),
queues.end());
}
}
void ActionQueueTable::Remove()
{
auto selectedRows =
tabWidget->Table()->selectionModel()->selectedRows();
if (selectedRows.empty()) {
return;
}
QStringList queueNames;
for (const auto &row : selectedRows) {
auto cell = tabWidget->Table()->item(row.row(), 0);
if (!cell) {
continue;
}
queueNames << cell->text();
}
int queueNameCount = queueNames.size();
if (queueNameCount == 1) {
QString deleteWarning = obs_module_text(
"AdvSceneSwitcher.actionQueueTab.removeSingleQueuePopup.text");
if (!DisplayMessage(deleteWarning.arg(queueNames.at(0)),
true)) {
return;
}
} else {
QString deleteWarning = obs_module_text(
"AdvSceneSwitcher.actionQueueTab.removeMultipleQueuesPopup.text");
if (!DisplayMessage(deleteWarning.arg(queueNameCount), true)) {
return;
}
}
{
auto lock = LockContext();
RemoveItemsByName(GetActionQueues(), queueNames);
}
for (const auto &name : queueNames) {
ActionQueueSignalManager::Instance()->Remove(name);
}
}
ActionQueueTable::ActionQueueTable(QTabWidget *parent)
: ResourceTable(
parent,
obs_module_text("AdvSceneSwitcher.actionQueueTab.help"),
obs_module_text(
"AdvSceneSwitcher.actionQueueTab.queueAddButton.tooltip"),
obs_module_text(
"AdvSceneSwitcher.actionQueueTab.queueRemoveButton.tooltip"),
QStringList()
<< obs_module_text(
"AdvSceneSwitcher.actionQueueTab.name.header")
<< obs_module_text(
"AdvSceneSwitcher.actionQueueTab.size.header")
<< obs_module_text(
"AdvSceneSwitcher.actionQueueTab.isRunning.header")
<< obs_module_text(
"AdvSceneSwitcher.actionQueueTab.runOnStartup.header"),
openSettingsDialog)
{
for (const auto &queue : GetActionQueues()) {
auto q = std::static_pointer_cast<ActionQueue>(queue);
AddItemTableRow(Table(), getCellLabels(q.get()));
}
SetHelpVisible(GetActionQueues().empty());
}
static void setupTab(QTabWidget *tab)
{
if (GetActionQueues().empty()) {
setTabVisible(tab, false);
}
QWidget::connect(ActionQueueSignalManager::Instance(),
&ActionQueueSignalManager::Rename,
[](const QString &oldName, const QString &newName) {
RenameItemTableRow(tabWidget->Table(), oldName,
newName);
});
QWidget::connect(
ActionQueueSignalManager::Instance(),
&ActionQueueSignalManager::Add, [tab](const QString &name) {
AddItemTableRow(
tabWidget->Table(),
getCellLabels(GetWeakActionQueueByQString(name)
.lock()
.get()));
tabWidget->SetHelpVisible(false);
tabWidget->HighlightAddButton(false);
setTabVisible(tab, true);
});
QWidget::connect(ActionQueueSignalManager::Instance(),
&ActionQueueSignalManager::Remove,
[](const QString &name) {
RemoveItemTableRow(tabWidget->Table(), name);
if (tabWidget->Table()->rowCount() == 0) {
tabWidget->SetHelpVisible(true);
tabWidget->HighlightAddButton(true);
}
});
auto timer = new QTimer(tabWidget);
timer->setInterval(1000);
QWidget::connect(timer, &QTimer::timeout,
[]() { updateQueueStatus(tabWidget->Table()); });
timer->start();
}
} // namespace advss

View File

@@ -0,0 +1,20 @@
#pragma once
#include "resource-table.hpp"
namespace advss {
class ActionQueueTable final : public ResourceTable {
Q_OBJECT
public:
static ActionQueueTable *Create();
private slots:
void Add();
void Remove();
private:
ActionQueueTable(QTabWidget *parent = nullptr);
};
} // namespace advss

View File

@@ -6,6 +6,13 @@ namespace advss {
static std::deque<std::shared_ptr<Item>> queues;
std::deque<std::shared_ptr<Item>> &GetActionQueues()
{
return queues;
}
void RegisterActionQueueTab();
void SetupActionQueues()
{
static bool done = false;
@@ -14,6 +21,7 @@ void SetupActionQueues()
}
AddSaveStep(SaveActionQueues);
AddLoadStep(LoadActionQueues);
RegisterActionQueueTab();
done = true;
}

View File

@@ -95,6 +95,7 @@ signals:
void Remove(const QString &);
};
std::deque<std::shared_ptr<Item>> &GetActionQueues();
void SetupActionQueues();
void SaveActionQueues(obs_data_t *);
void LoadActionQueues(obs_data_t *);

View File

@@ -187,24 +187,6 @@ void SwitcherData::RunPostLoadSteps()
postLoadSteps.clear();
}
void SwitcherData::AddPluginInitStep(std::function<void()> function)
{
std::lock_guard<std::mutex> lock(switcher->m);
pluginInitSteps.emplace_back(function);
}
void SwitcherData::AddPluginPostLoadStep(std::function<void()> function)
{
std::lock_guard<std::mutex> lock(switcher->m);
pluginPostLoadSteps.emplace_back(function);
}
void SwitcherData::AddPluginCleanupStep(std::function<void()> function)
{
std::lock_guard<std::mutex> lock(switcher->m);
pluginCleanupSteps.emplace_back(function);
}
void SwitcherData::AddSaveStep(std::function<void(obs_data_t *)> function)
{
std::lock_guard<std::mutex> lock(switcher->m);

View File

@@ -65,9 +65,6 @@ public:
void AddPostLoadStep(std::function<void()>);
void AddIntervalResetStep(std::function<void()>, bool lock = true);
void RunPostLoadSteps();
void AddPluginInitStep(std::function<void()>);
void AddPluginPostLoadStep(std::function<void()>);
void AddPluginCleanupStep(std::function<void()>);
bool CheckForMatch(OBSWeakSource &scene, OBSWeakSource &transition,
int &linger, bool &setPreviousSceneAsMatch,
bool &macroMatch);
@@ -106,9 +103,6 @@ public:
std::vector<std::function<void(obs_data_t *)>> loadSteps;
std::vector<std::function<void()>> postLoadSteps;
std::vector<std::function<void()>> resetIntervalSteps;
std::vector<std::function<void()>> pluginInitSteps;
std::vector<std::function<void()>> pluginPostLoadSteps;
std::vector<std::function<void()>> pluginCleanupSteps;
bool firstBoot = true;
bool transitionActive = false;
@@ -171,7 +165,6 @@ public:
/* --- Start of UI section --- */
bool settingsWindowOpened = false;
int lastOpenedTab = -1;
std::string lastImportPath;
QStringList loadFailureLibs;
bool warnPluginLoadFailure = true;

View File

@@ -347,4 +347,18 @@ void Item::Save(obs_data_t *obj) const
obs_data_set_string(obj, "name", _name.c_str());
}
void RemoveItemsByName(std::deque<std::shared_ptr<Item>> &items,
const QStringList &names)
{
for (const auto &name : names) {
items.erase(std::remove_if(
items.begin(), items.end(),
[name](const std::shared_ptr<Item> &item) {
return item->Name() ==
name.toStdString();
}),
items.end());
}
}
} // namespace advss

View File

@@ -32,6 +32,9 @@ protected:
friend ItemSettingsDialog;
};
void EXPORT RemoveItemsByName(std::deque<std::shared_ptr<Item>> &items,
const QStringList &names);
class ItemSettingsDialog : public QDialog {
Q_OBJECT

View File

@@ -9,7 +9,8 @@ namespace advss {
const char *obs_module_text(const char *text)
{
if (!advss::switcher) {
return "";
assert(false);
return "obs_module_text called too early";
}
return advss::switcher->Translate(text);
}

View File

@@ -3,6 +3,26 @@
namespace advss {
static std::vector<std::function<void()>> &getPluginInitSteps()
{
static std::vector<std::function<void()>> steps;
return steps;
}
static std::vector<std::function<void()>> &getPluginPostLoadSteps()
{
static std::vector<std::function<void()>> steps;
return steps;
}
static std::vector<std::function<void()>> &getPluginCleanupSteps()
{
static std::vector<std::function<void()>> steps;
return steps;
}
static std::mutex mutex;
void SavePluginSettings(obs_data_t *obj)
{
GetSwitcher()->SaveSettings(obj);
@@ -40,17 +60,44 @@ void RunPostLoadSteps()
void AddPluginInitStep(std::function<void()> step)
{
GetSwitcher()->AddPluginInitStep(step);
std::lock_guard<std::mutex> lock(mutex);
getPluginInitSteps().emplace_back(step);
}
void AddPluginPostLoadStep(std::function<void()> step)
{
GetSwitcher()->AddPluginPostLoadStep(step);
std::lock_guard<std::mutex> lock(mutex);
getPluginPostLoadSteps().emplace_back(step);
}
void AddPluginCleanupStep(std::function<void()> step)
{
GetSwitcher()->AddPluginCleanupStep(step);
std::lock_guard<std::mutex> lock(mutex);
getPluginCleanupSteps().emplace_back(step);
}
void RunPluginInitSteps()
{
std::lock_guard<std::mutex> lock(mutex);
for (const auto &step : getPluginInitSteps()) {
step();
}
}
void RunPluginPostLoadSteps()
{
std::lock_guard<std::mutex> lock(mutex);
for (const auto &step : getPluginPostLoadSteps()) {
step();
}
}
void RunPluginCleanupSteps()
{
std::lock_guard<std::mutex> lock(mutex);
for (const auto &step : getPluginCleanupSteps()) {
step();
}
}
void StopPlugin()

View File

@@ -17,6 +17,9 @@ EXPORT void RunPostLoadSteps();
EXPORT void AddPluginInitStep(std::function<void()>);
EXPORT void AddPluginPostLoadStep(std::function<void()>);
EXPORT void AddPluginCleanupStep(std::function<void()>);
void RunPluginInitSteps();
extern "C" EXPORT void RunPluginPostLoadSteps();
void RunPluginCleanupSteps();
EXPORT void StopPlugin();
EXPORT void StartPlugin();

View File

@@ -0,0 +1,70 @@
#include "resource-table-hotkey-handler.hpp"
#include <QKeyEvent>
#include <QWidget>
#include <unordered_map>
namespace advss {
void RegisterHotkeyFunction(QWidget *widget, Qt::Key key,
std::function<void()> func)
{
ResourceTabHotkeyHandler::Instance()->RegisterHandler(widget, key,
func);
}
void DeregisterHotkeyFunctions(QWidget *widget)
{
ResourceTabHotkeyHandler::Instance()->Deregister(widget);
}
ResourceTabHotkeyHandler *ResourceTabHotkeyHandler::Instance()
{
static ResourceTabHotkeyHandler handler;
return &handler;
}
void ResourceTabHotkeyHandler::RegisterHandler(QWidget *widget, Qt::Key key,
std::function<void()> func)
{
std::lock_guard<std::mutex> lock(_mutex);
_callbacks.emplace(widget, CallbackData{key, func});
widget->installEventFilter(this);
}
void ResourceTabHotkeyHandler::Deregister(QWidget *widget)
{
std::lock_guard<std::mutex> lock(_mutex);
_callbacks.erase(widget);
}
bool ResourceTabHotkeyHandler::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() != QEvent::KeyPress) {
return QObject::eventFilter(obj, event);
}
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
auto pressedKey = keyEvent->key();
std::lock_guard<std::mutex> lock(_mutex);
auto it = _callbacks.find(obj);
if (it == _callbacks.end()) {
return QObject::eventFilter(obj, event);
}
auto current = it;
while (current != _callbacks.end() && current->first == it->first) {
auto &[_, cbData] = *current;
if (pressedKey != cbData.key) {
++current;
continue;
}
cbData.func();
++current;
continue;
}
return QObject::eventFilter(obj, event);
}
} // namespace advss

View File

@@ -0,0 +1,37 @@
#pragma once
#include "export-symbol-helper.hpp"
#include <map>
#include <mutex>
#include <QEvent>
#include <QObject>
namespace advss {
EXPORT void RegisterHotkeyFunction(QWidget *, Qt::Key,
std::function<void()> func);
EXPORT void DeregisterHotkeyFunctions(QWidget *);
class ResourceTabHotkeyHandler : public QObject {
Q_OBJECT
public:
static ResourceTabHotkeyHandler *Instance();
void RegisterHandler(QWidget *, Qt::Key, std::function<void()> func);
void Deregister(QWidget *);
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
private:
struct CallbackData {
Qt::Key key;
std::function<void()> func;
};
ResourceTabHotkeyHandler() : QObject(){};
std::multimap<QObject *, CallbackData> _callbacks;
std::mutex _mutex;
};
} // namespace advss

View File

@@ -0,0 +1,157 @@
#include "resource-table.hpp"
#include "plugin-state-helpers.hpp"
#include "resource-table-hotkey-handler.hpp"
#include "ui-helpers.hpp"
#include <QGridLayout>
#include <QHeaderView>
namespace advss {
ResourceTable::ResourceTable(QTabWidget *parent, const QString &help,
const QString &addToolTip,
const QString &removeToolTip,
const QStringList &headers,
const std::function<void()> &openSettings)
: QWidget(parent),
_table(new QTableWidget()),
_add(new QPushButton()),
_remove(new QPushButton()),
_help(new QLabel(help))
{
_add->setMaximumWidth(22);
_add->setProperty("themeID",
QVariant(QString::fromUtf8("addIconSmall")));
_add->setFlat(true);
_add->setToolTip(addToolTip);
_remove->setMaximumWidth(22);
_remove->setProperty("themeID",
QVariant(QString::fromUtf8("removeIconSmall")));
_remove->setFlat(true);
_remove->setToolTip(removeToolTip);
_help->setWordWrap(true);
_help->setAlignment(Qt::AlignCenter);
_table->setColumnCount(headers.size());
_table->horizontalHeader()->setSectionResizeMode(
QHeaderView::ResizeMode::Interactive);
_table->setHorizontalHeaderLabels(headers);
_table->verticalHeader()->hide();
_table->setCornerButtonEnabled(false);
_table->setShowGrid(false);
_table->setEditTriggers(QAbstractItemView::NoEditTriggers);
_table->setSelectionBehavior(QAbstractItemView::SelectRows);
auto helpAndTableLayout = new QGridLayout();
helpAndTableLayout->setContentsMargins(0, 0, 0, 0);
helpAndTableLayout->addWidget(_table, 0, 0);
helpAndTableLayout->addWidget(_help, 0, 0, Qt::AlignCenter);
auto controlLayout = new QHBoxLayout;
controlLayout->setContentsMargins(0, 0, 0, 0);
controlLayout->addWidget(_add);
controlLayout->addWidget(_remove);
controlLayout->addStretch();
auto layout = new QVBoxLayout();
layout->addLayout(helpAndTableLayout);
layout->addLayout(controlLayout);
setLayout(layout);
QWidget::connect(_add, SIGNAL(clicked()), this, SLOT(Add()));
QWidget::connect(_remove, SIGNAL(clicked()), this, SLOT(Remove()));
QWidget::connect(_table, &QTableWidget::cellDoubleClicked,
[openSettings]() { openSettings(); });
RegisterHotkeyFunction(this, Qt::Key_F2, openSettings);
RegisterHotkeyFunction(this, Qt::Key_Delete, [this]() { Remove(); });
}
ResourceTable::~ResourceTable()
{
DeregisterHotkeyFunctions(this);
}
void ResourceTable::SetHelpVisible(bool visible) const
{
_help->setVisible(visible);
}
void ResourceTable::HighlightAddButton(bool enable)
{
_add->disconnect(_highlightConnection);
if (enable && HighlightUIElementsEnabled()) {
_highlightConnection = PulseWidget(_add, QColor(Qt::green));
}
}
void ResourceTable::resizeEvent(QResizeEvent *)
{
const auto columnCount = _table->columnCount();
const auto columnSize = (_table->width() - 1) / columnCount;
for (int i = 0; i < columnCount; ++i) {
_table->horizontalHeader()->resizeSection(i, columnSize);
}
}
void AddItemTableRow(QTableWidget *table, const QStringList &cellLabels)
{
int row = table->rowCount();
table->setRowCount(row + 1);
int col = 0;
for (const auto &cellLabel : cellLabels) {
auto *item = new QTableWidgetItem(cellLabel);
item->setToolTip(cellLabel);
table->setItem(row, col, item);
col++;
}
table->sortByColumn(0, Qt::AscendingOrder);
}
void UpdateItemTableRow(QTableWidget *table, int row,
const QStringList &cellLabels)
{
int col = 1; // Skip the name cell
for (const auto &cellLabel : cellLabels) {
auto item = table->item(row, col);
item->setText(cellLabel);
item->setToolTip(cellLabel);
col++;
}
}
void RenameItemTableRow(QTableWidget *table, const QString &oldName,
const QString &newName)
{
for (int row = 0; row < table->rowCount(); row++) {
auto item = table->item(row, 0);
if (!item) {
continue;
}
if (item->text() == oldName) {
item->setText(newName);
table->sortByColumn(0, Qt::AscendingOrder);
return;
}
}
assert(false);
}
void RemoveItemTableRow(QTableWidget *table, const QString &name)
{
for (int row = 0; row < table->rowCount(); ++row) {
auto item = table->item(row, 0);
if (item && item->text() == name) {
table->removeRow(row);
return;
}
}
table->sortByColumn(0, Qt::AscendingOrder);
}
} // namespace advss

View File

@@ -0,0 +1,50 @@
#pragma once
#include "export-symbol-helper.hpp"
#include <QLabel>
#include <QPushButton>
#include <QString>
#include <QTableWidget>
class QResizeEvent;
namespace advss {
class ADVSS_EXPORT ResourceTable : public QWidget {
Q_OBJECT
public:
ResourceTable(QTabWidget *parent, const QString &help,
const QString &addToolTip, const QString &removeToolTip,
const QStringList &headers,
const std::function<void()> &openSettings);
virtual ~ResourceTable();
QTableWidget *Table() const { return _table; }
void SetHelpVisible(bool) const;
void HighlightAddButton(bool);
protected slots:
virtual void Add() {}
virtual void Remove() {}
protected:
void resizeEvent(QResizeEvent *event);
private:
QTableWidget *_table;
QPushButton *_add;
QPushButton *_remove;
QLabel *_help;
QMetaObject::Connection _highlightConnection;
};
EXPORT void AddItemTableRow(QTableWidget *table, const QStringList &cells);
EXPORT void UpdateItemTableRow(QTableWidget *table, int row,
const QStringList &cells);
EXPORT void RenameItemTableRow(QTableWidget *table, const QString &oldName,
const QString &newName);
EXPORT void RemoveItemTableRow(QTableWidget *table, const QString &name);
} // namespace advss

View File

@@ -5,8 +5,8 @@
namespace advss {
EXPORT std::mutex *GetMutex();
EXPORT std::lock_guard<std::mutex> LockContext();
EXPORT std::unique_lock<std::mutex> *GetLoopLock();
[[nodiscard]] EXPORT std::mutex *GetMutex();
[[nodiscard]] EXPORT std::lock_guard<std::mutex> LockContext();
[[nodiscard]] EXPORT std::unique_lock<std::mutex> *GetLoopLock();
} // namespace advss

182
lib/utils/tab-helpers.cpp Normal file
View File

@@ -0,0 +1,182 @@
#include "tab-helpers.hpp"
#include "log-helper.hpp"
#include "obs-module-helper.hpp"
#include <obs.hpp>
#include <QTabBar>
#include <string>
#include <unordered_map>
#include <vector>
namespace advss {
static std::vector<std::string> tabNames = {
"generalTab", "macroTab", "windowTitleTab",
"executableTab", "screenRegionTab", "mediaTab",
"fileTab", "randomTab", "timeTab",
"idleTab", "sceneSequenceTab", "audioTab",
"videoTab", "networkTab", "sceneGroupTab",
"transitionsTab", "pauseTab", "sceneTriggerTab"};
static std::vector<int> tabOrder = std::vector<int>(tabNames.size());
namespace {
struct TabCallbacks {
std::function<QWidget *()> createWidget;
std::function<void(QTabWidget *)> setupTab;
};
} // namespace
static std::unordered_map<const char *, TabCallbacks> createTabCallbacks;
static int lastOpenedTab = -1;
void SetTabVisibleByName(QTabWidget *tabWidget, bool visible,
const QString &name)
{
for (int idx = 0; idx < tabWidget->count(); idx++) {
if (tabWidget->tabText(idx) != name) {
continue;
}
tabWidget->setTabVisible(idx, visible);
}
}
void AddSetupTabCallback(const char *tabName,
std::function<QWidget *()> createWidget,
std::function<void(QTabWidget *)> setupTab)
{
if (std::find(tabNames.begin(), tabNames.end(), tabName) !=
tabNames.end()) {
return;
}
tabNames.emplace_back(tabName);
TabCallbacks callbacks = {createWidget, setupTab};
createTabCallbacks.emplace(tabName, callbacks);
}
void SaveLastOpenedTab(QTabWidget *tabWidget)
{
lastOpenedTab = tabWidget->currentIndex();
}
void ResetLastOpenedTab()
{
lastOpenedTab = -1;
}
void SaveTabOrder(obs_data_t *obj)
{
OBSDataArrayAutoRelease tabWidgetOrder = obs_data_array_create();
for (size_t i = 0; i < tabNames.size(); i++) {
OBSDataAutoRelease entry = obs_data_create();
obs_data_set_int(entry, tabNames[i].c_str(), tabOrder[i]);
obs_data_array_push_back(tabWidgetOrder, entry);
}
obs_data_set_array(obj, "tabWidgetOrder", tabWidgetOrder);
}
static bool tabWidgetOrderValid()
{
auto tmp = std::vector<int>(tabNames.size());
std::iota(tmp.begin(), tmp.end(), 0);
for (auto &p : tmp) {
auto it = std::find(tabOrder.begin(), tabOrder.end(), p);
if (it == tabOrder.end()) {
return false;
}
}
return true;
}
static void resetTabWidgetOrder()
{
tabOrder = std::vector<int>(tabNames.size());
std::iota(tabOrder.begin(), tabOrder.end(), 0);
}
void LoadTabOrder(obs_data_t *obj)
{
OBSDataArrayAutoRelease defaultTabWidgetOrder = obs_data_array_create();
for (size_t i = 0; i < tabNames.size(); i++) {
OBSDataAutoRelease entry = obs_data_create();
obs_data_set_default_int(entry, tabNames[i].c_str(), i);
obs_data_array_push_back(defaultTabWidgetOrder, entry);
}
obs_data_set_default_array(obj, "tabWidgetOrder",
defaultTabWidgetOrder);
tabOrder.clear();
OBSDataArrayAutoRelease tabWidgetOrder =
obs_data_get_array(obj, "tabWidgetOrder");
for (size_t i = 0; i < tabNames.size(); i++) {
OBSDataAutoRelease entry =
obs_data_array_item(tabWidgetOrder, i);
tabOrder.emplace_back(
(int)(obs_data_get_int(entry, tabNames[i].c_str())));
}
if (!tabWidgetOrderValid()) {
resetTabWidgetOrder();
}
}
static int findTabIndex(QTabWidget *tabWidget, int pos)
{
int at = -1;
QString tabName = QString::fromStdString(tabNames.at(pos));
QWidget *page = tabWidget->findChild<QWidget *>(tabName);
if (page) {
at = tabWidget->indexOf(page);
}
if (at == -1) {
blog(LOG_INFO, "failed to find tab %s",
tabName.toUtf8().constData());
}
return at;
}
void SetTabOrder(QTabWidget *tabWidget)
{
if (!tabWidgetOrderValid()) {
resetTabWidgetOrder();
}
auto bar = tabWidget->tabBar();
for (int i = 0; i < bar->count(); ++i) {
int curPos = findTabIndex(tabWidget, tabOrder[i]);
if (i != curPos && curPos != -1) {
bar->moveTab(curPos, i);
}
}
QWidget::connect(bar, &QTabBar::tabMoved, [](int from, int to) {
std::swap(tabOrder[from], tabOrder[to]);
});
}
void SetCurrentTab(QTabWidget *tabWidget)
{
if (lastOpenedTab >= 0) {
tabWidget->setCurrentIndex(lastOpenedTab);
}
}
void SetupOtherTabs(QTabWidget *tabWidget)
{
for (const auto &[name, callbacks] : createTabCallbacks) {
auto widget = callbacks.createWidget();
widget->setObjectName(name);
auto tabText = obs_module_text(
(std::string("AdvSceneSwitcher.") + name + ".title")
.c_str());
tabWidget->insertTab(0, widget, tabText);
callbacks.setupTab(tabWidget);
}
}
} // namespace advss

22
lib/utils/tab-helpers.hpp Normal file
View File

@@ -0,0 +1,22 @@
#pragma once
#include "export-symbol-helper.hpp"
#include <obs-data.h>
#include <QTabWidget>
namespace advss {
EXPORT void AddSetupTabCallback(const char *tabName,
std::function<QWidget *()> createWidget,
std::function<void(QTabWidget *)> setupTab);
EXPORT void SetTabVisibleByName(QTabWidget *tabWidget, bool visible,
const QString &name);
void SaveLastOpenedTab(QTabWidget *tabWidget);
void ResetLastOpenedTab();
void SetTabOrder(QTabWidget *tabWidget);
void SetCurrentTab(QTabWidget *tabWidget);
void SetupOtherTabs(QTabWidget *tabWidget);
void SaveTabOrder(obs_data_t *obj);
void LoadTabOrder(obs_data_t *obj);
} // namespace advss

View File

@@ -65,6 +65,7 @@ public:
EXPORT void Load(obs_data_t *, Macro *, const char *name = "tempVar");
EXPORT std::optional<const TempVariable> GetTempVariable(Macro *) const;
EXPORT bool operator==(const TempVariableRef &other) const;
bool HasValidID() const { return !_id.empty(); }
private:
enum class SegmentType { NONE, CONDITION, ACTION, ELSEACTION };

View File

@@ -1,28 +1,61 @@
#include "advanced-scene-switcher.hpp"
#include "variable-tab.hpp"
#include "log-helper.hpp"
#include "obs-module-helper.hpp"
#include "plugin-state-helpers.hpp"
#include "sync-helpers.hpp"
#include "tab-helpers.hpp"
#include "ui-helpers.hpp"
#include "variable.hpp"
#include <QTableWidgetItem>
#include <QTimer>
namespace advss {
static void setVariableTabVisible(QTabWidget *tabWidget, bool visible)
{
for (int idx = 0; idx < tabWidget->count(); idx++) {
if (tabWidget->tabText(idx) !=
obs_module_text("AdvSceneSwitcher.variableTab.title")) {
continue;
}
static bool registerTab();
static void setupTab(QTabWidget *);
static bool registerTabDone = registerTab();
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
// TODO: Switch to setTabVisible() once QT 5.15 is more wide spread
tabWidget->setTabEnabled(idx, visible);
tabWidget->setStyleSheet(
"QTabBar::tab::disabled {width: 0; height: 0; margin: 0; padding: 0; border: none;} ");
#else
tabWidget->setTabVisible(idx, visible);
#endif
static VariableTable *tabWidget = nullptr;
static bool registerTab()
{
AddPluginInitStep([]() {
AddSetupTabCallback("variableTab", VariableTable::Create,
setupTab);
});
return true;
}
static void setTabVisible(QTabWidget *tabWidget, bool visible)
{
SetTabVisibleByName(
tabWidget, visible,
obs_module_text("AdvSceneSwitcher.variableTab.title"));
}
VariableTable *VariableTable::Create()
{
tabWidget = new VariableTable();
return tabWidget;
}
void VariableTable::Add()
{
auto newVariable = std::make_shared<Variable>();
auto accepted =
VariableSettingsDialog::AskForSettings(this, *newVariable);
if (!accepted) {
return;
}
{
auto lock = LockContext();
auto &variables = GetVariables();
variables.emplace_back(newVariable);
}
VariableSignalManager::Instance()->Add(
QString::fromStdString(newVariable->Name()));
}
static QString formatSaveActionText(Variable *variable)
@@ -91,46 +124,18 @@ static QString formatLastChangedTooltip(Variable *variable)
.arg(QString::fromStdString(variable->GetPreviousValue()));
}
static void addVariableRow(QTableWidget *table, Variable *variable)
static QStringList getCellLabels(Variable *variable, bool addName = true)
{
if (!variable) {
blog(LOG_INFO, "%s called with nullptr", __func__);
assert(false);
return;
assert(variable);
auto result = QStringList();
if (addName) {
result << QString::fromStdString(variable->Name());
}
int row = table->rowCount();
table->setRowCount(row + 1);
int col = 0;
auto *item =
new QTableWidgetItem(QString::fromStdString(variable->Name()));
table->setItem(row, col, item);
auto varValue = QString::fromStdString(variable->Value(false));
item = new QTableWidgetItem(varValue);
item->setToolTip(varValue);
table->setItem(row, ++col, item);
item = new QTableWidgetItem(formatSaveActionText(variable));
table->setItem(row, ++col, item);
item = new QTableWidgetItem(formatLastUsedText(variable));
table->setItem(row, ++col, item);
item = new QTableWidgetItem(formatLastChangedText(variable));
item->setToolTip(formatLastChangedTooltip(variable));
table->setItem(row, ++col, item);
table->sortByColumn(0, Qt::AscendingOrder);
}
static void removeVariableRow(QTableWidget *table, const QString &name)
{
for (int row = 0; row < table->rowCount(); ++row) {
auto item = table->item(row, 0);
if (item && item->text() == name) {
table->removeRow(row);
return;
}
}
table->sortByColumn(0, Qt::AscendingOrder);
result << QString::fromStdString(variable->Value(false))
<< formatSaveActionText(variable) << formatLastUsedText(variable)
<< formatLastChangedText(variable);
return result;
}
static void updateVariableStatus(QTableWidget *table)
@@ -147,116 +152,20 @@ static void updateVariableStatus(QTableWidget *table)
continue;
}
item = table->item(row, 1);
auto varValue = QString::fromStdString(variable->Value(false));
item->setText(varValue);
item->setToolTip(varValue);
item = table->item(row, 2);
item->setText(formatSaveActionText(variable.get()));
item = table->item(row, 3);
item->setText(formatLastUsedText(variable.get()));
item = table->item(row, 4);
item->setText(formatLastChangedText(variable.get()));
item->setToolTip(formatLastChangedTooltip(variable.get()));
UpdateItemTableRow(table, row,
getCellLabels(variable.get(), false));
}
}
static void renameVariable(QTableWidget *table, const QString &oldName,
const QString &newName)
static void openSettingsDialog()
{
for (int row = 0; row < table->rowCount(); row++) {
auto item = table->item(row, 0);
if (!item) {
continue;
}
if (item->text() == oldName) {
item->setText(newName);
table->sortByColumn(0, Qt::AscendingOrder);
return;
}
}
blog(LOG_INFO, "%s called but entry \"%s\" not found", __func__,
oldName.toStdString().c_str());
assert(false);
}
void AdvSceneSwitcher::SetupVariableTab()
{
ui->variables->installEventFilter(this);
if (GetVariables().empty()) {
setVariableTabVisible(ui->tabWidget, false);
} else {
ui->variablesHelp->hide();
}
static const QStringList horizontalHeaders =
QStringList()
<< obs_module_text("AdvSceneSwitcher.variableTab.name.header")
<< obs_module_text("AdvSceneSwitcher.variableTab.value.header")
<< obs_module_text(
"AdvSceneSwitcher.variableTab.saveLoadBehavior.header")
<< obs_module_text(
"AdvSceneSwitcher.variableTab.lastUsed.header")
<< obs_module_text(
"AdvSceneSwitcher.variableTab.lastChanged.header");
auto &variables = GetVariables();
ui->variables->setColumnCount(horizontalHeaders.size());
ui->variables->horizontalHeader()->setSectionResizeMode(
QHeaderView::ResizeMode::Stretch);
ui->variables->setHorizontalHeaderLabels(horizontalHeaders);
for (const auto &var : variables) {
auto variable = std::static_pointer_cast<Variable>(var);
addVariableRow(ui->variables, variable.get());
}
ui->variables->resizeColumnsToContents();
ui->variables->resizeRowsToContents();
QWidget::connect(
VariableSignalManager::Instance(),
&VariableSignalManager::Rename,
[this](const QString &oldName, const QString &newName) {
renameVariable(ui->variables, oldName, newName);
});
QWidget::connect(VariableSignalManager::Instance(),
&VariableSignalManager::Add, this,
[this](const QString &name) {
addVariableRow(ui->variables,
GetVariableByQString(name));
ui->variablesHelp->hide();
setVariableTabVisible(ui->tabWidget, true);
});
QWidget::connect(VariableSignalManager::Instance(),
&VariableSignalManager::Remove, this,
[this](const QString &name) {
removeVariableRow(ui->variables, name);
if (ui->variables->rowCount() == 0) {
ui->variablesHelp->show();
}
});
QWidget::connect(ui->variables, &QTableWidget::cellDoubleClicked, this,
&AdvSceneSwitcher::OpenSettingsForSelectedVariable);
auto timer = new QTimer(this);
timer->setInterval(1000);
QWidget::connect(timer, &QTimer::timeout,
[this]() { updateVariableStatus(ui->variables); });
timer->start();
}
void AdvSceneSwitcher::OpenSettingsForSelectedVariable()
{
auto selectedRows = ui->variables->selectionModel()->selectedRows();
auto selectedRows =
tabWidget->Table()->selectionModel()->selectedRows();
if (selectedRows.empty()) {
return;
}
auto cell = ui->variables->item(selectedRows.last().row(), 0);
auto cell = tabWidget->Table()->item(selectedRows.last().row(), 0);
if (!cell) {
return;
}
@@ -268,8 +177,8 @@ void AdvSceneSwitcher::OpenSettingsForSelectedVariable()
}
auto oldName = variable->Name();
bool accepted = VariableSettingsDialog::AskForSettings(ui->variables,
*variable.get());
bool accepted = VariableSettingsDialog::AskForSettings(
tabWidget->Table(), *variable.get());
if (accepted && oldName != variable->Name()) {
VariableSignalManager::Instance()->Rename(
QString::fromStdString(oldName),
@@ -277,35 +186,17 @@ void AdvSceneSwitcher::OpenSettingsForSelectedVariable()
}
}
static void removeVariablesWithNames(const QStringList &varNames)
void VariableTable::Remove()
{
for (const auto &name : varNames) {
auto variable = GetVariableByQString(name);
if (!variable) {
continue;
}
auto &variables = GetVariables();
variables.erase(
std::remove_if(
variables.begin(), variables.end(),
[variable](const std::shared_ptr<Item> &item) {
return item.get() == variable;
}),
variables.end());
}
}
void AdvSceneSwitcher::RemoveSelectedVariables()
{
auto selectedRows = ui->variables->selectionModel()->selectedRows();
auto selectedRows =
tabWidget->Table()->selectionModel()->selectedRows();
if (selectedRows.empty()) {
return;
}
QStringList varNames;
for (auto row : selectedRows) {
auto cell = ui->variables->item(row.row(), 0);
for (const auto &row : selectedRows) {
auto cell = tabWidget->Table()->item(row.row(), 0);
if (!cell) {
continue;
}
@@ -330,7 +221,7 @@ void AdvSceneSwitcher::RemoveSelectedVariables()
{
auto lock = LockContext();
removeVariablesWithNames(varNames);
RemoveItemsByName(GetVariables(), varNames);
}
for (const auto &name : varNames) {
@@ -338,28 +229,71 @@ void AdvSceneSwitcher::RemoveSelectedVariables()
}
}
void AdvSceneSwitcher::on_variableAdd_clicked()
VariableTable::VariableTable(QTabWidget *parent)
: ResourceTable(
parent, obs_module_text("AdvSceneSwitcher.variableTab.help"),
obs_module_text(
"AdvSceneSwitcher.variableTab.variableAddButton.tooltip"),
obs_module_text(
"AdvSceneSwitcher.variableTab.variableRemoveButton.tooltip"),
QStringList()
<< obs_module_text(
"AdvSceneSwitcher.variableTab.name.header")
<< obs_module_text(
"AdvSceneSwitcher.variableTab.value.header")
<< obs_module_text(
"AdvSceneSwitcher.variableTab.saveLoadBehavior.header")
<< obs_module_text(
"AdvSceneSwitcher.variableTab.lastUsed.header")
<< obs_module_text(
"AdvSceneSwitcher.variableTab.lastChanged.header"),
openSettingsDialog)
{
auto newVariable = std::make_shared<Variable>();
auto accepted =
VariableSettingsDialog::AskForSettings(this, *newVariable);
if (!accepted) {
return;
for (const auto &variable : GetVariables()) {
auto v = std::static_pointer_cast<Variable>(variable);
AddItemTableRow(Table(), getCellLabels(v.get()));
}
{
auto lock = LockContext();
auto &variables = GetVariables();
variables.emplace_back(newVariable);
}
VariableSignalManager::Instance()->Add(
QString::fromStdString(newVariable->Name()));
SetHelpVisible(GetVariables().empty());
}
void AdvSceneSwitcher::on_variableRemove_clicked()
static void setupTab(QTabWidget *tab)
{
RemoveSelectedVariables();
if (GetVariables().empty()) {
setTabVisible(tab, false);
}
QWidget::connect(VariableSignalManager::Instance(),
&VariableSignalManager::Rename,
[](const QString &oldName, const QString &newName) {
RenameItemTableRow(tabWidget->Table(), oldName,
newName);
});
QWidget::connect(
VariableSignalManager::Instance(), &VariableSignalManager::Add,
[tab](const QString &name) {
AddItemTableRow(
tabWidget->Table(),
getCellLabels(GetVariableByQString(name)));
tabWidget->SetHelpVisible(false);
tabWidget->HighlightAddButton(false);
setTabVisible(tab, true);
});
QWidget::connect(VariableSignalManager::Instance(),
&VariableSignalManager::Remove,
[](const QString &name) {
RemoveItemTableRow(tabWidget->Table(), name);
if (tabWidget->Table()->rowCount() == 0) {
tabWidget->SetHelpVisible(true);
tabWidget->HighlightAddButton(true);
}
});
auto timer = new QTimer(tabWidget);
timer->setInterval(1000);
QWidget::connect(timer, &QTimer::timeout,
[]() { updateVariableStatus(tabWidget->Table()); });
timer->start();
}
} // namespace advss

View File

@@ -0,0 +1,20 @@
#pragma once
#include "resource-table.hpp"
namespace advss {
class VariableTable final : public ResourceTable {
Q_OBJECT
public:
static VariableTable *Create();
private slots:
void Add();
void Remove();
private:
VariableTable(QTabWidget *parent = nullptr);
};
} // namespace advss

View File

@@ -87,6 +87,8 @@ target_sources(
macro-condition-file.hpp
macro-condition-filter.cpp
macro-condition-filter.hpp
macro-condition-folder.cpp
macro-condition-folder.hpp
macro-condition-hotkey.cpp
macro-condition-hotkey.hpp
macro-condition-idle.cpp
@@ -175,7 +177,9 @@ target_sources(
utils/transition-selection.cpp
utils/transition-selection.hpp
utils/websocket-helpers.cpp
utils/websocket-helpers.hpp)
utils/websocket-helpers.hpp
utils/websocket-tab.cpp
utils/websocket-tab.hpp)
if(OS_WINDOWS)
target_sources(${PROJECT_NAME} PRIVATE utils/windows/windows.cpp)

View File

@@ -94,15 +94,15 @@ MacroActionSystrayEdit::MacroActionSystrayEdit(
layout->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.action.systray.title")),
row, 0);
layout->addWidget(_title, ++row, 1);
layout->addWidget(_title, row++, 1);
layout->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.action.systray.message")),
row, 0);
layout->addWidget(_message, ++row, 1);
layout->addWidget(_message, row++, 1);
layout->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.action.systray.icon")),
row, 0);
layout->addWidget(_iconPath, ++row, 1);
layout->addWidget(_iconPath, row++, 1);
auto mainlayout = new QVBoxLayout();
mainlayout->addLayout(layout);

View File

@@ -151,7 +151,7 @@ MacroActionWebsocketEdit::MacroActionWebsocketEdit(
_apiType(new QComboBox(this)),
_messageType(new QComboBox(this)),
_message(new VariableTextEdit(this)),
_connection(new ConnectionSelection(this)),
_connection(new WSConnectionSelection(this)),
_editLayout(new QHBoxLayout()),
_settingsConflict(new QLabel())
{

View File

@@ -37,7 +37,7 @@ public:
API _api = API::SCENE_SWITCHER;
MessageType _type = MessageType::REQUEST;
StringVariable _message = obs_module_text("AdvSceneSwitcher.enterText");
std::weak_ptr<Connection> _connection;
std::weak_ptr<WSConnection> _connection;
private:
void SendRequest(const std::string &msg);
@@ -84,7 +84,7 @@ private:
QComboBox *_apiType;
QComboBox *_messageType;
VariableTextEdit *_message;
ConnectionSelection *_connection;
WSConnectionSelection *_connection;
QHBoxLayout *_editLayout;
QLabel *_settingsConflict;

View File

@@ -2,6 +2,7 @@
#include "layout-helpers.hpp"
#include "platform-funcs.hpp"
#include "selection-helpers.hpp"
#include "ui-helpers.hpp"
namespace advss {
@@ -163,7 +164,8 @@ MacroActionWindowEdit::MacroActionWindowEdit(
: QWidget(parent),
_actions(new QComboBox()),
_windows(new QComboBox()),
_regex(new RegexConfigWidget(this))
_regex(new RegexConfigWidget(this)),
_infoLayout(new QHBoxLayout())
{
populateActionSelection(_actions);
@@ -171,6 +173,22 @@ MacroActionWindowEdit::MacroActionWindowEdit(
_windows->setMaxVisibleItems(20);
PopulateWindowSelection(_windows);
auto focusLimitation = new QLabel(obs_module_text(
"AdvSceneSwitcher.action.window.type.setFocusWindow.limitation"));
_infoLayout->addWidget(focusLimitation);
const QString path = GetThemeTypeName() == "Light"
? ":/res/images/help.svg"
: ":/res/images/help_light.svg";
const QIcon icon(path);
const QPixmap pixmap = icon.pixmap(QSize(16, 16));
auto focusLimitationDetails = new QLabel();
focusLimitationDetails->setPixmap(pixmap);
focusLimitationDetails->setToolTip(obs_module_text(
"AdvSceneSwitcher.action.window.type.setFocusWindow.limitation.details"));
_infoLayout->addWidget(focusLimitationDetails);
_infoLayout->addStretch();
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QWidget::connect(_windows, SIGNAL(currentTextChanged(const QString &)),
@@ -179,12 +197,15 @@ MacroActionWindowEdit::MacroActionWindowEdit(
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
SLOT(RegexChanged(const RegexConfig &)));
auto layout = new QHBoxLayout;
auto entryLayout = new QHBoxLayout;
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.window.entry"),
layout,
entryLayout,
{{"{{actions}}", _actions},
{"{{windows}}", _windows},
{"{{regex}}", _regex}});
auto layout = new QVBoxLayout();
layout->addLayout(entryLayout);
layout->addLayout(_infoLayout);
setLayout(layout);
_entryData = entryData;
@@ -200,8 +221,7 @@ void MacroActionWindowEdit::UpdateEntryData()
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_windows->setCurrentText(QString::fromStdString(_entryData->_window));
_regex->SetRegexConfig(_entryData->_regex);
adjustSize();
updateGeometry();
SetWidgetVisibility();
}
void MacroActionWindowEdit::WindowChanged(const QString &text)
@@ -228,6 +248,15 @@ void MacroActionWindowEdit::RegexChanged(const RegexConfig &regex)
updateGeometry();
}
void MacroActionWindowEdit::SetWidgetVisibility()
{
SetLayoutVisible(_infoLayout,
_entryData->_action ==
MacroActionWindow::Action::SET_FOCUS_WINDOW);
adjustSize();
updateGeometry();
}
void MacroActionWindowEdit::ActionChanged(int value)
{
if (_loading || !_entryData) {
@@ -236,6 +265,7 @@ void MacroActionWindowEdit::ActionChanged(int value)
auto lock = LockContext();
_entryData->_action = static_cast<MacroActionWindow::Action>(value);
SetWidgetVisibility();
}
} // namespace advss

View File

@@ -59,9 +59,12 @@ signals:
void HeaderInfoChanged(const QString &);
private:
void SetWidgetVisibility();
QComboBox *_actions;
QComboBox *_windows;
RegexConfigWidget *_regex;
QHBoxLayout *_infoLayout;
std::shared_ptr<MacroActionWindow> _entryData;
bool _loading = true;

View File

@@ -61,13 +61,49 @@ MacroConditionAudio::~MacroConditionAudio()
obs_volmeter_destroy(_volmeter);
}
float MacroConditionAudio::GetVolumePeak()
{
using namespace std::chrono_literals;
using namespace std::chrono;
static constexpr std::chrono::milliseconds timeout = 250ms;
// OBS might rarely not update _peak quickly enough when very low
// intervals are configured on the General tab.
// In that case _peak might be set to negative infinity still, which
// will result in unexpected behavior, so we use the previously valid
// peak value instead.
//
// If no volume update was received within a timeout window, however, it
// is assumed, that the source no longer produces any audio output and
// thus a peak volume value of negative infinity is used.
float peak;
std::lock_guard<std::mutex> lock(_peakMutex);
auto msPassedSinceLastUpdate = duration_cast<milliseconds>(
high_resolution_clock::now() - _lastPeakUpdate);
if (_lastPeakUpdate.time_since_epoch().count() != 0 &&
msPassedSinceLastUpdate > timeout) {
peak = -std::numeric_limits<float>::infinity();
} else {
peak = _peakUpdated ? _peak : _previousPeak;
}
_previousPeak = peak;
_peak = -std::numeric_limits<float>::infinity();
_peakUpdated = false;
return peak;
}
bool MacroConditionAudio::CheckOutputCondition()
{
bool ret = false;
OBSSourceAutoRelease source =
obs_weak_source_get_source(_audioSource.GetSource());
double curVolume = _useDb ? _peak : DecibelToPercent(_peak) * 100;
float peak = GetVolumePeak();
double curVolume = _useDb ? peak : DecibelToPercent(peak) * 100;
switch (_outputCondition) {
case OutputCondition::ABOVE:
@@ -92,7 +128,6 @@ bool MacroConditionAudio::CheckOutputCondition()
SetTempVarValue("output_volume", std::to_string(curVolume));
// Reset for next check
_peak = -std::numeric_limits<float>::infinity();
if (_audioSource.GetType() == SourceSelection::Type::VARIABLE) {
ResetVolmeter();
}
@@ -329,11 +364,14 @@ void MacroConditionAudio::SetVolumeLevel(void *data, const float *,
return;
}
std::lock_guard<std::mutex> lock(c->_peakMutex);
for (int i = 0; i < MAX_AUDIO_CHANNELS; i++) {
if (peak[i] > c->_peak) {
c->_peak = peak[i];
}
}
c->_peakUpdated = true;
c->_lastPeakUpdate = std::chrono::high_resolution_clock::now();
}
void MacroConditionAudio::ResetVolmeter()

View File

@@ -71,9 +71,14 @@ private:
bool CheckMonitor();
bool CheckBalance();
void SetupTempVars();
float GetVolumePeak();
Type _checkType = Type::OUTPUT_VOLUME;
std::mutex _peakMutex;
float _peak = -std::numeric_limits<float>::infinity();
float _previousPeak = -std::numeric_limits<float>::infinity();
bool _peakUpdated = false;
std::chrono::high_resolution_clock::time_point _lastPeakUpdate = {};
static bool _registered;
static const std::string id;
};

View File

@@ -42,6 +42,12 @@ static std::string getRemoteData(std::string &url)
return readBuffer;
}
void MacroConditionFile::SetCondition(Condition condition)
{
_condition = condition;
SetupTempVars();
}
bool MacroConditionFile::MatchFileContent(QString &filedata)
{
if (_onlyMatchIfChanged) {
@@ -65,6 +71,7 @@ bool MacroConditionFile::CheckRemoteFileContent()
std::string path = _file;
std::string data = getRemoteData(path);
SetVariableValue(data);
SetTempVarValue("content", data);
QString qdata = QString::fromStdString(data);
return MatchFileContent(qdata);
}
@@ -86,6 +93,7 @@ bool MacroConditionFile::CheckLocalFileContent()
QString filedata = QTextStream(&file).readAll();
SetVariableValue(filedata.toStdString());
SetTempVarValue("content", filedata.toStdString());
bool match = MatchFileContent(filedata);
file.close();
@@ -114,6 +122,7 @@ bool MacroConditionFile::CheckChangeContent()
break;
}
SetTempVarValue("content", filedata.toStdString());
size_t newHash = strHash(filedata.toUtf8().constData());
const bool contentChanged = newHash != _lastHash;
_lastHash = newHash;
@@ -131,24 +140,39 @@ bool MacroConditionFile::CheckChangeDate()
SetVariableValue(newLastMod.toString().toStdString());
const bool dateChanged = _lastMod != newLastMod;
_lastMod = newLastMod;
SetTempVarValue("date", newLastMod.toString(Qt::ISODate).toStdString());
return dateChanged;
}
void MacroConditionFile::SetupTempVars()
{
MacroCondition::SetupTempVars();
if (_condition == Condition::DATE_CHANGE) {
AddTempvar(
"date",
obs_module_text("AdvSceneSwitcher.tempVar.file.date"));
} else {
AddTempvar("content",
obs_module_text(
"AdvSceneSwitcher.tempVar.file.content"));
}
}
bool MacroConditionFile::CheckCondition()
{
bool ret = false;
switch (_condition) {
case MacroConditionFile::ConditionType::MATCH:
case Condition::MATCH:
if (_fileType == FileType::REMOTE) {
ret = CheckRemoteFileContent();
break;
}
ret = CheckLocalFileContent();
break;
case MacroConditionFile::ConditionType::CONTENT_CHANGE:
case Condition::CONTENT_CHANGE:
ret = CheckChangeContent();
break;
case MacroConditionFile::ConditionType::DATE_CHANGE:
case Condition::DATE_CHANGE:
ret = CheckChangeDate();
break;
default:
@@ -158,7 +182,6 @@ bool MacroConditionFile::CheckCondition()
if (GetVariableValue().empty()) {
SetVariableValue(ret ? "true" : "false");
}
return ret;
}
@@ -187,8 +210,8 @@ bool MacroConditionFile::Load(obs_data_t *obj)
_file.Load(obj, "file");
_text.Load(obj, "text");
_fileType = static_cast<FileType>(obs_data_get_int(obj, "fileType"));
_condition =
static_cast<ConditionType>(obs_data_get_int(obj, "condition"));
SetCondition(
static_cast<Condition>(obs_data_get_int(obj, "condition")));
_useTime = obs_data_get_bool(obj, "useTime");
_onlyMatchIfChanged = obs_data_get_bool(obj, "onlyMatchIfChanged");
return true;
@@ -292,7 +315,8 @@ void MacroConditionFileEdit::UpdateEntryData()
}
_fileTypes->setCurrentIndex(static_cast<int>(_entryData->_fileType));
_conditions->setCurrentIndex(static_cast<int>(_entryData->_condition));
_conditions->setCurrentIndex(
static_cast<int>(_entryData->GetCondition()));
_filePath->SetPath(_entryData->_file);
_matchText->setPlainText(_entryData->_text);
_regex->SetRegexConfig(_entryData->_regex);
@@ -338,8 +362,8 @@ void MacroConditionFileEdit::ConditionChanged(int index)
}
auto lock = LockContext();
_entryData->_condition =
static_cast<MacroConditionFile::ConditionType>(index);
_entryData->SetCondition(
static_cast<MacroConditionFile::Condition>(index));
SetWidgetVisibility();
}
@@ -406,18 +430,18 @@ void MacroConditionFileEdit::SetWidgetVisibility()
return;
}
_matchText->setVisible(_entryData->_condition ==
MacroConditionFile::ConditionType::MATCH);
_regex->setVisible(_entryData->_condition ==
MacroConditionFile::ConditionType::MATCH);
_matchText->setVisible(_entryData->GetCondition() ==
MacroConditionFile::Condition::MATCH);
_regex->setVisible(_entryData->GetCondition() ==
MacroConditionFile::Condition::MATCH);
_checkModificationDate->setVisible(
_entryData->_useTime &&
_entryData->_condition ==
MacroConditionFile::ConditionType::MATCH);
_entryData->GetCondition() ==
MacroConditionFile::Condition::MATCH);
_checkFileContent->setVisible(
_entryData->_onlyMatchIfChanged &&
_entryData->_condition ==
MacroConditionFile::ConditionType::MATCH);
_entryData->GetCondition() ==
MacroConditionFile::Condition::MATCH);
adjustSize();
updateGeometry();
}

View File

@@ -31,16 +31,17 @@ public:
REMOTE,
};
enum class ConditionType {
enum class Condition {
MATCH,
CONTENT_CHANGE,
DATE_CHANGE,
};
void SetCondition(Condition condition);
Condition GetCondition() const { return _condition; }
StringVariable _file = obs_module_text("AdvSceneSwitcher.enterPath");
StringVariable _text = obs_module_text("AdvSceneSwitcher.enterText");
FileType _fileType = FileType::LOCAL;
ConditionType _condition = ConditionType::MATCH;
RegexConfig _regex;
// TODO: Remove in future version
@@ -53,7 +54,9 @@ private:
bool CheckLocalFileContent();
bool CheckChangeContent();
bool CheckChangeDate();
void SetupTempVars();
Condition _condition = Condition::MATCH;
QDateTime _lastMod;
size_t _lastHash = 0;
static bool _registered;

View File

@@ -0,0 +1,414 @@
#include "macro-condition-folder.hpp"
#include "macro-helpers.hpp"
#include "layout-helpers.hpp"
#include "ui-helpers.hpp"
#include <QDir>
#include <QFileInfo>
namespace advss {
const std::string MacroConditionFolder::id = "folder";
bool MacroConditionFolder::_registered = MacroConditionFactory::Register(
MacroConditionFolder::id,
{MacroConditionFolder::Create, MacroConditionFolderEdit::Create,
"AdvSceneSwitcher.condition.folder"});
static const std::map<MacroConditionFolder::Condition, std::string> conditions =
{
{MacroConditionFolder::Condition::ANY,
"AdvSceneSwitcher.condition.folder.condition.any"},
{MacroConditionFolder::Condition::FILE_ADD,
"AdvSceneSwitcher.condition.folder.condition.fileAdd"},
{MacroConditionFolder::Condition::FILE_CHANGE,
"AdvSceneSwitcher.condition.folder.condition.fileChange"},
{MacroConditionFolder::Condition::FILE_REMOVE,
"AdvSceneSwitcher.condition.folder.condition.fileRemove"},
{MacroConditionFolder::Condition::FOLDER_ADD,
"AdvSceneSwitcher.condition.folder.condition.folderAdd"},
{MacroConditionFolder::Condition::FOLDER_REMOVE,
"AdvSceneSwitcher.condition.folder.condition.folderRemove"},
};
MacroConditionFolder::MacroConditionFolder(Macro *m) : MacroCondition(m, true)
{
}
bool MacroConditionFolder::CheckCondition()
{
std::lock_guard<std::mutex> lock(_mutex);
bool ret = _matched;
if (_lastWatchedValue != _folder.UnresolvedValue()) {
SetupWatcher();
}
SetTempVarValues();
_newFiles.clear();
_changedFiles.clear();
_removedFiles.clear();
_newDirs.clear();
_removedDirs.clear();
_matched = false;
return ret;
}
bool MacroConditionFolder::Save(obs_data_t *obj) const
{
MacroCondition::Save(obj);
_folder.Save(obj, "file");
obs_data_set_bool(obj, "enableFilter", _enableFilter);
_regex.Save(obj);
_filter.Save(obj, "filter");
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
return true;
}
bool MacroConditionFolder::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_folder.Load(obj, "file");
_enableFilter = obs_data_get_bool(obj, "enableFilter");
_regex.Load(obj);
_regex.SetEnabled(true); // Already controlled via _enableFilter
_filter.Load(obj, "filter");
_condition = static_cast<Condition>(obs_data_get_int(obj, "condition"));
SetupWatcher();
return true;
}
std::string MacroConditionFolder::GetShortDesc() const
{
return _folder.UnresolvedValue();
}
void MacroConditionFolder::SetFolder(const std::string &folder)
{
_folder = folder;
SetupWatcher();
}
static QSet<QString> getFilesInDir(const QString &path)
{
QSet<QString> result;
for (const auto &file : QDir(path).entryList(QDir::Files)) {
result << file;
}
return result;
}
static QSet<QString> getDirsInDir(const QString &path)
{
QSet<QString> result;
for (const auto &dir : QDir(path).entryList(QDir::AllDirs)) {
result << dir;
}
return result;
}
static void reduceSetToPatternMatch(QSet<QString> &set,
const RegexConfig &regex,
const std::string &pattern)
{
QSet<QString> copy = set;
for (const auto &value : copy) {
if (!regex.Matches(value.toStdString(), pattern)) {
set.remove(value);
}
}
}
void MacroConditionFolder::DirectoryChanged(const QString &path)
{
std::lock_guard<std::mutex> lock(_mutex);
if (MacroIsPaused(GetMacro())) {
return;
}
auto currentFiles = getFilesInDir(path);
auto currentDirs = getDirsInDir(path);
if (currentFiles.count() > _currentFiles.count()) {
_newFiles += currentFiles - _currentFiles;
} else {
_removedFiles += _currentFiles - currentFiles;
}
if (currentDirs.count() > _currentDirs.count()) {
_newDirs += currentDirs - _currentDirs;
} else {
_removedDirs += _currentDirs - currentDirs;
}
if (_enableFilter) {
reduceSetToPatternMatch(_newFiles, _regex, _filter);
reduceSetToPatternMatch(_removedFiles, _regex, _filter);
reduceSetToPatternMatch(_newDirs, _regex, _filter);
reduceSetToPatternMatch(_removedDirs, _regex, _filter);
}
switch (_condition) {
case Condition::ANY:
_matched = _matched || _newFiles.count() > 0 ||
_removedFiles.count() > 0 || _newDirs.count() > 0 ||
_removedDirs.count() > 0;
break;
case Condition::FILE_ADD:
_matched = _newFiles.count() > 0;
break;
case Condition::FILE_REMOVE:
_matched = _removedFiles.count() > 0;
break;
case Condition::FOLDER_ADD:
_matched = _newDirs.count() > 0;
break;
case Condition::FOLDER_REMOVE:
_matched = _removedDirs.count() > 0;
break;
default:
break;
}
for (const auto &newFile : _newFiles) {
_watcher->addPath(path + "/" + newFile);
}
_currentFiles = currentFiles;
_currentDirs = currentDirs;
}
void MacroConditionFolder::FileChanged(const QString &path)
{
std::lock_guard<std::mutex> lock(_mutex);
QFileInfo fileInfo(path);
if (!fileInfo.exists()) {
return;
}
const auto fileName = fileInfo.fileName();
if (_enableFilter && !_regex.Matches(fileName.toStdString(), _filter)) {
return;
}
_changedFiles << fileName;
if (_condition == Condition::FILE_CHANGE ||
_condition == Condition::ANY) {
_matched = true;
}
}
static QStringList getFileWatcherList(const QSet<QString> &files,
const QString &dirName)
{
QStringList list;
for (const auto &value : files) {
list << (dirName + "/" + value);
}
return list;
}
void MacroConditionFolder::SetupWatcher()
{
_watcher = std::make_unique<QFileSystemWatcher>();
const auto path = QString::fromStdString(_folder);
_currentFiles = getFilesInDir(path);
_currentDirs = getDirsInDir(path);
_lastWatchedValue = _folder.UnresolvedValue();
connect(_watcher.get(), SIGNAL(directoryChanged(const QString &)), this,
SLOT(DirectoryChanged(const QString &)));
connect(_watcher.get(), SIGNAL(fileChanged(const QString &)), this,
SLOT(FileChanged(const QString &)));
_watcher.get()->addPaths(getFileWatcherList(_currentFiles, path));
_watcher->addPath(path);
}
void MacroConditionFolder::SetTempVarValues()
{
auto setVarHelper = [this](const QSet<QString> &set,
const std::string &id) {
std::string result;
for (const auto &value : set) {
result += value.toStdString() + "\n";
}
if (result.size() > 0) {
result.pop_back();
}
SetTempVarValue(id, result);
};
setVarHelper(_newFiles, "newFiles");
setVarHelper(_changedFiles, "changedFiles");
setVarHelper(_removedFiles, "removedFiles");
setVarHelper(_newDirs, "newDirs");
setVarHelper(_removedDirs, "removedDirs");
}
void MacroConditionFolder::SetupTempVars()
{
MacroCondition::SetupTempVars();
AddTempvar("newFiles",
obs_module_text("AdvSceneSwitcher.tempVar.folder.newFiles"));
AddTempvar("changedFiles",
obs_module_text(
"AdvSceneSwitcher.tempVar.folder.changedFiles"));
AddTempvar("removedFiles",
obs_module_text(
"AdvSceneSwitcher.tempVar.folder.removedFiles"));
AddTempvar("newDirs",
obs_module_text("AdvSceneSwitcher.tempVar.folder.newDirs"));
AddTempvar(
"removedDirs",
obs_module_text("AdvSceneSwitcher.tempVar.folder.removedDirs"));
}
static void populateConditions(QComboBox *list)
{
for (const auto &[value, name] : conditions) {
list->addItem(obs_module_text(name.c_str()),
static_cast<int>(value));
}
}
MacroConditionFolderEdit::MacroConditionFolderEdit(
QWidget *parent, std::shared_ptr<MacroConditionFolder> entryData)
: QWidget(parent),
_conditions(new QComboBox()),
_folder(new FileSelection(FileSelection::Type::FOLDER)),
_enableFilter(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.folder.enableFilter"))),
_filterLayout(new QHBoxLayout()),
_regex(new RegexConfigWidget(this, false)),
_filter(new VariableLineEdit(this))
{
QString path = GetThemeTypeName() == "Light"
? ":/res/images/help.svg"
: ":/res/images/help_light.svg";
QIcon icon(path);
QPixmap pixmap = icon.pixmap(QSize(16, 16));
auto tooltipLabel = new QLabel(this);
tooltipLabel->setPixmap(pixmap);
tooltipLabel->setToolTip(
obs_module_text("AdvSceneSwitcher.condition.folder.tooltip"));
populateConditions(_conditions);
QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
QWidget::connect(_folder, SIGNAL(PathChanged(const QString &)), this,
SLOT(PathChanged(const QString &)));
QWidget::connect(_enableFilter, SIGNAL(stateChanged(int)), this,
SLOT(EnableFilterChanged(int)));
QWidget::connect(_regex,
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
SLOT(RegexChanged(const RegexConfig &)));
QWidget::connect(_filter, SIGNAL(editingFinished()), this,
SLOT(FilterChanged()));
const std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{conditions}}", _conditions}, {"{{folder}}", _folder},
{"{{tooltip}}", tooltipLabel}, {"{{regex}}", _regex},
{"{{filter}}", _filter},
};
auto entryLayout = new QHBoxLayout();
entryLayout->setContentsMargins(0, 0, 0, 0);
PlaceWidgets(obs_module_text("AdvSceneSwitcher.condition.folder.entry"),
entryLayout, widgetPlaceholders, false);
_filterLayout->setContentsMargins(0, 0, 0, 0);
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.condition.folder.entry.filter"),
_filterLayout, widgetPlaceholders, false);
auto layout = new QVBoxLayout();
layout->addLayout(entryLayout);
layout->addWidget(_enableFilter);
layout->addLayout(_filterLayout);
setLayout(layout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroConditionFolderEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_conditions->setCurrentIndex(_conditions->findData(
static_cast<int>(_entryData->_condition)));
_folder->SetPath(_entryData->GetFolder());
_enableFilter->setChecked(_entryData->_enableFilter);
_regex->SetRegexConfig(_entryData->_regex);
_filter->setText(_entryData->_filter);
SetWidgetVisibility();
}
void MacroConditionFolderEdit::ConditionChanged(int index)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_condition = static_cast<MacroConditionFolder::Condition>(
_conditions->itemData(index).toInt());
}
void MacroConditionFolderEdit::PathChanged(const QString &text)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->SetFolder(text.toStdString());
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroConditionFolderEdit::RegexChanged(const RegexConfig &regex)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_regex = regex;
_entryData->_regex.SetEnabled(true);
}
void MacroConditionFolderEdit::FilterChanged()
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_filter = _filter->text().toStdString();
}
void MacroConditionFolderEdit::EnableFilterChanged(int value)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_enableFilter = value;
SetWidgetVisibility();
}
void MacroConditionFolderEdit::SetWidgetVisibility()
{
SetLayoutVisible(_filterLayout, _entryData->_enableFilter);
adjustSize();
updateGeometry();
}
} // namespace advss

View File

@@ -0,0 +1,109 @@
#pragma once
#include "macro-condition-edit.hpp"
#include "file-selection.hpp"
#include "regex-config.hpp"
#include "variable-line-edit.hpp"
#include <QFileSystemWatcher>
namespace advss {
class MacroConditionFolder : public QObject, public MacroCondition {
Q_OBJECT
public:
MacroConditionFolder(Macro *m);
bool CheckCondition();
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
std::string GetShortDesc() const;
std::string GetId() const { return id; };
static std::shared_ptr<MacroCondition> Create(Macro *m)
{
return std::make_shared<MacroConditionFolder>(m);
}
void SetFolder(const std::string &);
StringVariable GetFolder() const { return _folder; }
enum class Condition {
ANY,
FILE_ADD,
FILE_CHANGE,
FILE_REMOVE,
FOLDER_ADD,
FOLDER_REMOVE,
};
Condition _condition = Condition::ANY;
bool _enableFilter = false;
RegexConfig _regex = RegexConfig(true);
StringVariable _filter = ".*";
private slots:
void DirectoryChanged(const QString &);
void FileChanged(const QString &);
private:
void SetupWatcher();
void SetTempVarValues();
void SetupTempVars();
StringVariable _folder = obs_module_text("AdvSceneSwitcher.enterPath");
std::unique_ptr<QFileSystemWatcher> _watcher;
std::string _lastWatchedValue = "";
std::mutex _mutex;
bool _matched = false;
QSet<QString> _newFiles;
QSet<QString> _changedFiles;
QSet<QString> _removedFiles;
QSet<QString> _newDirs;
QSet<QString> _removedDirs;
QSet<QString> _currentFiles;
QSet<QString> _currentDirs;
static bool _registered;
static const std::string id;
};
class MacroConditionFolderEdit : public QWidget {
Q_OBJECT
public:
MacroConditionFolderEdit(
QWidget *parent,
std::shared_ptr<MacroConditionFolder> cond = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
return new MacroConditionFolderEdit(
parent,
std::dynamic_pointer_cast<MacroConditionFolder>(cond));
}
private slots:
void ConditionChanged(int index);
void PathChanged(const QString &text);
void EnableFilterChanged(int value);
void RegexChanged(const RegexConfig &);
void FilterChanged();
signals:
void HeaderInfoChanged(const QString &);
private:
void SetWidgetVisibility();
QComboBox *_conditions;
FileSelection *_folder;
QCheckBox *_enableFilter;
QHBoxLayout *_filterLayout;
RegexConfigWidget *_regex;
VariableLineEdit *_filter;
std::shared_ptr<MacroConditionFolder> _entryData;
bool _loading = true;
};
} // namespace advss

View File

@@ -23,13 +23,17 @@ MacroConditionHotkey::MacroConditionHotkey(Macro *m) : MacroCondition(m)
bool MacroConditionHotkey::CheckCondition()
{
const bool hotkeyIsCurrentlyPressed = _hotkey->GetPressed();
const bool hotkeyWasPressedSinceLastCheck = _hotkey->GetLastPressed() >
_lastCheck;
const bool keyStateCurrentlyMatches =
_checkPressed ? _hotkey->GetPressed() : !_hotkey->GetPressed();
const auto lastKeyStateMatch = _checkPressed
? _hotkey->GetLastPressed()
: _hotkey->GetLastReleased();
const bool hotkeySateChangedSinceLastCheck = lastKeyStateMatch >
_lastCheck;
const bool macroWasPausedSinceLastCheck =
MacroWasPausedSince(GetMacro(), _lastCheck);
bool ret = hotkeyIsCurrentlyPressed ||
(hotkeyWasPressedSinceLastCheck &&
bool ret = keyStateCurrentlyMatches ||
(hotkeySateChangedSinceLastCheck &&
!macroWasPausedSinceLastCheck);
_lastCheck = std::chrono::high_resolution_clock::now();
return ret;
@@ -39,6 +43,7 @@ bool MacroConditionHotkey::Save(obs_data_t *obj) const
{
MacroCondition::Save(obj);
_hotkey->Save(obj);
obs_data_set_bool(obj, "checkPressed", _checkPressed);
return true;
}
@@ -52,34 +57,47 @@ bool MacroConditionHotkey::Load(obs_data_t *obj)
"hotkey name conflict for \"%s\" - using previous key bind",
description);
}
if (obs_data_has_user_value(obj, "checkPressed")) {
_checkPressed = obs_data_get_bool(obj, "checkPressed");
} else { // TODO: Remove fallback at some point in the future
_checkPressed = true;
}
return true;
}
MacroConditionHotkeyEdit::MacroConditionHotkeyEdit(
QWidget *parent, std::shared_ptr<MacroConditionHotkey> entryData)
: QWidget(parent)
: QWidget(parent),
_name(new QLineEdit()),
_keyState(new QComboBox())
{
_name = new QLineEdit();
QLabel *line1 = new QLabel(obs_module_text(
"AdvSceneSwitcher.condition.hotkey.entry.line1"));
QLabel *hint = new QLabel(
obs_module_text("AdvSceneSwitcher.condition.hotkey.tip"));
_keyState->addItems(
QStringList()
<< obs_module_text("AdvSceneSwitcher.condition.hotkey.pressed")
<< obs_module_text(
"AdvSceneSwitcher.condition.hotkey.released"));
QWidget::connect(_name, SIGNAL(editingFinished()), this,
SLOT(NameChanged()));
QWidget::connect(_keyState, SIGNAL(currentIndexChanged(int)), this,
SLOT(KeyStateChanged(int)));
QHBoxLayout *switchLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{name}}", _name},
};
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.condition.hotkey.entry.line2"),
switchLayout, widgetPlaceholders);
auto keyStateLayout = new QHBoxLayout;
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.hotkey.entry.keyState"),
keyStateLayout, {{"{{keyState}}", _keyState}});
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addWidget(line1);
mainLayout->addLayout(switchLayout);
mainLayout->addWidget(hint);
auto nameLayout = new QHBoxLayout;
PlaceWidgets(
obs_module_text("AdvSceneSwitcher.condition.hotkey.entry.name"),
nameLayout, {{"{{name}}", _name}});
auto mainLayout = new QVBoxLayout;
mainLayout->addLayout(keyStateLayout);
mainLayout->addLayout(nameLayout);
mainLayout->addWidget(new QLabel(
obs_module_text("AdvSceneSwitcher.condition.hotkey.tip")));
setLayout(mainLayout);
_entryData = entryData;
@@ -116,6 +134,17 @@ void MacroConditionHotkeyEdit::UpdateEntryData()
_name->setText(
QString::fromStdString(_entryData->_hotkey->GetDescription()));
_keyState->setCurrentIndex(_entryData->_checkPressed ? 0 : 1);
}
void MacroConditionHotkeyEdit::KeyStateChanged(int index)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_checkPressed = index == 0;
}
} // namespace advss

View File

@@ -20,6 +20,7 @@ public:
}
std::shared_ptr<Hotkey> _hotkey;
bool _checkPressed = true;
private:
std::chrono::high_resolution_clock::time_point _lastCheck{};
@@ -46,12 +47,12 @@ public:
private slots:
void NameChanged();
protected:
QLineEdit *_name;
std::shared_ptr<MacroConditionHotkey> _entryData;
void KeyStateChanged(int);
private:
QLineEdit *_name;
QComboBox *_keyState;
std::shared_ptr<MacroConditionHotkey> _entryData;
bool _loading = true;
};

View File

@@ -45,11 +45,13 @@ static bool setupReplayBufferEventHandler()
bool MacroConditionReplayBuffer::ReplayBufferWasSaved()
{
bool timersAreInitialized =
_saveTime.time_since_epoch().count() != 0 &&
replayBufferSaveTime.time_since_epoch().count() != 0;
bool newSaveOccurred = timersAreInitialized &&
_saveTime != replayBufferSaveTime;
if (!_saveTimeInitialized) {
_saveTime = replayBufferSaveTime;
_saveTimeInitialized = true;
return false;
}
bool newSaveOccurred = _saveTime != replayBufferSaveTime;
_saveTime = replayBufferSaveTime;
return newSaveOccurred;
}

View File

@@ -29,6 +29,7 @@ public:
private:
bool ReplayBufferWasSaved();
bool _saveTimeInitialized = false;
std::chrono::high_resolution_clock::time_point _saveTime = {};
static bool _registered;
static const std::string id;

View File

@@ -131,7 +131,7 @@ void MacroConditionWebsocket::SetConnection(const std::string &connectionName)
_messageBuffer = connection->RegisterForEvents();
}
std::weak_ptr<Connection> MacroConditionWebsocket::GetConnection() const
std::weak_ptr<WSConnection> MacroConditionWebsocket::GetConnection() const
{
return _connection;
}
@@ -159,7 +159,7 @@ MacroConditionWebsocketEdit::MacroConditionWebsocketEdit(
_conditions(new QComboBox(this)),
_message(new VariableTextEdit(this)),
_regex(new RegexConfigWidget(parent)),
_connection(new ConnectionSelection(this)),
_connection(new WSConnectionSelection(this)),
_editLayout(new QHBoxLayout())
{
populateConditionSelection(_conditions);

View File

@@ -30,7 +30,7 @@ public:
void SetType(Type);
Type GetType() const { return _type; }
void SetConnection(const std::string &);
std::weak_ptr<Connection> GetConnection() const;
std::weak_ptr<WSConnection> GetConnection() const;
StringVariable _message = obs_module_text("AdvSceneSwitcher.enterText");
RegexConfig _regex;
@@ -38,7 +38,7 @@ private:
void SetupTempVars();
Type _type = Type::REQUEST;
std::weak_ptr<Connection> _connection;
std::weak_ptr<WSConnection> _connection;
WebsocketMessageBuffer _messageBuffer;
std::chrono::high_resolution_clock::time_point _lastCheck{};
@@ -82,7 +82,7 @@ private:
QComboBox *_conditions;
VariableTextEdit *_message;
RegexConfigWidget *_regex;
ConnectionSelection *_connection;
WSConnectionSelection *_connection;
QHBoxLayout *_editLayout;
bool _loading = true;
};

View File

@@ -9,7 +9,7 @@
#include <QAction>
#include <QMenu>
Q_DECLARE_METATYPE(advss::Connection *);
Q_DECLARE_METATYPE(advss::WSConnection *);
namespace advss {
@@ -55,7 +55,7 @@ static void loadConnections(obs_data_t *obj)
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(connectionArray, i);
auto con = Connection::Create();
auto con = WSConnection::Create();
connections.emplace_back(con);
connections.back()->Load(array_obj);
obs_data_release(array_obj);
@@ -63,10 +63,11 @@ static void loadConnections(obs_data_t *obj)
obs_data_array_release(connectionArray);
}
Connection::Connection(bool useCustomURI, std::string customURI,
std::string name, std::string address, uint64_t port,
std::string pass, bool connectOnStart, bool reconnect,
int reconnectDelay, bool useOBSWebsocketProtocol)
WSConnection::WSConnection(bool useCustomURI, std::string customURI,
std::string name, std::string address, uint64_t port,
std::string pass, bool connectOnStart,
bool reconnect, int reconnectDelay,
bool useOBSWebsocketProtocol)
: Item(name),
_useCustomURI(useCustomURI),
_customURI(customURI),
@@ -81,7 +82,7 @@ Connection::Connection(bool useCustomURI, std::string customURI,
{
}
Connection::Connection(const Connection &other) : Item(other)
WSConnection::WSConnection(const WSConnection &other) : Item(other)
{
_useCustomURI = other._useCustomURI;
_customURI = other._customURI;
@@ -96,7 +97,7 @@ Connection::Connection(const Connection &other) : Item(other)
_client.UseOBSWebsocketProtocol(_useOBSWSProtocol);
}
Connection &Connection::operator=(const Connection &other)
WSConnection &WSConnection::operator=(const WSConnection &other)
{
if (this != &other) {
_useCustomURI = other._useCustomURI;
@@ -115,7 +116,7 @@ Connection &Connection::operator=(const Connection &other)
return *this;
}
Connection::~Connection()
WSConnection::~WSConnection()
{
_client.Disconnect();
}
@@ -125,7 +126,7 @@ static std::string constructUri(std::string addr, int port)
return "ws://" + addr + ":" + std::to_string(port);
}
std::string Connection::GetURI()
std::string WSConnection::GetURI() const
{
if (_useCustomURI) {
return _customURI;
@@ -133,16 +134,16 @@ std::string Connection::GetURI()
return constructUri(_address, _port);
}
void Connection::Reconnect()
void WSConnection::Reconnect()
{
_client.Disconnect();
_client.Connect(GetURI(), _password, _reconnect, _reconnectDelay);
}
void Connection::SendMsg(const std::string &msg)
void WSConnection::SendMsg(const std::string &msg)
{
const auto status = _client.GetStatus();
if (status == WSConnection::Status::DISCONNECTED) {
if (status == WSClientConnection::Status::DISCONNECTED) {
_client.Connect(GetURI(), _password, _reconnect,
_reconnectDelay);
blog(LOG_WARNING,
@@ -151,12 +152,12 @@ void Connection::SendMsg(const std::string &msg)
return;
}
if (status == WSConnection::Status::AUTHENTICATED) {
if (status == WSClientConnection::Status::AUTHENTICATED) {
_client.SendRequest(msg);
}
}
void Connection::Load(obs_data_t *obj)
void WSConnection::Load(obs_data_t *obj)
{
Item::Load(obj);
@@ -184,7 +185,7 @@ void Connection::Load(obs_data_t *obj)
}
}
void Connection::Save(obs_data_t *obj) const
void WSConnection::Save(obs_data_t *obj) const
{
Item::Save(obj);
obs_data_set_bool(obj, "useCustomURI", _useCustomURI);
@@ -199,50 +200,50 @@ void Connection::Save(obs_data_t *obj) const
obs_data_set_int(obj, "version", 1);
}
WebsocketMessageBuffer Connection::RegisterForEvents()
WebsocketMessageBuffer WSConnection::RegisterForEvents()
{
return _client.RegisterForEvents();
}
void Connection::UseOBSWebsocketProtocol(bool useOBSWSProtocol)
void WSConnection::UseOBSWebsocketProtocol(bool useOBSWSProtocol)
{
_useOBSWSProtocol = useOBSWSProtocol;
_client.UseOBSWebsocketProtocol(useOBSWSProtocol);
}
Connection *GetConnectionByName(const QString &name)
WSConnection *GetConnectionByName(const QString &name)
{
return GetConnectionByName(name.toStdString());
}
Connection *GetConnectionByName(const std::string &name)
WSConnection *GetConnectionByName(const std::string &name)
{
for (auto &con : connections) {
if (con->Name() == name) {
return dynamic_cast<Connection *>(con.get());
return dynamic_cast<WSConnection *>(con.get());
}
}
return nullptr;
}
std::weak_ptr<Connection> GetWeakConnectionByName(const std::string &name)
std::weak_ptr<WSConnection> GetWeakConnectionByName(const std::string &name)
{
for (const auto &c : connections) {
if (c->Name() == name) {
std::weak_ptr<Connection> wp =
std::dynamic_pointer_cast<Connection>(c);
std::weak_ptr<WSConnection> wp =
std::dynamic_pointer_cast<WSConnection>(c);
return wp;
}
}
return std::weak_ptr<Connection>();
return std::weak_ptr<WSConnection>();
}
std::weak_ptr<Connection> GetWeakConnectionByQString(const QString &name)
std::weak_ptr<WSConnection> GetWeakConnectionByQString(const QString &name)
{
return GetWeakConnectionByName(name.toStdString());
}
std::string GetWeakConnectionName(std::weak_ptr<Connection> connection)
std::string GetWeakConnectionName(std::weak_ptr<WSConnection> connection)
{
auto con = connection.lock();
if (!con) {
@@ -268,40 +269,45 @@ static bool ConnectionNameAvailable(const std::string &name)
static bool AskForSettingsWrapper(QWidget *parent, Item &settings)
{
Connection &ConnectionSettings = dynamic_cast<Connection &>(settings);
return ConnectionSettingsDialog::AskForSettings(parent,
ConnectionSettings);
WSConnection &ConnectionSettings =
dynamic_cast<WSConnection &>(settings);
return WSConnectionSettingsDialog::AskForSettings(parent,
ConnectionSettings);
}
ConnectionSelection::ConnectionSelection(QWidget *parent)
: ItemSelection(connections, Connection::Create, AskForSettingsWrapper,
WSConnectionSelection::WSConnectionSelection(QWidget *parent)
: ItemSelection(connections, WSConnection::Create,
AskForSettingsWrapper,
"AdvSceneSwitcher.connection.select",
"AdvSceneSwitcher.connection.add",
"AdvSceneSwitcher.item.nameNotAvailable",
"AdvSceneSwitcher.connection.configure", parent)
{
// Connect to slots
QWidget::connect(
window(),
SIGNAL(ConnectionRenamed(const QString &, const QString &)),
this, SLOT(RenameItem(const QString &, const QString &)));
QWidget::connect(window(), SIGNAL(ConnectionAdded(const QString &)),
this, SLOT(AddItem(const QString &)));
QWidget::connect(window(), SIGNAL(ConnectionRemoved(const QString &)),
this, SLOT(RemoveItem(const QString &)));
QWidget::connect(ConnectionSelectionSignalManager::Instance(),
SIGNAL(Rename(const QString &, const QString &)), this,
SLOT(RenameItem(const QString &, const QString &)));
QWidget::connect(ConnectionSelectionSignalManager::Instance(),
SIGNAL(Add(const QString &)), this,
SLOT(AddItem(const QString &)));
QWidget::connect(ConnectionSelectionSignalManager::Instance(),
SIGNAL(Remove(const QString &)), this,
SLOT(RemoveItem(const QString &)));
// Forward signals
QWidget::connect(
this, SIGNAL(ItemRenamed(const QString &, const QString &)),
window(),
SIGNAL(ConnectionRenamed(const QString &, const QString &)));
QWidget::connect(this, SIGNAL(ItemAdded(const QString &)), window(),
SIGNAL(ConnectionAdded(const QString &)));
QWidget::connect(this, SIGNAL(ItemRemoved(const QString &)), window(),
SIGNAL(ConnectionRemoved(const QString &)));
QWidget::connect(this,
SIGNAL(ItemRenamed(const QString &, const QString &)),
ConnectionSelectionSignalManager::Instance(),
SIGNAL(Rename(const QString &, const QString &)));
QWidget::connect(this, SIGNAL(ItemAdded(const QString &)),
ConnectionSelectionSignalManager::Instance(),
SIGNAL(Add(const QString &)));
QWidget::connect(this, SIGNAL(ItemRemoved(const QString &)),
ConnectionSelectionSignalManager::Instance(),
SIGNAL(Remove(const QString &)));
}
void ConnectionSelection::SetConnection(const std::string &con)
void WSConnectionSelection::SetConnection(const std::string &con)
{
if (!!GetConnectionByName(con)) {
SetItem(con);
@@ -310,8 +316,8 @@ void ConnectionSelection::SetConnection(const std::string &con)
}
}
void ConnectionSelection::SetConnection(
const std::weak_ptr<Connection> &connection_)
void WSConnectionSelection::SetConnection(
const std::weak_ptr<WSConnection> &connection_)
{
auto connection = connection_.lock();
if (connection) {
@@ -321,8 +327,8 @@ void ConnectionSelection::SetConnection(
}
}
ConnectionSettingsDialog::ConnectionSettingsDialog(QWidget *parent,
const Connection &settings)
WSConnectionSettingsDialog::WSConnectionSettingsDialog(
QWidget *parent, const WSConnection &settings)
: ItemSettingsDialog(settings, connections,
"AdvSceneSwitcher.connection.select",
"AdvSceneSwitcher.connection.add",
@@ -449,7 +455,7 @@ ConnectionSettingsDialog::ConnectionSettingsDialog(QWidget *parent,
UseCustomURIChanged(settings._useCustomURI);
}
void ConnectionSettingsDialog::UseCustomURIChanged(int state)
void WSConnectionSettingsDialog::UseCustomURIChanged(int state)
{
SetGridLayoutRowVisible(_layout, _addressRow, !state);
SetGridLayoutRowVisible(_layout, _portRow, !state);
@@ -459,33 +465,33 @@ void ConnectionSettingsDialog::UseCustomURIChanged(int state)
updateGeometry();
}
void ConnectionSettingsDialog::ProtocolChanged(int state)
void WSConnectionSettingsDialog::ProtocolChanged(int state)
{
_password->setEnabled(state);
_showPassword->setEnabled(state);
}
void ConnectionSettingsDialog::ReconnectChanged(int state)
void WSConnectionSettingsDialog::ReconnectChanged(int state)
{
_reconnectDelay->setEnabled(state);
}
void ConnectionSettingsDialog::SetStatus()
void WSConnectionSettingsDialog::SetStatus()
{
switch (_testConnection.GetStatus()) {
case WSConnection::Status::DISCONNECTED:
case WSClientConnection::Status::DISCONNECTED:
_status->setText(obs_module_text(
"AdvSceneSwitcher.connection.status.disconnected"));
break;
case WSConnection::Status::CONNECTING:
case WSClientConnection::Status::CONNECTING:
_status->setText(obs_module_text(
"AdvSceneSwitcher.connection.status.connecting"));
break;
case WSConnection::Status::CONNECTED:
case WSClientConnection::Status::CONNECTED:
_status->setText(obs_module_text(
"AdvSceneSwitcher.connection.status.connected"));
break;
case WSConnection::Status::AUTHENTICATED:
case WSClientConnection::Status::AUTHENTICATED:
_status->setText(obs_module_text(
"AdvSceneSwitcher.connection.status.authenticated"));
break;
@@ -494,19 +500,19 @@ void ConnectionSettingsDialog::SetStatus()
}
}
void ConnectionSettingsDialog::ShowPassword()
void WSConnectionSettingsDialog::ShowPassword()
{
SetButtonIcon(_showPassword, ":res/images/visible.svg");
_password->setEchoMode(QLineEdit::Normal);
}
void ConnectionSettingsDialog::HidePassword()
void WSConnectionSettingsDialog::HidePassword()
{
SetButtonIcon(_showPassword, ":res/images/invisible.svg");
_password->setEchoMode(QLineEdit::PasswordEchoOnEdit);
}
void ConnectionSettingsDialog::TestConnection()
void WSConnectionSettingsDialog::TestConnection()
{
_testConnection.UseOBSWebsocketProtocol(_useOBSWSProtocol->isChecked());
_testConnection.Disconnect();
@@ -517,14 +523,14 @@ void ConnectionSettingsDialog::TestConnection()
_testConnection.Connect(uri, _password->text().toStdString(), false);
_statusTimer.setInterval(1000);
QWidget::connect(&_statusTimer, &QTimer::timeout, this,
&ConnectionSettingsDialog::SetStatus);
&WSConnectionSettingsDialog::SetStatus);
_statusTimer.start();
}
bool ConnectionSettingsDialog::AskForSettings(QWidget *parent,
Connection &settings)
bool WSConnectionSettingsDialog::AskForSettings(QWidget *parent,
WSConnection &settings)
{
ConnectionSettingsDialog dialog(parent, settings);
WSConnectionSettingsDialog dialog(parent, settings);
dialog.setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle"));
if (dialog.exec() != DialogCode::Accepted) {
return false;
@@ -544,4 +550,16 @@ bool ConnectionSettingsDialog::AskForSettings(QWidget *parent,
return true;
}
ConnectionSelectionSignalManager::ConnectionSelectionSignalManager(
QObject *parent)
: QObject(parent)
{
}
ConnectionSelectionSignalManager *ConnectionSelectionSignalManager::Instance()
{
static ConnectionSelectionSignalManager manager;
return &manager;
}
} // namespace advss

View File

@@ -18,22 +18,22 @@
namespace advss {
class ConnectionSelection;
class ConnectionSettingsDialog;
class WSConnectionSelection;
class WSConnectionSettingsDialog;
class Connection : public Item {
class WSConnection : public Item {
public:
Connection(bool useCustomURI, std::string customURI, std::string name,
std::string address, uint64_t port, std::string pass,
bool connectOnStart, bool reconnect, int reconnectDelay,
bool useOBSWebsocketProtocol);
Connection() = default;
Connection(const Connection &);
Connection &operator=(const Connection &);
~Connection();
WSConnection(bool useCustomURI, std::string customURI, std::string name,
std::string address, uint64_t port, std::string pass,
bool connectOnStart, bool reconnect, int reconnectDelay,
bool useOBSWebsocketProtocol);
WSConnection() = default;
WSConnection(const WSConnection &);
WSConnection &operator=(const WSConnection &);
~WSConnection();
static std::shared_ptr<Item> Create()
{
return std::make_shared<Connection>();
return std::make_shared<WSConnection>();
}
void Reconnect();
@@ -43,10 +43,11 @@ public:
std::string GetName() const { return _name; }
WebsocketMessageBuffer RegisterForEvents();
bool IsUsingOBSProtocol() const { return _useOBSWSProtocol; }
std::string GetURI() const;
uint64_t GetPort() const { return _port; }
private:
void UseOBSWebsocketProtocol(bool);
std::string GetURI();
bool _useCustomURI = false;
std::string _customURI = "ws://localhost:4455";
@@ -58,25 +59,18 @@ private:
int _reconnectDelay = 3;
bool _useOBSWSProtocol = true;
WSConnection _client;
WSClientConnection _client;
friend ConnectionSelection;
friend ConnectionSettingsDialog;
friend WSConnectionSelection;
friend WSConnectionSettingsDialog;
};
Connection *GetConnectionByName(const QString &);
Connection *GetConnectionByName(const std::string &);
std::weak_ptr<Connection> GetWeakConnectionByName(const std::string &name);
std::weak_ptr<Connection> GetWeakConnectionByQString(const QString &name);
std::string GetWeakConnectionName(std::weak_ptr<Connection>);
std::deque<std::shared_ptr<Item>> &GetConnections();
class ConnectionSettingsDialog : public ItemSettingsDialog {
class WSConnectionSettingsDialog : public ItemSettingsDialog {
Q_OBJECT
public:
ConnectionSettingsDialog(QWidget *parent, const Connection &);
static bool AskForSettings(QWidget *parent, Connection &settings);
WSConnectionSettingsDialog(QWidget *parent, const WSConnection &);
static bool AskForSettings(QWidget *parent, WSConnection &settings);
private slots:
void UseCustomURIChanged(int);
@@ -103,20 +97,39 @@ private:
QGridLayout *_layout;
QTimer _statusTimer;
WSConnection _testConnection;
WSClientConnection _testConnection;
int _customURIRow = -1;
int _addressRow = -1;
int _portRow = -1;
};
class ConnectionSelection : public ItemSelection {
class WSConnectionSelection : public ItemSelection {
Q_OBJECT
public:
ConnectionSelection(QWidget *parent = 0);
WSConnectionSelection(QWidget *parent = 0);
void SetConnection(const std::string &);
void SetConnection(const std::weak_ptr<Connection> &);
void SetConnection(const std::weak_ptr<WSConnection> &);
};
class ConnectionSelectionSignalManager : public QObject {
Q_OBJECT
public:
ConnectionSelectionSignalManager(QObject *parent = nullptr);
static ConnectionSelectionSignalManager *Instance();
signals:
void Rename(const QString &, const QString &);
void Add(const QString &);
void Remove(const QString &);
};
WSConnection *GetConnectionByName(const QString &);
WSConnection *GetConnectionByName(const std::string &);
std::weak_ptr<WSConnection> GetWeakConnectionByName(const std::string &name);
std::weak_ptr<WSConnection> GetWeakConnectionByQString(const QString &name);
std::string GetWeakConnectionName(std::weak_ptr<WSConnection>);
std::deque<std::shared_ptr<Item>> &GetConnections();
} // namespace advss

View File

@@ -116,6 +116,9 @@ void Hotkey::Callback(void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed)
if (pressed) {
hotkey->_lastPressed =
std::chrono::high_resolution_clock::now();
} else {
hotkey->_lastReleased =
std::chrono::high_resolution_clock::now();
}
hotkey->_pressed = pressed;
}

View File

@@ -27,6 +27,7 @@ public:
bool GetPressed() const { return _pressed; }
auto GetLastPressed() const { return _lastPressed; }
auto GetLastReleased() const { return _lastReleased; }
std::string GetDescription() const { return _description; }
bool UpdateDescription(const std::string &);
@@ -43,6 +44,7 @@ private:
obs_hotkey_id _hotkeyID = OBS_INVALID_HOTKEY_ID;
bool _pressed = false;
std::chrono::high_resolution_clock::time_point _lastPressed{};
std::chrono::high_resolution_clock::time_point _lastReleased{};
// When set will not attempt to share settings with existing hotkey
bool _ignoreExistingHotkeys = false;
};

View File

@@ -86,7 +86,7 @@ static void registerWebsocketVendor()
}
}
WSConnection::WSConnection(bool useOBSProtocol) : QObject(nullptr)
WSClientConnection::WSClientConnection(bool useOBSProtocol) : QObject(nullptr)
{
_client.get_alog().clear_channels(
websocketpp::log::alevel::frame_header |
@@ -98,15 +98,15 @@ WSConnection::WSConnection(bool useOBSProtocol) : QObject(nullptr)
#endif
UseOBSWebsocketProtocol(useOBSProtocol);
_client.set_close_handler(bind(&WSConnection::OnClose, this, _1));
_client.set_close_handler(bind(&WSClientConnection::OnClose, this, _1));
}
WSConnection::~WSConnection()
WSClientConnection::~WSClientConnection()
{
Disconnect();
}
void WSConnection::ConnectThread()
void WSClientConnection::ConnectThread()
{
do {
std::unique_lock<std::mutex> lck(_waitMtx);
@@ -144,8 +144,9 @@ void WSConnection::ConnectThread()
_status = Status::DISCONNECTED;
}
void WSConnection::Connect(const std::string &uri, const std::string &pass,
bool reconnect, int reconnectDelay)
void WSClientConnection::Connect(const std::string &uri,
const std::string &pass, bool reconnect,
int reconnectDelay)
{
std::lock_guard<std::mutex> lock(_connectMtx);
if (_status != Status::DISCONNECTED) {
@@ -161,11 +162,11 @@ void WSConnection::Connect(const std::string &uri, const std::string &pass,
if (_thread.joinable()) {
_thread.join();
}
_thread = std::thread(&WSConnection::ConnectThread, this);
_thread = std::thread(&WSClientConnection::ConnectThread, this);
blog(LOG_INFO, "connect to '%s' started", uri.c_str());
}
void WSConnection::Disconnect()
void WSClientConnection::Disconnect()
{
std::lock_guard<std::mutex> lock(_connectMtx);
_disconnect = true;
@@ -218,46 +219,46 @@ std::string ConstructVendorRequestMessage(const std::string &message)
return result;
}
void WSConnection::SendRequest(const std::string &msg)
void WSClientConnection::SendRequest(const std::string &msg)
{
Send(msg);
}
WebsocketMessageBuffer WSConnection::RegisterForEvents()
WebsocketMessageBuffer WSClientConnection::RegisterForEvents()
{
return _dispatcher.RegisterClient();
}
WSConnection::Status WSConnection::GetStatus() const
WSClientConnection::Status WSClientConnection::GetStatus() const
{
return _status;
}
void WSConnection::UseOBSWebsocketProtocol(bool useOBSProtocol)
void WSClientConnection::UseOBSWebsocketProtocol(bool useOBSProtocol)
{
_client.set_open_handler(bind(useOBSProtocol
? &WSConnection::OnOBSOpen
: &WSConnection::OnGenericOpen,
this, _1));
_client.set_open_handler(
bind(useOBSProtocol ? &WSClientConnection::OnOBSOpen
: &WSClientConnection::OnGenericOpen,
this, _1));
_client.set_message_handler(
bind(useOBSProtocol ? &WSConnection::OnOBSMessage
: &WSConnection::OnGenericMessage,
bind(useOBSProtocol ? &WSClientConnection::OnOBSMessage
: &WSClientConnection::OnGenericMessage,
this, _1, _2));
}
void WSConnection::OnGenericOpen(connection_hdl)
void WSClientConnection::OnGenericOpen(connection_hdl)
{
blog(LOG_INFO, "connection to %s opened", _uri.c_str());
_status = Status::AUTHENTICATED;
}
void WSConnection::OnOBSOpen(connection_hdl)
void WSClientConnection::OnOBSOpen(connection_hdl)
{
blog(LOG_INFO, "connection to %s opened", _uri.c_str());
_status = Status::CONNECTING;
}
void WSConnection::HandleHello(obs_data_t *helloMsg)
void WSClientConnection::HandleHello(obs_data_t *helloMsg)
{
_status = Status::CONNECTED;
@@ -293,7 +294,7 @@ void WSConnection::HandleHello(obs_data_t *helloMsg)
Send(response);
}
void WSConnection::HandleEvent(obs_data_t *msg)
void WSClientConnection::HandleEvent(obs_data_t *msg)
{
auto d = obs_data_get_obj(msg, "d");
auto eventData = obs_data_get_obj(d, "eventData");
@@ -319,7 +320,7 @@ void WSConnection::HandleEvent(obs_data_t *msg)
obs_data_release(d);
}
void WSConnection::HandleResponse(obs_data_t *response)
void WSClientConnection::HandleResponse(obs_data_t *response)
{
auto data = obs_data_get_obj(response, "d");
auto id = obs_data_get_string(data, "requestId");
@@ -333,7 +334,8 @@ void WSConnection::HandleResponse(obs_data_t *response)
obs_data_release(data);
}
void WSConnection::OnGenericMessage(connection_hdl, client::message_ptr message)
void WSClientConnection::OnGenericMessage(connection_hdl,
client::message_ptr message)
{
if (!message) {
return;
@@ -347,7 +349,8 @@ void WSConnection::OnGenericMessage(connection_hdl, client::message_ptr message)
vblog(LOG_INFO, "received event msg \"%s\"", payload.c_str());
}
void WSConnection::OnOBSMessage(connection_hdl, client::message_ptr message)
void WSClientConnection::OnOBSMessage(connection_hdl,
client::message_ptr message)
{
if (!message) {
return;
@@ -392,7 +395,7 @@ void WSConnection::OnOBSMessage(connection_hdl, client::message_ptr message)
obs_data_release(json);
}
void WSConnection::Send(const std::string &msg)
void WSClientConnection::Send(const std::string &msg)
{
if (_connection.expired()) {
return;
@@ -408,7 +411,7 @@ void WSConnection::Send(const std::string &msg)
vblog(LOG_INFO, "sent message to '%s':\n%s", _uri.c_str(), msg.c_str());
}
void WSConnection::OnClose(connection_hdl)
void WSClientConnection::OnClose(connection_hdl)
{
blog(LOG_INFO, "client-connection to %s closed.", _uri.c_str());
_status = Status::DISCONNECTED;

View File

@@ -30,13 +30,13 @@ void SendWebsocketEvent(const std::string &);
std::string ConstructVendorRequestMessage(const std::string &message);
[[nodiscard]] WebsocketMessageBuffer RegisterForWebsocketMessages();
class WSConnection : public QObject {
class WSClientConnection : public QObject {
using server = websocketpp::server<websocketpp::config::asio>;
using client = websocketpp::client<websocketpp::config::asio_client>;
public:
explicit WSConnection(bool useOBSProtocol = true);
virtual ~WSConnection();
explicit WSClientConnection(bool useOBSProtocol = true);
virtual ~WSClientConnection();
void Connect(const std::string &uri, const std::string &pass,
bool _reconnect, int reconnectDelay = 10);

View File

@@ -0,0 +1,243 @@
#include "websocket-tab.hpp"
#include "connection-manager.hpp"
#include "obs-module-helper.hpp"
#include "sync-helpers.hpp"
#include "tab-helpers.hpp"
#include "ui-helpers.hpp"
#include <QTabWidget>
namespace advss {
static bool registerTab();
static void setupTab(QTabWidget *);
static bool registerTabDone = registerTab();
static WSConnectionsTable *tabWidget = nullptr;
static bool registerTab()
{
AddSetupTabCallback("websocketConnectionTab",
WSConnectionsTable::Create, setupTab);
return true;
}
static void setTabVisible(QTabWidget *tabWidget, bool visible)
{
SetTabVisibleByName(
tabWidget, visible,
obs_module_text(
"AdvSceneSwitcher.websocketConnectionTab.title"));
}
WSConnectionsTable *WSConnectionsTable::Create()
{
tabWidget = new WSConnectionsTable();
return tabWidget;
}
void WSConnectionsTable::Add()
{
auto newConnection = std::make_shared<WSConnection>();
auto accepted = WSConnectionSettingsDialog::AskForSettings(
this, *newConnection);
if (!accepted) {
return;
}
{
auto lock = LockContext();
auto &connections = GetConnections();
connections.emplace_back(newConnection);
}
ConnectionSelectionSignalManager::Instance()->Add(
QString::fromStdString(newConnection->Name()));
}
void WSConnectionsTable::Remove()
{
auto selectedRows =
tabWidget->Table()->selectionModel()->selectedRows();
if (selectedRows.empty()) {
return;
}
QStringList connectionNames;
for (auto row : selectedRows) {
auto cell = tabWidget->Table()->item(row.row(), 0);
if (!cell) {
continue;
}
connectionNames << cell->text();
}
int connectionNameCount = connectionNames.size();
if (connectionNameCount == 1) {
QString deleteWarning = obs_module_text(
"AdvSceneSwitcher.websocketConnectionTab.removeSingleConnectionPopup.text");
if (!DisplayMessage(deleteWarning.arg(connectionNames.at(0)),
true)) {
return;
}
} else {
QString deleteWarning = obs_module_text(
"AdvSceneSwitcher.websocketConnectionTab.removeMultipleConnectionsPopup.text");
if (!DisplayMessage(deleteWarning.arg(connectionNameCount),
true)) {
return;
}
}
{
auto lock = LockContext();
RemoveItemsByName(GetConnections(), connectionNames);
}
for (const auto &name : connectionNames) {
ConnectionSelectionSignalManager::Instance()->Remove(name);
}
}
static QString formatProtocolUsageText(WSConnection *connection)
{
return obs_module_text(
connection->IsUsingOBSProtocol()
? "AdvSceneSwitcher.websocketConnectionTab.protocol.yes"
: "AdvSceneSwitcher.websocketConnectionTab.protocol.no");
}
static QStringList getCellLabels(WSConnection *connection, bool addName = true)
{
assert(connection);
auto result = QStringList();
if (addName) {
result << QString::fromStdString(connection->Name());
}
result << QString::fromStdString(connection->GetURI())
<< QString::number(connection->GetPort())
<< formatProtocolUsageText(connection);
return result;
}
static void openSettingsDialog()
{
auto selectedRows =
tabWidget->Table()->selectionModel()->selectedRows();
if (selectedRows.empty()) {
return;
}
auto cell = tabWidget->Table()->item(selectedRows.last().row(), 0);
if (!cell) {
return;
}
auto weakConnection = GetWeakConnectionByQString(cell->text());
auto connection = weakConnection.lock();
if (!connection) {
return;
}
auto oldName = connection->Name();
bool accepted = WSConnectionSettingsDialog::AskForSettings(
tabWidget->Table(), *connection.get());
if (accepted && oldName != connection->Name()) {
ConnectionSelectionSignalManager::Instance()->Rename(
QString::fromStdString(oldName),
QString::fromStdString(connection->Name()));
}
}
static const QStringList headers =
QStringList()
<< obs_module_text(
"AdvSceneSwitcher.websocketConnectionTab.name.header")
<< obs_module_text(
"AdvSceneSwitcher.websocketConnectionTab.address.header")
<< obs_module_text(
"AdvSceneSwitcher.websocketConnectionTab.port.header")
<< obs_module_text(
"AdvSceneSwitcher.websocketConnectionTab.protocol.header");
WSConnectionsTable::WSConnectionsTable(QTabWidget *parent)
: ResourceTable(
parent,
obs_module_text(
"AdvSceneSwitcher.websocketConnectionTab.help"),
obs_module_text(
"AdvSceneSwitcher.websocketConnectionTab.websocketConnectionAddButton.tooltip"),
obs_module_text(
"AdvSceneSwitcher.websocketConnectionTab.websocketConnectionRemoveButton.tooltip"),
headers, openSettingsDialog)
{
for (const auto &connection : GetConnections()) {
auto c = std::static_pointer_cast<WSConnection>(connection);
AddItemTableRow(Table(), getCellLabels(c.get()));
}
SetHelpVisible(GetConnections().empty());
}
static void updateConnectionStatus(QTableWidget *table)
{
for (int row = 0; row < table->rowCount(); row++) {
auto item = table->item(row, 0);
if (!item) {
continue;
}
auto weakConnection = GetWeakConnectionByQString(item->text());
auto connection = weakConnection.lock();
if (!connection) {
continue;
}
UpdateItemTableRow(table, row,
getCellLabels(connection.get(), false));
}
}
static void setupTab(QTabWidget *tab)
{
if (GetConnections().empty()) {
setTabVisible(tab, false);
}
QWidget::connect(ConnectionSelectionSignalManager::Instance(),
&ConnectionSelectionSignalManager::Rename,
[](const QString &oldName, const QString &newName) {
RenameItemTableRow(tabWidget->Table(), oldName,
newName);
});
QWidget::connect(
ConnectionSelectionSignalManager::Instance(),
&ConnectionSelectionSignalManager::Add,
[tab](const QString &name) {
AddItemTableRow(
tabWidget->Table(),
getCellLabels(GetConnectionByName(name)));
tabWidget->SetHelpVisible(false);
tabWidget->HighlightAddButton(false);
setTabVisible(tab, true);
});
QWidget::connect(ConnectionSelectionSignalManager::Instance(),
&ConnectionSelectionSignalManager::Remove,
[](const QString &name) {
RemoveItemTableRow(tabWidget->Table(), name);
if (tabWidget->Table()->rowCount() == 0) {
tabWidget->SetHelpVisible(true);
tabWidget->HighlightAddButton(true);
}
});
auto timer = new QTimer(tabWidget);
timer->setInterval(1000);
QWidget::connect(timer, &QTimer::timeout,
[]() { updateConnectionStatus(tabWidget->Table()); });
timer->start();
}
} // namespace advss

View File

@@ -0,0 +1,20 @@
#pragma once
#include "resource-table.hpp"
namespace advss {
class WSConnectionsTable final : public ResourceTable {
Q_OBJECT
public:
static WSConnectionsTable *Create();
private slots:
void Add();
void Remove();
private:
WSConnectionsTable(QTabWidget *parent = nullptr);
};
} // namespace advss

View File

@@ -175,22 +175,43 @@ void PressKeys(const std::vector<HotkeyType> keys, int duration)
}
}
static HWND getHWNDfromTitle(const std::string &title)
static bool windowIsValid(HWND window)
{
HWND hwnd = NULL;
if (!IsWindowVisible(window)) {
return false;
}
// Only accept for top level windows and not controls within them
if (window != GetAncestor(window, GA_ROOT)) {
return false;
}
return true;
}
static std::vector<HWND> getHWNDfromTitle(const std::string &title)
{
HWND hwnd = nullptr;
HWND hPrevWnd = nullptr;
std::vector<HWND> hwnds;
wchar_t wTitle[512];
os_utf8_to_wcs(title.c_str(), 0, wTitle, 512);
hwnd = FindWindowEx(NULL, NULL, NULL, wTitle);
return hwnd;
while ((hwnd = FindWindowEx(NULL, hPrevWnd, NULL, wTitle)) != nullptr) {
if (windowIsValid(hwnd)) {
hwnds.emplace_back(hwnd);
}
hPrevWnd = hwnd;
}
return hwnds;
}
std::string GetWindowClassByWindowTitle(const std::string &window)
{
HWND hwnd = NULL;
hwnd = getHWNDfromTitle(window);
if (!hwnd) {
auto hwnds = getHWNDfromTitle(window);
if (hwnds.empty()) {
return "";
}
auto hwnd = hwnds.at(0);
std::wstring wClass;
wClass.resize(1024);
if (!GetClassNameW(hwnd, &wClass[0], wClass.capacity())) {
@@ -206,41 +227,54 @@ std::string GetWindowClassByWindowTitle(const std::string &window)
void SetFocusWindow(const std::string &title)
{
auto handle = getHWNDfromTitle(title);
if (!handle) {
auto hwnds = getHWNDfromTitle(title);
if (hwnds.empty()) {
return;
}
ShowWindow(handle, SW_RESTORE);
SetForegroundWindow(handle);
SetFocus(handle);
SetActiveWindow(handle);
for (const auto &handle : hwnds) {
const bool isMinimized = IsIconic(handle);
ShowWindow(handle, isMinimized ? SW_RESTORE : SW_SHOW);
SetForegroundWindow(handle);
SetFocus(handle);
SetActiveWindow(handle);
}
}
void CloseWindow(const std::string &title)
{
auto handle = getHWNDfromTitle(title);
if (!handle) {
auto hwnds = getHWNDfromTitle(title);
if (hwnds.empty()) {
return;
}
SendMessage(handle, WM_CLOSE, 0, 0);
for (const auto &handle : hwnds) {
SendMessage(handle, WM_CLOSE, 0, 0);
}
}
void MaximizeWindow(const std::string &title)
{
auto handle = getHWNDfromTitle(title);
if (!handle) {
auto hwnds = getHWNDfromTitle(title);
if (hwnds.empty()) {
return;
}
ShowWindow(handle, SW_MAXIMIZE);
for (const auto &handle : hwnds) {
ShowWindow(handle, SW_MAXIMIZE);
}
}
void MinimizeWindow(const std::string &title)
{
auto handle = getHWNDfromTitle(title);
if (!handle) {
auto hwnds = getHWNDfromTitle(title);
if (hwnds.empty()) {
return;
}
ShowWindow(handle, SW_MINIMIZE);
for (const auto &handle : hwnds) {
ShowWindow(handle, SW_MINIMIZE);
}
}
class RawMouseInputFilter;

View File

@@ -20,46 +20,56 @@ std::map<std::pair<MidiDeviceType, std::string>, MidiDeviceInstance *>
static bool setupDeviceObservers()
{
static std::vector<libremidi::observer> observers;
for (auto api : libremidi::available_apis()) {
libremidi::observer_configuration cbs;
cbs.input_added = [=](const libremidi::input_port &p) {
auto dev = MidiDeviceInstance::GetDevice(p);
if (!dev) {
return;
}
blog(LOG_INFO, "MIDI input connected: %s",
p.port_name.c_str());
dev->ClosePort();
dev->OpenPort();
};
cbs.input_removed = [=](const libremidi::input_port &p) {
auto dev = MidiDeviceInstance::GetDevice(p);
if (!dev) {
return;
}
blog(LOG_INFO, "MIDI input removed: %s",
p.port_name.c_str());
};
cbs.output_added = [=](const libremidi::output_port &p) {
auto dev = MidiDeviceInstance::GetDevice(p);
if (!dev) {
return;
}
blog(LOG_INFO, "MIDI output connected: %s",
p.port_name.c_str());
dev->ClosePort();
dev->OpenPort();
};
cbs.output_removed = [=](const libremidi::output_port &p) {
auto dev = MidiDeviceInstance::GetDevice(p);
if (!dev) {
return;
}
blog(LOG_INFO, "MIDI output removed: %s",
p.port_name.c_str());
};
observers.emplace_back(
cbs, libremidi::observer_configuration_for(api));
try {
for (auto api : libremidi::available_apis()) {
libremidi::observer_configuration cbs;
cbs.input_added = [=](const libremidi::input_port &p) {
auto dev = MidiDeviceInstance::GetDevice(p);
if (!dev) {
return;
}
blog(LOG_INFO, "MIDI input connected: %s",
p.port_name.c_str());
dev->ClosePort();
dev->OpenPort();
};
cbs.input_removed = [=](const libremidi::input_port &p) {
auto dev = MidiDeviceInstance::GetDevice(p);
if (!dev) {
return;
}
blog(LOG_INFO, "MIDI input removed: %s",
p.port_name.c_str());
};
cbs.output_added = [=](const libremidi::output_port &p) {
auto dev = MidiDeviceInstance::GetDevice(p);
if (!dev) {
return;
}
blog(LOG_INFO, "MIDI output connected: %s",
p.port_name.c_str());
dev->ClosePort();
dev->OpenPort();
};
cbs.output_removed =
[=](const libremidi::output_port &p) {
auto dev =
MidiDeviceInstance::GetDevice(
p);
if (!dev) {
return;
}
blog(LOG_INFO,
"MIDI output removed: %s",
p.port_name.c_str());
};
observers.emplace_back(
cbs,
libremidi::observer_configuration_for(api));
}
} catch (const libremidi::driver_error &error) {
blog(LOG_WARNING, "Failed to setup midi device observers: %s",
error.what());
}
return true;
}
@@ -424,11 +434,16 @@ bool MidiDevice::SendMessge(const MidiMessage &m) const
static std::optional<libremidi::output_port>
getOutPortFromName(const std::string &name)
{
libremidi::observer obs;
for (const libremidi::output_port &port : obs.get_output_ports()) {
if (getNameFromPortInformation(port) == name) {
return port;
try {
libremidi::observer obs;
for (const libremidi::output_port &port :
obs.get_output_ports()) {
if (getNameFromPortInformation(port) == name) {
return port;
}
}
} catch (const libremidi::driver_error &error) {
blog(LOG_WARNING, "%s: %s", __func__, error.what());
}
return {};
}
@@ -436,11 +451,16 @@ getOutPortFromName(const std::string &name)
static std::optional<libremidi::input_port>
getInPortFromName(const std::string &name)
{
libremidi::observer obs;
for (const libremidi::input_port &port : obs.get_input_ports()) {
if (getNameFromPortInformation(port) == name) {
return port;
try {
libremidi::observer obs;
for (const libremidi::input_port &port :
obs.get_input_ports()) {
if (getNameFromPortInformation(port) == name) {
return port;
}
}
} catch (const libremidi::driver_error &error) {
blog(LOG_WARNING, "%s: %s", __func__, error.what());
}
return {};
}

View File

@@ -55,7 +55,9 @@ target_sources(
token.cpp
token.hpp
twitch-helpers.cpp
twitch-helpers.hpp)
twitch-helpers.hpp
twitch-tab.cpp
twitch-tab.hpp)
setup_advss_plugin(${PROJECT_NAME})
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")

View File

@@ -351,6 +351,11 @@ bool TokenIsValid(const std::weak_ptr<TwitchToken> &token_)
return token->IsValid();
}
std::deque<std::shared_ptr<Item>> &GetTwitchTokens()
{
return twitchTokens;
}
static bool ConnectionNameAvailable(const QString &name)
{
return !GetTwitchTokenByName(name);

View File

@@ -50,6 +50,7 @@ public:
std::string GetUserID() const { return _userID; }
std::shared_ptr<EventSub> GetEventSub();
bool IsValid(bool forceUpdate = false) const;
size_t PermissionCount() const { return _tokenOptions.size(); }
private:
std::string _token;
@@ -152,6 +153,7 @@ std::weak_ptr<TwitchToken> GetWeakTwitchTokenByName(const std::string &name);
std::weak_ptr<TwitchToken> GetWeakTwitchTokenByQString(const QString &name);
std::string GetWeakTwitchTokenName(std::weak_ptr<TwitchToken>);
bool TokenIsValid(const std::weak_ptr<TwitchToken> &token);
std::deque<std::shared_ptr<Item>> &GetTwitchTokens();
} // namespace advss

View File

@@ -0,0 +1,227 @@
#include "twitch-tab.hpp"
#include "obs-module-helper.hpp"
#include "sync-helpers.hpp"
#include "tab-helpers.hpp"
#include "token.hpp"
#include "ui-helpers.hpp"
#include <QTabWidget>
namespace advss {
static bool registerTab();
static void setupTab(QTabWidget *);
static bool registerTabDone = registerTab();
static TwitchConnectionsTable *tabWidget = nullptr;
static bool registerTab()
{
AddSetupTabCallback("twitchConnectionTab",
TwitchConnectionsTable::Create, setupTab);
return true;
}
static void setTabVisible(QTabWidget *tabWidget, bool visible)
{
SetTabVisibleByName(
tabWidget, visible,
obs_module_text("AdvSceneSwitcher.twitchConnectionTab.title"));
}
static bool twitchTabIsSelected(QTabWidget *tabWidget)
{
return tabWidget->tabText(tabWidget->currentIndex()) ==
obs_module_text("AdvSceneSwitcher.twitchConnectionTab.title");
}
TwitchConnectionsTable *TwitchConnectionsTable::Create()
{
tabWidget = new TwitchConnectionsTable();
return tabWidget;
}
void TwitchConnectionsTable::Add()
{
auto newToken = std::make_shared<TwitchToken>();
auto accepted =
TwitchTokenSettingsDialog::AskForSettings(this, *newToken);
if (!accepted) {
return;
}
{
auto lock = LockContext();
auto &tokens = GetTwitchTokens();
tokens.emplace_back(newToken);
}
TwitchConnectionSignalManager::Instance()->Add(
QString::fromStdString(newToken->Name()));
}
void TwitchConnectionsTable::Remove()
{
auto selectedRows =
tabWidget->Table()->selectionModel()->selectedRows();
if (selectedRows.empty()) {
return;
}
QStringList tokenNames;
for (auto row : selectedRows) {
auto cell = tabWidget->Table()->item(row.row(), 0);
if (!cell) {
continue;
}
tokenNames << cell->text();
}
int tokenNameCount = tokenNames.size();
if (tokenNameCount == 1) {
QString deleteWarning = obs_module_text(
"AdvSceneSwitcher.twitchConnectionTab.removeSingleConnectionPopup.text");
if (!DisplayMessage(deleteWarning.arg(tokenNames.at(0)),
true)) {
return;
}
} else {
QString deleteWarning = obs_module_text(
"AdvSceneSwitcher.twitchConnectionTab.removeMultipleConnectionsPopup.text");
if (!DisplayMessage(deleteWarning.arg(tokenNameCount), true)) {
return;
}
}
{
auto lock = LockContext();
RemoveItemsByName(GetTwitchTokens(), tokenNames);
}
for (const auto &name : tokenNames) {
TwitchConnectionSignalManager::Instance()->Remove(name);
}
}
static QStringList getCellLabels(TwitchToken *token, bool addName = true)
{
assert(token);
auto result = QStringList();
if (addName) {
result << QString::fromStdString(token->Name());
}
result << QString::fromStdString(obs_module_text(
token->IsValid()
? "AdvSceneSwitcher.twitchConnectionTab.yes"
: "AdvSceneSwitcher.twitchConnectionTab.no"))
<< QString::number(token->PermissionCount());
return result;
}
static void openSettingsDialog()
{
auto selectedRows =
tabWidget->Table()->selectionModel()->selectedRows();
if (selectedRows.empty()) {
return;
}
auto cell = tabWidget->Table()->item(selectedRows.last().row(), 0);
if (!cell) {
return;
}
auto weakToken = GetWeakTwitchTokenByQString(cell->text());
auto token = weakToken.lock();
if (!token) {
return;
}
TwitchTokenSettingsDialog::AskForSettings(tabWidget->Table(),
*token.get());
}
static const QStringList headers =
QStringList()
<< obs_module_text("AdvSceneSwitcher.twitchConnectionTab.name.header")
<< obs_module_text(
"AdvSceneSwitcher.twitchConnectionTab.isValid.header")
<< obs_module_text(
"AdvSceneSwitcher.twitchConnectionTab.permissionCount.header");
TwitchConnectionsTable::TwitchConnectionsTable(QTabWidget *parent)
: ResourceTable(
parent,
obs_module_text("AdvSceneSwitcher.twitchConnectionTab.help"),
obs_module_text(
"AdvSceneSwitcher.twitchConnectionTab.twitchConnectionAddButton.tooltip"),
obs_module_text(
"AdvSceneSwitcher.twitchConnectionTab.twitchConnectionRemoveButton.tooltip"),
headers, openSettingsDialog)
{
for (const auto &token : GetTwitchTokens()) {
auto t = std::static_pointer_cast<TwitchToken>(token);
AddItemTableRow(Table(), getCellLabels(t.get()));
}
SetHelpVisible(GetTwitchTokens().empty());
}
static void updateConnectionStatus(QTableWidget *table)
{
for (int row = 0; row < table->rowCount(); row++) {
auto item = table->item(row, 0);
if (!item) {
continue;
}
auto weakToken = GetWeakTwitchTokenByQString(item->text());
auto token = weakToken.lock();
if (!token) {
continue;
}
UpdateItemTableRow(table, row,
getCellLabels(token.get(), false));
}
}
static void setupTab(QTabWidget *tab)
{
if (GetTwitchTokens().empty()) {
setTabVisible(tab, false);
}
QWidget::connect(
TwitchConnectionSignalManager::Instance(),
&TwitchConnectionSignalManager::Add,
[tab](const QString &name) {
AddItemTableRow(
tabWidget->Table(),
getCellLabels(GetWeakTwitchTokenByQString(name)
.lock()
.get()));
tabWidget->SetHelpVisible(false);
tabWidget->HighlightAddButton(false);
setTabVisible(tab, true);
});
QWidget::connect(TwitchConnectionSignalManager::Instance(),
&TwitchConnectionSignalManager::Remove,
[](const QString &name) {
RemoveItemTableRow(tabWidget->Table(), name);
if (tabWidget->Table()->rowCount() == 0) {
tabWidget->SetHelpVisible(true);
tabWidget->HighlightAddButton(true);
}
});
QWidget::connect(tab, &QTabWidget::currentChanged, [tab]() {
if (twitchTabIsSelected(tab)) {
updateConnectionStatus(tabWidget->Table());
}
});
}
} // namespace advss

View File

@@ -0,0 +1,20 @@
#pragma once
#include "resource-table.hpp"
namespace advss {
class TwitchConnectionsTable final : public ResourceTable {
Q_OBJECT
public:
static TwitchConnectionsTable *Create();
private slots:
void Add();
void Remove();
private:
TwitchConnectionsTable(QTabWidget *parent = nullptr);
};
} // namespace advss