diff --git a/obs/data/locale/en-US.ini b/obs/data/locale/en-US.ini
index 81d72f809..c09e8e39d 100644
--- a/obs/data/locale/en-US.ini
+++ b/obs/data/locale/en-US.ini
@@ -250,6 +250,7 @@ Basic.MainMenu.File.Remux="Re&mux Recordings"
Basic.MainMenu.File.Settings="&Settings"
Basic.MainMenu.File.ShowSettingsFolder="Show Settings Folder"
Basic.MainMenu.File.ShowProfileFolder="Show Profile Folder"
+Basic.MainMenu.AlwaysOnTop="&Always On Top"
Basic.MainMenu.File.Exit="E&xit"
# basic mode edit menu
diff --git a/obs/forms/OBSBasic.ui b/obs/forms/OBSBasic.ui
index 8ca9bae68..9449a8549 100644
--- a/obs/forms/OBSBasic.ui
+++ b/obs/forms/OBSBasic.ui
@@ -542,6 +542,8 @@
+
+
diff --git a/obs/platform-osx.mm b/obs/platform-osx.mm
index a230cd99a..8f68ea43f 100644
--- a/obs/platform-osx.mm
+++ b/obs/platform-osx.mm
@@ -130,3 +130,21 @@ vector GetPreferredLocales()
return result;
}
+
+bool IsAlwaysOnTop(QMainWindow *window)
+{
+ return (window->windowFlags() & Qt::WindowStaysOnTopHint) != 0;
+}
+
+void SetAlwaysOnTop(QMainWindow *window, bool enable)
+{
+ Qt::WindowFlags flags = window->windowFlags();
+
+ if (enable)
+ flags |= Qt::WindowStaysOnTopHint;
+ else
+ flags &= ~Qt::WindowStaysOnTopHint;
+
+ window->setWindowFlags(flags);
+ window->show();
+}
diff --git a/obs/platform-windows.cpp b/obs/platform-windows.cpp
index 3d624909b..c34bbd565 100644
--- a/obs/platform-windows.cpp
+++ b/obs/platform-windows.cpp
@@ -202,3 +202,16 @@ void SetAeroEnabled(bool enable)
func(enable ? DWM_EC_ENABLECOMPOSITION : DWM_EC_DISABLECOMPOSITION);
}
+
+bool IsAlwaysOnTop(QMainWindow *window)
+{
+ DWORD exStyle = GetWindowLong((HWND)window->winId(), GWL_EXSTYLE);
+ return (exStyle & WS_EX_TOPMOST) != 0;
+}
+
+void SetAlwaysOnTop(QMainWindow *window, bool enable)
+{
+ HWND hwnd = (HWND)window->winId();
+ SetWindowPos(hwnd, enable ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0,
+ SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
+}
diff --git a/obs/platform-x11.cpp b/obs/platform-x11.cpp
index 424a8f78f..582d626d3 100644
--- a/obs/platform-x11.cpp
+++ b/obs/platform-x11.cpp
@@ -147,3 +147,21 @@ vector GetPreferredLocales()
return {};
}
+
+bool IsAlwaysOnTop(QMainWindow *window)
+{
+ return (window->windowFlags() & Qt::WindowStaysOnTopHint) != 0;
+}
+
+void SetAlwaysOnTop(QMainWindow *window, bool enable)
+{
+ Qt::WindowFlags flags = window->windowFlags();
+
+ if (enable)
+ flags |= Qt::WindowStaysOnTopHint;
+ else
+ flags &= ~Qt::WindowStaysOnTopHint;
+
+ window->setWindowFlags(flags);
+ window->show();
+}
diff --git a/obs/platform.hpp b/obs/platform.hpp
index 30a6ae33d..5f1658910 100644
--- a/obs/platform.hpp
+++ b/obs/platform.hpp
@@ -22,6 +22,8 @@
#include
#include
+class QMainWindow;
+
struct MonitorInfo {
int32_t x, y;
uint32_t cx, cy;
@@ -43,6 +45,9 @@ std::string GetDefaultVideoSavePath();
std::vector GetPreferredLocales();
+bool IsAlwaysOnTop(QMainWindow *window);
+void SetAlwaysOnTop(QMainWindow *window, bool enable);
+
#ifdef _WIN32
uint32_t GetWindowsVersion();
void SetAeroEnabled(bool enable);
diff --git a/obs/window-basic-main.cpp b/obs/window-basic-main.cpp
index d0994ac6a..d944febaa 100644
--- a/obs/window-basic-main.cpp
+++ b/obs/window-basic-main.cpp
@@ -928,7 +928,20 @@ void OBSBasic::OBSInit()
connect(ui->preview, &OBSQTDisplay::DisplayCreated, addDisplay);
+#ifdef _WIN32
show();
+#endif
+
+ bool alwaysOnTop = config_get_bool(App()->GlobalConfig(), "BasicWindow",
+ "AlwaysOnTop");
+ if (alwaysOnTop) {
+ SetAlwaysOnTop(this, true);
+ ui->actionAlwaysOnTop->setChecked(true);
+ }
+
+#ifndef _WIN32
+ show();
+#endif
QList defSizes;
@@ -1177,6 +1190,7 @@ OBSBasic::~OBSBasic()
QRect lastGeom = normalGeometry();
QList splitterSizes = ui->mainSplitter->sizes();
+ bool alwaysOnTop = IsAlwaysOnTop(this);
config_set_int(App()->GlobalConfig(), "BasicWindow", "cx",
lastGeom.width());
@@ -1192,6 +1206,8 @@ OBSBasic::~OBSBasic()
splitterSizes[1]);
config_set_bool(App()->GlobalConfig(), "BasicWindow", "PreviewEnabled",
previewEnabled);
+ config_set_bool(App()->GlobalConfig(), "BasicWindow", "AlwaysOnTop",
+ alwaysOnTop);
config_save_safe(App()->GlobalConfig(), "tmp", nullptr);
#ifdef _WIN32
@@ -3326,6 +3342,28 @@ void OBSBasic::on_previewDisabledLabel_customContextMenuRequested(
UNUSED_PARAMETER(pos);
}
+void OBSBasic::on_actionAlwaysOnTop_triggered()
+{
+ CloseDialogs();
+
+ /* Make sure all dialogs are safely and successfully closed before
+ * switching the always on top mode due to the fact that windows all
+ * have to be recreated, so queue the actual toggle to happen after
+ * all events related to closing the dialogs have finished */
+ QMetaObject::invokeMethod(this, "ToggleAlwaysOnTop",
+ Qt::QueuedConnection);
+}
+
+void OBSBasic::ToggleAlwaysOnTop()
+{
+ bool isAlwaysOnTop = IsAlwaysOnTop(this);
+
+ ui->actionAlwaysOnTop->setChecked(!isAlwaysOnTop);
+ SetAlwaysOnTop(this, !isAlwaysOnTop);
+
+ show();
+}
+
void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const
{
const char *val = config_get_string(basicConfig, "Video", "FPSCommon");
diff --git a/obs/window-basic-main.hpp b/obs/window-basic-main.hpp
index 6d84f9db8..fb2eda4e6 100644
--- a/obs/window-basic-main.hpp
+++ b/obs/window-basic-main.hpp
@@ -229,6 +229,8 @@ private slots:
void RemoveSelectedScene();
void RemoveSelectedSceneItem();
+ void ToggleAlwaysOnTop();
+
void ReorderSources(OBSScene scene);
void ProcessHotkey(obs_hotkey_id id, bool pressed);
@@ -377,6 +379,8 @@ private slots:
void on_actionShowSettingsFolder_triggered();
void on_actionShowProfileFolder_triggered();
+ void on_actionAlwaysOnTop_triggered();
+
void logUploadFinished(const QString &text, const QString &error);
void updateFileFinished(const QString &text, const QString &error);