text: /* See LICENSE for licence details. */
  /* yaft.c: include main function */
  #include "yaft.h"
  #include "conf.h"
  #include "util.h"
  #include "fb/common.h"
  #include "terminal.h"
  #include "ctrlseq/esc.h"
  #include "ctrlseq/csi.h"
  #include "ctrlseq/osc.h"
  #include "ctrlseq/dcs.h"
  #include "parse.h"
  
  void sig_handler(int signo)
  {
  	sigset_t sigset;
  	/* global */
  	extern volatile sig_atomic_t vt_active;
  	extern volatile sig_atomic_t child_alive;
  	extern volatile sig_atomic_t need_redraw;
  
  	logging(DEBUG, "caught signal! no:%d\n", signo);
  
  	if (signo == SIGCHLD) {
  		child_alive = false;
  		wait(NULL);
  	} else if (signo == SIGUSR1) { /* vt activate */
  		vt_active   = true;
  		need_redraw = true;
  		ioctl(STDIN_FILENO, VT_RELDISP, VT_ACKACQ);
  	} else if (signo == SIGUSR2) { /* vt deactivate */
  		vt_active = false;
  		ioctl(STDIN_FILENO, VT_RELDISP, 1);
  
  		if (BACKGROUND_DRAW) { /* update passive cursor */
  			need_redraw = true;
  		} else {               /* sleep until next vt switching */
  			sigfillset(&sigset);
  			sigdelset(&sigset, SIGUSR1);
  			sigsuspend(&sigset);
  		}
  	} else if (signo == SIGTERM) {
  		exit(0);
  	}
  }
  
  void set_rawmode(int fd, struct termios *save_tm)
  {
  	struct termios tm;
  
  	tm = *save_tm;
  	tm.c_iflag     = tm.c_oflag = 0;
  	tm.c_cflag    &= ~CSIZE;
  	tm.c_cflag    |= CS8;
  	tm.c_lflag    &= ~(ECHO | ISIG | ICANON);
  	tm.c_cc[VMIN]  = 1; /* min data size (byte) */
  	tm.c_cc[VTIME] = 0; /* time out */
  	etcsetattr(fd, TCSAFLUSH, &tm);
  }
  
  bool tty_init(struct termios *termios_orig)
  {
  	struct sigaction sigact;
  
  	memset(&sigact, 0, sizeof(struct sigaction));
  	sigact.sa_handler = sig_handler;
  	sigact.sa_flags   = SA_RESTART;
  	esigaction(SIGCHLD, &sigact, NULL);
  	esigaction(SIGTERM, &sigact, NULL);
  
  	if (VT_CONTROL) {
  		esigaction(SIGUSR1, &sigact, NULL);
  		esigaction(SIGUSR2, &sigact, NULL);
  
  		struct vt_mode vtm;
  		vtm.mode   = VT_PROCESS;
  		vtm.waitv  = 0;
  		vtm.acqsig = SIGUSR1;
  		vtm.relsig = SIGUSR2;
  		vtm.frsig  = 0;
  
  		if (ioctl(STDIN_FILENO, VT_SETMODE, &vtm))
  			logging(WARN, "ioctl: VT_SETMODE failed (maybe here is not console)\n");
  
  		if (FORCE_TEXT_MODE == false) {
  			if (ioctl(STDIN_FILENO, KDSETMODE, KD_GRAPHICS))
  				logging(WARN, "ioctl: KDSETMODE failed (maybe here is not console)\n");
  		}
  	}
  
  	etcgetattr(STDIN_FILENO, termios_orig);
  	set_rawmode(STDIN_FILENO, termios_orig);
  	ewrite(STDIN_FILENO, "\033[?25l", 6); /* make cusor invisible */
  
  	return true;
  }
  
  void tty_die(struct termios *termios_orig)
  {
   	/* no error handling */
  	struct sigaction sigact;
  	struct vt_mode vtm;
  
  	memset(&sigact, 0, sizeof(struct sigaction));
  	sigact.sa_handler = SIG_DFL;
  	sigaction(SIGCHLD, &sigact, NULL);
  
  	if (VT_CONTROL) {
  		sigaction(SIGUSR1, &sigact, NULL);
  		sigaction(SIGUSR2, &sigact, NULL);
  
  		vtm.mode   = VT_AUTO;
  		vtm.waitv  = 0;
  		vtm.relsig = vtm.acqsig = vtm.frsig = 0;
  
  		ioctl(STDIN_FILENO, VT_SETMODE, &vtm);
  
  		if (FORCE_TEXT_MODE == false)
  			ioctl(STDIN_FILENO, KDSETMODE, KD_TEXT);
  	}
  
  	tcsetattr(STDIN_FILENO, TCSAFLUSH, termios_orig);
  	fflush(stdout);
  	ewrite(STDIN_FILENO, "\033[?25h", 6); /* make cursor visible */
  }
  
  bool fork_and_exec(int *master, const char *cmd, char *const argv[], int lines, int cols)
  {
  	pid_t pid;
  	struct winsize ws = {.ws_row = lines, .ws_col = cols,
  		/* XXX: this variables are UNUSED (man tty_ioctl),
  			but useful for calculating terminal cell size */
  		.ws_ypixel = CELL_HEIGHT * lines, .ws_xpixel = CELL_WIDTH * cols};
  
  	pid = eforkpty(master, NULL, NULL, &ws);
  	if (pid < 0)
  		return false;
  	else if (pid == 0) { /* child */
  		esetenv("TERM", term_name, 1);
  		eexecvp(cmd, argv);
  		/* never reach here */
  		exit(EXIT_FAILURE);
  	}
  	return true;
  }
  
  int check_fds(fd_set *fds, struct timeval *tv, int input, int master)
  {
  	FD_ZERO(fds);
  	FD_SET(input, fds);
  	FD_SET(master, fds);
  	tv->tv_sec  = 0;
  	tv->tv_usec = SELECT_TIMEOUT;
  	return eselect(master + 1, fds, NULL, NULL, tv);
  }
  
  #define KEY_ENTRY(seq, name) { seq, sizeof(seq) - 1, name, sizeof(name) - 1 }
  struct {
  	const char* seq;
  	size_t seqlen;
  	const char* name;
  	size_t namelen;
  } fkey_table[] = {
  	KEY_ENTRY("\x0", "C-@"),
  	KEY_ENTRY("\x1", "C-a"),
  	KEY_ENTRY("\x2", "C-b"),
  	KEY_ENTRY("\x3", "C-c"),
  	KEY_ENTRY("\x4", "C-d"),
  	KEY_ENTRY("\x5", "C-e"),
  	KEY_ENTRY("\x6", "C-f"),
  	KEY_ENTRY("\x7", "C-g"),
  	KEY_ENTRY("\x8", "C-h"),
  	KEY_ENTRY("\x9", "C-i"),
  	KEY_ENTRY("\xa", "C-j"),
  	KEY_ENTRY("\xb", "C-k"),
  	KEY_ENTRY("\xc", "C-l"),
  	KEY_ENTRY("\xd", "Return"),
  	KEY_ENTRY("\xe", "C-n"),
  	KEY_ENTRY("\xf", "C-o"),
  	KEY_ENTRY("\x10", "C-p"),
  	KEY_ENTRY("\x11", "C-q"),
  	KEY_ENTRY("\x12", "C-r"),
  	KEY_ENTRY("\x13", "C-s"),
  	KEY_ENTRY("\x14", "C-t"),
  	KEY_ENTRY("\x15", "C-u"),
  	KEY_ENTRY("\x16", "C-v"),
  	KEY_ENTRY("\x17", "C-w"),
  	KEY_ENTRY("\x18", "C-x"),
  	KEY_ENTRY("\x19", "C-y"),
  	KEY_ENTRY("\x1a", "C-z"),
  	KEY_ENTRY("\x1c", "C-\\"),
  	KEY_ENTRY("\x1d", "C-]"),
  	KEY_ENTRY("\x1e", "C-^"),
  	KEY_ENTRY("\x1f", "C-_"),
  	KEY_ENTRY("\x20", "SPC"),
  	KEY_ENTRY("\x7f", "DEL"),
  	KEY_ENTRY("\033[[A", "F1"),
  	KEY_ENTRY("\x1b", "C-["), // エスケープシーケンスよりも後に置く。
  	KEY_ENTRY("\033[[A", "F1"),
  	KEY_ENTRY("\033[[B", "F2"),
  	KEY_ENTRY("\033[[C", "F3"),
  	KEY_ENTRY("\033[[D", "F4"),
  	KEY_ENTRY("\033[[E", "F5"),
  	KEY_ENTRY("\033[17~", "F6"),
  	KEY_ENTRY("\033[18~", "F7"),
  	KEY_ENTRY("\033[19~", "F8"),
  	KEY_ENTRY("\033[20~", "F9"),
  	KEY_ENTRY("\033[21~", "F10"),
  	KEY_ENTRY("\033[23~", "F11"),
  	KEY_ENTRY("\033[24~", "F12"),
  	KEY_ENTRY("\033[P", "Pause"),
  	KEY_ENTRY("\033[2~", "Insert"),
  	KEY_ENTRY("\033[1~", "Home"),
  	KEY_ENTRY("\033[5~", "PageUp"),
  	KEY_ENTRY("\033[3~", "Delete"),
  	KEY_ENTRY("\033[4~", "End"),
  	KEY_ENTRY("\033[6~", "PageDown"),
  	KEY_ENTRY("\033[D", "Left"),
  	KEY_ENTRY("\033[A", "Up"),
  	KEY_ENTRY("\033[B", "Down"),
  	KEY_ENTRY("\033[C", "Right"),
  	KEY_ENTRY("\033[25~", "(shift) F1"),
  	KEY_ENTRY("\033[26~", "(shift) F2"),
  	KEY_ENTRY("\033[28~", "(shift) F3"),
  	KEY_ENTRY("\033[29~", "(shift) F4"),
  	KEY_ENTRY("\033[31~", "(shift) F5"),
  	KEY_ENTRY("\033[32~", "(shift) F6"),
  	KEY_ENTRY("\033[33~", "(shift) F7"),
  	KEY_ENTRY("\033[34~", "(shift) F8"),
  };
  #undef KEY_ENTRY
  
  struct framebuffer_t fb;
  struct terminal_t term;
  static bool fb_initialized = false;
  static bool term_initialized = false;
  static bool tty_initialized = false;
  
  void cleanup_subsystems(void)
  {
  	extern struct termios termios_orig;
  
  	if (tty_initialized) {
  		tty_die(&termios_orig);
  		tty_initialized = false;
  	}
  	if (term_initialized) {
  		term_die(&term);
  		term_initialized = false;
  	}
  	if (fb_initialized) {
  		fb_die(&fb);
  		fb_initialized = false;
  	}
  }
  
  int search_fkey(const uint8_t *buf)
  {
  	for (int j = 0; j < (int) (sizeof(fkey_table)/sizeof(fkey_table[0])); j++) {
  		if (strncmp((const char*) buf, fkey_table[j].seq, fkey_table[j].seqlen) == 0) {
  			return j;
  		}
  	}
  	return -1;
  }
  
  int main(int argc, char *const argv[])
  {
  	extern const char *shell_cmd; /* defined in conf.h */
  	const char *cmd;
  	uint8_t buf[BUFSIZE];
  	ssize_t size;
  	fd_set fds;
  	struct timeval tv;
  	/* global */
  	extern volatile sig_atomic_t need_redraw;
  	extern volatile sig_atomic_t child_alive;
  	extern struct termios termios_orig;
  
  	atexit(cleanup_subsystems);
  
  	/* init */
  	if (setlocale(LC_ALL, "") == NULL) /* for wcwidth() */
  		logging(WARN, "setlocale falied\n");
  
  	if (!(fb_initialized = fb_init(&fb))) {
  		logging(FATAL, "framebuffer initialize failed\n");
  		goto ErrorExit;
  	}
  
  	if (!(term_initialized = term_init(&term, fb.info.width, fb.info.height))) {
  		logging(FATAL, "terminal initialize failed\n");
  		goto ErrorExit;
  	}
  
  	if (!init_input_method(&term)) {
  		logging(ERROR, "input method initialize failed\n");
  	}
  	set_input_mode(&term, "latin");
  
  	if (!(tty_initialized = tty_init(&termios_orig))) {
  		logging(FATAL, "tty initialize failed\n");
  		goto ErrorExit;
  	}
  
  	/* fork and exec shell */
  	cmd = (argc < 2) ? shell_cmd: argv[1];
  	if (!fork_and_exec(&term.fd, cmd, argv + 1, term.lines, term.cols)) {
  		logging(FATAL, "forkpty failed\n");
  		goto ErrorExit;
  	}
  	child_alive = true;
  
  	term.input_method_enabled = true;
  
  	/* main loop */
  	while (child_alive) {
  		if (need_redraw) {
  			need_redraw = false;
  			cmap_update(fb.fd, fb.cmap); /* after VT switching, need to restore cmap (in 8bpp mode) */
  			redraw(&term);
  			refresh(&fb, &term);
  		}
  
  		if (check_fds(&fds, &tv, STDIN_FILENO, term.fd) == -1)
  			continue;
  
  		if (FD_ISSET(STDIN_FILENO, &fds)) {
  			if ((size = read(STDIN_FILENO, buf, BUFSIZE)) > 0) {
  				if (term.input_method_enabled) {
  					for (int i = 0; i < size; ) {
  						int j = search_fkey(buf + i);
  						if (j >= 0) {
  							if (!im_write(&term, fkey_table[j].name, fkey_table[j].namelen))
  								ewrite(term.fd, fkey_table[j].seq, fkey_table[j].seqlen);
  
  							i += fkey_table[j].seqlen;
  						} else {
  							if (!im_write(&term, (const char*)buf + i, 1))
  								ewrite(term.fd, &buf[i], 1);
  
  							i++;
  						}
  					}
  				} else {
  					ewrite(term.fd, buf, size);
  				}
  			}
  		}
  		if (FD_ISSET(term.fd, &fds)) {
  			if ((size = read(term.fd, buf, BUFSIZE)) > 0) {
  				if (VERBOSE)
  					ewrite(STDOUT_FILENO, buf, size);
  				parse(&term, buf, size);
  				if (LAZY_DRAW && size == BUFSIZE)
  					continue; /* maybe more data arrives soon */
  				refresh(&fb, &term);
  			}
  		}
  	}
  
  	/* normal exit */
  	return EXIT_SUCCESS;
  
  	/* error exit */
  ErrorExit:
  	return EXIT_FAILURE;
  }
  