Отправная точка — скриншот темы «Conky Conky Conky» 2010 года: тонкое кольцо-циферблат с засечками, дуга прогресса минут, диагональные линии-«коннекторы», расходящиеся от кольца к блокам статистики, угловой индикатор RAM, топ процессов по CPU и по памяти, простой текстовый to-do-список внизу. Ниже — как это воссоздать на современном Linux-рабочем столе, с полным разбором багов и тупиков, которые встретились по пути.
| Параметр | Значение |
|---|---|
| ОС | Ubuntu 26.04 LTS (Resolute Raccoon) |
| DE / сессия | Cinnamon, X11 (не Wayland) |
| Conky | conky-all 1.22.2-1build1 |
| Python | 3.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_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 присутствует, а вызвать её нечем.
cairo_xlib_surface_create — не тратьте время на поиск опечатки в своём скрипте, это отсутствующий в бинарнике символ. Проверяется одной командой: strings $(which conky) | grep cairo_xlib_surface_create. Пусто — данный путь не работает в принципе, нужен план Б.Раз 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} — картинка и текст живут в одной системе координат окна, поэтому их несложно состыковать.
Кольцо — это не «прогресс-бар по кругу», а три независимых слоя, нарисованных поверх друг друга:
i · 6° − 90°.(минуты·60 + секунды) / 3600.Всё это — десяток вызовов cairo_arc/move_to/line_to/stroke с разной толщиной линии и альфой, ничего специфичного к circular-геометрии сверх обычной тригонометрии.
Первая версия использовала ${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 нет и не будет — куб считается напрямую как геометрия:
time.time() — кадр анимации нигде не хранится, угол каждую секунду просто пересчитывается заново из текущего времени.depth = 1 / (1 + (z + 2) · 0.12).Поскольку conky и так перерисовывает панель каждую секунду, отдельный набор кадров («флипбук») не понадобился — реальное время само по себе и есть источник анимации. Один полный оборот занимает около 7 минут — крутится еле заметно.
Первая версия куба на некоторых углах поворота показывала точки как будто в зеркальном отражении. Баг оказался в двух слоях.
Слой 1 — не тот знак у осей грани. Каждая грань описывается тройкой (нормаль, ось-U, ось-V). Если для «противоположных» граней оси выбраны без учёта ориентации — след U×V не совпадает с нормалью — паттерн получается зеркальным на одной из двух граней. Лечится явным условием: ось V — всегда мировая «вертикаль» на любой грани, а знак оси U подбирается так, чтобы cross(U, V) == normal.
Слой 2 — перспектива переворачивает «право/лево» уже во время вращения. На углах, близких к «ребру», узор всё равно иногда мог казаться зеркальным — перспективный член считается отдельно для каждой вершины по её собственному Z, а не единым масштабом для всей грани.
col → 2 − col). Проверено перебором 180 углов поворота по всем 4 граням — ноль случаев рассинхрона после фикса.
Картинка рисуется в фиксированных пиксельных координатах 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, а не на глаз.Не всё в виджете рисуется картинкой — большая часть строк это штатные переменные conky, плюс пара мест, где штатной переменной не нашлось:
${battery_status} / ${battery_percent}, обычный UPower-бэкенд.${acpiacadapter} не сработала (нет /proc/acpi/ac_adapter), читается напрямую из /sys/class/power_supply/AC0/online.nmcli -t -f TYPE,NAME connection show --active.${downspeedgraph имя_интерфейса}.Готовый конфиг и генератор — с построчными комментариями к каждой строке — можно забрать архивом и поставить себе:
conky.conf + ring_gen.py + install.sh (спрашивает перед sudo apt и автозапуском) + README.md.| Симптом | Причина / что проверить |
|---|---|
Ошибка 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, а не со всего экрана.
Комментарии и обсуждение — на интерактивной версии статьи.
Перейти к обсуждению →