Cross-Platform Wallpaper Manager in C++20: Architecture and System Integration
The CLI utility Rwal manages wallpapers in KDE, GNOME, and is planned for Windows. A key element is the IWallpaperSetter interface, implementing the Adapter pattern. This isolates business logic from DE specifics, minimizing #ifdef.
// src/wallpaper/IWallpaperSetter.hpp
class IWallpaperSetter {
public:
virtual ~IWallpaperSetter() = default;
virtual bool setWallpaper(const std::string& path) = 0;
};
A runtime factory detects the environment and returns the appropriate implementation. Adding support for a new DE only requires a new derived class.
Integration with KDE Plasma via D-Bus
KDE requires interaction with org.kde.plasmashell. The KdeSetter implementation sends a JavaScript script to apply wallpapers to all desktops.
// Simplified snippet from src/wallpaper/KdeSetter.cpp
QDBusInterface remoteApp("org.kde.plasmashell", "/PlasmaShell", "org.kde.PlasmaShell");
QString script = QString(
"var allDesktops = desktops();"
"for (var i = 0; i < allDesktops.length; i++) {"
" var d = allDesktops[i];"
" d.wallpaperPlugin = 'org.kde.image';"
" d.currentConfigGroup = Array('Wallpapers', 'org.kde.image', 'General');"
" d.writeConfig('Image', 'file://%1');"
"}"
).arg(QString::fromStdString(path));
remoteApp.call("evaluateScript", script);
The script dynamically configures wallpaperPlugin and Image for each desktop's settings. D-Bus error handling is implemented via QDBusError.
GNOME: GSettings and Theme Modes
For GNOME, gsettings is used via QProcess. It accounts for picture-uri-dark and picture-uri for dark/light themes.
// From src/wallpaper/GnomeSetter.cpp
QProcess::execute("gsettings", {
"set", "org.gnome.desktop.background", "picture-uri-dark",
QString("file://%1").arg(QString::fromStdString(path))
});
The code checks for gsettings availability and falls back to direct copying to ~/.config. This ensures stability in containers.
Network Layer: RAII Wrapper for libcurl
Image downloading is implemented via CurlWrapper with RAII. std::unique_ptr with a custom deleter guarantees cleanup.
// src/net/CurlWrapper.hpp
using CurlPtr = std::unique_ptr<CURL, void(*)(CURL*)>;
// Implementation
CurlWrapper::CurlWrapper() : curl_(curl_easy_init(), curl_easy_cleanup) {
if (!curl_) throw std::runtime_error("Failed to initialize CURL");
}
The class encapsulates CURLOPT_USERAGENT, CURLOPT_TIMEOUT, and progress callbacks. Downloading to memory uses CURLOPT_WRITEFUNCTION.
CMake: Modular C++20 Build
The project requires C++20 for std::jthread and planned std::format. CMake finds Qt5 DBus, CURL, nlohmann_json.
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt5 REQUIRED COMPONENTS Core DBus Widgets)
find_package(CURL REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED)
add_subdirectory(src/wallpaper)
target_link_libraries(${PROJECT_NAME} PRIVATE wallpaper_lib Qt5::DBus CURL::libcurl nlohmann_json::nlohmann_json)
Avoiding Docker simplified access to the host's D-Bus. Static linking of CURL minimizes dependencies.
Asynchrony with std::jthread
Downloading 4K images blocked the UI. The solution is std::jthread from C++20:
- Auto-join when exiting scope.
- stop_token for operation cancellation.
- co_await compatibility with coroutines.
std::jthread loader(& {
// Loading with stoken.stop_requested() check
});
Cross-compiler compatibility: GCC 11+ and Clang 14+ with -fcoroutines.
Key Takeaways
- Adapter pattern allows adding DE support in 15 minutes without core refactoring.
- RAII for libcurl prevents leaks in exception paths.
std::jthreadsolves UI freezes during network operations.- Modular CMake ensures portable builds.
- D-Bus scripting is the only reliable method for Plasma.
Architectural Decisions and Analysis
Static analysis with clang-tidy revealed Qt signals with potential use-after-free. ADRs document the choice of D-Bus over direct config editing.
Development Plans:
- Windows WinAPI
SystemParametersInfoW. QtNetworkorboost::asiofor async.- JSON parsing optimization for 100MB+ responses.
The project demonstrates system programming in C++20 for mid-level developers: from interfaces to coroutines.
— Editorial Team
No comments yet.