diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini
index 0d898995b83b5d..0f1e92fba79f48 100644
--- a/UI/data/locale/en-US.ini
+++ b/UI/data/locale/en-US.ini
@@ -101,6 +101,8 @@ LogViewer="Log Viewer"
ShowOnStartup="Show on startup"
OpenFile="Open file"
AddValue="Add %1"
+SetAsGlobal="Set as Global"
+RemoveAsGlobal="Remove as Global"
# warning if program already open
AlreadyRunning.Title="OBS is already running"
diff --git a/UI/data/themes/Acri.qss b/UI/data/themes/Acri.qss
index 1a8d2450140d24..06f61a2e9f4da6 100644
--- a/UI/data/themes/Acri.qss
+++ b/UI/data/themes/Acri.qss
@@ -1070,6 +1070,8 @@ OBSBasic {
qproperty-groupIcon: url(./Dark/sources/group.svg);
qproperty-sceneIcon: url(./Dark/sources/scene.svg);
qproperty-defaultIcon: url(./Dark/sources/default.svg);
+ qproperty-pinIcon: url(./Dark/pin.svg);
+ qproperty-pinSelectedIcon: url(:res/images/pin_selected.svg);
}
/* Scene Tree */
diff --git a/UI/data/themes/Dark.qss b/UI/data/themes/Dark.qss
index c8612ead0e3062..d927a2920f9a08 100644
--- a/UI/data/themes/Dark.qss
+++ b/UI/data/themes/Dark.qss
@@ -795,6 +795,8 @@ OBSBasic {
qproperty-groupIcon: url(./Dark/sources/group.svg);
qproperty-sceneIcon: url(./Dark/sources/scene.svg);
qproperty-defaultIcon: url(./Dark/sources/default.svg);
+ qproperty-pinIcon: url(./Dark/pin.svg);
+ qproperty-pinSelectedIcon: url(:res/images/pin_selected.svg);
}
/* Scene Tree */
diff --git a/UI/data/themes/Dark/pin.svg b/UI/data/themes/Dark/pin.svg
new file mode 100644
index 00000000000000..7fcab91ceef838
--- /dev/null
+++ b/UI/data/themes/Dark/pin.svg
@@ -0,0 +1,3 @@
+
diff --git a/UI/data/themes/Rachni.qss b/UI/data/themes/Rachni.qss
index 7e069345f52f19..75a8569a8cd948 100644
--- a/UI/data/themes/Rachni.qss
+++ b/UI/data/themes/Rachni.qss
@@ -1381,6 +1381,8 @@ OBSBasic {
qproperty-groupIcon: url(./Dark/sources/group.svg);
qproperty-sceneIcon: url(./Dark/sources/scene.svg);
qproperty-defaultIcon: url(./Dark/sources/default.svg);
+ qproperty-pinIcon: url(./Dark/pin.svg);
+ qproperty-pinSelectedIcon: url(:res/images/pin_selected.svg);
}
/* Scene Tree */
diff --git a/UI/data/themes/System.qss b/UI/data/themes/System.qss
index 93c935a5477c0a..a169562e6dabbc 100644
--- a/UI/data/themes/System.qss
+++ b/UI/data/themes/System.qss
@@ -231,6 +231,8 @@ OBSBasic {
qproperty-groupIcon: url(:res/images/sources/group.svg);
qproperty-sceneIcon: url(:res/images/sources/scene.svg);
qproperty-defaultIcon: url(:res/images/sources/default.svg);
+ qproperty-pinIcon: url(:res/images/pin.svg);
+ qproperty-pinSelectedIcon: url(:res/images/pin_selected.svg);
}
/* Scene Tree */
diff --git a/UI/forms/images/pin.svg b/UI/forms/images/pin.svg
new file mode 100644
index 00000000000000..abbb76a595e475
--- /dev/null
+++ b/UI/forms/images/pin.svg
@@ -0,0 +1,3 @@
+
diff --git a/UI/forms/images/pin_selected.svg b/UI/forms/images/pin_selected.svg
new file mode 100644
index 00000000000000..bb51797c3df5ae
--- /dev/null
+++ b/UI/forms/images/pin_selected.svg
@@ -0,0 +1,3 @@
+
diff --git a/UI/forms/obs.qrc b/UI/forms/obs.qrc
index 9ff1a1d7e9264d..e41d8488c931b5 100644
--- a/UI/forms/obs.qrc
+++ b/UI/forms/obs.qrc
@@ -53,6 +53,8 @@
images/media/media_restart.svg
images/media/media_stop.svg
images/interact.svg
+ images/pin.svg
+ images/pin_selected.svg
images/settings/output.svg
diff --git a/UI/scene-tree.cpp b/UI/scene-tree.cpp
index 8e3006305b68e7..b00e3dfc258429 100644
--- a/UI/scene-tree.cpp
+++ b/UI/scene-tree.cpp
@@ -13,6 +13,7 @@ SceneTree::SceneTree(QWidget *parent_) : QListWidget(parent_)
installEventFilter(this);
setDragDropMode(InternalMove);
setMovement(QListView::Snap);
+ setIconSize(QSize(16, 16));
}
void SceneTree::SetGridMode(bool grid)
diff --git a/UI/window-basic-main-icons.cpp b/UI/window-basic-main-icons.cpp
index 56096bbed66a15..849455e6f772d6 100644
--- a/UI/window-basic-main-icons.cpp
+++ b/UI/window-basic-main-icons.cpp
@@ -112,6 +112,16 @@ void OBSBasic::SetDefaultIcon(const QIcon &icon)
defaultIcon = icon;
}
+void OBSBasic::SetPinIcon(const QIcon &icon)
+{
+ pinIcon = icon;
+}
+
+void OBSBasic::SetPinSelectedIcon(const QIcon &icon)
+{
+ pinSelectedIcon = icon;
+}
+
QIcon OBSBasic::GetImageIcon() const
{
return imageIcon;
@@ -186,3 +196,13 @@ QIcon OBSBasic::GetDefaultIcon() const
{
return defaultIcon;
}
+
+QIcon OBSBasic::GetPinIcon() const
+{
+ return pinIcon;
+}
+
+QIcon OBSBasic::GetPinSelectedIcon() const
+{
+ return pinSelectedIcon;
+}
diff --git a/UI/window-basic-main-transitions.cpp b/UI/window-basic-main-transitions.cpp
index b68e359feb4540..8d77210ae4d7e9 100644
--- a/UI/window-basic-main-transitions.cpp
+++ b/UI/window-basic-main-transitions.cpp
@@ -722,7 +722,10 @@ template static T GetOBSRef(QListWidgetItem *item)
void OBSBasic::SetCurrentScene(OBSSource scene, bool force)
{
- if (!IsPreviewProgramMode()) {
+ if (SceneIsGlobal(obs_scene_from_source(scene))) {
+ obs_set_output_source(7, scene);
+ UpdatePinIcons();
+ } else if (!IsPreviewProgramMode()) {
TransitionToScene(scene, force);
} else {
OBSSource actualLastScene = OBSGetStrongRef(lastScene);
diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp
index 9ed75982f6ba82..670214628b9c62 100644
--- a/UI/window-basic-main.cpp
+++ b/UI/window-basic-main.cpp
@@ -443,6 +443,8 @@ OBSBasic::OBSBasic(QWidget *parent)
connect(ui->scenes, SIGNAL(scenesReordered()), this,
SLOT(ScenesReordered()));
+
+ connect(App(), &OBSApp::StyleChanged, this, &OBSBasic::UpdatePinIcons);
}
static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent,
@@ -513,14 +515,17 @@ static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder,
/* -------------------------------- */
obs_source_t *transition = obs_get_output_source(0);
+ obs_source_t *global = obs_get_output_source(7);
obs_source_t *currentScene = obs_scene_get_source(scene);
const char *sceneName = obs_source_get_name(currentScene);
const char *programName = obs_source_get_name(curProgramScene);
+ const char *globalSceneName = obs_source_get_name(global);
const char *sceneCollection = config_get_string(
App()->GlobalConfig(), "Basic", "SceneCollection");
obs_data_set_string(saveData, "current_scene", sceneName);
+ obs_data_set_string(saveData, "current_global_scene", globalSceneName);
obs_data_set_string(saveData, "current_program_scene", programName);
obs_data_set_array(saveData, "scene_order", sceneOrder);
obs_data_set_string(saveData, "name", sceneCollection);
@@ -536,6 +541,7 @@ static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder,
obs_source_get_name(transition));
obs_data_set_int(saveData, "transition_duration", transitionDuration);
obs_source_release(transition);
+ obs_source_release(global);
return saveData;
}
@@ -962,6 +968,8 @@ void OBSBasic::LoadData(obs_data_t *data, const char *file)
obs_data_array_t *groups = obs_data_get_array(data, "groups");
obs_data_array_t *transitions = obs_data_get_array(data, "transitions");
const char *sceneName = obs_data_get_string(data, "current_scene");
+ const char *globalSceneName =
+ obs_data_get_string(data, "current_global_scene");
const char *programSceneName =
obs_data_get_string(data, "current_program_scene");
const char *transitionName =
@@ -989,6 +997,7 @@ void OBSBasic::LoadData(obs_data_t *data, const char *file)
obs_source_t *curScene;
obs_source_t *curProgramScene;
obs_source_t *curTransition;
+ obs_source_t *curGlobalScene;
if (!name || !*name)
name = curSceneCollection;
@@ -1038,6 +1047,7 @@ void OBSBasic::LoadData(obs_data_t *data, const char *file)
retryScene:
curScene = obs_get_source_by_name(sceneName);
curProgramScene = obs_get_source_by_name(programSceneName);
+ curGlobalScene = obs_get_source_by_name(globalSceneName);
/* if the starting scene command line parameter is bad at all,
* fall back to original settings */
@@ -1056,11 +1066,11 @@ void OBSBasic::LoadData(obs_data_t *data, const char *file)
obs_source_addref(curScene);
}
- SetCurrentScene(curScene, true);
if (IsPreviewProgramMode())
TransitionToScene(curProgramScene, true);
obs_source_release(curScene);
obs_source_release(curProgramScene);
+ obs_source_release(curGlobalScene);
obs_data_array_release(sources);
obs_data_array_release(groups);
@@ -1175,6 +1185,10 @@ void OBSBasic::LoadData(obs_data_t *data, const char *file)
obs_missing_files_destroy(files);
}
+ LoadGlobalScenes();
+ SetCurrentScene(curGlobalScene);
+ SetCurrentScene(curScene, true);
+
disableSaving--;
if (api) {
@@ -4787,6 +4801,16 @@ void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos)
connect(pasteFilters, SIGNAL(triggered()), this,
SLOT(ScenePasteFilters()));
+ popup.addSeparator();
+
+ bool global = SceneIsGlobal(GetCurrentScene());
+ if (!global)
+ popup.addAction(QTStr("SetAsGlobal"), this,
+ SLOT(SetSceneAsGlobal()));
+ else
+ popup.addAction(QTStr("RemoveAsGlobal"), this,
+ SLOT(RemoveSceneAsGlobal()));
+
popup.addSeparator();
popup.addAction(QTStr("Duplicate"), this,
SLOT(DuplicateSelectedScene()));
@@ -9175,3 +9199,74 @@ void OBSBasic::ShowStatusBarMessage(const QString &message)
ui->statusbar->clearMessage();
ui->statusbar->showMessage(message, 10000);
}
+
+void OBSBasic::LoadGlobalScenes()
+{
+ for (int i = 0; i < ui->scenes->count(); i++) {
+ QListWidgetItem *item = ui->scenes->item(i);
+ OBSScene scene = GetOBSRef(item);
+
+ if (SceneIsGlobal(scene))
+ item->setIcon(GetPinIcon());
+ }
+}
+
+bool OBSBasic::SceneIsGlobal(OBSScene scene)
+{
+ OBSSource source = obs_scene_get_source(scene);
+ obs_data_t *priv_settings = obs_source_get_private_settings(source);
+ bool dsk = obs_data_get_bool(priv_settings, "is_dsk");
+ obs_data_release(priv_settings);
+
+ return dsk;
+}
+
+void OBSBasic::SetSceneAsGlobal()
+{
+ OBSSource source = GetCurrentSceneSource();
+
+ obs_data_t *priv_settings = obs_source_get_private_settings(source);
+ obs_data_set_bool(priv_settings, "is_dsk", true);
+ obs_data_release(priv_settings);
+
+ QListWidgetItem *item = ui->scenes->currentItem();
+ item->setIcon(GetPinIcon());
+
+ SetCurrentScene(source);
+}
+
+void OBSBasic::RemoveSceneAsGlobal()
+{
+ OBSSource source = GetCurrentSceneSource();
+
+ obs_data_t *priv_settings = obs_source_get_private_settings(source);
+ obs_data_set_bool(priv_settings, "is_dsk", false);
+ obs_data_release(priv_settings);
+
+ QListWidgetItem *item = ui->scenes->currentItem();
+ item->setIcon(QIcon());
+ obs_set_output_source(7, nullptr);
+}
+
+void OBSBasic::UpdatePinIcons()
+{
+ OBSSource globalSource = obs_get_output_source(7);
+
+ if (!globalSource)
+ return;
+
+ for (int i = 0; i < ui->scenes->count(); i++) {
+ QListWidgetItem *item = ui->scenes->item(i);
+ OBSScene scene = GetOBSRef(item);
+ OBSSource sceneSource = obs_scene_get_source(scene);
+
+ if (SceneIsGlobal(scene)) {
+ if (globalSource == sceneSource)
+ item->setIcon(GetPinSelectedIcon());
+ else
+ item->setIcon(GetPinIcon());
+ }
+ }
+
+ obs_source_release(globalSource);
+}
diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp
index e6a1f665ad374f..1ed70818a8e566 100644
--- a/UI/window-basic-main.hpp
+++ b/UI/window-basic-main.hpp
@@ -152,6 +152,10 @@ class OBSBasic : public OBSMainWindow {
DESIGNABLE true)
Q_PROPERTY(QIcon defaultIcon READ GetDefaultIcon WRITE SetDefaultIcon
DESIGNABLE true)
+ Q_PROPERTY(
+ QIcon pinIcon READ GetPinIcon WRITE SetPinIcon DESIGNABLE true)
+ Q_PROPERTY(QIcon pinSelectedIcon READ GetPinSelectedIcon WRITE
+ SetPinSelectedIcon DESIGNABLE true)
friend class OBSAbout;
friend class OBSBasicPreview;
@@ -526,6 +530,8 @@ class OBSBasic : public OBSMainWindow {
QIcon groupIcon;
QIcon sceneIcon;
QIcon defaultIcon;
+ QIcon pinIcon;
+ QIcon pinSelectedIcon;
QIcon GetImageIcon() const;
QIcon GetColorIcon() const;
@@ -540,6 +546,8 @@ class OBSBasic : public OBSMainWindow {
QIcon GetMediaIcon() const;
QIcon GetBrowserIcon() const;
QIcon GetDefaultIcon() const;
+ QIcon GetPinIcon() const;
+ QIcon GetPinSelectedIcon() const;
QSlider *tBar;
bool tBarActive = false;
@@ -709,6 +717,8 @@ private slots:
void SetGroupIcon(const QIcon &icon);
void SetSceneIcon(const QIcon &icon);
void SetDefaultIcon(const QIcon &icon);
+ void SetPinIcon(const QIcon &icon);
+ void SetPinSelectedIcon(const QIcon &icon);
void TBarChanged(int value);
void TBarReleased();
@@ -1048,6 +1058,12 @@ public slots:
void UpdateContextBar(bool force = false);
void UpdateContextBarDeferred(bool force = false);
+ bool SceneIsGlobal(OBSScene scene);
+ void SetSceneAsGlobal();
+ void RemoveSceneAsGlobal();
+ void LoadGlobalScenes();
+ void UpdatePinIcons();
+
public:
explicit OBSBasic(QWidget *parent = 0);
virtual ~OBSBasic();