물어보면 돼 ㅎㅎ

러스트 빠돌이가 해봤는지

열등감 개쩔어

비꼬면서 나까는거 봐라

ㅎㅎ

gpt나 제미니나 똑같은 얘기한다.

고급 수준이라고

궁금하면 코드 올리고 물어봐


ㅆㅇㅆ, 러빨러

너네들은 온니 나 까는게 목적이잖아 

ㅎㅎㅎ


코드 llm한테 올리고 물어보라고.

열등감 개쩔어서

프갤에서 허수아비 공격하지 말고

할짓거리 없으면

야간 택배나 뎅기던가 해라.


// -*- 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;
}