Ми}{@лbI4

Блог хеллоуворлдщика

Индикатор раскладки клавиатуры для tint2 [Часть 2]

17.04.2022 linux, tint2, xkb, debian, клавиатура, bash, c lang

В первой части я рассказал, как сделать индикатор раскладки клавиатуры используя данные индикатора CapsLock и командный язык Bash. В этой записи я продолжу тему индикации. Теперь будем реализовывать её используя и Bash, и C (Си).

Индикатор раскладки клавиатуры также будет работать только под X11. Я просто не использую другую оконную систему.

Недостаток реализации предыдущего индикатора раскладки клавиатуры заключался в том, что мы завязывались на данных индикатора CapsLock. В случае, если мы не используем CapsLock для переключения раскладки, или же у нас больше трех языков, то данное решение бесполезно. В добавок, у нас не было возможности переключать раскладку по левому клику мыши по иконки в tint2. Для её реализации требовалось бы прибегнуть к хаку в виде пакета xdotool, который может триггерить событие устройств ввода, тем самым эмулируя физическое переключение раскладки клавиатуры. Всё это привело к тому, что я решил вспомнить былое и реализовать утилиту на языке C, которая бы возвращался код раскладки и могла переключать её.

Перед тем, как приступить к реализации своей версии, я посмотрел разные варианты уже имеющихся, где только смог найти. Но мне хотелось сделать все по-своему. Заодно написать что-нибудь на Си.

Я ждал этого 8 лет! В ВЕБЕ!

В итоге я быстро накидал первую версию. Проверил в течении пары дней, что все работает. И сделал рефакторинг, уменьшив количество кода и упростив реализацию. До этого было много обработок различных ошибок, которые я решил просто выбрасывать в stderr и возвращать bool. Ну и так по мелочи.

Утилиту я назвал xkbklu, что в переводе означает XKB Keyboard layout Utitlity.

Исходник:

// main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <X11/XKBlib.h>
#include <X11/extensions/XKBrules.h>

Display *dpy;

/**
 * @param[out] l_code A language code of current keyboard layout.
 * @param[out] l_pos A layout position in XKB group list.
 * @param[out] t_layout Total number of layouts in XKB group list.
 * @return "true" if executed sucessully, "false" if an
 * error occured.
 */
bool get_status(char *l_code[], int *l_pos, int *t_layout) {
    XkbStateRec state;
    XkbGetState(dpy, XkbUseCoreKbd, &state);
    XkbRF_VarDefsRec rules;
    if (!XkbRF_GetNamesProp(dpy, NULL, &rules)) {
        fprintf(stderr, "ERR: Cannot get XKB-rules.\n");
        return false;
    }

    char *layout = strtok(rules.layout, ",");
    char *cur_layout = NULL;
    int layout_size = 0;
    while (layout != NULL) {
        if (layout_size == state.group) {
            cur_layout = layout;
        }

        ++layout_size;

        layout = strtok(NULL, ",");
    }
    if (cur_layout == NULL) {
        fprintf(stderr, "ERR: Undefined current layout.\n");
        return false;
    }

    if (l_code != NULL) {
        *l_code = cur_layout;
    }
    if (l_pos != NULL) {
        *l_pos = (int)state.group;
    }
    if (t_layout != NULL) {
        *t_layout = layout_size;
    }

    return true;
}

/**
 * @return "true" if executed sucessully, "false" if an
 * error occured.
 */
bool switch_layout(void) {
    int l_pos = 0;
    int t_layout = 0;
    if (!get_status(NULL, &l_pos, &t_layout)) {
        return false;
    }

    int next_layout = l_pos + 1 >= t_layout ? 0 : l_pos + 1;
    if (!XkbLockGroup(dpy, XkbUseCoreKbd, next_layout)) {
        fprintf(stderr, "ERR: Cannot switch keyboard layout.\n");
        return false;
    }

    return true;
}

void show_help(void) {
    printf("xkbklu - The helper for xkb* utilities.\n");
    printf("\n");
    printf("Commands:\n");
    printf("switch  -   Switch layout of keyboard.\n");
    printf("status  -   Return layout code of keyboard.\n");
}

int main(int argc, char *argv[]) {
    dpy = XOpenDisplay(NULL);
    if (dpy == NULL) {
        fprintf(stderr, "ERR: Cannot open display.\n");
        return EXIT_FAILURE;
    }

    int exit_code = EXIT_FAILURE;
    char *command = argc > 1 ? argv[1] : "";

    if (strcmp(command, "status") == 0) {
        char *lang;
        if (get_status(&lang, NULL, NULL)) {
            printf("%s\n", lang);
            exit_code = EXIT_SUCCESS;
        }
    } else if (strcmp(command, "switch") == 0) {
        if (switch_layout()) {
            exit_code = EXIT_SUCCESS;
        }
    } else if (
        strcmp(command, "") == 0
        || strcmp(command, "--help") == 0
        || strcmp(command, "-h") == 0
    ) {
        show_help();
        exit_code = EXIT_SUCCESS;
    } else {
        fprintf(stderr, "ERR: Command is not found.\n");
    }

    XCloseDisplay(dpy);

    return exit_code;
}

Зависимости:

  • X11;
  • xkbfile.

Сборка:

$ gcc -Wall -o ./xkbklu -I /usr/include ./main.c -lX11 -lxkbfile

Использование:

xkbklu - The helper for xkb* utilities.

Commands:
switch  -   Switch layout of keyboard.
status  -   Return layout code of keyboard.

Теперь можно доработать наш индикатор для tint2. Для этого перенесем полученный после сборки бинарник xkbklu в ~/.local/bin/, и внесем правки в Bash скрипт keyboard-layout:

#!/usr/bin/env bash

ICON_PATH="/home/$(whoami)/.local/share/keyboard-layout"

get_status() {
  local code=$(xkbklu status)
  local icon="${ICON_PATH}/flag-${code}.svg"
  if [ -f $icon ]; then
    echo $icon
  else
    echo "${ICON_PATH}/no-flag.svg"
  fi
}

switch_layout() {
  xkbklu switch
}

case "$1" in
  'switch')
    switch_layout
    ;;

  'status')
    get_status
    ;;

  *)
    # do nothing
    ;;
esac

exit 0

И конечно же, результат работы:

Репозиторий утилиты на Github: https://github.com/bupy7/xkbklu