OBSBasicSourceSelect.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. /******************************************************************************
  2. Copyright (C) 2023 by Lain Bailey <[email protected]>
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation, either version 2 of the License, or
  6. (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. ******************************************************************************/
  14. #include "OBSBasicSourceSelect.hpp"
  15. #include <qt-wrappers.hpp>
  16. #include "moc_OBSBasicSourceSelect.cpp"
  17. struct AddSourceData {
  18. /* Input data */
  19. obs_source_t *source;
  20. bool visible;
  21. obs_transform_info *transform = nullptr;
  22. obs_sceneitem_crop *crop = nullptr;
  23. obs_blending_method *blend_method = nullptr;
  24. obs_blending_type *blend_mode = nullptr;
  25. obs_scale_type *scale_type = nullptr;
  26. const char *show_transition_id = nullptr;
  27. const char *hide_transition_id = nullptr;
  28. OBSData show_transition_settings;
  29. OBSData hide_transition_settings;
  30. uint32_t show_transition_duration = 300;
  31. uint32_t hide_transition_duration = 300;
  32. OBSData private_settings;
  33. /* Return data */
  34. obs_sceneitem_t *scene_item = nullptr;
  35. };
  36. bool OBSBasicSourceSelect::EnumSources(void *data, obs_source_t *source)
  37. {
  38. if (obs_source_is_hidden(source))
  39. return true;
  40. OBSBasicSourceSelect *window = static_cast<OBSBasicSourceSelect *>(data);
  41. const char *name = obs_source_get_name(source);
  42. const char *id = obs_source_get_unversioned_id(source);
  43. if (strcmp(id, window->id) == 0)
  44. window->ui->sourceList->addItem(QT_UTF8(name));
  45. return true;
  46. }
  47. bool OBSBasicSourceSelect::EnumGroups(void *data, obs_source_t *source)
  48. {
  49. OBSBasicSourceSelect *window = static_cast<OBSBasicSourceSelect *>(data);
  50. const char *name = obs_source_get_name(source);
  51. const char *id = obs_source_get_unversioned_id(source);
  52. if (strcmp(id, window->id) == 0) {
  53. OBSBasic *main = OBSBasic::Get();
  54. OBSScene scene = main->GetCurrentScene();
  55. obs_sceneitem_t *existing = obs_scene_get_group(scene, name);
  56. if (!existing)
  57. window->ui->sourceList->addItem(QT_UTF8(name));
  58. }
  59. return true;
  60. }
  61. void OBSBasicSourceSelect::OBSSourceAdded(void *data, calldata_t *calldata)
  62. {
  63. OBSBasicSourceSelect *window = static_cast<OBSBasicSourceSelect *>(data);
  64. obs_source_t *source = (obs_source_t *)calldata_ptr(calldata, "source");
  65. QMetaObject::invokeMethod(window, "SourceAdded", Q_ARG(OBSSource, source));
  66. }
  67. void OBSBasicSourceSelect::OBSSourceRemoved(void *data, calldata_t *calldata)
  68. {
  69. OBSBasicSourceSelect *window = static_cast<OBSBasicSourceSelect *>(data);
  70. obs_source_t *source = (obs_source_t *)calldata_ptr(calldata, "source");
  71. QMetaObject::invokeMethod(window, "SourceRemoved", Q_ARG(OBSSource, source));
  72. }
  73. void OBSBasicSourceSelect::SourceAdded(OBSSource source)
  74. {
  75. const char *name = obs_source_get_name(source);
  76. const char *sourceId = obs_source_get_unversioned_id(source);
  77. if (strcmp(sourceId, id) != 0)
  78. return;
  79. ui->sourceList->addItem(name);
  80. }
  81. void OBSBasicSourceSelect::SourceRemoved(OBSSource source)
  82. {
  83. const char *name = obs_source_get_name(source);
  84. const char *sourceId = obs_source_get_unversioned_id(source);
  85. if (strcmp(sourceId, id) != 0)
  86. return;
  87. QList<QListWidgetItem *> items = ui->sourceList->findItems(name, Qt::MatchFixedString);
  88. if (!items.count())
  89. return;
  90. delete items[0];
  91. }
  92. static void AddSource(void *_data, obs_scene_t *scene)
  93. {
  94. AddSourceData *data = (AddSourceData *)_data;
  95. obs_sceneitem_t *sceneitem;
  96. sceneitem = obs_scene_add(scene, data->source);
  97. if (data->transform != nullptr)
  98. obs_sceneitem_set_info2(sceneitem, data->transform);
  99. if (data->crop != nullptr)
  100. obs_sceneitem_set_crop(sceneitem, data->crop);
  101. if (data->blend_method != nullptr)
  102. obs_sceneitem_set_blending_method(sceneitem, *data->blend_method);
  103. if (data->blend_mode != nullptr)
  104. obs_sceneitem_set_blending_mode(sceneitem, *data->blend_mode);
  105. if (data->scale_type != nullptr)
  106. obs_sceneitem_set_scale_filter(sceneitem, *data->scale_type);
  107. if (data->show_transition_id && *data->show_transition_id) {
  108. OBSSourceAutoRelease source = obs_source_create(data->show_transition_id, data->show_transition_id,
  109. data->show_transition_settings, nullptr);
  110. if (source)
  111. obs_sceneitem_set_transition(sceneitem, true, source);
  112. }
  113. if (data->hide_transition_id && *data->hide_transition_id) {
  114. OBSSourceAutoRelease source = obs_source_create(data->hide_transition_id, data->hide_transition_id,
  115. data->hide_transition_settings, nullptr);
  116. if (source)
  117. obs_sceneitem_set_transition(sceneitem, false, source);
  118. }
  119. obs_sceneitem_set_transition_duration(sceneitem, true, data->show_transition_duration);
  120. obs_sceneitem_set_transition_duration(sceneitem, false, data->hide_transition_duration);
  121. obs_sceneitem_set_visible(sceneitem, data->visible);
  122. if (data->private_settings) {
  123. OBSDataAutoRelease newPrivateSettings = obs_sceneitem_get_private_settings(sceneitem);
  124. obs_data_apply(newPrivateSettings, data->private_settings);
  125. }
  126. data->scene_item = sceneitem;
  127. }
  128. char *get_new_source_name(const char *name, const char *format)
  129. {
  130. struct dstr new_name = {0};
  131. int inc = 0;
  132. dstr_copy(&new_name, name);
  133. for (;;) {
  134. OBSSourceAutoRelease existing_source = obs_get_source_by_name(new_name.array);
  135. if (!existing_source)
  136. break;
  137. dstr_printf(&new_name, format, name, ++inc + 1);
  138. }
  139. return new_name.array;
  140. }
  141. static void AddExisting(OBSSource source, bool visible, bool duplicate, SourceCopyInfo *info = nullptr)
  142. {
  143. OBSBasic *main = OBSBasic::Get();
  144. OBSScene scene = main->GetCurrentScene();
  145. if (!scene)
  146. return;
  147. if (duplicate) {
  148. OBSSource from = source;
  149. char *new_name = get_new_source_name(obs_source_get_name(source), "%s %d");
  150. source = obs_source_duplicate(from, new_name, false);
  151. obs_source_release(source);
  152. bfree(new_name);
  153. if (!source)
  154. return;
  155. }
  156. AddSourceData data;
  157. data.source = source;
  158. data.visible = visible;
  159. if (info) {
  160. data.transform = &info->transform;
  161. data.crop = &info->crop;
  162. data.blend_method = &info->blend_method;
  163. data.blend_mode = &info->blend_mode;
  164. data.scale_type = &info->scale_type;
  165. data.show_transition_id = info->show_transition_id;
  166. data.hide_transition_id = info->hide_transition_id;
  167. data.show_transition_settings = std::move(info->show_transition_settings);
  168. data.hide_transition_settings = std::move(info->hide_transition_settings);
  169. data.show_transition_duration = info->show_transition_duration;
  170. data.hide_transition_duration = info->hide_transition_duration;
  171. data.private_settings = std::move(info->private_settings);
  172. }
  173. obs_enter_graphics();
  174. obs_scene_atomic_update(scene, AddSource, &data);
  175. obs_leave_graphics();
  176. }
  177. static void AddExisting(const char *name, bool visible, bool duplicate)
  178. {
  179. OBSSourceAutoRelease source = obs_get_source_by_name(name);
  180. if (source) {
  181. AddExisting(source.Get(), visible, duplicate);
  182. }
  183. }
  184. bool AddNew(QWidget *parent, const char *id, const char *name, const bool visible, OBSSource &newSource,
  185. OBSSceneItem &newSceneItem)
  186. {
  187. OBSBasic *main = OBSBasic::Get();
  188. OBSScene scene = main->GetCurrentScene();
  189. bool success = false;
  190. if (!scene)
  191. return false;
  192. OBSSourceAutoRelease source = obs_get_source_by_name(name);
  193. if (source && parent) {
  194. OBSMessageBox::information(parent, QTStr("NameExists.Title"), QTStr("NameExists.Text"));
  195. } else {
  196. const char *v_id = obs_get_latest_input_type_id(id);
  197. source = obs_source_create(v_id, name, NULL, nullptr);
  198. if (source) {
  199. AddSourceData data;
  200. data.source = source;
  201. data.visible = visible;
  202. obs_enter_graphics();
  203. obs_scene_atomic_update(scene, AddSource, &data);
  204. obs_leave_graphics();
  205. newSource = source;
  206. newSceneItem = data.scene_item;
  207. /* set monitoring if source monitors by default */
  208. uint32_t flags = obs_source_get_output_flags(source);
  209. if ((flags & OBS_SOURCE_MONITOR_BY_DEFAULT) != 0) {
  210. obs_source_set_monitoring_type(source, OBS_MONITORING_TYPE_MONITOR_ONLY);
  211. }
  212. success = true;
  213. }
  214. }
  215. return success;
  216. }
  217. void OBSBasicSourceSelect::on_buttonBox_accepted()
  218. {
  219. bool useExisting = ui->selectExisting->isChecked();
  220. bool visible = ui->sourceVisible->isChecked();
  221. if (useExisting) {
  222. QListWidgetItem *item = ui->sourceList->currentItem();
  223. if (!item)
  224. return;
  225. QString source_name = item->text();
  226. AddExisting(QT_TO_UTF8(source_name), visible, false);
  227. OBSBasic *main = OBSBasic::Get();
  228. const char *scene_name = obs_source_get_name(main->GetCurrentSceneSource());
  229. auto undo = [scene_name, main](const std::string &) {
  230. obs_source_t *scene_source = obs_get_source_by_name(scene_name);
  231. main->SetCurrentScene(scene_source, true);
  232. obs_source_release(scene_source);
  233. obs_scene_t *scene = obs_get_scene_by_name(scene_name);
  234. OBSSceneItem item;
  235. auto cb = [](obs_scene_t *, obs_sceneitem_t *sceneitem, void *data) {
  236. OBSSceneItem &last = *static_cast<OBSSceneItem *>(data);
  237. last = sceneitem;
  238. return true;
  239. };
  240. obs_scene_enum_items(scene, cb, &item);
  241. obs_sceneitem_remove(item);
  242. obs_scene_release(scene);
  243. };
  244. auto redo = [scene_name, main, source_name, visible](const std::string &) {
  245. obs_source_t *scene_source = obs_get_source_by_name(scene_name);
  246. main->SetCurrentScene(scene_source, true);
  247. obs_source_release(scene_source);
  248. AddExisting(QT_TO_UTF8(source_name), visible, false);
  249. };
  250. undo_s.add_action(QTStr("Undo.Add").arg(source_name), undo, redo, "", "");
  251. } else {
  252. if (ui->sourceName->text().isEmpty()) {
  253. OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text"));
  254. return;
  255. }
  256. OBSSceneItem item;
  257. if (!AddNew(this, id, QT_TO_UTF8(ui->sourceName->text()), visible, newSource, item))
  258. return;
  259. OBSBasic *main = OBSBasic::Get();
  260. std::string scene_name = obs_source_get_name(main->GetCurrentSceneSource());
  261. auto undo = [scene_name, main](const std::string &data) {
  262. OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str());
  263. obs_source_remove(source);
  264. OBSSourceAutoRelease scene_source = obs_get_source_by_name(scene_name.c_str());
  265. main->SetCurrentScene(scene_source.Get(), true);
  266. };
  267. OBSDataAutoRelease wrapper = obs_data_create();
  268. obs_data_set_string(wrapper, "id", id);
  269. obs_data_set_int(wrapper, "item_id", obs_sceneitem_get_id(item));
  270. obs_data_set_string(wrapper, "name", ui->sourceName->text().toUtf8().constData());
  271. obs_data_set_bool(wrapper, "visible", visible);
  272. auto redo = [scene_name, main](const std::string &data) {
  273. OBSSourceAutoRelease scene_source = obs_get_source_by_name(scene_name.c_str());
  274. main->SetCurrentScene(scene_source.Get(), true);
  275. OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str());
  276. OBSSource source;
  277. OBSSceneItem item;
  278. AddNew(NULL, obs_data_get_string(dat, "id"), obs_data_get_string(dat, "name"),
  279. obs_data_get_bool(dat, "visible"), source, item);
  280. obs_sceneitem_set_id(item, (int64_t)obs_data_get_int(dat, "item_id"));
  281. };
  282. undo_s.add_action(QTStr("Undo.Add").arg(ui->sourceName->text()), undo, redo,
  283. std::string(obs_source_get_name(newSource)), std::string(obs_data_get_json(wrapper)));
  284. }
  285. done(DialogCode::Accepted);
  286. }
  287. void OBSBasicSourceSelect::on_buttonBox_rejected()
  288. {
  289. done(DialogCode::Rejected);
  290. }
  291. static inline const char *GetSourceDisplayName(const char *id)
  292. {
  293. if (strcmp(id, "scene") == 0)
  294. return Str("Basic.Scene");
  295. else if (strcmp(id, "group") == 0)
  296. return Str("Group");
  297. const char *v_id = obs_get_latest_input_type_id(id);
  298. return obs_source_get_display_name(v_id);
  299. }
  300. OBSBasicSourceSelect::OBSBasicSourceSelect(OBSBasic *parent, const char *id_, undo_stack &undo_s)
  301. : QDialog(parent),
  302. ui(new Ui::OBSBasicSourceSelect),
  303. id(id_),
  304. undo_s(undo_s)
  305. {
  306. setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
  307. ui->setupUi(this);
  308. ui->sourceList->setAttribute(Qt::WA_MacShowFocusRect, false);
  309. QString placeHolderText{QT_UTF8(GetSourceDisplayName(id))};
  310. QString text{placeHolderText};
  311. int i = 2;
  312. OBSSourceAutoRelease source = nullptr;
  313. while ((source = obs_get_source_by_name(QT_TO_UTF8(text)))) {
  314. text = QString("%1 %2").arg(placeHolderText).arg(i++);
  315. }
  316. ui->sourceName->setText(text);
  317. ui->sourceName->setFocus(); //Fixes deselect of text.
  318. ui->sourceName->selectAll();
  319. installEventFilter(CreateShortcutFilter());
  320. connect(ui->createNew, &QRadioButton::pressed, this, [&]() {
  321. QPushButton *button = ui->buttonBox->button(QDialogButtonBox::Ok);
  322. if (!button->isEnabled())
  323. button->setEnabled(true);
  324. });
  325. connect(ui->selectExisting, &QRadioButton::pressed, this, [&]() {
  326. QPushButton *button = ui->buttonBox->button(QDialogButtonBox::Ok);
  327. bool enabled = ui->sourceList->selectedItems().size() != 0;
  328. if (button->isEnabled() != enabled)
  329. button->setEnabled(enabled);
  330. });
  331. connect(ui->sourceList, &QListWidget::itemSelectionChanged, this, [&]() {
  332. QPushButton *button = ui->buttonBox->button(QDialogButtonBox::Ok);
  333. if (!button->isEnabled())
  334. button->setEnabled(true);
  335. });
  336. if (strcmp(id_, "scene") == 0) {
  337. OBSBasic *main = OBSBasic::Get();
  338. OBSSource curSceneSource = main->GetCurrentSceneSource();
  339. ui->selectExisting->setChecked(true);
  340. ui->createNew->setChecked(false);
  341. ui->createNew->setEnabled(false);
  342. ui->sourceName->setEnabled(false);
  343. ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
  344. int count = main->ui->scenes->count();
  345. for (int i = 0; i < count; i++) {
  346. QListWidgetItem *item = main->ui->scenes->item(i);
  347. OBSScene scene = GetOBSRef<OBSScene>(item);
  348. OBSSource sceneSource = obs_scene_get_source(scene);
  349. if (curSceneSource == sceneSource)
  350. continue;
  351. const char *name = obs_source_get_name(sceneSource);
  352. ui->sourceList->addItem(QT_UTF8(name));
  353. }
  354. } else if (strcmp(id_, "group") == 0) {
  355. obs_enum_sources(EnumGroups, this);
  356. } else {
  357. obs_enum_sources(EnumSources, this);
  358. }
  359. }
  360. void OBSBasicSourceSelect::SourcePaste(SourceCopyInfo &info, bool dup)
  361. {
  362. OBSSource source = OBSGetStrongRef(info.weak_source);
  363. if (!source)
  364. return;
  365. AddExisting(source, info.visible, dup, &info);
  366. }