window-projector.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  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. #include "multiview.hpp"
  12. static QList<OBSProjector *> multiviewProjectors;
  13. static bool updatingMultiview = false, mouseSwitching, transitionOnDoubleClick;
  14. OBSProjector::OBSProjector(QWidget *widget, obs_source_t *source_, int monitor,
  15. ProjectorType type_)
  16. : OBSQTDisplay(widget, Qt::Window),
  17. weakSource(OBSGetWeakRef(source_))
  18. {
  19. OBSSource source = GetSource();
  20. if (source) {
  21. destroyedSignal.Connect(obs_source_get_signal_handler(source),
  22. "destroy", OBSSourceDestroyed, 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. if (source)
  47. UpdateProjectorTitle(QT_UTF8(obs_source_get_name(source)));
  48. else
  49. UpdateProjectorTitle(QString());
  50. QAction *action = new QAction(this);
  51. action->setShortcut(Qt::Key_Escape);
  52. addAction(action);
  53. connect(action, SIGNAL(triggered()), this, SLOT(EscapeTriggered()));
  54. setAttribute(Qt::WA_DeleteOnClose, true);
  55. //disable application quit when last window closed
  56. setAttribute(Qt::WA_QuitOnClose, false);
  57. installEventFilter(CreateShortcutFilter());
  58. auto addDrawCallback = [this]() {
  59. bool isMultiview = type == ProjectorType::Multiview;
  60. obs_display_add_draw_callback(
  61. GetDisplay(),
  62. isMultiview ? OBSRenderMultiview : OBSRender, this);
  63. obs_display_set_background_color(GetDisplay(), 0x000000);
  64. };
  65. connect(this, &OBSQTDisplay::DisplayCreated, addDrawCallback);
  66. connect(App(), &QGuiApplication::screenRemoved, this,
  67. &OBSProjector::ScreenRemoved);
  68. if (type == ProjectorType::Multiview) {
  69. multiview = new Multiview();
  70. UpdateMultiview();
  71. multiviewProjectors.push_back(this);
  72. }
  73. App()->IncrementSleepInhibition();
  74. if (source)
  75. obs_source_inc_showing(source);
  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. OBSSource source = GetSource();
  88. if (source)
  89. obs_source_dec_showing(source);
  90. if (isMultiview) {
  91. delete multiview;
  92. multiviewProjectors.removeAll(this);
  93. }
  94. App()->DecrementSleepInhibition();
  95. screen = nullptr;
  96. }
  97. void OBSProjector::SetMonitor(int monitor)
  98. {
  99. savedMonitor = monitor;
  100. screen = QGuiApplication::screens()[monitor];
  101. setGeometry(screen->geometry());
  102. showFullScreen();
  103. SetHideCursor();
  104. }
  105. void OBSProjector::SetHideCursor()
  106. {
  107. if (savedMonitor == -1)
  108. return;
  109. bool hideCursor = config_get_bool(GetGlobalConfig(), "BasicWindow",
  110. "HideProjectorCursor");
  111. if (hideCursor && type != ProjectorType::Multiview)
  112. setCursor(Qt::BlankCursor);
  113. else
  114. setCursor(Qt::ArrowCursor);
  115. }
  116. void OBSProjector::OBSRenderMultiview(void *data, uint32_t cx, uint32_t cy)
  117. {
  118. OBSProjector *window = (OBSProjector *)data;
  119. if (updatingMultiview || !window->ready)
  120. return;
  121. window->multiview->Render(cx, cy);
  122. }
  123. void OBSProjector::OBSRender(void *data, uint32_t cx, uint32_t cy)
  124. {
  125. OBSProjector *window = reinterpret_cast<OBSProjector *>(data);
  126. if (!window->ready)
  127. return;
  128. OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
  129. OBSSource source = window->GetSource();
  130. uint32_t targetCX;
  131. uint32_t targetCY;
  132. int x, y;
  133. int newCX, newCY;
  134. float scale;
  135. if (source) {
  136. targetCX = std::max(obs_source_get_width(source), 1u);
  137. targetCY = std::max(obs_source_get_height(source), 1u);
  138. } else {
  139. struct obs_video_info ovi;
  140. obs_get_video_info(&ovi);
  141. targetCX = ovi.base_width;
  142. targetCY = ovi.base_height;
  143. }
  144. GetScaleAndCenterPos(targetCX, targetCY, cx, cy, x, y, scale);
  145. newCX = int(scale * float(targetCX));
  146. newCY = int(scale * float(targetCY));
  147. startRegion(x, y, newCX, newCY, 0.0f, float(targetCX), 0.0f,
  148. float(targetCY));
  149. if (window->type == ProjectorType::Preview &&
  150. main->IsPreviewProgramMode()) {
  151. OBSSource curSource = main->GetCurrentSceneSource();
  152. if (source != curSource) {
  153. obs_source_dec_showing(source);
  154. obs_source_inc_showing(curSource);
  155. source = curSource;
  156. window->weakSource = OBSGetWeakRef(source);
  157. }
  158. } else if (window->type == ProjectorType::Preview &&
  159. !main->IsPreviewProgramMode()) {
  160. window->weakSource = nullptr;
  161. }
  162. if (source)
  163. obs_source_video_render(source);
  164. else
  165. obs_render_main_texture();
  166. endRegion();
  167. }
  168. void OBSProjector::OBSSourceDestroyed(void *data, calldata_t *)
  169. {
  170. OBSProjector *window = reinterpret_cast<OBSProjector *>(data);
  171. QMetaObject::invokeMethod(window, "EscapeTriggered");
  172. }
  173. void OBSProjector::mouseDoubleClickEvent(QMouseEvent *event)
  174. {
  175. OBSQTDisplay::mouseDoubleClickEvent(event);
  176. if (!mouseSwitching)
  177. return;
  178. if (!transitionOnDoubleClick)
  179. return;
  180. // Only MultiView projectors handle double click
  181. if (this->type != ProjectorType::Multiview)
  182. return;
  183. OBSBasic *main = (OBSBasic *)obs_frontend_get_main_window();
  184. if (!main->IsPreviewProgramMode())
  185. return;
  186. if (event->button() == Qt::LeftButton) {
  187. QPoint pos = event->pos();
  188. OBSSource src =
  189. multiview->GetSourceByPosition(pos.x(), pos.y());
  190. if (!src)
  191. return;
  192. if (main->GetProgramSource() != src)
  193. main->TransitionToScene(src);
  194. }
  195. }
  196. void OBSProjector::mousePressEvent(QMouseEvent *event)
  197. {
  198. OBSQTDisplay::mousePressEvent(event);
  199. if (event->button() == Qt::RightButton) {
  200. OBSBasic *main =
  201. reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
  202. QMenu popup(this);
  203. QMenu *projectorMenu = new QMenu(QTStr("Fullscreen"));
  204. main->AddProjectorMenuMonitors(projectorMenu, this,
  205. SLOT(OpenFullScreenProjector()));
  206. popup.addMenu(projectorMenu);
  207. if (GetMonitor() > -1) {
  208. popup.addAction(QTStr("Windowed"), this,
  209. SLOT(OpenWindowedProjector()));
  210. } else if (!this->isMaximized()) {
  211. popup.addAction(QTStr("ResizeProjectorWindowToContent"),
  212. this, SLOT(ResizeToContent()));
  213. }
  214. QAction *alwaysOnTopButton = new QAction(
  215. QTStr("Basic.MainMenu.View.AlwaysOnTop"), this);
  216. alwaysOnTopButton->setCheckable(true);
  217. alwaysOnTopButton->setChecked(isAlwaysOnTop);
  218. connect(alwaysOnTopButton, &QAction::toggled, this,
  219. &OBSProjector::AlwaysOnTopToggled);
  220. popup.addAction(alwaysOnTopButton);
  221. popup.addAction(QTStr("Close"), this, SLOT(EscapeTriggered()));
  222. popup.exec(QCursor::pos());
  223. } else if (event->button() == Qt::LeftButton) {
  224. // Only MultiView projectors handle left click
  225. if (this->type != ProjectorType::Multiview)
  226. return;
  227. if (!mouseSwitching)
  228. return;
  229. QPoint pos = event->pos();
  230. OBSSource src =
  231. multiview->GetSourceByPosition(pos.x(), pos.y());
  232. if (!src)
  233. return;
  234. OBSBasic *main = (OBSBasic *)obs_frontend_get_main_window();
  235. if (main->GetCurrentSceneSource() != src)
  236. main->SetCurrentScene(src, false);
  237. }
  238. }
  239. void OBSProjector::EscapeTriggered()
  240. {
  241. OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
  242. main->DeleteProjector(this);
  243. }
  244. void OBSProjector::UpdateMultiview()
  245. {
  246. MultiviewLayout multiviewLayout = static_cast<MultiviewLayout>(
  247. config_get_int(GetGlobalConfig(), "BasicWindow",
  248. "MultiviewLayout"));
  249. bool drawLabel = config_get_bool(GetGlobalConfig(), "BasicWindow",
  250. "MultiviewDrawNames");
  251. bool drawSafeArea = config_get_bool(GetGlobalConfig(), "BasicWindow",
  252. "MultiviewDrawAreas");
  253. mouseSwitching = config_get_bool(GetGlobalConfig(), "BasicWindow",
  254. "MultiviewMouseSwitch");
  255. transitionOnDoubleClick = config_get_bool(
  256. GetGlobalConfig(), "BasicWindow", "TransitionOnDoubleClick");
  257. multiview->Update(multiviewLayout, drawLabel, drawSafeArea);
  258. }
  259. void OBSProjector::UpdateProjectorTitle(QString name)
  260. {
  261. bool window = (GetMonitor() == -1);
  262. QString title = nullptr;
  263. switch (type) {
  264. case ProjectorType::Scene:
  265. if (!window)
  266. title = QTStr("SceneProjector") + " - " + name;
  267. else
  268. title = QTStr("SceneWindow") + " - " + name;
  269. break;
  270. case ProjectorType::Source:
  271. if (!window)
  272. title = QTStr("SourceProjector") + " - " + name;
  273. else
  274. title = QTStr("SourceWindow") + " - " + name;
  275. break;
  276. case ProjectorType::Preview:
  277. if (!window)
  278. title = QTStr("PreviewProjector");
  279. else
  280. title = QTStr("PreviewWindow");
  281. break;
  282. case ProjectorType::StudioProgram:
  283. if (!window)
  284. title = QTStr("StudioProgramProjector");
  285. else
  286. title = QTStr("StudioProgramWindow");
  287. break;
  288. case ProjectorType::Multiview:
  289. if (!window)
  290. title = QTStr("MultiviewProjector");
  291. else
  292. title = QTStr("MultiviewWindowed");
  293. break;
  294. default:
  295. title = name;
  296. break;
  297. }
  298. setWindowTitle(title);
  299. }
  300. OBSSource OBSProjector::GetSource()
  301. {
  302. return OBSGetStrongRef(weakSource);
  303. }
  304. ProjectorType OBSProjector::GetProjectorType()
  305. {
  306. return type;
  307. }
  308. int OBSProjector::GetMonitor()
  309. {
  310. return savedMonitor;
  311. }
  312. void OBSProjector::UpdateMultiviewProjectors()
  313. {
  314. obs_enter_graphics();
  315. updatingMultiview = true;
  316. obs_leave_graphics();
  317. for (auto &projector : multiviewProjectors)
  318. projector->UpdateMultiview();
  319. obs_enter_graphics();
  320. updatingMultiview = false;
  321. obs_leave_graphics();
  322. }
  323. void OBSProjector::RenameProjector(QString oldName, QString newName)
  324. {
  325. if (oldName == newName)
  326. return;
  327. UpdateProjectorTitle(newName);
  328. }
  329. void OBSProjector::OpenFullScreenProjector()
  330. {
  331. if (!isFullScreen())
  332. prevGeometry = geometry();
  333. int monitor = sender()->property("monitor").toInt();
  334. SetMonitor(monitor);
  335. OBSSource source = GetSource();
  336. UpdateProjectorTitle(QT_UTF8(obs_source_get_name(source)));
  337. }
  338. void OBSProjector::OpenWindowedProjector()
  339. {
  340. showFullScreen();
  341. showNormal();
  342. setCursor(Qt::ArrowCursor);
  343. if (!prevGeometry.isNull())
  344. setGeometry(prevGeometry);
  345. else
  346. resize(480, 270);
  347. savedMonitor = -1;
  348. OBSSource source = GetSource();
  349. UpdateProjectorTitle(QT_UTF8(obs_source_get_name(source)));
  350. screen = nullptr;
  351. }
  352. void OBSProjector::ResizeToContent()
  353. {
  354. OBSSource source = GetSource();
  355. uint32_t targetCX;
  356. uint32_t targetCY;
  357. int x, y, newX, newY;
  358. float scale;
  359. if (source) {
  360. targetCX = std::max(obs_source_get_width(source), 1u);
  361. targetCY = std::max(obs_source_get_height(source), 1u);
  362. } else {
  363. struct obs_video_info ovi;
  364. obs_get_video_info(&ovi);
  365. targetCX = ovi.base_width;
  366. targetCY = ovi.base_height;
  367. }
  368. QSize size = this->size();
  369. GetScaleAndCenterPos(targetCX, targetCY, size.width(), size.height(), x,
  370. y, scale);
  371. newX = size.width() - (x * 2);
  372. newY = size.height() - (y * 2);
  373. resize(newX, newY);
  374. }
  375. void OBSProjector::AlwaysOnTopToggled(bool isAlwaysOnTop)
  376. {
  377. SetIsAlwaysOnTop(isAlwaysOnTop, true);
  378. }
  379. void OBSProjector::closeEvent(QCloseEvent *event)
  380. {
  381. EscapeTriggered();
  382. event->accept();
  383. }
  384. bool OBSProjector::IsAlwaysOnTop() const
  385. {
  386. return isAlwaysOnTop;
  387. }
  388. bool OBSProjector::IsAlwaysOnTopOverridden() const
  389. {
  390. return isAlwaysOnTopOverridden;
  391. }
  392. void OBSProjector::SetIsAlwaysOnTop(bool isAlwaysOnTop, bool isOverridden)
  393. {
  394. this->isAlwaysOnTop = isAlwaysOnTop;
  395. this->isAlwaysOnTopOverridden = isOverridden;
  396. SetAlwaysOnTop(this, isAlwaysOnTop);
  397. }
  398. void OBSProjector::ScreenRemoved(QScreen *screen_)
  399. {
  400. if (GetMonitor() < 0 || !screen)
  401. return;
  402. if (screen == screen_)
  403. EscapeTriggered();
  404. }