ㅆㅇㅆ아, 나더러 초보자라고 모함하지 말고,
내 실력 궁금하면 다음 코드를 GPT, 제미니한테 입력하고 프로그래머 실력이 어느 정도냐고 물어봐라.
그리고 제발 프갤와서 깽판치지 말고, 시비 걸지 말거라.
그리고 특히,
너 전문 분야 아니면, 괜히 남의 전문 분야에 껴들어서
잘난척하면서 타인에게 실력이 없다는 식으로 비하하지 좀 말라.
// -*- 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 "clair.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 clair_event_loop_t* 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;
CimIcHandle 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)
{
clair_event_loop_destroy (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_destroy (cons->ic);
}
free (cons);
}
static void cb_fd (clair_event_source_t* io,
clair_fd_t fd,
clair_event_mask_t events,
void* user_data)
{
if (events & (CLAIR_EVENT_ERROR | CLAIR_EVENT_HANG_UP))
{
clair_event_loop_remove (nimf_cons_loop, io);
clair_event_loop_quit (nimf_cons_loop);
return;
}
NimfCons* cons = (NimfCons*) user_data;
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));
clair_event_loop_remove (nimf_cons_loop, io);
return;
}
}
}
}
static int cb_compare (const void* a, const void* b)
{
const int* keyval = a;
const Keyval2Vt100* key = b;
return (*keyval - key->keyval);
}
static void cb_stdin (clair_event_source_t* io,
clair_fd_t fd,
clair_event_mask_t events,
void* user_data)
{
if (events & (CLAIR_EVENT_ERROR | CLAIR_EVENT_HANG_UP))
{
clair_event_loop_remove (nimf_cons_loop, io);
clair_event_loop_quit (nimf_cons_loop);
return;
}
NimfCons* cons = (NimfCons*) user_data;
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");
clair_event_loop_remove (nimf_cons_loop, io);
return;
}
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;
}
}
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;
}
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");
}
}
}
}
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 (CimIcHandle ic,
const CimPreedit* preedit,
void* user_data)
{
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 (CimIcHandle ic, const char* text, void* user_data)
{
size_t n_bytes = strlen (text);
NimfCons* cons = (NimfCons*) user_data;
if (c_write (cons->fd1, text, n_bytes) != n_bytes)
c_log_critical ("write failed: %s", strerror (errno));
}
const static CimCallbacks callbacks = {
.preedit_changed = cb_preedit_changed,
.commit = cb_commit
};
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_create ();
cim_ic_set_callbacks (cons->ic, &callbacks, cons);
cim_ic_focus_in (cons->ic);
nimf_cons_loop = clair_event_loop_create ();
clair_event_loop_add_watch (nimf_cons_loop,
cons->fd1,
CLAIR_EVENT_INPUT,
cb_fd,
cons);
clair_event_loop_add_watch (nimf_cons_loop,
STDIN_FILENO,
CLAIR_EVENT_INPUT,
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 clair_event_loop_run (nimf_cons_loop);
return true;
}
static void cb_signal (int signo)
{
if (nimf_cons_loop)
clair_event_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;
}
댓글 0