window-projector.cpp 29 KB

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