- PVSM.RU - https://www.pvsm.ru -
Привет! Меня зовут Aloncie. Пока в моем окружении часто спорят о том, какой язык программирования учить первым, я решил не выбирать легких путей и закопаться в «кишки» системного программирования.
Мой проект Rwal — это CLI-утилита (с перспективой перехода на GUI) для управления обоями, которая должна одинаково хорошо чувствовать себя в разных окружениях: от KDE и GNOME до Windows. В этой статье я подробно разберу архитектуру проекта, работу с D-Bus, интеграцию со стандартами C++20 и то, как я организовал сборку.
Главная проблема при написании менеджера обоев — фрагментация сред рабочего стола (DE) в Linux. В KDE это делается через скрипты Plasma, в GNOME — через GSettings. Чтобы код не превратился в нагромождение #ifdef, я использовал паттерн Адаптер.
Основой стал базовый интерфейс IWallpaperSetter. Это позволяет остальной части приложения не знать, в какой среде оно запущено.
// src/wallpaper/IWallpaperSetter.hpp
class IWallpaperSetter {
public:
virtual ~IWallpaperSetter() = default;
virtual bool setWallpaper(const std::string& path) = 0;
};
Для KDE Plasma простого вызова системной команды недостаточно. Приходится общаться с org.kde.plasmashell через D-Bus. Я реализовал это в KdeSetter.cpp. Особенность в том, что мы посылаем Plasma-скрипт на языке JavaScript, который находит все рабочие столы и меняет им фон.
// Упрощенный фрагмент из 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);
В GNOME всё прозрачнее: мы используем QProcess для вызова утилиты gsettings. Однако здесь важно учитывать, что настройки разделены на светлую и темную темы.
// Из src/wallpaper/GnomeSetter.cpp
QProcess::execute("gsettings", { "set", "org.gnome.desktop.background", "picture-uri-dark", QString("file://%1").arg(QString::fromStdString(path))
});
Для загрузки высококачественных изображений я выбрал libcurl. Чтобы избежать утечек памяти и типичных проблем C-style библиотек, я реализовал обертку CurlWrapper с использованием принципов RAII.
Особое внимание уделил управлению ресурсами через std::unique_ptr с кастомным делейтером. Это гарантирует очистку дескриптора CURL даже при возникновении исключений.
// src/net/CurlWrapper.hpp
using CurlPtr = std::unique_ptr<CURL, void(*)(CURL*)>;
// Реализация
CurlWrapper::CurlWrapper() : curl_(curl_easy_init(), curl_easy_cleanup) { if (!curl_) throw std::runtime_error("Failed to initialize CURL");
}
Это решение позволило мне инкапсулировать логику настройки запросов (User-Agent, таймауты) внутри одного класса, предоставляя приложению чистый интерфейс для скачивания файлов.
Изначально я пробовал использовать Docker для изоляции окружения, но для системной утилиты, которой нужен доступ к D-Bus хоста, это создавало лишние накладные расходы. В итоге я перешел на «чистый» CMake.
В моем CMakeLists.txt я жестко задал стандарт C++20. Это критично, так как проект использует современные фичи вроде std::format (в планах) и асинхронные потоки.
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)
Одной из самых раздражающих проблем стали фризы интерфейса при загрузке 4K-изображений. Сейчас я работаю над внедрением std::jthread из стандарта C++20.
Почему именно jthread?
Авто-join: поток сам завершится корректно при выходе из области видимости.
Stop Tokens: это позволяет элегантно прервать загрузку, если пользователь передумал или закрыл программу, не дожидаясь таймаута сокета.
Также я столкнулся с тем, что разные версии GCC и Clang имеют разную степень поддержки заголовка <format> и jthread. Это заставило меня глубже разобраться в настройках компилятора и линковке libstdc++.
Этот проект стал для меня тренажером по проектированию систем. Основные выводы:
Интерфейсы — это сила. Разделение на IWallpaperSetter позволило добавить поддержку нового DE за 15 минут.
Статический анализ. Использование clang-tidy помогло найти несколько потенциальных use-after-free при работе с Qt-сигналами.
Документирование решений (ADR). Даже если ты единственный разработчик, полезно записывать, почему ты выбрал D-Bus вместо прямого редактирования конфигов Plasma.
Rwal находится в активной разработке. В планах:
Полноценный адаптер для Windows (через WinAPI SystemParametersInfo).
Переход на асинхронные запросы через QtNetwork или boost::asio для лучшей интеграции с event loop.
Оптимизация потребления памяти при парсинге больших JSON-ответов от API фотостоков.
Для меня, как для ученика 10 класса, работа над системным инструментом — это лучший способ понять, как устроена ОС. Системное программирование — это не страшно, если уметь декомпозировать задачи.
Буду рад конструктивной критике архитектуры и советами по работе с потоками в C++!
GitHub проекта [1]
Автор: Aloncie
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/qt-2/448851
Ссылки в тексте:
[1] GitHub проекта: https://github.com/Aloncie/Rwal
[2] Источник: https://habr.com/ru/articles/1019502/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1019502
Нажмите здесь для печати.