ГлавнаяБлогРазбор кода Stacer: что можно украсть из Qt-проекта
Алгоритмы

Разбор кода Stacer: что можно украсть из Qt-проекта

Разбираем исходный код системного оптимизатора Stacer на Qt/C++. Узнайте, как устроена архитектура, работа с /proc, привилегии и темизация. Примените лучшие практики в своих проектах.

Al
Редакция Algolitalgolit.ru
12 мин чтения16 июля 2026 г.

Зачем читать эту статью?

Вы учите Qt по учебникам, где строят калькуляторы и туду-листы? Они учат API, но не показывают, как устроено реальное приложение. Мы возьмём готовый проект — Stacer, Linux-оптимизатор, — и разберём его код. Вы узнаете, как организовать архитектуру, работать с системой, управлять привилегиями и делать темы. После прочтения вы сможете применять эти приёмы в своих приложениях.

Stacer заброшен — последний коммит в августе 2023, 678 коммитов и тишина. Это идеально для изучения: код не меняется, можно спокойно всё прочитать. В проекте около 50 заголовочных файлов, 48 исходников и 26 .ui-форм — хватит на вечер.

Архитектура: как разделить ядро и GUI

Первым делом смотрим CMakeLists.txt. Верхний уровень:

add_subdirectory(stacer-core)
add_subdirectory(stacer)

В stacer-core подключаются только Qt5 Core и Network — без Widgets, без Charts. Это статическая библиотека, которая не может рисовать пиксели. Всё, что связано с GUI, лежит в stacer, которое линкуется с Widgets, Charts, Svg, Concurrent.

Такое разделение — редкость. Обычно в проектах обещают разделение, а потом в data-слое появляется QMessageBox. Здесь линковщик не даст этого сделать. stacer-core делится на три части по глаголам:

  • Info/ — чтение состояния системы
  • Tools/ — изменение состояния
  • Utils/ — примитивы для обоих

Читать, писать, инфраструктура. Никогда не приходится гадать, где что лежит.

Как мониторить систему: читаем /proc

Stacer читает текстовые файлы из /proc. Например:

#define PROC_CPUINFO "/proc/cpuinfo"
#define PROC_LOADAVG "/proc/loadavg"
#define PROC_STAT    "/proc/stat"

Количество ядер: прочитать /proc/cpuinfo, отфильтровать строки с ^processor, посчитать. Средняя загрузка: прочитать /proc/loadavg, разделить по пробелам, взять первые три числа. Частота: отфильтровать ^cpu MHz, взять число после двоеточия.

Всё это — регулярки над текстовыми файлами. procfs — стандартный интерфейс Linux.

Самое неочевидное: вычисление CPU%

/proc/stat выдаёт счётчики тиков с момента загрузки: user, system, idle, iowait и т.д. Они только растут. CPU usage — это не значение, а отношение между двумя замерами. Из cpu_info.cpp:

int CpuInfo::getCpuPercent(const QList<double>& cpuTimes, const int& processor) const
{
    const int N = getCpuCoreCount() + 1;
    static QVector<double> l_idles(N);
    static QVector<double> l_totals(N);
    int utilisation = 0;
    if (cpuTimes.count() > 0)
    {
        double idle = cpuTimes.at(3) + cpuTimes.at(4); // idle + iowait
        double total = 0.0;
        for (const double& t : cpuTimes) total += t;
        double idle_delta = idle - l_idles[processor];
        double total_delta = total - l_totals[processor];
        if (total_delta)
            utilisation = 100 * ((total_delta - idle_delta) / total_delta);
        l_idles[processor] = idle;
        l_totals[processor] = total;
    }
    return utilisation;
}

Функция запоминает предыдущие значения в статических переменных. Это значит, что она stateful: если вызвать её два раза подряд из разных мест, второй вызов получит мусор. Первый вызов после старта всегда бессмыслен. Но это правильный алгоритм — так работает top.

Таймер обновления — из dashboard_page.cpp:

connect(mTimer, &QTimer::timeout, this, &DashboardPage::updateCpuBar);
connect(mTimer, &QTimer::timeout, this, &DashboardPage::updateMemoryBar);
connect(mTimer, &QTimer::timeout, this, &DashboardPage::updateNetworkBar);
QTimer *timerDisk = new QTimer(this);
connect(timerDisk, &QTimer::timeout, this, &DashboardPage::updateDiskBar);
timerDisk->start(5 * 1000);
mTimer->start(1 * 1000);

Один таймер на 1 секунду разветвляется на три слота, второй на 5 секунд — для диска, потому что диск меняется медленно. Никакого push, UI сам дёргает данные по таймеру.

Привилегии: pkexec вместо sudo

Stacer может убивать процессы, чистить кеши, управлять systemd, удалять пакеты. Всё это требует root. Но Stacer не запускается от root. Из command_util.cpp:

QString CommandUtil::sudoExec(const QString& cmd, QStringList args, QByteArray data)
{
    args.push_front(cmd);
    QString result("");
    try {
        result = CommandUtil::exec("pkexec", args, data);
    } catch (QString& ex) {
        qCritical() << ex;
    }
    return result;
}

Команда запускается через pkexec. polkit показывает диалог авторизации, и только одна команда выполняется с правами root. Приложение остаётся непривилегированным — баг в коде темы не сможет удалить вашу домашнюю папку. Привилегия выдаётся на каждую операцию отдельно.

Всего 16 вызовов sudoExec. Альтернатива — запускать весь GUI под sudo, но тогда event loop и парсер стилей будут root-процессами.

В Tools/package_tool.cpp та же идея: команды отправляются в apt-get, dnf, yum, pacman или snap в зависимости от дистрибутива.

Двенадцать экранов: структура страниц

Каждая страница — папка в Pages/ с тремя файлами: .h, .cpp, .ui. При старте приложение создаёт их все и помещает в SlidingStackedWidget — наследник QStackedWidget с анимацией перехода. Это мелочь, но она делает интерфейс приятнее.

Две страницы создаются условно:

if (ToolManager::ins()->checkSourceRepository()) {
    aptSourceManagerPage = new APTSourceManagerPage(mSlidingStacked);
    mListPages.insert(7, aptSourceManagerPage);
    mListSidebarButtons.insert(7, ui->btnAptSourceManager);
} else {
    ui->btnAptSourceManager->hide();
}

На Arch нет APT, поэтому страница не создаётся, кнопка скрывается. Аналогично для Gnome — проверяется DESKTOP_SESSION и дистрибутив.

Темизация: CSS-переменные своими руками

QSS похож на CSS, но в нём нет переменных. Stacer сделал их сам. Каждая тема — папка со style.qss и values.ini. AppManager::updateStylesheet делает так:

QString appThemePath = QString(":/static/themes/%1/style").arg(mSettingManager->getThemeName());
mStyleValues = new QSettings(QString("%1/values.ini").arg(appThemePath), QSettings::IniFormat);
mStylesheetFileContent = FileUtil::readStringFromFile(QString("%1/style.qss").arg(appThemePath));
for (const QString& key : mStyleValues->allKeys()) {
    mStylesheetFileContent.replace(key, mStyleValues->value(key).toString());
}
qApp->setStyleSheet(mStylesheetFileContent);
emit SignalMapper::ins()->sigChangedAppTheme();

Читаем стили, заменяем все @color01 на #fff из ini, применяем. Смена темы — указать другую папку.

Виджеты подключаются к стилям через setAccessibleName("danger"), а QSS выбирает по этому имени. Это Qt-приём: поле доступности используется как CSS-класс. И в конце — сигнал SignalMapper::ins()->sigChangedAppTheme(). SignalMapper — глобальный QObject-шина сигналов. Три сигнала:

signals:
    void sigChangedAppTheme();
    void sigUninstallStarted();
    void sigUninstallFinished();

Виджеты, которые сами рисуют (круговые диаграммы), не могут получать цвета из QSS, поэтому им нужно знать, когда тема сменилась. Без шины AppManager пришлось бы хранить указатели на все такие виджеты. С шиной — крик в пустоту, и кто слушает, тот реагирует.

Что можно украсть для своего проекта

  • Разделить OS-слой на библиотеку без Widgets — линковщик принудительно соблюдает границу.
  • Делить слой по глаголам — читать, писать, инфраструктура.
  • Никогда не запускать всё приложение от root — pkexec для каждой команды.
  • Опрашивать через QTimer — два таймера с разной частотой для разных затрат.
  • QSS + ini-файл — даёт переменные, которых нет в QSS.
  • Сигнальная шина — лучше, чем N указателей при глобальных изменениях.

Если хотите проследить сквозной пример: процент CPU. Таймер в dashboard_page.cpp, через InfoManager::getCpuPercents, в CpuInfo::getCpuPercents, который читает /proc/stat, вычисляет дельту и отдаёт число в кастомный CircleBar. Весь стек приложения в одном числе, раз в секунду.

Практический вывод

Прямо сейчас откройте любой свой Qt-проект и проверьте: можно ли собрать ядро без GUI? Есть ли в нём QMessageBox? Если да — вынесите системный слой в отдельную статическую библиотеку. Это займёт час, но сэкономит дни отладки.

А если хотите глубже разобраться — форкните Stacer и попробуйте добавить новую страницу. Или напишите своё приложение с SlidingStackedWidget. Делитесь опытом в комментариях!

#Qt#C++#архитектура#Linux#системное программирование
Al
Редакция Algolit

Пишем про алгоритмы, подготовку к собеседованиям и карьеру в IT — так, чтобы было понятно и полезно.

Хочешь закрепить знания на практике?

Решай задачи на Algolit — интерактивная платформа для обучения

Начать бесплатно →