#include "WaylandOpenGLDriver.h" #include "VideoFactories.h" #include "WallpaperEngine/Application/WallpaperApplication.h" #include "WallpaperEngine/Logging/Log.h" #define class _class #define namespace _namespace #define static extern "C" { #include "color-management-v1-protocol.h" #include "wlr-layer-shell-unstable-v1-protocol.h" #include "xdg-output-unstable-v1-protocol.h" #include "xdg-shell-protocol.h" #ifdef ENABLE_KDE_FEATURES #include "plasma-shell-protocol.h" #endif #include } #undef class #undef namespace #undef static #include #include #include #include #include using namespace WallpaperEngine::Render::Drivers; constexpr int EVENT_WAIT_TIMEOUT_MS = 100; constexpr auto MISSED_FRAME_TIMEOUT = std::chrono::seconds (1); static void handlePointerEnter ( void* data, struct wl_pointer* wl_pointer, uint32_t serial, struct wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y ) { const auto driver = static_cast (data); const auto viewport = driver->surfaceToViewport (surface); driver->viewportInFocus = viewport; wl_surface_set_buffer_scale (viewport->cursorSurface, viewport->scale); wl_surface_attach (viewport->cursorSurface, wl_cursor_image_get_buffer (viewport->pointer->images[0]), 0, 0); wl_pointer_set_cursor ( wl_pointer, serial, viewport->cursorSurface, viewport->pointer->images[0]->hotspot_x, viewport->pointer->images[0]->hotspot_y ); wl_surface_commit (viewport->cursorSurface); } static void handlePointerLeave (void* data, struct wl_pointer* wl_pointer, uint32_t serial, struct wl_surface* surface) { } static void handlePointerAxis (void* data, wl_pointer* wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { } static void handlePointerMotion ( void* data, struct wl_pointer* wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y ) { const auto driver = static_cast (data); const auto x = wl_fixed_to_double (surface_x); auto y = wl_fixed_to_double (surface_y); if (!driver->viewportInFocus) { return; } // Wayland has Y=0 at the top, OpenGL has Y=0 at the bottom const double viewportHeight = static_cast (driver->viewportInFocus->size.y); y = viewportHeight - y; driver->viewportInFocus->mousePos = { x * driver->viewportInFocus->scale, y * driver->viewportInFocus->scale }; } static void handlePointerButton ( void* data, struct wl_pointer* wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t button_state ) { const auto driver = static_cast (data); if (!driver->viewportInFocus) { return; } if (button == BTN_LEFT) { if (button_state == WL_POINTER_BUTTON_STATE_PRESSED) { driver->viewportInFocus->leftClick = WallpaperEngine::Input::MouseClickStatus::Clicked; } else if (button_state == WL_POINTER_BUTTON_STATE_RELEASED) { driver->viewportInFocus->leftClick = WallpaperEngine::Input::MouseClickStatus::Released; } } else if (button == BTN_RIGHT) { if (button_state == WL_POINTER_BUTTON_STATE_PRESSED) { driver->viewportInFocus->rightClick = WallpaperEngine::Input::MouseClickStatus::Clicked; } else if (button_state == WL_POINTER_BUTTON_STATE_RELEASED) { driver->viewportInFocus->rightClick = WallpaperEngine::Input::MouseClickStatus::Released; } } } constexpr struct wl_pointer_listener pointerListener = { .enter = handlePointerEnter, .leave = handlePointerLeave, .motion = handlePointerMotion, .button = handlePointerButton, .axis = handlePointerAxis }; static void handleCapabilities (void* data, wl_seat* wl_seat, uint32_t capabilities) { if (capabilities & WL_SEAT_CAPABILITY_POINTER) { wl_pointer_add_listener (wl_seat_get_pointer (wl_seat), &pointerListener, data); } } constexpr struct wl_seat_listener seatListener = { .capabilities = handleCapabilities }; static void colorManagerSupportedIntent (void*, wp_color_manager_v1*, uint32_t) { } static void colorManagerSupportedFeature (void* data, wp_color_manager_v1*, uint32_t feature) { if (feature == WP_COLOR_MANAGER_V1_FEATURE_PARAMETRIC) { static_cast (data)->colorSupport.parametric = true; } } static void colorManagerSupportedTransferFunction (void* data, wp_color_manager_v1*, uint32_t tf) { if (tf == WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ) { static_cast (data)->colorSupport.pq = true; } } static void colorManagerSupportedPrimaries (void* data, wp_color_manager_v1*, uint32_t primaries) { if (primaries == WP_COLOR_MANAGER_V1_PRIMARIES_BT2020) { static_cast (data)->colorSupport.bt2020 = true; } } static void colorManagerDone (void*, wp_color_manager_v1*) { } constexpr struct wp_color_manager_v1_listener colorManagerListener = { .supported_intent = colorManagerSupportedIntent, .supported_feature = colorManagerSupportedFeature, .supported_tf_named = colorManagerSupportedTransferFunction, .supported_primaries_named = colorManagerSupportedPrimaries, .done = colorManagerDone, }; static void hdrDescriptionFailed (void*, wp_image_description_v1*, uint32_t cause, const char* message) { sLog.error ("--hdr: the compositor refused a PQ image description (", cause, "): ", message); } static void hdrDescriptionReady (void* data, wp_image_description_v1*, uint32_t) { static_cast (data)->hdrDescriptionReady = true; } constexpr struct wp_image_description_v1_listener hdrDescriptionListener = { .failed = hdrDescriptionFailed, .ready = hdrDescriptionReady, }; static void handleGlobal (void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { const auto driver = static_cast (data); if (strcmp (interface, wl_compositor_interface.name) == 0) { driver->getWaylandContext ()->compositor = static_cast (wl_registry_bind (registry, name, &wl_compositor_interface, 4)); } else if (strcmp (interface, wl_shm_interface.name) == 0) { driver->getWaylandContext ()->shm = static_cast (wl_registry_bind (registry, name, &wl_shm_interface, 1)); } else if (strcmp (interface, wl_output_interface.name) == 0) { driver->m_screens.emplace_back ( new WallpaperEngine::Render::Drivers::Output::WaylandOutputViewport (driver, name, registry) ); } else if (strcmp (interface, zwlr_layer_shell_v1_interface.name) == 0) { driver->getWaylandContext ()->layerShell = static_cast (wl_registry_bind (registry, name, &zwlr_layer_shell_v1_interface, 1)); } else if (strcmp (interface, wl_seat_interface.name) == 0) { driver->getWaylandContext ()->seat = static_cast (wl_registry_bind (registry, name, &wl_seat_interface, 1)); wl_seat_add_listener (driver->getWaylandContext ()->seat, &seatListener, driver); } else if (strcmp (interface, zxdg_output_manager_v1_interface.name) == 0) { driver->getWaylandContext ()->xdgOutputManager = static_cast ( wl_registry_bind (registry, name, &zxdg_output_manager_v1_interface, std::min (version, 3u)) ); } else if ( strcmp (interface, wp_color_manager_v1_interface.name) == 0 && driver->getApp ().getContext ().settings.render.hdr ) { driver->getWaylandContext ()->colorManager = static_cast (wl_registry_bind (registry, name, &wp_color_manager_v1_interface, 1)); wp_color_manager_v1_add_listener (driver->getWaylandContext ()->colorManager, &colorManagerListener, driver); #ifdef ENABLE_KDE_FEATURES } else if (strcmp (interface, org_kde_plasma_shell_interface.name) == 0) { driver->getWaylandContext ()->plasmaShell = static_cast ( wl_registry_bind (registry, name, &org_kde_plasma_shell_interface, std::min (version, 8u)) ); #endif } } static void handleGlobalRemoved (void* data, struct wl_registry* registry, uint32_t id) { const auto driver = static_cast (data); // a monitor being unplugged/disabled removes its global; leaving the viewport around leaks its // layer-shell/EGL surfaces for the rest of this client's connection since nothing else disconnects it const auto it = std::ranges::find_if (driver->m_screens, [id] (const auto* viewport) { return viewport->waylandName == id; }); if (it != driver->m_screens.end ()) { driver->onLayerClose (*it); } } constexpr struct wl_registry_listener registryListener = { .global = handleGlobal, .global_remove = handleGlobalRemoved, }; void WaylandOpenGLDriver::initEGL () { const char* CLIENT_EXTENSIONS = eglQueryString (EGL_NO_DISPLAY, EGL_EXTENSIONS); if (!CLIENT_EXTENSIONS) { sLog.exception ("Failed to query EGL Extensions"); } const auto CLIENTEXTENSIONS = std::string (CLIENT_EXTENSIONS); if (CLIENTEXTENSIONS.find ("EGL_EXT_platform_base") == std::string::npos) { sLog.exception ("EGL_EXT_platform_base not supported by EGL!"); } if (CLIENTEXTENSIONS.find ("EGL_EXT_platform_wayland") == std::string::npos) { sLog.exception ("EGL_EXT_platform_wayland not supported by EGL!"); } const auto eglGetPlatformDisplayEXT = reinterpret_cast (eglGetProcAddress ("eglGetPlatformDisplayEXT")); m_eglContext.eglCreatePlatformWindowSurfaceEXT = reinterpret_cast ( eglGetProcAddress ("eglCreatePlatformWindowSurfaceEXT") ); if (!eglGetPlatformDisplayEXT || !m_eglContext.eglCreatePlatformWindowSurfaceEXT) { sLog.exception ("EGL did not return EXT proc pointers!"); } m_eglContext.display = eglGetPlatformDisplayEXT (EGL_PLATFORM_WAYLAND_EXT, m_waylandContext.display, nullptr); if (m_eglContext.display == EGL_NO_DISPLAY) { this->finishEGL (); sLog.exception ("eglGetPlatformDisplayEXT failed!"); } if (!eglInitialize (m_eglContext.display, nullptr, nullptr)) { this->finishEGL (); sLog.exception ("eglInitialize failed!"); } const auto CLIENTEXTENSIONSPOSTINIT = std::string (eglQueryString (m_eglContext.display, EGL_EXTENSIONS)); if (CLIENTEXTENSIONSPOSTINIT.find ("EGL_KHR_create_context") == std::string::npos) { this->finishEGL (); sLog.exception ("EGL_KHR_create_context not supported!"); } EGLint matchedConfigs = 0; const EGLint CONFIG_ATTRIBUTES[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RED_SIZE, 1, EGL_GREEN_SIZE, 1, EGL_BLUE_SIZE, 1, EGL_SAMPLES, 4, EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, EGL_NONE, }; // PQ in 8 bits bands visibly, so HDR outputs want a 10 bit buffer (fine for the SDR ones too) if (this->isHDRAvailable ()) { const EGLint HDR_CONFIG_ATTRIBUTES[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RED_SIZE, 10, EGL_GREEN_SIZE, 10, EGL_BLUE_SIZE, 10, EGL_SAMPLES, 4, EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, EGL_NONE, }; EGLConfig configs[64]; EGLint count = 0; if (eglChooseConfig (m_eglContext.display, HDR_CONFIG_ATTRIBUTES, configs, 64, &count)) { for (EGLint i = 0; i < count && matchedConfigs == 0; i++) { EGLint red = 0; eglGetConfigAttrib (m_eglContext.display, configs[i], EGL_RED_SIZE, &red); if (red == 10) { m_eglContext.config = configs[i]; matchedConfigs = 1; } } } if (matchedConfigs == 0) { sLog.error ("--hdr: no 10 bit EGL config, HDR output will band"); } } if (matchedConfigs == 0 && !eglChooseConfig (m_eglContext.display, CONFIG_ATTRIBUTES, &m_eglContext.config, 1, &matchedConfigs)) { this->finishEGL (); sLog.exception ("eglChooseConfig failed!"); } if (matchedConfigs == 0) { this->finishEGL (); sLog.exception ("eglChooseConfig failed! (matched 0 configs)"); } if (!eglBindAPI (EGL_OPENGL_API)) { this->finishEGL (); sLog.exception ("eglBindAPI failed!"); } const EGLint CONTEXT_ATTRIBUTES[] = { EGL_CONTEXT_MAJOR_VERSION_KHR, 3, EGL_CONTEXT_MINOR_VERSION_KHR, 3, EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR, // required for glDebugMessageCallback (WallpaperApplication::setupOpenGLDebugging) on drivers that // only emit KHR_debug output when the context is created with this flag; Mesa tends to be lenient EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, EGL_NONE, }; m_eglContext.context = eglCreateContext (m_eglContext.display, m_eglContext.config, EGL_NO_CONTEXT, CONTEXT_ATTRIBUTES); if (m_eglContext.context == EGL_NO_CONTEXT) { this->finishEGL (); sLog.error ("eglCreateContext error " + std::to_string (eglGetError ())); sLog.exception ("eglCreateContext failed!"); } } void WaylandOpenGLDriver::finishEGL () const { eglMakeCurrent (EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); if (m_eglContext.display) { eglTerminate (m_eglContext.display); } eglReleaseThread (); } void WaylandOpenGLDriver::onLayerClose (Output::WaylandOutputViewport* viewport) { sLog.error ("Compositor closed our LS, freeing data..."); if (viewport->frameCallback) { wl_callback_destroy (viewport->frameCallback); viewport->frameCallback = nullptr; } if (viewport->eglSurface) { eglDestroySurface (m_eglContext.display, viewport->eglSurface); } if (viewport->eglWindow) { wl_egl_window_destroy (viewport->eglWindow); } if (viewport->colorSurface) { wp_color_management_surface_v1_destroy (viewport->colorSurface); viewport->colorSurface = nullptr; } if (viewport->colorOutput) { wp_color_management_output_v1_destroy (viewport->colorOutput); viewport->colorOutput = nullptr; } if (viewport->layerSurface) { zwlr_layer_surface_v1_destroy (viewport->layerSurface); } #ifdef ENABLE_KDE_FEATURES if (viewport->plasmaSurface) { org_kde_plasma_surface_destroy (viewport->plasmaSurface); viewport->plasmaSurface = nullptr; } #endif if (viewport->xdgOutput) { zxdg_output_v1_destroy (viewport->xdgOutput); viewport->xdgOutput = nullptr; } if (viewport->surface) { wl_surface_destroy (viewport->surface); } if (viewport->cursorSurface) { wl_surface_destroy (viewport->cursorSurface); } if (viewport->cursorTheme) { wl_cursor_theme_destroy (viewport->cursorTheme); } if (viewport->output) { wl_output_release (viewport->output); } std::erase (this->m_screens, viewport); this->getOutput ().reset (); delete viewport; } WaylandOpenGLDriver::WaylandOpenGLDriver (ApplicationContext& context, WallpaperApplication& app) : VideoDriver (app, m_mouseInput), m_output (context, *this), m_requestedExit (false), m_frameCounter (0), m_context (context), m_mouseInput (*this) { initWaylandRegistry (); initColorManagement (); initEGL (); setupOutputLayerSurfaces (); initGLEW (); } void WaylandOpenGLDriver::initWaylandRegistry () { m_waylandContext.display = wl_display_connect (nullptr); if (!m_waylandContext.display) { sLog.exception ("Failed to query wayland display"); } m_waylandContext.registry = wl_display_get_registry (m_waylandContext.display); wl_registry_add_listener (m_waylandContext.registry, ®istryListener, this); wl_display_dispatch (m_waylandContext.display); wl_display_roundtrip (m_waylandContext.display); if (!m_waylandContext.compositor || !m_waylandContext.shm || !m_waylandContext.layerShell || this->m_screens.empty ()) { sLog.exception ("Failed to bind to required interfaces"); } if (m_waylandContext.xdgOutputManager) { for (const auto& o : this->m_screens) { o->setupXdgOutput (m_waylandContext.xdgOutputManager); } wl_display_roundtrip (m_waylandContext.display); } else if (!m_context.settings.general.spanGroups.empty ()) { sLog.error ("zxdg_output_manager_v1 is unavailable; screen-span positions will be incorrect."); } } void WaylandOpenGLDriver::initColorManagement () { if (!m_context.settings.render.hdr) { return; } if (!m_waylandContext.colorManager) { sLog.error ("--hdr: the compositor has no color management protocol (wp_color_manager_v1), staying SDR"); return; } // the supported_* events and done follow the bind wl_display_roundtrip (m_waylandContext.display); if (!colorSupport.parametric || !colorSupport.pq || !colorSupport.bt2020) { sLog.error ("--hdr: the compositor can't take PQ with BT.2020 primaries, staying SDR"); return; } auto* creator = wp_color_manager_v1_create_parametric_creator (m_waylandContext.colorManager); wp_image_description_creator_params_v1_set_tf_named (creator, WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ); wp_image_description_creator_params_v1_set_primaries_named (creator, WP_COLOR_MANAGER_V1_PRIMARIES_BT2020); this->m_hdrDescription = wp_image_description_creator_params_v1_create (creator); wp_image_description_v1_add_listener (this->m_hdrDescription, &hdrDescriptionListener, this); wl_display_roundtrip (m_waylandContext.display); if (!this->hdrDescriptionReady) { return; } // every output reports whether it runs in HDR, and again whenever that changes for (const auto& o : this->m_screens) { o->setupColorManagement (); } // image description ready -> get_information -> its events wl_display_roundtrip (m_waylandContext.display); wl_display_roundtrip (m_waylandContext.display); for (const auto& o : this->m_screens) { sLog.out ("Output ", o->name, o->isOutputHDR () ? " runs in HDR, drawing PQ" : " runs in SDR"); } } bool WaylandOpenGLDriver::isHDRAvailable () const { return this->hdrDescriptionReady; } wp_image_description_v1* WaylandOpenGLDriver::getHDRDescription () const { return this->m_hdrDescription; } void WaylandOpenGLDriver::setupOutputLayerSurfaces () { bool any = false; for (const auto& o : this->m_screens) { if (!isScreenRequested (o->name)) { continue; } o->setupLS (); any = true; } if (!any) { sLog.error ("No outputs could be initialized, please check the parameters and try again"); sLog.error ("Detected outputs:"); for (const auto& o : this->m_screens) { sLog.error (" ", o->name); } sLog.error ("Requested: "); for (const auto& o : m_context.settings.general.screenBackgrounds | std::views::keys) { sLog.error (" ", o); } for (const auto& spanGroup : m_context.settings.general.spanGroups) { for (const auto& screen : spanGroup.screens) { sLog.error (" ", screen, " (span group)"); } } sLog.exception ("Cannot continue..."); } this->m_layerSurfacesReady = true; } bool WaylandOpenGLDriver::isScreenRequested (const std::string& name) const { if (m_context.settings.general.screenBackgrounds.contains (name)) { return true; } return std::ranges::any_of (m_context.settings.general.spanGroups, [&name] (const auto& spanGroup) { return std::ranges::find (spanGroup.screens, name) != spanGroup.screens.end (); }); } void WaylandOpenGLDriver::setupLateOutputs () { // KDE often drops and re-adds outputs while it applies the screen layout at login, and monitors can // wake up after we started; the wallpapers stay registered by screen name, only the surface is missing for (const auto& o : this->m_screens) { if (o->layerSurface || !o->initialized || !isScreenRequested (o->name)) { continue; } sLog.out ("Output ", o->name, " appeared, attaching the wallpaper"); if (m_waylandContext.xdgOutputManager && !o->xdgOutput) { o->setupXdgOutput (m_waylandContext.xdgOutputManager); } o->setupLS (); } } void WaylandOpenGLDriver::initGLEW () { glewExperimental = GL_TRUE; if (const GLenum result = glewInit (); result != GLEW_OK) { const char* error = reinterpret_cast (glewGetErrorString (result)); // on Wayland+EGL, GLEW may report GLEW_ERROR_NO_GLX_DISPLAY or a null string when the build // also includes X11 but no GLX display is present; non-fatal, the EGL context is already current if (result == GLEW_ERROR_NO_GLX_DISPLAY || error == nullptr) { sLog.out ("Failed to initialize GLEW, but continuing with EGL context: ", error ? error : "No GLX display"); } else { sLog.error ("Failed to initialize GLEW: ", error); sLog.exception ("Cannot continue..."); } } } WaylandOpenGLDriver::~WaylandOpenGLDriver () { for (const auto& screen : this->m_screens) { if (screen->xdgOutput) { zxdg_output_v1_destroy (screen->xdgOutput); screen->xdgOutput = nullptr; } } eglMakeCurrent (EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); if (m_eglContext.context != EGL_NO_CONTEXT) { eglDestroyContext (m_eglContext.display, m_eglContext.context); } eglTerminate (m_eglContext.display); eglReleaseThread (); if (this->m_waylandContext.display) { wl_display_disconnect (this->m_waylandContext.display); } } void WaylandOpenGLDriver::dispatchEventQueue () { if (this->m_layerSurfacesReady) { this->setupLateOutputs (); } // rendering only continues from frame callbacks, so a surface that never got its first frame or whose // callback the compositor dropped would stay blank forever; this also draws the very first frame const auto now = std::chrono::steady_clock::now (); for (const auto& o : this->m_screens) { if (o->layerSurface && now - o->lastSwap > MISSED_FRAME_TIMEOUT) { o->renderFrame (); } } // the compositor stops sending frame callbacks to hidden surfaces, a blocking dispatch would stall the main loop wl_display* display = m_waylandContext.display; while (wl_display_prepare_read (display) != 0) { if (wl_display_dispatch_pending (display) == -1) { m_requestedExit = true; return; } } wl_display_flush (display); pollfd fd = { .fd = wl_display_get_fd (display), .events = POLLIN, .revents = 0 }; const int ready = poll (&fd, 1, EVENT_WAIT_TIMEOUT_MS); if (ready > 0) { if (wl_display_read_events (display) == -1) { m_requestedExit = true; return; } } else { wl_display_cancel_read (display); if (ready == -1 && errno != EINTR) { m_requestedExit = true; return; } } if (wl_display_dispatch_pending (display) == -1) { m_requestedExit = true; } m_frameCounter++; } Output::Output& WaylandOpenGLDriver::getOutput () { return this->m_output; } float WaylandOpenGLDriver::getRenderTime () const { return static_cast (std::chrono::duration_cast ( std::chrono::high_resolution_clock::now () - renderStart ) .count ()) / 1000000.0; } bool WaylandOpenGLDriver::closeRequested () { return this->m_requestedExit; } void WaylandOpenGLDriver::resizeWindow (glm::ivec2 size) { } void WaylandOpenGLDriver::resizeWindow (glm::ivec4 sizeandpos) { } void WaylandOpenGLDriver::showWindow () { } void WaylandOpenGLDriver::hideWindow () { } glm::ivec2 WaylandOpenGLDriver::getFramebufferSize () const { return glm::ivec2 { 0, 0 }; } uint32_t WaylandOpenGLDriver::getFrameCounter () const { return m_frameCounter; } WaylandOpenGLDriver::SEGLContext* WaylandOpenGLDriver::getEGLContext () { return &this->m_eglContext; } void* WaylandOpenGLDriver::getProcAddress (const char* name) const { return reinterpret_cast (eglGetProcAddress (name)); } void* WaylandOpenGLDriver::getWaylandDisplay () const { return this->m_waylandContext.display; } WaylandOpenGLDriver::WaylandContext* WaylandOpenGLDriver::getWaylandContext () { return &this->m_waylandContext; } Output::WaylandOutputViewport* WaylandOpenGLDriver::surfaceToViewport (const wl_surface* surface) const { for (const auto& o : m_screens) { if (o->surface == surface) { return o; } } return nullptr; } __attribute__ ((constructor)) void registerWaylandOpenGL () { sVideoFactories.registerDriver ( ApplicationContext::DESKTOP_BACKGROUND, "wayland", [] (ApplicationContext& context, WallpaperApplication& application) -> std::unique_ptr { return std::make_unique (context, application); } ); }