#include "CWallpaperApplication.h" #include "Steam/FileSystem/FileSystem.h" #include "WallpaperEngine/Assets/CDirectory.h" #include "WallpaperEngine/Assets/CVirtualContainer.h" #include "WallpaperEngine/Core/CVideo.h" #include "WallpaperEngine/Logging/CLog.h" #include "WallpaperEngine/Render/CRenderContext.h" #include "WallpaperEngine/Application/CApplicationState.h" #include "WallpaperEngine/Audio/Drivers/Detectors/CPulseAudioPlayingDetector.h" #include float g_Time; float g_TimeLast; float g_Daytime; namespace WallpaperEngine::Application { CWallpaperApplication::CWallpaperApplication (CApplicationContext& context) : m_context (context), m_defaultBackground (nullptr) { this->loadBackgrounds (); this->setupProperties (); } void CWallpaperApplication::setupContainer (CCombinedContainer& container, const std::string& bg) const { std::filesystem::path basepath = bg; container.add (new CDirectory (basepath)); container.addPkg (basepath / "scene.pkg"); container.addPkg (basepath / "gifscene.pkg"); container.add (new CDirectory (this->m_context.settings.general.assets)); // add two possible patches directories to the container // hopefully one sticks bool relative = true; bool absolute = true; try { container.add (new CDirectory ("../share/")); } catch (std::runtime_error& ex) { relative = false; } try { container.add (new CDirectory (DATADIR)); } catch (std::runtime_error& ex) { absolute = false; } if (!relative && !absolute) sLog.error ( "WARNING: Shader patches directory cannot be found, this might make some backgrounds not work " "properly" ); // TODO: move this somewhere else? auto* virtualContainer = new CVirtualContainer (); // // Had to get a little creative with the effects to achieve the same bloom effect without any custom code // these virtual files are loaded by an image in the scene that takes current _rt_FullFrameBuffer and // applies the bloom effect to render it out to the screen // // add the effect file for screen bloom // add some model for the image element even if it's going to waste rendering cycles virtualContainer->add ( "effects/wpenginelinux/bloomeffect.json", "{" "\t\"name\":\"camerabloom_wpengine_linux\"," "\t\"group\":\"wpengine_linux_camera\"," "\t\"dependencies\":[]," "\t\"passes\":" "\t[" "\t\t{" "\t\t\t\"material\": \"materials/util/downsample_quarter_bloom.json\"," "\t\t\t\"target\": \"_rt_4FrameBuffer\"," "\t\t\t\"bind\":" "\t\t\t[" "\t\t\t\t{" "\t\t\t\t\t\"name\": \"_rt_FullFrameBuffer\"," "\t\t\t\t\t\"index\": 0" "\t\t\t\t}" "\t\t\t]" "\t\t}," "\t\t{" "\t\t\t\"material\": \"materials/util/downsample_eighth_blur_v.json\"," "\t\t\t\"target\": \"_rt_8FrameBuffer\"," "\t\t\t\"bind\":" "\t\t\t[" "\t\t\t\t{" "\t\t\t\t\t\"name\": \"_rt_4FrameBuffer\"," "\t\t\t\t\t\"index\": 0" "\t\t\t\t}" "\t\t\t]" "\t\t}," "\t\t{" "\t\t\t\"material\": \"materials/util/blur_h_bloom.json\"," "\t\t\t\"target\": \"_rt_Bloom\"," "\t\t\t\"bind\":" "\t\t\t[" "\t\t\t\t{" "\t\t\t\t\t\"name\": \"_rt_8FrameBuffer\"," "\t\t\t\t\t\"index\": 0" "\t\t\t\t}" "\t\t\t]" "\t\t}," "\t\t{" "\t\t\t\"material\": \"materials/util/combine.json\"," "\t\t\t\"target\": \"_rt_FullFrameBuffer\"," "\t\t\t\"bind\":" "\t\t\t[" "\t\t\t\t{" "\t\t\t\t\t\"name\": \"_rt_imageLayerComposite_-1_a\"," "\t\t\t\t\t\"index\": 0" "\t\t\t\t}," "\t\t\t\t{" "\t\t\t\t\t\"name\": \"_rt_Bloom\"," "\t\t\t\t\t\"index\": 1" "\t\t\t\t}" "\t\t\t]" "\t\t}" "\t]" "}" ); virtualContainer->add ( "models/wpenginelinux.json", "{" "\t\"material\":\"materials/wpenginelinux.json\"" "}" ); // models require materials, so add that too virtualContainer->add ( "materials/wpenginelinux.json", "{" "\t\"passes\":" "\t\t[" "\t\t\t{" "\t\t\t\t\"blending\": \"normal\"," "\t\t\t\t\"cullmode\": \"nocull\"," "\t\t\t\t\"depthtest\": \"disabled\"," "\t\t\t\t\"depthwrite\": \"disabled\"," "\t\t\t\t\"shader\": \"genericimage2\"," "\t\t\t\t\"textures\": [\"_rt_FullFrameBuffer\"]" "\t\t\t}" "\t\t]" "}" ); container.add (virtualContainer); } void CWallpaperApplication::loadBackgrounds () { for (const auto& it : this->m_context.settings.general.screenBackgrounds) { // ignore the screen settings if there was no background specified // the default will be used if (it.second.empty ()) continue; this->m_backgrounds[it.first] = this->loadBackground (it.second); } // load the default project if required if (!this->m_context.settings.general.defaultBackground.empty ()) this->m_defaultBackground = this->loadBackground (this->m_context.settings.general.defaultBackground); } Core::CProject* CWallpaperApplication::loadBackground (const std::string& bg) { auto* container = new CCombinedContainer (); this->setupContainer (*container, bg); return Core::CProject::fromFile ("project.json", container); } void CWallpaperApplication::setupPropertiesForProject (Core::CProject* project) { // show properties if required for (auto cur : project->getProperties ()) { // update the value of the property auto override = this->m_context.settings.general.properties.find (cur->getName ()); if (override != this->m_context.settings.general.properties.end ()) { sLog.out ("Applying override value for ", cur->getName ()); cur->update (override->second); } if (this->m_context.settings.general.onlyListProperties) sLog.out (cur->dump ()); } } void CWallpaperApplication::setupProperties () { for (const auto& it : this->m_backgrounds) this->setupPropertiesForProject (it.second); if (this->m_defaultBackground != nullptr) this->setupPropertiesForProject (this->m_defaultBackground); } void CWallpaperApplication::takeScreenshot ( const Render::CRenderContext& context, const std::filesystem::path& filename, FREE_IMAGE_FORMAT format ) { // this should be getting called at the end of the frame, so the right thing should be bound already int width = context.getOutput ()->getFullWidth (); int height = context.getOutput ()->getFullHeight (); // make room for storing the pixel data uint8_t* buffer = new uint8_t[width * height * sizeof (uint8_t) * 3]; uint8_t* pixel = buffer; // read the image into the buffer glReadPixels (0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, buffer); // build the output file with FreeImage FIBITMAP* bitmap = FreeImage_Allocate (width, height, 24); RGBQUAD color; // now get access to the pixels for (int y = height; y > 0; y--) { for (int x = 0; x < width; x++) { color.rgbRed = *pixel++; color.rgbGreen = *pixel++; color.rgbBlue = *pixel++; // set the pixel in the destination FreeImage_SetPixelColor (bitmap, x, y, &color); } } // finally save the file FreeImage_Save (format, bitmap, filename.c_str (), 0); // free all the used memory delete[] buffer; FreeImage_Unload (bitmap); } void CWallpaperApplication::show () { // initialize OpenGL driver #ifdef ENABLE_WAYLAND const bool WAYLAND = getenv("WAYLAND_DISPLAY") && this->m_context.settings.render.mode == CApplicationContext::WAYLAND_LAYER_SHELL; if (WAYLAND) { videoDriver = std::make_unique("wallpaperengine", this->m_context, this); inputContext = std::make_unique(*(WallpaperEngine::Render::Drivers::CWaylandOpenGLDriver*)videoDriver.get()); } else { #endif videoDriver = std::make_unique("wallpaperengine", this->m_context); inputContext = std::make_unique(*(WallpaperEngine::Render::Drivers::CX11OpenGLDriver*)videoDriver.get()); #ifdef ENABLE_WAYLAND } if (WAYLAND) fullscreenDetector = std::make_unique(this->m_context, *(WallpaperEngine::Render::Drivers::CWaylandOpenGLDriver*)videoDriver.get()); else #endif fullscreenDetector = std::make_unique(this->m_context, *videoDriver); // stereo mix recorder for audio processing WallpaperEngine::Audio::Drivers::Recorders::CPulseAudioPlaybackRecorder audioRecorder; // audio playing detector WallpaperEngine::Audio::Drivers::Detectors::CPulseAudioPlayingDetector audioDetector (this->m_context, *fullscreenDetector); // initialize sdl audio driver audioDriver = std::make_unique (this->m_context, audioDetector, audioRecorder); // initialize audio context audioContext = std::make_unique (*audioDriver); // initialize the requested output switch (this->m_context.settings.render.mode) { case CApplicationContext::EXPLICIT_WINDOW: case CApplicationContext::NORMAL_WINDOW: output = new WallpaperEngine::Render::Drivers::Output::CGLFWWindowOutput (this->m_context, *videoDriver, *fullscreenDetector); break; case CApplicationContext::X11_BACKGROUND: output = new WallpaperEngine::Render::Drivers::Output::CX11Output (this->m_context, *videoDriver, *fullscreenDetector); break; #ifdef ENABLE_WAYLAND case CApplicationContext::WAYLAND_LAYER_SHELL: output = new WallpaperEngine::Render::Drivers::Output::CWaylandOutput (this->m_context, *videoDriver, *fullscreenDetector); break; #endif } // initialize render context context = std::make_unique (output, *videoDriver, *inputContext, *this); // set all the specific wallpapers required for (const auto& it : this->m_backgrounds) context->setWallpaper ( it.first, WallpaperEngine::Render::CWallpaper::fromWallpaper (it.second->getWallpaper (), *context, *audioContext) ); // set the default rendering wallpaper if available if (this->m_defaultBackground != nullptr) context->setDefaultWallpaper (WallpaperEngine::Render::CWallpaper::fromWallpaper ( this->m_defaultBackground->getWallpaper (), *context, *audioContext )); #ifdef ENABLE_WAYLAND if (WAYLAND) { renderFrame(); while (this->m_context.state.general.keepRunning && !videoDriver->closeRequested ()) { videoDriver->dispatchEventQueue(); } } else { #endif while (!videoDriver->closeRequested () && this->m_context.state.general.keepRunning) { renderFrame(); } #ifdef ENABLE_WAYLAND } #endif // ensure this is updated as sometimes it might not come from a signal this->m_context.state.general.keepRunning = false; sLog.out ("Stop requested"); SDL_Quit (); } void CWallpaperApplication::renderFrame() { static float startTime, endTime, minimumTime = 1.0f / this->m_context.settings.render.maximumFPS; static time_t seconds; static struct tm* timeinfo; // update g_Daytime time (&seconds); timeinfo = localtime(&seconds); g_Daytime = ((timeinfo->tm_hour * 60) + timeinfo->tm_min) / (24.0 * 60.0); // update audio recorder audioDriver->update (); // update input information inputContext->update (); // keep track of the previous frame's time g_TimeLast = g_Time; // calculate the current time value g_Time = videoDriver->getRenderTime (); // get the start time of the frame startTime = g_Time; // render the scene context->render (); // get the end time of the frame endTime = videoDriver->getRenderTime (); // ensure the frame time is correct to not overrun FPS if ((endTime - startTime) < minimumTime) usleep ((minimumTime - (endTime - startTime)) * CLOCKS_PER_SEC); if (!this->m_context.settings.screenshot.take || videoDriver->getFrameCounter () != 5) return; this->takeScreenshot (*context, this->m_context.settings.screenshot.path, this->m_context.settings.screenshot.format); // disable screenshot just in case the counter overflows this->m_context.settings.screenshot.take = false; } void CWallpaperApplication::signal (int signal) { this->m_context.state.general.keepRunning = false; } const std::map& CWallpaperApplication::getBackgrounds () const { return this->m_backgrounds; } Core::CProject* CWallpaperApplication::getDefaultBackground () const { return this->m_defaultBackground; } CApplicationContext& CWallpaperApplication::getContext () const { return this->m_context; } WallpaperEngine::Render::Drivers::Output::COutput* CWallpaperApplication::getOutput() const { return this->output; } }