window-projector.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876
  1. #include <QAction>
  2. #include <QGuiApplication>
  3. #include <QMouseEvent>
  4. #include <QMenu>
  5. #include <QScreen>
  6. #include "window-basic-main.hpp"
  7. #include "display-helpers.hpp"
  8. #include "qt-wrappers.hpp"
  9. #include "platform.hpp"
  10. #define HORIZONTAL_TOP 0
  11. #define HORIZONTAL_BOTTOM 1
  12. #define VERTICAL_LEFT 2
  13. #define VERTICAL_RIGHT 3
  14. static QList<OBSProjector *> multiviewProjectors;
  15. static bool updatingMultiview = false;
  16. static int multiviewLayout = HORIZONTAL_TOP;
  17. OBSProjector::OBSProjector(QWidget *widget, obs_source_t *source_, bool window)
  18. : OBSQTDisplay (widget,
  19. Qt::Window),
  20. source (source_),
  21. removedSignal (obs_source_get_signal_handler(source),
  22. "remove", OBSSourceRemoved, this)
  23. {
  24. if (!window) {
  25. setWindowFlags(Qt::FramelessWindowHint |
  26. Qt::X11BypassWindowManagerHint);
  27. }
  28. setAttribute(Qt::WA_DeleteOnClose, true);
  29. //disable application quit when last window closed
  30. setAttribute(Qt::WA_QuitOnClose, false);
  31. installEventFilter(CreateShortcutFilter());
  32. auto addDrawCallback = [this] ()
  33. {
  34. bool isMultiview = type == ProjectorType::Multiview;
  35. obs_display_add_draw_callback(GetDisplay(),
  36. isMultiview ? OBSRenderMultiview : OBSRender,
  37. this);
  38. obs_display_set_background_color(GetDisplay(), 0x000000);
  39. };
  40. connect(this, &OBSQTDisplay::DisplayCreated, addDrawCallback);
  41. bool hideCursor = config_get_bool(GetGlobalConfig(),
  42. "BasicWindow", "HideProjectorCursor");
  43. if (hideCursor && !window) {
  44. QPixmap empty(16, 16);
  45. empty.fill(Qt::transparent);
  46. setCursor(QCursor(empty));
  47. }
  48. App()->IncrementSleepInhibition();
  49. resize(480, 270);
  50. }
  51. OBSProjector::~OBSProjector()
  52. {
  53. bool isMultiview = type == ProjectorType::Multiview;
  54. obs_display_remove_draw_callback(GetDisplay(),
  55. isMultiview ? OBSRenderMultiview : OBSRender, this);
  56. if (source)
  57. obs_source_dec_showing(source);
  58. if (isMultiview) {
  59. for (OBSWeakSource &weakSrc : multiviewScenes) {
  60. OBSSource src = OBSGetStrongRef(weakSrc);
  61. if (src)
  62. obs_source_dec_showing(src);
  63. }
  64. obs_enter_graphics();
  65. gs_vertexbuffer_destroy(outerBox);
  66. gs_vertexbuffer_destroy(innerBox);
  67. gs_vertexbuffer_destroy(leftVLine);
  68. gs_vertexbuffer_destroy(rightVLine);
  69. gs_vertexbuffer_destroy(leftLine);
  70. gs_vertexbuffer_destroy(topLine);
  71. gs_vertexbuffer_destroy(rightLine);
  72. obs_leave_graphics();
  73. }
  74. if (type == ProjectorType::Multiview)
  75. multiviewProjectors.removeAll(this);
  76. App()->DecrementSleepInhibition();
  77. }
  78. static OBSSource CreateLabel(const char *name, size_t h)
  79. {
  80. obs_data_t *settings = obs_data_create();
  81. obs_data_t *font = obs_data_create();
  82. std::string text;
  83. text += " ";
  84. text += name;
  85. text += " ";
  86. #if defined(_WIN32)
  87. obs_data_set_string(font, "face", "Arial");
  88. #elif defined(__APPLE__)
  89. obs_data_set_string(font, "face", "Helvetica");
  90. #else
  91. obs_data_set_string(font, "face", "Monospace");
  92. #endif
  93. obs_data_set_int(font, "flags", 1); // Bold text
  94. obs_data_set_int(font, "size", int(h / 9.81));
  95. obs_data_set_obj(settings, "font", font);
  96. obs_data_set_string(settings, "text", text.c_str());
  97. obs_data_set_bool(settings, "outline", false);
  98. #ifdef _WIN32
  99. const char *text_source_id = "text_gdiplus";
  100. #else
  101. const char *text_source_id = "text_ft2_source";
  102. #endif
  103. OBSSource txtSource = obs_source_create_private(text_source_id, name,
  104. settings);
  105. obs_source_release(txtSource);
  106. obs_data_release(font);
  107. obs_data_release(settings);
  108. return txtSource;
  109. }
  110. void OBSProjector::Init(int monitor, bool window, QString title,
  111. ProjectorType type_)
  112. {
  113. QScreen *screen = QGuiApplication::screens()[monitor];
  114. if (!window)
  115. setGeometry(screen->geometry());
  116. bool alwaysOnTop = config_get_bool(GetGlobalConfig(),
  117. "BasicWindow", "ProjectorAlwaysOnTop");
  118. if (alwaysOnTop && !window)
  119. SetAlwaysOnTop(this, true);
  120. if (window)
  121. setWindowTitle(title);
  122. show();
  123. if (source)
  124. obs_source_inc_showing(source);
  125. if (!window) {
  126. QAction *action = new QAction(this);
  127. action->setShortcut(Qt::Key_Escape);
  128. addAction(action);
  129. connect(action, SIGNAL(triggered()), this,
  130. SLOT(EscapeTriggered()));
  131. activateWindow();
  132. }
  133. savedMonitor = monitor;
  134. isWindow = window;
  135. type = type_;
  136. if (type == ProjectorType::Multiview) {
  137. obs_enter_graphics();
  138. gs_render_start(true);
  139. gs_vertex2f(0.001f, 0.001f);
  140. gs_vertex2f(0.001f, 0.997f);
  141. gs_vertex2f(0.997f, 0.997f);
  142. gs_vertex2f(0.997f, 0.001f);
  143. gs_vertex2f(0.001f, 0.001f);
  144. outerBox = gs_render_save();
  145. gs_render_start(true);
  146. gs_vertex2f(0.04f, 0.04f);
  147. gs_vertex2f(0.04f, 0.96f);
  148. gs_vertex2f(0.96f, 0.96f);
  149. gs_vertex2f(0.96f, 0.04f);
  150. gs_vertex2f(0.04f, 0.04f);
  151. innerBox = gs_render_save();
  152. gs_render_start(true);
  153. gs_vertex2f(0.15f, 0.04f);
  154. gs_vertex2f(0.15f, 0.96f);
  155. leftVLine = gs_render_save();
  156. gs_render_start(true);
  157. gs_vertex2f(0.85f, 0.04f);
  158. gs_vertex2f(0.85f, 0.96f);
  159. rightVLine = gs_render_save();
  160. gs_render_start(true);
  161. gs_vertex2f(0.0f, 0.5f);
  162. gs_vertex2f(0.075f, 0.5f);
  163. leftLine = gs_render_save();
  164. gs_render_start(true);
  165. gs_vertex2f(0.5f, 0.0f);
  166. gs_vertex2f(0.5f, 0.09f);
  167. topLine = gs_render_save();
  168. gs_render_start(true);
  169. gs_vertex2f(0.925f, 0.5f);
  170. gs_vertex2f(1.0f, 0.5f);
  171. rightLine = gs_render_save();
  172. obs_leave_graphics();
  173. UpdateMultiview();
  174. multiviewProjectors.push_back(this);
  175. }
  176. ready = true;
  177. }
  178. static inline void renderVB(gs_effect_t *effect, gs_vertbuffer_t *vb,
  179. int cx, int cy)
  180. {
  181. if (!vb)
  182. return;
  183. matrix4 transform;
  184. matrix4_identity(&transform);
  185. transform.x.x = cx;
  186. transform.y.y = cy;
  187. gs_load_vertexbuffer(vb);
  188. gs_matrix_push();
  189. gs_matrix_mul(&transform);
  190. while (gs_effect_loop(effect, "Solid"))
  191. gs_draw(GS_LINESTRIP, 0, 0);
  192. gs_matrix_pop();
  193. }
  194. static inline uint32_t labelOffset(obs_source_t *label, uint32_t cx)
  195. {
  196. uint32_t w = obs_source_get_width(label);
  197. w = uint32_t(float(w) * 0.5f);
  198. return (cx / 2) - w;
  199. }
  200. void OBSProjector::OBSRenderMultiview(void *data, uint32_t cx, uint32_t cy)
  201. {
  202. OBSProjector *window = (OBSProjector *)data;
  203. if (updatingMultiview || !window->ready)
  204. return;
  205. OBSBasic *main = (OBSBasic *)obs_frontend_get_main_window();
  206. uint32_t targetCX, targetCY;
  207. int x, y;
  208. float fX, fY, halfCX, halfCY, sourceX, sourceY, labelX, labelY,
  209. quarterCX, quarterCY, scale, targetCXF, targetCYF,
  210. hiCX, hiCY, qiX, qiY, qiCX, qiCY, hiScaleX, hiScaleY,
  211. qiScaleX, qiScaleY;
  212. uint32_t offset;
  213. gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID);
  214. gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color");
  215. struct obs_video_info ovi;
  216. obs_get_video_info(&ovi);
  217. targetCX = ovi.base_width;
  218. targetCY = ovi.base_height;
  219. GetScaleAndCenterPos(targetCX, targetCY, cx, cy, x, y, scale);
  220. targetCXF = float(targetCX);
  221. targetCYF = float(targetCY);
  222. fX = float(x);
  223. fY = float(y);
  224. halfCX = (targetCXF + 1) / 2;
  225. halfCY = (targetCYF + 1) / 2;
  226. hiCX = (halfCX - 4.0);
  227. hiCY = (halfCY - 4.0);
  228. hiScaleX = hiCX / targetCXF;
  229. hiScaleY = hiCY / targetCYF;
  230. quarterCX = (halfCX + 1) / 2;
  231. quarterCY = (halfCY + 1) / 2;
  232. qiCX = (quarterCX - 8.0);
  233. qiCY = (quarterCY - 8.0);
  234. qiScaleX = qiCX / targetCXF;
  235. qiScaleY = qiCY / targetCYF;
  236. OBSSource previewSrc = main->GetCurrentSceneSource();
  237. OBSSource programSrc = main->GetProgramSource();
  238. bool studioMode = main->IsPreviewProgramMode();
  239. auto drawBox = [solid, color] (float cx, float cy,
  240. uint32_t colorVal)
  241. {
  242. gs_effect_set_color(color, colorVal);
  243. while (gs_effect_loop(solid, "Solid"))
  244. gs_draw_sprite(nullptr, 0, (uint32_t)cx, (uint32_t)cy);
  245. };
  246. auto setRegion = [fX, fY, scale] (float x, float y, float cx, float cy)
  247. {
  248. float vX = int(fX + x * scale);
  249. float vY = int(fY + y * scale);
  250. float vCX = int(cx * scale);
  251. float vCY = int(cy * scale);
  252. float oL = x;
  253. float oT = y;
  254. float oR = (x + cx);
  255. float oB = (y + cy);
  256. gs_projection_push();
  257. gs_viewport_push();
  258. gs_set_viewport(vX, vY, vCX, vCY);
  259. gs_ortho(oL, oR, oT, oB, -100.0f, 100.0f);
  260. };
  261. auto resetRegion = [] ()
  262. {
  263. gs_viewport_pop();
  264. gs_projection_pop();
  265. };
  266. auto calcBaseSource = [&](size_t i)
  267. {
  268. switch (multiviewLayout) {
  269. case VERTICAL_LEFT:
  270. sourceX = halfCX;
  271. sourceY = (i / 2 ) * quarterCY;
  272. if (i % 2 != 0)
  273. sourceX = halfCX + quarterCX;
  274. break;
  275. case VERTICAL_RIGHT:
  276. sourceX = 0;
  277. sourceY = (i / 2 ) * quarterCY;
  278. if (i % 2 != 0)
  279. sourceX = quarterCX;
  280. break;
  281. case HORIZONTAL_BOTTOM:
  282. if (i < 4) {
  283. sourceX = (float(i) * quarterCX);
  284. sourceY = 0;
  285. } else {
  286. sourceX = (float(i - 4) * quarterCX);
  287. sourceY = quarterCY;
  288. }
  289. break;
  290. default: //HORIZONTAL_TOP:
  291. if (i < 4) {
  292. sourceX = (float(i) * quarterCX);
  293. sourceY = halfCY;
  294. } else {
  295. sourceX = (float(i - 4) * quarterCX);
  296. sourceY = halfCY + quarterCY;
  297. }
  298. }
  299. };
  300. auto calcPreviewProgram = [&](bool program)
  301. {
  302. switch (multiviewLayout) {
  303. case VERTICAL_LEFT:
  304. sourceX = 2.0f;
  305. sourceY = halfCY + 2.0f;
  306. labelX = offset;
  307. labelY = halfCY * 1.8f;
  308. if (program) {
  309. sourceY = 2.0f;
  310. labelY = halfCY * 0.8f;
  311. }
  312. break;
  313. case VERTICAL_RIGHT:
  314. sourceX = halfCX + 2.0f;
  315. sourceY = halfCY + 2.0f;
  316. labelX = halfCX + offset;
  317. labelY = halfCY * 1.8f;
  318. if (program) {
  319. sourceY = 2.0f;
  320. labelY = halfCY * 0.8f;
  321. }
  322. break;
  323. case HORIZONTAL_BOTTOM:
  324. sourceX = 2.0f;
  325. sourceY = halfCY + 2.0f;
  326. labelX = offset;
  327. labelY = halfCY * 1.8f;
  328. if (program) {
  329. sourceX = halfCX + 2.0f;
  330. labelX = halfCX + offset;
  331. }
  332. break;
  333. default: //HORIZONTAL_TOP:
  334. sourceX = 2.0f;
  335. sourceY = 2.0f;
  336. labelX = offset;
  337. labelY = halfCY * 0.8f;
  338. if (program) {
  339. sourceX = halfCX + 2.0f;
  340. labelX = halfCX + offset;
  341. }
  342. }
  343. };
  344. /* ----------------------------- */
  345. /* draw sources */
  346. gs_projection_push();
  347. gs_viewport_push();
  348. gs_set_viewport(x, y, targetCX * scale, targetCY * scale);
  349. gs_ortho(0.0f, targetCXF, 0.0f, targetCYF, -100.0f, 100.0f);
  350. for (size_t i = 0; i < 8; i++) {
  351. OBSSource src = OBSGetStrongRef(window->multiviewScenes[i]);
  352. obs_source *label = window->multiviewLabels[i + 2];
  353. if (!src)
  354. continue;
  355. if (!label)
  356. continue;
  357. calcBaseSource(i);
  358. qiX = sourceX + 4.0f;
  359. qiY = sourceY + 4.0f;
  360. /* ----------- */
  361. if (src == previewSrc || src == programSrc) {
  362. uint32_t colorVal = src == programSrc
  363. ? 0xFFFF0000
  364. : 0xFF00FF00;
  365. gs_matrix_push();
  366. gs_matrix_translate3f(sourceX, sourceY, 0.0f);
  367. drawBox(quarterCX, quarterCY, colorVal);
  368. gs_matrix_pop();
  369. gs_matrix_push();
  370. gs_matrix_translate3f(qiX, qiY, 0.0f);
  371. drawBox(qiCX, qiCY, 0xFF000000);
  372. gs_matrix_pop();
  373. }
  374. /* ----------- */
  375. gs_matrix_push();
  376. gs_matrix_translate3f(qiX, qiY, 0.0f);
  377. gs_matrix_scale3f(qiScaleX, qiScaleY, 1.0f);
  378. setRegion(qiX, qiY, qiCX, qiCY);
  379. obs_source_video_render(src);
  380. resetRegion();
  381. gs_effect_set_color(color, 0xFFFFFFFF);
  382. renderVB(solid, window->outerBox, targetCX, targetCY);
  383. gs_matrix_pop();
  384. /* ----------- */
  385. offset = labelOffset(label, quarterCX);
  386. cx = obs_source_get_width(label);
  387. cy = obs_source_get_height(label);
  388. gs_matrix_push();
  389. gs_matrix_translate3f(sourceX + offset,
  390. (quarterCY * 0.8f) + sourceY, 0.0f);
  391. drawBox(cx, cy + int(quarterCX * 0.015f), 0xD91F1F1F);
  392. obs_source_video_render(label);
  393. gs_matrix_pop();
  394. }
  395. gs_effect_set_color(color, 0xFFFFFFFF);
  396. /* ----------------------------- */
  397. /* draw preview */
  398. obs_source_t *previewLabel = window->multiviewLabels[0];
  399. offset = labelOffset(previewLabel, halfCX);
  400. calcPreviewProgram(false);
  401. gs_matrix_push();
  402. gs_matrix_translate3f(sourceX, sourceY, 0.0f);
  403. gs_matrix_scale3f(hiScaleX, hiScaleY, 1.0f);
  404. setRegion(sourceX, sourceY, hiCX, hiCY);
  405. if (studioMode) {
  406. obs_source_video_render(previewSrc);
  407. } else {
  408. obs_render_main_texture();
  409. }
  410. resetRegion();
  411. gs_matrix_pop();
  412. /* ----------- */
  413. gs_matrix_push();
  414. gs_matrix_translate3f(sourceX, sourceY, 0.0f);
  415. gs_matrix_scale3f(hiScaleX, hiScaleY, 1.0f);
  416. renderVB(solid, window->outerBox, targetCX, targetCY);
  417. renderVB(solid, window->innerBox, targetCX, targetCY);
  418. renderVB(solid, window->leftVLine, targetCX, targetCY);
  419. renderVB(solid, window->rightVLine, targetCX, targetCY);
  420. renderVB(solid, window->leftLine, targetCX, targetCY);
  421. renderVB(solid, window->topLine, targetCX, targetCY);
  422. renderVB(solid, window->rightLine, targetCX, targetCY);
  423. gs_matrix_pop();
  424. /* ----------- */
  425. cx = obs_source_get_width(previewLabel);
  426. cy = obs_source_get_height(previewLabel);
  427. gs_matrix_push();
  428. gs_matrix_translate3f(labelX, labelY, 0.0f);
  429. drawBox(cx, cy + int(halfCX * 0.015f), 0xD91F1F1F);
  430. obs_source_video_render(previewLabel);
  431. gs_matrix_pop();
  432. /* ----------------------------- */
  433. /* draw program */
  434. obs_source_t *programLabel = window->multiviewLabels[1];
  435. offset = labelOffset(programLabel, halfCX);
  436. calcPreviewProgram(true);
  437. gs_matrix_push();
  438. gs_matrix_translate3f(sourceX, sourceY, 0.0f);
  439. gs_matrix_scale3f(hiScaleX, hiScaleY, 1.0f);
  440. setRegion(sourceX, sourceY, hiCX, hiCY);
  441. obs_render_main_texture();
  442. resetRegion();
  443. gs_matrix_pop();
  444. /* ----------- */
  445. gs_matrix_push();
  446. gs_matrix_translate3f(sourceX, sourceY, 0.0f);
  447. gs_matrix_scale3f(hiScaleX, hiScaleY, 1.0f);
  448. renderVB(solid, window->outerBox, targetCX, targetCY);
  449. gs_matrix_pop();
  450. /* ----------- */
  451. cx = obs_source_get_width(programLabel);
  452. cy = obs_source_get_height(programLabel);
  453. gs_matrix_push();
  454. gs_matrix_translate3f(labelX, labelY, 0.0f);
  455. drawBox(cx, cy + int(halfCX * 0.015f), 0xD91F1F1F);
  456. obs_source_video_render(programLabel);
  457. gs_matrix_pop();
  458. /* ----------------------------- */
  459. gs_viewport_pop();
  460. gs_projection_pop();
  461. }
  462. void OBSProjector::OBSRender(void *data, uint32_t cx, uint32_t cy)
  463. {
  464. OBSProjector *window = reinterpret_cast<OBSProjector*>(data);
  465. if (!window->ready)
  466. return;
  467. OBSBasic *main = reinterpret_cast<OBSBasic*>(App()->GetMainWindow());
  468. uint32_t targetCX;
  469. uint32_t targetCY;
  470. int x, y;
  471. int newCX, newCY;
  472. float scale;
  473. if (window->source) {
  474. targetCX = std::max(obs_source_get_width(window->source), 1u);
  475. targetCY = std::max(obs_source_get_height(window->source), 1u);
  476. } else {
  477. struct obs_video_info ovi;
  478. obs_get_video_info(&ovi);
  479. targetCX = ovi.base_width;
  480. targetCY = ovi.base_height;
  481. }
  482. GetScaleAndCenterPos(targetCX, targetCY, cx, cy, x, y, scale);
  483. newCX = int(scale * float(targetCX));
  484. newCY = int(scale * float(targetCY));
  485. gs_viewport_push();
  486. gs_projection_push();
  487. gs_ortho(0.0f, float(targetCX), 0.0f, float(targetCY), -100.0f, 100.0f);
  488. gs_set_viewport(x, y, newCX, newCY);
  489. OBSSource source = window->source;
  490. if (window->type == ProjectorType::Preview &&
  491. main->IsPreviewProgramMode()) {
  492. OBSSource curSource = main->GetCurrentSceneSource();
  493. if (window->source != curSource) {
  494. obs_source_dec_showing(window->source);
  495. obs_source_inc_showing(curSource);
  496. source = curSource;
  497. }
  498. }
  499. if (source) {
  500. obs_source_video_render(source);
  501. } else {
  502. obs_render_main_texture();
  503. }
  504. gs_projection_pop();
  505. gs_viewport_pop();
  506. }
  507. void OBSProjector::OBSSourceRemoved(void *data, calldata_t *params)
  508. {
  509. OBSProjector *window = reinterpret_cast<OBSProjector*>(data);
  510. window->deleteLater();
  511. UNUSED_PARAMETER(params);
  512. }
  513. static int getSourceByPosition(int x, int y)
  514. {
  515. struct obs_video_info ovi;
  516. obs_get_video_info(&ovi);
  517. float ratio = float(ovi.base_width) / float(ovi.base_height);
  518. QWidget *rec = QApplication::activeWindow();
  519. int cx = rec->width();
  520. int cy = rec->height();
  521. int minX = 0;
  522. int minY = 0;
  523. int maxX = cx;
  524. int maxY = cy;
  525. int halfX = cx / 2;
  526. int halfY = cy / 2;
  527. int pos = -1;
  528. switch (multiviewLayout) {
  529. case VERTICAL_LEFT:
  530. if (float(cx) / float(cy) > ratio) {
  531. int validX = cy * ratio;
  532. maxX = halfX + (validX / 2);
  533. } else {
  534. int validY = cx / ratio;
  535. minY = halfY - (validY / 2);
  536. maxY = halfY + (validY / 2);
  537. }
  538. minX = halfX;
  539. if (x < minX || x > maxX || y < minY || y > maxY)
  540. break;
  541. pos = 2 * ((y - minY) / ((maxY - minY) / 4));
  542. if (x > minX + ((maxX - minX) / 2))
  543. pos++;
  544. break;
  545. case VERTICAL_RIGHT:
  546. if (float(cx) / float(cy) > ratio) {
  547. int validX = cy * ratio;
  548. minX = halfX - (validX / 2);
  549. } else {
  550. int validY = cx / ratio;
  551. minY = halfY - (validY / 2);
  552. maxY = halfY + (validY / 2);
  553. }
  554. maxX = halfX;
  555. if (x < minX || x > maxX || y < minY || y > maxY)
  556. break;
  557. pos = 2 * ((y - minY) / ((maxY - minY) / 4));
  558. if (x > minX + ((maxX - minX) / 2))
  559. pos++;
  560. break;
  561. case HORIZONTAL_BOTTOM:
  562. if (float(cx) / float(cy) > ratio) {
  563. int validX = cy * ratio;
  564. minX = halfX - (validX / 2);
  565. maxX = halfX + (validX / 2);
  566. } else {
  567. int validY = cx / ratio;
  568. minY = halfY - (validY / 2);
  569. }
  570. maxY = halfY;
  571. if (x < minX || x > maxX || y < minY || y > maxY)
  572. break;
  573. pos = (x - minX) / ((maxX - minX) / 4);
  574. if (y > minY + ((maxY - minY) / 2))
  575. pos += 4;
  576. break;
  577. default: // HORIZONTAL_TOP
  578. if (float(cx) / float(cy) > ratio) {
  579. int validX = cy * ratio;
  580. minX = halfX - (validX / 2);
  581. maxX = halfX + (validX / 2);
  582. } else {
  583. int validY = cx / ratio;
  584. maxY = halfY + (validY / 2);
  585. }
  586. minY = halfY;
  587. if (x < minX || x > maxX || y < minY || y > maxY)
  588. break;
  589. pos = (x - minX) / ((maxX - minX) / 4);
  590. if (y > minY + ((maxY - minY) / 2))
  591. pos += 4;
  592. }
  593. return pos;
  594. }
  595. void OBSProjector::mouseDoubleClickEvent(QMouseEvent *event)
  596. {
  597. OBSQTDisplay::mouseDoubleClickEvent(event);
  598. if (!config_get_bool(GetGlobalConfig(), "BasicWindow",
  599. "TransitionOnDoubleClick"))
  600. return;
  601. OBSBasic *main = (OBSBasic*)obs_frontend_get_main_window();
  602. if (!main->IsPreviewProgramMode())
  603. return;
  604. if (event->button() == Qt::LeftButton) {
  605. int pos = getSourceByPosition(event->x(), event->y());
  606. if (pos < 0)
  607. return;
  608. OBSSource src = OBSGetStrongRef(multiviewScenes[pos]);
  609. if (!src)
  610. return;
  611. if (main->GetProgramSource() != src)
  612. main->TransitionToScene(src);
  613. }
  614. }
  615. void OBSProjector::mousePressEvent(QMouseEvent *event)
  616. {
  617. OBSQTDisplay::mousePressEvent(event);
  618. if (event->button() == Qt::RightButton) {
  619. QMenu popup(this);
  620. popup.addAction(QTStr("Close"), this, SLOT(EscapeTriggered()));
  621. popup.exec(QCursor::pos());
  622. }
  623. if (event->button() == Qt::LeftButton) {
  624. int pos = getSourceByPosition(event->x(), event->y());
  625. if (pos < 0)
  626. return;
  627. OBSSource src = OBSGetStrongRef(multiviewScenes[pos]);
  628. if (!src)
  629. return;
  630. OBSBasic *main = (OBSBasic*)obs_frontend_get_main_window();
  631. if (main->GetCurrentSceneSource() != src)
  632. main->SetCurrentScene(src, false);
  633. }
  634. }
  635. void OBSProjector::EscapeTriggered()
  636. {
  637. if (!isWindow) {
  638. OBSBasic *main = (OBSBasic*)obs_frontend_get_main_window();
  639. main->RemoveSavedProjectors(savedMonitor);
  640. }
  641. deleteLater();
  642. }
  643. void OBSProjector::UpdateMultiview()
  644. {
  645. for (OBSWeakSource &val : multiviewScenes)
  646. val = nullptr;
  647. for (OBSSource &val : multiviewLabels)
  648. val = nullptr;
  649. struct obs_video_info ovi;
  650. obs_get_video_info(&ovi);
  651. uint32_t h = ovi.base_height;
  652. struct obs_frontend_source_list scenes = {};
  653. obs_frontend_get_scenes(&scenes);
  654. int curIdx = 0;
  655. multiviewLabels[0] = CreateLabel(Str("StudioMode.Preview"), h / 2);
  656. multiviewLabels[1] = CreateLabel(Str("StudioMode.Program"), h / 2);
  657. for (size_t i = 0; i < scenes.sources.num && curIdx < 8; i++) {
  658. obs_source_t *src = scenes.sources.array[i];
  659. OBSData data = obs_source_get_private_settings(src);
  660. obs_data_release(data);
  661. obs_data_set_default_bool(data, "show_in_multiview", true);
  662. if (!obs_data_get_bool(data, "show_in_multiview"))
  663. continue;
  664. multiviewScenes[curIdx] = OBSGetWeakRef(src);
  665. obs_source_inc_showing(src);
  666. std::string name;
  667. name += std::to_string(curIdx + 1);
  668. name += " - ";
  669. name += obs_source_get_name(src);
  670. multiviewLabels[curIdx + 2] = CreateLabel(name.c_str(), h / 3);
  671. curIdx++;
  672. }
  673. obs_frontend_source_list_free(&scenes);
  674. const char *multiviewLayoutText = config_get_string(GetGlobalConfig(),
  675. "BasicWindow", "MultiviewLayout");
  676. if (astrcmpi(multiviewLayoutText, "horizontalbottom") == 0)
  677. multiviewLayout = HORIZONTAL_BOTTOM;
  678. else if (astrcmpi(multiviewLayoutText, "verticalleft") == 0)
  679. multiviewLayout = VERTICAL_LEFT;
  680. else if (astrcmpi(multiviewLayoutText, "verticalright") == 0)
  681. multiviewLayout = VERTICAL_RIGHT;
  682. else
  683. multiviewLayout = HORIZONTAL_TOP;
  684. }
  685. void OBSProjector::UpdateMultiviewProjectors()
  686. {
  687. obs_enter_graphics();
  688. updatingMultiview = true;
  689. obs_leave_graphics();
  690. for (auto &projector : multiviewProjectors)
  691. projector->UpdateMultiview();
  692. obs_enter_graphics();
  693. updatingMultiview = false;
  694. obs_leave_graphics();
  695. }