← На главную

Conky Conky Conky на Ubuntu 26.04: воссоздаём виджет на pycairo (баги conky-all 1.22.2)

Окружение: Ubuntu 26.04 LTS, Cinnamon (X11), conky-all 1.22.2, Python 3.14 + pycairo 1.27.0

Отправная точка — скриншот темы «Conky Conky Conky» 2010 года: тонкое кольцо-циферблат с засечками, дуга прогресса минут, диагональные линии-«коннекторы», расходящиеся от кольца к блокам статистики, угловой индикатор RAM, топ процессов по CPU и по памяти, простой текстовый to-do-список внизу. Ниже — как это воссоздать на современном Linux-рабочем столе, с полным разбором багов и тупиков, которые встретились по пути.

Итоговый вид виджета: круглый циферблат, линии-коннекторы к блокам статистики, вращающийся куб под циферблатом

Окружение

ПараметрЗначение
ОСUbuntu 26.04 LTS (Resolute Raccoon)
DE / сессияCinnamon, X11 (не Wayland)
Conkyconky-all 1.22.2-1build1
Python3.14.4 + pycairo 1.27.0
Мониторыдва монитора 1920×1080, один помечен primary

Ключевая деталь, определившая весь дальнейший путь: conky-all в этой сборке заявляет поддержку Lua-биндингов Cairo («Lua bindings: Cairo, Imlib2, RSVG» в conky --version), и почти все гайды по такого рода темам 2010–2018 годов рисуют кольца именно через Lua-скрипт внутри conky. Он не заработал — и почему, разобрано ниже.

Тупик: Lua+Cairo не работает

Классический рецепт для таких колец — lua_draw_hook_post в конфиге и скрипт вида:

local cs = cairo_xlib_surface_create(conky_window.display,
    conky_window.drawable, conky_window.visual,
    conky_window.width, conky_window.height)
local cr = cairo_create(cs)

На этой сборке — падение: attempt to call a nil value (global 'cairo_xlib_surface_create'). Разбор символов бинарника (strings /usr/bin/conky | grep cairo_) показал, что реально экспортированы только базовые функции — cairo_create, cairo_arc_negative, cairo_move_to, cairo_line_to, cairo_set_source_rgba и ещё десяток — но не cairo_xlib_surface_create, не cairo_arc (только «negative»-вариант), и вообще ни одной функции для текста (cairo_show_text, cairo_select_font_face отсутствуют).

В исходниках conky (тег v1.22.2) нашлось объяснение: xlib-специфичные биндинги живут в отдельном файле lua/cairo_xlib.pkg, который в этой сборке Ubuntu, судя по всему, просто не скомпилирован — вероятно, ради одного бинарника, одинаково работающего и под X11, и под Wayland (у Wayland нет своего эквивалента xlib-поверхности). При этом conky_window.display, .drawable и .visual в таблице всё ещё заполняются (проверено по src/lua/llua.cc) — то есть «половина» API присутствует, а вызвать её нечем.

Вывод. Если у вас похожая сборка conky и Lua-рецепт из старого гайда падает на cairo_xlib_surface_create — не тратьте время на поиск опечатки в своём скрипте, это отсутствующий в бинарнике символ. Проверяется одной командой: strings $(which conky) | grep cairo_xlib_surface_create. Пусто — данный путь не работает в принципе, нужен план Б.

План Б: Python рисует, conky показывает

Раз conky не может исполнить cairo-код с полным доступом к своей же поверхности, векторную графику стали рисовать снаружи: маленький Python-скрипт (ring_gen.py) на pycairo (тот же Cairo, но с полноценным API — там cairo_arc, текст, всё есть) рисует прозрачный PNG нужного размера, а conky просто накладывает его как обычную картинку:

${execi 1 python3 ~/.config/conky/ring_gen.py}${image ~/.config/conky/panel.png -p 0,0 -s 950x630 -n}

${execi 1 ...} запускает скрипт раз в секунду (его stdout пуст, поэтому в тексте виджета ничего лишнего не появляется — сам вызов используется только ради побочного эффекта: перезаписи файла), а ${image ... -n} тут же отображает то, что скрипт нарисовал. Дальше на этот же PNG накладывается обычный текст conky (диски, батарея, топ процессов) через ${goto x}/${voffset n} — картинка и текст живут в одной системе координат окна, поэтому их несложно состыковать.

Циферблат и линии-коннекторы

Кольцо — это не «прогресс-бар по кругу», а три независимых слоя, нарисованных поверх друг друга:

  • Статичный циферблат — окружность плюс 60 засечек (12 крупных на «часах», 48 мелких на «минутах»), нарисованных в цикле по углу i · 6° − 90°.
  • Дуга прогресса минут — тонкая яркая дуга чуть за пределами циферблата, от −90° (12 часов) по часовой стрелке на угол, пропорциональный (минуты·60 + секунды) / 3600.
  • Линии-коннекторы — декоративные «спицы», отходящие от края кольца под фиксированными углами к блокам статистики. Чисто эстетический элемент из оригинала — просто отрезки от точки на окружности кольца до точки чуть дальше по тому же радиусу.

Всё это — десяток вызовов cairo_arc/move_to/line_to/stroke с разной толщиной линии и альфой, ничего специфичного к circular-геометрии сверх обычной тригонометрии.

Грабли: кэш ${image}

Первая версия использовала ${image panel.png -f 1} — флаг -f по документации «задаёт интервал сброса кэша». Ожидание: обновление раз в секунду, синхронно с ${execi 1 ...}. Реальность: изображение обновлялось нестабильно — вероятно, гонка между собственным таймером conky на перечитывание файла и моментом, когда Python ещё не дописал PNG.

Фикс. Заменить -f 1 на -n (полностью отключить кэш Imlib2 для этой картинки — перезагружать с диска на каждой перерисовке). Дороже по CPU, чем правильно работающий -f, но при 950×630px и раз в секунду разницы не заметно, а обновление стало 100% надёжным.

Грабли: невидимые чёрные точки

Самый долгий баг во всей истории. Задача была простая — наложить готовую PNG-иконку (значок из 3×3 сетки с чёрными кружками) поверх кольца через cairo.ImageSurface.create_from_png() + set_source_surface + paint(). В сгенерированном файле иконка была на месте — проверено и через Pillow, и напрямую по альфа-каналу. А в живом окне conky — пусто. Ни намёка на иконку, при том что кольцо рядом рисовалось прекрасно.

Переломный момент — прямой захват содержимого окна conky через X11 (python-xlib, get_image() по конкретному Window ID), в обход скриншотов рабочего стола. Изолированный тест решил вопрос за один шаг — тот же PNG-файл, но с залитым красным квадратом вместо иконки:

Красный квадрат корректно виден на позиции иконки После перехода на белую маску иконка стала видна

Красный квадрат отрисовался мгновенно. Значит, дело не в позиции, не в кэше, не в размере окна — дело именно в содержимом: чёрные (около rgb(0,0,0)) пиксели с частичной прозрачностью, вложенные во второй PNG внутри Cairo-поверхности, Imlib2/conky не отрисовывает — независимо от того, кто финально сохраняет файл, cairo или Pillow. Похоже на баг обработки premultiplied-alpha именно для близких к чёрному цветов где-то в связке conky↔Imlib2.

Фикс. Не заливать иконку её «естественным» чёрным цветом, а использовать её альфа-канал как маску и залить сверху белым: cr.set_source_rgba(1,1,1,0.85); cr.mask_surface(icon_surface, x, y) вместо cr.set_source_surface(...); cr.paint(). Обходит баг целиком и заодно лучше вписывается в бело-линейную эстетику остального виджета.

От иконки к вращающемуся кубу

По ходу работы статичную иконку RAM заменили на вращающийся проволочный куб с той же 5-точечной раскладкой на боковых гранях. Настоящей 3D-графики в conky нет и не будет — куб считается напрямую как геометрия:

  1. 8 вершин единичного куба и 12 рёбер между ними.
  2. Поворот вокруг оси Y на угол, взятый прямо из time.time() — кадр анимации нигде не хранится, угол каждую секунду просто пересчитывается заново из текущего времени.
  3. Фиксированный наклон по X на 25°, чтобы куб не выглядел плоским квадратом при повороте.
  4. Простая перспективная проекция: depth = 1 / (1 + (z + 2) · 0.12).
  5. На каждой боковой грани — своя локальная сетка 3×3 с 5 точками, нарисованная только если грань сейчас повёрнута к зрителю (backface culling по знаку Z нормали).

Поскольку conky и так перерисовывает панель каждую секунду, отдельный набор кадров («флипбук») не понадобился — реальное время само по себе и есть источник анимации. Один полный оборот занимает около 7 минут — крутится еле заметно.

Грабли: куб зеркалится на лету

Первая версия куба на некоторых углах поворота показывала точки как будто в зеркальном отражении. Баг оказался в двух слоях.

Слой 1 — не тот знак у осей грани. Каждая грань описывается тройкой (нормаль, ось-U, ось-V). Если для «противоположных» граней оси выбраны без учёта ориентации — след U×V не совпадает с нормалью — паттерн получается зеркальным на одной из двух граней. Лечится явным условием: ось V — всегда мировая «вертикаль» на любой грани, а знак оси U подбирается так, чтобы cross(U, V) == normal.

Слой 2 — перспектива переворачивает «право/лево» уже во время вращения. На углах, близких к «ребру», узор всё равно иногда мог казаться зеркальным — перспективный член считается отдельно для каждой вершины по её собственному Z, а не единым масштабом для всей грани.

Фикс. Не доверять знаку оси, а проверять экранные координаты по факту: перед отрисовкой точек сравнивается спроецированный X нижней-левой и нижней-правой ячейки; если «левая» оказалась правее «правой» — столбцы этой грани на этом кадре зеркалятся (col → 2 − col). Проверено перебором 180 углов поворота по всем 4 граням — ноль случаев рассинхрона после фикса.
Сетка кадров куба на 18 разных углах поворота — узор одинаковый на каждом

Позиционирование текста поверх картинки

Картинка рисуется в фиксированных пиксельных координатах Python-скриптом и не знает о тексте conky вообще ничего. Обратная синхронизация — вручную, координаты подбирались по факту, глядя на результат.

Отдельный приём — «манёвр» для строки RAM: она сначала «спрыгивает» большим ${voffset} вниз, рисуется под кубом, а сразу следом идёт компенсирующий отрицательный ${voffset}, возвращающий курсор туда, где он был бы, если бы этой строки не существовало — иначе всё, что рисуется после, съехало бы вниз вместе с ней.

${voffset 268}${goto 120}${font Ubuntu:size=9}${color1}RAM  ${color}${font Ubuntu:size=12}${memperc}%${voffset -286}
${voffset 10}${goto 310}${font Ubuntu:size=9}${color1}BATTERY${goto 460}${color1}AC ADAPTER

Грабли: виджет не на том мониторе

Во время отладки один из тестовых конфигов использовал own_window_type = 'override'. Тестовое окно после этого пропало из виду — но не потому, что что-то сломалось в отрисовке: xwininfo показал, что оно появилось на втором физическом мониторе, хотя xinerama_head = 0 был явно указан. Основной рабочий конфиг (own_window_type = 'normal') всё это время стоял ровно там, где нужно.

Вывод. own_window_type = 'override' в этой сборке, похоже, игнорирует xinerama_head. Для multi-monitor использовать 'normal' с явным xinerama_head, а позицию проверять через xwininfo -root -tree | grep conky, а не на глаз.

VPN, батарея и остальная телеметрия

Не всё в виджете рисуется картинкой — большая часть строк это штатные переменные conky, плюс пара мест, где штатной переменной не нашлось:

  • Батарея${battery_status} / ${battery_percent}, обычный UPower-бэкенд.
  • AC-адаптер — штатная ${acpiacadapter} не сработала (нет /proc/acpi/ac_adapter), читается напрямую из /sys/class/power_supply/AC0/online.
  • VPN — своей переменной в conky нет, имя активного соединения достаётся через nmcli -t -f TYPE,NAME connection show --active.
  • График трафика VPN — штатный ${downspeedgraph имя_интерфейса}.

Скачать проект

Готовый конфиг и генератор — с построчными комментариями к каждой строке — можно забрать архивом и поставить себе:

conky-conky-conky.zipconky.conf + ring_gen.py + install.sh (спрашивает перед sudo apt и автозапуском) + README.md.

⬇ Скачать архив (~11 КБ)

Чеклист troubleshooting

СимптомПричина / что проверить
Ошибка cairo_xlib_surface_create в LuaСборка conky без xlib cairo-биндингов: strings $(which conky) | grep cairo_xlib_surface_create
Картинка не обновляетсяДобавьте -n к ${image}
Часть картинки не рисуетсяПроверьте близкий к чёрному цвет с частичной прозрачностью — перерисуйте как альфа-маску
Виджет не на том монитореxinerama_head = N + own_window_type = 'normal'
Два виджета conky наложилисьСтарый автозапуск в ~/.config/autostart/ — заменить, не дублировать
${downspeedgraph} пустойНормально при отсутствии трафика прямо сейчас

Сборка/отладка и вся эта статья — результат одной длинной сессии на живом рабочем месте; все скриншоты сняты напрямую с окна conky через X11, а не со всего экрана.

💬 Обсуждение и вопросы

Комментарии и обсуждение — на интерактивной версии статьи.

Перейти к обсуждению →