window-projector.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062
  1. #include <QAction>
  2. #include <QGuiApplication>
  3. #include <QMouseEvent>
  4. #include <QMenu>
  5. #include <QScreen>
  6. #include "obs-app.hpp"
  7. #include "window-basic-main.hpp"
  8. #include "display-helpers.hpp"
  9. #include "qt-wrappers.hpp"
  10. #include "platform.hpp"
  11. static QList<OBSProjector *> multiviewProjectors;
  12. static bool updatingMultiview = false, drawLabel, drawSafeArea, mouseSwitching,
  13. transitionOnDoubleClick;
  14. static MultiviewLayout multiviewLayout;
  15. static size_t maxSrcs, numSrcs;
  16. OBSProjector::OBSProjector(QWidget *widget, obs_source_t *source_, int monitor,
  17. ProjectorType type_)
  18. : OBSQTDisplay(widget, Qt::Window),
  19. source(source_),
  20. removedSignal(obs_source_get_signal_handler(source), "remove",
  21. OBSSourceRemoved, this)
  22. {
  23. type = type_;
  24. setWindowIcon(QIcon::fromTheme("obs", QIcon(":/res/images/obs.png")));
  25. if (monitor == -1)
  26. resize(480, 270);
  27. else
  28. SetMonitor(monitor);
  29. UpdateProjectorTitle(QT_UTF8(obs_source_get_name(source)));
  30. QAction *action = new QAction(this);
  31. action->setShortcut(Qt::Key_Escape);
  32. addAction(action);
  33. connect(action, SIGNAL(triggered()), this, SLOT(EscapeTriggered()));
  34. SetAlwaysOnTop(this, config_get_bool(GetGlobalConfig(), "BasicWindow",
  35. "ProjectorAlwaysOnTop"));
  36. setAttribute(Qt::WA_DeleteOnClose, true);
  37. //disable application quit when last window closed
  38. setAttribute(Qt::WA_QuitOnClose, false);
  39. installEventFilter(CreateShortcutFilter());
  40. auto addDrawCallback = [this]() {
  41. bool isMultiview = type == ProjectorType::Multiview;
  42. obs_display_add_draw_callback(
  43. GetDisplay(),
  44. isMultiview ? OBSRenderMultiview : OBSRender, this);
  45. obs_display_set_background_color(GetDisplay(), 0x000000);
  46. };
  47. connect(this, &OBSQTDisplay::DisplayCreated, addDrawCallback);
  48. if (type == ProjectorType::Multiview) {
  49. obs_enter_graphics();
  50. // All essential action should be placed inside this area
  51. gs_render_start(true);
  52. gs_vertex2f(actionSafePercentage, actionSafePercentage);
  53. gs_vertex2f(actionSafePercentage, 1 - actionSafePercentage);
  54. gs_vertex2f(1 - actionSafePercentage, 1 - actionSafePercentage);
  55. gs_vertex2f(1 - actionSafePercentage, actionSafePercentage);
  56. gs_vertex2f(actionSafePercentage, actionSafePercentage);
  57. actionSafeMargin = gs_render_save();
  58. // All graphics should be placed inside this area
  59. gs_render_start(true);
  60. gs_vertex2f(graphicsSafePercentage, graphicsSafePercentage);
  61. gs_vertex2f(graphicsSafePercentage, 1 - graphicsSafePercentage);
  62. gs_vertex2f(1 - graphicsSafePercentage,
  63. 1 - graphicsSafePercentage);
  64. gs_vertex2f(1 - graphicsSafePercentage, graphicsSafePercentage);
  65. gs_vertex2f(graphicsSafePercentage, graphicsSafePercentage);
  66. graphicsSafeMargin = gs_render_save();
  67. // 4:3 safe area for widescreen
  68. gs_render_start(true);
  69. gs_vertex2f(fourByThreeSafePercentage, graphicsSafePercentage);
  70. gs_vertex2f(1 - fourByThreeSafePercentage,
  71. graphicsSafePercentage);
  72. gs_vertex2f(1 - fourByThreeSafePercentage,
  73. 1 - graphicsSafePercentage);
  74. gs_vertex2f(fourByThreeSafePercentage,
  75. 1 - graphicsSafePercentage);
  76. gs_vertex2f(fourByThreeSafePercentage, graphicsSafePercentage);
  77. fourByThreeSafeMargin = gs_render_save();
  78. gs_render_start(true);
  79. gs_vertex2f(0.0f, 0.5f);
  80. gs_vertex2f(lineLength, 0.5f);
  81. leftLine = gs_render_save();
  82. gs_render_start(true);
  83. gs_vertex2f(0.5f, 0.0f);
  84. gs_vertex2f(0.5f, lineLength);
  85. topLine = gs_render_save();
  86. gs_render_start(true);
  87. gs_vertex2f(1.0f, 0.5f);
  88. gs_vertex2f(1 - lineLength, 0.5f);
  89. rightLine = gs_render_save();
  90. obs_leave_graphics();
  91. solid = obs_get_base_effect(OBS_EFFECT_SOLID);
  92. color = gs_effect_get_param_by_name(solid, "color");
  93. UpdateMultiview();
  94. multiviewProjectors.push_back(this);
  95. }
  96. App()->IncrementSleepInhibition();
  97. if (source)
  98. obs_source_inc_showing(source);
  99. ready = true;
  100. show();
  101. // We need it here to allow keyboard input in X11 to listen to Escape
  102. activateWindow();
  103. }
  104. OBSProjector::~OBSProjector()
  105. {
  106. bool isMultiview = type == ProjectorType::Multiview;
  107. obs_display_remove_draw_callback(
  108. GetDisplay(), isMultiview ? OBSRenderMultiview : OBSRender,
  109. this);
  110. if (source)
  111. obs_source_dec_showing(source);
  112. if (isMultiview) {
  113. for (OBSWeakSource &weakSrc : multiviewScenes) {
  114. OBSSource src = OBSGetStrongRef(weakSrc);
  115. if (src)
  116. obs_source_dec_showing(src);
  117. }
  118. obs_enter_graphics();
  119. gs_vertexbuffer_destroy(actionSafeMargin);
  120. gs_vertexbuffer_destroy(graphicsSafeMargin);
  121. gs_vertexbuffer_destroy(fourByThreeSafeMargin);
  122. gs_vertexbuffer_destroy(leftLine);
  123. gs_vertexbuffer_destroy(topLine);
  124. gs_vertexbuffer_destroy(rightLine);
  125. obs_leave_graphics();
  126. }
  127. if (type == ProjectorType::Multiview)
  128. multiviewProjectors.removeAll(this);
  129. App()->DecrementSleepInhibition();
  130. }
  131. void OBSProjector::SetMonitor(int monitor)
  132. {
  133. savedMonitor = monitor;
  134. QScreen *screen = QGuiApplication::screens()[monitor];
  135. setGeometry(screen->geometry());
  136. showFullScreen();
  137. SetHideCursor();
  138. }
  139. void OBSProjector::SetHideCursor()
  140. {
  141. if (savedMonitor == -1)
  142. return;
  143. bool hideCursor = config_get_bool(GetGlobalConfig(), "BasicWindow",
  144. "HideProjectorCursor");
  145. if (hideCursor && type != ProjectorType::Multiview)
  146. setCursor(Qt::BlankCursor);
  147. else
  148. setCursor(Qt::ArrowCursor);
  149. }
  150. static OBSSource CreateLabel(const char *name, size_t h)
  151. {
  152. obs_data_t *settings = obs_data_create();
  153. obs_data_t *font = obs_data_create();
  154. std::string text;
  155. text += " ";
  156. text += name;
  157. text += " ";
  158. #if defined(_WIN32)
  159. obs_data_set_string(font, "face", "Arial");
  160. #elif defined(__APPLE__)
  161. obs_data_set_string(font, "face", "Helvetica");
  162. #else
  163. obs_data_set_string(font, "face", "Monospace");
  164. #endif
  165. obs_data_set_int(font, "flags", 1); // Bold text
  166. obs_data_set_int(font, "size", int(h / 9.81));
  167. obs_data_set_obj(settings, "font", font);
  168. obs_data_set_string(settings, "text", text.c_str());
  169. obs_data_set_bool(settings, "outline", false);
  170. #ifdef _WIN32
  171. const char *text_source_id = "text_gdiplus";
  172. #else
  173. const char *text_source_id = "text_ft2_source";
  174. #endif
  175. OBSSource txtSource =
  176. obs_source_create_private(text_source_id, name, settings);
  177. obs_source_release(txtSource);
  178. obs_data_release(font);
  179. obs_data_release(settings);
  180. return txtSource;
  181. }
  182. static inline uint32_t labelOffset(obs_source_t *label, uint32_t cx)
  183. {
  184. uint32_t w = obs_source_get_width(label);
  185. int n; // Number of scenes per row
  186. switch (multiviewLayout) {
  187. case MultiviewLayout::HORIZONTAL_TOP_24_SCENES:
  188. n = 6;
  189. break;
  190. default:
  191. n = 4;
  192. break;
  193. }
  194. w = uint32_t(w * ((1.0f) / n));
  195. return (cx / 2) - w;
  196. }
  197. static inline void startRegion(int vX, int vY, int vCX, int vCY, float oL,
  198. float oR, float oT, float oB)
  199. {
  200. gs_projection_push();
  201. gs_viewport_push();
  202. gs_set_viewport(vX, vY, vCX, vCY);
  203. gs_ortho(oL, oR, oT, oB, -100.0f, 100.0f);
  204. }
  205. static inline void endRegion()
  206. {
  207. gs_viewport_pop();
  208. gs_projection_pop();
  209. }
  210. void OBSProjector::OBSRenderMultiview(void *data, uint32_t cx, uint32_t cy)
  211. {
  212. OBSProjector *window = (OBSProjector *)data;
  213. if (updatingMultiview || !window->ready)
  214. return;
  215. OBSBasic *main = (OBSBasic *)obs_frontend_get_main_window();
  216. uint32_t targetCX, targetCY;
  217. int x, y;
  218. float scale;
  219. targetCX = (uint32_t)window->fw;
  220. targetCY = (uint32_t)window->fh;
  221. GetScaleAndCenterPos(targetCX, targetCY, cx, cy, x, y, scale);
  222. OBSSource previewSrc = main->GetCurrentSceneSource();
  223. OBSSource programSrc = main->GetProgramSource();
  224. bool studioMode = main->IsPreviewProgramMode();
  225. auto renderVB = [&](gs_vertbuffer_t *vb, int cx, int cy,
  226. uint32_t colorVal) {
  227. if (!vb)
  228. return;
  229. matrix4 transform;
  230. matrix4_identity(&transform);
  231. transform.x.x = cx;
  232. transform.y.y = cy;
  233. gs_load_vertexbuffer(vb);
  234. gs_matrix_push();
  235. gs_matrix_mul(&transform);
  236. gs_effect_set_color(window->color, colorVal);
  237. while (gs_effect_loop(window->solid, "Solid"))
  238. gs_draw(GS_LINESTRIP, 0, 0);
  239. gs_matrix_pop();
  240. };
  241. auto drawBox = [&](float cx, float cy, uint32_t colorVal) {
  242. gs_effect_set_color(window->color, colorVal);
  243. while (gs_effect_loop(window->solid, "Solid"))
  244. gs_draw_sprite(nullptr, 0, (uint32_t)cx, (uint32_t)cy);
  245. };
  246. auto setRegion = [&](float bx, float by, float cx, float cy) {
  247. float vX = int(x + bx * scale);
  248. float vY = int(y + by * scale);
  249. float vCX = int(cx * scale);
  250. float vCY = int(cy * scale);
  251. float oL = bx;
  252. float oT = by;
  253. float oR = (bx + cx);
  254. float oB = (by + cy);
  255. startRegion(vX, vY, vCX, vCY, oL, oR, oT, oB);
  256. };
  257. auto calcBaseSource = [&](size_t i) {
  258. switch (multiviewLayout) {
  259. case MultiviewLayout::HORIZONTAL_TOP_24_SCENES:
  260. window->sourceX = (i % 6) * window->scenesCX;
  261. window->sourceY =
  262. window->pvwprgCY + (i / 6) * window->scenesCY;
  263. break;
  264. case MultiviewLayout::VERTICAL_LEFT_8_SCENES:
  265. window->sourceX = window->pvwprgCX;
  266. window->sourceY = (i / 2) * window->scenesCY;
  267. if (i % 2 != 0)
  268. window->sourceX += window->scenesCX;
  269. break;
  270. case MultiviewLayout::VERTICAL_RIGHT_8_SCENES:
  271. window->sourceX = 0;
  272. window->sourceY = (i / 2) * window->scenesCY;
  273. if (i % 2 != 0)
  274. window->sourceX = window->scenesCX;
  275. break;
  276. case MultiviewLayout::HORIZONTAL_BOTTOM_8_SCENES:
  277. if (i < 4) {
  278. window->sourceX = (float(i) * window->scenesCX);
  279. window->sourceY = 0;
  280. } else {
  281. window->sourceX =
  282. (float(i - 4) * window->scenesCX);
  283. window->sourceY = window->scenesCY;
  284. }
  285. break;
  286. default: // MultiviewLayout::HORIZONTAL_TOP_8_SCENES:
  287. if (i < 4) {
  288. window->sourceX = (float(i) * window->scenesCX);
  289. window->sourceY = window->pvwprgCY;
  290. } else {
  291. window->sourceX =
  292. (float(i - 4) * window->scenesCX);
  293. window->sourceY =
  294. window->pvwprgCY + window->scenesCY;
  295. }
  296. }
  297. window->siX = window->sourceX + window->thickness;
  298. window->siY = window->sourceY + window->thickness;
  299. };
  300. auto calcPreviewProgram = [&](bool program) {
  301. switch (multiviewLayout) {
  302. case MultiviewLayout::HORIZONTAL_TOP_24_SCENES:
  303. window->sourceX =
  304. window->thickness + window->pvwprgCX / 2;
  305. window->sourceY = window->thickness;
  306. window->labelX = window->offset + window->pvwprgCX / 2;
  307. window->labelY = window->pvwprgCY * 0.85f;
  308. if (program) {
  309. window->sourceX += window->pvwprgCX;
  310. window->labelX += window->pvwprgCX;
  311. }
  312. break;
  313. case MultiviewLayout::VERTICAL_LEFT_8_SCENES:
  314. window->sourceX = window->thickness;
  315. window->sourceY = window->pvwprgCY + window->thickness;
  316. window->labelX = window->offset;
  317. window->labelY = window->pvwprgCY * 1.85f;
  318. if (program) {
  319. window->sourceY = window->thickness;
  320. window->labelY = window->pvwprgCY * 0.85f;
  321. }
  322. break;
  323. case MultiviewLayout::VERTICAL_RIGHT_8_SCENES:
  324. window->sourceX = window->pvwprgCX + window->thickness;
  325. window->sourceY = window->pvwprgCY + window->thickness;
  326. window->labelX = window->pvwprgCX + window->offset;
  327. window->labelY = window->pvwprgCY * 1.85f;
  328. if (program) {
  329. window->sourceY = window->thickness;
  330. window->labelY = window->pvwprgCY * 0.85f;
  331. }
  332. break;
  333. case MultiviewLayout::HORIZONTAL_BOTTOM_8_SCENES:
  334. window->sourceX = window->thickness;
  335. window->sourceY = window->pvwprgCY + window->thickness;
  336. window->labelX = window->offset;
  337. window->labelY = window->pvwprgCY * 1.85f;
  338. if (program) {
  339. window->sourceX += window->pvwprgCX;
  340. window->labelX += window->pvwprgCX;
  341. }
  342. break;
  343. default: // MultiviewLayout::HORIZONTAL_TOP_8_SCENES:
  344. window->sourceX = window->thickness;
  345. window->sourceY = window->thickness;
  346. window->labelX = window->offset;
  347. window->labelY = window->pvwprgCY * 0.85f;
  348. if (program) {
  349. window->sourceX += window->pvwprgCX;
  350. window->labelX += window->pvwprgCX;
  351. }
  352. }
  353. };
  354. auto paintAreaWithColor = [&](float tx, float ty, float cx, float cy,
  355. uint32_t color) {
  356. gs_matrix_push();
  357. gs_matrix_translate3f(tx, ty, 0.0f);
  358. drawBox(cx, cy, color);
  359. gs_matrix_pop();
  360. };
  361. // Define the whole usable region for the multiview
  362. startRegion(x, y, targetCX * scale, targetCY * scale, 0.0f, window->fw,
  363. 0.0f, window->fh);
  364. // Change the background color to highlight all sources
  365. drawBox(window->fw, window->fh, outerColor);
  366. /* ----------------------------- */
  367. /* draw sources */
  368. for (size_t i = 0; i < maxSrcs; i++) {
  369. // Handle all the offsets
  370. calcBaseSource(i);
  371. if (i >= numSrcs) {
  372. // Just paint the background and continue
  373. paintAreaWithColor(window->sourceX, window->sourceY,
  374. window->scenesCX, window->scenesCY,
  375. outerColor);
  376. paintAreaWithColor(window->siX, window->siY,
  377. window->siCX, window->siCY,
  378. backgroundColor);
  379. continue;
  380. }
  381. OBSSource src = OBSGetStrongRef(window->multiviewScenes[i]);
  382. // We have a source. Now chose the proper highlight color
  383. uint32_t colorVal = outerColor;
  384. if (src == programSrc)
  385. colorVal = programColor;
  386. else if (src == previewSrc)
  387. colorVal = studioMode ? previewColor : programColor;
  388. // Paint the background
  389. paintAreaWithColor(window->sourceX, window->sourceY,
  390. window->scenesCX, window->scenesCY,
  391. colorVal);
  392. paintAreaWithColor(window->siX, window->siY, window->siCX,
  393. window->siCY, backgroundColor);
  394. /* ----------- */
  395. // Render the source
  396. gs_matrix_push();
  397. gs_matrix_translate3f(window->siX, window->siY, 0.0f);
  398. gs_matrix_scale3f(window->siScaleX, window->siScaleY, 1.0f);
  399. setRegion(window->siX, window->siY, window->siCX, window->siCY);
  400. obs_source_video_render(src);
  401. endRegion();
  402. gs_matrix_pop();
  403. /* ----------- */
  404. // Render the label
  405. if (!drawLabel)
  406. continue;
  407. obs_source *label = window->multiviewLabels[i + 2];
  408. if (!label)
  409. continue;
  410. window->offset = labelOffset(label, window->scenesCX);
  411. gs_matrix_push();
  412. gs_matrix_translate3f(
  413. window->sourceX + window->offset,
  414. (window->scenesCY * 0.85f) + window->sourceY, 0.0f);
  415. gs_matrix_scale3f(window->ppiScaleX, window->ppiScaleY, 1.0f);
  416. drawBox(obs_source_get_width(label),
  417. obs_source_get_height(label) +
  418. int(window->sourceY * 0.015f),
  419. labelColor);
  420. obs_source_video_render(label);
  421. gs_matrix_pop();
  422. }
  423. /* ----------------------------- */
  424. /* draw preview */
  425. obs_source_t *previewLabel = window->multiviewLabels[0];
  426. window->offset = labelOffset(previewLabel, window->pvwprgCX);
  427. calcPreviewProgram(false);
  428. // Paint the background
  429. paintAreaWithColor(window->sourceX, window->sourceY, window->ppiCX,
  430. window->ppiCY, backgroundColor);
  431. // Scale and Draw the preview
  432. gs_matrix_push();
  433. gs_matrix_translate3f(window->sourceX, window->sourceY, 0.0f);
  434. gs_matrix_scale3f(window->ppiScaleX, window->ppiScaleY, 1.0f);
  435. setRegion(window->sourceX, window->sourceY, window->ppiCX,
  436. window->ppiCY);
  437. if (studioMode)
  438. obs_source_video_render(previewSrc);
  439. else
  440. obs_render_main_texture();
  441. if (drawSafeArea) {
  442. renderVB(window->actionSafeMargin, targetCX, targetCY,
  443. outerColor);
  444. renderVB(window->graphicsSafeMargin, targetCX, targetCY,
  445. outerColor);
  446. renderVB(window->fourByThreeSafeMargin, targetCX, targetCY,
  447. outerColor);
  448. renderVB(window->leftLine, targetCX, targetCY, outerColor);
  449. renderVB(window->topLine, targetCX, targetCY, outerColor);
  450. renderVB(window->rightLine, targetCX, targetCY, outerColor);
  451. }
  452. endRegion();
  453. gs_matrix_pop();
  454. /* ----------- */
  455. // Draw the Label
  456. if (drawLabel) {
  457. gs_matrix_push();
  458. gs_matrix_translate3f(window->labelX, window->labelY, 0.0f);
  459. gs_matrix_scale3f(window->ppiScaleX, window->ppiScaleY, 1.0f);
  460. drawBox(obs_source_get_width(previewLabel),
  461. obs_source_get_height(previewLabel) +
  462. int(window->pvwprgCX * 0.015f),
  463. labelColor);
  464. obs_source_video_render(previewLabel);
  465. gs_matrix_pop();
  466. }
  467. /* ----------------------------- */
  468. /* draw program */
  469. obs_source_t *programLabel = window->multiviewLabels[1];
  470. window->offset = labelOffset(programLabel, window->pvwprgCX);
  471. calcPreviewProgram(true);
  472. paintAreaWithColor(window->sourceX, window->sourceY, window->ppiCX,
  473. window->ppiCY, backgroundColor);
  474. // Scale and Draw the program
  475. gs_matrix_push();
  476. gs_matrix_translate3f(window->sourceX, window->sourceY, 0.0f);
  477. gs_matrix_scale3f(window->ppiScaleX, window->ppiScaleY, 1.0f);
  478. setRegion(window->sourceX, window->sourceY, window->ppiCX,
  479. window->ppiCY);
  480. obs_render_main_texture();
  481. endRegion();
  482. gs_matrix_pop();
  483. /* ----------- */
  484. // Draw the Label
  485. if (drawLabel) {
  486. gs_matrix_push();
  487. gs_matrix_translate3f(window->labelX, window->labelY, 0.0f);
  488. gs_matrix_scale3f(window->ppiScaleX, window->ppiScaleY, 1.0f);
  489. drawBox(obs_source_get_width(programLabel),
  490. obs_source_get_height(programLabel) +
  491. int(window->pvwprgCX * 0.015f),
  492. labelColor);
  493. obs_source_video_render(programLabel);
  494. gs_matrix_pop();
  495. }
  496. // Region for future usage with additional info.
  497. if (multiviewLayout == MultiviewLayout::HORIZONTAL_TOP_24_SCENES) {
  498. // Just paint the background for now
  499. paintAreaWithColor(window->thickness, window->thickness,
  500. window->siCX,
  501. window->siCY * 2 + window->thicknessx2,
  502. backgroundColor);
  503. paintAreaWithColor(
  504. window->thickness +
  505. 2.5 * (window->thicknessx2 + window->ppiCX),
  506. window->thickness, window->siCX,
  507. window->siCY * 2 + window->thicknessx2,
  508. backgroundColor);
  509. }
  510. endRegion();
  511. }
  512. void OBSProjector::OBSRender(void *data, uint32_t cx, uint32_t cy)
  513. {
  514. OBSProjector *window = reinterpret_cast<OBSProjector *>(data);
  515. if (!window->ready)
  516. return;
  517. OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
  518. OBSSource source = window->source;
  519. uint32_t targetCX;
  520. uint32_t targetCY;
  521. int x, y;
  522. int newCX, newCY;
  523. float scale;
  524. if (source) {
  525. targetCX = std::max(obs_source_get_width(source), 1u);
  526. targetCY = std::max(obs_source_get_height(source), 1u);
  527. } else {
  528. struct obs_video_info ovi;
  529. obs_get_video_info(&ovi);
  530. targetCX = ovi.base_width;
  531. targetCY = ovi.base_height;
  532. }
  533. GetScaleAndCenterPos(targetCX, targetCY, cx, cy, x, y, scale);
  534. newCX = int(scale * float(targetCX));
  535. newCY = int(scale * float(targetCY));
  536. startRegion(x, y, newCX, newCY, 0.0f, float(targetCX), 0.0f,
  537. float(targetCY));
  538. if (window->type == ProjectorType::Preview &&
  539. main->IsPreviewProgramMode()) {
  540. OBSSource curSource = main->GetCurrentSceneSource();
  541. if (source != curSource) {
  542. obs_source_dec_showing(source);
  543. obs_source_inc_showing(curSource);
  544. source = curSource;
  545. window->source = source;
  546. }
  547. } else if (window->type == ProjectorType::Preview &&
  548. !main->IsPreviewProgramMode()) {
  549. window->source = nullptr;
  550. }
  551. if (source)
  552. obs_source_video_render(source);
  553. else
  554. obs_render_main_texture();
  555. endRegion();
  556. }
  557. void OBSProjector::OBSSourceRemoved(void *data, calldata_t *params)
  558. {
  559. OBSProjector *window = reinterpret_cast<OBSProjector *>(data);
  560. window->deleteLater();
  561. UNUSED_PARAMETER(params);
  562. }
  563. static int getSourceByPosition(int x, int y, float ratio)
  564. {
  565. int pos = -1;
  566. QWidget *rec = QApplication::activeWindow();
  567. if (!rec)
  568. return pos;
  569. int cx = rec->width();
  570. int cy = rec->height();
  571. int minX = 0;
  572. int minY = 0;
  573. int maxX = cx;
  574. int maxY = cy;
  575. switch (multiviewLayout) {
  576. case MultiviewLayout::HORIZONTAL_TOP_24_SCENES:
  577. if (float(cx) / float(cy) > ratio) {
  578. int validX = cy * ratio;
  579. minX = (cx / 2) - (validX / 2);
  580. maxX = (cx / 2) + (validX / 2);
  581. minY = cy / 3;
  582. } else {
  583. int validY = cx / ratio;
  584. maxY = (cy / 2) + (validY / 2);
  585. minY = (cy / 2) - (validY / 6);
  586. }
  587. if (x < minX || x > maxX || y < minY || y > maxY)
  588. break;
  589. pos = (x - minX) / ((maxX - minX) / 6);
  590. pos += ((y - minY) / ((maxY - minY) / 4)) * 6;
  591. break;
  592. case MultiviewLayout::VERTICAL_LEFT_8_SCENES:
  593. if (float(cx) / float(cy) > ratio) {
  594. int validX = cy * ratio;
  595. maxX = (cx / 2) + (validX / 2);
  596. } else {
  597. int validY = cx / ratio;
  598. minY = (cy / 2) - (validY / 2);
  599. maxY = (cy / 2) + (validY / 2);
  600. }
  601. minX = cx / 2;
  602. if (x < minX || x > maxX || y < minY || y > maxY)
  603. break;
  604. pos = 2 * ((y - minY) / ((maxY - minY) / 4));
  605. if (x > minX + ((maxX - minX) / 2))
  606. pos++;
  607. break;
  608. case MultiviewLayout::VERTICAL_RIGHT_8_SCENES:
  609. if (float(cx) / float(cy) > ratio) {
  610. int validX = cy * ratio;
  611. minX = (cx / 2) - (validX / 2);
  612. } else {
  613. int validY = cx / ratio;
  614. minY = (cy / 2) - (validY / 2);
  615. maxY = (cy / 2) + (validY / 2);
  616. }
  617. maxX = (cx / 2);
  618. if (x < minX || x > maxX || y < minY || y > maxY)
  619. break;
  620. pos = 2 * ((y - minY) / ((maxY - minY) / 4));
  621. if (x > minX + ((maxX - minX) / 2))
  622. pos++;
  623. break;
  624. case MultiviewLayout::HORIZONTAL_BOTTOM_8_SCENES:
  625. if (float(cx) / float(cy) > ratio) {
  626. int validX = cy * ratio;
  627. minX = (cx / 2) - (validX / 2);
  628. maxX = (cx / 2) + (validX / 2);
  629. } else {
  630. int validY = cx / ratio;
  631. minY = (cy / 2) - (validY / 2);
  632. }
  633. maxY = (cy / 2);
  634. if (x < minX || x > maxX || y < minY || y > maxY)
  635. break;
  636. pos = (x - minX) / ((maxX - minX) / 4);
  637. if (y > minY + ((maxY - minY) / 2))
  638. pos += 4;
  639. break;
  640. default: // MultiviewLayout::HORIZONTAL_TOP_8_SCENES
  641. if (float(cx) / float(cy) > ratio) {
  642. int validX = cy * ratio;
  643. minX = (cx / 2) - (validX / 2);
  644. maxX = (cx / 2) + (validX / 2);
  645. } else {
  646. int validY = cx / ratio;
  647. maxY = (cy / 2) + (validY / 2);
  648. }
  649. minY = (cy / 2);
  650. if (x < minX || x > maxX || y < minY || y > maxY)
  651. break;
  652. pos = (x - minX) / ((maxX - minX) / 4);
  653. if (y > minY + ((maxY - minY) / 2))
  654. pos += 4;
  655. }
  656. return pos;
  657. }
  658. void OBSProjector::mouseDoubleClickEvent(QMouseEvent *event)
  659. {
  660. OBSQTDisplay::mouseDoubleClickEvent(event);
  661. if (!mouseSwitching)
  662. return;
  663. if (!transitionOnDoubleClick)
  664. return;
  665. OBSBasic *main = (OBSBasic *)obs_frontend_get_main_window();
  666. if (!main->IsPreviewProgramMode())
  667. return;
  668. if (event->button() == Qt::LeftButton) {
  669. int pos = getSourceByPosition(event->x(), event->y(), ratio);
  670. if (pos < 0 || pos >= (int)numSrcs)
  671. return;
  672. OBSSource src = OBSGetStrongRef(multiviewScenes[pos]);
  673. if (!src)
  674. return;
  675. if (main->GetProgramSource() != src)
  676. main->TransitionToScene(src);
  677. }
  678. }
  679. void OBSProjector::mousePressEvent(QMouseEvent *event)
  680. {
  681. OBSQTDisplay::mousePressEvent(event);
  682. if (event->button() == Qt::RightButton) {
  683. OBSBasic *main =
  684. reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
  685. QMenu popup(this);
  686. QMenu *projectorMenu = new QMenu(QTStr("Fullscreen"));
  687. main->AddProjectorMenuMonitors(projectorMenu, this,
  688. SLOT(OpenFullScreenProjector()));
  689. popup.addMenu(projectorMenu);
  690. if (GetMonitor() > -1)
  691. popup.addAction(QTStr("Windowed"), this,
  692. SLOT(OpenWindowedProjector()));
  693. popup.addAction(QTStr("Close"), this, SLOT(EscapeTriggered()));
  694. popup.exec(QCursor::pos());
  695. }
  696. if (!mouseSwitching)
  697. return;
  698. if (event->button() == Qt::LeftButton) {
  699. int pos = getSourceByPosition(event->x(), event->y(), ratio);
  700. if (pos < 0 || pos >= (int)numSrcs)
  701. return;
  702. OBSSource src = OBSGetStrongRef(multiviewScenes[pos]);
  703. if (!src)
  704. return;
  705. OBSBasic *main = (OBSBasic *)obs_frontend_get_main_window();
  706. if (main->GetCurrentSceneSource() != src)
  707. main->SetCurrentScene(src, false);
  708. }
  709. }
  710. void OBSProjector::EscapeTriggered()
  711. {
  712. OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
  713. main->DeleteProjector(this);
  714. }
  715. void OBSProjector::UpdateMultiview()
  716. {
  717. multiviewScenes.clear();
  718. multiviewLabels.clear();
  719. struct obs_video_info ovi;
  720. obs_get_video_info(&ovi);
  721. uint32_t w = ovi.base_width;
  722. uint32_t h = ovi.base_height;
  723. fw = float(w);
  724. fh = float(h);
  725. ratio = fw / fh;
  726. struct obs_frontend_source_list scenes = {};
  727. obs_frontend_get_scenes(&scenes);
  728. multiviewLabels.emplace_back(
  729. CreateLabel(Str("StudioMode.Preview"), h / 2));
  730. multiviewLabels.emplace_back(
  731. CreateLabel(Str("StudioMode.Program"), h / 2));
  732. multiviewLayout = static_cast<MultiviewLayout>(config_get_int(
  733. GetGlobalConfig(), "BasicWindow", "MultiviewLayout"));
  734. drawLabel = config_get_bool(GetGlobalConfig(), "BasicWindow",
  735. "MultiviewDrawNames");
  736. drawSafeArea = config_get_bool(GetGlobalConfig(), "BasicWindow",
  737. "MultiviewDrawAreas");
  738. mouseSwitching = config_get_bool(GetGlobalConfig(), "BasicWindow",
  739. "MultiviewMouseSwitch");
  740. transitionOnDoubleClick = config_get_bool(
  741. GetGlobalConfig(), "BasicWindow", "TransitionOnDoubleClick");
  742. switch (multiviewLayout) {
  743. case MultiviewLayout::HORIZONTAL_TOP_24_SCENES:
  744. pvwprgCX = fw / 3;
  745. pvwprgCY = fh / 3;
  746. maxSrcs = 24;
  747. break;
  748. default:
  749. pvwprgCX = fw / 2;
  750. pvwprgCY = fh / 2;
  751. maxSrcs = 8;
  752. }
  753. ppiCX = pvwprgCX - thicknessx2;
  754. ppiCY = pvwprgCY - thicknessx2;
  755. ppiScaleX = (pvwprgCX - thicknessx2) / fw;
  756. ppiScaleY = (pvwprgCY - thicknessx2) / fh;
  757. scenesCX = pvwprgCX / 2;
  758. scenesCY = pvwprgCY / 2;
  759. siCX = scenesCX - thicknessx2;
  760. siCY = scenesCY - thicknessx2;
  761. siScaleX = (scenesCX - thicknessx2) / fw;
  762. siScaleY = (scenesCY - thicknessx2) / fh;
  763. numSrcs = 0;
  764. size_t i = 0;
  765. while (i < scenes.sources.num && numSrcs < maxSrcs) {
  766. obs_source_t *src = scenes.sources.array[i++];
  767. OBSData data = obs_source_get_private_settings(src);
  768. obs_data_release(data);
  769. obs_data_set_default_bool(data, "show_in_multiview", true);
  770. if (!obs_data_get_bool(data, "show_in_multiview"))
  771. continue;
  772. // We have a displayable source.
  773. numSrcs++;
  774. multiviewScenes.emplace_back(OBSGetWeakRef(src));
  775. obs_source_inc_showing(src);
  776. std::string name = std::to_string(numSrcs) + " - " +
  777. obs_source_get_name(src);
  778. multiviewLabels.emplace_back(CreateLabel(name.c_str(), h / 3));
  779. }
  780. obs_frontend_source_list_free(&scenes);
  781. }
  782. void OBSProjector::UpdateProjectorTitle(QString name)
  783. {
  784. bool window = (GetMonitor() == -1);
  785. QString title = nullptr;
  786. switch (type) {
  787. case ProjectorType::Scene:
  788. if (!window)
  789. title = QTStr("SceneProjector") + " - " + name;
  790. else
  791. title = QTStr("SceneWindow") + " - " + name;
  792. break;
  793. case ProjectorType::Source:
  794. if (!window)
  795. title = QTStr("SourceProjector") + " - " + name;
  796. else
  797. title = QTStr("SourceWindow") + " - " + name;
  798. break;
  799. case ProjectorType::Preview:
  800. if (!window)
  801. title = QTStr("PreviewProjector");
  802. else
  803. title = QTStr("PreviewWindow");
  804. break;
  805. case ProjectorType::StudioProgram:
  806. if (!window)
  807. title = QTStr("StudioProgramProjector");
  808. else
  809. title = QTStr("StudioProgramWindow");
  810. break;
  811. case ProjectorType::Multiview:
  812. if (!window)
  813. title = QTStr("MultiviewProjector");
  814. else
  815. title = QTStr("MultiviewWindowed");
  816. break;
  817. default:
  818. title = name;
  819. break;
  820. }
  821. setWindowTitle(title);
  822. }
  823. OBSSource OBSProjector::GetSource()
  824. {
  825. return source;
  826. }
  827. ProjectorType OBSProjector::GetProjectorType()
  828. {
  829. return type;
  830. }
  831. int OBSProjector::GetMonitor()
  832. {
  833. return savedMonitor;
  834. }
  835. void OBSProjector::UpdateMultiviewProjectors()
  836. {
  837. obs_enter_graphics();
  838. updatingMultiview = true;
  839. obs_leave_graphics();
  840. for (auto &projector : multiviewProjectors)
  841. projector->UpdateMultiview();
  842. obs_enter_graphics();
  843. updatingMultiview = false;
  844. obs_leave_graphics();
  845. }
  846. void OBSProjector::RenameProjector(QString oldName, QString newName)
  847. {
  848. if (oldName == newName)
  849. return;
  850. UpdateProjectorTitle(newName);
  851. }
  852. void OBSProjector::OpenFullScreenProjector()
  853. {
  854. if (!isFullScreen())
  855. prevGeometry = geometry();
  856. int monitor = sender()->property("monitor").toInt();
  857. SetMonitor(monitor);
  858. UpdateProjectorTitle(QT_UTF8(obs_source_get_name(source)));
  859. }
  860. void OBSProjector::OpenWindowedProjector()
  861. {
  862. showFullScreen();
  863. showNormal();
  864. setCursor(Qt::ArrowCursor);
  865. if (!prevGeometry.isNull())
  866. setGeometry(prevGeometry);
  867. else
  868. resize(480, 270);
  869. savedMonitor = -1;
  870. UpdateProjectorTitle(QT_UTF8(obs_source_get_name(source)));
  871. }
  872. void OBSProjector::closeEvent(QCloseEvent *event)
  873. {
  874. EscapeTriggered();
  875. event->accept();
  876. }