WallpaperApplication.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840
  1. #include "WallpaperApplication.h"
  2. #include "Steam/FileSystem/FileSystem.h"
  3. #include "WallpaperEngine/Application/ApplicationState.h"
  4. #include "WallpaperEngine/Assets/AssetLoadException.h"
  5. #include "WallpaperEngine/Audio/Drivers/Detectors/PulseAudioPlayingDetector.h"
  6. #include "WallpaperEngine/FileSystem/Container.h"
  7. #include "WallpaperEngine/Logging/Log.h"
  8. #include "WallpaperEngine/Render/Drivers/VideoFactories.h"
  9. #include "WallpaperEngine/Render/RenderContext.h"
  10. #include "WallpaperEngine/Data/Dumpers/StringPrinter.h"
  11. #include "WallpaperEngine/Data/Parsers/ProjectParser.h"
  12. #include "WallpaperEngine/Data/Model/Property.h"
  13. #include "WallpaperEngine/Data/Model/Wallpaper.h"
  14. #include "WallpaperEngine/Debugging/CallStack.h"
  15. #if DEMOMODE
  16. #include "recording.h"
  17. #endif /* DEMOMODE */
  18. #include <algorithm>
  19. #include <numeric>
  20. #include <unistd.h>
  21. #define STB_IMAGE_WRITE_IMPLEMENTATION
  22. #include <stb_image_write.h>
  23. #define FULLSCREEN_CHECK_WAIT_TIME 250
  24. float g_Time;
  25. float g_TimeLast;
  26. float g_Daytime;
  27. using namespace WallpaperEngine::Assets;
  28. using namespace WallpaperEngine::Application;
  29. using namespace WallpaperEngine::Data::Model;
  30. using namespace WallpaperEngine::FileSystem;
  31. void CustomGLDebugCallback(GLenum source,
  32. GLenum type,
  33. GLuint id,
  34. GLenum severity,
  35. GLsizei length,
  36. const GLchar *message,
  37. const void *userParam) {
  38. if (severity != GL_DEBUG_SEVERITY_HIGH) {
  39. return;
  40. }
  41. sLog.error("OpenGL error: ", message, ", type: ", type, ", id: ", id);
  42. std::vector<WallpaperEngine::Debugging::CallStack::CallInfo> callInfo;
  43. WallpaperEngine::Debugging::CallStack::GetCalls(callInfo);
  44. for(std::vector<WallpaperEngine::Debugging::CallStack::CallInfo>::size_type i = 0; i < callInfo.size(); ++i) {
  45. fprintf(stderr,
  46. "[%3lu] %15lu: %s in %s\n",
  47. callInfo.size()-i,
  48. callInfo[i].offset,
  49. callInfo[i].function.c_str(),
  50. callInfo[i].module.c_str()
  51. );
  52. }
  53. }
  54. WallpaperApplication::WallpaperApplication (ApplicationContext& context) :
  55. m_context (context) {
  56. this->loadBackgrounds ();
  57. this->setupProperties ();
  58. this->setupBrowser();
  59. this->initializePlaylists ();
  60. }
  61. AssetLocatorUniquePtr WallpaperApplication::setupAssetLocator (const std::string& bg) const {
  62. auto container = std::make_unique <Container> ();
  63. const std::filesystem::path path = bg;
  64. container->mount (path, "/");
  65. try {
  66. container->mount (path / "scene.pkg", "/");
  67. } catch (std::runtime_error&) { }
  68. try {
  69. container->mount (path / "gifscene.pkg", "/");
  70. } catch (std::runtime_error&) { }
  71. try {
  72. container->mount (this->m_context.settings.general.assets, "/");
  73. } catch (std::runtime_error&) {
  74. sLog.exception ("Cannot find a valid assets folder, resolved to ", this->m_context.settings.general.assets);
  75. }
  76. // mount the current directory as root
  77. try {
  78. container->mount (std::filesystem::current_path (), "/");
  79. } catch (std::runtime_error&) { }
  80. auto& vfs = container->getVFS ();
  81. //
  82. // Had to get a little creative with the effects to achieve the same bloom effect without any custom code
  83. // these virtual files are loaded by an image in the scene that takes current _rt_FullFrameBuffer and
  84. // applies the bloom effect to render it out to the screen
  85. //
  86. // add the effect file for screen bloom
  87. // add some model for the image element even if it's going to waste rendering cycles
  88. vfs.add (
  89. "effects/wpenginelinux/bloomeffect.json",
  90. {
  91. {"name", "camerabloom_wpengine_linux"},
  92. {"group", "wpengine_linux_camera"},
  93. {"dependencies", JSON::array ()},
  94. {"passes",
  95. JSON::array (
  96. {
  97. {
  98. {"material", "materials/util/downsample_quarter_bloom.json"},
  99. {"target", "_rt_4FrameBuffer"},
  100. {
  101. "bind",
  102. JSON::array (
  103. {
  104. {
  105. {"name", "_rt_FullFrameBuffer"},
  106. {"index", 0}
  107. }
  108. }
  109. )
  110. }
  111. },
  112. {
  113. {"material", "materials/util/downsample_eighth_blur_v.json"},
  114. {"target", "_rt_8FrameBuffer"},
  115. {
  116. "bind",
  117. JSON::array (
  118. {
  119. {
  120. {"name", "_rt_4FrameBuffer"},
  121. {"index", 0}
  122. }
  123. }
  124. )
  125. }
  126. },
  127. {
  128. {"material", "materials/util/blur_h_bloom.json"},
  129. {"target", "_rt_Bloom"},
  130. {
  131. "bind",
  132. JSON::array (
  133. {
  134. {
  135. {"name", "_rt_8FrameBuffer"},
  136. {"index", 0}
  137. }
  138. }
  139. )
  140. }
  141. },
  142. {
  143. {"material", "materials/util/combine.json"},
  144. {"target", "_rt_FullFrameBuffer"},
  145. {
  146. "bind",
  147. JSON::array (
  148. {
  149. {
  150. {"name", "_rt_imageLayerComposite_-1_a"},
  151. {"index", 0}
  152. },
  153. {
  154. {"name", "_rt_Bloom"},
  155. {"index", 1}
  156. }
  157. }
  158. )
  159. }
  160. }
  161. }
  162. ),
  163. }
  164. }
  165. );
  166. vfs.add (
  167. "models/wpenginelinux.json",
  168. {
  169. {"material","materials/wpenginelinux.json"}
  170. }
  171. );
  172. vfs.add(
  173. "materials/wpenginelinux.json",
  174. {
  175. {"passes", JSON::array (
  176. {
  177. {
  178. {"blending", "normal"},
  179. {"cullmode", "nocull"},
  180. {"depthtest", "disabled"},
  181. {"depthwrite", "disabled"},
  182. {"shader", "genericimage2"},
  183. {"textures", JSON::array ({"_rt_FullFrameBuffer"})}
  184. }
  185. }
  186. )}}
  187. );
  188. vfs.add(
  189. "shaders/commands/copy.frag",
  190. "uniform sampler2D g_Texture0;\n"
  191. "in vec2 v_TexCoord;\n"
  192. "void main () {\n"
  193. "out_FragColor = texture (g_Texture0, v_TexCoord);\n"
  194. "}"
  195. );
  196. vfs.add(
  197. "shaders/commands/copy.vert",
  198. "in vec3 a_Position;\n"
  199. "in vec2 a_TexCoord;\n"
  200. "out vec2 v_TexCoord;\n"
  201. "void main () {\n"
  202. "gl_Position = vec4 (a_Position, 1.0);\n"
  203. "v_TexCoord = a_TexCoord;\n"
  204. "}"
  205. );
  206. return std::make_unique <AssetLocator> (std::move (container));
  207. }
  208. void WallpaperApplication::loadBackgrounds () {
  209. if (this->m_context.settings.render.mode == ApplicationContext::NORMAL_WINDOW ||
  210. this->m_context.settings.render.mode == ApplicationContext::EXPLICIT_WINDOW) {
  211. auto path = this->m_context.settings.general.defaultBackground;
  212. if (this->m_context.settings.general.defaultPlaylist.has_value () &&
  213. !this->m_context.settings.general.defaultPlaylist->items.empty ()) {
  214. path = this->m_context.settings.general.defaultPlaylist->items.front ();
  215. }
  216. this->m_backgrounds ["default"] = this->loadBackground (path);
  217. return;
  218. }
  219. for (const auto& [screen, path] : this->m_context.settings.general.screenBackgrounds) {
  220. // screens with no screen should use the default
  221. if (path.empty ()) {
  222. this->m_backgrounds [screen] = this->loadBackground (this->m_context.settings.general.defaultBackground);
  223. } else {
  224. this->m_backgrounds [screen] = this->loadBackground (path);
  225. }
  226. }
  227. }
  228. ProjectUniquePtr WallpaperApplication::loadBackground (const std::string& bg) {
  229. auto container = this->setupAssetLocator (bg);
  230. auto json = WallpaperEngine::Data::JSON::JSON::parse (container->readString ("project.json"));
  231. return WallpaperEngine::Data::Parsers::ProjectParser::parse (json, std::move(container));
  232. }
  233. std::vector<std::size_t> WallpaperApplication::buildPlaylistOrder (
  234. const ApplicationContext::PlaylistDefinition& definition) {
  235. std::vector<std::size_t> order (definition.items.size ());
  236. std::iota (order.begin (), order.end (), 0);
  237. if (definition.settings.order == "random") {
  238. std::shuffle (order.begin (), order.end (), this->m_playlistRng);
  239. }
  240. return order;
  241. }
  242. void WallpaperApplication::initializePlaylists () {
  243. const bool hasDefaultPlaylist = this->m_context.settings.general.defaultPlaylist.has_value ();
  244. const bool hasScreenPlaylists = !this->m_context.settings.general.screenPlaylists.empty ();
  245. if (!hasDefaultPlaylist && !hasScreenPlaylists)
  246. return;
  247. const auto now = std::chrono::steady_clock::now ();
  248. auto registerPlaylist = [this, now](const std::string& key, const ApplicationContext::PlaylistDefinition& playlist,
  249. std::optional<std::filesystem::path> currentPath) {
  250. if (playlist.items.empty ())
  251. return;
  252. ActivePlaylist state;
  253. state.definition = playlist;
  254. state.order = this->buildPlaylistOrder (playlist);
  255. if (state.order.empty ())
  256. return;
  257. if (currentPath.has_value ()) {
  258. state.orderIndex = 0;
  259. for (std::size_t i = 0; i < state.order.size (); i ++) {
  260. if (playlist.items [state.order [i]] == currentPath.value ()) {
  261. state.orderIndex = i;
  262. break;
  263. }
  264. }
  265. }
  266. const uint32_t delayMinutes = std::max<uint32_t> (1, state.definition.settings.delayMinutes);
  267. state.nextSwitch = now + std::chrono::minutes (delayMinutes);
  268. state.lastUpdate = now;
  269. this->m_activePlaylists.insert_or_assign (key, std::move (state));
  270. };
  271. if (hasDefaultPlaylist &&
  272. (this->m_context.settings.render.mode == ApplicationContext::NORMAL_WINDOW ||
  273. this->m_context.settings.render.mode == ApplicationContext::EXPLICIT_WINDOW)) {
  274. const auto& playlist = this->m_context.settings.general.defaultPlaylist.value ();
  275. const auto currentPath = playlist.items.empty ()
  276. ? std::optional<std::filesystem::path> {this->m_context.settings.general.defaultBackground}
  277. : std::optional<std::filesystem::path> {playlist.items.front ()};
  278. registerPlaylist ("default", playlist, currentPath);
  279. }
  280. for (const auto& [screen, playlist] : this->m_context.settings.general.screenPlaylists) {
  281. const auto current = this->m_context.settings.general.screenBackgrounds.find (screen);
  282. const auto currentPath = current != this->m_context.settings.general.screenBackgrounds.end ()
  283. ? std::optional<std::filesystem::path> {current->second}
  284. : std::nullopt;
  285. registerPlaylist (screen, playlist, currentPath);
  286. }
  287. }
  288. void WallpaperApplication::ensureBrowserForProject (const Project& project) {
  289. if (!project.wallpaper->is<Web> ())
  290. return;
  291. if (!this->m_browserContext) {
  292. this->m_browserContext = std::make_unique <WebBrowser::WebBrowserContext> (*this);
  293. }
  294. }
  295. bool WallpaperApplication::makeAnyViewportCurrent () const {
  296. if (!this->m_renderContext)
  297. return false;
  298. const auto& viewports = this->m_renderContext->getOutput ().getViewports ();
  299. if (viewports.empty ())
  300. return false;
  301. viewports.begin ()->second->makeCurrent ();
  302. return true;
  303. }
  304. bool WallpaperApplication::preflightWallpaper (const std::string& path) {
  305. try {
  306. // avoid mutating state, just ensure project.json parses
  307. auto container = this->setupAssetLocator (path);
  308. const auto json = WallpaperEngine::Data::JSON::JSON::parse (container->readString ("project.json"));
  309. if (!json.contains ("type") || !json.contains ("file")) {
  310. sLog.error ("Preflight failed for ", path, ": missing required fields");
  311. return false;
  312. }
  313. return true;
  314. } catch (const std::exception& e) {
  315. sLog.error ("Preflight failed for ", path, ": ", e.what ());
  316. return false;
  317. }
  318. }
  319. bool WallpaperApplication::selectNextCandidate (ActivePlaylist& playlist, std::size_t& outOrderIndex) {
  320. if (playlist.order.empty ())
  321. return false;
  322. std::size_t attempts = 0;
  323. std::size_t candidateOrderIndex = outOrderIndex;
  324. while (attempts < playlist.order.size ()) {
  325. const auto candidateIndex = playlist.order [candidateOrderIndex];
  326. if (!playlist.failedIndices.contains (candidateIndex)) {
  327. outOrderIndex = candidateOrderIndex;
  328. return true;
  329. }
  330. attempts++;
  331. candidateOrderIndex = (candidateOrderIndex + 1) % playlist.order.size ();
  332. }
  333. return false;
  334. }
  335. void WallpaperApplication::advancePlaylist (
  336. const std::string& screen, ActivePlaylist& playlist,
  337. const std::chrono::steady_clock::time_point& now) {
  338. if (playlist.order.empty ())
  339. return;
  340. playlist.orderIndex = (playlist.orderIndex + 1) % playlist.order.size ();
  341. if (playlist.orderIndex == 0 && playlist.definition.settings.order == "random") {
  342. std::shuffle (playlist.order.begin (), playlist.order.end (), this->m_playlistRng);
  343. }
  344. std::size_t candidateOrderIndex = playlist.orderIndex;
  345. if (!this->selectNextCandidate (playlist, candidateOrderIndex)) {
  346. sLog.error ("All playlist items failed for ", screen, ", keeping current wallpaper");
  347. const uint32_t delayMinutes = std::max<uint32_t> (1, playlist.definition.settings.delayMinutes);
  348. playlist.nextSwitch = now + std::chrono::minutes (delayMinutes);
  349. return;
  350. }
  351. const auto candidateIndex = playlist.order [candidateOrderIndex];
  352. const auto& candidatePath = playlist.definition.items [candidateIndex];
  353. if (!this->preflightWallpaper (candidatePath.string ())) {
  354. playlist.failedIndices.insert (candidateIndex);
  355. if (!this->selectNextCandidate (playlist, candidateOrderIndex)) {
  356. sLog.error ("All playlist items failed for ", screen, ", keeping current wallpaper");
  357. const uint32_t delayMinutes = std::max<uint32_t> (1, playlist.definition.settings.delayMinutes);
  358. playlist.nextSwitch = now + std::chrono::minutes (delayMinutes);
  359. return;
  360. }
  361. }
  362. playlist.orderIndex = candidateOrderIndex;
  363. const auto& nextPath = playlist.definition.items [playlist.order [playlist.orderIndex]];
  364. bool loaded = false;
  365. try {
  366. if (!this->makeAnyViewportCurrent ()) {
  367. sLog.error ("Cannot switch playlist on ", screen, ": no active viewport");
  368. throw std::runtime_error ("No viewport available");
  369. }
  370. auto project = this->loadBackground (nextPath.string ());
  371. this->setupPropertiesForProject (*project);
  372. this->ensureBrowserForProject (*project);
  373. this->m_backgrounds [screen] = std::move (project);
  374. const auto scalingIt = this->m_context.settings.general.screenScalings.find (screen);
  375. const auto clampIt = this->m_context.settings.general.screenClamps.find (screen);
  376. const auto scaling = scalingIt != this->m_context.settings.general.screenScalings.end ()
  377. ? scalingIt->second
  378. : this->m_context.settings.render.window.scalingMode;
  379. const auto clamp = clampIt != this->m_context.settings.general.screenClamps.end ()
  380. ? clampIt->second
  381. : this->m_context.settings.render.window.clamp;
  382. if (this->m_renderContext) {
  383. this->m_renderContext->setWallpaper (
  384. screen,
  385. WallpaperEngine::Render::CWallpaper::fromWallpaper (
  386. *this->m_backgrounds [screen]->wallpaper,
  387. *this->m_renderContext, *this->m_audioContext, this->m_browserContext.get (),
  388. scaling,
  389. clamp
  390. )
  391. );
  392. }
  393. this->m_context.settings.general.screenBackgrounds [screen] = nextPath;
  394. loaded = true;
  395. } catch (const std::exception& e) {
  396. sLog.error ("Failed to advance playlist on ", screen, ": ", e.what ());
  397. }
  398. if (!loaded) {
  399. playlist.failedIndices.insert (playlist.order [playlist.orderIndex]);
  400. // Keep current position; next timer tick will retry advancement
  401. sLog.error ("Failed to load wallpaper for ", screen, ", will retry on next cycle");
  402. }
  403. const uint32_t delayMinutes = std::max<uint32_t> (1, playlist.definition.settings.delayMinutes);
  404. playlist.nextSwitch = now + std::chrono::minutes (delayMinutes);
  405. }
  406. void WallpaperApplication::updatePlaylists () {
  407. if (this->m_activePlaylists.empty ())
  408. return;
  409. const auto now = std::chrono::steady_clock::now ();
  410. for (auto& [screen, playlist] : this->m_activePlaylists) {
  411. playlist.lastUpdate = now;
  412. if (playlist.definition.settings.mode != "timer")
  413. continue;
  414. if (playlist.definition.items.size () <= 1)
  415. continue;
  416. if (now < playlist.nextSwitch)
  417. continue;
  418. this->advancePlaylist (screen, playlist, now);
  419. }
  420. }
  421. void WallpaperApplication::setupPropertiesForProject (const Project& project) {
  422. // show properties if required
  423. for (const auto& [key, cur] : project.properties) {
  424. // update the value of the property
  425. auto override = this->m_context.settings.general.properties.find (key);
  426. if (override != this->m_context.settings.general.properties.end ()) {
  427. sLog.out ("Applying override value for ", key);
  428. cur->update (override->second);
  429. }
  430. if (this->m_context.settings.general.onlyListProperties) {
  431. sLog.out (cur->dump ());
  432. }
  433. }
  434. }
  435. void WallpaperApplication::setupProperties () {
  436. for (const auto& [background, info] : this->m_backgrounds)
  437. this->setupPropertiesForProject (*info);
  438. }
  439. void WallpaperApplication::setupBrowser () {
  440. bool anyWebProject = std::any_of (
  441. this->m_backgrounds.begin (), this->m_backgrounds.end (),
  442. [](const std::pair<const std::string, ProjectUniquePtr>& pair) -> bool {
  443. return pair.second->wallpaper->is<Web> ();
  444. }
  445. );
  446. // do not perform any initialization if no web background is present
  447. if (!anyWebProject || this->m_browserContext) {
  448. return;
  449. }
  450. this->m_browserContext = std::make_unique <WebBrowser::WebBrowserContext> (*this);
  451. }
  452. void WallpaperApplication::takeScreenshot (const std::filesystem::path& filename) const {
  453. // this should be getting called at the end of the frame, so the right thing should be bound already
  454. const int width = this->m_renderContext->getOutput ().getFullWidth ();
  455. const int height = this->m_renderContext->getOutput ().getFullHeight ();
  456. const bool vflip = this->m_renderContext->getOutput ().renderVFlip ();
  457. // build the output file with stbi_image_write
  458. auto* bitmap = new uint8_t [width * height * 3] {0};
  459. int xoffset = 0;
  460. for (const auto& [screen, viewport] : this->m_renderContext->getOutput ().getViewports ()) {
  461. // activate opengl context so we can read from the framebuffer
  462. viewport->makeCurrent ();
  463. // make room for storing the pixel of this viewport
  464. const auto bufferSize = (viewport->viewport.z - viewport->viewport.x) * (viewport->viewport.w - viewport->viewport.y) * 3;
  465. auto* buffer = new uint8_t [bufferSize];
  466. const uint8_t* pixel = buffer;
  467. // read the viewport data into the pixel buffer
  468. glPixelStorei (GL_PACK_ALIGNMENT, 1);
  469. // 4.5 supports glReadnPixels, anything older doesn't...
  470. if (GLEW_VERSION_4_5) {
  471. glReadnPixels (viewport->viewport.x, viewport->viewport.y, viewport->viewport.z, viewport->viewport.w,
  472. GL_RGB, GL_UNSIGNED_BYTE, bufferSize, buffer);
  473. } else {
  474. // fallback to old version
  475. glReadPixels (viewport->viewport.x, viewport->viewport.y, viewport->viewport.z, viewport->viewport.w,
  476. GL_RGB, GL_UNSIGNED_BYTE, buffer);
  477. }
  478. if (const GLenum error = glGetError (); error != GL_NO_ERROR) {
  479. sLog.error ("Cannot obtain pixel data for screen ", screen, ". OpenGL error: ", error);
  480. delete [] buffer;
  481. continue;
  482. }
  483. // now get access to the pixels
  484. for (int y = 0; y < viewport->viewport.w; y++) {
  485. for (int x = 0; x < viewport->viewport.z; x++) {
  486. const int xfinal = x + xoffset;
  487. const int yfinal = vflip ? (viewport->viewport.w - y - 1) : y;
  488. bitmap [yfinal * width * 3 + xfinal * 3] = *pixel++;
  489. bitmap [yfinal * width * 3 + xfinal * 3 + 1] = *pixel++;
  490. bitmap [yfinal * width * 3 + xfinal * 3 + 2] = *pixel++;
  491. }
  492. }
  493. if (viewport->single)
  494. xoffset += viewport->viewport.z;
  495. // free the buffer allocated for the viewport
  496. delete [] buffer;
  497. }
  498. if (const auto extension = filename.extension (); extension == ".bmp") {
  499. stbi_write_bmp (filename.c_str (), width, height, 3, bitmap);
  500. } else if (extension == ".png") {
  501. stbi_write_png (filename.c_str (), width, height, 3, bitmap, width * 3);
  502. } else if (extension == ".jpg" || extension == ".jpeg") {
  503. stbi_write_jpg (filename.c_str (), width, height, 3, bitmap, 100);
  504. }
  505. delete [] bitmap;
  506. }
  507. void WallpaperApplication::setupOutput () {
  508. const char* XDG_SESSION_TYPE = getenv ("XDG_SESSION_TYPE");
  509. if (!XDG_SESSION_TYPE) {
  510. sLog.exception (
  511. "Cannot read environment variable XDG_SESSION_TYPE, window server detection failed. Please ensure proper values are set");
  512. }
  513. sLog.debug ("Checking for window servers: ");
  514. for (const auto& windowServer : sVideoFactories.getRegisteredDrivers ()) {
  515. sLog.debug("\t", windowServer);
  516. }
  517. this->m_videoDriver = sVideoFactories.createVideoDriver (
  518. this->m_context.settings.render.mode, XDG_SESSION_TYPE, this->m_context, *this);
  519. this->m_fullScreenDetector = sVideoFactories.createFullscreenDetector (XDG_SESSION_TYPE, this->m_context, *this->m_videoDriver);
  520. }
  521. void WallpaperApplication::setupAudio () {
  522. // ensure audioprocessing is required by any background, and we have it enabled
  523. const bool audioProcessingRequired = std::ranges::any_of (
  524. this->m_backgrounds,
  525. [](const std::pair<const std::string, ProjectUniquePtr>& pair) -> bool {
  526. return pair.second->supportsAudioProcessing;
  527. }
  528. );
  529. if (audioProcessingRequired && this->m_context.settings.audio.audioprocessing) {
  530. this->m_audioRecorder = std::make_unique <WallpaperEngine::Audio::Drivers::Recorders::PulseAudioPlaybackRecorder> ();
  531. } else {
  532. this->m_audioRecorder = std::make_unique <WallpaperEngine::Audio::Drivers::Recorders::PlaybackRecorder> ();
  533. }
  534. if (this->m_context.settings.audio.automute) {
  535. m_audioDetector = std::make_unique <WallpaperEngine::Audio::Drivers::Detectors::PulseAudioPlayingDetector> (this->m_context, *this->m_fullScreenDetector);
  536. } else {
  537. m_audioDetector = std::make_unique <WallpaperEngine::Audio::Drivers::Detectors::AudioPlayingDetector> (this->m_context, *this->m_fullScreenDetector);
  538. }
  539. // initialize sdl audio driver
  540. m_audioDriver = std::make_unique <WallpaperEngine::Audio::Drivers::SDLAudioDriver> (this->m_context, *this->m_audioDetector, *this->m_audioRecorder);
  541. // initialize audio context
  542. m_audioContext = std::make_unique <WallpaperEngine::Audio::AudioContext> (*m_audioDriver);
  543. }
  544. void WallpaperApplication::prepareOutputs () {
  545. // initialize render context
  546. m_renderContext = std::make_unique <WallpaperEngine::Render::RenderContext> (*m_videoDriver, *this);
  547. // create a new background for each screen
  548. // set all the specific wallpapers required
  549. for (const auto& [background, info] : this->m_backgrounds) {
  550. const auto scalingIt = this->m_context.settings.general.screenScalings.find (background);
  551. const auto clampIt = this->m_context.settings.general.screenClamps.find (background);
  552. const auto scaling = scalingIt != this->m_context.settings.general.screenScalings.end ()
  553. ? scalingIt->second
  554. : this->m_context.settings.render.window.scalingMode;
  555. const auto clamp = clampIt != this->m_context.settings.general.screenClamps.end ()
  556. ? clampIt->second
  557. : this->m_context.settings.render.window.clamp;
  558. m_renderContext->setWallpaper (
  559. background,
  560. WallpaperEngine::Render::CWallpaper::fromWallpaper (
  561. *info->wallpaper, *m_renderContext, *m_audioContext, m_browserContext.get (),
  562. scaling,
  563. clamp
  564. )
  565. );
  566. }
  567. }
  568. void WallpaperApplication::setupOpenGLDebugging () {
  569. #if !NDEBUG
  570. glDebugMessageCallback(CustomGLDebugCallback, nullptr);
  571. glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
  572. #endif
  573. }
  574. void WallpaperApplication::show () {
  575. this->setupOutput ();
  576. this->setupAudio ();
  577. this->prepareOutputs ();
  578. this->setupOpenGLDebugging ();
  579. static time_t seconds;
  580. static struct tm* timeinfo;
  581. if (this->m_context.settings.general.dumpStructure) {
  582. auto prettyPrinter = Data::Dumpers::StringPrinter ();
  583. for (const auto& [background, info] : this->m_renderContext->getWallpapers ()) {
  584. prettyPrinter.printWallpaper (info->getWallpaperData ());
  585. }
  586. std::cout << prettyPrinter.str () << std::endl;
  587. }
  588. #if DEMOMODE
  589. // ensure only one background is running so everything can be properly caught
  590. if (this->m_renderContext->getWallpapers ().size () > 1) {
  591. sLog.exception ("Demo mode only supports one background");
  592. }
  593. int width = this->m_renderContext->getWallpapers ().begin ()->second->getWidth ();
  594. int height = this->m_renderContext->getWallpapers ().begin ()->second->getHeight ();
  595. std::vector<uint8_t> pixels(width * height * 3);
  596. bool initialized = false;
  597. int frame = 0;
  598. #endif /* DEMOMODE */
  599. while (this->m_context.state.general.keepRunning) {
  600. // update g_Daytime
  601. time (&seconds);
  602. timeinfo = localtime (&seconds);
  603. g_Daytime = ((timeinfo->tm_hour * 60.0f) + timeinfo->tm_min) / (24.0f * 60.0f);
  604. // keep track of the previous frame's time
  605. g_TimeLast = g_Time;
  606. // calculate the current time value
  607. g_Time = m_videoDriver->getRenderTime ();
  608. // update audio recorder
  609. m_audioDriver->update ();
  610. // update input information
  611. m_videoDriver->getInputContext ().update ();
  612. // process driver events
  613. m_videoDriver->dispatchEventQueue ();
  614. if (m_videoDriver->closeRequested()) {
  615. sLog.out ("Stop requested by driver");
  616. this->m_context.state.general.keepRunning = false;
  617. }
  618. #if DEMOMODE
  619. // wait for a full render cycle before actually starting
  620. // this gives some extra time for video and web decoders to set themselves up
  621. // because of size changes
  622. if (m_videoDriver->getFrameCounter () > (uint32_t) this->m_context.settings.render.maximumFPS) {
  623. if (!initialized) {
  624. width = this->m_renderContext->getWallpapers ().begin ()->second->getWidth ();
  625. height = this->m_renderContext->getWallpapers ().begin ()->second->getHeight ();
  626. pixels.reserve(width * height * 3);
  627. init_encoder ("output.webm", width, height);
  628. initialized = true;
  629. }
  630. glBindFramebuffer (GL_FRAMEBUFFER, this->m_renderContext->getWallpapers ().begin ()->second->getWallpaperFramebuffer());
  631. glPixelStorei (GL_PACK_ALIGNMENT, 1);
  632. glReadPixels (0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels.data ());
  633. write_video_frame (pixels.data ());
  634. frame ++;
  635. // stop after the given framecount
  636. if (frame >= FRAME_COUNT) {
  637. this->m_context.state.general.keepRunning = false;
  638. }
  639. }
  640. #endif /* DEMOMODE */
  641. // check for fullscreen windows and wait until there's none fullscreen
  642. if (this->m_fullScreenDetector->anythingFullscreen () && this->m_context.state.general.keepRunning) {
  643. this->m_isPaused = true;
  644. this->m_pauseStart = std::chrono::steady_clock::now ();
  645. m_renderContext->setPause (true);
  646. while (this->m_fullScreenDetector->anythingFullscreen () && this->m_context.state.general.keepRunning)
  647. usleep (FULLSCREEN_CHECK_WAIT_TIME);
  648. m_renderContext->setPause (false);
  649. // account for paused duration in playlist timers
  650. const auto pausedNow = std::chrono::steady_clock::now ();
  651. const auto pausedDuration = pausedNow - this->m_pauseStart;
  652. for (auto& [_, playlist] : this->m_activePlaylists) {
  653. if (!playlist.definition.settings.updateOnPause) {
  654. playlist.nextSwitch += pausedDuration;
  655. playlist.lastUpdate += pausedDuration;
  656. }
  657. }
  658. this->m_isPaused = false;
  659. }
  660. this->updatePlaylists ();
  661. if (!this->m_context.settings.screenshot.take || m_videoDriver->getFrameCounter () < this->m_context.settings.screenshot.delay)
  662. continue;
  663. this->takeScreenshot (this->m_context.settings.screenshot.path);
  664. this->m_context.settings.screenshot.take = false;
  665. }
  666. sLog.out ("Stopping");
  667. #if DEMOMODE
  668. close_encoder ();
  669. #endif /* DEMOMODE */
  670. SDL_Quit ();
  671. }
  672. void WallpaperApplication::update (Render::Drivers::Output::OutputViewport* viewport) {
  673. // render the scene
  674. m_renderContext->render (viewport);
  675. }
  676. void WallpaperApplication::signal (int signal) {
  677. sLog.out ("Stop requested by signal ", signal);
  678. this->m_context.state.general.keepRunning = false;
  679. }
  680. const std::map<std::string, ProjectUniquePtr>& WallpaperApplication::getBackgrounds () const {
  681. return this->m_backgrounds;
  682. }
  683. ApplicationContext& WallpaperApplication::getContext () const {
  684. return this->m_context;
  685. }
  686. const WallpaperEngine::Render::Drivers::Output::Output& WallpaperApplication::getOutput () const {
  687. return this->m_renderContext->getOutput ();
  688. }