diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index 768edafb4..17dc8372f 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -778,6 +778,7 @@ Basic.Settings.General.Theme="Theme" Basic.Settings.General.Language="Language" Basic.Settings.General.EnableAutoUpdates="Automatically check for updates on startup" Basic.Settings.General.OpenStatsOnStartup="Open stats dialog on startup" +Basic.Settings.General.HideOBSWindowsFromCapture="Hide OBS windows from display capture" Basic.Settings.General.WarnBeforeStartingStream="Show confirmation dialog when starting streams" Basic.Settings.General.WarnBeforeStoppingStream="Show confirmation dialog when stopping streams" Basic.Settings.General.WarnBeforeStoppingRecord="Show confirmation dialog when stopping recording" diff --git a/UI/forms/OBSBasicSettings.ui b/UI/forms/OBSBasicSettings.ui index fee695752..e180119ae 100644 --- a/UI/forms/OBSBasicSettings.ui +++ b/UI/forms/OBSBasicSettings.ui @@ -245,6 +245,13 @@ + + + + Basic.Settings.General.HideOBSWindowsFromCapture + + + diff --git a/UI/obs-app.cpp b/UI/obs-app.cpp index 87f458b84..48f6b41ff 100644 --- a/UI/obs-app.cpp +++ b/UI/obs-app.cpp @@ -1571,6 +1571,44 @@ bool OBSApp::TranslateString(const char *lookupVal, const char **out) const return text_lookup_getstr(App()->GetTextLookup(), lookupVal, out); } +// Global handler to receive all QEvent::Show events so we can apply +// display affinity on any newly created windows and dialogs without +// caring where they are coming from (e.g. plugins). +bool OBSApp::notify(QObject *receiver, QEvent *e) +{ + QWidget *w; + QWindow *window; + int windowType; + + if (!receiver->isWidgetType()) + goto skip; + + if (e->type() != QEvent::Show) + goto skip; + + w = qobject_cast(receiver); + + if (!w->isWindow()) + goto skip; + + window = w->windowHandle(); + if (!window) + goto skip; + + windowType = window->flags() & Qt::WindowType::WindowType_Mask; + + if (windowType == Qt::WindowType::Dialog || + windowType == Qt::WindowType::Window || + windowType == Qt::WindowType::Tool) { + OBSBasic *main = reinterpret_cast(GetMainWindow()); + if (main) + main->SetDisplayAffinity(window); + } + +skip: + return QApplication::notify(receiver, e); +} + QString OBSTranslator::translate(const char *context, const char *sourceText, const char *disambiguation, int n) const { diff --git a/UI/obs-app.hpp b/UI/obs-app.hpp index a534f6d25..1d76fe38b 100644 --- a/UI/obs-app.hpp +++ b/UI/obs-app.hpp @@ -105,6 +105,8 @@ private: void AddExtraThemeColor(QPalette &pal, int group, const char *name, uint32_t color); + bool notify(QObject *receiver, QEvent *e) override; + public: OBSApp(int &argc, char **argv, profiler_name_store_t *store); ~OBSApp(); diff --git a/UI/platform-osx.mm b/UI/platform-osx.mm index 35891b1b9..2c9e37ada 100644 --- a/UI/platform-osx.mm +++ b/UI/platform-osx.mm @@ -196,6 +196,12 @@ void SetAlwaysOnTop(QWidget *window, bool enable) window->show(); } +bool SetDisplayAffinitySupported(void) +{ + // Not implemented yet + return false; +} + typedef void (*set_int_t)(int); void EnableOSXVSync(bool enable) diff --git a/UI/platform-windows.cpp b/UI/platform-windows.cpp index 9e757f73f..c0cad3577 100644 --- a/UI/platform-windows.cpp +++ b/UI/platform-windows.cpp @@ -162,6 +162,20 @@ uint32_t GetWindowsVersion() return ver; } +uint32_t GetWindowsBuild() +{ + static uint32_t build = 0; + + if (build == 0) { + struct win_version_info ver_info; + + get_win_ver(&ver_info); + build = ver_info.build; + } + + return build; +} + void SetAeroEnabled(bool enable) { static HRESULT(WINAPI * func)(UINT) = nullptr; @@ -229,6 +243,27 @@ void SetWin32DropStyle(QWidget *window) SetWindowLongPtr(hwnd, GWL_EXSTYLE, ex_style); } +bool SetDisplayAffinitySupported(void) +{ + static bool checked = false; + static bool supported; + + /* this has to be version gated as setting WDA_EXCLUDEFROMCAPTURE on + older Windows builds behaves like WDA_MONITOR (black box) */ + + if (!checked) { + if (GetWindowsVersion() > 0x0A00 || + GetWindowsVersion() == 0x0A00 && GetWindowsBuild() > 19041) + supported = true; + else + supported = false; + + checked = true; + } + + return supported; +} + bool DisableAudioDucking(bool disable) { ComPtr devEmum; diff --git a/UI/platform-x11.cpp b/UI/platform-x11.cpp index 60035210a..536b1ea86 100644 --- a/UI/platform-x11.cpp +++ b/UI/platform-x11.cpp @@ -251,3 +251,9 @@ void SetAlwaysOnTop(QWidget *window, bool enable) window->setWindowFlags(flags); window->show(); } + +bool SetDisplayAffinitySupported(void) +{ + // Not implemented yet + return false; +} diff --git a/UI/platform.hpp b/UI/platform.hpp index 7bac1e1f7..30ef393d8 100644 --- a/UI/platform.hpp +++ b/UI/platform.hpp @@ -37,8 +37,11 @@ std::vector GetPreferredLocales(); bool IsAlwaysOnTop(QWidget *window); void SetAlwaysOnTop(QWidget *window, bool enable); +bool SetDisplayAffinitySupported(void); + #ifdef _WIN32 uint32_t GetWindowsVersion(); +uint32_t GetWindowsBuild(); void SetAeroEnabled(bool enable); void SetProcessPriority(const char *priority); void SetWin32DropStyle(QWidget *window); diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index 53310ae65..7670af985 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -9947,3 +9947,29 @@ void OBSBasic::UpdatePreviewSafeAreas() drawSafeAreas = config_get_bool(App()->GlobalConfig(), "BasicWindow", "ShowSafeAreas"); } + +void OBSBasic::SetDisplayAffinity(QWindow *window) +{ + if (!SetDisplayAffinitySupported()) + return; + + bool hideFromCapture = config_get_bool(App()->GlobalConfig(), + "BasicWindow", + "HideOBSWindowsFromCapture"); + + // Don't hide projectors, those are designed to be visible / captured + if (window->property("isOBSProjectorWindow") == true) + return; + +#ifdef _WIN32 + HWND hwnd = (HWND)window->winId(); + + if (hideFromCapture) + SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE); + else + SetWindowDisplayAffinity(hwnd, WDA_NONE); +#else +// TODO: Implement for other platforms if possible. Don't forget to +// implement SetDisplayAffinitySupported too! +#endif +} diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp index d68070041..565314bcb 100644 --- a/UI/window-basic-main.hpp +++ b/UI/window-basic-main.hpp @@ -943,6 +943,8 @@ public: void UpdateEditMenu(); + void SetDisplayAffinity(QWindow *window); + protected: virtual void closeEvent(QCloseEvent *event) override; virtual void changeEvent(QEvent *event) override; diff --git a/UI/window-basic-settings.cpp b/UI/window-basic-settings.cpp index b5d4be7ee..10b278c33 100644 --- a/UI/window-basic-settings.cpp +++ b/UI/window-basic-settings.cpp @@ -383,6 +383,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) HookWidget(ui->theme, COMBO_CHANGED, GENERAL_CHANGED); HookWidget(ui->enableAutoUpdates, CHECK_CHANGED, GENERAL_CHANGED); HookWidget(ui->openStatsOnStartup, CHECK_CHANGED, GENERAL_CHANGED); + HookWidget(ui->hideOBSFromCapture, CHECK_CHANGED, GENERAL_CHANGED); HookWidget(ui->warnBeforeStreamStart,CHECK_CHANGED, GENERAL_CHANGED); HookWidget(ui->warnBeforeStreamStop, CHECK_CHANGED, GENERAL_CHANGED); HookWidget(ui->warnBeforeRecordStop, CHECK_CHANGED, GENERAL_CHANGED); @@ -589,6 +590,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) #ifdef _WIN32 uint32_t winVer = GetWindowsVersion(); if (winVer > 0 && winVer < 0x602) { + // Older than Windows 8 toggleAero = new QCheckBox( QTStr("Basic.Settings.Video.DisableAero"), this); QFormLayout *videoLayout = reinterpret_cast( @@ -600,6 +602,11 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) &OBSBasicSettings::ToggleDisableAero); } + if (!SetDisplayAffinitySupported()) { + delete ui->hideOBSFromCapture; + ui->hideOBSFromCapture = nullptr; + } + #define PROCESS_PRIORITY(val) \ { \ "Basic.Settings.Advanced.General.ProcessPriority."##val, val \ @@ -627,6 +634,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) delete ui->processPriority; delete ui->enableNewSocketLoop; delete ui->enableLowLatencyMode; + delete ui->hideOBSFromCapture; #ifdef __linux__ delete ui->browserHWAccel; delete ui->sourcesGroup; @@ -642,6 +650,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) ui->processPriority = nullptr; ui->enableNewSocketLoop = nullptr; ui->enableLowLatencyMode = nullptr; + ui->hideOBSFromCapture = nullptr; #ifdef __linux__ ui->browserHWAccel = nullptr; ui->sourcesGroup = nullptr; @@ -1226,6 +1235,15 @@ void OBSBasicSettings::LoadGeneralSettings() "OpenStatsOnStartup"); ui->openStatsOnStartup->setChecked(openStatsOnStartup); +#if defined(_WIN32) + if (ui->hideOBSFromCapture) { + bool hideWindowFromCapture = + config_get_bool(GetGlobalConfig(), "BasicWindow", + "HideOBSWindowsFromCapture"); + ui->hideOBSFromCapture->setChecked(hideWindowFromCapture); + } +#endif + bool recordWhenStreaming = config_get_bool( GetGlobalConfig(), "BasicWindow", "RecordWhenStreaming"); ui->recordWhenStreaming->setChecked(recordWhenStreaming); @@ -2974,6 +2992,20 @@ void OBSBasicSettings::SaveGeneralSettings() config_set_bool(GetGlobalConfig(), "General", "EnableAutoUpdates", ui->enableAutoUpdates->isChecked()); +#endif +#ifdef _WIN32 + if (WidgetChanged(ui->hideOBSFromCapture)) { + bool hide_window = ui->hideOBSFromCapture->isChecked(); + config_set_bool(GetGlobalConfig(), "BasicWindow", + "HideOBSWindowsFromCapture", hide_window); + + QWindowList windows = QGuiApplication::allWindows(); + for (auto window : windows) { + if (window->isVisible()) { + main->SetDisplayAffinity(window); + } + } + } #endif if (WidgetChanged(ui->openStatsOnStartup)) config_set_bool(main->Config(), "General", "OpenStatsOnStartup", diff --git a/UI/window-projector.cpp b/UI/window-projector.cpp index 7eb04e011..f831a3b50 100644 --- a/UI/window-projector.cpp +++ b/UI/window-projector.cpp @@ -30,6 +30,10 @@ OBSProjector::OBSProjector(QWidget *widget, obs_source_t *source_, int monitor, if (isAlwaysOnTop) setWindowFlags(Qt::WindowStaysOnTopHint); + // Mark the window as a projector so SetDisplayAffinity + // can skip it + windowHandle()->setProperty("isOBSProjectorWindow", true); + #if defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__) // Prevents resizing of projector windows setAttribute(Qt::WA_PaintOnScreen, false);