CWallpaperApplication.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. #include "CWallpaperApplication.h"
  2. #include "Steam/FileSystem/FileSystem.h"
  3. #include "WallpaperEngine/Application/CApplicationState.h"
  4. #include "WallpaperEngine/Assets/CAssetLoadException.h"
  5. #include "WallpaperEngine/Assets/CDirectory.h"
  6. #include "WallpaperEngine/Assets/CVirtualContainer.h"
  7. #include "WallpaperEngine/Audio/Drivers/Detectors/CPulseAudioPlayingDetector.h"
  8. #include "WallpaperEngine/Core/Wallpapers/CVideo.h"
  9. #include "WallpaperEngine/Input/Drivers/CGLFWMouseInput.h"
  10. #include "WallpaperEngine/Logging/CLog.h"
  11. #include "WallpaperEngine/Render/CRenderContext.h"
  12. #include "WallpaperEngine/Input/Drivers/CWaylandMouseInput.h"
  13. #include "WallpaperEngine/Render/Drivers/CWaylandOpenGLDriver.h"
  14. #include "WallpaperEngine/Render/Drivers/Detectors/CX11FullScreenDetector.h"
  15. #include <unistd.h>
  16. #define STB_IMAGE_WRITE_IMPLEMENTATION
  17. #include <stb_image_write.h>
  18. #define FULLSCREEN_CHECK_WAIT_TIME 250
  19. float g_Time;
  20. float g_TimeLast;
  21. float g_Daytime;
  22. namespace WallpaperEngine::Application {
  23. CWallpaperApplication::CWallpaperApplication (CApplicationContext& context,
  24. WallpaperEngine::WebBrowser::CWebBrowserContext& browserContext) :
  25. m_context (context),
  26. m_browserContext (browserContext) {
  27. this->loadBackgrounds ();
  28. this->setupProperties ();
  29. }
  30. CWallpaperApplication::~CWallpaperApplication () {
  31. delete m_renderContext;
  32. delete m_videoDriver;
  33. delete m_audioContext;
  34. delete m_audioDriver;
  35. delete m_inputContext;
  36. }
  37. void CWallpaperApplication::setupContainer (CCombinedContainer& container, const std::string& bg) const {
  38. const std::filesystem::path basepath = bg;
  39. container.add (new CDirectory (basepath));
  40. container.addPkg (basepath / "scene.pkg");
  41. container.addPkg (basepath / "gifscene.pkg");
  42. try {
  43. container.add (new CDirectory (this->m_context.settings.general.assets));
  44. } catch (CAssetLoadException&) {
  45. sLog.exception("Cannot find a valid assets folder, resolved to ", this->m_context.settings.general.assets);
  46. }
  47. // add two possible patches directories to the container
  48. // hopefully one sticks
  49. bool relative = true;
  50. bool absolute = true;
  51. try {
  52. container.add (new CDirectory ("../share/"));
  53. } catch (CAssetLoadException&) {
  54. relative = false;
  55. }
  56. try {
  57. container.add (new CDirectory (DATADIR));
  58. } catch (CAssetLoadException&) {
  59. absolute = false;
  60. }
  61. if (!relative && !absolute)
  62. sLog.error ("WARNING: Shader patches directory cannot be found, this might make some backgrounds not work "
  63. "properly");
  64. // TODO: move this somewhere else?
  65. auto* virtualContainer = new CVirtualContainer ();
  66. //
  67. // Had to get a little creative with the effects to achieve the same bloom effect without any custom code
  68. // these virtual files are loaded by an image in the scene that takes current _rt_FullFrameBuffer and
  69. // applies the bloom effect to render it out to the screen
  70. //
  71. // add the effect file for screen bloom
  72. // add some model for the image element even if it's going to waste rendering cycles
  73. virtualContainer->add ("effects/wpenginelinux/bloomeffect.json",
  74. "{"
  75. "\t\"name\":\"camerabloom_wpengine_linux\","
  76. "\t\"group\":\"wpengine_linux_camera\","
  77. "\t\"dependencies\":[],"
  78. "\t\"passes\":"
  79. "\t["
  80. "\t\t{"
  81. "\t\t\t\"material\": \"materials/util/downsample_quarter_bloom.json\","
  82. "\t\t\t\"target\": \"_rt_4FrameBuffer\","
  83. "\t\t\t\"bind\":"
  84. "\t\t\t["
  85. "\t\t\t\t{"
  86. "\t\t\t\t\t\"name\": \"_rt_FullFrameBuffer\","
  87. "\t\t\t\t\t\"index\": 0"
  88. "\t\t\t\t}"
  89. "\t\t\t]"
  90. "\t\t},"
  91. "\t\t{"
  92. "\t\t\t\"material\": \"materials/util/downsample_eighth_blur_v.json\","
  93. "\t\t\t\"target\": \"_rt_8FrameBuffer\","
  94. "\t\t\t\"bind\":"
  95. "\t\t\t["
  96. "\t\t\t\t{"
  97. "\t\t\t\t\t\"name\": \"_rt_4FrameBuffer\","
  98. "\t\t\t\t\t\"index\": 0"
  99. "\t\t\t\t}"
  100. "\t\t\t]"
  101. "\t\t},"
  102. "\t\t{"
  103. "\t\t\t\"material\": \"materials/util/blur_h_bloom.json\","
  104. "\t\t\t\"target\": \"_rt_Bloom\","
  105. "\t\t\t\"bind\":"
  106. "\t\t\t["
  107. "\t\t\t\t{"
  108. "\t\t\t\t\t\"name\": \"_rt_8FrameBuffer\","
  109. "\t\t\t\t\t\"index\": 0"
  110. "\t\t\t\t}"
  111. "\t\t\t]"
  112. "\t\t},"
  113. "\t\t{"
  114. "\t\t\t\"material\": \"materials/util/combine.json\","
  115. "\t\t\t\"target\": \"_rt_FullFrameBuffer\","
  116. "\t\t\t\"bind\":"
  117. "\t\t\t["
  118. "\t\t\t\t{"
  119. "\t\t\t\t\t\"name\": \"_rt_imageLayerComposite_-1_a\","
  120. "\t\t\t\t\t\"index\": 0"
  121. "\t\t\t\t},"
  122. "\t\t\t\t{"
  123. "\t\t\t\t\t\"name\": \"_rt_Bloom\","
  124. "\t\t\t\t\t\"index\": 1"
  125. "\t\t\t\t}"
  126. "\t\t\t]"
  127. "\t\t}"
  128. "\t]"
  129. "}");
  130. virtualContainer->add ("models/wpenginelinux.json", "{"
  131. "\t\"material\":\"materials/wpenginelinux.json\""
  132. "}");
  133. // models require materials, so add that too
  134. virtualContainer->add ("materials/wpenginelinux.json", "{"
  135. "\t\"passes\":"
  136. "\t\t["
  137. "\t\t\t{"
  138. "\t\t\t\t\"blending\": \"normal\","
  139. "\t\t\t\t\"cullmode\": \"nocull\","
  140. "\t\t\t\t\"depthtest\": \"disabled\","
  141. "\t\t\t\t\"depthwrite\": \"disabled\","
  142. "\t\t\t\t\"shader\": \"genericimage2\","
  143. "\t\t\t\t\"textures\": [\"_rt_FullFrameBuffer\"]"
  144. "\t\t\t}"
  145. "\t\t]"
  146. "}");
  147. container.add (virtualContainer);
  148. }
  149. void CWallpaperApplication::loadBackgrounds () {
  150. if (this->m_context.settings.render.mode == CApplicationContext::NORMAL_WINDOW || this->m_context.settings.render.mode == CApplicationContext::EXPLICIT_WINDOW) {
  151. this->m_backgrounds ["default"] = this->loadBackground(this->m_context.settings.general.defaultBackground);
  152. return;
  153. }
  154. for (const auto& [screen, path] : this->m_context.settings.general.screenBackgrounds) {
  155. // screens with no screen should use the default
  156. if (path.empty ()) {
  157. this->m_backgrounds [screen] = this->loadBackground (this->m_context.settings.general.defaultBackground);
  158. } else {
  159. this->m_backgrounds [screen] = this->loadBackground (path);
  160. }
  161. }
  162. }
  163. Core::CProject* CWallpaperApplication::loadBackground (const std::string& bg) {
  164. auto* container = new CCombinedContainer ();
  165. this->setupContainer (*container, bg);
  166. return Core::CProject::fromFile ("project.json", container);
  167. }
  168. void CWallpaperApplication::setupPropertiesForProject (const Core::CProject* project) {
  169. // show properties if required
  170. for (const auto cur : project->getProperties ()) {
  171. // update the value of the property
  172. auto override = this->m_context.settings.general.properties.find (cur->getName ());
  173. if (override != this->m_context.settings.general.properties.end ()) {
  174. sLog.out ("Applying override value for ", cur->getName ());
  175. cur->update (override->second);
  176. }
  177. if (this->m_context.settings.general.onlyListProperties)
  178. sLog.out (cur->dump ());
  179. }
  180. }
  181. void CWallpaperApplication::setupProperties () {
  182. for (const auto& [backgrounc, info] : this->m_backgrounds)
  183. this->setupPropertiesForProject (info);
  184. }
  185. void CWallpaperApplication::takeScreenshot (const std::filesystem::path& filename) {
  186. // this should be getting called at the end of the frame, so the right thing should be bound already
  187. const int width = this->m_renderContext->getOutput ().getFullWidth ();
  188. const int height = this->m_renderContext->getOutput ().getFullHeight ();
  189. // build the output file with stbi_image_write
  190. auto* bitmap = new uint8_t [width * height * sizeof (uint8_t) * 3] {0};
  191. int xoffset = 0;
  192. for (const auto& [screen, viewport] : this->m_renderContext->getOutput ().getViewports ()) {
  193. // activate opengl context so we can read from the framebuffer
  194. viewport->makeCurrent ();
  195. // make room for storing the pixel of this viewport
  196. auto* buffer = new uint8_t [viewport->viewport.z * viewport->viewport.w * sizeof (uint8_t) * 3];
  197. const uint8_t* pixel = buffer;
  198. // read the viewport data into the pixel buffer
  199. glReadPixels (viewport->viewport.x, viewport->viewport.y, viewport->viewport.z, viewport->viewport.w, GL_RGB,
  200. GL_UNSIGNED_BYTE, buffer);
  201. // now get access to the pixels
  202. for (int y = viewport->viewport.w; y > 0; y--) {
  203. for (int x = 0; x < viewport->viewport.z; x++) {
  204. int xfinal = x + xoffset;
  205. int yfinal = this->m_renderContext->getOutput ().renderVFlip () ? (viewport->viewport.w - y) : y;
  206. bitmap[yfinal * width * 3 + xfinal * 3] = *pixel++;
  207. bitmap[yfinal * width * 3 + xfinal * 3 + 1] = *pixel++;
  208. bitmap[yfinal * width * 3 + xfinal * 3 + 2] = *pixel++;
  209. }
  210. }
  211. if (viewport->single)
  212. xoffset += viewport->viewport.z;
  213. // free the buffer allocated for the viewport
  214. delete [] buffer;
  215. }
  216. auto extension = filename.extension();
  217. if (extension == ".bmp") {
  218. stbi_write_bmp (filename.c_str(), width, height, 3, bitmap);
  219. } else if (extension == ".png") {
  220. stbi_write_png (filename.c_str(), width, height, 3, bitmap, width * 3);
  221. } else if (extension == ".jpg" || extension == ".jpeg") {
  222. stbi_write_jpg (filename.c_str(), width, height, 3, bitmap, 100);
  223. }
  224. }
  225. void CWallpaperApplication::show () {
  226. const char* XDG_SESSION_TYPE = getenv ("XDG_SESSION_TYPE");
  227. if (!XDG_SESSION_TYPE) {
  228. sLog.exception (
  229. "Cannot read environment variable XDG_SESSION_TYPE, window server detection failed. Please ensure proper values are set");
  230. }
  231. sLog.debug("Checking for window servers: ");
  232. #ifdef ENABLE_WAYLAND
  233. sLog.debug("\twayland");
  234. #endif // ENABLE_WAYLAND
  235. #ifdef ENABLE_X11
  236. sLog.debug("\tx11");
  237. #endif // ENABLE_X11
  238. #ifdef ENABLE_WAYLAND
  239. bool isWayland = strncmp ("wayland", XDG_SESSION_TYPE, strlen ("wayland")) == 0;
  240. #endif // ENABLE_WAYLAND
  241. #ifdef ENABLE_X11
  242. bool isX11 = strncmp ("x11", XDG_SESSION_TYPE, strlen ("x11")) == 0;
  243. #endif // ENABLE_X11
  244. if (this->m_context.settings.render.mode == CApplicationContext::DESKTOP_BACKGROUND) {
  245. #ifdef ENABLE_WAYLAND
  246. if (isWayland) {
  247. m_videoDriver = new WallpaperEngine::Render::Drivers::CWaylandOpenGLDriver (this->m_context, *this);
  248. m_inputContext =
  249. new WallpaperEngine::Input::CInputContext (new WallpaperEngine::Input::Drivers::CWaylandMouseInput (
  250. reinterpret_cast<WallpaperEngine::Render::Drivers::CWaylandOpenGLDriver*> (m_videoDriver)));
  251. this->m_fullScreenDetector =
  252. new WallpaperEngine::Render::Drivers::Detectors::CWaylandFullScreenDetector (this->m_context);
  253. }
  254. #endif // ENABLE_WAYLAND
  255. #ifdef ENABLE_X11
  256. #ifdef ENABLE_WAYLAND
  257. else
  258. #endif // ENABLE_WAYLAND
  259. if (isX11) {
  260. m_videoDriver =
  261. new WallpaperEngine::Render::Drivers::CGLFWOpenGLDriver ("wallpaperengine", this->m_context, *this);
  262. m_inputContext =
  263. new WallpaperEngine::Input::CInputContext (new WallpaperEngine::Input::Drivers::CGLFWMouseInput (
  264. reinterpret_cast<Render::Drivers::CGLFWOpenGLDriver*> (m_videoDriver)));
  265. this->m_fullScreenDetector = new WallpaperEngine::Render::Drivers::Detectors::CX11FullScreenDetector (
  266. this->m_context, *reinterpret_cast<Render::Drivers::CGLFWOpenGLDriver*> (m_videoDriver));
  267. }
  268. #endif // ENABLE_X11
  269. #ifdef ENABLE_X11
  270. else
  271. #endif // ENABLE_X11
  272. {
  273. sLog.exception (
  274. "Cannot run in background mode, window server could not be detected. XDG_SESSION_TYPE must be wayland or x11");
  275. }
  276. } else {
  277. m_videoDriver =
  278. new WallpaperEngine::Render::Drivers::CGLFWOpenGLDriver ("wallpaperengine", this->m_context, *this);
  279. #ifdef ENABLE_WAYLAND
  280. if (isWayland) {
  281. this->m_fullScreenDetector =
  282. new WallpaperEngine::Render::Drivers::Detectors::CWaylandFullScreenDetector (this->m_context);
  283. }
  284. #endif // ENABLE_WAYLAND
  285. #ifdef ENABLE_X11
  286. #ifdef ENABLE_WAYLAND
  287. else
  288. #endif // ENABLE_WAYLAND
  289. if (isX11) {
  290. this->m_fullScreenDetector = new WallpaperEngine::Render::Drivers::Detectors::CX11FullScreenDetector (
  291. this->m_context, *reinterpret_cast<Render::Drivers::CGLFWOpenGLDriver*> (m_videoDriver));
  292. }
  293. #endif // ENABLE_X11
  294. else {
  295. this->m_fullScreenDetector =
  296. new WallpaperEngine::Render::Drivers::Detectors::CFullScreenDetector (this->m_context);
  297. }
  298. m_inputContext = new WallpaperEngine::Input::CInputContext (new WallpaperEngine::Input::Drivers::CGLFWMouseInput (
  299. reinterpret_cast<Render::Drivers::CGLFWOpenGLDriver*> (m_videoDriver)));
  300. }
  301. if (this->m_context.settings.audio.audioprocessing) {
  302. this->m_audioRecorder = new WallpaperEngine::Audio::Drivers::Recorders::CPulseAudioPlaybackRecorder ();
  303. } else {
  304. this->m_audioRecorder = new WallpaperEngine::Audio::Drivers::Recorders::CPlaybackRecorder ();
  305. }
  306. // audio playing detector
  307. WallpaperEngine::Audio::Drivers::Detectors::CPulseAudioPlayingDetector audioDetector (this->m_context,
  308. *this->m_fullScreenDetector);
  309. // initialize sdl audio driver
  310. m_audioDriver =
  311. new WallpaperEngine::Audio::Drivers::CSDLAudioDriver (this->m_context, audioDetector, *this->m_audioRecorder);
  312. // initialize audio context
  313. m_audioContext = new WallpaperEngine::Audio::CAudioContext (*m_audioDriver);
  314. // initialize render context
  315. m_renderContext = new WallpaperEngine::Render::CRenderContext (*m_videoDriver, *m_inputContext, *this);
  316. // create a new background for each screen
  317. // set all the specific wallpapers required
  318. for (const auto& [background, info] : this->m_backgrounds) {
  319. m_renderContext->setWallpaper (background, WallpaperEngine::Render::CWallpaper::fromWallpaper (
  320. info->getWallpaper (), *m_renderContext, *m_audioContext, m_browserContext,
  321. this->m_context.settings.general.screenScalings [background]));
  322. }
  323. // wallpapers are setup, free browsesr context if possible
  324. if (!this->m_browserContext.isUsed ()) {
  325. this->m_browserContext.stop ();
  326. }
  327. static time_t seconds;
  328. static struct tm* timeinfo;
  329. while (this->m_context.state.general.keepRunning && !m_videoDriver->closeRequested ()) {
  330. // update g_Daytime
  331. time (&seconds);
  332. timeinfo = localtime (&seconds);
  333. g_Daytime = ((timeinfo->tm_hour * 60) + timeinfo->tm_min) / (24.0 * 60.0);
  334. // keep track of the previous frame's time
  335. g_TimeLast = g_Time;
  336. // calculate the current time value
  337. g_Time = m_videoDriver->getRenderTime ();
  338. // update audio recorder
  339. m_audioDriver->update ();
  340. // update input information
  341. m_inputContext->update ();
  342. // process driver events
  343. m_videoDriver->dispatchEventQueue ();
  344. if (!this->m_context.settings.screenshot.take || m_videoDriver->getFrameCounter () < 5)
  345. continue;
  346. this->takeScreenshot (this->m_context.settings.screenshot.path);
  347. this->m_context.settings.screenshot.take = false;
  348. }
  349. // ensure this is updated as sometimes it might not come from a signal
  350. this->m_context.state.general.keepRunning = false;
  351. sLog.out ("Stop requested");
  352. SDL_Quit ();
  353. }
  354. void CWallpaperApplication::update (Render::Drivers::Output::COutputViewport* viewport) {
  355. // check for fullscreen windows and wait until there's none fullscreen
  356. while (this->m_fullScreenDetector->anythingFullscreen () && this->m_context.state.general.keepRunning)
  357. usleep (FULLSCREEN_CHECK_WAIT_TIME);
  358. // render the scene
  359. m_renderContext->render (viewport);
  360. }
  361. void CWallpaperApplication::signal (int signal) {
  362. this->m_context.state.general.keepRunning = false;
  363. }
  364. const std::map<std::string, Core::CProject*>& CWallpaperApplication::getBackgrounds () const {
  365. return this->m_backgrounds;
  366. }
  367. CApplicationContext& CWallpaperApplication::getContext () const {
  368. return this->m_context;
  369. }
  370. const WallpaperEngine::Render::Drivers::Output::COutput& CWallpaperApplication::getOutput () const {
  371. return this->m_renderContext->getOutput ();
  372. }
  373. } // namespace WallpaperEngine::Application