mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-08-29 04:36:21 -05:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95537f39e4 | ||
|
|
0490c97ad9 | ||
|
|
4b17314c66 | ||
|
|
7459793fe3 | ||
|
|
8a39a859be | ||
|
|
f8a592f17a | ||
|
|
f824ac8f4e | ||
|
|
8e9323173a |
2
.github/workflows/build-debian.yml
vendored
2
.github/workflows/build-debian.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
||||
# devscripts and libobs-dev are needed but they were already installed
|
||||
# from check_libobs_revision and install_frontend_header sections.
|
||||
sudo apt update
|
||||
sudo apt install cmake debhelper libcurl4-openssl-dev libxss-dev libxtst-dev qtbase5-dev libopencv-dev
|
||||
sudo apt install cmake debhelper libcurl4-openssl-dev libxss-dev libxtst-dev qtbase5-dev libopencv-dev libprocps-dev
|
||||
- name: build
|
||||
run: |
|
||||
debuild --no-lintian --no-sign
|
||||
|
||||
9
.github/workflows/build.yml
vendored
9
.github/workflows/build.yml
vendored
@@ -1,10 +1,6 @@
|
||||
name: build obs plugin
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
on: [push, pull_request, workflow_dispatch]
|
||||
|
||||
env:
|
||||
PLUGIN_NAME: SceneSwitcher
|
||||
@@ -183,7 +179,8 @@ jobs:
|
||||
libxss-dev \
|
||||
libx11-xcb-dev \
|
||||
libxcb-xfixes0-dev \
|
||||
libopencv-dev
|
||||
libopencv-dev \
|
||||
libprocps-dev
|
||||
- name: 'Configure'
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Note that you will have to use OBS version 26.1 or newer!
|
||||
Note that you will have to use OBS version 27 or newer!
|
||||
|
||||
--- Windows ---
|
||||
Recommended: Run the provided installer. (You might have to click 'More info' and select 'Run anyway' if it is blocked by Windows)
|
||||
@@ -8,17 +8,17 @@ It is usually located at 'C:\Program Files (x86)\obs-studio\'.
|
||||
Remember to install the Visual C++ Redistributable for Visual Studio 2019, if you have not done so already. (See plugin overview page)
|
||||
|
||||
--- macOS ---
|
||||
Recommended: Run the provided installer. (You might have to right-click or control-click and select 'open' if it is blocked)
|
||||
|
||||
Alternatively extract the *so file and data folder and either ...
|
||||
|
||||
... right-click the OBS app inside your Applications folder and choose 'Show Package Contents'.
|
||||
Copy the advanced-scene-switcher.so file to 'Contents/Plugins' and the 'data' folder to 'Contents/Resources'.
|
||||
|
||||
... or copy the advanced-scene-switcher.so file to Library/Application Support/obs-studio/plugins/advanced-scene-switcher/bin/ .
|
||||
And the 'data' folder to 'Library/Application Support/obs-studio/plugins/advanced-scene-switcher/'.
|
||||
Run the provided installer. (You might have to right-click or control-click and select 'open' if it is blocked)
|
||||
|
||||
--- Linux ---
|
||||
Note that the plugin has dependencies to:
|
||||
* libXss
|
||||
* libopencv-imgproc
|
||||
* libopencv-objdetect
|
||||
Optional:
|
||||
* libXtst
|
||||
* libcurl
|
||||
|
||||
Copy the advanced-scene-switcher.so file and into the OBS Studio plugin folder.
|
||||
The location of this folder can vary, so you might have to look around a bit.
|
||||
|
||||
|
||||
@@ -137,9 +137,20 @@ endif()
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
find_package(X11 REQUIRED COMPONENTS Xtst Xss)
|
||||
link_libraries(${X11_LIBRARIES})
|
||||
find_path(PROCPS_INCLUDE_DIR NAMES proc/procps.h)
|
||||
if(NOT PROCPS_INCLUDE_DIR)
|
||||
message(
|
||||
FATAL_ERROR "procps include dir not found - please set PROCPS_INCLUDE_DIR"
|
||||
)
|
||||
endif()
|
||||
find_library(PROCPS_LIBRARY NAMES procps)
|
||||
if(NOT PROCPS_LIBRARY)
|
||||
message(FATAL_ERROR "procps lib not found - please set PROCPS_LIBRARY")
|
||||
endif()
|
||||
message (STATUS "WHAT THE FUCK IS GOING ON ${PROCPS_LIBRARY}")
|
||||
link_libraries(${X11_LIBRARIES} ${procps_LIBRARIES})
|
||||
include_directories("${X11_INCLUDE_DIR}" "${X11_Xtst_INCLUDE_PATH}"
|
||||
"${X11_Xss_INCLUDE_PATH}")
|
||||
"${X11_Xss_INCLUDE_PATH}" "${PROCPS_INCLUDE_DIR}")
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
@@ -154,7 +165,7 @@ elseif(APPLE)
|
||||
else()
|
||||
set(advanced-scene-switcher_PLATFORM_SOURCES
|
||||
src/linux/advanced-scene-switcher-nix.cpp)
|
||||
set(advanced-scene-switcher_PLATFORM_LIBS Xss)
|
||||
set(advanced-scene-switcher_PLATFORM_LIBS Xss ${PROCPS_LIBRARY})
|
||||
endif()
|
||||
|
||||
# asio and websocketpp
|
||||
@@ -401,11 +412,7 @@ if(BUILD_OUT_OF_TREE)
|
||||
else()
|
||||
# In tree build
|
||||
target_link_libraries(
|
||||
advanced-scene-switcher
|
||||
${advanced-scene-switcher_PLATFORM_LIBS}
|
||||
${OpenCV_LIBRARIES}
|
||||
obs-frontend-api
|
||||
Qt5::Widgets
|
||||
libobs)
|
||||
advanced-scene-switcher ${advanced-scene-switcher_PLATFORM_LIBS}
|
||||
${OpenCV_LIBRARIES} obs-frontend-api Qt5::Widgets libobs)
|
||||
install_obs_plugin_with_data(advanced-scene-switcher data)
|
||||
endif()
|
||||
|
||||
@@ -28,6 +28,7 @@ AdvSceneSwitcher.generalTab.generalBehavior.cooldown="After a match do not switc
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.cooldownHint="During this time potential matches will be ignored!"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.verboseLogging="Enable verbose logging"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo="Save window position and size"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.showTrayNotifications="Show system tray notifications"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="Disable UI hints"
|
||||
AdvSceneSwitcher.generalTab.priority="Priority"
|
||||
AdvSceneSwitcher.generalTab.priority.description="Switching methods priority (Highest priority is at the top)"
|
||||
@@ -119,6 +120,8 @@ AdvSceneSwitcher.condition.video.condition.object="contains object"
|
||||
AdvSceneSwitcher.condition.video.askFileAction="Do you want to use an existing file or create a screenshot of the currently selected source?"
|
||||
AdvSceneSwitcher.condition.video.askFileAction.file="Use existing file"
|
||||
AdvSceneSwitcher.condition.video.askFileAction.screenshot="Create screenshot"
|
||||
AdvSceneSwitcher.condition.video.usePatternForChangedCheck="Use pattern matching"
|
||||
AdvSceneSwitcher.condition.video.usePatternForChangedCheck.tooltip="This will allow you to control how much the image has to change for the condition to be true."
|
||||
AdvSceneSwitcher.condition.video.patternThreshold="Threshold: "
|
||||
AdvSceneSwitcher.condition.video.patternThresholdDescription="A higher threshold value means that the pattern needs to match the video source more closely."
|
||||
AdvSceneSwitcher.condition.video.patternThresholdUseAlphaAsMask="Use alpha channel as mask for pattern."
|
||||
@@ -139,6 +142,8 @@ AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}Reduce CPU lo
|
||||
AdvSceneSwitcher.condition.stream="Streaming"
|
||||
AdvSceneSwitcher.condition.stream.state.start="Stream running"
|
||||
AdvSceneSwitcher.condition.stream.state.stop="Stream stopped"
|
||||
AdvSceneSwitcher.condition.stream.state.starting="Stream starting"
|
||||
AdvSceneSwitcher.condition.stream.state.stopping="Stream stopping"
|
||||
AdvSceneSwitcher.condition.stream.entry="{{streamState}}"
|
||||
AdvSceneSwitcher.condition.record="Recording"
|
||||
AdvSceneSwitcher.condition.record.state.start="Recording running"
|
||||
@@ -614,6 +619,8 @@ AdvSceneSwitcher.selectWindowTip="Use \"OBS\" to specify OBS window\nUse \"Task
|
||||
|
||||
AdvSceneSwitcher.status.active="Active"
|
||||
AdvSceneSwitcher.status.inactive="Inactive"
|
||||
AdvSceneSwitcher.running="Plugin running"
|
||||
AdvSceneSwitcher.stopped="Plugin stopped"
|
||||
|
||||
AdvSceneSwitcher.unit.milliseconds="milliseconds"
|
||||
AdvSceneSwitcher.unit.secends="seconds"
|
||||
|
||||
@@ -306,6 +306,30 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showTrayNotifications">
|
||||
<property name="text">
|
||||
<string>AdvSceneSwitcher.generalTab.generalBehavior.showTrayNotifications</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<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>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_48">
|
||||
<item>
|
||||
@@ -355,8 +379,7 @@
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_41">
|
||||
<item>
|
||||
<widget class="QListWidget" name="priorityList">
|
||||
</widget>
|
||||
<widget class="QListWidget" name="priorityList"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <QAction>
|
||||
#include <QFileDialog>
|
||||
#include <regex>
|
||||
#include <filesystem>
|
||||
|
||||
#include <obs-module.h>
|
||||
#include <obs-frontend-api.h>
|
||||
@@ -36,9 +37,16 @@ bool translationAvailable()
|
||||
void AdvSceneSwitcher::loadUI()
|
||||
{
|
||||
if (!translationAvailable()) {
|
||||
(void)DisplayMessage(
|
||||
"Failed to find plug-in's 'data' directory.\n"
|
||||
"Please check installation instructions!");
|
||||
QString msg = "Failed to find plug-in's 'data' directory.\n"
|
||||
"Please check installation instructions!\n\n"
|
||||
"Data most likely expected at:\n\n";
|
||||
#ifdef _WIN32
|
||||
msg += QString::fromStdString(
|
||||
(std::filesystem::current_path().string()));
|
||||
msg += "/";
|
||||
#endif
|
||||
msg += obs_get_module_data_path(obs_current_module());
|
||||
(void)DisplayMessage(msg);
|
||||
}
|
||||
|
||||
#if __APPLE__
|
||||
@@ -104,33 +112,23 @@ static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *)
|
||||
{
|
||||
if (saving) {
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
|
||||
switcher->Prune();
|
||||
|
||||
obs_data_t *obj = obs_data_create();
|
||||
|
||||
switcher->saveSettings(obj);
|
||||
|
||||
obs_data_set_obj(save_data, "advanced-scene-switcher", obj);
|
||||
|
||||
obs_data_release(obj);
|
||||
} else {
|
||||
switcher->m.lock();
|
||||
|
||||
obs_data_t *obj =
|
||||
obs_data_get_obj(save_data, "advanced-scene-switcher");
|
||||
|
||||
if (!obj) {
|
||||
obj = obs_data_create();
|
||||
}
|
||||
if (switcher->versionChanged(obj, g_GIT_SHA1)) {
|
||||
AskBackup(obj);
|
||||
}
|
||||
|
||||
switcher->loadSettings(obj);
|
||||
|
||||
obs_data_release(obj);
|
||||
|
||||
switcher->m.unlock();
|
||||
|
||||
// Stop the scene switcher at least once to
|
||||
@@ -166,9 +164,7 @@ void SwitcherData::Thread()
|
||||
bool setPrevSceneAfterLinger = false;
|
||||
bool macroMatch = false;
|
||||
macroSceneSwitched = false;
|
||||
|
||||
endTime = std::chrono::high_resolution_clock::now();
|
||||
|
||||
auto runTime =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
endTime - startTime);
|
||||
@@ -415,6 +411,12 @@ void SwitcherData::Start()
|
||||
if (networkConfig.ClientEnabled) {
|
||||
client.connect(networkConfig.GetClientUri());
|
||||
}
|
||||
|
||||
if (showSystemTrayNotifications) {
|
||||
DisplayTrayMessage(
|
||||
obs_module_text("AdvSceneSwitcher.pluginName"),
|
||||
obs_module_text("AdvSceneSwitcher.running"));
|
||||
}
|
||||
}
|
||||
|
||||
void ResetMacroCounters()
|
||||
@@ -441,6 +443,12 @@ void SwitcherData::Stop()
|
||||
|
||||
server.stop();
|
||||
client.disconnect();
|
||||
|
||||
if (showSystemTrayNotifications) {
|
||||
DisplayTrayMessage(
|
||||
obs_module_text("AdvSceneSwitcher.pluginName"),
|
||||
obs_module_text("AdvSceneSwitcher.stopped"));
|
||||
}
|
||||
}
|
||||
|
||||
void SwitcherData::setWaitScene()
|
||||
@@ -558,6 +566,18 @@ void setTranstionEnd()
|
||||
std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
void setStreamStarting()
|
||||
{
|
||||
switcher->lastStreamStartingTime =
|
||||
std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
void setStreamStopping()
|
||||
{
|
||||
switcher->lastStreamStoppingTime =
|
||||
std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
// Note to future self:
|
||||
// be careful using switcher->m here as there is potential for deadlocks when using
|
||||
// frontend functions such as obs_frontend_set_current_scene()
|
||||
@@ -597,6 +617,12 @@ static void OBSEvent(enum obs_frontend_event event, void *switcher)
|
||||
case OBS_FRONTEND_EVENT_TRANSITION_STOPPED:
|
||||
setTranstionEnd();
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_STREAMING_STARTING:
|
||||
setStreamStarting();
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_STREAMING_STOPPING:
|
||||
setStreamStopping();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -210,6 +210,15 @@ void AdvSceneSwitcher::on_saveWindowGeo_stateChanged(int state)
|
||||
switcher->saveWindowGeo = state;
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_showTrayNotifications_stateChanged(int state)
|
||||
{
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
switcher->showSystemTrayNotifications = state;
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_uiHintsDisable_stateChanged(int state)
|
||||
{
|
||||
if (loading) {
|
||||
@@ -503,6 +512,8 @@ void SwitcherData::saveGeneralSettings(obs_data_t *obj)
|
||||
static_cast<int>(autoStartEvent));
|
||||
|
||||
obs_data_set_bool(obj, "verbose", verbose);
|
||||
obs_data_set_bool(obj, "showSystemTrayNotifications",
|
||||
showSystemTrayNotifications);
|
||||
obs_data_set_bool(obj, "disableHints", disableHints);
|
||||
|
||||
obs_data_set_int(obj, "priority0", functionNamesByPriority[0]);
|
||||
@@ -574,6 +585,8 @@ void SwitcherData::loadGeneralSettings(obs_data_t *obj)
|
||||
obs_data_get_int(obj, "autoStartEvent"));
|
||||
|
||||
verbose = obs_data_get_bool(obj, "verbose");
|
||||
showSystemTrayNotifications =
|
||||
obs_data_get_bool(obj, "showSystemTrayNotifications");
|
||||
disableHints = obs_data_get_bool(obj, "disableHints");
|
||||
|
||||
obs_data_set_default_int(obj, "priority0", default_priority_0);
|
||||
@@ -800,6 +813,8 @@ void AdvSceneSwitcher::setupGeneralTab()
|
||||
|
||||
ui->verboseLogging->setChecked(switcher->verbose);
|
||||
ui->saveWindowGeo->setChecked(switcher->saveWindowGeo);
|
||||
ui->showTrayNotifications->setChecked(
|
||||
switcher->showSystemTrayNotifications);
|
||||
ui->uiHintsDisable->setChecked(switcher->disableHints);
|
||||
|
||||
for (int p : switcher->functionNamesByPriority) {
|
||||
|
||||
@@ -164,6 +164,7 @@ public slots:
|
||||
|
||||
void on_verboseLogging_stateChanged(int state);
|
||||
void on_saveWindowGeo_stateChanged(int state);
|
||||
void on_showTrayNotifications_stateChanged(int state);
|
||||
void on_uiHintsDisable_stateChanged(int state);
|
||||
|
||||
void on_exportSettings_clicked();
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
enum class StreamState {
|
||||
STOP,
|
||||
START,
|
||||
STARTING,
|
||||
STOPPING,
|
||||
};
|
||||
|
||||
class MacroConditionStream : public MacroCondition {
|
||||
@@ -22,6 +24,9 @@ public:
|
||||
StreamState _streamState;
|
||||
|
||||
private:
|
||||
std::chrono::high_resolution_clock::time_point _lastStreamStartingTime{};
|
||||
std::chrono::high_resolution_clock::time_point _lastStreamStoppingTime{};
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
@@ -51,6 +51,7 @@ public:
|
||||
VideoCondition _condition = VideoCondition::MATCH;
|
||||
std::string _file = obs_module_text("AdvSceneSwitcher.enterPath");
|
||||
bool _useAlphaAsMask = false;
|
||||
bool _usePatternForChangedCheck = false;
|
||||
PatternMatchData _patternData;
|
||||
double _patternThreshold = 0.8;
|
||||
cv::CascadeClassifier _objectCascade;
|
||||
@@ -65,6 +66,7 @@ public:
|
||||
int _throttleCount = 3;
|
||||
|
||||
private:
|
||||
bool OutputChanged();
|
||||
bool ScreenshotContainsPattern();
|
||||
bool ScreenshotContainsObject();
|
||||
bool Compare();
|
||||
@@ -127,6 +129,7 @@ private slots:
|
||||
void ConditionChanged(int cond);
|
||||
void ImagePathChanged(const QString &text);
|
||||
void ImageBrowseButtonClicked();
|
||||
void UsePatternForChangedCheckChanged(int value);
|
||||
void PatternThresholdChanged(double);
|
||||
void UseAlphaAsMaskChanged(int value);
|
||||
|
||||
@@ -148,6 +151,7 @@ protected:
|
||||
QComboBox *_videoSelection;
|
||||
QComboBox *_condition;
|
||||
|
||||
QCheckBox *_usePatternForChangedCheck;
|
||||
FileSelection *_imagePath;
|
||||
ThresholdSlider *_patternThreshold;
|
||||
QCheckBox *_useAlphaAsMask;
|
||||
|
||||
@@ -70,6 +70,7 @@ struct SwitcherData {
|
||||
bool stop = false;
|
||||
bool verbose = false;
|
||||
bool disableHints = false;
|
||||
bool showSystemTrayNotifications = false;
|
||||
bool showFrame = false;
|
||||
bool transitionOverrideOverride = false;
|
||||
bool adjustActiveTransitionType = true;
|
||||
@@ -81,6 +82,8 @@ struct SwitcherData {
|
||||
OBSWeakSource previousScene = nullptr;
|
||||
std::chrono::high_resolution_clock::time_point lastSceneChangeTime{};
|
||||
std::chrono::high_resolution_clock::time_point lastTransitionEndTime{};
|
||||
std::chrono::high_resolution_clock::time_point lastStreamStartingTime{};
|
||||
std::chrono::high_resolution_clock::time_point lastStreamStoppingTime{};
|
||||
OBSWeakSource lastRandomScene;
|
||||
SceneGroup *lastRandomSceneGroup;
|
||||
OBSWeakSource nonMatchingScene;
|
||||
|
||||
@@ -55,6 +55,7 @@ void listAddClicked(QListWidget *list, QWidget *newWidget,
|
||||
bool listMoveUp(QListWidget *list);
|
||||
bool listMoveDown(QListWidget *list);
|
||||
bool DisplayMessage(const QString &msg, bool question = false);
|
||||
void DisplayTrayMessage(const QString &title, const QString &msg);
|
||||
void addSelectionEntry(QComboBox *sel, const char *description,
|
||||
bool selectable = false, const char *tooltip = "");
|
||||
void populateTransitionSelection(QComboBox *sel, bool addCurrent = true,
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
#include <QStringList>
|
||||
#include <QRegularExpression>
|
||||
#include <QLibrary>
|
||||
#include <proc/readproc.h>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
static Display *xdisplay = 0;
|
||||
|
||||
@@ -363,17 +366,78 @@ bool isFullscreen(const std::string &title)
|
||||
void GetProcessList(QStringList &processes)
|
||||
{
|
||||
processes.clear();
|
||||
for (size_t i = 0; i < getTopLevelWindows().size(); ++i) {
|
||||
std::string s = GetWindowTitle(i);
|
||||
if (s != "")
|
||||
processes << QString::fromStdString(s);
|
||||
PROCTAB *proc = openproc(PROC_FILLSTAT);
|
||||
proc_t proc_info;
|
||||
memset(&proc_info, 0, sizeof(proc_info));
|
||||
while (readproc(proc, &proc_info) != NULL) {
|
||||
QString procName(proc_info.cmd);
|
||||
if (!procName.isEmpty() && !processes.contains(proc_info.cmd)) {
|
||||
processes << QString(proc_info.cmd);
|
||||
}
|
||||
}
|
||||
closeproc(proc);
|
||||
}
|
||||
|
||||
int getForegroundProcessPid()
|
||||
{
|
||||
if (!ewmhIsSupported()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto dpy = disp();
|
||||
Atom active = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", true);
|
||||
Atom actualType;
|
||||
int format;
|
||||
unsigned long num, bytes;
|
||||
Window *window = 0;
|
||||
int pid = -1;
|
||||
|
||||
Window rootWin = RootWindow(dpy, 0);
|
||||
|
||||
int xstatus = XGetWindowProperty(dpy, rootWin, active, 0L, ~0L, false,
|
||||
AnyPropertyType, &actualType, &format,
|
||||
&num, &bytes, (uint8_t **)&window);
|
||||
|
||||
if (xstatus == 0 && window == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
Atom atom, actual_type;
|
||||
int actual_format;
|
||||
unsigned long nitems;
|
||||
unsigned long bytes_after;
|
||||
unsigned char *prop;
|
||||
atom = XInternAtom(dpy, "_NET_WM_PID", True);
|
||||
auto status = XGetWindowProperty(dpy, *window, atom, 0, 1024, False,
|
||||
AnyPropertyType, &actual_type,
|
||||
&actual_format, &nitems, &bytes_after,
|
||||
&prop);
|
||||
|
||||
if (status != 0) {
|
||||
return -2;
|
||||
}
|
||||
if (!prop) {
|
||||
return -3;
|
||||
}
|
||||
|
||||
pid = prop[1] * 256;
|
||||
pid += prop[0];
|
||||
return pid;
|
||||
}
|
||||
|
||||
std::string getProcNameFromPid(int pid)
|
||||
{
|
||||
std::string path = "/proc/" + std::to_string(pid) + "/comm";
|
||||
std::ifstream t(path);
|
||||
std::stringstream buffer;
|
||||
buffer << t.rdbuf();
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
bool isInFocus(const QString &executable)
|
||||
{
|
||||
std::string current;
|
||||
GetCurrentWindowTitle(current);
|
||||
auto pid = getForegroundProcessPid();
|
||||
std::string current = getProcNameFromPid(pid);
|
||||
|
||||
// True if executable switch equals current window
|
||||
bool equals = (executable.toStdString() == current);
|
||||
|
||||
@@ -13,11 +13,21 @@ bool MacroConditionStream::_registered = MacroConditionFactory::Register(
|
||||
static std::map<StreamState, std::string> streamStates = {
|
||||
{StreamState::STOP, "AdvSceneSwitcher.condition.stream.state.stop"},
|
||||
{StreamState::START, "AdvSceneSwitcher.condition.stream.state.start"},
|
||||
{StreamState::STARTING,
|
||||
"AdvSceneSwitcher.condition.stream.state.starting"},
|
||||
{StreamState::STOPPING,
|
||||
"AdvSceneSwitcher.condition.stream.state.stopping"},
|
||||
};
|
||||
|
||||
bool MacroConditionStream::CheckCondition()
|
||||
{
|
||||
bool stateMatch = false;
|
||||
|
||||
bool streamStarting = switcher->lastStreamStartingTime !=
|
||||
_lastStreamStartingTime;
|
||||
bool streamStopping = switcher->lastStreamStoppingTime !=
|
||||
_lastStreamStoppingTime;
|
||||
|
||||
switch (_streamState) {
|
||||
case StreamState::STOP:
|
||||
stateMatch = !obs_frontend_streaming_active();
|
||||
@@ -25,9 +35,22 @@ bool MacroConditionStream::CheckCondition()
|
||||
case StreamState::START:
|
||||
stateMatch = obs_frontend_streaming_active();
|
||||
break;
|
||||
case StreamState::STARTING:
|
||||
stateMatch = streamStarting;
|
||||
break;
|
||||
case StreamState::STOPPING:
|
||||
stateMatch = streamStopping;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (streamStarting) {
|
||||
_lastStreamStartingTime = switcher->lastStreamStartingTime;
|
||||
}
|
||||
if (streamStopping) {
|
||||
_lastStreamStoppingTime = switcher->lastStreamStoppingTime;
|
||||
}
|
||||
return stateMatch;
|
||||
}
|
||||
|
||||
|
||||
@@ -99,6 +99,8 @@ bool MacroConditionVideo::Save(obs_data_t *obj)
|
||||
GetWeakSourceName(_videoSource).c_str());
|
||||
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
|
||||
obs_data_set_string(obj, "filePath", _file.c_str());
|
||||
obs_data_set_bool(obj, "usePatternForChangedCheck",
|
||||
_usePatternForChangedCheck);
|
||||
obs_data_set_double(obj, "threshold", _patternThreshold);
|
||||
obs_data_set_bool(obj, "useAlphaAsMask", _useAlphaAsMask);
|
||||
obs_data_set_string(obj, "modelDataPath", _modelDataPath.c_str());
|
||||
@@ -132,6 +134,8 @@ bool MacroConditionVideo::Load(obs_data_t *obj)
|
||||
_condition =
|
||||
static_cast<VideoCondition>(obs_data_get_int(obj, "condition"));
|
||||
_file = obs_data_get_string(obj, "filePath");
|
||||
_usePatternForChangedCheck =
|
||||
obs_data_get_bool(obj, "usePatternForChangedCheck");
|
||||
_patternThreshold = obs_data_get_double(obj, "threshold");
|
||||
_useAlphaAsMask = obs_data_get_bool(obj, "useAlphaAsMask");
|
||||
_modelDataPath = obs_data_get_string(obj, "modelDataPath");
|
||||
@@ -284,6 +288,18 @@ bool MacroConditionVideo::ScreenshotContainsPattern()
|
||||
return countNonZero(result) > 0;
|
||||
}
|
||||
|
||||
bool MacroConditionVideo::OutputChanged()
|
||||
{
|
||||
if (_usePatternForChangedCheck) {
|
||||
cv::Mat result;
|
||||
_patternData = createPatternData(_matchImage);
|
||||
matchPattern(_screenshotData->image, _patternData,
|
||||
_patternThreshold, result, _useAlphaAsMask);
|
||||
return countNonZero(result) == 0;
|
||||
}
|
||||
return _screenshotData->image != _matchImage;
|
||||
}
|
||||
|
||||
std::vector<cv::Rect> matchObject(QImage &img, cv::CascadeClassifier &cascade,
|
||||
double scaleFactor, int minNeighbors,
|
||||
cv::Size minSize, cv::Size maxSize)
|
||||
@@ -319,9 +335,9 @@ bool MacroConditionVideo::Compare()
|
||||
case VideoCondition::DIFFER:
|
||||
return _screenshotData->image != _matchImage;
|
||||
case VideoCondition::HAS_CHANGED:
|
||||
return _screenshotData->image != _matchImage;
|
||||
return OutputChanged();
|
||||
case VideoCondition::HAS_NOT_CHANGED:
|
||||
return _screenshotData->image == _matchImage;
|
||||
return !OutputChanged();
|
||||
case VideoCondition::NO_IMAGE:
|
||||
return _screenshotData->image.isNull();
|
||||
case VideoCondition::PATTERN:
|
||||
@@ -398,6 +414,10 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
|
||||
|
||||
_imagePath = new FileSelection();
|
||||
_imagePath->Button()->disconnect();
|
||||
_usePatternForChangedCheck = new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.usePatternForChangedCheck"));
|
||||
_usePatternForChangedCheck->setToolTip(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.usePatternForChangedCheck.tooltip"));
|
||||
_patternThreshold = new ThresholdSlider(
|
||||
0., 1.,
|
||||
obs_module_text(
|
||||
@@ -445,6 +465,8 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
|
||||
SLOT(ImagePathChanged(const QString &)));
|
||||
QWidget::connect(_imagePath->Button(), SIGNAL(clicked()), this,
|
||||
SLOT(ImageBrowseButtonClicked()));
|
||||
QWidget::connect(_usePatternForChangedCheck, SIGNAL(stateChanged(int)),
|
||||
this, SLOT(UsePatternForChangedCheckChanged(int)));
|
||||
QWidget::connect(_patternThreshold, SIGNAL(DoubleValueChanged(double)),
|
||||
this, SLOT(PatternThresholdChanged(double)));
|
||||
QWidget::connect(_useAlphaAsMask, SIGNAL(stateChanged(int)), this,
|
||||
@@ -523,6 +545,7 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
|
||||
showMatchLayout->addStretch();
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(entryLine1Layout);
|
||||
mainLayout->addWidget(_usePatternForChangedCheck);
|
||||
mainLayout->addWidget(_patternThreshold);
|
||||
mainLayout->addWidget(_useAlphaAsMask);
|
||||
mainLayout->addLayout(_modelPathLayout);
|
||||
@@ -683,6 +706,18 @@ void MacroConditionVideoEdit::ImageBrowseButtonClicked()
|
||||
ImagePathChanged(path);
|
||||
}
|
||||
|
||||
void MacroConditionVideoEdit::UsePatternForChangedCheckChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_usePatternForChangedCheck = value;
|
||||
_patternThreshold->setVisible(value);
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
void MacroConditionVideoEdit::PatternThresholdChanged(double value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
@@ -828,9 +863,9 @@ void MacroConditionVideoEdit::ShowMatchClicked()
|
||||
}
|
||||
|
||||
QImage markedIamge;
|
||||
QImage pattern = _entryData->GetMatchImage();
|
||||
if (_entryData->_condition == VideoCondition::PATTERN) {
|
||||
cv::Mat result;
|
||||
QImage pattern = _entryData->GetMatchImage();
|
||||
matchPattern(screenshot->image, pattern,
|
||||
_entryData->_patternThreshold, result,
|
||||
_entryData->_useAlphaAsMask);
|
||||
@@ -899,6 +934,19 @@ bool needsThrottleControls(VideoCondition cond)
|
||||
cond == VideoCondition::OBJECT;
|
||||
}
|
||||
|
||||
bool needsThreshold(VideoCondition cond)
|
||||
{
|
||||
return cond == VideoCondition::PATTERN ||
|
||||
cond == VideoCondition::HAS_CHANGED ||
|
||||
cond == VideoCondition::HAS_NOT_CHANGED;
|
||||
}
|
||||
|
||||
bool patternControlIsOptional(VideoCondition cond)
|
||||
{
|
||||
return cond == VideoCondition::HAS_CHANGED ||
|
||||
cond == VideoCondition::HAS_NOT_CHANGED;
|
||||
}
|
||||
|
||||
void setLayoutVisible(QLayout *layout, bool visible)
|
||||
{
|
||||
for (int i = 0; i < layout->count(); ++i) {
|
||||
@@ -912,8 +960,9 @@ void setLayoutVisible(QLayout *layout, bool visible)
|
||||
void MacroConditionVideoEdit::SetWidgetVisibility()
|
||||
{
|
||||
_imagePath->setVisible(requiresFileInput(_entryData->_condition));
|
||||
_patternThreshold->setVisible(_entryData->_condition ==
|
||||
VideoCondition::PATTERN);
|
||||
_usePatternForChangedCheck->setVisible(
|
||||
patternControlIsOptional(_entryData->_condition));
|
||||
_patternThreshold->setVisible(needsThreshold(_entryData->_condition));
|
||||
_useAlphaAsMask->setVisible(_entryData->_condition ==
|
||||
VideoCondition::PATTERN);
|
||||
_showMatch->setVisible(needsShowMatch(_entryData->_condition));
|
||||
@@ -931,6 +980,13 @@ void MacroConditionVideoEdit::SetWidgetVisibility()
|
||||
needsObjectControls(_entryData->_condition));
|
||||
setLayoutVisible(_throttleControlLayout,
|
||||
needsThrottleControls(_entryData->_condition));
|
||||
|
||||
if (_entryData->_condition == VideoCondition::HAS_CHANGED ||
|
||||
_entryData->_condition == VideoCondition::HAS_NOT_CHANGED) {
|
||||
_patternThreshold->setVisible(
|
||||
_entryData->_usePatternForChangedCheck);
|
||||
}
|
||||
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
@@ -944,6 +1000,8 @@ void MacroConditionVideoEdit::UpdateEntryData()
|
||||
GetWeakSourceName(_entryData->_videoSource).c_str());
|
||||
_condition->setCurrentIndex(static_cast<int>(_entryData->_condition));
|
||||
_imagePath->SetPath(QString::fromStdString(_entryData->_file));
|
||||
_usePatternForChangedCheck->setChecked(
|
||||
_entryData->_usePatternForChangedCheck);
|
||||
_patternThreshold->SetDoubleValue(_entryData->_patternThreshold);
|
||||
_useAlphaAsMask->setChecked(_entryData->_useAlphaAsMask);
|
||||
_modelDataPath->SetPath(_entryData->GetModelDataPath().c_str());
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <QTimer>
|
||||
#include <QMessageBox>
|
||||
#include <QJsonDocument>
|
||||
#include <QSystemTrayIcon>
|
||||
#include <unordered_map>
|
||||
#include <regex>
|
||||
#include <set>
|
||||
@@ -443,6 +444,17 @@ bool DisplayMessage(const QString &msg, bool question)
|
||||
return false;
|
||||
}
|
||||
|
||||
void DisplayTrayMessage(const QString &title, const QString &msg)
|
||||
{
|
||||
auto tray = reinterpret_cast<QSystemTrayIcon *>(
|
||||
obs_frontend_get_system_tray());
|
||||
if (!tray) {
|
||||
return;
|
||||
}
|
||||
|
||||
tray->showMessage(title, msg);
|
||||
}
|
||||
|
||||
void addSelectionEntry(QComboBox *sel, const char *description, bool selectable,
|
||||
const char *tooltip)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user