저는 리눅스 프레임버퍼 콘솔 한글 입력기를 만들지 않겠습니다.
Wed, Feb 12 2025 02:47:37 KST2025.02.12 업데이트
안녕하세요.
제가 예전에 오픈소스 프로젝트를 진행하면서 여러 불미스러운 일들도 있었고 괴롭힘도 많이 당했었습니다.
https://nimfsoft.art/blog/2023/05/18/the-bare-faces-of-some-people/
지금도 저를 괴롭히는 사람이 있어요.
제가 요즘에 디시인사이드 프로그래밍 갤러리에서 프로그래밍 관련 잡담을 하면서 놀고 있습니다. 그런데 사이버 스토커 때문에 마음이 편하질 않습니다.
이 사건은 최소 2022년 2월로 거슬러 올라갑니다. 아마 그전부터 한국 우분투 포럼에서 리눅스 프레임버퍼 콘솔 한글 입력기를 만들어 달라고 요구했을 것 같습니다.
저는 당연히 저러한 요구를 거부합니다. 일단 제가 필요로 하지 않고, 만드는 데 시간이 무척 오래 걸립니다. 6개월에서 1년은 걸릴 것 같습니다. 손해가 크다는 얘기죠. 저 사람은 돈을 줄테니 만들어 달라는 것도 아니고 ‘공짜로 내놓으라’ 이러는 건데 무례하기까지 합니다.
저 사람은 최근에도 리눅스 프레임버퍼 콘솔 한글 입력기를 만들어 달라고 요구하고 있습니다. 저러한 요구가 수년 동안 100번은 넘는 것 같습니다.
저 사람의 요구를 거부하면 저 사람은 사이버 스토킹을 합니다. 제가 쓰는 글마다 의미 없는 댓글이나 악플을 남깁니다. 하지 말라고 해도 수년을 저러더군요. 제가 쌍욕하면 그 사람의 사이버 스토킹 행위가 멈출 줄 알았는데 마찬가지입니다. 그러고는 저를 정신병자로 몰아갑니다. 무임으로 개발/출판/강의를 해달라는 요구 및 저를 괴롭힌 글들을 저 사람이 스스로 삭제하여 검색도 잘 안 됩니다. 저는 몇몇 사람들로부터 이러한 유형의 괴롭힘을 5년 이상 당한 사람입니다.
저 사람은 사과도 없고 저런 짓을 약 4년 정도를 계속 반복하고 있습니다. 수백 건은 넘는 것 같습니다. 저는 오픈소스 프로젝트를 2019년에 최종적으로 그만두었으며 그 후로 오픈소스 프로젝트를 하지 않고 있으며 오픈소스와는 무관한 사람입니다. 제가 무슨 오픈소스 노예도 아니고… 괴롭힘의 목적이 노동력 착취입니까? 제발 저 좀 괴롭히지 마십시오. 아주 지긋지긋합니다.
콘솔 입력기 코드를 공개할 터이니 원하시는 것이 있으면 직접 만드시거나, 만들 줄 모르면 공부를 해서 만드시거나, 아니면 개발자 구인 사이트에 개발 의뢰을 하십시오.
개발자 구인 사이트로는 원티드긱스, 이랜서, 위시켓, 크몽, 잡코리아 등 여러 사이트가 있습니다.
아래 코드들은 Nimf 콘솔 입력기 관련 코드입니다. FreeBSD에서는 작동하지만,
리눅스 콘솔에는 버그가 있어서 아마 화면에 제대로 표시가 안 될 것입니다. 코드에 대한 문의는 저한테 하지 마시고 리눅스 커널 개발자 또는 프리랜서 개발자들에게 하시기 바랍니다. 참고로 저는 오픈소스쪽 사람들의 노예가 아닙니다. 감사합니다.
nimf-cons.c
// -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 2; tab-width: 2 -*-
/*
nimf-cons.c
This file is part of Nimf.
Copyright (C) 2022-2025 Hodong Kim
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE
FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifdef __linux__
#define _XOPEN_SOURCE 600 // posix_openpt()
#define _DEFAULT_SOURCE // cfmakeraw()
#endif
#include <stdlib.h>
#include <fcntl.h>
#include <libintl.h>
#include "c-mem.h"
#include <unistd.h>
#include "c-log.h"
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <sys/wait.h>
#include <signal.h>
#include <sys/uio.h>
#include "nimf-ic.h"
#include "c-loop.h"
#include "c-settings.h"
#include "nimf-key-syms.h"
#include <ctype.h>
#include <xkbcommon/xkbcommon.h>
#include "c-str.h"
#include "c-spawn.h"
#include "c-unix.h"
#ifdef __FreeBSD__
#include <sys/consio.h>
#include <sys/kbio.h>
// https://cgit.freebsd.org/src/plain/sys/dev/evdev/evdev_utils.c
uint16_t evdev_scancode2key(int *state, int scancode);
#elif defined(__linux__)
#include <linux/vt.h>
#include <linux/kd.h>
#else
#error "This platform is not supported"
#endif
typedef struct _Keyval2Vt100 Keyval2Vt100;
struct _Keyval2Vt100
{
uint32_t keyval;
char* code;
};
static Keyval2Vt100 keyval2vt100[] = {
{ NIMF_KEY_BackSpace, "\010" }, // 0xff08
{ NIMF_KEY_Tab, "\011" }, // 0xff09
{ NIMF_KEY_Return, "\012" }, // 0xff0d
{ NIMF_KEY_Escape, "\033" }, // 0xff1b
{ NIMF_KEY_Home, "\033[1~" }, // 0xff50
{ NIMF_KEY_Left, "\033[D" },
{ NIMF_KEY_Up, "\033[A" },
{ NIMF_KEY_Right, "\033[C" },
{ NIMF_KEY_Down, "\033[B" },
{ NIMF_KEY_Page_Up, "\033[5~" },
{ NIMF_KEY_Page_Down, "\033[6~" },
{ NIMF_KEY_End, "\033[4~" }, // 0xff57
{ NIMF_KEY_Insert, "\033[2~" }, // 0xff63
{ NIMF_KEY_F1, "\033OP" }, // 0xffbe
{ NIMF_KEY_F2, "\033OQ" },
{ NIMF_KEY_F3, "\033OR" },
{ NIMF_KEY_F4, "\033OS" },
{ NIMF_KEY_F5, "\033[15~" },
{ NIMF_KEY_F6, "\033[17~" },
{ NIMF_KEY_F7, "\033[18~" },
{ NIMF_KEY_F8, "\033[19~" },
{ NIMF_KEY_F9, "\033[20~" },
{ NIMF_KEY_F10, "\033[21~" },
{ NIMF_KEY_F11, "\033[23~" },
{ NIMF_KEY_F12, "\033[24~" }, // 0xffc9
{ NIMF_KEY_Delete, "\033[3~" }, // 0xffff
};
typedef struct xkb_context XkbContext;
typedef struct xkb_keymap XkbKeymap;
typedef struct xkb_state XkbState;
extern char** environ;
static CLoop* nimf_cons_loop;
typedef struct _NimfCons NimfCons;
struct _NimfCons {
int fd1;
pid_t pid;
struct termios attr;
struct winsize size;
bool attr_stored;
int kbmode;
CimIc* ic;
XkbContext* context;
XkbKeymap* keymap;
XkbState* state;
};
static void nimf_cons_restore (NimfCons* cons)
{
if (cons->kbmode > -1)
if (ioctl (STDIN_FILENO, KDSKBMODE, cons->kbmode) == -1)
c_log_critical ("KDSKBMODE failed: %s", strerror (errno));
if (cons->attr_stored)
if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &cons->attr) == -1)
c_log_critical ("tcsetattr failed: %s", strerror (errno));
}
static void nimf_cons_free (NimfCons* cons)
{
nimf_cons_restore (cons);
if (nimf_cons_loop)
{
c_loop_free (nimf_cons_loop);
nimf_cons_loop = NULL;
}
if (cons->fd1 > -1)
close (cons->fd1);
if (cons->pid > 0)
waitpid (cons->pid, NULL, 0);
xkb_state_unref (cons->state);
xkb_keymap_unref (cons->keymap);
xkb_context_unref (cons->context);
if (cons->ic)
{
cim_ic_focus_out (cons->ic);
cim_ic_free (cons->ic);
}
free (cons);
}
static bool cb_fd (int fd, short revents, NimfCons* cons)
{
if (revents & (POLLHUP | POLLERR | POLLNVAL))
{
c_loop_quit (nimf_cons_loop);
return C_SOURCE_REMOVE;
}
ssize_t n_read;
uint8_t buf[16];
n_read = c_read (fd, buf, sizeof buf);
if (n_read > 0)
{
if (c_write (STDOUT_FILENO, buf, n_read) != n_read)
c_log_critical ("write failed: %s", strerror (errno));
const CimPreedit* preedit = cim_ic_get_preedit (cons->ic);
if (preedit->text[0])
{
size_t len;
struct iovec iov[3];
iov[0].iov_base = "\033[s";
iov[0].iov_len = strlen (iov[0].iov_base);
iov[1].iov_base = preedit->text;
iov[1].iov_len = strlen (iov[1].iov_base);
iov[2].iov_base = "\033[u";
iov[2].iov_len = strlen (iov[2].iov_base);
len = iov[0].iov_len + iov[1].iov_len + iov[2].iov_len;
if (writev (STDOUT_FILENO, iov, 3) != len)
{
c_log_critical ("%s:", strerror (errno));
return C_SOURCE_REMOVE;
}
}
}
return C_SOURCE_CONTINUE;
}
static int cb_compare (const void* a, const void* b)
{
const int* keyval = a;
const Keyval2Vt100* key = b;
return (*keyval - key->keyval);
}
static bool cb_stdin (int fd, short revents, NimfCons* cons)
{
if (revents & (POLLHUP | POLLERR | POLLNVAL))
{
c_loop_quit (nimf_cons_loop);
return C_SOURCE_REMOVE;
}
uint8_t scancode;
uint16_t keycode;
ssize_t n_read;
CimEvent event;
#ifdef __FreeBSD__
int state = 0;
do {
#endif
n_read = c_read (STDIN_FILENO, &scancode, sizeof (uint8_t));
if (n_read != sizeof (uint8_t))
{
c_log_critical ("read failed");
return C_SOURCE_REMOVE;
}
if (scancode & 0x80)
event.type = CIM_EVENT_KEY_RELEASE;
else
event.type = CIM_EVENT_KEY_PRESS;
#ifdef __FreeBSD__
keycode = evdev_scancode2key (&state, scancode);
} while (state);
#elif defined __linux__
keycode = scancode & 0x7f;
#else
#error "This platform is not supported"
#endif
if (keycode)
event.keycode = keycode + 8;
event.keyval = xkb_state_key_get_one_sym (cons->state, event.keycode);
xkb_mod_mask_t mask;
mask = xkb_state_serialize_mods (cons->state,
XKB_STATE_MODS_DEPRESSED |
XKB_STATE_MODS_LATCHED);
event.state = mask;
if (event.type == CIM_EVENT_KEY_PRESS)
xkb_state_update_key (cons->state, event.keycode, XKB_KEY_DOWN);
else
xkb_state_update_key (cons->state, event.keycode, XKB_KEY_UP);
bool retval = cim_ic_filter_event (cons->ic, &event);
if (!retval && event.type == CIM_EVENT_KEY_PRESS)
{
char* text = NULL;
if (0x20 <= event.keyval && event.keyval <= 0x07e)
{
text = (char[]) { event.keyval, 0 };
}
else
{
const size_t len = C_N_ELEMENTS (keyval2vt100);
const Keyval2Vt100* key;
key = bsearch (&event.keyval, keyval2vt100, len,
sizeof (keyval2vt100[0]), cb_compare);
if (key)
text = key->code;
}
if (event.keyval >= NIMF_KEY_F1 && event.keyval <= NIMF_KEY_F12)
{
if (event.state & NIMF_MOD1_MASK) // alt
{
if (ioctl (STDIN_FILENO, VT_ACTIVATE,
event.keyval - NIMF_KEY_F1 + 1) == -1)
c_log_critical ("VT_ACTIVATE failed");
return C_SOURCE_CONTINUE;
}
}
else if (event.keyval >= NIMF_KEY_Switch_VT_1 &&
event.keyval <= NIMF_KEY_Switch_VT_12)
{
if (ioctl (STDIN_FILENO, VT_ACTIVATE,
event.keyval - NIMF_KEY_Switch_VT_1 + 1) == -1)
c_log_critical ("VT_ACTIVATE failed");
return C_SOURCE_CONTINUE;
}
if (text)
{
size_t n_bytes = strlen (text);
if (n_bytes == 1)
{
if (event.state & NIMF_CONTROL_MASK)
{
if (text[0] == 'c')
text[0] = 3; // ETX
else if (text[0] == 'd')
text[0] = 4; // EOT
}
}
if (n_bytes > 0)
{
if (c_write (cons->fd1, text, n_bytes) != n_bytes)
c_log_critical ("write failed");
}
}
}
return C_SOURCE_CONTINUE;
}
static bool cons_save_term (NimfCons* cons)
{
if (tcgetattr (STDIN_FILENO, &cons->attr) == -1)
return false;
cons->attr_stored = true;
if (ioctl (STDIN_FILENO, TIOCGWINSZ, &cons->size) == -1 ||
ioctl (STDIN_FILENO, KDGKBMODE, &cons->kbmode) == -1)
return false;
return true;
}
static bool cons_set_term (NimfCons* cons)
{
struct termios attr;
attr = cons->attr;
cfmakeraw (&attr);
attr.c_iflag = IGNPAR | IGNBRK;
attr.c_oflag = OPOST | ONLCR;
attr.c_cflag = CREAD | CS8;
attr.c_lflag &= ~(ICANON | ECHO | ISIG);
attr.c_cc[VTIME] = 0;
attr.c_cc[VMIN] = 1;
if (tcsetattr (STDIN_FILENO, TCSANOW | TCSAFLUSH, &attr) == -1)
return false;
#ifdef __FreeBSD__
if (ioctl (STDIN_FILENO, KDSKBMODE, K_RAW) == -1)
return false;
#elif defined __linux__
if (ioctl (STDIN_FILENO, KDSKBMODE, K_MEDIUMRAW) == -1)
return false;
#else
#error "This platform is not supported"
#endif
return true;
}
static void cb_preedit_changed (CimIc* ic,
const CimPreedit* preedit,
NimfCons* cons)
{
size_t len;
struct iovec iov[3];
iov[0].iov_base = "\033[s";
iov[0].iov_len = strlen (iov[0].iov_base);
if (preedit->text[0])
iov[1].iov_base = preedit->text;
else
iov[1].iov_base = " ";
iov[1].iov_len = strlen (iov[1].iov_base);
iov[2].iov_base = "\033[u";
iov[2].iov_len = strlen (iov[2].iov_base);
len = iov[0].iov_len + iov[1].iov_len + iov[2].iov_len;
if (writev (STDOUT_FILENO, iov, 3) != len)
c_log_critical ("%s", strerror (errno));
}
static void cb_commit (CimIc* ic, const char* text, NimfCons* cons)
{
size_t n_bytes = strlen (text);
if (c_write (cons->fd1, text, n_bytes) != n_bytes)
c_log_critical ("write failed: %s", strerror (errno));
}
NimfCons* nimf_cons_new ()
{
NimfCons* cons;
cons = c_calloc (1, sizeof (NimfCons));
cons->kbmode = -1;
cons->fd1 = posix_openpt (O_RDWR | O_NOCTTY);
if (cons->fd1 == -1 ||
grantpt (cons->fd1) == -1 ||
unlockpt (cons->fd1) == -1 ||
!cons_save_term (cons) ||
!cons_set_term (cons))
{
c_log_critical ("%s", strerror (errno));
nimf_cons_free (cons);
return NULL;
}
return cons;
}
bool nimf_cons_fork (NimfCons* cons)
{
cons->pid = fork ();
if (cons->pid == 0) // child
{
const char* pts_name = ptsname (cons->fd1);
if (!pts_name)
{
c_log_critical ("ptsname failed: %s", strerror (errno));
exit (1);
}
int fd2 = open (pts_name, O_RDWR);
if (fd2 == -1)
{
c_log_critical ("open failed: %s", strerror (errno));
exit (1);
}
close (cons->fd1);
if (setsid () == -1 ||
ioctl (fd2, TIOCSCTTY, NULL) == -1 ||
ioctl (fd2, TIOCSWINSZ, &cons->size) == -1 ||
dup2 (fd2, 0) == -1 ||
dup2 (fd2, 1) == -1 ||
dup2 (fd2, 2) == -1)
{
c_log_critical ("%s", strerror (errno));
exit (1);
}
if (fd2 > 2)
close (fd2);
char* shell = getenv ("SHELL");
if (!shell)
shell = "/bin/sh";
char* args[] = { shell, NULL };
if (!getenv ("TERM"))
setenv ("TERM", "dumb", 1);
if (execve (args[0], args, environ) == -1)
{
c_log_critical ("execve failed: %s", strerror (errno));
exit (1);
}
}
else if (cons->pid > 0) // parent
{
struct xkb_rule_names rmlvo;
CSettings* settings;
char* conf_dir;
const char* layout;
const char* variant;
char** options;
if (!(cons->context = xkb_context_new (XKB_CONTEXT_NO_FLAGS)))
{
c_log_critical ("xkb_context_new failed");
return false;
}
conf_dir = nimf_get_config_dir ();
if (!conf_dir)
{
c_log_critical ("nimf_get_config_dir failed");
return false;
}
settings = c_settings_new (conf_dir, NIMF_SETTINGS_SCHEMA_DIR, "nimf.inputs.console");
layout = c_settings_get_string (settings, "layout");
variant = c_settings_get_string (settings, "variant");
options = c_settings_get_strv (settings, "options");
rmlvo.rules = "evdev";
rmlvo.model = "pc105";
rmlvo.layout = layout;
rmlvo.variant = variant;
rmlvo.options = c_strv_join ((const char**) options, ",");
cons->keymap = xkb_keymap_new_from_names (cons->context, &rmlvo, 0);
free (conf_dir);
c_strv_free (options);
c_settings_free (settings);
free ((char*) rmlvo.options);
if (cons->keymap == NULL)
{
c_log_critical ("xkb_keymap_new_from_names failed");
return false;
}
cons->state = xkb_state_new (cons->keymap);
char* path = cim_get_cim_so_path ();
if (access (path, F_OK))
{
if (symlink (IM_NIMF_SO_PATH, path))
c_log_warning ("symlink failed:", strerror (errno));
}
free (path);
cons->ic = cim_ic_new ();
cim_ic_set_callbacks (cons->ic,
CIM_CB_PREEDIT_CHANGED, cb_preedit_changed, cons,
CIM_CB_COMMIT, cb_commit, cons, -1);
cim_ic_focus_in (cons->ic);
nimf_cons_loop = c_loop_new ();
c_loop_add_fd (nimf_cons_loop, cons->fd1, POLLIN, (CFdCallback) cb_fd,
cons);
c_loop_add_fd (nimf_cons_loop, STDIN_FILENO, POLLIN,
(CFdCallback) cb_stdin, cons);
return true;
}
c_log_critical ("fork failed: %s", strerror (errno));
return false;
}
bool nimf_cons_run (NimfCons* cons)
{
if (nimf_cons_loop)
return c_loop_run (nimf_cons_loop);
return true;
}
static void cb_signal (int signo)
{
if (nimf_cons_loop)
c_loop_quit (nimf_cons_loop);
}
int main ()
{
NimfCons* cons = NULL;
#ifdef __FreeBSD__
bool font_loaded = false;
#endif
const char* warning = gettext (
"\033[7mWARNING\033[0m Nimf Console Input Method is an alpha version.\n"
"This software is provided to demonstrate multilingual input on the console.\n"
"\033[92mThis software has not been fully tested and may crash your terminal.\033[0m\n"
"Would you like to run it anyway? [y/N]: ");
printf ("%s", warning);
int retval = 1;
char buf[3];
if (!fgets (buf, 3, stdin))
goto finally;
c_str_chomp (buf);
if (strlen (buf) != 1 || (buf[0] != 'y' && buf[0] != 'Y'))
goto finally;
setlocale (LC_ALL, "");
bindtextdomain (GETTEXT_PACKAGE, NIMF_LOCALE_DIR);
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
textdomain (GETTEXT_PACKAGE);
if (!isatty (STDIN_FILENO))
goto finally;
#ifdef __FreeBSD__
int status;
if (c_spawn (&status, "/usr/sbin/vidcontrol", "-f",
"/usr/share/vt/fonts/duos.fnt", NULL))
{
if (!status)
{
font_loaded = true;
c_spawn (NULL, "/usr/bin/clear", NULL);
}
else
{
c_log_warning ("duos.fnt is not loaded");
}
}
#endif
cons = nimf_cons_new ();
if (!cons)
goto finally;
struct sigaction action;
action.sa_flags = 0;
action.sa_handler = cb_signal;
sigemptyset (&action.sa_mask);
// Can't catch SIGKILL and SIGSTOP
int signals[] = {
SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGFPE, SIGBUS,
SIGSEGV, SIGSYS, SIGPIPE, SIGALRM, SIGTERM, SIGTSTP, SIGCHLD, SIGTTIN,
SIGTTOU, SIGXCPU, SIGXFSZ, SIGVTALRM, SIGPROF, SIGUSR1, SIGUSR2,
#ifdef SIGEMT
SIGEMT,
#endif
#ifdef SIGTHR
SIGTHR,
#endif
#ifdef SIGLIBRT
SIGLIBRT,
#endif
#ifdef SIGPOLL
SIGPOLL
#endif
};
for (int i = 0; i < C_N_ELEMENTS (signals); i++)
sigaction (signals[i], &action, NULL);
if (nimf_cons_fork (cons))
{
puts ("\033[7mNimf Console Input Method started\033[0m");
retval = !nimf_cons_run (cons);
}
else
{
c_log_critical ("nimf_cons_fork failed");
}
finally:
#ifdef __FreeBSD__
if (font_loaded)
{
c_spawn (NULL, "/usr/sbin/vidcontrol", "-f", NULL);
c_spawn (NULL, "/usr/bin/clear", NULL);
}
#endif
if (cons)
nimf_cons_free (cons);
if (errno && errno != EINTR)
perror ("nimf-cons only works on the console");
if (!retval)
puts ("\033[7mNimf Console Input Method exits\033[0m");
return retval;
}
evdev-scancode2key.c
// -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 2; tab-width: 2 -*-
/*
evdev-scancode2key.c
This file is part of Nimf.
nimf-cons uses evdev_scancode2key().
evdev_at_set1_scancodes[] and evdev_scancode2key() are taken from
https://cgit.freebsd.org/src/plain/sys/dev/evdev/evdev_utils.c
The license is as follows.
*/
/*-
* Copyright (c) 2014 Jakub Wojciech Klama <jceel@FreeBSD.org>
* Copyright (c) 2015-2016 Vladimir Kondratyev <wulf@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifdef __FreeBSD__
#include <stdint.h>
#include <dev/evdev/input-event-codes.h>
// https://cgit.freebsd.org/src/plain/sys/dev/evdev/evdev_utils.c
#define NONE KEY_RESERVED
static uint16_t evdev_at_set1_scancodes[] = {
/* 0x00 - 0x1f */
NONE, KEY_ESC, KEY_1, KEY_2,
KEY_3, KEY_4, KEY_5, KEY_6,
KEY_7, KEY_8, KEY_9, KEY_0,
KEY_MINUS, KEY_EQUAL, KEY_BACKSPACE, KEY_TAB,
KEY_Q, KEY_W, KEY_E, KEY_R,
KEY_T, KEY_Y, KEY_U, KEY_I,
KEY_O, KEY_P, KEY_LEFTBRACE, KEY_RIGHTBRACE,
KEY_ENTER, KEY_LEFTCTRL, KEY_A, KEY_S,
/* 0x20 - 0x3f */
KEY_D, KEY_F, KEY_G, KEY_H,
KEY_J, KEY_K, KEY_L, KEY_SEMICOLON,
KEY_APOSTROPHE, KEY_GRAVE, KEY_LEFTSHIFT, KEY_BACKSLASH,
KEY_Z, KEY_X, KEY_C, KEY_V,
KEY_B, KEY_N, KEY_M, KEY_COMMA,
KEY_DOT, KEY_SLASH, KEY_RIGHTSHIFT, KEY_KPASTERISK,
KEY_LEFTALT, KEY_SPACE, KEY_CAPSLOCK, KEY_F1,
KEY_F2, KEY_F3, KEY_F4, KEY_F5,
/* 0x40 - 0x5f */
KEY_F6, KEY_F7, KEY_F8, KEY_F9,
KEY_F10, KEY_NUMLOCK, KEY_SCROLLLOCK, KEY_KP7,
KEY_KP8, KEY_KP9, KEY_KPMINUS, KEY_KP4,
KEY_KP5, KEY_KP6, KEY_KPPLUS, KEY_KP1,
KEY_KP2, KEY_KP3, KEY_KP0, KEY_KPDOT,
NONE, NONE, KEY_102ND, KEY_F11,
KEY_F12, NONE, NONE, NONE,
NONE, KEY_F13, NONE, NONE,
/* 0x60 - 0x7f */
NONE, NONE, NONE, NONE,
NONE, NONE, NONE, NONE,
NONE, NONE, NONE, NONE,
NONE, NONE, NONE, NONE,
KEY_KATAKANAHIRAGANA, KEY_HANJA, KEY_HANGEUL, KEY_RO,
NONE, NONE, KEY_ZENKAKUHANKAKU, KEY_HIRAGANA,
KEY_KATAKANA, KEY_HENKAN, NONE, KEY_MUHENKAN,
NONE, KEY_YEN, KEY_KPCOMMA, NONE,
/* 0x00 - 0x1f. 0xE0 prefixed */
NONE, NONE, NONE, NONE,
NONE, NONE, NONE, NONE,
NONE, NONE, NONE, NONE,
NONE, NONE, NONE, NONE,
KEY_PREVIOUSSONG, NONE, NONE, NONE,
NONE, NONE, NONE, NONE,
NONE, KEY_NEXTSONG, NONE, NONE,
KEY_KPENTER, KEY_RIGHTCTRL, NONE, NONE,
/* 0x20 - 0x3f. 0xE0 prefixed */
KEY_MUTE, KEY_CALC, KEY_PLAYPAUSE, NONE,
KEY_STOPCD, NONE, NONE, NONE,
NONE, NONE, NONE, NONE,
NONE, NONE, KEY_VOLUMEDOWN, NONE,
KEY_VOLUMEUP, NONE, KEY_HOMEPAGE, NONE,
NONE, KEY_KPSLASH, NONE, KEY_SYSRQ,
KEY_RIGHTALT, NONE, NONE, KEY_F13,
KEY_F14, KEY_F15, KEY_F16, KEY_F17,
/* 0x40 - 0x5f. 0xE0 prefixed */
KEY_F18, KEY_F19, KEY_F20, KEY_F21,
KEY_F22, NONE, KEY_PAUSE, KEY_HOME,
KEY_UP, KEY_PAGEUP, NONE, KEY_LEFT,
NONE, KEY_RIGHT, NONE, KEY_END,
KEY_DOWN, KEY_PAGEDOWN, KEY_INSERT, KEY_DELETE,
NONE, NONE, NONE, KEY_F23,
KEY_F24, NONE, NONE, KEY_LEFTMETA,
KEY_RIGHTMETA, KEY_MENU, KEY_POWER, KEY_SLEEP,
/* 0x60 - 0x7f. 0xE0 prefixed */
NONE, NONE, NONE, KEY_WAKEUP,
NONE, KEY_SEARCH, KEY_BOOKMARKS, KEY_REFRESH,
KEY_STOP, KEY_FORWARD, KEY_BACK, KEY_COMPUTER,
KEY_MAIL, KEY_MEDIA, NONE, NONE,
NONE, NONE, NONE, NONE,
NONE, NONE, NONE, NONE,
NONE, NONE, NONE, NONE,
NONE, NONE, NONE, NONE,
};
// https://cgit.freebsd.org/src/plain/sys/dev/evdev/evdev_utils.c
uint16_t
evdev_scancode2key(int *state, int scancode)
{
uint16_t keycode;
/* translate the scan code into a keycode */
keycode = evdev_at_set1_scancodes[scancode & 0x7f];
switch (*state) {
case 0x00: /* normal scancode */
switch(scancode) {
case 0xE0:
case 0xE1:
*state = scancode;
return (NONE);
}
break;
case 0xE0: /* 0xE0 prefix */
*state = 0;
keycode = evdev_at_set1_scancodes[0x80 + (scancode & 0x7f)];
break;
case 0xE1: /* 0xE1 prefix */
/*
* The pause/break key on the 101 keyboard produces:
* E1-1D-45 E1-9D-C5
* Ctrl-pause/break produces:
* E0-46 E0-C6 (See above.)
*/
*state = 0;
if ((scancode & 0x7f) == 0x1D)
*state = scancode;
return (NONE);
/* NOT REACHED */
case 0x1D: /* pause / break */
case 0x9D:
if ((*state ^ scancode) & 0x80)
return (NONE);
*state = 0;
if ((scancode & 0x7f) != 0x45)
return (NONE);
keycode = KEY_PAUSE;
break;
}
return (keycode);
}
#endif
gen-font.rb
#!/usr/bin/env ruby
# download public domain fonts
#`curl -O "https://gitlab.freedesktop.org/xorg/font/misc-misc/-/raw/master/18x18ko.bdf"`
#`curl -O "https://gitlab.freedesktop.org/xorg/font/misc-misc/-/raw/master/9x18.bdf"`
def extract_hex(bdf)
data = {}
code = nil
state = nil
f = File.open(bdf)
f.each_line { |line|
line.chomp!
if (line.start_with? "ENCODING ")
code = line.split[1].to_i
data[code] = ""
elsif (line == "BITMAP")
state = :bitmap
elsif (line == "ENDCHAR")
code = nil
state = nil
elsif (state == :bitmap)
data[code] << line
end
}
f.close
data
end
data = extract_hex("18x18ko-mod.bdf")
en = extract_hex("9x18.bdf")
en.each { |k, v| data[k] = v }
f3 = File.open("duos.hex", "w")
f3.puts("# Width: 9")
f3.puts("# Height: 18")
sorted_data = data.sort
sorted_data.each { |k, v|
f3.printf("%04X:%s\n", k, v)
}
f3.close
`vtfontcvt duos.hex duos.fnt`
nimf.inputs.console.schema.yaml
---
schema-name:
type: s
default: Console
summary: schema name for nimf-settings
description: This key is intended for nimf-cons.
layout:
type: s
default: us
summary: Keyboard layout
description: Keyboard layout
variant:
type: s
default: ''
summary: Variant of the layout
description: Variant of the layout
options:
type: as
default: []
summary: Keyboard options
description: Keyboard options
Makefile
include ../../config.mk
TARGET = nimf-cons
SCHEMAS = nimf.inputs.console.schema.yaml
UNAME = `uname`
IM_NIMF_SO_PATH = $(libdir)/input.d/im-nimf.so
NIMF_DEPS_CFLAGS = `pkg-config --cflags xkbcommon` -I/usr/local/include -I$(top_srcdir)/clair/src
NIMF_DEPS_LIBS = `pkg-config --libs xkbcommon` \
$(top_srcdir)/clair/src/libclair.so \
$(top_srcdir)/libnimf/libnimf.so.$(LIBNIMF_MAJOR)
SOURCES = nimf-cons.c evdev-scancode2key.c $(top_srcdir)/libnimf/cim.c
CFLAGS = \
$(NIMF_DEPS_CFLAGS) \
$(EXTRA_CFLAGS) \
-I$(top_srcdir)/libnimf \
-DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\" \
-DVERSION=\"$(VERSION)\" \
-DNIMF_LOCALE_DIR=\"$(NIMF_LOCALE_DIR)\" \
-DNIMF_SETTINGS_SCHEMA_DIR=\"$(NIMF_SETTINGS_SCHEMA_DIR)\" \
-DIM_NIMF_SO_PATH=\"$(IM_NIMF_SO_PATH)\" \
-fPIE
LDFLAGS = $(EXTRA_LDFLAGS) $(INTL_LDFLAG) $(DL_LDFLAG) -pie \
-Wl,--as-needed $(NIMF_DEPS_LIBS)
all: $(TARGET)
$(TARGET): $(SOURCES) Makefile $(top_srcdir)/libnimf/nimf-utils.h
$(CC) $(CFLAGS) $(SOURCES) $(LDFLAGS) -o $(TARGET)
clean:
rm -f $(TARGET) *.core font/duos.*
install:
mkdir -p $(DESTDIR)$(prefix)/bin
install -m 755 $(TARGET) $(DESTDIR)$(prefix)/bin
# install csettings schemas
mkdir -p $(DESTDIR)$(NIMF_SETTINGS_SCHEMA_DIR)
install -m 644 $(SCHEMAS) $(DESTDIR)$(NIMF_SETTINGS_SCHEMA_DIR)
mkdir -p $(DESTDIR)/usr/share/vt/fonts
if [ "$(UNAME)" = "FreeBSD" ]; then \
cd font && ./gen-font.rb && cd ..; \
install -m 644 font/duos.fnt $(DESTDIR)/usr/share/vt/fonts; \
fi
uninstall:
rm -f $(DESTDIR)$(prefix)/bin/$(TARGET)
-rmdir -p $(DESTDIR)$(prefix)/bin
# remove csettings schemas
for schema in $(SCHEMAS); do rm -f $(DESTDIR)$(NIMF_SETTINGS_SCHEMA_DIR)/"$$schema"; done
-rmdir -p $(DESTDIR)$(NIMF_SETTINGS_SCHEMA_DIR)
if [ "$(UNAME)" = "FreeBSD" ]; then \
rm -f $(DESTDIR)/usr/share/vt/fonts/duos.fnt; \
fi
-rmdir -p $(DESTDIR)/usr/share/vt/fonts