window-projector.cpp 28 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094
  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. SetAlwaysOnTop(this, config_get_bool(GetGlobalConfig(), "BasicWindow",
  25. "ProjectorAlwaysOnTop"));
  26. setWindowIcon(QIcon::fromTheme("obs", QIcon(":/res/images/obs.png")));
  27. if (monitor == -1)
  28. resize(480, 270);
  29. else
  30. SetMonitor(monitor);
  31. UpdateProjectorTitle(QT_UTF8(obs_source_get_name(source)));
  32. QAction *action = new QAction(this);
  33. action->setShortcut(Qt::Key_Escape);
  34. addAction(action);
  35. connect(action, SIGNAL(triggered()), this, SLOT(EscapeTriggered()));
  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. } else if (!this->isMaximized()) {
  694. popup.addAction(QTStr("ResizeProjectorWindowToContent"),
  695. this, SLOT(ResizeToContent()));
  696. }
  697. popup.addAction(QTStr("Close"), this, SLOT(EscapeTriggered()));
  698. popup.exec(QCursor::pos());
  699. }
  700. if (!mouseSwitching)
  701. return;
  702. if (event->button() == Qt::LeftButton) {
  703. int pos = getSourceByPosition(event->x(), event->y(), ratio);
  704. if (pos < 0 || pos >= (int)numSrcs)
  705. return;
  706. OBSSource src = OBSGetStrongRef(multiviewScenes[pos]);
  707. if (!src)
  708. return;
  709. OBSBasic *main = (OBSBasic *)obs_frontend_get_main_window();
  710. if (main->GetCurrentSceneSource() != src)
  711. main->SetCurrentScene(src, false);
  712. }
  713. }
  714. void OBSProjector::EscapeTriggered()
  715. {
  716. OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
  717. main->DeleteProjector(this);
  718. }
  719. void OBSProjector::UpdateMultiview()
  720. {
  721. multiviewScenes.clear();
  722. multiviewLabels.clear();
  723. struct obs_video_info ovi;
  724. obs_get_video_info(&ovi);
  725. uint32_t w = ovi.base_width;
  726. uint32_t h = ovi.base_height;
  727. fw = float(w);
  728. fh = float(h);
  729. ratio = fw / fh;
  730. struct obs_frontend_source_list scenes = {};
  731. obs_frontend_get_scenes(&scenes);
  732. multiviewLabels.emplace_back(
  733. CreateLabel(Str("StudioMode.Preview"), h / 2));
  734. multiviewLabels.emplace_back(
  735. CreateLabel(Str("StudioMode.Program"), h / 2));
  736. multiviewLayout = static_cast<MultiviewLayout>(config_get_int(
  737. GetGlobalConfig(), "BasicWindow", "MultiviewLayout"));
  738. drawLabel = config_get_bool(GetGlobalConfig(), "BasicWindow",
  739. "MultiviewDrawNames");
  740. drawSafeArea = config_get_bool(GetGlobalConfig(), "BasicWindow",
  741. "MultiviewDrawAreas");
  742. mouseSwitching = config_get_bool(GetGlobalConfig(), "BasicWindow",
  743. "MultiviewMouseSwitch");
  744. transitionOnDoubleClick = config_get_bool(
  745. GetGlobalConfig(), "BasicWindow", "TransitionOnDoubleClick");
  746. switch (multiviewLayout) {
  747. case MultiviewLayout::HORIZONTAL_TOP_24_SCENES:
  748. pvwprgCX = fw / 3;
  749. pvwprgCY = fh / 3;
  750. maxSrcs = 24;
  751. break;
  752. default:
  753. pvwprgCX = fw / 2;
  754. pvwprgCY = fh / 2;
  755. maxSrcs = 8;
  756. }
  757. ppiCX = pvwprgCX - thicknessx2;
  758. ppiCY = pvwprgCY - thicknessx2;
  759. ppiScaleX = (pvwprgCX - thicknessx2) / fw;
  760. ppiScaleY = (pvwprgCY - thicknessx2) / fh;
  761. scenesCX = pvwprgCX / 2;
  762. scenesCY = pvwprgCY / 2;
  763. siCX = scenesCX - thicknessx2;
  764. siCY = scenesCY - thicknessx2;
  765. siScaleX = (scenesCX - thicknessx2) / fw;
  766. siScaleY = (scenesCY - thicknessx2) / fh;
  767. numSrcs = 0;
  768. size_t i = 0;
  769. while (i < scenes.sources.num && numSrcs < maxSrcs) {
  770. obs_source_t *src = scenes.sources.array[i++];
  771. OBSData data = obs_source_get_private_settings(src);
  772. obs_data_release(data);
  773. obs_data_set_default_bool(data, "show_in_multiview", true);
  774. if (!obs_data_get_bool(data, "show_in_multiview"))
  775. continue;
  776. // We have a displayable source.
  777. numSrcs++;
  778. multiviewScenes.emplace_back(OBSGetWeakRef(src));
  779. obs_source_inc_showing(src);
  780. std::string name = std::to_string(numSrcs) + " - " +
  781. obs_source_get_name(src);
  782. multiviewLabels.emplace_back(CreateLabel(name.c_str(), h / 3));
  783. }
  784. obs_frontend_source_list_free(&scenes);
  785. }
  786. void OBSProjector::UpdateProjectorTitle(QString name)
  787. {
  788. bool window = (GetMonitor() == -1);
  789. QString title = nullptr;
  790. switch (type) {
  791. case ProjectorType::Scene:
  792. if (!window)
  793. title = QTStr("SceneProjector") + " - " + name;
  794. else
  795. title = QTStr("SceneWindow") + " - " + name;
  796. break;
  797. case ProjectorType::Source:
  798. if (!window)
  799. title = QTStr("SourceProjector") + " - " + name;
  800. else
  801. title = QTStr("SourceWindow") + " - " + name;
  802. break;
  803. case ProjectorType::Preview:
  804. if (!window)
  805. title = QTStr("PreviewProjector");
  806. else
  807. title = QTStr("PreviewWindow");
  808. break;
  809. case ProjectorType::StudioProgram:
  810. if (!window)
  811. title = QTStr("StudioProgramProjector");
  812. else
  813. title = QTStr("StudioProgramWindow");
  814. break;
  815. case ProjectorType::Multiview:
  816. if (!window)
  817. title = QTStr("MultiviewProjector");
  818. else
  819. title = QTStr("MultiviewWindowed");
  820. break;
  821. default:
  822. title = name;
  823. break;
  824. }
  825. setWindowTitle(title);
  826. }
  827. OBSSource OBSProjector::GetSource()
  828. {
  829. return source;
  830. }
  831. ProjectorType OBSProjector::GetProjectorType()
  832. {
  833. return type;
  834. }
  835. int OBSProjector::GetMonitor()
  836. {
  837. return savedMonitor;
  838. }
  839. void OBSProjector::UpdateMultiviewProjectors()
  840. {
  841. obs_enter_graphics();
  842. updatingMultiview = true;
  843. obs_leave_graphics();
  844. for (auto &projector : multiviewProjectors)
  845. projector->UpdateMultiview();
  846. obs_enter_graphics();
  847. updatingMultiview = false;
  848. obs_leave_graphics();
  849. }
  850. void OBSProjector::RenameProjector(QString oldName, QString newName)
  851. {
  852. if (oldName == newName)
  853. return;
  854. UpdateProjectorTitle(newName);
  855. }
  856. void OBSProjector::OpenFullScreenProjector()
  857. {
  858. if (!isFullScreen())
  859. prevGeometry = geometry();
  860. int monitor = sender()->property("monitor").toInt();
  861. SetMonitor(monitor);
  862. UpdateProjectorTitle(QT_UTF8(obs_source_get_name(source)));
  863. }
  864. void OBSProjector::OpenWindowedProjector()
  865. {
  866. showFullScreen();
  867. showNormal();
  868. setCursor(Qt::ArrowCursor);
  869. if (!prevGeometry.isNull())
  870. setGeometry(prevGeometry);
  871. else
  872. resize(480, 270);
  873. savedMonitor = -1;
  874. UpdateProjectorTitle(QT_UTF8(obs_source_get_name(source)));
  875. }
  876. void OBSProjector::ResizeToContent()
  877. {
  878. OBSSource source = GetSource();
  879. uint32_t targetCX;
  880. uint32_t targetCY;
  881. int x, y, newX, newY;
  882. float scale;
  883. if (source) {
  884. targetCX = std::max(obs_source_get_width(source), 1u);
  885. targetCY = std::max(obs_source_get_height(source), 1u);
  886. } else {
  887. struct obs_video_info ovi;
  888. obs_get_video_info(&ovi);
  889. targetCX = ovi.base_width;
  890. targetCY = ovi.base_height;
  891. }
  892. QSize size = this->size();
  893. GetScaleAndCenterPos(targetCX, targetCY, size.width(), size.height(), x,
  894. y, scale);
  895. newX = size.width() - (x * 2);
  896. newY = size.height() - (y * 2);
  897. resize(newX, newY);
  898. }
  899. void OBSProjector::closeEvent(QCloseEvent *event)
  900. {
  901. EscapeTriggered();
  902. event->accept();
  903. }