window-projector.cpp 32 KB

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