window-projector.cpp 23 KB

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