
Если сравнить расширяемое приложение с коробочкой, то плагины, это полезные вещи, которыми можно наполнить эту коробочку, придающие новое содержание нашему приложению. В этом ряду стоит и такая вещь как Script-fu. Но что если я скажу, что Script-fu так же может быть такой же "коробочкой" и может иметь расширения, давайте разбираться. Но сразу скажу, счастливы будут не все.
Плагины к tinyscheme
Изначальный интерпретатор на базе которого создавался Script-fu позволяет загружать специальным образом написанные для него плагины. Самих плагинов к тинисхеме написано немного. Да что там немного, раз два и обчёлся. Поэтому я рассмотрю плагин поставляемый с гимпом, tsx, он так и расшифровывается тинисхема экстеншен, т.е расширение.
Загрузка плагина в Script-fu
Предположим вы разместили плагин к тинисхеме в домашней директории "~/work/scheme/tiny/tsx/". Попробуем дать команду в консоли:
(getenv "HOME") ;;...../work/scheme/tiny (define p-ext (string-append (getenv "HOME") "/work/scheme/tiny/")) (define p-ext-tsx (string-append p-ext "tsx/tsx")) (load-extension p-ext-tsx) ;;Error: eval: unbound variable: load-extension
К моему ВЕЛИКОМУ сожалению, дорогие друзья, в стандартных сборках GIMP, из тинисхемы, на базе которой сделан script-fu, удален модуль dynload.c. Его просто нет даже в архивах сборок исходных кодов дистрибутивов GIMP. А именно он отвечает за загрузку расширений в тинисхеме. По какой причине это сделано я не знаю, но зато я знаю как этот модуль добавить в ваш GIMP.
Но я расскажу Вам, как проделал этот процесс на Линуксе, в принципе это можно сделать и на Виндовсе, но это не моя родная система, и у меня там нет никаких рабочих инструментов(MinGW есть обрезанный, обычную tinyscheme и плагины к ней я могу скомпилировать, но уже целый GIMP вряд ли).
Добавление возможности загружать плагины в Script-fu под Linux
Итак, нам надо пересобрать Script-fu, чтобы он поддерживал загрузку расширений. Первым делом вам понадобиться архив исходников вашей установленной сборки GIMP. Её можно скачать например здесь: https://download.gimp.org/gimp/v2.10/ . Качаем к себе и распаковываем. Запускаем ./configure. Эта процедура сгенерирует Makefile, в каждой поддиректории проекта, заодно проверив всё ли есть для сборки проекта GIMP. Нам ВЕСЬ гимп собирать не надо(но желательно, иначе сборка script-fu превратиться "в увлекательное занятие"!!
Далее мы идём в каталог: "gimp-2.10.30/plug-ins/script-fu" ищем строчку "-DUSE_INTERFACE=1" в makefile и под ней добавляем две строчки:
``` -DSUN_DL=1 \ -DUSE_DL=1 \ ```
так что бы ниже ещё была строчка:
``` -DUSE_STRLWR=0 ```
В общем это параметры компиляции плагина script-fu(SUN_DL в данной директории не обзяателен).
Переходим в директорию tinyscheme ищем ту же строчку в файле makefile и опять добавляем:
``` -DSUN_DL=1 \ -DUSE_DL=1 \ ```
Для систем виндовс надо вместо SUN_DL писать _WIN32.
Далее идем на гитхаб https://github.com/GNOME/gimp/tree/master/plug-ins/script-fu/libscriptfu/tinyscheme - зеркало исходников GIMP, и качаем от туда два файла dynload.c и dynload.h и размещаем их в директории tinyscheme. В makefile в той же директории надо провести ещё одну правку. Ищем строку:
``` m_libtinyscheme_a_OBJECTS = scheme.$(OBJEXT) ``` и добавляем компиляцию dynload ``` m_libtinyscheme_a_OBJECTS = scheme.$(OBJEXT) dynload.$(OBJEXT)` ```
Опс. и чуть было не забыл, надо добавить линковку динамического загрузчика библиотек dl в makefile формирующего script-fu
LDADD = \ -ldl \ $(libgimpui) \ ....
Всё! Дальше идем в директорию выше и собираем плагин скрипт-фу с помощью команды make.
К сожалению для сборки этого небольшого плагина, требуются много сгенерированных и скомпилированных файлов из дистрибутива gimp, типа libgimpui-2.0.la, libgimpmodule-2.0.la и т.п. Их можно сгенерировать вручную прыгая из директории в директорию и давай команду make с нужным файлом, но я советую, всё таки собрать предварительно весь гимп командой make из верхней директории дистрибутива, это долго но проще, а уже после этого перебраться в директорию script-fu и там дать команду make.
Сам скомпилированный плагин находиться в директории ./.libs. Проверим на всякий случай, есть ли в собранном плагине строчка: load-extension, если нет, то что то у вас пошло не так.
Копируем этот плагин в место, где лежал оригинальный плагин, у меня это директория /usr/lib/gimp/2.0/plug-ins/script-fu, предварительно сохранив оригинальную версию плагина, ОБЯЗАТЕЛЬНО в другой директории(gimp может загрузить и старую переименованную версию плагина)!
Теперь можем грузить гимп, и плагин script-fu, если загрузилось поздравляю всё отлично.
Даже можно дать комманду в консоли скрип-фу:
load-extension ;;#
Мы это сделали!
Библиотеки расширения тинисхемы
Собственно библиотек расширения тинисхемы, хм.. раз два и обчёлся, о чём я уже говорил, вот что я нашёл:
https://github.com/mherasg/tsx - это и есть тинисхема экстеншен.
https://github.com/ignorabimus/tinyscheme-ffi - не пробовал, но по описанию эта библиотека позволяет загрузить любую динамическу библиотеку из тинисхемы, а не только её родное расширение и работать с функциями этой библиотеки как с обычными функциями тинисхемы.
https://sourceforge.net/projects/tinyscheme/files/ - тут лежит помимо самой тинисхемы, библиотека работы с регулярными выражениями из тинисхемы.
http://www.geonius.com/software/tsion/ - работа с сетью, директориями и пр.
Не думаю, что кому-то захочется строить сетевой веб сервер в консоли гимпа, а в принцпе чем чёрт не шутит? ))) Но данные библиотеки можно использовать для изучения принципов построения расширений тинисхемы.
Итак если у вас есть уже скомпилированная версия расширений тинисхемы, и вы попробуете её загрузить из консоли script-fu с помощью комманды load-extension, ваша попытка неминуемо окончиться крахом script-fu. Почему это происходит? Дело в том что разработчики scrip-fu существенно изменили тинисхему используемую с гимпом, и все расширения надо компилировать с учётом новых заголовочных файлов поставляемых с исходниками gimp.
Компилируем расширение тинисхемы
Берём проект tsx и распаковываем его.
Правим makefile
#SCHEME_H_DIR=.. SCHEME_H_DIR=<ВАШ ПУТЬ ДО ИСХОДНИКОВ ГИМП>/gimp-2.10.30/plug-ins/script-fu/tinyscheme GLIB_H=pkg-config --cflags glib-2.0 GLIB_L=pkg-config --libs glib-2.0 CC=gcc CFLAGS=-DUSE_DL=1 -I $(SCHEME_H_DIR) $(GLIB_H)
даём команду сборки:
make
При удачном завершении сборки мы получим файл: tsx.so который можно будет загружать в качестве расширения тинисхемы:
(define p-ext (string-append (getenv "HOME") "/work/scheme/tiny/gimp/")) (define p-ext-tsx (string-append p-ext "tsx/tsx")) (load-extension p-ext-tsx)
Теперь нам доступна интересная команда для работы:
(system "pwd") (system (string-append "ls " (getenv "HOME") "/work/gimp"))
Вот только результаты её работы вы сможете увидеть, если запустили гимп из какой нибудь консоли, и будут они именно в ней! Команда не вернет никаких данных, кроме кода возврата команды, ну это уже вопросы к разработчику данного расширения, почему оно так работает. Но то, что теперь из тинисхемы мы можем выполнять системные команды уже хорошо.
Пишем собственное расширение ��инисхемы
Для написания расширения тинисхемы нам понадобиться небольшое знание Си. В качастве примеров я выбрал несколько функций битовых операций, отсутствующих в оригинальной тинисхеме, операцию получения случайных чисел и альтернативу функции system из tsx, позволяющую получать консольный вывод комманды.
Расширение размещаем в отдельной директории и загружаем с помощью комманд:
(define p-ext (string-append (getenv "HOME") "/work/scheme/tiny/gimp/")) (define p-ext-my (string-append p-ext "my-ext/myext")) (load-extension p-ext-my)
Начнем с самой простой функции, получения случайного числа:
pointer foreign_rand(scheme * sc, pointer args) { pointer ret; unsigned long rez; rez = rand(); ret = sc->vptr->mk_integer(sc,rez); return ret; }
Нам ничего не надо передавать в функцию, а просто получить случайное число с помощью функции rand и сформировать значение в схеме на основе этого числа, это можно сделать с помощью вызова функции sc->vptr->mk_integer(sc,rez).
Чтобы наша функция была доступна в тинисхеме, надо написать функцию инициализации, которую выполняет модуль dynload при загрузке динамической библиотеки расширения. В ней выполняются функции определения для схемы, связывающие код функции с именем функции в схеме. Там же инициализируем генератор случайных чисел: srand(time(NULL)); В эту же функцию будем добавлять определения для схемы и других функций расширения.
void init_myext (scheme * sc) { printf("in run init_myext"); //просто печать для отладки ...... srand(time(NULL)); sc->vptr->scheme_define(sc,sc->global_env, sc->vptr->mk_symbol(sc,"rand"), sc->vptr->mk_foreign_func(sc, foreign_rand)); ..... printf("exit from init_myext"); }
Теперь разберёмся с передачей значений из схемы в функцию си, на примере написания битовой операции отритцания. Для выполнения этой операции нам надо передать одно целое значение в функцию си. Но поэксперементировав с этой функцией я решил, что лучше передавать еще одно значение, битовую маску нашего интереса, означающую сколько(а по факту какие) значащих битов мы хотим получить из функции.
pointer foreign_bitwise_not(scheme * sc, pointer args) { pointer first_arg; unsigned long var; pointer ret; pointer interest_arg; unsigned long interest; unsigned long rez; if(args == sc->NIL) { return sc->F; } first_arg = sc->vptr->pair_car(args);//получаем первое значение if(!sc->vptr->is_integer(first_arg)) { return sc->F; } var = sc->vptr->ivalue(first_arg); //получаем первое значение args = sc->vptr->pair_cdr(args); //получаем второе значение interest_arg = sc->vptr->pair_car(args); if(!sc->vptr->is_number(interest_arg)) { return sc->F; } interest = sc->vptr->ivalue(interest_arg); //получаем второе значение rez = (~var) & interest; //выполняем операцию ret = sc->vptr->mk_integer(sc,rez); //возвращаем значение return ret; }
Аргументы передаваемые через args, представляют собой список из тинисхемы, нам надо просто его разобрать и преобразовать значения схемы в значения си.
Далее также надо зарегистрировать нашу функцию и сделать её доступной для схемы в функции init_myext
sc->vptr->scheme_define(sc,sc->global_env, sc->vptr->mk_symbol(sc,"bitwise-not"), sc->vptr->mk_foreign_func(sc, foreign_bitwise_not));
Давайте попробуем пример:
(bitwise-not #x0 #xFF) ;;255 (bitwise-not #x01 #xFF) ;;254 (bitwise-not #x01 #xFFFF) ;;65534 (number->string 5 2) ;;"101" (number->string (bitwise-not 5 #xF) 2) ;;"1010"
Мы разобрали унарную функцию побитового отритцания. А следующие функции будут бинарные(имеющие два операнда) и ещё один дополнительный аргумент маску интересующих нас битов. Приведу полный пример операции побитового или.
pointer foreign_bitwise_or(scheme * sc, pointer args) { pointer first_arg; pointer second_arg; unsigned long var1, var2; pointer ret; pointer interest_arg; unsigned long interest; unsigned long rez; if(args == sc->NIL) { return sc->F; } first_arg = sc->vptr->pair_car(args); //первый аргумент if(!sc->vptr->is_number(first_arg)) { return sc->F; } var1 = sc->vptr->ivalue(first_arg); //первый аргумент args = sc->vptr->pair_cdr(args); //второй аргумент second_arg = sc->vptr->pair_car(args); if(!sc->vptr->is_number(second_arg)) { return sc->F; } var2 = sc->vptr->ivalue(second_arg); //второй аргумент args = sc->vptr->pair_cdr(args); //третий аргумент interest_arg = sc->vptr->pair_car(args); if(!sc->vptr->is_number(interest_arg)) { return sc->F; } interest = sc->vptr->ivalue(interest_arg);//третий аргумент rez = (var1 | var2) & interest; //операция побитового или и наложение маски ret = sc->vptr->mk_integer(sc,rez); //возврат значения return ret; }
Операция побитового и отличается от или, только выполняемой над аргументами операцией.
rez = (var1 & var2) & interest;
Операция побитового исключающего или, аналогична:
rez = (var1 ^ var2) & interest;
Также надо прописать эти функции в операцию иницализацию расширения:
sc->vptr->scheme_define(sc,sc->global_env, sc->vptr->mk_symbol(sc,"bitwise-or"), sc->vptr->mk_foreign_func(sc, foreign_bitwise_or)); sc->vptr->scheme_define(sc,sc->global_env, sc->vptr->mk_symbol(sc,"bitwise-and"), sc->vptr->mk_foreign_func(sc, foreign_bitwise_and)); sc->vptr->scheme_define(sc,sc->global_env, sc->vptr->mk_symbol(sc,"bitwise-xor"), sc->vptr->mk_foreign_func(sc, foreign_bitwise_xor));
Проверим работу операций:
(number->string (bitwise-or #b010101 #b000111 #b111111) 2) ;;"10111" (number->string (bitwise-and #b010101 #b000111 #b111111) 2) ;;"101" (number->string (bitwise-xor #b010101 #b000111 #b111111) 2) ;;"10010"
Ну а теперь осталось написать аналог функции system из tsx
pointer foreign_cmd_run(scheme * sc, pointer args) { pointer first_arg; char* command; pointer ret; FILE* fp; char arr[LINESIZE]; if(args == sc->NIL) return sc->F; first_arg = sc->vptr->pair_car(args); //принимаем первый аргумент if(!sc->vptr->is_string(first_arg)) return sc->F; command = sc->vptr->string_value(first_arg); if(0 == command) return sc->F; //принимаем первый аргумент fp = popen(command,"r"); //запускаем на исполнение комманду, создавая канал. //чтение простое, не обрабатывает слишком длинные строки, надо бы проверять сколько прочитали //и если строка слишком длинная складывать её с остатком. ret = sc->NIL; while (fgets(arr, LINESIZE, fp) != NULL) { printf("read: %s", arr); ret = sc->vptr->cons(sc, sc->vptr->mk_string(sc, arr), ret); } pclose(fp); // возвращаем перевёрнутый список вывода, вертеть обратно надо уже в схеме return ret; }
И эту функцию также надо зарегистрировать с схеме в функции инициализации расширения.
sc->vptr->scheme_define(sc,sc->global_env, sc->vptr->mk_symbol(sc,"cmd-run"), sc->vptr->mk_foreign_func(sc, foreign_cmd_run));
Пробуем:
(cmd-run "pwd") ;;("/usr/bin\n")# (cmd-run (string-append "ls " (getenv "HOME"))) ;;("work\n" "video\n" "tmp\n" "quicklisp\n" "inst\n" ;; "game\n" "doc\n" "distr\n" "bin\n" "basedraw01.png\n" "asdf\n" ;; "Videos\n" "Templates\n" "Public\n" "Pictures\n" "Photo\n" "Music\n" ;; "Downloads\n" "Documents\n" "Desktop\n") (reverse (cmd-run (string-append "/sbin/ifconfig" " -a"))) ;;("eth1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500\n" ;;" inet ... ;;... ;;" TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0\n" ;;"\n")
Если мы разместим макросы которые мы написали раньше, в файле util.scm.
Продемонстрирую сеанс работы со скрипт фу:
;;определяем пути и загружаем библиотеку и расширение my-ext (define path-home (getenv "HOME")) (define path-lib (string-append path-home "/work/gimp/lib/")) (define path-work (string-append path-home "/work/gimp/")) (load (string-append path-lib "util.scm")) (define p-ext (string-append path-home "/work/scheme/tiny/gimp/")) (define p-ext-my (string-append p-ext "my-ext/myext")) (load-extension p-ext-my) ;;определяем функию получения случайного числа на основе функции из my-ext (define (random i) (modulo (rand) i)) ;;определяем функцию получения списка случайных чисел заданной длины и огр. m (define (rand-lst m len) (let ((rez '())) (for (i 1 len) (set! rez (cons (random m) rez))) rez)) (rand-lst 21 30) ;;(15 10 18 15 13 0 5 3 13 8 14 2 12 3 7 3 8 1 19 10 12 13 17 18 3 9 16 17 18 13) (rand-lst 21 30) ;;(7 17 15 18 1 0 4 1 16 3 11 9 10 11 18 9 4 17 3 2 0 13 11 8 19 16 6 12 2 10)
Вот так вот легко и "непринужденно" мы добавили в Script-fu несколько функций, вернее написали расширение при загрузке которого в Script-fu добавляются несколько функций.
Таким образом поддержка загрузки расширений в тинисхеме является мощным средством расширения функциональности script-fu.
