diff options
Diffstat (limited to 'winsup/cygwin/fhandler/console.cc')
-rw-r--r-- | winsup/cygwin/fhandler/console.cc | 4333 |
1 files changed, 4333 insertions, 0 deletions
diff --git a/winsup/cygwin/fhandler/console.cc b/winsup/cygwin/fhandler/console.cc new file mode 100644 index 000000000..a4a367005 --- /dev/null +++ b/winsup/cygwin/fhandler/console.cc @@ -0,0 +1,4333 @@ +/* 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" + +/* 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->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 && con.is_legacy) + +#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; + +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; + +/* 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; + HANDLE output_handle; +public: + void init (HANDLE &handle) + { + output_handle = handle; + empty (); + } + inline void put (char x) + { + if (ixput == WPBUF_LEN) + send (); + buf[ixput++] = x; + } + inline void empty () { ixput = 0u; } + inline void send () + { + 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); + WriteConsoleW (output_handle, bufw, len, NULL, 0); + release_attach_mutex (); + } +} wpbuf; + +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& create) +{ + wchar_t namebuf[(sizeof "XXXXXXXXXXXXXXXXXX-consNNNNNNNNNN")]; + __small_swprintf (namebuf, L"%S-cons%p", &cygheap->installation_key, hw); + + shared_locations m = create ? SH_SHARED_CONSOLE : SH_JUSTOPEN; + console_state *res = (console_state *) + open_shared (namebuf, 0, h, sizeof (*shared_console_info), &m); + create = m != SH_JUSTOPEN; + return res; +} + +class console_unit +{ + int n; + unsigned long bitmask; + HWND me; + +public: + operator int () const {return n;} + console_unit (HWND); + friend BOOL CALLBACK enum_windows (HWND, LPARAM); +}; + +BOOL CALLBACK +enum_windows (HWND hw, LPARAM lp) +{ + console_unit *this1 = (console_unit *) lp; + if (hw == this1->me) + return TRUE; + HANDLE h = NULL; + fhandler_console::console_state *cs; + if ((cs = fhandler_console::open_shared_console (hw, h))) + { + this1->bitmask ^= 1 << cs->tty_min_state.getntty (); + UnmapViewOfFile ((void *) cs); + CloseHandle (h); + } + return TRUE; +} + +console_unit::console_unit (HWND me0): + bitmask (0xffffffff), me (me0) +{ + EnumWindows (enum_windows, (LPARAM) this); + n = (_minor_t) ffs (bitmask) - 1; + if (n < 0) + api_fatal ("console device allocation failure - too many consoles in use, max consoles is 32"); +} + +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; + 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); + 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; + if (ak->bKeyDown != bk->bKeyDown + || ak->uChar.UnicodeChar != bk->uChar.UnicodeChar + || ak->wRepeatCount != bk->wRepeatCount) + 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 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; + } + + DWORD inrec_size1 = + wincap.cons_need_small_input_record_buf () ? INREC_SIZE : inrec_size; + + struct m + { + inline static size_t bytes (size_t n) + { + return sizeof (INPUT_RECORD) * n; + } + }; + termios &ti = ttyp->ti; + while (con.owner == myself->pid) + { + 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; + if (!wincap.cons_need_small_input_record_buf ()) + inrec_size1 = inrec_size; + } + } + + WaitForSingleObject (p->input_mutex, mutex_timeout); + total_read = 0; + 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_size1), + &len); + total_read += len; + } + release_attach_mutex (); + break; + case WAIT_TIMEOUT: + con.num_processed = 0; + case WAIT_SIGNALED: + case WAIT_CANCELED: + break; + default: /* Error */ + 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); + 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_size1), &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; + if (!wincap.cons_need_small_input_record_buf ()) + inrec_size1 = inrec_size; + } + } + + /* Check if writeback was successfull. */ + acquire_attach_mutex (mutex_timeout); + PeekConsoleInputW (p->input_handle, input_tmp, inrec_size1, &n); + release_attach_mutex (); + if (n < min (total_read, inrec_size1)) + break; /* Someone has read input without acquiring + input_mutex. ConEmu cygwin-connector? */ + if (inrec_eq (input_rec, input_tmp, + min (total_read, inrec_size1))) + 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_size1), &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_size1), + &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: + ReleaseMutex (p->input_mutex); + cygwait (40); + } + free (input_rec); + free (input_tmp); +} + +bool +fhandler_console::set_unit () +{ + bool created; + fh_devices devset; + lock_ttys here; + HWND me; + fh_devices this_unit = dev (); + bool generic_console = this_unit == FH_CONIN || this_unit == FH_CONOUT; + if (shared_console_info) + { + fh_devices shared_unit = + (fh_devices) shared_console_info->tty_min_state.getntty (); + devset = (shared_unit == this_unit || this_unit == FH_CONSOLE + || generic_console + || this_unit == FH_TTY) ? + shared_unit : FH_ERROR; + created = false; + } + else if ((!generic_console && + (myself->ctty != -1 && !iscons_dev (myself->ctty))) + || !(me = GetConsoleWindow ())) + devset = FH_ERROR; + else + { + created = true; + shared_console_info = + open_shared_console (me, cygheap->console_h, created); + ProtectHandleINH (cygheap->console_h); + if (created) + shared_console_info-> + tty_min_state.setntty (DEV_CONS_MAJOR, console_unit (me)); + devset = (fh_devices) shared_console_info->tty_min_state.getntty (); + if (created) + con.owner = myself->pid; + } + if (!created && shared_console_info) + { + while (con.owner > MAX_PID) + Sleep (1); + pinfo p (con.owner); + if (!p) + con.owner = myself->pid; + } + + dev ().parse (devset); + if (devset != FH_ERROR) + pc.file_attributes (FILE_ATTRIBUTE_NORMAL); + else + { + 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->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; + } +} + +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; +} + +/* 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) +{ + DWORD oflags; + WaitForSingleObject (p->input_mutex, mutex_timeout); + acquire_attach_mutex (mutex_timeout); + GetConsoleMode (p->input_handle, &oflags); + DWORD flags = oflags + & (ENABLE_EXTENDED_FLAGS | ENABLE_INSERT_MODE | ENABLE_QUICK_EDIT_MODE); + switch (m) + { + case tty::restore: + flags |= ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT; + break; + case tty::cygwin: + flags |= ENABLE_WINDOW_INPUT; + if (con.master_thread_suspended) + 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; + } + 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); + } + 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) +{ + 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: + 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; + } + acquire_attach_mutex (mutex_timeout); + SetConsoleMode (p->output_handle, flags); + 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, use tty::restore + for background process instead. */ + tty::cons_mode conmode = + (get_ttyp ()->getpgid ()== myself->pgid) ? tty::native : tty::restore; + set_input_mode (conmode, &tc ()->ti, get_handle_set ()); + set_output_mode (conmode, &tc ()->ti, get_handle_set ()); + set_disable_master_thread (true, this); +} + +void +fhandler_console::cleanup_for_non_cygwin_app (handle_set_t *p) +{ + termios dummy = {0, }; + termios *ti = + shared_console_info ? &(shared_console_info->tty_min_state.ti) : &dummy; + /* Cleaning-up console mode for non-cygwin app. */ + /* conmode can be tty::restore when non-cygwin app is + exec'ed from login shell. */ + tty::cons_mode conmode = + (con.owner == myself->pid) ? tty::restore : tty::cygwin; + set_output_mode (conmode, ti, p); + set_input_mode (conmode, ti, p); + set_disable_master_thread (con.owner == myself->pid); +} + +/* 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; + if (iscons_dev (n)) + return fhandler_console::shared_console_info ? + &fhandler_console::shared_console_info->tty_min_state : NULL; + else if (istty_slave_dev (n)) + return &ttys[device::minor (n)]; + 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 (); +} + +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); + SetConsoleCursorPosition (get_output_handle (), con.b.dwCursorPosition); + 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) +{ + /* Re-setting ENABLE_VIRTUAL_TERMINAL_PROCESSING + fixes the tab position. */ + DWORD mode; + acquire_attach_mutex (mutex_timeout); + GetConsoleMode (h, &mode); + SetConsoleMode (h, mode & ~ENABLE_VIRTUAL_TERMINAL_PROCESSING); + SetConsoleMode (h, mode); + 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 ()); + get_ttyp ()->kill_pgrp (SIGWINCH); + 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); + BOOL r = GetConsoleScreenBufferInfo (get_output_handle (), &now); + 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) + { + set_input_mode (tty::cygwin, &tc ()->ti, get_handle_set ()); + set_disable_master_thread (false, this); + } + if (sig == SIGTTOU) + 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); + + int copied_chars = 0; + + DWORD timeout = is_nonblocking () ? 0 : INFINITE; + + 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: + 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); + BOOL res = GetConsoleMode (get_handle (), &mode); + 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) + { + buf[copied_chars++] = *con.cons_rapoi++; + buflen --; + } + + copied_chars += + get_readahead_into_buffer (buf + copied_chars, buflen); + + if (!con_ra.ralen) + input_ready = false; + release_input_mutex (); + +#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) + 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); + BOOL r = + PeekConsoleInputW (get_handle (), input_rec, INREC_SIZE, &total_read); + 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); + ReadConsoleInputW (get_handle (), input_rec, discard_len, &discarded); + release_attach_mutex (); + con.num_processed -= min (con.num_processed, discarded); + } + return stat; +} + +bool +dev_console::fillin (HANDLE h) +{ + acquire_attach_mutex (mutex_timeout); + bool ret = GetConsoleScreenBufferInfo (h, &b); + 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); + ScrollConsoleScreenBufferW (h, &sr1, &sr2, dest, &fill); + 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 != -2) + 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); + + /* Open the input handle as handle_ */ + h = CreateFileW (L"CONIN$", GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, &sec_none, + OPEN_EXISTING, 0, 0); + + if (h == INVALID_HANDLE_VALUE) + { + __seterrno (); + return 0; + } + set_handle (h); + handle_set.input_handle = h; + + h = CreateFileW (L"CONOUT$", GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, &sec_none, + OPEN_EXISTING, 0, 0); + + if (h == INVALID_HANDLE_VALUE) + { + __seterrno (); + return 0; + } + set_output_handle (h); + handle_set.output_handle = h; + wpbuf.init (get_output_handle ()); + + setup_io_mutex (); + handle_set.input_mutex = input_mutex; + handle_set.output_mutex = output_mutex; + + 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 (myself->pid == con.owner && wincap.has_con_24bit_colors ()) + { + bool is_legacy = false; + DWORD dwMode; + /* Check xterm compatible mode in output */ + acquire_attach_mutex (mutex_timeout); + 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); + release_attach_mutex (); + con.is_legacy = is_legacy; + extern int sawTERM; + if (con_is_legacy && !sawTERM) + setenv ("TERM", "cygwin", 1); + } + + debug_printf ("opened conin$ %p, conout$ %p", get_handle (), + get_output_handle ()); + + if (myself->pid == 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); + new cygthread (::cons_master_thread, this, "consm"); + WaitForSingleObject (thread_sync_event, INFINITE); + CloseHandle (thread_sync_event); + } + return 1; +} + +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); + 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_input_mode (tty::cygwin, &get_ttyp ()->ti, &handle_set); + set_disable_master_thread (false, this); + } + 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 () +{ + debug_printf ("closing: %p, %p", get_handle (), get_output_handle ()); + + acquire_output_mutex (mutex_timeout); + + if (shared_console_info) + { + /* Restore console mode if this is the last closure. */ + OBJECT_BASIC_INFORMATION obi; + NTSTATUS status; + status = NtQueryObject (get_handle (), ObjectBasicInformation, + &obi, sizeof obi, NULL); + if ((NT_SUCCESS (status) && obi.HandleCount == 1) + || myself->pid == con.owner) + { + /* Cleaning-up console mode for cygwin apps. */ + 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); + } + } + + release_output_mutex (); + + if (shared_console_info && con.owner == myself->pid + && master_thread_started) + { + char name[MAX_PATH]; + shared_name (name, CONS_THREAD_SYNC, get_minor ()); + thread_sync_event = OpenEvent (MAXIMUM_ALLOWED, FALSE, name); + con.owner = MAX_PID + 1; + WaitForSingleObject (thread_sync_event, INFINITE); + CloseHandle (thread_sync_event); + con.owner = 0; + } + + CloseHandle (input_mutex); + input_mutex = NULL; + CloseHandle (output_mutex); + output_mutex = NULL; + + CloseHandle (get_handle ()); + CloseHandle (get_output_handle ()); + + if (con_ra.rabuf) + free (con_ra.rabuf); + + if (!have_execed && !invisible_console) + free_console (); + 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); + BOOL r = PeekConsoleInputW (get_handle (), inp, INREC_SIZE, &n); + 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); + BOOL r = FlushConsoleInputBuffer (get_handle ()); + 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; + 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 unit) : + fhandler_termios (), input_ready (false), thread_sync_event (NULL), + input_mutex (NULL), output_mutex (NULL) +{ + if (unit > 0) + dev ().parse (unit); + setup (); + trunc_buf.len = 0; + _tc = &(shared_console_info->tty_min_state); +} + +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); + SetConsoleTextAttribute (h, current_win32_attr); + 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); + 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); + 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); + FillConsoleOutputCharacterW (h, L' ', num, tlc, &done); + FillConsoleOutputAttribute (h, current_win32_attr, num, tlc, &done); + 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); + SetConsoleCursorPosition (get_output_handle (), pos); + 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); + BOOL r = WriteConsoleW (get_output_handle (), buf, nbytes, &done, 0); + 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; + + acquire_attach_mutex (mutex_timeout); + BOOL success = ReadConsoleOutputW (h, buf, bufsiz, coord, ®ion); + release_attach_mutex (); + 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; + if (!ReadConsoleOutputWrapper (h, save_buf, save_bufsize, now)) + debug_printf ("ReadConsoleOutputWrapper(h, ...) failed during save, %E"); + + /* Position at top of buffer */ + COORD cob = {}; + acquire_attach_mutex (mutex_timeout); + if (!SetConsoleCursorPosition (h, cob)) + debug_printf ("SetConsoleCursorInfo(%p, ...) failed during save, %E", h); + 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); + BOOL res = WriteConsoleOutputW (h, save_buf, save_bufsize, cob, &now); + 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); + 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); + 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); + for (int i = 0; i < con.args[0]; i++) + WriteConsoleW (get_output_handle (), &last_char, 1, 0, 0); + 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); + WriteConsoleW (get_output_handle (), L"\033[2K", 4, 0, 0); + release_attach_mutex (); + break; + } + acquire_attach_mutex (mutex_timeout); + 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); + 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); + WriteConsoleW (get_output_handle (), L"\033[2K", 4, 0, 0); + 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); + 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); + 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); + 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); + 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'); + } + /* Call fix_tab_position() if screen has been alternated. */ + if (need_fix_tab_position) + fix_tab_position (get_output_handle ()); + } + 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); + 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; + } + 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); + 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); + 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) +{ + CONSOLE_FONT_INFOEX cfi; + LOGFONTW lf; + + cfi.cbSize = sizeof cfi; + acquire_attach_mutex (mutex_timeout); + BOOL r = GetCurrentConsoleFontEx (hdl, 0, &cfi); + 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 ()); + + DWORD done; + acquire_attach_mutex (mutex_timeout); + WriteConsoleW (get_output_handle (), &rp_char, 1, &done, 0); + 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); + WriteConsoleW (get_output_handle (), L"\n", 1, &done, 0); + 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); + while (ret-- > 0) + { + WCHAR w = *(found + 1); + WriteConsoleW (get_output_handle (), &w, 1, &done, 0); + found++; + } + 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); + WriteConsoleW (get_output_handle (), + buf, wcslen (buf), 0, 0); + 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++; + 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) +{ + // 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 +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); + SetConsoleTitleW (buf); + 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 (get_output_handle ()); + + 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); +} + +/* 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 (con.disable_master_thread == x) + return; + if (cons == NULL) + { + if (cygheap->ctty && cygheap->ctty->get_major () == DEV_CONS_MAJOR) + cons = (fhandler_console *) cygheap->ctty; + else + return; + } + cons->acquire_input_mutex (mutex_timeout); + con.disable_master_thread = x; + cons->release_input_mutex (); +} |