/* fhandler_console.cc

This file is part of Cygwin.

This software is a copyrighted work licensed under the terms of the
Cygwin license.  Please consult the file "CYGWIN_LICENSE" for
details. */

#include "winsup.h"
#include "miscfuncs.h"
#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>
#include <ctype.h>
#include <sys/param.h>
#include <sys/cygwin.h>
#include <cygwin/kd.h>
#include <unistd.h>
#include "cygerrno.h"
#include "security.h"
#include "path.h"
#include "fhandler.h"
#include "dtable.h"
#include "cygheap.h"
#include "sigproc.h"
#include "pinfo.h"
#include "shared_info.h"
#include "cygtls.h"
#include "tls_pbuf.h"
#include "registry.h"
#include <asm/socket.h>
#include "sync.h"
#include "child_info.h"
#include "cygwait.h"
#include "winf.h"
#include "psapi.h"

/* Don't make this bigger than NT_MAX_PATH as long as the temporary buffer
   is allocated using tmp_pathbuf!!! */
#define CONVERT_LIMIT NT_MAX_PATH

#define ALT_PRESSED (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)
#define CTRL_PRESSED (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)

#define con (shared_console_info[unit]->con)
#define srTop (con.b.srWindow.Top + con.scroll_region.Top)
#define srBottom ((con.scroll_region.Bottom < 0) ? \
		  con.b.srWindow.Bottom : \
		  con.b.srWindow.Top + con.scroll_region.Bottom)
#define con_is_legacy (shared_console_info[unit] && con.is_legacy)

static HANDLE NO_COPY shared_info_mutex;
static int NO_COPY shared_info_state[MAX_CONS_DEV];

#define CONS_THREAD_SYNC "cygcons.thread_sync"
static bool NO_COPY master_thread_started = false;

const unsigned fhandler_console::MAX_WRITE_CHARS = 16384;

fhandler_console::console_state NO_COPY
  *fhandler_console::shared_console_info[MAX_CONS_DEV + 1];

static bool NO_COPY inside_pcon_checked = false;
static bool NO_COPY inside_pcon = false;
static int NO_COPY parent_pty;
static HANDLE NO_COPY parent_pty_input_mutex = NULL;

bool NO_COPY fhandler_console::invisible_console;

/* con_ra is shared in the same process.
   Only one console can exist in a process, therefore, static is suitable. */
static struct fhandler_base::rabuf_t con_ra;

/* Write pending buffer for ESC sequence handling
   in xterm compatible mode */
static wchar_t last_char;

DWORD
fhandler_console::attach_console (DWORD owner, bool *err)
{
  DWORD resume_pid = (DWORD) -1;
  if (!process_alive (owner))
    return resume_pid;
  DWORD attached =
    get_console_process_id (owner, true, false, false);
  if (!attached)
    {
      resume_pid =
	get_console_process_id (GetCurrentProcessId (), false, false, false);
      FreeConsole ();
      BOOL r = AttachConsole (owner);
      if (!r)
	{
	  if (resume_pid)
	    AttachConsole (resume_pid);
	  if (err)
	    *err = true;
	  return (DWORD) -1;
	}
    }
  return resume_pid;
}

void
fhandler_console::detach_console (DWORD resume_pid, DWORD owner)
{
  if (resume_pid == (DWORD) -1)
    return;
  if (resume_pid)
    {
      FreeConsole ();
      AttachConsole (resume_pid);
    }
  else if (GetCurrentProcessId () != owner)
    FreeConsole ();
}

DWORD
fhandler_console::get_owner ()
{
  return con.owner;
}

/* simple helper class to accumulate output in a buffer
   and send that to the console on request: */
static class write_pending_buffer
{
private:
  static const size_t WPBUF_LEN = 256u;
  char buf[WPBUF_LEN];
  size_t ixput;
public:
  void init ()
  {
    empty ();
  }
  inline void put (HANDLE output_handle, DWORD owner, char x)
  {
    if (ixput == WPBUF_LEN)
      send (output_handle, owner);
    buf[ixput++] = x;
  }
  inline void empty () { ixput = 0u; }
  inline void send (HANDLE output_handle, DWORD owner)
  {
    if (!output_handle)
      {
	empty ();
	return;
      }
    mbtowc_p f_mbtowc =
      (__MBTOWC == __ascii_mbtowc) ? __utf8_mbtowc : __MBTOWC;
    wchar_t bufw[WPBUF_LEN];
    DWORD len = 0;
    mbstate_t ps;
    memset (&ps, 0, sizeof (ps));
    char *p = buf;
    while (ixput)
      {
	int bytes = f_mbtowc (_REENT, bufw + len, p, ixput, &ps);
	if (bytes < 0)
	  {
	    if ((size_t) ps.__count < ixput)
	      { /* Discard one byte and retry. */
		p++;
		ixput--;
		memset (&ps, 0, sizeof (ps));
		continue;
	      }
	    /* Halfway through the multibyte char. */
	    memmove (buf, p, ixput);
	    break;
	  }
	else
	  {
	    len++;
	    p += bytes;
	    ixput -= bytes;
	  }
      }
    acquire_attach_mutex (mutex_timeout);
    DWORD resume_pid = fhandler_console::attach_console (owner);
    WriteConsoleW (output_handle, bufw, len, NULL, 0);
    fhandler_console::detach_console (resume_pid, owner);
    release_attach_mutex ();
  }
} wpbuf;

void
fhandler_console::wpbuf_put (char c)
{
  wpbuf.put (get_output_handle (), con.owner, c);
}

void
fhandler_console::wpbuf_send ()
{
  wpbuf.send (get_output_handle (), con.owner);
}

static void
beep ()
{
  const WCHAR ding[] = L"\\media\\ding.wav";
  reg_key r (HKEY_CURRENT_USER, KEY_ALL_ACCESS, L"AppEvents", L"Schemes",
	     L"Apps", L".Default", L".Default", L".Current", NULL);
  if (r.created ())
    {
      tmp_pathbuf tp;

      PWCHAR ding_path = tp.w_get ();
      wcpcpy (wcpcpy (ding_path, windows_directory), ding);
      r.set_string (L"", ding_path);
    }
  MessageBeep (MB_OK);
}

fhandler_console::console_state *
fhandler_console::open_shared_console (HWND hw, HANDLE& h, bool& created)
{
  wchar_t namebuf[(sizeof "XXXXXXXXXXXXXXXXXX-consNNNNNNNNNN")];
  __small_swprintf (namebuf, L"%S-cons%p", &cygheap->installation_key, hw);

  shared_locations m = created ? SH_SHARED_CONSOLE : SH_JUSTOPEN;
  console_state *res = (console_state *)
    open_shared (namebuf, 0, h, sizeof (console_state), m, created);
  if (m == SH_SHARED_CONSOLE)
    cygheap->shared_regions.console_shared_addr = res;
  return res;
}

fhandler_console::console_unit::console_unit (int n0, HANDLE *input_mutex) :
  n (-1)
{
  char buf[MAX_PATH];
  for (int i = max(0, n0); i < MAX_CONS_DEV; i++)
    {
      shared_name (buf, "cygcons.input.mutex", i);
      SetLastError (ERROR_SUCCESS);
      HANDLE input_mutex0 = CreateMutex (&sec_none, FALSE, buf);
      DWORD err = GetLastError ();
      if (err == ERROR_ALREADY_EXISTS || err == ERROR_ACCESS_DENIED)
	{
	  if (n0 >= 0)
	    n = i;
	}
      else if (n0 == CONS_SCAN_UNUSED)
	{
	  n = i;
	  if (input_mutex)
	    *input_mutex = input_mutex0;
	  break;
	}
      if (input_mutex0)
	CloseHandle (input_mutex0);
      if (n0 >= 0)
	break;
    }
  if (n0 == CONS_SCAN_UNUSED && n < 0)
    {
      __small_sprintf (buf, "console device allocation failure - "
		       "too many consoles in use, max consoles is %d",
		       MAX_CONS_DEV);
      api_fatal (buf);
    }
}

fhandler_console::console_unit::operator console_state * () const
{
  if (n < 0 || n >= MAX_CONS_DEV)
    return NULL;
  HANDLE h = NULL;
  fhandler_console::console_state *cs;
  HWND hw = cygwin_shared->cons_hwnd[n];
  if ((cs = fhandler_console::open_shared_console (hw, h)))
    CloseHandle (h);
  return cs;
}

static DWORD
cons_master_thread (VOID *arg)
{
  fhandler_console *fh = (fhandler_console *) arg;
  tty *ttyp = (tty *) fh->tc ();
  fhandler_console::handle_set_t handle_set;
  fh->get_duplicated_handle_set (&handle_set);
  HANDLE thread_sync_event;
  if (DuplicateHandle (GetCurrentProcess (), fh->thread_sync_event,
		       GetCurrentProcess (), &thread_sync_event,
		       0, FALSE, DUPLICATE_SAME_ACCESS))
    {
      SetEvent (thread_sync_event);
      master_thread_started = true;
      /* Do not touch class members after here because the class instance
	 may have been destroyed. */
      fhandler_console::cons_master_thread (&handle_set, ttyp);
      fhandler_console::close_handle_set (&handle_set);
      SetEvent (thread_sync_event);
      CloseHandle (thread_sync_event);
      master_thread_started = false;
    }
  else
    debug_printf ("cons_master_thread not started because thread_sync_event "
		  "could not be duplicated %08x", GetLastError ());
  return 0;
}

/* Compare two INPUT_RECORD sequences */
static inline bool
inrec_eq (const INPUT_RECORD *a, const INPUT_RECORD *b, DWORD n)
{
  for (DWORD i = 0; i < n; i++)
    {
      if (a[i].EventType != b[i].EventType)
	return false;
      else if (a[i].EventType == KEY_EVENT)
	{ /* wVirtualKeyCode, wVirtualScanCode and dwControlKeyState
	     of the readback key event may be different from that of
	     written event. Therefore they are ignored. */
	  const KEY_EVENT_RECORD *ak = &a[i].Event.KeyEvent;
	  const KEY_EVENT_RECORD *bk = &b[i].Event.KeyEvent;
	  WCHAR c1 = ak->uChar.UnicodeChar;
	  WCHAR c2 = bk->uChar.UnicodeChar;
	  if (inside_pcon)
	    {
	      /* Workaround for pseudo console in Windows 11 */
	      if (c1 == 8) /* Ctrl-H */
		c1 = 127; /* Backspace */
	      if (c2 == 8) /* Ctrl-H */
		c2 = 127; /* Backspace */
	    }
	  /* On Windows 11, conhost normalizes wRepeatCount from 0 to 1
	     on readback. Treat them as equivalent for comparison. */
	  WORD r1 = ak->wRepeatCount;
	  WORD r2 = bk->wRepeatCount;
	  if (r1 == 0)
	    r1 = 1;
	  if (r2 == 0)
	    r2 = 1;
	  if (ak->bKeyDown != bk->bKeyDown
	      || c1 != c2
	      || r1 != r2)
	    return false;
	}
      else if (a[i].EventType == MOUSE_EVENT)
	{
	  const MOUSE_EVENT_RECORD *am = &a[i].Event.MouseEvent;
	  const MOUSE_EVENT_RECORD *bm = &b[i].Event.MouseEvent;
	  if (am->dwMousePosition.X != bm->dwMousePosition.X
	      || am->dwMousePosition.Y != bm->dwMousePosition.Y
	      || am->dwButtonState != bm->dwButtonState
	      || am->dwControlKeyState != bm->dwControlKeyState
	      || am->dwEventFlags != bm->dwEventFlags)
	    return false;
	}
      else if (a[i].EventType == WINDOW_BUFFER_SIZE_EVENT)
	{
	  const WINDOW_BUFFER_SIZE_RECORD
	    *aw = &a[i].Event.WindowBufferSizeEvent;
	  const WINDOW_BUFFER_SIZE_RECORD
	    *bw = &b[i].Event.WindowBufferSizeEvent;
	  if (aw->dwSize.X != bw->dwSize.X
	      || aw->dwSize.Y != bw->dwSize.Y)
	    return false;
	}
      else if (a[i].EventType == MENU_EVENT)
	{
	  const MENU_EVENT_RECORD *am = &a[i].Event.MenuEvent;
	  const MENU_EVENT_RECORD *bm = &b[i].Event.MenuEvent;
	  if (am->dwCommandId != bm->dwCommandId)
	    return false;
	}
      else if (a[i].EventType == FOCUS_EVENT)
	{
	  const FOCUS_EVENT_RECORD *af = &a[i].Event.FocusEvent;
	  const FOCUS_EVENT_RECORD *bf = &b[i].Event.FocusEvent;
	  if (af->bSetFocus != bf->bSetFocus)
	    return false;
	}
    }
  return true;
}

/* This thread processes signals derived from input messages.
   Without this thread, those signals can be handled only when
   the process calls read() or select(). This thread reads input
   records, processes signals and removes corresponding record.
   The other input records are kept back for read() or select(). */
void
fhandler_console::cons_master_thread (handle_set_t *p, tty *ttyp)
{
  const _minor_t unit = p->unit;
  const int additional_space = 128; /* Possible max number of incoming events
				       during the process. Additional space
				       should be left for writeback fix. */
  DWORD inrec_size = INREC_SIZE + additional_space;
  INPUT_RECORD *input_rec =
    (INPUT_RECORD *) malloc (inrec_size * sizeof (INPUT_RECORD));
  INPUT_RECORD *input_tmp =
    (INPUT_RECORD *) malloc (inrec_size * sizeof (INPUT_RECORD));

  if (!input_rec || !input_tmp)
    { /* Cannot continue */
      free (input_rec);
      free (input_tmp);
      return;
    }

  struct m
  {
    inline static size_t bytes (size_t n)
      {
	return sizeof (INPUT_RECORD) * n;
      }
  };
  termios &ti = ttyp->ti;
  while (con.owner == GetCurrentProcessId ())
    {
      DWORD total_read, n, i;

      if (con.disable_master_thread)
	{
	  cygwait (40);
	  continue;
	}

      acquire_attach_mutex (mutex_timeout);
      GetNumberOfConsoleInputEvents (p->input_handle, &total_read);
      release_attach_mutex ();
      if (total_read > INREC_SIZE)
	{
	  cygwait (40);
	  acquire_attach_mutex (mutex_timeout);
	  GetNumberOfConsoleInputEvents (p->input_handle, &n);
	  release_attach_mutex ();
	  if (n < total_read)
	    {
	      /* read() seems to be called. Process special keys
		 in process_input_message (). */
	      con.master_thread_suspended = true;
	      continue;
	    }
	  total_read = n;
	}
      con.master_thread_suspended = false;
      if (total_read + additional_space > inrec_size)
	{
	  DWORD new_inrec_size = total_read + additional_space;
	  INPUT_RECORD *new_input_rec = (INPUT_RECORD *)
	    realloc (input_rec, m::bytes (new_inrec_size));
	  if (new_input_rec)
	    input_rec = new_input_rec;
	  INPUT_RECORD *new_input_tmp = (INPUT_RECORD *)
	    realloc (input_tmp, m::bytes (new_inrec_size));
	  if (new_input_tmp)
	    input_tmp = new_input_tmp;
	  if (new_input_rec && new_input_tmp)
	    inrec_size = new_inrec_size;
	}

      WaitForSingleObject (p->input_mutex, mutex_timeout);
      /* Ensure accessing input recored is not disabled. */
      if (con.disable_master_thread)
	{
	  ReleaseMutex (p->input_mutex);
	  continue;
	}
      total_read = 0;
      if (inside_pcon && parent_pty_input_mutex)
	WaitForSingleObject (parent_pty_input_mutex, mutex_timeout);
      switch (cygwait (p->input_handle, (DWORD) 0))
	{
	case WAIT_OBJECT_0:
	  acquire_attach_mutex (mutex_timeout);
	  total_read = 0;
	  while (cygwait (p->input_handle, (DWORD) 0) == WAIT_OBJECT_0
		 && total_read < inrec_size)
	    {
	      DWORD len;
	      ReadConsoleInputW (p->input_handle, input_rec + total_read,
				 min (inrec_size - total_read, inrec_size),
				 &len);
	      total_read += len;
	    }
	  release_attach_mutex ();
	  break;
	case WAIT_TIMEOUT:
	  con.num_processed = 0;
	case WAIT_SIGNALED:
	case WAIT_CANCELED:
	  break;
	default: /* Error */
	  free (input_rec);
	  free (input_tmp);
	  if (inside_pcon && parent_pty_input_mutex)
	    ReleaseMutex (parent_pty_input_mutex);
	  ReleaseMutex (p->input_mutex);
	  return;
	}
      /* If ENABLE_VIRTUAL_TERMINAL_INPUT is not set, changing
	 window height does not generate WINDOW_BUFFER_SIZE_EVENT.
	 Therefore, check windows size every time here. */
      if (!wincap.has_con_24bit_colors () || con_is_legacy)
	{
	  SHORT y = con.dwWinSize.Y;
	  SHORT x = con.dwWinSize.X;
	  con.fillin (p->output_handle);
	  if (y != con.dwWinSize.Y || x != con.dwWinSize.X)
	    {
	      con.scroll_region.Top = 0;
	      con.scroll_region.Bottom = -1;
	      ttyp->kill_pgrp (SIGWINCH);
	    }
	}
      for (i = con.num_processed; i < total_read; i++)
	{
	  wchar_t wc;
	  char c;
	  bool processed = false;
	  switch (input_rec[i].EventType)
	    {
	    case KEY_EVENT:
	      if (!input_rec[i].Event.KeyEvent.bKeyDown)
		continue;
	      wc = input_rec[i].Event.KeyEvent.uChar.UnicodeChar;
	      if (!wc || (wint_t) wc >= 0x80)
		continue;
	      c = (char) wc;
	      switch (process_sigs (c, ttyp, NULL))
		{
		case signalled:
		case not_signalled_but_done:
		case done_with_debugger:
		  processed = true;
		  ttyp->output_stopped = false;
		  if (ti.c_lflag & NOFLSH)
		    goto remove_record;
		  con.num_processed = 0;
		  goto skip_writeback;
		default: /* not signalled */
		  break;
		}
	      processed = process_stop_start (c, ttyp);
	      break;
	    case WINDOW_BUFFER_SIZE_EVENT:
	      SHORT y = con.dwWinSize.Y;
	      SHORT x = con.dwWinSize.X;
	      con.fillin (p->output_handle);
	      if (y != con.dwWinSize.Y || x != con.dwWinSize.X)
		{
		  con.scroll_region.Top = 0;
		  con.scroll_region.Bottom = -1;
		  if (wincap.has_con_24bit_colors () && !con_is_legacy
		      && wincap.has_con_broken_tabs ())
		    fix_tab_position (p->output_handle, con.owner);
		  ttyp->kill_pgrp (SIGWINCH);
		}
	      processed = true;
	      break;
	    }
remove_record:
	  if (processed)
	    { /* Remove corresponding record. */
	      if (total_read > i + 1)
		memmove (input_rec + i, input_rec + i + 1,
			 m::bytes (total_read - i - 1));
	      total_read--;
	      i--;
	    }
	}
      con.num_processed = total_read;
      if (total_read)
	{
	  do
	    {
	      /* Writeback input records other than interrupt. */
	      acquire_attach_mutex (mutex_timeout);
	      n = 0;
	      while (n < total_read)
		{
		  DWORD len;
		  WriteConsoleInputW (p->input_handle, input_rec + n,
				      min (total_read - n, inrec_size), &len);
		  n += len;
		}
	      release_attach_mutex ();

	      acquire_attach_mutex (mutex_timeout);
	      GetNumberOfConsoleInputEvents (p->input_handle, &n);
	      release_attach_mutex ();
	      if (n + additional_space > inrec_size)
		{
		  DWORD new_inrec_size = n + additional_space;
		  INPUT_RECORD *new_input_rec = (INPUT_RECORD *)
		    realloc (input_rec, m::bytes (new_inrec_size));
		  if (new_input_rec)
		    input_rec = new_input_rec;
		  INPUT_RECORD *new_input_tmp = (INPUT_RECORD *)
		    realloc (input_tmp, m::bytes (new_inrec_size));
		  if (new_input_tmp)
		    input_tmp = new_input_tmp;
		  if (new_input_rec && new_input_tmp)
		    inrec_size = new_inrec_size;
		}

	      /* Check if writeback was successfull. */
	      acquire_attach_mutex (mutex_timeout);
	      PeekConsoleInputW (p->input_handle, input_tmp, inrec_size, &n);
	      release_attach_mutex ();
	      if (n < min (total_read, inrec_size))
		break; /* Someone has read input without acquiring
			  input_mutex. ConEmu cygwin-connector? */
	      if (inrec_eq (input_rec, input_tmp,
			    min (total_read, inrec_size)))
		break; /* OK */
	      /* Try to fix */
	      acquire_attach_mutex (mutex_timeout);
	      n = 0;
	      while (cygwait (p->input_handle, (DWORD) 0) == WAIT_OBJECT_0
		     && n < inrec_size)
		{
		  DWORD len;
		  ReadConsoleInputW (p->input_handle, input_tmp + n,
				     min (inrec_size - n, inrec_size), &len);
		  n += len;
		}
	      release_attach_mutex ();
	      bool fixed = false;
	      for (DWORD ofs = n - total_read; ofs > 0; ofs--)
		{
		  if (inrec_eq (input_rec, input_tmp + ofs, total_read))
		    {
		      memcpy (input_rec + total_read, input_tmp,
			      m::bytes (ofs));
		      memcpy (input_rec + total_read + ofs,
			      input_tmp + total_read + ofs,
			      m::bytes (n - ofs - total_read));
		      fixed = true;
		      break;
		    }
		}
	      if (!fixed)
		{
		  for (DWORD i = 0, j = 0; j < n; j++)
		    if (i == total_read
			|| !inrec_eq (input_rec + i, input_tmp + j, 1))
		      {
			if (total_read + j - i >= n)
			  { /* Something is wrong. Giving up. */
			    acquire_attach_mutex (mutex_timeout);
			    DWORD l = 0;
			    while (l < n)
			      {
				DWORD len;
				WriteConsoleInputW (p->input_handle,
						    input_tmp + l,
						    min (n - l, inrec_size),
						    &len);
				l += len;
			      }
			    release_attach_mutex ();
			    goto skip_writeback;
			  }
			input_rec[total_read + j - i] = input_tmp[j];
		      }
		    else
		      i++;
		}
	      total_read = n;
	    }
	  while (true);
	}
skip_writeback:
      if (inside_pcon && parent_pty_input_mutex)
	ReleaseMutex (parent_pty_input_mutex);
      ReleaseMutex (p->input_mutex);
      cygwait (40);
    }
  free (input_rec);
  free (input_tmp);
}

bool
fhandler_console::set_unit ()
{
  bool created = false;
  fh_devices devset;
  lock_ttys here;
  HWND me;
  fh_devices this_unit = dev ();
  bool generic_console =
    this_unit == FH_CONSOLE || this_unit == FH_CONIN || this_unit == FH_CONOUT;
  if (!generic_console && this_unit != FH_TTY)
    unit = get_minor ();
  else if (myself->ctty != CTTY_UNINITIALIZED)
    unit = device::minor (myself->ctty);

  if (!shared_info_mutex)
    shared_info_mutex = CreateMutex (&sec_none_nih, FALSE, NULL);

  WaitForSingleObject (shared_info_mutex, INFINITE);

  if (shared_console_info[unit])
    ; /* Do nothing */
  else if (generic_console
	   && myself->ctty != CTTY_UNINITIALIZED && !iscons_dev (myself->ctty))
    devset = FH_ERROR;
  else
    {
      if (!generic_console && (dev_t) myself->ctty != get_device ())
	{
	  shared_console_info[unit] = console_unit (unit);
	  shared_info_state[unit]++;
	}
      if (generic_console || !shared_console_info[unit])
	{
	  me = GetConsoleWindow ();
	  if (!me)
	    devset = FH_ERROR;
	  else
	    {
	      created = true;
	      fhandler_console::console_state *cs =
		open_shared_console (me, cygheap->console_h, created);
	      shared_info_state[unit]++;
	      ProtectHandleINH (cygheap->console_h);
	      if (created)
		{
		  unit = console_unit (CONS_SCAN_UNUSED, &input_mutex);
		  cygwin_shared->cons_hwnd[unit] = me;
		  cs->tty_min_state.setntty (DEV_CONS_MAJOR, unit);
		}
	      else
		unit = device::minor (cs->tty_min_state.ntty);
	      shared_console_info[unit] = cs;
	      if (created)
		con.owner = GetCurrentProcessId ();
	    }
	}
    }
  ReleaseMutex (shared_info_mutex);

  if (shared_console_info[unit])
    {
      devset = (fh_devices) shared_console_info[unit]->tty_min_state.getntty ();
      _tc = &(shared_console_info[unit]->tty_min_state);
    }

  dev ().parse (devset);
  if (devset != FH_ERROR)
    pc.file_attributes (FILE_ATTRIBUTE_NORMAL);
  else
    {
      _tc = NULL;
      set_handle (NULL);
      set_output_handle (NULL);
      created = false;
    }
  return created;
}

/* Allocate and initialize the shared record for the current console. */
void
fhandler_console::setup ()
{
  if (set_unit ())
    {
      con.scroll_region.Bottom = -1;
      con.dwLastCursorPosition.X = -1;
      con.dwLastCursorPosition.Y = -1;
      con.dwLastMousePosition.X = -1;
      con.dwLastMousePosition.Y = -1;
      con.savex = con.savey = -1;
      con.screen_alternated = false;
      con.dwLastButtonState = 0;	/* none pressed */
      con.last_button_code = 3;	/* released */
      con.underline_color = FOREGROUND_GREEN | FOREGROUND_BLUE;
      con.dim_color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
      con.meta_mask = LEFT_ALT_PRESSED;
      /* Set the mask that determines if an input keystroke is modified by
	 META.  We set this based on the keyboard layout language loaded
	 for the current thread.  The left <ALT> key always generates
	 META, but the right <ALT> key only generates META if we are using
	 an English keyboard because many "international" keyboards
	 replace common shell symbols ('[', '{', etc.) with accented
	 language-specific characters (umlaut, accent grave, etc.).  On
	 these keyboards right <ALT> (called AltGr) is used to produce the
	 shell symbols and should not be interpreted as META. */
      if (PRIMARYLANGID (LOWORD (GetKeyboardLayout (0))) == LANG_ENGLISH)
	con.meta_mask |= RIGHT_ALT_PRESSED;
      con.set_default_attr ();
      con.backspace_keycode = CERASE;
      con.cons_rapoi = NULL;
      shared_console_info[unit]->tty_min_state.is_console = true;
      con.cursor_key_app_mode = false;
      con.disable_master_thread = true;
      con.master_thread_suspended = false;
      con.num_processed = 0;
      con.curr_input_mode = tty::restore;
      con.curr_output_mode = tty::restore;
    }
}

char *&
fhandler_console::rabuf ()
{
  return con_ra.rabuf;
}

size_t &
fhandler_console::ralen ()
{
  return con_ra.ralen;
}

size_t &
fhandler_console::raixget ()
{
  return con_ra.raixget;
}

size_t &
fhandler_console::raixput ()
{
  return con_ra.raixput;
}

size_t &
fhandler_console::rabuflen ()
{
  return con_ra.rabuflen;
}

static DWORD prev_input_mode_backup;
static DWORD prev_output_mode_backup;

/* The function set_{in,out}put_mode() should be static so that they
   can be called even after the fhandler_console instance is deleted. */
void
fhandler_console::set_input_mode (tty::cons_mode m, const termios *t,
				  const handle_set_t *p)
{
  const _minor_t unit = p->unit;
  DWORD oflags;
  WaitForSingleObject (p->input_mutex, mutex_timeout);
  acquire_attach_mutex (mutex_timeout);
  DWORD resume_pid = attach_console (con.owner);
  GetConsoleMode (p->input_handle, &oflags);
  DWORD flags = oflags
    & (ENABLE_EXTENDED_FLAGS | ENABLE_INSERT_MODE | ENABLE_QUICK_EDIT_MODE);
  switch (m)
    {
    case tty::restore:
      flags = con.prev_input_mode;
      con.prev_input_mode = prev_input_mode_backup;
      break;
    case tty::cygwin:
      flags |= ENABLE_WINDOW_INPUT;
      if (con.master_thread_suspended || con.disable_master_thread)
	flags |= ENABLE_PROCESSED_INPUT;
      if (wincap.has_con_24bit_colors () && !con_is_legacy)
	flags |= ENABLE_VIRTUAL_TERMINAL_INPUT;
      else
	flags |= ENABLE_MOUSE_INPUT;
      break;
    case tty::native:
      if (t->c_lflag & ECHO)
	flags |= ENABLE_ECHO_INPUT;
      if (t->c_lflag & ICANON)
	flags |= ENABLE_LINE_INPUT;
      if (flags & ENABLE_ECHO_INPUT && !(flags & ENABLE_LINE_INPUT))
	/* This is illegal, so turn off the echo here, and fake it
	   when we read the characters */
	flags &= ~ENABLE_ECHO_INPUT;
      if (t->c_lflag & ISIG)
	flags |= ENABLE_PROCESSED_INPUT;
      break;
    }
  con.curr_input_mode = m;
  SetConsoleMode (p->input_handle, flags);
  if (!(oflags & ENABLE_VIRTUAL_TERMINAL_INPUT)
      && (flags & ENABLE_VIRTUAL_TERMINAL_INPUT)
      && con.cursor_key_app_mode)
    { /* Restore DECCKM */
      set_output_mode (tty::cygwin, t, p);
      WriteConsoleW (p->output_handle, L"\033[?1h", 5, NULL, 0);
    }
  detach_console (resume_pid, con.owner);
  release_attach_mutex ();
  ReleaseMutex (p->input_mutex);
}

void
fhandler_console::set_output_mode (tty::cons_mode m, const termios *t,
				   const handle_set_t *p)
{
  const _minor_t unit = p->unit;
  DWORD flags = ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT;
  if (con.orig_virtual_terminal_processing_mode)
    flags |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
  WaitForSingleObject (p->output_mutex, mutex_timeout);
  switch (m)
    {
    case tty::restore:
      flags = con.prev_output_mode;
      con.prev_output_mode = prev_output_mode_backup;
      break;
    case tty::cygwin:
      if (wincap.has_con_24bit_colors () && !con_is_legacy)
	flags |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
      fallthrough;
    case tty::native:
      if (wincap.has_con_24bit_colors () && !con_is_legacy
	  && (!(t->c_oflag & OPOST) || !(t->c_oflag & ONLCR)))
	flags |= DISABLE_NEWLINE_AUTO_RETURN;
      break;
    }
  con.curr_output_mode = m;
  acquire_attach_mutex (mutex_timeout);
  DWORD resume_pid = attach_console (con.owner);
  SetConsoleMode (p->output_handle, flags);
  detach_console (resume_pid, con.owner);
  release_attach_mutex ();
  ReleaseMutex (p->output_mutex);
}

void
fhandler_console::setup_for_non_cygwin_app ()
{
  /* Setting-up console mode for non-cygwin app. */
  /* If conmode is set to tty::native for non-cygwin apps
     in background, tty settings of the shell is reflected
     to the console mode of the app. So, do not change the
     console mode. */
  if (get_ttyp ()->getpgid () == myself->pgid)
    {
      set_input_mode (tty::native, &tc ()->ti, get_handle_set ());
      set_output_mode (tty::native, &tc ()->ti, get_handle_set ());
      set_disable_master_thread (true, this);
    }
}

void
fhandler_console::cleanup_for_non_cygwin_app (handle_set_t *p)
{
  const _minor_t unit = p->unit;
  termios dummy = {0, };
  termios *ti = shared_console_info[unit] ?
    &(shared_console_info[unit]->tty_min_state.ti) : &dummy;
  /* Cleaning-up console mode for non-cygwin app. */
  set_disable_master_thread (con.owner == GetCurrentProcessId ());
  /* conmode can be tty::restore when non-cygwin app is
     exec'ed from login shell. */
  tty::cons_mode conmode = cons_mode_on_close (p);
  set_output_mode (conmode, ti, p);
  set_input_mode (conmode, ti, p);
}

/* Return the tty structure associated with a given tty number.  If the
   tty number is < 0, just return a dummy record. */
tty_min *
tty_list::get_cttyp ()
{
  dev_t n = myself->ctty;
  const _minor_t unit = device::minor (n);
  if (iscons_dev (n))
    return fhandler_console::shared_console_info[unit] ?
      &fhandler_console::shared_console_info[unit]->tty_min_state : NULL;
  else if (istty_slave_dev (n))
    return &ttys[unit];
  else
    return NULL;
}

void
fhandler_console::setup_io_mutex (void)
{
  char buf[MAX_PATH];
  DWORD res;

  res = WAIT_FAILED;
  if (!input_mutex || WAIT_FAILED == (res = acquire_input_mutex (0)))
    {
      shared_name (buf, "cygcons.input.mutex", get_minor ());
      input_mutex = OpenMutex (MAXIMUM_ALLOWED, TRUE, buf);
      if (!input_mutex)
	input_mutex = CreateMutex (&sec_none, FALSE, buf);
      if (!input_mutex)
	{
	  __seterrno ();
	  return;
	}
    }
  if (res == WAIT_OBJECT_0)
    release_input_mutex ();

  res = WAIT_FAILED;
  if (!output_mutex || WAIT_FAILED == (res = acquire_output_mutex (0)))
    {
      shared_name (buf, "cygcons.output.mutex", get_minor ());
      output_mutex = OpenMutex (MAXIMUM_ALLOWED, TRUE, buf);
      if (!output_mutex)
	output_mutex = CreateMutex (&sec_none, FALSE, buf);
      if (!output_mutex)
	{
	  __seterrno ();
	  return;
	}
    }
  if (res == WAIT_OBJECT_0)
    release_output_mutex ();

  extern HANDLE attach_mutex;
  if (!attach_mutex)
    attach_mutex = CreateMutex (&sec_none_nih, FALSE, NULL);
}

inline DWORD
dev_console::con_to_str (char *d, int dlen, WCHAR w)
{
  return sys_wcstombs (d, dlen, &w, 1);
}

inline UINT
dev_console::get_console_cp ()
{
  /* The alternate charset is always 437, just as in the Linux console. */
  return alternate_charset_active ? 437 : 0;
}

inline DWORD
dev_console::str_to_con (mbtowc_p f_mbtowc, PWCHAR d, const char *s, DWORD sz)
{
  return _sys_mbstowcs (f_mbtowc, d, CONVERT_LIMIT, s, sz);
}

bool
fhandler_console::set_raw_win32_keyboard_mode (bool new_mode)
{
  bool old_mode = con.raw_win32_keyboard_mode;
  con.raw_win32_keyboard_mode = new_mode;
  syscall_printf ("raw keyboard mode %sabled",
		  con.raw_win32_keyboard_mode ? "en" : "dis");
  return old_mode;
};

void
fhandler_console::set_cursor_maybe ()
{
  con.fillin (get_output_handle ());
  /* Nothing to do for xterm compatible mode. */
  if (wincap.has_con_24bit_colors () && !con_is_legacy)
    return;
  if (con.dwLastCursorPosition.X != con.b.dwCursorPosition.X ||
      con.dwLastCursorPosition.Y != con.b.dwCursorPosition.Y)
    {
      acquire_attach_mutex (mutex_timeout);
      DWORD resume_pid = attach_console (con.owner);
      SetConsoleCursorPosition (get_output_handle (), con.b.dwCursorPosition);
      detach_console (resume_pid, con.owner);
      release_attach_mutex ();
      con.dwLastCursorPosition = con.b.dwCursorPosition;
    }
}

/* Workaround for a bug of windows xterm compatible mode. */
/* The horizontal tab positions are broken after resize. */
void
fhandler_console::fix_tab_position (HANDLE h, DWORD owner)
{
  /* Re-setting ENABLE_VIRTUAL_TERMINAL_PROCESSING
     fixes the tab position. */
  DWORD mode;
  acquire_attach_mutex (mutex_timeout);
  DWORD resume_pid = attach_console (owner);
  GetConsoleMode (h, &mode);
  SetConsoleMode (h, mode & ~ENABLE_VIRTUAL_TERMINAL_PROCESSING);
  SetConsoleMode (h, mode);
  detach_console (resume_pid, owner);
  release_attach_mutex ();
}

bool
fhandler_console::send_winch_maybe ()
{
  SHORT y = con.dwWinSize.Y;
  SHORT x = con.dwWinSize.X;
  con.fillin (get_output_handle ());

  if (y != con.dwWinSize.Y || x != con.dwWinSize.X)
    {
      con.scroll_region.Top = 0;
      con.scroll_region.Bottom = -1;
      if (wincap.has_con_24bit_colors () && !con_is_legacy
	  && wincap.has_con_broken_tabs ())
	fix_tab_position (get_output_handle (), con.owner);
      /* longjmp() may be called in the signal handler like less,
	 so release input_mutex temporarily before kill_pgrp(). */
      release_input_mutex ();
      get_ttyp ()->kill_pgrp (SIGWINCH);
      acquire_input_mutex (mutex_timeout);
      return true;
    }
  return false;
}

/* Check whether a mouse event is to be reported as an escape sequence */
bool
fhandler_console::mouse_aware (MOUSE_EVENT_RECORD& mouse_event)
{
  if (!con.use_mouse)
    return 0;

  /* Adjust mouse position by window scroll buffer offset
     and remember adjusted position in state for use by read() */
  CONSOLE_SCREEN_BUFFER_INFO now;
  acquire_attach_mutex (mutex_timeout);
  DWORD resume_pid = attach_console (con.owner);
  BOOL r = GetConsoleScreenBufferInfo (get_output_handle (), &now);
  detach_console (resume_pid, con.owner);
  release_attach_mutex ();
  if (!r)
    /* Cannot adjust position by window scroll buffer offset */
    return 0;

  con.dwMousePosition.X = mouse_event.dwMousePosition.X - now.srWindow.Left;
  con.dwMousePosition.Y = mouse_event.dwMousePosition.Y - now.srWindow.Top;

  return ((mouse_event.dwEventFlags == 0
	   || mouse_event.dwEventFlags == DOUBLE_CLICK)
	  && mouse_event.dwButtonState != con.dwLastButtonState)
	 || mouse_event.dwEventFlags == MOUSE_WHEELED
	 || (mouse_event.dwEventFlags == MOUSE_MOVED
	     && (con.dwMousePosition.X != con.dwLastMousePosition.X
		 || con.dwMousePosition.Y != con.dwLastMousePosition.Y)
	     && ((con.use_mouse >= 2 && mouse_event.dwButtonState)
		 || con.use_mouse >= 3));
}


bg_check_types
fhandler_console::bg_check (int sig, bool dontsignal)
{
  /* Setting-up console mode for cygwin app. This is necessary if the
     cygwin app and other non-cygwin apps are started simultaneously
     in the same process group. */
  if (sig == SIGTTIN && con.curr_input_mode != tty::cygwin)
    {
      set_disable_master_thread (false, this);
      set_input_mode (tty::cygwin, &tc ()->ti, get_handle_set ());
    }
  if (sig == SIGTTOU && con.curr_output_mode != tty::cygwin)
    set_output_mode (tty::cygwin, &tc ()->ti, get_handle_set ());

  return fhandler_termios::bg_check (sig, dontsignal);
}

void
fhandler_console::read (void *pv, size_t& buflen)
{
  termios_printf ("read(%p,%d)", pv, buflen);

  push_process_state process_state (PID_TTYIN);

  size_t copied_chars = 0;

  DWORD timeout = is_nonblocking () ? 0 :
    (get_ttyp ()->ti.c_lflag & ICANON ? INFINITE :
     (get_ttyp ()->ti.c_cc[VMIN] == 0 ? 0 :
      (get_ttyp ()->ti.c_cc[VTIME]*100 ? : INFINITE)));

read_more:
  while (!input_ready && !get_cons_readahead_valid ())
    {
      int bgres;
      if ((bgres = bg_check (SIGTTIN)) <= bg_eof)
	{
	  buflen = bgres;
	  return;
	}

      set_cursor_maybe (); /* to make cursor appear on the screen immediately */
wait_retry:
      switch (cygwait (get_handle (), timeout))
	{
	case WAIT_OBJECT_0:
	  break;
	case WAIT_SIGNALED:
	  goto sig_exit;
	case WAIT_CANCELED:
	  process_state.pop ();
	  pthread::static_cancel_self ();
	  /*NOTREACHED*/
	case WAIT_TIMEOUT:
	  if (copied_chars)
	    {
	      buflen = copied_chars;
	      return;
	    }
	  set_sig_errno (EAGAIN);
	  buflen = (size_t) -1;
	  return;
	default:
	  if (GetLastError () == ERROR_INVALID_HANDLE)
	    { /* Confirm the handle is still valid */
	      DWORD mode;
	      acquire_attach_mutex (mutex_timeout);
	      DWORD resume_pid = attach_console (con.owner);
	      BOOL res = GetConsoleMode (get_handle (), &mode);
	      detach_console (resume_pid, con.owner);
	      release_attach_mutex ();
	      if (res)
		goto wait_retry;
	    }
	  goto err;
	}

#define buf ((char *) pv)

      int ret;
      acquire_input_mutex (mutex_timeout);
      ret = process_input_message ();
      switch (ret)
	{
	case input_error:
	  release_input_mutex ();
	  goto err;
	case input_processing:
	  release_input_mutex ();
	  continue;
	case input_ok: /* input ready */
	  break;
	case input_signalled: /* signalled */
	case input_winch:
	  release_input_mutex ();
	  if (global_sigs[get_ttyp ()->last_sig].sa_flags & SA_RESTART)
	    continue;
	  goto sig_exit;
	default:
	  /* Should not come here */
	  release_input_mutex ();
	  goto err;
	}
    }

  /* Check console read-ahead buffer filled from terminal requests */
  while (con.cons_rapoi && *con.cons_rapoi && buflen > copied_chars)
    buf[copied_chars++] = *con.cons_rapoi++;

  copied_chars +=
    get_readahead_into_buffer (buf + copied_chars, buflen - copied_chars);

  if (!con_ra.ralen)
    input_ready = false;
  release_input_mutex ();

  if (buflen > copied_chars && !(get_ttyp ()->ti.c_lflag & ICANON)
      && copied_chars < get_ttyp ()->ti.c_cc[VMIN])
    goto read_more;

#undef buf

  buflen = copied_chars;
  return;

err:
  __seterrno ();
  buflen = (size_t) -1;
  return;

sig_exit:
  set_sig_errno (EINTR);
  buflen = (size_t) -1;
}

fhandler_console::input_states
fhandler_console::process_input_message (void)
{
  char tmp[60];

  if (!shared_console_info[unit])
    return input_error;

  termios *ti = &(get_ttyp ()->ti);

  fhandler_console::input_states stat = input_processing;
  DWORD total_read, i;
  INPUT_RECORD input_rec[INREC_SIZE];

  acquire_attach_mutex (mutex_timeout);
  DWORD resume_pid = attach_console (con.owner);
  BOOL r =
    PeekConsoleInputW (get_handle (), input_rec, INREC_SIZE, &total_read);
  detach_console (resume_pid, con.owner);
  release_attach_mutex ();
  if (!r)
    {
      termios_printf ("PeekConsoleInput failed, %E");
      return input_error;
    }

  for (i = 0; i < total_read; i ++)
    {
      DWORD nread = 1;
      const char *toadd = NULL;

      const WCHAR &unicode_char =
	input_rec[i].Event.KeyEvent.uChar.UnicodeChar;
      const DWORD &ctrl_key_state =
	input_rec[i].Event.KeyEvent.dwControlKeyState;

      /* check the event that occurred */
      switch (input_rec[i].EventType)
	{
	case KEY_EVENT:

	  con.nModifiers = 0;

#ifdef DEBUGGING
	  /* allow manual switching to/from raw mode via ctrl-alt-scrolllock */
	  if (input_rec[i].Event.KeyEvent.bKeyDown
	      && input_rec[i].Event.KeyEvent.wVirtualKeyCode == VK_SCROLL
	      && (ctrl_key_state & (LEFT_ALT_PRESSED | LEFT_CTRL_PRESSED))
	      == (LEFT_ALT_PRESSED | LEFT_CTRL_PRESSED))
	    {
	      set_raw_win32_keyboard_mode (!con.raw_win32_keyboard_mode);
	      continue;
	    }
#endif

	  if (con.raw_win32_keyboard_mode)
	    {
	      __small_sprintf (tmp, "\033{%u;%u;%u;%u;%u;%luK",
			       input_rec[i].Event.KeyEvent.bKeyDown,
			       input_rec[i].Event.KeyEvent.wRepeatCount,
			       input_rec[i].Event.KeyEvent.wVirtualKeyCode,
			       input_rec[i].Event.KeyEvent.wVirtualScanCode,
			       input_rec[i].Event.KeyEvent.uChar.UnicodeChar,
			       input_rec[i].Event.KeyEvent.dwControlKeyState);
	      toadd = tmp;
	      nread = strlen (toadd);
	      break;
	    }

	  /* Ignore key up events, except for Alt+Numpad events. */
	  if (!input_rec[i].Event.KeyEvent.bKeyDown &&
	      !is_alt_numpad_event (&input_rec[i]))
	    continue;
	  /* Ignore Alt+Numpad keys.  They are eventually handled below after
	     releasing the Alt key. */
	  if (input_rec[i].Event.KeyEvent.bKeyDown
	      && is_alt_numpad_key (&input_rec[i]))
	    continue;

	  if (ctrl_key_state & SHIFT_PRESSED)
	    con.nModifiers |= 1;
	  if (ctrl_key_state & RIGHT_ALT_PRESSED)
	    con.nModifiers |= 2;
	  if (ctrl_key_state & CTRL_PRESSED)
	    con.nModifiers |= 4;
	  if (ctrl_key_state & LEFT_ALT_PRESSED)
	    con.nModifiers |= 8;

	  /* Allow Backspace to emit ^? and escape sequences. */
	  if (input_rec[i].Event.KeyEvent.wVirtualKeyCode == VK_BACK)
	    {
	      char c = con.backspace_keycode;
	      nread = 0;
	      if (ctrl_key_state & ALT_PRESSED)
		{
		  if (con.metabit)
		    c |= 0x80;
		  else
		    tmp[nread++] = '\e';
		}
	      tmp[nread++] = c;
	      tmp[nread] = 0;
	      toadd = tmp;
	    }
	  /* Allow Ctrl-Space to emit ^@ */
	  else if (input_rec[i].Event.KeyEvent.wVirtualKeyCode
		   == ((wincap.has_con_24bit_colors () && !con_is_legacy) ?
		       '2' : VK_SPACE)
		   && (ctrl_key_state & CTRL_PRESSED)
		   && !(ctrl_key_state & ALT_PRESSED))
	    toadd = "";
	  else if (unicode_char == 0
		   /* arrow/function keys */
		   || (input_rec[i].Event.KeyEvent.dwControlKeyState
		       & ENHANCED_KEY))
	    {
	      toadd = get_nonascii_key (input_rec[i], tmp);
	      if (!toadd)
		{
		  con.nModifiers = 0;
		  continue;
		}
	      nread = strlen (toadd);
	    }
	  else
	    {
	      WCHAR second = unicode_char >= 0xd800 && unicode_char <= 0xdbff
		  && i + 1 < total_read ?
		  input_rec[i + 1].Event.KeyEvent.uChar.UnicodeChar : 0;

	      if (second < 0xdc00 || second > 0xdfff)
		{
		  nread = con.con_to_str (tmp + 1, 59, unicode_char);
		}
	      else
		{
		  /* handle surrogate pairs */
		  WCHAR pair[2] = { unicode_char, second };
		  nread = sys_wcstombs (tmp + 1, 59, pair, 2);
		  i++;
		}

	      /* Determine if the keystroke is modified by META.  The tricky
		 part is to distinguish whether the right Alt key should be
		 recognized as Alt, or as AltGr. */
	      bool meta =
		/* Alt but not AltGr (= left ctrl + right alt)? */
		(ctrl_key_state & ALT_PRESSED) != 0
		&& ((ctrl_key_state & CTRL_PRESSED) == 0
		    /* but also allow Alt-AltGr: */
		    || (ctrl_key_state & ALT_PRESSED) == ALT_PRESSED
		    || (unicode_char <= 0x1f || unicode_char == 0x7f));
	      if (!meta)
		{
		  /* Determine if the character is in the current multibyte
		     charset.  The test is easy.  If the multibyte sequence
		     is > 1 and the first byte is ASCII CAN, the character
		     has been translated into the ASCII CAN + UTF-8 replacement
		     sequence.  If so, just ignore the keypress.
		     FIXME: Is there a better solution? */
		  if (nread > 1 && tmp[1] == 0x18)
		    beep ();
		  else
		    toadd = tmp + 1;
		}
	      else if (con.metabit)
		{
		  tmp[1] |= 0x80;
		  toadd = tmp + 1;
		}
	      else
		{
		  tmp[0] = '\033';
		  tmp[1] = cyg_tolower (tmp[1]);
		  toadd = tmp;
		  nread++;
		  con.nModifiers &= ~4;
		}
	    }
	  break;

	case MOUSE_EVENT:
	  send_winch_maybe ();
	    {
	      MOUSE_EVENT_RECORD& mouse_event = input_rec[i].Event.MouseEvent;
	      /* As a unique guard for mouse report generation,
		 call mouse_aware() which is common with select(), so the result
		 of select() and the actual read() will be consistent on the
		 issue of whether input (i.e. a mouse escape sequence) will
		 be available or not */
	      if (mouse_aware (mouse_event))
		{
		  /* Note: Reported mouse position was already retrieved by
		     mouse_aware() and adjusted by window scroll buffer offset */

		  /* Treat the double-click event like a regular button press */
		  if (mouse_event.dwEventFlags == DOUBLE_CLICK)
		    {
		      syscall_printf ("mouse: double-click -> click");
		      mouse_event.dwEventFlags = 0;
		    }

		  /* This code assumes Windows never reports multiple button
		     events at the same time. */
		  int b = 0;
		  char sz[32];
		  char mode6_term = 'M';

		  if (mouse_event.dwEventFlags == MOUSE_WHEELED)
		    {
		      if (mouse_event.dwButtonState & 0xFF800000)
			{
			  b = 0x41;
			  strcpy (sz, "wheel down");
			}
		      else
			{
			  b = 0x40;
			  strcpy (sz, "wheel up");
			}
		    }
		  else
		    {
		      /* Ignore unimportant mouse buttons */
		      mouse_event.dwButtonState &= 0x7;

		      if (mouse_event.dwEventFlags == MOUSE_MOVED)
			{
			  b = con.last_button_code;
			}
		      else if (mouse_event.dwButtonState < con.dwLastButtonState
			       && !con.ext_mouse_mode6)
			{
			  b = 3;
			  strcpy (sz, "btn up");
			}
		      else if ((mouse_event.dwButtonState & 1)
			       != (con.dwLastButtonState & 1))
			{
			  b = 0;
			  strcpy (sz, "btn1 down");
			}
		      else if ((mouse_event.dwButtonState & 2)
			       != (con.dwLastButtonState & 2))
			{
			  b = 2;
			  strcpy (sz, "btn2 down");
			}
		      else if ((mouse_event.dwButtonState & 4)
			       != (con.dwLastButtonState & 4))
			{
			  b = 1;
			  strcpy (sz, "btn3 down");
			}

		      if (con.ext_mouse_mode6 /* distinguish release */
			  && mouse_event.dwButtonState < con.dwLastButtonState)
			mode6_term = 'm';

		      con.last_button_code = b;

		      if (mouse_event.dwEventFlags == MOUSE_MOVED)
			{
			  b += 32;
			  strcpy (sz, "move");
			}
		      else
			{
			  /* Remember the modified button state */
			  con.dwLastButtonState = mouse_event.dwButtonState;
			}
		    }

		  /* Remember mouse position */
		  con.dwLastMousePosition.X = con.dwMousePosition.X;
		  con.dwLastMousePosition.Y = con.dwMousePosition.Y;

		  /* Remember the modifiers */
		  con.nModifiers = 0;
		  if (mouse_event.dwControlKeyState & SHIFT_PRESSED)
		    con.nModifiers |= 0x4;
		  if (mouse_event.dwControlKeyState & ALT_PRESSED)
		    con.nModifiers |= 0x8;
		  if (mouse_event.dwControlKeyState & CTRL_PRESSED)
		    con.nModifiers |= 0x10;

		  /* Indicate the modifiers */
		  b |= con.nModifiers;

		  /* We can now create the code. */
		  if (con.ext_mouse_mode6)
		    {
		      __small_sprintf (tmp, "\033[<%d;%d;%d%c", b,
				       con.dwMousePosition.X + 1,
				       con.dwMousePosition.Y + 1,
				       mode6_term);
		      nread = strlen (tmp);
		    }
		  else if (con.ext_mouse_mode15)
		    {
		      __small_sprintf (tmp, "\033[%d;%d;%dM", b + 32,
				       con.dwMousePosition.X + 1,
				       con.dwMousePosition.Y + 1);
		      nread = strlen (tmp);
		    }
		  else if (con.ext_mouse_mode5)
		    {
		      unsigned int xcode = con.dwMousePosition.X + ' ' + 1;
		      unsigned int ycode = con.dwMousePosition.Y + ' ' + 1;

		      __small_sprintf (tmp, "\033[M%c", b + ' ');
		      nread = 4;
		      /* the neat nested encoding function of mintty
			 does not compile in g++, so let's unfold it: */
		      if (xcode < 0x80)
			tmp [nread++] = xcode;
		      else if (xcode < 0x800)
			{
			  tmp [nread++] = 0xC0 + (xcode >> 6);
			  tmp [nread++] = 0x80 + (xcode & 0x3F);
			}
		      else
			tmp [nread++] = 0;
		      if (ycode < 0x80)
			tmp [nread++] = ycode;
		      else if (ycode < 0x800)
			{
			  tmp [nread++] = 0xC0 + (ycode >> 6);
			  tmp [nread++] = 0x80 + (ycode & 0x3F);
			}
		      else
			tmp [nread++] = 0;
		    }
		  else
		    {
		      unsigned int xcode = con.dwMousePosition.X + ' ' + 1;
		      unsigned int ycode = con.dwMousePosition.Y + ' ' + 1;
		      if (xcode >= 256)
			xcode = 0;
		      if (ycode >= 256)
			ycode = 0;
		      __small_sprintf (tmp, "\033[M%c%c%c", b + ' ',
				       xcode, ycode);
		      nread = 6;	/* tmp may contain NUL bytes */
		    }
		  syscall_printf ("mouse: %s at (%d,%d)", sz,
				  con.dwMousePosition.X,
				  con.dwMousePosition.Y);

		  toadd = tmp;
		}
	    }
	  break;

	case FOCUS_EVENT:
	  if (con.use_focus)
	    {
	      if (input_rec[i].Event.FocusEvent.bSetFocus)
		__small_sprintf (tmp, "\033[I");
	      else
		__small_sprintf (tmp, "\033[O");

	      toadd = tmp;
	      nread = 3;
	    }
	  break;

	case WINDOW_BUFFER_SIZE_EVENT:
	  if (send_winch_maybe ())
	    {
	      stat = input_winch;
	      goto out;
	    }
	  /* fall through */
	default:
	  continue;
	}

      if (toadd)
	{
	  ssize_t ret;
	  line_edit_status res = line_edit (toadd, nread, *ti, &ret);
	  if (res == line_edit_signalled)
	    {
	      stat = input_signalled;
	      goto out;
	    }
	  else if (res == line_edit_input_done)
	    {
	      input_ready = true;
	      stat = input_ok;
	      if (ti->c_lflag & ICANON)
		goto out;
	    }
	}
    }
out:
  /* Discard processed recored. */
  DWORD discard_len = min (total_read, i + 1);
  /* If input is signalled, do not discard input here because
     tcflush() is already called from line_edit(). */
  if (stat == input_signalled && !(ti->c_lflag & NOFLSH))
    discard_len = 0;
  if (discard_len)
    {
      DWORD discarded;
      acquire_attach_mutex (mutex_timeout);
      DWORD resume_pid = attach_console (con.owner);
      ReadConsoleInputW (get_handle (), input_rec, discard_len, &discarded);
      detach_console (resume_pid, con.owner);
      release_attach_mutex ();
      con.num_processed -= min (con.num_processed, discarded);
    }
  return stat;
}

bool
dev_console::fillin (HANDLE h)
{
  acquire_attach_mutex (mutex_timeout);
  DWORD resume_pid = fhandler_console::attach_console (owner);
  bool ret = GetConsoleScreenBufferInfo (h, &b);
  fhandler_console::detach_console (resume_pid, owner);
  release_attach_mutex ();

  if (ret)
    {
      dwWinSize.Y = 1 + b.srWindow.Bottom - b.srWindow.Top;
      dwWinSize.X = 1 + b.srWindow.Right - b.srWindow.Left;
      if (b.dwCursorPosition.Y > dwEnd.Y
	  || (b.dwCursorPosition.Y >= dwEnd.Y
	      && b.dwCursorPosition.X > dwEnd.X))
	dwEnd = b.dwCursorPosition;
    }
  else
    {
      memset (&b, 0, sizeof (b));
      dwWinSize.Y = 25;
      dwWinSize.X = 80;
      b.srWindow.Bottom = 24;
      b.srWindow.Right = 79;
    }

  return ret;
}

void
dev_console::scroll_buffer (HANDLE h, int x1, int y1, int x2, int y2,
			    int xn, int yn)
{
/* Scroll the screen context.
   x1, y1 - ul corner
   x2, y2 - dr corner
   xn, yn - new ul corner
   Negative values represents current screen dimensions
*/
  SMALL_RECT sr1, sr2;
  CHAR_INFO fill;
  COORD dest;
  fill.Char.UnicodeChar = L' ';
  fill.Attributes = current_win32_attr;

  fillin (h);
  sr1.Left = x1 >= 0 ? x1 : dwWinSize.X - 1;
  sr1.Top = y1 >= 0 ? y1 : b.srWindow.Bottom;
  sr1.Right = x2 >= 0 ? x2 : dwWinSize.X - 1;
  sr1.Bottom = y2 >= 0 ? y2 : b.srWindow.Bottom;
  sr2.Top = b.srWindow.Top + scroll_region.Top;
  sr2.Left = 0;
  sr2.Bottom = (scroll_region.Bottom < 0) ?
    b.srWindow.Bottom : b.srWindow.Top + scroll_region.Bottom;
  sr2.Right = dwWinSize.X - 1;
  if (sr1.Bottom > sr2.Bottom && sr1.Top <= sr2.Bottom)
    sr1.Bottom = sr2.Bottom;
  dest.X = xn >= 0 ? xn : dwWinSize.X - 1;
  dest.Y = yn >= 0 ? yn : b.srWindow.Bottom;
  acquire_attach_mutex (mutex_timeout);
  DWORD resume_pid = fhandler_console::attach_console (owner);
  ScrollConsoleScreenBufferW (h, &sr1, &sr2, dest, &fill);
  fhandler_console::detach_console (resume_pid, owner);
  release_attach_mutex ();
}

inline void
fhandler_console::scroll_buffer (int x1, int y1, int x2, int y2,
				 int xn, int yn)
{
  con.scroll_buffer (get_output_handle (), x1, y1, x2, y2, xn, yn);
}

inline void
fhandler_console::scroll_buffer_screen (int x1, int y1, int x2, int y2,
					int xn, int yn)
{
  if (y1 >= 0)
    y1 += con.b.srWindow.Top;
  if (y2 >= 0)
    y2 += con.b.srWindow.Top;
  if (yn >= 0)
    yn += con.b.srWindow.Top;
  con.scroll_buffer (get_output_handle (), x1, y1, x2, y2, xn, yn);
}

int
fhandler_console::dup (fhandler_base *child, int flags)
{
  /* See comments in fhandler_pty_slave::dup */
  if (myself->ctty != CTTY_RELEASED)
    myself->set_ctty (this, flags);
  return 0;
}

static void hook_conemu_cygwin_connector();

int
fhandler_console::open (int flags, mode_t)
{
  HANDLE h;

  if (dev () == FH_ERROR)
    {
      set_errno (EPERM);	/* constructor found an error */
      return 0;
    }

  tcinit (false);

  set_handle (NULL);
  set_output_handle (NULL);

  setup_io_mutex ();
  acquire_output_mutex (mutex_timeout);

  if (!process_alive (con.owner))
    con.owner = GetCurrentProcessId ();

  /* Open the input handle as handle_ */
  bool err = false;
  DWORD resume_pid = attach_console (con.owner, &err);
  if (err)
    {
      release_output_mutex ();
      set_errno (EACCES);
      return 0;
    }
  h = CreateFileW (L"CONIN$", GENERIC_READ | GENERIC_WRITE,
		  FILE_SHARE_READ | FILE_SHARE_WRITE, &sec_none,
		  OPEN_EXISTING, 0, 0);
  detach_console (resume_pid, con.owner);

  if (h == INVALID_HANDLE_VALUE)
    {
      release_output_mutex ();
      __seterrno ();
      return 0;
    }
  set_handle (h);
  handle_set.input_handle = h;

  resume_pid = attach_console (con.owner, &err);
  if (err)
    {
      release_output_mutex ();
      set_errno (EACCES);
      return 0;
    }
  h = CreateFileW (L"CONOUT$", GENERIC_READ | GENERIC_WRITE,
		  FILE_SHARE_READ | FILE_SHARE_WRITE, &sec_none,
		  OPEN_EXISTING, 0, 0);
  detach_console (resume_pid, con.owner);

  if (h == INVALID_HANDLE_VALUE)
    {
      release_output_mutex ();
      __seterrno ();
      return 0;
    }
  set_output_handle (h);
  handle_set.output_handle = h;
  release_output_mutex ();

  if (con.owner == GetCurrentProcessId ())
    {
      GetConsoleMode (get_handle (), &con.prev_input_mode);
      GetConsoleMode (get_output_handle (), &con.prev_output_mode);
    }

  wpbuf.init ();

  handle_set.input_mutex = input_mutex;
  handle_set.output_mutex = output_mutex;

  handle_set.unit = unit;

  if (con.fillin (get_output_handle ()))
    {
      con.current_win32_attr = con.b.wAttributes;
      if (!con.default_color)
	con.default_color = con.b.wAttributes;
      con.set_default_attr ();
    }

  set_open_status ();

  if (GetCurrentProcessId () == con.owner && wincap.has_con_24bit_colors ())
    {
      bool is_legacy = false;
      DWORD dwMode;
      /* Check xterm compatible mode in output */
      acquire_attach_mutex (mutex_timeout);
      DWORD resume_pid = attach_console (con.owner);
      GetConsoleMode (get_output_handle (), &dwMode);
      con.orig_virtual_terminal_processing_mode =
	!!(dwMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING);
      if (!SetConsoleMode (get_output_handle (),
			   dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING))
	is_legacy = true;
      SetConsoleMode (get_output_handle (), dwMode);
      /* Check xterm compatible mode in input */
      GetConsoleMode (get_handle (), &dwMode);
      if (!SetConsoleMode (get_handle (),
			   dwMode | ENABLE_VIRTUAL_TERMINAL_INPUT))
	is_legacy = true;
      SetConsoleMode (get_handle (), dwMode);
      detach_console (resume_pid, con.owner);
      release_attach_mutex ();
      con.is_legacy = is_legacy;
      extern int sawTERM;
      if (con_is_legacy && !sawTERM)
	setenv ("TERM", "cygwin", 1);
    }

  if (con.curr_input_mode != tty::cygwin)
    {
      prev_input_mode_backup = con.prev_input_mode;
      GetConsoleMode (get_handle (), &con.prev_input_mode);
      set_input_mode (tty::cygwin, &get_ttyp ()->ti, &handle_set);
    }
  if (con.curr_output_mode != tty::cygwin)
    {
      prev_output_mode_backup = con.prev_output_mode;
      GetConsoleMode (get_output_handle (), &con.prev_output_mode);
      set_output_mode (tty::cygwin, &get_ttyp ()->ti, &handle_set);
    }

  debug_printf ("opened conin$ %p, conout$ %p", get_handle (),
		get_output_handle ());

  if (GetCurrentProcessId () == con.owner)
    {
      if (GetModuleHandle ("ConEmuHk64.dll"))
	hook_conemu_cygwin_connector ();
      char name[MAX_PATH];
      shared_name (name, CONS_THREAD_SYNC, get_minor ());
      thread_sync_event = CreateEvent(NULL, FALSE, FALSE, name);
      if (thread_sync_event)
	{
	  new cygthread (::cons_master_thread, this, "consm");
	  WaitForSingleObject (thread_sync_event, INFINITE);
	  CloseHandle (thread_sync_event);
	}
      else
	debug_printf ("Failed to create thread_sync_event %08x",
		      GetLastError ());
    }
  return 1;
}

void
fhandler_console::setup_pcon_hand_over ()
{
  /* Prepare for pcon hand over */
  if (!inside_pcon_checked)
    for (int i = 0; i < NTTYS; i++)
      {
	if (!cygwin_shared->tty[i]->pcon_activated)
	  continue;
	DWORD owner = cygwin_shared->tty[i]->nat_pipe_owner_pid;
	if (get_console_process_id (owner, true, false, false, false))
	  {
	    inside_pcon = true;
	    atexit (fhandler_console::pcon_hand_over_proc);
	    parent_pty = i;
	    parent_pty_input_mutex =
	      cygwin_shared->tty[i]->open_input_mutex (MAXIMUM_ALLOWED);
	    break;
	  }
      }
  inside_pcon_checked = true;
}

void
fhandler_console::pcon_hand_over_proc (void)
{
  if (!inside_pcon)
    return;
  tty *ttyp = cygwin_shared->tty[parent_pty];
  char buf[MAX_PATH];
  shared_name (buf, PIPE_SW_MUTEX, parent_pty);
  HANDLE mtx = OpenMutex (MAXIMUM_ALLOWED, FALSE, buf);
  DWORD res = WaitForSingleObject (mtx, INFINITE);
  if (res == WAIT_OBJECT_0 || res == WAIT_ABANDONED)
    {
      DWORD owner = ttyp->nat_pipe_owner_pid;
      if (owner == GetCurrentProcessId ()
	  || owner == (myself->exec_dwProcessId ?: myself->dwProcessId))
	fhandler_pty_slave::close_pseudoconsole (ttyp, 0);
    }
  else
    system_printf("Acquiring pcon_ho_mutex failed.");
  ReleaseMutex (mtx);
  ForceCloseHandle (parent_pty_input_mutex);
}

bool
fhandler_console::open_setup (int flags)
{
  set_flags ((flags & ~O_TEXT) | O_BINARY);
  if (myself->set_ctty (this, flags) && !myself->cygstarted)
    {
      init_console_handler (true);
      setup_pcon_hand_over ();

      /* Initialize handle_set */
      handle_set.input_handle = get_handle ();
      handle_set.output_handle = get_output_handle ();
      handle_set.input_mutex = input_mutex;
      handle_set.output_mutex = output_mutex;
      handle_set.unit = unit;
    }
  return fhandler_base::open_setup (flags);
}

void
fhandler_console::post_open_setup (int fd)
{
  /* Setting-up console mode for cygwin app started from non-cygwin app. */
  if (fd == 0)
    {
      set_disable_master_thread (false, this);
      set_input_mode (tty::cygwin, &get_ttyp ()->ti, &handle_set);
    }
  else if (fd == 1 || fd == 2)
    set_output_mode (tty::cygwin, &get_ttyp ()->ti, &handle_set);

  fhandler_base::post_open_setup (fd);
}

int
fhandler_console::close (int flag)
{
  debug_printf ("closing: %p, %p", get_handle (), get_output_handle ());

  acquire_output_mutex (mutex_timeout);

  if (shared_console_info[unit] && con.curr_input_mode != tty::restore
      && (dev_t) myself->ctty == get_device ()
      && cons_mode_on_close (&handle_set) == tty::restore)
    {
      set_output_mode (tty::restore, &get_ttyp ()->ti, &handle_set);
      set_input_mode (tty::restore, &get_ttyp ()->ti, &handle_set);
      set_disable_master_thread (true, this);
    }

  if (shared_console_info[unit] && con.owner == GetCurrentProcessId ())
    {
      if (master_thread_started)
	{
	  char name[MAX_PATH];
	  shared_name (name, CONS_THREAD_SYNC, get_minor ());
	  thread_sync_event = OpenEvent (MAXIMUM_ALLOWED, FALSE, name);
	  if (thread_sync_event)
	    {
	      con.owner = (DWORD) -1;
	      WaitForSingleObject (thread_sync_event, INFINITE);
	      CloseHandle (thread_sync_event);
	    }
	  else
	    debug_printf ("Failed to open thread_sync_event %08x",
			  GetLastError ());
	}
      con.owner = 0;
    }

  CloseHandle (get_handle ());
  CloseHandle (get_output_handle ());

  if (con_ra.rabuf)
    free (con_ra.rabuf);
  memset (&con_ra, 0, sizeof (con_ra));

  if (!have_execed && !invisible_console
      && (!CTTY_IS_VALID (myself->ctty)
	  || get_device () == (dev_t) myself->ctty))
    {
      /* ConEmu hack. Detach from ConEmu to unhook console APIs. */
      HMODULE h = GetModuleHandle ("ConEmuHk64.dll");
      if (h)
	{
	  MODULEINFO mi;
	  if (GetModuleInformation (GetCurrentProcess (), h, &mi, sizeof (mi)))
	    {
	      BOOL (*DllMain)(HINSTANCE, DWORD, LPVOID) =
		(BOOL (*)(HINSTANCE, DWORD, LPVOID)) mi.EntryPoint;
	      DllMain (h, DLL_PROCESS_DETACH, NULL);
	    }
	}

      /* Freeing console to detach the process from the console. */
      free_console ();
    }

  release_output_mutex ();

  CloseHandle (input_mutex);
  input_mutex = NULL;
  CloseHandle (output_mutex);
  output_mutex = NULL;

  WaitForSingleObject (shared_info_mutex, INFINITE);
  if (--shared_info_state[unit] == 0 && shared_console_info[unit])
    {
      UnmapViewOfFile ((void *) shared_console_info[unit]);
      shared_console_info[unit] = NULL;
    }
  ReleaseMutex (shared_info_mutex);

  return 0;
}

int
fhandler_console::ioctl (unsigned int cmd, void *arg)
{
  int res = fhandler_termios::ioctl (cmd, arg);
  if (res <= 0)
    return res;
  acquire_output_mutex (mutex_timeout);
  switch (cmd)
    {
      case TIOCGWINSZ:
	int st;

	st = con.fillin (get_output_handle ());
	if (st)
	  {
	    /* *not* the buffer size, the actual screen size... */
	    /* based on Left Top Right Bottom of srWindow */
	    ((struct winsize *) arg)->ws_row = con.dwWinSize.Y;
	    ((struct winsize *) arg)->ws_col = con.dwWinSize.X;
	    syscall_printf ("WINSZ: (row=%d,col=%d)",
			   ((struct winsize *) arg)->ws_row,
			   ((struct winsize *) arg)->ws_col);
	    release_output_mutex ();
	    return 0;
	  }
	else
	  {
	    syscall_printf ("WINSZ failed");
	    __seterrno ();
	    release_output_mutex ();
	    return -1;
	  }
	release_output_mutex ();
	return 0;
      case TIOCSWINSZ:
	bg_check (SIGTTOU);
	release_output_mutex ();
	return 0;
      case KDGKBMETA:
	*(int *) arg = (con.metabit) ? K_METABIT : K_ESCPREFIX;
	release_output_mutex ();
	return 0;
      case KDSKBMETA:
	if ((intptr_t) arg == K_METABIT)
	  con.metabit = TRUE;
	else if ((intptr_t) arg == K_ESCPREFIX)
	  con.metabit = FALSE;
	else
	  {
	    set_errno (EINVAL);
	    release_output_mutex ();
	    return -1;
	  }
	release_output_mutex ();
	return 0;
      case TIOCLINUX:
	if (*(unsigned char *) arg == 6)
	  {
	    *(unsigned char *) arg = (unsigned char) con.nModifiers;
	    release_output_mutex ();
	    return 0;
	  }
	set_errno (EINVAL);
	release_output_mutex ();
	return -1;
      case FIONREAD:
	{
	  DWORD n;
	  int ret = 0;
	  INPUT_RECORD inp[INREC_SIZE];
	  acquire_attach_mutex (mutex_timeout);
	  DWORD resume_pid = attach_console (con.owner);
	  BOOL r = PeekConsoleInputW (get_handle (), inp, INREC_SIZE, &n);
	  detach_console (resume_pid, con.owner);
	  release_attach_mutex ();
	  if (!r)
	    {
	      set_errno (EINVAL);
	      release_output_mutex ();
	      return -1;
	    }
	  bool saw_eol = false;
	  for (DWORD i=0; i<n; i++)
	    if (inp[i].EventType == KEY_EVENT &&
		inp[i].Event.KeyEvent.bKeyDown &&
		inp[i].Event.KeyEvent.uChar.UnicodeChar)
	      {
		WCHAR wc = inp[i].Event.KeyEvent.uChar.UnicodeChar;
		char mbs[8];
		int len = con.con_to_str (mbs, sizeof (mbs), wc);
		if ((get_ttyp ()->ti.c_lflag & ICANON) &&
		    len == 1 && CCEQ (get_ttyp ()->ti.c_cc[VEOF], mbs[0]))
		  {
		    saw_eol = true;
		    break;
		  }
		ret += len;
		const char eols[] = {
		  '\n',
		  '\r',
		  (char) get_ttyp ()->ti.c_cc[VEOL],
		  (char) get_ttyp ()->ti.c_cc[VEOL2]
		};
		if ((get_ttyp ()->ti.c_lflag & ICANON) &&
		    len == 1 && memchr (eols, mbs[0], sizeof (eols)))
		  {
		    saw_eol = true;
		    break;
		  }
	      }
	  if ((get_ttyp ()->ti.c_lflag & ICANON) && !saw_eol)
	    *(int *) arg = 0;
	  else
	    *(int *) arg = ret;
	  release_output_mutex ();
	  return 0;
	}
	break;
    }

  release_output_mutex ();
  return fhandler_base::ioctl (cmd, arg);
}

int
fhandler_console::tcflush (int queue)
{
  int res = 0;
  if (queue == TCIFLUSH
      || queue == TCIOFLUSH)
    {
      acquire_attach_mutex (mutex_timeout);
      DWORD resume_pid = attach_console (con.owner);
      BOOL r = FlushConsoleInputBuffer (get_handle ());
      detach_console (resume_pid, con.owner);
      release_attach_mutex ();
      if (!r)
	{
	  __seterrno ();
	  res = -1;
	}
      con.num_processed = 0;
    }
  return res;
}

int
fhandler_console::tcsetattr (int a, struct termios const *t)
{
  get_ttyp ()->ti = *t;
  set_input_mode (tty::cygwin, t, &handle_set);
  set_output_mode (tty::cygwin, t, &handle_set);
  return 0;
}

int
fhandler_console::tcgetattr (struct termios *t)
{
  *t = get_ttyp ()->ti;
  t->c_cflag |= CS8;
  return 0;
}

fhandler_console::fhandler_console (fh_devices devunit) :
  fhandler_termios (), input_ready (false), thread_sync_event (NULL),
  input_mutex (NULL), output_mutex (NULL), unit (MAX_CONS_DEV)
{
  dev_referred_via = (dev_t) devunit;
  if (devunit > 0)
    dev ().parse (devunit);
  setup ();
  trunc_buf.len = 0;
}

void
dev_console::set_color (HANDLE h)
{
  WORD win_fg = fg;
  WORD win_bg = bg;
  if (reverse)
    {
      WORD save_fg = win_fg;
      win_fg = (win_bg & BACKGROUND_RED   ? FOREGROUND_RED   : 0) |
	       (win_bg & BACKGROUND_GREEN ? FOREGROUND_GREEN : 0) |
	       (win_bg & BACKGROUND_BLUE  ? FOREGROUND_BLUE  : 0) |
	       (win_bg & BACKGROUND_INTENSITY ? FOREGROUND_INTENSITY : 0);
      win_bg = (save_fg & FOREGROUND_RED   ? BACKGROUND_RED   : 0) |
	       (save_fg & FOREGROUND_GREEN ? BACKGROUND_GREEN : 0) |
	       (save_fg & FOREGROUND_BLUE  ? BACKGROUND_BLUE  : 0) |
	       (save_fg & FOREGROUND_INTENSITY ? BACKGROUND_INTENSITY : 0);
    }

  /* apply attributes */
  if (underline)
    win_fg = underline_color;
  /* emulate blink with bright background */
  if (blink)
    win_bg |= BACKGROUND_INTENSITY;
  if (intensity == INTENSITY_INVISIBLE)
    win_fg = win_bg;
  else if (intensity != INTENSITY_BOLD)
    /* nothing to do */;
    /* apply foreground intensity only in non-reverse mode! */
  else if (reverse)
    win_bg |= BACKGROUND_INTENSITY;
  else
    win_fg |= FOREGROUND_INTENSITY;

  current_win32_attr = win_fg | win_bg;
  if (h)
    {
      acquire_attach_mutex (mutex_timeout);
      DWORD resume_pid = fhandler_console::attach_console (owner);
      SetConsoleTextAttribute (h, current_win32_attr);
      fhandler_console::detach_console (resume_pid, owner);
      release_attach_mutex ();
    }
}

#define FOREGROUND_ATTR_MASK (FOREGROUND_RED | FOREGROUND_GREEN | \
			      FOREGROUND_BLUE | FOREGROUND_INTENSITY)
#define BACKGROUND_ATTR_MASK (BACKGROUND_RED | BACKGROUND_GREEN | \
			      BACKGROUND_BLUE | BACKGROUND_INTENSITY)
void
dev_console::set_default_attr ()
{
  blink = underline = reverse = false;
  intensity = INTENSITY_NORMAL;
  fg = default_color & FOREGROUND_ATTR_MASK;
  bg = default_color & BACKGROUND_ATTR_MASK;
  set_color (NULL);
}

int
dev_console::set_cl_x (cltype x)
{
  if (x == cl_disp_beg || x == cl_buf_beg)
    return 0;
  if (x == cl_disp_end)
    return dwWinSize.X - 1;
  if (x == cl_buf_end)
    return b.dwSize.X - 1;
  return b.dwCursorPosition.X;
}

int
dev_console::set_cl_y (cltype y)
{
  if (y == cl_buf_beg)
    return 0;
  if (y == cl_disp_beg)
    return b.srWindow.Top;
  if (y == cl_disp_end)
    return b.srWindow.Bottom;
  if (y == cl_buf_end)
    return b.dwSize.Y - 1;
  return b.dwCursorPosition.Y;
}

bool
dev_console::scroll_window (HANDLE h, int x1, int y1, int x2, int y2)
{
  if (save_buf || x1 != 0 || x2 != dwWinSize.X - 1 || y1 != b.srWindow.Top
      || y2 != b.srWindow.Bottom || b.dwSize.Y <= dwWinSize.Y)
    return false;

  SMALL_RECT sr;
  int toscroll = dwEnd.Y - b.srWindow.Top + 1;
  sr.Left = sr.Right = dwEnd.X = 0;

  acquire_attach_mutex (mutex_timeout);
  DWORD resume_pid = fhandler_console::attach_console (owner);
  if (b.srWindow.Bottom + toscroll >= b.dwSize.Y)
    {
      /* So we're at the end of the buffer and scrolling the console window
	 would move us beyond the buffer.  What we do here is to scroll the
	 console buffer upward by just as much so that the current last line
	 becomes the last line just prior to the first window line.  That
	 keeps the end of the console buffer intact, as desired. */
      SMALL_RECT br;
      COORD dest;
      CHAR_INFO fill;

      br.Left = 0;
      br.Top = (b.srWindow.Bottom - b.srWindow.Top) + 1
	       - (b.dwSize.Y - dwEnd.Y - 1);
      br.Right = b.dwSize.X - 1;
      br.Bottom = b.dwSize.Y - 1;
      dest.X = dest.Y = 0;
      fill.Char.UnicodeChar = L' ';
      fill.Attributes = current_win32_attr;
      ScrollConsoleScreenBufferW (h, &br, NULL, dest, &fill);
      /* Since we're moving the console buffer under the console window
	 we only have to move the console window if the user scrolled the
	 window upwards.  The number of lines is the distance to the
	 buffer bottom. */
      toscroll = b.dwSize.Y - b.srWindow.Bottom - 1;
      /* Fix dwEnd to reflect the new cursor line.  Take the above scrolling
	 into account and subtract 1 to account for the increment below. */
      dwEnd.Y = b.dwCursorPosition.Y + toscroll - 1;
    }
  if (toscroll)
    {
      /* FIXME: For some reason SetConsoleWindowInfo does not correctly
	 set the scrollbars.  Calling SetConsoleCursorPosition here is
	 just a workaround which doesn't cover all cases.  In some scenarios
	 the scrollbars are still off by one console window size. */

      /* The reminder of the console buffer is big enough to simply move
         the console window.  We have to set the cursor first, otherwise
	 the scroll bars will not be corrected.  */
      SetConsoleCursorPosition (h, dwEnd);
      /* If the user scolled manually, setting the cursor position might scroll
         the console window so that the cursor is not at the top.  Correct
	 the action by moving the window down again so the cursor is one line
	 above the new window position. */
      GetConsoleScreenBufferInfo (h, &b);
      if (b.dwCursorPosition.Y >= b.srWindow.Top)
	toscroll = b.dwCursorPosition.Y - b.srWindow.Top + 1;
      /* Move the window accordingly. */
      sr.Top = sr.Bottom = toscroll;
      SetConsoleWindowInfo (h, FALSE, &sr);
    }
  /* Eventually set cursor to new end position at the top of the window. */
  dwEnd.Y++;
  SetConsoleCursorPosition (h, dwEnd);
  fhandler_console::detach_console (resume_pid, owner);
  release_attach_mutex ();
  /* Fix up console buffer info. */
  fillin (h);
  return true;
}

/*
 * Clear the screen context from x1/y1 to x2/y2 cell.
 * Negative values represents current screen dimensions
 */
void
fhandler_console::clear_screen (cltype xc1, cltype yc1, cltype xc2, cltype yc2)
{
  HANDLE h = get_output_handle ();
  SHORT oldEndY = con.dwEnd.Y;

  con.fillin (h);

  int x1 = con.set_cl_x (xc1);
  int y1 = con.set_cl_y (yc1);
  int x2 = con.set_cl_x (xc2);
  int y2 = con.set_cl_y (yc2);

  /* Make correction for the following situation:  The console buffer
     is only partially used and the user scrolled down into the as yet
     unused area so far that the cursor is outside the window buffer. */
  if (oldEndY < con.dwEnd.Y && oldEndY < con.b.srWindow.Top)
    {
      con.dwEnd.Y = con.b.dwCursorPosition.Y = oldEndY;
      y1 = con.b.srWindow.Top;
    }

  /* Detect special case - scroll the screen if we have a buffer in order to
     preserve the buffer. */
  if (!con.scroll_window (h, x1, y1, x2, y2))
    con.clear_screen (h, x1, y1, x2, y2);
}

void
dev_console::clear_screen (HANDLE h, int x1, int y1, int x2, int y2)
{
  COORD tlc;
  DWORD done;
  int num;

  num = abs (y1 - y2) * b.dwSize.X + abs (x1 - x2) + 1;

  if ((y2 * b.dwSize.X + x2) > (y1 * b.dwSize.X + x1))
    {
      tlc.X = x1;
      tlc.Y = y1;
    }
  else
    {
      tlc.X = x2;
      tlc.Y = y2;
    }
  acquire_attach_mutex (mutex_timeout);
  DWORD resume_pid = fhandler_console::attach_console (owner);
  FillConsoleOutputCharacterW (h, L' ', num, tlc, &done);
  FillConsoleOutputAttribute (h, current_win32_attr, num, tlc, &done);
  fhandler_console::detach_console (resume_pid, owner);
  release_attach_mutex ();
}

void
fhandler_console::cursor_set (bool rel_to_top, int x, int y)
{
  COORD pos;

  con.fillin (get_output_handle ());
#if 0
  /* Setting y to the current b.srWindow.Bottom here is the reason that the window
     isn't scrolled back to the current cursor position like it's done in
     any other terminal.  Rather, the curser is forced to the bottom of the
     currently scrolled region.  This breaks the console buffer content if
     output is generated while the user had the window scrolled back.  This
     behaviour is very old, it has no matching ChangeLog entry.
     Just disable for now but keep the code in for future reference. */
  if (y > con.b.srWindow.Bottom)
    y = con.b.srWindow.Bottom;
  else
#endif
  if (y < 0)
    y = 0;
  else if (rel_to_top)
    y += con.b.srWindow.Top;

  if (x > con.dwWinSize.X)
    x = con.dwWinSize.X - 1;
  else if (x < 0)
    x = 0;

  pos.X = x;
  pos.Y = y;
  acquire_attach_mutex (mutex_timeout);
  DWORD resume_pid = attach_console (con.owner);
  SetConsoleCursorPosition (get_output_handle (), pos);
  detach_console (resume_pid, con.owner);
  release_attach_mutex ();
}

void
fhandler_console::cursor_rel (int x, int y)
{
  con.fillin (get_output_handle ());
  x += con.b.dwCursorPosition.X;
  y += con.b.dwCursorPosition.Y;
  cursor_set (false, x, y);
}

void
fhandler_console::cursor_get (int *x, int *y)
{
  con.fillin (get_output_handle ());
  *y = con.b.dwCursorPosition.Y;
  *x = con.b.dwCursorPosition.X;
}

/* VT100 line drawing graphics mode maps `abcdefghijklmnopqrstuvwxyz{|}~ to
   graphical characters */
static const wchar_t __vt100_conv[31] = {
	0x25C6, /* Black Diamond */
	0x2592, /* Medium Shade */
	0x2409, /* Symbol for Horizontal Tabulation */
	0x240C, /* Symbol for Form Feed */
	0x240D, /* Symbol for Carriage Return */
	0x240A, /* Symbol for Line Feed */
	0x00B0, /* Degree Sign */
	0x00B1, /* Plus-Minus Sign */
	0x2424, /* Symbol for Newline */
	0x240B, /* Symbol for Vertical Tabulation */
	0x2518, /* Box Drawings Light Up And Left */
	0x2510, /* Box Drawings Light Down And Left */
	0x250C, /* Box Drawings Light Down And Right */
	0x2514, /* Box Drawings Light Up And Right */
	0x253C, /* Box Drawings Light Vertical And Horizontal */
	0x23BA, /* Horizontal Scan Line-1 */
	0x23BB, /* Horizontal Scan Line-3 */
	0x2500, /* Box Drawings Light Horizontal */
	0x23BC, /* Horizontal Scan Line-7 */
	0x23BD, /* Horizontal Scan Line-9 */
	0x251C, /* Box Drawings Light Vertical And Right */
	0x2524, /* Box Drawings Light Vertical And Left */
	0x2534, /* Box Drawings Light Up And Horizontal */
	0x252C, /* Box Drawings Light Down And Horizontal */
	0x2502, /* Box Drawings Light Vertical */
	0x2264, /* Less-Than Or Equal To */
	0x2265, /* Greater-Than Or Equal To */
	0x03C0, /* Greek Small Letter Pi */
	0x2260, /* Not Equal To */
	0x00A3, /* Pound Sign */
	0x00B7, /* Middle Dot */
};

inline bool
fhandler_console::write_console (PWCHAR buf, DWORD len, DWORD& done)
{
  if (con.iso_2022_G1
	? con.vt100_graphics_mode_G1
	: con.vt100_graphics_mode_G0)
    for (DWORD i = 0; i < len; i ++)
      if (buf[i] >= (unsigned char) '`' && buf[i] <= (unsigned char) '~')
	buf[i] = __vt100_conv[buf[i] - (unsigned char) '`'];

  if (len > 0)
    last_char = buf[len-1];

  while (len > 0)
    {
      DWORD nbytes = len > MAX_WRITE_CHARS ? MAX_WRITE_CHARS : len;
      acquire_attach_mutex (mutex_timeout);
      DWORD resume_pid = attach_console (con.owner);
      BOOL r = WriteConsoleW (get_output_handle (), buf, nbytes, &done, 0);
      detach_console (resume_pid, con.owner);
      release_attach_mutex ();
      if (!r)
	{
	  __seterrno ();
	  return false;
	}
      len -= done;
      buf += done;
    }
  return true;
}

/* The following three functions were adapted (i.e., mildly modified) from
   http://stackoverflow.com/questions/14699043/replacement-to-systemcolor */

/* Split a rectangular region into two smaller rectangles based on the
   largest dimension. */
static void
region_split (PCHAR_INFO& buf, COORD& bufsiz, SMALL_RECT& region,
	      PCHAR_INFO& buf_b, COORD& bufsiz_b, SMALL_RECT& region_b)
{
  region_b = region;
  bufsiz_b = bufsiz;

  SHORT half = (1 + region.Bottom - region.Top) / 2;
  region_b.Top += half;
  region.Bottom = (bufsiz.Y = region_b.Top) - 1;
  buf_b = buf + (half * (1 + region.Right));
  bufsiz_b.Y = region_b.Bottom - region_b.Top;
}

/* Utility function to figure out the distance between two points. */
static SHORT
delta (SHORT first, SHORT second)
{
  return (second >= first) ? (second - first + 1) : 0;
}

/* Subdivide the ReadConsoleInput operation into smaller and smaller chunks as
   needed until it succeeds in reading the entire screen buffer. */
static BOOL
ReadConsoleOutputWrapper (HANDLE h, PCHAR_INFO buf, COORD bufsiz,
			  SMALL_RECT region)
{
  COORD coord = {};
  SHORT width = delta (region.Left, region.Right);
  SHORT height = delta (region.Top, region.Bottom);

  if ((width == 0) || (height == 0))
    return TRUE;

  BOOL success = ReadConsoleOutputW (h, buf, bufsiz, coord, &region);
  if (success)
    /* it worked */;
  else if (GetLastError () == ERROR_NOT_ENOUGH_MEMORY && (width * height) > 1)
    {
      PCHAR_INFO buf_b;
      COORD bufsiz_b;
      SMALL_RECT region_b;
      region_split (buf, bufsiz, region, buf_b, bufsiz_b, region_b);
      success = ReadConsoleOutputWrapper (h, buf, bufsiz, region)
		&& ReadConsoleOutputWrapper (h, buf_b, bufsiz_b, region_b);
    }
  return success;
}

void
dev_console::save_restore (HANDLE h, char c)
{
  if (c == 'h') /* save */
    {
      fillin (h);
      save_bufsize.X = b.dwSize.X;
      if ((save_bufsize.Y = dwEnd.Y + 1) > b.dwSize.Y)
	save_bufsize.X = b.dwSize.Y;

      if (save_buf)
	cfree (save_buf);
      size_t screen_size = sizeof (CHAR_INFO) * save_bufsize.X * save_bufsize.Y;
      save_buf = (PCHAR_INFO) cmalloc_abort (HEAP_1_BUF, screen_size);

      save_cursor = b.dwCursorPosition;	/* Remember where we were. */
      save_top = b.srWindow.Top;

      SMALL_RECT now = {};		/* Read the whole buffer */
      now.Bottom = save_bufsize.Y - 1;
      now.Right = save_bufsize.X - 1;
      acquire_attach_mutex (mutex_timeout);
      DWORD resume_pid = fhandler_console::attach_console (owner);
      if (!ReadConsoleOutputWrapper (h, save_buf, save_bufsize, now))
	debug_printf ("ReadConsoleOutputWrapper(h, ...) failed during save, %E");
      fhandler_console::detach_console (resume_pid, owner);
      release_attach_mutex ();

      /* Position at top of buffer */
      COORD cob = {};
      acquire_attach_mutex (mutex_timeout);
      resume_pid = fhandler_console::attach_console (owner);
      if (!SetConsoleCursorPosition (h, cob))
	debug_printf ("SetConsoleCursorInfo(%p, ...) failed during save, %E", h);
      fhandler_console::detach_console (resume_pid, owner);
      release_attach_mutex ();

      /* Clear entire buffer */
      clear_screen (h, 0, 0, now.Right, now.Bottom);
      b.dwCursorPosition.X = b.dwCursorPosition.Y = dwEnd.X = dwEnd.Y = 0;
    }
  else if (save_buf)
    {
      COORD cob = {};
      SMALL_RECT now = {};
      now.Bottom = save_bufsize.Y - 1;
      now.Right = save_bufsize.X - 1;
      /* Restore whole buffer */
      clear_screen (h, 0, 0, b.dwSize.X - 1, b.dwSize.Y - 1);
      acquire_attach_mutex (mutex_timeout);
      DWORD resume_pid = fhandler_console::attach_console (owner);
      BOOL res = WriteConsoleOutputW (h, save_buf, save_bufsize, cob, &now);
      fhandler_console::detach_console (resume_pid, owner);
      release_attach_mutex ();
      if (!res)
	debug_printf ("WriteConsoleOutputW failed, %E");

      cfree (save_buf);
      save_buf = NULL;

      cob.X = 0;
      cob.Y = save_top;
      /* CGF: NOOP?  Doesn't seem to position screen as expected */
      /* Temporarily position at top of screen */
      acquire_attach_mutex (mutex_timeout);
      resume_pid = fhandler_console::attach_console (owner);
      if (!SetConsoleCursorPosition (h, cob))
	debug_printf ("SetConsoleCursorInfo(%p, cob) failed during restore, %E", h);
      /* Position where we were previously */
      if (!SetConsoleCursorPosition (h, save_cursor))
	debug_printf ("SetConsoleCursorInfo(%p, save_cursor) failed during restore, %E", h);
      fhandler_console::detach_console (resume_pid, owner);
      release_attach_mutex ();
      /* Get back correct version of buffer information */
      dwEnd.X = dwEnd.Y = 0;
      fillin (h);
    }
}

#define BAK 1
#define ESC 2
#define NOR 0
#define IGN 4
#if 1
#define ERR 5
#else
#define ERR NOR
#endif
#define DWN 6
#define BEL 7
#define TAB 8 /* We should't let the console deal with these */
#define CR 13
#define LF 10
#define SO 14
#define SI 15

static const char base_chars[256] =
{
/*00 01 02 03 04 05 06 07 */ IGN, ERR, ERR, NOR, NOR, NOR, NOR, BEL,
/*08 09 0A 0B 0C 0D 0E 0F */ BAK, TAB, DWN, ERR, ERR, CR,  SO,  SI,
/*10 11 12 13 14 15 16 17 */ NOR, NOR, ERR, ERR, ERR, ERR, ERR, ERR,
/*18 19 1A 1B 1C 1D 1E 1F */ NOR, NOR, ERR, ESC, ERR, ERR, ERR, ERR,
/*   !  "  #  $  %  &  '  */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
/*(  )  *  +  ,  -  .  /  */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
/*0  1  2  3  4  5  6  7  */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
/*8  9  :  ;  <  =  >  ?  */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
/*@  A  B  C  D  E  F  G  */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
/*H  I  J  K  L  M  N  O  */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
/*P  Q  R  S  T  U  V  W  */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
/*X  Y  Z  [  \  ]  ^  _  */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
/*`  a  b  c  d  e  f  g  */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
/*h  i  j  k  l  m  n  o  */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
/*p  q  r  s  t  u  v  w  */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
/*x  y  z  {  |  }  ~  7F */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
/*80 81 82 83 84 85 86 87 */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
/*88 89 8A 8B 8C 8D 8E 8F */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
/*90 91 92 93 94 95 96 97 */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
/*98 99 9A 9B 9C 9D 9E 9F */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
/*A0 A1 A2 A3 A4 A5 A6 A7 */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
/*A8 A9 AA AB AC AD AE AF */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
/*B0 B1 B2 B3 B4 B5 B6 B7 */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
/*B8 B9 BA BB BC BD BE BF */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
/*C0 C1 C2 C3 C4 C5 C6 C7 */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
/*C8 C9 CA CB CC CD CE CF */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
/*D0 D1 D2 D3 D4 D5 D6 D7 */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
/*D8 D9 DA DB DC DD DE DF */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
/*E0 E1 E2 E3 E4 E5 E6 E7 */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
/*E8 E9 EA EB EC ED EE EF */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
/*F0 F1 F2 F3 F4 F5 F6 F7 */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
/*F8 F9 FA FB FC FD FE FF */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR };

static const char table256[256] =
{
   0, 4, 2, 6, 1, 5, 3, 7, 8,12,10,14, 9,13,11,15,
   0, 1, 1, 1, 9, 9, 2, 3, 3, 3, 3, 9, 2, 3, 3, 3,
   3,11, 2, 3, 3, 3,11,11,10, 3, 3,11,11,11,10,10,
  11,11,11,11, 4, 5, 5, 5, 5, 9, 6, 8, 8, 8, 8, 9,
   6, 8, 8, 8, 8, 7, 6, 8, 8, 8, 7, 7, 6, 8, 8, 7,
   7,11,10,10, 7, 7,11,11, 4, 5, 5, 5, 5,13, 6, 8,
   8, 8, 8, 7, 6, 8, 8, 8, 7, 7, 6, 8, 8, 7, 7, 7,
   6, 8, 7, 7, 7, 7,14, 7, 7, 7, 7, 7, 4, 5, 5, 5,
  13,13, 6, 8, 8, 8, 7, 7, 6, 8, 8, 7, 7, 7, 6, 8,
   7, 7, 7, 7,14, 7, 7, 7, 7, 7,14, 7, 7, 7, 7,15,
  12, 5, 5,13,13,13, 6, 8, 8, 7, 7,13, 6, 8, 7, 7,
   7, 7,14, 7, 7, 7, 7, 7,14, 7, 7, 7, 7,15,14,14,
   7, 7,15,15,12,12,13,13,13,13,12,12, 7, 7,13,13,
  14, 7, 7, 7, 7, 7,14, 7, 7, 7, 7,15,14,14, 7, 7,
  15,15,14,14, 7,15,15,15, 0, 0, 0, 0, 0, 0, 8, 8,
   8, 8, 8, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7,15,15
};

void
fhandler_console::char_command (char c)
{
  int x, y, n;
  char buf[40];
  wchar_t bufw[40];
  int r, g, b;

  if (wincap.has_con_24bit_colors () && !con_is_legacy)
    {
      /* For xterm compatible mode */
      switch (c)
	{
#if 0 /* These sequences, which are supported by real xterm, are
	 not supported by xterm compatible mode. Therefore they
	 were implemented once. However, these are not declared
	 in terminfo of xterm-256color, therefore, do not appear
	 to be necessary. */
	case '`': /* HPA */
	  if (con.args[0] == 0)
	    con.args[0] = 1;
	  cursor_get (&x, &y);
	  cursor_set (false, con.args[0]-1, y);
	  break;
	case 'a': /* HPR */
	  if (con.args[0] == 0)
	    con.args[0] = 1;
	  cursor_rel (con.args[0], 0);
	  break;
	case 'e': /* VPR */
	  if (con.args[0] == 0)
	    con.args[0] = 1;
	  cursor_rel (0, con.args[0]);
	  break;
#endif
	case 'b': /* REP */
	  wpbuf_put (c);
	  if (wincap.has_con_esc_rep ())
	    /* Just send the sequence */
	    wpbuf_send ();
	  else if (last_char && last_char != L'\n')
	    {
	      acquire_attach_mutex (mutex_timeout);
	      DWORD resume_pid = attach_console (con.owner);
	      for (int i = 0; i < con.args[0]; i++)
		WriteConsoleW (get_output_handle (), &last_char, 1, 0, 0);
	      detach_console (resume_pid, con.owner);
	      release_attach_mutex ();
	    }
	  break;
	case 'r': /* DECSTBM */
	  con.scroll_region.Top = con.args[0] ? con.args[0] - 1 : 0;
	  con.scroll_region.Bottom = con.args[1] ? con.args[1] - 1 : -1;
	  wpbuf_put (c);
	  /* Just send the sequence */
	  wpbuf_send ();
	  break;
	case 'L': /* IL */
	  if (wincap.has_con_broken_il_dl ())
	    {
	      /* Use "CSI Ps T" instead */
	      cursor_get (&x, &y);
	      if (y < srTop || y > srBottom)
		break;
	      if (y == con.b.srWindow.Bottom)
		{
		  acquire_attach_mutex (mutex_timeout);
		  DWORD resume_pid = attach_console (con.owner);
		  WriteConsoleW (get_output_handle (), L"\033[2K", 4, 0, 0);
		  detach_console (resume_pid, con.owner);
		  release_attach_mutex ();
		  break;
		}
	      acquire_attach_mutex (mutex_timeout);
	      DWORD resume_pid = attach_console (con.owner);
	      if (y == con.b.srWindow.Top
		  && srBottom == con.b.srWindow.Bottom)
		{
		  /* Erase scroll down area */
		  n = con.args[0] ? : 1;
		  __small_swprintf (bufw, L"\033[%d;1H\033[J\033[%d;%dH",
				    srBottom - (n-1) - con.b.srWindow.Top + 1,
				    y + 1 - con.b.srWindow.Top, x + 1);
		  WriteConsoleW (get_output_handle (),
				 bufw, wcslen (bufw), 0, 0);
		}
	      __small_swprintf (bufw, L"\033[%d;%dr",
				y + 1 - con.b.srWindow.Top,
				srBottom + 1 - con.b.srWindow.Top);
	      WriteConsoleW (get_output_handle (), bufw, wcslen (bufw), 0, 0);
	      wpbuf_put ('T');
	      wpbuf_send ();
	      __small_swprintf (bufw, L"\033[%d;%dr",
				srTop + 1 - con.b.srWindow.Top,
				srBottom + 1 - con.b.srWindow.Top);
	      WriteConsoleW (get_output_handle (), bufw, wcslen (bufw), 0, 0);
	      __small_swprintf (bufw, L"\033[%d;%dH",
				y + 1 - con.b.srWindow.Top, x + 1);
	      WriteConsoleW (get_output_handle (), bufw, wcslen (bufw), 0, 0);
	      detach_console (resume_pid, con.owner);
	      release_attach_mutex ();
	    }
	  else
	    {
	      wpbuf_put (c);
	      /* Just send the sequence */
	      wpbuf_send ();
	    }
	  break;
	case 'M': /* DL */
	  if (wincap.has_con_broken_il_dl ())
	    {
	      /* Use "CSI Ps S" instead */
	      cursor_get (&x, &y);
	      if (y < srTop || y > srBottom)
		break;
	      if (y == con.b.srWindow.Bottom)
		{
		  acquire_attach_mutex (mutex_timeout);
		  DWORD resume_pid = attach_console (con.owner);
		  WriteConsoleW (get_output_handle (), L"\033[2K", 4, 0, 0);
		  detach_console (resume_pid, con.owner);
		  release_attach_mutex ();
		  break;
		}
	      __small_swprintf (bufw, L"\033[%d;%dr",
				y + 1 - con.b.srWindow.Top,
				srBottom + 1 - con.b.srWindow.Top);
	      acquire_attach_mutex (mutex_timeout);
	      DWORD resume_pid = attach_console (con.owner);
	      WriteConsoleW (get_output_handle (), bufw, wcslen (bufw), 0, 0);
	      wpbuf_put ('S');
	      wpbuf_send ();
	      __small_swprintf (bufw, L"\033[%d;%dr",
				srTop + 1 - con.b.srWindow.Top,
				srBottom + 1 - con.b.srWindow.Top);
	      WriteConsoleW (get_output_handle (), bufw, wcslen (bufw), 0, 0);
	      __small_swprintf (bufw, L"\033[%d;%dH",
				y + 1 - con.b.srWindow.Top, x + 1);
	      WriteConsoleW (get_output_handle (), bufw, wcslen (bufw), 0, 0);
	      detach_console (resume_pid, con.owner);
	      release_attach_mutex ();
	    }
	  else
	    {
	      wpbuf_put (c);
	      /* Just send the sequence */
	      wpbuf_send ();
	    }
	  break;
	case 'J': /* ED */
	  wpbuf_put (c);
	  if (con.args[0] == 3 && con.savey >= 0)
	    {
	      con.fillin (get_output_handle ());
	      con.savey -= con.b.srWindow.Top;
	    }
	  if (con.args[0] == 3 && wincap.has_con_broken_csi3j ())
	    { /* Workaround for broken CSI3J in Win10 1809 */
	      CONSOLE_SCREEN_BUFFER_INFO sbi;
	      acquire_attach_mutex (mutex_timeout);
	      DWORD resume_pid = attach_console (con.owner);
	      GetConsoleScreenBufferInfo (get_output_handle (), &sbi);
	      SMALL_RECT r = {0, sbi.srWindow.Top,
		(SHORT) (sbi.dwSize.X - 1), (SHORT) (sbi.dwSize.Y - 1)};
	      CHAR_INFO f = {' ', sbi.wAttributes};
	      COORD d = {0, 0};
	      ScrollConsoleScreenBufferA (get_output_handle (),
					  &r, NULL, d, &f);
	      SetConsoleCursorPosition (get_output_handle (), d);
	      d = sbi.dwCursorPosition;
	      d.Y -= sbi.srWindow.Top;
	      SetConsoleCursorPosition (get_output_handle (), d);
	      detach_console (resume_pid, con.owner);
	      release_attach_mutex ();
	    }
	  else
	    /* Just send the sequence */
	    wpbuf_send ();
	  break;
	case 'h': /* DECSET */
	case 'l': /* DECRST */
	  wpbuf_put (c);
	  /* Just send the sequence */
	  wpbuf_send ();
	  if (con.saw_question_mark)
	    {
	      bool need_fix_tab_position = false;
	      for (int i = 0; i < con.nargs; i++)
		{
		  if (con.args[i] == 1049)
		    {
		      con.screen_alternated = (c == 'h');
		      need_fix_tab_position = wincap.has_con_broken_tabs ();
		    }
		  if (con.args[i] == 1) /* DECCKM */
		    con.cursor_key_app_mode = (c == 'h');
		  if (con.args[i] == 9001) /* win32-input-mode (https://github.com/microsoft/terminal/blob/main/doc/specs/%234999%20-%20Improved%20keyboard%20handling%20in%20Conpty.md) */
		    {
		      set_disable_master_thread (c == 'h', this);
		      if (con.curr_input_mode == tty::cygwin)
			set_input_mode (tty::cygwin,
					&tc ()->ti, get_handle_set ());
		    }
		}
	      /* Call fix_tab_position() if screen has been alternated. */
	      if (need_fix_tab_position)
		fix_tab_position (get_output_handle (), con.owner);
	    }
	  break;
	case 'p':
	  if (con.saw_exclamation_mark) /* DECSTR Soft reset */
	    {
	      con.scroll_region.Top = 0;
	      con.scroll_region.Bottom = -1;
	      con.savex = con.savey = -1;
	      con.cursor_key_app_mode = false;
	    }
	  wpbuf_put (c);
	  /* Just send the sequence */
	  wpbuf_send ();
	  break;
	case 'm':
	  if (con.saw_greater_than_sign)
	    break; /* Ignore unsupported CSI > Pm m */
	  /* Text attribute settings */
	  wpbuf_put (c);
	  /* Just send the sequence */
	  wpbuf_send ();
	  break;
	default:
	  /* Other escape sequences */
	  wpbuf_put (c);
	  /* Just send the sequence */
	  wpbuf_send ();
	  break;
	}
      return;
    }

  /* For legacy cygwin treminal */
  switch (c)
    {
    case 'm':   /* Set Graphics Rendition */
       for (int i = 0; i < con.nargs; i++)
	 switch (con.args[i])
	   {
	     case 0:    /* normal color */
	       con.set_default_attr ();
	       break;
	     case 1:    /* bold */
	       con.intensity = INTENSITY_BOLD;
	       break;
	     case 2:	/* dim */
	       con.intensity = INTENSITY_DIM;
	       break;
	     case 4:	/* underlined */
	       con.underline = 1;
	       break;
	     case 5:    /* blink mode */
	       con.blink = true;
	       break;
	     case 7:    /* reverse */
	       con.reverse = true;
	       break;
	     case 8:    /* invisible */
	       con.intensity = INTENSITY_INVISIBLE;
	       break;
	     case 10:   /* end alternate charset */
	       con.alternate_charset_active = false;
	       break;
	     case 11:   /* start alternate charset */
	       con.alternate_charset_active = true;
	       break;
	     case 22:
	     case 28:
	       con.intensity = INTENSITY_NORMAL;
	       break;
	     case 24:
	       con.underline = false;
	       break;
	     case 25:
	       con.blink = false;
	       break;
	     case 27:
	       con.reverse = false;
	       break;
	     case 30:		/* BLACK foreground */
	       con.fg = 0;
	       break;
	     case 31:		/* RED foreground */
	       con.fg = FOREGROUND_RED;
	       break;
	     case 32:		/* GREEN foreground */
	       con.fg = FOREGROUND_GREEN;
	       break;
	     case 33:		/* YELLOW foreground */
	       con.fg = FOREGROUND_RED | FOREGROUND_GREEN;
	       break;
	     case 34:		/* BLUE foreground */
	       con.fg = FOREGROUND_BLUE;
	       break;
	     case 35:		/* MAGENTA foreground */
	       con.fg = FOREGROUND_RED | FOREGROUND_BLUE;
	       break;
	     case 36:		/* CYAN foreground */
	       con.fg = FOREGROUND_BLUE | FOREGROUND_GREEN;
	       break;
	     case 37:		/* WHITE foreg */
	       con.fg = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED;
	       break;
	     case 38:
	       if (con.nargs < i + 2)
		 /* Sequence error (abort) */
		 break;
	       switch (con.args[i + 1])
		 {
		 case 2:
		   if (con.nargs < i + 5)
		     /* Sequence error (abort) */
		     break;
		   r = con.args[i + 2];
		   g = con.args[i + 3];
		   b = con.args[i + 4];
		   r = r < (95 + 1) / 2 ? 0 : r > 255 ? 5 : (r - 55 + 20) / 40;
		   g = g < (95 + 1) / 2 ? 0 : g > 255 ? 5 : (g - 55 + 20) / 40;
		   b = b < (95 + 1) / 2 ? 0 : b > 255 ? 5 : (b - 55 + 20) / 40;
		   con.fg = table256[16 + r*36 + g*6 + b];
		   i += 4;
		   break;
		 case 5:
		   if (con.nargs < i + 3)
		     /* Sequence error (abort) */
		     break;
		   {
		     int idx = con.args[i + 2];
		     if (idx < 0)
		       idx = 0;
		     if (idx > 255)
		       idx = 255;
		     con.fg = table256[idx];
		     i += 2;
		   }
		   break;
		 }
	       break;
	     case 39:
	       con.fg = con.default_color & FOREGROUND_ATTR_MASK;
	       break;
	     case 40:		/* BLACK background */
	       con.bg = 0;
	       break;
	     case 41:		/* RED background */
	       con.bg = BACKGROUND_RED;
	       break;
	     case 42:		/* GREEN background */
	       con.bg = BACKGROUND_GREEN;
	       break;
	     case 43:		/* YELLOW background */
	       con.bg = BACKGROUND_RED | BACKGROUND_GREEN;
	       break;
	     case 44:		/* BLUE background */
	       con.bg = BACKGROUND_BLUE;
	       break;
	     case 45:		/* MAGENTA background */
	       con.bg = BACKGROUND_RED | BACKGROUND_BLUE;
	       break;
	     case 46:		/* CYAN background */
	       con.bg = BACKGROUND_BLUE | BACKGROUND_GREEN;
	       break;
	     case 47:    /* WHITE background */
	       con.bg = BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED;
	       break;
	     case 48:
	       if (con.nargs < i + 2)
		 /* Sequence error (abort) */
		 break;
	       switch (con.args[i + 1])
		 {
		 case 2:
		   if (con.nargs < i + 5)
		     /* Sequence error (abort) */
		     break;
		   r = con.args[i + 2];
		   g = con.args[i + 3];
		   b = con.args[i + 4];
		   r = r < (95 + 1) / 2 ? 0 : r > 255 ? 5 : (r - 55 + 20) / 40;
		   g = g < (95 + 1) / 2 ? 0 : g > 255 ? 5 : (g - 55 + 20) / 40;
		   b = b < (95 + 1) / 2 ? 0 : b > 255 ? 5 : (b - 55 + 20) / 40;
		   con.bg = table256[16 + r*36 + g*6 + b] << 4;
		   i += 4;
		   break;
		 case 5:
		   if (con.nargs < i + 3)
		     /* Sequence error (abort) */
		     break;
		   {
		     int idx = con.args[i + 2];
		     if (idx < 0)
		       idx = 0;
		     if (idx > 255)
		       idx = 255;
		     con.bg = table256[idx] << 4;
		     i += 2;
		   }
		   break;
		 }
	       break;
	     case 49:
	       con.bg = con.default_color & BACKGROUND_ATTR_MASK;
	       break;
	   }
       con.set_color (get_output_handle ());
      break;
    case 'q': /* Set cursor style (DECSCUSR) */
      if (con.saw_space)
	{
	    CONSOLE_CURSOR_INFO console_cursor_info;
	    acquire_attach_mutex (mutex_timeout);
	    DWORD resume_pid = attach_console (con.owner);
	    GetConsoleCursorInfo (get_output_handle (), &console_cursor_info);
	    switch (con.args[0])
	      {
		case 0: /* blinking block */
		case 1: /* blinking block (default) */
		case 2: /* steady block */
		  console_cursor_info.dwSize = 100;
		  SetConsoleCursorInfo (get_output_handle (),
					&console_cursor_info);
		  break;
		case 3: /* blinking underline */
		case 4: /* steady underline */
		  console_cursor_info.dwSize = 10; /* or Windows default 25? */
		  SetConsoleCursorInfo (get_output_handle (),
					&console_cursor_info);
		  break;
		default: /* use value as percentage */
		  console_cursor_info.dwSize = con.args[0];
		  SetConsoleCursorInfo (get_output_handle (),
					&console_cursor_info);
		  break;
	      }
	    detach_console (resume_pid, con.owner);
	    release_attach_mutex ();
	}
      break;
    case 'h':
    case 'l':
      if (!con.saw_question_mark)
	{
	  switch (con.args[0])
	    {
	    case 4:    /* Insert mode */
	      con.insert_mode = (c == 'h') ? true : false;
	      syscall_printf ("insert mode %sabled",
			      con.insert_mode ? "en" : "dis");
	      break;
	    }
	  break;
	}
      switch (con.args[0])
	{
	case 25: /* Show/Hide Cursor (DECTCEM) */
	  {
	    CONSOLE_CURSOR_INFO console_cursor_info;
	    acquire_attach_mutex (mutex_timeout);
	    DWORD resume_pid = attach_console (con.owner);
	    GetConsoleCursorInfo (get_output_handle (), & console_cursor_info);
	    if (c == 'h')
	      console_cursor_info.bVisible = TRUE;
	    else
	      console_cursor_info.bVisible = FALSE;
	    SetConsoleCursorInfo (get_output_handle (), & console_cursor_info);
	    detach_console (resume_pid, con.owner);
	    release_attach_mutex ();
	    break;
	  }
	case 47:   /* Save/Restore screen */
	  con.save_restore (get_output_handle (), c);
	  break;

	case 67: /* DECBKM ("DEC Backarrow Key Mode") */
	  con.backspace_keycode = (c == 'h' ? CTRL('H') : CERASE);
	  break;

	case 1000: /* Mouse tracking */
	  con.use_mouse = (c == 'h') ? 1 : 0;
	  break;

	case 1002: /* Mouse button event tracking */
	  con.use_mouse = (c == 'h') ? 2 : 0;
	  break;

	case 1003: /* Mouse any event tracking */
	  con.use_mouse = (c == 'h') ? 3 : 0;
	  break;

	case 1004: /* Focus in/out event reporting */
	  con.use_focus = (c == 'h') ? true : false;
	  break;

	case 1005: /* Extended mouse mode */
	  con.ext_mouse_mode5 = c == 'h';
	  break;

	case 1006: /* SGR extended mouse mode */
	  con.ext_mouse_mode6 = c == 'h';
	  break;

	case 1015: /* Urxvt extended mouse mode */
	  con.ext_mouse_mode15 = c == 'h';
	  break;

	case 2000: /* Raw keyboard mode */
	  set_raw_win32_keyboard_mode ((c == 'h') ? true : false);
	  break;

	default: /* Ignore */
	  syscall_printf ("unknown h/l command: %d", con.args[0]);
	  break;
	}
      break;
    case 'J':
      switch (con.args[0])
	{
	case 0:			/* Clear to end of screen */
	  clear_screen (cl_curr_pos, cl_curr_pos, cl_disp_end, cl_disp_end);
	  break;
	case 1:			/* Clear from beginning of screen to cursor */
	  clear_screen (cl_disp_beg, cl_disp_beg, cl_curr_pos, cl_curr_pos);
	  break;
	case 2:			/* Clear screen */
	  cursor_get (&x, &y);
	  clear_screen (cl_disp_beg, cl_disp_beg, cl_disp_end, cl_disp_end);
	  cursor_set (false, x, y);
	  break;
	default:
	  goto bad_escape;
	}
      break;

    case 'A':
      cursor_rel (0, -(con.args[0] ?: 1));
      break;
    case 'B':
      cursor_rel (0, con.args[0] ?: 1);
      break;
    case 'C':
      cursor_rel (con.args[0] ?: 1, 0);
      break;
    case 'D':
      cursor_rel (-(con.args[0] ?: 1),0);
      break;
    case 'K':
      switch (con.args[0])
	{
	  case 0:		/* Clear to end of line */
	    clear_screen (cl_curr_pos, cl_curr_pos, cl_disp_end, cl_curr_pos);
	    break;
	  case 2:		/* Clear line */
	    clear_screen (cl_disp_beg, cl_curr_pos, cl_disp_end, cl_curr_pos);
	    break;
	  case 1:		/* Clear from bol to cursor */
	    clear_screen (cl_disp_beg, cl_curr_pos, cl_curr_pos, cl_curr_pos);
	    break;
	  default:
	    goto bad_escape;
	}
      break;
    case 'H':
    case 'f':
      cursor_set (true, (con.args[1] ?: 1) - 1,
			(con.args[0] ?: 1) - 1);
      break;
    case 'G':   /* hpa - position cursor at column n - 1 */
      cursor_get (&x, &y);
      cursor_set (false, (con.args[0] ? con.args[0] - 1 : 0), y);
      break;
    case 'd':   /* vpa - position cursor at line n */
      cursor_get (&x, &y);
      cursor_set (true, x, (con.args[0] ? con.args[0] - 1 : 0));
      break;
    case 's':   /* Save cursor position */
      cursor_get (&con.savex, &con.savey);
      con.savey -= con.b.srWindow.Top;
      break;
    case 'u':   /* Restore cursor position */
      cursor_set (true, con.savex, con.savey);
      break;
    case 'I':	/* TAB */
      cursor_get (&x, &y);
      cursor_set (false, 8 * (x / 8 + 1), y);
      break;
    case 'L':				/* AL - insert blank lines */
      n = con.args[0] ?: 1;
      cursor_get (&x, &y);
      scroll_buffer (0, y, -1, -1, 0, y + n);
      break;
    case 'M':				/* DL - delete lines */
      n = con.args[0] ?: 1;
      cursor_get (&x, &y);
      scroll_buffer (0, y + n, -1, -1, 0, y);
      break;
    case '@':				/* IC - insert chars */
      n = con.args[0] ?: 1;
      cursor_get (&x, &y);
      scroll_buffer (x, y, -1, y, x + n, y);
      break;
    case 'P':				/* DC - delete chars */
      n = con.args[0] ?: 1;
      cursor_get (&x, &y);
      scroll_buffer (x + n, y, -1, y, x, y);
      break;
    case 'S':				/* SF - Scroll forward */
      n = con.args[0] ?: 1;
      scroll_buffer_screen (0, n, -1, -1, 0, 0);
      break;
    case 'T':				/* SR - Scroll down */
      con.fillin (get_output_handle ());
      n = con.b.srWindow.Top + con.args[0] ?: 1;
      scroll_buffer_screen (0, 0, -1, -1, 0, n);
      break;
    case 'X':				/* ec - erase chars */
      n = con.args[0] ?: 1;
      cursor_get (&x, &y);
      scroll_buffer (x + n, y, -1, y, x, y);
      scroll_buffer (x, y, -1, y, x + n, y);
      break;
    case 'Z':				/* Back tab */
      cursor_get (&x, &y);
      cursor_set (false, ((8 * (x / 8 + 1)) - 8), y);
      break;
    case 'b':				/* Repeat char #1 #2 times */
      if (con.insert_mode)
	{
	  cursor_get (&x, &y);
	  scroll_buffer (x, y, -1, y, x + con.args[1], y);
	}
      while (con.args[1]--)
	WriteFile (get_output_handle (), &con.args[0], 1, (DWORD *) &x, 0);
      break;
    case 'c':				/* u9 - Terminal enquire string */
      if (con.saw_greater_than_sign)
	/* Generate Secondary Device Attribute report, using 67 = ASCII 'C'
	   to indicate Cygwin (convention used by Rxvt, Urxvt, Screen, Mintty),
	   and cygwin version for terminal version. */
	__small_sprintf (buf, "\033[>67;%d%02d;0c",
			 CYGWIN_VERSION_DLL_MAJOR, CYGWIN_VERSION_DLL_MINOR);
      else
	strcpy (buf, "\033[?6c");
      /* The generated report needs to be injected for read-ahead into the
	 fhandler_console object associated with standard input.
	 So puts_readahead does not work.
	 Use a common console read-ahead buffer instead. */
      acquire_input_mutex (mutex_timeout);
      con.cons_rapoi = NULL;
      strcpy (con.cons_rabuf, buf);
      con.cons_rapoi = con.cons_rabuf;
      release_input_mutex ();
      /* Wake up read() or select() by sending a message
	 which has no effect */
      PostMessageW (GetConsoleWindow (), WM_SETFOCUS, 0, 0);
      break;
    case 'n':
      switch (con.args[0])
	{
	case 6:				/* u7 - Cursor position request */
	  cursor_get (&x, &y);
	  y -= con.b.srWindow.Top;
	  /* x -= con.b.srWindow.Left;		// not available yet */
	  __small_sprintf (buf, "\033[%d;%dR", y + 1, x + 1);
	  acquire_input_mutex (mutex_timeout);
	  con.cons_rapoi = NULL;
	  strcpy (con.cons_rabuf, buf);
	  con.cons_rapoi = con.cons_rabuf;
	  release_input_mutex ();
	  /* Wake up read() or select() by sending a message
	     which has no effect */
	  PostMessageW (GetConsoleWindow (), WM_SETFOCUS, 0, 0);
	  break;
	default:
	  goto bad_escape;
	}
      break;
    case 'r':				/* Set Scroll region */
      con.scroll_region.Top = con.args[0] ? con.args[0] - 1 : 0;
      con.scroll_region.Bottom = con.args[1] ? con.args[1] - 1 : -1;
      cursor_set (true, 0, 0);
      break;
    case 'g':				/* TAB set/clear */
      break;
    default:
bad_escape:
      break;
    }
}

#define NUM_REPLACEMENT_CHARS	3

static const wchar_t replacement_char[NUM_REPLACEMENT_CHARS] =
{
  0xfffd, /* REPLACEMENT CHARACTER */
  0x25a1, /* WHITE SQUARE */
  0x2592  /* MEDIUM SHADE */
};
/* nFont member is always 0 so we have to use the facename. */
static WCHAR cons_facename[LF_FACESIZE];
static WCHAR rp_char;
static NO_COPY HDC cdc;

static int CALLBACK
enum_proc (const LOGFONTW *lf, const TEXTMETRICW *tm,
	   DWORD FontType, LPARAM lParam)
{
  int *done = (int *) lParam;
  *done = 1;
  return 0;
}

static void
check_font (HANDLE hdl, DWORD owner)
{
  CONSOLE_FONT_INFOEX cfi;
  LOGFONTW lf;

  cfi.cbSize = sizeof cfi;
  acquire_attach_mutex (mutex_timeout);
  DWORD resume_pid = fhandler_console::attach_console (owner);
  BOOL r = GetCurrentConsoleFontEx (hdl, 0, &cfi);
  fhandler_console::detach_console (resume_pid, owner);
  release_attach_mutex ();
  if (!r)
    return;
  /* Switched font? */
  if (wcscmp (cons_facename, cfi.FaceName) == 0)
    return;
  if (!cdc && !(cdc = GetDC (GetConsoleWindow ())))
    return;
  /* Some FaceNames like DejaVu Sans Mono are sometimes returned with stray
     trailing chars.  Fix it. */
  lf.lfCharSet = DEFAULT_CHARSET;
  lf.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE;
  wchar_t *cp = wcpcpy (lf.lfFaceName, cfi.FaceName) - 1;
  int done = 0;
  do
    {
      EnumFontFamiliesExW (cdc, &lf, enum_proc, (LPARAM) &done, 0);
      if (!done)
	*cp-- = L'\0';
    }
  while (!done && cp >= lf.lfFaceName);
  /* What, really?  No recognizable font? */
  if (!done)
    {
      rp_char = L'?';
      return;
    }
  /* Yes.  Check for the best replacement char. */
  HFONT f = CreateFontW (0, 0, 0, 0,
			 cfi.FontWeight, FALSE, FALSE, FALSE,
			 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
			 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
			 FIXED_PITCH | FF_DONTCARE, lf.lfFaceName);
  if (!f)
    return;

  HFONT old_f = (HFONT) SelectObject(cdc, f);
  if (old_f)
    {
      WORD glyph_idx[NUM_REPLACEMENT_CHARS];

      if (GetGlyphIndicesW (cdc, replacement_char,
			    NUM_REPLACEMENT_CHARS, glyph_idx,
			    GGI_MARK_NONEXISTING_GLYPHS) != GDI_ERROR)
	{
	  int i;

	  for (i = 0; i < NUM_REPLACEMENT_CHARS; ++i)
	    if (glyph_idx[i] != 0xffff)
	      break;
	  if (i == NUM_REPLACEMENT_CHARS)
	    i = 0;
	  rp_char = replacement_char[i];
	  /* Note that we copy the original name returned by
	     GetCurrentConsoleFontEx, even if it was broken.
	     This allows an early return, rather than to store
	     the fixed name and then having to enum font families
	     all over again. */
	  wcscpy (cons_facename, cfi.FaceName);
	}
      SelectObject (cdc, old_f);
    }
  DeleteObject (f);
}

/* This gets called when we found an invalid input character.
   Print one of the above Unicode chars as replacement char. */
inline void
fhandler_console::write_replacement_char ()
{
  check_font (get_output_handle (), unit);

  DWORD done;
  acquire_attach_mutex (mutex_timeout);
  DWORD resume_pid = attach_console (con.owner);
  WriteConsoleW (get_output_handle (), &rp_char, 1, &done, 0);
  detach_console (resume_pid, con.owner);
  release_attach_mutex ();
}

const unsigned char *
fhandler_console::write_normal (const unsigned char *src,
				const unsigned char *end)
{
  /* Scan forward to see what a char which needs special treatment */
  DWORD done;
  DWORD buf_len;
  const unsigned char *found = src;
  int ret;
  mbstate_t ps;
  mbtowc_p f_mbtowc;

  /* The alternate charset is always 437, just as in the Linux console. */
  f_mbtowc = con.get_console_cp () ? __cp_mbtowc (437) : __MBTOWC;
  if (f_mbtowc == __ascii_mbtowc)
    f_mbtowc = __utf8_mbtowc;

  /* First check if we have cached lead bytes of a former try to write
     a truncated multibyte sequence.  If so, process it. */
  if (trunc_buf.len)
    {
      const unsigned char *nfound;
      int cp_len = MIN (end - src, 4 - trunc_buf.len);
      memcpy (trunc_buf.buf + trunc_buf.len, src, cp_len);
      memset (&ps, 0, sizeof ps);
      switch (ret = f_mbtowc (_REENT, NULL, (const char *) trunc_buf.buf,
			       trunc_buf.len + cp_len, &ps))
	{
	case -2:
	  /* Still truncated multibyte sequence?  Keep in trunc_buf. */
	  trunc_buf.len += cp_len;
	  return end;
	case -1:
	  /* Give up, print replacement chars for trunc_buf... */
	  for (int i = 0; i < trunc_buf.len; ++i)
	    write_replacement_char ();
	  /* ... mark trunc_buf as unused... */
	  trunc_buf.len = 0;
	  /* ... and proceed. */
	  nfound = NULL;
	  break;
	case 0:
	  nfound = trunc_buf.buf + 1;
	  break;
	default:
	  nfound = trunc_buf.buf + ret;
	  break;
	}
      /* Valid multibyte sequence?  Process. */
      if (nfound)
	{
	  buf_len = con.str_to_con (f_mbtowc, write_buf,
				    (const char *) trunc_buf.buf,
				    nfound - trunc_buf.buf);
	  if (!write_console (write_buf, buf_len, done))
	    {
	      debug_printf ("multibyte sequence write failed, handle %p",
			    get_output_handle ());
	      return 0;
	    }
	  found = src + (nfound - trunc_buf.buf - trunc_buf.len);
	  trunc_buf.len = 0;
	  return found;
	}
    }

  /* Loop over src buffer as long as we have just simple characters.  Stop
     as soon as we reach the conversion limit, or if we encounter a control
     character or a truncated or invalid mutibyte sequence. */
  /* If system has 24 bit color capability, just write all control
     sequences to console since xterm compatible mode is enabled. */
  memset (&ps, 0, sizeof ps);
  while (found < end
	 && found - src < CONVERT_LIMIT
	 && base_chars[*found] != IGN
	 && base_chars[*found] != ESC
	 && ((wincap.has_con_24bit_colors () && !con_is_legacy)
	     || base_chars[*found] == NOR))
    {
      switch (ret = f_mbtowc (_REENT, NULL, (const char *) found,
			       end - found, &ps))
	{
	case -2: /* Truncated multibyte sequence.  Store for next write. */
	  trunc_buf.len = end - found;
	  memcpy (trunc_buf.buf, found, trunc_buf.len);
	  goto do_print;
	case -1: /* Invalid multibyte sequence. Handled below. */
	  goto do_print;
	case 0:
	  found++;
	  break;
	default:
	  found += ret;
	  break;
	}
    }

do_print:

  /* Print all the base characters out */
  if (found != src)
    {
      DWORD len = found - src;
      buf_len = con.str_to_con (f_mbtowc, write_buf, (const char *) src, len);
      if (!buf_len)
	{
	  debug_printf ("conversion error, handle %p",
			get_output_handle ());
	  __seterrno ();
	  return 0;
	}

      if (con.insert_mode)
	{
	  int x, y;
	  cursor_get (&x, &y);
	  scroll_buffer (x, y, -1, y, x + buf_len, y);
	}

      if (!write_console (write_buf, buf_len, done))
	{
	  debug_printf ("write failed, handle %p", get_output_handle ());
	  return 0;
	}
      /* Stop here if we reached the conversion limit. */
      if (len >= CONVERT_LIMIT)
	return found + trunc_buf.len;
    }
  /* If there's still something in the src buffer, but it's not a truncated
     multibyte sequence, then we stumbled over a control character or an
     invalid multibyte sequence.  Print it. */
  if (found < end && trunc_buf.len == 0)
    {
      int x, y;
      switch (base_chars[*found])
	{
	case SO:	/* Shift Out: Invoke G1 character set (ISO 2022) */
	  con.iso_2022_G1 = true;
	  break;
	case SI:	/* Shift In: Invoke G0 character set (ISO 2022) */
	  con.iso_2022_G1 = false;
	  break;
	case BEL:
	  beep ();
	  break;
	case ESC:
	  con.state = gotesc;
	  wpbuf_put (*found);
	  break;
	case DWN:
	  cursor_get (&x, &y);
	  if (y >= srBottom)
	    {
	      if (y >= con.b.srWindow.Bottom && !con.scroll_region.Top)
		{
		  acquire_attach_mutex (mutex_timeout);
		  DWORD resume_pid = attach_console (con.owner);
		  WriteConsoleW (get_output_handle (), L"\n", 1, &done, 0);
		  detach_console (resume_pid, con.owner);
		  release_attach_mutex ();
		}
	      else
		{
		  scroll_buffer (0, srTop + 1, -1, srBottom, 0, srTop);
		  y--;
		}
	    }
	  cursor_set (false,
		      ((get_ttyp ()->ti.c_oflag & ONLCR) ? 0 : x), y + 1);
	  break;
	case BAK:
	  cursor_rel (-1, 0);
	  break;
	case IGN:
	 /* Up to release 3.1.3 we called cursor_rel (1, 0); to move the cursor
	    one step to the right.  However, that neither matches the terminfo
	    for the cygwin terminal, nor the one for the xterm terminal. */
	  break;
	case CR:
	  cursor_get (&x, &y);
	  cursor_set (false, 0, y);
	  break;
	case ERR:
	  /* Don't print chars marked as ERR chars, except for a ASCII CAN
	     sequence which is printed as singlebyte chars from the UTF
	     Basic Latin and Latin 1 Supplement plains. */
	  if (*found == 0x18)
	    {
	      write_replacement_char ();
	      if (found + 1 < end)
		{
		  ret = __utf8_mbtowc (_REENT, NULL, (const char *) found + 1,
				       end - found - 1, &ps);
		  if (ret != -1)
		    {
		      acquire_attach_mutex (mutex_timeout);
		      DWORD resume_pid = attach_console (con.owner);
		      while (ret-- > 0)
			{
			  WCHAR w = *(found + 1);
			  WriteConsoleW (get_output_handle (), &w, 1, &done, 0);
			  found++;
			}
		      detach_console (resume_pid, con.owner);
		      release_attach_mutex ();
		    }
		}
	    }
	  break;
	case TAB:
	  cursor_get (&x, &y);
	  cursor_set (false, 8 * (x / 8 + 1), y);
	  break;
	case NOR:
	  write_replacement_char ();
	  break;
	}
      found++;
    }
  return found + trunc_buf.len;
}

ssize_t
fhandler_console::write (const void *vsrc, size_t len)
{
  bg_check_types bg = bg_check (SIGTTOU);
  if (bg <= bg_eof)
    return (ssize_t) bg;

  if (get_ttyp ()->ti.c_lflag & FLUSHO)
    return len; /* Discard write data */

  if (get_ttyp ()->output_stopped && is_nonblocking ())
    {
      set_errno (EAGAIN);
      return -1;
    }
  while (get_ttyp ()->output_stopped)
    cygwait (10);

  push_process_state process_state (PID_TTYOU);

  acquire_output_mutex (mutex_timeout);

  /* Run and check for ansi sequences */
  unsigned const char *src = (unsigned char *) vsrc;
  unsigned const char *end = src + len;
  /* This might look a bit far fetched, but using the TLS path buffer allows
     to allocate a big buffer without using the stack too much.  Doing it here
     in write instead of in write_normal should be faster, too. */
  tmp_pathbuf tp;
  write_buf = tp.w_get ();

  debug_printf ("%p, %ld", vsrc, len);

  while (src < end)
    {
      paranoid_printf ("char %0c state is %d", *src, con.state);
      switch (con.state)
	{
	case normal:
	  src = write_normal (src, end);
	  if (!src) /* write_normal failed */
	    {
	      release_output_mutex ();
	      return -1;
	    }
	  break;
	case gotesc:
	  if (*src == '[')		/* CSI Control Sequence Introducer */
	    {
	      wpbuf_put (*src);
	      con.state = gotsquare;
	      memset (con.args, 0, sizeof con.args);
	      con.nargs = 0;
	      con.saw_question_mark = false;
	      con.saw_greater_than_sign = false;
	      con.saw_space = false;
	      con.saw_exclamation_mark = false;
	    }
	  else if (*src == '8')		/* DECRC Restore cursor position */
	    {
	      if (con.screen_alternated)
		{
		  /* For xterm mode only */
		  /* Just send the sequence */
		  wpbuf_put (*src);
		  wpbuf_send ();
		}
	      else if (con.savex >= 0 && con.savey >= 0)
		cursor_set (false, con.savex, con.savey);
	      con.state = normal;
	      wpbuf.empty();
	    }
	  else if (*src == '7')		/* DECSC Save cursor position */
	    {
	      if (con.screen_alternated)
		{
		  /* For xterm mode only */
		  /* Just send the sequence */
		  wpbuf_put (*src);
		  wpbuf_send ();
		}
	      else
		cursor_get (&con.savex, &con.savey);
	      con.state = normal;
	      wpbuf.empty();
	    }
	  else if (wincap.has_con_24bit_colors () && !con_is_legacy
		   && wincap.has_con_broken_il_dl () && *src == 'M')
	    { /* Reverse Index (scroll down) */
	      int x, y;
	      cursor_get (&x, &y);
	      if (y == srTop)
		{
		  if (y == con.b.srWindow.Top
		      && srBottom == con.b.srWindow.Bottom)
		    {
		      /* Erase scroll down area */
		      wchar_t buf[] = L"\033[32768;1H\033[J\033[32768;32768";
		      __small_swprintf (buf, L"\033[%d;1H\033[J\033[%d;%dH",
					srBottom - con.b.srWindow.Top + 1,
					y + 1 - con.b.srWindow.Top, x + 1);
		      acquire_attach_mutex (mutex_timeout);
		      DWORD resume_pid = attach_console (con.owner);
		      WriteConsoleW (get_output_handle (),
				     buf, wcslen (buf), 0, 0);
		      detach_console (resume_pid, con.owner);
		      release_attach_mutex ();
		    }
		  /* Substitute "CSI Ps T" */
		  wpbuf_put ('[');
		  wpbuf_put ('T');
		}
	      else
		wpbuf_put (*src);
	      wpbuf_send ();
	      con.state = normal;
	      wpbuf.empty();
	    }
	  else if (*src == ']')		/* OSC Operating System Command */
	    {
	      wpbuf_put (*src);
	      con.rarg = 0;
	      con.my_title_buf[0] = '\0';
	      con.state = gotrsquare;
	    }
	  else if (wincap.has_con_24bit_colors () && !con_is_legacy)
	    {
	      if (*src == 'c') /* RIS Full reset */
		{
		  con.scroll_region.Top = 0;
		  con.scroll_region.Bottom = -1;
		  con.savex = con.savey = -1;
		  con.cursor_key_app_mode = false;
		}
	      /* ESC sequences below (e.g. OSC, etc) are left to xterm
		 emulation in xterm compatible mode, therefore, are not
		 handled and just sent them. */
	      wpbuf_put (*src);
	      /* Just send the sequence */
	      wpbuf_send ();
	      con.state = normal;
	      wpbuf.empty();
	    }
	  else if (*src == '(')		/* Designate G0 character set */
	    {
	      wpbuf_put (*src);
	      con.state = gotparen;
	    }
	  else if (*src == ')')		/* Designate G1 character set */
	    {
	      wpbuf_put (*src);
	      con.state = gotrparen;
	    }
	  else if (*src == 'M')		/* Reverse Index (scroll down) */
	    {
	      con.fillin (get_output_handle ());
	      scroll_buffer_screen (0, 0, -1, -1, 0, 1);
	      con.state = normal;
	      wpbuf.empty();
	    }
	  else if (*src == 'c')		/* RIS Full Reset */
	    {
	      con.set_default_attr ();
	      con.vt100_graphics_mode_G0 = false;
	      con.vt100_graphics_mode_G1 = false;
	      con.iso_2022_G1 = false;
	      cursor_set (false, 0, 0);
	      clear_screen (cl_buf_beg, cl_buf_beg, cl_buf_end, cl_buf_end);
	      con.state = normal;
	      wpbuf.empty();
	    }
	  else if (*src == 'R')		/* ? */
	    {
	      con.state = normal;
	      wpbuf.empty();
	    }
	  else
	    {
	      con.state = normal;
	      wpbuf.empty();
	    }
	  src++;
	  break;
	case gotarg1:
	  if (isdigit (*src))
	    {
	      if (con.nargs < MAXARGS)
		con.args[con.nargs] = con.args[con.nargs] * 10 + *src - '0';
	      wpbuf_put (*src);
	      src++;
	    }
	  else if (*src == ';')
	    {
	      wpbuf_put (*src);
	      src++;
	      if (con.nargs < MAXARGS)
		con.nargs++;
	    }
	  else if (*src == ' ')
	    {
	      wpbuf_put (*src);
	      src++;
	      con.saw_space = true;
	      con.state = gotcommand;
	    }
	  else
	    con.state = gotcommand;
	  break;
	case gotcommand:
	  if (con.nargs < MAXARGS)
	    con.nargs++;
	  if (*src == '%' && con.nargs == 1 && con.args[0] == 0)
	    { /* Ignore intermediate byte in CSI sequence used by vim. */
	      src++;
	      break;
	    }
	  char_command (*src++);
	  con.state = normal;
	  wpbuf.empty();
	  break;
	case gotrsquare:
	  if (isdigit (*src))
	    con.rarg = con.rarg * 10 + (*src - '0');
	  else if (*src == ';')
	    {
	      if (con.rarg == 0 || con.rarg == 2)
		con.state = gettitle;
	      else if ((con.rarg >= 4 && con.rarg <= 6)
		       || (con.rarg >=10 && con.rarg <= 19)
		       || (con.rarg >=104 && con.rarg <= 106)
		       || (con.rarg >=110 && con.rarg <= 119))
		con.state = eatpalette;
	      else
		con.state = eattitle;
	    }
	  else if (*src == '\033')
	    con.state = endpalette;
	  else if (*src == '\007')
	    {
	      wpbuf_put (*src);
	      if (wincap.has_con_24bit_colors () && !con_is_legacy)
		wpbuf_send ();
	      wpbuf.empty ();
	      con.state = normal;
	      src++;
	      break;
	    }
	  wpbuf_put (*src);
	  src++;
	  break;
	case eattitle:
	case gettitle:
	  {
	    wpbuf_put (*src);
	    int n = strlen (con.my_title_buf);
	    if (*src < ' ')
	      {
		if (wincap.has_con_24bit_colors () && !con_is_legacy)
		  wpbuf_send ();
		else if (*src == '\007' && con.state == gettitle)
		  set_console_title (con.my_title_buf);
		con.state = normal;
		wpbuf.empty();
	      }
	    else if (n < TITLESIZE)
	      {
		con.my_title_buf[n++] = *src;
		con.my_title_buf[n] = '\0';
	      }
	    src++;
	    break;
	  }
	case eatpalette:
	  wpbuf_put (*src);
	  if (*src == '?')
	    con.saw_question_mark = true;
	  else if (*src == '\033')
	    con.state = endpalette;
	  else if (*src == '\a')
	    {
	      /* Send OSC Ps; Pt BEL other than OSC Ps; ? BEL */
	      if (wincap.has_con_24bit_colors () && !con_is_legacy
		  && !con.saw_question_mark)
		wpbuf_send ();
	      con.state = normal;
	      wpbuf.empty();
	    }
	  src++;
	  break;
	case endpalette:
	  wpbuf_put (*src);
	  if (*src == '\\')
	    {
	      /* Send OSC Ps; Pt ST other than OSC Ps; ? ST */
	      if (wincap.has_con_24bit_colors () && !con_is_legacy
		  && !con.saw_question_mark)
		wpbuf_send ();
	      con.state = normal;
	    }
	  else
	    /* Sequence error (abort) */
	    con.state = normal;
	  wpbuf.empty();
	  src++;
	  break;
	case gotsquare:
	  if (*src == ';')
	    {
	      con.state = gotarg1;
	      wpbuf_put (*src);
	      if (con.nargs < MAXARGS)
		con.nargs++;
	      src++;
	    }
	  else if (isalpha (*src))
	    con.state = gotcommand;
	  else if (*src != '@' && !isalpha (*src) && !isdigit (*src))
	    {
	      if (*src == '?')
		con.saw_question_mark = true;
	      else if (*src == '>')
		con.saw_greater_than_sign = true;
	      else if (*src == '!')
		con.saw_exclamation_mark = true;
	      wpbuf_put (*src);
	      /* ignore any extra chars between [ and first arg or command */
	      src++;
	    }
	  else
	    con.state = gotarg1;
	  break;
	case gotparen:	/* Designate G0 Character Set (ISO 2022) */
	  if (*src == '0')
	    con.vt100_graphics_mode_G0 = true;
	  else
	    con.vt100_graphics_mode_G0 = false;
	  con.state = normal;
	  wpbuf.empty();
	  src++;
	  break;
	case gotrparen:	/* Designate G1 Character Set (ISO 2022) */
	  if (*src == '0')
	    con.vt100_graphics_mode_G1 = true;
	  else
	    con.vt100_graphics_mode_G1 = false;
	  con.state = normal;
	  wpbuf.empty();
	  src++;
	  break;
	}
    }
  release_output_mutex ();

  syscall_printf ("%ld = fhandler_console::write(...)", len);

  return len;
}

void
fhandler_console::doecho (const void *str, DWORD len)
{
  bool stopped = get_ttyp ()->output_stopped;
  get_ttyp ()->output_stopped = false;
  write (str, len);
  get_ttyp ()->output_stopped = stopped;
}

static const struct {
  int vk;
  const char *val[4];
} keytable[] = {
	       /* NORMAL */    /* SHIFT */     /* CTRL */     /* CTRL-SHIFT */
  /* Unmodified and Alt-modified keypad keys comply with linux console
     SHIFT, CTRL, CTRL-SHIFT modifiers comply with xterm modifier usage */
  {VK_NUMPAD5,	{"\033[G",	"\033[1;2G",	"\033[1;5G",	"\033[1;6G"}},
  {VK_CLEAR,	{"\033[G",	"\033[1;2G",	"\033[1;5G",	"\033[1;6G"}},
  {VK_LEFT,	{"\033[D",	"\033[1;2D",	"\033[1;5D",	"\033[1;6D"}},
  {VK_RIGHT,	{"\033[C",	"\033[1;2C",	"\033[1;5C",	"\033[1;6C"}},
  {VK_UP,	{"\033[A",	"\033[1;2A",	"\033[1;5A",	"\033[1;6A"}},
  {VK_DOWN,	{"\033[B",	"\033[1;2B",	"\033[1;5B",	"\033[1;6B"}},
  {VK_PRIOR,	{"\033[5~",	"\033[5;2~",	"\033[5;5~",	"\033[5;6~"}},
  {VK_NEXT,	{"\033[6~",	"\033[6;2~",	"\033[6;5~",	"\033[6;6~"}},
  {VK_HOME,	{"\033[1~",	"\033[1;2~",	"\033[1;5~",	"\033[1;6~"}},
  {VK_END,	{"\033[4~",	"\033[4;2~",	"\033[4;5~",	"\033[4;6~"}},
  {VK_INSERT,	{"\033[2~",	"\033[2;2~",	"\033[2;5~",	"\033[2;6~"}},
  {VK_DELETE,	{"\033[3~",	"\033[3;2~",	"\033[3;5~",	"\033[3;6~"}},
  /* F1...F12, SHIFT-F1...SHIFT-F10 comply with linux console
     F6...F12, and all modified F-keys comply with rxvt (compatible extension) */
  {VK_F1,	{"\033[[A",	"\033[23~",	"\033[11^",	"\033[23^"}},
  {VK_F2,	{"\033[[B",	"\033[24~",	"\033[12^",	"\033[24^"}},
  {VK_F3,	{"\033[[C",	"\033[25~",	"\033[13^",	"\033[25^"}},
  {VK_F4,	{"\033[[D",	"\033[26~",	"\033[14^",	"\033[26^"}},
  {VK_F5,	{"\033[[E",	"\033[28~",	"\033[15^",	"\033[28^"}},
  {VK_F6,	{"\033[17~",	"\033[29~",	"\033[17^",	"\033[29^"}},
  {VK_F7,	{"\033[18~",	"\033[31~",	"\033[18^",	"\033[31^"}},
  {VK_F8,	{"\033[19~",	"\033[32~",	"\033[19^",	"\033[32^"}},
  {VK_F9,	{"\033[20~",	"\033[33~",	"\033[20^",	"\033[33^"}},
  {VK_F10,	{"\033[21~",	"\033[34~",	"\033[21^",	"\033[34^"}},
  {VK_F11,	{"\033[23~",	"\033[23$",	"\033[23^",	"\033[23@"}},
  {VK_F12,	{"\033[24~",	"\033[24$",	"\033[24^",	"\033[24@"}},
  /* CTRL-6 complies with Windows cmd console but should be fixed */
  {'6',		{NULL,		NULL,		"\036",		NULL}},
  /* Table end marker */
  {0}
};

const char *
fhandler_console::get_nonascii_key (INPUT_RECORD& input_rec, char *tmp)
{
#define NORMAL  0
#define SHIFT	1
#define CONTROL	2
/*#define CONTROLSHIFT	3*/

  int modifier_index = NORMAL;
  if (input_rec.Event.KeyEvent.dwControlKeyState & SHIFT_PRESSED)
    modifier_index = SHIFT;
  if (input_rec.Event.KeyEvent.dwControlKeyState & CTRL_PRESSED)
    modifier_index += CONTROL;

  for (int i = 0; keytable[i].vk; i++)
    if (input_rec.Event.KeyEvent.wVirtualKeyCode == keytable[i].vk)
      {
	if ((input_rec.Event.KeyEvent.dwControlKeyState & ALT_PRESSED)
	    && keytable[i].val[modifier_index] != NULL)
	  { /* Generic ESC prefixing if Alt is pressed */
	    tmp[0] = '\033';
	    strcpy (tmp + 1, keytable[i].val[modifier_index]);
	    return tmp;
	  }
	else
	  return keytable[i].val[modifier_index];
      }

  if (input_rec.Event.KeyEvent.uChar.AsciiChar)
    {
      tmp[0] = input_rec.Event.KeyEvent.uChar.AsciiChar;
      tmp[1] = '\0';
      return tmp;
    }
  return NULL;
}

int
fhandler_console::init (HANDLE h, DWORD a, mode_t bin, int64_t dummy)
{
  // this->fhandler_termios::init (h, mode, bin);
  /* Ensure both input and output console handles are open */
  int flags = 0;

  a &= GENERIC_READ | GENERIC_WRITE;
  if (a == GENERIC_READ)
    flags = O_RDONLY;
  if (a == GENERIC_WRITE)
    flags = O_WRONLY;
  if (a == (GENERIC_READ | GENERIC_WRITE))
    flags = O_RDWR;
  open_with_arch (flags | O_BINARY | (h ? 0 : O_NOCTTY));

  return !tcsetattr (0, &get_ttyp ()->ti);
}

int
fhandler_console::igncr_enabled ()
{
  return get_ttyp ()->ti.c_iflag & IGNCR;
}

void
fhandler_console::set_close_on_exec (bool val)
{
  close_on_exec (val);
}

void
fhandler_console::set_console_title (char *title)
{
  wchar_t buf[TITLESIZE + 1];
  sys_mbstowcs (buf, TITLESIZE + 1, title);
  lock_ttys here (15000);
  acquire_attach_mutex (mutex_timeout);
  DWORD resume_pid = attach_console (con.owner);
  SetConsoleTitleW (buf);
  detach_console (resume_pid, con.owner);
  release_attach_mutex ();
  debug_printf ("title '%W'", buf);
}

static bool NO_COPY gdb_inferior_noncygwin = false;

void
fhandler_console::set_console_mode_to_native ()
{
  /* Setting-up console mode for non-cygwin app started by GDB. This is
     called from hooked CreateProcess() and ContinueDebugEvent(). */
  cygheap_fdenum cfd (false);
  while (cfd.next () >= 0)
    if (cfd->get_major () == DEV_CONS_MAJOR)
      {
	fhandler_console *cons = (fhandler_console *) (fhandler_base *) cfd;
	if (cons->get_device () == cons->tc ()->getntty ())
	  {
	    termios *cons_ti = &cons->tc ()->ti;
	    set_input_mode (tty::native, cons_ti, cons->get_handle_set ());
	    set_output_mode (tty::native, cons_ti, cons->get_handle_set ());
	    set_disable_master_thread (true, cons);
	    break;
	  }
      }
}

#define DEF_HOOK(name) static __typeof__ (name) *name##_Orig
/* CreateProcess() is hooked for GDB etc. */
DEF_HOOK (CreateProcessA);
DEF_HOOK (CreateProcessW);
DEF_HOOK (ContinueDebugEvent);
DEF_HOOK (GetProcAddress); /* Hooked for ConEmu cygwin connector */

static BOOL
CreateProcessA_Hooked
     (LPCSTR n, LPSTR c, LPSECURITY_ATTRIBUTES pa, LPSECURITY_ATTRIBUTES ta,
      BOOL inh, DWORD f, LPVOID e, LPCSTR d,
      LPSTARTUPINFOA si, LPPROCESS_INFORMATION pi)
{
  if (f & (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS))
    mutex_timeout = 0; /* to avoid deadlock in GDB */
  gdb_inferior_noncygwin = !fhandler_termios::path_iscygexec_a (n, c);
  if (gdb_inferior_noncygwin)
    fhandler_console::set_console_mode_to_native ();
  init_console_handler (false);
  return CreateProcessA_Orig (n, c, pa, ta, inh, f, e, d, si, pi);
}

static BOOL
CreateProcessW_Hooked
     (LPCWSTR n, LPWSTR c, LPSECURITY_ATTRIBUTES pa, LPSECURITY_ATTRIBUTES ta,
      BOOL inh, DWORD f, LPVOID e, LPCWSTR d,
      LPSTARTUPINFOW si, LPPROCESS_INFORMATION pi)
{
  if (f & (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS))
    mutex_timeout = 0; /* to avoid deadlock in GDB */
  gdb_inferior_noncygwin = !fhandler_termios::path_iscygexec_w (n, c);
  if (gdb_inferior_noncygwin)
    fhandler_console::set_console_mode_to_native ();
  init_console_handler (false);
  return CreateProcessW_Orig (n, c, pa, ta, inh, f, e, d, si, pi);
}

static BOOL
ContinueDebugEvent_Hooked
     (DWORD p, DWORD t, DWORD s)
{
  if (gdb_inferior_noncygwin)
    fhandler_console::set_console_mode_to_native ();
  init_console_handler (false);
  return ContinueDebugEvent_Orig (p, t, s);
}

/* Hooked for ConEmu cygwin connector */
static FARPROC
GetProcAddress_Hooked (HMODULE h, LPCSTR n)
{
  if (strcmp(n, "RequestTermConnector") == 0)
    fhandler_console::set_disable_master_thread (true);
  return GetProcAddress_Orig (h, n);
}

void
fhandler_console::fixup_after_fork_exec (bool execing)
{
  set_unit ();
  setup_io_mutex ();
  wpbuf.init ();
  if (cygheap->ctty == this && !get_handle () && !get_output_handle ())
    {
      close_with_arch ();
      cygheap->ctty = NULL;
      return;
    }
  setup_pcon_hand_over ();

  if (!execing)
    return;

#define DO_HOOK(module, name) \
  if (!name##_Orig) \
    { \
      void *api = hook_api (module, #name, (void *) name##_Hooked); \
      name##_Orig = (__typeof__ (name) *) api; \
      /*if (api) system_printf (#name " hooked.");*/ \
    }
  /* CreateProcess() is hooked for GDB etc. */
  DO_HOOK (NULL, CreateProcessA);
  DO_HOOK (NULL, CreateProcessW);
  DO_HOOK (NULL, ContinueDebugEvent);
}

static void
hook_conemu_cygwin_connector()
{
  DO_HOOK (NULL, GetProcAddress);
}

/* Ugly workaround to create invisible console required since Windows 7.

   First try to just attach to any console which may have started this
   app.  If that works use this as our "invisible console".

   This will fail if not started from the command prompt.  In that case, start
   a dummy console application in a hidden state so that we can use its console
   as our invisible console.  This probably works everywhere but process
   creation is slow and to be avoided if possible so the window station method
   is vastly preferred.

   FIXME: This is not completely thread-safe since it creates two inheritable
   handles which are known only to this function.  If another thread starts
   a process the new process will inherit these handles.  However, since this
   function is currently only called at startup and during exec, it shouldn't
   be a big deal.  */
bool
fhandler_console::create_invisible_console_workaround (bool force)
{
  /* If force is set, avoid to reattach to existing console. */
  if (force || !AttachConsole (-1))
    {
      bool taskbar;
      DWORD err = force ? 0 : GetLastError ();
      path_conv helper ("/bin/cygwin-console-helper.exe");
      HANDLE hello = NULL;
      HANDLE goodbye = NULL;
      /* If err == ERROR_PROC_FOUND then this method won't work.  But that's
	 ok.  The window station method should work ok when AttachConsole doesn't
	 work.

	 If the helper doesn't exist or we can't create event handles then we
	 can't use this method. */
      if (err == ERROR_PROC_NOT_FOUND || !helper.exists ()
	  || !(hello = CreateEvent (&sec_none, true, false, NULL))
	  || !(goodbye = CreateEvent (&sec_none, true, false, NULL)))
	{
	  AllocConsole ();	/* This is just sanity check code.  We should
				   never actually hit here unless we're running
				   in an environment which lacks the helper
				   app. */
	  taskbar = true;
	}
      else
	{
	  STARTUPINFOW si = {};
	  PROCESS_INFORMATION pi;
	  size_t len = helper.get_wide_win32_path_len ();
	  WCHAR cmd[len + 1];
	  WCHAR args[len + 1 + (2 * sizeof (" 0xffffffffffffffff")) + 1];
	  WCHAR title[] = L"invisible cygwin console";

	  /* Create a new hidden process.  Use the two event handles as
	     argv[1] and argv[2]. */

	  helper.get_wide_win32_path (cmd);
	  __small_swprintf (args, L"\"%W\" %p %p", cmd, hello, goodbye);

	  si.cb = sizeof (si);
	  si.dwFlags = STARTF_USESHOWWINDOW;
	  si.wShowWindow = SW_HIDE;
	  si.lpTitle = title;

	  BOOL x = CreateProcessW (cmd, args,
				   &sec_none_nih, &sec_none_nih, true,
				   CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
	  if (x)
	    {
	      CloseHandle (pi.hProcess);	/* Don't need */
	      CloseHandle (pi.hThread);		/*  these.    */
	    }
	  taskbar = false;
	  /* Wait for subprocess to indicate that it is live.  This may not
	     actually be needed but it's hard to say since it is possible that
	     there will be no console for a brief time after the process
	     returns and there is no easy way to determine if/when this happens
	     in Windows.  So play it safe. */
	  if (!x || (WaitForSingleObject (hello, 10000) != WAIT_OBJECT_0)
	      || !AttachConsole (pi.dwProcessId))
	    AllocConsole ();	/* Oh well.  Watch the flash. */
	}

      if (!taskbar)
	/* Setting the owner of the console window to HWND_MESSAGE seems to
	   hide it from the taskbar.  Don't know if this method is faster than
	   calling ShowWindowAsync but it should guarantee no taskbar presence
	   for the hidden console. */
	SetParent (GetConsoleWindow (), HWND_MESSAGE);
      if (hello)
	CloseHandle (hello);
      if (goodbye)
	{
	  SetEvent (goodbye);	/* Tell helper process it's ok to exit. */
	  CloseHandle (goodbye);
	}
    }
  return invisible_console = true;
}

void
fhandler_console::free_console ()
{
  BOOL res = FreeConsole ();
  debug_printf ("freed console, res %d", res);
  init_console_handler (false);
}

bool
fhandler_console::need_invisible (bool force)
{
  BOOL b = false;
  /* If force is set, forcibly create a new invisible console
     even if a console device already exists. */
  if (exists () && !force)
    invisible_console = false;
  else
    {
      HWINSTA h;
      /* The intent here is to allocate an "invisible" console if we have no
	 controlling tty or to reuse the existing console if we already have
	 a tty.  So, first get the old window station.  If there is no controlling
	 terminal, create a new window station and then set it as the current
	 window station.  The subsequent AllocConsole will then be allocated
	 invisibly.  But, after doing that we have to restore any existing windows
	 station or, strangely, characters will not be displayed in any windows
	 drawn on the current screen.  We only do this if we have changed to
	 a new window station and if we had an existing windows station previously.
	 We also close the previously opened window station even though AllocConsole
	 is now "using" it.  This doesn't seem to cause any problems.

	 Things to watch out for if you make changes in this code:

	 - Flashing, black consoles showing up when you start, e.g., ssh in
	   an xterm.
	 - Non-displaying of characters in rxvt or xemacs if you start a
	   process using setsid: bash -lc "setsid rxvt".  */

      h = GetProcessWindowStation ();

      USEROBJECTFLAGS oi;
      DWORD len;
      if (!h
	  || !GetUserObjectInformationW (h, UOI_FLAGS, &oi, sizeof (oi), &len)
	  || !(oi.dwFlags & WSF_VISIBLE))
	{
	  b = true;
	  debug_printf ("window station is not visible");
	  AllocConsole ();
	  invisible_console = true;
	}
      b = create_invisible_console_workaround (force);
    }

  debug_printf ("invisible_console %d", invisible_console);
  return b;
}

DWORD
fhandler_console::__acquire_input_mutex (const char *fn, int ln, DWORD ms)
{
#ifdef DEBUGGING
  strace.prntf (_STRACE_TERMIOS, fn, "(%d): trying to get input_mutex", ln);
#endif
  DWORD res = WaitForSingleObject (input_mutex, ms);
  if (res != WAIT_OBJECT_0)
    strace.prntf (_STRACE_TERMIOS, fn,
		  "(%d): Failed to acquire input_mutex %08x",
		  ln, GetLastError ());
#ifdef DEBUGGING
  else
    strace.prntf (_STRACE_TERMIOS, fn, "(%d): got input_mutex", ln);
#endif
  return res;
}

void
fhandler_console::__release_input_mutex (const char *fn, int ln)
{
  ReleaseMutex (input_mutex);
#ifdef DEBUGGING
  strace.prntf (_STRACE_TERMIOS, fn, "(%d): release input_mutex", ln);
#endif
}

DWORD
fhandler_console::__acquire_output_mutex (const char *fn, int ln, DWORD ms)
{
#ifdef DEBUGGING
  strace.prntf (_STRACE_TERMIOS, fn, "(%d): trying to get output_mutex", ln);
#endif
  DWORD res = WaitForSingleObject (output_mutex, ms);
  if (res != WAIT_OBJECT_0)
    strace.prntf (_STRACE_TERMIOS, fn,
		  "(%d): Failed to acquire output_mutex %08x",
		  ln, GetLastError ());
#ifdef DEBUGGING
  else
    strace.prntf (_STRACE_TERMIOS, fn, "(%d): got output_mutex", ln);
#endif
  return res;
}

void
fhandler_console::__release_output_mutex (const char *fn, int ln)
{
  ReleaseMutex (output_mutex);
#ifdef DEBUGGING
  strace.prntf (_STRACE_TERMIOS, fn, "(%d): release output_mutex", ln);
#endif
}

void
fhandler_console::get_duplicated_handle_set (handle_set_t *p)
{
  DuplicateHandle (GetCurrentProcess (), get_handle (),
		   GetCurrentProcess (), &p->input_handle,
		   0, FALSE, DUPLICATE_SAME_ACCESS);
  DuplicateHandle (GetCurrentProcess (), get_output_handle (),
		   GetCurrentProcess (), &p->output_handle,
		   0, FALSE, DUPLICATE_SAME_ACCESS);
  DuplicateHandle (GetCurrentProcess (), input_mutex,
		   GetCurrentProcess (), &p->input_mutex,
		   0, FALSE, DUPLICATE_SAME_ACCESS);
  DuplicateHandle (GetCurrentProcess (), output_mutex,
		   GetCurrentProcess (), &p->output_mutex,
		   0, FALSE, DUPLICATE_SAME_ACCESS);
  p->unit = unit;
}

/* The function close_handle_set() should be static so that they can
   be called even after the fhandler_console instance is deleted. */
void
fhandler_console::close_handle_set (handle_set_t *p)
{
  CloseHandle (p->input_handle);
  p->input_handle = NULL;
  CloseHandle (p->output_handle);
  p->output_handle = NULL;
  CloseHandle (p->input_mutex);
  p->input_mutex = NULL;
  CloseHandle (p->output_mutex);
  p->output_mutex = NULL;
}

bool
fhandler_console::need_console_handler ()
{
  return con.disable_master_thread || con.master_thread_suspended;
}

void
fhandler_console::set_disable_master_thread (bool x, fhandler_console *cons)
{
  if (cons == NULL)
    {
      if (cygheap->ctty && cygheap->ctty->get_major () == DEV_CONS_MAJOR)
	cons = (fhandler_console *) cygheap->ctty;
      else
	return;
    }
  const _minor_t unit = cons->get_minor ();
  cons->acquire_input_mutex (mutex_timeout);
  con.disable_master_thread = x;
  cons->release_input_mutex ();
}

int
fhandler_console::fstat (struct stat *st)
{
  /* When stat() is called, fh_alloc() in dtable.cc omits to initialize
     the console instance. Due to this, get_ttyp() returns NULL here.
     So, calling set_unit() is necessary to access getsid(). */
  if (!get_ttyp ())
    {
      dev_referred_via = get_device ();
      set_unit ();
    }

  fhandler_termios::fstat (st);
  st->st_mode = S_IFCHR | S_IRUSR | S_IWUSR;
  pinfo p (get_ttyp ()->getsid ());
  if (p)
    {
      st->st_uid = p->uid;
      st->st_gid = p->gid;
    }
  return 0;
}

tty::cons_mode
fhandler_console::cons_mode_on_close (handle_set_t *p)
{
  int unit = p->unit;
  if (myself->ppid != 1) /* Execed from normal cygwin process. */
    return tty::cygwin;

  if (!process_alive (con.owner)) /* The Master process already died. */
    return tty::restore;
  if (con.owner == GetCurrentProcessId ()) /* Master process */
    return tty::restore;

  PROCESS_BASIC_INFORMATION pbi;
  NTSTATUS status =
    NtQueryInformationProcess (GetCurrentProcess (), ProcessBasicInformation,
			       &pbi, sizeof (pbi), NULL);
  if (NT_SUCCESS (status) && cygwin_pid (con.owner)
      && !process_alive ((DWORD) pbi.InheritedFromUniqueProcessId))
    /* Execed from normal cygwin process and the parent has been exited. */
    return tty::cygwin;

  return tty::restore; /* otherwise, restore */
}
