henry_flower: A melancholy wolf (Default)
henry_flower ([personal profile] henry_flower) wrote2024-09-03 01:50 pm

Emoji та юнікод ув динамічному меню

Коли рік тому до імаксу додали інтерактивну хфункцію emoji-insert, я подумав що hell froze over. Окремий emoji інпут, звичайно, мають всі ОС багато років. Ув лайнаксі це залежить від середовища стільниці, і якщо ними (середовищами стільниці) не користуватися, то невблаганну залізну хватку прогресу можна не помітити.

Нещодавно на хакір н'юз хтось презентував свого менхетнського проекту: emoji-клавіятуру написану на голому XCB. На що хтось ув коментарях зазначив що то є вражаюче досягнення, але маючи список пар

emoji1    опис1
emoji2    опис2
…

то emoji вставляються за допомогою будь-якого динамічного меню без спеціяльної клавіятури.

Раніше таким меню була аплікація dmenu (яку я колись ламав для інкрементного аналога alt-tab для FVWM), але зараз гіпадріли користуються rofi, який є ув пекеджах будь-якого дістро:

$ ls /etc | rofi -dmenu -i

Одного rofi буде замало, треба механізма вставки тексту, який друкує rofi на stdout, до активного вікна. Ув xorg це вміє робити xdotool:

$ ls /etc | rofi -dmenu -i | xdotool type --file -

Де взяти список пар? Федора має unicode-emoji пекеджа (unicode-data ув Деб'яні) з файлом emoji-test.txt:

Зліва від середника там список codepoints. Наприклад, щоб отримати чорного кота 🐈‍⬛, треба взяти звичайного кота (0x1F408, 🐈), додати ZERO WIDTH JOINER (0x200D) та чорного квадрату (0x2B1B, ⬛).

На жаль, %b та \Uxxxx ув printf є непортабельні:

$ e="printf '%b\n' '\U1F408\U200D\U2B1B'"
$ bash -c "$e"
🐈‍⬛
$ bash -c /bin/"$e"
/bin/printf: missing hexadecimal number in escape
$ dash -c "$e"
\U1F408\U200D\U2B1B
$ busybox sh -c "$e"
\U1F408\U200D\U2B1B
$ zsh -c "$e"
🐈‍⬛
$ ksh -c "$e"
\U1F408\U200D\U2B1B

Але якщо перевести всі hex до int та роздрукувати кожен байт з int ув octal, це буде працювати навіть ув busybox:

$ echo $'\360\237\220\210\342\200\215\342\254\233'
🐈‍⬛

Файл emoji-test.txt містить 3773 придатних для використання emojis. Щоб не генерувати статичні пари ув якомусь my-dull-list.txt, які будуть задавнюватися при кожному оновленні пекеджу, краще написати швидкого шелóвого фільтра.

Look, ma--no execve() after fork()s:

$ cat codepoint.sh
codepoint_to_oct() {
    local dec="`printf %d 0x${1:-0}`"
    local b1 b2 b3 b4

    if [ "$dec" -lt $((0x80)) ]; then    # 1-byte
        b1=$dec
    elif [ "$dec" -lt $((0x800)) ]; then # 2-byte
        b1=$((0xC0 | (dec >> 6)))
        b2=$((0x80 | (dec & 0x3F)))
    elif [ "$dec" -lt $((0x10000)) ]; then
        b1=$((0xE0 | (dec >> 12)))
        b2=$((0x80 | ((dec >> 6) & 0x3F)))
        b3=$((0x80 | (dec & 0x3F)))
    elif [ "$dec" -lt $((0x200000)) ]; then
        b1=$((0xF0 | (dec >> 18)))
        b2=$((0x80 | ((dec >> 12) & 0x3F)))
        b3=$((0x80 | ((dec >> 6) & 0x3F)))
        b4=$((0x80 | (dec & 0x3F)))
    else
        return 1                # out of range
    fi

    printf \\%03o "$b1" $b2 $b3 $b4
}

emoji_print() { printf %b "`for i; do codepoint_to_oct "$i"; done`"; }

Кросиві var assignments ув codepoint_to_oct() написав chatgpt, після чого Гостре Око помітив що генерувати взагалі потрібно нічого, тому що emoji-test.txt має для кожного codepint готовий emoji.

$ cat emoji-list.sh
: "${DB:=/usr/share/unicode/emoji/emoji-test.txt}"

grep '; fully-qualified ' "$DB" \
    | grep -v 'skin tone' \
    | sed -E 's/.+# ([^ ]+) E[^ ]+ (.+)/\1\t\2/'

Як відфільтровувати безцінні варіянти з кольором шкіри, список виходить дещо коротший:

$ ./emoji-list.sh | wc -l
1898
$ ./emoji-list.sh | tail -3
🏴󠁧󠁢󠁥󠁮󠁧󠁿    flag: England
🏴󠁧󠁢󠁳󠁣󠁴󠁿    flag: Scotland
🏴󠁧󠁢󠁷󠁬󠁳󠁿    flag: Wales

Щоб не викидати codepoint.sh, над яким тяжко пітнів чатбот, можна зробити додаткового хфінта: друкувати список не лише emojis, а велику частку utf8:

$ ./unidata-list.sh | grep TELEPHONE
℡    TELEPHONE SIGN
⌕    TELEPHONE RECORDER
☎    BLACK TELEPHONE
☏    WHITE TELEPHONE
✆    TELEPHONE LOCATION SIGN
📞    TELEPHONE RECEIVER
🕻    LEFT HAND TELEPHONE RECEIVER
🕼    TELEPHONE RECEIVER WITH PAGE
🕽    RIGHT HAND TELEPHONE RECEIVER
🕾    WHITE TOUCHTONE TELEPHONE
🕿    BLACK TOUCHTONE TELEPHONE
🖀    TELEPHONE ON TOP OF MODEM

Довжина списку таких пар є 34,856. Джерело є текстовий хфайл UnicodeData.txt з unicode-ucd пекеджу ув Федорі (unicode-data ув Деб'яні):

$ grep TELEPHONE /usr/share/unicode/ucd/UnicodeData.txt | head -3
2121;TELEPHONE SIGN;So;0;ON;<compat> 0054 0045 004C;;;;N;T E L SYMBOL;;;;
2315;TELEPHONE RECORDER;So;0;ON;;;;;N;;;;;
260E;BLACK TELEPHONE;So;0;ON;;;;;N;;;;;

Якщо генерувати списка лінійно, це буде займати кількадесят секунд. Хоча rofi вміє не чекати, а показувати рядки як вони з'являються на його stdin, процес ліпше пришвидшити:

$ cat unidata-list.sh
__dir__=$(dirname "$(readlink -f "$0")")

: "${DB:=/usr/share/unicode/ucd/UnicodeData.txt}"

sed 1,33d "$DB" | while IFS=\; read -r codepoint desc _ _ _ _ _ _ _ _ alt _; do
    [ -n "$alt" ] && [ '<' = "`printf %c "$desc"`" ] && desc=$alt
    printf '%s %s\n' "$codepoint" "$desc"
done | grep -Eav '^.+\s<' \
     | xargs -r -d \\n -n1 -P "`nproc`" "$__dir__/codepoint.sh"

до чатботівского codepoint.sh додати:

…
set -e
codepoint=${1%% *}; [ "$codepoint" ]
char=`emoji_print "$codepoint"`; [ "$char" ]
printf '%s\t%s\n' "$char" "${1#[0-9A-F]* }"

Якщо навіть з такою кенкаренсі все одно доводиться чекати, споглядаючи цифри прогресу ув rofi, рекомендую користуватися нормальними комп'ютерами, а не raspberry pi.


Post a comment in response:

If you don't have an account you can create one now.
HTML doesn't work in the subject.
More info about formatting