Pattr

Как сделать темную тему для Pure Data

В который раз открываю Pd ночью и слепну от белого фона. Пора залезть в исходники и исправить.

Помню по предыдущим попыткам, что интерфейс написан на Tcl/Tk — это интерпретируемый язык, а значит интересующие нас исходные файлы скорее всего лежат в дистрибутиве программы, и их можно редактировать, не перекомпилируя все. Так и есть: в папке tcl находится код, который отвечает за создание диалговых окон, обработке нажатий клавиш и т.д.

Небольшими усилиями получилось сменить цвет фона, шрифта, консоли и др. Например, цвет фона можно изменить, заменив “white” на “black” в следующей строке файла pd-gui.tcl':

option add *PatchWindow*Canvas.background "white" startupFile

А можно и совсем переопределить многие цвета по умолчанию, заменив эту строку на

::tk_bisque

Однако это не повлияло на отрисовку самих объектов в патче, и спустя пару-тройку часов чтения .tcl файлов стало ясно, что это делается на стороне C, и что придется разбираться, как устроен интерефейс Pd. В этот раз ограничу себя целью вынести управление цветами отрисовки патча ванильной версии Pd на сторону Tcl, чтобы впоследствии можно было легко менять цвета без компиляции.

Графический интерфейс

Интерфейс работает в отдельном процессе, который запускается после инициализации ядра. Взаимодействие между ними производится через сокет-соединение: при редактировании патча в Пд шлются команды для изменения внутреннего состояния, также Пд, в свою очередь, отправляет команды интерфейсу. Я изначально предполагал, что команды интерфейсу включают что-то общее, типа координат, названий объекта и т.п., но оказалось, что Пд отправляет команды отрисовки буквально: с координатами углов прямоугольников, толщиной и цветом линий и т.д.

Реализация этого становится понятна, если взглянуть на следующий отрывок кода из файла pd_connect.tcl (функция pd_readsocket):

...
86       set cmds_from_pd ""
87       if {![catch {uplevel #0 $docmds} errorname]} {
88           # we ran the command block without error, reset the buffer
89       } else {
90           # oops, error, alert the user:
91           global errorInfo
...

В строке 87 буквально интерпретируется и исполняется полученный из сокета в переменную $docmds Tcl код. На стороне ядра, он отправляется функцией sys_vgui. Зная это, мы теперь можем найти, откуда именно отправляются те или иные команды из ядра в интерфейс. Также будет полезно логировать получаемые команды, добавив ::pdwindow::debug [concat "DOCMDS:" $docmds "\n"] после строки 88.

Во многих случаях функция sys_vgui вызывается с аргументами “create line”, “create rectangle”, “create polygon” и т.д. — очень похоже на команды отрисовки. И действительно, документация Tk это подтверждает, например, тут. Подобные команды имеют схожий набор аргументов, из которых нас интересует только -fill и -outline, задающие цвет заливки и контура геометрического примитива.

Зная все это, можно начинать попытки модификации исходников. Начем с простого: изменим цвет обычного текстового объекта и проводов на зеленый.

Код в файле g_text.c отвечает за отрисовку текстовых объектов и сообщений. Попробуем подставить -fill green во все create * команды и компилируем. Результат:

Три проблемы:

  • прямоугольники инлетов и аутлетов имеют черный контур;
  • цвет меняется на черный после перетаскивания;
  • текст все еще черный.

Первый решается добавлением аргумента -outline green командам, отправляемым из функции glist_drawiofor. Второй — сменой “black” на “green” в функции text_select. Третий — аналогичной заменой в вызовах функцях rtext_select и sys_vgui("pdtk_text_new*, в файлах g_text.c, g_rtext.c, а также в функции glist_deselectline в файле g_editor.c. Кому интересно, выяснить это мне помог вывод принимаемых интерфейсом команд в консоль Пд, который мы включили ранее модификацией файла pd_connect.tcl. Им и поиском по исходникам (grep/ripgrep).

Смена цвета проводов производится в следующих местах:

  • g_canvas.c — функция canvas_drawlines;
  • g_editor.c — функция canvas_doclick, tryconnect и glist_deselectline.

Результат:

Как видно, цвет объекта array не изменился. Все потому, что для графических объектов подобные манипуляции необходимо проводить отдельно. Например, для этого объекта в файле g_array.c, для бэнга в g_bang.c и т.п. (Как видно, работающие с отображением исходники имеют префикс g_.) Но это рутина, ее опущу, двигаемся дальше.

Модификации

Как уже упоминал, в идеале хотелось бы вывести переменные цвета на сторону Tcl: таким образом для изменения схемы не потребовалось бы перекомпилировать весь код.

Для начала определимся с палитрой цветов. Сделаем просто:

  • pd_col_foreground — цвет объектов, инлетов, аутлетов и проводов;
  • pd_col_background — цвет фона;
  • pd_col_selection — цвет выделения.

Добавим соответствующие переменные в pd_connect.tcl, например, сразу после блока namespace eval ...:

set pd_col_foreground "#ffffff"
set pd_col_background "#202020"
set pd_col_selection  "#0000bb"

Дальше, зададим цвет фона в pd-gui.tcl. Это нужно, т.к. мы не создавали отдельный Tcl модуль для палитры.

Остается рутина вроде описанной в предыдущей секции. Отличие будет в том, что вместо green надо подставлять ${pd_col_foreground}.

Результат

Описанными манипуляциями можно “отемнить” практически весь интерфейс Пд. Выглядит примерно так:

Код можно посмотреть в репозитории. На случай если репозиторий перестанет существовать, я загрузил .diff-файл.

Не все изменения так элементарны как первыя попытка:

  • Отрисовка array и внутренностей table разбросана как минимум по g_array.c, g_canvas.c, g_template.c. Рекомендую отлавливать команды отрисовки в консоли и искать вызовы команд с похожими тэгами.
  • Некоторые объекты задают код из C переменных, что делает более сложным вынос цветов в Tcl. Зато такие случаи легче модифицировать: например, в конструкторе bang_new задаются цвета по умолчанию, которые нужно заменить лишь в одном месте. Впрочем, это не отменяет того, что кроме этих переменных, цвет задается напрямую строкой -fill black, что также необходимо исправить. Также, некоторые цвета задаются из глобальных переменных (напр. IEM_GUI_COLOR_NORMAL).
  • Цвет выделяющего прямоугольника задается в файле g_editor.c, в самом конце функции do_click. В относящемся к делу вызове нужно задать цвет не для -fill, а для -outline, т.к. мы хотим сделать белым края прямоугольника, оставив внутреннюю его часть прозрачной.

Что можно улучшить

  • Я так и не разобрался, почему не меняется цвет данных таблиц, когда их отображение находится в режиме полигона и кривой Безье.
  • Цвет выделения большинства интерфейсных объектов отличается от намеренного и выглядит плохо на темном фоне. Я пробовал решить это редактированием значения переменной IEM_GUI_COLOR_SELECTED (g_all_guis.h), но это не помогло. Вполне возможно, что причина этого та же, что и для предыдущего пункта.
  • Чтобы глазам было легче, вместо черного фона я выбрал серый (#202020). Но мне кажется, если снизить интенсивность белого, то будет еще комфортнее.
  • В идеале, палитра должна быть в отдельном Tcl модуле, но мне лень было копаться в системе сборки проекта для добавления еще одного файла.
  • Довольно несложно сделать отдельный цвет для инлетов и аутлетов.
  • Цвета бэнга задаются в коде, тривиально вывести их в Tcl не получится (см. g_bang.c функция bng_new). Чтобы вынести их в Tcl нужен рефакторинг на стороне C.
  • Если посмотреть определение функции tk_bisque, то можно понять, как можно сменить цвет оконных меню, а также консоли.
  • Совсем хорошо было бы иметь кнопку “dark mode” в интерфейсе.

P.S. Если вы промотали страницу, чтобы найти дистрибутив Пд с темной темой, то пробуйте на свой страх и риск: могут быть баги, а также эта сборка без поддержки ASIO.

03 Feb 2020  OSCII