ApplicationContext.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654
  1. #include "ApplicationContext.h"
  2. #include "Steam/FileSystem/FileSystem.h"
  3. #include "WallpaperEngine/Data/JSON.h"
  4. #include "WallpaperEngine/Logging/Log.h"
  5. #include <algorithm>
  6. #include <cstdlib>
  7. #include <cstring>
  8. #include <fstream>
  9. #include <iostream>
  10. #include <optional>
  11. #include <stdexcept>
  12. #include <string_view>
  13. #include <argparse/argparse.hpp>
  14. #define WORKSHOP_APP_ID 431960
  15. #define APP_DIRECTORY "wallpaper_engine"
  16. using namespace WallpaperEngine::Application;
  17. using WallpaperEngine::Data::JSON::JSON;
  18. std::filesystem::path ApplicationContext::resolvePlaylistItemPath (const std::string& raw) const {
  19. if (raw.empty ()) {
  20. return {};
  21. }
  22. std::string cleaned = raw;
  23. constexpr std::string_view windowsPrefix = "\\\\?\\";
  24. if (cleaned.rfind (windowsPrefix, 0) == 0) {
  25. cleaned = cleaned.substr (windowsPrefix.length ());
  26. }
  27. std::replace (cleaned.begin (), cleaned.end (), '\\', '/');
  28. if (cleaned.size () > 1 && cleaned[1] == ':') {
  29. cleaned = cleaned.substr (2);
  30. }
  31. if (!cleaned.empty () && cleaned.front () != '/') {
  32. cleaned.insert (cleaned.begin (), '/');
  33. }
  34. std::filesystem::path path = std::filesystem::path (cleaned).lexically_normal ();
  35. if (std::filesystem::is_regular_file (path)) {
  36. path = path.parent_path ();
  37. }
  38. return path;
  39. }
  40. std::filesystem::path ApplicationContext::configFilePath () const {
  41. try {
  42. return Steam::FileSystem::appDirectory (APP_DIRECTORY, "") / "config.json";
  43. } catch (std::runtime_error&) {
  44. sLog.exception ("Cannot locate wallpaper engine installation to read config.json");
  45. return {};
  46. }
  47. }
  48. std::optional<JSON> ApplicationContext::parseConfigJson (const std::filesystem::path& path) const {
  49. if (path.empty ()) {
  50. return std::nullopt;
  51. }
  52. std::ifstream configFile (path);
  53. if (!configFile.is_open ()) {
  54. sLog.exception ("Cannot open wallpaper engine config file at ", path);
  55. return std::nullopt;
  56. }
  57. try {
  58. return JSON::parse (configFile);
  59. } catch (const std::exception& e) {
  60. sLog.error ("Failed parsing wallpaper engine config.json: ", e.what ());
  61. return std::nullopt;
  62. }
  63. }
  64. std::optional<ApplicationContext::PlaylistDefinition>
  65. ApplicationContext::buildPlaylistDefinition (const JSON& playlistJson, const std::string& fallbackName) const {
  66. PlaylistDefinition definition;
  67. definition.name = playlistJson.optional<std::string> ("name", fallbackName);
  68. definition.settings = this->parsePlaylistSettings (playlistJson);
  69. definition.items
  70. = this->collectPlaylistItems (playlistJson, definition.name.empty () ? fallbackName : definition.name);
  71. if (definition.items.empty ()) {
  72. return std::nullopt;
  73. }
  74. if (definition.name.empty ()) {
  75. if (fallbackName.empty ()) {
  76. sLog.error ("Skipping playlist with no name");
  77. return std::nullopt;
  78. }
  79. definition.name = fallbackName;
  80. }
  81. return definition;
  82. }
  83. ApplicationContext::PlaylistSettings ApplicationContext::parsePlaylistSettings (const JSON& playlistJson) const {
  84. PlaylistSettings settings;
  85. const auto settingsJson = playlistJson.optional ("settings");
  86. settings.delayMinutes = settingsJson ? settingsJson->optional<uint32_t> ("delay", 60) : 60;
  87. settings.mode = settingsJson ? settingsJson->optional<std::string> ("mode", "timer") : "timer";
  88. settings.order = settingsJson ? settingsJson->optional<std::string> ("order", "sequential") : "sequential";
  89. settings.updateOnPause = settingsJson ? settingsJson->optional<bool> ("updateonpause", false) : false;
  90. settings.videoSequence = settingsJson ? settingsJson->optional<bool> ("videosequence", false) : false;
  91. return settings;
  92. }
  93. std::vector<std::filesystem::path>
  94. ApplicationContext::collectPlaylistItems (const JSON& playlistJson, const std::string& name) const {
  95. std::vector<std::filesystem::path> items;
  96. const auto jsonItems = playlistJson.optional ("items");
  97. if (!jsonItems.has_value () || !jsonItems->is_array ()) {
  98. sLog.error ("Skipping playlist ", name, ": missing items");
  99. return items;
  100. }
  101. for (const auto& rawItem : *jsonItems) {
  102. if (!rawItem.is_string ()) {
  103. continue;
  104. }
  105. auto resolvedPath = this->resolvePlaylistItemPath (rawItem.get<std::string> ());
  106. if (resolvedPath.empty ()) {
  107. continue;
  108. }
  109. if (!std::filesystem::exists (resolvedPath)) {
  110. sLog.error ("Skipping playlist item not found: ", resolvedPath.string ());
  111. continue;
  112. }
  113. items.push_back (resolvedPath);
  114. }
  115. if (items.empty ()) {
  116. sLog.error ("Skipping playlist ", name, ": no usable items found");
  117. }
  118. return items;
  119. }
  120. void ApplicationContext::registerPlaylist (PlaylistDefinition&& definition) {
  121. this->m_configPlaylists.insert_or_assign (definition.name, std::move (definition));
  122. }
  123. void ApplicationContext::loadPlaylistsFromConfig () {
  124. if (this->m_loadedConfigPlaylists) {
  125. return;
  126. }
  127. this->m_loadedConfigPlaylists = true;
  128. const auto configPath = this->configFilePath ();
  129. const auto root = this->parseConfigJson (configPath);
  130. if (!root.has_value ()) {
  131. return;
  132. }
  133. const auto steamUser = root->optional ("steamuser");
  134. if (!steamUser.has_value ()) {
  135. sLog.exception ("Cannot find steamuser section in config.json");
  136. }
  137. auto addPlaylist = [this] (const JSON& playlistJson, const std::string& fallbackName) {
  138. auto definition = this->buildPlaylistDefinition (playlistJson, fallbackName);
  139. if (!definition) {
  140. return;
  141. }
  142. this->registerPlaylist (std::move (*definition));
  143. };
  144. if (const auto general = steamUser->optional ("general")) {
  145. if (const auto playlists = general->optional ("playlists")) {
  146. for (const auto& playlist : *playlists) {
  147. try {
  148. addPlaylist (playlist, playlist.optional<std::string> ("name", ""));
  149. } catch (const std::exception& e) {
  150. sLog.error ("Failed parsing playlist: ", e.what ());
  151. }
  152. }
  153. }
  154. }
  155. if (const auto wallpaperConfig = steamUser->optional ("wallpaperconfig")) {
  156. if (const auto selected = wallpaperConfig->optional ("selectedwallpapers")) {
  157. for (const auto& entry : selected->items ()) {
  158. const auto playlist = entry.value ().optional ("playlist");
  159. if (!playlist.has_value ()) {
  160. continue;
  161. }
  162. try {
  163. addPlaylist (*playlist, entry.key ());
  164. } catch (const std::exception& e) {
  165. sLog.error ("Failed parsing playlist for ", entry.key (), ": ", e.what ());
  166. }
  167. }
  168. }
  169. }
  170. }
  171. const ApplicationContext::PlaylistDefinition& ApplicationContext::getPlaylistFromConfig (const std::string& name) {
  172. if (!this->m_loadedConfigPlaylists) {
  173. this->loadPlaylistsFromConfig ();
  174. }
  175. const auto cur = this->m_configPlaylists.find (name);
  176. if (cur == this->m_configPlaylists.end ()) {
  177. std::string available;
  178. for (auto it = this->m_configPlaylists.begin (); it != this->m_configPlaylists.end (); ++it) {
  179. available += it->first;
  180. if (std::next (it) != this->m_configPlaylists.end ()) {
  181. available += ", ";
  182. }
  183. }
  184. const std::string availableText = available.empty () ? "" : std::string (". Available: ") + available;
  185. sLog.exception ("Playlist not found in config.json: ", name, availableText);
  186. throw std::runtime_error ("Playlist not found: " + name);
  187. }
  188. return cur->second;
  189. }
  190. ApplicationContext::ApplicationContext (int argc, char* argv[]) : m_argc (argc), m_argv (argv) { }
  191. void ApplicationContext::loadSettingsFromArgv () {
  192. std::string lastScreen;
  193. argparse::ArgumentParser program ("linux-wallpaperengine", "0.0", argparse::default_arguments::help);
  194. auto& backgroundGroup = program.add_group ("Background options");
  195. auto& backgroundMode = backgroundGroup.add_mutually_exclusive_group (false);
  196. backgroundGroup.add_argument ("background id")
  197. .help ("The background to use as default for screens with no background specified")
  198. .default_value ("")
  199. .action ([this] (const std::string& value) -> void {
  200. if (!value.empty ()) {
  201. this->settings.general.defaultBackground = translateBackground (value);
  202. }
  203. });
  204. backgroundMode.add_argument ("-w", "--window")
  205. .help ("Window geometry to use for the given screen")
  206. .action ([this] (const std::string& value) -> void {
  207. if (this->settings.render.mode == DESKTOP_BACKGROUND) {
  208. sLog.exception ("Cannot run in both background and window mode");
  209. }
  210. if (this->settings.render.mode == EXPLICIT_WINDOW) {
  211. sLog.exception ("Only one window at a time can be specified in explicit window mode");
  212. }
  213. this->settings.render.mode = EXPLICIT_WINDOW;
  214. if (value.empty ()) {
  215. sLog.exception ("Window geometry cannot be empty");
  216. }
  217. const char* str = value.c_str ();
  218. const char* delim1 = strchr (str, 'x');
  219. const char* delim2 = delim1 ? strchr (delim1 + 1, 'x') : nullptr;
  220. const char* delim3 = delim2 ? strchr (delim2 + 1, 'x') : nullptr;
  221. if (delim1 == nullptr || delim2 == nullptr || delim3 == nullptr) {
  222. sLog.exception ("Window geometry must be in the format: XxYxWxH");
  223. }
  224. this->settings.render.window.geometry.x = strtol (str, nullptr, 10);
  225. this->settings.render.window.geometry.y = strtol (delim1 + 1, nullptr, 10);
  226. this->settings.render.window.geometry.z = strtol (delim2 + 1, nullptr, 10);
  227. this->settings.render.window.geometry.w = strtol (delim3 + 1, nullptr, 10);
  228. })
  229. .append ();
  230. backgroundMode.add_argument ("-r", "--screen-root")
  231. .help ("The screen the following settings will have an effect on")
  232. .action ([this, &lastScreen] (const std::string& value) -> void {
  233. if (this->settings.general.screenBackgrounds.find (value)
  234. != this->settings.general.screenBackgrounds.end ()) {
  235. sLog.exception ("Cannot specify the same screen more than once: ", value);
  236. }
  237. if (this->settings.render.mode == EXPLICIT_WINDOW) {
  238. sLog.exception ("Cannot run in both background and window mode");
  239. }
  240. this->settings.render.mode = DESKTOP_BACKGROUND;
  241. lastScreen = value;
  242. this->settings.general.screenBackgrounds[lastScreen] = "";
  243. this->settings.general.screenScalings[lastScreen] = this->settings.render.window.scalingMode;
  244. this->settings.general.screenClamps[lastScreen] = this->settings.render.window.clamp;
  245. })
  246. .append ();
  247. backgroundGroup.add_argument ("-b", "--bg")
  248. .help ("After --screen-root, specifies the background to use for the given screen")
  249. .action ([this, &lastScreen] (const std::string& value) -> void {
  250. this->settings.general.screenBackgrounds[lastScreen] = translateBackground (value);
  251. // set the default background to the last one used
  252. this->settings.general.defaultBackground = translateBackground (value);
  253. })
  254. .append ();
  255. backgroundGroup.add_argument ("--playlist")
  256. .help (
  257. "Uses a playlist from wallpaper engine's config.json. If used after --screen-root it is applied to that "
  258. "screen, otherwise it is used in window mode."
  259. )
  260. .action ([this, &lastScreen] (const std::string& value) -> void {
  261. const auto& playlist = this->getPlaylistFromConfig (value);
  262. if (lastScreen.empty ()) {
  263. this->settings.general.defaultPlaylist = playlist;
  264. if (this->settings.general.defaultBackground.empty () && !playlist.items.empty ()) {
  265. this->settings.general.defaultBackground = playlist.items.front ();
  266. }
  267. } else {
  268. this->settings.general.screenPlaylists[lastScreen] = playlist;
  269. if (!playlist.items.empty ()) {
  270. this->settings.general.screenBackgrounds[lastScreen] = playlist.items.front ();
  271. }
  272. if (this->settings.general.defaultBackground.empty () && !playlist.items.empty ()) {
  273. this->settings.general.defaultBackground = playlist.items.front ();
  274. }
  275. }
  276. })
  277. .append ();
  278. backgroundGroup.add_argument ("--scaling")
  279. .help (
  280. "Scaling mode to use when rendering the background, this applies to the previous --window or --screen-root "
  281. "output, or the default background if no other background is specified"
  282. )
  283. .choices ("stretch", "fit", "fill", "default")
  284. .action ([this, &lastScreen] (const std::string& value) -> void {
  285. WallpaperEngine::Render::WallpaperState::TextureUVsScaling mode;
  286. if (value == "stretch") {
  287. mode = WallpaperEngine::Render::WallpaperState::TextureUVsScaling::StretchUVs;
  288. } else if (value == "fit") {
  289. mode = WallpaperEngine::Render::WallpaperState::TextureUVsScaling::ZoomFitUVs;
  290. } else if (value == "fill") {
  291. mode = WallpaperEngine::Render::WallpaperState::TextureUVsScaling::ZoomFillUVs;
  292. } else if (value == "default") {
  293. mode = WallpaperEngine::Render::WallpaperState::TextureUVsScaling::DefaultUVs;
  294. } else {
  295. sLog.exception ("Invalid scaling mode: ", value);
  296. }
  297. if (this->settings.render.mode == DESKTOP_BACKGROUND) {
  298. this->settings.general.screenScalings[lastScreen] = mode;
  299. } else {
  300. this->settings.render.window.scalingMode = mode;
  301. }
  302. })
  303. .append ();
  304. backgroundGroup.add_argument ("--clamp")
  305. .help (
  306. "Clamp mode to use when rendering the background, this applies to the previous --window or --screen-root "
  307. "output, or the default background if no other background is specified"
  308. )
  309. .choices ("clamp", "border", "repeat")
  310. .action ([this, &lastScreen] (const std::string& value) -> void {
  311. TextureFlags flags;
  312. if (value == "clamp") {
  313. flags = TextureFlags_ClampUVs;
  314. } else if (value == "border") {
  315. flags = TextureFlags_ClampUVsBorder;
  316. } else if (value == "repeat") {
  317. flags = TextureFlags_NoFlags;
  318. } else {
  319. sLog.exception ("Invalid clamp mode: ", value);
  320. }
  321. if (this->settings.render.mode == DESKTOP_BACKGROUND) {
  322. this->settings.general.screenClamps[lastScreen] = flags;
  323. } else {
  324. this->settings.render.window.clamp = flags;
  325. }
  326. });
  327. auto& performanceGroup = program.add_group ("Performance options");
  328. performanceGroup.add_argument ("-f", "--fps")
  329. .help ("Limits the FPS to the given number, useful to keep battery consumption low")
  330. .default_value (30)
  331. .store_into (this->settings.render.maximumFPS);
  332. performanceGroup.add_argument ("--no-fullscreen-pause")
  333. .help ("Prevents the background pausing when an app is fullscreen")
  334. .flag ()
  335. .action ([this] (const std::string& value) -> void { this->settings.render.pauseOnFullscreen = false; });
  336. performanceGroup.add_argument ("--fullscreen-pause-only-active")
  337. .help ("Wayland only: pause only when a fullscreen window is active (activated)")
  338. .flag ()
  339. .action ([this] (const std::string& value) -> void {
  340. this->settings.render.pauseOnFullscreenOnlyWhenActive = true;
  341. });
  342. performanceGroup.add_argument ("--fullscreen-pause-ignore-appid")
  343. .help ("Wayland only: ignore fullscreen windows whose app_id contains this value (repeatable)")
  344. .action ([this] (const std::string& value) -> void {
  345. if (!value.empty ()) {
  346. this->settings.render.fullscreenPauseIgnoreAppIds.push_back (value);
  347. }
  348. })
  349. .append ();
  350. auto& audioGroup = program.add_group ("Sound settings");
  351. auto& audioSettingsGroup = audioGroup.add_mutually_exclusive_group (false);
  352. audioSettingsGroup.add_argument ("-v", "--volume")
  353. .help ("Volume for all the sounds in the background")
  354. .default_value (15)
  355. .store_into (this->settings.audio.volume);
  356. audioSettingsGroup.add_argument ("-s", "--silent")
  357. .help ("Mutes all the sound the wallpaper might produce")
  358. .flag ()
  359. .action ([this] (const std::string& value) -> void { this->settings.audio.enabled = false; });
  360. audioGroup.add_argument ("--noautomute")
  361. .help ("Disables the automute when an app is playing sound")
  362. .flag ()
  363. .action ([this] (const std::string& value) -> void { this->settings.audio.automute = false; });
  364. audioGroup.add_argument ("--no-audio-processing")
  365. .help ("Disables audio processing for backgrounds")
  366. .flag ()
  367. .action ([this] (const std::string& value) -> void { this->settings.audio.audioprocessing = false; });
  368. auto& screenshotGroup = program.add_group ("Screenshot options");
  369. screenshotGroup.add_argument ("--screenshot")
  370. .help ("Takes a screenshot of the background for it's use with tools like PyWAL")
  371. .default_value ("")
  372. .action ([this] (const std::string& value) -> void {
  373. this->settings.screenshot.take = true;
  374. this->settings.screenshot.path = value;
  375. });
  376. screenshotGroup.add_argument ("--screenshot-delay")
  377. .help ("Frames to wait before taking the screenshot")
  378. .default_value<uint32_t> (5)
  379. .store_into (this->settings.screenshot.delay);
  380. auto& contentGroup = program.add_group ("Content options");
  381. contentGroup.add_argument ("--assets-dir")
  382. .help ("Folder where the assets are stored")
  383. .default_value ("")
  384. .action ([this] (const std::string& value) -> void { this->settings.general.assets = value; });
  385. auto& configurationGroup = program.add_group ("Wallpaper configuration options");
  386. configurationGroup.add_argument ("--disable-particles")
  387. .help ("Disables particles for the backgrounds")
  388. .flag ()
  389. .action ([this] (const std::string& value) -> void { this->settings.general.disableParticles = true; });
  390. configurationGroup.add_argument ("--disable-mouse")
  391. .help ("Disables mouse interaction with the backgrounds")
  392. .flag ()
  393. .action ([this] (const std::string& value) -> void { this->settings.mouse.enabled = false; });
  394. configurationGroup.add_argument ("--disable-parallax")
  395. .help ("Disables parallax effect for the backgrounds")
  396. .flag ()
  397. .action ([this] (const std::string& value) -> void { this->settings.mouse.disableparallax = true; });
  398. configurationGroup.add_argument ("-l", "--list-properties")
  399. .help ("List all the available properties and their configuration")
  400. .flag ()
  401. .store_into (this->settings.general.onlyListProperties);
  402. configurationGroup.add_argument ("--set-property", "--property")
  403. .help ("Overrides the default value of the given property")
  404. .action ([this] (const std::string& value) -> void {
  405. const std::string::size_type equals = value.find ('=');
  406. // properties without value are treated as booleans for now
  407. if (equals == std::string::npos) {
  408. this->settings.general.properties[value] = "1";
  409. } else {
  410. this->settings.general.properties[value.substr (0, equals)] = value.substr (equals + 1);
  411. }
  412. })
  413. .append ();
  414. auto& debuggingGroup = program.add_group ("Debugging options");
  415. debuggingGroup.add_argument ("-z", "--dump-structure")
  416. .help ("Dumps the structure of the backgrounds")
  417. .flag ()
  418. .store_into (this->settings.general.dumpStructure);
  419. debuggingGroup.add_argument ("--render-debug")
  420. .help (
  421. "Scene render debug mode: base-only, no-solid-final, pass-log, object=<id>, skip-object=<id>, or skip-effect=<id>. Can be repeated."
  422. )
  423. .action ([this] (const std::string& value) -> void {
  424. const auto parseDebugId = [&value] (const std::string& prefix) -> std::optional<int> {
  425. try {
  426. return std::stoi (value.substr (prefix.length ()));
  427. } catch (const std::invalid_argument&) {
  428. sLog.exception ("Invalid numeric value for --render-debug ", value);
  429. } catch (const std::out_of_range&) {
  430. sLog.exception ("Out-of-range numeric value for --render-debug ", value);
  431. }
  432. return std::nullopt;
  433. };
  434. if (value == "base-only") {
  435. this->settings.render.debug.baseOnly = true;
  436. } else if (value == "no-solid-final") {
  437. this->settings.render.debug.noSolidFinal = true;
  438. } else if (value == "pass-log") {
  439. this->settings.render.debug.passLog = true;
  440. } else if (value.rfind ("object=", 0) == 0) {
  441. this->settings.render.debug.objectFilter = parseDebugId ("object=");
  442. } else if (value.rfind ("skip-object=", 0) == 0) {
  443. if (const auto id = parseDebugId ("skip-object="); id.has_value ()) {
  444. this->settings.render.debug.skipObjects.emplace_back (*id);
  445. }
  446. } else if (value.rfind ("skip-effect=", 0) == 0) {
  447. if (const auto id = parseDebugId ("skip-effect="); id.has_value ()) {
  448. this->settings.render.debug.skipEffects.emplace_back (*id);
  449. }
  450. } else {
  451. sLog.exception ("Invalid render debug mode: ", value);
  452. }
  453. })
  454. .append ();
  455. program.add_epilog (
  456. "Usage examples:\n"
  457. " linux-wallpaperengine --screen-root HDMI-1 --bg 2317494988 --scaling fill --clamp border\n"
  458. " Runs the background 2317494988 on screen HDMI-1, scaling it to fill the screen and clamping the UVs to "
  459. "the border\n\n"
  460. " linux-wallpaperengine 2317494988\n"
  461. " Previews the background 2317494988 on a window\n\n"
  462. " linux-wallpaperengine --screen-root HDMI-1 --bg 2317494988 --screen-root HDMI-2 --bg 1108150151\n"
  463. " Runs two backgrounds on two screens, one on HDMI-1 and the other on HDMI-2\n\n"
  464. " linux-wallpaperengine --screen-root HDMI-1 --screen-root HDMI-2 2317494988\n"
  465. " Runs the background 2317494988 on two screens, one on HDMI-1 and the other on HDMI-2\n\n"
  466. );
  467. try {
  468. program.parse_known_args (this->m_argc, this->m_argv);
  469. if (this->settings.general.defaultBackground.empty ()) {
  470. throw std::runtime_error ("At least one background ID must be specified");
  471. }
  472. this->settings.audio.volume = std::max (0, std::min (this->settings.audio.volume, 128));
  473. this->settings.screenshot.delay
  474. = std::max<uint32_t> (0, std::min<uint32_t> (this->settings.screenshot.delay, 5));
  475. // use std::cout on this in case logging is disabled, this way it's easy to look at what is running
  476. std::stringbuf buffer;
  477. std::ostream bufferStream (&buffer);
  478. bufferStream << "Running with: ";
  479. for (int i = 0; i < this->m_argc; i++) {
  480. bufferStream << this->m_argv[i];
  481. bufferStream << " ";
  482. }
  483. std::cout << buffer.str () << std::endl;
  484. // perform some extra validation on the inputs
  485. this->validateAssets ();
  486. this->validateScreenshot ();
  487. // setup application state
  488. this->state.general.keepRunning = true;
  489. this->state.audio.enabled = this->settings.audio.enabled;
  490. this->state.audio.volume = this->settings.audio.volume;
  491. this->state.mouse.enabled = this->settings.mouse.enabled;
  492. #if DEMOMODE
  493. sLog.error ("WARNING: RUNNING IN DEMO MODE WILL STOP WALLPAPERS AFTER 5 SECONDS SO VIDEO CAN BE RECORDED");
  494. // special settings for demomode
  495. this->settings.render.maximumFPS = 30;
  496. this->settings.screenshot.take = false;
  497. this->settings.render.pauseOnFullscreen = false;
  498. #endif /* DEMOMODE */
  499. } catch (const std::runtime_error& e) {
  500. throw std::runtime_error (
  501. std::string (e.what ()) + ". Use " + std::string (this->m_argv[0]) + " --help for more information"
  502. );
  503. }
  504. }
  505. int ApplicationContext::getArgc () const { return this->m_argc; }
  506. char** ApplicationContext::getArgv () const { return this->m_argv; }
  507. std::filesystem::path ApplicationContext::translateBackground (const std::string& bgIdOrPath) {
  508. if (bgIdOrPath.find ('/') == std::string::npos) {
  509. return Steam::FileSystem::workshopDirectory (WORKSHOP_APP_ID, bgIdOrPath);
  510. }
  511. return bgIdOrPath;
  512. }
  513. void ApplicationContext::validateAssets () {
  514. if (!this->settings.general.assets.empty ()) {
  515. sLog.out (
  516. "Using wallpaper engine's assets at ", this->settings.general.assets, " based on --assets-dir parameter"
  517. );
  518. return;
  519. }
  520. try {
  521. this->settings.general.assets = Steam::FileSystem::appDirectory (APP_DIRECTORY, "assets");
  522. } catch (std::runtime_error&) {
  523. // set current path as assets' folder
  524. this->settings.general.assets = std::filesystem::canonical ("/proc/self/exe").parent_path () / "assets";
  525. }
  526. }
  527. void ApplicationContext::validateScreenshot () const {
  528. if (!this->settings.screenshot.take) {
  529. return;
  530. }
  531. if (!this->settings.screenshot.path.has_extension ()) {
  532. sLog.exception ("Cannot determine screenshot format");
  533. }
  534. const std::string extension = this->settings.screenshot.path.extension ();
  535. if (extension != ".bmp" && extension != ".png" && extension != ".jpeg" && extension != ".jpg") {
  536. sLog.exception ("Cannot determine screenshot format, unknown extension ", extension);
  537. }
  538. }