main.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. #include <FreeImage.h>
  2. #include <GL/glew.h>
  3. #include <GLFW/glfw3.h>
  4. #include <SDL.h>
  5. #include <csignal>
  6. #include <filesystem>
  7. #include <getopt.h>
  8. #include <iostream>
  9. #include <libgen.h>
  10. #include <stdlib.h>
  11. #include <sys/stat.h>
  12. #include <unistd.h>
  13. #include "WallpaperEngine/Core/CProject.h"
  14. #include "WallpaperEngine/Render/CRenderContext.h"
  15. #include "WallpaperEngine/Render/CVideo.h"
  16. #include "WallpaperEngine/Render/CWallpaper.h"
  17. #include "WallpaperEngine/Assets/CPackage.h"
  18. #include "WallpaperEngine/Assets/CDirectory.h"
  19. #include "WallpaperEngine/Assets/CVirtualContainer.h"
  20. #include "WallpaperEngine/Assets/CCombinedContainer.h"
  21. #include "WallpaperEngine/Assets/CPackageLoadException.h"
  22. #include "Steam/FileSystem/FileSystem.h"
  23. #include "WallpaperEngine/Audio/CAudioContext.h"
  24. #include "WallpaperEngine/Audio/Drivers/CSDLAudioDriver.h"
  25. #include "WallpaperEngine/Render/Drivers/COpenGLDriver.h"
  26. #include "common.h"
  27. #define WORKSHOP_APP_ID 431960
  28. #define APP_DIRECTORY "wallpaper_engine"
  29. float g_Time;
  30. float g_TimeLast;
  31. bool g_KeepRunning = true;
  32. bool g_AudioEnabled = true;
  33. int g_AudioVolume = 128;
  34. void print_help (const char* route)
  35. {
  36. sLog.out ("Usage: ", route, " [options] background_path/background_id");
  37. sLog.out ("");
  38. sLog.out ("where background_path/background_id can be:");
  39. sLog.out ("\tthe ID of the background (for autodetection on your steam installation)");
  40. sLog.out ("\ta full path to the background's folder");
  41. sLog.out ("");
  42. sLog.out ("options:");
  43. sLog.out ("\t--silent\t\t\t\t\tMutes all the sound the wallpaper might produce");
  44. sLog.out ("\t--volume <amount>\t\t\tSets the volume for all the sounds in the background");
  45. sLog.out ("\t--screen-root <screen name>\tDisplay as screen's background");
  46. sLog.out ("\t--fps <maximum-fps>\t\t\tLimits the FPS to the given number, useful to keep battery consumption low");
  47. sLog.out ("\t--assets-dir <path>\t\t\tFolder where the assets are stored");
  48. sLog.out ("\t--screenshot\t\t\t\tTakes a screenshot of the background");
  49. sLog.out ("\t--list-properties\t\t\tList all the available properties and their possible values");
  50. sLog.out ("\t--set-property <name=value>\tOverrides the default value of the given property");
  51. }
  52. std::string stringPathFixes(const std::string& s)
  53. {
  54. if (s.empty () == true)
  55. return s;
  56. std::string str (s);
  57. // remove single-quotes from the arguments
  58. if (str [0] == '\'' && str [str.size() - 1] == '\'')
  59. str
  60. .erase (str.size() - 1, 1)
  61. .erase (0, 1);
  62. return std::move (str);
  63. }
  64. void signalhandler(int sig)
  65. {
  66. g_KeepRunning = false;
  67. }
  68. void addPkg (CCombinedContainer* containers, const std::filesystem::path& path, std::string pkgfile)
  69. {
  70. try
  71. {
  72. auto scene_path = std::filesystem::path (path) / pkgfile;
  73. // add the package to the list
  74. containers->add (new WallpaperEngine::Assets::CPackage (scene_path));
  75. sLog.out ("Detected ", pkgfile, " file at ", scene_path, ". Adding to list of searchable paths");
  76. }
  77. catch (CPackageLoadException& ex)
  78. {
  79. // ignore this error, the package file was not found
  80. sLog.out ("No ", pkgfile, " file found at ", path, ". Defaulting to normal folder storage");
  81. }
  82. catch (std::runtime_error& ex)
  83. {
  84. // the package was found but there was an error loading it (wrong header or something)
  85. sLog.exception ("Failed to load scene.pkg file: ", ex.what());
  86. }
  87. }
  88. CVirtualContainer* buildVirtualContainer ()
  89. {
  90. CVirtualContainer* container = new WallpaperEngine::Assets::CVirtualContainer ();
  91. //
  92. // Had to get a little creative with the effects to achieve the same bloom effect without any custom code
  93. // these virtual files are loaded by an image in the scene that takes current _rt_FullFrameBuffer and
  94. // applies the bloom effect to render it out to the screen
  95. //
  96. // add the effect file for screen bloom
  97. container->add (
  98. "effects/wpenginelinux/bloomeffect.json",
  99. "{"
  100. "\t\"name\":\"camerabloom_wpengine_linux\","
  101. "\t\"group\":\"wpengine_linux_camera\","
  102. "\t\"dependencies\":[],"
  103. "\t\"passes\":"
  104. "\t["
  105. "\t\t{"
  106. "\t\t\t\"material\": \"materials/util/downsample_quarter_bloom.json\","
  107. "\t\t\t\"target\": \"_rt_4FrameBuffer\","
  108. "\t\t\t\"bind\":"
  109. "\t\t\t["
  110. "\t\t\t\t{"
  111. "\t\t\t\t\t\"name\": \"_rt_FullFrameBuffer\","
  112. "\t\t\t\t\t\"index\": 0"
  113. "\t\t\t\t}"
  114. "\t\t\t]"
  115. "\t\t},"
  116. "\t\t{"
  117. "\t\t\t\"material\": \"materials/util/downsample_eighth_blur_v.json\","
  118. "\t\t\t\"target\": \"_rt_8FrameBuffer\","
  119. "\t\t\t\"bind\":"
  120. "\t\t\t["
  121. "\t\t\t\t{"
  122. "\t\t\t\t\t\"name\": \"_rt_4FrameBuffer\","
  123. "\t\t\t\t\t\"index\": 0"
  124. "\t\t\t\t}"
  125. "\t\t\t]"
  126. "\t\t},"
  127. "\t\t{"
  128. "\t\t\t\"material\": \"materials/util/blur_h_bloom.json\","
  129. "\t\t\t\"target\": \"_rt_Bloom\","
  130. "\t\t\t\"bind\":"
  131. "\t\t\t["
  132. "\t\t\t\t{"
  133. "\t\t\t\t\t\"name\": \"_rt_8FrameBuffer\","
  134. "\t\t\t\t\t\"index\": 0"
  135. "\t\t\t\t}"
  136. "\t\t\t]"
  137. "\t\t},"
  138. "\t\t{"
  139. "\t\t\t\"material\": \"materials/util/combine.json\","
  140. "\t\t\t\"target\": \"_rt_FullFrameBuffer\","
  141. "\t\t\t\"bind\":"
  142. "\t\t\t["
  143. "\t\t\t\t{"
  144. "\t\t\t\t\t\"name\": \"_rt_imageLayerComposite_-1_a\","
  145. "\t\t\t\t\t\"index\": 0"
  146. "\t\t\t\t},"
  147. "\t\t\t\t{"
  148. "\t\t\t\t\t\"name\": \"_rt_Bloom\","
  149. "\t\t\t\t\t\"index\": 1"
  150. "\t\t\t\t}"
  151. "\t\t\t]"
  152. "\t\t}"
  153. "\t]"
  154. "}"
  155. );
  156. // add some model for the image element even if it's going to waste rendering cycles
  157. container->add (
  158. "models/wpenginelinux.json",
  159. "{"
  160. "\t\"material\":\"materials/wpenginelinux.json\""
  161. "}"
  162. );
  163. // models require materials, so add that too
  164. container->add (
  165. "materials/wpenginelinux.json",
  166. "{"
  167. "\t\"passes\":"
  168. "\t\t["
  169. "\t\t\t{"
  170. "\t\t\t\t\"blending\": \"normal\","
  171. "\t\t\t\t\"cullmode\": \"nocull\","
  172. "\t\t\t\t\"depthtest\": \"disabled\","
  173. "\t\t\t\t\"depthwrite\": \"disabled\","
  174. "\t\t\t\t\"shader\": \"genericimage2\","
  175. "\t\t\t\t\"textures\": [\"_rt_FullFrameBuffer\"]"
  176. "\t\t\t}"
  177. "\t\t]"
  178. "}"
  179. );
  180. return container;
  181. }
  182. void takeScreenshot (WallpaperEngine::Render::CWallpaper* wp, const std::filesystem::path& filename, FREE_IMAGE_FORMAT format)
  183. {
  184. GLint width, height;
  185. // bind texture and get the size
  186. glBindFramebuffer (GL_FRAMEBUFFER, wp->getWallpaperFramebuffer ());
  187. glBindTexture (GL_TEXTURE_2D, wp->getWallpaperTexture ());
  188. glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
  189. glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);
  190. // make room for storing the pixel data
  191. uint8_t* buffer = new uint8_t [width * height * sizeof (uint8_t) * 3];
  192. uint8_t* pixel = buffer;
  193. // read the image into the buffer
  194. glReadPixels (0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, buffer);
  195. // build the output file with FreeImage
  196. FIBITMAP* bitmap = FreeImage_Allocate (width, height, 24);
  197. RGBQUAD color;
  198. // now get access to the pixels
  199. for (int y = height; y > 0; y --)
  200. {
  201. for (int x = 0; x < width; x ++)
  202. {
  203. color.rgbRed = *pixel ++;
  204. color.rgbGreen = *pixel ++;
  205. color.rgbBlue = *pixel ++;
  206. // set the pixel in the destination
  207. FreeImage_SetPixelColor (bitmap, x, y, &color);
  208. }
  209. }
  210. // finally save the file
  211. FreeImage_Save (format, bitmap, filename.c_str (), 0);
  212. // free all the used memory
  213. delete[] buffer;
  214. FreeImage_Unload (bitmap);
  215. // unbind the textures
  216. glBindTexture (GL_TEXTURE_2D, GL_NONE);
  217. }
  218. void initLogging ()
  219. {
  220. sLog.addOutput (new std::ostream (std::cout.rdbuf ()));
  221. sLog.addError (new std::ostream (std::cerr.rdbuf ()));
  222. }
  223. int main (int argc, char* argv[])
  224. {
  225. initLogging ();
  226. std::vector <std::string> screens;
  227. std::map <std::string, std::string> propertyOverrides;
  228. int maximumFPS = 30;
  229. bool shouldTakeScreenshot = false;
  230. bool shouldListPropertiesAndStop = false;
  231. FREE_IMAGE_FORMAT screenshotFormat = FIF_UNKNOWN;
  232. std::string path;
  233. std::filesystem::path assetsPath;
  234. std::filesystem::path screenshotPath;
  235. static struct option long_options [] = {
  236. {"screen-root", required_argument, 0, 'r'},
  237. {"pkg", required_argument, 0, 'p'},
  238. {"dir", required_argument, 0, 'd'},
  239. {"silent", no_argument, 0, 's'},
  240. {"volume", required_argument, 0, 'v'},
  241. {"help", no_argument, 0, 'h'},
  242. {"fps", required_argument, 0, 'f'},
  243. {"assets-dir", required_argument, 0, 'a'},
  244. {"screenshot", required_argument, 0, 'c'},
  245. {"list-properties", no_argument, 0, 'l'},
  246. {"set-property", required_argument, 0, 'o'},
  247. {nullptr, 0, 0, 0}
  248. };
  249. while (true)
  250. {
  251. int c = getopt_long (argc, argv, "r:p:d:shf:a:", long_options, nullptr);
  252. if (c == -1)
  253. break;
  254. switch (c)
  255. {
  256. case 'o':
  257. {
  258. std::string value = optarg;
  259. std::string::size_type equals = value.find ('=');
  260. // properties without value are treated as booleans for now
  261. if (equals == std::string::npos)
  262. propertyOverrides.insert_or_assign (value, "1");
  263. else
  264. propertyOverrides.insert_or_assign (
  265. value.substr (0, equals),
  266. value.substr (equals + 1)
  267. );
  268. }
  269. break;
  270. case 'l':
  271. shouldListPropertiesAndStop = true;
  272. break;
  273. case 'r':
  274. screens.emplace_back (optarg);
  275. break;
  276. case 'p':
  277. case 'd':
  278. sLog.error ("--dir/--pkg is deprecated and not used anymore");
  279. path = stringPathFixes (optarg);
  280. break;
  281. case 's':
  282. g_AudioEnabled = false;
  283. break;
  284. case 'h':
  285. print_help (argv [0]);
  286. break;
  287. case 'f':
  288. maximumFPS = atoi (optarg);
  289. break;
  290. case 'a': assetsPath = optarg;
  291. break;
  292. case 'v':
  293. g_AudioVolume = atoi (optarg);
  294. break;
  295. case 'c':
  296. shouldTakeScreenshot = true;
  297. screenshotPath = stringPathFixes (optarg);
  298. break;
  299. }
  300. }
  301. if (path.empty () == true)
  302. {
  303. if (optind < argc && strlen (argv [optind]) > 0)
  304. {
  305. path = argv [optind];
  306. }
  307. else
  308. {
  309. print_help (argv [0]);
  310. return 0;
  311. }
  312. }
  313. // validate screenshot file just to make sure
  314. if (shouldTakeScreenshot == true)
  315. {
  316. if (screenshotPath.has_extension () == false)
  317. sLog.exception ("Cannot determine screenshot format");
  318. std::string extension = screenshotPath.extension ();
  319. if (extension == ".bmp")
  320. screenshotFormat = FIF_BMP;
  321. else if (extension == ".png")
  322. screenshotFormat = FIF_PNG;
  323. else if (extension == ".jpg" || extension == ".jpeg")
  324. screenshotFormat = FIF_JPEG;
  325. else
  326. sLog.exception ("Cannot determine screenshot format, unknown extension ", extension);
  327. }
  328. // check if the background might be an ID and try to find the right path in the steam installation folder
  329. if (path.find ('/') == std::string::npos)
  330. path = Steam::FileSystem::workshopDirectory (WORKSHOP_APP_ID, path);
  331. WallpaperEngine::Assets::CCombinedContainer containers;
  332. // the background's path is required to load project.json regardless of the type of background we're using
  333. containers.add (new WallpaperEngine::Assets::CDirectory (path));
  334. // add the virtual container for mocked up files
  335. containers.add (buildVirtualContainer ());
  336. // try to add the common packages
  337. addPkg (&containers, path, "scene.pkg");
  338. addPkg (&containers, path, "gifscene.pkg");
  339. if (assetsPath.empty () == true)
  340. {
  341. try
  342. {
  343. assetsPath = Steam::FileSystem::appDirectory (APP_DIRECTORY, "assets");
  344. }
  345. catch (std::runtime_error)
  346. {
  347. // set current path as assets' folder
  348. std::filesystem::path directory = std::filesystem::canonical ("/proc/self/exe")
  349. .parent_path () / "assets";
  350. }
  351. }
  352. else
  353. {
  354. sLog.out ("Found wallpaper engine's assets at ", assetsPath, " based on --assets-dir parameter");
  355. }
  356. if (assetsPath.empty () == true)
  357. sLog.exception ("Cannot determine a valid path for the wallpaper engine assets");
  358. // add containers to the list
  359. containers.add (new WallpaperEngine::Assets::CDirectory (assetsPath));
  360. // parse the project.json file
  361. auto project = WallpaperEngine::Core::CProject::fromFile ("project.json", &containers);
  362. // go to the right folder so the videos will play
  363. if (project->getWallpaper ()->is <WallpaperEngine::Core::CVideo> () == true)
  364. chdir (path.c_str ());
  365. // show properties if required
  366. for (auto cur : project->getProperties ())
  367. {
  368. // update the value of the property
  369. auto override = propertyOverrides.find (cur->getName ());
  370. if (override != propertyOverrides.end ())
  371. {
  372. sLog.out ("Applying override value for ", cur->getName ());
  373. cur->update (override->second);
  374. }
  375. if (shouldListPropertiesAndStop)
  376. sLog.out (cur->dump ());
  377. }
  378. // halt if the list-properties option was specified
  379. if (shouldListPropertiesAndStop)
  380. return 0;
  381. // attach signals so if a stop is requested the X11 resources are freed and the program shutsdown gracefully
  382. std::signal(SIGINT, signalhandler);
  383. std::signal(SIGTERM, signalhandler);
  384. // initialize sdl audio driver
  385. WallpaperEngine::Audio::Drivers::CSDLAudioDriver audioDriver;
  386. // initialize audio context
  387. WallpaperEngine::Audio::CAudioContext audioContext (&audioDriver);
  388. // initialize OpenGL driver
  389. WallpaperEngine::Render::Drivers::COpenGLDriver videoDriver ("Wallpaper Engine");
  390. // initialize render context
  391. WallpaperEngine::Render::CRenderContext context (screens, videoDriver, &containers);
  392. // initialize mouse support
  393. context.setMouse (new CMouseInput (videoDriver.getWindow ()));
  394. // ensure the context knows what wallpaper to render
  395. context.setWallpaper (
  396. WallpaperEngine::Render::CWallpaper::fromWallpaper (project->getWallpaper (), &context, &audioContext)
  397. );
  398. float startTime, endTime, minimumTime = 1.0f / maximumFPS;
  399. while (videoDriver.closeRequested () == false && g_KeepRunning == true)
  400. {
  401. // keep track of the previous frame's time
  402. g_TimeLast = g_Time;
  403. // calculate the current time value
  404. g_Time = videoDriver.getRenderTime ();
  405. // get the start time of the frame
  406. startTime = g_Time;
  407. // render the scene
  408. context.render ();
  409. // get the end time of the frame
  410. endTime = videoDriver.getRenderTime ();
  411. // ensure the frame time is correct to not overrun FPS
  412. if ((endTime - startTime) < minimumTime)
  413. usleep ((minimumTime - (endTime - startTime)) * CLOCKS_PER_SEC);
  414. if (shouldTakeScreenshot == true && videoDriver.getFrameCounter () == 5)
  415. {
  416. takeScreenshot (context.getWallpaper (), screenshotPath, screenshotFormat);
  417. // disable screenshot just in case the counter overflows
  418. shouldTakeScreenshot = false;
  419. }
  420. }
  421. // ensure this is updated as sometimes it might not come from a signal
  422. g_KeepRunning = false;
  423. sLog.out ("Stop requested");
  424. SDL_Quit ();
  425. return 0;
  426. }