window-projector.cpp 28 KB

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