From 586fb34fc81b49b648403b7703c76a70052943f0 Mon Sep 17 00:00:00 2001 From: Kunal Dubey Date: Sun, 31 May 2026 10:07:02 +0530 Subject: [PATCH 01/11] frontend: scaffold for clipboard service and serializer --- frontend/cmake/ui-utility.cmake | 4 +++ frontend/utility/OBSClipboardSerializer.cpp | 21 +++++++++++++++ frontend/utility/OBSClipboardSerializer.hpp | 29 ++++++++++++++++++++ frontend/utility/OBSClipboardService.cpp | 10 +++++++ frontend/utility/OBSClipboardService.hpp | 30 +++++++++++++++++++++ 5 files changed, 94 insertions(+) create mode 100644 frontend/utility/OBSClipboardSerializer.cpp create mode 100644 frontend/utility/OBSClipboardSerializer.hpp create mode 100644 frontend/utility/OBSClipboardService.cpp create mode 100644 frontend/utility/OBSClipboardService.hpp diff --git a/frontend/cmake/ui-utility.cmake b/frontend/cmake/ui-utility.cmake index 387385da8d2f0d..40b31ca97561ef 100644 --- a/frontend/cmake/ui-utility.cmake +++ b/frontend/cmake/ui-utility.cmake @@ -28,6 +28,10 @@ target_sources( utility/MultitrackVideoOutput.cpp utility/MultitrackVideoOutput.hpp utility/NativeEventFilter.hpp + utility/OBSClipboardService.hpp + utility/OBSClipboardService.cpp + utility/OBSClipboardSerializer.hpp + utility/OBSClipboardSerializer.cpp utility/OBSCanvas.cpp utility/OBSCanvas.hpp utility/OBSEventFilter.hpp diff --git a/frontend/utility/OBSClipboardSerializer.cpp b/frontend/utility/OBSClipboardSerializer.cpp new file mode 100644 index 00000000000000..5fbb3d1ef640b9 --- /dev/null +++ b/frontend/utility/OBSClipboardSerializer.cpp @@ -0,0 +1,21 @@ +#include "OBSClipboardSerializer.hpp" + +OBSData OBSClipboardSerializer::SerializeSceneItem(OBSSceneItem item) +{ + return OBSData(); +} + +OBSData OBSClipboardSerializer::SerializeFilters(OBSSource source) +{ + return OBSData(); +} + +OBSData OBSClipboardSerializer::SerializeTransform(OBSSceneItem item) +{ + return OBSData(); +} + +OBSData OBSClipboardSerializer::SerializeTransition(OBSSceneItem item, bool show) +{ + return OBSData(); +} diff --git a/frontend/utility/OBSClipboardSerializer.hpp b/frontend/utility/OBSClipboardSerializer.hpp new file mode 100644 index 00000000000000..59c46794eaa076 --- /dev/null +++ b/frontend/utility/OBSClipboardSerializer.hpp @@ -0,0 +1,29 @@ +/****************************************************************************** + Copyright (C) 2026 by Kunal Dubey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#pragma once + +#include +#include + +class OBSClipboardSerializer { +public: + static OBSData SerializeSceneItem(OBSSceneItem item); + static OBSData SerializeFilters(OBSSource source); + static OBSData SerializeTransform(OBSSceneItem item); + static OBSData SerializeTransition(OBSSceneItem item, bool show); +}; \ No newline at end of file diff --git a/frontend/utility/OBSClipboardService.cpp b/frontend/utility/OBSClipboardService.cpp new file mode 100644 index 00000000000000..6257563c1b82ac --- /dev/null +++ b/frontend/utility/OBSClipboardService.cpp @@ -0,0 +1,10 @@ +#include "OBSClipboardService.hpp" + +bool OBSClipboardService::canPasteSceneItems(bool duplicate) const +{ + return false; +} + +void OBSClipboardService::copySceneItems(const std::vector &items) {} + +void OBSClipboardService::pasteSceneItems(OBSScene scene, bool duplicate) {} diff --git a/frontend/utility/OBSClipboardService.hpp b/frontend/utility/OBSClipboardService.hpp new file mode 100644 index 00000000000000..ca040ae0a9e1e9 --- /dev/null +++ b/frontend/utility/OBSClipboardService.hpp @@ -0,0 +1,30 @@ +/****************************************************************************** + Copyright (C) 2026 by Kunal Dubey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#pragma once + +#include +#include +#include +#include + +class OBSClipboardService { +public: + bool canPasteSceneItems(bool duplicate) const; + void copySceneItems(const std::vector &items); + void pasteSceneItems(OBSScene scene, bool duplicate); +}; \ No newline at end of file From 45400c92ead2dde7c440a536683e2151d92fa652 Mon Sep 17 00:00:00 2001 From: Kunal Dubey Date: Sun, 31 May 2026 11:36:41 +0530 Subject: [PATCH 02/11] frontend: added clipboard mime types --- frontend/cmake/ui-utility.cmake | 1 + frontend/utility/OBSClipboardMimeTypes.hpp | 10 ++++++ frontend/utility/OBSClipboardSerializer.cpp | 21 ++++++++++--- frontend/utility/OBSClipboardSerializer.hpp | 1 - frontend/utility/OBSClipboardService.cpp | 34 +++++++++++++++++++++ frontend/utility/OBSClipboardService.hpp | 13 ++++++-- 6 files changed, 73 insertions(+), 7 deletions(-) create mode 100644 frontend/utility/OBSClipboardMimeTypes.hpp diff --git a/frontend/cmake/ui-utility.cmake b/frontend/cmake/ui-utility.cmake index 40b31ca97561ef..9690bc4270662a 100644 --- a/frontend/cmake/ui-utility.cmake +++ b/frontend/cmake/ui-utility.cmake @@ -32,6 +32,7 @@ target_sources( utility/OBSClipboardService.cpp utility/OBSClipboardSerializer.hpp utility/OBSClipboardSerializer.cpp + utility/OBSClipboardMimeTypes.hpp utility/OBSCanvas.cpp utility/OBSCanvas.hpp utility/OBSEventFilter.hpp diff --git a/frontend/utility/OBSClipboardMimeTypes.hpp b/frontend/utility/OBSClipboardMimeTypes.hpp new file mode 100644 index 00000000000000..6472cacf5c4245 --- /dev/null +++ b/frontend/utility/OBSClipboardMimeTypes.hpp @@ -0,0 +1,10 @@ +#pragma once + +namespace OBSClipboard { +inline constexpr auto SceneItems = "application/x-obs-studio-scene-items+json"; +inline constexpr auto SourceFilters = "application/x-obs-studio-source-filters+json"; +inline constexpr auto SceneItemTransform = "application/x-obs-studio-scene-item-transform+json"; +inline constexpr auto SceneItemTransition = "application/x-obs-studio-scene-item-transition+json"; + +inline constexpr int PayloadVersion = 1; +} // namespace OBSClipboard \ No newline at end of file diff --git a/frontend/utility/OBSClipboardSerializer.cpp b/frontend/utility/OBSClipboardSerializer.cpp index 5fbb3d1ef640b9..1fe4befa482f59 100644 --- a/frontend/utility/OBSClipboardSerializer.cpp +++ b/frontend/utility/OBSClipboardSerializer.cpp @@ -2,20 +2,33 @@ OBSData OBSClipboardSerializer::SerializeSceneItem(OBSSceneItem item) { - return OBSData(); + if (!item) { + return {}; + } + + return {}; } OBSData OBSClipboardSerializer::SerializeFilters(OBSSource source) { - return OBSData(); + if (!source) { + return {}; + } + return {}; } OBSData OBSClipboardSerializer::SerializeTransform(OBSSceneItem item) { - return OBSData(); + if (!item) { + return {}; + } + return {}; } OBSData OBSClipboardSerializer::SerializeTransition(OBSSceneItem item, bool show) { - return OBSData(); + if (!item) { + return {}; + } + return {}; } diff --git a/frontend/utility/OBSClipboardSerializer.hpp b/frontend/utility/OBSClipboardSerializer.hpp index 59c46794eaa076..8318f00e790327 100644 --- a/frontend/utility/OBSClipboardSerializer.hpp +++ b/frontend/utility/OBSClipboardSerializer.hpp @@ -18,7 +18,6 @@ #pragma once #include -#include class OBSClipboardSerializer { public: diff --git a/frontend/utility/OBSClipboardService.cpp b/frontend/utility/OBSClipboardService.cpp index 6257563c1b82ac..f745f0be0bd4b6 100644 --- a/frontend/utility/OBSClipboardService.cpp +++ b/frontend/utility/OBSClipboardService.cpp @@ -1,10 +1,44 @@ #include "OBSClipboardService.hpp" +#include "OBSClipboardMimeTypes.hpp" +#include "OBSClipboardSerializer.hpp" + +#include +#include +#include + bool OBSClipboardService::canPasteSceneItems(bool duplicate) const { return false; } +bool OBSClipboardService::canPasteFilters() const +{ + return false; +} + +bool OBSClipboardService::canPasteTransform() const +{ + return false; +} + +bool OBSClipboardService::canPasteTransition() const +{ + return false; +} + void OBSClipboardService::copySceneItems(const std::vector &items) {} +void OBSClipboardService::copyFilters(OBSSource source) {} + +void OBSClipboardService::copyTransform(OBSSceneItem item) {} + +void OBSClipboardService::copyTransition(OBSSceneItem item, bool show) {} + void OBSClipboardService::pasteSceneItems(OBSScene scene, bool duplicate) {} + +void OBSClipboardService::pasteFilters(OBSSource destination) {} + +void OBSClipboardService::pasteTransform(const std::vector &items) {} + +void OBSClipboardService::pasteTransition(const std::vector &items, bool show) {} diff --git a/frontend/utility/OBSClipboardService.hpp b/frontend/utility/OBSClipboardService.hpp index ca040ae0a9e1e9..dec7fd3a2d3266 100644 --- a/frontend/utility/OBSClipboardService.hpp +++ b/frontend/utility/OBSClipboardService.hpp @@ -18,13 +18,22 @@ #pragma once #include -#include -#include #include class OBSClipboardService { public: bool canPasteSceneItems(bool duplicate) const; + bool canPasteFilters() const; + bool canPasteTransform() const; + bool canPasteTransition() const; + void copySceneItems(const std::vector &items); + void copyFilters(OBSSource source); + void copyTransform(OBSSceneItem item); + void copyTransition(OBSSceneItem item, bool show); + void pasteSceneItems(OBSScene scene, bool duplicate); + void pasteFilters(OBSSource destination); + void pasteTransform(const std::vector &items); + void pasteTransition(const std::vector &items, bool show); }; \ No newline at end of file From 4745db9871f763c4b6358c658ca2f3c595f465d5 Mon Sep 17 00:00:00 2001 From: Kunal Dubey Date: Sun, 31 May 2026 15:31:10 +0530 Subject: [PATCH 03/11] frontend: add serialize and deserialize for transform --- frontend/utility/OBSClipboardSerializer.cpp | 65 ++++++++++++++++++++- frontend/utility/OBSClipboardSerializer.hpp | 5 ++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/frontend/utility/OBSClipboardSerializer.cpp b/frontend/utility/OBSClipboardSerializer.cpp index 1fe4befa482f59..b1daf5dfea8de2 100644 --- a/frontend/utility/OBSClipboardSerializer.cpp +++ b/frontend/utility/OBSClipboardSerializer.cpp @@ -22,7 +22,31 @@ OBSData OBSClipboardSerializer::SerializeTransform(OBSSceneItem item) if (!item) { return {}; } - return {}; + + obs_transform_info transform; + obs_sceneitem_crop crop; + + obs_sceneitem_get_info2(item, &transform); + obs_sceneitem_get_crop(item, &crop); + + OBSDataAutoRelease data = obs_data_create(); + obs_data_set_double(data, "pos_x", transform.pos.x); + obs_data_set_double(data, "pos_y", transform.pos.y); + obs_data_set_double(data, "rot", transform.rot); + obs_data_set_double(data, "scale_x", transform.scale.x); + obs_data_set_double(data, "scale_y", transform.scale.y); + obs_data_set_int(data, "alignment", transform.alignment); + obs_data_set_int(data, "bounds_type", transform.bounds_type); + obs_data_set_int(data, "bounds_alignment", transform.bounds_alignment); + obs_data_set_double(data, "bounds_x", transform.bounds.x); + obs_data_set_double(data, "bounds_y", transform.bounds.y); + + obs_data_set_int(data, "crop_left", crop.left); + obs_data_set_int(data, "crop_top", crop.top); + obs_data_set_int(data, "crop_right", crop.right); + obs_data_set_int(data, "crop_bottom", crop.bottom); + + return OBSData(data); } OBSData OBSClipboardSerializer::SerializeTransition(OBSSceneItem item, bool show) @@ -32,3 +56,42 @@ OBSData OBSClipboardSerializer::SerializeTransition(OBSSceneItem item, bool show } return {}; } + +bool OBSClipboardSerializer::DeserializeSceneItem(OBSData data) +{ + return false; +} + +bool OBSClipboardSerializer::DeserializeFilters(OBSData data) +{ + return false; +} + +bool OBSClipboardSerializer::DeserializeTransform(OBSData data, obs_transform_info &transform, obs_sceneitem_crop &crop) +{ + if (!data) { + return false; + } + transform.pos.x = obs_data_get_double(data, "pos_x"); + transform.pos.y = obs_data_get_double(data, "pos_y"); + transform.rot = obs_data_get_double(data, "rot"); + transform.scale.x = obs_data_get_double(data, "scale_x"); + transform.scale.y = obs_data_get_double(data, "scale_y"); + transform.alignment = obs_data_get_int(data, "alignment"); + transform.bounds_type = static_cast(obs_data_get_int(data, "bounds_type")); + transform.bounds_alignment = obs_data_get_int(data, "bounds_alignment"); + transform.bounds.x = obs_data_get_double(data, "bounds_x"); + transform.bounds.y = obs_data_get_double(data, "bounds_y"); + + crop.left = obs_data_get_int(data, "crop_left"); + crop.top = obs_data_get_int(data, "crop_top"); + crop.right = obs_data_get_int(data, "crop_right"); + crop.bottom = obs_data_get_int(data, "crop_bottom"); + + return true; +} + +bool OBSClipboardSerializer::DeserializeTransition(OBSData data) +{ + return false; +} diff --git a/frontend/utility/OBSClipboardSerializer.hpp b/frontend/utility/OBSClipboardSerializer.hpp index 8318f00e790327..89c1a5caa1503e 100644 --- a/frontend/utility/OBSClipboardSerializer.hpp +++ b/frontend/utility/OBSClipboardSerializer.hpp @@ -25,4 +25,9 @@ class OBSClipboardSerializer { static OBSData SerializeFilters(OBSSource source); static OBSData SerializeTransform(OBSSceneItem item); static OBSData SerializeTransition(OBSSceneItem item, bool show); + + static bool DeserializeSceneItem(OBSData data); + static bool DeserializeFilters(OBSData data); + static bool DeserializeTransform(OBSData data, obs_transform_info &transform, obs_sceneitem_crop &crop); + static bool DeserializeTransition(OBSData data); }; \ No newline at end of file From be0d44bc48d45d85e0c74446761f98f08b075daf Mon Sep 17 00:00:00 2001 From: Kunal Dubey Date: Sun, 31 May 2026 23:15:55 +0530 Subject: [PATCH 04/11] frontend: wired transform to clipboard service --- frontend/utility/OBSClipboardMimeTypes.hpp | 9 +-- frontend/utility/OBSClipboardService.cpp | 72 +++++++++++++++++++++- frontend/utility/OBSClipboardService.hpp | 5 ++ 3 files changed, 79 insertions(+), 7 deletions(-) diff --git a/frontend/utility/OBSClipboardMimeTypes.hpp b/frontend/utility/OBSClipboardMimeTypes.hpp index 6472cacf5c4245..d592d2b8c1721f 100644 --- a/frontend/utility/OBSClipboardMimeTypes.hpp +++ b/frontend/utility/OBSClipboardMimeTypes.hpp @@ -1,10 +1,11 @@ #pragma once +#include namespace OBSClipboard { -inline constexpr auto SceneItems = "application/x-obs-studio-scene-items+json"; -inline constexpr auto SourceFilters = "application/x-obs-studio-source-filters+json"; -inline constexpr auto SceneItemTransform = "application/x-obs-studio-scene-item-transform+json"; -inline constexpr auto SceneItemTransition = "application/x-obs-studio-scene-item-transition+json"; +inline constexpr std::string_view SceneItems = "application/x-obs-studio-scene-items+json"; +inline constexpr std::string_view SourceFilters = "application/x-obs-studio-source-filters+json"; +inline constexpr std::string_view SceneItemTransform = "application/x-obs-studio-scene-item-transform+json"; +inline constexpr std::string_view SceneItemTransition = "application/x-obs-studio-scene-item-transition+json"; inline constexpr int PayloadVersion = 1; } // namespace OBSClipboard \ No newline at end of file diff --git a/frontend/utility/OBSClipboardService.cpp b/frontend/utility/OBSClipboardService.cpp index f745f0be0bd4b6..4f22c66e38a9ac 100644 --- a/frontend/utility/OBSClipboardService.cpp +++ b/frontend/utility/OBSClipboardService.cpp @@ -19,7 +19,7 @@ bool OBSClipboardService::canPasteFilters() const bool OBSClipboardService::canPasteTransform() const { - return false; + return !!getMimeData(OBSClipboard::SceneItemTransform); } bool OBSClipboardService::canPasteTransition() const @@ -31,7 +31,11 @@ void OBSClipboardService::copySceneItems(const std::vector &items) void OBSClipboardService::copyFilters(OBSSource source) {} -void OBSClipboardService::copyTransform(OBSSceneItem item) {} +void OBSClipboardService::copyTransform(OBSSceneItem item) +{ + OBSData payload = OBSClipboardSerializer::SerializeTransform(item); + setMimeData(OBSClipboard::SceneItemTransform, payload); +} void OBSClipboardService::copyTransition(OBSSceneItem item, bool show) {} @@ -39,6 +43,68 @@ void OBSClipboardService::pasteSceneItems(OBSScene scene, bool duplicate) {} void OBSClipboardService::pasteFilters(OBSSource destination) {} -void OBSClipboardService::pasteTransform(const std::vector &items) {} +void OBSClipboardService::pasteTransform(const std::vector &items) +{ + OBSData payload = getMimeData(OBSClipboard::SceneItemTransform); + if (!payload) + return; + + obs_transform_info transform; + obs_sceneitem_crop crop; + + if (!OBSClipboardSerializer::DeserializeTransform(payload, transform, crop)) + return; + + for (OBSSceneItem item : items) { + if (!item || obs_sceneitem_locked(item)) + continue; + + obs_sceneitem_defer_update_begin(item); + obs_sceneitem_set_info2(item, &transform); + obs_sceneitem_set_crop(item, &crop); + obs_sceneitem_defer_update_end(item); + } +} void OBSClipboardService::pasteTransition(const std::vector &items, bool show) {} + +void OBSClipboardService::setMimeData(const std::string_view mimeType, OBSData payload) +{ + if (!mimeType) { + return; + } + if (!payload) { + return; + } + QMimeData *mimeData = new QMimeData(); + + OBSDataAutoRelease envelope = obs_data_create(); + obs_data_set_int(envelope, "version", OBSClipboard::PayloadVersion); + obs_data_set_obj(envelope, "payload", payload); + + mimeData->setData(mimeType, QByteArray(obs_data_get_json(envelope))); + + QApplication::clipboard()->setMimeData(mimeData); +} + +OBSData OBSClipboardService::getMimeData(const std::string_view mimeType) +{ + if (!mimeType) { + return {}; + } + QMimeData *mimeData = new QMimeData(); + if (!mimeData->hasFormat(mimeType)) { + return {}; + } + QByteArray data = mimeData->data(mimeType); + OBSDataAutoRelease envelope = obs_data_create_from_json(data.constData()); + if (!envelope) { + return {}; + } + if (obs_data_get_int(envelope, "version") != OBSClipboard::PayloadVersion) { + return {}; + } + OBSDataAutoRelease payload = obs_data_get_obj(envelope, "payload"); + + return OBSData(payload); +} diff --git a/frontend/utility/OBSClipboardService.hpp b/frontend/utility/OBSClipboardService.hpp index dec7fd3a2d3266..02dcf0d8045dc9 100644 --- a/frontend/utility/OBSClipboardService.hpp +++ b/frontend/utility/OBSClipboardService.hpp @@ -19,6 +19,7 @@ #include #include +#include class OBSClipboardService { public: @@ -36,4 +37,8 @@ class OBSClipboardService { void pasteFilters(OBSSource destination); void pasteTransform(const std::vector &items); void pasteTransition(const std::vector &items, bool show); + +private: + void setMimeData(const std::string_view mimeType, OBSData payload); + OBSData getMimeData(const std::string_view mimeType); }; \ No newline at end of file From a249193f230ad0d03254a89bfcb7083f90a5d600 Mon Sep 17 00:00:00 2001 From: Kunal Dubey Date: Mon, 1 Jun 2026 05:49:17 +0530 Subject: [PATCH 05/11] frontend: refactor clipboard mime types and serializer to use char arrays and improve data handling --- frontend/utility/OBSClipboardMimeTypes.hpp | 11 +++++----- frontend/utility/OBSClipboardSerializer.cpp | 23 +++++++++------------ frontend/utility/OBSClipboardSerializer.hpp | 4 ++-- frontend/utility/OBSClipboardService.cpp | 8 +++---- frontend/utility/OBSClipboardService.hpp | 7 +++---- 5 files changed, 24 insertions(+), 29 deletions(-) diff --git a/frontend/utility/OBSClipboardMimeTypes.hpp b/frontend/utility/OBSClipboardMimeTypes.hpp index d592d2b8c1721f..4b567782661362 100644 --- a/frontend/utility/OBSClipboardMimeTypes.hpp +++ b/frontend/utility/OBSClipboardMimeTypes.hpp @@ -1,11 +1,10 @@ #pragma once -#include namespace OBSClipboard { -inline constexpr std::string_view SceneItems = "application/x-obs-studio-scene-items+json"; -inline constexpr std::string_view SourceFilters = "application/x-obs-studio-source-filters+json"; -inline constexpr std::string_view SceneItemTransform = "application/x-obs-studio-scene-item-transform+json"; -inline constexpr std::string_view SceneItemTransition = "application/x-obs-studio-scene-item-transition+json"; +inline constexpr char SceneItems[] = "application/x-obs-studio-scene-items+json"; +inline constexpr char SourceFilters[] = "application/x-obs-studio-source-filters+json"; +inline constexpr char SceneItemTransform[] = "application/x-obs-studio-scene-item-transform+json"; +inline constexpr char SceneItemTransition[] = "application/x-obs-studio-scene-item-transition+json"; inline constexpr int PayloadVersion = 1; -} // namespace OBSClipboard \ No newline at end of file +} // namespace OBSClipboard diff --git a/frontend/utility/OBSClipboardSerializer.cpp b/frontend/utility/OBSClipboardSerializer.cpp index b1daf5dfea8de2..035c3f279b9233 100644 --- a/frontend/utility/OBSClipboardSerializer.cpp +++ b/frontend/utility/OBSClipboardSerializer.cpp @@ -30,16 +30,14 @@ OBSData OBSClipboardSerializer::SerializeTransform(OBSSceneItem item) obs_sceneitem_get_crop(item, &crop); OBSDataAutoRelease data = obs_data_create(); - obs_data_set_double(data, "pos_x", transform.pos.x); - obs_data_set_double(data, "pos_y", transform.pos.y); + obs_data_set_vec2(data, "pos", &transform.pos); obs_data_set_double(data, "rot", transform.rot); - obs_data_set_double(data, "scale_x", transform.scale.x); - obs_data_set_double(data, "scale_y", transform.scale.y); + obs_data_set_vec2(data, "scale", &transform.scale); obs_data_set_int(data, "alignment", transform.alignment); obs_data_set_int(data, "bounds_type", transform.bounds_type); obs_data_set_int(data, "bounds_alignment", transform.bounds_alignment); - obs_data_set_double(data, "bounds_x", transform.bounds.x); - obs_data_set_double(data, "bounds_y", transform.bounds.y); + obs_data_set_vec2(data, "bounds", &transform.bounds); + obs_data_set_bool(data, "crop_to_bounds", transform.crop_to_bounds); obs_data_set_int(data, "crop_left", crop.left); obs_data_set_int(data, "crop_top", crop.top); @@ -67,21 +65,20 @@ bool OBSClipboardSerializer::DeserializeFilters(OBSData data) return false; } -bool OBSClipboardSerializer::DeserializeTransform(OBSData data, obs_transform_info &transform, obs_sceneitem_crop &crop) +bool OBSClipboardSerializer::DeserializeTransform(const OBSData &data, obs_transform_info &transform, + obs_sceneitem_crop &crop) { if (!data) { return false; } - transform.pos.x = obs_data_get_double(data, "pos_x"); - transform.pos.y = obs_data_get_double(data, "pos_y"); + obs_data_get_vec2(data, "pos", &transform.pos); transform.rot = obs_data_get_double(data, "rot"); - transform.scale.x = obs_data_get_double(data, "scale_x"); - transform.scale.y = obs_data_get_double(data, "scale_y"); + obs_data_get_vec2(data, "scale", &transform.scale); transform.alignment = obs_data_get_int(data, "alignment"); transform.bounds_type = static_cast(obs_data_get_int(data, "bounds_type")); transform.bounds_alignment = obs_data_get_int(data, "bounds_alignment"); - transform.bounds.x = obs_data_get_double(data, "bounds_x"); - transform.bounds.y = obs_data_get_double(data, "bounds_y"); + obs_data_get_vec2(data, "bounds", &transform.bounds); + transform.crop_to_bounds = obs_data_get_bool(data, "crop_to_bounds"); crop.left = obs_data_get_int(data, "crop_left"); crop.top = obs_data_get_int(data, "crop_top"); diff --git a/frontend/utility/OBSClipboardSerializer.hpp b/frontend/utility/OBSClipboardSerializer.hpp index 89c1a5caa1503e..1d742ddb9764e4 100644 --- a/frontend/utility/OBSClipboardSerializer.hpp +++ b/frontend/utility/OBSClipboardSerializer.hpp @@ -28,6 +28,6 @@ class OBSClipboardSerializer { static bool DeserializeSceneItem(OBSData data); static bool DeserializeFilters(OBSData data); - static bool DeserializeTransform(OBSData data, obs_transform_info &transform, obs_sceneitem_crop &crop); + static bool DeserializeTransform(const OBSData &data, obs_transform_info &transform, obs_sceneitem_crop &crop); static bool DeserializeTransition(OBSData data); -}; \ No newline at end of file +}; diff --git a/frontend/utility/OBSClipboardService.cpp b/frontend/utility/OBSClipboardService.cpp index 4f22c66e38a9ac..221f8f175b38c8 100644 --- a/frontend/utility/OBSClipboardService.cpp +++ b/frontend/utility/OBSClipboardService.cpp @@ -68,7 +68,7 @@ void OBSClipboardService::pasteTransform(const std::vector &items) void OBSClipboardService::pasteTransition(const std::vector &items, bool show) {} -void OBSClipboardService::setMimeData(const std::string_view mimeType, OBSData payload) +void OBSClipboardService::setMimeData(const char *mimeType, const OBSData &payload) { if (!mimeType) { return; @@ -87,13 +87,13 @@ void OBSClipboardService::setMimeData(const std::string_view mimeType, OBSData p QApplication::clipboard()->setMimeData(mimeData); } -OBSData OBSClipboardService::getMimeData(const std::string_view mimeType) +OBSData OBSClipboardService::getMimeData(const char *mimeType) const { if (!mimeType) { return {}; } - QMimeData *mimeData = new QMimeData(); - if (!mimeData->hasFormat(mimeType)) { + const QMimeData *mimeData = QApplication::clipboard()->mimeData(); + if (!mimeData || !mimeData->hasFormat(mimeType)) { return {}; } QByteArray data = mimeData->data(mimeType); diff --git a/frontend/utility/OBSClipboardService.hpp b/frontend/utility/OBSClipboardService.hpp index 02dcf0d8045dc9..110817e4b9e1ee 100644 --- a/frontend/utility/OBSClipboardService.hpp +++ b/frontend/utility/OBSClipboardService.hpp @@ -19,7 +19,6 @@ #include #include -#include class OBSClipboardService { public: @@ -39,6 +38,6 @@ class OBSClipboardService { void pasteTransition(const std::vector &items, bool show); private: - void setMimeData(const std::string_view mimeType, OBSData payload); - OBSData getMimeData(const std::string_view mimeType); -}; \ No newline at end of file + void setMimeData(const char *mimeType, const OBSData &payload); + OBSData getMimeData(const char *mimeType) const; +}; From f08055dd9fcb3ada599baba0b368b2170f8e673f Mon Sep 17 00:00:00 2001 From: Kunal Dubey Date: Mon, 1 Jun 2026 14:06:45 +0530 Subject: [PATCH 06/11] frontend: added serializer and service for filters --- frontend/utility/OBSClipboardSerializer.cpp | 21 +++++++++++---- frontend/utility/OBSClipboardSerializer.hpp | 6 ++--- frontend/utility/OBSClipboardService.cpp | 30 ++++++++++++++++++--- 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/frontend/utility/OBSClipboardSerializer.cpp b/frontend/utility/OBSClipboardSerializer.cpp index 035c3f279b9233..189d13e9bad821 100644 --- a/frontend/utility/OBSClipboardSerializer.cpp +++ b/frontend/utility/OBSClipboardSerializer.cpp @@ -14,7 +14,14 @@ OBSData OBSClipboardSerializer::SerializeFilters(OBSSource source) if (!source) { return {}; } - return {}; + OBSDataArrayAutoRelease filters = obs_source_backup_filters(source); + if (!filters) { + return {}; + } + OBSDataAutoRelease data = obs_data_create(); + obs_data_set_array(data, "filters", filters); + + return OBSData(data); } OBSData OBSClipboardSerializer::SerializeTransform(OBSSceneItem item) @@ -55,14 +62,18 @@ OBSData OBSClipboardSerializer::SerializeTransition(OBSSceneItem item, bool show return {}; } -bool OBSClipboardSerializer::DeserializeSceneItem(OBSData data) +bool OBSClipboardSerializer::DeserializeSceneItem(const OBSData &data) { return false; } -bool OBSClipboardSerializer::DeserializeFilters(OBSData data) +bool OBSClipboardSerializer::DeserializeFilters(const OBSData &data, OBSDataArrayAutoRelease &filters) { - return false; + if (!data) { + return false; + } + filters = obs_data_get_array(data, "filters"); + return !!filters; } bool OBSClipboardSerializer::DeserializeTransform(const OBSData &data, obs_transform_info &transform, @@ -88,7 +99,7 @@ bool OBSClipboardSerializer::DeserializeTransform(const OBSData &data, obs_trans return true; } -bool OBSClipboardSerializer::DeserializeTransition(OBSData data) +bool OBSClipboardSerializer::DeserializeTransition(const OBSData &data) { return false; } diff --git a/frontend/utility/OBSClipboardSerializer.hpp b/frontend/utility/OBSClipboardSerializer.hpp index 1d742ddb9764e4..face2418c3a62d 100644 --- a/frontend/utility/OBSClipboardSerializer.hpp +++ b/frontend/utility/OBSClipboardSerializer.hpp @@ -26,8 +26,8 @@ class OBSClipboardSerializer { static OBSData SerializeTransform(OBSSceneItem item); static OBSData SerializeTransition(OBSSceneItem item, bool show); - static bool DeserializeSceneItem(OBSData data); - static bool DeserializeFilters(OBSData data); + static bool DeserializeSceneItem(const OBSData &data); + static bool DeserializeFilters(const OBSData &data, OBSDataArrayAutoRelease &filters); static bool DeserializeTransform(const OBSData &data, obs_transform_info &transform, obs_sceneitem_crop &crop); - static bool DeserializeTransition(OBSData data); + static bool DeserializeTransition(const OBSData &data); }; diff --git a/frontend/utility/OBSClipboardService.cpp b/frontend/utility/OBSClipboardService.cpp index 221f8f175b38c8..61ef3490f01b25 100644 --- a/frontend/utility/OBSClipboardService.cpp +++ b/frontend/utility/OBSClipboardService.cpp @@ -14,7 +14,7 @@ bool OBSClipboardService::canPasteSceneItems(bool duplicate) const bool OBSClipboardService::canPasteFilters() const { - return false; + return !!getMimeData(OBSClipboard::SourceFilters); } bool OBSClipboardService::canPasteTransform() const @@ -29,10 +29,20 @@ bool OBSClipboardService::canPasteTransition() const void OBSClipboardService::copySceneItems(const std::vector &items) {} -void OBSClipboardService::copyFilters(OBSSource source) {} +void OBSClipboardService::copyFilters(OBSSource source) +{ + if (!source) { + return; + } + OBSData payload = OBSClipboardSerializer::SerializeFilters(source); + setMimeData(OBSClipboard::SourceFilters, payload); +} void OBSClipboardService::copyTransform(OBSSceneItem item) { + if (!item) { + return; + } OBSData payload = OBSClipboardSerializer::SerializeTransform(item); setMimeData(OBSClipboard::SceneItemTransform, payload); } @@ -41,7 +51,21 @@ void OBSClipboardService::copyTransition(OBSSceneItem item, bool show) {} void OBSClipboardService::pasteSceneItems(OBSScene scene, bool duplicate) {} -void OBSClipboardService::pasteFilters(OBSSource destination) {} +void OBSClipboardService::pasteFilters(OBSSource destination) +{ + if (!destination) { + return; + } + OBSData payload = getMimeData(OBSClipboard::SourceFilters); + if (!payload) { + return; + } + OBSDataArrayAutoRelease filters; + if (!OBSClipboardSerializer::DeserializeFilters(payload, filters)) { + return; + } + obs_source_restore_filters(destination, filters); +} void OBSClipboardService::pasteTransform(const std::vector &items) { From 362b0daed0ac56a253fb6854b2dc276625046ced Mon Sep 17 00:00:00 2001 From: Kunal Dubey Date: Mon, 1 Jun 2026 14:30:39 +0530 Subject: [PATCH 07/11] frontend: added serializer and service for transitions --- frontend/utility/OBSClipboardSerializer.cpp | 15 ++++++++----- frontend/utility/OBSClipboardSerializer.hpp | 4 +++- frontend/utility/OBSClipboardService.cpp | 24 +++++++++++++++++---- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/frontend/utility/OBSClipboardSerializer.cpp b/frontend/utility/OBSClipboardSerializer.cpp index 189d13e9bad821..c4af3811e21f93 100644 --- a/frontend/utility/OBSClipboardSerializer.cpp +++ b/frontend/utility/OBSClipboardSerializer.cpp @@ -59,11 +59,15 @@ OBSData OBSClipboardSerializer::SerializeTransition(OBSSceneItem item, bool show if (!item) { return {}; } - return {}; + OBSDataAutoRelease data = obs_sceneitem_transition_save(item, show); + return OBSData(data); } bool OBSClipboardSerializer::DeserializeSceneItem(const OBSData &data) { + if (!data) { + return false; + } return false; } @@ -99,7 +103,8 @@ bool OBSClipboardSerializer::DeserializeTransform(const OBSData &data, obs_trans return true; } -bool OBSClipboardSerializer::DeserializeTransition(const OBSData &data) -{ - return false; -} +// not needed as obs_sceneitem_transition_load does the job for us +// bool OBSClipboardSerializer::DeserializeTransition(const OBSData &data) +// { +// return false; +// } diff --git a/frontend/utility/OBSClipboardSerializer.hpp b/frontend/utility/OBSClipboardSerializer.hpp index face2418c3a62d..9e56875917804e 100644 --- a/frontend/utility/OBSClipboardSerializer.hpp +++ b/frontend/utility/OBSClipboardSerializer.hpp @@ -29,5 +29,7 @@ class OBSClipboardSerializer { static bool DeserializeSceneItem(const OBSData &data); static bool DeserializeFilters(const OBSData &data, OBSDataArrayAutoRelease &filters); static bool DeserializeTransform(const OBSData &data, obs_transform_info &transform, obs_sceneitem_crop &crop); - static bool DeserializeTransition(const OBSData &data); + + // not needed as obs_sceneitem_transition_load does the job for us + // static bool DeserializeTransition(const OBSData &data); }; diff --git a/frontend/utility/OBSClipboardService.cpp b/frontend/utility/OBSClipboardService.cpp index 61ef3490f01b25..db049c5038993d 100644 --- a/frontend/utility/OBSClipboardService.cpp +++ b/frontend/utility/OBSClipboardService.cpp @@ -24,7 +24,7 @@ bool OBSClipboardService::canPasteTransform() const bool OBSClipboardService::canPasteTransition() const { - return false; + return !!getMimeData(OBSClipboard::SceneItemTransition); } void OBSClipboardService::copySceneItems(const std::vector &items) {} @@ -47,7 +47,11 @@ void OBSClipboardService::copyTransform(OBSSceneItem item) setMimeData(OBSClipboard::SceneItemTransform, payload); } -void OBSClipboardService::copyTransition(OBSSceneItem item, bool show) {} +void OBSClipboardService::copyTransition(OBSSceneItem item, bool show) +{ + OBSData payload = OBSClipboardSerializer::SerializeTransition(item, show); + setMimeData(OBSClipboard::SceneItemTransition, payload); +} void OBSClipboardService::pasteSceneItems(OBSScene scene, bool duplicate) {} @@ -70,8 +74,9 @@ void OBSClipboardService::pasteFilters(OBSSource destination) void OBSClipboardService::pasteTransform(const std::vector &items) { OBSData payload = getMimeData(OBSClipboard::SceneItemTransform); - if (!payload) + if (!payload) { return; + } obs_transform_info transform; obs_sceneitem_crop crop; @@ -90,7 +95,18 @@ void OBSClipboardService::pasteTransform(const std::vector &items) } } -void OBSClipboardService::pasteTransition(const std::vector &items, bool show) {} +void OBSClipboardService::pasteTransition(const std::vector &items, bool show) +{ + OBSData payload = getMimeData(OBSClipboard::SceneItemTransition); + if (!payload) { + return; + } + for (OBSSceneItem item : items) { + if (item) { + obs_sceneitem_transition_load(item, payload, show); + } + } +} void OBSClipboardService::setMimeData(const char *mimeType, const OBSData &payload) { From c2e1b81e1ae2c2e73fac1da6e9bf64f77e1580af Mon Sep 17 00:00:00 2001 From: Kunal Dubey Date: Mon, 1 Jun 2026 17:31:26 +0530 Subject: [PATCH 08/11] frontend: added serializer and service for sceneitems --- frontend/utility/OBSClipboardSerializer.cpp | 26 +++- frontend/utility/OBSClipboardSerializer.hpp | 3 +- frontend/utility/OBSClipboardService.cpp | 157 +++++++++++++++++++- 3 files changed, 180 insertions(+), 6 deletions(-) diff --git a/frontend/utility/OBSClipboardSerializer.cpp b/frontend/utility/OBSClipboardSerializer.cpp index c4af3811e21f93..86a74cf0aecb5b 100644 --- a/frontend/utility/OBSClipboardSerializer.cpp +++ b/frontend/utility/OBSClipboardSerializer.cpp @@ -6,7 +6,23 @@ OBSData OBSClipboardSerializer::SerializeSceneItem(OBSSceneItem item) return {}; } - return {}; + OBSSource source = obs_sceneitem_get_source(item); + if (!source) { + return {}; + } + OBSDataArrayAutoRelease items = obs_data_array_create(); + obs_sceneitem_save(item, items); + + OBSDataAutoRelease sourceData = obs_save_source(source); + if (!sourceData) { + return {}; + } + OBSDataAutoRelease data = obs_data_create(); + obs_data_set_array(data, "items", items); + obs_data_set_obj(data, "source", sourceData); + obs_data_set_int(data, "output_flags", obs_source_get_output_flags(source)); + + return OBSData(data); } OBSData OBSClipboardSerializer::SerializeFilters(OBSSource source) @@ -63,12 +79,16 @@ OBSData OBSClipboardSerializer::SerializeTransition(OBSSceneItem item, bool show return OBSData(data); } -bool OBSClipboardSerializer::DeserializeSceneItem(const OBSData &data) +bool OBSClipboardSerializer::DeserializeSceneItem(const OBSData &data, OBSDataArrayAutoRelease &items, + OBSDataAutoRelease &source, uint32_t &outputFlags) { if (!data) { return false; } - return false; + items = obs_data_get_array(data, "items"); + source = obs_data_get_obj(data, "source"); + outputFlags = static_cast(obs_data_get_int(data, "output_flags")); + return !!items && obs_data_array_count(items) && !!source; } bool OBSClipboardSerializer::DeserializeFilters(const OBSData &data, OBSDataArrayAutoRelease &filters) diff --git a/frontend/utility/OBSClipboardSerializer.hpp b/frontend/utility/OBSClipboardSerializer.hpp index 9e56875917804e..c62200afc90cf9 100644 --- a/frontend/utility/OBSClipboardSerializer.hpp +++ b/frontend/utility/OBSClipboardSerializer.hpp @@ -26,7 +26,8 @@ class OBSClipboardSerializer { static OBSData SerializeTransform(OBSSceneItem item); static OBSData SerializeTransition(OBSSceneItem item, bool show); - static bool DeserializeSceneItem(const OBSData &data); + static bool DeserializeSceneItem(const OBSData &data, OBSDataArrayAutoRelease &items, + OBSDataAutoRelease &source, uint32_t &outputFlags); static bool DeserializeFilters(const OBSData &data, OBSDataArrayAutoRelease &filters); static bool DeserializeTransform(const OBSData &data, obs_transform_info &transform, obs_sceneitem_crop &crop); diff --git a/frontend/utility/OBSClipboardService.cpp b/frontend/utility/OBSClipboardService.cpp index db049c5038993d..17b65579b2258a 100644 --- a/frontend/utility/OBSClipboardService.cpp +++ b/frontend/utility/OBSClipboardService.cpp @@ -7,8 +7,85 @@ #include #include +namespace { +OBSDataAutoRelease CloneData(const OBSData &data) +{ + if (!data) { + return {}; + } + + return obs_data_create_from_json(obs_data_get_json(data)); +} + +OBSSourceAutoRelease ResolveReferencedSource(const OBSData &sourceData) +{ + const char *uuid = obs_data_get_string(sourceData, "uuid"); + if (uuid && *uuid) { + return obs_get_source_by_uuid(uuid); + } + + const char *name = obs_data_get_string(sourceData, "name"); + if (name && *name) { + return obs_get_source_by_name(name); + } + + return {}; +} + +void PrepareRootSceneItem(obs_data_array_t *items, obs_source_t *source = nullptr) +{ + const size_t count = obs_data_array_count(items); + + for (size_t i = 0; i < count; i++) { + OBSDataAutoRelease item = obs_data_array_item(items, i); + + if (obs_data_get_bool(item, "group_item_backup")) { + continue; + } + + obs_data_erase(item, "id"); + + if (source) { + obs_data_set_string(item, "name", obs_source_get_name(source)); + obs_data_set_string(item, "source_uuid", obs_source_get_uuid(source)); + } + + return; + } +} +} // namespace + bool OBSClipboardService::canPasteSceneItems(bool duplicate) const { + OBSData payload = getMimeData(OBSClipboard::SceneItems); + if (!payload) { + return false; + } + + OBSDataArrayAutoRelease serializedItems = obs_data_get_array(payload, "items"); + if (!serializedItems) { + return false; + } + + const size_t count = obs_data_array_count(serializedItems); + for (size_t i = 0; i < count; i++) { + OBSDataAutoRelease serializedItem = obs_data_array_item(serializedItems, i); + + OBSDataArrayAutoRelease items; + OBSDataAutoRelease sourceData; + uint32_t outputFlags = 0; + + if (!OBSClipboardSerializer::DeserializeSceneItem(serializedItem, items, sourceData, outputFlags)) { + continue; + } + + const bool requiresReference = !duplicate || (outputFlags & OBS_SOURCE_DO_NOT_DUPLICATE); + + if (!requiresReference || ResolveReferencedSource(sourceData)) { + return true; + } + } + return false; } @@ -27,7 +104,24 @@ bool OBSClipboardService::canPasteTransition() const return !!getMimeData(OBSClipboard::SceneItemTransition); } -void OBSClipboardService::copySceneItems(const std::vector &items) {} +void OBSClipboardService::copySceneItems(const std::vector &items) +{ + OBSDataArrayAutoRelease serializedItems = obs_data_array_create(); + for (OBSSceneItem item : items) { + OBSData data = OBSClipboardSerializer::SerializeSceneItem(item); + if (data) { + obs_data_array_push_back(serializedItems, data); + } + } + + if (!obs_data_array_count(serializedItems)) { + return; + } + + OBSDataAutoRelease payload = obs_data_create(); + obs_data_set_array(payload, "items", serializedItems); + setMimeData(OBSClipboard::SceneItems, payload); +} void OBSClipboardService::copyFilters(OBSSource source) { @@ -53,7 +147,66 @@ void OBSClipboardService::copyTransition(OBSSceneItem item, bool show) setMimeData(OBSClipboard::SceneItemTransition, payload); } -void OBSClipboardService::pasteSceneItems(OBSScene scene, bool duplicate) {} +void OBSClipboardService::pasteSceneItems(OBSScene scene, bool duplicate) +{ + if (!scene) { + return; + } + OBSData payload = getMimeData(OBSClipboard::SceneItems); + if (!payload) { + return; + } + OBSDataArrayAutoRelease serializedItems = obs_data_get_array(payload, "items"); + if (!serializedItems) { + return; + } + + for (size_t i = obs_data_array_count(serializedItems); i > 0; i--) { + OBSDataAutoRelease serializedItem = obs_data_array_item(serializedItems, i - 1); + OBSDataAutoRelease copy = CloneData(serializedItem); + if (!copy) { + continue; + } + + OBSDataArrayAutoRelease items; + OBSDataAutoRelease sourceData; + uint32_t outputFlags = 0; + + if (!OBSClipboardSerializer::DeserializeSceneItem(copy, items, sourceData, outputFlags)) { + continue; + } + + const bool useReference = !duplicate || (outputFlags & OBS_SOURCE_DO_NOT_DUPLICATE); + + if (useReference) { + OBSSourceAutoRelease source = ResolveReferencedSource(sourceData); + if (!source) { + continue; + } + + const char *name = obs_source_get_name(source); + + if (obs_scene_get_group(scene, name)) { + continue; + } + + PrepareRootSceneItem(items); + obs_sceneitems_add(scene, items); + continue; + } + + obs_data_erase(sourceData, "uuid"); + + OBSSourceAutoRelease source = obs_load_source(sourceData); + if (!source) { + continue; + } + + obs_source_load2(source); + PrepareRootSceneItem(items, source); + obs_sceneitems_add(scene, items); + } +} void OBSClipboardService::pasteFilters(OBSSource destination) { From e8e5e67ef5bf5761f2466ba8946b1c072554756a Mon Sep 17 00:00:00 2001 From: Kunal Dubey Date: Mon, 1 Jun 2026 17:55:35 +0530 Subject: [PATCH 09/11] frontend: fixed function signatures for build --- frontend/utility/OBSClipboardSerializer.cpp | 2 +- frontend/utility/OBSClipboardSerializer.hpp | 2 +- frontend/utility/OBSClipboardService.cpp | 11 ++++++++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/frontend/utility/OBSClipboardSerializer.cpp b/frontend/utility/OBSClipboardSerializer.cpp index 86a74cf0aecb5b..3947fa1066e84d 100644 --- a/frontend/utility/OBSClipboardSerializer.cpp +++ b/frontend/utility/OBSClipboardSerializer.cpp @@ -79,7 +79,7 @@ OBSData OBSClipboardSerializer::SerializeTransition(OBSSceneItem item, bool show return OBSData(data); } -bool OBSClipboardSerializer::DeserializeSceneItem(const OBSData &data, OBSDataArrayAutoRelease &items, +bool OBSClipboardSerializer::DeserializeSceneItem(const OBSDataAutoRelease &data, OBSDataArrayAutoRelease &items, OBSDataAutoRelease &source, uint32_t &outputFlags) { if (!data) { diff --git a/frontend/utility/OBSClipboardSerializer.hpp b/frontend/utility/OBSClipboardSerializer.hpp index c62200afc90cf9..e2c7728936d05a 100644 --- a/frontend/utility/OBSClipboardSerializer.hpp +++ b/frontend/utility/OBSClipboardSerializer.hpp @@ -26,7 +26,7 @@ class OBSClipboardSerializer { static OBSData SerializeTransform(OBSSceneItem item); static OBSData SerializeTransition(OBSSceneItem item, bool show); - static bool DeserializeSceneItem(const OBSData &data, OBSDataArrayAutoRelease &items, + static bool DeserializeSceneItem(const OBSDataAutoRelease &data, OBSDataArrayAutoRelease &items, OBSDataAutoRelease &source, uint32_t &outputFlags); static bool DeserializeFilters(const OBSData &data, OBSDataArrayAutoRelease &filters); static bool DeserializeTransform(const OBSData &data, obs_transform_info &transform, obs_sceneitem_crop &crop); diff --git a/frontend/utility/OBSClipboardService.cpp b/frontend/utility/OBSClipboardService.cpp index 17b65579b2258a..ac327d3349e32f 100644 --- a/frontend/utility/OBSClipboardService.cpp +++ b/frontend/utility/OBSClipboardService.cpp @@ -8,7 +8,7 @@ #include namespace { -OBSDataAutoRelease CloneData(const OBSData &data) +OBSDataAutoRelease CloneData(const OBSDataAutoRelease &data) { if (!data) { return {}; @@ -17,7 +17,7 @@ OBSDataAutoRelease CloneData(const OBSData &data) return obs_data_create_from_json(obs_data_get_json(data)); } -OBSSourceAutoRelease ResolveReferencedSource(const OBSData &sourceData) +OBSSourceAutoRelease ResolveReferencedSource(const OBSDataAutoRelease &sourceData) { const char *uuid = obs_data_get_string(sourceData, "uuid"); if (uuid && *uuid) { @@ -120,7 +120,7 @@ void OBSClipboardService::copySceneItems(const std::vector &items) OBSDataAutoRelease payload = obs_data_create(); obs_data_set_array(payload, "items", serializedItems); - setMimeData(OBSClipboard::SceneItems, payload); + setMimeData(OBSClipboard::SceneItems, OBSData(payload.Get())); } void OBSClipboardService::copyFilters(OBSSource source) @@ -197,6 +197,11 @@ void OBSClipboardService::pasteSceneItems(OBSScene scene, bool duplicate) obs_data_erase(sourceData, "uuid"); + const char *id = obs_data_get_string(sourceData, "id"); + if (!id || !*id) { + continue; + } + OBSSourceAutoRelease source = obs_load_source(sourceData); if (!source) { continue; From 872f134eb814e0baec5ae92187366d55564b2923 Mon Sep 17 00:00:00 2001 From: Kunal Dubey Date: Mon, 1 Jun 2026 18:48:23 +0530 Subject: [PATCH 10/11] frontend: fix consistency issues --- frontend/utility/OBSClipboardSerializer.cpp | 12 +++------ frontend/utility/OBSClipboardSerializer.hpp | 11 +++------ frontend/utility/OBSClipboardService.cpp | 27 +++++++++++++++------ frontend/utility/OBSClipboardService.hpp | 2 +- 4 files changed, 28 insertions(+), 24 deletions(-) diff --git a/frontend/utility/OBSClipboardSerializer.cpp b/frontend/utility/OBSClipboardSerializer.cpp index 3947fa1066e84d..c5a9f0c4f6399c 100644 --- a/frontend/utility/OBSClipboardSerializer.cpp +++ b/frontend/utility/OBSClipboardSerializer.cpp @@ -79,7 +79,7 @@ OBSData OBSClipboardSerializer::SerializeTransition(OBSSceneItem item, bool show return OBSData(data); } -bool OBSClipboardSerializer::DeserializeSceneItem(const OBSDataAutoRelease &data, OBSDataArrayAutoRelease &items, +bool OBSClipboardSerializer::DeserializeSceneItem(obs_data_t *data, OBSDataArrayAutoRelease &items, OBSDataAutoRelease &source, uint32_t &outputFlags) { if (!data) { @@ -91,7 +91,7 @@ bool OBSClipboardSerializer::DeserializeSceneItem(const OBSDataAutoRelease &data return !!items && obs_data_array_count(items) && !!source; } -bool OBSClipboardSerializer::DeserializeFilters(const OBSData &data, OBSDataArrayAutoRelease &filters) +bool OBSClipboardSerializer::DeserializeFilters(obs_data_t *data, OBSDataArrayAutoRelease &filters) { if (!data) { return false; @@ -100,7 +100,7 @@ bool OBSClipboardSerializer::DeserializeFilters(const OBSData &data, OBSDataArra return !!filters; } -bool OBSClipboardSerializer::DeserializeTransform(const OBSData &data, obs_transform_info &transform, +bool OBSClipboardSerializer::DeserializeTransform(obs_data_t *data, obs_transform_info &transform, obs_sceneitem_crop &crop) { if (!data) { @@ -122,9 +122,3 @@ bool OBSClipboardSerializer::DeserializeTransform(const OBSData &data, obs_trans return true; } - -// not needed as obs_sceneitem_transition_load does the job for us -// bool OBSClipboardSerializer::DeserializeTransition(const OBSData &data) -// { -// return false; -// } diff --git a/frontend/utility/OBSClipboardSerializer.hpp b/frontend/utility/OBSClipboardSerializer.hpp index e2c7728936d05a..11045be63aaa06 100644 --- a/frontend/utility/OBSClipboardSerializer.hpp +++ b/frontend/utility/OBSClipboardSerializer.hpp @@ -26,11 +26,8 @@ class OBSClipboardSerializer { static OBSData SerializeTransform(OBSSceneItem item); static OBSData SerializeTransition(OBSSceneItem item, bool show); - static bool DeserializeSceneItem(const OBSDataAutoRelease &data, OBSDataArrayAutoRelease &items, - OBSDataAutoRelease &source, uint32_t &outputFlags); - static bool DeserializeFilters(const OBSData &data, OBSDataArrayAutoRelease &filters); - static bool DeserializeTransform(const OBSData &data, obs_transform_info &transform, obs_sceneitem_crop &crop); - - // not needed as obs_sceneitem_transition_load does the job for us - // static bool DeserializeTransition(const OBSData &data); + static bool DeserializeSceneItem(obs_data_t *data, OBSDataArrayAutoRelease &items, OBSDataAutoRelease &source, + uint32_t &outputFlags); + static bool DeserializeFilters(obs_data_t *data, OBSDataArrayAutoRelease &filters); + static bool DeserializeTransform(obs_data_t *data, obs_transform_info &transform, obs_sceneitem_crop &crop); }; diff --git a/frontend/utility/OBSClipboardService.cpp b/frontend/utility/OBSClipboardService.cpp index ac327d3349e32f..ff2f1a3bd2244b 100644 --- a/frontend/utility/OBSClipboardService.cpp +++ b/frontend/utility/OBSClipboardService.cpp @@ -7,8 +7,10 @@ #include #include +#include + namespace { -OBSDataAutoRelease CloneData(const OBSDataAutoRelease &data) +OBSDataAutoRelease CloneData(obs_data_t *data) { if (!data) { return {}; @@ -17,7 +19,7 @@ OBSDataAutoRelease CloneData(const OBSDataAutoRelease &data) return obs_data_create_from_json(obs_data_get_json(data)); } -OBSSourceAutoRelease ResolveReferencedSource(const OBSDataAutoRelease &sourceData) +OBSSourceAutoRelease ResolveReferencedSource(obs_data_t *sourceData) { const char *uuid = obs_data_get_string(sourceData, "uuid"); if (uuid && *uuid) { @@ -32,6 +34,12 @@ OBSSourceAutoRelease ResolveReferencedSource(const OBSDataAutoRelease &sourceDat return {}; } +bool CanLoadDuplicateSource(obs_data_t *sourceData) +{ + const char *id = obs_data_get_string(sourceData, "id"); + return id && *id && strcmp(id, "group") != 0; +} + void PrepareRootSceneItem(obs_data_array_t *items, obs_source_t *source = nullptr) { const size_t count = obs_data_array_count(items); @@ -81,7 +89,8 @@ bool OBSClipboardService::canPasteSceneItems(bool duplicate) const const bool requiresReference = !duplicate || (outputFlags & OBS_SOURCE_DO_NOT_DUPLICATE); - if (!requiresReference || ResolveReferencedSource(sourceData)) { + if ((!requiresReference && CanLoadDuplicateSource(sourceData)) || + (requiresReference && ResolveReferencedSource(sourceData))) { return true; } } @@ -120,7 +129,7 @@ void OBSClipboardService::copySceneItems(const std::vector &items) OBSDataAutoRelease payload = obs_data_create(); obs_data_set_array(payload, "items", serializedItems); - setMimeData(OBSClipboard::SceneItems, OBSData(payload.Get())); + setMimeData(OBSClipboard::SceneItems, payload); } void OBSClipboardService::copyFilters(OBSSource source) @@ -197,8 +206,7 @@ void OBSClipboardService::pasteSceneItems(OBSScene scene, bool duplicate) obs_data_erase(sourceData, "uuid"); - const char *id = obs_data_get_string(sourceData, "id"); - if (!id || !*id) { + if (!CanLoadDuplicateSource(sourceData)) { continue; } @@ -266,7 +274,7 @@ void OBSClipboardService::pasteTransition(const std::vector &items } } -void OBSClipboardService::setMimeData(const char *mimeType, const OBSData &payload) +void OBSClipboardService::setMimeData(const char *mimeType, obs_data_t *payload) { if (!mimeType) { return; @@ -278,6 +286,8 @@ void OBSClipboardService::setMimeData(const char *mimeType, const OBSData &paylo OBSDataAutoRelease envelope = obs_data_create(); obs_data_set_int(envelope, "version", OBSClipboard::PayloadVersion); + obs_data_set_string(envelope, "type", mimeType); + obs_data_set_string(envelope, "created_with", obs_get_version_string()); obs_data_set_obj(envelope, "payload", payload); mimeData->setData(mimeType, QByteArray(obs_data_get_json(envelope))); @@ -302,6 +312,9 @@ OBSData OBSClipboardService::getMimeData(const char *mimeType) const if (obs_data_get_int(envelope, "version") != OBSClipboard::PayloadVersion) { return {}; } + if (strcmp(obs_data_get_string(envelope, "type"), mimeType) != 0) { + return {}; + } OBSDataAutoRelease payload = obs_data_get_obj(envelope, "payload"); return OBSData(payload); diff --git a/frontend/utility/OBSClipboardService.hpp b/frontend/utility/OBSClipboardService.hpp index 110817e4b9e1ee..11ab7316a20acc 100644 --- a/frontend/utility/OBSClipboardService.hpp +++ b/frontend/utility/OBSClipboardService.hpp @@ -38,6 +38,6 @@ class OBSClipboardService { void pasteTransition(const std::vector &items, bool show); private: - void setMimeData(const char *mimeType, const OBSData &payload); + void setMimeData(const char *mimeType, obs_data_t *payload); OBSData getMimeData(const char *mimeType) const; }; From faf0bd4c48797eeef6918845a0ea72828b9c191d Mon Sep 17 00:00:00 2001 From: Kunal Dubey Date: Mon, 1 Jun 2026 23:09:35 +0530 Subject: [PATCH 11/11] frontend: fix group duplication and filter paste --- frontend/utility/OBSClipboardService.cpp | 40 +++++++++++++++++++++--- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/frontend/utility/OBSClipboardService.cpp b/frontend/utility/OBSClipboardService.cpp index ff2f1a3bd2244b..5cfe366cbca2ad 100644 --- a/frontend/utility/OBSClipboardService.cpp +++ b/frontend/utility/OBSClipboardService.cpp @@ -8,6 +8,7 @@ #include #include +#include namespace { OBSDataAutoRelease CloneData(obs_data_t *data) @@ -37,7 +38,23 @@ OBSSourceAutoRelease ResolveReferencedSource(obs_data_t *sourceData) bool CanLoadDuplicateSource(obs_data_t *sourceData) { const char *id = obs_data_get_string(sourceData, "id"); - return id && *id && strcmp(id, "group") != 0; + return id && *id; +} + +std::string GetUniqueFilterName(obs_source_t *destination, const char *name) +{ + std::string uniqueName = name; + int suffix = 1; + + for (;;) { + OBSSourceAutoRelease existing = obs_source_get_filter_by_name(destination, uniqueName.c_str()); + if (!existing) { + break; + } + uniqueName = std::string(name) + " " + std::to_string(++suffix); + } + + return uniqueName; } void PrepareRootSceneItem(obs_data_array_t *items, obs_source_t *source = nullptr) @@ -234,7 +251,22 @@ void OBSClipboardService::pasteFilters(OBSSource destination) if (!OBSClipboardSerializer::DeserializeFilters(payload, filters)) { return; } - obs_source_restore_filters(destination, filters); + + for (size_t i = obs_data_array_count(filters); i > 0; i--) { + OBSDataAutoRelease serializedFilter = obs_data_array_item(filters, i - 1); + OBSDataAutoRelease filterData = CloneData(serializedFilter); + if (!filterData) { + continue; + } + + const std::string name = GetUniqueFilterName(destination, obs_data_get_string(filterData, "name")); + obs_data_set_string(filterData, "name", name.c_str()); + obs_data_erase(filterData, "uuid"); + OBSSourceAutoRelease filter = obs_load_private_source(filterData); + if (filter) { + obs_source_filter_add(destination, filter); + } + } } void OBSClipboardService::pasteTransform(const std::vector &items) @@ -244,8 +276,8 @@ void OBSClipboardService::pasteTransform(const std::vector &items) return; } - obs_transform_info transform; - obs_sceneitem_crop crop; + obs_transform_info transform = {}; + obs_sceneitem_crop crop = {}; if (!OBSClipboardSerializer::DeserializeTransform(payload, transform, crop)) return;