KonvergoWindow.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. #include "KonvergoWindow.h"
  2. #include <QTimer>
  3. #include <QJsonObject>
  4. #include <QScreen>
  5. #include <QQuickItem>
  6. #include <QGuiApplication>
  7. #include "input/InputKeyboard.h"
  8. #include "settings/SettingsComponent.h"
  9. #include "settings/SettingsSection.h"
  10. #include "system/SystemComponent.h"
  11. #include "player/PlayerComponent.h"
  12. #include "player/PlayerQuickItem.h"
  13. #include "display/DisplayComponent.h"
  14. #include "QsLog.h"
  15. #include "power/PowerComponent.h"
  16. #include "utils/Utils.h"
  17. #include "Globals.h"
  18. #include "EventFilter.h"
  19. ///////////////////////////////////////////////////////////////////////////////////////////////////
  20. KonvergoWindow::KonvergoWindow(QWindow* parent) : QQuickWindow(parent), m_debugLayer(false), m_lastScale(1.0)
  21. {
  22. // NSWindowCollectionBehaviorFullScreenPrimary is only set on OSX if Qt::WindowFullscreenButtonHint is set on the window.
  23. setFlags(flags() | Qt::WindowFullscreenButtonHint);
  24. m_infoTimer = new QTimer(this);
  25. m_infoTimer->setInterval(1000);
  26. installEventFilter(new EventFilter(this));
  27. connect(m_infoTimer, &QTimer::timeout, this, &KonvergoWindow::updateDebugInfo);
  28. InputComponent::Get().registerHostCommand("close", this, "close");
  29. InputComponent::Get().registerHostCommand("toggleDebug", this, "toggleDebug");
  30. InputComponent::Get().registerHostCommand("reload", this, "reloadWeb");
  31. InputComponent::Get().registerHostCommand("fullscreen", this, "toggleFullscreen");
  32. #ifdef TARGET_RPI
  33. // On RPI, we use dispmanx layering - the video is on a layer below Konvergo,
  34. // and during playback the Konvergo window is partially transparent. The OSD
  35. // will be visible on top of the video as part of the Konvergo window.
  36. setColor(QColor("transparent"));
  37. #else
  38. setColor(QColor("#111111"));
  39. #endif
  40. QRect loadedGeo = loadGeometry();
  41. notifyScale(loadedGeo.size());
  42. connect(SettingsComponent::Get().getSection(SETTINGS_SECTION_MAIN), &SettingsSection::valuesUpdated,
  43. this, &KonvergoWindow::updateMainSectionSettings);
  44. connect(this, &KonvergoWindow::visibilityChanged,
  45. this, &KonvergoWindow::onVisibilityChanged);
  46. connect(this, &KonvergoWindow::enableVideoWindowSignal,
  47. this, &KonvergoWindow::enableVideoWindow, Qt::QueuedConnection);
  48. connect(&PlayerComponent::Get(), &PlayerComponent::windowVisible,
  49. this, &KonvergoWindow::playerWindowVisible);
  50. // this is using old syntax because ... reasons. QQuickCloseEvent is not public class
  51. connect(this, SIGNAL(closing(QQuickCloseEvent*)), this, SLOT(closingWindow()));
  52. connect(qApp, &QCoreApplication::aboutToQuit, this, &KonvergoWindow::closingWindow);
  53. #ifdef KONVERGO_OPENELEC
  54. setVisibility(QWindow::FullScreen);
  55. #else
  56. updateFullscreenState(false);
  57. #endif
  58. // Check the always on top setting and activate it if necessary.
  59. updateAlwaysOnTopState();
  60. emit enableVideoWindowSignal();
  61. }
  62. /////////////////////////////////////////////////////////////////////////////////////////
  63. void KonvergoWindow::closingWindow()
  64. {
  65. if (!SettingsComponent::Get().value(SETTINGS_SECTION_MAIN, "fullscreen").toBool())
  66. saveGeometry();
  67. qApp->quit();
  68. }
  69. ///////////////////////////////////////////////////////////////////////////////////////////////////
  70. KonvergoWindow::~KonvergoWindow()
  71. {
  72. DisplayComponent::Get().setApplicationWindow(nullptr);
  73. }
  74. ///////////////////////////////////////////////////////////////////////////////////////////////////
  75. bool KonvergoWindow::fitsInScreens(const QRect& rc)
  76. {
  77. for(QScreen *screen : QGuiApplication::screens())
  78. {
  79. if (screen->virtualGeometry().contains(rc))
  80. return true;
  81. }
  82. return false;
  83. }
  84. ///////////////////////////////////////////////////////////////////////////////////////////////////
  85. void KonvergoWindow::saveGeometry()
  86. {
  87. QRect rc = geometry();
  88. // lets make sure we are not saving something craycray
  89. if (rc.size().width() < windowMinSize().width() || rc.size().height() < windowMinSize().height())
  90. return;
  91. if (!fitsInScreens(rc))
  92. return;
  93. QVariantMap map = {{"x", rc.x()}, {"y", rc.y()},
  94. {"width", rc.width()}, {"height", rc.height()}};
  95. SettingsComponent::Get().setValue(SETTINGS_SECTION_STATE, "geometry", map);
  96. SettingsComponent::Get().setValue(SETTINGS_SECTION_STATE, "lastUsedScreen", screen()->name());
  97. }
  98. ///////////////////////////////////////////////////////////////////////////////////////////////////
  99. QRect KonvergoWindow::loadGeometry()
  100. {
  101. QRect rc = loadGeometryRect();
  102. QScreen* myScreen = loadLastScreen();
  103. if (!myScreen)
  104. myScreen = screen();
  105. QRect nsize = rc;
  106. if (SettingsComponent::Get().value(SETTINGS_SECTION_MAIN, "fullscreen").toBool())
  107. {
  108. QLOG_DEBUG() << "Load FullScreen geo...";
  109. // On OSX we need to set the geometry to the size we want when we
  110. // return from fullscreen otherwise when we exit fullscreen it
  111. // will stay small or big. On Windows we need to set it to max
  112. // resolution for the screen (i.e. fullscreen) otherwise it will
  113. // just scale the webcontent to the minimum size we have defined
  114. //
  115. #ifndef Q_OS_MAC
  116. nsize = myScreen->geometry();
  117. #endif
  118. setGeometry(nsize);
  119. setScreen(myScreen);
  120. }
  121. else
  122. {
  123. setGeometry(nsize);
  124. saveGeometry();
  125. }
  126. return nsize;
  127. }
  128. ///////////////////////////////////////////////////////////////////////////////////////////////////
  129. QRect KonvergoWindow::loadGeometryRect()
  130. {
  131. // if we dont have anything, default to 720p in the middle of the screen
  132. QRect defaultRect = QRect((screen()->geometry().width() - webUISize().width()) / 2,
  133. (screen()->geometry().height() - webUISize().height()) / 2,
  134. webUISize().width(), webUISize().height());
  135. QVariantMap map = SettingsComponent::Get().value(SETTINGS_SECTION_STATE, "geometry").toMap();
  136. if (map.isEmpty())
  137. return defaultRect;
  138. QRect rc(map["x"].toInt(), map["y"].toInt(), map["width"].toInt(), map["height"].toInt());
  139. QLOG_DEBUG() << "Restoring geo:" << rc;
  140. if (!rc.isValid() || rc.isEmpty())
  141. {
  142. QLOG_DEBUG() << "Geo bad, going for defaults";
  143. return defaultRect;
  144. }
  145. QSize minsz = windowMinSize();
  146. // Clamp to min size if we have really small values in there
  147. if (rc.size().width() < minsz.width())
  148. rc.setWidth(minsz.width());
  149. if (rc.size().height() < minsz.height())
  150. rc.setHeight(minsz.height());
  151. // also make sure we are not putting windows outside the screen somewhere
  152. if (!fitsInScreens(rc))
  153. {
  154. QLOG_DEBUG() << "Could not fit stored geo into current screens";
  155. return defaultRect;
  156. }
  157. return rc;
  158. }
  159. ///////////////////////////////////////////////////////////////////////////////////////////////////
  160. void KonvergoWindow::enableVideoWindow()
  161. {
  162. PlayerComponent::Get().setWindow(this);
  163. DisplayComponent::Get().setApplicationWindow(this);
  164. }
  165. ///////////////////////////////////////////////////////////////////////////////////////////////////
  166. void KonvergoWindow::setFullScreen(bool enable)
  167. {
  168. QLOG_DEBUG() << "setting fullscreen = " << enable;
  169. SettingsComponent::Get().setValue(SETTINGS_SECTION_MAIN, "fullscreen", enable);
  170. }
  171. ///////////////////////////////////////////////////////////////////////////////////////////////////
  172. void KonvergoWindow::setAlwaysOnTop(bool enable)
  173. {
  174. QLOG_DEBUG() << "setting always on top = " << enable;
  175. // Update the settings value.
  176. SettingsComponent::Get().setValue(SETTINGS_SECTION_MAIN, "alwaysOnTop", enable);
  177. }
  178. ///////////////////////////////////////////////////////////////////////////////////////////////////
  179. void KonvergoWindow::playerWindowVisible(bool visible)
  180. {
  181. // adjust webengineview transparecy depending on player visibility
  182. QQuickItem *web = findChild<QQuickItem *>("web");
  183. if (web)
  184. web->setProperty("backgroundColor", visible ? "transparent" : "#111111");
  185. }
  186. ///////////////////////////////////////////////////////////////////////////////////////////////////
  187. void KonvergoWindow::updateMainSectionSettings(const QVariantMap& values)
  188. {
  189. // update mouse visibility if needed
  190. if (values.find("disablemouse") != values.end())
  191. {
  192. SystemComponent::Get().setCursorVisibility(!SettingsComponent::Get().value(SETTINGS_SECTION_MAIN, "disablemouse").toBool());
  193. }
  194. if (values.find("alwaysOnTop") != values.end())
  195. {
  196. updateAlwaysOnTopState();
  197. }
  198. if (values.find("fullscreen") == values.end())
  199. return;
  200. updateFullscreenState();
  201. }
  202. ///////////////////////////////////////////////////////////////////////////////////////////////////
  203. void KonvergoWindow::updateFullscreenState(bool saveGeo)
  204. {
  205. if (SettingsComponent::Get().value(SETTINGS_SECTION_MAIN, "fullscreen").toBool() || SystemComponent::Get().isOpenELEC())
  206. {
  207. // if we were go from windowed to fullscreen
  208. // we want to store our current windowed position
  209. if (!isFullScreen() && saveGeo)
  210. saveGeometry();
  211. setVisibility(QWindow::FullScreen);
  212. }
  213. else
  214. {
  215. setVisibility(QWindow::Windowed);
  216. loadGeometry();
  217. }
  218. }
  219. ///////////////////////////////////////////////////////////////////////////////////////////////////
  220. void KonvergoWindow::updateAlwaysOnTopState()
  221. {
  222. QLOG_DEBUG() << "Changing always-on-top state";
  223. Qt::WindowFlags forceOnTopFlags = Qt::WindowStaysOnTopHint;
  224. #ifdef Q_OS_LINUX
  225. forceOnTopFlags = forceOnTopFlags | Qt::X11BypassWindowManagerHint;
  226. #endif
  227. if(SettingsComponent::Get().value(SETTINGS_SECTION_MAIN, "alwaysOnTop").toBool())
  228. {
  229. setFlags(flags() | forceOnTopFlags);
  230. }
  231. else
  232. {
  233. setFlags(flags() & ~forceOnTopFlags);
  234. }
  235. show();
  236. }
  237. ///////////////////////////////////////////////////////////////////////////////////////////////////
  238. void KonvergoWindow::onVisibilityChanged(QWindow::Visibility visibility)
  239. {
  240. QLOG_DEBUG() << (visibility == QWindow::FullScreen ? "FullScreen" : "Windowed") << "visbility set to " << visibility;
  241. if (visibility == QWindow::Windowed)
  242. loadGeometry();
  243. if (visibility == QWindow::FullScreen)
  244. PowerComponent::Get().setFullscreenState(true);
  245. else if (visibility == QWindow::Windowed)
  246. PowerComponent::Get().setFullscreenState(false);
  247. notifyScale(size());
  248. }
  249. /////////////////////////////////////////////////////////////////////////////////////////
  250. void KonvergoWindow::focusOutEvent(QFocusEvent * ev)
  251. {
  252. #ifdef Q_OS_WIN32
  253. // Do this to workaround DWM compositor bugs with fullscreened OpenGL applications.
  254. // The compositor will not properly redraw anything when focusing other windows.
  255. if (visibility() == QWindow::FullScreen && SettingsComponent::Get().value(SETTINGS_SECTION_MAIN, "minimizeOnDefocus").toBool())
  256. {
  257. QLOG_DEBUG() << "minimizing window";
  258. showMinimized();
  259. }
  260. #endif
  261. }
  262. /////////////////////////////////////////////////////////////////////////////////////////
  263. void KonvergoWindow::RegisterClass()
  264. {
  265. qmlRegisterType<KonvergoWindow>("Konvergo", 1, 0, "KonvergoWindow");
  266. }
  267. /////////////////////////////////////////////////////////////////////////////////////////
  268. void KonvergoWindow::onScreenCountChanged(int newCount)
  269. {
  270. updateFullscreenState(false);
  271. }
  272. /////////////////////////////////////////////////////////////////////////////////////////
  273. void KonvergoWindow::updateDebugInfo()
  274. {
  275. if (m_systemDebugInfo.size() == 0)
  276. m_systemDebugInfo = SystemComponent::Get().debugInformation();
  277. m_debugInfo = m_systemDebugInfo;
  278. m_debugInfo += DisplayComponent::Get().debugInformation();
  279. PlayerQuickItem* video = findChild<PlayerQuickItem*>("video");
  280. if (video)
  281. m_debugInfo += video->debugInfo();
  282. m_videoInfo = PlayerComponent::Get().videoInformation();
  283. emit debugInfoChanged();
  284. }
  285. /////////////////////////////////////////////////////////////////////////////////////////
  286. void KonvergoWindow::toggleDebug()
  287. {
  288. if (property("showDebugLayer").toBool())
  289. {
  290. m_infoTimer->stop();
  291. setProperty("showDebugLayer", false);
  292. }
  293. else
  294. {
  295. m_infoTimer->start();
  296. updateDebugInfo();
  297. setProperty("showDebugLayer", true);
  298. }
  299. }
  300. /////////////////////////////////////////////////////////////////////////////////////////
  301. void KonvergoWindow::notifyScale(const QSize& size)
  302. {
  303. qreal scale = CalculateScale(size);
  304. if (scale != m_lastScale)
  305. {
  306. QLOG_DEBUG() << "windowScale updated to:" << scale << "webscale:" << CalculateWebScale(size, devicePixelRatio());
  307. m_lastScale = scale;
  308. emit SystemComponent::Get().scaleChanged(CalculateWebScale(size, devicePixelRatio()));
  309. }
  310. emit webScaleChanged();
  311. }
  312. /////////////////////////////////////////////////////////////////////////////////////////
  313. void KonvergoWindow::resizeEvent(QResizeEvent* event)
  314. {
  315. QLOG_DEBUG() << "resize event:" << event->size();
  316. // This next block should never really be needed in a prefect world...
  317. // Unfortunatly this is an imperfect world and on windows sometimes what
  318. // would happen on startup is that we got a resize event that would make
  319. // the window much smaller than fullscreen.
  320. //
  321. if (isFullScreen())
  322. {
  323. QSize fsSize = screen()->size();
  324. if (event->size().width() < fsSize.width() || event->size().height() < fsSize.height())
  325. {
  326. QLOG_DEBUG() << "Ignoring resize event when in fullscreen...";
  327. return;
  328. }
  329. }
  330. notifyScale(event->size());
  331. QQuickWindow::resizeEvent(event);
  332. }
  333. /////////////////////////////////////////////////////////////////////////////////////////
  334. #define ROUND(x) (qRound(x * 1000) / 1000.0)
  335. /////////////////////////////////////////////////////////////////////////////////////////
  336. qreal KonvergoWindow::CalculateScale(const QSize& size)
  337. {
  338. qreal horizontalScale = (qreal)size.width() / (qreal)WEBUI_SIZE.width();
  339. qreal verticalScale = (qreal)size.height() / (qreal)WEBUI_SIZE.height();
  340. return ROUND(qMin(horizontalScale, verticalScale));
  341. }
  342. /////////////////////////////////////////////////////////////////////////////////////////
  343. qreal KonvergoWindow::CalculateWebScale(const QSize& size, qreal devicePixelRatio)
  344. {
  345. qreal horizontalScale = (qreal)size.width() / (qreal)WEBUI_SIZE.width();
  346. qreal verticalScale = (qreal)size.height() / (qreal)WEBUI_SIZE.height();
  347. qreal minScale = qMin(horizontalScale, qMin(verticalScale, (qreal)(WEBUI_MAX_HEIGHT / devicePixelRatio) / (qreal)WEBUI_SIZE.height()));
  348. qreal minWinScale = 240.0 / (qreal)WEBUI_SIZE.height();
  349. return ROUND(qMax(minWinScale, minScale));
  350. }
  351. /////////////////////////////////////////////////////////////////////////////////////////
  352. QScreen* KonvergoWindow::loadLastScreen()
  353. {
  354. QString screenName = SettingsComponent::Get().value(SETTINGS_SECTION_STATE, "lastUsedScreen").toString();
  355. if (screenName.isEmpty())
  356. return nullptr;
  357. for (QScreen* scr : QGuiApplication::screens())
  358. {
  359. if (scr->name() == screenName)
  360. return scr;
  361. }
  362. QLOG_DEBUG() << "Tried to find screen:" << screenName << "but it was not present";
  363. return nullptr;
  364. }