mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-08-29 04:36:21 -05:00
Compare commits
24 Commits
lock
...
item-trans
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c977a2e2f | ||
|
|
0f1273ab4c | ||
|
|
67f6b1b459 | ||
|
|
a04c51397d | ||
|
|
2073ef6ce9 | ||
|
|
7dfd9ddcb5 | ||
|
|
6215c39024 | ||
|
|
1c856911e8 | ||
|
|
ed17c2ac6d | ||
|
|
1ce332a91d | ||
|
|
2aa27fc051 | ||
|
|
31b239d5dc | ||
|
|
7c3779e287 | ||
|
|
3f13c33b10 | ||
|
|
8727a65513 | ||
|
|
baf3d0e63c | ||
|
|
ea2d1e7e9d | ||
|
|
f9f26ad50b | ||
|
|
9911c41a35 | ||
|
|
d590bc11f8 | ||
|
|
31c4c1f5d7 | ||
|
|
42ff48d637 | ||
|
|
d44376e869 | ||
|
|
4b1fd9e700 |
90
.github/scripts/Package-Windows.ps1
vendored
90
.github/scripts/Package-Windows.ps1
vendored
@@ -68,44 +68,53 @@ function Package {
|
||||
|
||||
Remove-Item @RemoveArgs
|
||||
|
||||
# Build a staging directory with two subfolders:
|
||||
# recommended/ - new layout, extract to %ProgramData%\obs-studio\
|
||||
# legacy/ - old layout, extract to the OBS install directory
|
||||
$ReleasePath = "${ProjectRoot}/release/${Configuration}"
|
||||
$StagingPath = "${ProjectRoot}/release/zip-staging"
|
||||
$NewBinPath = "${ReleasePath}/${ProductName}/bin/64bit"
|
||||
$NewDataPath = "${ReleasePath}/${ProductName}/data"
|
||||
|
||||
Remove-Item -Path $StagingPath -Recurse -Force -ErrorAction SilentlyContinue
|
||||
New-Item -ItemType Directory -Force -Path $StagingPath | Out-Null
|
||||
Copy-Item -Path "${ProjectRoot}/build-aux/CI/windows/README.txt" -Destination "${StagingPath}/README.txt"
|
||||
$ReleasePath = "${ProjectRoot}/release/${Configuration}"
|
||||
$NewBinPath = "${ReleasePath}/${ProductName}/bin/64bit"
|
||||
$NewDataPath = "${ReleasePath}/${ProductName}/data"
|
||||
$CIWindowsDir = "${ProjectRoot}/build-aux/CI/windows"
|
||||
|
||||
# --- Recommended zip (new layout, extract to %ProgramData%\obs-studio\) ---
|
||||
Log-Group "Archiving ${ProductName} (recommended)..."
|
||||
$RecStaging = "${ProjectRoot}/release/zip-staging-rec"
|
||||
Remove-Item -Path $RecStaging -Recurse -Force -ErrorAction SilentlyContinue
|
||||
New-Item -ItemType Directory -Force -Path $RecStaging | Out-Null
|
||||
Copy-Item -Path "${CIWindowsDir}/README.txt" -Destination "${RecStaging}/README.txt"
|
||||
if ( Test-Path -Path $NewBinPath ) {
|
||||
$RecBinPath = "${StagingPath}/recommended/${ProductName}/bin/64bit"
|
||||
$LegacyBinPath = "${StagingPath}/legacy/obs-plugins/64bit"
|
||||
New-Item -ItemType Directory -Force -Path $RecBinPath | Out-Null
|
||||
New-Item -ItemType Directory -Force -Path $LegacyBinPath | Out-Null
|
||||
Copy-Item -Path "${NewBinPath}/*" -Destination $RecBinPath -Recurse -Force
|
||||
Copy-Item -Path "${NewBinPath}/*" -Destination $LegacyBinPath -Recurse -Force
|
||||
$RecBinPath = "${RecStaging}/${ProductName}/bin/64bit"
|
||||
New-Item -ItemType Directory -Force -Path $RecBinPath | Out-Null
|
||||
Copy-Item -Path "${NewBinPath}/*" -Destination $RecBinPath -Recurse -Force
|
||||
}
|
||||
if ( Test-Path -Path $NewDataPath ) {
|
||||
$RecDataPath = "${StagingPath}/recommended/${ProductName}/data"
|
||||
$LegacyDataPath = "${StagingPath}/legacy/data/obs-plugins/${ProductName}"
|
||||
New-Item -ItemType Directory -Force -Path $RecDataPath | Out-Null
|
||||
New-Item -ItemType Directory -Force -Path $LegacyDataPath | Out-Null
|
||||
Copy-Item -Path "${NewDataPath}/*" -Destination $RecDataPath -Recurse -Force
|
||||
Copy-Item -Path "${NewDataPath}/*" -Destination $LegacyDataPath -Recurse -Force
|
||||
$RecDataPath = "${RecStaging}/${ProductName}/data"
|
||||
New-Item -ItemType Directory -Force -Path $RecDataPath | Out-Null
|
||||
Copy-Item -Path "${NewDataPath}/*" -Destination $RecDataPath -Recurse -Force
|
||||
}
|
||||
Compress-Archive -Force -Path (Get-ChildItem -Path $RecStaging) `
|
||||
-CompressionLevel Optimal `
|
||||
-DestinationPath "${ProjectRoot}/release/${OutputName}.zip"
|
||||
Remove-Item -Path $RecStaging -Recurse -Force
|
||||
Log-Group
|
||||
|
||||
Log-Group "Archiving ${ProductName}..."
|
||||
$CompressArgs = @{
|
||||
Path = (Get-ChildItem -Path $StagingPath)
|
||||
CompressionLevel = 'Optimal'
|
||||
DestinationPath = "${ProjectRoot}/release/${OutputName}.zip"
|
||||
Verbose = ($null -ne $Env:CI)
|
||||
# --- Legacy zip (old layout, extract to OBS install directory) ---
|
||||
Log-Group "Archiving ${ProductName} (legacy)..."
|
||||
$LegStaging = "${ProjectRoot}/release/zip-staging-leg"
|
||||
Remove-Item -Path $LegStaging -Recurse -Force -ErrorAction SilentlyContinue
|
||||
New-Item -ItemType Directory -Force -Path $LegStaging | Out-Null
|
||||
Copy-Item -Path "${CIWindowsDir}/README-legacy.txt" -Destination "${LegStaging}/README.txt"
|
||||
if ( Test-Path -Path $NewBinPath ) {
|
||||
$LegBinPath = "${LegStaging}/obs-plugins/64bit"
|
||||
New-Item -ItemType Directory -Force -Path $LegBinPath | Out-Null
|
||||
Copy-Item -Path "${NewBinPath}/*" -Destination $LegBinPath -Recurse -Force
|
||||
}
|
||||
Compress-Archive -Force @CompressArgs
|
||||
Remove-Item -Path $StagingPath -Recurse -Force
|
||||
if ( Test-Path -Path $NewDataPath ) {
|
||||
$LegDataPath = "${LegStaging}/data/obs-plugins/${ProductName}"
|
||||
New-Item -ItemType Directory -Force -Path $LegDataPath | Out-Null
|
||||
Copy-Item -Path "${NewDataPath}/*" -Destination $LegDataPath -Recurse -Force
|
||||
}
|
||||
Compress-Archive -Force -Path (Get-ChildItem -Path $LegStaging) `
|
||||
-CompressionLevel Optimal `
|
||||
-DestinationPath "${ProjectRoot}/release/${OutputName}-legacy.zip"
|
||||
Remove-Item -Path $LegStaging -Recurse -Force
|
||||
Log-Group
|
||||
|
||||
if ( ( $BuildInstaller ) ) {
|
||||
@@ -120,7 +129,24 @@ function Package {
|
||||
Push-Location -Stack BuildTemp
|
||||
Ensure-Location -Path "${ProjectRoot}/release"
|
||||
Remove-Item -Path Package -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Copy-Item -Path ${Configuration} -Destination Package -Recurse
|
||||
|
||||
# Recommended layout (for %ProgramData%\obs-studio\plugins\)
|
||||
$PkgRec = "Package/recommended"
|
||||
New-Item -ItemType Directory -Force -Path $PkgRec | Out-Null
|
||||
Copy-Item -Path "${Configuration}/*" -Destination $PkgRec -Recurse -Force
|
||||
|
||||
# Legacy layout (for OBS installation directory)
|
||||
if ( Test-Path "${Configuration}/${ProductName}/bin/64bit" ) {
|
||||
$PkgLegBin = "Package/legacy/obs-plugins/64bit"
|
||||
New-Item -ItemType Directory -Force -Path $PkgLegBin | Out-Null
|
||||
Copy-Item -Path "${Configuration}/${ProductName}/bin/64bit/*" -Destination $PkgLegBin -Recurse -Force
|
||||
}
|
||||
if ( Test-Path "${Configuration}/${ProductName}/data" ) {
|
||||
$PkgLegData = "Package/legacy/data/obs-plugins/${ProductName}"
|
||||
New-Item -ItemType Directory -Force -Path $PkgLegData | Out-Null
|
||||
Copy-Item -Path "${Configuration}/${ProductName}/data/*" -Destination $PkgLegData -Recurse -Force
|
||||
}
|
||||
|
||||
Invoke-External iscc ${IsccFile} /O"${ProjectRoot}/release" /F"${OutputName}-Installer"
|
||||
Remove-Item -Path Package -Recurse
|
||||
Pop-Location -Stack BuildTemp
|
||||
|
||||
23
build-aux/CI/windows/README-legacy.txt
Normal file
23
build-aux/CI/windows/README-legacy.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
Advanced Scene Switcher - Windows Installation (Legacy)
|
||||
========================================================
|
||||
|
||||
For full installation instructions visit:
|
||||
https://github.com/WarmUpTill/SceneSwitcher/wiki/Installation
|
||||
|
||||
|
||||
This archive uses the legacy plugin layout for older OBS versions,
|
||||
portable OBS installs, and Steam.
|
||||
|
||||
Extract the CONTENTS of this archive into your OBS installation directory,
|
||||
typically:
|
||||
|
||||
C:\Program Files\obs-studio\
|
||||
|
||||
Merge the data\ and obs-plugins\ folders with the existing ones when prompted.
|
||||
|
||||
Steam users: right-click OBS in your library -> Manage -> Browse local files,
|
||||
then extract the contents of this archive into that directory.
|
||||
|
||||
|
||||
For OBS 28 and newer the recommended archive is preferred:
|
||||
advanced-scene-switcher-<version>-windows-x64.zip
|
||||
@@ -1,22 +1,15 @@
|
||||
Advanced Scene Switcher - Windows Installation
|
||||
==============================================
|
||||
===============================================
|
||||
|
||||
For full installation instructions visit:
|
||||
https://github.com/WarmUpTill/SceneSwitcher/wiki/Installation
|
||||
|
||||
|
||||
Quick guide
|
||||
-----------
|
||||
This archive uses the recommended plugin layout for OBS 28 and newer.
|
||||
|
||||
This archive contains two installation methods.
|
||||
Choose ONE depending on your OBS version.
|
||||
Extract the CONTENTS of this archive into:
|
||||
|
||||
|
||||
recommended\ (preferred)
|
||||
---------------------------------------------
|
||||
Extract the CONTENTS of this folder into:
|
||||
|
||||
C:\ProgramData\obs-studio\
|
||||
C:\ProgramData\obs-studio\plugins\
|
||||
|
||||
Tip: press Win+R and type %ProgramData%\obs-studio\plugins to open that folder.
|
||||
|
||||
@@ -24,14 +17,5 @@ After extracting, the path should look like:
|
||||
C:\ProgramData\obs-studio\plugins\advanced-scene-switcher\bin\64bit\advanced-scene-switcher.dll
|
||||
|
||||
|
||||
legacy\ (older OBS versions, portable OBS installs, or if the recommended method does not work)
|
||||
---------------------------------------------------------------------------
|
||||
Extract the CONTENTS of this folder into your OBS installation directory,
|
||||
typically:
|
||||
|
||||
C:\Program Files\obs-studio\
|
||||
|
||||
Merge the data\ and obs-plugins\ folders with the existing ones when prompted.
|
||||
|
||||
Steam users: right-click OBS in your library -> Manage -> Browse local files,
|
||||
then extract the contents of legacy\ into that directory.
|
||||
If the plugin does not appear in OBS, try the legacy archive instead:
|
||||
advanced-scene-switcher-<version>-windows-x64-legacy.zip
|
||||
|
||||
@@ -157,7 +157,9 @@ else()
|
||||
set_target_properties(${target} PROPERTIES INSTALL_RPATH
|
||||
"$ORIGIN:$ORIGIN/..")
|
||||
endif()
|
||||
message(STATUS "ADVSS: ENABLED PLUGIN ${target}")
|
||||
get_filename_component(_plugin_dir_name "${CMAKE_CURRENT_SOURCE_DIR}" NAME)
|
||||
set_property(GLOBAL APPEND PROPERTY ADVSS_PLUGINS_ENABLED
|
||||
${_plugin_dir_name})
|
||||
endfunction()
|
||||
|
||||
function(install_advss_plugin_dependency_target target dep)
|
||||
|
||||
@@ -6,7 +6,11 @@ include(buildspec_common)
|
||||
|
||||
# _check_dependencies_windows: Set up Windows slice for _check_dependencies
|
||||
function(_check_dependencies_windows)
|
||||
set(arch ${CMAKE_GENERATOR_PLATFORM})
|
||||
if(CMAKE_GENERATOR_PLATFORM)
|
||||
set(arch ${CMAKE_GENERATOR_PLATFORM})
|
||||
else()
|
||||
set(arch x64)
|
||||
endif()
|
||||
set(platform windows-${arch})
|
||||
|
||||
set(dependencies_dir "${CMAKE_CURRENT_SOURCE_DIR}/.deps")
|
||||
|
||||
@@ -23,14 +23,17 @@ SolidCompression=yes
|
||||
DirExistsWarning=no
|
||||
UsePreviousAppDir=no
|
||||
SetupIconFile=installer.ico
|
||||
UninstallDisplayIcon={app}\advanced-scene-switcher\data\res\images\logo.ico
|
||||
UninstallDisplayIcon={code:GetUninstallIconPath}
|
||||
DisableDirPage=no
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
[Files]
|
||||
Source: "..\release\Package\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
; Recommended layout - used when installing to %ProgramData%\obs-studio\plugins\
|
||||
Source: "..\release\Package\recommended\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Check: IsRecommendedLayout
|
||||
; Legacy layout - used when installing to the OBS installation directory
|
||||
Source: "..\release\Package\legacy\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Check: IsLegacyLayout
|
||||
Source: "..\LICENSE"; Flags: dontcopy
|
||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||
|
||||
@@ -53,23 +56,54 @@ begin
|
||||
);
|
||||
end;
|
||||
|
||||
// Returns true when the selected install directory is under %ProgramData%,
|
||||
// meaning the recommended plugin layout should be used.
|
||||
function IsRecommendedLayout(): Boolean;
|
||||
begin
|
||||
Result := Pos(LowerCase(ExpandConstant('{commonappdata}')),
|
||||
LowerCase(WizardDirValue())) = 1;
|
||||
end;
|
||||
|
||||
function IsLegacyLayout(): Boolean;
|
||||
begin
|
||||
Result := not IsRecommendedLayout();
|
||||
end;
|
||||
|
||||
function GetUninstallIconPath(Param: string): string;
|
||||
begin
|
||||
if IsRecommendedLayout() then
|
||||
Result := ExpandConstant('{app}\advanced-scene-switcher\data\res\images\logo.ico')
|
||||
else
|
||||
Result := ExpandConstant('{app}\data\obs-plugins\advanced-scene-switcher\res\images\logo.ico');
|
||||
end;
|
||||
|
||||
function GetDirName(Value: string): string;
|
||||
var
|
||||
PrevPath: string;
|
||||
UninstallKey: string;
|
||||
begin
|
||||
Result := ExpandConstant('{commonappdata}\obs-studio\plugins');
|
||||
UninstallKey := 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + '{' + '{#MyAppId}' + '}_is1';
|
||||
if RegQueryStringValue(HKLM, UninstallKey, 'InstallLocation', PrevPath) and (PrevPath <> '') then
|
||||
Result := PrevPath;
|
||||
end;
|
||||
|
||||
// Remove files placed by the legacy installer (obs-plugins/64bit and
|
||||
// data/obs-plugins layout) so the plugin is not loaded twice by OBS.
|
||||
// We read InstallLocation from our own previous uninstall entry, which is
|
||||
// the exact directory the old installer used regardless of OBS install path.
|
||||
// Only runs when upgrading from a legacy location to the recommended one.
|
||||
procedure RemoveLegacyFiles();
|
||||
var
|
||||
OldInstallPath: string;
|
||||
UninstallKey: string;
|
||||
begin
|
||||
if not IsRecommendedLayout() then
|
||||
Exit;
|
||||
UninstallKey := 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + '{' + '{#MyAppId}' + '}_is1';
|
||||
if not RegQueryStringValue(HKLM, UninstallKey, 'InstallLocation', OldInstallPath) then
|
||||
Exit;
|
||||
// Skip if the previous install was already in the recommended location
|
||||
if Pos(LowerCase(ExpandConstant('{commonappdata}')), LowerCase(OldInstallPath)) = 1 then
|
||||
Exit;
|
||||
DeleteFile(OldInstallPath + '\obs-plugins\64bit\advanced-scene-switcher.dll');
|
||||
DeleteFile(OldInstallPath + '\obs-plugins\64bit\advanced-scene-switcher.pdb');
|
||||
DeleteFile(OldInstallPath + '\obs-plugins\64bit\advanced-scene-switcher-lib.dll');
|
||||
@@ -83,4 +117,3 @@ begin
|
||||
if CurStep = ssInstall then
|
||||
RemoveLegacyFiles();
|
||||
end;
|
||||
|
||||
|
||||
@@ -47,6 +47,13 @@ AdvSceneSwitcher.generalTab.generalBehavior.hideLegacyTabs="Hide tabs which can
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.alwaysShowMacroSearch="Always show macro search"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.alwaysShowFeatureTabs="Always show all feature tabs"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.alwaysShowFeatureTabs.tooltip="Show tabs that are normally hidden until first used in a macro (e.g. Action Queues, Variables, Twitch, Websockets)."
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.settingsLock="Lock settings dialog with password"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.title="Settings Locked"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.passwordLabel="Enter password:"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.wrongPassword="Wrong password!"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.newPasswordLabel="New password:"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.confirmPasswordLabel="Confirm password:"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.passwordMismatch="Passwords do not match!"
|
||||
AdvSceneSwitcher.generalTab.matchBehavior="Match behavior"
|
||||
AdvSceneSwitcher.generalTab.priority="Priority"
|
||||
AdvSceneSwitcher.generalTab.priority.description="Switching methods priority (Highest priority is at the top)"
|
||||
@@ -2539,6 +2546,7 @@ AdvSceneSwitcher.selectCurrentScene="Current Scene"
|
||||
AdvSceneSwitcher.selectPreviewScene="Preview Scene"
|
||||
AdvSceneSwitcher.selectAnyScene="Any Scene"
|
||||
AdvSceneSwitcher.currentTransition="Current Transition"
|
||||
AdvSceneSwitcher.noneTransition="None"
|
||||
AdvSceneSwitcher.anyTransition="Any Transition"
|
||||
AdvSceneSwitcher.selectTransition="--select transition--"
|
||||
AdvSceneSwitcher.selectWindow="--select window--"
|
||||
|
||||
2
deps/cpp-httplib
vendored
2
deps/cpp-httplib
vendored
Submodule deps/cpp-httplib updated: 811dd0b6f2...008e107d0f
@@ -258,6 +258,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="settingsLock">
|
||||
<property name="text">
|
||||
<string>AdvSceneSwitcher.generalTab.generalBehavior.settingsLock</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -19,8 +19,10 @@
|
||||
#include <obs-frontend-api.h>
|
||||
#include <QAction>
|
||||
#include <QDirIterator>
|
||||
#include <QInputDialog>
|
||||
#include <QLibrary>
|
||||
#include <QMainWindow>
|
||||
#include <QMessageBox>
|
||||
#include <QTextStream>
|
||||
#include <regex>
|
||||
|
||||
@@ -459,12 +461,13 @@ void SwitcherData::Stop()
|
||||
SetMacroAbortWait(true);
|
||||
GetMacroWaitCV().notify_all();
|
||||
GetMacroTransitionCV().notify_all();
|
||||
StopAllMacros();
|
||||
SignalStopAllMacros();
|
||||
StopAndClearAllActionQueues();
|
||||
CloseAllInputDialogs();
|
||||
th->wait();
|
||||
delete th;
|
||||
th = nullptr;
|
||||
WaitForAllMacros();
|
||||
RunStopSteps();
|
||||
}
|
||||
|
||||
@@ -718,19 +721,54 @@ static void LoadPlugins()
|
||||
}
|
||||
}
|
||||
|
||||
static bool settingsPasswordIsCorrect(QWidget *parent)
|
||||
{
|
||||
bool ok;
|
||||
QString input = QInputDialog::getText(
|
||||
parent,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.title"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.passwordLabel"),
|
||||
QLineEdit::Password, QString(), &ok);
|
||||
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (input.toStdString() == switcher->settingsLockPassword) {
|
||||
return true;
|
||||
}
|
||||
|
||||
QMessageBox::warning(
|
||||
parent,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.title"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.wrongPassword"));
|
||||
return false;
|
||||
}
|
||||
|
||||
void OpenSettingsWindow()
|
||||
{
|
||||
if (switcher->settingsWindowOpened) {
|
||||
AdvSceneSwitcher::window->show();
|
||||
AdvSceneSwitcher::window->raise();
|
||||
AdvSceneSwitcher::window->activateWindow();
|
||||
} else {
|
||||
AdvSceneSwitcher::window =
|
||||
new AdvSceneSwitcher(static_cast<QMainWindow *>(
|
||||
obs_frontend_get_main_window()));
|
||||
AdvSceneSwitcher::window->setAttribute(Qt::WA_DeleteOnClose);
|
||||
AdvSceneSwitcher::window->show();
|
||||
return;
|
||||
}
|
||||
|
||||
auto parent =
|
||||
static_cast<QMainWindow *>(obs_frontend_get_main_window());
|
||||
if (switcher->settingsLockEnabled &&
|
||||
!switcher->settingsLockPassword.empty() &&
|
||||
!settingsPasswordIsCorrect(parent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AdvSceneSwitcher::window = new AdvSceneSwitcher(parent);
|
||||
AdvSceneSwitcher::window->setAttribute(Qt::WA_DeleteOnClose);
|
||||
AdvSceneSwitcher::window->show();
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::HighlightMacroSettingsButton(bool enable)
|
||||
|
||||
@@ -68,6 +68,7 @@ public slots:
|
||||
void on_warnPluginLoadFailure_stateChanged(int state);
|
||||
void on_suppressCrashRecoveryDialog_stateChanged(int state);
|
||||
void on_hideLegacyTabs_stateChanged(int state);
|
||||
void on_settingsLock_stateChanged(int state);
|
||||
void on_priorityUp_clicked();
|
||||
void on_priorityDown_clicked();
|
||||
void on_threadPriority_currentTextChanged(const QString &text);
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
#include <obs-frontend-api.h>
|
||||
#include <QFileDialog>
|
||||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
@@ -243,6 +245,64 @@ void AdvSceneSwitcher::on_hideLegacyTabs_stateChanged(int state)
|
||||
ui->priorityBox->setVisible(!switcher->hideLegacyTabs);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_settingsLock_stateChanged(int state)
|
||||
{
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!state) {
|
||||
switcher->settingsLockEnabled = false;
|
||||
switcher->settingsLockPassword = "";
|
||||
return;
|
||||
}
|
||||
|
||||
bool ok;
|
||||
QString newPassword = QInputDialog::getText(
|
||||
this,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.title"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.newPasswordLabel"),
|
||||
QLineEdit::Password, QString(), &ok);
|
||||
|
||||
const auto disablePassword = [this]() {
|
||||
QSignalBlocker blocker(ui->settingsLock);
|
||||
ui->settingsLock->setChecked(false);
|
||||
ui->settingsLock->setCheckState(Qt::Unchecked);
|
||||
switcher->settingsLockEnabled = false;
|
||||
switcher->settingsLockPassword = "";
|
||||
};
|
||||
|
||||
if (!ok || newPassword.isEmpty()) {
|
||||
disablePassword();
|
||||
return;
|
||||
}
|
||||
|
||||
QString confirm = QInputDialog::getText(
|
||||
this,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.title"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.confirmPasswordLabel"),
|
||||
QLineEdit::Password, QString(), &ok);
|
||||
if (!ok || newPassword != confirm) {
|
||||
if (ok) {
|
||||
QMessageBox::warning(
|
||||
this,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.title"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.passwordMismatch"));
|
||||
}
|
||||
disablePassword();
|
||||
return;
|
||||
}
|
||||
|
||||
switcher->settingsLockEnabled = true;
|
||||
switcher->settingsLockPassword = newPassword.toStdString();
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::SetDeprecationWarnings()
|
||||
{
|
||||
QString toolTip =
|
||||
@@ -567,6 +627,9 @@ void SwitcherData::SaveGeneralSettings(obs_data_t *obj)
|
||||
disableFilterComboboxFilter);
|
||||
obs_data_set_bool(obj, "warnPluginLoadFailure", warnPluginLoadFailure);
|
||||
obs_data_set_bool(obj, "hideLegacyTabs", hideLegacyTabs);
|
||||
obs_data_set_bool(obj, "settingsLockEnabled", settingsLockEnabled);
|
||||
obs_data_set_string(obj, "settingsLockPassword",
|
||||
settingsLockPassword.c_str());
|
||||
|
||||
SaveFunctionPriorities(obj, functionNamesByPriority);
|
||||
|
||||
@@ -638,6 +701,8 @@ void SwitcherData::LoadGeneralSettings(obs_data_t *obj)
|
||||
warnPluginLoadFailure = obs_data_get_bool(obj, "warnPluginLoadFailure");
|
||||
obs_data_set_default_bool(obj, "hideLegacyTabs", true);
|
||||
hideLegacyTabs = obs_data_get_bool(obj, "hideLegacyTabs");
|
||||
settingsLockEnabled = obs_data_get_bool(obj, "settingsLockEnabled");
|
||||
settingsLockPassword = obs_data_get_string(obj, "settingsLockPassword");
|
||||
|
||||
SetDefaultFunctionPriorities(obj);
|
||||
LoadFunctionPriorities(obj, functionNamesByPriority);
|
||||
@@ -960,6 +1025,7 @@ void AdvSceneSwitcher::SetupGeneralTab()
|
||||
ui->warnPluginLoadFailure->setChecked(switcher->warnPluginLoadFailure);
|
||||
ui->suppressCrashRecoveryDialog->setChecked(GetSuppressCrashDialog());
|
||||
ui->hideLegacyTabs->setChecked(switcher->hideLegacyTabs);
|
||||
ui->settingsLock->setChecked(switcher->settingsLockEnabled);
|
||||
|
||||
populatePriorityFunctionList(ui->priorityList);
|
||||
populateThreadPriorityList(ui->threadPriority);
|
||||
|
||||
@@ -294,6 +294,30 @@ std::vector<WindowInfo> GetWindows(const WindowQueryOptions &options)
|
||||
result.emplace_back(std::move(info));
|
||||
}
|
||||
|
||||
// When KWin compat is active, native Wayland windows are not tracked by
|
||||
// X11 and therefore do not appear in the list above. Merge in any window
|
||||
// reported by KWin that is not already present (matched by title) so that
|
||||
// title and focus conditions work for native Wayland windows.
|
||||
if (KWin) {
|
||||
for (const auto &[pid, title] :
|
||||
FocusNotifier::getWindowList()) {
|
||||
bool found = false;
|
||||
for (const auto &info : result) {
|
||||
if (info.title == title) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
WindowInfo waylandWindow;
|
||||
waylandWindow.title = title;
|
||||
waylandWindow.focused =
|
||||
(title == foregroundTitle);
|
||||
result.emplace_back(std::move(waylandWindow));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,35 +12,82 @@
|
||||
|
||||
namespace advss {
|
||||
|
||||
std::mutex FocusNotifier::_mutex;
|
||||
int FocusNotifier::activePID = -1;
|
||||
std::string FocusNotifier::activeTitle = {};
|
||||
std::map<std::string, std::pair<int, std::string>> FocusNotifier::_windows;
|
||||
|
||||
int FocusNotifier::getActiveWindowPID()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
return activePID;
|
||||
}
|
||||
|
||||
std::string FocusNotifier::getActiveWindowTitle()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
return activeTitle;
|
||||
}
|
||||
|
||||
std::vector<std::pair<int, std::string>> FocusNotifier::getWindowList()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
std::vector<std::pair<int, std::string>> result;
|
||||
result.reserve(_windows.size());
|
||||
for (const auto &entry : _windows) {
|
||||
result.emplace_back(entry.second);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void FocusNotifier::focusChanged(const int pid)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
activePID = pid;
|
||||
}
|
||||
|
||||
void FocusNotifier::focusTitle(const QString &title)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
activeTitle = title.toStdString();
|
||||
}
|
||||
|
||||
void FocusNotifier::focusUpdate(const int pid, const QString &title)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
activePID = pid;
|
||||
activeTitle = title.toStdString();
|
||||
}
|
||||
|
||||
void FocusNotifier::windowAdded(const QString &id, const int pid,
|
||||
const QString &title)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
_windows[id.toStdString()] = {pid, title.toStdString()};
|
||||
}
|
||||
|
||||
void FocusNotifier::windowRemoved(const QString &id)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
_windows.erase(id.toStdString());
|
||||
}
|
||||
|
||||
void FocusNotifier::windowTitleChanged(const QString &id, const QString &title)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
auto it = _windows.find(id.toStdString());
|
||||
if (it != _windows.end()) {
|
||||
it->second.second = title.toStdString();
|
||||
}
|
||||
}
|
||||
|
||||
bool isKWinAvailable()
|
||||
{
|
||||
const QDBusConnectionInterface *interface =
|
||||
QDBusConnection::sessionBus().interface();
|
||||
if (!interface)
|
||||
if (!interface) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const QStringList services =
|
||||
interface->registeredServiceNames().value();
|
||||
@@ -53,25 +100,32 @@ bool startKWinScript(QString &scriptObjectPath)
|
||||
"/tmp/AdvancedSceneSwitcher/KWinFocusNotifier.js";
|
||||
|
||||
const QString script =
|
||||
R"(workspace.windowActivated.connect(function(client) {
|
||||
if (!client) return;
|
||||
if (!client.pid) return;
|
||||
if (!client.caption) return;
|
||||
R"(var adss = "com.github.AdvancedSceneSwitcher";
|
||||
var adssPath = "/com/github/AdvancedSceneSwitcher";
|
||||
|
||||
callDBus(
|
||||
"com.github.AdvancedSceneSwitcher",
|
||||
"/com/github/AdvancedSceneSwitcher",
|
||||
"com.github.AdvancedSceneSwitcher",
|
||||
"focusChanged",
|
||||
client.pid
|
||||
);
|
||||
callDBus(
|
||||
"com.github.AdvancedSceneSwitcher",
|
||||
"/com/github/AdvancedSceneSwitcher",
|
||||
"com.github.AdvancedSceneSwitcher",
|
||||
"focusTitle",
|
||||
client.caption
|
||||
);
|
||||
function trackWindow(window) {
|
||||
var id = window.internalId.toString();
|
||||
callDBus(adss, adssPath, adss, "windowAdded", id, window.pid, window.caption);
|
||||
window.captionChanged.connect(function() {
|
||||
callDBus(adss, adssPath, adss, "windowTitleChanged", id, window.caption);
|
||||
});
|
||||
}
|
||||
|
||||
var windows = workspace.windowList();
|
||||
for (var i = 0; i < windows.length; i++) {
|
||||
trackWindow(windows[i]);
|
||||
}
|
||||
|
||||
workspace.windowAdded.connect(trackWindow);
|
||||
|
||||
workspace.windowRemoved.connect(function(window) {
|
||||
callDBus(adss, adssPath, adss, "windowRemoved", window.internalId.toString());
|
||||
});
|
||||
|
||||
workspace.windowActivated.connect(function(client) {
|
||||
callDBus(adss, adssPath, adss, "focusUpdate",
|
||||
client ? client.pid : 0,
|
||||
client ? client.caption : "");
|
||||
}))";
|
||||
|
||||
if (const QDir dir; !dir.mkpath(QFileInfo(scriptPath).absolutePath())) {
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace advss {
|
||||
|
||||
@@ -10,18 +14,27 @@ class FocusNotifier final : public QObject {
|
||||
Q_OBJECT
|
||||
Q_CLASSINFO("D-Bus Interface", "com.github.AdvancedSceneSwitcher")
|
||||
|
||||
static std::mutex _mutex;
|
||||
static int activePID;
|
||||
static std::string activeTitle;
|
||||
// Maps KWin internal window ID to {pid, title}
|
||||
static std::map<std::string, std::pair<int, std::string>> _windows;
|
||||
|
||||
public:
|
||||
using QObject::QObject;
|
||||
|
||||
static int getActiveWindowPID();
|
||||
static std::string getActiveWindowTitle();
|
||||
static std::vector<std::pair<int, std::string>> getWindowList();
|
||||
|
||||
public slots:
|
||||
void focusChanged(const int pid);
|
||||
void focusTitle(const QString &title);
|
||||
void focusUpdate(const int pid, const QString &title);
|
||||
void windowAdded(const QString &id, const int pid,
|
||||
const QString &title);
|
||||
void windowRemoved(const QString &id);
|
||||
void windowTitleChanged(const QString &id, const QString &title);
|
||||
};
|
||||
|
||||
bool isKWinAvailable();
|
||||
|
||||
@@ -164,4 +164,9 @@ std::shared_ptr<MacroSegment> MacroActionEdit::Data() const
|
||||
return *_entryData;
|
||||
}
|
||||
|
||||
void MacroActionEdit::FocusTypeSelection()
|
||||
{
|
||||
_actionSelection->setFocus();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
|
||||
@@ -17,6 +17,7 @@ public:
|
||||
std::shared_ptr<MacroAction> * = nullptr);
|
||||
void SetupWidgets(bool basicSetup = false);
|
||||
void SetEntryData(std::shared_ptr<MacroAction> *);
|
||||
void FocusTypeSelection() override;
|
||||
|
||||
private slots:
|
||||
void ActionSelectionChanged(const QString &text);
|
||||
|
||||
@@ -263,6 +263,7 @@ bool MacroActionMacro::Load(obs_data_t *obj)
|
||||
if (obs_data_has_user_value(obj, "nestedMacro")) {
|
||||
OBSDataAutoRelease nestedMacroData =
|
||||
obs_data_get_obj(obj, "nestedMacro");
|
||||
_nestedMacro = std::make_shared<Macro>();
|
||||
_nestedMacro->Load(nestedMacroData);
|
||||
}
|
||||
|
||||
@@ -309,6 +310,15 @@ void MacroActionMacro::ResolveVariablesToFixedValues()
|
||||
{
|
||||
_actionIndex.ResolveVariables();
|
||||
_label.ResolveVariables();
|
||||
|
||||
if (_action == Action::NESTED_MACRO) {
|
||||
for (auto &action : _nestedMacro->Actions()) {
|
||||
action->ResolveVariablesToFixedValues();
|
||||
}
|
||||
for (auto &action : _nestedMacro->ElseActions()) {
|
||||
action->ResolveVariablesToFixedValues();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void runActionsHelper(Macro *macro, bool runElseActions, bool setInputs,
|
||||
|
||||
@@ -305,4 +305,9 @@ std::shared_ptr<MacroSegment> MacroConditionEdit::Data() const
|
||||
return *_entryData;
|
||||
}
|
||||
|
||||
void MacroConditionEdit::FocusTypeSelection()
|
||||
{
|
||||
_conditionSelection->setFocus();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
|
||||
@@ -43,6 +43,7 @@ public:
|
||||
void SetRootNode(bool);
|
||||
void SetupWidgets(bool basicSetup = false);
|
||||
void SetEntryData(std::shared_ptr<MacroCondition> *);
|
||||
void FocusTypeSelection() override;
|
||||
|
||||
private slots:
|
||||
void LogicSelectionChanged(int idx);
|
||||
|
||||
@@ -650,6 +650,18 @@ void MacroEdit::HighlightCondition(int idx, QColor color) const
|
||||
ui->conditionsList->Highlight(idx, color);
|
||||
}
|
||||
|
||||
void MacroEdit::ScrollAndFocusNewSegment(MacroSegmentList *list, int newIdx,
|
||||
int *currentIdx)
|
||||
{
|
||||
QTimer::singleShot(0, this, [this, list, newIdx, currentIdx]() {
|
||||
list->ensureWidgetVisible(list->WidgetAt(*currentIdx));
|
||||
auto *widget = list->WidgetAt(newIdx);
|
||||
if (widget) {
|
||||
widget->FocusTypeSelection();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void resetSegmentHighlights(MacroSegmentList *list)
|
||||
{
|
||||
MacroSegmentEdit *widget = nullptr;
|
||||
@@ -1087,10 +1099,7 @@ void MacroEdit::AddMacroAction(Macro *macro, int idx, const std::string &id,
|
||||
ui->actionsList->SetHelpMsgVisible(false);
|
||||
emit(MacroSegmentOrderChanged());
|
||||
|
||||
QTimer::singleShot(0, this, [this]() {
|
||||
ui->actionsList->ensureWidgetVisible(
|
||||
ui->actionsList->WidgetAt(currentActionIdx));
|
||||
});
|
||||
ScrollAndFocusNewSegment(ui->actionsList, idx, ¤tActionIdx);
|
||||
}
|
||||
|
||||
void MacroEdit::AddMacroAction(int idx)
|
||||
@@ -1398,10 +1407,8 @@ void MacroEdit::AddMacroElseAction(Macro *macro, int idx, const std::string &id,
|
||||
ui->elseActionsList->SetHelpMsgVisible(false);
|
||||
emit(MacroSegmentOrderChanged());
|
||||
|
||||
QTimer::singleShot(0, this, [this]() {
|
||||
ui->elseActionsList->ensureWidgetVisible(
|
||||
ui->elseActionsList->WidgetAt(currentElseActionIdx));
|
||||
});
|
||||
ScrollAndFocusNewSegment(ui->elseActionsList, idx,
|
||||
¤tElseActionIdx);
|
||||
}
|
||||
|
||||
void MacroEdit::AddMacroElseAction(int idx)
|
||||
@@ -1611,10 +1618,7 @@ void MacroEdit::AddMacroCondition(Macro *macro, int idx, const std::string &id,
|
||||
ui->conditionsList->SetHelpMsgVisible(false);
|
||||
emit(MacroSegmentOrderChanged());
|
||||
|
||||
QTimer::singleShot(0, this, [this]() {
|
||||
ui->conditionsList->ensureWidgetVisible(
|
||||
ui->conditionsList->WidgetAt(currentConditionIdx));
|
||||
});
|
||||
ScrollAndFocusNewSegment(ui->conditionsList, idx, ¤tConditionIdx);
|
||||
}
|
||||
|
||||
void MacroEdit::on_conditionAdd_clicked()
|
||||
|
||||
@@ -116,6 +116,8 @@ private:
|
||||
void PopulateMacroActions(Macro &m, uint32_t afterIdx = 0);
|
||||
void PopulateMacroElseActions(Macro &m, uint32_t afterIdx = 0);
|
||||
void PopulateMacroConditions(Macro &m, uint32_t afterIdx = 0);
|
||||
void ScrollAndFocusNewSegment(MacroSegmentList *list, int newIdx,
|
||||
int *currentIdx);
|
||||
void SetupMacroSegmentSelection(MacroSection type, int idx);
|
||||
void
|
||||
SetupContextMenu(const QPoint &pos,
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
#include "macro-export-import-dialog.hpp"
|
||||
#include "macro-export-extensions.hpp"
|
||||
#include "obs-module-helper.hpp"
|
||||
#include "section.hpp"
|
||||
|
||||
#include <obs.hpp>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLabel>
|
||||
#include <QLayout>
|
||||
#include <QScrollArea>
|
||||
#include <QSplitter>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
@@ -44,10 +47,14 @@ static bool isValidData(const QString &json)
|
||||
// Extension section
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Section *MacroExportImportDialog::BuildExtensionSection()
|
||||
QWidget *MacroExportImportDialog::BuildExtensionWidget()
|
||||
{
|
||||
auto outerSection = new Section(300, this);
|
||||
outerSection->AddHeaderWidget(new QLabel(obs_module_text(
|
||||
auto wrapper = new QWidget(this);
|
||||
auto wrapperLayout = new QVBoxLayout(wrapper);
|
||||
wrapperLayout->setContentsMargins(0, 0, 0, 0);
|
||||
wrapperLayout->setSpacing(4);
|
||||
|
||||
wrapperLayout->addWidget(new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.macroTab.export.additionalContent")));
|
||||
|
||||
auto outerContent = new QWidget();
|
||||
@@ -57,7 +64,6 @@ Section *MacroExportImportDialog::BuildExtensionSection()
|
||||
|
||||
const auto &extensions = GetMacroExportExtensions();
|
||||
|
||||
// Build a sorted index list (alphabetical by translated display name).
|
||||
std::vector<int> order(extensions.size());
|
||||
std::iota(order.begin(), order.end(), 0);
|
||||
std::sort(order.begin(), order.end(), [&](int a, int b) {
|
||||
@@ -81,6 +87,7 @@ Section *MacroExportImportDialog::BuildExtensionSection()
|
||||
&MacroExportImportDialog::UpdateExportString);
|
||||
outerLayout->addWidget(ui.mainCheck);
|
||||
_extensionUIs.append(std::move(ui));
|
||||
_extensionOrder.push_back(extIdx);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -88,11 +95,18 @@ Section *MacroExportImportDialog::BuildExtensionSection()
|
||||
// Section: the main checkbox sits in the header (next to the
|
||||
// toggle arrow) and the per-item checkboxes live in the
|
||||
// collapsible content area.
|
||||
const auto items = ext.getExportItems();
|
||||
auto items = ext.getExportItems();
|
||||
if (items.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::sort(items.begin(), items.end(),
|
||||
[](const QPair<QString, QString> &a,
|
||||
const QPair<QString, QString> &b) {
|
||||
return QString::localeAwareCompare(
|
||||
a.second, b.second) < 0;
|
||||
});
|
||||
|
||||
auto innerSection = new Section(200, outerContent);
|
||||
|
||||
ui.mainCheck =
|
||||
@@ -132,13 +146,22 @@ Section *MacroExportImportDialog::BuildExtensionSection()
|
||||
UpdateExportString();
|
||||
});
|
||||
|
||||
innerSection->SetContent(subWidget, false);
|
||||
innerSection->SetContent(subWidget, true);
|
||||
outerLayout->addWidget(innerSection);
|
||||
_extensionUIs.append(std::move(ui));
|
||||
_extensionOrder.push_back(extIdx);
|
||||
}
|
||||
|
||||
outerSection->SetContent(outerContent, true);
|
||||
return outerSection;
|
||||
outerLayout->addStretch();
|
||||
|
||||
auto scrollArea = new QScrollArea(wrapper);
|
||||
scrollArea->setWidget(outerContent);
|
||||
scrollArea->setWidgetResizable(true);
|
||||
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
scrollArea->setFrameShape(QFrame::NoFrame);
|
||||
wrapperLayout->addWidget(scrollArea);
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -180,12 +203,25 @@ MacroExportImportDialog::MacroExportImportDialog(Type type,
|
||||
auto layout = new QVBoxLayout(this);
|
||||
|
||||
if (type == Type::EXPORT_MACRO && !GetMacroExportExtensions().empty()) {
|
||||
layout->addWidget(BuildExtensionSection());
|
||||
auto splitter = new QSplitter(Qt::Vertical, this);
|
||||
splitter->setChildrenCollapsible(false);
|
||||
splitter->addWidget(BuildExtensionWidget());
|
||||
|
||||
auto textWidget = new QWidget(splitter);
|
||||
auto textLayout = new QVBoxLayout(textWidget);
|
||||
textLayout->setContentsMargins(0, 0, 0, 0);
|
||||
textLayout->addWidget(label);
|
||||
textLayout->addWidget(_importExportString);
|
||||
textLayout->addWidget(_usePlainText);
|
||||
splitter->addWidget(textWidget);
|
||||
|
||||
layout->addWidget(splitter);
|
||||
} else {
|
||||
layout->addWidget(label);
|
||||
layout->addWidget(_importExportString);
|
||||
layout->addWidget(_usePlainText);
|
||||
}
|
||||
|
||||
layout->addWidget(label);
|
||||
layout->addWidget(_importExportString);
|
||||
layout->addWidget(_usePlainText);
|
||||
layout->addWidget(buttons);
|
||||
setLayout(layout);
|
||||
|
||||
@@ -209,19 +245,26 @@ QString MacroExportImportDialog::BuildExportJson() const
|
||||
}
|
||||
|
||||
const auto &extensions = GetMacroExportExtensions();
|
||||
for (int i = 0;
|
||||
i < (int)extensions.size() && i < (int)_extensionUIs.size(); ++i) {
|
||||
if (!_extensionUIs[i].mainCheck->isChecked()) {
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < (int)_extensionUIs.size(); ++i) {
|
||||
const auto &ui = _extensionUIs[i];
|
||||
|
||||
QStringList selectedIds;
|
||||
for (const auto &[id, cb] : _extensionUIs[i].itemChecks) {
|
||||
if (cb->isChecked()) {
|
||||
selectedIds << id;
|
||||
if (ui.itemChecks.isEmpty()) {
|
||||
if (!ui.mainCheck->isChecked()) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
for (const auto &[id, cb] : ui.itemChecks) {
|
||||
if (cb->isChecked()) {
|
||||
selectedIds << id;
|
||||
}
|
||||
}
|
||||
if (selectedIds.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
extensions[i].save(data, selectedIds);
|
||||
|
||||
extensions[_extensionOrder[i]].save(data, selectedIds);
|
||||
}
|
||||
|
||||
const char *json = obs_data_get_json(data);
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "section.hpp"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDialog>
|
||||
#include <QList>
|
||||
#include <QPlainTextEdit>
|
||||
|
||||
#include <obs-data.h>
|
||||
#include <vector>
|
||||
|
||||
namespace advss {
|
||||
|
||||
@@ -34,7 +33,7 @@ private:
|
||||
QList<QPair<QString, QCheckBox *>> itemChecks;
|
||||
};
|
||||
|
||||
Section *BuildExtensionSection();
|
||||
QWidget *BuildExtensionWidget();
|
||||
QString BuildExportJson() const;
|
||||
void RefreshExportText();
|
||||
|
||||
@@ -42,6 +41,7 @@ private:
|
||||
QPlainTextEdit *_importExportString;
|
||||
QCheckBox *_usePlainText;
|
||||
QList<ExtensionUI> _extensionUIs;
|
||||
std::vector<int> _extensionOrder;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
|
||||
@@ -76,7 +76,8 @@ EXPORT bool RunMacroElseActions(Macro *, bool forceParallel = false,
|
||||
bool ignorePause = false);
|
||||
EXPORT bool RunMacros();
|
||||
|
||||
void StopAllMacros();
|
||||
void SignalStopAllMacros();
|
||||
void WaitForAllMacros();
|
||||
|
||||
EXPORT void LoadMacros(obs_data_t *obj);
|
||||
EXPORT void SaveMacros(obs_data_t *obj);
|
||||
|
||||
@@ -74,11 +74,11 @@ void LoadMacroList(obs_data_t *obj, std::vector<MacroRef> ¯os,
|
||||
{
|
||||
obs_data_array_t *array = obs_data_get_array(obj, name.c_str());
|
||||
size_t count = obs_data_array_count(array);
|
||||
macros.reserve(macros.size() + count);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
obs_data_t *array_obj = obs_data_array_item(array, i);
|
||||
MacroRef ref;
|
||||
ref.Load(array_obj);
|
||||
macros.push_back(ref);
|
||||
macros.emplace_back();
|
||||
macros.back().Load(array_obj);
|
||||
obs_data_release(array_obj);
|
||||
}
|
||||
obs_data_array_release(array);
|
||||
|
||||
@@ -134,6 +134,7 @@ public:
|
||||
void SetSelected(bool);
|
||||
virtual std::shared_ptr<MacroSegment> Data() const = 0;
|
||||
virtual void SetupWidgets(bool basicSetup = false) = 0;
|
||||
virtual void FocusTypeSelection() {}
|
||||
|
||||
void SetupVarMappings(MacroSegment *segment);
|
||||
|
||||
|
||||
@@ -395,7 +395,7 @@ void AdvSceneSwitcher::ImportMacros()
|
||||
std::vector<std::shared_ptr<Macro>> importedMacros;
|
||||
auto &tempMacros = GetTemporaryMacros();
|
||||
|
||||
auto lock = LockContext();
|
||||
std::unique_lock<std::mutex> lock(*GetMutex());
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
tempMacros.clear();
|
||||
|
||||
@@ -405,10 +405,14 @@ void AdvSceneSwitcher::ImportMacros()
|
||||
macro->Load(array_obj);
|
||||
RunAndClearPostLoadSteps();
|
||||
|
||||
if (macroNameExists(macro->Name()) &&
|
||||
!ResolveMacroImportNameConflict(macro)) {
|
||||
groupSize--;
|
||||
continue;
|
||||
if (macroNameExists(macro->Name())) {
|
||||
lock.unlock();
|
||||
bool resolved = ResolveMacroImportNameConflict(macro);
|
||||
lock.lock();
|
||||
if (!resolved) {
|
||||
groupSize--;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
importedMacros.emplace_back(macro);
|
||||
|
||||
@@ -1316,7 +1316,14 @@ bool RunMacros()
|
||||
return true;
|
||||
}
|
||||
|
||||
void StopAllMacros()
|
||||
void SignalStopAllMacros()
|
||||
{
|
||||
for (const auto &m : GetAllMacros()) {
|
||||
m->SignalStop();
|
||||
}
|
||||
}
|
||||
|
||||
void WaitForAllMacros()
|
||||
{
|
||||
for (const auto &m : GetAllMacros()) {
|
||||
m->Stop();
|
||||
|
||||
@@ -61,6 +61,7 @@ public:
|
||||
PauseStateSaveBehavior GetPauseStateSaveBehavior() const;
|
||||
|
||||
void Stop();
|
||||
void SignalStop() { _stop = true; }
|
||||
bool GetStop() const { return _stop; }
|
||||
void ResetTimers();
|
||||
|
||||
@@ -228,7 +229,7 @@ void LoadMacros(obs_data_t *obj);
|
||||
void SaveMacros(obs_data_t *obj);
|
||||
bool CheckMacros();
|
||||
bool RunMacros();
|
||||
void StopAllMacros();
|
||||
void WaitForAllMacros();
|
||||
void InvalidateMacroTempVarValues();
|
||||
std::shared_ptr<Macro> GetMacroWithInvalidConditionInterval();
|
||||
|
||||
|
||||
@@ -163,6 +163,8 @@ public:
|
||||
bool disableHints = false;
|
||||
bool disableFilterComboboxFilter = false;
|
||||
bool hideLegacyTabs = true;
|
||||
bool settingsLockEnabled = false;
|
||||
std::string settingsLockPassword;
|
||||
bool saveWindowGeo = false;
|
||||
QPoint windowPos = {};
|
||||
QSize windowSize = {};
|
||||
|
||||
@@ -135,7 +135,7 @@ QStringList GetSourceNames()
|
||||
}
|
||||
|
||||
void PopulateTransitionSelection(QComboBox *sel, bool addCurrent, bool addAny,
|
||||
bool addSelect)
|
||||
bool addSelect, bool addNone)
|
||||
{
|
||||
|
||||
obs_frontend_source_list *transitions = new obs_frontend_source_list();
|
||||
@@ -168,6 +168,11 @@ void PopulateTransitionSelection(QComboBox *sel, bool addCurrent, bool addAny,
|
||||
addSelect ? 1 : 0,
|
||||
obs_module_text("AdvSceneSwitcher.anyTransition"));
|
||||
}
|
||||
if (addNone) {
|
||||
sel->insertItem(
|
||||
addSelect ? 1 : 0,
|
||||
obs_module_text("AdvSceneSwitcher.noneTransition"));
|
||||
}
|
||||
}
|
||||
|
||||
void PopulateWindowSelection(QComboBox *sel, bool addSelect)
|
||||
|
||||
@@ -19,7 +19,8 @@ EXPORT QStringList GetSourceNames();
|
||||
|
||||
EXPORT void PopulateTransitionSelection(QComboBox *sel, bool addCurrent = true,
|
||||
bool addAny = false,
|
||||
bool addSelect = true);
|
||||
bool addSelect = true,
|
||||
bool addNone = false);
|
||||
EXPORT void PopulateWindowSelection(QComboBox *sel, bool addSelect = true);
|
||||
void PopulateAudioSelection(QComboBox *sel, bool addSelect = true);
|
||||
void PopulateVideoSelection(QComboBox *sel, bool addMainOutput = false,
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
macro(add_plugin plugin)
|
||||
option(ENABLE_${plugin}_PLUGIN "Enable the ${plugin} plugin" ON)
|
||||
if(ENABLE_${plugin}_PLUGIN)
|
||||
set_property(GLOBAL APPEND PROPERTY ADVSS_PLUGINS_PENDING ${plugin})
|
||||
add_subdirectory(${plugin})
|
||||
else()
|
||||
set_property(GLOBAL APPEND PROPERTY ADVSS_PLUGINS_DISABLED_BY_OPTION
|
||||
${plugin})
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
@@ -37,3 +41,32 @@ add_plugin(stream-deck)
|
||||
add_plugin(twitch)
|
||||
add_plugin(usb)
|
||||
add_plugin(video)
|
||||
|
||||
# ---------------------------------------------------------------------------- #
|
||||
|
||||
# Print plugin summary
|
||||
get_property(_advss_enabled GLOBAL PROPERTY ADVSS_PLUGINS_ENABLED)
|
||||
get_property(_advss_pending GLOBAL PROPERTY ADVSS_PLUGINS_PENDING)
|
||||
get_property(_advss_disabled_opt GLOBAL
|
||||
PROPERTY ADVSS_PLUGINS_DISABLED_BY_OPTION)
|
||||
|
||||
set(_advss_disabled_deps)
|
||||
foreach(_p ${_advss_pending})
|
||||
if(NOT _p IN_LIST _advss_enabled)
|
||||
list(APPEND _advss_disabled_deps ${_p})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
message(STATUS "")
|
||||
message(STATUS "ADVSS Plugin Summary")
|
||||
message(STATUS "====================")
|
||||
foreach(_p ${_advss_enabled})
|
||||
message(STATUS " ENABLED: ${_p}")
|
||||
endforeach()
|
||||
foreach(_p ${_advss_disabled_deps})
|
||||
message(STATUS " DISABLED: ${_p} (missing dependencies)")
|
||||
endforeach()
|
||||
foreach(_p ${_advss_disabled_opt})
|
||||
message(STATUS " DISABLED: ${_p} (ENABLE_${_p}_PLUGIN=OFF)")
|
||||
endforeach()
|
||||
message(STATUS "")
|
||||
|
||||
@@ -160,12 +160,11 @@ static void attachRestoreContext(obs_sceneitem_t *item,
|
||||
|
||||
static void setSceneItemVisibility(obs_sceneitem_t *item,
|
||||
const bool setTransition,
|
||||
const OBSWeakSource &transitionWeak,
|
||||
const TransitionSelection &transitionSel,
|
||||
const bool setDuration,
|
||||
const Duration &duration,
|
||||
MacroActionSceneVisibility::Action action)
|
||||
{
|
||||
const OBSSourceAutoRelease transition = OBSGetStrongRef(transitionWeak);
|
||||
const bool itemIsVisible = obs_sceneitem_visible(item);
|
||||
|
||||
const OBSSource currentTransition =
|
||||
@@ -175,6 +174,16 @@ static void setSceneItemVisibility(obs_sceneitem_t *item,
|
||||
|
||||
OBSSource privateTransitionSource = nullptr;
|
||||
if (setTransition) {
|
||||
OBSSourceAutoRelease transition;
|
||||
if (transitionSel.GetType() ==
|
||||
TransitionSelection::Type::CURRENT) {
|
||||
transition = obs_source_get_ref(
|
||||
obs_sceneitem_get_transition(item,
|
||||
!itemIsVisible));
|
||||
} else {
|
||||
transition =
|
||||
OBSGetStrongRef(transitionSel.GetTransition());
|
||||
}
|
||||
privateTransitionSource = SetSceneItemTransition(
|
||||
item, transition, !itemIsVisible);
|
||||
} else {
|
||||
@@ -229,8 +238,7 @@ bool MacroActionSceneVisibility::PerformAction()
|
||||
{
|
||||
auto items = _source.GetSceneItems(_scene);
|
||||
for (const auto &item : items) {
|
||||
setSceneItemVisibility(item, _updateTransition,
|
||||
_transition.GetTransition(),
|
||||
setSceneItemVisibility(item, _updateTransition, _transition,
|
||||
_updateDuration, _duration, _action);
|
||||
}
|
||||
return true;
|
||||
@@ -340,7 +348,7 @@ MacroActionSceneVisibilityEdit::MacroActionSceneVisibilityEdit(
|
||||
},
|
||||
SceneItemSelectionWidget::NameClashMode::ALL)),
|
||||
_updateTransition(new QCheckBox(this)),
|
||||
_transitions(new TransitionSelectionWidget(this, false, false)),
|
||||
_transitions(new TransitionSelectionWidget(this, true, false, true)),
|
||||
_updateDuration(new QCheckBox(this)),
|
||||
_duration(new DurationSelection(this, false)),
|
||||
_durationLayout(new QHBoxLayout),
|
||||
@@ -474,8 +482,11 @@ void MacroActionSceneVisibilityEdit::ActionChanged(int value)
|
||||
|
||||
void MacroActionSceneVisibilityEdit::SetWidgetVisibility()
|
||||
{
|
||||
const auto transitionType = _entryData->_transition.GetType();
|
||||
const bool hideDurationSelection =
|
||||
_entryData->_updateTransition &&
|
||||
transitionType != TransitionSelection::Type::CURRENT &&
|
||||
transitionType != TransitionSelection::Type::NONE &&
|
||||
IsFixedLengthTransition(
|
||||
_entryData->_transition.GetTransition());
|
||||
|
||||
|
||||
@@ -406,15 +406,17 @@ void MacroActionTransitionEdit::SetWidgetVisibility()
|
||||
MacroActionTransition::Type::TBAR;
|
||||
const bool isReleaseTbar = _entryData->_type ==
|
||||
MacroActionTransition::Type::RELEASE_TBAR;
|
||||
_sources->setVisible(
|
||||
_entryData->_type == MacroActionTransition::Type::SOURCE_HIDE ||
|
||||
_entryData->_type == MacroActionTransition::Type::SOURCE_SHOW);
|
||||
const bool isSourceTransition =
|
||||
_entryData->_type == MacroActionTransition::Type::SOURCE_SHOW ||
|
||||
_entryData->_type == MacroActionTransition::Type::SOURCE_HIDE;
|
||||
_sources->setVisible(isSourceTransition);
|
||||
_scenes->setVisible(_entryData->_type !=
|
||||
MacroActionTransition::Type::SCENE &&
|
||||
!isTbar && !isReleaseTbar);
|
||||
SetLayoutVisible(_transitionLayout, !isTbar && !isReleaseTbar);
|
||||
SetLayoutVisible(_durationLayout, !isTbar && !isReleaseTbar);
|
||||
SetLayoutVisible(_tbarLayout, isTbar);
|
||||
_transitions->EnableNoneEntry(isSourceTransition);
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
|
||||
@@ -163,7 +163,7 @@ void MacroConditionTransition::TransitionStarted(void *data, calldata_t *cd)
|
||||
// Look up the original by name to get a comparable reference.
|
||||
const bool studioModeActive =
|
||||
obs_frontend_preview_program_mode_active();
|
||||
if (studioModeActive) {
|
||||
if (studioModeActive && startSource) {
|
||||
OBSSourceAutoRelease startSourceByName = obs_get_source_by_name(
|
||||
obs_source_get_name(startSource));
|
||||
if (startSourceByName) {
|
||||
|
||||
@@ -48,6 +48,8 @@ OBSWeakSource TransitionSelection::GetTransition() const
|
||||
obs_source_release(source);
|
||||
return weakSource;
|
||||
}
|
||||
case Type::NONE:
|
||||
return nullptr;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -63,6 +65,8 @@ std::string TransitionSelection::ToString() const
|
||||
return obs_module_text("AdvSceneSwitcher.currentTransition");
|
||||
case Type::ANY:
|
||||
return obs_module_text("AdvSceneSwitcher.anyTransition");
|
||||
case Type::NONE:
|
||||
return obs_module_text("AdvSceneSwitcher.noneTransition");
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -70,14 +74,16 @@ std::string TransitionSelection::ToString() const
|
||||
}
|
||||
|
||||
TransitionSelectionWidget::TransitionSelectionWidget(QWidget *parent,
|
||||
bool current, bool any)
|
||||
bool current, bool any,
|
||||
bool none)
|
||||
: FilterComboBox(parent,
|
||||
obs_module_text("AdvSceneSwitcher.selectTransition")),
|
||||
_addCurrent(current),
|
||||
_addAny(any)
|
||||
_addAny(any),
|
||||
_addNone(none)
|
||||
{
|
||||
setDuplicatesEnabled(true);
|
||||
PopulateTransitionSelection(this, current, any, false);
|
||||
PopulateTransitionSelection(this, current, any, false, none);
|
||||
|
||||
QWidget::connect(this, SIGNAL(currentTextChanged(const QString &)),
|
||||
this, SLOT(SelectionChanged(const QString &)));
|
||||
@@ -86,8 +92,9 @@ TransitionSelectionWidget::TransitionSelectionWidget(QWidget *parent,
|
||||
void TransitionSelectionWidget::SetTransition(const TransitionSelection &t)
|
||||
{
|
||||
// Order of entries
|
||||
// 1. Any transition
|
||||
// 2. Current transition
|
||||
// 1. None transition
|
||||
// 2. Any transition
|
||||
// 3. Current transition
|
||||
// 4. Transitions
|
||||
|
||||
switch (t.GetType()) {
|
||||
@@ -102,6 +109,10 @@ void TransitionSelectionWidget::SetTransition(const TransitionSelection &t)
|
||||
setCurrentIndex(findText(QString::fromStdString(
|
||||
obs_module_text("AdvSceneSwitcher.anyTransition"))));
|
||||
break;
|
||||
case TransitionSelection::Type::NONE:
|
||||
setCurrentIndex(findText(QString::fromStdString(
|
||||
obs_module_text("AdvSceneSwitcher.noneTransition"))));
|
||||
break;
|
||||
default:
|
||||
setCurrentIndex(-1);
|
||||
break;
|
||||
@@ -120,6 +131,17 @@ void TransitionSelectionWidget::EnableAnyEntry(bool enable)
|
||||
Populate();
|
||||
}
|
||||
|
||||
void TransitionSelectionWidget::EnableNoneEntry(bool enable)
|
||||
{
|
||||
if (_addNone == enable) {
|
||||
return;
|
||||
}
|
||||
const auto selection = GetCurrentSelection();
|
||||
_addNone = enable;
|
||||
Populate();
|
||||
SetTransition(selection);
|
||||
}
|
||||
|
||||
void TransitionSelectionWidget::showEvent(QShowEvent *event)
|
||||
{
|
||||
FilterComboBox::showEvent(event);
|
||||
@@ -133,7 +155,7 @@ void TransitionSelectionWidget::Populate()
|
||||
{
|
||||
const QSignalBlocker blocker(this);
|
||||
clear();
|
||||
PopulateTransitionSelection(this, _addCurrent, _addAny);
|
||||
PopulateTransitionSelection(this, _addCurrent, _addAny, true, _addNone);
|
||||
}
|
||||
|
||||
static bool isFirstEntry(const QComboBox *l, QString name, int idx)
|
||||
@@ -163,6 +185,9 @@ TransitionSelection TransitionSelectionWidget::GetCurrentSelection() const
|
||||
if (IsAnyTransitionSelected(text)) {
|
||||
result._type = TransitionSelection::Type::ANY;
|
||||
}
|
||||
if (IsNoneTransitionSelected(text)) {
|
||||
result._type = TransitionSelection::Type::NONE;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -187,6 +212,16 @@ bool TransitionSelectionWidget::IsAnyTransitionSelected(
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TransitionSelectionWidget::IsNoneTransitionSelected(
|
||||
const QString &name) const
|
||||
{
|
||||
if (name == QString::fromStdString((obs_module_text(
|
||||
"AdvSceneSwitcher.noneTransition")))) {
|
||||
return isFirstEntry(this, name, currentIndex());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void TransitionSelectionWidget::SelectionChanged(const QString &)
|
||||
{
|
||||
emit TransitionChanged(GetCurrentSelection());
|
||||
|
||||
@@ -16,6 +16,7 @@ public:
|
||||
TRANSITION,
|
||||
CURRENT,
|
||||
ANY,
|
||||
NONE,
|
||||
};
|
||||
|
||||
Type GetType() const { return _type; }
|
||||
@@ -33,10 +34,11 @@ class TransitionSelectionWidget : public FilterComboBox {
|
||||
|
||||
public:
|
||||
TransitionSelectionWidget(QWidget *parent, bool current = true,
|
||||
bool any = false);
|
||||
bool any = false, bool none = false);
|
||||
void SetTransition(const TransitionSelection &);
|
||||
void EnableCurrentEntry(bool enable);
|
||||
void EnableAnyEntry(bool enable);
|
||||
void EnableNoneEntry(bool enable);
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *event) override;
|
||||
@@ -52,9 +54,11 @@ private:
|
||||
TransitionSelection GetCurrentSelection() const;
|
||||
bool IsCurrentTransitionSelected(const QString &name) const;
|
||||
bool IsAnyTransitionSelected(const QString &name) const;
|
||||
bool IsNoneTransitionSelected(const QString &name) const;
|
||||
|
||||
bool _addCurrent;
|
||||
bool _addAny;
|
||||
bool _addNone;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
|
||||
@@ -26,7 +26,7 @@ if(NOT OpenVR_DIR)
|
||||
set(OpenVR_DIR ${ADVSS_SOURCE_DIR}/deps/openvr)
|
||||
endif()
|
||||
|
||||
if(EXISTS ${OpenVR_DIR})
|
||||
if(EXISTS "${OpenVR_DIR}/CMakeLists.txt")
|
||||
set(SIZEOF_VOIDP ${CMAKE_SIZEOF_VOID_P})
|
||||
if((NOT APPLE) AND (CMAKE_SIZEOF_VOID_P EQUAL 8))
|
||||
set(PROCESSOR_ARCH "64")
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "twitch-helpers.hpp"
|
||||
|
||||
#include <log-helper.hpp>
|
||||
#include <plugin-state-helpers.hpp>
|
||||
|
||||
#undef DispatchMessage
|
||||
|
||||
@@ -339,6 +340,24 @@ static constexpr std::string_view defaultURL =
|
||||
std::map<TwitchChatConnection::ChatMapKey, std::weak_ptr<TwitchChatConnection>>
|
||||
TwitchChatConnection::_chatMap = {};
|
||||
|
||||
void TwitchChatConnection::DisconnectAll()
|
||||
{
|
||||
for (auto &[key, weakConn] : _chatMap) {
|
||||
auto conn = weakConn.lock();
|
||||
if (conn) {
|
||||
conn->Disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool setupChatConnectionSupport()
|
||||
{
|
||||
AddStopStep(TwitchChatConnection::DisconnectAll);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool _chatSetup = setupChatConnectionSupport();
|
||||
|
||||
TwitchChatConnection::TwitchChatConnection(const TwitchToken &token,
|
||||
const TwitchChannel &channel)
|
||||
: QObject(nullptr),
|
||||
|
||||
@@ -84,6 +84,7 @@ public:
|
||||
static std::shared_ptr<TwitchChatConnection>
|
||||
GetChatConnection(const TwitchToken &token,
|
||||
const TwitchChannel &channel);
|
||||
static void DisconnectAll();
|
||||
[[nodiscard]] ChatMessageBuffer RegisterForMessages();
|
||||
[[nodiscard]] ChatMessageBuffer RegisterForWhispers();
|
||||
void SendChatMessage(const std::string &message);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "twitch-helpers.hpp"
|
||||
|
||||
#include <log-helper.hpp>
|
||||
#include <plugin-state-helpers.hpp>
|
||||
|
||||
#ifdef VERIFY_TIMESTAMPS
|
||||
#include "date/tz.h"
|
||||
@@ -71,6 +72,22 @@ void EventSub::UnregisterInstance()
|
||||
_instances.erase(it, _instances.end());
|
||||
}
|
||||
|
||||
void EventSub::DisconnectAll()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_instancesMtx);
|
||||
for (auto *instance : _instances) {
|
||||
instance->Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
static bool setupEventSubSupport()
|
||||
{
|
||||
AddStopStep(EventSub::DisconnectAll);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool _eventSubSetup = setupEventSubSupport();
|
||||
|
||||
void EventSub::ConnectThread()
|
||||
{
|
||||
_client->reset();
|
||||
@@ -262,12 +279,21 @@ std::string EventSub::AddEventSubscription(std::shared_ptr<TwitchToken> token,
|
||||
return "";
|
||||
}
|
||||
|
||||
// Capture the session ID and release the lock before making the HTTP
|
||||
// request. Holding the mutex during a network call would block
|
||||
// SubscriptionIsActive() on the condition-check thread for the entire
|
||||
// duration of the request, causing visibly slow macro evaluations.
|
||||
const std::string sessionID = eventSub->_sessionID;
|
||||
lock.unlock();
|
||||
|
||||
OBSDataAutoRelease postData = copyData(subscription.data);
|
||||
setTransportData(postData.Get(), eventSub->_sessionID);
|
||||
setTransportData(postData.Get(), sessionID);
|
||||
auto result = SendPostRequest(*token, registerSubscriptionURL.data(),
|
||||
registerSubscriptionPath.data(), {},
|
||||
postData.Get());
|
||||
|
||||
lock.lock();
|
||||
|
||||
if (result.status != 202) {
|
||||
const char *error = obs_data_get_string(result.data, "error");
|
||||
const char *message =
|
||||
@@ -280,6 +306,24 @@ std::string EventSub::AddEventSubscription(std::shared_ptr<TwitchToken> token,
|
||||
return "";
|
||||
}
|
||||
|
||||
// Verify the session has not been replaced while the HTTP request was
|
||||
// in flight (e.g. a reconnect occurred). If it was, the subscription
|
||||
// we just created belongs to a dead session and must be discarded so
|
||||
// the caller will retry with the new session ID.
|
||||
if (eventSub->_sessionID != sessionID) {
|
||||
blog(LOG_INFO,
|
||||
"discarding Twitch EventSub registration - session ID "
|
||||
"changed during HTTP request");
|
||||
return "";
|
||||
}
|
||||
|
||||
// Another thread may have registered the same subscription while the
|
||||
// lock was released.
|
||||
if (isAlreadySubscribed(eventSub->_activeSubscriptions, subscription)) {
|
||||
eventSub->LogActiveSubscriptions();
|
||||
return eventSub->_activeSubscriptions.find(subscription)->id;
|
||||
}
|
||||
|
||||
OBSDataArrayAutoRelease replyArray =
|
||||
obs_data_get_array(result.data, "data");
|
||||
OBSDataAutoRelease replyData = obs_data_array_item(replyArray, 0);
|
||||
@@ -439,9 +483,10 @@ void EventSub::OnMessage(connection_hdl, EventSubWSClient::message_ptr message)
|
||||
|
||||
void EventSub::HandleWelcome(obs_data_t *data)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_subscriptionMtx);
|
||||
OBSDataAutoRelease session = obs_data_get_obj(data, "session");
|
||||
_sessionID = obs_data_get_string(session, "id");
|
||||
ClearActiveSubscriptions();
|
||||
_activeSubscriptions.clear();
|
||||
blog(LOG_INFO, "Twitch EventSub connected");
|
||||
vblog(LOG_INFO, "Twitch EventSub session id: %s", _sessionID.c_str());
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ public:
|
||||
|
||||
void Connect();
|
||||
void Disconnect();
|
||||
static void DisconnectAll();
|
||||
[[nodiscard]] EventSubMessageBuffer RegisterForEvents();
|
||||
bool SubscriptionIsActive(const std::string &id);
|
||||
static std::string AddEventSubscription(std::shared_ptr<TwitchToken>,
|
||||
|
||||
@@ -547,16 +547,18 @@ bool MacroConditionTwitch::CheckChatMessageRemove(TwitchToken &token)
|
||||
|
||||
bool MacroConditionTwitch::ChatConnectionIsSetup(TwitchToken &token)
|
||||
{
|
||||
if (_chatConnection) {
|
||||
_chatConnection->ConnectToChat();
|
||||
return true;
|
||||
}
|
||||
|
||||
_chatConnection =
|
||||
TwitchChatConnection::GetChatConnection(token, _channel);
|
||||
if (!_chatConnection) {
|
||||
_chatConnection = TwitchChatConnection::GetChatConnection(
|
||||
token, _channel);
|
||||
if (!_chatConnection) {
|
||||
return false;
|
||||
}
|
||||
_chatBuffer = _chatConnection->RegisterForMessages();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
_chatBuffer = _chatConnection->RegisterForMessages();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionTwitch::HandleChatEvents(
|
||||
|
||||
@@ -403,11 +403,12 @@ bool MacroConditionVideo::CheckBrightnessThreshold()
|
||||
|
||||
bool MacroConditionVideo::CheckOCR()
|
||||
{
|
||||
if (!_ocrParameters.Initialized()) {
|
||||
auto *ocr = _ocrParameters.GetOCR();
|
||||
if (!ocr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto text = RunOCR(_ocrParameters.GetOCR(), _screenshotData.GetImage(),
|
||||
auto text = RunOCR(ocr, _screenshotData.GetImage(),
|
||||
_ocrParameters.color, _ocrParameters.colorThreshold);
|
||||
if (!text) {
|
||||
return false;
|
||||
|
||||
@@ -82,7 +82,7 @@ bool ObjDetectParameters::Save(obs_data_t *obj) const
|
||||
minSize.Save(data, "minSize");
|
||||
maxSize.Save(data, "maxSize");
|
||||
obs_data_set_obj(obj, "objectMatchData", data);
|
||||
obs_data_set_int(data, "version", 1);
|
||||
obs_data_set_int(data, "version", 2);
|
||||
obs_data_release(data);
|
||||
return true;
|
||||
}
|
||||
@@ -121,6 +121,21 @@ bool ObjDetectParameters::Load(obs_data_t *obj)
|
||||
// TODO: Remove this fallback in a future version
|
||||
if (!obs_data_has_user_value(data, "version")) {
|
||||
scaleFactor = obs_data_get_double(data, "scaleFactor");
|
||||
} else if (obs_data_get_int(data, "version") == 1) {
|
||||
#ifdef _WIN32
|
||||
// On Windows the plugin directory moved from
|
||||
// "Program Files/.../data/obs-plugins/advanced-scene-switcher"
|
||||
// to "ProgramData/.../plugins/advanced-scene-switcher/data",
|
||||
// which invalidates previously saved default model paths.
|
||||
const std::string oldPrefix =
|
||||
"../../data/obs-plugins/advanced-scene-switcher/res/cascadeClassifiers/";
|
||||
if (modelPath.substr(0, oldPrefix.size()) == oldPrefix) {
|
||||
modelPath = std::string(obs_get_module_data_path(
|
||||
obs_current_module())) +
|
||||
"/res/cascadeClassifiers/" +
|
||||
modelPath.substr(oldPrefix.size());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if (scaleFactor.IsFixedType() && !isScaleFactorValid(scaleFactor)) {
|
||||
scaleFactor = 1.1;
|
||||
@@ -303,9 +318,6 @@ OCRParameters::OCRParameters(const OCRParameters &other)
|
||||
useConfig(other.useConfig),
|
||||
configFile(other.configFile)
|
||||
{
|
||||
if (!initDone) {
|
||||
Setup();
|
||||
}
|
||||
}
|
||||
|
||||
OCRParameters &OCRParameters::operator=(const OCRParameters &other)
|
||||
@@ -319,9 +331,7 @@ OCRParameters &OCRParameters::operator=(const OCRParameters &other)
|
||||
languageCode = other.languageCode;
|
||||
useConfig = other.useConfig;
|
||||
configFile = other.configFile;
|
||||
if (!initDone) {
|
||||
Setup();
|
||||
}
|
||||
initDone = false;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -337,7 +347,7 @@ bool OCRParameters::Save(obs_data_t *obj) const
|
||||
SaveColor(data, "textColor", color);
|
||||
colorThreshold.Save(data, "colorThreshold");
|
||||
obs_data_set_int(data, "pageSegMode", static_cast<int>(pageSegMode));
|
||||
obs_data_set_int(data, "version", 2);
|
||||
obs_data_set_int(data, "version", 3);
|
||||
obs_data_set_obj(obj, "ocrData", data);
|
||||
obs_data_release(data);
|
||||
return true;
|
||||
@@ -356,6 +366,23 @@ bool OCRParameters::Load(obs_data_t *obj)
|
||||
tesseractBasePath = std::string(obs_get_module_data_path(
|
||||
obs_current_module())) +
|
||||
"/res/ocr/";
|
||||
} else if (obs_data_get_int(data, "version") == 2) {
|
||||
#ifdef _WIN32
|
||||
// On Windows the plugin directory moved from
|
||||
// "Program Files/.../data/obs-plugins/advanced-scene-switcher"
|
||||
// to "ProgramData/.../plugins/advanced-scene-switcher/data",
|
||||
// which invalidates the previously saved default OCR path.
|
||||
const std::string oldPath = tesseractBasePath;
|
||||
if (oldPath ==
|
||||
"../../data/obs-plugins/advanced-scene-switcher/res/ocr/" ||
|
||||
oldPath ==
|
||||
"../../data/obs-plugins/advanced-scene-switcher/res/ocr") {
|
||||
tesseractBasePath =
|
||||
std::string(obs_get_module_data_path(
|
||||
obs_current_module())) +
|
||||
"/res/ocr/";
|
||||
}
|
||||
#endif
|
||||
}
|
||||
useConfig = obs_data_get_bool(data, "useConfig");
|
||||
configFile = obs_data_get_string(data, "configFile");
|
||||
@@ -380,6 +407,14 @@ void OCRParameters::SetPageMode(tesseract::PageSegMode mode)
|
||||
}
|
||||
}
|
||||
|
||||
tesseract::TessBaseAPI *OCRParameters::GetOCR()
|
||||
{
|
||||
if (!initDone) {
|
||||
Setup();
|
||||
}
|
||||
return initDone ? ocr.get() : nullptr;
|
||||
}
|
||||
|
||||
bool OCRParameters::SetLanguageCode(const std::string &value)
|
||||
{
|
||||
const auto dataPath = QString::fromStdString(tesseractBasePath) + "/" +
|
||||
@@ -389,7 +424,7 @@ bool OCRParameters::SetLanguageCode(const std::string &value)
|
||||
return false;
|
||||
}
|
||||
languageCode = value;
|
||||
Setup();
|
||||
initDone = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -408,7 +443,7 @@ bool OCRParameters::SetTesseractBasePath(const std::string &value)
|
||||
return false;
|
||||
}
|
||||
tesseractBasePath = value;
|
||||
Setup();
|
||||
initDone = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -420,13 +455,13 @@ std::string OCRParameters::GetTesseractBasePath() const
|
||||
void OCRParameters::EnableCustomConfig(bool enable)
|
||||
{
|
||||
useConfig = enable;
|
||||
Setup();
|
||||
initDone = false;
|
||||
}
|
||||
|
||||
void OCRParameters::SetCustomConfigFile(const std::string &filename)
|
||||
{
|
||||
configFile = filename;
|
||||
Setup();
|
||||
initDone = false;
|
||||
}
|
||||
|
||||
void OCRParameters::Setup()
|
||||
|
||||
@@ -108,10 +108,7 @@ public:
|
||||
void SetCustomConfigFile(const std::string &);
|
||||
std::string GetCustomConfigFile() const { return configFile; }
|
||||
tesseract::PageSegMode GetPageMode() const { return pageSegMode; }
|
||||
tesseract::TessBaseAPI *GetOCR() const
|
||||
{
|
||||
return initDone ? ocr.get() : nullptr;
|
||||
}
|
||||
tesseract::TessBaseAPI *GetOCR();
|
||||
|
||||
StringVariable text = obs_module_text("AdvSceneSwitcher.enterText");
|
||||
RegexConfig regex = RegexConfig::PartialMatchRegexConfig();
|
||||
|
||||
Reference in New Issue
Block a user