#include "CApplicationContext.h" #include "Steam/FileSystem/FileSystem.h" #include "WallpaperEngine/Logging/CLog.h" #include #include #include #include #define WORKSHOP_APP_ID 431960 #define APP_DIRECTORY "wallpaper_engine" using namespace WallpaperEngine::Application; CApplicationContext::CApplicationContext (int argc, char* argv []) : m_argc (argc), m_argv (argv) { std::string lastScreen; argparse::ArgumentParser program ("linux-wallpaperengine", "0.0", argparse::default_arguments::help); auto& backgroundGroup = program.add_group ("Background options"); auto& backgroundMode = backgroundGroup.add_mutually_exclusive_group (false); backgroundGroup.add_argument ("background id") .help ("The background to use as default for screens with no background specified") .default_value ("") .action([this](const std::string& value) -> void { if (!value.empty()) { this->settings.general.defaultBackground = translateBackground (value); } }); backgroundMode.add_argument ("-w", "--window") .help ("Window geometry to use for the given screen") .action ([this](const std::string& value) -> void { if (this->settings.render.mode == DESKTOP_BACKGROUND) { sLog.exception ("Cannot run in both background and window mode"); } if (this->settings.render.mode == EXPLICIT_WINDOW) { sLog.exception ("Only one window at a time can be specified in explicit window mode"); } this->settings.render.mode = EXPLICIT_WINDOW; if (value.empty ()) { sLog.exception ("Window geometry cannot be empty"); } const char* str = value.c_str (); const char* delim1 = strchr (str, 'x'); const char* delim2 = delim1 ? strchr (delim1 + 1, 'x') : nullptr; const char* delim3 = delim2 ? strchr (delim2 + 1, 'x') : nullptr; if (delim1 == nullptr || delim2 == nullptr || delim3 == nullptr) { sLog.exception ("Window geometry must be in the format: XxYxWxH"); } this->settings.render.window.geometry.x = strtol(str, nullptr, 10); this->settings.render.window.geometry.y = strtol (delim1 + 1, nullptr, 10); this->settings.render.window.geometry.z = strtol (delim2 + 1, nullptr, 10); this->settings.render.window.geometry.w = strtol (delim3 + 1, nullptr, 10); }) .append (); backgroundMode.add_argument ("-r", "--screen-root") .help ("The screen the following settings will have an effect on") .action([this, &lastScreen](const std::string& value) -> void { if (this->settings.general.screenBackgrounds.find (value) != this->settings.general.screenBackgrounds.end ()) { sLog.exception ("Cannot specify the same screen more than once: ", value); } if (this->settings.render.mode == EXPLICIT_WINDOW) { sLog.exception ("Cannot run in both background and window mode"); } this->settings.render.mode = DESKTOP_BACKGROUND; lastScreen = value; this->settings.general.screenBackgrounds [lastScreen] = ""; this->settings.general.screenScalings [lastScreen] = this->settings.render.window.scalingMode; this->settings.general.screenClamps [lastScreen] = this->settings.render.window.clamp; }) .append (); backgroundGroup.add_argument ("-b", "--bg") .help ("After --screen-root, specifies the background to use for the given screen") .action ([this, &lastScreen](const std::string& value) -> void { this->settings.general.screenBackgrounds [lastScreen] = translateBackground (value); // set the default background to the last one used this->settings.general.defaultBackground = translateBackground (value); }) .append (); backgroundGroup.add_argument ("--scaling") .help ("Scaling mode to use when rendering the background, this applies to the previous --window or --screen-root output, or the default background if no other background is specified") .choices ("stretch", "fit", "fill", "default") .action([this, &lastScreen](const std::string& value) -> void { WallpaperEngine::Render::CWallpaperState::TextureUVsScaling mode; if (value == "stretch") { mode = WallpaperEngine::Render::CWallpaperState::TextureUVsScaling::StretchUVs; } else if (value == "fit") { mode = WallpaperEngine::Render::CWallpaperState::TextureUVsScaling::ZoomFitUVs; } else if (value == "fill") { mode = WallpaperEngine::Render::CWallpaperState::TextureUVsScaling::ZoomFillUVs; } else if (value == "default") { mode = WallpaperEngine::Render::CWallpaperState::TextureUVsScaling::DefaultUVs; } else { sLog.exception ("Invalid scaling mode: ", value); } if (this->settings.render.mode == DESKTOP_BACKGROUND) { this->settings.general.screenScalings [lastScreen] = mode; } else { this->settings.render.window.scalingMode = mode; } }) .append (); backgroundGroup.add_argument ("--clamp") .help ("Clamp mode to use when rendering the background, this applies to the previous --window or --screen-root output, or the default background if no other background is specified") .choices("clamp", "border", "repeat") .action([this, &lastScreen](const std::string& value) -> void { WallpaperEngine::Assets::ITexture::TextureFlags flags; if (value == "clamp") { flags = WallpaperEngine::Assets::ITexture::TextureFlags::ClampUVs; } else if (value == "border") { flags = WallpaperEngine::Assets::ITexture::TextureFlags::ClampUVsBorder; } else if (value == "repeat") { flags = WallpaperEngine::Assets::ITexture::TextureFlags::NoFlags; } else { sLog.exception ("Invalid clamp mode: ", value); } if (this->settings.render.mode == DESKTOP_BACKGROUND) { this->settings.general.screenClamps [lastScreen] = flags; } else { this->settings.render.window.clamp = flags; } }); auto& performanceGroup = program.add_group ("Performance options"); performanceGroup.add_argument ("-f", "--fps") .help ("Limits the FPS to the given number, useful to keep battery consumption low") .default_value (30) .store_into(this->settings.render.maximumFPS); performanceGroup.add_argument ("--no-fullscreen-pause") .help ("Prevents the background pausing when an app is fullscreen") .flag () .action ([this](const std::string& value) -> void { this->settings.render.pauseOnFullscreen = false; }); auto& audioGroup = program.add_group ("Sound settings"); auto& audioSettingsGroup = audioGroup.add_mutually_exclusive_group (false); audioSettingsGroup.add_argument ("-v", "--volume") .help ("Volume for all the sounds in the background") .default_value (15) .store_into (this->settings.audio.volume); audioSettingsGroup.add_argument ("-s", "--silent") .help ("Mutes all the sound the wallpaper might produce") .flag () .action ([this](const std::string& value) -> void { this->settings.audio.enabled = false; }); audioGroup.add_argument ("--noautomute") .help ("Disables the automute when an app is playing sound") .flag () .action([this](const std::string& value) -> void { this->settings.audio.automute = false; }); audioGroup.add_argument ("--no-audio-processing") .help ("Disables audio processing for backgrounds") .flag () .action ([this](const std::string& value) -> void { this->settings.audio.audioprocessing = false; }); auto& screenshotGroup = program.add_group ("Screenshot options"); screenshotGroup.add_argument ("--screenshot") .help ("Takes a screenshot of the background for it's use with tools like PyWAL") .default_value ("") .action ([this](const std::string& value) -> void { this->settings.screenshot.take = true; this->settings.screenshot.path = value; }); screenshotGroup.add_argument ("--screenshot-delay") .help ("Frames to wait before taking the screenshot") .default_value (5) .store_into (this->settings.screenshot.delay); auto& contentGroup = program.add_group ("Content options"); contentGroup.add_argument ("--assets-dir") .help ("Folder where the assets are stored") .default_value ("") .action ([this](const std::string& value) -> void { this->settings.general.assets = value; }); auto& configurationGroup = program.add_group ("Wallpaper configuration options"); configurationGroup.add_argument ("--disable-mouse") .help ("Disables mouse interaction with the backgrounds") .flag () .action ([this](const std::string& value) -> void { this->settings.mouse.enabled = false; }); configurationGroup.add_argument ("--disable-parallax") .help ("Disables parallax effect for the backgrounds") .flag () .action ([this](const std::string& value) -> void { this->settings.mouse.disableparallax = true; }); configurationGroup.add_argument ("-l", "--list-properties") .help ("List all the available properties and their configuration") .flag () .store_into (this->settings.general.onlyListProperties); configurationGroup.add_argument ("--set-property", "--property") .help ("Overrides the default value of the given property") .action([this](const std::string& value) -> void { const std::string::size_type equals = value.find ('='); // properties without value are treated as booleans for now if (equals == std::string::npos) this->settings.general.properties [value] = "1"; else this->settings.general.properties [value.substr (0, equals)] = value.substr (equals + 1); }); auto& debuggingGroup = program.add_group ("Debugging options"); debuggingGroup.add_argument ("-z", "--dump-structure") .help ("Dumps the structure of the backgrounds") .flag () .store_into (this->settings.general.dumpStructure); program.add_epilog ( "Usage examples:\n" " linux-wallpaperengine --screen-root HDMI-1 --bg 2317494988 --scaling fill --clamp border\n" " Runs the background 2317494988 on screen HDMI-1, scaling it to fill the screen and clamping the UVs to the border\n\n" " linux-wallpaperengine 2317494988\n" " Previews the background 2317494988 on a window\n\n" " linux-wallpaperengine --screen-root HDMI-1 --bg 2317494988 --screen-root HDMI-2 --bg 1108150151\n" " Runs two backgrounds on two screens, one on HDMI-1 and the other on HDMI-2\n\n" " linux-wallpaperengine --screen-root HDMI-1 --screen-root HDMI-2 2317494988\n" " Runs the background 2317494988 on two screens, one on HDMI-1 and the other on HDMI-2\n\n" ); program.parse_known_args (argc, argv); if (this->settings.general.defaultBackground.empty ()) { throw std::runtime_error ("No default background specified. Either --bg/-b or must be specified"); } this->settings.audio.volume = std::max(0, std::min (this->settings.audio.volume, 128)); this->settings.screenshot.delay = std::max (0, std::min (this->settings.screenshot.delay, 5)); // use std::cout on this in case logging is disabled, this way it's easy to look at what is running std::stringbuf buffer; std::ostream bufferStream (&buffer); bufferStream << "Running with: "; for (int i = 0; i < argc; i ++) { bufferStream << argv [i]; bufferStream << " "; } std::cout << buffer.str() << std::endl; // perform some extra validation on the inputs this->validateAssets (); this->validateScreenshot (); // setup application state this->state.general.keepRunning = true; this->state.audio.enabled = this->settings.audio.enabled; this->state.audio.volume = this->settings.audio.volume; this->state.mouse.enabled = this->settings.mouse.enabled; #if DEMOMODE sLog.error ("WARNING: RUNNING IN DEMO MODE WILL STOP WALLPAPERS AFTER 5 SECONDS SO VIDEO CAN BE RECORDED"); // special settings for demomode this->settings.render.maximumFPS = 30; this->settings.screenshot.take = false; this->settings.render.pauseOnFullscreen = false; #endif /* DEMOMODE */ } int CApplicationContext::getArgc () const { return this->m_argc; } char** CApplicationContext::getArgv () const { return this->m_argv; } std::filesystem::path CApplicationContext::translateBackground (const std::string& bgIdOrPath) { if (bgIdOrPath.find ('/') == std::string::npos) return Steam::FileSystem::workshopDirectory (WORKSHOP_APP_ID, bgIdOrPath); return bgIdOrPath; } void CApplicationContext::validateAssets () { if (!this->settings.general.assets.empty ()) { sLog.out ("Using wallpaper engine's assets at ", this->settings.general.assets, " based on --assets-dir parameter"); return; } try { this->settings.general.assets = Steam::FileSystem::appDirectory (APP_DIRECTORY, "assets"); } catch (std::runtime_error&) { // set current path as assets' folder this->settings.general.assets = std::filesystem::canonical ("/proc/self/exe").parent_path () / "assets"; } } void CApplicationContext::validateScreenshot () const { if (!this->settings.screenshot.take) return; if (!this->settings.screenshot.path.has_extension ()) sLog.exception ("Cannot determine screenshot format"); const std::string extension = this->settings.screenshot.path.extension (); if (extension != ".bmp" && extension != ".png" && extension != ".jpeg" && extension != ".jpg") sLog.exception ("Cannot determine screenshot format, unknown extension ", extension); }