Compare commits

..

1 Commits
1.34.2 ... lock

Author SHA1 Message Date
WarmUpTill
8174004eda Add option to lock settings dialog with simple password
This is meant as a simple tampering protection.
The settings are not encrypted and the password is stored in plaintext.
2026-05-26 22:57:06 +02:00
15 changed files with 92 additions and 233 deletions

View File

@@ -68,53 +68,44 @@ function Package {
Remove-Item @RemoveArgs
$ReleasePath = "${ProjectRoot}/release/${Configuration}"
$NewBinPath = "${ReleasePath}/${ProductName}/bin/64bit"
$NewDataPath = "${ReleasePath}/${ProductName}/data"
$CIWindowsDir = "${ProjectRoot}/build-aux/CI/windows"
# 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"
# --- 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 = "${RecStaging}/${ProductName}/bin/64bit"
New-Item -ItemType Directory -Force -Path $RecBinPath | Out-Null
Copy-Item -Path "${NewBinPath}/*" -Destination $RecBinPath -Recurse -Force
$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
}
if ( Test-Path -Path $NewDataPath ) {
$RecDataPath = "${RecStaging}/${ProductName}/data"
New-Item -ItemType Directory -Force -Path $RecDataPath | Out-Null
Copy-Item -Path "${NewDataPath}/*" -Destination $RecDataPath -Recurse -Force
$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
}
Compress-Archive -Force -Path (Get-ChildItem -Path $RecStaging) `
-CompressionLevel Optimal `
-DestinationPath "${ProjectRoot}/release/${OutputName}.zip"
Remove-Item -Path $RecStaging -Recurse -Force
Log-Group
# --- 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
Log-Group "Archiving ${ProductName}..."
$CompressArgs = @{
Path = (Get-ChildItem -Path $StagingPath)
CompressionLevel = 'Optimal'
DestinationPath = "${ProjectRoot}/release/${OutputName}.zip"
Verbose = ($null -ne $Env:CI)
}
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
Compress-Archive -Force @CompressArgs
Remove-Item -Path $StagingPath -Recurse -Force
Log-Group
if ( ( $BuildInstaller ) ) {
@@ -129,24 +120,7 @@ function Package {
Push-Location -Stack BuildTemp
Ensure-Location -Path "${ProjectRoot}/release"
Remove-Item -Path Package -Recurse -Force -ErrorAction SilentlyContinue
# 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
}
Copy-Item -Path ${Configuration} -Destination Package -Recurse
Invoke-External iscc ${IsccFile} /O"${ProjectRoot}/release" /F"${OutputName}-Installer"
Remove-Item -Path Package -Recurse
Pop-Location -Stack BuildTemp

View File

@@ -1,23 +0,0 @@
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

View File

@@ -1,15 +1,22 @@
Advanced Scene Switcher - Windows Installation
===============================================
==============================================
For full installation instructions visit:
https://github.com/WarmUpTill/SceneSwitcher/wiki/Installation
This archive uses the recommended plugin layout for OBS 28 and newer.
Quick guide
-----------
Extract the CONTENTS of this archive into:
This archive contains two installation methods.
Choose ONE depending on your OBS version.
C:\ProgramData\obs-studio\plugins\
recommended\ (preferred)
---------------------------------------------
Extract the CONTENTS of this folder into:
C:\ProgramData\obs-studio\
Tip: press Win+R and type %ProgramData%\obs-studio\plugins to open that folder.
@@ -17,5 +24,14 @@ After extracting, the path should look like:
C:\ProgramData\obs-studio\plugins\advanced-scene-switcher\bin\64bit\advanced-scene-switcher.dll
If the plugin does not appear in OBS, try the legacy archive instead:
advanced-scene-switcher-<version>-windows-x64-legacy.zip
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.

View File

@@ -23,17 +23,14 @@ SolidCompression=yes
DirExistsWarning=no
UsePreviousAppDir=no
SetupIconFile=installer.ico
UninstallDisplayIcon={code:GetUninstallIconPath}
UninstallDisplayIcon={app}\advanced-scene-switcher\data\res\images\logo.ico
DisableDirPage=no
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Files]
; 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: "..\release\Package\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "..\LICENSE"; Flags: dontcopy
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
@@ -56,54 +53,23 @@ 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.
// Only runs when upgrading from a legacy location to the recommended one.
// We read InstallLocation from our own previous uninstall entry, which is
// the exact directory the old installer used regardless of OBS install path.
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');
@@ -117,3 +83,4 @@ begin
if CurStep = ssInstall then
RemoveLegacyFiles();
end;

View File

@@ -461,13 +461,12 @@ void SwitcherData::Stop()
SetMacroAbortWait(true);
GetMacroWaitCV().notify_all();
GetMacroTransitionCV().notify_all();
SignalStopAllMacros();
StopAllMacros();
StopAndClearAllActionQueues();
CloseAllInputDialogs();
th->wait();
delete th;
th = nullptr;
WaitForAllMacros();
RunStopSteps();
}

View File

@@ -76,8 +76,7 @@ EXPORT bool RunMacroElseActions(Macro *, bool forceParallel = false,
bool ignorePause = false);
EXPORT bool RunMacros();
void SignalStopAllMacros();
void WaitForAllMacros();
void StopAllMacros();
EXPORT void LoadMacros(obs_data_t *obj);
EXPORT void SaveMacros(obs_data_t *obj);

View File

@@ -74,11 +74,11 @@ void LoadMacroList(obs_data_t *obj, std::vector<MacroRef> &macros,
{
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);
macros.emplace_back();
macros.back().Load(array_obj);
MacroRef ref;
ref.Load(array_obj);
macros.push_back(ref);
obs_data_release(array_obj);
}
obs_data_array_release(array);

View File

@@ -395,7 +395,7 @@ void AdvSceneSwitcher::ImportMacros()
std::vector<std::shared_ptr<Macro>> importedMacros;
auto &tempMacros = GetTemporaryMacros();
std::unique_lock<std::mutex> lock(*GetMutex());
auto lock = LockContext();
for (size_t i = 0; i < count; i++) {
tempMacros.clear();
@@ -405,14 +405,10 @@ void AdvSceneSwitcher::ImportMacros()
macro->Load(array_obj);
RunAndClearPostLoadSteps();
if (macroNameExists(macro->Name())) {
lock.unlock();
bool resolved = ResolveMacroImportNameConflict(macro);
lock.lock();
if (!resolved) {
groupSize--;
continue;
}
if (macroNameExists(macro->Name()) &&
!ResolveMacroImportNameConflict(macro)) {
groupSize--;
continue;
}
importedMacros.emplace_back(macro);

View File

@@ -1316,14 +1316,7 @@ bool RunMacros()
return true;
}
void SignalStopAllMacros()
{
for (const auto &m : GetAllMacros()) {
m->SignalStop();
}
}
void WaitForAllMacros()
void StopAllMacros()
{
for (const auto &m : GetAllMacros()) {
m->Stop();

View File

@@ -61,7 +61,6 @@ public:
PauseStateSaveBehavior GetPauseStateSaveBehavior() const;
void Stop();
void SignalStop() { _stop = true; }
bool GetStop() const { return _stop; }
void ResetTimers();
@@ -229,7 +228,7 @@ void LoadMacros(obs_data_t *obj);
void SaveMacros(obs_data_t *obj);
bool CheckMacros();
bool RunMacros();
void WaitForAllMacros();
void StopAllMacros();
void InvalidateMacroTempVarValues();
std::shared_ptr<Macro> GetMacroWithInvalidConditionInterval();

View File

@@ -262,21 +262,12 @@ 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(), sessionID);
setTransportData(postData.Get(), eventSub->_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 =
@@ -289,24 +280,6 @@ 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);
@@ -466,10 +439,9 @@ 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");
_activeSubscriptions.clear();
ClearActiveSubscriptions();
blog(LOG_INFO, "Twitch EventSub connected");
vblog(LOG_INFO, "Twitch EventSub session id: %s", _sessionID.c_str());
}

View File

@@ -403,12 +403,11 @@ bool MacroConditionVideo::CheckBrightnessThreshold()
bool MacroConditionVideo::CheckOCR()
{
auto *ocr = _ocrParameters.GetOCR();
if (!ocr) {
if (!_ocrParameters.Initialized()) {
return false;
}
auto text = RunOCR(ocr, _screenshotData.GetImage(),
auto text = RunOCR(_ocrParameters.GetOCR(), _screenshotData.GetImage(),
_ocrParameters.color, _ocrParameters.colorThreshold);
if (!text) {
return false;

View File

@@ -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", 2);
obs_data_set_int(data, "version", 1);
obs_data_release(data);
return true;
}
@@ -121,21 +121,6 @@ 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;
@@ -318,6 +303,9 @@ OCRParameters::OCRParameters(const OCRParameters &other)
useConfig(other.useConfig),
configFile(other.configFile)
{
if (!initDone) {
Setup();
}
}
OCRParameters &OCRParameters::operator=(const OCRParameters &other)
@@ -331,7 +319,9 @@ OCRParameters &OCRParameters::operator=(const OCRParameters &other)
languageCode = other.languageCode;
useConfig = other.useConfig;
configFile = other.configFile;
initDone = false;
if (!initDone) {
Setup();
}
return *this;
}
@@ -347,7 +337,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", 3);
obs_data_set_int(data, "version", 2);
obs_data_set_obj(obj, "ocrData", data);
obs_data_release(data);
return true;
@@ -366,23 +356,6 @@ 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");
@@ -407,14 +380,6 @@ 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) + "/" +
@@ -424,7 +389,7 @@ bool OCRParameters::SetLanguageCode(const std::string &value)
return false;
}
languageCode = value;
initDone = false;
Setup();
return true;
}
@@ -443,7 +408,7 @@ bool OCRParameters::SetTesseractBasePath(const std::string &value)
return false;
}
tesseractBasePath = value;
initDone = false;
Setup();
return true;
}
@@ -455,13 +420,13 @@ std::string OCRParameters::GetTesseractBasePath() const
void OCRParameters::EnableCustomConfig(bool enable)
{
useConfig = enable;
initDone = false;
Setup();
}
void OCRParameters::SetCustomConfigFile(const std::string &filename)
{
configFile = filename;
initDone = false;
Setup();
}
void OCRParameters::Setup()

View File

@@ -108,7 +108,10 @@ public:
void SetCustomConfigFile(const std::string &);
std::string GetCustomConfigFile() const { return configFile; }
tesseract::PageSegMode GetPageMode() const { return pageSegMode; }
tesseract::TessBaseAPI *GetOCR();
tesseract::TessBaseAPI *GetOCR() const
{
return initDone ? ocr.get() : nullptr;
}
StringVariable text = obs_module_text("AdvSceneSwitcher.enterText");
RegexConfig regex = RegexConfig::PartialMatchRegexConfig();