Compare commits

..

21 Commits

Author SHA1 Message Date
WarmUpTill
e683fd82f4 Draft source interaction action 2024-07-09 21:23:38 +02:00
WarmUpTill
17c72b772d Display plugin load failure warning only once 2024-07-09 19:10:51 +02:00
WarmUpTill
3ff9a1f270 Add USB condition type
It allows users to check if a given USB device is currently connected
2024-07-08 20:54:26 +02:00
WarmUpTill
7d120d8b6a Fix MacOS version of install_advss_plugin_dependency_file 2024-07-08 20:54:26 +02:00
WarmUpTill
53cfe4b030 Add libusb dependency 2024-07-08 20:54:26 +02:00
WarmUpTill
4dcd748a1f Switch to github repo of openssl 2024-07-08 20:54:26 +02:00
WarmUpTill
223ce0d0a0 Fix potential crash in StatusControl 2024-07-03 13:02:34 +02:00
WarmUpTill
5bd3341681 Add option to clear message buffer on match 2024-07-03 13:02:34 +02:00
WarmUpTill
bb527d6910 Improve error logging of commercial start via Twitch action 2024-07-03 12:55:49 +02:00
WarmUpTill
dfedbcf31a Add option to set maximum replay buffer time 2024-06-26 19:19:41 +02:00
WarmUpTill
1bbfd16b3f Add option to check disk space available
Based on the OBS stats dock implementation, which queries the drive to
which outputs would write to
2024-06-22 15:40:47 +02:00
WarmUpTill
7f5737d03f Use default cursor icon on macro segment control hover
Previously the cursor icon would be set to Qt::SplitVCursor as those
widgets were moved into the splitter handle layout
2024-06-20 21:41:28 +02:00
WarmUpTill
7702541d81 Cleanup
* Increase update frequency of AutoUpdateTooltipLabel
* Prevent crash in TempVariableRef::PostLoad()
* Ensure that correct widget is passed for MacroSelection connections
2024-06-20 21:41:28 +02:00
WarmUpTill
3bc15e585c Switch QPushButton to QToolButton
This will be more consistent with the widget styles used throughout OBS
and solves a few layout issues
2024-06-20 21:41:28 +02:00
WarmUpTill
d77101b6aa Restore fullscreen projector display selection
Instead of discarding the monitor selection now only no longer perform
the action while the monitor setup is changed.
2024-06-18 21:24:28 +02:00
WarmUpTill
c1e23f9c7a Improve slide show condition
* Add temp var support
* Add regex support for path check
* Improve layout stretch handling
2024-06-16 18:42:06 +02:00
WarmUpTill
70415cd8f4 Add support for "slideshow_v2" 2024-06-16 18:42:06 +02:00
WarmUpTill
f50cd76493 Fix i386 build issue 2024-06-16 11:04:05 +02:00
炭酸コーラ
e4af698881 Add Japanese translation 2024-06-15 10:11:45 +02:00
WarmUpTill
33120a359c Fix Twitch "goal type" temp var tooltips being cut off early 2024-06-15 10:11:45 +02:00
WarmUpTill
091a364097 Print line numbers in check-locale.py in case of error 2024-06-15 10:11:45 +02:00
68 changed files with 5107 additions and 869 deletions

View File

@@ -9,4 +9,5 @@ package 'libxtst-dev'
package 'libxss-dev'
package 'libopencv-dev'
package 'libtesseract-dev'
package 'libprocps-dev'
package 'libprocps-dev'
package 'libusb-1.0-0-dev'

View File

@@ -3,3 +3,4 @@ brew "coreutils"
brew "cmake"
brew "jq"
brew "xcbeautify"
brew "automake"

View File

@@ -1,3 +1,4 @@
package 'cmake', path: 'Cmake\bin', bin: 'cmake'
package 'innosetup', path: 'Inno Setup 6', bin: 'iscc'
package 'OpenSSL', path: 'OpenSSL', bin: 'openssl'
package 'OpenSSL', path: 'OpenSSL', bin: 'openssl'
package 'Microsoft.VisualStudio.Locator', path: 'vswhere', bin: 'vswhere'

View File

@@ -380,7 +380,7 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
pushd ${advss_dep_path}
log_info "Prepare openssl ..."
rm -rf openssl
git clone git://git.openssl.org/openssl.git --branch openssl-3.1.2 --depth 1
git clone https://github.com/openssl/openssl.git --branch openssl-3.1.2 --depth 1
mv openssl openssl_x86
cp -r openssl_x86 openssl_arm
@@ -405,6 +405,55 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
log_info "Clean up openssl dir..."
mv openssl_x86 openssl
rm -rf openssl_arm
popd
pushd ${project_root}/deps/libusb
log_info "Prepare libusb ..."
log_info "Building libusb x86 (deps) ..."
export MACOSX_DEPLOYMENT_TARGET=10.9
mkdir ${project_root}/deps/libusb/out_x86
./autogen.sh
./configure --host=x86_64-apple-darwin --prefix=${advss_dep_path}
make && make install
log_info "Building libusb x86 ..."
make clean
rm -r ${project_root}/deps/libusb/out_x86
mkdir ${project_root}/deps/libusb/out_x86
./configure --host=aarch64-apple-darwin --prefix=${project_root}/deps/libusb/out_x86
make && make install
log_info "Building libusb arm ..."
make clean
export SDKROOT=$(xcrun --sdk macosx --show-sdk-path)
export CC=$(xcrun --sdk macosx --find clang)
export CXX=$(xcrun --sdk macosx --find clang++)
export CFLAGS="-arch arm64 -isysroot $SDKROOT"
export CXXFLAGS="-arch arm64 -isysroot $SDKROOT"
export LDFLAGS="-arch arm64 -isysroot $SDKROOT"
export MACOSX_DEPLOYMENT_TARGET=10.15
mkdir ${project_root}/deps/libusb/out_arm
./configure --host=aarch64-apple-darwin --prefix=${project_root}/deps/libusb/out_arm
make && make install
log_info "Combine arm and x86 libusb binaries ..."
lipo -create ${project_root}/deps/libusb/out_x86/lib/libusb-1.0.0.dylib \
${project_root}/deps/libusb/out_arm/lib/libusb-1.0.0.dylib \
-output ${advss_dep_path}/lib/temp-libusb-1.0.0.dylib
mv ${advss_dep_path}/lib/temp-libusb-1.0.0.dylib ${advss_dep_path}/lib/libusb-1.0.0.dylib
install_name_tool -id @rpath/libusb-1.0.0.dylib ${advss_dep_path}/lib/libusb-1.0.0.dylib
log_info "Clean up libusb ..."
unset SDKROOT
unset CC
unset CXX
unset CFLAGS
unset CXXFLAGS
unset LDFLAGS
unset MACOSX_DEPLOYMENT_TARGET
popd
;;
linux)
# Nothing to do for now

View File

@@ -199,6 +199,26 @@ function Build {
Log-Information "Install tesseract..."
Invoke-External cmake --install "${TesseractBuildPath}" --prefix "${ADVSSDepPath}" @TesseractCmakeArgs
Push-Location -Stack BuildLibusbTemp
Ensure-Location $ProjectRoot
$LibusbPath = "${ProjectRoot}/deps/libusb"
Log-Information "Building libusb..."
$msbuildExe = vswhere -latest -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe | select-object -first 1
if ($msbuildExe) {
Invoke-External $msbuildExe "${LibusbPath}/msvc/libusb.sln" /property:Configuration=Release /property:Platform=x64
$libusbBuildResultDirectory = "${LibusbPath}/build/v143/x64/Release"
if (-not (Test-Path -Path $libusbBuildResultDirectory)) {
$libusbBuildResultDirectory = "${LibusbPath}/x64/Release/dll"
}
Copy-Item -Path "${libusbBuildResultDirectory}/*" -Destination ${ADVSSDepPath} -Recurse -Force
} else {
Log-Information "Failed to locate msbuild.exe - skipping libusb build"
}
}
Build

3
.gitmodules vendored
View File

@@ -25,3 +25,6 @@
[submodule "deps/json"]
path = deps/json
url = https://github.com/nlohmann/json.git
[submodule "deps/libusb"]
path = deps/libusb
url = https://github.com/libusb/libusb.git

View File

@@ -157,6 +157,8 @@ target_sources(
lib/utils/backup.hpp
lib/utils/curl-helper.cpp
lib/utils/curl-helper.hpp
lib/utils/cursor-shape-changer.cpp
lib/utils/cursor-shape-changer.hpp
lib/utils/double-slider.cpp
lib/utils/double-slider.hpp
lib/utils/duration-control.cpp
@@ -174,6 +176,8 @@ target_sources(
lib/utils/item-selection-helpers.hpp
lib/utils/layout-helpers.cpp
lib/utils/layout-helpers.hpp
lib/utils/list-controls.cpp
lib/utils/list-controls.hpp
lib/utils/list-editor.cpp
lib/utils/list-editor.hpp
lib/utils/log-helper.cpp

View File

@@ -12,11 +12,13 @@ class localeEntry:
locale = ""
widgetPlaceholders = []
qStringArgs = []
lineNum = -1
def __init__(self, locale, widgetPlaceholders, qStringArgs) -> None:
def __init__(self, locale, widgetPlaceholders, qStringArgs, lineNum) -> None:
self.locale = locale
self.widgetPlaceholders = widgetPlaceholders
self.qStringArgs = qStringArgs
self.lineNum = lineNum
def getNonDefaultLocales(dir):
@@ -32,7 +34,7 @@ def getNonDefaultLocales(dir):
def getAllLocaleEntries(file):
localeEntries = []
with open(file, "r", encoding="UTF-8") as f:
for line in f.readlines():
for lineNum, line in enumerate(f):
widgetPlaceholders = []
qStringArgs = []
@@ -49,7 +51,9 @@ def getAllLocaleEntries(file):
qStringArgs.append(match.group(0))
localeEntries.append(
localeEntry(line.split("=")[0], widgetPlaceholders, qStringArgs)
localeEntry(
line.split("=")[0], widgetPlaceholders, qStringArgs, lineNum + 1
)
)
return localeEntries
@@ -73,8 +77,8 @@ def checkLocaleEntries(file, expectedLocaleEntries):
if expectedLocaleEntry is None:
result = False
print(
'ERROR: Locale entry "{}" from "{}" not found in "{}"'.format(
localeEntry.locale, file, defaultLocaleFile
'ERROR: Locale entry "{}" from "{}:{}" not found in "{}"'.format(
localeEntry.locale, file, localeEntry.lineNum, defaultLocaleFile
)
)
continue
@@ -83,8 +87,13 @@ def checkLocaleEntries(file, expectedLocaleEntries):
if placeholder not in expectedLocaleEntry.widgetPlaceholders:
result = False
print(
'ERROR: Locale entry "{}" from "{}" does contain "{}" widget placeholder while "{}" does not'.format(
localeEntry.locale, file, placeholder, defaultLocaleFile
'ERROR: Locale entry "{}" from "{}:{}" does contain "{}" widget placeholder while "{}:{}" does not'.format(
localeEntry.locale,
file,
localeEntry.lineNum,
placeholder,
defaultLocaleFile,
expectedLocaleEntry.lineNum,
)
)
@@ -92,8 +101,8 @@ def checkLocaleEntries(file, expectedLocaleEntries):
if placeholder not in localeEntry.widgetPlaceholders:
result = False
print(
'ERROR: Locale entry "{}" from "{}" does not contain "{}" widget placeholder'.format(
localeEntry.locale, file, placeholder
'ERROR: Locale entry "{}" from "{}:{}" does not contain "{}" widget placeholder'.format(
localeEntry.locale, file, localeEntry.lineNum, placeholder
)
)
@@ -101,8 +110,13 @@ def checkLocaleEntries(file, expectedLocaleEntries):
if arg not in expectedLocaleEntry.qStringArgs:
result = False
print(
'ERROR: Locale entry "{}" from "{}" does contain "{}" QString arg while "{}" does not'.format(
localeEntry.locale, file, arg, defaultLocaleFile
'ERROR: Locale entry "{}" from "{}:{}" does contain "{}" QString arg while "{}:{}" does not'.format(
localeEntry.locale,
file,
localeEntry.lineNum,
arg,
defaultLocaleFile,
expectedLocaleEntry.lineNum,
)
)
@@ -110,8 +124,8 @@ def checkLocaleEntries(file, expectedLocaleEntries):
if arg not in localeEntry.qStringArgs:
result = False
print(
'ERROR: Locale entry "{}" from "{}" does not contain "{}" QString arg'.format(
localeEntry.locale, file, arg
'ERROR: Locale entry "{}" from "{}:{}" does not contain "{}" QString arg'.format(
localeEntry.locale, file, localeEntry.lineNum, arg
)
)

View File

@@ -60,10 +60,36 @@ if(OS_MACOS)
${dep}_Development)
endfunction()
function(install_advss_plugin_dependency_file ${target} dep)
target_sources(advanced-scene-switcher PRIVATE ${dep})
set_source_files_properties(${dep} PROPERTIES MACOSX_PACKAGE_LOCATION
${ADVSS_BUNDLE_PLUGIN_DIR})
function(install_advss_plugin_dependency_file target dep)
get_filename_component(_FILENAME ${dep} NAME)
string(REGEX REPLACE "\\.[^.]*$" "" _FILENAMENOEXT ${_FILENAME})
set(_DEP_NAME "${target}-${_FILENAMENOEXT}")
install(
FILES "${dep}"
DESTINATION "${ADVSS_BUNDLE_PLUGIN_DIR}"
COMPONENT ${_DEP_NAME}_Runtime
DESTINATION "${ADVSS_BUNDLE_PLUGIN_DIR}"
COMPONENT ${_DEP_NAME}_Runtime
NAMELINK_COMPONENT ${_DEP_NAME}_Development)
install(
FILES "${dep}"
DESTINATION "${ADVSS_BUNDLE_PLUGIN_DIR}"
COMPONENT obs_${_DEP_NAME}
EXCLUDE_FROM_ALL
DESTINATION "${ADVSS_BUNDLE_PLUGIN_DIR}"
COMPONENT obs_${_DEP_NAME}
EXCLUDE_FROM_ALL)
add_custom_command(
TARGET ${target}
POST_BUILD
COMMAND
"${CMAKE_COMMAND}" --install ${CMAKE_CURRENT_BINARY_DIR} --config
$<CONFIG> --prefix ${CMAKE_INSTALL_PREFIX} --component obs_${_DEP_NAME}
COMMENT "Installing ${_DEP_NAME} to OBS rundir\n"
VERBATIM)
endfunction()
# --- End of section ---

View File

@@ -427,7 +427,6 @@ AdvSceneSwitcher.action.replay.saveWarn="Warnung: Ein zu häufiges Speichern kan
AdvSceneSwitcher.action.replay.type.stop="Replay Buffer stoppen"
AdvSceneSwitcher.action.replay.type.start="Replay Buffer starten"
AdvSceneSwitcher.action.replay.type.save="Replay Buffer speichern"
AdvSceneSwitcher.action.replay.entry="{{actions}}"
AdvSceneSwitcher.action.streaming="Stream"
AdvSceneSwitcher.action.streaming.type.stop="Stream stoppen"
AdvSceneSwitcher.action.streaming.type.start="Stream starten"

View File

@@ -620,7 +620,7 @@ AdvSceneSwitcher.condition.slideshow.condition.slideChanged="Slide changed"
AdvSceneSwitcher.condition.slideshow.condition.slideIndex="Current slide number is"
AdvSceneSwitcher.condition.slideshow.condition.slidePath="Current slide path is"
AdvSceneSwitcher.condition.slideshow.updateIntervalTooltip="Information about the slide show status will only be updated based on the configured time between slides"
AdvSceneSwitcher.condition.slideshow.entry="{{sources}}{{conditions}}{{index}}{{path}}"
AdvSceneSwitcher.condition.slideshow.entry="{{sources}}{{conditions}}{{index}}{{path}}{{regex}}"
AdvSceneSwitcher.condition.twitch="Twitch"
AdvSceneSwitcher.condition.twitch.type.event.channel.stream.online="Stream went online"
AdvSceneSwitcher.condition.twitch.type.event.channel.stream.offline="Stream went offline"
@@ -703,6 +703,16 @@ AdvSceneSwitcher.condition.folder.condition.folderRemove="A directory was remove
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}}"
AdvSceneSwitcher.condition.usb="USB"
AdvSceneSwitcher.condition.usb.description="A USB device matching the following properties is connected:"
AdvSceneSwitcher.condition.usb.vendorID="Vendor ID:"
AdvSceneSwitcher.condition.usb.productID="Product ID:"
AdvSceneSwitcher.condition.usb.busNumber="Bus Number:"
AdvSceneSwitcher.condition.usb.deviceAddress="Device Address:"
AdvSceneSwitcher.condition.usb.vendorName="Vendor Name:"
AdvSceneSwitcher.condition.usb.productName="Product Name:"
AdvSceneSwitcher.condition.usb.serialNumber="Serial Number:"
AdvSceneSwitcher.condition.noDevicesFoundWarning="No USB devices detected!\nThe plugin might not have the required permissions to check for USB devices."
; Macro Actions
AdvSceneSwitcher.action.scene="Switch scene"
@@ -748,10 +758,12 @@ AdvSceneSwitcher.action.recording.split.hint="Make sure to enable automatic file
AdvSceneSwitcher.action.recording.entry="{{actions}}{{recordFolder}}{{recordFileFormat}}{{pauseHint}}{{splitHint}}"
AdvSceneSwitcher.action.replay="Replay buffer"
AdvSceneSwitcher.action.replay.saveWarn="Warning: Saving too frequently might result in the replay buffer not actually being saved!"
AdvSceneSwitcher.action.replay.durationWarn="Warning: Changing the maximum replay time will only apply the next time the replay buffer is started!"
AdvSceneSwitcher.action.replay.type.stop="Stop replay buffer"
AdvSceneSwitcher.action.replay.type.start="Start replay buffer"
AdvSceneSwitcher.action.replay.type.save="Save replay buffer"
AdvSceneSwitcher.action.replay.entry="{{actions}}"
AdvSceneSwitcher.action.replay.type.duration="Set maximum replay time"
AdvSceneSwitcher.action.replay.entry="{{actions}}{{duration}}"
AdvSceneSwitcher.action.streaming="Streaming"
AdvSceneSwitcher.action.streaming.type.stop="Stop streaming"
AdvSceneSwitcher.action.streaming.type.start="Start streaming"
@@ -797,6 +809,12 @@ AdvSceneSwitcher.action.source.type.deinterlaceOrder="Set deinterlace field orde
AdvSceneSwitcher.action.source.type.openInteractionDialog="Open interaction dialog"
AdvSceneSwitcher.action.source.type.openFilterDialog="Open filter dialog"
AdvSceneSwitcher.action.source.type.openPropertiesDialog="Open properties dialog"
AdvSceneSwitcher.action.source.type.interact="Interact"
AdvSceneSwitcher.action.source.type.interact.selectType="Interaction type:{{type}}"
AdvSceneSwitcher.action.source.type.interact.mouseClick="Send mouse click"
AdvSceneSwitcher.action.source.type.interact.mouseMove="Send mouse move"
AdvSceneSwitcher.action.source.type.interact.mouseWheel="Send mouse wheel"
AdvSceneSwitcher.action.source.type.interact.keyPress="Send key press"
AdvSceneSwitcher.action.source.entry="{{actions}}{{sources}}{{settingsButtons}}{{deinterlaceMode}}{{deinterlaceOrder}}{{refresh}}"
AdvSceneSwitcher.action.source.entry.settings="{{settings}}{{settingsInputMethod}}{{settingValue}}{{tempVar}}"
AdvSceneSwitcher.action.source.warning="Warning: Enabling and disabling sources globally cannot be controlled by the OBS UI\nYou might be looking for \"Scene item visibility\""
@@ -1117,6 +1135,11 @@ AdvSceneSwitcher.item.emptyName="Empty name not allowed!"
AdvSceneSwitcher.item.nameNotAvailable="Name not available!"
AdvSceneSwitcher.item.nameReserved="Name reserved!"
AdvSceneSwitcher.listControls.add="Add"
AdvSceneSwitcher.listControls.remove="Remove"
AdvSceneSwitcher.listControls.up="Up"
AdvSceneSwitcher.listControls.down="Down"
AdvSceneSwitcher.macroSegmentSelection.invalid="Invalid selection!"
AdvSceneSwitcher.validation.text.maxLength="Only %1 characters are allowed in this field!"
@@ -1440,24 +1463,13 @@ AdvSceneSwitcher.tempVar.twitch.status.prediction.description="The status of the
AdvSceneSwitcher.tempVar.twitch.id.goal="Goal ID"
AdvSceneSwitcher.tempVar.twitch.type.goal="Goal type"
AdvSceneSwitcher.tempVar.twitch.type.goal.description="The type of goal. Possible values are:\n
follow — The goal is to increase followers.\n
subscription — The goal is to increase subscriptions. This type shows the net increase or decrease in tier points associated with the subscriptions.\n
subscription_count — The goal is to increase subscriptions. This type shows the net increase or decrease in the number of subscriptions.\n
new_subscription — The goal is to increase subscriptions. This type shows only the net increase in tier points associated with the subscriptions (it does not account for users that unsubscribed since the goal started).\n
new_subscription_count — The goal is to increase subscriptions. This type shows only the net increase in the number of subscriptions (it does not account for users that unsubscribed since the goal started)."
AdvSceneSwitcher.tempVar.twitch.type.goal.description="The type of goal. Possible values are:\n follow — The goal is to increase followers.\n subscription — The goal is to increase subscriptions. This type shows the net increase or decrease in tier points associated with the subscriptions.\n subscription_count — The goal is to increase subscriptions. This type shows the net increase or decrease in the number of subscriptions.\n new_subscription — The goal is to increase subscriptions. This type shows only the net increase in tier points associated with the subscriptions (it does not account for users that unsubscribed since the goal started).\n new_subscription_count — The goal is to increase subscriptions. This type shows only the net increase in the number of subscriptions (it does not account for users that unsubscribed since the goal started)."
AdvSceneSwitcher.tempVar.twitch.description.goal="Goal description"
AdvSceneSwitcher.tempVar.twitch.description.goal.description="A description of the goal, if specified. The description may contain a maximum of 40 characters."
AdvSceneSwitcher.tempVar.twitch.is_achieved.goal="Goal is achieved"
AdvSceneSwitcher.tempVar.twitch.is_achieved.goal.description="A Boolean value that indicates whether the broadcaster achieved their goal. Is true if the goal was achieved; otherwise, false."
AdvSceneSwitcher.tempVar.twitch.current_amount.goal="Goal current amount"
AdvSceneSwitcher.tempVar.twitch.current_amount.goal.description="The goal's current value.\n
The goal's type determines how this value is increased or decreased.\n
If type is follow, this field is set to the broadcaster's current number of followers. This number increases with new followers and decreases when users unfollow the broadcaster.\n
If type is subscription, this field is increased and decreased by the points value associated with the subscription tier. For example, if a tier-two subscription is worth 2 points, this field is increased or decreased by 2, not 1.\n
If type is subscription_count, this field is increased by 1 for each new subscription and decreased by 1 for each user that unsubscribes.\n
If type is new_subscription, this field is increased by the points value associated with the subscription tier. For example, if a tier-two subscription is worth 2 points, this field is increased by 2, not 1.\n
If type is new_subscription_count, this field is increased by 1 for each new subscription."
AdvSceneSwitcher.tempVar.twitch.current_amount.goal.description="The goal's current value.\nThe goal's type determines how this value is increased or decreased.\n If type is follow, this field is set to the broadcaster's current number of followers. This number increases with new followers and decreases when users unfollow the broadcaster.\n If type is subscription, this field is increased and decreased by the points value associated with the subscription tier. For example, if a tier-two subscription is worth 2 points, this field is increased or decreased by 2, not 1.\n If type is subscription_count, this field is increased by 1 for each new subscription and decreased by 1 for each user that unsubscribes.\n If type is new_subscription, this field is increased by the points value associated with the subscription tier. For example, if a tier-two subscription is worth 2 points, this field is increased by 2, not 1.\n If type is new_subscription_count, this field is increased by 1 for each new subscription."
AdvSceneSwitcher.tempVar.twitch.target_amount.goal="Goal target amount"
AdvSceneSwitcher.tempVar.twitch.target_amount.goal.description="The goal's target value. For example, if the broadcaster has 200 followers before creating the goal, and their goal is to double that number, this field is set to 400."
AdvSceneSwitcher.tempVar.twitch.started_at.goal="Goal start time"
@@ -1775,6 +1787,21 @@ AdvSceneSwitcher.tempVar.source.settings="Settings string"
AdvSceneSwitcher.tempVar.filter.setting="Setting value"
AdvSceneSwitcher.tempVar.filter.settings="Settings string"
AdvSceneSwitcher.tempVar.slideShow.index="Slide index"
AdvSceneSwitcher.tempVar.slideShow.index.description="The index of the slide currently being displayed.\nIf the index cannot be determined, -1 is returned."
AdvSceneSwitcher.tempVar.slideShow.path="Slide path"
AdvSceneSwitcher.tempVar.slideShow.path.description="The filesystem path of the slide currently being displayed."
AdvSceneSwitcher.tempVar.slideShow.fileName="Slide file name"
AdvSceneSwitcher.tempVar.slideShow.fileName.description="The name of file of the slide currently being displayed."
AdvSceneSwitcher.tempVar.usb.vendorID="Vendor ID"
AdvSceneSwitcher.tempVar.usb.productID="Product ID"
AdvSceneSwitcher.tempVar.usb.busNumber="Bus Number"
AdvSceneSwitcher.tempVar.usb.deviceAddress="Device Address"
AdvSceneSwitcher.tempVar.usb.vendorName="Vendor Name"
AdvSceneSwitcher.tempVar.usb.productName="Product Name"
AdvSceneSwitcher.tempVar.usb.serialNumber="Serial Number"
AdvSceneSwitcher.selectScene="--select scene--"
AdvSceneSwitcher.selectPreviousScene="Previous Scene"
AdvSceneSwitcher.selectCurrentScene="Current Scene"
@@ -1866,6 +1893,27 @@ AdvSceneSwitcher.audio.monitor.both="Monitor and Output"
AdvSceneSwitcher.noSettingsButtons="No buttons found!"
AdvSceneSwitcher.clearBufferOnMatch="Clear message buffer when matching message was found"
AdvSceneSwitcher.key="Key:"
AdvSceneSwitcher.key.up="Key up:"
AdvSceneSwitcher.modifierKey="Modifier keys:"
AdvSceneSwitcher.modifierKey.shift="Shift"
AdvSceneSwitcher.modifierKey.ctrl="Ctrl"
AdvSceneSwitcher.modifierKey.alt="Alt"
AdvSceneSwitcher.modifierKey.meta="Meta"
AdvSceneSwitcher.mouse.button="Mouse button:"
AdvSceneSwitcher.mouse.button.left="Left"
AdvSceneSwitcher.mouse.button.middle="Middle"
AdvSceneSwitcher.mouse.button.right="Right"
AdvSceneSwitcher.mouse.position="Mouse position:"
AdvSceneSwitcher.mouse.up="Mouse up event:"
AdvSceneSwitcher.mouse.count="Mouse click count:"
AdvSceneSwitcher.mouse.leave="Mouse leave event:"
AdvSceneSwitcher.mouse.scrollAmount="Mouse scroll amount:"
; Legacy tabs below - please don't waste your time adding translations for these :)
; Transition Tab
AdvSceneSwitcher.transitionTab.title="Transition"

View File

@@ -348,7 +348,6 @@ AdvSceneSwitcher.action.replay.saveWarn="Advertencia: ¡Guardar con demasiada fr
AdvSceneSwitcher.action.replay.type.stop="Detener el búfer de reproducción"
AdvSceneSwitcher.action.replay.type.start="Iniciar búfer de reproducción"
AdvSceneSwitcher.action.replay.type.save="Guardar búfer de reproducción"
AdvSceneSwitcher.action.replay.entry="{{actions}}"
AdvSceneSwitcher.action.streaming="Transmisión"
AdvSceneSwitcher.action.streaming.type.stop="Detener transmisión"
AdvSceneSwitcher.action.streaming.type.start="Iniciar transmisión"

2090
data/locale/ja-JP.ini Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -137,7 +137,6 @@ AdvSceneSwitcher.action.replay="Буфер воспроизведения"
AdvSceneSwitcher.action.replay.type.stop="Остановить буфер воспроизведения"
AdvSceneSwitcher.action.replay.type.start="Начать воспроизведение буфера"
AdvSceneSwitcher.action.replay.type.save="Сохранить буфер воспроизведения"
AdvSceneSwitcher.action.replay.entry="{{actions}}"
AdvSceneSwitcher.action.streaming="Потоковое вещание"
AdvSceneSwitcher.action.streaming.type.stop="Остановить потоковое вещание"
AdvSceneSwitcher.action.streaming.type.start="Начать потоковое вещание"

View File

@@ -279,7 +279,6 @@ AdvSceneSwitcher.action.replay="Tekrar arabelleği"
AdvSceneSwitcher.action.replay.type.stop="Tekrar arabelleğini durdur"
AdvSceneSwitcher.action.replay.type.start="Tekrar arabelleğini başlat"
AdvSceneSwitcher.action.replay.type.save="Tekrar arabelleğini kaydet"
AdvSceneSwitcher.action.replay.entry="{{actions}}"
AdvSceneSwitcher.action.streaming="Yayın"
AdvSceneSwitcher.action.streaming.type.stop="Yayın durdur"
AdvSceneSwitcher.action.streaming.type.start="Yayın başlat"

View File

@@ -481,7 +481,6 @@ AdvSceneSwitcher.action.replay.saveWarn="警告:保存过于频繁可能会导
AdvSceneSwitcher.action.replay.type.stop="停止回放缓存"
AdvSceneSwitcher.action.replay.type.start="启动回放缓存"
AdvSceneSwitcher.action.replay.type.save="保存回放缓存"
AdvSceneSwitcher.action.replay.entry="{{actions}}"
AdvSceneSwitcher.action.streaming="推流"
AdvSceneSwitcher.action.streaming.type.stop="停止推流"
AdvSceneSwitcher.action.streaming.type.start="开始推流"

1
deps/libusb vendored Submodule

Submodule deps/libusb added at d52e355daa

View File

@@ -68,7 +68,7 @@
<x>0</x>
<y>0</y>
<width>962</width>
<height>1033</height>
<height>1040</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_19">
@@ -572,40 +572,22 @@
<item>
<layout class="QHBoxLayout" name="horizontalLayout_32">
<item>
<widget class="QPushButton" name="priorityUp">
<property name="maximumSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<widget class="QToolButton" name="priorityUp">
<property name="icon">
<iconset>
<normaloff>../../../forms/images/up.png</normaloff>../../../forms/images/up.png</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="themeID" stdset="0">
<string notr="true">upArrowIconSmall</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="priorityDown">
<property name="maximumSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<widget class="QToolButton" name="priorityDown">
<property name="icon">
<iconset>
<normaloff>../../../forms/images/down.png</normaloff>../../../forms/images/down.png</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="themeID" stdset="0">
<string notr="true">downArrowIconSmall</string>
</property>
@@ -739,135 +721,47 @@
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_61">
<layout class="QHBoxLayout" name="macroControlLayout">
<item>
<widget class="QPushButton" name="macroAdd">
<property name="maximumSize">
<size>
<width>22</width>
<height>16777215</height>
</size>
</property>
<widget class="QToolButton" name="macroAdd">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.macroAddButton</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="macroRemove">
<property name="maximumSize">
<size>
<width>22</width>
<height>16777215</height>
</size>
</property>
<widget class="QToolButton" name="macroRemove">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.macroRemoveButton</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_130">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>5</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="Line" name="line_46">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_131">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>5</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="macroUp">
<property name="maximumSize">
<size>
<width>22</width>
<height>16777215</height>
</size>
</property>
<widget class="QToolButton" name="macroUp">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.macroUpButton</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="themeID" stdset="0">
<string notr="true">upArrowIconSmall</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="macroDown">
<property name="maximumSize">
<size>
<width>22</width>
<height>16777215</height>
</size>
</property>
<widget class="QToolButton" name="macroDown">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.macroDownButton</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="themeID" stdset="0">
<string notr="true">downArrowIconSmall</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_65">
<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>
@@ -960,22 +854,13 @@
</spacer>
</item>
<item>
<widget class="QPushButton" name="macroSettings">
<property name="maximumSize">
<size>
<width>22</width>
<height>16777215</height>
</size>
</property>
<widget class="QToolButton" name="macroSettings">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.openMacroSettingsButton</string>
</property>
<property name="text">
<string/>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="themeID" stdset="0">
<string notr="true">propertiesIconSmall</string>
</property>
@@ -1013,173 +898,67 @@
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_12">
<layout class="QHBoxLayout" name="conditionControlsLayout">
<property name="leftMargin">
<number>9</number>
</property>
<item>
<widget class="QPushButton" name="conditionAdd">
<property name="maximumSize">
<size>
<width>22</width>
<height>16777215</height>
</size>
</property>
<widget class="QToolButton" name="conditionAdd">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.conditionAddButton</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="conditionRemove">
<property name="maximumSize">
<size>
<width>22</width>
<height>16777215</height>
</size>
</property>
<widget class="QToolButton" name="conditionRemove">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.conditionRemoveButton</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_135">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>5</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="Line" name="line_48">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_134">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>5</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="conditionTop">
<property name="maximumSize">
<size>
<width>22</width>
<height>16777215</height>
</size>
</property>
<widget class="QToolButton" name="conditionTop">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.conditionTopButton</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="conditionUp">
<property name="maximumSize">
<size>
<width>22</width>
<height>16777215</height>
</size>
</property>
<widget class="QToolButton" name="conditionUp">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.conditionUpButton</string>
</property>
<property name="text">
<string/>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="themeID" stdset="0">
<string notr="true">upArrowIconSmall</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="conditionDown">
<property name="maximumSize">
<size>
<width>22</width>
<height>16777215</height>
</size>
</property>
<widget class="QToolButton" name="conditionDown">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.conditionDownButton</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="themeID" stdset="0">
<string notr="true">downArrowIconSmall</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="conditionBottom">
<property name="maximumSize">
<size>
<width>22</width>
<height>16777215</height>
</size>
</property>
<widget class="QToolButton" name="conditionBottom">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.conditionBottomButton</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_15">
<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>
@@ -1213,155 +992,62 @@
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_21">
<layout class="QHBoxLayout" name="actionControlsLayout">
<property name="leftMargin">
<number>9</number>
</property>
<item>
<widget class="QPushButton" name="actionAdd">
<property name="maximumSize">
<size>
<width>22</width>
<height>16777215</height>
</size>
</property>
<widget class="QToolButton" name="actionAdd">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.actionAddButton</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="actionRemove">
<property name="maximumSize">
<size>
<width>22</width>
<height>16777215</height>
</size>
</property>
<widget class="QToolButton" name="actionRemove">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.actionRemoveButton</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_133">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>5</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="Line" name="line_47">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_132">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>5</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="actionTop">
<property name="maximumSize">
<size>
<width>22</width>
<height>16777215</height>
</size>
</property>
<widget class="QToolButton" name="actionTop">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.actionTopButton</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="actionUp">
<property name="maximumSize">
<size>
<width>22</width>
<height>16777215</height>
</size>
</property>
<widget class="QToolButton" name="actionUp">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.actionUpButton</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="themeID" stdset="0">
<string notr="true">upArrowIconSmall</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="actionDown">
<property name="maximumSize">
<size>
<width>22</width>
<height>16777215</height>
</size>
</property>
<widget class="QToolButton" name="actionDown">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.actionDownButton</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="themeID" stdset="0">
<string notr="true">downArrowIconSmall</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="actionBottom">
<property name="maximumSize">
<size>
<width>22</width>
<height>16777215</height>
</size>
</property>
<widget class="QToolButton" name="actionBottom">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.actionBottomButton</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
@@ -1378,22 +1064,13 @@
</spacer>
</item>
<item>
<widget class="QPushButton" name="toggleElseActions">
<property name="maximumSize">
<size>
<width>22</width>
<height>16777215</height>
</size>
</property>
<widget class="QToolButton" name="toggleElseActions">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.toggleShowElseSection</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
@@ -1425,170 +1102,64 @@
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_33">
<layout class="QHBoxLayout" name="elseActionControlsLayout">
<property name="leftMargin">
<number>9</number>
</property>
<item>
<widget class="QPushButton" name="elseActionAdd">
<property name="maximumSize">
<size>
<width>22</width>
<height>16777215</height>
</size>
</property>
<widget class="QToolButton" name="elseActionAdd">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.elseActionAddButton</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="elseActionRemove">
<property name="maximumSize">
<size>
<width>22</width>
<height>16777215</height>
</size>
</property>
<widget class="QToolButton" name="elseActionRemove">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.elseActionRemoveButton</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_138">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>5</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="Line" name="line_50">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_139">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>5</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="elseActionTop">
<property name="maximumSize">
<size>
<width>22</width>
<height>16777215</height>
</size>
</property>
<widget class="QToolButton" name="elseActionTop">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.elseActionTopButton</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="elseActionUp">
<property name="maximumSize">
<size>
<width>22</width>
<height>16777215</height>
</size>
</property>
<widget class="QToolButton" name="elseActionUp">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.elseActionUpButton</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="themeID" stdset="0">
<string notr="true">upArrowIconSmall</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="elseActionDown">
<property name="maximumSize">
<size>
<width>22</width>
<height>16777215</height>
</size>
</property>
<widget class="QToolButton" name="elseActionDown">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.elseActionDownButton</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="themeID" stdset="0">
<string notr="true">downArrowIconSmall</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="elseActionBottom">
<property name="maximumSize">
<size>
<width>22</width>
<height>16777215</height>
</size>
</property>
<widget class="QToolButton" name="elseActionBottom">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.elseActionBottomButton</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_22">
<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>

View File

@@ -70,6 +70,9 @@ static void DisplayMissingDependencyWarning()
QString warning(obs_module_text(
"AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailureMessage"));
DisplayMessage(warning.arg(failedLibsString));
// Only display the warning once per plugin load
switcher->loadFailureLibs.clear();
}
static void DisplayMissingDataDirWarning()

View File

@@ -49,15 +49,9 @@ void MacroInputVariables::Load(obs_data_t *obj)
void MacroInputVariables::SetValues(const StringList &values)
{
if (_inputVariables.size() != (size_t)values.size()) {
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
blog(LOG_WARNING,
"%s - sizes do not match (variables: %ld, values %d)",
__func__, _inputVariables.size(), values.size());
#else
blog(LOG_WARNING,
"%s - sizes do not match (variables: %ld, values %lld)",
__func__, _inputVariables.size(), values.size());
#endif
"%s - sizes do not match (variables: %d, values %d)",
__func__, (int)_inputVariables.size(), (int)values.size());
}
for (size_t index = 0;

View File

@@ -40,9 +40,12 @@ void MacroList::SetContent(const std::vector<MacroRef> &macros)
UpdateListSize();
}
void MacroList::AddControl(QWidget *widget)
QAction *MacroList::AddControl(QWidget *widget, bool addSeperator)
{
_controlsLayout->insertWidget(_controlsLayout->count() - 1, widget);
if (addSeperator) {
_controls->addSeparator();
}
return _controls->addWidget(widget);
}
int MacroList::CurrentRow()

View File

@@ -9,7 +9,7 @@ class ADVSS_EXPORT MacroList final : public ListEditor {
public:
MacroList(QWidget *parent, bool allowDuplicates, bool reorder);
void SetContent(const std::vector<MacroRef> &);
void AddControl(QWidget *);
QAction *AddControl(QWidget *, bool addSeperator = true);
int CurrentRow();
private slots:

View File

@@ -19,11 +19,13 @@ MacroSelection::MacroSelection(QWidget *parent)
addItem(QString::fromStdString(m->Name()));
}
QWidget::connect(parent, SIGNAL(MacroAdded(const QString &)), this,
QWidget::connect(GetSettingsWindow(),
SIGNAL(MacroAdded(const QString &)), this,
SLOT(MacroAdd(const QString &)));
QWidget::connect(parent, SIGNAL(MacroRemoved(const QString &)), this,
QWidget::connect(GetSettingsWindow(),
SIGNAL(MacroRemoved(const QString &)), this,
SLOT(MacroRemove(const QString &)));
QWidget::connect(parent,
QWidget::connect(GetSettingsWindow(),
SIGNAL(MacroRenamed(const QString &, const QString &)),
this,
SLOT(MacroRename(const QString &, const QString &)));

View File

@@ -1,5 +1,6 @@
#include "advanced-scene-switcher.hpp"
#include "action-queue.hpp"
#include "cursor-shape-changer.hpp"
#include "macro-action-edit.hpp"
#include "macro-condition-edit.hpp"
#include "macro-export-import-dialog.hpp"
@@ -774,10 +775,12 @@ void AdvSceneSwitcher::on_macroSettings_clicked()
static void moveControlsToSplitter(QSplitter *splitter, int idx,
QLayoutItem *item)
{
static int splitterHandleWidth = 38;
static int splitterHandleWidth = 32;
auto handle = splitter->handle(idx);
auto layout = item->layout();
layout->setContentsMargins(7, 7, 7, 7);
int leftMargin, rightMargin;
layout->getContentsMargins(&leftMargin, nullptr, &rightMargin, nullptr);
layout->setContentsMargins(leftMargin, 0, rightMargin, 0);
handle->setLayout(layout);
splitter->setHandleWidth(splitterHandleWidth);
splitter->setStyleSheet("QSplitter::handle {background: transparent;}");
@@ -827,6 +830,32 @@ static void runSegmentHighligtChecks(AdvSceneSwitcher *ss)
}
}
static QToolBar *
setupToolBar(const std::initializer_list<std::initializer_list<QWidget *>>
&widgetGroups)
{
auto toolbar = new QToolBar();
toolbar->setIconSize({16, 16});
QAction *lastSeperator = nullptr;
for (const auto &widgetGroup : widgetGroups) {
for (const auto &widget : widgetGroup) {
toolbar->addWidget(widget);
}
lastSeperator = toolbar->addSeparator();
}
if (lastSeperator) {
toolbar->removeAction(lastSeperator);
}
// Prevent "extension" button from showing up
toolbar->setSizePolicy(QSizePolicy::MinimumExpanding,
QSizePolicy::Preferred);
return toolbar;
}
void AdvSceneSwitcher::SetupMacroTab()
{
ui->macroElseActions->installEventFilter(this);
@@ -835,6 +864,11 @@ void AdvSceneSwitcher::SetupMacroTab()
if (GetMacros().size() == 0 && !switcher->disableHints) {
addPulse = HighlightWidget(ui->macroAdd, QColor(Qt::green));
}
auto macroControls = setupToolBar({{ui->macroAdd, ui->macroRemove},
{ui->macroUp, ui->macroDown}});
ui->macroControlLayout->addWidget(macroControls);
ui->macros->Reset(GetMacros(),
GetGlobalMacroSettings()._highlightExecuted);
connect(ui->macros, SIGNAL(MacroSelectionAboutToChange()), this,
@@ -886,13 +920,7 @@ void AdvSceneSwitcher::SetupMacroTab()
SLOT(HighlightOnChange()));
onChangeHighlightTimer.start();
// Move condition controls into splitter handle layout
moveControlsToSplitter(ui->macroActionConditionSplitter, 1,
ui->macroConditionsLayout->takeAt(1));
moveControlsToSplitter(ui->macroElseActionSplitter, 1,
ui->macroActionsLayout->takeAt(1));
// Set action and condition control icons
// Set action and condition toolbars
const std::string pathPrefix =
GetDataFilePath("res/images/" + GetThemeTypeName());
SetButtonIcon(ui->actionTop, (pathPrefix + "DoubleUp.svg").c_str());
@@ -907,6 +935,44 @@ void AdvSceneSwitcher::SetupMacroTab()
SetButtonIcon(ui->toggleElseActions,
(pathPrefix + "NotEqual.svg").c_str());
auto conditionToolbar =
setupToolBar({{ui->conditionAdd, ui->conditionRemove},
{ui->conditionTop, ui->conditionUp,
ui->conditionDown, ui->conditionBottom}});
auto actionToolbar = setupToolBar({{ui->actionAdd, ui->actionRemove},
{ui->actionTop, ui->actionUp,
ui->actionDown, ui->actionBottom}});
auto elseActionToolbar =
setupToolBar({{ui->elseActionAdd, ui->elseActionRemove},
{ui->elseActionTop, ui->elseActionUp,
ui->elseActionDown, ui->elseActionBottom}});
ui->conditionControlsLayout->addWidget(conditionToolbar);
ui->actionControlsLayout->insertWidget(0, actionToolbar);
ui->elseActionControlsLayout->addWidget(elseActionToolbar);
// Move condition controls into splitter handle layout
moveControlsToSplitter(ui->macroActionConditionSplitter, 1,
ui->macroConditionsLayout->takeAt(1));
moveControlsToSplitter(ui->macroElseActionSplitter, 1,
ui->macroActionsLayout->takeAt(1));
// Override splitter cursor icon when hovering over controls in splitter
SetCursorOnWidgetHover(ui->conditionAdd, Qt::CursorShape::ArrowCursor);
SetCursorOnWidgetHover(ui->conditionRemove,
Qt::CursorShape::ArrowCursor);
SetCursorOnWidgetHover(ui->conditionTop, Qt::CursorShape::ArrowCursor);
SetCursorOnWidgetHover(ui->conditionUp, Qt::CursorShape::ArrowCursor);
SetCursorOnWidgetHover(ui->conditionDown, Qt::CursorShape::ArrowCursor);
SetCursorOnWidgetHover(ui->conditionBottom,
Qt::CursorShape::ArrowCursor);
SetCursorOnWidgetHover(ui->actionAdd, Qt::CursorShape::ArrowCursor);
SetCursorOnWidgetHover(ui->actionRemove, Qt::CursorShape::ArrowCursor);
SetCursorOnWidgetHover(ui->actionTop, Qt::CursorShape::ArrowCursor);
SetCursorOnWidgetHover(ui->actionUp, Qt::CursorShape::ArrowCursor);
SetCursorOnWidgetHover(ui->actionDown, Qt::CursorShape::ArrowCursor);
SetCursorOnWidgetHover(ui->actionBottom, Qt::CursorShape::ArrowCursor);
// Reserve more space for macro edit area than for the macro list
ui->macroListMacroEditSplitter->setStretchFactor(0, 1);
ui->macroListMacroEditSplitter->setStretchFactor(1, 4);
@@ -1400,7 +1466,7 @@ static void fade(QWidget *widget, bool fadeOut)
animation->start(QPropertyAnimation::DeleteWhenStopped);
}
void fadeWidgets(const std::vector<QWidget *> &widgets, bool fadeOut)
static void fadeWidgets(const std::vector<QWidget *> &widgets, bool fadeOut)
{
for (const auto &widget : widgets) {
fade(widget, fadeOut);

View File

@@ -0,0 +1,34 @@
#include "cursor-shape-changer.hpp"
#include <QCursor>
#include <QEvent>
#include <QGuiApplication>
namespace advss {
CursorShapeChanger::CursorShapeChanger(QObject *parent, Qt::CursorShape shape)
: QObject(parent),
_shape(shape)
{
}
bool CursorShapeChanger::eventFilter(QObject *o, QEvent *e)
{
if ((e->type() == QEvent::HoverEnter || e->type() == QEvent::Enter) &&
!_overrideActive) {
_overrideActive = true;
QGuiApplication::setOverrideCursor(QCursor(_shape));
} else if (_overrideActive && e->type() == QEvent::Leave) {
_overrideActive = false;
QGuiApplication::restoreOverrideCursor();
}
return QObject::eventFilter(o, e);
}
void SetCursorOnWidgetHover(QWidget *widget, Qt::CursorShape shape)
{
widget->installEventFilter(new CursorShapeChanger(widget, shape));
}
} // namespace advss

View File

@@ -0,0 +1,20 @@
#pragma once
#include <QWidget>
namespace advss {
class CursorShapeChanger : public QObject {
public:
CursorShapeChanger(QObject *, Qt::CursorShape shape);
protected:
bool eventFilter(QObject *o, QEvent *e) override;
private:
Qt::CursorShape _shape;
std::atomic_bool _overrideActive = {false};
};
void SetCursorOnWidgetHover(QWidget *widget, Qt::CursorShape shape);
} // namespace advss

View File

@@ -0,0 +1,44 @@
#include "list-controls.hpp"
#include "obs-module-helper.hpp"
#include "ui-helpers.hpp"
#include <QLayout>
#include <QToolButton>
namespace advss {
ListControls::ListControls(QWidget *parent, bool reorder) : QToolBar(parent)
{
setObjectName("listControls");
setStyleSheet("#listControls { background-color: transparent; }");
setIconSize({16, 16});
AddActionHelper("addIconSmall", "AdvSceneSwitcher.listControls.add",
[this]() { Add(); });
AddActionHelper("removeIconSmall",
"AdvSceneSwitcher.listControls.remove",
[this]() { Remove(); });
if (reorder) {
addSeparator();
AddActionHelper("upArrowIconSmall",
"AdvSceneSwitcher.listControls.up",
[this]() { Up(); });
AddActionHelper("downArrowIconSmall",
"AdvSceneSwitcher.listControls.down",
[this]() { Down(); });
}
}
void ListControls::AddActionHelper(const char *theme, const char *tooltip,
const std::function<void()> &signal)
{
auto button = new QToolButton(this);
button->setToolTip(obs_module_text(tooltip));
button->setProperty("themeID", QVariant(QString(theme)));
(void)addWidget(button);
button->connect(button, &QToolButton::clicked, this, signal);
}
} // namespace advss

View File

@@ -0,0 +1,25 @@
#pragma once
#include "export-symbol-helper.hpp"
#include <QToolBar>
namespace advss {
class ADVSS_EXPORT ListControls final : public QToolBar {
Q_OBJECT
public:
ListControls(QWidget *parent = nullptr, bool reorder = true);
signals:
void Add();
void Remove();
void Up();
void Down();
private:
void AddActionHelper(const char *theme, const char *tooltip,
const std::function<void()> &signal);
};
} // namespace advss

View File

@@ -6,53 +6,19 @@ namespace advss {
ListEditor::ListEditor(QWidget *parent, bool reorder)
: QWidget(parent),
_list(new QListWidget()),
_add(new QPushButton()),
_remove(new QPushButton()),
_up(new QPushButton()),
_down(new QPushButton()),
_controlsLayout(new QHBoxLayout()),
_controls(new ListControls(this, reorder)),
_mainLayout(new QVBoxLayout())
{
_add->setMaximumWidth(22);
_add->setProperty("themeID",
QVariant(QString::fromUtf8("addIconSmall")));
_add->setFlat(true);
_remove->setMaximumWidth(22);
_remove->setProperty("themeID",
QVariant(QString::fromUtf8("removeIconSmall")));
_remove->setFlat(true);
_up->setMaximumWidth(22);
_up->setProperty("themeID",
QVariant(QString::fromUtf8("upArrowIconSmall")));
_up->setFlat(true);
_down->setMaximumWidth(22);
_down->setProperty("themeID",
QVariant(QString::fromUtf8("downArrowIconSmall")));
_down->setFlat(true);
QWidget::connect(_add, SIGNAL(clicked()), this, SLOT(Add()));
QWidget::connect(_remove, SIGNAL(clicked()), this, SLOT(Remove()));
QWidget::connect(_up, SIGNAL(clicked()), this, SLOT(Up()));
QWidget::connect(_down, SIGNAL(clicked()), this, SLOT(Down()));
QWidget::connect(_controls, SIGNAL(Add()), this, SLOT(Add()));
QWidget::connect(_controls, SIGNAL(Remove()), this, SLOT(Remove()));
QWidget::connect(_controls, SIGNAL(Up()), this, SLOT(Up()));
QWidget::connect(_controls, SIGNAL(Down()), this, SLOT(Down()));
QWidget::connect(_list, SIGNAL(itemDoubleClicked(QListWidgetItem *)),
this, SLOT(Clicked(QListWidgetItem *)));
_controlsLayout->setContentsMargins(0, 0, 0, 0);
_controlsLayout->addWidget(_add);
_controlsLayout->addWidget(_remove);
if (reorder) {
auto line = new QFrame();
line->setFrameShape(QFrame::VLine);
line->setFrameShadow(QFrame::Sunken);
_controlsLayout->addWidget(line);
_controlsLayout->addWidget(_up);
_controlsLayout->addWidget(_down);
}
_controlsLayout->addStretch();
_mainLayout->setContentsMargins(0, 0, 0, 0);
_mainLayout->addWidget(_list);
_mainLayout->addLayout(_controlsLayout);
_mainLayout->addWidget(_controls);
setLayout(_mainLayout);
}

View File

@@ -1,7 +1,7 @@
#pragma once
#include "export-symbol-helper.hpp"
#include "list-controls.hpp"
#include <QPushButton>
#include <QListWidget>
#include <QLayout>
@@ -27,11 +27,7 @@ protected:
void UpdateListSize();
QListWidget *_list;
QPushButton *_add;
QPushButton *_remove;
QPushButton *_up;
QPushButton *_down;
QHBoxLayout *_controlsLayout;
ListControls *_controls;
QVBoxLayout *_mainLayout;
};

View File

@@ -88,12 +88,10 @@ RegexConfig RegexConfig::PartialMatchRegexConfig()
RegexConfigWidget::RegexConfigWidget(QWidget *parent, bool showEnable)
: QWidget(parent),
_openSettings(new QPushButton()),
_openSettings(new QToolButton()),
_enable(new QPushButton())
{
_openSettings->setMaximumWidth(22);
SetButtonIcon(_openSettings, ":/settings/images/settings/general.svg");
_openSettings->setFlat(true);
_openSettings->setToolTip(
obs_module_text("AdvSceneSwitcher.regex.configure"));

View File

@@ -2,12 +2,13 @@
#include "export-symbol-helper.hpp"
#include <obs-data.h>
#include <QWidget>
#include <QCheckBox>
#include <QPushButton>
#include <QDialog>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QRegularExpression>
#include <QToolButton>
#include <QWidget>
namespace advss {
@@ -63,7 +64,7 @@ signals:
private:
void SetVisibility();
QPushButton *_openSettings;
QToolButton *_openSettings;
QPushButton *_enable;
RegexConfig _config;
};

View File

@@ -15,20 +15,16 @@ ResourceTable::ResourceTable(QTabWidget *parent, const QString &help,
const std::function<void()> &openSettings)
: QWidget(parent),
_table(new QTableWidget()),
_add(new QPushButton()),
_remove(new QPushButton()),
_add(new QToolButton()),
_remove(new QToolButton()),
_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);

View File

@@ -2,8 +2,8 @@
#include "export-symbol-helper.hpp"
#include <QLabel>
#include <QPushButton>
#include <QString>
#include <QToolButton>
#include <QTableWidget>
class QResizeEvent;
@@ -33,8 +33,8 @@ protected:
private:
QTableWidget *_table;
QPushButton *_add;
QPushButton *_remove;
QToolButton *_add;
QToolButton *_remove;
QLabel *_help;
QObject *_highlightConnection = nullptr;

View File

@@ -131,7 +131,9 @@ void StatusControl::SetStarted()
_button->setText(
obs_module_text("AdvSceneSwitcher.generalTab.status.stop"));
_status->setText(obs_module_text("AdvSceneSwitcher.status.active"));
_pulse->deleteLater();
if (_pulse) {
_pulse->deleteLater();
}
SetStatusStyleSheet(false);
_setToStopped = false;
}

View File

@@ -182,6 +182,10 @@ void TempVariableRef::Load(obs_data_t *obj, Macro *macro, const char *name)
void TempVariableRef::PostLoad(int idx, SegmentType type, Macro *macro)
{
if (!macro) {
return;
}
std::deque<std::shared_ptr<MacroSegment>> segments;
switch (type) {
case SegmentType::NONE:
@@ -458,7 +462,7 @@ void AutoUpdateTooltipLabel::enterEvent(QEnterEvent *event)
void AutoUpdateTooltipLabel::enterEvent(QEvent *event)
#endif
{
_timer->start(1000);
_timer->start(300);
QLabel::enterEvent(event);
}

View File

@@ -70,7 +70,7 @@ void SetHeightToContentHeight(QListWidget *list)
list->setMaximumHeight(height);
}
void SetButtonIcon(QPushButton *button, const char *path)
void SetButtonIcon(QAbstractButton *button, const char *path)
{
QIcon icon;
icon.addFile(QString::fromUtf8(path), QSize(), QIcon::Normal,

View File

@@ -1,11 +1,11 @@
#pragma once
#include "export-symbol-helper.hpp"
#include <QAbstractButton>
#include <QColor>
#include <QComboBox>
#include <QIcon>
#include <QListWidget>
#include <QPushButton>
#include <QString>
#include <QWidget>
#include <string>
@@ -19,7 +19,7 @@ EXPORT QObject *HighlightWidget(QWidget *widget, QColor startColor,
bool once = false);
EXPORT void SetHeightToContentHeight(QListWidget *list);
EXPORT void SetButtonIcon(QPushButton *button, const char *path);
EXPORT void SetButtonIcon(QAbstractButton *button, const char *path);
EXPORT int
FindIdxInRagne(QComboBox *list, int start, int stop, const std::string &value,
Qt::MatchFlags = Qt::MatchExactly | Qt::MatchCaseSensitive);

View File

@@ -31,4 +31,5 @@ install_advss_plugin_dependency(...)
add_plugin(midi)
add_plugin(openvr)
add_plugin(twitch)
add_plugin(usb)
add_plugin(video)

View File

@@ -166,6 +166,8 @@ target_sources(
utils/source-properties-button.hpp
utils/source-settings-helpers.cpp
utils/source-settings-helpers.hpp
utils/source-interact.cpp
utils/source-interact.hpp
utils/source-setting.cpp
utils/source-setting.hpp
utils/striped-frame.cpp

View File

@@ -60,8 +60,13 @@ bool MacroActionProjector::PerformAction()
}
if (_fullscreen && _monitor == -1) {
blog(LOG_INFO, "refusing to open fullscreen projector"
" with invalid display selection");
blog(LOG_INFO, "refusing to open fullscreen projector "
"with invalid display selection");
return true;
}
if (_fullscreen && MonitorSetupChanged()) {
blog(LOG_INFO, "refusing to open fullscreen projector - "
"monitor setup seems to have changed!");
return true;
}
@@ -109,13 +114,6 @@ bool MacroActionProjector::Load(obs_data_t *obj)
_fullscreen = obs_data_get_bool(obj, "fullscreen");
_scene.Load(obj);
_source.Load(obj);
if (MonitorSetupChanged()) {
blog(LOG_INFO, "monitor setup seems to have changed! "
"resetting projector action monitor selection!");
_monitor = -1;
}
return true;
}
@@ -149,10 +147,13 @@ void MacroActionProjector::SetMonitor(int idx)
int MacroActionProjector::GetMonitor() const
{
if (MonitorSetupChanged()) {
return -1;
}
return _monitor;
}
bool MacroActionProjector::MonitorSetupChanged()
bool MacroActionProjector::MonitorSetupChanged() const
{
if (_monitorName.empty()) {
return false;
@@ -248,41 +249,25 @@ void MacroActionProjectorEdit::UpdateEntryData()
void MacroActionProjectorEdit::SceneChanged(const SceneSelection &s)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_scene = s;
}
void MacroActionProjectorEdit::SourceChanged(const SourceSelection &source)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_source = source;
}
void MacroActionProjectorEdit::MonitorChanged(int value)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->SetMonitor(value);
}
void MacroActionProjectorEdit::WindowTypeChanged(int)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_fullscreen =
_windowTypes->currentText() ==
obs_module_text("AdvSceneSwitcher.action.projector.fullscreen");
@@ -291,11 +276,7 @@ void MacroActionProjectorEdit::WindowTypeChanged(int)
void MacroActionProjectorEdit::TypeChanged(int value)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_type = static_cast<MacroActionProjector::Type>(value);
SetWidgetVisibility();
}

View File

@@ -33,7 +33,7 @@ public:
bool _fullscreen = true;
private:
bool MonitorSetupChanged();
bool MonitorSetupChanged() const;
int _monitor = -1;
// Only used to detect display setup changes
@@ -66,17 +66,17 @@ private slots:
void SourceChanged(const SourceSelection &);
void MonitorChanged(int value);
protected:
private:
void SetWidgetVisibility();
QComboBox *_windowTypes;
QComboBox *_types;
SceneSelectionWidget *_scenes;
SourceSelectionWidget *_sources;
QHBoxLayout *_monitorSelection;
QComboBox *_monitors;
std::shared_ptr<MacroActionProjector> _entryData;
private:
void SetWidgetVisibility();
std::shared_ptr<MacroActionProjector> _entryData;
bool _loading = true;
};

View File

@@ -2,6 +2,7 @@
#include "layout-helpers.hpp"
#include <obs-frontend-api.h>
#include <util/config-file.h>
namespace advss {
@@ -12,31 +13,49 @@ bool MacroActionReplayBuffer::_registered = MacroActionFactory::Register(
{MacroActionReplayBuffer::Create, MacroActionReplayBufferEdit::Create,
"AdvSceneSwitcher.action.replay"});
static const std::map<ReplayBufferAction, std::string> actionTypes = {
{ReplayBufferAction::STOP, "AdvSceneSwitcher.action.replay.type.stop"},
{ReplayBufferAction::START,
"AdvSceneSwitcher.action.replay.type.start"},
{ReplayBufferAction::SAVE, "AdvSceneSwitcher.action.replay.type.save"},
static const std::map<MacroActionReplayBuffer::Action, std::string>
actionTypes = {
{MacroActionReplayBuffer::Action::STOP,
"AdvSceneSwitcher.action.replay.type.stop"},
{MacroActionReplayBuffer::Action::START,
"AdvSceneSwitcher.action.replay.type.start"},
{MacroActionReplayBuffer::Action::SAVE,
"AdvSceneSwitcher.action.replay.type.save"},
{MacroActionReplayBuffer::Action::DURATION,
"AdvSceneSwitcher.action.replay.type.duration"},
};
bool MacroActionReplayBuffer::PerformAction()
{
switch (_action) {
case ReplayBufferAction::STOP:
case Action::STOP:
if (obs_frontend_replay_buffer_active()) {
obs_frontend_replay_buffer_stop();
}
break;
case ReplayBufferAction::START:
case Action::START:
if (!obs_frontend_replay_buffer_active()) {
obs_frontend_replay_buffer_start();
}
break;
case ReplayBufferAction::SAVE:
case Action::SAVE:
if (obs_frontend_replay_buffer_active()) {
obs_frontend_replay_buffer_save();
}
break;
case Action::DURATION: {
auto conf = obs_frontend_get_profile_config();
auto value = std::to_string(_duration.Seconds());
config_set_string(conf, "SimpleOutput", "RecRBTime",
value.c_str());
config_set_string(conf, "AdvOut", "RecRBTime", value.c_str());
if (config_save(conf) != CONFIG_SUCCESS) {
blog(LOG_WARNING,
"failed to set replay buffer duration");
}
break;
}
default:
break;
}
@@ -58,14 +77,15 @@ bool MacroActionReplayBuffer::Save(obs_data_t *obj) const
{
MacroAction::Save(obj);
obs_data_set_int(obj, "action", static_cast<int>(_action));
_duration.Save(obj);
return true;
}
bool MacroActionReplayBuffer::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_action = static_cast<ReplayBufferAction>(
obs_data_get_int(obj, "action"));
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
_duration.Load(obj);
return true;
}
@@ -90,23 +110,24 @@ MacroActionReplayBufferEdit::MacroActionReplayBufferEdit(
QWidget *parent, std::shared_ptr<MacroActionReplayBuffer> entryData)
: QWidget(parent),
_actions(new QComboBox()),
_saveWarning(new QLabel(
obs_module_text("AdvSceneSwitcher.action.replay.saveWarn")))
_warning(new QLabel(
obs_module_text("AdvSceneSwitcher.action.replay.saveWarn"))),
_duration(new DurationSelection(this, false, 5.))
{
populateActionSelection(_actions);
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QWidget::connect(_duration, SIGNAL(DurationChanged(const Duration &)),
this, SLOT(DurationChanged(const Duration &)));
QHBoxLayout *layout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{actions}}", _actions},
};
auto layout = new QHBoxLayout;
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.replay.entry"),
layout, widgetPlaceholders);
layout,
{{"{{actions}}", _actions}, {"{{duration}}", _duration}});
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(layout);
mainLayout->addWidget(_saveWarning);
mainLayout->addWidget(_warning);
setLayout(mainLayout);
_entryData = entryData;
@@ -120,21 +141,41 @@ void MacroActionReplayBufferEdit::UpdateEntryData()
return;
}
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_saveWarning->setVisible(_entryData->_action ==
ReplayBufferAction::SAVE);
_duration->SetDuration(_entryData->_duration);
SetWidgetVisiblity();
}
void MacroActionReplayBufferEdit::SetWidgetVisiblity()
{
_warning->setVisible(_entryData->_action ==
MacroActionReplayBuffer::Action::SAVE);
_duration->setVisible(_entryData->_action ==
MacroActionReplayBuffer::Action::DURATION);
_warning->setText(obs_module_text(
(_entryData->_action == MacroActionReplayBuffer::Action::SAVE)
? "AdvSceneSwitcher.action.replay.saveWarn"
: "AdvSceneSwitcher.action.replay.durationWarn"));
_warning->setVisible(_entryData->_action ==
MacroActionReplayBuffer::Action::SAVE ||
_entryData->_action ==
MacroActionReplayBuffer::Action::DURATION);
adjustSize();
updateGeometry();
}
void MacroActionReplayBufferEdit::ActionChanged(int value)
{
if (_loading || !_entryData) {
return;
}
GUARD_LOADING_AND_LOCK();
_entryData->_action =
static_cast<MacroActionReplayBuffer::Action>(value);
auto lock = LockContext();
_entryData->_action = static_cast<ReplayBufferAction>(value);
_saveWarning->setVisible(_entryData->_action ==
ReplayBufferAction::SAVE);
adjustSize();
SetWidgetVisiblity();
}
void MacroActionReplayBufferEdit::DurationChanged(const Duration &duration)
{
GUARD_LOADING_AND_LOCK();
_entryData->_duration = duration;
}
} // namespace advss

View File

@@ -6,12 +6,6 @@
namespace advss {
enum class ReplayBufferAction {
STOP,
START,
SAVE,
};
class MacroActionReplayBuffer : public MacroAction {
public:
MacroActionReplayBuffer(Macro *m) : MacroAction(m) {}
@@ -23,7 +17,14 @@ public:
static std::shared_ptr<MacroAction> Create(Macro *m);
std::shared_ptr<MacroAction> Copy() const;
ReplayBufferAction _action = ReplayBufferAction::STOP;
enum class Action {
STOP,
START,
SAVE,
DURATION,
};
Action _action = Action::STOP;
Duration _duration;
private:
static bool _registered;
@@ -49,13 +50,16 @@ public:
private slots:
void ActionChanged(int value);
protected:
QComboBox *_actions;
QLabel *_saveWarning;
std::shared_ptr<MacroActionReplayBuffer> _entryData;
void DurationChanged(const Duration &);
private:
void SetWidgetVisiblity();
QComboBox *_actions;
QLabel *_warning;
DurationSelection *_duration;
std::shared_ptr<MacroActionReplayBuffer> _entryData;
bool _loading = true;
};

View File

@@ -197,11 +197,7 @@ MacroActionSequenceEdit::MacroActionSequenceEdit(
_resetIndex->setMinimum(1);
auto line = new QFrame();
line->setFrameShape(QFrame::VLine);
line->setFrameShadow(QFrame::Sunken);
_macroList->AddControl(line);
_macroList->AddControl(_continueFrom);
(void)_macroList->AddControl(_continueFrom);
QWidget::connect(_macroList, SIGNAL(Added(const std::string &)), this,
SLOT(Add(const std::string &)));

View File

@@ -36,6 +36,8 @@ const static std::map<MacroActionSource::Action, std::string> actionTypes = {
"AdvSceneSwitcher.action.source.type.openFilterDialog"},
{MacroActionSource::Action::OPEN_PROPERTIES_DIALOG,
"AdvSceneSwitcher.action.source.type.openPropertiesDialog"},
{MacroActionSource::Action::INTERACT,
"AdvSceneSwitcher.action.source.type.interact"},
};
const static std::map<obs_deinterlace_mode, std::string> deinterlaceModes = {
@@ -142,6 +144,9 @@ bool MacroActionSource::PerformAction()
case Action::SETTINGS_BUTTON:
PressSourceButton(_button, s);
break;
case Action::INTERACT:
_interaction.SendToSource(s);
break;
case Action::DEINTERLACE_MODE:
obs_source_set_deinterlace_mode(s, _deinterlaceMode);
break;
@@ -199,6 +204,7 @@ bool MacroActionSource::Save(obs_data_t *obj) const
static_cast<int>(_deinterlaceMode));
obs_data_set_int(obj, "deinterlaceOrder",
static_cast<int>(_deinterlaceOrder));
_interaction.Save(obj);
return true;
}
@@ -223,6 +229,7 @@ bool MacroActionSource::Load(obs_data_t *obj)
obs_data_get_int(obj, "deinterlaceMode"));
_deinterlaceOrder = static_cast<obs_deinterlace_field_order>(
obs_data_get_int(obj, "deinterlaceOrder"));
_interaction.Load(obj);
return true;
}
@@ -309,7 +316,8 @@ MacroActionSourceEdit::MacroActionSourceEdit(
_warning(new QLabel(
obs_module_text("AdvSceneSwitcher.action.source.warning"))),
_refreshSettingSelection(new QPushButton(
obs_module_text("AdvSceneSwitcher.action.source.refresh")))
obs_module_text("AdvSceneSwitcher.action.source.refresh"))),
_interaction(new SourceInteractionWidget())
{
populateActionSelection(_actions);
auto sources = GetSourceNames();
@@ -349,6 +357,13 @@ MacroActionSourceEdit::MacroActionSourceEdit(
SLOT(SelectionChanged(const SourceSetting &)));
QWidget::connect(_refreshSettingSelection, SIGNAL(clicked()), this,
SLOT(RefreshVariableSourceSelectionValue()));
QWidget::connect(
_interaction,
SIGNAL(SettingsChanged(SourceInteractionInstance *)), this,
SLOT(InteractionSettingsChanged(SourceInteractionInstance *)));
QWidget::connect(_interaction,
SIGNAL(TypeChanged(SourceInteraction::Type)), this,
SLOT(InteractionTypeChanged(SourceInteraction::Type)));
auto entryLayout = new QHBoxLayout;
entryLayout->setContentsMargins(0, 0, 0, 0);
@@ -381,6 +396,7 @@ MacroActionSourceEdit::MacroActionSourceEdit(
buttonLayout->addWidget(_getSettings);
buttonLayout->addStretch();
mainLayout->addLayout(buttonLayout);
mainLayout->addWidget(_interaction);
setLayout(mainLayout);
_entryData = entryData;
@@ -411,6 +427,7 @@ void MacroActionSourceEdit::UpdateEntryData()
static_cast<int>(_entryData->_settingsInputMethod)));
_tempVars->SetVariable(_entryData->_tempVar);
_manualSettingValue->setPlainText(_entryData->_manualSettingValue);
_interaction->SetSourceInteractionSelection(_entryData->_interaction);
SetWidgetVisibility();
}
@@ -561,6 +578,29 @@ void MacroActionSourceEdit::ManualSettingsValueChanged()
updateGeometry();
}
void MacroActionSourceEdit::InteractionTypeChanged(SourceInteraction::Type value)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_interaction.SetType(value);
adjustSize();
updateGeometry();
}
void MacroActionSourceEdit::InteractionSettingsChanged(
SourceInteractionInstance *value)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_interaction.SetSettings(value);
}
void MacroActionSourceEdit::RefreshVariableSourceSelectionValue()
{
_sourceSettings->SetSource(_entryData->_source.GetSource());
@@ -617,6 +657,8 @@ void MacroActionSourceEdit::SetWidgetVisibility()
_entryData->_source.GetType() ==
SourceSelection::Type::VARIABLE);
_interaction->setVisible(_entryData->_action ==
MacroActionSource::Action::INTERACT);
adjustSize();
updateGeometry();
}

View File

@@ -1,6 +1,7 @@
#pragma once
#include "macro-action-edit.hpp"
#include "variable-text-edit.hpp"
#include "source-interact.hpp"
#include "source-properties-button.hpp"
#include "source-selection.hpp"
#include "source-setting.hpp"
@@ -34,6 +35,7 @@ public:
OBS_DEINTERLACE_FIELD_ORDER_TOP;
TempVariableRef _tempVar;
SourceSetting _setting;
SourceInteraction _interaction;
enum class Action {
ENABLE,
@@ -46,6 +48,7 @@ public:
OPEN_INTERACTION_DIALOG,
OPEN_FILTER_DIALOG,
OPEN_PROPERTIES_DIALOG,
INTERACT,
};
Action _action = Action::SETTINGS;
@@ -92,6 +95,8 @@ private slots:
void ManualSettingsValueChanged();
void RefreshVariableSourceSelectionValue();
void InteractionTypeChanged(SourceInteraction::Type value);
void InteractionSettingsChanged(SourceInteractionInstance *value);
signals:
void HeaderInfoChanged(const QString &);
@@ -112,6 +117,7 @@ private:
QComboBox *_deinterlaceOrder;
QLabel *_warning;
QPushButton *_refreshSettingSelection;
SourceInteractionWidget *_interaction;
std::shared_ptr<MacroActionSource> _entryData;
bool _loading = true;

View File

@@ -4,6 +4,7 @@
#include <obs-frontend-api.h>
#include <QListView>
#include <util/config-file.h>
namespace advss {
@@ -64,7 +65,7 @@ MacroConditionStats::~MacroConditionStats()
os_cpu_usage_info_destroy(_cpu_info);
}
bool MacroConditionStats::CheckFPS()
bool MacroConditionStats::CheckFPS() const
{
switch (_condition) {
case Condition::ABOVE:
@@ -79,7 +80,7 @@ bool MacroConditionStats::CheckFPS()
return false;
}
bool MacroConditionStats::CheckCPU()
bool MacroConditionStats::CheckCPU() const
{
double usage = os_cpu_usage_info_query(_cpu_info);
@@ -96,7 +97,7 @@ bool MacroConditionStats::CheckCPU()
return false;
}
bool MacroConditionStats::CheckMemory()
bool MacroConditionStats::CheckMemory() const
{
auto rss =
(long double)os_get_proc_resident_size() / (1024.0l * 1024.0l);
@@ -114,7 +115,7 @@ bool MacroConditionStats::CheckMemory()
return false;
}
bool MacroConditionStats::CheckAvgFrametime()
bool MacroConditionStats::CheckAvgFrametime() const
{
auto num = (long double)obs_get_average_frame_time_ns() / 1000000.0l;
@@ -266,7 +267,7 @@ bool MacroConditionStats::CheckStreamBitrate()
return false;
}
bool MacroConditionStats::CheckStreamMBSent()
bool MacroConditionStats::CheckStreamMBSent() const
{
OBSOutputAutoRelease output = obs_frontend_get_streaming_output();
uint64_t totalBytes = output ? obs_output_get_total_bytes(output) : 0;
@@ -322,7 +323,7 @@ bool MacroConditionStats::CheckRecordingBitrate()
return false;
}
bool MacroConditionStats::CheckRecordingMBSent()
bool MacroConditionStats::CheckRecordingMBSent() const
{
OBSOutputAutoRelease output = obs_frontend_get_recording_output();
uint64_t totalBytes = output ? obs_output_get_total_bytes(output) : 0;
@@ -341,6 +342,53 @@ bool MacroConditionStats::CheckRecordingMBSent()
return false;
}
// Based on OBSBasic::GetCurrentOutputPath()
static const char *getCurrentOutputPath()
{
const char *path = nullptr;
auto config = obs_frontend_get_profile_config();
if (!config) {
return path;
}
const char *mode = config_get_string(config, "Output", "Mode");
if (strcmp(mode, "Advanced") == 0) {
const char *advanced_mode =
config_get_string(config, "AdvOut", "RecType");
if (strcmp(advanced_mode, "FFmpeg") == 0) {
path = config_get_string(config, "AdvOut",
"FFFilePath");
} else {
path = config_get_string(config, "AdvOut",
"RecFilePath");
}
} else {
path = config_get_string(config, "SimpleOutput", "FilePath");
}
return path;
}
bool MacroConditionStats::CheckDiskUsage() const
{
#define MBYTE (1024ULL * 1024ULL)
auto path = getCurrentOutputPath();
auto mb = os_get_free_disk_space(path) / MBYTE;
switch (_condition) {
case Condition::ABOVE:
return mb > _value;
case Condition::EQUALS:
return DoubleEquals(mb, _value, 0.1);
case Condition::BELOW:
return mb < _value;
default:
break;
}
return false;
}
bool MacroConditionStats::CheckCondition()
{
switch (_type) {
@@ -349,8 +397,7 @@ bool MacroConditionStats::CheckCondition()
case Type::CPU_USAGE:
return CheckCPU();
case Type::DISK_USAGE:
// TODO: not implemented
break;
return CheckDiskUsage();
case Type::MEM_USAGE:
return CheckMemory();
case Type::AVG_FRAMETIME:
@@ -412,24 +459,13 @@ std::string MacroConditionStats::GetShortDesc() const
return "";
}
static inline void populateStatsTypes(QComboBox *list)
template<class T>
static inline void populateList(QComboBox *list,
const std::map<T, std::string> &map)
{
list->clear();
for (auto entry : statsTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
// TODO: not implemented
if (entry.first == MacroConditionStats::Type::DISK_USAGE) {
qobject_cast<QListView *>(list->view())
->setRowHidden(list->count() - 1, true);
}
}
}
static inline void populateConditionSelection(QComboBox *list)
{
list->clear();
for (auto entry : statsConditionTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
for (const auto &[_, name] : map) {
list->addItem(obs_module_text(name.c_str()));
}
}
@@ -442,8 +478,8 @@ MacroConditionStatsEdit::MacroConditionStatsEdit(
{
_value->setMaximum(999999999999);
populateStatsTypes(_stats);
populateConditionSelection(_condition);
populateList(_stats, statsTypes);
populateList(_condition, statsConditionTypes);
setToolTip(
obs_module_text("AdvSceneSwitcher.condition.stats.dockHint"));
@@ -457,14 +493,12 @@ MacroConditionStatsEdit::MacroConditionStatsEdit(
QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
QHBoxLayout *layout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{value}}", _value},
{"{{stats}}", _stats},
{"{{condition}}", _condition},
};
auto layout = new QHBoxLayout;
PlaceWidgets(obs_module_text("AdvSceneSwitcher.condition.stats.entry"),
layout, widgetPlaceholders);
layout,
{{"{{value}}", _value},
{"{{stats}}", _stats},
{"{{condition}}", _condition}});
setLayout(layout);
_entryData = entryData;
@@ -474,22 +508,14 @@ MacroConditionStatsEdit::MacroConditionStatsEdit(
void MacroConditionStatsEdit::ValueChanged(const NumberVariable<double> &value)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_value = value;
}
void MacroConditionStatsEdit::StatsTypeChanged(int type)
{
if (_loading || !_entryData) {
return;
}
{
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_type =
static_cast<MacroConditionStats::Type>(type);
}
@@ -501,11 +527,7 @@ void MacroConditionStatsEdit::StatsTypeChanged(int type)
void MacroConditionStatsEdit::ConditionChanged(int cond)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_condition =
static_cast<MacroConditionStats::Condition>(cond);
}

View File

@@ -50,18 +50,19 @@ public:
Condition _condition = Condition::ABOVE;
private:
bool CheckFPS();
bool CheckCPU();
bool CheckMemory();
bool CheckAvgFrametime();
bool CheckFPS() const;
bool CheckCPU() const;
bool CheckMemory() const;
bool CheckAvgFrametime() const;
bool CheckRenderLag();
bool CheckEncodeLag();
bool CheckStreamDroppedFrames();
bool CheckStreamBitrate();
bool CheckStreamMBSent();
bool CheckStreamMBSent() const;
bool CheckRecordingDroppedFrames();
bool CheckRecordingBitrate();
bool CheckRecordingMBSent();
bool CheckRecordingMBSent() const;
bool CheckDiskUsage() const;
os_cpu_usage_info_t *_cpu_info = nullptr;
uint32_t _first_encoded = 0xFFFFFFFF;
@@ -109,15 +110,14 @@ private slots:
signals:
void HeaderInfoChanged(const QString &);
protected:
QComboBox *_stats;
QComboBox *_condition;
VariableDoubleSpinBox *_value;
std::shared_ptr<MacroConditionStats> _entryData;
private:
void SetWidgetVisibility();
QComboBox *_stats;
QComboBox *_condition;
VariableDoubleSpinBox *_value;
std::shared_ptr<MacroConditionStats> _entryData;
bool _loading = true;
};

View File

@@ -2,6 +2,8 @@
#include "macro-helpers.hpp"
#include "layout-helpers.hpp"
#include <QFileInfo>
namespace advss {
const std::string MacroConditionSlideshow::id = "slideshow";
@@ -50,25 +52,28 @@ bool MacroConditionSlideshow::CheckCondition()
switch (_condition) {
case MacroConditionSlideshow::Condition::SLIDE_CHANGED:
if (!_slideChanged) {
SetVariableValue("false");
SetVariableValues("false");
return false;
}
_slideChanged = false;
SetVariableValue("true");
SetVariableValues("true");
return true;
case MacroConditionSlideshow::Condition::SLIDE_INDEX:
if (_currentIndex == -1) {
SetVariableValue("-1");
SetVariableValues("-1");
return false;
}
SetVariableValue(std::to_string(_currentIndex + 1));
SetVariableValues(std::to_string(_currentIndex + 1));
return _currentIndex == _index - 1;
case MacroConditionSlideshow::Condition::SLIDE_PATH:
if (_currentPath[0] == '\0') {
SetVariableValue("");
SetVariableValues("");
return false;
}
SetVariableValue(_currentPath);
SetVariableValues(_currentPath);
if (_regex.Enabled()) {
return _regex.Matches(_currentPath, _path);
}
return std::string(_path) == std::string(_currentPath);
}
@@ -82,6 +87,7 @@ bool MacroConditionSlideshow::Save(obs_data_t *obj) const
_source.Save(obj);
_index.Save(obj, "index");
_path.Save(obj, "path");
_regex.Save(obj);
return true;
}
@@ -92,6 +98,7 @@ bool MacroConditionSlideshow::Load(obs_data_t *obj)
_source.Load(obj);
_index.Load(obj, "index");
_path.Load(obj, "path");
_regex.Load(obj);
auto s = _source.GetSource();
AddSignalHandler(s);
@@ -175,6 +182,35 @@ void MacroConditionSlideshow::Reset()
_currentPath = "";
}
void MacroConditionSlideshow::SetupTempVars()
{
MacroCondition::SetupTempVars();
AddTempvar(
"index",
obs_module_text("AdvSceneSwitcher.tempVar.slideShow.index"),
obs_module_text(
"AdvSceneSwitcher.tempVar.slideShow.index.description"));
AddTempvar(
"path",
obs_module_text("AdvSceneSwitcher.tempVar.slideShow.path"),
obs_module_text(
"AdvSceneSwitcher.tempVar.slideShow.path.description"));
AddTempvar(
"fileName",
obs_module_text("AdvSceneSwitcher.tempVar.slideShow.fileName"),
obs_module_text(
"AdvSceneSwitcher.tempVar.slideShow.fileName.description"));
}
void MacroConditionSlideshow::SetVariableValues(const std::string &value)
{
SetVariableValue(value);
SetTempVarValue("index", std::to_string(_currentIndex + 1));
SetTempVarValue("path", _currentPath ? _currentPath : "");
QFileInfo fileInfo(_currentPath ? _currentPath : "");
SetTempVarValue("fileName", fileInfo.fileName().toStdString());
}
static void populateConditionSelection(QComboBox *list)
{
for (const auto &[_, name] : conditions) {
@@ -184,11 +220,14 @@ static void populateConditionSelection(QComboBox *list)
static QStringList getSlideshowNames()
{
static constexpr std::array<std::string_view, 2> slideShowIds{
"slideshow", "slideshow_v2"};
auto sourceEnum = [](void *param, obs_source_t *source) -> bool /* -- */
{
QStringList *list = reinterpret_cast<QStringList *>(param);
std::string sourceId = obs_source_get_id(source);
if (sourceId.compare("slideshow") == 0) {
auto sourceId = obs_source_get_id(source);
if (std::find(slideShowIds.begin(), slideShowIds.end(),
sourceId) != slideShowIds.end()) {
*list << obs_source_get_name(source);
}
return true;
@@ -205,7 +244,9 @@ MacroConditionSlideshowEdit::MacroConditionSlideshowEdit(
_conditions(new QComboBox(this)),
_index(new VariableSpinBox(this)),
_path(new VariableLineEdit(this)),
_sources(new SourceSelectionWidget(this, QStringList(), true))
_sources(new SourceSelectionWidget(this, QStringList(), true)),
_regex(new RegexConfigWidget(this)),
_layout(new QHBoxLayout())
{
setToolTip(obs_module_text(
"AdvSceneSwitcher.condition.slideshow.updateIntervalTooltip"));
@@ -227,16 +268,19 @@ MacroConditionSlideshowEdit::MacroConditionSlideshowEdit(
this, SLOT(IndexChanged(const NumberVariable<int> &)));
QWidget::connect(_path, SIGNAL(editingFinished()), this,
SLOT(PathChanged()));
QWidget::connect(_regex,
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
SLOT(RegexChanged(const RegexConfig &)));
auto layout = new QHBoxLayout;
PlaceWidgets(
obs_module_text("AdvSceneSwitcher.condition.slideshow.entry"),
layout,
_layout,
{{"{{conditions}}", _conditions},
{"{{sources}}", _sources},
{"{{index}}", _index},
{"{{path}}", _path}});
setLayout(layout);
{"{{path}}", _path},
{"{{regex}}", _regex}});
setLayout(_layout);
_entryData = entryData;
UpdateEntryData();
@@ -245,11 +289,7 @@ MacroConditionSlideshowEdit::MacroConditionSlideshowEdit(
void MacroConditionSlideshowEdit::ConditionChanged(int index)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_condition =
static_cast<MacroConditionSlideshow::Condition>(index);
SetWidgetVisibility();
@@ -257,11 +297,7 @@ void MacroConditionSlideshowEdit::ConditionChanged(int index)
void MacroConditionSlideshowEdit::SourceChanged(const SourceSelection &source)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->SetSource(source);
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
@@ -269,24 +305,22 @@ void MacroConditionSlideshowEdit::SourceChanged(const SourceSelection &source)
void MacroConditionSlideshowEdit::IndexChanged(const NumberVariable<int> &value)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_index = value;
}
void MacroConditionSlideshowEdit::PathChanged()
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_path = _path->text().toStdString();
}
void MacroConditionSlideshowEdit::RegexChanged(const RegexConfig &regex)
{
GUARD_LOADING_AND_LOCK();
_entryData->_regex = regex;
}
void MacroConditionSlideshowEdit::UpdateEntryData()
{
if (!_entryData) {
@@ -297,6 +331,7 @@ void MacroConditionSlideshowEdit::UpdateEntryData()
_sources->SetSource(_entryData->GetSource());
_index->SetValue(_entryData->_index);
_path->setText(_entryData->_path);
_regex->SetRegexConfig(_entryData->_regex);
SetWidgetVisibility();
}
@@ -310,6 +345,14 @@ void MacroConditionSlideshowEdit::SetWidgetVisibility()
MacroConditionSlideshow::Condition::SLIDE_INDEX);
_path->setVisible(_entryData->_condition ==
MacroConditionSlideshow::Condition::SLIDE_PATH);
_regex->setVisible(_entryData->_condition ==
MacroConditionSlideshow::Condition::SLIDE_PATH);
if (_entryData->_condition ==
MacroConditionSlideshow::Condition::SLIDE_PATH) {
RemoveStretchIfPresent(_layout);
} else {
AddStretchIfNecessary(_layout);
}
}
} // namespace advss

View File

@@ -1,6 +1,7 @@
#pragma once
#include "macro-condition-edit.hpp"
#include "source-selection.hpp"
#include "regex-config.hpp"
#include "variable-spinbox.hpp"
#include "variable-line-edit.hpp"
@@ -31,12 +32,15 @@ public:
Condition _condition = Condition::SLIDE_CHANGED;
IntVariable _index;
StringVariable _path;
RegexConfig _regex;
private:
static void SlideChanged(void *condition, calldata_t *);
void RemoveSignalHandler();
void AddSignalHandler(const OBSWeakSource &);
void Reset();
void SetupTempVars();
void SetVariableValues(const std::string &value);
SourceSelection _source;
OBSWeakSource _currentSignalSource;
@@ -70,6 +74,7 @@ private slots:
void SourceChanged(const SourceSelection &);
void IndexChanged(const NumberVariable<int> &);
void PathChanged();
void RegexChanged(const RegexConfig &);
signals:
void HeaderInfoChanged(const QString &);
@@ -80,6 +85,8 @@ private:
VariableSpinBox *_index;
VariableLineEdit *_path;
SourceSelectionWidget *_sources;
RegexConfigWidget *_regex;
QHBoxLayout *_layout;
std::shared_ptr<MacroConditionSlideshow> _entryData;
bool _loading = true;

View File

@@ -47,17 +47,27 @@ bool MacroConditionWebsocket::CheckCondition()
continue;
}
if (_regex.Enabled()) {
if (_regex.Matches(*message, _message)) {
SetTempVarValue("message", *message);
SetVariableValue(*message);
return true;
if (!_regex.Matches(*message, _message)) {
continue;
}
SetTempVarValue("message", *message);
SetVariableValue(*message);
if (_clearBufferOnMatch) {
_messageBuffer->Clear();
}
return true;
} else {
if (*message == std::string(_message)) {
SetTempVarValue("message", *message);
SetVariableValue(*message);
return true;
if (*message != std::string(_message)) {
continue;
}
SetTempVarValue("message", *message);
SetVariableValue(*message);
if (_clearBufferOnMatch) {
_messageBuffer->Clear();
}
return true;
}
}
SetVariableValue("");
@@ -72,6 +82,8 @@ bool MacroConditionWebsocket::Save(obs_data_t *obj) const
_regex.Save(obj);
obs_data_set_string(obj, "connection",
GetWeakConnectionName(_connection).c_str());
obs_data_set_bool(obj, "clearBufferOnMatch", _clearBufferOnMatch);
obs_data_set_int(obj, "version", 1);
return true;
}
@@ -89,6 +101,11 @@ bool MacroConditionWebsocket::Load(obs_data_t *obj)
_connection =
GetWeakConnectionByName(obs_data_get_string(obj, "connection"));
_clearBufferOnMatch = obs_data_get_bool(obj, "clearBufferOnMatch");
if (!obs_data_has_user_value(obj, "version")) {
_clearBufferOnMatch = true;
}
SetType(_type);
return true;
}
@@ -160,6 +177,8 @@ MacroConditionWebsocketEdit::MacroConditionWebsocketEdit(
_message(new VariableTextEdit(this)),
_regex(new RegexConfigWidget(parent)),
_connection(new WSConnectionSelection(this)),
_clearBufferOnMatch(new QCheckBox(
obs_module_text("AdvSceneSwitcher.clearBufferOnMatch"))),
_editLayout(new QHBoxLayout())
{
populateConditionSelection(_conditions);
@@ -174,6 +193,8 @@ MacroConditionWebsocketEdit::MacroConditionWebsocketEdit(
QWidget::connect(_connection, SIGNAL(SelectionChanged(const QString &)),
this,
SLOT(ConnectionSelectionChanged(const QString &)));
QWidget::connect(_clearBufferOnMatch, SIGNAL(stateChanged(int)), this,
SLOT(ClearBufferOnMatchChanged(int)));
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(_editLayout);
@@ -183,6 +204,7 @@ MacroConditionWebsocketEdit::MacroConditionWebsocketEdit(
regexLayout->addStretch();
regexLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->addLayout(regexLayout);
mainLayout->addWidget(_clearBufferOnMatch);
setLayout(mainLayout);
_entryData = entryData;
@@ -232,6 +254,7 @@ void MacroConditionWebsocketEdit::UpdateEntryData()
_message->setPlainText(_entryData->_message);
_regex->SetRegexConfig(_entryData->_regex);
_connection->SetConnection(_entryData->GetConnection());
_clearBufferOnMatch->setChecked(_entryData->_clearBufferOnMatch);
if (_entryData->GetType() == MacroConditionWebsocket::Type::REQUEST) {
SetupRequestEdit();
@@ -245,11 +268,7 @@ void MacroConditionWebsocketEdit::UpdateEntryData()
void MacroConditionWebsocketEdit::ConditionChanged(int index)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->SetType(static_cast<MacroConditionWebsocket::Type>(index));
if (_entryData->GetType() == MacroConditionWebsocket::Type::REQUEST) {
SetupRequestEdit();
@@ -262,11 +281,7 @@ void MacroConditionWebsocketEdit::ConditionChanged(int index)
void MacroConditionWebsocketEdit::MessageChanged()
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_message = _message->toPlainText().toUtf8().constData();
adjustSize();
@@ -276,22 +291,20 @@ void MacroConditionWebsocketEdit::MessageChanged()
void MacroConditionWebsocketEdit::ConnectionSelectionChanged(
const QString &connection)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->SetConnection(connection.toStdString());
emit(HeaderInfoChanged(connection));
}
void MacroConditionWebsocketEdit::ClearBufferOnMatchChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_clearBufferOnMatch = value;
}
void MacroConditionWebsocketEdit::RegexChanged(const RegexConfig &conf)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_regex = conf;
adjustSize();

View File

@@ -33,6 +33,7 @@ public:
std::weak_ptr<WSConnection> GetConnection() const;
StringVariable _message = obs_module_text("AdvSceneSwitcher.enterText");
RegexConfig _regex;
bool _clearBufferOnMatch = true;
private:
void SetupTempVars();
@@ -69,12 +70,10 @@ private slots:
void MessageChanged();
void RegexChanged(const RegexConfig &);
void ConnectionSelectionChanged(const QString &);
void ClearBufferOnMatchChanged(int);
signals:
void HeaderInfoChanged(const QString &);
protected:
std::shared_ptr<MacroConditionWebsocket> _entryData;
private:
void SetupRequestEdit();
void SetupEventEdit();
@@ -83,7 +82,10 @@ private:
VariableTextEdit *_message;
RegexConfigWidget *_regex;
WSConnectionSelection *_connection;
QCheckBox *_clearBufferOnMatch;
QHBoxLayout *_editLayout;
std::shared_ptr<MacroConditionWebsocket> _entryData;
bool _loading = true;
};

View File

@@ -0,0 +1,882 @@
#include "source-interact.hpp"
#include "obs-module-helper.hpp"
#include "layout-helpers.hpp"
#include <QLayout>
#include <QLabel>
namespace advss {
void Position::Save(obs_data_t *obj, const char *name) const
{
OBSDataAutoRelease data = obs_data_create();
x.Save(data, "x");
y.Save(data, "y");
obs_data_set_obj(obj, name, data);
}
void Position::Load(obs_data_t *obj, const char *name)
{
OBSDataAutoRelease data = obs_data_get_obj(obj, name);
x.Load(data, "x");
y.Load(data, "y");
}
void Modifier::Save(obs_data_t *obj, const char *name) const
{
OBSDataAutoRelease data = obs_data_create();
obs_data_set_bool(data, "shift", _shift);
obs_data_set_bool(data, "ctrl", _ctrl);
obs_data_set_bool(data, "alt", _alt);
obs_data_set_bool(data, "meta", _meta);
obs_data_set_obj(obj, name, data);
}
void Modifier::Load(obs_data_t *obj, const char *name)
{
OBSDataAutoRelease data = obs_data_get_obj(obj, name);
_shift = obs_data_get_bool(data, "shift");
_ctrl = obs_data_get_bool(data, "ctrl");
_alt = obs_data_get_bool(data, "alt");
_meta = obs_data_get_bool(data, "meta");
}
uint32_t Modifier::GetModifiers() const
{
uint32_t modifiers = INTERACT_NONE;
if (_shift) {
modifiers |= INTERACT_SHIFT_KEY;
}
if (_ctrl) {
modifiers |= INTERACT_CONTROL_KEY;
}
if (_alt) {
modifiers |= INTERACT_ALT_KEY;
}
if (_alt) {
modifiers |= INTERACT_COMMAND_KEY;
}
return modifiers;
}
SourceInteraction::SourceInteraction()
{
_interaction = std::make_unique<MouseClick>();
}
SourceInteraction::SourceInteraction(const SourceInteraction &other)
{
OBSDataAutoRelease data = obs_data_create();
other.Save(data);
Load(data);
}
void SourceInteraction::Save(obs_data_t *obj, const char *name) const
{
OBSDataAutoRelease data = obs_data_create();
obs_data_set_int(data, "type", static_cast<int>(_type));
_interaction->Save(data, "settings");
obs_data_set_obj(obj, name, data);
}
void SourceInteraction::Load(obs_data_t *obj, const char *name)
{
OBSDataAutoRelease data = obs_data_get_obj(obj, name);
_type = static_cast<Type>(obs_data_get_int(data, "type"));
UpdateType();
_interaction->Load(data, "settings");
}
void SourceInteraction::SetSettings(SourceInteractionInstance *value)
{
_interaction.reset(value);
}
void SourceInteraction::SetType(Type t)
{
_type = t;
UpdateType();
}
void SourceInteraction::SendToSource(obs_source_t *source) const
{
_interaction->SendToSource(source);
}
void SourceInteraction::UpdateType()
{
switch (_type) {
case SourceInteraction::Type::CLICK:
_interaction.reset(new MouseClick);
break;
case SourceInteraction::Type::MOVE:
_interaction.reset(new MouseMove);
break;
case SourceInteraction::Type::WHEEL:
_interaction.reset(new MouseWheel);
break;
case SourceInteraction::Type::KEY:
_interaction.reset(new KeyPress);
break;
}
}
void MouseClick::Save(obs_data_t *obj, const char *name) const
{
OBSDataAutoRelease data = obs_data_create();
obs_data_set_int(data, "button", static_cast<int>(_button));
obs_data_set_bool(data, "up", _up);
_pos.Save(data);
_count.Save(data, "count");
_modifier.Save(data);
obs_data_set_obj(obj, name, data);
}
void MouseClick::Load(obs_data_t *obj, const char *name)
{
OBSDataAutoRelease data = obs_data_get_obj(obj, name);
_button = static_cast<Button>(obs_data_get_int(data, "button"));
_up = obs_data_get_bool(data, "up");
_pos.Load(data);
_count.Load(data, "count");
_modifier.Load(data);
}
void MouseClick::SendToSource(obs_source_t *source) const
{
obs_mouse_event ev = {_modifier.GetModifiers(), _pos.x, _pos.y};
obs_source_send_mouse_click(source, &ev, static_cast<int32_t>(_button),
_up, _count);
}
void MouseMove::Save(obs_data_t *obj, const char *name) const
{
OBSDataAutoRelease data = obs_data_create();
_pos.Save(data);
_modifier.Save(data);
obs_data_set_obj(obj, name, data);
}
void MouseMove::Load(obs_data_t *obj, const char *name)
{
OBSDataAutoRelease data = obs_data_get_obj(obj, name);
_pos.Load(data);
_modifier.Load(data);
}
void MouseMove::SendToSource(obs_source_t *source) const
{
obs_mouse_event ev = {_modifier.GetModifiers(), _pos.x, _pos.y};
obs_source_send_mouse_move(source, &ev, _leaveSource);
}
void MouseWheel::Save(obs_data_t *obj, const char *name) const
{
OBSDataAutoRelease data = obs_data_create();
_pos.Save(data);
_amount.Save(data, "amount");
_pos.Save(data);
_modifier.Save(data);
obs_data_set_obj(obj, name, data);
}
void MouseWheel::Load(obs_data_t *obj, const char *name)
{
OBSDataAutoRelease data = obs_data_get_obj(obj, name);
_pos.Load(data);
_amount.Load(data, "amount");
_modifier.Load(data);
}
void MouseWheel::SendToSource(obs_source_t *source) const
{
obs_mouse_event ev = {_modifier.GetModifiers(), _pos.x, _pos.y};
obs_source_send_mouse_wheel(source, &ev, _amount.x, _amount.y);
}
void KeyPress::Save(obs_data_t *obj, const char *name) const
{
OBSDataAutoRelease data = obs_data_create();
obs_data_set_bool(data, "up", _up);
_modifier.Save(data);
obs_data_set_obj(obj, name, data);
}
void KeyPress::Load(obs_data_t *obj, const char *name)
{
OBSDataAutoRelease data = obs_data_get_obj(obj, name);
_up = obs_data_get_bool(data, "up");
_modifier.Load(data);
}
void KeyPress::SendToSource(obs_source_t *source) const
{
char text = _text;
obs_key_event ev = {_modifier.GetModifiers(), &text, 0, 0, 0};
obs_source_send_key_click(source, &ev, _up);
}
PositionSelection::PositionSelection(int min, int max, QWidget *parent)
: QWidget(parent),
_x(new VariableSpinBox),
_y(new VariableSpinBox)
{
_x->setMinimum(min);
_y->setMinimum(min);
_x->setMaximum(max);
_y->setMaximum(max);
connect(_x, SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
this, SLOT(XChanged(const NumberVariable<int> &)));
connect(_y, SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
this, SLOT(YChanged(const NumberVariable<int> &)));
auto layout = new QHBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(_x);
layout->setStretch(0, 3);
auto label = new QLabel("x");
label->setAlignment(Qt::AlignCenter);
layout->addWidget(label);
layout->addWidget(_y);
layout->setStretch(2, 3);
setLayout(layout);
}
void PositionSelection::SetPosition(const Position &pos)
{
_x->SetValue(pos.x);
_y->SetValue(pos.y);
}
Position PositionSelection::GetPosition() const
{
return Position{_x->Value(), _y->Value()};
}
void PositionSelection::XChanged(const NumberVariable<int> &value)
{
emit PositionChanged(Position{value, _y->Value()});
}
void PositionSelection::YChanged(const NumberVariable<int> &value)
{
emit PositionChanged(Position{_x->Value(), value});
}
ModifierSelection::ModifierSelection(QWidget *parent)
: QWidget(parent),
_shift(new QCheckBox(
obs_module_text("AdvSceneSwitcher.modifierKey.shift"))),
_ctrl(new QCheckBox(
obs_module_text("AdvSceneSwitcher.modifierKey.ctrl"))),
_alt(new QCheckBox(
obs_module_text("AdvSceneSwitcher.modifierKey.alt"))),
_meta(new QCheckBox(
obs_module_text("AdvSceneSwitcher.modifierKey.meta")))
{
connect(_shift, SIGNAL(stateChanged(int)), this,
SLOT(ShiftChanged(int)));
connect(_ctrl, SIGNAL(stateChanged(int)), this, SLOT(CtrlChanged(int)));
connect(_alt, SIGNAL(stateChanged(int)), this, SLOT(AltChanged(int)));
connect(_meta, SIGNAL(stateChanged(int)), this, SLOT(MetaChanged(int)));
auto layout = new QHBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(_shift);
layout->addWidget(_ctrl);
layout->addWidget(_alt);
layout->addWidget(_meta);
setLayout(layout);
}
void ModifierSelection::SetModifier(const Modifier &modifier)
{
_shift->setChecked(modifier._shift);
_ctrl->setChecked(modifier._ctrl);
_alt->setChecked(modifier._alt);
_meta->setChecked(modifier._meta);
}
Modifier ModifierSelection::GetCurrentModifier() const
{
Modifier modifier;
modifier.EnableShift(_shift->isChecked());
modifier.EnableCtrl(_ctrl->isChecked());
modifier.EnableAlt(_alt->isChecked());
modifier.EnableMeta(_meta->isChecked());
return modifier;
}
void ModifierSelection::ShiftChanged(int value)
{
auto modifier = GetCurrentModifier();
modifier.EnableShift(value);
emit ModifierChanged(modifier);
}
void ModifierSelection::CtrlChanged(int value)
{
auto modifier = GetCurrentModifier();
modifier.EnableCtrl(value);
emit ModifierChanged(modifier);
}
void ModifierSelection::AltChanged(int value)
{
auto modifier = GetCurrentModifier();
modifier.EnableAlt(value);
emit ModifierChanged(modifier);
}
void ModifierSelection::MetaChanged(int value)
{
auto modifier = GetCurrentModifier();
modifier.EnableMeta(value);
emit ModifierChanged(modifier);
}
static inline void populateInteractionTypeSelection(QComboBox *list)
{
list->addItem(
obs_module_text(
"AdvSceneSwitcher.action.source.type.interact.mouseClick"),
(int)SourceInteraction::Type::CLICK);
list->addItem(
obs_module_text(
"AdvSceneSwitcher.action.source.type.interact.mouseMove"),
(int)SourceInteraction::Type::MOVE);
list->addItem(
obs_module_text(
"AdvSceneSwitcher.action.source.type.interact.mouseWheel"),
(int)SourceInteraction::Type::WHEEL);
list->addItem(
obs_module_text(
"AdvSceneSwitcher.action.source.type.interact.keyPress"),
(int)SourceInteraction::Type::KEY);
}
SourceInteractionWidget::SourceInteractionWidget(QWidget *parent)
: QWidget(parent),
_type(new QComboBox),
_settingsLayout(new QHBoxLayout)
{
populateInteractionTypeSelection(_type);
connect(_type, SIGNAL(currentIndexChanged(int)), this,
SLOT(TypeChange(int)));
auto typeLayout = new QHBoxLayout();
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.action.source.type.interact.selectType"),
typeLayout, {{"{{type}}", _type}});
SetupSettingsWidget(SourceInteraction::Type::CLICK);
auto layout = new QVBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
layout->addLayout(typeLayout);
layout->addLayout(_settingsLayout);
setLayout(layout);
}
void SourceInteractionWidget::SetupSettingsWidget(SourceInteraction::Type type)
{
_type->setCurrentIndex(_type->findData(static_cast<int>(type)));
ClearLayout(_settingsLayout);
switch (type) {
case SourceInteraction::Type::CLICK:
SetupMouseClick();
break;
case SourceInteraction::Type::MOVE:
SetupMouseMove();
break;
case SourceInteraction::Type::WHEEL:
SetupMouseWheel();
break;
case SourceInteraction::Type::KEY:
SetupKeyPress();
break;
}
_settingsLayout->update();
adjustSize();
updateGeometry();
}
void SourceInteractionWidget::SetupMouseClick()
{
auto widget = new MouseClickSettings();
connect(widget, SIGNAL(MouseClickSettingsChanged(const MouseClick &)),
this, SLOT(MouseClickSettingsChanged(const MouseClick &)));
_settingsLayout->addWidget(widget);
}
void SourceInteractionWidget::SetupMouseMove()
{
auto widget = new MouseMoveSettings();
connect(widget, SIGNAL(MouseMoveSettingsChanged(const MouseMove &)),
this, SLOT(MouseMoveSettingsChanged(const MouseMove &)));
_settingsLayout->addWidget(widget);
}
void SourceInteractionWidget::SetupMouseWheel()
{
auto widget = new MouseWheelSettings();
connect(widget, SIGNAL(MouseWheelSettingsChanged(const MouseWheel &)),
this, SLOT(MouseWheelSettingsChanged(const MouseWheel &)));
_settingsLayout->addWidget(widget);
}
void SourceInteractionWidget::SetupKeyPress()
{
auto widget = new KeyPressSettings();
connect(widget, SIGNAL(KeyPressSettingsChanged(const KeyPress &)), this,
SLOT(KeyPressSettingsChanged(const KeyPress &)));
_settingsLayout->addWidget(widget);
}
void SourceInteractionWidget::TypeChange(int idx)
{
auto type = static_cast<SourceInteraction::Type>(
_type->itemData(idx).toInt());
SetupSettingsWidget(type);
emit TypeChanged(type);
}
void SourceInteractionWidget::SetSourceInteractionSelection(
const SourceInteraction &interaction)
{
SetupSettingsWidget(interaction._type);
auto item = _settingsLayout->itemAt(0);
if (!item) {
return;
}
auto widget = item->widget();
if (!widget) {
return;
}
switch (interaction._type) {
case SourceInteraction::Type::CLICK: {
auto mouseClickWidget =
dynamic_cast<MouseClickSettings *>(widget);
if (!mouseClickWidget) {
return;
}
auto mouseClickSettings = dynamic_cast<MouseClick *>(
interaction._interaction.get());
if (!mouseClickSettings) {
return;
}
mouseClickWidget->SetMouseClickSettings(*mouseClickSettings);
}
return;
case SourceInteraction::Type::MOVE: {
auto mouseMoveWidget =
dynamic_cast<MouseMoveSettings *>(widget);
if (!mouseMoveWidget) {
return;
}
auto mouseMoveSettings = dynamic_cast<MouseMove *>(
interaction._interaction.get());
if (!mouseMoveSettings) {
return;
}
mouseMoveWidget->SetMouseMoveSettings(*mouseMoveSettings);
}
return;
case SourceInteraction::Type::WHEEL: {
auto mouseWheelWidget =
dynamic_cast<MouseWheelSettings *>(widget);
if (!mouseWheelWidget) {
return;
}
auto mouseWheelSettings = dynamic_cast<MouseWheel *>(
interaction._interaction.get());
if (!mouseWheelSettings) {
return;
}
mouseWheelWidget->SetMouseWheelSettings(*mouseWheelSettings);
}
return;
case SourceInteraction::Type::KEY: {
auto KeyPressWidget = dynamic_cast<KeyPressSettings *>(widget);
if (!KeyPressWidget) {
return;
}
auto KeyPressSettings = dynamic_cast<KeyPress *>(
interaction._interaction.get());
if (!KeyPressSettings) {
return;
}
KeyPressWidget->SetKeyPressSettings(*KeyPressSettings);
}
return;
}
adjustSize();
updateGeometry();
}
void SourceInteractionWidget::MouseClickSettingsChanged(const MouseClick &value)
{
auto click = new MouseClick();
*click = value;
emit SettingsChanged(click);
}
void SourceInteractionWidget::MouseMoveSettingsChanged(const MouseMove &value)
{
auto move = new MouseMove();
*move = value;
emit SettingsChanged(move);
}
void SourceInteractionWidget::MouseWheelSettingsChanged(const MouseWheel &value)
{
auto wheel = new MouseWheel();
*wheel = value;
emit SettingsChanged(wheel);
}
void SourceInteractionWidget::KeyPressSettingsChanged(const KeyPress &value)
{
auto key = new KeyPress();
*key = value;
emit SettingsChanged(key);
}
static inline void populateMouseButtonSelection(QComboBox *list)
{
list->addItem(obs_module_text("AdvSceneSwitcher.mouse.button.left"));
list->addItem(obs_module_text("AdvSceneSwitcher.mouse.button.middle"));
list->addItem(obs_module_text("AdvSceneSwitcher.mouse.button.right"));
}
MouseClickSettings::MouseClickSettings(QWidget *parent)
: QWidget(parent),
_button(new QComboBox),
_position(new PositionSelection(0, 999999)),
_up(new QCheckBox),
_count(new VariableSpinBox),
_modifier(new ModifierSelection)
{
_count->setMinimum(1);
_count->setMaximum(99);
populateMouseButtonSelection(_button);
connect(_button, SIGNAL(currentIndexChanged(int)), this,
SLOT(ButtonChanged(int)));
connect(_position, SIGNAL(PositionChanged(const Position &)), this,
SLOT(PositionChanged(const Position &)));
connect(_up, SIGNAL(stateChanged(int)), this, SLOT(UpChanged(int)));
connect(_count,
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
this, SLOT(CountChanged(const NumberVariable<int> &)));
connect(_modifier, SIGNAL(ModifierChanged(const Modifier &)), this,
SLOT(ModifierChanged(const Modifier &)));
auto layout = new QGridLayout();
layout->setColumnStretch(1, 3);
layout->setContentsMargins(0, 0, 0, 0);
int row = 0;
layout->addWidget(
new QLabel(obs_module_text("AdvSceneSwitcher.mouse.button")),
row, 0);
layout->addWidget(_button, row, 1);
++row;
layout->addWidget(
new QLabel(obs_module_text("AdvSceneSwitcher.mouse.position")),
row, 0);
layout->addWidget(_position, row, 1);
++row;
layout->addWidget(
new QLabel(obs_module_text("AdvSceneSwitcher.mouse.up")), row,
0);
layout->addWidget(_up, row, 1);
++row;
layout->addWidget(
new QLabel(obs_module_text("AdvSceneSwitcher.mouse.count")),
row, 0);
layout->addWidget(_count, row, 1);
++row;
layout->addWidget(
new QLabel(obs_module_text("AdvSceneSwitcher.modifierKey")),
row, 0);
layout->addWidget(_modifier, row, 1);
++row;
setLayout(layout);
}
void MouseClickSettings::SetMouseClickSettings(const MouseClick &click)
{
_button->setCurrentIndex(static_cast<int>(click._button));
_position->SetPosition(click._pos);
_up->setChecked(click._up);
_count->SetValue(click._count);
_modifier->SetModifier(click._modifier);
}
void MouseClickSettings::ButtonChanged(int button)
{
auto click = GetCurrentMouseClick();
click.SetButton(static_cast<MouseClick::Button>(button));
emit MouseClickSettingsChanged(click);
}
void MouseClickSettings::PositionChanged(const Position &pos)
{
auto click = GetCurrentMouseClick();
click.SetPosition(pos);
emit MouseClickSettingsChanged(click);
}
void MouseClickSettings::UpChanged(int value)
{
auto click = GetCurrentMouseClick();
click.SetDirection(value);
emit MouseClickSettingsChanged(click);
}
void MouseClickSettings::CountChanged(const IntVariable &value)
{
auto click = GetCurrentMouseClick();
click.SetCount(value);
emit MouseClickSettingsChanged(click);
}
void MouseClickSettings::ModifierChanged(const Modifier &modifier)
{
auto click = GetCurrentMouseClick();
click.SetModifier(modifier);
emit MouseClickSettingsChanged(click);
}
MouseClick MouseClickSettings::GetCurrentMouseClick() const
{
MouseClick click;
click.SetButton(
static_cast<MouseClick::Button>(_button->currentIndex()));
click.SetPosition(_position->GetPosition());
click.SetDirection(_up->isChecked());
click.SetCount(_count->Value());
click.SetModifier(_modifier->GetModifier());
return click;
}
MouseMoveSettings::MouseMoveSettings(QWidget *parent)
: QWidget(parent),
_position(new PositionSelection(0, 999999)),
_leaveEvent(new QCheckBox),
_modifier(new ModifierSelection)
{
connect(_position, SIGNAL(PositionChanged(const Position &)), this,
SLOT(PositionChanged(const Position &)));
connect(_leaveEvent, SIGNAL(stateChanged(int)), this,
SLOT(LeaveChanged(int)));
connect(_modifier, SIGNAL(ModifierChanged(const Modifier &)), this,
SLOT(ModifierChanged(const Modifier &)));
auto layout = new QGridLayout();
layout->setColumnStretch(1, 3);
layout->setContentsMargins(0, 0, 0, 0);
int row = 0;
layout->addWidget(
new QLabel(obs_module_text("AdvSceneSwitcher.mouse.position")),
row, 0);
layout->addWidget(_position, row, 1);
++row;
layout->addWidget(
new QLabel(obs_module_text("AdvSceneSwitcher.mouse.leave")),
row, 0);
layout->addWidget(_leaveEvent, row, 1);
++row;
layout->addWidget(
new QLabel(obs_module_text("AdvSceneSwitcher.modifierKey")),
row, 0);
layout->addWidget(_modifier, row, 1);
++row;
setLayout(layout);
}
void MouseMoveSettings::SetMouseMoveSettings(const MouseMove &move)
{
_position->SetPosition(move._pos);
_leaveEvent->setChecked(move._leaveSource);
_modifier->SetModifier(move._modifier);
}
void MouseMoveSettings::PositionChanged(const Position &pos)
{
auto move = GetCurrentMouseMove();
move.SetPosition(pos);
emit MouseMoveSettingsChanged(move);
}
void MouseMoveSettings::LeaveChanged(int value)
{
auto move = GetCurrentMouseMove();
move.SetLeave(value);
emit MouseMoveSettingsChanged(move);
}
void MouseMoveSettings::ModifierChanged(const Modifier &modifier)
{
auto move = GetCurrentMouseMove();
move.SetModifier(modifier);
emit MouseMoveSettingsChanged(move);
}
MouseMove MouseMoveSettings::GetCurrentMouseMove() const
{
MouseMove move;
move.SetPosition(_position->GetPosition());
move.SetLeave(_leaveEvent->isChecked());
move.SetModifier(_modifier->GetModifier());
return move;
}
MouseWheelSettings::MouseWheelSettings(QWidget *parent)
: QWidget(parent),
_position(new PositionSelection(0, 999999)),
_amount(new PositionSelection(-999999, 999999)),
_modifier(new ModifierSelection)
{
connect(_position, SIGNAL(PositionChanged(const Position &)), this,
SLOT(PositionChanged(const Position &)));
connect(_amount, SIGNAL(PositionChanged(const Position &)), this,
SLOT(AmountChanged(const Position &)));
connect(_modifier, SIGNAL(ModifierChanged(const Modifier &)), this,
SLOT(ModifierChanged(const Modifier &)));
auto layout = new QGridLayout();
layout->setColumnStretch(1, 3);
layout->setContentsMargins(0, 0, 0, 0);
int row = 0;
layout->addWidget(
new QLabel(obs_module_text("AdvSceneSwitcher.mouse.position")),
row, 0);
layout->addWidget(_position, row, 1);
++row;
layout->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.mouse.scrollAmount")),
row, 0);
layout->addWidget(_amount, row, 1);
++row;
layout->addWidget(
new QLabel(obs_module_text("AdvSceneSwitcher.modifierKey")),
row, 0);
layout->addWidget(_modifier, row, 1);
++row;
setLayout(layout);
}
void MouseWheelSettings::SetMouseWheelSettings(const MouseWheel &wheel)
{
_position->SetPosition(wheel._pos);
_position->SetPosition(wheel._amount);
_modifier->SetModifier(wheel._modifier);
}
void MouseWheelSettings::PositionChanged(const Position &pos)
{
auto wheel = GetCurrentMouseWheel();
wheel.SetPosition(pos);
emit MouseWheelSettingsChanged(wheel);
}
void MouseWheelSettings::AmountChanged(const Position &amount)
{
auto wheel = GetCurrentMouseWheel();
wheel.SetAmount(amount);
emit MouseWheelSettingsChanged(wheel);
}
void MouseWheelSettings::ModifierChanged(const Modifier &modifier)
{
auto move = GetCurrentMouseWheel();
move.SetModifier(modifier);
emit MouseWheelSettingsChanged(move);
}
MouseWheel MouseWheelSettings::GetCurrentMouseWheel() const
{
MouseWheel wheel;
wheel.SetPosition(_position->GetPosition());
wheel.SetAmount(_amount->GetPosition());
wheel.SetModifier(_modifier->GetModifier());
return wheel;
}
KeyPressSettings::KeyPressSettings(QWidget *parent)
: QWidget(parent),
_text(new QLineEdit()),
_up(new QCheckBox()),
_modifier(new ModifierSelection)
{
_text->setMaxLength(1);
connect(_text, SIGNAL(editingFinished()), this, SLOT(TextChanged()));
connect(_up, SIGNAL(stateChanged(int)), this, SLOT(UpChanged(int)));
connect(_modifier, SIGNAL(ModifierChanged(const Modifier &)), this,
SLOT(ModifierChanged(const Modifier &)));
auto layout = new QGridLayout();
layout->setColumnStretch(1, 3);
layout->setContentsMargins(0, 0, 0, 0);
int row = 0;
layout->addWidget(new QLabel(obs_module_text("AdvSceneSwitcher.key")),
row, 0);
layout->addWidget(_text, row, 1);
++row;
layout->addWidget(
new QLabel(obs_module_text("AdvSceneSwitcher.key.up")), row, 0);
layout->addWidget(_up, row, 1);
++row;
layout->addWidget(
new QLabel(obs_module_text("AdvSceneSwitcher.modifierKey")),
row, 0);
layout->addWidget(_modifier, row, 1);
++row;
setLayout(layout);
}
void KeyPressSettings::SetKeyPressSettings(const KeyPress &key)
{
_text->setText(QString(key._text));
_up->setChecked(key._up);
_modifier->SetModifier(key._modifier);
}
void KeyPressSettings::TextChanged()
{
auto key = GetCurrentKeyPress();
key.SetKey(_text->text().at(0).toLatin1());
emit KeyPressSettingsChanged(key);
}
void KeyPressSettings::UpChanged(int value)
{
auto key = GetCurrentKeyPress();
key.SetDirection(value);
emit KeyPressSettingsChanged(key);
}
void KeyPressSettings::ModifierChanged(const Modifier &modifier)
{
auto move = GetCurrentKeyPress();
move.SetModifier(modifier);
emit KeyPressSettingsChanged(move);
}
KeyPress KeyPressSettings::GetCurrentKeyPress() const
{
KeyPress key;
key.SetDirection(_up->isChecked());
key.SetModifier(_modifier->GetModifier());
return key;
}
} // namespace advss

View File

@@ -0,0 +1,337 @@
#pragma once
#include "variable-number.hpp"
#include "variable-spinbox.hpp"
#include <chrono>
#include <memory>
#include <obs.hpp>
#include <QWidget>
#include <QSpinBox>
#include <QComboBox>
#include <QCheckBox>
#include <QLineEdit>
#include <QLayout>
namespace advss {
struct Position {
void Save(obs_data_t *obj, const char *name = "position") const;
void Load(obs_data_t *obj, const char *name = "position");
IntVariable x = 0;
IntVariable y = 0;
};
class Modifier {
public:
void Save(obs_data_t *obj, const char *name = "modifier") const;
void Load(obs_data_t *obj, const char *name = "modifier");
void EnableShift(bool value) { _shift = value; }
void EnableCtrl(bool value) { _ctrl = value; }
void EnableAlt(bool value) { _alt = value; }
void EnableMeta(bool value) { _meta = value; }
uint32_t GetModifiers() const;
private:
bool _shift = false;
bool _ctrl = false;
bool _alt = false;
bool _meta = false;
friend class ModifierSelection;
};
class SourceInteractionInstance;
class SourceInteraction {
public:
SourceInteraction();
SourceInteraction(const SourceInteraction &);
void Save(obs_data_t *obj, const char *name = "interaction") const;
void Load(obs_data_t *obj, const char *name = "interaction");
enum class Type {
CLICK,
MOVE,
WHEEL,
KEY,
};
void SetType(Type);
void SetSettings(SourceInteractionInstance *);
void SendToSource(obs_source_t *source) const;
private:
void UpdateType();
Type _type = Type::CLICK;
std::unique_ptr<SourceInteractionInstance> _interaction;
friend class SourceInteractionWidget;
};
class SourceInteractionInstance {
public:
virtual void Save(obs_data_t *obj, const char *name) const = 0;
virtual void Load(obs_data_t *obj, const char *name) = 0;
void SetModifier(Modifier modifier) { _modifier = modifier; }
virtual void SendToSource(obs_source_t *source) const = 0;
virtual ~SourceInteractionInstance(){};
protected:
Modifier _modifier;
};
/* Interaction types below */
class MouseClick : public SourceInteractionInstance {
public:
void Save(obs_data_t *obj,
const char *name = "mouseClick") const override;
void Load(obs_data_t *obj, const char *name = "mouseClick") override;
enum class Button {
LEFT,
MIDDLE,
RIGHT,
};
void SetButton(Button button) { _button = button; }
void SetDirection(bool up) { _up = up; }
void SetCount(const IntVariable &count) { _count = count; }
void SetPosition(const Position &pos) { _pos = pos; }
void SendToSource(obs_source_t *source) const override;
private:
Button _button = Button::LEFT;
Position _pos;
bool _up = false;
IntVariable _count = 1;
friend class MouseClickSettings;
};
class MouseMove : public SourceInteractionInstance {
public:
void Save(obs_data_t *obj,
const char *name = "mouseMove") const override;
void Load(obs_data_t *obj, const char *name = "mouseMove") override;
void SetLeave(bool leave) { _leaveSource = leave; }
void SetPosition(const Position &pos) { _pos = pos; }
void SendToSource(obs_source_t *source) const override;
private:
Position _pos;
bool _leaveSource = false;
friend class MouseMoveSettings;
};
class MouseWheel : public SourceInteractionInstance {
public:
void Save(obs_data_t *obj,
const char *name = "mouseWheel") const override;
void Load(obs_data_t *obj, const char *name = "mouseWheel") override;
void SetPosition(const Position &pos) { _pos = pos; }
void SetAmount(const Position &amount) { _amount = amount; }
void SendToSource(obs_source_t *source) const override;
private:
Position _pos;
Position _amount;
friend class MouseWheelSettings;
};
class KeyPress : public SourceInteractionInstance {
public:
void Save(obs_data_t *obj,
const char *name = "keyPress") const override;
void Load(obs_data_t *obj, const char *name = "keyPress") override;
void SetDirection(bool up) { _up = up; }
void SetKey(char value) { _text = value; }
void SendToSource(obs_source_t *source) const override;
private:
char _text;
bool _up = false;
friend class KeyPressSettings;
};
/* Helper widgets below */
class PositionSelection : public QWidget {
Q_OBJECT
public:
PositionSelection(int min, int max, QWidget *parent = 0);
void SetPosition(const Position &);
Position GetPosition() const;
private slots:
void XChanged(const NumberVariable<int> &);
void YChanged(const NumberVariable<int> &);
signals:
void PositionChanged(const Position &value);
private:
VariableSpinBox *_x;
VariableSpinBox *_y;
};
class ModifierSelection : public QWidget {
Q_OBJECT
public:
ModifierSelection(QWidget *parent = 0);
void SetModifier(const Modifier &);
Modifier GetModifier() const { return GetCurrentModifier(); }
private slots:
void ShiftChanged(int);
void CtrlChanged(int);
void AltChanged(int);
void MetaChanged(int);
signals:
void ModifierChanged(const Modifier &value);
private:
Modifier GetCurrentModifier() const;
QCheckBox *_shift;
QCheckBox *_ctrl;
QCheckBox *_alt;
QCheckBox *_meta;
};
/* Settings widgets below */
class SourceInteractionWidget : public QWidget {
Q_OBJECT
public:
SourceInteractionWidget(QWidget *parent = 0);
void SetSourceInteractionSelection(const SourceInteraction &);
private slots:
void TypeChange(int);
void MouseClickSettingsChanged(const MouseClick &);
void MouseMoveSettingsChanged(const MouseMove &);
void MouseWheelSettingsChanged(const MouseWheel &);
void KeyPressSettingsChanged(const KeyPress &);
signals:
void TypeChanged(SourceInteraction::Type value);
void SettingsChanged(SourceInteractionInstance *value);
private:
void SetupSettingsWidget(SourceInteraction::Type);
void SetupMouseClick();
void SetupMouseMove();
void SetupMouseWheel();
void SetupKeyPress();
QComboBox *_type;
QHBoxLayout *_settingsLayout;
};
class MouseClickSettings : public QWidget {
Q_OBJECT
public:
MouseClickSettings(QWidget *parent = 0);
void SetMouseClickSettings(const MouseClick &);
private slots:
void ButtonChanged(int);
void PositionChanged(const Position &);
void UpChanged(int);
void CountChanged(const NumberVariable<int> &);
void ModifierChanged(const Modifier &);
signals:
void MouseClickSettingsChanged(const MouseClick &value);
private:
MouseClick GetCurrentMouseClick() const;
QComboBox *_button;
PositionSelection *_position;
QCheckBox *_up;
VariableSpinBox *_count;
ModifierSelection *_modifier;
};
class MouseMoveSettings : public QWidget {
Q_OBJECT
public:
MouseMoveSettings(QWidget *parent = 0);
void SetMouseMoveSettings(const MouseMove &);
private slots:
void PositionChanged(const Position &);
void LeaveChanged(int);
void ModifierChanged(const Modifier &);
signals:
void MouseMoveSettingsChanged(const MouseMove &value);
private:
MouseMove GetCurrentMouseMove() const;
PositionSelection *_position;
QCheckBox *_leaveEvent;
ModifierSelection *_modifier;
};
class MouseWheelSettings : public QWidget {
Q_OBJECT
public:
MouseWheelSettings(QWidget *parent = 0);
void SetMouseWheelSettings(const MouseWheel &);
private slots:
void PositionChanged(const Position &);
void AmountChanged(const Position &);
void ModifierChanged(const Modifier &);
signals:
void MouseWheelSettingsChanged(const MouseWheel &value);
private:
MouseWheel GetCurrentMouseWheel() const;
PositionSelection *_position;
PositionSelection *_amount;
ModifierSelection *_modifier;
};
class KeyPressSettings : public QWidget {
Q_OBJECT
public:
KeyPressSettings(QWidget *parent = 0);
void SetKeyPressSettings(const KeyPress &);
private slots:
void TextChanged();
void UpChanged(int);
void ModifierChanged(const Modifier &);
signals:
void KeyPressSettingsChanged(const KeyPress &value);
private:
KeyPress GetCurrentKeyPress() const;
QLineEdit *_text;
QCheckBox *_up;
ModifierSelection *_modifier;
};
} // namespace advss

View File

@@ -33,6 +33,9 @@ bool MacroConditionMidi::CheckCondition()
}
if (message->Matches(_message)) {
SetVariableValues(*message);
if (_clearBufferOnMatch) {
_messageBuffer->Clear();
}
return true;
}
}
@@ -45,6 +48,8 @@ bool MacroConditionMidi::Save(obs_data_t *obj) const
MacroCondition::Save(obj);
_message.Save(obj);
_device.Save(obj);
obs_data_set_bool(obj, "clearBufferOnMatch", _clearBufferOnMatch);
obs_data_set_int(obj, "version", 1);
return true;
}
@@ -54,6 +59,10 @@ bool MacroConditionMidi::Load(obs_data_t *obj)
_message.Load(obj);
_device.Load(obj);
_messageBuffer = _device.RegisterForMidiMessages();
_clearBufferOnMatch = obs_data_get_bool(obj, "clearBufferOnMatch");
if (!obs_data_has_user_value(obj, "version")) {
_clearBufferOnMatch = true;
}
return true;
}
@@ -106,7 +115,9 @@ MacroConditionMidiEdit::MacroConditionMidiEdit(
_resetMidiDevices(new QPushButton(
obs_module_text("AdvSceneSwitcher.midi.resetDevices"))),
_listen(new QPushButton(
obs_module_text("AdvSceneSwitcher.midi.startListen")))
obs_module_text("AdvSceneSwitcher.midi.startListen"))),
_clearBufferOnMatch(new QCheckBox(
obs_module_text("AdvSceneSwitcher.clearBufferOnMatch")))
{
QWidget::connect(_devices,
SIGNAL(DeviceSelectionChanged(const MidiDevice &)),
@@ -119,6 +130,8 @@ MacroConditionMidiEdit::MacroConditionMidiEdit(
SLOT(ResetMidiDevices()));
QWidget::connect(_listen, SIGNAL(clicked()), this,
SLOT(ToggleListen()));
QWidget::connect(_clearBufferOnMatch, SIGNAL(stateChanged(int)), this,
SLOT(ClearBufferOnMatchChanged(int)));
QWidget::connect(&_listenTimer, SIGNAL(timeout()), this,
SLOT(SetMessageSelectionToLastReceived()));
@@ -135,6 +148,7 @@ MacroConditionMidiEdit::MacroConditionMidiEdit(
mainLayout->addWidget(_message);
mainLayout->addLayout(listenLayout);
mainLayout->addWidget(_resetMidiDevices);
mainLayout->addWidget(_clearBufferOnMatch);
setLayout(mainLayout);
_listenTimer.setInterval(100);
@@ -157,6 +171,7 @@ void MacroConditionMidiEdit::UpdateEntryData()
_message->SetMessage(_entryData->_message);
_devices->SetDevice(_entryData->GetDevice());
_clearBufferOnMatch->setChecked(_entryData->_clearBufferOnMatch);
adjustSize();
updateGeometry();
@@ -182,14 +197,16 @@ void MacroConditionMidiEdit::DeviceSelectionChanged(const MidiDevice &device)
void MacroConditionMidiEdit::MidiMessageChanged(const MidiMessage &message)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_message = message;
}
void MacroConditionMidiEdit::ClearBufferOnMatchChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_clearBufferOnMatch = value;
}
void MacroConditionMidiEdit::ResetMidiDevices()
{
auto lock = LockContext();

View File

@@ -2,6 +2,7 @@
#include "macro-condition-edit.hpp"
#include "midi-helpers.hpp"
#include <QCheckBox>
#include <QPushButton>
#include <QTimer>
@@ -23,6 +24,7 @@ public:
void SetDevice(const MidiDevice &dev);
const MidiDevice &GetDevice() const { return _device; }
MidiMessage _message;
bool _clearBufferOnMatch = true;
private:
void SetupTempVars();
@@ -55,6 +57,7 @@ public:
private slots:
void DeviceSelectionChanged(const MidiDevice &);
void MidiMessageChanged(const MidiMessage &);
void ClearBufferOnMatchChanged(int);
void ResetMidiDevices();
void ToggleListen();
void SetMessageSelectionToLastReceived();
@@ -64,12 +67,13 @@ signals:
private:
void EnableListening(bool);
std::shared_ptr<MacroConditionMidi> _entryData;
MidiDeviceSelection *_devices;
MidiMessageSelection *_message;
QPushButton *_resetMidiDevices;
QPushButton *_listen;
QCheckBox *_clearBufferOnMatch;
std::shared_ptr<MacroConditionMidi> _entryData;
QTimer _listenTimer;
MidiMessageBuffer _messageBuffer;
bool _currentlyListening = false;

View File

@@ -281,9 +281,8 @@ void ProgressDialog::CategoryCountUpdated(int value)
}
TwitchCategorySearchButton::TwitchCategorySearchButton(QWidget *parent)
: QPushButton(parent)
: QToolButton(parent)
{
setMaximumWidth(22);
const std::string pathPrefix =
GetDataFilePath("res/images/" + GetThemeTypeName());
SetButtonIcon(this, (pathPrefix + "Search.svg").c_str());

View File

@@ -6,7 +6,7 @@
#include <QDialog>
#include <QLabel>
#include <QCheckBox>
#include <QPushButton>
#include <QToolButton>
namespace advss {
@@ -94,7 +94,7 @@ private:
static std::map<QString, int> _streamingCategories;
};
class TwitchCategorySearchButton : public QPushButton {
class TwitchCategorySearchButton : public QToolButton {
Q_OBJECT
public:

View File

@@ -175,19 +175,26 @@ void MacroActionTwitch::StartCommercial(
"/helix/channels/commercial", {},
data.Get());
if (result.status != 200) {
if (result.status == 200) {
OBSDataArrayAutoRelease replyArray =
obs_data_get_array(result.data, "data");
OBSDataAutoRelease replyData =
obs_data_array_item(replyArray, 0);
vblog(LOG_INFO,
"Commercial started! (%d)\n"
"length: %lld\n"
"message: %s\n"
"retry_after: %lld\n",
result.status, obs_data_get_int(replyData, "length"),
obs_data_get_string(replyData, "message"),
obs_data_get_int(replyData, "retry_after"));
} else {
blog(LOG_INFO,
"Failed to start commercial! (%d)\n"
"length: %lld\n"
"message: %s\n"
"retry_after: %lld\n",
result.status, obs_data_get_int(replyData, "length"),
obs_data_get_string(replyData, "message"),
obs_data_get_int(replyData, "retry_after"));
"error: %s\n"
"message: %s\n",
result.status, obs_data_get_string(result.data, "error"),
obs_data_get_string(result.data, "message"));
}
}

View File

@@ -305,6 +305,10 @@ bool MacroConditionTwitch::CheckChannelGenericEvents(TwitchToken &token)
std::bind(&MacroConditionTwitch::SetTempVarValue, this,
std::placeholders::_1,
std::placeholders::_2));
if (_clearBufferOnMatch) {
_eventBuffer->Clear();
}
return true;
}
@@ -343,6 +347,10 @@ bool MacroConditionTwitch::CheckChannelLiveEvents(TwitchToken &token)
std::bind(&MacroConditionTwitch::SetTempVarValue, this,
std::placeholders::_1,
std::placeholders::_2));
if (_clearBufferOnMatch) {
_eventBuffer->Clear();
}
return true;
}
@@ -376,11 +384,18 @@ bool MacroConditionTwitch::CheckChatMessages(TwitchToken &token)
if (!message) {
continue;
}
if (stringMatches(_regexChat, message->message, _chatMessage)) {
SetTempVarValue("chatter", message->source.nick);
SetTempVarValue("chat_message", message->message);
return true;
if (!stringMatches(_regexChat, message->message,
_chatMessage)) {
continue;
}
SetTempVarValue("chatter", message->source.nick);
SetTempVarValue("chat_message", message->message);
if (_clearBufferOnMatch) {
_eventBuffer->Clear();
}
return true;
}
return false;
}
@@ -588,6 +603,8 @@ bool MacroConditionTwitch::Save(obs_data_t *obj) const
_chatMessage.Save(obj, "chatMessage");
_regexChat.Save(obj, "regexChat");
_category.Save(obj);
obs_data_set_bool(obj, "clearBufferOnMatch", _clearBufferOnMatch);
obs_data_set_int(obj, "version", 1);
return true;
}
@@ -605,6 +622,10 @@ bool MacroConditionTwitch::Load(obs_data_t *obj)
_chatMessage.Load(obj, "chatMessage");
_regexChat.Load(obj, "regexChat");
_category.Load(obj);
_clearBufferOnMatch = obs_data_get_bool(obj, "clearBufferOnMatch");
if (!obs_data_has_user_value(obj, "version")) {
_clearBufferOnMatch = false;
}
_subscriptionID = "";
ResetChatConnection();
@@ -1244,7 +1265,9 @@ MacroConditionTwitchEdit::MacroConditionTwitchEdit(
_regexTitle(new RegexConfigWidget(parent)),
_chatMessage(new VariableTextEdit(this, 5, 1, 1)),
_regexChat(new RegexConfigWidget(parent)),
_category(new TwitchCategoryWidget(this))
_category(new TwitchCategoryWidget(this)),
_clearBufferOnMatch(new QCheckBox(
obs_module_text("AdvSceneSwitcher.clearBufferOnMatch")))
{
_streamTitle->setSizePolicy(QSizePolicy::MinimumExpanding,
QSizePolicy::Preferred);
@@ -1280,6 +1303,8 @@ MacroConditionTwitchEdit::MacroConditionTwitchEdit(
SLOT(CategoreyChanged(const TwitchCategory &)));
QWidget::connect(this, SIGNAL(TempVarsChanged()), window(),
SIGNAL(SegmentTempVarsChanged()));
QWidget::connect(_clearBufferOnMatch, SIGNAL(stateChanged(int)), this,
SLOT(ClearBufferOnMatchChanged(int)));
PlaceWidgets(obs_module_text("AdvSceneSwitcher.condition.twitch.entry"),
_layout,
@@ -1305,6 +1330,7 @@ MacroConditionTwitchEdit::MacroConditionTwitchEdit(
mainLayout->addLayout(chatLayout);
mainLayout->addLayout(accountLayout);
mainLayout->addWidget(_tokenWarning);
mainLayout->addWidget(_clearBufferOnMatch);
setLayout(mainLayout);
_tokenCheckTimer.start(1000);
@@ -1336,11 +1362,7 @@ void MacroConditionTwitchEdit::ConditionChanged(int idx)
void MacroConditionTwitchEdit::TwitchTokenChanged(const QString &token)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->SetToken(GetWeakTwitchTokenByQString(token));
_category->SetToken(_entryData->GetToken());
_channel->SetToken(_entryData->GetToken());
@@ -1390,11 +1412,7 @@ void MacroConditionTwitchEdit::CheckToken()
void MacroConditionTwitchEdit::ChannelChanged(const TwitchChannel &channel)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->SetChannel(channel);
_pointsReward->SetChannel(channel);
_entryData->ResetChatConnection();
@@ -1403,41 +1421,25 @@ void MacroConditionTwitchEdit::ChannelChanged(const TwitchChannel &channel)
void MacroConditionTwitchEdit::PointsRewardChanged(
const TwitchPointsReward &pointsReward)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->SetPointsReward(pointsReward);
}
void MacroConditionTwitchEdit::StreamTitleChanged()
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_streamTitle = _streamTitle->text().toStdString();
}
void MacroConditionTwitchEdit::ChatMessageChanged()
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_chatMessage = _chatMessage->toPlainText().toStdString();
}
void MacroConditionTwitchEdit::RegexTitleChanged(const RegexConfig &conf)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_regexTitle = conf;
adjustSize();
@@ -1446,11 +1448,7 @@ void MacroConditionTwitchEdit::RegexTitleChanged(const RegexConfig &conf)
void MacroConditionTwitchEdit::RegexChatChanged(const RegexConfig &conf)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_regexChat = conf;
adjustSize();
@@ -1459,14 +1457,16 @@ void MacroConditionTwitchEdit::RegexChatChanged(const RegexConfig &conf)
void MacroConditionTwitchEdit::CategoreyChanged(const TwitchCategory &category)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_category = category;
}
void MacroConditionTwitchEdit::ClearBufferOnMatchChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_clearBufferOnMatch = value;
}
void MacroConditionTwitchEdit::SetWidgetVisibility()
{
auto condition = _entryData->GetCondition();
@@ -1492,6 +1492,10 @@ void MacroConditionTwitchEdit::SetWidgetVisibility()
MacroConditionTwitch::Condition::CHAT_MESSAGE_RECEIVED);
_category->setVisible(
condition == MacroConditionTwitch::Condition::CATEGORY_POLLING);
_clearBufferOnMatch->setVisible(
_entryData->IsUsingEventSubCondition() ||
_entryData->GetCondition() ==
MacroConditionTwitch::Condition::CHAT_MESSAGE_RECEIVED);
if (condition == MacroConditionTwitch::Condition::TITLE_POLLING) {
RemoveStretchIfPresent(_layout);
@@ -1525,6 +1529,7 @@ void MacroConditionTwitchEdit::UpdateEntryData()
_regexChat->SetRegexConfig(_entryData->_regexChat);
_category->SetToken(_entryData->GetToken());
_category->SetCategory(_entryData->_category);
_clearBufferOnMatch->setChecked(_entryData->_clearBufferOnMatch);
SetWidgetVisibility();
}

View File

@@ -89,6 +89,7 @@ public:
void SetPointsReward(const TwitchPointsReward &pointsReward);
TwitchPointsReward GetPointsReward() const { return _pointsReward; }
void ResetChatConnection();
bool IsUsingEventSubCondition();
bool CheckCondition();
bool Save(obs_data_t *obj) const;
@@ -103,6 +104,7 @@ public:
StringVariable _chatMessage;
RegexConfig _regexChat = RegexConfig::PartialMatchRegexConfig();
TwitchCategory _category;
bool _clearBufferOnMatch = false;
private:
bool CheckChannelGenericEvents(TwitchToken &token);
@@ -112,7 +114,6 @@ private:
void RegisterEventSubscription();
void ResetSubscription();
void SetupEventSubscription(EventSub &);
bool IsUsingEventSubCondition();
bool EventSubscriptionIsSetup(const std::shared_ptr<EventSub> &);
void AddChannelGenericEventSubscription(
const char *version, bool includeModeratorId = false,
@@ -169,14 +170,12 @@ private slots:
void RegexTitleChanged(const RegexConfig &);
void RegexChatChanged(const RegexConfig &);
void CategoreyChanged(const TwitchCategory &);
void ClearBufferOnMatchChanged(int);
signals:
void HeaderInfoChanged(const QString &);
void TempVarsChanged();
protected:
std::shared_ptr<MacroConditionTwitch> _entryData;
private:
void SetWidgetVisibility();
void SetTokenWarning(bool visible, const QString &text = "");
@@ -193,7 +192,9 @@ private:
VariableTextEdit *_chatMessage;
RegexConfigWidget *_regexChat;
TwitchCategoryWidget *_category;
QCheckBox *_clearBufferOnMatch;
std::shared_ptr<MacroConditionTwitch> _entryData;
bool _loading = true;
};

110
plugins/usb/CMakeLists.txt Normal file
View File

@@ -0,0 +1,110 @@
cmake_minimum_required(VERSION 3.14)
project(advanced-scene-switcher-usb)
# --- Check libusb requirements ---
get_target_property(ADVSS_SOURCE_DIR advanced-scene-switcher-lib SOURCE_DIR)
set(libusb_DEPS_DIR "${ADVSS_SOURCE_DIR}/deps/libusb")
if(OS_LINUX)
find_package(PkgConfig)
if(NOT PKG_CONFIG_FOUND)
message(WARNING "pkg-config not found!\n" "USB condition will be disabled!")
return()
endif()
pkg_check_modules(libusb OPTIONAL libusb)
find_path(
libusb_HEADER_DIR
NAMES libusb.h
PATHS ${libusb_INCLUDEDIR} /usr/include /usr/local/include
/opt/local/include
PATH_SUFFIXES libusb-1.0)
find_library(
libusb_LINK_LIBRARIES
NAMES usb-1.0 usb
PATHS /usr/lib /usr/local/lib /opt/local/lib)
elseif(OS_MACOS)
find_path(
libusb_HEADER_DIR
NAMES libusb.h
PATHS ${libusb_DEPS_DIR}/libusb)
# The NO_DEFAULT_PATH option is set since we have to use the "universal"
# variant of the library, which combines the x86 and arm variants
find_library(
libusb_LINK_LIBRARIES
NAMES usb-1.0.0 usb-1.0 usb
PATHS ${CMAKE_PREFIX_PATH}
PATH_SUFFIXES lib
NO_DEFAULT_PATH)
find_file(
libusb_RUNTIME_LIBRARY
NAMES libusb-1.0.0.dylib
PATHS ${CMAKE_PREFIX_PATH}
PATH_SUFFIXES lib
NO_DEFAULT_PATH)
elseif(OS_WINDOWS)
find_path(
libusb_HEADER_DIR
NAMES libusb.h
PATHS ${libusb_DEPS_DIR}/libusb)
# Intentionally not using find_library since we have to use the libusb-1.0.lib
# file from within the dll folder instead of the lib folder to avoid linking
# issues due to conflicting LIBCMT versions
find_file(
libusb_LINK_LIBRARIES
NAMES libusb-1.0.lib
PATHS ${CMAKE_PREFIX_PATH}
PATH_SUFFIXES dll)
find_file(
libusb_RUNTIME_LIBRARY
NAMES libusb-1.0.dll
PATHS ${CMAKE_PREFIX_PATH}
PATH_SUFFIXES dll)
endif()
if(NOT libusb_HEADER_DIR)
message(WARNING "libusb headers not found. Make sure libusb is installed. "
"USB condition will be disabled!\n\n"
"libusb sources are available under: ${libusb_DEPS_DIR}")
return()
endif()
if(NOT libusb_LINK_LIBRARIES)
message(WARNING "libusb library not found. Make sure libusb is installed. "
"USB condition will be disabled!\n\n"
"libusb sources are available under: ${libusb_DEPS_DIR}")
return()
endif()
if(NOT OS_LINUX AND NOT libusb_RUNTIME_LIBRARY)
message(
WARNING "libusb runtime library not found. Make sure libusb is installed. "
"USB condition will be disabled!\n\n"
"libusb sources are available under: ${libusb_DEPS_DIR}")
return()
endif()
# --- End of section ---
add_library(${PROJECT_NAME} MODULE)
target_sources(
${PROJECT_NAME} PRIVATE macro-condition-usb.cpp macro-condition-usb.hpp
usb-helpers.cpp usb-helpers.hpp)
setup_advss_plugin(${PROJECT_NAME})
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
target_include_directories(${PROJECT_NAME} PRIVATE ${libusb_HEADER_DIR})
target_link_libraries(${PROJECT_NAME} PRIVATE ${libusb_LINK_LIBRARIES})
install_advss_plugin(${PROJECT_NAME})
if(NOT OS_LINUX)
install_advss_plugin_dependency(TARGET ${PROJECT_NAME} DEPENDENCIES
${libusb_RUNTIME_LIBRARY})
endif()

View File

@@ -0,0 +1,381 @@
#include "macro-condition-usb.hpp"
#include "layout-helpers.hpp"
#include "obs.hpp"
namespace advss {
const std::string MacroConditionUSB::id = "usb";
bool MacroConditionUSB::_registered = MacroConditionFactory::Register(
MacroConditionUSB::id,
{MacroConditionUSB::Create, MacroConditionUSBEdit::Create,
"AdvSceneSwitcher.condition.usb"});
bool MacroConditionUSB::DeviceMatchOption::Matches(
const std::string &value) const
{
if (!regex.Enabled()) {
return pattern == value;
}
return regex.Matches(value, pattern);
}
void MacroConditionUSB::DeviceMatchOption::Save(obs_data_t *obj,
const char *name) const
{
OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "pattern", pattern.c_str());
regex.Save(data);
obs_data_set_obj(obj, name, data);
}
void MacroConditionUSB::DeviceMatchOption::Load(obs_data_t *obj,
const char *name)
{
OBSDataAutoRelease data = obs_data_get_obj(obj, name);
pattern = obs_data_get_string(data, "pattern");
regex.Load(data);
}
bool MacroConditionUSB::CheckCondition()
{
const auto devs = GetUSBDevices();
if (devs.empty()) {
return false;
}
for (const auto &dev : devs) {
if (_vendorID.Matches(dev.vendorID) &&
_productID.Matches(dev.productID) &&
_busNumber.Matches(dev.busNumber) &&
_deviceAddress.Matches(dev.deviceAddress) &&
_vendorName.Matches(dev.vendorName) &&
_productName.Matches(dev.productName) &&
_serialNumber.Matches(dev.serialNumber)) {
SetTempVarValue("vendorID", dev.vendorID);
SetTempVarValue("productID", dev.productID);
SetTempVarValue("busNumber", dev.busNumber);
SetTempVarValue("deviceAddress", dev.deviceAddress);
SetTempVarValue("vendorName", dev.vendorName);
SetTempVarValue("productName", dev.productName);
SetTempVarValue("serialNumber", dev.serialNumber);
return true;
}
}
return false;
}
bool MacroConditionUSB::Save(obs_data_t *obj) const
{
MacroCondition::Save(obj);
_vendorID.Save(obj, "vendorID");
_productID.Save(obj, "productID");
_busNumber.Save(obj, "busNumber");
_deviceAddress.Save(obj, "deviceAddress");
_vendorName.Save(obj, "vendorName");
_productName.Save(obj, "productName");
_serialNumber.Save(obj, "serialNumber");
return true;
}
bool MacroConditionUSB::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_vendorID.Load(obj, "vendorID");
_productID.Load(obj, "productID");
_busNumber.Load(obj, "busNumber");
_deviceAddress.Load(obj, "deviceAddress");
_vendorName.Load(obj, "vendorName");
_productName.Load(obj, "productName");
_serialNumber.Load(obj, "serialNumber");
return true;
}
void MacroConditionUSB::SetupTempVars()
{
AddTempvar("vendorName",
obs_module_text("AdvSceneSwitcher.tempVar.usb.vendorName"));
AddTempvar("productName",
obs_module_text("AdvSceneSwitcher.tempVar.usb.productName"));
AddTempvar("vendorID",
obs_module_text("AdvSceneSwitcher.tempVar.usb.vendorID"));
AddTempvar("productID",
obs_module_text("AdvSceneSwitcher.tempVar.usb.productID"));
AddTempvar("busNumber",
obs_module_text("AdvSceneSwitcher.tempVar.usb.busNumber"));
AddTempvar(
"deviceAddress",
obs_module_text("AdvSceneSwitcher.tempVar.usb.deviceAddress"));
AddTempvar(
"serialNumber",
obs_module_text("AdvSceneSwitcher.tempVar.usb.serialNumber"));
}
static void setupDevicePropertySelection(QComboBox *list,
const QSet<QString> &items)
{
list->setEditable(true);
list->setMaxVisibleItems(20);
list->setDuplicatesEnabled(false);
for (const auto &item : items) {
list->addItem(item);
}
list->model()->sort(0);
}
void static populateNewLayoutRow(QGridLayout *layout, int &row,
const char *labelText, QComboBox *property,
RegexConfigWidget *regex)
{
layout->addWidget(new QLabel(obs_module_text(labelText)), row, 0);
auto settingsLayout = new QHBoxLayout();
property->setSizePolicy(QSizePolicy::MinimumExpanding,
QSizePolicy::MinimumExpanding);
settingsLayout->addWidget(property);
settingsLayout->addWidget(regex);
settingsLayout->setContentsMargins(0, 0, 0, 0);
layout->addLayout(settingsLayout, row, 1);
row++;
}
MacroConditionUSBEdit::MacroConditionUSBEdit(
QWidget *parent, std::shared_ptr<MacroConditionUSB> entryData)
: QWidget(parent),
_vendorID(new QComboBox()),
_productID(new QComboBox()),
_busNumber(new QComboBox()),
_deviceAddress(new QComboBox()),
_vendorName(new QComboBox()),
_productName(new QComboBox()),
_serialNumber(new QComboBox()),
_vendorIDRegex(new RegexConfigWidget()),
_productIDRegex(new RegexConfigWidget()),
_busNumberRegex(new RegexConfigWidget()),
_deviceAddressRegex(new RegexConfigWidget()),
_vendorNameRegex(new RegexConfigWidget()),
_productNameRegex(new RegexConfigWidget()),
_serialNumberRegex(new RegexConfigWidget())
{
QSet<QString> vendorIDs;
QSet<QString> productIDs;
QSet<QString> busNumbers;
QSet<QString> deviceAddresses;
QSet<QString> vendorNames;
QSet<QString> productNames;
QSet<QString> serialNumbers;
const auto devs = GetUSBDevices();
for (const auto &dev : devs) {
vendorIDs.insert(QString::fromStdString(dev.vendorID));
productIDs.insert(QString::fromStdString(dev.productID));
busNumbers.insert(QString::fromStdString(dev.busNumber));
deviceAddresses.insert(
QString::fromStdString(dev.deviceAddress));
vendorNames.insert(QString::fromStdString(dev.vendorName));
productNames.insert(QString::fromStdString(dev.productName));
serialNumbers.insert(QString::fromStdString(dev.serialNumber));
}
setupDevicePropertySelection(_vendorID, vendorIDs);
setupDevicePropertySelection(_productID, productIDs);
setupDevicePropertySelection(_busNumber, busNumbers);
setupDevicePropertySelection(_deviceAddress, deviceAddresses);
setupDevicePropertySelection(_vendorName, vendorNames);
setupDevicePropertySelection(_productName, productNames);
setupDevicePropertySelection(_serialNumber, serialNumbers);
QWidget::connect(_vendorID, &QComboBox::currentTextChanged, this,
&MacroConditionUSBEdit::VendorIDChanged);
QWidget::connect(_productID, &QComboBox::currentTextChanged, this,
&MacroConditionUSBEdit::ProductIDChanged);
QWidget::connect(_busNumber, &QComboBox::currentTextChanged, this,
&MacroConditionUSBEdit::BusNumberChanged);
QWidget::connect(_deviceAddress, &QComboBox::currentTextChanged, this,
&MacroConditionUSBEdit::DeviceAddressChanged);
QWidget::connect(_vendorName, &QComboBox::currentTextChanged, this,
&MacroConditionUSBEdit::VendorNameChanged);
QWidget::connect(_productName, &QComboBox::currentTextChanged, this,
&MacroConditionUSBEdit::ProductNameChanged);
QWidget::connect(_serialNumber, &QComboBox::currentTextChanged, this,
&MacroConditionUSBEdit::SerialNumberChanged);
QWidget::connect(_vendorIDRegex,
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
SLOT(VendorIDRegexChanged(const RegexConfig &)));
QWidget::connect(_productIDRegex,
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
SLOT(ProductIDRegexChanged(const RegexConfig &)));
QWidget::connect(_busNumberRegex,
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
SLOT(BusNumberRegexChanged(const RegexConfig &)));
QWidget::connect(_deviceAddressRegex,
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
SLOT(DeviceAddressRegexChanged(const RegexConfig &)));
QWidget::connect(_vendorNameRegex,
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
SLOT(VendorNameRegexChanged(const RegexConfig &)));
QWidget::connect(_productNameRegex,
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
SLOT(ProductNameRegexChanged(const RegexConfig &)));
QWidget::connect(_serialNumberRegex,
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
SLOT(SerialNumberRegexChanged(const RegexConfig &)));
int row = 0;
auto devicePropertyLayout = new QGridLayout;
populateNewLayoutRow(devicePropertyLayout, row,
"AdvSceneSwitcher.condition.usb.vendorName",
_vendorName, _vendorNameRegex);
populateNewLayoutRow(devicePropertyLayout, row,
"AdvSceneSwitcher.condition.usb.productName",
_productName, _productNameRegex);
populateNewLayoutRow(devicePropertyLayout, row,
"AdvSceneSwitcher.condition.usb.vendorID",
_vendorID, _vendorIDRegex);
populateNewLayoutRow(devicePropertyLayout, row,
"AdvSceneSwitcher.condition.usb.productID",
_productID, _productIDRegex);
populateNewLayoutRow(devicePropertyLayout, row,
"AdvSceneSwitcher.condition.usb.busNumber",
_busNumber, _busNumberRegex);
populateNewLayoutRow(devicePropertyLayout, row,
"AdvSceneSwitcher.condition.usb.deviceAddress",
_deviceAddress, _deviceAddressRegex);
populateNewLayoutRow(devicePropertyLayout, row,
"AdvSceneSwitcher.condition.usb.serialNumber",
_serialNumber, _serialNumberRegex);
MinimizeSizeOfColumn(devicePropertyLayout, 0);
devicePropertyLayout->setContentsMargins(0, 0, 0, 0);
auto layout = new QVBoxLayout();
layout->addWidget(new QLabel(
obs_module_text("AdvSceneSwitcher.condition.usb.description")));
layout->addLayout(devicePropertyLayout);
if (devs.empty()) {
layout->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.condition.noDevicesFoundWarning")));
}
setLayout(layout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroConditionUSBEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_vendorID->setCurrentText(
QString::fromStdString(_entryData->_vendorID.pattern));
_productID->setCurrentText(
QString::fromStdString(_entryData->_productID.pattern));
_busNumber->setCurrentText(
QString::fromStdString(_entryData->_busNumber.pattern));
_deviceAddress->setCurrentText(
QString::fromStdString(_entryData->_deviceAddress.pattern));
_vendorName->setCurrentText(
QString::fromStdString(_entryData->_vendorName.pattern));
_productName->setCurrentText(
QString::fromStdString(_entryData->_productName.pattern));
_serialNumber->setCurrentText(
QString::fromStdString(_entryData->_serialNumber.pattern));
_vendorIDRegex->SetRegexConfig(_entryData->_vendorID.regex);
_productIDRegex->SetRegexConfig(_entryData->_productID.regex);
_busNumberRegex->SetRegexConfig(_entryData->_busNumber.regex);
_deviceAddressRegex->SetRegexConfig(_entryData->_deviceAddress.regex);
_vendorNameRegex->SetRegexConfig(_entryData->_vendorName.regex);
_productNameRegex->SetRegexConfig(_entryData->_productName.regex);
_serialNumberRegex->SetRegexConfig(_entryData->_serialNumber.regex);
adjustSize();
updateGeometry();
}
void MacroConditionUSBEdit::VendorIDChanged(const QString &text)
{
GUARD_LOADING_AND_LOCK();
_entryData->_vendorID.pattern = text.toStdString();
}
void MacroConditionUSBEdit::ProductIDChanged(const QString &text)
{
GUARD_LOADING_AND_LOCK();
_entryData->_productID.pattern = text.toStdString();
}
void MacroConditionUSBEdit::BusNumberChanged(const QString &text)
{
GUARD_LOADING_AND_LOCK();
_entryData->_busNumber.pattern = text.toStdString();
}
void MacroConditionUSBEdit::DeviceAddressChanged(const QString &text)
{
GUARD_LOADING_AND_LOCK();
_entryData->_deviceAddress.pattern = text.toStdString();
}
void MacroConditionUSBEdit::VendorNameChanged(const QString &text)
{
GUARD_LOADING_AND_LOCK();
_entryData->_vendorName.pattern = text.toStdString();
}
void MacroConditionUSBEdit::ProductNameChanged(const QString &text)
{
GUARD_LOADING_AND_LOCK();
_entryData->_productName.pattern = text.toStdString();
}
void MacroConditionUSBEdit::SerialNumberChanged(const QString &text)
{
GUARD_LOADING_AND_LOCK();
_entryData->_serialNumber.pattern = text.toStdString();
}
void MacroConditionUSBEdit::VendorIDRegexChanged(const RegexConfig &regex)
{
GUARD_LOADING_AND_LOCK();
_entryData->_vendorID.regex = regex;
}
void MacroConditionUSBEdit::ProductIDRegexChanged(const RegexConfig &regex)
{
GUARD_LOADING_AND_LOCK();
_entryData->_productID.regex = regex;
}
void MacroConditionUSBEdit::BusNumberRegexChanged(const RegexConfig &regex)
{
GUARD_LOADING_AND_LOCK();
_entryData->_busNumber.regex = regex;
}
void MacroConditionUSBEdit::DeviceAddressRegexChanged(const RegexConfig &regex)
{
GUARD_LOADING_AND_LOCK();
_entryData->_deviceAddress.regex = regex;
}
void MacroConditionUSBEdit::VendorNameRegexChanged(const RegexConfig &regex)
{
GUARD_LOADING_AND_LOCK();
_entryData->_vendorName.regex = regex;
}
void MacroConditionUSBEdit::ProductNameRegexChanged(const RegexConfig &regex)
{
GUARD_LOADING_AND_LOCK();
_entryData->_productName.regex = regex;
}
void MacroConditionUSBEdit::SerialNumberRegexChanged(const RegexConfig &regex)
{
GUARD_LOADING_AND_LOCK();
_entryData->_serialNumber.regex = regex;
}
} // namespace advss

View File

@@ -0,0 +1,97 @@
#pragma once
#include "macro-condition-edit.hpp"
#include "usb-helpers.hpp"
#include "regex-config.hpp"
#include <QPushButton>
namespace advss {
class MacroConditionUSB : public MacroCondition {
public:
MacroConditionUSB(Macro *m) : MacroCondition(m, true) {}
bool CheckCondition();
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
std::string GetId() const { return id; };
static std::shared_ptr<MacroCondition> Create(Macro *m)
{
return std::make_shared<MacroConditionUSB>(m);
}
struct DeviceMatchOption {
std::string pattern = ".*";
RegexConfig regex = RegexConfig(true);
bool Matches(const std::string &value) const;
void Save(obs_data_t *, const char *) const;
void Load(obs_data_t *, const char *);
};
DeviceMatchOption _vendorID;
DeviceMatchOption _productID;
DeviceMatchOption _busNumber;
DeviceMatchOption _deviceAddress;
DeviceMatchOption _vendorName;
DeviceMatchOption _productName;
DeviceMatchOption _serialNumber;
private:
void SetupTempVars();
static bool _registered;
static const std::string id;
};
class MacroConditionUSBEdit : public QWidget {
Q_OBJECT
public:
MacroConditionUSBEdit(QWidget *parent,
std::shared_ptr<MacroConditionUSB> cond = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
return new MacroConditionUSBEdit(
parent,
std::dynamic_pointer_cast<MacroConditionUSB>(cond));
}
private slots:
void VendorIDChanged(const QString &text);
void ProductIDChanged(const QString &text);
void BusNumberChanged(const QString &text);
void DeviceAddressChanged(const QString &text);
void VendorNameChanged(const QString &text);
void ProductNameChanged(const QString &text);
void SerialNumberChanged(const QString &text);
void VendorIDRegexChanged(const RegexConfig &regex);
void ProductIDRegexChanged(const RegexConfig &regex);
void BusNumberRegexChanged(const RegexConfig &regex);
void DeviceAddressRegexChanged(const RegexConfig &regex);
void VendorNameRegexChanged(const RegexConfig &regex);
void ProductNameRegexChanged(const RegexConfig &regex);
void SerialNumberRegexChanged(const RegexConfig &regex);
private:
QComboBox *_vendorID;
QComboBox *_productID;
QComboBox *_busNumber;
QComboBox *_deviceAddress;
QComboBox *_vendorName;
QComboBox *_productName;
QComboBox *_serialNumber;
RegexConfigWidget *_vendorIDRegex;
RegexConfigWidget *_productIDRegex;
RegexConfigWidget *_busNumberRegex;
RegexConfigWidget *_deviceAddressRegex;
RegexConfigWidget *_vendorNameRegex;
RegexConfigWidget *_productNameRegex;
RegexConfigWidget *_serialNumberRegex;
std::shared_ptr<MacroConditionUSB> _entryData;
bool _loading = true;
};
} // namespace advss

233
plugins/usb/usb-helpers.cpp Normal file
View File

@@ -0,0 +1,233 @@
#include "usb-helpers.hpp"
#include "log-helper.hpp"
#include "plugin-state-helpers.hpp"
#include <chrono>
#include <libusb.h>
#include <mutex>
#define LOG_PREFIX "[usb] "
namespace advss {
static bool setup();
static bool setupDone = setup();
static bool hotplugsAreSupported = false;
static std::mutex mutex;
static bool setup()
{
AddPluginInitStep([]() {
libusb_init(NULL);
hotplugsAreSupported =
libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG);
});
AddPluginCleanupStep([]() { libusb_exit(NULL); });
return true;
}
static void logLibusbError(int value, const char *msg)
{
if (value >= LIBUSB_SUCCESS) {
return;
}
vblog(LOG_WARNING, LOG_PREFIX "%s: %s", msg, libusb_strerror(value));
}
static USBDeviceInfo
getDeviceInfo(libusb_device *device, libusb_device_handle *handle,
const struct libusb_device_descriptor &descriptor)
{
char vendor_name[256] = {};
char product_name[256] = {};
char serial_number[256] = {};
int ret = libusb_get_string_descriptor_ascii(handle,
descriptor.iManufacturer,
(uint8_t *)vendor_name,
sizeof(vendor_name));
logLibusbError(ret, "Failed to query vendor name");
ret = libusb_get_string_descriptor_ascii(handle, descriptor.iProduct,
(uint8_t *)product_name,
sizeof(product_name));
logLibusbError(ret, "Failed to query product name");
ret = libusb_get_string_descriptor_ascii(handle,
descriptor.iSerialNumber,
(uint8_t *)serial_number,
sizeof(serial_number));
logLibusbError(ret, "Failed to query serial number");
const USBDeviceInfo deviceInfo = {
std::to_string(descriptor.idVendor),
std::to_string(descriptor.idProduct),
std::to_string(libusb_get_bus_number(device)),
std::to_string(libusb_get_device_address(device)),
vendor_name,
product_name,
serial_number};
return deviceInfo;
}
static std::vector<USBDeviceInfo> pollUSBDevices()
{
libusb_device **devices;
ssize_t count = libusb_get_device_list(NULL, &devices);
if (count < 0) {
logLibusbError(count, "Failed to query device list");
return {};
}
std::vector<USBDeviceInfo> result;
for (int i = 0; i < count; i++) {
libusb_device *device = devices[i];
struct libusb_device_descriptor descriptor;
int ret = libusb_get_device_descriptor(device, &descriptor);
if (ret != LIBUSB_SUCCESS) {
logLibusbError(ret, "Error getting device descriptor");
continue;
}
libusb_device_handle *handle;
ret = libusb_open(device, &handle);
if (ret != LIBUSB_SUCCESS) {
logLibusbError(ret, "Error opening device");
continue;
}
result.emplace_back(getDeviceInfo(device, handle, descriptor));
libusb_close(handle);
}
libusb_free_device_list(devices, 1);
return result;
}
static int hotplugCallback(struct libusb_context *ctx,
struct libusb_device *device,
libusb_hotplug_event event, void *user_data)
{
auto devices = static_cast<std::vector<USBDeviceInfo> *>(user_data);
static libusb_device_handle *handle = nullptr;
struct libusb_device_descriptor descriptor;
int ret = libusb_get_device_descriptor(device, &descriptor);
logLibusbError(ret, "Error getting device descriptor");
ret = libusb_open(device, &handle);
if (ret != LIBUSB_SUCCESS) {
logLibusbError(ret, "Error opening device");
return 0;
}
const auto deviceInfo = getDeviceInfo(device, handle, descriptor);
std::lock_guard<std::mutex> lock(mutex);
if (LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED == event) {
devices->emplace_back(deviceInfo);
} else if (LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT == event) {
devices->erase(std::find(devices->begin(), devices->end(),
deviceInfo));
}
libusb_close(handle);
return 0;
}
static std::vector<USBDeviceInfo> getHotplugBasedDeviceList()
{
static std::vector<USBDeviceInfo> devices;
static bool hotplugSetupDone = false;
if (!hotplugSetupDone) {
hotplugSetupDone = true;
devices = pollUSBDevices();
int ret = libusb_hotplug_register_callback(
nullptr,
LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED |
LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT,
0, LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY,
LIBUSB_HOTPLUG_MATCH_ANY, hotplugCallback, &devices,
nullptr);
if (ret != LIBUSB_SUCCESS) {
hotplugsAreSupported = false;
} else {
blog(LOG_WARNING, LOG_PREFIX "hotplug supported!");
}
}
std::lock_guard<std::mutex> lock(mutex);
return devices;
}
static std::vector<USBDeviceInfo> getPollingBasedDeviceList()
{
// Polling can be very expensive
// Perform this operation at most once every 10 seconds
static const int timeout = 10;
static std::vector<USBDeviceInfo> deviceList = {};
static std::chrono::high_resolution_clock::time_point lastUpdate = {};
std::lock_guard<std::mutex> lock(mutex);
auto now = std::chrono::high_resolution_clock::now();
if (std::chrono::duration_cast<std::chrono::seconds>(now - lastUpdate)
.count() >= timeout) {
deviceList = pollUSBDevices();
lastUpdate = now;
}
return deviceList;
}
std::vector<USBDeviceInfo> GetUSBDevices()
{
// Hotplug events do not seem to be firing consistently during testing
// on Linux so for now only rely on polling based functionality instead
return getPollingBasedDeviceList();
/*
if (hotplugsAreSupported) {
return getHotplugBasedDeviceList();
} else {
return getPollingBasedDeviceList();
}
*/
}
QStringList GetUSBDevicesStringList()
{
QStringList result;
const auto devices = GetUSBDevices();
for (const auto &device : devices) {
result << device.ToQString();
}
return result;
}
std::string USBDeviceInfo::ToString() const
{
return "Vendor ID: " + vendorID + "\nProduct ID: " + productID +
"\nBus Number:" + busNumber +
"\nDevice Address:" + deviceAddress +
"\nVendor Name:" + vendorName + "\nProduct Name:" + productName +
"\nSerial Number:" + serialNumber;
}
QString USBDeviceInfo::ToQString() const
{
return QString::fromStdString(ToString());
}
bool USBDeviceInfo::operator==(const USBDeviceInfo &other)
{
return vendorID == other.vendorID && productID == other.productID &&
busNumber == other.busNumber &&
deviceAddress == other.deviceAddress &&
vendorName == other.vendorName &&
productName == other.productName &&
serialNumber == other.serialNumber;
}
} // namespace advss

View File

@@ -0,0 +1,27 @@
#pragma once
#include <QString>
#include <QStringList>
#include <string>
#include <vector>
namespace advss {
struct USBDeviceInfo {
std::string vendorID;
std::string productID;
std::string busNumber;
std::string deviceAddress;
std::string vendorName;
std::string productName;
std::string serialNumber;
std::string ToString() const;
QString ToQString() const;
bool operator==(const USBDeviceInfo &other);
};
std::vector<USBDeviceInfo> GetUSBDevices();
QStringList GetUSBDevicesStringList();
} // namespace advss

View File

@@ -12,7 +12,7 @@ QObject *HighlightWidget(QWidget *widget, QColor startColor, QColor endColor,
void SetHeightToContentHeight(QListWidget *list) {}
void SetButtonIcon(QPushButton *button, const char *path) {}
void SetButtonIcon(QAbstractButton *button, const char *path) {}
int FindIdxInRagne(QComboBox *list, int start, int stop,
const std::string &value, Qt::MatchFlags flags)