summaryrefslogtreecommitdiffstats
path: root/src/os_win32.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/os_win32.c')
-rw-r--r--src/os_win32.c9010
1 files changed, 9010 insertions, 0 deletions
diff --git a/src/os_win32.c b/src/os_win32.c
new file mode 100644
index 0000000..a151b19
--- /dev/null
+++ b/src/os_win32.c
@@ -0,0 +1,9010 @@
+/* vi:set ts=8 sts=4 sw=4 noet:
+ *
+ * VIM - Vi IMproved by Bram Moolenaar
+ *
+ * Do ":help uganda" in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+/*
+ * os_win32.c
+ *
+ * Used for both the console version and the Win32 GUI. A lot of code is for
+ * the console version only, so there is a lot of "#ifndef FEAT_GUI_MSWIN".
+ *
+ * Win32 (Windows NT and Windows 95) system-dependent routines.
+ * Portions lifted from the Win32 SDK samples, the MSDOS-dependent code,
+ * NetHack 3.1.3, GNU Emacs 19.30, and Vile 5.5.
+ *
+ * George V. Reilly <george@reilly.org> wrote most of this.
+ * Roger Knobbe <rogerk@wonderware.com> did the initial port of Vim 3.0.
+ */
+
+#include "vim.h"
+
+#ifdef FEAT_MZSCHEME
+# include "if_mzsch.h"
+#endif
+
+#include <sys/types.h>
+#include <signal.h>
+#include <limits.h>
+
+// cproto fails on missing include files
+#ifndef PROTO
+# include <process.h>
+# include <winternl.h>
+#endif
+
+#undef chdir
+#ifdef __GNUC__
+# ifndef __MINGW32__
+# include <dirent.h>
+# endif
+#else
+# include <direct.h>
+#endif
+
+#ifndef PROTO
+# if !defined(FEAT_GUI_MSWIN)
+# include <shellapi.h>
+# endif
+#endif
+
+#ifdef FEAT_JOB_CHANNEL
+# include <tlhelp32.h>
+#endif
+
+#ifdef __MINGW32__
+# ifndef FROM_LEFT_1ST_BUTTON_PRESSED
+# define FROM_LEFT_1ST_BUTTON_PRESSED 0x0001
+# endif
+# ifndef RIGHTMOST_BUTTON_PRESSED
+# define RIGHTMOST_BUTTON_PRESSED 0x0002
+# endif
+# ifndef FROM_LEFT_2ND_BUTTON_PRESSED
+# define FROM_LEFT_2ND_BUTTON_PRESSED 0x0004
+# endif
+# ifndef FROM_LEFT_3RD_BUTTON_PRESSED
+# define FROM_LEFT_3RD_BUTTON_PRESSED 0x0008
+# endif
+# ifndef FROM_LEFT_4TH_BUTTON_PRESSED
+# define FROM_LEFT_4TH_BUTTON_PRESSED 0x0010
+# endif
+
+/*
+ * EventFlags
+ */
+# ifndef MOUSE_MOVED
+# define MOUSE_MOVED 0x0001
+# endif
+# ifndef DOUBLE_CLICK
+# define DOUBLE_CLICK 0x0002
+# endif
+#endif
+
+// Record all output and all keyboard & mouse input
+// #define MCH_WRITE_DUMP
+
+#ifdef MCH_WRITE_DUMP
+FILE* fdDump = NULL;
+#endif
+
+/*
+ * When generating prototypes for Win32 on Unix, these lines make the syntax
+ * errors disappear. They do not need to be correct.
+ */
+#ifdef PROTO
+# define WINAPI
+typedef char * LPCSTR;
+typedef char * LPWSTR;
+typedef int ACCESS_MASK;
+typedef int BOOL;
+typedef int BOOLEAN;
+typedef int CALLBACK;
+typedef int COLORREF;
+typedef int CONSOLE_CURSOR_INFO;
+typedef int COORD;
+typedef int DWORD;
+typedef int HANDLE;
+typedef int LPHANDLE;
+typedef int HDC;
+typedef int HFONT;
+typedef int HICON;
+typedef int HINSTANCE;
+typedef int HWND;
+typedef int INPUT_RECORD;
+typedef int INT;
+typedef int KEY_EVENT_RECORD;
+typedef int LOGFONT;
+typedef int LPBOOL;
+typedef int LPCTSTR;
+typedef int LPDWORD;
+typedef int LPSTR;
+typedef int LPTSTR;
+typedef int LPVOID;
+typedef int MOUSE_EVENT_RECORD;
+typedef int PACL;
+typedef int PDWORD;
+typedef int PHANDLE;
+typedef int PRINTDLG;
+typedef int PSECURITY_DESCRIPTOR;
+typedef int PSID;
+typedef int SECURITY_INFORMATION;
+typedef int SHORT;
+typedef int SMALL_RECT;
+typedef int TEXTMETRIC;
+typedef int TOKEN_INFORMATION_CLASS;
+typedef int TRUSTEE;
+typedef int WORD;
+typedef int WCHAR;
+typedef void VOID;
+typedef int BY_HANDLE_FILE_INFORMATION;
+typedef int SE_OBJECT_TYPE;
+typedef int PSNSECINFO;
+typedef int PSNSECINFOW;
+typedef int STARTUPINFO;
+typedef int PROCESS_INFORMATION;
+typedef int LPSECURITY_ATTRIBUTES;
+# define __stdcall // empty
+#endif
+
+#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL)
+// Win32 Console handles for input and output
+static HANDLE g_hConIn = INVALID_HANDLE_VALUE;
+static HANDLE g_hConOut = INVALID_HANDLE_VALUE;
+
+// Win32 Screen buffer,coordinate,console I/O information
+static SMALL_RECT g_srScrollRegion;
+static COORD g_coord; // 0-based, but external coords are 1-based
+
+// The attribute of the screen when the editor was started
+static WORD g_attrDefault = 7; // lightgray text on black background
+static WORD g_attrCurrent;
+
+static int g_fCBrkPressed = FALSE; // set by ctrl-break interrupt
+static int g_fCtrlCPressed = FALSE; // set when ctrl-C or ctrl-break detected
+static int g_fForceExit = FALSE; // set when forcefully exiting
+
+static void scroll(unsigned cLines);
+static void set_scroll_region(unsigned left, unsigned top,
+ unsigned right, unsigned bottom);
+static void set_scroll_region_tb(unsigned top, unsigned bottom);
+static void set_scroll_region_lr(unsigned left, unsigned right);
+static void insert_lines(unsigned cLines);
+static void delete_lines(unsigned cLines);
+static void gotoxy(unsigned x, unsigned y);
+static void standout(void);
+static int s_cursor_visible = TRUE;
+static int did_create_conin = FALSE;
+// The 'input_record_buffer' is an internal dynamic fifo queue of MS-Windows
+// console INPUT_RECORD events that are normally read from the console input
+// buffer. This provides an injection point for testing the low-level handling
+// of INPUT_RECORDs.
+typedef struct input_record_buffer_node_S
+{
+ INPUT_RECORD ir;
+ struct input_record_buffer_node_S *next;
+} input_record_buffer_node_T;
+typedef struct input_record_buffer_S
+{
+ input_record_buffer_node_T *head;
+ input_record_buffer_node_T *tail;
+ int length;
+} input_record_buffer_T;
+static input_record_buffer_T input_record_buffer;
+static int read_input_record_buffer(INPUT_RECORD* irEvents, int nMaxLength);
+static int write_input_record_buffer(INPUT_RECORD* irEvents, int nLength);
+#endif
+#ifdef FEAT_GUI_MSWIN
+static int s_dont_use_vimrun = TRUE;
+static int need_vimrun_warning = FALSE;
+static char *vimrun_path = "vimrun ";
+#endif
+
+static int win32_getattrs(char_u *name);
+static int win32_setattrs(char_u *name, int attrs);
+static int win32_set_archive(char_u *name);
+
+static int conpty_working = 0;
+static int conpty_type = 0;
+static int conpty_stable = 0;
+static int conpty_fix_type = 0;
+static void vtp_flag_init();
+
+#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL)
+static int vtp_working = 0;
+static void vtp_init();
+static void vtp_exit();
+static void vtp_sgr_bulk(int arg);
+static void vtp_sgr_bulks(int argc, int *argv);
+
+static int wt_working = 0;
+static void wt_init(void);
+
+static int g_color_index_bg = 0;
+static int g_color_index_fg = 7;
+
+# ifdef FEAT_TERMGUICOLORS
+static guicolor_T save_console_bg_rgb;
+static guicolor_T save_console_fg_rgb;
+static guicolor_T store_console_bg_rgb;
+static guicolor_T store_console_fg_rgb;
+static int default_console_color_bg = 0x000000; // black
+static int default_console_color_fg = 0xc0c0c0; // white
+# define USE_VTP (vtp_working && is_term_win32() \
+ && (p_tgc || t_colors >= 256))
+# define USE_WT (wt_working)
+# else
+# define USE_VTP 0
+# define USE_WT 0
+# endif
+
+static void set_console_color_rgb(void);
+static void reset_console_color_rgb(void);
+static void restore_console_color_rgb(void);
+#endif // !FEAT_GUI_MSWIN || VIMDLL
+
+// This flag is newly created from Windows 10
+#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
+# define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
+#endif
+
+#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL)
+static int suppress_winsize = 1; // don't fiddle with console
+#endif
+
+static WCHAR *exe_pathw = NULL;
+
+static BOOL win8_or_later = FALSE;
+static BOOL win10_22H2_or_later = FALSE;
+#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL)
+static BOOL use_alternate_screen_buffer = FALSE;
+#endif
+
+/*
+ * Get version number including build number
+ */
+typedef BOOL (WINAPI *PfnRtlGetVersion)(LPOSVERSIONINFOW);
+#define MAKE_VER(major, minor, build) \
+ (((major) << 24) | ((minor) << 16) | (build))
+
+ static DWORD
+get_build_number(void)
+{
+ OSVERSIONINFOW osver;
+ HMODULE hNtdll;
+ PfnRtlGetVersion pRtlGetVersion;
+ DWORD ver = MAKE_VER(0, 0, 0);
+
+ osver.dwOSVersionInfoSize = sizeof(OSVERSIONINFOW);
+ hNtdll = GetModuleHandle("ntdll.dll");
+ if (hNtdll == NULL)
+ return ver;
+
+ pRtlGetVersion =
+ (PfnRtlGetVersion)GetProcAddress(hNtdll, "RtlGetVersion");
+ pRtlGetVersion(&osver);
+ ver = MAKE_VER(min(osver.dwMajorVersion, 255),
+ min(osver.dwMinorVersion, 255),
+ min(osver.dwBuildNumber, 32767));
+ return ver;
+}
+
+#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL)
+ static BOOL
+is_ambiwidth_event(
+ INPUT_RECORD *ir)
+{
+ return ir->EventType == KEY_EVENT
+ && ir->Event.KeyEvent.bKeyDown
+ && ir->Event.KeyEvent.wRepeatCount == 1
+ && ir->Event.KeyEvent.wVirtualKeyCode == 0x12
+ && ir->Event.KeyEvent.wVirtualScanCode == 0x38
+ && ir->Event.KeyEvent.uChar.UnicodeChar == 0
+ && ir->Event.KeyEvent.dwControlKeyState == 2;
+}
+
+ static void
+make_ambiwidth_event(
+ INPUT_RECORD *down,
+ INPUT_RECORD *up)
+{
+ down->Event.KeyEvent.wVirtualKeyCode = 0;
+ down->Event.KeyEvent.wVirtualScanCode = 0;
+ down->Event.KeyEvent.uChar.UnicodeChar
+ = up->Event.KeyEvent.uChar.UnicodeChar;
+ down->Event.KeyEvent.dwControlKeyState = 0;
+}
+
+/*
+ * Version of ReadConsoleInput() that works with IME.
+ * Works around problems on Windows 8.
+ */
+ static BOOL
+read_console_input(
+ HANDLE hInput,
+ INPUT_RECORD *lpBuffer,
+ int nLength,
+ LPDWORD lpEvents)
+{
+ enum
+ {
+ IRSIZE = 10
+ };
+ static INPUT_RECORD s_irCache[IRSIZE];
+ static DWORD s_dwIndex = 0;
+ static DWORD s_dwMax = 0;
+ DWORD dwEvents;
+ int head;
+ int tail;
+ int i;
+ static INPUT_RECORD s_irPseudo;
+
+ if (s_dwMax == 0 && input_record_buffer.length > 0)
+ {
+ dwEvents = read_input_record_buffer(s_irCache, IRSIZE);
+ s_dwIndex = 0;
+ s_dwMax = dwEvents;
+ }
+
+ if (nLength == -2)
+ return (s_dwMax > 0) ? TRUE : FALSE;
+
+ if (!win8_or_later)
+ {
+ if (nLength == -1)
+ return PeekConsoleInputW(hInput, lpBuffer, 1, lpEvents);
+ return ReadConsoleInputW(hInput, lpBuffer, 1, &dwEvents);
+ }
+
+ if (s_dwMax == 0)
+ {
+ if (!vtp_working && nLength == -1)
+ return PeekConsoleInputW(hInput, lpBuffer, 1, lpEvents);
+ GetNumberOfConsoleInputEvents(hInput, &dwEvents);
+ if (dwEvents == 0 && nLength == -1)
+ return PeekConsoleInputW(hInput, lpBuffer, 1, lpEvents);
+ ReadConsoleInputW(hInput, s_irCache, IRSIZE, &dwEvents);
+ s_dwIndex = 0;
+ s_dwMax = dwEvents;
+ if (dwEvents == 0)
+ {
+ *lpEvents = 0;
+ return TRUE;
+ }
+
+ for (i = s_dwIndex; i < (int)s_dwMax - 1; ++i)
+ if (is_ambiwidth_event(&s_irCache[i]))
+ make_ambiwidth_event(&s_irCache[i], &s_irCache[i + 1]);
+
+ if (s_dwMax > 1)
+ {
+ head = 0;
+ tail = s_dwMax - 1;
+ while (head != tail)
+ {
+ if (s_irCache[head].EventType == WINDOW_BUFFER_SIZE_EVENT
+ && s_irCache[head + 1].EventType
+ == WINDOW_BUFFER_SIZE_EVENT)
+ {
+ // Remove duplicate event to avoid flicker.
+ for (i = head; i < tail; ++i)
+ s_irCache[i] = s_irCache[i + 1];
+ --tail;
+ continue;
+ }
+ head++;
+ }
+ s_dwMax = tail + 1;
+ }
+ }
+
+ if (s_irCache[s_dwIndex].EventType == KEY_EVENT)
+ {
+ if (s_irCache[s_dwIndex].Event.KeyEvent.wRepeatCount > 1)
+ {
+ s_irPseudo = s_irCache[s_dwIndex];
+ s_irPseudo.Event.KeyEvent.wRepeatCount = 1;
+ s_irCache[s_dwIndex].Event.KeyEvent.wRepeatCount--;
+ *lpBuffer = s_irPseudo;
+ *lpEvents = 1;
+ return TRUE;
+ }
+ }
+
+ *lpBuffer = s_irCache[s_dwIndex];
+ if (!(nLength == -1 || nLength == -2) && ++s_dwIndex >= s_dwMax)
+ s_dwMax = 0;
+ *lpEvents = 1;
+ return TRUE;
+}
+
+/*
+ * Version of PeekConsoleInput() that works with IME.
+ */
+ static BOOL
+peek_console_input(
+ HANDLE hInput,
+ INPUT_RECORD *lpBuffer,
+ DWORD nLength UNUSED,
+ LPDWORD lpEvents)
+{
+ return read_console_input(hInput, lpBuffer, -1, lpEvents);
+}
+
+# ifdef FEAT_CLIENTSERVER
+ static DWORD
+msg_wait_for_multiple_objects(
+ DWORD nCount,
+ LPHANDLE pHandles,
+ BOOL fWaitAll,
+ DWORD dwMilliseconds,
+ DWORD dwWakeMask)
+{
+ if (read_console_input(NULL, NULL, -2, NULL))
+ return WAIT_OBJECT_0;
+ return MsgWaitForMultipleObjects(nCount, pHandles, fWaitAll,
+ dwMilliseconds, dwWakeMask);
+}
+# endif
+
+# ifndef FEAT_CLIENTSERVER
+ static DWORD
+wait_for_single_object(
+ HANDLE hHandle,
+ DWORD dwMilliseconds)
+{
+ if (read_console_input(NULL, NULL, -2, NULL))
+ return WAIT_OBJECT_0;
+ return WaitForSingleObject(hHandle, dwMilliseconds);
+}
+# endif
+#endif // !FEAT_GUI_MSWIN || VIMDLL
+
+ void
+mch_get_exe_name(void)
+{
+ // Maximum length of $PATH is more than MAXPATHL. 8191 is often mentioned
+ // as the maximum length that works (plus a NUL byte).
+#define MAX_ENV_PATH_LEN 8192
+ char temp[MAX_ENV_PATH_LEN];
+ char_u *p;
+ WCHAR buf[MAX_PATH];
+ int updated = FALSE;
+ static int enc_prev = -1;
+
+ if (exe_name == NULL || exe_pathw == NULL || enc_prev != enc_codepage)
+ {
+ // store the name of the executable, may be used for $VIM
+ GetModuleFileNameW(NULL, buf, MAX_PATH);
+ if (*buf != NUL)
+ {
+ if (enc_codepage == -1)
+ enc_codepage = GetACP();
+ if (exe_name != NULL)
+ vim_free(exe_name);
+ exe_name = utf16_to_enc(buf, NULL);
+ enc_prev = enc_codepage;
+
+ WCHAR *wp = wcsrchr(buf, '\\');
+ if (wp != NULL)
+ *wp = NUL;
+ if (exe_pathw != NULL)
+ vim_free(exe_pathw);
+ exe_pathw = _wcsdup(buf);
+ updated = TRUE;
+ }
+ }
+
+ if (exe_pathw == NULL || !updated)
+ return;
+
+ char_u *exe_path = utf16_to_enc(exe_pathw, NULL);
+ if (exe_path == NULL)
+ return;
+
+ // Append our starting directory to $PATH, so that when doing
+ // "!xxd" it's found in our starting directory. Needed because
+ // SearchPath() also looks there.
+ p = mch_getenv("PATH");
+ if (p == NULL
+ || STRLEN(p) + STRLEN(exe_path) + 2 < MAX_ENV_PATH_LEN)
+ {
+ if (p == NULL || *p == NUL)
+ temp[0] = NUL;
+ else
+ {
+ STRCPY(temp, p);
+ STRCAT(temp, ";");
+ }
+ STRCAT(temp, exe_path);
+ vim_setenv((char_u *)"PATH", (char_u *)temp);
+ }
+ vim_free(exe_path);
+}
+
+/*
+ * Unescape characters in "p" that appear in "escaped".
+ */
+ static void
+unescape_shellxquote(char_u *p, char_u *escaped)
+{
+ int l = (int)STRLEN(p);
+ int n;
+
+ while (*p != NUL)
+ {
+ if (*p == '^' && vim_strchr(escaped, p[1]) != NULL)
+ mch_memmove(p, p + 1, l--);
+ n = (*mb_ptr2len)(p);
+ p += n;
+ l -= n;
+ }
+}
+
+/*
+ * Load library "name".
+ */
+ HINSTANCE
+vimLoadLib(const char *name)
+{
+ HINSTANCE dll = NULL;
+
+ // No need to load any library when registering OLE.
+ if (found_register_arg)
+ return NULL;
+
+ // NOTE: Do not use mch_dirname() and mch_chdir() here, they may call
+ // vimLoadLib() recursively, which causes a stack overflow.
+ if (exe_pathw == NULL)
+ mch_get_exe_name();
+
+ if (exe_pathw == NULL)
+ return NULL;
+
+ WCHAR old_dirw[MAXPATHL];
+
+ if (GetCurrentDirectoryW(MAXPATHL, old_dirw) == 0)
+ return NULL;
+
+ // Change directory to where the executable is, both to make
+ // sure we find a .dll there and to avoid looking for a .dll
+ // in the current directory.
+ SetCurrentDirectoryW(exe_pathw);
+ dll = LoadLibrary(name);
+ SetCurrentDirectoryW(old_dirw);
+ return dll;
+}
+
+#if defined(VIMDLL) || defined(PROTO)
+/*
+ * Check if the current executable file is for the GUI subsystem.
+ */
+ int
+mch_is_gui_executable(void)
+{
+ PBYTE pImage = (PBYTE)GetModuleHandle(NULL);
+ PIMAGE_DOS_HEADER pDOS = (PIMAGE_DOS_HEADER)pImage;
+ PIMAGE_NT_HEADERS pPE;
+
+ if (pDOS->e_magic != IMAGE_DOS_SIGNATURE)
+ return FALSE;
+ pPE = (PIMAGE_NT_HEADERS)(pImage + pDOS->e_lfanew);
+ if (pPE->Signature != IMAGE_NT_SIGNATURE)
+ return FALSE;
+ if (pPE->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_GUI)
+ return TRUE;
+ return FALSE;
+}
+#endif
+
+#if defined(DYNAMIC_ICONV) || defined(DYNAMIC_GETTEXT) \
+ || defined(FEAT_PYTHON3) || defined(PROTO)
+/*
+ * Get related information about 'funcname' which is imported by 'hInst'.
+ * If 'info' is 0, return the function address.
+ * If 'info' is 1, return the module name which the function is imported from.
+ * If 'info' is 2, hook the function with 'ptr', and return the original
+ * function address.
+ */
+ static void *
+get_imported_func_info(HINSTANCE hInst, const char *funcname, int info,
+ const void *ptr)
+{
+ PBYTE pImage = (PBYTE)hInst;
+ PIMAGE_DOS_HEADER pDOS = (PIMAGE_DOS_HEADER)hInst;
+ PIMAGE_NT_HEADERS pPE;
+ PIMAGE_IMPORT_DESCRIPTOR pImpDesc;
+ PIMAGE_THUNK_DATA pIAT; // Import Address Table
+ PIMAGE_THUNK_DATA pINT; // Import Name Table
+ PIMAGE_IMPORT_BY_NAME pImpName;
+
+ if (pDOS->e_magic != IMAGE_DOS_SIGNATURE)
+ return NULL;
+ pPE = (PIMAGE_NT_HEADERS)(pImage + pDOS->e_lfanew);
+ if (pPE->Signature != IMAGE_NT_SIGNATURE)
+ return NULL;
+ pImpDesc = (PIMAGE_IMPORT_DESCRIPTOR)(pImage
+ + pPE->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
+ .VirtualAddress);
+ for (; pImpDesc->FirstThunk; ++pImpDesc)
+ {
+ if (!pImpDesc->OriginalFirstThunk)
+ continue;
+ pIAT = (PIMAGE_THUNK_DATA)(pImage + pImpDesc->FirstThunk);
+ pINT = (PIMAGE_THUNK_DATA)(pImage + pImpDesc->OriginalFirstThunk);
+ for (; pIAT->u1.Function; ++pIAT, ++pINT)
+ {
+ if (IMAGE_SNAP_BY_ORDINAL(pINT->u1.Ordinal))
+ continue;
+ pImpName = (PIMAGE_IMPORT_BY_NAME)(pImage
+ + (UINT_PTR)(pINT->u1.AddressOfData));
+ if (strcmp((char *)pImpName->Name, funcname) == 0)
+ {
+ void *original;
+ DWORD old, new = PAGE_READWRITE;
+
+ switch (info)
+ {
+ case 0:
+ return (void *)pIAT->u1.Function;
+ case 1:
+ return (void *)(pImage + pImpDesc->Name);
+ case 2:
+ original = (void *)pIAT->u1.Function;
+ VirtualProtect(&pIAT->u1.Function, sizeof(void *),
+ new, &old);
+ pIAT->u1.Function = (UINT_PTR)ptr;
+ VirtualProtect(&pIAT->u1.Function, sizeof(void *),
+ old, &new);
+ return original;
+ default:
+ return NULL;
+ }
+ }
+ }
+ }
+ return NULL;
+}
+
+/*
+ * Get the module handle which 'funcname' in 'hInst' is imported from.
+ */
+ HINSTANCE
+find_imported_module_by_funcname(HINSTANCE hInst, const char *funcname)
+{
+ char *modulename;
+
+ modulename = (char *)get_imported_func_info(hInst, funcname, 1, NULL);
+ if (modulename != NULL)
+ return GetModuleHandleA(modulename);
+ return NULL;
+}
+
+/*
+ * Get the address of 'funcname' which is imported by 'hInst' DLL.
+ */
+ void *
+get_dll_import_func(HINSTANCE hInst, const char *funcname)
+{
+ return get_imported_func_info(hInst, funcname, 0, NULL);
+}
+
+/*
+ * Hook the function named 'funcname' which is imported by 'hInst' DLL,
+ * and return the original function address.
+ */
+ void *
+hook_dll_import_func(HINSTANCE hInst, const char *funcname, const void *hook)
+{
+ return get_imported_func_info(hInst, funcname, 2, hook);
+}
+#endif
+
+#if defined(DYNAMIC_GETTEXT) || defined(PROTO)
+# ifndef GETTEXT_DLL
+# define GETTEXT_DLL "libintl.dll"
+# define GETTEXT_DLL_ALT1 "libintl-8.dll"
+# define GETTEXT_DLL_ALT2 "intl.dll"
+# endif
+// Dummy functions
+static char *null_libintl_gettext(const char *);
+static char *null_libintl_ngettext(const char *, const char *, unsigned long n);
+static char *null_libintl_textdomain(const char *);
+static char *null_libintl_bindtextdomain(const char *, const char *);
+static char *null_libintl_bind_textdomain_codeset(const char *, const char *);
+static int null_libintl_wputenv(const wchar_t *);
+
+static HINSTANCE hLibintlDLL = NULL;
+char *(*dyn_libintl_gettext)(const char *) = null_libintl_gettext;
+char *(*dyn_libintl_ngettext)(const char *, const char *, unsigned long n)
+ = null_libintl_ngettext;
+char *(*dyn_libintl_textdomain)(const char *) = null_libintl_textdomain;
+char *(*dyn_libintl_bindtextdomain)(const char *, const char *)
+ = null_libintl_bindtextdomain;
+char *(*dyn_libintl_bind_textdomain_codeset)(const char *, const char *)
+ = null_libintl_bind_textdomain_codeset;
+int (*dyn_libintl_wputenv)(const wchar_t *) = null_libintl_wputenv;
+
+ int
+dyn_libintl_init(void)
+{
+ int i;
+ static struct
+ {
+ char *name;
+ FARPROC *ptr;
+ } libintl_entry[] =
+ {
+ {"gettext", (FARPROC*)&dyn_libintl_gettext},
+ {"ngettext", (FARPROC*)&dyn_libintl_ngettext},
+ {"textdomain", (FARPROC*)&dyn_libintl_textdomain},
+ {"bindtextdomain", (FARPROC*)&dyn_libintl_bindtextdomain},
+ {NULL, NULL}
+ };
+ HINSTANCE hmsvcrt;
+
+ // No need to initialize twice.
+ if (hLibintlDLL != NULL)
+ return 1;
+ // Load gettext library (libintl.dll and other names).
+ hLibintlDLL = vimLoadLib(GETTEXT_DLL);
+# ifdef GETTEXT_DLL_ALT1
+ if (!hLibintlDLL)
+ hLibintlDLL = vimLoadLib(GETTEXT_DLL_ALT1);
+# endif
+# ifdef GETTEXT_DLL_ALT2
+ if (!hLibintlDLL)
+ hLibintlDLL = vimLoadLib(GETTEXT_DLL_ALT2);
+# endif
+ if (!hLibintlDLL)
+ {
+ if (p_verbose > 0)
+ {
+ verbose_enter();
+ semsg(_(e_could_not_load_library_str_str), GETTEXT_DLL, GetWin32Error());
+ verbose_leave();
+ }
+ return 0;
+ }
+ for (i = 0; libintl_entry[i].name != NULL
+ && libintl_entry[i].ptr != NULL; ++i)
+ {
+ if ((*libintl_entry[i].ptr = GetProcAddress(hLibintlDLL,
+ libintl_entry[i].name)) == NULL)
+ {
+ dyn_libintl_end();
+ if (p_verbose > 0)
+ {
+ verbose_enter();
+ semsg(_(e_could_not_load_library_function_str), libintl_entry[i].name);
+ verbose_leave();
+ }
+ return 0;
+ }
+ }
+
+ // The bind_textdomain_codeset() function is optional.
+ dyn_libintl_bind_textdomain_codeset = (char *(*)(const char *, const char *))
+ GetProcAddress(hLibintlDLL, "bind_textdomain_codeset");
+ if (dyn_libintl_bind_textdomain_codeset == NULL)
+ dyn_libintl_bind_textdomain_codeset =
+ null_libintl_bind_textdomain_codeset;
+
+ // _wputenv() function for the libintl.dll is optional.
+ hmsvcrt = find_imported_module_by_funcname(hLibintlDLL, "getenv");
+ if (hmsvcrt != NULL)
+ dyn_libintl_wputenv = (int (*)(const wchar_t *))
+ GetProcAddress(hmsvcrt, "_wputenv");
+ if (dyn_libintl_wputenv == NULL || dyn_libintl_wputenv == _wputenv)
+ dyn_libintl_wputenv = null_libintl_wputenv;
+
+ return 1;
+}
+
+ void
+dyn_libintl_end(void)
+{
+ if (hLibintlDLL)
+ FreeLibrary(hLibintlDLL);
+ hLibintlDLL = NULL;
+ dyn_libintl_gettext = null_libintl_gettext;
+ dyn_libintl_ngettext = null_libintl_ngettext;
+ dyn_libintl_textdomain = null_libintl_textdomain;
+ dyn_libintl_bindtextdomain = null_libintl_bindtextdomain;
+ dyn_libintl_bind_textdomain_codeset = null_libintl_bind_textdomain_codeset;
+ dyn_libintl_wputenv = null_libintl_wputenv;
+}
+
+ static char *
+null_libintl_gettext(const char *msgid)
+{
+ return (char*)msgid;
+}
+
+ static char *
+null_libintl_ngettext(
+ const char *msgid,
+ const char *msgid_plural,
+ unsigned long n)
+{
+ return (char *)(n == 1 ? msgid : msgid_plural);
+}
+
+ static char *
+null_libintl_bindtextdomain(
+ const char *domainname UNUSED,
+ const char *dirname UNUSED)
+{
+ return NULL;
+}
+
+ static char *
+null_libintl_bind_textdomain_codeset(
+ const char *domainname UNUSED,
+ const char *codeset UNUSED)
+{
+ return NULL;
+}
+
+ static char *
+null_libintl_textdomain(const char *domainname UNUSED)
+{
+ return NULL;
+}
+
+ static int
+null_libintl_wputenv(const wchar_t *envstring UNUSED)
+{
+ return 0;
+}
+
+#endif // DYNAMIC_GETTEXT
+
+// This symbol is not defined in older versions of the SDK or Visual C++
+
+#ifndef VER_PLATFORM_WIN32_WINDOWS
+# define VER_PLATFORM_WIN32_WINDOWS 1
+#endif
+
+#ifdef HAVE_ACL
+# ifndef PROTO
+# include <aclapi.h>
+# endif
+# ifndef PROTECTED_DACL_SECURITY_INFORMATION
+# define PROTECTED_DACL_SECURITY_INFORMATION 0x80000000L
+# endif
+#endif
+
+#ifdef HAVE_ACL
+/*
+ * Enables or disables the specified privilege.
+ */
+ static BOOL
+win32_enable_privilege(LPTSTR lpszPrivilege, BOOL bEnable)
+{
+ BOOL bResult;
+ LUID luid;
+ HANDLE hToken;
+ TOKEN_PRIVILEGES tokenPrivileges;
+
+ if (!OpenProcessToken(GetCurrentProcess(),
+ TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
+ return FALSE;
+
+ if (!LookupPrivilegeValue(NULL, lpszPrivilege, &luid))
+ {
+ CloseHandle(hToken);
+ return FALSE;
+ }
+
+ tokenPrivileges.PrivilegeCount = 1;
+ tokenPrivileges.Privileges[0].Luid = luid;
+ tokenPrivileges.Privileges[0].Attributes = bEnable ?
+ SE_PRIVILEGE_ENABLED : 0;
+
+ bResult = AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges,
+ sizeof(TOKEN_PRIVILEGES), NULL, NULL);
+
+ CloseHandle(hToken);
+
+ return bResult && GetLastError() == ERROR_SUCCESS;
+}
+#endif
+
+#ifdef _MSC_VER
+// Suppress the deprecation warning for using GetVersionEx().
+// It is needed for implementing "windowsversion()".
+# pragma warning(push)
+# pragma warning(disable: 4996)
+#endif
+/*
+ * Set "win8_or_later" and fill in "windowsVersion" if possible.
+ */
+ void
+PlatformId(void)
+{
+ static int done = FALSE;
+
+ if (done)
+ return;
+
+ OSVERSIONINFO ovi;
+
+ ovi.dwOSVersionInfoSize = sizeof(ovi);
+ GetVersionEx(&ovi);
+
+#ifdef FEAT_EVAL
+ vim_snprintf(windowsVersion, sizeof(windowsVersion), "%d.%d",
+ (int)ovi.dwMajorVersion, (int)ovi.dwMinorVersion);
+#endif
+ if ((ovi.dwMajorVersion == 6 && ovi.dwMinorVersion >= 2)
+ || ovi.dwMajorVersion > 6)
+ win8_or_later = TRUE;
+
+ if ((ovi.dwMajorVersion == 10 && ovi.dwBuildNumber >= 19045)
+ || ovi.dwMajorVersion > 10)
+ win10_22H2_or_later = TRUE;
+
+#ifdef HAVE_ACL
+ // Enable privilege for getting or setting SACLs.
+ win32_enable_privilege(SE_SECURITY_NAME, TRUE);
+#endif
+ done = TRUE;
+}
+#ifdef _MSC_VER
+# pragma warning(pop)
+#endif
+
+#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL)
+
+# define SHIFT (SHIFT_PRESSED)
+# define CTRL (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED)
+# define ALT (RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED)
+# define ALT_GR (RIGHT_ALT_PRESSED | LEFT_CTRL_PRESSED)
+
+
+// When uChar.AsciiChar is 0, then we need to look at wVirtualKeyCode.
+// We map function keys to their ANSI terminal equivalents, as produced
+// by ANSI.SYS, for compatibility with the MS-DOS version of Vim. Any
+// ANSI key with a value >= '\300' is nonstandard, but provided anyway
+// so that the user can have access to all SHIFT-, CTRL-, and ALT-
+// combinations of function/arrow/etc keys.
+
+static const struct
+{
+ WORD wVirtKey;
+ BOOL fAnsiKey;
+ int chAlone;
+ int chShift;
+ int chCtrl;
+ int chAlt;
+} VirtKeyMap[] =
+{
+// Key ANSI alone shift ctrl alt
+ { VK_ESCAPE,FALSE, ESC, ESC, ESC, ESC, },
+
+ { VK_F1, TRUE, ';', 'T', '^', 'h', },
+ { VK_F2, TRUE, '<', 'U', '_', 'i', },
+ { VK_F3, TRUE, '=', 'V', '`', 'j', },
+ { VK_F4, TRUE, '>', 'W', 'a', 'k', },
+ { VK_F5, TRUE, '?', 'X', 'b', 'l', },
+ { VK_F6, TRUE, '@', 'Y', 'c', 'm', },
+ { VK_F7, TRUE, 'A', 'Z', 'd', 'n', },
+ { VK_F8, TRUE, 'B', '[', 'e', 'o', },
+ { VK_F9, TRUE, 'C', '\\', 'f', 'p', },
+ { VK_F10, TRUE, 'D', ']', 'g', 'q', },
+ { VK_F11, TRUE, '\205', '\207', '\211', '\213', },
+ { VK_F12, TRUE, '\206', '\210', '\212', '\214', },
+
+ { VK_HOME, TRUE, 'G', '\302', 'w', '\303', },
+ { VK_UP, TRUE, 'H', '\304', '\305', '\306', },
+ { VK_PRIOR, TRUE, 'I', '\307', '\204', '\310', }, // PgUp
+ { VK_LEFT, TRUE, 'K', '\311', 's', '\312', },
+ { VK_RIGHT, TRUE, 'M', '\313', 't', '\314', },
+ { VK_END, TRUE, 'O', '\315', 'u', '\316', },
+ { VK_DOWN, TRUE, 'P', '\317', '\320', '\321', },
+ { VK_NEXT, TRUE, 'Q', '\322', 'v', '\323', }, // PgDn
+ { VK_INSERT,TRUE, 'R', '\324', '\325', '\326', },
+ { VK_DELETE,TRUE, 'S', '\327', '\330', '\331', },
+ { VK_BACK, TRUE, 'x', 'y', 'z', '{', }, // Backspace
+
+ { VK_SNAPSHOT,TRUE, 0, 0, 0, 'r', }, // PrtScrn
+
+# if 0
+ // Most people don't have F13-F20, but what the hell...
+ { VK_F13, TRUE, '\332', '\333', '\334', '\335', },
+ { VK_F14, TRUE, '\336', '\337', '\340', '\341', },
+ { VK_F15, TRUE, '\342', '\343', '\344', '\345', },
+ { VK_F16, TRUE, '\346', '\347', '\350', '\351', },
+ { VK_F17, TRUE, '\352', '\353', '\354', '\355', },
+ { VK_F18, TRUE, '\356', '\357', '\360', '\361', },
+ { VK_F19, TRUE, '\362', '\363', '\364', '\365', },
+ { VK_F20, TRUE, '\366', '\367', '\370', '\371', },
+# endif
+ { VK_ADD, TRUE, 'N', 'N', 'N', 'N', }, // keyp '+'
+ { VK_SUBTRACT, TRUE,'J', 'J', 'J', 'J', }, // keyp '-'
+ // { VK_DIVIDE, TRUE,'N', 'N', 'N', 'N', }, // keyp '/'
+ { VK_MULTIPLY, TRUE,'7', '7', '7', '7', }, // keyp '*'
+
+ { VK_NUMPAD0,TRUE, '\332', '\333', '\334', '\335', },
+ { VK_NUMPAD1,TRUE, '\336', '\337', '\340', '\341', },
+ { VK_NUMPAD2,TRUE, '\342', '\343', '\344', '\345', },
+ { VK_NUMPAD3,TRUE, '\346', '\347', '\350', '\351', },
+ { VK_NUMPAD4,TRUE, '\352', '\353', '\354', '\355', },
+ { VK_NUMPAD5,TRUE, '\356', '\357', '\360', '\361', },
+ { VK_NUMPAD6,TRUE, '\362', '\363', '\364', '\365', },
+ { VK_NUMPAD7,TRUE, '\366', '\367', '\370', '\371', },
+ { VK_NUMPAD8,TRUE, '\372', '\373', '\374', '\375', },
+ // Sorry, out of number space! <negri>
+ { VK_NUMPAD9,TRUE, '\376', '\377', '|', '}', },
+};
+
+
+/*
+ * The return code indicates key code size.
+ */
+ static int
+win32_kbd_patch_key(
+ KEY_EVENT_RECORD *pker)
+{
+ UINT uMods = pker->dwControlKeyState;
+ static int s_iIsDead = 0;
+ static WORD awAnsiCode[2];
+ static BYTE abKeystate[256];
+
+
+ if (s_iIsDead == 2)
+ {
+ pker->uChar.UnicodeChar = (WCHAR) awAnsiCode[1];
+ s_iIsDead = 0;
+ return 1;
+ }
+
+ // check if it already has a valid unicode character.
+ if (pker->uChar.UnicodeChar != 0)
+ return 1;
+
+ CLEAR_FIELD(abKeystate);
+
+ // Clear any pending dead keys
+ ToUnicode(VK_SPACE, MapVirtualKey(VK_SPACE, 0), abKeystate, awAnsiCode, 2, 0);
+
+ if (uMods & SHIFT_PRESSED)
+ abKeystate[VK_SHIFT] = 0x80;
+ if (uMods & CAPSLOCK_ON)
+ abKeystate[VK_CAPITAL] = 1;
+
+ if ((uMods & ALT_GR) == ALT_GR)
+ {
+ abKeystate[VK_CONTROL] = abKeystate[VK_LCONTROL] =
+ abKeystate[VK_MENU] = abKeystate[VK_RMENU] = 0x80;
+ }
+
+ s_iIsDead = ToUnicode(pker->wVirtualKeyCode, pker->wVirtualScanCode,
+ abKeystate, awAnsiCode, 2, 0);
+
+ if (s_iIsDead > 0)
+ pker->uChar.UnicodeChar = (WCHAR) awAnsiCode[0];
+
+ return s_iIsDead;
+}
+
+static BOOL g_fJustGotFocus = FALSE;
+
+/*
+ * Decode a KEY_EVENT into one or two keystrokes
+ */
+ static BOOL
+decode_key_event(
+ KEY_EVENT_RECORD *pker,
+ WCHAR *pch,
+ WCHAR *pch2,
+ int *pmodifiers,
+ BOOL fDoPost UNUSED)
+{
+ int i;
+ const int nModifs = pker->dwControlKeyState & (SHIFT | ALT | CTRL);
+
+ *pch = *pch2 = NUL;
+ g_fJustGotFocus = FALSE;
+
+ // ignore key up events
+ if (!pker->bKeyDown)
+ return FALSE;
+
+ // ignore some keystrokes
+ switch (pker->wVirtualKeyCode)
+ {
+ // modifiers
+ case VK_SHIFT:
+ case VK_CONTROL:
+ case VK_MENU: // Alt key
+ return FALSE;
+
+ default:
+ break;
+ }
+
+ // Shift-TAB
+ if (pker->wVirtualKeyCode == VK_TAB && (nModifs & SHIFT_PRESSED))
+ {
+ *pch = K_NUL;
+ *pch2 = '\017';
+ return TRUE;
+ }
+
+ for (i = ARRAY_LENGTH(VirtKeyMap); --i >= 0; )
+ {
+ if (VirtKeyMap[i].wVirtKey == pker->wVirtualKeyCode)
+ {
+ *pch = VirtKeyMap[i].chAlone;
+ if ((nModifs & SHIFT) != 0)
+ *pch = VirtKeyMap[i].chShift;
+ else if ((nModifs & CTRL) != 0 && (nModifs & ~CTRL) == 0)
+ *pch = VirtKeyMap[i].chCtrl;
+ else if ((nModifs & ALT) != 0)
+ *pch = VirtKeyMap[i].chAlt;
+
+ if (*pch != 0)
+ {
+ if (VirtKeyMap[i].fAnsiKey)
+ {
+ *pch2 = *pch;
+ *pch = K_NUL;
+ if (pmodifiers)
+ {
+ if (pker->wVirtualKeyCode >= VK_F1
+ && pker->wVirtualKeyCode <= VK_F12)
+ {
+ if ((nModifs & ALT) != 0)
+ {
+ *pmodifiers |= MOD_MASK_ALT;
+ if ((nModifs & SHIFT) == 0)
+ *pch2 = VirtKeyMap[i].chAlone;
+ }
+ if ((nModifs & CTRL) != 0)
+ {
+ *pmodifiers |= MOD_MASK_CTRL;
+ if ((nModifs & SHIFT) == 0)
+ *pch2 = VirtKeyMap[i].chAlone;
+ }
+ }
+ else if (pker->wVirtualKeyCode >= VK_END
+ && pker->wVirtualKeyCode <= VK_DOWN)
+ {
+ // (0x23 - 0x28): VK_END, VK_HOME,
+ // VK_LEFT, VK_UP, VK_RIGHT, VK_DOWN
+
+ *pmodifiers = 0;
+ *pch2 = VirtKeyMap[i].chAlone;
+ if ((nModifs & SHIFT) != 0
+ && (nModifs & ~SHIFT) == 0)
+ {
+ *pch2 = VirtKeyMap[i].chShift;
+ }
+ if ((nModifs & CTRL) != 0
+ && (nModifs & ~CTRL) == 0)
+ {
+ *pch2 = VirtKeyMap[i].chCtrl;
+ if (pker->wVirtualKeyCode == VK_UP
+ || pker->wVirtualKeyCode == VK_DOWN)
+ {
+ *pmodifiers |= MOD_MASK_CTRL;
+ *pch2 = VirtKeyMap[i].chAlone;
+ }
+ }
+ if ((nModifs & SHIFT) != 0
+ && (nModifs & CTRL) != 0)
+ {
+ *pmodifiers |= MOD_MASK_CTRL;
+ *pch2 = VirtKeyMap[i].chShift;
+ }
+ if ((nModifs & ALT) != 0)
+ {
+ *pch2 = VirtKeyMap[i].chAlt;
+ *pmodifiers |= MOD_MASK_ALT;
+ if ((nModifs & ~ALT) == 0)
+ {
+ *pch2 = VirtKeyMap[i].chAlone;
+ }
+ else if ((nModifs & SHIFT) != 0)
+ {
+ *pch2 = VirtKeyMap[i].chShift;
+ }
+ else if ((nModifs & CTRL) != 0)
+ {
+ if (pker->wVirtualKeyCode == VK_UP
+ || pker->wVirtualKeyCode == VK_DOWN)
+ {
+ *pmodifiers |= MOD_MASK_CTRL;
+ *pch2 = VirtKeyMap[i].chAlone;
+ }
+ else
+ {
+ *pch2 = VirtKeyMap[i].chCtrl;
+ }
+ }
+ }
+ }
+ else
+ {
+ *pch2 = VirtKeyMap[i].chAlone;
+ if ((nModifs & SHIFT) != 0)
+ *pmodifiers |= MOD_MASK_SHIFT;
+ if ((nModifs & CTRL) != 0)
+ *pmodifiers |= MOD_MASK_CTRL;
+ if ((nModifs & ALT) != 0)
+ *pmodifiers |= MOD_MASK_ALT;
+ }
+ }
+ }
+
+ return TRUE;
+ }
+ }
+ }
+
+ i = win32_kbd_patch_key(pker);
+
+ if (i < 0)
+ *pch = NUL;
+ else
+ {
+ *pch = (i > 0) ? pker->uChar.UnicodeChar : NUL;
+
+ if (pmodifiers != NULL)
+ {
+ // Pass on the ALT key as a modifier, but only when not combined
+ // with CTRL (which is ALTGR).
+ if ((nModifs & ALT) != 0 && (nModifs & CTRL) == 0)
+ *pmodifiers |= MOD_MASK_ALT;
+
+ // Pass on SHIFT only for special keys, because we don't know when
+ // it's already included with the character.
+ if ((nModifs & SHIFT) != 0 && *pch <= 0x20)
+ *pmodifiers |= MOD_MASK_SHIFT;
+
+ // Pass on CTRL only for non-special keys, because we don't know
+ // when it's already included with the character. And not when
+ // combined with ALT (which is ALTGR).
+ if ((nModifs & CTRL) != 0 && (nModifs & ALT) == 0
+ && *pch >= 0x20 && *pch < 0x80)
+ *pmodifiers |= MOD_MASK_CTRL;
+ }
+ }
+
+ return (*pch != NUL);
+}
+
+# if defined(FEAT_EVAL)
+ static int
+encode_key_event(dict_T *args, INPUT_RECORD *ir)
+{
+ static int s_dwMods = 0;
+
+ char_u *action = dict_get_string(args, "event", TRUE);
+ if (action && (STRICMP(action, "keydown") == 0
+ || STRICMP(action, "keyup") == 0))
+ {
+ BOOL isKeyDown = STRICMP(action, "keydown") == 0;
+ WORD vkCode = dict_get_number_def(args, "keycode", 0);
+ if (vkCode <= 0 || vkCode >= 0xFF)
+ {
+ semsg(_(e_invalid_argument_nr), (long)vkCode);
+ return FALSE;
+ }
+
+ ir->EventType = KEY_EVENT;
+ KEY_EVENT_RECORD ker;
+ ZeroMemory(&ker, sizeof(ker));
+ ker.bKeyDown = isKeyDown;
+ ker.wRepeatCount = 1;
+ ker.wVirtualScanCode = 0;
+ ker.dwControlKeyState = 0;
+ int mods = (int)dict_get_number(args, "modifiers");
+ // Encode the win32 console key modifiers from Vim keyboard modifiers.
+ if (mods)
+ {
+ // If "modifiers" is explicitly set in the args, then we reset any
+ // remembered modifer key state that may have been set from earlier
+ // mod-key-down events, even if they are not yet unset by earlier
+ // mod-key-up events.
+ s_dwMods = 0;
+ if (mods & MOD_MASK_SHIFT)
+ ker.dwControlKeyState |= SHIFT_PRESSED;
+ if (mods & MOD_MASK_CTRL)
+ ker.dwControlKeyState |= LEFT_CTRL_PRESSED;
+ if (mods & MOD_MASK_ALT)
+ ker.dwControlKeyState |= LEFT_ALT_PRESSED;
+ }
+
+ if (vkCode == VK_LSHIFT || vkCode == VK_RSHIFT || vkCode == VK_SHIFT)
+ {
+ if (isKeyDown)
+ s_dwMods |= SHIFT_PRESSED;
+ else
+ s_dwMods &= ~SHIFT_PRESSED;
+ }
+ else if (vkCode == VK_LCONTROL || vkCode == VK_CONTROL)
+ {
+ if (isKeyDown)
+ s_dwMods |= LEFT_CTRL_PRESSED;
+ else
+ s_dwMods &= ~LEFT_CTRL_PRESSED;
+ }
+ else if (vkCode == VK_RCONTROL)
+ {
+ if (isKeyDown)
+ s_dwMods |= RIGHT_CTRL_PRESSED;
+ else
+ s_dwMods &= ~RIGHT_CTRL_PRESSED;
+ }
+ else if (vkCode == VK_LMENU || vkCode == VK_MENU)
+ {
+ if (isKeyDown)
+ s_dwMods |= LEFT_ALT_PRESSED;
+ else
+ s_dwMods &= ~LEFT_ALT_PRESSED;
+ }
+ else if (vkCode == VK_RMENU)
+ {
+ if (isKeyDown)
+ s_dwMods |= RIGHT_ALT_PRESSED;
+ else
+ s_dwMods &= ~RIGHT_ALT_PRESSED;
+ }
+ ker.dwControlKeyState |= s_dwMods;
+ ker.wVirtualKeyCode = vkCode;
+ ker.uChar.UnicodeChar = 0;
+ ir->Event.KeyEvent = ker;
+ vim_free(action);
+ }
+ else
+ {
+ if (action == NULL)
+ {
+ semsg(_(e_missing_argument_str), "event");
+ }
+ else
+ {
+ semsg(_(e_invalid_value_for_argument_str_str), "event", action);
+ vim_free(action);
+ }
+ return FALSE;
+ }
+ return TRUE;
+}
+# endif // FEAT_EVAL
+#endif // !FEAT_GUI_MSWIN || VIMDLL
+
+
+/*
+ * For the GUI the mouse handling is in gui_w32.c.
+ */
+#if defined(FEAT_GUI_MSWIN) && !defined(VIMDLL)
+ void
+mch_setmouse(int on UNUSED)
+{
+}
+#else // !FEAT_GUI_MSWIN || VIMDLL
+static int g_fMouseAvail = FALSE; // mouse present
+static int g_fMouseActive = FALSE; // mouse enabled
+static int g_nMouseClick = -1; // mouse status
+static int g_xMouse; // mouse x coordinate
+static int g_yMouse; // mouse y coordinate
+static DWORD g_cmodein = 0; // Original console input mode
+static DWORD g_cmodeout = 0; // Original console output mode
+
+/*
+ * Enable or disable mouse input
+ */
+ void
+mch_setmouse(int on)
+{
+ DWORD cmodein;
+
+# ifdef VIMDLL
+ if (gui.in_use)
+ return;
+# endif
+ if (!g_fMouseAvail)
+ return;
+
+ g_fMouseActive = on;
+ GetConsoleMode(g_hConIn, &cmodein);
+
+ if (g_fMouseActive)
+ {
+ cmodein |= ENABLE_MOUSE_INPUT;
+ cmodein &= ~ENABLE_QUICK_EDIT_MODE;
+ }
+ else
+ {
+ cmodein &= ~ENABLE_MOUSE_INPUT;
+ cmodein |= g_cmodein & ENABLE_QUICK_EDIT_MODE;
+ }
+
+ SetConsoleMode(g_hConIn, cmodein | ENABLE_EXTENDED_FLAGS);
+}
+
+
+# if defined(FEAT_BEVAL_TERM) || defined(PROTO)
+/*
+ * Called when 'balloonevalterm' changed.
+ */
+ void
+mch_bevalterm_changed(void)
+{
+ mch_setmouse(g_fMouseActive);
+}
+# endif
+
+/*
+ * Win32 console mouse scroll event handler.
+ * Console version of the _OnMouseWheel() function in gui_w32.c
+ *
+ * This encodes the mouse scroll direction and keyboard modifiers into
+ * g_nMouseClick, and the mouse position into g_xMouse and g_yMouse
+ *
+ * The direction of the scroll is decoded from two fields of the win32 console
+ * mouse event record;
+ * 1. The orientation - vertical or horizontal flag - from dwEventFlags
+ * 2. The sign - positive or negative (aka delta flag) - from dwButtonState
+ *
+ * When scroll orientation is HORIZONTAL
+ * - If the high word of the dwButtonState member contains a positive
+ * value, the wheel was rotated to the right.
+ * - Otherwise, the wheel was rotated to the left.
+ * When scroll orientation is VERTICAL
+ * - If the high word of the dwButtonState member contains a positive value,
+ * the wheel was rotated forward, away from the user.
+ * - Otherwise, the wheel was rotated backward, toward the user.
+ */
+ static void
+decode_mouse_wheel(MOUSE_EVENT_RECORD *pmer)
+{
+ int horizontal = (pmer->dwEventFlags == MOUSE_HWHEELED);
+ int zDelta = pmer->dwButtonState;
+
+ g_xMouse = pmer->dwMousePosition.X;
+ g_yMouse = pmer->dwMousePosition.Y;
+
+# ifdef FEAT_PROP_POPUP
+ int lcol = g_xMouse;
+ int lrow = g_yMouse;
+ win_T *wp = mouse_find_win(&lrow, &lcol, FIND_POPUP);
+ if (wp != NULL && popup_is_popup(wp))
+ {
+ g_nMouseClick = -1;
+ cmdarg_T cap;
+ oparg_T oa;
+ CLEAR_FIELD(cap);
+ clear_oparg(&oa);
+ cap.oap = &oa;
+ if (horizontal)
+ {
+ cap.arg = zDelta < 0 ? MSCR_LEFT : MSCR_RIGHT;
+ cap.cmdchar = zDelta < 0 ? K_MOUSELEFT : K_MOUSERIGHT;
+ }
+ else
+ {
+ cap.cmdchar = zDelta < 0 ? K_MOUSEUP : K_MOUSEDOWN;
+ cap.arg = zDelta < 0 ? MSCR_UP : MSCR_DOWN;
+ }
+
+ // Mouse hovers over popup window, scroll it if possible.
+ mouse_row = wp->w_winrow;
+ mouse_col = wp->w_wincol;
+ nv_mousescroll(&cap);
+ update_screen(0);
+ setcursor();
+ out_flush();
+ return;
+ }
+# endif
+ mouse_col = g_xMouse;
+ mouse_row = g_yMouse;
+
+ char_u modifiers = 0;
+ char_u direction = 0;
+
+ // Decode the direction into an event that Vim can process
+ if (horizontal)
+ direction = zDelta >= 0 ? KE_MOUSELEFT : KE_MOUSERIGHT;
+ else
+ direction = zDelta >= 0 ? KE_MOUSEDOWN : KE_MOUSEUP;
+
+ // Decode the win32 console key modifiers into Vim mouse modifiers.
+ if (pmer->dwControlKeyState & SHIFT_PRESSED)
+ modifiers |= MOD_MASK_SHIFT; // MOUSE_SHIFT;
+ if (pmer->dwControlKeyState & (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED))
+ modifiers |= MOD_MASK_CTRL; // MOUSE_CTRL;
+ if (pmer->dwControlKeyState & (RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED))
+ modifiers |= MOD_MASK_ALT; // MOUSE_ALT;
+
+ // add (bitwise or) the scroll direction and the key modifier chars
+ // together.
+ g_nMouseClick = ((direction << 8) | modifiers);
+
+ return;
+}
+
+/*
+ * Decode a MOUSE_EVENT. If it's a valid event, return MOUSE_LEFT,
+ * MOUSE_MIDDLE, or MOUSE_RIGHT for a click; MOUSE_DRAG for a mouse
+ * move with a button held down; and MOUSE_RELEASE after a MOUSE_DRAG
+ * or a MOUSE_LEFT, _MIDDLE, or _RIGHT. We encode the button type,
+ * the number of clicks, and the Shift/Ctrl/Alt modifiers in g_nMouseClick,
+ * and we return the mouse position in g_xMouse and g_yMouse.
+ *
+ * Every MOUSE_LEFT, _MIDDLE, or _RIGHT will be followed by zero or more
+ * MOUSE_DRAGs and one MOUSE_RELEASE. MOUSE_RELEASE will be followed only
+ * by MOUSE_LEFT, _MIDDLE, or _RIGHT.
+ *
+ * For multiple clicks, we send, say, MOUSE_LEFT/1 click, MOUSE_RELEASE,
+ * MOUSE_LEFT/2 clicks, MOUSE_RELEASE, MOUSE_LEFT/3 clicks, MOUSE_RELEASE, ....
+ *
+ * Windows will send us MOUSE_MOVED notifications whenever the mouse
+ * moves, even if it stays within the same character cell. We ignore
+ * all MOUSE_MOVED messages if the position hasn't really changed, and
+ * we ignore all MOUSE_MOVED messages where no button is held down (i.e.,
+ * we're only interested in MOUSE_DRAG).
+ *
+ * All of this is complicated by the code that fakes MOUSE_MIDDLE on
+ * 2-button mouses by pressing the left & right buttons simultaneously.
+ * In practice, it's almost impossible to click both at the same time,
+ * so we need to delay a little. Also, we tend not to get MOUSE_RELEASE
+ * in such cases, if the user is clicking quickly.
+ */
+ static BOOL
+decode_mouse_event(
+ MOUSE_EVENT_RECORD *pmer)
+{
+ static int s_nOldButton = -1;
+ static int s_nOldMouseClick = -1;
+ static int s_xOldMouse = -1;
+ static int s_yOldMouse = -1;
+ static linenr_T s_old_topline = 0;
+# ifdef FEAT_DIFF
+ static int s_old_topfill = 0;
+# endif
+ static int s_cClicks = 1;
+ static BOOL s_fReleased = TRUE;
+ static DWORD s_dwLastClickTime = 0;
+ static BOOL s_fNextIsMiddle = FALSE;
+
+ static DWORD cButtons = 0; // number of buttons supported
+
+ const DWORD LEFT = FROM_LEFT_1ST_BUTTON_PRESSED;
+ const DWORD MIDDLE = FROM_LEFT_2ND_BUTTON_PRESSED;
+ const DWORD RIGHT = RIGHTMOST_BUTTON_PRESSED;
+ const DWORD LEFT_RIGHT = LEFT | RIGHT;
+
+ int nButton;
+
+ if (cButtons == 0 && !GetNumberOfConsoleMouseButtons(&cButtons))
+ cButtons = 2;
+
+ if (!g_fMouseAvail || !g_fMouseActive)
+ {
+ g_nMouseClick = -1;
+ return FALSE;
+ }
+
+ // get a spurious MOUSE_EVENT immediately after receiving focus; ignore
+ if (g_fJustGotFocus)
+ {
+ g_fJustGotFocus = FALSE;
+ return FALSE;
+ }
+
+ // If there is an unprocessed mouse click drop this one.
+ if (g_nMouseClick != -1)
+ return TRUE;
+
+ if (pmer->dwEventFlags == MOUSE_WHEELED
+ || pmer->dwEventFlags == MOUSE_HWHEELED)
+ {
+ decode_mouse_wheel(pmer);
+ return TRUE; // we now should have a mouse scroll in g_nMouseClick
+ }
+
+ nButton = -1;
+ g_xMouse = pmer->dwMousePosition.X;
+ g_yMouse = pmer->dwMousePosition.Y;
+
+ if (pmer->dwEventFlags == MOUSE_MOVED)
+ {
+ // Ignore MOUSE_MOVED events if (x, y) hasn't changed. (We get these
+ // events even when the mouse moves only within a char cell.)
+ if (s_xOldMouse == g_xMouse && s_yOldMouse == g_yMouse)
+ return FALSE;
+ }
+
+ // If no buttons are pressed...
+ if ((pmer->dwButtonState & ((1 << cButtons) - 1)) == 0)
+ {
+ nButton = MOUSE_RELEASE;
+
+ // If the last thing returned was MOUSE_RELEASE, ignore this
+ if (s_fReleased)
+ {
+# ifdef FEAT_BEVAL_TERM
+ // do return mouse move events when we want them
+ if (p_bevalterm)
+ nButton = MOUSE_DRAG;
+ else
+# endif
+ return FALSE;
+ }
+
+ s_fReleased = TRUE;
+ }
+ else // one or more buttons pressed
+ {
+ // on a 2-button mouse, hold down left and right buttons
+ // simultaneously to get MIDDLE.
+
+ if (cButtons == 2 && s_nOldButton != MOUSE_DRAG)
+ {
+ DWORD dwLR = (pmer->dwButtonState & LEFT_RIGHT);
+
+ // if either left or right button only is pressed, see if the
+ // next mouse event has both of them pressed
+ if (dwLR == LEFT || dwLR == RIGHT)
+ {
+ for (;;)
+ {
+ // wait a short time for next input event
+ if (WaitForSingleObject(g_hConIn, p_mouset / 3)
+ != WAIT_OBJECT_0)
+ break;
+ else
+ {
+ DWORD cRecords = 0;
+ INPUT_RECORD ir;
+ MOUSE_EVENT_RECORD* pmer2 = &ir.Event.MouseEvent;
+
+ peek_console_input(g_hConIn, &ir, 1, &cRecords);
+
+ if (cRecords == 0 || ir.EventType != MOUSE_EVENT
+ || !(pmer2->dwButtonState & LEFT_RIGHT))
+ break;
+ else
+ {
+ if (pmer2->dwEventFlags != MOUSE_MOVED)
+ {
+ read_console_input(g_hConIn, &ir, 1, &cRecords);
+
+ return decode_mouse_event(pmer2);
+ }
+ else if (s_xOldMouse == pmer2->dwMousePosition.X &&
+ s_yOldMouse == pmer2->dwMousePosition.Y)
+ {
+ // throw away spurious mouse move
+ read_console_input(g_hConIn, &ir, 1, &cRecords);
+
+ // are there any more mouse events in queue?
+ peek_console_input(g_hConIn, &ir, 1, &cRecords);
+
+ if (cRecords==0 || ir.EventType != MOUSE_EVENT)
+ break;
+ }
+ else
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (s_fNextIsMiddle)
+ {
+ nButton = (pmer->dwEventFlags == MOUSE_MOVED)
+ ? MOUSE_DRAG : MOUSE_MIDDLE;
+ s_fNextIsMiddle = FALSE;
+ }
+ else if (cButtons == 2 &&
+ ((pmer->dwButtonState & LEFT_RIGHT) == LEFT_RIGHT))
+ {
+ nButton = MOUSE_MIDDLE;
+
+ if (! s_fReleased && pmer->dwEventFlags != MOUSE_MOVED)
+ {
+ s_fNextIsMiddle = TRUE;
+ nButton = MOUSE_RELEASE;
+ }
+ }
+ else if ((pmer->dwButtonState & LEFT) == LEFT)
+ nButton = MOUSE_LEFT;
+ else if ((pmer->dwButtonState & MIDDLE) == MIDDLE)
+ nButton = MOUSE_MIDDLE;
+ else if ((pmer->dwButtonState & RIGHT) == RIGHT)
+ nButton = MOUSE_RIGHT;
+
+ if (! s_fReleased && ! s_fNextIsMiddle
+ && nButton != s_nOldButton && s_nOldButton != MOUSE_DRAG)
+ return FALSE;
+
+ s_fReleased = s_fNextIsMiddle;
+ }
+
+ if (pmer->dwEventFlags == 0 || pmer->dwEventFlags == DOUBLE_CLICK)
+ {
+ // button pressed or released, without mouse moving
+ if (nButton != -1 && nButton != MOUSE_RELEASE)
+ {
+ DWORD dwCurrentTime = GetTickCount();
+
+ if (s_xOldMouse != g_xMouse
+ || s_yOldMouse != g_yMouse
+ || s_nOldButton != nButton
+ || s_old_topline != curwin->w_topline
+# ifdef FEAT_DIFF
+ || s_old_topfill != curwin->w_topfill
+# endif
+ || (int)(dwCurrentTime - s_dwLastClickTime) > p_mouset)
+ {
+ s_cClicks = 1;
+ }
+ else if (++s_cClicks > 4)
+ {
+ s_cClicks = 1;
+ }
+
+ s_dwLastClickTime = dwCurrentTime;
+ }
+ }
+ else if (pmer->dwEventFlags == MOUSE_MOVED)
+ {
+ if (nButton != -1 && nButton != MOUSE_RELEASE)
+ nButton = MOUSE_DRAG;
+
+ s_cClicks = 1;
+ }
+
+ if (nButton == -1)
+ return FALSE;
+
+ if (nButton != MOUSE_RELEASE)
+ s_nOldButton = nButton;
+
+ g_nMouseClick = nButton;
+
+ if (pmer->dwControlKeyState & SHIFT_PRESSED)
+ g_nMouseClick |= MOUSE_SHIFT;
+ if (pmer->dwControlKeyState & (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED))
+ g_nMouseClick |= MOUSE_CTRL;
+ if (pmer->dwControlKeyState & (RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED))
+ g_nMouseClick |= MOUSE_ALT;
+
+ if (nButton != MOUSE_DRAG && nButton != MOUSE_RELEASE)
+ SET_NUM_MOUSE_CLICKS(g_nMouseClick, s_cClicks);
+
+ // only pass on interesting (i.e., different) mouse events
+ if (s_xOldMouse == g_xMouse
+ && s_yOldMouse == g_yMouse
+ && s_nOldMouseClick == g_nMouseClick)
+ {
+ g_nMouseClick = -1;
+ return FALSE;
+ }
+
+ s_xOldMouse = g_xMouse;
+ s_yOldMouse = g_yMouse;
+ s_old_topline = curwin->w_topline;
+# ifdef FEAT_DIFF
+ s_old_topfill = curwin->w_topfill;
+# endif
+ s_nOldMouseClick = g_nMouseClick;
+
+ return TRUE;
+}
+
+# ifdef FEAT_EVAL
+ static int
+encode_mouse_event(dict_T *args, INPUT_RECORD *ir)
+{
+ int button;
+ int row;
+ int col;
+ int repeated_click;
+ int_u mods = 0;
+ int move;
+
+ if (!dict_has_key(args, "row") || !dict_has_key(args, "col"))
+ return FALSE;
+
+ // Note: "move" is optional, requires fewer arguments
+ move = (int)dict_get_bool(args, "move", FALSE);
+ if (!move && (!dict_has_key(args, "button")
+ || !dict_has_key(args, "multiclick")
+ || !dict_has_key(args, "modifiers")))
+ return FALSE;
+
+ row = (int)dict_get_number(args, "row") - 1;
+ col = (int)dict_get_number(args, "col") - 1;
+
+ ir->EventType = MOUSE_EVENT;
+ MOUSE_EVENT_RECORD mer;
+ ZeroMemory(&mer, sizeof(mer));
+ mer.dwMousePosition.X = col;
+ mer.dwMousePosition.Y = row;
+
+ if (move)
+ {
+ mer.dwButtonState = 0;
+ mer.dwEventFlags = MOUSE_MOVED;
+ }
+ else
+ {
+ button = (int)dict_get_number(args, "button");
+ repeated_click = (int)dict_get_number(args, "multiclick");
+ mods = (int)dict_get_number(args, "modifiers");
+ // Reset the scroll values to known values.
+ // XXX: Remove this when/if the scroll step is made configurable.
+ mouse_set_hor_scroll_step(6);
+ mouse_set_vert_scroll_step(3);
+
+ switch (button)
+ {
+ case MOUSE_LEFT:
+ mer.dwButtonState = FROM_LEFT_1ST_BUTTON_PRESSED;
+ mer.dwEventFlags = repeated_click == 1 ? DOUBLE_CLICK : 0;
+ break;
+ case MOUSE_MIDDLE:
+ mer.dwButtonState = FROM_LEFT_2ND_BUTTON_PRESSED;
+ mer.dwEventFlags = repeated_click == 1 ? DOUBLE_CLICK : 0;
+ break;
+ case MOUSE_RIGHT:
+ mer.dwButtonState = RIGHTMOST_BUTTON_PRESSED;
+ mer.dwEventFlags = repeated_click == 1 ? DOUBLE_CLICK : 0;
+ break;
+ case MOUSE_RELEASE:
+ // umm? Assume Left Release?
+ mer.dwEventFlags = 0;
+
+ case MOUSE_MOVE:
+ mer.dwButtonState = 0;
+ mer.dwEventFlags = MOUSE_MOVED;
+ break;
+ case MOUSE_X1:
+ mer.dwButtonState = FROM_LEFT_3RD_BUTTON_PRESSED;
+ break;
+ case MOUSE_X2:
+ mer.dwButtonState = FROM_LEFT_4TH_BUTTON_PRESSED;
+ break;
+ case MOUSE_4: // KE_MOUSEDOWN;
+ mer.dwButtonState = -1;
+ mer.dwEventFlags = MOUSE_WHEELED;
+ break;
+ case MOUSE_5: // KE_MOUSEUP;
+ mer.dwButtonState = +1;
+ mer.dwEventFlags = MOUSE_WHEELED;
+ break;
+ case MOUSE_6: // KE_MOUSELEFT;
+ mer.dwButtonState = -1;
+ mer.dwEventFlags = MOUSE_HWHEELED;
+ break;
+ case MOUSE_7: // KE_MOUSERIGHT;
+ mer.dwButtonState = +1;
+ mer.dwEventFlags = MOUSE_HWHEELED;
+ break;
+ default:
+ semsg(_(e_invalid_argument_str), "button");
+ return FALSE;
+ }
+ }
+
+ mer.dwControlKeyState = 0;
+ if (mods != 0)
+ {
+ // Encode the win32 console key modifiers from Vim MOUSE modifiers.
+ if (mods & MOUSE_SHIFT)
+ mer.dwControlKeyState |= SHIFT_PRESSED;
+ if (mods & MOUSE_CTRL)
+ mer.dwControlKeyState |= LEFT_CTRL_PRESSED;
+ if (mods & MOUSE_ALT)
+ mer.dwControlKeyState |= LEFT_ALT_PRESSED;
+ }
+ ir->Event.MouseEvent = mer;
+ return TRUE;
+}
+# endif // FEAT_EVAL
+
+ static int
+write_input_record_buffer(INPUT_RECORD* irEvents, int nLength)
+{
+ int nCount = 0;
+ while (nCount < nLength)
+ {
+ input_record_buffer.length++;
+ input_record_buffer_node_T *event_node =
+ malloc(sizeof(input_record_buffer_node_T));
+ event_node->ir = irEvents[nCount++];
+ event_node->next = NULL;
+ if (input_record_buffer.tail == NULL)
+ {
+ input_record_buffer.head = event_node;
+ input_record_buffer.tail = event_node;
+ }
+ else
+ {
+ input_record_buffer.tail->next = event_node;
+ input_record_buffer.tail = input_record_buffer.tail->next;
+ }
+ }
+ return nCount;
+}
+
+ static int
+read_input_record_buffer(INPUT_RECORD* irEvents, int nMaxLength)
+{
+ int nCount = 0;
+ while (nCount < nMaxLength && input_record_buffer.head != NULL)
+ {
+ input_record_buffer.length--;
+ input_record_buffer_node_T *pop_head = input_record_buffer.head;
+ irEvents[nCount++] = pop_head->ir;
+ input_record_buffer.head = pop_head->next;
+ vim_free(pop_head);
+ if (input_record_buffer.length == 0)
+ input_record_buffer.tail = NULL;
+ }
+ return nCount;
+}
+#endif // !FEAT_GUI_MSWIN || VIMDLL
+
+#ifdef FEAT_EVAL
+/*
+ * The 'test_mswin_event' function is for testing Vim's low-level handling of
+ * user input events. ie, this manages the encoding of INPUT_RECORD events
+ * so that we have a way to test how Vim decodes INPUT_RECORD events in Windows
+ * consoles.
+ *
+ * The 'test_mswin_event' function is based on 'test_gui_event'. In fact, when
+ * the Windows GUI is running, the arguments; 'event' and 'args', are the same.
+ * So, it acts as an alias for 'test_gui_event' for the Windows GUI.
+ *
+ * When the Windows console is running, the arguments; 'event' and 'args', are
+ * a subset of what 'test_gui_event' handles, ie, only "key" and "mouse"
+ * events are encoded as INPUT_RECORD events.
+ *
+ * Note: INPUT_RECORDs are only used by the Windows console, not the GUI. The
+ * GUI sends MSG structs instead.
+ */
+ int
+test_mswin_event(char_u *event, dict_T *args)
+{
+ int lpEventsWritten = 0;
+
+# if defined(VIMDLL) || defined(FEAT_GUI_MSWIN)
+ if (gui.in_use)
+ return test_gui_w32_sendevent(event, args);
+# endif
+
+# if defined(VIMDLL) || !defined(FEAT_GUI_MSWIN)
+
+// Currently implemented event record types are; KEY_EVENT and MOUSE_EVENT
+// Potentially could also implement: FOCUS_EVENT and WINDOW_BUFFER_SIZE_EVENT
+// Maybe also: MENU_EVENT
+
+ INPUT_RECORD ir;
+ BOOL input_encoded = FALSE;
+ BOOL execute = FALSE;
+ if (STRCMP(event, "key") == 0)
+ {
+ execute = dict_get_bool(args, "execute", FALSE);
+ if (dict_has_key(args, "event"))
+ input_encoded = encode_key_event(args, &ir);
+ else if (!execute)
+ {
+ semsg(_(e_missing_argument_str), "event");
+ return FALSE;
+ }
+ }
+ else if (STRCMP(event, "mouse") == 0)
+ {
+ execute = TRUE;
+ input_encoded = encode_mouse_event(args, &ir);
+ }
+ else
+ {
+ semsg(_(e_invalid_value_for_argument_str_str), "event", event);
+ return FALSE;
+ }
+
+ // Ideally, WriteConsoleInput would be used to inject these low-level
+ // events. But, this doesnt work well in the CI test environment. So
+ // implementing an input_record_buffer instead.
+ if (input_encoded)
+ lpEventsWritten = write_input_record_buffer(&ir, 1);
+
+ // Set flags to execute the event, ie. like feedkeys mode X.
+ if (execute)
+ {
+ int save_msg_scroll = msg_scroll;
+ // Avoid a 1 second delay when the keys start Insert mode.
+ msg_scroll = FALSE;
+ ch_log(NULL, "test_mswin_event() executing");
+ exec_normal(TRUE, TRUE, TRUE);
+ msg_scroll |= save_msg_scroll;
+ }
+
+# endif
+ return lpEventsWritten;
+}
+#endif // FEAT_EVAL
+
+#ifdef MCH_CURSOR_SHAPE
+/*
+ * Set the shape of the cursor.
+ * 'thickness' can be from 1 (thin) to 99 (block)
+ */
+ static void
+mch_set_cursor_shape(int thickness)
+{
+ if (vtp_working)
+ {
+ if (*T_CSI == NUL)
+ {
+ // If 't_SI' is not set, use the default cursor styles.
+ if (thickness < 50)
+ vtp_printf("\033[3 q"); // underline
+ else
+ vtp_printf("\033[0 q"); // default
+ }
+ }
+ else
+ {
+ CONSOLE_CURSOR_INFO ConsoleCursorInfo;
+ ConsoleCursorInfo.dwSize = thickness;
+ ConsoleCursorInfo.bVisible = s_cursor_visible;
+
+ SetConsoleCursorInfo(g_hConOut, &ConsoleCursorInfo);
+ if (s_cursor_visible)
+ SetConsoleCursorPosition(g_hConOut, g_coord);
+ }
+}
+
+ void
+mch_update_cursor(void)
+{
+ int idx;
+ int thickness;
+
+# ifdef VIMDLL
+ if (gui.in_use)
+ return;
+# endif
+
+ /*
+ * How the cursor is drawn depends on the current mode.
+ */
+ idx = get_shape_idx(FALSE);
+
+ if (shape_table[idx].shape == SHAPE_BLOCK)
+ thickness = 99; // 100 doesn't work on W95
+ else
+ thickness = shape_table[idx].percentage;
+ mch_set_cursor_shape(thickness);
+}
+#endif
+
+#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL)
+/*
+ * Handle FOCUS_EVENT.
+ */
+ static void
+handle_focus_event(INPUT_RECORD ir)
+{
+ g_fJustGotFocus = ir.Event.FocusEvent.bSetFocus;
+ ui_focus_change((int)g_fJustGotFocus);
+}
+
+static void ResizeConBuf(HANDLE hConsole, COORD coordScreen);
+
+/*
+ * Wait until console input from keyboard or mouse is available,
+ * or the time is up.
+ * When "ignore_input" is TRUE even wait when input is available.
+ * Return TRUE if something is available FALSE if not.
+ */
+ static int
+WaitForChar(long msec, int ignore_input)
+{
+ DWORD dwNow = 0, dwEndTime = 0;
+ INPUT_RECORD ir;
+ DWORD cRecords;
+ WCHAR ch, ch2;
+# ifdef FEAT_TIMERS
+ int tb_change_cnt = typebuf.tb_change_cnt;
+# endif
+
+ if (msec > 0)
+ // Wait until the specified time has elapsed.
+ dwEndTime = GetTickCount() + msec;
+ else if (msec < 0)
+ // Wait forever.
+ dwEndTime = INFINITE;
+
+ // We need to loop until the end of the time period, because
+ // we might get multiple unusable mouse events in that time.
+ for (;;)
+ {
+ // Only process messages when waiting.
+ if (msec != 0)
+ {
+# ifdef MESSAGE_QUEUE
+ parse_queued_messages();
+# endif
+# ifdef FEAT_MZSCHEME
+ mzvim_check_threads();
+# endif
+# ifdef FEAT_CLIENTSERVER
+ serverProcessPendingMessages();
+# endif
+ }
+
+ if (g_nMouseClick != -1
+# ifdef FEAT_CLIENTSERVER
+ || (!ignore_input && input_available())
+# endif
+ )
+ return TRUE;
+
+ if (msec > 0)
+ {
+ // If the specified wait time has passed, return. Beware that
+ // GetTickCount() may wrap around (overflow).
+ dwNow = GetTickCount();
+ if ((int)(dwNow - dwEndTime) >= 0)
+ break;
+ }
+ if (msec != 0)
+ {
+ DWORD dwWaitTime = dwEndTime - dwNow;
+
+ // Don't wait for more than 11 msec to avoid dropping characters,
+ // check channel while waiting for input and handle a callback from
+ // 'balloonexpr'.
+ if (dwWaitTime > 11)
+ dwWaitTime = 11;
+
+# ifdef FEAT_MZSCHEME
+ if (mzthreads_allowed() && p_mzq > 0 && (long)dwWaitTime > p_mzq)
+ dwWaitTime = p_mzq; // don't wait longer than 'mzquantum'
+# endif
+# ifdef FEAT_TIMERS
+ // When waiting very briefly don't trigger timers.
+ if (dwWaitTime > 10)
+ {
+ long due_time;
+
+ // Trigger timers and then get the time in msec until the next
+ // one is due. Wait up to that time.
+ due_time = check_due_timer();
+ if (typebuf.tb_change_cnt != tb_change_cnt)
+ {
+ // timer may have used feedkeys().
+ return FALSE;
+ }
+ if (due_time > 0 && dwWaitTime > (DWORD)due_time)
+ dwWaitTime = due_time;
+ }
+# endif
+ if (
+# ifdef FEAT_CLIENTSERVER
+ // Wait for either an event on the console input or a
+ // message in the client-server window.
+ msg_wait_for_multiple_objects(1, &g_hConIn, FALSE,
+ dwWaitTime, QS_SENDMESSAGE) != WAIT_OBJECT_0
+# else
+ wait_for_single_object(g_hConIn, dwWaitTime)
+ != WAIT_OBJECT_0
+# endif
+ )
+ continue;
+ }
+
+ cRecords = 0;
+ peek_console_input(g_hConIn, &ir, 1, &cRecords);
+
+# ifdef FEAT_MBYTE_IME
+ // May have to redraw if the cursor ends up in the wrong place.
+ // Only when not peeking.
+ if (State == MODE_CMDLINE && msg_row == Rows - 1 && msec != 0)
+ {
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+
+ if (GetConsoleScreenBufferInfo(g_hConOut, &csbi))
+ {
+ if (csbi.dwCursorPosition.Y != msg_row)
+ {
+ // The screen is now messed up, must redraw the command
+ // line and later all the windows.
+ redraw_all_later(UPD_CLEAR);
+ compute_cmdrow();
+ redrawcmd();
+ }
+ }
+ }
+# endif
+
+ if (cRecords > 0)
+ {
+ if (ir.EventType == KEY_EVENT && ir.Event.KeyEvent.bKeyDown)
+ {
+# ifdef FEAT_MBYTE_IME
+ // Windows IME sends two '\n's with only one 'ENTER'. First:
+ // wVirtualKeyCode == 13. second: wVirtualKeyCode == 0
+ if (ir.Event.KeyEvent.uChar.UnicodeChar == 0
+ && ir.Event.KeyEvent.wVirtualKeyCode == 13)
+ {
+ read_console_input(g_hConIn, &ir, 1, &cRecords);
+ continue;
+ }
+# endif
+ if (decode_key_event(&ir.Event.KeyEvent, &ch, &ch2,
+ NULL, FALSE))
+ return TRUE;
+ }
+
+ read_console_input(g_hConIn, &ir, 1, &cRecords);
+
+ if (ir.EventType == FOCUS_EVENT)
+ handle_focus_event(ir);
+ else if (ir.EventType == WINDOW_BUFFER_SIZE_EVENT)
+ {
+ COORD dwSize = ir.Event.WindowBufferSizeEvent.dwSize;
+
+ // Only call shell_resized() when the size actually changed to
+ // avoid the screen is cleared.
+ if (dwSize.X != Columns || dwSize.Y != Rows)
+ {
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+ GetConsoleScreenBufferInfo(g_hConOut, &csbi);
+ dwSize.X = csbi.srWindow.Right - csbi.srWindow.Left + 1;
+ dwSize.Y = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
+ if (dwSize.X != Columns || dwSize.Y != Rows)
+ {
+ ResizeConBuf(g_hConOut, dwSize);
+ shell_resized();
+ }
+ }
+ }
+ else if (ir.EventType == MOUSE_EVENT
+ && decode_mouse_event(&ir.Event.MouseEvent))
+ return TRUE;
+ }
+ else if (msec == 0)
+ break;
+ }
+
+# ifdef FEAT_CLIENTSERVER
+ // Something might have been received while we were waiting.
+ if (input_available())
+ return TRUE;
+# endif
+
+ return FALSE;
+}
+
+/*
+ * return non-zero if a character is available
+ */
+ int
+mch_char_avail(void)
+{
+# ifdef VIMDLL
+ if (gui.in_use)
+ return TRUE;
+# endif
+ return WaitForChar(0L, FALSE);
+}
+
+# if defined(FEAT_TERMINAL) || defined(PROTO)
+/*
+ * Check for any pending input or messages.
+ */
+ int
+mch_check_messages(void)
+{
+# ifdef VIMDLL
+ if (gui.in_use)
+ return TRUE;
+# endif
+ return WaitForChar(0L, TRUE);
+}
+# endif
+
+/*
+ * Create the console input. Used when reading stdin doesn't work.
+ */
+ static void
+create_conin(void)
+{
+ g_hConIn = CreateFile("CONIN$", GENERIC_READ|GENERIC_WRITE,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ (LPSECURITY_ATTRIBUTES) NULL,
+ OPEN_EXISTING, 0, (HANDLE)NULL);
+ did_create_conin = TRUE;
+}
+
+/*
+ * Get a keystroke or a mouse event, use a blocking wait.
+ */
+ static WCHAR
+tgetch(int *pmodifiers, WCHAR *pch2)
+{
+ WCHAR ch;
+
+ for (;;)
+ {
+ INPUT_RECORD ir;
+ DWORD cRecords = 0;
+
+# ifdef FEAT_CLIENTSERVER
+ (void)WaitForChar(-1L, FALSE);
+ if (input_available())
+ return 0;
+ if (g_nMouseClick != -1)
+ return 0;
+# endif
+ if (read_console_input(g_hConIn, &ir, 1, &cRecords) == 0)
+ {
+ if (did_create_conin)
+ read_error_exit();
+ create_conin();
+ continue;
+ }
+
+ if (ir.EventType == KEY_EVENT)
+ {
+ if (decode_key_event(&ir.Event.KeyEvent, &ch, pch2,
+ pmodifiers, TRUE))
+ return ch;
+ }
+ else if (ir.EventType == FOCUS_EVENT)
+ handle_focus_event(ir);
+ else if (ir.EventType == WINDOW_BUFFER_SIZE_EVENT)
+ shell_resized();
+ else if (ir.EventType == MOUSE_EVENT)
+ {
+ if (decode_mouse_event(&ir.Event.MouseEvent))
+ return 0;
+ }
+ }
+}
+#endif // !FEAT_GUI_MSWIN
+
+
+/*
+ * mch_inchar(): low-level input function.
+ * Get one or more characters from the keyboard or the mouse.
+ * If time == 0, do not wait for characters.
+ * If time == n, wait a short time for characters.
+ * If time == -1, wait forever for characters.
+ * Returns the number of characters read into buf.
+ */
+ int
+mch_inchar(
+ char_u *buf UNUSED,
+ int maxlen UNUSED,
+ long time UNUSED,
+ int tb_change_cnt UNUSED)
+{
+#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL)
+
+ int len;
+ int c;
+# ifdef VIMDLL
+// Extra space for maximum three CSIs. E.g. U+1B6DB -> 0xF0 0x9B 0x9B 0x9B.
+# define TYPEAHEADSPACE 6
+# else
+# define TYPEAHEADSPACE 0
+# endif
+# define TYPEAHEADLEN (20 + TYPEAHEADSPACE)
+ static char_u typeahead[TYPEAHEADLEN]; // previously typed bytes.
+ static int typeaheadlen = 0;
+
+# ifdef VIMDLL
+ if (gui.in_use)
+ return 0;
+# endif
+
+ // First use any typeahead that was kept because "buf" was too small.
+ if (typeaheadlen > 0)
+ goto theend;
+
+ if (time >= 0)
+ {
+ if (!WaitForChar(time, FALSE)) // no character available
+ return 0;
+ }
+ else // time == -1, wait forever
+ {
+ mch_set_winsize_now(); // Allow winsize changes from now on
+
+ /*
+ * If there is no character available within 2 seconds (default)
+ * write the autoscript file to disk. Or cause the CursorHold event
+ * to be triggered.
+ */
+ if (!WaitForChar(p_ut, FALSE))
+ {
+ if (trigger_cursorhold() && maxlen >= 3)
+ {
+ buf[0] = K_SPECIAL;
+ buf[1] = KS_EXTRA;
+ buf[2] = (int)KE_CURSORHOLD;
+ return 3;
+ }
+ before_blocking();
+ }
+ }
+
+ /*
+ * Try to read as many characters as there are, until the buffer is full.
+ */
+
+ // we will get at least one key. Get more if they are available.
+ g_fCBrkPressed = FALSE;
+
+# ifdef MCH_WRITE_DUMP
+ if (fdDump)
+ fputc('[', fdDump);
+# endif
+
+ // Keep looping until there is something in the typeahead buffer and more
+ // to get and still room in the buffer (up to two bytes for a char and
+ // three bytes for a modifier).
+ while ((typeaheadlen == 0 || WaitForChar(0L, FALSE))
+ && typeaheadlen + 5 + TYPEAHEADSPACE <= TYPEAHEADLEN)
+ {
+ if (typebuf_changed(tb_change_cnt))
+ {
+ // "buf" may be invalid now if a client put something in the
+ // typeahead buffer and "buf" is in the typeahead buffer.
+ typeaheadlen = 0;
+ break;
+ }
+ if (g_nMouseClick != -1)
+ {
+# ifdef MCH_WRITE_DUMP
+ if (fdDump)
+ fprintf(fdDump, "{%02x @ %d, %d}",
+ g_nMouseClick, g_xMouse, g_yMouse);
+# endif
+ char_u modifiers = ((char_u *)(&g_nMouseClick))[0];
+ char_u scroll_dir = ((char_u *)(&g_nMouseClick))[1];
+
+ if (scroll_dir == KE_MOUSEDOWN
+ || scroll_dir == KE_MOUSEUP
+ || scroll_dir == KE_MOUSELEFT
+ || scroll_dir == KE_MOUSERIGHT)
+ {
+ if (modifiers > 0)
+ {
+ // use K_SPECIAL instead of CSI to make mappings work
+ typeahead[typeaheadlen++] = K_SPECIAL;
+ typeahead[typeaheadlen++] = KS_MODIFIER;
+ typeahead[typeaheadlen++] = modifiers;
+ }
+ typeahead[typeaheadlen++] = CSI;
+ typeahead[typeaheadlen++] = KS_EXTRA;
+ typeahead[typeaheadlen++] = scroll_dir;
+ }
+ else
+ {
+ typeahead[typeaheadlen++] = ESC + 128;
+ typeahead[typeaheadlen++] = 'M';
+ typeahead[typeaheadlen++] = g_nMouseClick;
+ }
+
+ // Pass the pointer coordinates of the mouse event in 2 bytes,
+ // allowing for > 223 columns. Both for click and scroll events.
+ // This is the same as what is used for the GUI.
+ typeahead[typeaheadlen++] = (char_u)(g_xMouse / 128 + ' ' + 1);
+ typeahead[typeaheadlen++] = (char_u)(g_xMouse % 128 + ' ' + 1);
+ typeahead[typeaheadlen++] = (char_u)(g_yMouse / 128 + ' ' + 1);
+ typeahead[typeaheadlen++] = (char_u)(g_yMouse % 128 + ' ' + 1);
+
+ g_nMouseClick = -1;
+ }
+ else
+ {
+ WCHAR ch2 = NUL;
+ int modifiers = 0;
+
+ c = tgetch(&modifiers, &ch2);
+
+ c = simplify_key(c, &modifiers);
+
+ // Some chars need adjustment when the Ctrl modifier is used.
+ ++no_reduce_keys;
+ c = may_adjust_key_for_ctrl(modifiers, c);
+ --no_reduce_keys;
+
+ // remove the SHIFT modifier for keys where it's already included,
+ // e.g., '(' and '*'
+ modifiers = may_remove_shift_modifier(modifiers, c);
+
+ if (typebuf_changed(tb_change_cnt))
+ {
+ // "buf" may be invalid now if a client put something in the
+ // typeahead buffer and "buf" is in the typeahead buffer.
+ typeaheadlen = 0;
+ break;
+ }
+
+ if (c == Ctrl_C && ctrl_c_interrupts)
+ {
+# if defined(FEAT_CLIENTSERVER)
+ trash_input_buf();
+# endif
+ got_int = TRUE;
+ }
+
+ if (g_nMouseClick == -1)
+ {
+ int n = 1;
+
+ if (ch2 == NUL)
+ {
+ int i, j;
+ char_u *p;
+ WCHAR ch[2];
+
+ ch[0] = c;
+ if (c >= 0xD800 && c <= 0xDBFF) // High surrogate
+ {
+ ch[1] = tgetch(&modifiers, &ch2);
+ n++;
+ }
+ p = utf16_to_enc(ch, &n);
+ if (p != NULL)
+ {
+ for (i = 0, j = 0; i < n; i++)
+ {
+ typeahead[typeaheadlen + j++] = p[i];
+# ifdef VIMDLL
+ if (p[i] == CSI)
+ {
+ typeahead[typeaheadlen + j++] = KS_EXTRA;
+ typeahead[typeaheadlen + j++] = KE_CSI;
+ }
+# endif
+ }
+ n = j;
+ vim_free(p);
+ }
+ }
+ else
+ {
+ typeahead[typeaheadlen] = c;
+# ifdef VIMDLL
+ if (c == CSI)
+ {
+ typeahead[typeaheadlen + 1] = KS_EXTRA;
+ typeahead[typeaheadlen + 2] = KE_CSI;
+ n = 3;
+ }
+# endif
+ }
+ if (ch2 != NUL)
+ {
+ if (c == K_NUL)
+ {
+ switch (ch2)
+ {
+ case (WCHAR)'\324': // SHIFT+Insert
+ case (WCHAR)'\325': // CTRL+Insert
+ case (WCHAR)'\327': // SHIFT+Delete
+ case (WCHAR)'\330': // CTRL+Delete
+ typeahead[typeaheadlen + n] = (char_u)ch2;
+ n++;
+ break;
+
+ default:
+ typeahead[typeaheadlen + n] = 3;
+ typeahead[typeaheadlen + n + 1] = (char_u)ch2;
+ n += 2;
+ break;
+ }
+ }
+ else
+ {
+ typeahead[typeaheadlen + n] = 3;
+ typeahead[typeaheadlen + n + 1] = (char_u)ch2;
+ n += 2;
+ }
+ }
+
+ // Use the ALT key to set the 8th bit of the character
+ // when it's one byte, the 8th bit isn't set yet and not
+ // using a double-byte encoding (would become a lead
+ // byte).
+ if ((modifiers & MOD_MASK_ALT)
+ && n == 1
+ && (typeahead[typeaheadlen] & 0x80) == 0
+ && !enc_dbcs
+ )
+ {
+ n = (*mb_char2bytes)(typeahead[typeaheadlen] | 0x80,
+ typeahead + typeaheadlen);
+ modifiers &= ~MOD_MASK_ALT;
+ }
+
+ if (modifiers != 0)
+ {
+ // Prepend modifiers to the character.
+ mch_memmove(typeahead + typeaheadlen + 3,
+ typeahead + typeaheadlen, n);
+ typeahead[typeaheadlen++] = K_SPECIAL;
+ typeahead[typeaheadlen++] = (char_u)KS_MODIFIER;
+ typeahead[typeaheadlen++] = modifiers;
+ }
+
+ typeaheadlen += n;
+
+# ifdef MCH_WRITE_DUMP
+ if (fdDump)
+ fputc(c, fdDump);
+# endif
+ }
+ }
+ }
+
+# ifdef MCH_WRITE_DUMP
+ if (fdDump)
+ {
+ fputs("]\n", fdDump);
+ fflush(fdDump);
+ }
+# endif
+
+theend:
+ // Move typeahead to "buf", as much as fits.
+ len = 0;
+ while (len < maxlen && typeaheadlen > 0)
+ {
+ buf[len++] = typeahead[0];
+ mch_memmove(typeahead, typeahead + 1, --typeaheadlen);
+ }
+# ifdef FEAT_EVAL
+ if (len > 0)
+ {
+ buf[len] = NUL;
+ ch_log(NULL, "raw key input: \"%s\"", buf);
+ }
+# endif
+ return len;
+
+#else // FEAT_GUI_MSWIN
+ return 0;
+#endif // FEAT_GUI_MSWIN
+}
+
+#ifndef PROTO
+# ifndef __MINGW32__
+# include <shellapi.h> // required for FindExecutable()
+# endif
+#endif
+
+/*
+ * Return TRUE if "name" is an executable file, FALSE if not or it doesn't exist.
+ * When returning TRUE and "path" is not NULL save the path and set "*path" to
+ * the allocated memory.
+ * TODO: Should somehow check if it's really executable.
+ */
+ static int
+executable_file(char *name, char_u **path)
+{
+ int attrs = win32_getattrs((char_u *)name);
+
+ // The file doesn't exist or is a folder.
+ if (attrs == -1 || (attrs & FILE_ATTRIBUTE_DIRECTORY))
+ return FALSE;
+ // Check if the file is an AppExecLink, a special alias used by Windows
+ // Store for its apps.
+ if (attrs & FILE_ATTRIBUTE_REPARSE_POINT)
+ {
+ char_u *res = resolve_appexeclink((char_u *)name);
+ if (res == NULL)
+ return FALSE;
+ // The path is already absolute.
+ if (path != NULL)
+ *path = res;
+ else
+ vim_free(res);
+ }
+ else if (path != NULL)
+ *path = FullName_save((char_u *)name, FALSE);
+ return TRUE;
+}
+
+/*
+ * If "use_path" is TRUE: Return TRUE if "name" is in $PATH.
+ * If "use_path" is FALSE: Return TRUE if "name" exists.
+ * If "use_pathext" is TRUE search "name" with extensions in $PATHEXT.
+ * When returning TRUE and "path" is not NULL save the path and set "*path" to
+ * the allocated memory.
+ */
+ static int
+executable_exists(char *name, char_u **path, int use_path, int use_pathext)
+{
+ // WinNT and later can use _MAX_PATH wide characters for a pathname, which
+ // means that the maximum pathname is _MAX_PATH * 3 bytes when 'enc' is
+ // UTF-8.
+ char_u buf[_MAX_PATH * 3];
+ size_t len = STRLEN(name);
+ size_t tmplen;
+ char_u *p, *e, *e2;
+ char_u *pathbuf = NULL;
+ char_u *pathext = NULL;
+ char_u *pathextbuf = NULL;
+ char_u *shname = NULL;
+ int noext = FALSE;
+ int retval = FALSE;
+
+ if (len >= sizeof(buf)) // safety check
+ return FALSE;
+
+ // Using the name directly when a Unix-shell like 'shell'.
+ shname = gettail(p_sh);
+ if (strstr((char *)shname, "sh") != NULL &&
+ !(strstr((char *)shname, "powershell") != NULL
+ || strstr((char *)shname, "pwsh") != NULL))
+ noext = TRUE;
+
+ if (use_pathext)
+ {
+ pathext = mch_getenv("PATHEXT");
+ if (pathext == NULL)
+ pathext = (char_u *)".com;.exe;.bat;.cmd";
+
+ if (noext == FALSE)
+ {
+ /*
+ * Loop over all extensions in $PATHEXT.
+ * Check "name" ends with extension.
+ */
+ p = pathext;
+ while (*p)
+ {
+ if (p[0] == ';'
+ || (p[0] == '.' && (p[1] == NUL || p[1] == ';')))
+ {
+ // Skip empty or single ".".
+ ++p;
+ continue;
+ }
+ e = vim_strchr(p, ';');
+ if (e == NULL)
+ e = p + STRLEN(p);
+ tmplen = e - p;
+
+ if (_strnicoll(name + len - tmplen, (char *)p, tmplen) == 0)
+ {
+ noext = TRUE;
+ break;
+ }
+
+ p = e;
+ }
+ }
+ }
+
+ // Prepend single "." to pathext, it means no extension added.
+ if (pathext == NULL)
+ pathext = (char_u *)".";
+ else if (noext == TRUE)
+ {
+ if (pathextbuf == NULL)
+ pathextbuf = alloc(STRLEN(pathext) + 3);
+ if (pathextbuf == NULL)
+ {
+ retval = FALSE;
+ goto theend;
+ }
+ STRCPY(pathextbuf, ".;");
+ STRCAT(pathextbuf, pathext);
+ pathext = pathextbuf;
+ }
+
+ // Use $PATH when "use_path" is TRUE and "name" is basename.
+ if (use_path && gettail((char_u *)name) == (char_u *)name)
+ {
+ p = mch_getenv("PATH");
+ if (p != NULL)
+ {
+ pathbuf = alloc(STRLEN(p) + 3);
+ if (pathbuf == NULL)
+ {
+ retval = FALSE;
+ goto theend;
+ }
+
+ if (mch_getenv("NoDefaultCurrentDirectoryInExePath") == NULL)
+ STRCPY(pathbuf, ".;");
+ else
+ *pathbuf = NUL;
+ STRCAT(pathbuf, p);
+ }
+ }
+
+ /*
+ * Walk through all entries in $PATH to check if "name" exists there and
+ * is an executable file.
+ */
+ p = (pathbuf != NULL) ? pathbuf : (char_u *)".";
+ while (*p)
+ {
+ if (*p == ';') // Skip empty entry
+ {
+ ++p;
+ continue;
+ }
+ e = vim_strchr(p, ';');
+ if (e == NULL)
+ e = p + STRLEN(p);
+
+ if (e - p + len + 2 > sizeof(buf))
+ {
+ retval = FALSE;
+ goto theend;
+ }
+ // A single "." that means current dir.
+ if (e - p == 1 && *p == '.')
+ STRCPY(buf, name);
+ else
+ {
+ vim_strncpy(buf, p, e - p);
+ add_pathsep(buf);
+ STRCAT(buf, name);
+ }
+ tmplen = STRLEN(buf);
+
+ /*
+ * Loop over all extensions in $PATHEXT.
+ * Check "name" with extension added.
+ */
+ p = pathext;
+ while (*p)
+ {
+ if (*p == ';')
+ {
+ // Skip empty entry
+ ++p;
+ continue;
+ }
+ e2 = vim_strchr(p, (int)';');
+ if (e2 == NULL)
+ e2 = p + STRLEN(p);
+
+ if (!(p[0] == '.' && (p[1] == NUL || p[1] == ';')))
+ {
+ // Not a single "." that means no extension is added.
+ if (e2 - p + tmplen + 1 > sizeof(buf))
+ {
+ retval = FALSE;
+ goto theend;
+ }
+ vim_strncpy(buf + tmplen, p, e2 - p);
+ }
+ if (executable_file((char *)buf, path))
+ {
+ retval = TRUE;
+ goto theend;
+ }
+
+ p = e2;
+ }
+
+ p = e;
+ }
+
+theend:
+ free(pathextbuf);
+ free(pathbuf);
+ return retval;
+}
+
+#if (defined(__MINGW32__) && __MSVCRT_VERSION__ >= 0x800) || \
+ (defined(_MSC_VER) && _MSC_VER >= 1400)
+/*
+ * Bad parameter handler.
+ *
+ * Certain MS CRT functions will intentionally crash when passed invalid
+ * parameters to highlight possible security holes. Setting this function as
+ * the bad parameter handler will prevent the crash.
+ *
+ * In debug builds the parameters contain CRT information that might help track
+ * down the source of a problem, but in non-debug builds the arguments are all
+ * NULL/0. Debug builds will also produce assert dialogs from the CRT, it is
+ * worth allowing these to make debugging of issues easier.
+ */
+ static void
+bad_param_handler(const wchar_t *expression UNUSED,
+ const wchar_t *function UNUSED,
+ const wchar_t *file UNUSED,
+ unsigned int line UNUSED,
+ uintptr_t pReserved UNUSED)
+{
+}
+
+# define SET_INVALID_PARAM_HANDLER \
+ ((void)_set_invalid_parameter_handler(bad_param_handler))
+#else
+# define SET_INVALID_PARAM_HANDLER
+#endif
+
+#ifdef FEAT_GUI_MSWIN
+
+/*
+ * GUI version of mch_init().
+ */
+ static void
+mch_init_g(void)
+{
+# ifndef __MINGW32__
+ extern int _fmode;
+# endif
+
+ // Silently handle invalid parameters to CRT functions
+ SET_INVALID_PARAM_HANDLER;
+
+ // Let critical errors result in a failure, not in a dialog box. Required
+ // for the timestamp test to work on removed floppies.
+ SetErrorMode(SEM_FAILCRITICALERRORS);
+
+ _fmode = O_BINARY; // we do our own CR-LF translation
+
+ // Specify window size. Is there a place to get the default from?
+ Rows = 25;
+ Columns = 80;
+
+ // Look for 'vimrun'
+ {
+ char_u vimrun_location[_MAX_PATH + 4];
+
+ // First try in same directory as gvim.exe
+ STRCPY(vimrun_location, exe_name);
+ STRCPY(gettail(vimrun_location), "vimrun.exe");
+ if (mch_getperm(vimrun_location) >= 0)
+ {
+ if (*skiptowhite(vimrun_location) != NUL)
+ {
+ // Enclose path with white space in double quotes.
+ mch_memmove(vimrun_location + 1, vimrun_location,
+ STRLEN(vimrun_location) + 1);
+ *vimrun_location = '"';
+ STRCPY(gettail(vimrun_location), "vimrun\" ");
+ }
+ else
+ STRCPY(gettail(vimrun_location), "vimrun ");
+
+ vimrun_path = (char *)vim_strsave(vimrun_location);
+ s_dont_use_vimrun = FALSE;
+ }
+ else if (executable_exists("vimrun.exe", NULL, TRUE, FALSE))
+ s_dont_use_vimrun = FALSE;
+
+ // Don't give the warning for a missing vimrun.exe right now, but only
+ // when vimrun was supposed to be used. Don't bother people that do
+ // not need vimrun.exe.
+ if (s_dont_use_vimrun)
+ need_vimrun_warning = TRUE;
+ }
+
+ /*
+ * If "finstr.exe" doesn't exist, use "grep -n" for 'grepprg'.
+ * Otherwise the default "findstr /n" is used.
+ */
+ if (!executable_exists("findstr.exe", NULL, TRUE, FALSE))
+ set_option_value_give_err((char_u *)"grepprg",
+ 0, (char_u *)"grep -n", 0);
+
+# ifdef FEAT_CLIPBOARD
+ win_clip_init();
+# endif
+
+ vtp_flag_init();
+}
+
+
+#endif // FEAT_GUI_MSWIN
+
+#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL)
+
+# define SRWIDTH(sr) ((sr).Right - (sr).Left + 1)
+# define SRHEIGHT(sr) ((sr).Bottom - (sr).Top + 1)
+
+/*
+ * ClearConsoleBuffer()
+ * Description:
+ * Clears the entire contents of the console screen buffer, using the
+ * specified attribute.
+ * Returns:
+ * TRUE on success
+ */
+ static BOOL
+ClearConsoleBuffer(WORD wAttribute)
+{
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+ COORD coord;
+ DWORD NumCells, dummy;
+
+ if (!GetConsoleScreenBufferInfo(g_hConOut, &csbi))
+ return FALSE;
+
+ NumCells = csbi.dwSize.X * csbi.dwSize.Y;
+ coord.X = 0;
+ coord.Y = 0;
+ if (!FillConsoleOutputCharacter(g_hConOut, ' ', NumCells,
+ coord, &dummy))
+ return FALSE;
+ if (!FillConsoleOutputAttribute(g_hConOut, wAttribute, NumCells,
+ coord, &dummy))
+ return FALSE;
+
+ return TRUE;
+}
+
+/*
+ * FitConsoleWindow()
+ * Description:
+ * Checks if the console window will fit within given buffer dimensions.
+ * Also, if requested, will shrink the window to fit.
+ * Returns:
+ * TRUE on success
+ */
+ static BOOL
+FitConsoleWindow(
+ COORD dwBufferSize,
+ BOOL WantAdjust)
+{
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+ COORD dwWindowSize;
+ BOOL NeedAdjust = FALSE;
+
+ if (!GetConsoleScreenBufferInfo(g_hConOut, &csbi))
+ return FALSE;
+
+ /*
+ * A buffer resize will fail if the current console window does
+ * not lie completely within that buffer. To avoid this, we might
+ * have to move and possibly shrink the window.
+ */
+ if (csbi.srWindow.Right >= dwBufferSize.X)
+ {
+ dwWindowSize.X = SRWIDTH(csbi.srWindow);
+ if (dwWindowSize.X > dwBufferSize.X)
+ dwWindowSize.X = dwBufferSize.X;
+ csbi.srWindow.Right = dwBufferSize.X - 1;
+ csbi.srWindow.Left = dwBufferSize.X - dwWindowSize.X;
+ NeedAdjust = TRUE;
+ }
+ if (csbi.srWindow.Bottom >= dwBufferSize.Y)
+ {
+ dwWindowSize.Y = SRHEIGHT(csbi.srWindow);
+ if (dwWindowSize.Y > dwBufferSize.Y)
+ dwWindowSize.Y = dwBufferSize.Y;
+ csbi.srWindow.Bottom = dwBufferSize.Y - 1;
+ csbi.srWindow.Top = dwBufferSize.Y - dwWindowSize.Y;
+ NeedAdjust = TRUE;
+ }
+ if (NeedAdjust && WantAdjust)
+ {
+ if (!SetConsoleWindowInfo(g_hConOut, TRUE, &csbi.srWindow))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+typedef struct ConsoleBufferStruct
+{
+ BOOL IsValid;
+ CONSOLE_SCREEN_BUFFER_INFO Info;
+ PCHAR_INFO Buffer;
+ COORD BufferSize;
+ PSMALL_RECT Regions;
+ int NumRegions;
+} ConsoleBuffer;
+
+/*
+ * SaveConsoleBuffer()
+ * Description:
+ * Saves important information about the console buffer, including the
+ * actual buffer contents. The saved information is suitable for later
+ * restoration by RestoreConsoleBuffer().
+ * Returns:
+ * TRUE if all information was saved; FALSE otherwise
+ * If FALSE, still sets cb->IsValid if buffer characteristics were saved.
+ */
+ static BOOL
+SaveConsoleBuffer(
+ ConsoleBuffer *cb)
+{
+ DWORD NumCells;
+ COORD BufferCoord;
+ SMALL_RECT ReadRegion;
+ WORD Y, Y_incr;
+ int i, numregions;
+
+ if (cb == NULL)
+ return FALSE;
+
+ if (!GetConsoleScreenBufferInfo(g_hConOut, &cb->Info))
+ {
+ cb->IsValid = FALSE;
+ return FALSE;
+ }
+ cb->IsValid = TRUE;
+
+ // VTP uses alternate screen buffer.
+ // No need to save buffer contents for restoration.
+ if (use_alternate_screen_buffer)
+ return TRUE;
+
+ /*
+ * Allocate a buffer large enough to hold the entire console screen
+ * buffer. If this ConsoleBuffer structure has already been initialized
+ * with a buffer of the correct size, then just use that one.
+ */
+ if (!cb->IsValid || cb->Buffer == NULL ||
+ cb->BufferSize.X != cb->Info.dwSize.X ||
+ cb->BufferSize.Y != cb->Info.dwSize.Y)
+ {
+ cb->BufferSize.X = cb->Info.dwSize.X;
+ cb->BufferSize.Y = cb->Info.dwSize.Y;
+ NumCells = cb->BufferSize.X * cb->BufferSize.Y;
+ vim_free(cb->Buffer);
+ cb->Buffer = ALLOC_MULT(CHAR_INFO, NumCells);
+ if (cb->Buffer == NULL)
+ return FALSE;
+ }
+
+ /*
+ * We will now copy the console screen buffer into our buffer.
+ * ReadConsoleOutput() seems to be limited as far as how much you
+ * can read at a time. Empirically, this number seems to be about
+ * 12000 cells (rows * columns). Start at position (0, 0) and copy
+ * in chunks until it is all copied. The chunks will all have the
+ * same horizontal characteristics, so initialize them now. The
+ * height of each chunk will be (12000 / width).
+ */
+ BufferCoord.X = 0;
+ ReadRegion.Left = 0;
+ ReadRegion.Right = cb->Info.dwSize.X - 1;
+ Y_incr = 12000 / cb->Info.dwSize.X;
+
+ numregions = (cb->Info.dwSize.Y + Y_incr - 1) / Y_incr;
+ if (cb->Regions == NULL || numregions != cb->NumRegions)
+ {
+ cb->NumRegions = numregions;
+ vim_free(cb->Regions);
+ cb->Regions = ALLOC_MULT(SMALL_RECT, cb->NumRegions);
+ if (cb->Regions == NULL)
+ {
+ VIM_CLEAR(cb->Buffer);
+ return FALSE;
+ }
+ }
+
+ for (i = 0, Y = 0; i < cb->NumRegions; i++, Y += Y_incr)
+ {
+ /*
+ * Read into position (0, Y) in our buffer.
+ */
+ BufferCoord.Y = Y;
+ /*
+ * Read the region whose top left corner is (0, Y) and whose bottom
+ * right corner is (width - 1, Y + Y_incr - 1). This should define
+ * a region of size width by Y_incr. Don't worry if this region is
+ * too large for the remaining buffer; it will be cropped.
+ */
+ ReadRegion.Top = Y;
+ ReadRegion.Bottom = Y + Y_incr - 1;
+ if (!ReadConsoleOutputW(g_hConOut, // output handle
+ cb->Buffer, // our buffer
+ cb->BufferSize, // dimensions of our buffer
+ BufferCoord, // offset in our buffer
+ &ReadRegion)) // region to save
+ {
+ VIM_CLEAR(cb->Buffer);
+ VIM_CLEAR(cb->Regions);
+ return FALSE;
+ }
+ cb->Regions[i] = ReadRegion;
+ }
+
+ return TRUE;
+}
+
+/*
+ * RestoreConsoleBuffer()
+ * Description:
+ * Restores important information about the console buffer, including the
+ * actual buffer contents, if desired. The information to restore is in
+ * the same format used by SaveConsoleBuffer().
+ * Returns:
+ * TRUE on success
+ */
+ static BOOL
+RestoreConsoleBuffer(
+ ConsoleBuffer *cb,
+ BOOL RestoreScreen)
+{
+ COORD BufferCoord;
+ SMALL_RECT WriteRegion;
+ int i;
+
+ // VTP uses alternate screen buffer.
+ // No need to restore buffer contents.
+ if (use_alternate_screen_buffer)
+ return TRUE;
+
+ if (cb == NULL || !cb->IsValid)
+ return FALSE;
+
+ /*
+ * Before restoring the buffer contents, clear the current buffer, and
+ * restore the cursor position and window information. Doing this now
+ * prevents old buffer contents from "flashing" onto the screen.
+ */
+ if (RestoreScreen)
+ ClearConsoleBuffer(cb->Info.wAttributes);
+
+ FitConsoleWindow(cb->Info.dwSize, TRUE);
+ if (!SetConsoleScreenBufferSize(g_hConOut, cb->Info.dwSize))
+ return FALSE;
+ if (!SetConsoleTextAttribute(g_hConOut, cb->Info.wAttributes))
+ return FALSE;
+
+ if (!RestoreScreen)
+ {
+ /*
+ * No need to restore the screen buffer contents, so we're done.
+ */
+ return TRUE;
+ }
+
+ if (!SetConsoleCursorPosition(g_hConOut, cb->Info.dwCursorPosition))
+ return FALSE;
+ if (!SetConsoleWindowInfo(g_hConOut, TRUE, &cb->Info.srWindow))
+ return FALSE;
+
+ /*
+ * Restore the screen buffer contents.
+ */
+ if (cb->Buffer != NULL)
+ {
+ for (i = 0; i < cb->NumRegions; i++)
+ {
+ BufferCoord.X = cb->Regions[i].Left;
+ BufferCoord.Y = cb->Regions[i].Top;
+ WriteRegion = cb->Regions[i];
+ if (!WriteConsoleOutputW(g_hConOut, // output handle
+ cb->Buffer, // our buffer
+ cb->BufferSize, // dimensions of our buffer
+ BufferCoord, // offset in our buffer
+ &WriteRegion)) // region to restore
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+# define FEAT_RESTORE_ORIG_SCREEN
+# ifdef FEAT_RESTORE_ORIG_SCREEN
+static ConsoleBuffer g_cbOrig = { 0 };
+# endif
+static ConsoleBuffer g_cbNonTermcap = { 0 };
+static ConsoleBuffer g_cbTermcap = { 0 };
+
+char g_szOrigTitle[256] = { 0 };
+HWND g_hWnd = NULL; // also used in os_mswin.c
+static HICON g_hOrigIconSmall = NULL;
+static HICON g_hOrigIcon = NULL;
+static HICON g_hVimIcon = NULL;
+static BOOL g_fCanChangeIcon = FALSE;
+
+/*
+ * GetConsoleIcon()
+ * Description:
+ * Attempts to retrieve the small icon and/or the big icon currently in
+ * use by a given window.
+ * Returns:
+ * TRUE on success
+ */
+ static BOOL
+GetConsoleIcon(
+ HWND hWnd,
+ HICON *phIconSmall,
+ HICON *phIcon)
+{
+ if (hWnd == NULL)
+ return FALSE;
+
+ if (phIconSmall != NULL)
+ *phIconSmall = (HICON)SendMessage(hWnd, WM_GETICON,
+ (WPARAM)ICON_SMALL, (LPARAM)0);
+ if (phIcon != NULL)
+ *phIcon = (HICON)SendMessage(hWnd, WM_GETICON,
+ (WPARAM)ICON_BIG, (LPARAM)0);
+ return TRUE;
+}
+
+/*
+ * SetConsoleIcon()
+ * Description:
+ * Attempts to change the small icon and/or the big icon currently in
+ * use by a given window.
+ * Returns:
+ * TRUE on success
+ */
+ static BOOL
+SetConsoleIcon(
+ HWND hWnd,
+ HICON hIconSmall,
+ HICON hIcon)
+{
+ if (hWnd == NULL)
+ return FALSE;
+
+ if (hIconSmall != NULL)
+ SendMessage(hWnd, WM_SETICON,
+ (WPARAM)ICON_SMALL, (LPARAM)hIconSmall);
+ if (hIcon != NULL)
+ SendMessage(hWnd, WM_SETICON,
+ (WPARAM)ICON_BIG, (LPARAM) hIcon);
+ return TRUE;
+}
+
+/*
+ * SaveConsoleTitleAndIcon()
+ * Description:
+ * Saves the current console window title in g_szOrigTitle, for later
+ * restoration. Also, attempts to obtain a handle to the console window,
+ * and use it to save the small and big icons currently in use by the
+ * console window. This is not always possible on some versions of Windows;
+ * nor is it possible when running Vim remotely using Telnet (since the
+ * console window the user sees is owned by a remote process).
+ */
+ static void
+SaveConsoleTitleAndIcon(void)
+{
+ // Save the original title.
+ if (!GetConsoleTitle(g_szOrigTitle, sizeof(g_szOrigTitle)))
+ return;
+
+ /*
+ * Obtain a handle to the console window using GetConsoleWindow() from
+ * KERNEL32.DLL; we need to handle in order to change the window icon.
+ * This function only exists on NT-based Windows, starting with Windows
+ * 2000. On older operating systems, we can't change the window icon
+ * anyway.
+ */
+ g_hWnd = GetConsoleWindow();
+ if (g_hWnd == NULL)
+ return;
+
+ // Save the original console window icon.
+ GetConsoleIcon(g_hWnd, &g_hOrigIconSmall, &g_hOrigIcon);
+ if (g_hOrigIconSmall == NULL || g_hOrigIcon == NULL)
+ return;
+
+ // Extract the first icon contained in the Vim executable.
+ if (
+# ifdef FEAT_LIBCALL
+ mch_icon_load((HANDLE *)&g_hVimIcon) == FAIL ||
+# endif
+ g_hVimIcon == NULL)
+ g_hVimIcon = ExtractIcon(NULL, (LPCSTR)exe_name, 0);
+ if (g_hVimIcon != NULL)
+ g_fCanChangeIcon = TRUE;
+}
+
+static int g_fWindInitCalled = FALSE;
+static int g_fTermcapMode = FALSE;
+static CONSOLE_CURSOR_INFO g_cci;
+
+/*
+ * non-GUI version of mch_init().
+ */
+ static void
+mch_init_c(void)
+{
+# ifndef FEAT_RESTORE_ORIG_SCREEN
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+# endif
+# ifndef __MINGW32__
+ extern int _fmode;
+# endif
+
+ // Silently handle invalid parameters to CRT functions
+ SET_INVALID_PARAM_HANDLER;
+
+ // Let critical errors result in a failure, not in a dialog box. Required
+ // for the timestamp test to work on removed floppies.
+ SetErrorMode(SEM_FAILCRITICALERRORS);
+
+ _fmode = O_BINARY; // we do our own CR-LF translation
+ out_flush();
+
+ // Obtain handles for the standard Console I/O devices
+ if (read_cmd_fd == 0)
+ g_hConIn = GetStdHandle(STD_INPUT_HANDLE);
+ else
+ create_conin();
+ g_hConOut = GetStdHandle(STD_OUTPUT_HANDLE);
+
+ wt_init();
+ vtp_flag_init();
+# ifdef FEAT_RESTORE_ORIG_SCREEN
+ // Save the initial console buffer for later restoration
+ SaveConsoleBuffer(&g_cbOrig);
+ g_attrCurrent = g_attrDefault = g_cbOrig.Info.wAttributes;
+# else
+ // Get current text attributes
+ GetConsoleScreenBufferInfo(g_hConOut, &csbi);
+ g_attrCurrent = g_attrDefault = csbi.wAttributes;
+# endif
+ if (cterm_normal_fg_color == 0)
+ cterm_normal_fg_color = (g_attrCurrent & 0xf) + 1;
+ if (cterm_normal_bg_color == 0)
+ cterm_normal_bg_color = ((g_attrCurrent >> 4) & 0xf) + 1;
+
+ // Fg and Bg color index number at startup
+ g_color_index_fg = g_attrDefault & 0xf;
+ g_color_index_bg = (g_attrDefault >> 4) & 0xf;
+
+ // set termcap codes to current text attributes
+ update_tcap(g_attrCurrent);
+
+ GetConsoleCursorInfo(g_hConOut, &g_cci);
+ GetConsoleMode(g_hConIn, &g_cmodein);
+ GetConsoleMode(g_hConOut, &g_cmodeout);
+
+ SaveConsoleTitleAndIcon();
+ /*
+ * Set both the small and big icons of the console window to Vim's icon.
+ * Note that Vim presently only has one size of icon (32x32), but it
+ * automatically gets scaled down to 16x16 when setting the small icon.
+ */
+ if (g_fCanChangeIcon)
+ SetConsoleIcon(g_hWnd, g_hVimIcon, g_hVimIcon);
+
+ ui_get_shellsize();
+
+ vtp_init();
+ // Switch to a new alternate screen buffer.
+ if (use_alternate_screen_buffer)
+ vtp_printf("\033[?1049h");
+
+# ifdef MCH_WRITE_DUMP
+ fdDump = fopen("dump", "wt");
+
+ if (fdDump)
+ {
+ time_t t;
+
+ time(&t);
+ fputs(ctime(&t), fdDump);
+ fflush(fdDump);
+ }
+# endif
+
+ g_fWindInitCalled = TRUE;
+
+ g_fMouseAvail = GetSystemMetrics(SM_MOUSEPRESENT);
+
+# ifdef FEAT_CLIPBOARD
+ win_clip_init();
+# endif
+}
+
+/*
+ * non-GUI version of mch_exit().
+ * Shut down and exit with status `r'
+ * Careful: mch_exit() may be called before mch_init()!
+ */
+ static void
+mch_exit_c(int r)
+{
+ exiting = TRUE;
+
+ vtp_exit();
+
+ stoptermcap();
+ if (g_fWindInitCalled)
+ settmode(TMODE_COOK);
+
+ ml_close_all(TRUE); // remove all memfiles
+
+ if (g_fWindInitCalled)
+ {
+ mch_restore_title(SAVE_RESTORE_BOTH);
+ /*
+ * Restore both the small and big icons of the console window to
+ * what they were at startup. Don't do this when the window is
+ * closed, Vim would hang here.
+ */
+ if (g_fCanChangeIcon && !g_fForceExit)
+ SetConsoleIcon(g_hWnd, g_hOrigIconSmall, g_hOrigIcon);
+
+# ifdef MCH_WRITE_DUMP
+ if (fdDump)
+ {
+ time_t t;
+
+ time(&t);
+ fputs(ctime(&t), fdDump);
+ fclose(fdDump);
+ }
+ fdDump = NULL;
+# endif
+ }
+
+ SetConsoleCursorInfo(g_hConOut, &g_cci);
+ SetConsoleMode(g_hConIn, g_cmodein | ENABLE_EXTENDED_FLAGS);
+ SetConsoleMode(g_hConOut, g_cmodeout);
+
+# ifdef DYNAMIC_GETTEXT
+ dyn_libintl_end();
+# endif
+
+ exit(r);
+}
+#endif // !FEAT_GUI_MSWIN
+
+ void
+mch_init(void)
+{
+#ifdef VIMDLL
+ if (gui.starting)
+ mch_init_g();
+ else
+ mch_init_c();
+#elif defined(FEAT_GUI_MSWIN)
+ mch_init_g();
+#else
+ mch_init_c();
+#endif
+}
+
+ void
+mch_exit(int r)
+{
+#ifdef FEAT_NETBEANS_INTG
+ netbeans_send_disconnect();
+#endif
+
+#ifdef VIMDLL
+ if (gui.in_use || gui.starting)
+ mch_exit_g(r);
+ else
+ mch_exit_c(r);
+#elif defined(FEAT_GUI_MSWIN)
+ mch_exit_g(r);
+#else
+ mch_exit_c(r);
+#endif
+}
+
+/*
+ * Do we have an interactive window?
+ */
+ int
+mch_check_win(
+ int argc UNUSED,
+ char **argv UNUSED)
+{
+ mch_get_exe_name();
+
+#if defined(FEAT_GUI_MSWIN) && !defined(VIMDLL)
+ return OK; // GUI always has a tty
+#else
+# ifdef VIMDLL
+ if (gui.in_use)
+ return OK;
+# endif
+ if (isatty(1))
+ return OK;
+ return FAIL;
+#endif
+}
+
+/*
+ * Set the case of the file name, if it already exists.
+ * When "len" is > 0, also expand short to long filenames.
+ */
+ void
+fname_case(
+ char_u *name,
+ int len)
+{
+ int flen;
+ WCHAR *p;
+ WCHAR buf[_MAX_PATH + 1];
+
+ flen = (int)STRLEN(name);
+ if (flen == 0)
+ return;
+
+ slash_adjust(name);
+
+ p = enc_to_utf16(name, NULL);
+ if (p == NULL)
+ return;
+
+ if (GetLongPathNameW(p, buf, _MAX_PATH))
+ {
+ char_u *q = utf16_to_enc(buf, NULL);
+
+ if (q != NULL)
+ {
+ if (len > 0 || flen >= (int)STRLEN(q))
+ vim_strncpy(name, q, (len > 0) ? len - 1 : flen);
+ vim_free(q);
+ }
+ }
+ vim_free(p);
+}
+
+
+/*
+ * Insert user name in s[len].
+ */
+ int
+mch_get_user_name(
+ char_u *s,
+ int len)
+{
+ WCHAR wszUserName[256 + 1]; // UNLEN is 256
+ DWORD wcch = ARRAY_LENGTH(wszUserName);
+
+ if (GetUserNameW(wszUserName, &wcch))
+ {
+ char_u *p = utf16_to_enc(wszUserName, NULL);
+
+ if (p != NULL)
+ {
+ vim_strncpy(s, p, len - 1);
+ vim_free(p);
+ return OK;
+ }
+ }
+ s[0] = NUL;
+ return FAIL;
+}
+
+
+/*
+ * Insert host name in s[len].
+ */
+ void
+mch_get_host_name(
+ char_u *s,
+ int len)
+{
+ WCHAR wszHostName[256 + 1];
+ DWORD wcch = ARRAY_LENGTH(wszHostName);
+
+ if (!GetComputerNameW(wszHostName, &wcch))
+ return;
+
+ char_u *p = utf16_to_enc(wszHostName, NULL);
+ if (p == NULL)
+ return;
+
+ vim_strncpy(s, p, len - 1);
+ vim_free(p);
+}
+
+
+/*
+ * return process ID
+ */
+ long
+mch_get_pid(void)
+{
+ return (long)GetCurrentProcessId();
+}
+
+/*
+ * return TRUE if process "pid" is still running
+ */
+ int
+mch_process_running(long pid)
+{
+ HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, 0, (DWORD)pid);
+ DWORD status = 0;
+ int ret = FALSE;
+
+ if (hProcess == NULL)
+ return FALSE; // might not have access
+ if (GetExitCodeProcess(hProcess, &status) )
+ ret = status == STILL_ACTIVE;
+ CloseHandle(hProcess);
+ return ret;
+}
+
+/*
+ * Get name of current directory into buffer 'buf' of length 'len' bytes.
+ * Return OK for success, FAIL for failure.
+ */
+ int
+mch_dirname(
+ char_u *buf,
+ int len)
+{
+ WCHAR wbuf[_MAX_PATH + 1];
+
+ /*
+ * Originally this was:
+ * return (getcwd(buf, len) != NULL ? OK : FAIL);
+ * But the Win32s known bug list says that getcwd() doesn't work
+ * so use the Win32 system call instead. <Negri>
+ */
+ if (GetCurrentDirectoryW(_MAX_PATH, wbuf) == 0)
+ return FAIL;
+
+ WCHAR wcbuf[_MAX_PATH + 1];
+ char_u *p = NULL;
+
+ if (GetLongPathNameW(wbuf, wcbuf, _MAX_PATH) != 0)
+ {
+ p = utf16_to_enc(wcbuf, NULL);
+ if (STRLEN(p) >= (size_t)len)
+ {
+ // long path name is too long, fall back to short one
+ vim_free(p);
+ p = NULL;
+ }
+ }
+ if (p == NULL)
+ p = utf16_to_enc(wbuf, NULL);
+
+ if (p == NULL)
+ return FAIL;
+
+ vim_strncpy(buf, p, len - 1);
+ vim_free(p);
+ return OK;
+}
+
+/*
+ * Get file permissions for "name".
+ * Return mode_t or -1 for error.
+ */
+ long
+mch_getperm(char_u *name)
+{
+ stat_T st;
+ int n;
+
+ n = mch_stat((char *)name, &st);
+ return n == 0 ? (long)(unsigned short)st.st_mode : -1L;
+}
+
+
+/*
+ * Set file permission for "name" to "perm".
+ *
+ * Return FAIL for failure, OK otherwise.
+ */
+ int
+mch_setperm(char_u *name, long perm)
+{
+ long n;
+ WCHAR *p;
+
+ p = enc_to_utf16(name, NULL);
+ if (p == NULL)
+ return FAIL;
+
+ n = _wchmod(p, perm);
+ vim_free(p);
+ if (n == -1)
+ return FAIL;
+
+ win32_set_archive(name);
+
+ return OK;
+}
+
+/*
+ * Set hidden flag for "name".
+ */
+ void
+mch_hide(char_u *name)
+{
+ int attrs = win32_getattrs(name);
+ if (attrs == -1)
+ return;
+
+ attrs |= FILE_ATTRIBUTE_HIDDEN;
+ win32_setattrs(name, attrs);
+}
+
+/*
+ * Return TRUE if file "name" exists and is hidden.
+ */
+ int
+mch_ishidden(char_u *name)
+{
+ int f = win32_getattrs(name);
+
+ if (f == -1)
+ return FALSE; // file does not exist at all
+
+ return (f & FILE_ATTRIBUTE_HIDDEN) != 0;
+}
+
+/*
+ * return TRUE if "name" is a directory
+ * return FALSE if "name" is not a directory or upon error
+ */
+ int
+mch_isdir(char_u *name)
+{
+ int f = win32_getattrs(name);
+
+ if (f == -1)
+ return FALSE; // file does not exist at all
+
+ return (f & FILE_ATTRIBUTE_DIRECTORY) != 0;
+}
+
+/*
+ * return TRUE if "name" is a directory, NOT a symlink to a directory
+ * return FALSE if "name" is not a directory
+ * return FALSE for error
+ */
+ int
+mch_isrealdir(char_u *name)
+{
+ return mch_isdir(name) && !mch_is_symbolic_link(name);
+}
+
+/*
+ * Create directory "name".
+ * Return 0 on success, -1 on error.
+ */
+ int
+mch_mkdir(char_u *name)
+{
+ WCHAR *p;
+ int retval;
+
+ p = enc_to_utf16(name, NULL);
+ if (p == NULL)
+ return -1;
+ retval = _wmkdir(p);
+ vim_free(p);
+ return retval;
+}
+
+/*
+ * Delete directory "name".
+ * Return 0 on success, -1 on error.
+ */
+ int
+mch_rmdir(char_u *name)
+{
+ WCHAR *p;
+ int retval;
+
+ p = enc_to_utf16(name, NULL);
+ if (p == NULL)
+ return -1;
+ retval = _wrmdir(p);
+ vim_free(p);
+ return retval;
+}
+
+/*
+ * Return TRUE if file "fname" has more than one link.
+ */
+ int
+mch_is_hard_link(char_u *fname)
+{
+ BY_HANDLE_FILE_INFORMATION info;
+
+ return win32_fileinfo(fname, &info) == FILEINFO_OK
+ && info.nNumberOfLinks > 1;
+}
+
+/*
+ * Return TRUE if "name" is a symbolic link (or a junction).
+ */
+ int
+mch_is_symbolic_link(char_u *name)
+{
+ HANDLE hFind;
+ int res = FALSE;
+ DWORD fileFlags = 0, reparseTag = 0;
+ WCHAR *wn;
+ WIN32_FIND_DATAW findDataW;
+
+ wn = enc_to_utf16(name, NULL);
+ if (wn == NULL)
+ return FALSE;
+
+ hFind = FindFirstFileW(wn, &findDataW);
+ vim_free(wn);
+ if (hFind != INVALID_HANDLE_VALUE)
+ {
+ fileFlags = findDataW.dwFileAttributes;
+ reparseTag = findDataW.dwReserved0;
+ FindClose(hFind);
+ }
+
+ if ((fileFlags & FILE_ATTRIBUTE_REPARSE_POINT)
+ && (reparseTag == IO_REPARSE_TAG_SYMLINK
+ || reparseTag == IO_REPARSE_TAG_MOUNT_POINT))
+ res = TRUE;
+
+ return res;
+}
+
+/*
+ * Return TRUE if file "fname" has more than one link or if it is a symbolic
+ * link.
+ */
+ int
+mch_is_linked(char_u *fname)
+{
+ if (mch_is_hard_link(fname) || mch_is_symbolic_link(fname))
+ return TRUE;
+ return FALSE;
+}
+
+/*
+ * Get the by-handle-file-information for "fname".
+ * Returns FILEINFO_OK when OK.
+ * Returns FILEINFO_ENC_FAIL when enc_to_utf16() failed.
+ * Returns FILEINFO_READ_FAIL when CreateFile() failed.
+ * Returns FILEINFO_INFO_FAIL when GetFileInformationByHandle() failed.
+ */
+ int
+win32_fileinfo(char_u *fname, BY_HANDLE_FILE_INFORMATION *info)
+{
+ HANDLE hFile;
+ int res = FILEINFO_READ_FAIL;
+ WCHAR *wn;
+
+ wn = enc_to_utf16(fname, NULL);
+ if (wn == NULL)
+ return FILEINFO_ENC_FAIL;
+
+ hFile = CreateFileW(wn, // file name
+ GENERIC_READ, // access mode
+ FILE_SHARE_READ | FILE_SHARE_WRITE, // share mode
+ NULL, // security descriptor
+ OPEN_EXISTING, // creation disposition
+ FILE_FLAG_BACKUP_SEMANTICS, // file attributes
+ NULL); // handle to template file
+ vim_free(wn);
+
+ if (hFile == INVALID_HANDLE_VALUE)
+ return FILEINFO_READ_FAIL;
+
+ if (GetFileInformationByHandle(hFile, info) != 0)
+ res = FILEINFO_OK;
+ else
+ res = FILEINFO_INFO_FAIL;
+ CloseHandle(hFile);
+
+ return res;
+}
+
+/*
+ * get file attributes for `name'
+ * -1 : error
+ * else FILE_ATTRIBUTE_* defined in winnt.h
+ */
+ static int
+win32_getattrs(char_u *name)
+{
+ int attr;
+ WCHAR *p;
+
+ p = enc_to_utf16(name, NULL);
+ if (p == NULL)
+ return INVALID_FILE_ATTRIBUTES;
+
+ attr = GetFileAttributesW(p);
+ vim_free(p);
+
+ return attr;
+}
+
+/*
+ * set file attributes for `name' to `attrs'
+ *
+ * return -1 for failure, 0 otherwise
+ */
+ static int
+win32_setattrs(char_u *name, int attrs)
+{
+ int res;
+ WCHAR *p;
+
+ p = enc_to_utf16(name, NULL);
+ if (p == NULL)
+ return -1;
+
+ res = SetFileAttributesW(p, attrs);
+ vim_free(p);
+
+ return res ? 0 : -1;
+}
+
+/*
+ * Set archive flag for "name".
+ */
+ static int
+win32_set_archive(char_u *name)
+{
+ int attrs = win32_getattrs(name);
+ if (attrs == -1)
+ return -1;
+
+ attrs |= FILE_ATTRIBUTE_ARCHIVE;
+ return win32_setattrs(name, attrs);
+}
+
+/*
+ * Return TRUE if file or directory "name" is writable (not readonly).
+ * Strange semantics of Win32: a readonly directory is writable, but you can't
+ * delete a file. Let's say this means it is writable.
+ */
+ int
+mch_writable(char_u *name)
+{
+ int attrs = win32_getattrs(name);
+
+ return (attrs != -1 && (!(attrs & FILE_ATTRIBUTE_READONLY)
+ || (attrs & FILE_ATTRIBUTE_DIRECTORY)));
+}
+
+/*
+ * Return TRUE if "name" can be executed, FALSE if not.
+ * If "use_path" is FALSE only check if "name" is executable.
+ * When returning TRUE and "path" is not NULL save the path and set "*path" to
+ * the allocated memory.
+ */
+ int
+mch_can_exe(char_u *name, char_u **path, int use_path UNUSED)
+{
+ return executable_exists((char *)name, path, TRUE, TRUE);
+}
+
+/*
+ * Check what "name" is:
+ * NODE_NORMAL: file or directory (or doesn't exist)
+ * NODE_WRITABLE: writable device, socket, fifo, etc.
+ * NODE_OTHER: non-writable things
+ */
+ int
+mch_nodetype(char_u *name)
+{
+ HANDLE hFile;
+ int type;
+ WCHAR *wn;
+
+ // We can't open a file with a name "\\.\con" or "\\.\prn" and trying to
+ // read from it later will cause Vim to hang. Thus return NODE_WRITABLE
+ // here.
+ if (STRNCMP(name, "\\\\.\\", 4) == 0)
+ return NODE_WRITABLE;
+
+ wn = enc_to_utf16(name, NULL);
+ if (wn == NULL)
+ return NODE_NORMAL;
+
+ hFile = CreateFileW(wn, // file name
+ GENERIC_WRITE, // access mode
+ 0, // share mode
+ NULL, // security descriptor
+ OPEN_EXISTING, // creation disposition
+ 0, // file attributes
+ NULL); // handle to template file
+ vim_free(wn);
+ if (hFile == INVALID_HANDLE_VALUE)
+ return NODE_NORMAL;
+
+ type = GetFileType(hFile);
+ CloseHandle(hFile);
+ if (type == FILE_TYPE_CHAR)
+ return NODE_WRITABLE;
+ if (type == FILE_TYPE_DISK)
+ return NODE_NORMAL;
+ return NODE_OTHER;
+}
+
+#ifdef HAVE_ACL
+struct my_acl
+{
+ PSECURITY_DESCRIPTOR pSecurityDescriptor;
+ PSID pSidOwner;
+ PSID pSidGroup;
+ PACL pDacl;
+ PACL pSacl;
+};
+#endif
+
+/*
+ * Return a pointer to the ACL of file "fname" in allocated memory.
+ * Return NULL if the ACL is not available for whatever reason.
+ */
+ vim_acl_T
+mch_get_acl(char_u *fname)
+{
+#ifndef HAVE_ACL
+ return (vim_acl_T)NULL;
+#else
+ struct my_acl *p = NULL;
+ DWORD err;
+
+ p = ALLOC_CLEAR_ONE(struct my_acl);
+ if (p != NULL)
+ {
+ WCHAR *wn;
+
+ wn = enc_to_utf16(fname, NULL);
+ if (wn == NULL)
+ {
+ vim_free(p);
+ return NULL;
+ }
+
+ // Try to retrieve the entire security descriptor.
+ err = GetNamedSecurityInfoW(
+ wn, // Abstract filename
+ SE_FILE_OBJECT, // File Object
+ OWNER_SECURITY_INFORMATION |
+ GROUP_SECURITY_INFORMATION |
+ DACL_SECURITY_INFORMATION |
+ SACL_SECURITY_INFORMATION,
+ &p->pSidOwner, // Ownership information.
+ &p->pSidGroup, // Group membership.
+ &p->pDacl, // Discretionary information.
+ &p->pSacl, // For auditing purposes.
+ &p->pSecurityDescriptor);
+ if (err == ERROR_ACCESS_DENIED ||
+ err == ERROR_PRIVILEGE_NOT_HELD)
+ {
+ // Retrieve only DACL.
+ (void)GetNamedSecurityInfoW(
+ wn,
+ SE_FILE_OBJECT,
+ DACL_SECURITY_INFORMATION,
+ NULL,
+ NULL,
+ &p->pDacl,
+ NULL,
+ &p->pSecurityDescriptor);
+ }
+ if (p->pSecurityDescriptor == NULL)
+ {
+ mch_free_acl((vim_acl_T)p);
+ p = NULL;
+ }
+ vim_free(wn);
+ }
+
+ return (vim_acl_T)p;
+#endif
+}
+
+#ifdef HAVE_ACL
+/*
+ * Check if "acl" contains inherited ACE.
+ */
+ static BOOL
+is_acl_inherited(PACL acl)
+{
+ DWORD i;
+ ACL_SIZE_INFORMATION acl_info;
+ PACCESS_ALLOWED_ACE ace;
+
+ acl_info.AceCount = 0;
+ GetAclInformation(acl, &acl_info, sizeof(acl_info), AclSizeInformation);
+ for (i = 0; i < acl_info.AceCount; i++)
+ {
+ GetAce(acl, i, (LPVOID *)&ace);
+ if (ace->Header.AceFlags & INHERITED_ACE)
+ return TRUE;
+ }
+ return FALSE;
+}
+#endif
+
+/*
+ * Set the ACL of file "fname" to "acl" (unless it's NULL).
+ * Errors are ignored.
+ * This must only be called with "acl" equal to what mch_get_acl() returned.
+ */
+ void
+mch_set_acl(char_u *fname, vim_acl_T acl)
+{
+#ifdef HAVE_ACL
+ struct my_acl *p = (struct my_acl *)acl;
+ SECURITY_INFORMATION sec_info = 0;
+ WCHAR *wn;
+
+ if (p == NULL)
+ return;
+
+ wn = enc_to_utf16(fname, NULL);
+ if (wn == NULL)
+ return;
+
+ // Set security flags
+ if (p->pSidOwner)
+ sec_info |= OWNER_SECURITY_INFORMATION;
+ if (p->pSidGroup)
+ sec_info |= GROUP_SECURITY_INFORMATION;
+ if (p->pDacl)
+ {
+ sec_info |= DACL_SECURITY_INFORMATION;
+ // Do not inherit its parent's DACL.
+ // If the DACL is inherited, Cygwin permissions would be changed.
+ if (!is_acl_inherited(p->pDacl))
+ sec_info |= PROTECTED_DACL_SECURITY_INFORMATION;
+ }
+ if (p->pSacl)
+ sec_info |= SACL_SECURITY_INFORMATION;
+
+ (void)SetNamedSecurityInfoW(
+ wn, // Abstract filename
+ SE_FILE_OBJECT, // File Object
+ sec_info,
+ p->pSidOwner, // Ownership information.
+ p->pSidGroup, // Group membership.
+ p->pDacl, // Discretionary information.
+ p->pSacl // For auditing purposes.
+ );
+ vim_free(wn);
+#endif
+}
+
+ void
+mch_free_acl(vim_acl_T acl)
+{
+#ifdef HAVE_ACL
+ struct my_acl *p = (struct my_acl *)acl;
+
+ if (p != NULL)
+ {
+ LocalFree(p->pSecurityDescriptor); // Free the memory just in case
+ vim_free(p);
+ }
+#endif
+}
+
+#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL)
+
+/*
+ * handler for ctrl-break, ctrl-c interrupts, and fatal events.
+ */
+ static BOOL WINAPI
+handler_routine(
+ DWORD dwCtrlType)
+{
+ INPUT_RECORD ir;
+ DWORD out;
+
+ switch (dwCtrlType)
+ {
+ case CTRL_C_EVENT:
+ if (ctrl_c_interrupts)
+ g_fCtrlCPressed = TRUE;
+ return TRUE;
+
+ case CTRL_BREAK_EVENT:
+ g_fCBrkPressed = TRUE;
+ ctrl_break_was_pressed = TRUE;
+ // ReadConsoleInput is blocking, send a key event to continue.
+ ir.EventType = KEY_EVENT;
+ ir.Event.KeyEvent.bKeyDown = TRUE;
+ ir.Event.KeyEvent.wRepeatCount = 1;
+ ir.Event.KeyEvent.wVirtualKeyCode = VK_CANCEL;
+ ir.Event.KeyEvent.wVirtualScanCode = 0;
+ ir.Event.KeyEvent.dwControlKeyState = 0;
+ ir.Event.KeyEvent.uChar.UnicodeChar = 0;
+ WriteConsoleInput(g_hConIn, &ir, 1, &out);
+ return TRUE;
+
+ // fatal events: shut down gracefully
+ case CTRL_CLOSE_EVENT:
+ case CTRL_LOGOFF_EVENT:
+ case CTRL_SHUTDOWN_EVENT:
+ windgoto((int)Rows - 1, 0);
+ g_fForceExit = TRUE;
+
+ vim_snprintf((char *)IObuff, IOSIZE, _("Vim: Caught %s event\n"),
+ (dwCtrlType == CTRL_CLOSE_EVENT
+ ? _("close")
+ : dwCtrlType == CTRL_LOGOFF_EVENT
+ ? _("logoff")
+ : _("shutdown")));
+# ifdef DEBUG
+ OutputDebugString(IObuff);
+# endif
+
+ preserve_exit(); // output IObuff, preserve files and exit
+
+ return TRUE; // not reached
+
+ default:
+ return FALSE;
+ }
+}
+
+
+/*
+ * set the tty in (raw) ? "raw" : "cooked" mode
+ */
+ void
+mch_settmode(tmode_T tmode)
+{
+ DWORD cmodein;
+ DWORD cmodeout;
+ BOOL bEnableHandler;
+
+# ifdef VIMDLL
+ if (gui.in_use)
+ return;
+# endif
+ GetConsoleMode(g_hConIn, &cmodein);
+ GetConsoleMode(g_hConOut, &cmodeout);
+ if (tmode == TMODE_RAW)
+ {
+ cmodein &= ~(ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT |
+ ENABLE_ECHO_INPUT);
+ if (g_fMouseActive)
+ {
+ cmodein |= ENABLE_MOUSE_INPUT;
+ cmodein &= ~ENABLE_QUICK_EDIT_MODE;
+ }
+ else
+ {
+ cmodein |= g_cmodein & ENABLE_QUICK_EDIT_MODE;
+ }
+ cmodeout &= ~(
+# ifdef FEAT_TERMGUICOLORS
+ // Do not turn off the ENABLE_PROCESSED_OUTPUT flag when using
+ // VTP.
+ ((vtp_working) ? 0 : ENABLE_PROCESSED_OUTPUT) |
+# else
+ ENABLE_PROCESSED_OUTPUT |
+# endif
+ ENABLE_WRAP_AT_EOL_OUTPUT);
+ bEnableHandler = TRUE;
+ }
+ else // cooked
+ {
+ cmodein |= (ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT |
+ ENABLE_ECHO_INPUT);
+ cmodeout |= (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT);
+ bEnableHandler = FALSE;
+ }
+ SetConsoleMode(g_hConIn, cmodein | ENABLE_EXTENDED_FLAGS);
+ SetConsoleMode(g_hConOut, cmodeout);
+ SetConsoleCtrlHandler(handler_routine, bEnableHandler);
+
+# ifdef MCH_WRITE_DUMP
+ if (fdDump)
+ {
+ fprintf(fdDump, "mch_settmode(%s, in = %x, out = %x)\n",
+ tmode == TMODE_RAW ? "raw" :
+ tmode == TMODE_COOK ? "cooked" : "normal",
+ cmodein, cmodeout);
+ fflush(fdDump);
+ }
+# endif
+}
+
+
+/*
+ * Get the size of the current window in `Rows' and `Columns'
+ * Return OK when size could be determined, FAIL otherwise.
+ */
+ int
+mch_get_shellsize(void)
+{
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+
+# ifdef VIMDLL
+ if (gui.in_use)
+ return OK;
+# endif
+ if (!g_fTermcapMode && g_cbTermcap.IsValid)
+ {
+ /*
+ * For some reason, we are trying to get the screen dimensions
+ * even though we are not in termcap mode. The 'Rows' and 'Columns'
+ * variables are really intended to mean the size of Vim screen
+ * while in termcap mode.
+ */
+ Rows = g_cbTermcap.Info.dwSize.Y;
+ Columns = g_cbTermcap.Info.dwSize.X;
+ }
+ else if (GetConsoleScreenBufferInfo(g_hConOut, &csbi))
+ {
+ Rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
+ Columns = csbi.srWindow.Right - csbi.srWindow.Left + 1;
+ }
+ else
+ {
+ Rows = 25;
+ Columns = 80;
+ }
+ return OK;
+}
+
+/*
+ * Resize console buffer to 'COORD'
+ */
+ static void
+ResizeConBuf(
+ HANDLE hConsole,
+ COORD coordScreen)
+{
+ if (use_alternate_screen_buffer)
+ return;
+
+ if (!SetConsoleScreenBufferSize(hConsole, coordScreen))
+ {
+# ifdef MCH_WRITE_DUMP
+ if (fdDump)
+ {
+ fprintf(fdDump, "SetConsoleScreenBufferSize failed: %lx\n",
+ GetLastError());
+ fflush(fdDump);
+ }
+# endif
+ }
+}
+
+/*
+ * Resize console window size to 'srWindowRect'
+ */
+ static void
+ResizeWindow(
+ HANDLE hConsole,
+ SMALL_RECT srWindowRect)
+{
+ if (!SetConsoleWindowInfo(hConsole, TRUE, &srWindowRect))
+ {
+# ifdef MCH_WRITE_DUMP
+ if (fdDump)
+ {
+ fprintf(fdDump, "SetConsoleWindowInfo failed: %lx\n",
+ GetLastError());
+ fflush(fdDump);
+ }
+# endif
+ }
+}
+
+/*
+ * Set a console window to `xSize' * `ySize'
+ */
+ static void
+ResizeConBufAndWindow(
+ HANDLE hConsole,
+ int xSize,
+ int ySize)
+{
+ CONSOLE_SCREEN_BUFFER_INFO csbi; // hold current console buffer info
+ SMALL_RECT srWindowRect; // hold the new console size
+ COORD coordScreen;
+ COORD cursor;
+ static int resized = FALSE;
+
+# ifdef MCH_WRITE_DUMP
+ if (fdDump)
+ {
+ fprintf(fdDump, "ResizeConBufAndWindow(%d, %d)\n", xSize, ySize);
+ fflush(fdDump);
+ }
+# endif
+
+ // get the largest size we can size the console window to
+ coordScreen = GetLargestConsoleWindowSize(hConsole);
+
+ // define the new console window size and scroll position
+ srWindowRect.Left = srWindowRect.Top = (SHORT) 0;
+ srWindowRect.Right = (SHORT) (min(xSize, coordScreen.X) - 1);
+ srWindowRect.Bottom = (SHORT) (min(ySize, coordScreen.Y) - 1);
+
+ if (GetConsoleScreenBufferInfo(g_hConOut, &csbi))
+ {
+ int sx, sy;
+
+ sx = csbi.srWindow.Right - csbi.srWindow.Left + 1;
+ sy = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
+ if (sy < ySize || sx < xSize)
+ {
+ /*
+ * Increasing number of lines/columns, do buffer first.
+ * Use the maximal size in x and y direction.
+ */
+ if (sy < ySize)
+ coordScreen.Y = ySize;
+ else
+ coordScreen.Y = sy;
+ if (sx < xSize)
+ coordScreen.X = xSize;
+ else
+ coordScreen.X = sx;
+ SetConsoleScreenBufferSize(hConsole, coordScreen);
+ }
+ }
+
+ // define the new console buffer size
+ coordScreen.X = xSize;
+ coordScreen.Y = ySize;
+
+ // In the new console call API, only the first time in reverse order
+ if (!vtp_working || resized)
+ {
+ ResizeWindow(hConsole, srWindowRect);
+ ResizeConBuf(hConsole, coordScreen);
+ }
+ else
+ {
+ // Workaround for a Windows 10 bug
+ cursor.X = srWindowRect.Left;
+ cursor.Y = srWindowRect.Top;
+ SetConsoleCursorPosition(hConsole, cursor);
+
+ ResizeConBuf(hConsole, coordScreen);
+ ResizeWindow(hConsole, srWindowRect);
+ resized = TRUE;
+ }
+}
+
+
+/*
+ * Set the console window to `Rows' * `Columns'
+ */
+ void
+mch_set_shellsize(void)
+{
+ COORD coordScreen;
+
+# ifdef VIMDLL
+ if (gui.in_use)
+ return;
+# endif
+ // Don't change window size while still starting up
+ if (suppress_winsize != 0)
+ {
+ suppress_winsize = 2;
+ return;
+ }
+
+ if (term_console)
+ {
+ coordScreen = GetLargestConsoleWindowSize(g_hConOut);
+
+ // Clamp Rows and Columns to reasonable values
+ if (Rows > coordScreen.Y)
+ Rows = coordScreen.Y;
+ if (Columns > coordScreen.X)
+ Columns = coordScreen.X;
+
+ ResizeConBufAndWindow(g_hConOut, Columns, Rows);
+ }
+}
+
+/*
+ * Rows and/or Columns has changed.
+ */
+ void
+mch_new_shellsize(void)
+{
+# ifdef VIMDLL
+ if (gui.in_use)
+ return;
+# endif
+ set_scroll_region(0, 0, Columns - 1, Rows - 1);
+}
+
+
+/*
+ * Called when started up, to set the winsize that was delayed.
+ */
+ void
+mch_set_winsize_now(void)
+{
+ if (suppress_winsize == 2)
+ {
+ suppress_winsize = 0;
+ mch_set_shellsize();
+ shell_resized();
+ }
+ suppress_winsize = 0;
+}
+#endif // FEAT_GUI_MSWIN
+
+ static BOOL
+vim_create_process(
+ char *cmd,
+ BOOL inherit_handles,
+ DWORD flags,
+ STARTUPINFO *si,
+ PROCESS_INFORMATION *pi,
+ LPVOID *env,
+ char *cwd)
+{
+ BOOL ret = FALSE;
+ WCHAR *wcmd, *wcwd = NULL;
+
+ wcmd = enc_to_utf16((char_u *)cmd, NULL);
+ if (wcmd == NULL)
+ return FALSE;
+ if (cwd != NULL)
+ {
+ wcwd = enc_to_utf16((char_u *)cwd, NULL);
+ if (wcwd == NULL)
+ goto theend;
+ }
+
+ ret = CreateProcessW(
+ NULL, // Executable name
+ wcmd, // Command to execute
+ NULL, // Process security attributes
+ NULL, // Thread security attributes
+ inherit_handles, // Inherit handles
+ flags, // Creation flags
+ env, // Environment
+ wcwd, // Current directory
+ (LPSTARTUPINFOW)si, // Startup information
+ pi); // Process information
+theend:
+ vim_free(wcmd);
+ vim_free(wcwd);
+ return ret;
+}
+
+
+ static HINSTANCE
+vim_shell_execute(
+ char *cmd,
+ INT n_show_cmd)
+{
+ HINSTANCE ret;
+ WCHAR *wcmd;
+
+ wcmd = enc_to_utf16((char_u *)cmd, NULL);
+ if (wcmd == NULL)
+ return (HINSTANCE) 0;
+
+ ret = ShellExecuteW(NULL, NULL, wcmd, NULL, NULL, n_show_cmd);
+ vim_free(wcmd);
+ return ret;
+}
+
+
+#if defined(FEAT_GUI_MSWIN) || defined(PROTO)
+
+/*
+ * Specialised version of system() for Win32 GUI mode.
+ * This version proceeds as follows:
+ * 1. Create a console window for use by the subprocess
+ * 2. Run the subprocess (it gets the allocated console by default)
+ * 3. Wait for the subprocess to terminate and get its exit code
+ * 4. Prompt the user to press a key to close the console window
+ */
+ static int
+mch_system_classic(char *cmd, int options)
+{
+ STARTUPINFO si;
+ PROCESS_INFORMATION pi;
+ DWORD ret = 0;
+ HWND hwnd = GetFocus();
+
+ si.cb = sizeof(si);
+ si.lpReserved = NULL;
+ si.lpDesktop = NULL;
+ si.lpTitle = NULL;
+ si.dwFlags = STARTF_USESHOWWINDOW;
+ /*
+ * It's nicer to run a filter command in a minimized window.
+ * Don't activate the window to keep focus on Vim.
+ */
+ if (options & SHELL_DOOUT)
+ si.wShowWindow = SW_SHOWMINNOACTIVE;
+ else
+ si.wShowWindow = SW_SHOWNORMAL;
+ si.cbReserved2 = 0;
+ si.lpReserved2 = NULL;
+
+ // Now, run the command
+ vim_create_process(cmd, FALSE,
+ CREATE_DEFAULT_ERROR_MODE | CREATE_NEW_CONSOLE,
+ &si, &pi, NULL, NULL);
+
+ // Wait for the command to terminate before continuing
+ {
+# ifdef FEAT_GUI
+ int delay = 1;
+
+ // Keep updating the window while waiting for the shell to finish.
+ for (;;)
+ {
+ MSG msg;
+
+ if (PeekMessageW(&msg, (HWND)NULL, 0, 0, PM_REMOVE))
+ {
+ TranslateMessage(&msg);
+ DispatchMessageW(&msg);
+ delay = 1;
+ continue;
+ }
+ if (WaitForSingleObject(pi.hProcess, delay) != WAIT_TIMEOUT)
+ break;
+
+ // We start waiting for a very short time and then increase it, so
+ // that we respond quickly when the process is quick, and don't
+ // consume too much overhead when it's slow.
+ if (delay < 50)
+ delay += 10;
+ }
+# else
+ WaitForSingleObject(pi.hProcess, INFINITE);
+# endif
+
+ // Get the command exit code
+ GetExitCodeProcess(pi.hProcess, &ret);
+ }
+
+ // Close the handles to the subprocess, so that it goes away
+ CloseHandle(pi.hThread);
+ CloseHandle(pi.hProcess);
+
+ // Try to get input focus back. Doesn't always work though.
+ PostMessage(hwnd, WM_SETFOCUS, 0, 0);
+
+ return ret;
+}
+
+/*
+ * Thread launched by the gui to send the current buffer data to the
+ * process. This way avoid to hang up vim totally if the children
+ * process take a long time to process the lines.
+ */
+ static unsigned int __stdcall
+sub_process_writer(LPVOID param)
+{
+ HANDLE g_hChildStd_IN_Wr = param;
+ linenr_T lnum = curbuf->b_op_start.lnum;
+ DWORD len = 0;
+ DWORD l;
+ char_u *lp = ml_get(lnum);
+ char_u *s;
+ int written = 0;
+
+ for (;;)
+ {
+ l = (DWORD)STRLEN(lp + written);
+ if (l == 0)
+ len = 0;
+ else if (lp[written] == NL)
+ {
+ // NL -> NUL translation
+ WriteFile(g_hChildStd_IN_Wr, "", 1, &len, NULL);
+ }
+ else
+ {
+ s = vim_strchr(lp + written, NL);
+ WriteFile(g_hChildStd_IN_Wr, (char *)lp + written,
+ s == NULL ? l : (DWORD)(s - (lp + written)),
+ &len, NULL);
+ }
+ if (len == l)
+ {
+ // Finished a line, add a NL, unless this line should not have
+ // one.
+ if (lnum != curbuf->b_op_end.lnum
+ || (!curbuf->b_p_bin
+ && curbuf->b_p_fixeol)
+ || (lnum != curbuf->b_no_eol_lnum
+ && (lnum != curbuf->b_ml.ml_line_count
+ || curbuf->b_p_eol)))
+ {
+ WriteFile(g_hChildStd_IN_Wr, "\n", 1,
+ (LPDWORD)&vim_ignored, NULL);
+ }
+
+ ++lnum;
+ if (lnum > curbuf->b_op_end.lnum)
+ break;
+
+ lp = ml_get(lnum);
+ written = 0;
+ }
+ else if (len > 0)
+ written += len;
+ }
+
+ // finished all the lines, close pipe
+ CloseHandle(g_hChildStd_IN_Wr);
+ return 0;
+}
+
+
+# define BUFLEN 100 // length for buffer, stolen from unix version
+
+/*
+ * This function read from the children's stdout and write the
+ * data on screen or in the buffer accordingly.
+ */
+ static void
+dump_pipe(int options,
+ HANDLE g_hChildStd_OUT_Rd,
+ garray_T *ga,
+ char_u buffer[],
+ DWORD *buffer_off)
+{
+ DWORD availableBytes = 0;
+ DWORD i;
+ int ret;
+ DWORD len;
+ DWORD toRead;
+
+ // we query the pipe to see if there is any data to read
+ // to avoid to perform a blocking read
+ ret = PeekNamedPipe(g_hChildStd_OUT_Rd, // pipe to query
+ NULL, // optional buffer
+ 0, // buffer size
+ NULL, // number of read bytes
+ &availableBytes, // available bytes total
+ NULL); // byteLeft
+
+ // We got real data in the pipe, read it
+ while (ret != 0 && availableBytes > 0)
+ {
+ toRead = (DWORD)(BUFLEN - *buffer_off);
+ toRead = availableBytes < toRead ? availableBytes : toRead;
+ ReadFile(g_hChildStd_OUT_Rd, buffer + *buffer_off, toRead , &len, NULL);
+
+ // If we haven't read anything, there is a problem
+ if (len == 0)
+ break;
+
+ availableBytes -= len;
+
+ if (options & SHELL_READ)
+ {
+ // Do NUL -> NL translation, append NL separated
+ // lines to the current buffer.
+ for (i = 0; i < len; ++i)
+ {
+ if (buffer[i] == NL)
+ append_ga_line(ga);
+ else if (buffer[i] == NUL)
+ ga_append(ga, NL);
+ else
+ ga_append(ga, buffer[i]);
+ }
+ }
+ else if (has_mbyte)
+ {
+ int l;
+ int c;
+ char_u *p;
+
+ len += *buffer_off;
+ buffer[len] = NUL;
+
+ // Check if the last character in buffer[] is
+ // incomplete, keep these bytes for the next
+ // round.
+ for (p = buffer; p < buffer + len; p += l)
+ {
+ l = MB_CPTR2LEN(p);
+ if (l == 0)
+ l = 1; // NUL byte?
+ else if (MB_BYTE2LEN(*p) != l)
+ break;
+ }
+ if (p == buffer) // no complete character
+ {
+ // avoid getting stuck at an illegal byte
+ if (len >= 12)
+ ++p;
+ else
+ {
+ *buffer_off = len;
+ return;
+ }
+ }
+ c = *p;
+ *p = NUL;
+ msg_puts((char *)buffer);
+ if (p < buffer + len)
+ {
+ *p = c;
+ *buffer_off = (DWORD)((buffer + len) - p);
+ mch_memmove(buffer, p, *buffer_off);
+ return;
+ }
+ *buffer_off = 0;
+ }
+ else
+ {
+ buffer[len] = NUL;
+ msg_puts((char *)buffer);
+ }
+
+ windgoto(msg_row, msg_col);
+ cursor_on();
+ out_flush();
+ }
+}
+
+/*
+ * Version of system to use for windows NT > 5.0 (Win2K), use pipe
+ * for communication and doesn't open any new window.
+ */
+ static int
+mch_system_piped(char *cmd, int options)
+{
+ STARTUPINFO si;
+ PROCESS_INFORMATION pi;
+ DWORD ret = 0;
+
+ HANDLE g_hChildStd_IN_Rd = NULL;
+ HANDLE g_hChildStd_IN_Wr = NULL;
+ HANDLE g_hChildStd_OUT_Rd = NULL;
+ HANDLE g_hChildStd_OUT_Wr = NULL;
+
+ char_u buffer[BUFLEN + 1]; // reading buffer + size
+ DWORD len;
+
+ // buffer used to receive keys
+ char_u ta_buf[BUFLEN + 1]; // TypeAHead
+ int ta_len = 0; // valid bytes in ta_buf[]
+
+ DWORD i;
+ int noread_cnt = 0;
+ garray_T ga;
+ int delay = 1;
+ DWORD buffer_off = 0; // valid bytes in buffer[]
+ char *p = NULL;
+
+ SECURITY_ATTRIBUTES saAttr;
+
+ // Set the bInheritHandle flag so pipe handles are inherited.
+ saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
+ saAttr.bInheritHandle = TRUE;
+ saAttr.lpSecurityDescriptor = NULL;
+
+ if ( ! CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0)
+ // Ensure the read handle to the pipe for STDOUT is not inherited.
+ || ! SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0)
+ // Create a pipe for the child process's STDIN.
+ || ! CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0)
+ // Ensure the write handle to the pipe for STDIN is not inherited.
+ || ! SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0) )
+ {
+ CloseHandle(g_hChildStd_IN_Rd);
+ CloseHandle(g_hChildStd_IN_Wr);
+ CloseHandle(g_hChildStd_OUT_Rd);
+ CloseHandle(g_hChildStd_OUT_Wr);
+ msg_puts(_("\nCannot create pipes\n"));
+ }
+
+ si.cb = sizeof(si);
+ si.lpReserved = NULL;
+ si.lpDesktop = NULL;
+ si.lpTitle = NULL;
+ si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
+
+ // set-up our file redirection
+ si.hStdError = g_hChildStd_OUT_Wr;
+ si.hStdOutput = g_hChildStd_OUT_Wr;
+ si.hStdInput = g_hChildStd_IN_Rd;
+ si.wShowWindow = SW_HIDE;
+ si.cbReserved2 = 0;
+ si.lpReserved2 = NULL;
+
+ if (options & SHELL_READ)
+ ga_init2(&ga, 1, BUFLEN);
+
+ if (cmd != NULL)
+ {
+ p = (char *)vim_strsave((char_u *)cmd);
+ if (p != NULL)
+ unescape_shellxquote((char_u *)p, p_sxe);
+ else
+ p = cmd;
+ }
+
+ // Now, run the command.
+ // About "Inherit handles" being TRUE: this command can be litigious,
+ // handle inheritance was deactivated for pending temp file, but, if we
+ // deactivate it, the pipes don't work for some reason.
+ vim_create_process(p, TRUE, CREATE_DEFAULT_ERROR_MODE,
+ &si, &pi, NULL, NULL);
+
+ if (p != cmd)
+ vim_free(p);
+
+ // Close our unused side of the pipes
+ CloseHandle(g_hChildStd_IN_Rd);
+ CloseHandle(g_hChildStd_OUT_Wr);
+
+ if (options & SHELL_WRITE)
+ {
+ HANDLE thread = (HANDLE)
+ _beginthreadex(NULL, // security attributes
+ 0, // default stack size
+ sub_process_writer, // function to be executed
+ g_hChildStd_IN_Wr, // parameter
+ 0, // creation flag, start immediately
+ NULL); // we don't care about thread id
+ CloseHandle(thread);
+ g_hChildStd_IN_Wr = NULL;
+ }
+
+ // Keep updating the window while waiting for the shell to finish.
+ for (;;)
+ {
+ MSG msg;
+
+ if (PeekMessageW(&msg, (HWND)NULL, 0, 0, PM_REMOVE))
+ {
+ TranslateMessage(&msg);
+ DispatchMessageW(&msg);
+ }
+
+ // write pipe information in the window
+ if ((options & (SHELL_READ|SHELL_WRITE))
+# ifdef FEAT_GUI
+ || gui.in_use
+# endif
+ )
+ {
+ len = 0;
+ if (!(options & SHELL_EXPAND)
+ && ((options &
+ (SHELL_READ|SHELL_WRITE|SHELL_COOKED))
+ != (SHELL_READ|SHELL_WRITE|SHELL_COOKED)
+# ifdef FEAT_GUI
+ || gui.in_use
+# endif
+ )
+ && (ta_len > 0 || noread_cnt > 4))
+ {
+ if (ta_len == 0)
+ {
+ // Get extra characters when we don't have any. Reset the
+ // counter and timer.
+ noread_cnt = 0;
+ len = ui_inchar(ta_buf, BUFLEN, 10L, 0);
+ }
+ if (ta_len > 0 || len > 0)
+ {
+ /*
+ * For pipes: Check for CTRL-C: send interrupt signal to
+ * child. Check for CTRL-D: EOF, close pipe to child.
+ */
+ if (len == 1 && cmd != NULL)
+ {
+ if (ta_buf[ta_len] == Ctrl_C)
+ {
+ // Learn what exit code is expected, for
+ // now put 9 as SIGKILL
+ TerminateProcess(pi.hProcess, 9);
+ }
+ if (ta_buf[ta_len] == Ctrl_D)
+ {
+ CloseHandle(g_hChildStd_IN_Wr);
+ g_hChildStd_IN_Wr = NULL;
+ }
+ }
+
+ len = term_replace_keycodes(ta_buf, ta_len, len);
+
+ /*
+ * For pipes: echo the typed characters. For a pty this
+ * does not seem to work.
+ */
+ for (i = ta_len; i < ta_len + len; ++i)
+ {
+ if (ta_buf[i] == '\n' || ta_buf[i] == '\b')
+ msg_putchar(ta_buf[i]);
+ else if (has_mbyte)
+ {
+ int l = (*mb_ptr2len)(ta_buf + i);
+
+ msg_outtrans_len(ta_buf + i, l);
+ i += l - 1;
+ }
+ else
+ msg_outtrans_len(ta_buf + i, 1);
+ }
+ windgoto(msg_row, msg_col);
+ out_flush();
+
+ ta_len += len;
+
+ /*
+ * Write the characters to the child, unless EOF has been
+ * typed for pipes. Write one character at a time, to
+ * avoid losing too much typeahead. When writing buffer
+ * lines, drop the typed characters (only check for
+ * CTRL-C).
+ */
+ if (options & SHELL_WRITE)
+ ta_len = 0;
+ else if (g_hChildStd_IN_Wr != NULL)
+ {
+ WriteFile(g_hChildStd_IN_Wr, (char*)ta_buf,
+ 1, &len, NULL);
+ // if we are typing in, we want to keep things reactive
+ delay = 1;
+ if (len > 0)
+ {
+ ta_len -= len;
+ mch_memmove(ta_buf, ta_buf + len, ta_len);
+ }
+ }
+ }
+ }
+ }
+
+ if (ta_len)
+ ui_inchar_undo(ta_buf, ta_len);
+
+ if (WaitForSingleObject(pi.hProcess, delay) != WAIT_TIMEOUT)
+ {
+ dump_pipe(options, g_hChildStd_OUT_Rd, &ga, buffer, &buffer_off);
+ break;
+ }
+
+ ++noread_cnt;
+ dump_pipe(options, g_hChildStd_OUT_Rd, &ga, buffer, &buffer_off);
+
+ // We start waiting for a very short time and then increase it, so
+ // that we respond quickly when the process is quick, and don't
+ // consume too much overhead when it's slow.
+ if (delay < 50)
+ delay += 10;
+ }
+
+ // Close the pipe
+ CloseHandle(g_hChildStd_OUT_Rd);
+ if (g_hChildStd_IN_Wr != NULL)
+ CloseHandle(g_hChildStd_IN_Wr);
+
+ WaitForSingleObject(pi.hProcess, INFINITE);
+
+ // Get the command exit code
+ GetExitCodeProcess(pi.hProcess, &ret);
+
+ if (options & SHELL_READ)
+ {
+ if (ga.ga_len > 0)
+ {
+ append_ga_line(&ga);
+ // remember that the NL was missing
+ curbuf->b_no_eol_lnum = curwin->w_cursor.lnum;
+ }
+ else
+ curbuf->b_no_eol_lnum = 0;
+ ga_clear(&ga);
+ }
+
+ // Close the handles to the subprocess, so that it goes away
+ CloseHandle(pi.hThread);
+ CloseHandle(pi.hProcess);
+
+ return ret;
+}
+
+ static int
+mch_system_g(char *cmd, int options)
+{
+ // if we can pipe and the shelltemp option is off
+ if (!p_stmp)
+ return mch_system_piped(cmd, options);
+ else
+ return mch_system_classic(cmd, options);
+}
+#endif
+
+#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL)
+ static int
+mch_system_c(char *cmd, int options UNUSED)
+{
+ int ret;
+ WCHAR *wcmd;
+ char_u *buf;
+ size_t len;
+
+ // If the command starts and ends with double quotes, enclose the command
+ // in parentheses.
+ len = STRLEN(cmd);
+ if (len >= 2 && cmd[0] == '"' && cmd[len - 1] == '"')
+ {
+ len += 3;
+ buf = alloc(len);
+ if (buf == NULL)
+ return -1;
+ vim_snprintf((char *)buf, len, "(%s)", cmd);
+ wcmd = enc_to_utf16(buf, NULL);
+ free(buf);
+ }
+ else
+ wcmd = enc_to_utf16((char_u *)cmd, NULL);
+
+ if (wcmd == NULL)
+ return -1;
+
+ ret = _wsystem(wcmd);
+ vim_free(wcmd);
+ return ret;
+}
+
+#endif
+
+ static int
+mch_system(char *cmd, int options)
+{
+#ifdef VIMDLL
+ if (gui.in_use || gui.starting)
+ return mch_system_g(cmd, options);
+ else
+ return mch_system_c(cmd, options);
+#elif defined(FEAT_GUI_MSWIN)
+ return mch_system_g(cmd, options);
+#else
+ return mch_system_c(cmd, options);
+#endif
+}
+
+#if defined(FEAT_GUI) && defined(FEAT_TERMINAL)
+/*
+ * Use a terminal window to run a shell command in.
+ */
+ static int
+mch_call_shell_terminal(
+ char_u *cmd,
+ int options UNUSED) // SHELL_*, see vim.h
+{
+ jobopt_T opt;
+ char_u *newcmd = NULL;
+ typval_T argvar[2];
+ long_u cmdlen;
+ int retval = -1;
+ buf_T *buf;
+ job_T *job;
+ aco_save_T aco;
+ oparg_T oa; // operator arguments
+
+ if (cmd == NULL)
+ cmdlen = STRLEN(p_sh) + 1;
+ else
+ cmdlen = STRLEN(p_sh) + STRLEN(p_shcf) + STRLEN(cmd) + 10;
+ newcmd = alloc(cmdlen);
+ if (newcmd == NULL)
+ return 255;
+ if (cmd == NULL)
+ {
+ STRCPY(newcmd, p_sh);
+ ch_log(NULL, "starting terminal to run a shell");
+ }
+ else
+ {
+ vim_snprintf((char *)newcmd, cmdlen, "%s %s %s", p_sh, p_shcf, cmd);
+ ch_log(NULL, "starting terminal for system command '%s'", cmd);
+ }
+
+ init_job_options(&opt);
+
+ argvar[0].v_type = VAR_STRING;
+ argvar[0].vval.v_string = newcmd;
+ argvar[1].v_type = VAR_UNKNOWN;
+ buf = term_start(argvar, NULL, &opt, TERM_START_SYSTEM);
+ if (buf == NULL)
+ {
+ vim_free(newcmd);
+ return 255;
+ }
+
+ job = term_getjob(buf->b_term);
+ ++job->jv_refcount;
+
+ // Find a window to make "buf" curbuf.
+ aucmd_prepbuf(&aco, buf);
+ if (curbuf == buf)
+ {
+ // Only do this when a window was found for "buf".
+ clear_oparg(&oa);
+ while (term_use_loop())
+ {
+ if (oa.op_type == OP_NOP && oa.regname == NUL && !VIsual_active)
+ {
+ // If terminal_loop() returns OK we got a key that is handled
+ // in Normal model. We don't do redrawing anyway.
+ if (terminal_loop(TRUE) == OK)
+ normal_cmd(&oa, TRUE);
+ }
+ else
+ normal_cmd(&oa, TRUE);
+ }
+ retval = job->jv_exitval;
+ ch_log(NULL, "system command finished");
+
+ job_unref(job);
+
+ // restore curwin/curbuf and a few other things
+ aucmd_restbuf(&aco);
+ }
+
+ wait_return(TRUE);
+ do_buffer(DOBUF_WIPE, DOBUF_FIRST, FORWARD, buf->b_fnum, TRUE);
+
+ vim_free(newcmd);
+ return retval;
+}
+#endif
+
+/*
+ * Either execute a command by calling the shell or start a new shell
+ */
+ int
+mch_call_shell(
+ char_u *cmd,
+ int options) // SHELL_*, see vim.h
+{
+ int x = 0;
+ int tmode = cur_tmode;
+ WCHAR szShellTitle[512];
+
+#ifdef FEAT_EVAL
+ ch_log(NULL, "executing shell command: %s", cmd);
+#endif
+ // Change the title to reflect that we are in a subshell.
+ if (GetConsoleTitleW(szShellTitle, ARRAY_LENGTH(szShellTitle) - 4) > 0)
+ {
+ if (cmd == NULL)
+ wcscat(szShellTitle, L" :sh");
+ else
+ {
+ WCHAR *wn = enc_to_utf16((char_u *)cmd, NULL);
+
+ if (wn != NULL)
+ {
+ wcscat(szShellTitle, L" - !");
+ if ((wcslen(szShellTitle) + wcslen(wn) <
+ ARRAY_LENGTH(szShellTitle)))
+ wcscat(szShellTitle, wn);
+ SetConsoleTitleW(szShellTitle);
+ vim_free(wn);
+ }
+ }
+ }
+
+ out_flush();
+
+#ifdef MCH_WRITE_DUMP
+ if (fdDump)
+ {
+ fprintf(fdDump, "mch_call_shell(\"%s\", %d)\n", cmd, options);
+ fflush(fdDump);
+ }
+#endif
+#if defined(FEAT_GUI) && defined(FEAT_TERMINAL)
+ // TODO: make the terminal window work with input or output redirected.
+ if (
+# ifdef VIMDLL
+ gui.in_use &&
+# endif
+ vim_strchr(p_go, GO_TERMINAL) != NULL
+ && (options & (SHELL_FILTER|SHELL_DOOUT|SHELL_WRITE|SHELL_READ)) == 0)
+ {
+ char_u *cmdbase = cmd;
+
+ if (cmdbase != NULL)
+ // Skip a leading quote and (.
+ while (*cmdbase == '"' || *cmdbase == '(')
+ ++cmdbase;
+
+ // Check the command does not begin with "start "
+ if (cmdbase == NULL || STRNICMP(cmdbase, "start", 5) != 0
+ || !VIM_ISWHITE(cmdbase[5]))
+ {
+ // Use a terminal window to run the command in.
+ x = mch_call_shell_terminal(cmd, options);
+ resettitle();
+ return x;
+ }
+ }
+#endif
+
+ /*
+ * Catch all deadly signals while running the external command, because a
+ * CTRL-C, Ctrl-Break or illegal instruction might otherwise kill us.
+ */
+ signal(SIGINT, SIG_IGN);
+#if defined(__GNUC__) && !defined(__MINGW32__)
+ signal(SIGKILL, SIG_IGN);
+#else
+ signal(SIGBREAK, SIG_IGN);
+#endif
+ signal(SIGILL, SIG_IGN);
+ signal(SIGFPE, SIG_IGN);
+ signal(SIGSEGV, SIG_IGN);
+ signal(SIGTERM, SIG_IGN);
+ signal(SIGABRT, SIG_IGN);
+
+ if (options & SHELL_COOKED)
+ settmode(TMODE_COOK); // set to normal mode
+
+ if (cmd == NULL)
+ {
+ x = mch_system((char *)p_sh, options);
+ }
+ else
+ {
+ // we use "command" or "cmd" to start the shell; slow but easy
+ char_u *newcmd = NULL;
+ char_u *cmdbase = cmd;
+ long_u cmdlen;
+
+ // Skip a leading ", ( and "(.
+ if (*cmdbase == '"' )
+ ++cmdbase;
+ if (*cmdbase == '(')
+ ++cmdbase;
+
+ if ((STRNICMP(cmdbase, "start", 5) == 0) && VIM_ISWHITE(cmdbase[5]))
+ {
+ STARTUPINFO si;
+ PROCESS_INFORMATION pi;
+ DWORD flags = CREATE_NEW_CONSOLE;
+ INT n_show_cmd = SW_SHOWNORMAL;
+ char_u *p;
+
+ ZeroMemory(&si, sizeof(si));
+ si.cb = sizeof(si);
+ si.lpReserved = NULL;
+ si.lpDesktop = NULL;
+ si.lpTitle = NULL;
+ si.dwFlags = 0;
+ si.cbReserved2 = 0;
+ si.lpReserved2 = NULL;
+
+ cmdbase = skipwhite(cmdbase + 5);
+ if ((STRNICMP(cmdbase, "/min", 4) == 0)
+ && VIM_ISWHITE(cmdbase[4]))
+ {
+ cmdbase = skipwhite(cmdbase + 4);
+ si.dwFlags = STARTF_USESHOWWINDOW;
+ si.wShowWindow = SW_SHOWMINNOACTIVE;
+ n_show_cmd = SW_SHOWMINNOACTIVE;
+ }
+ else if ((STRNICMP(cmdbase, "/b", 2) == 0)
+ && VIM_ISWHITE(cmdbase[2]))
+ {
+ cmdbase = skipwhite(cmdbase + 2);
+ flags = CREATE_NO_WINDOW;
+ si.dwFlags = STARTF_USESTDHANDLES;
+ si.hStdInput = CreateFile("\\\\.\\NUL", // File name
+ GENERIC_READ, // Access flags
+ 0, // Share flags
+ NULL, // Security att.
+ OPEN_EXISTING, // Open flags
+ FILE_ATTRIBUTE_NORMAL, // File att.
+ NULL); // Temp file
+ si.hStdOutput = si.hStdInput;
+ si.hStdError = si.hStdInput;
+ }
+
+ // Remove a trailing ", ) and )" if they have a match
+ // at the start of the command.
+ if (cmdbase > cmd)
+ {
+ p = cmdbase + STRLEN(cmdbase);
+ if (p > cmdbase && p[-1] == '"' && *cmd == '"')
+ *--p = NUL;
+ if (p > cmdbase && p[-1] == ')'
+ && (*cmd =='(' || cmd[1] == '('))
+ *--p = NUL;
+ }
+
+ newcmd = cmdbase;
+ unescape_shellxquote(cmdbase, p_sxe);
+
+ /*
+ * If creating new console, arguments are passed to the
+ * 'cmd.exe' as-is. If it's not, arguments are not treated
+ * correctly for current 'cmd.exe'. So unescape characters in
+ * shellxescape except '|' for avoiding to be treated as
+ * argument to them. Pass the arguments to sub-shell.
+ */
+ if (flags != CREATE_NEW_CONSOLE)
+ {
+ char_u *subcmd;
+ char_u *cmd_shell = mch_getenv("COMSPEC");
+
+ if (cmd_shell == NULL || *cmd_shell == NUL)
+ cmd_shell = (char_u *)default_shell();
+
+ subcmd = vim_strsave_escaped_ext(cmdbase,
+ (char_u *)"|", '^', FALSE);
+ if (subcmd != NULL)
+ {
+ // make "cmd.exe /c arguments"
+ cmdlen = STRLEN(cmd_shell) + STRLEN(subcmd) + 5;
+ newcmd = alloc(cmdlen);
+ if (newcmd != NULL)
+ vim_snprintf((char *)newcmd, cmdlen, "%s /c %s",
+ cmd_shell, subcmd);
+ else
+ newcmd = cmdbase;
+ vim_free(subcmd);
+ }
+ }
+
+ /*
+ * Now, start the command as a process, so that it doesn't
+ * inherit our handles which causes unpleasant dangling swap
+ * files if we exit before the spawned process
+ */
+ if (vim_create_process((char *)newcmd, FALSE, flags,
+ &si, &pi, NULL, NULL))
+ x = 0;
+ else if (vim_shell_execute((char *)newcmd, n_show_cmd)
+ > (HINSTANCE)32)
+ x = 0;
+ else
+ {
+ x = -1;
+#ifdef FEAT_GUI_MSWIN
+# ifdef VIMDLL
+ if (gui.in_use)
+# endif
+ emsg(_(e_command_not_found));
+#endif
+ }
+
+ if (newcmd != cmdbase)
+ vim_free(newcmd);
+
+ if (si.dwFlags == STARTF_USESTDHANDLES && si.hStdInput != NULL)
+ {
+ // Close the handle to \\.\NUL created above.
+ CloseHandle(si.hStdInput);
+ }
+ // Close the handles to the subprocess, so that it goes away
+ CloseHandle(pi.hThread);
+ CloseHandle(pi.hProcess);
+ }
+ else
+ {
+ cmdlen =
+#ifdef FEAT_GUI_MSWIN
+ ((gui.in_use || gui.starting) ?
+ (!s_dont_use_vimrun && p_stmp ?
+ STRLEN(vimrun_path) : STRLEN(p_sh) + STRLEN(p_shcf))
+ : 0) +
+#endif
+ STRLEN(p_sh) + STRLEN(p_shcf) + STRLEN(cmd) + 10;
+
+ newcmd = alloc(cmdlen);
+ if (newcmd != NULL)
+ {
+#if defined(FEAT_GUI_MSWIN)
+ if (
+# ifdef VIMDLL
+ (gui.in_use || gui.starting) &&
+# endif
+ need_vimrun_warning)
+ {
+ char *msg = _("VIMRUN.EXE not found in your $PATH.\n"
+ "External commands will not pause after completion.\n"
+ "See :help win32-vimrun for more information.");
+ char *title = _("Vim Warning");
+ WCHAR *wmsg = enc_to_utf16((char_u *)msg, NULL);
+ WCHAR *wtitle = enc_to_utf16((char_u *)title, NULL);
+
+ if (wmsg != NULL && wtitle != NULL)
+ MessageBoxW(NULL, wmsg, wtitle, MB_ICONWARNING);
+ vim_free(wmsg);
+ vim_free(wtitle);
+ need_vimrun_warning = FALSE;
+ }
+ if (
+# ifdef VIMDLL
+ (gui.in_use || gui.starting) &&
+# endif
+ !s_dont_use_vimrun && p_stmp)
+ // Use vimrun to execute the command. It opens a console
+ // window, which can be closed without killing Vim.
+ vim_snprintf((char *)newcmd, cmdlen, "%s%s%s %s %s",
+ vimrun_path,
+ (msg_silent != 0 || (options & SHELL_DOOUT))
+ ? "-s " : "",
+ p_sh, p_shcf, cmd);
+ else if (
+# ifdef VIMDLL
+ (gui.in_use || gui.starting) &&
+# endif
+ s_dont_use_vimrun && STRCMP(p_shcf, "/c") == 0)
+ // workaround for the case that "vimrun" does not exist
+ vim_snprintf((char *)newcmd, cmdlen, "%s %s %s %s %s",
+ p_sh, p_shcf, p_sh, p_shcf, cmd);
+ else
+#endif
+ vim_snprintf((char *)newcmd, cmdlen, "%s %s %s",
+ p_sh, p_shcf, cmd);
+ x = mch_system((char *)newcmd, options);
+ vim_free(newcmd);
+ }
+ }
+ }
+
+ if (tmode == TMODE_RAW)
+ {
+ // The shell may have messed with the mode, always set it.
+ cur_tmode = TMODE_UNKNOWN;
+ settmode(TMODE_RAW); // set to raw mode
+ }
+
+ // Print the return value, unless "vimrun" was used.
+ if (x != 0 && !(options & SHELL_SILENT) && !emsg_silent
+#if defined(FEAT_GUI_MSWIN)
+ && ((gui.in_use || gui.starting) ?
+ ((options & SHELL_DOOUT) || s_dont_use_vimrun || !p_stmp) : 1)
+#endif
+ )
+ {
+ smsg(_("shell returned %d"), x);
+ msg_putchar('\n');
+ }
+ resettitle();
+
+ signal(SIGINT, SIG_DFL);
+#if defined(__GNUC__) && !defined(__MINGW32__)
+ signal(SIGKILL, SIG_DFL);
+#else
+ signal(SIGBREAK, SIG_DFL);
+#endif
+ signal(SIGILL, SIG_DFL);
+ signal(SIGFPE, SIG_DFL);
+ signal(SIGSEGV, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+ signal(SIGABRT, SIG_DFL);
+
+ return x;
+}
+
+#if defined(FEAT_JOB_CHANNEL) || defined(PROTO)
+ static HANDLE
+job_io_file_open(
+ char_u *fname,
+ DWORD dwDesiredAccess,
+ DWORD dwShareMode,
+ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
+ DWORD dwCreationDisposition,
+ DWORD dwFlagsAndAttributes)
+{
+ HANDLE h;
+ WCHAR *wn;
+
+ wn = enc_to_utf16(fname, NULL);
+ if (wn == NULL)
+ return INVALID_HANDLE_VALUE;
+
+ h = CreateFileW(wn, dwDesiredAccess, dwShareMode,
+ lpSecurityAttributes, dwCreationDisposition,
+ dwFlagsAndAttributes, NULL);
+ vim_free(wn);
+ return h;
+}
+
+/*
+ * Turn the dictionary "env" into a NUL separated list that can be used as the
+ * environment argument of vim_create_process().
+ */
+ void
+win32_build_env(dict_T *env, garray_T *gap, int is_terminal)
+{
+ hashitem_T *hi;
+ long_u todo = env != NULL ? env->dv_hashtab.ht_used : 0;
+ LPVOID base = GetEnvironmentStringsW();
+
+ // for last \0
+ if (ga_grow(gap, 1) == FAIL)
+ return;
+
+ if (env != NULL)
+ {
+ for (hi = env->dv_hashtab.ht_array; todo > 0; ++hi)
+ {
+ if (!HASHITEM_EMPTY(hi))
+ {
+ typval_T *item = &dict_lookup(hi)->di_tv;
+ WCHAR *wkey = enc_to_utf16((char_u *)hi->hi_key, NULL);
+ WCHAR *wval = enc_to_utf16(tv_get_string(item), NULL);
+ --todo;
+ if (wkey != NULL && wval != NULL)
+ {
+ size_t n;
+ size_t lkey = wcslen(wkey);
+ size_t lval = wcslen(wval);
+
+ if (ga_grow(gap, (int)(lkey + lval + 2)) == FAIL)
+ continue;
+ for (n = 0; n < lkey; n++)
+ *((WCHAR*)gap->ga_data + gap->ga_len++) = wkey[n];
+ *((WCHAR*)gap->ga_data + gap->ga_len++) = L'=';
+ for (n = 0; n < lval; n++)
+ *((WCHAR*)gap->ga_data + gap->ga_len++) = wval[n];
+ *((WCHAR*)gap->ga_data + gap->ga_len++) = L'\0';
+ }
+ vim_free(wkey);
+ vim_free(wval);
+ }
+ }
+ }
+
+ if (base)
+ {
+ WCHAR *p = (WCHAR*) base;
+
+ // for last \0
+ if (ga_grow(gap, 1) == FAIL)
+ return;
+
+ while (*p != 0 || *(p + 1) != 0)
+ {
+ if (ga_grow(gap, 1) == OK)
+ *((WCHAR*)gap->ga_data + gap->ga_len++) = *p;
+ p++;
+ }
+ FreeEnvironmentStrings(base);
+ *((WCHAR*)gap->ga_data + gap->ga_len++) = L'\0';
+ }
+
+# if defined(FEAT_CLIENTSERVER) || defined(FEAT_TERMINAL)
+ {
+# ifdef FEAT_CLIENTSERVER
+ char_u *servername = get_vim_var_str(VV_SEND_SERVER);
+ size_t servername_len = STRLEN(servername);
+# endif
+# ifdef FEAT_TERMINAL
+ char_u *version = get_vim_var_str(VV_VERSION);
+ size_t version_len = STRLEN(version);
+# endif
+ // size of "VIM_SERVERNAME=" and value,
+ // plus "VIM_TERMINAL=" and value,
+ // plus two terminating NULs
+ size_t n = 0
+# ifdef FEAT_CLIENTSERVER
+ + 15 + servername_len
+# endif
+# ifdef FEAT_TERMINAL
+ + 13 + version_len + 2
+# endif
+ ;
+
+ if (ga_grow(gap, (int)n) == OK)
+ {
+# ifdef FEAT_CLIENTSERVER
+ for (n = 0; n < 15; n++)
+ *((WCHAR*)gap->ga_data + gap->ga_len++) =
+ (WCHAR)"VIM_SERVERNAME="[n];
+ for (n = 0; n < servername_len; n++)
+ *((WCHAR*)gap->ga_data + gap->ga_len++) =
+ (WCHAR)servername[n];
+ *((WCHAR*)gap->ga_data + gap->ga_len++) = L'\0';
+# endif
+# ifdef FEAT_TERMINAL
+ if (is_terminal)
+ {
+ for (n = 0; n < 13; n++)
+ *((WCHAR*)gap->ga_data + gap->ga_len++) =
+ (WCHAR)"VIM_TERMINAL="[n];
+ for (n = 0; n < version_len; n++)
+ *((WCHAR*)gap->ga_data + gap->ga_len++) =
+ (WCHAR)version[n];
+ *((WCHAR*)gap->ga_data + gap->ga_len++) = L'\0';
+ }
+# endif
+ }
+ }
+# endif
+}
+
+/*
+ * Create a pair of pipes.
+ * Return TRUE for success, FALSE for failure.
+ */
+ static BOOL
+create_pipe_pair(HANDLE handles[2])
+{
+ static LONG s;
+ char name[64];
+ SECURITY_ATTRIBUTES sa;
+
+ sprintf(name, "\\\\?\\pipe\\vim-%08lx-%08lx",
+ GetCurrentProcessId(),
+ InterlockedIncrement(&s));
+
+ // Create named pipe. Max size of named pipe is 65535.
+ handles[1] = CreateNamedPipe(
+ name,
+ PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED,
+ PIPE_TYPE_BYTE | PIPE_NOWAIT,
+ 1, MAX_NAMED_PIPE_SIZE, 0, 0, NULL);
+
+ if (handles[1] == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ sa.nLength = sizeof(sa);
+ sa.bInheritHandle = TRUE;
+ sa.lpSecurityDescriptor = NULL;
+
+ handles[0] = CreateFile(name,
+ FILE_GENERIC_READ,
+ FILE_SHARE_READ, &sa,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
+
+ if (handles[0] == INVALID_HANDLE_VALUE)
+ {
+ CloseHandle(handles[1]);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+ void
+mch_job_start(char *cmd, job_T *job, jobopt_T *options)
+{
+ STARTUPINFO si;
+ PROCESS_INFORMATION pi;
+ HANDLE jo;
+ SECURITY_ATTRIBUTES saAttr;
+ channel_T *channel = NULL;
+ HANDLE ifd[2];
+ HANDLE ofd[2];
+ HANDLE efd[2];
+ garray_T ga;
+
+ int use_null_for_in = options->jo_io[PART_IN] == JIO_NULL;
+ int use_null_for_out = options->jo_io[PART_OUT] == JIO_NULL;
+ int use_null_for_err = options->jo_io[PART_ERR] == JIO_NULL;
+ int use_file_for_in = options->jo_io[PART_IN] == JIO_FILE;
+ int use_file_for_out = options->jo_io[PART_OUT] == JIO_FILE;
+ int use_file_for_err = options->jo_io[PART_ERR] == JIO_FILE;
+ int use_out_for_err = options->jo_io[PART_ERR] == JIO_OUT;
+
+ if (use_out_for_err && use_null_for_out)
+ use_null_for_err = TRUE;
+
+ ifd[0] = INVALID_HANDLE_VALUE;
+ ifd[1] = INVALID_HANDLE_VALUE;
+ ofd[0] = INVALID_HANDLE_VALUE;
+ ofd[1] = INVALID_HANDLE_VALUE;
+ efd[0] = INVALID_HANDLE_VALUE;
+ efd[1] = INVALID_HANDLE_VALUE;
+ ga_init2(&ga, sizeof(wchar_t), 500);
+
+ jo = CreateJobObject(NULL, NULL);
+ if (jo == NULL)
+ {
+ job->jv_status = JOB_FAILED;
+ goto failed;
+ }
+
+ if (options->jo_env != NULL)
+ win32_build_env(options->jo_env, &ga, FALSE);
+
+ ZeroMemory(&pi, sizeof(pi));
+ ZeroMemory(&si, sizeof(si));
+ si.cb = sizeof(si);
+ si.dwFlags |= STARTF_USESHOWWINDOW;
+ si.wShowWindow = SW_HIDE;
+
+ saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
+ saAttr.bInheritHandle = TRUE;
+ saAttr.lpSecurityDescriptor = NULL;
+
+ if (use_file_for_in)
+ {
+ char_u *fname = options->jo_io_name[PART_IN];
+
+ ifd[0] = job_io_file_open(fname, GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ &saAttr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL);
+ if (ifd[0] == INVALID_HANDLE_VALUE)
+ {
+ semsg(_(e_cant_open_file_str), fname);
+ goto failed;
+ }
+ }
+ else if (!use_null_for_in
+ && (!create_pipe_pair(ifd)
+ || !SetHandleInformation(ifd[1], HANDLE_FLAG_INHERIT, 0)))
+ goto failed;
+
+ if (use_file_for_out)
+ {
+ char_u *fname = options->jo_io_name[PART_OUT];
+
+ ofd[1] = job_io_file_open(fname, GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ &saAttr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL);
+ if (ofd[1] == INVALID_HANDLE_VALUE)
+ {
+ semsg(_(e_cant_open_file_str), fname);
+ goto failed;
+ }
+ }
+ else if (!use_null_for_out &&
+ (!CreatePipe(&ofd[0], &ofd[1], &saAttr, 0)
+ || !SetHandleInformation(ofd[0], HANDLE_FLAG_INHERIT, 0)))
+ goto failed;
+
+ if (use_file_for_err)
+ {
+ char_u *fname = options->jo_io_name[PART_ERR];
+
+ efd[1] = job_io_file_open(fname, GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ &saAttr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL);
+ if (efd[1] == INVALID_HANDLE_VALUE)
+ {
+ semsg(_(e_cant_open_file_str), fname);
+ goto failed;
+ }
+ }
+ else if (!use_out_for_err && !use_null_for_err &&
+ (!CreatePipe(&efd[0], &efd[1], &saAttr, 0)
+ || !SetHandleInformation(efd[0], HANDLE_FLAG_INHERIT, 0)))
+ goto failed;
+
+ si.dwFlags |= STARTF_USESTDHANDLES;
+ si.hStdInput = ifd[0];
+ si.hStdOutput = ofd[1];
+ si.hStdError = use_out_for_err ? ofd[1] : efd[1];
+
+ if (!use_null_for_in || !use_null_for_out || !use_null_for_err)
+ {
+ if (options->jo_set & JO_CHANNEL)
+ {
+ channel = options->jo_channel;
+ if (channel != NULL)
+ ++channel->ch_refcount;
+ }
+ else
+ channel = add_channel();
+ if (channel == NULL)
+ goto failed;
+ }
+
+ if (!vim_create_process(cmd, TRUE,
+ CREATE_SUSPENDED |
+ CREATE_DEFAULT_ERROR_MODE |
+ CREATE_NEW_PROCESS_GROUP |
+ CREATE_UNICODE_ENVIRONMENT |
+ CREATE_NEW_CONSOLE,
+ &si, &pi,
+ ga.ga_data,
+ (char *)options->jo_cwd))
+ {
+ CloseHandle(jo);
+ job->jv_status = JOB_FAILED;
+ goto failed;
+ }
+
+ ga_clear(&ga);
+
+ if (!AssignProcessToJobObject(jo, pi.hProcess))
+ {
+ // if failing, switch the way to terminate
+ // process with TerminateProcess.
+ CloseHandle(jo);
+ jo = NULL;
+ }
+ ResumeThread(pi.hThread);
+ CloseHandle(pi.hThread);
+ job->jv_proc_info = pi;
+ job->jv_job_object = jo;
+ job->jv_status = JOB_STARTED;
+
+ CloseHandle(ifd[0]);
+ CloseHandle(ofd[1]);
+ if (!use_out_for_err && !use_null_for_err)
+ CloseHandle(efd[1]);
+
+ job->jv_channel = channel;
+ if (channel != NULL)
+ {
+ channel_set_pipes(channel,
+ use_file_for_in || use_null_for_in
+ ? INVALID_FD : (sock_T)ifd[1],
+ use_file_for_out || use_null_for_out
+ ? INVALID_FD : (sock_T)ofd[0],
+ use_out_for_err || use_file_for_err || use_null_for_err
+ ? INVALID_FD : (sock_T)efd[0]);
+ channel_set_job(channel, job, options);
+ }
+ return;
+
+failed:
+ CloseHandle(ifd[0]);
+ CloseHandle(ofd[0]);
+ CloseHandle(efd[0]);
+ CloseHandle(ifd[1]);
+ CloseHandle(ofd[1]);
+ CloseHandle(efd[1]);
+ channel_unref(channel);
+ ga_clear(&ga);
+}
+
+ char *
+mch_job_status(job_T *job)
+{
+ DWORD dwExitCode = 0;
+
+ if (!GetExitCodeProcess(job->jv_proc_info.hProcess, &dwExitCode)
+ || dwExitCode != STILL_ACTIVE)
+ {
+ job->jv_exitval = (int)dwExitCode;
+ if (job->jv_status < JOB_ENDED)
+ {
+ ch_log(job->jv_channel, "Job ended");
+ job->jv_status = JOB_ENDED;
+ }
+ return "dead";
+ }
+ return "run";
+}
+
+ job_T *
+mch_detect_ended_job(job_T *job_list)
+{
+ HANDLE jobHandles[MAXIMUM_WAIT_OBJECTS];
+ job_T *jobArray[MAXIMUM_WAIT_OBJECTS];
+ job_T *job = job_list;
+
+ while (job != NULL)
+ {
+ DWORD n;
+ DWORD result;
+
+ for (n = 0; n < MAXIMUM_WAIT_OBJECTS
+ && job != NULL; job = job->jv_next)
+ {
+ if (job->jv_status == JOB_STARTED)
+ {
+ jobHandles[n] = job->jv_proc_info.hProcess;
+ jobArray[n] = job;
+ ++n;
+ }
+ }
+ if (n == 0)
+ continue;
+ result = WaitForMultipleObjects(n, jobHandles, FALSE, 0);
+ if (result >= WAIT_OBJECT_0 && result < WAIT_OBJECT_0 + n)
+ {
+ job_T *wait_job = jobArray[result - WAIT_OBJECT_0];
+
+ if (STRCMP(mch_job_status(wait_job), "dead") == 0)
+ return wait_job;
+ }
+ }
+ return NULL;
+}
+
+ static BOOL
+terminate_all(HANDLE process, int code)
+{
+ PROCESSENTRY32 pe;
+ HANDLE h = INVALID_HANDLE_VALUE;
+ DWORD pid = GetProcessId(process);
+
+ if (pid != 0)
+ {
+ h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if (h != INVALID_HANDLE_VALUE)
+ {
+ pe.dwSize = sizeof(PROCESSENTRY32);
+ if (!Process32First(h, &pe))
+ goto theend;
+
+ do
+ {
+ if (pe.th32ParentProcessID == pid)
+ {
+ HANDLE ph = OpenProcess(
+ PROCESS_ALL_ACCESS, FALSE, pe.th32ProcessID);
+ if (ph != NULL)
+ {
+ terminate_all(ph, code);
+ CloseHandle(ph);
+ }
+ }
+ } while (Process32Next(h, &pe));
+
+ CloseHandle(h);
+ }
+ }
+
+theend:
+ return TerminateProcess(process, code);
+}
+
+/*
+ * Send a (deadly) signal to "job".
+ * Return FAIL if it didn't work.
+ */
+ int
+mch_signal_job(job_T *job, char_u *how)
+{
+ int ret;
+
+ if (STRCMP(how, "term") == 0 || STRCMP(how, "kill") == 0 || *how == NUL)
+ {
+ // deadly signal
+ if (job->jv_job_object != NULL)
+ {
+ if (job->jv_channel != NULL && job->jv_channel->ch_anonymous_pipe)
+ job->jv_channel->ch_killing = TRUE;
+ return TerminateJobObject(job->jv_job_object, (UINT)-1) ? OK : FAIL;
+ }
+ return terminate_all(job->jv_proc_info.hProcess, -1) ? OK : FAIL;
+ }
+
+ if (!AttachConsole(job->jv_proc_info.dwProcessId))
+ return FAIL;
+ ret = GenerateConsoleCtrlEvent(
+ STRCMP(how, "int") == 0 ? CTRL_C_EVENT : CTRL_BREAK_EVENT,
+ job->jv_proc_info.dwProcessId)
+ ? OK : FAIL;
+ FreeConsole();
+ return ret;
+}
+
+/*
+ * Clear the data related to "job".
+ */
+ void
+mch_clear_job(job_T *job)
+{
+ if (job->jv_status == JOB_FAILED)
+ return;
+
+ if (job->jv_job_object != NULL)
+ CloseHandle(job->jv_job_object);
+ CloseHandle(job->jv_proc_info.hProcess);
+}
+#endif
+
+
+#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL)
+
+/*
+ * Start termcap mode
+ */
+ static void
+termcap_mode_start(void)
+{
+ DWORD cmodein;
+
+ if (g_fTermcapMode)
+ return;
+
+ SaveConsoleBuffer(&g_cbNonTermcap);
+
+ if (g_cbTermcap.IsValid)
+ {
+ /*
+ * We've been in termcap mode before. Restore certain screen
+ * characteristics, including the buffer size and the window
+ * size. Since we will be redrawing the screen, we don't need
+ * to restore the actual contents of the buffer.
+ */
+ RestoreConsoleBuffer(&g_cbTermcap, FALSE);
+ reset_console_color_rgb();
+ SetConsoleWindowInfo(g_hConOut, TRUE, &g_cbTermcap.Info.srWindow);
+ Rows = g_cbTermcap.Info.dwSize.Y;
+ Columns = g_cbTermcap.Info.dwSize.X;
+ }
+ else
+ {
+ /*
+ * This is our first time entering termcap mode. Clear the console
+ * screen buffer, and resize the buffer to match the current window
+ * size. We will use this as the size of our editing environment.
+ */
+ ClearConsoleBuffer(g_attrCurrent);
+ set_console_color_rgb();
+ ResizeConBufAndWindow(g_hConOut, Columns, Rows);
+ }
+
+ resettitle();
+
+ GetConsoleMode(g_hConIn, &cmodein);
+ if (g_fMouseActive)
+ {
+ cmodein |= ENABLE_MOUSE_INPUT;
+ cmodein &= ~ENABLE_QUICK_EDIT_MODE;
+ }
+ else
+ {
+ cmodein &= ~ENABLE_MOUSE_INPUT;
+ cmodein |= g_cmodein & ENABLE_QUICK_EDIT_MODE;
+ }
+ cmodein |= ENABLE_WINDOW_INPUT;
+ SetConsoleMode(g_hConIn, cmodein | ENABLE_EXTENDED_FLAGS);
+
+ redraw_later_clear();
+ g_fTermcapMode = TRUE;
+}
+
+
+/*
+ * End termcap mode
+ */
+ static void
+termcap_mode_end(void)
+{
+ DWORD cmodein;
+ ConsoleBuffer *cb;
+ COORD coord;
+ DWORD dwDummy;
+
+ if (!g_fTermcapMode)
+ return;
+
+ SaveConsoleBuffer(&g_cbTermcap);
+
+ GetConsoleMode(g_hConIn, &cmodein);
+ cmodein &= ~(ENABLE_MOUSE_INPUT | ENABLE_WINDOW_INPUT);
+ cmodein |= g_cmodein & ENABLE_QUICK_EDIT_MODE;
+ SetConsoleMode(g_hConIn, cmodein | ENABLE_EXTENDED_FLAGS);
+
+# ifdef FEAT_RESTORE_ORIG_SCREEN
+ cb = exiting ? &g_cbOrig : &g_cbNonTermcap;
+# else
+ cb = &g_cbNonTermcap;
+# endif
+ RestoreConsoleBuffer(cb, p_rs);
+ restore_console_color_rgb();
+
+ // Switch back to main screen buffer.
+ if (exiting && use_alternate_screen_buffer)
+ vtp_printf("\033[?1049l");
+
+ if (!USE_WT && (p_rs || exiting))
+ {
+ /*
+ * Clear anything that happens to be on the current line.
+ */
+ coord.X = 0;
+ coord.Y = (SHORT) (p_rs ? cb->Info.dwCursorPosition.Y : (Rows - 1));
+ FillConsoleOutputCharacter(g_hConOut, ' ',
+ cb->Info.dwSize.X, coord, &dwDummy);
+ /*
+ * The following is just for aesthetics. If we are exiting without
+ * restoring the screen, then we want to have a prompt string
+ * appear at the bottom line. However, the command interpreter
+ * seems to always advance the cursor one line before displaying
+ * the prompt string, which causes the screen to scroll. To
+ * counter this, move the cursor up one line before exiting.
+ */
+ if (exiting && !p_rs)
+ coord.Y--;
+ /*
+ * Position the cursor at the leftmost column of the desired row.
+ */
+ SetConsoleCursorPosition(g_hConOut, coord);
+ }
+ SetConsoleCursorInfo(g_hConOut, &g_cci);
+ g_fTermcapMode = FALSE;
+}
+#endif // !FEAT_GUI_MSWIN || VIMDLL
+
+
+#if defined(FEAT_GUI_MSWIN) && !defined(VIMDLL)
+ void
+mch_write(
+ char_u *s UNUSED,
+ int len UNUSED)
+{
+ // never used
+}
+
+#else
+
+/*
+ * clear `n' chars, starting from `coord'
+ */
+ static void
+clear_chars(
+ COORD coord,
+ DWORD n)
+{
+ if (!vtp_working)
+ {
+ DWORD dwDummy;
+
+ FillConsoleOutputCharacter(g_hConOut, ' ', n, coord, &dwDummy);
+ FillConsoleOutputAttribute(g_hConOut, g_attrCurrent, n, coord,
+ &dwDummy);
+ }
+ else
+ {
+ set_console_color_rgb();
+ gotoxy(coord.X + 1, coord.Y + 1);
+ vtp_printf("\033[%dX", n);
+ }
+}
+
+
+/*
+ * Clear the screen
+ */
+ static void
+clear_screen(void)
+{
+ g_coord.X = g_coord.Y = 0;
+
+ if (!vtp_working)
+ clear_chars(g_coord, Rows * Columns);
+ else
+ {
+ set_console_color_rgb();
+ gotoxy(1, 1);
+ vtp_printf("\033[2J");
+ }
+}
+
+
+/*
+ * Clear to end of display
+ */
+ static void
+clear_to_end_of_display(void)
+{
+ COORD save = g_coord;
+
+ if (!vtp_working)
+ clear_chars(g_coord, (Rows - g_coord.Y - 1)
+ * Columns + (Columns - g_coord.X));
+ else
+ {
+ set_console_color_rgb();
+ gotoxy(g_coord.X + 1, g_coord.Y + 1);
+ vtp_printf("\033[0J");
+
+ gotoxy(save.X + 1, save.Y + 1);
+ g_coord = save;
+ }
+}
+
+
+/*
+ * Clear to end of line
+ */
+ static void
+clear_to_end_of_line(void)
+{
+ COORD save = g_coord;
+
+ if (!vtp_working)
+ clear_chars(g_coord, Columns - g_coord.X);
+ else
+ {
+ set_console_color_rgb();
+ gotoxy(g_coord.X + 1, g_coord.Y + 1);
+ vtp_printf("\033[0K");
+
+ gotoxy(save.X + 1, save.Y + 1);
+ g_coord = save;
+ }
+}
+
+
+/*
+ * Scroll the scroll region up by `cLines' lines
+ */
+ static void
+scroll(unsigned cLines)
+{
+ COORD oldcoord = g_coord;
+
+ gotoxy(g_srScrollRegion.Left + 1, g_srScrollRegion.Top + 1);
+ delete_lines(cLines);
+
+ g_coord = oldcoord;
+}
+
+
+/*
+ * Set the scroll region
+ */
+ static void
+set_scroll_region(
+ unsigned left,
+ unsigned top,
+ unsigned right,
+ unsigned bottom)
+{
+ if (left >= right
+ || top >= bottom
+ || right > (unsigned) Columns - 1
+ || bottom > (unsigned) Rows - 1)
+ return;
+
+ g_srScrollRegion.Left = left;
+ g_srScrollRegion.Top = top;
+ g_srScrollRegion.Right = right;
+ g_srScrollRegion.Bottom = bottom;
+}
+
+ static void
+set_scroll_region_tb(
+ unsigned top,
+ unsigned bottom)
+{
+ if (top >= bottom || bottom > (unsigned)Rows - 1)
+ return;
+
+ g_srScrollRegion.Top = top;
+ g_srScrollRegion.Bottom = bottom;
+}
+
+ static void
+set_scroll_region_lr(
+ unsigned left,
+ unsigned right)
+{
+ if (left >= right || right > (unsigned)Columns - 1)
+ return;
+
+ g_srScrollRegion.Left = left;
+ g_srScrollRegion.Right = right;
+}
+
+
+/*
+ * Insert `cLines' lines at the current cursor position
+ */
+ static void
+insert_lines(unsigned cLines)
+{
+ SMALL_RECT source, clip;
+ COORD dest;
+ CHAR_INFO fill;
+
+ gotoxy(g_srScrollRegion.Left + 1, g_srScrollRegion.Top + 1);
+
+ dest.X = g_srScrollRegion.Left;
+ dest.Y = g_coord.Y + cLines;
+
+ source.Left = g_srScrollRegion.Left;
+ source.Top = g_coord.Y;
+ source.Right = g_srScrollRegion.Right;
+ source.Bottom = g_srScrollRegion.Bottom - cLines;
+
+ clip.Left = g_srScrollRegion.Left;
+ clip.Top = g_coord.Y;
+ clip.Right = g_srScrollRegion.Right;
+ clip.Bottom = g_srScrollRegion.Bottom;
+
+ fill.Char.AsciiChar = ' ';
+ if (!USE_VTP)
+ fill.Attributes = g_attrCurrent;
+ else
+ fill.Attributes = g_attrDefault;
+
+ set_console_color_rgb();
+
+ ScrollConsoleScreenBuffer(g_hConOut, &source, &clip, dest, &fill);
+
+ // Here we have to deal with a win32 console flake: If the scroll
+ // region looks like abc and we scroll c to a and fill with d we get
+ // cbd... if we scroll block c one line at a time to a, we get cdd...
+ // vim expects cdd consistently... So we have to deal with that
+ // here... (this also occurs scrolling the same way in the other
+ // direction).
+
+ if (source.Bottom < dest.Y)
+ {
+ COORD coord;
+ int i;
+
+ coord.X = source.Left;
+ for (i = clip.Top; i < dest.Y; ++i)
+ {
+ coord.Y = i;
+ clear_chars(coord, source.Right - source.Left + 1);
+ }
+ }
+
+ if (vtp_working)
+ {
+ COORD coord;
+ int i;
+
+ coord.X = source.Left;
+ for (i = source.Top; i < dest.Y; ++i)
+ {
+ coord.Y = i;
+ clear_chars(coord, source.Right - source.Left + 1);
+ }
+ }
+}
+
+
+/*
+ * Delete `cLines' lines at the current cursor position
+ */
+ static void
+delete_lines(unsigned cLines)
+{
+ SMALL_RECT source, clip;
+ COORD dest;
+ CHAR_INFO fill;
+ int nb;
+
+ gotoxy(g_srScrollRegion.Left + 1, g_srScrollRegion.Top + 1);
+
+ dest.X = g_srScrollRegion.Left;
+ dest.Y = g_coord.Y;
+
+ source.Left = g_srScrollRegion.Left;
+ source.Top = g_coord.Y + cLines;
+ source.Right = g_srScrollRegion.Right;
+ source.Bottom = g_srScrollRegion.Bottom;
+
+ clip.Left = g_srScrollRegion.Left;
+ clip.Top = g_coord.Y;
+ clip.Right = g_srScrollRegion.Right;
+ clip.Bottom = g_srScrollRegion.Bottom;
+
+ fill.Char.AsciiChar = ' ';
+ if (!vtp_working)
+ fill.Attributes = g_attrCurrent;
+ else
+ fill.Attributes = g_attrDefault;
+
+ set_console_color_rgb();
+
+ ScrollConsoleScreenBuffer(g_hConOut, &source, &clip, dest, &fill);
+
+ // Here we have to deal with a win32 console flake; See insert_lines()
+ // above.
+
+ nb = dest.Y + (source.Bottom - source.Top) + 1;
+
+ if (nb < source.Top)
+ {
+ COORD coord;
+ int i;
+
+ coord.X = source.Left;
+ for (i = nb; i < clip.Bottom; ++i)
+ {
+ coord.Y = i;
+ clear_chars(coord, source.Right - source.Left + 1);
+ }
+ }
+
+ if (vtp_working)
+ {
+ COORD coord;
+ int i;
+
+ coord.X = source.Left;
+ for (i = nb; i <= source.Bottom; ++i)
+ {
+ coord.Y = i;
+ clear_chars(coord, source.Right - source.Left + 1);
+ }
+ }
+}
+
+
+/*
+ * Set the cursor position to (x,y) (1-based).
+ */
+ static void
+gotoxy(
+ unsigned x,
+ unsigned y)
+{
+ if (x < 1 || x > (unsigned)Columns || y < 1 || y > (unsigned)Rows)
+ return;
+
+ if (!USE_VTP)
+ {
+ // There are reports of double-width characters not displayed
+ // correctly. This workaround should fix it, similar to how it's done
+ // for VTP.
+ g_coord.X = 0;
+ SetConsoleCursorPosition(g_hConOut, g_coord);
+
+ // external cursor coords are 1-based; internal are 0-based
+ g_coord.X = x - 1;
+ g_coord.Y = y - 1;
+ SetConsoleCursorPosition(g_hConOut, g_coord);
+ }
+ else
+ {
+ // Move the cursor to the left edge of the screen to prevent screen
+ // destruction. Insider build bug. Always enabled because it's cheap
+ // and avoids mistakes with recognizing the build.
+ vtp_printf("\033[%d;%dH", g_coord.Y + 1, 1);
+
+ vtp_printf("\033[%d;%dH", y, x);
+
+ g_coord.X = x - 1;
+ g_coord.Y = y - 1;
+ }
+}
+
+
+/*
+ * Set the current text attribute = (foreground | background)
+ * See ../runtime/doc/os_win32.txt for the numbers.
+ */
+ static void
+textattr(WORD wAttr)
+{
+ g_attrCurrent = wAttr & 0xff;
+
+ SetConsoleTextAttribute(g_hConOut, wAttr);
+}
+
+
+ static void
+textcolor(WORD wAttr)
+{
+ g_attrCurrent = (g_attrCurrent & 0xf0) + (wAttr & 0x0f);
+
+ if (!vtp_working)
+ SetConsoleTextAttribute(g_hConOut, g_attrCurrent);
+ else
+ vtp_sgr_bulk(wAttr);
+}
+
+
+ static void
+textbackground(WORD wAttr)
+{
+ g_attrCurrent = (g_attrCurrent & 0x0f) + ((wAttr & 0x0f) << 4);
+
+ if (!vtp_working)
+ SetConsoleTextAttribute(g_hConOut, g_attrCurrent);
+ else
+ vtp_sgr_bulk(wAttr);
+}
+
+
+/*
+ * restore the default text attribute (whatever we started with)
+ */
+ static void
+normvideo(void)
+{
+ if (!vtp_working)
+ textattr(g_attrDefault);
+ else
+ vtp_sgr_bulk(0);
+}
+
+
+static WORD g_attrPreStandout = 0;
+
+/*
+ * Make the text standout, by brightening it
+ */
+ static void
+standout(void)
+{
+ g_attrPreStandout = g_attrCurrent;
+
+ textattr((WORD) (g_attrCurrent|FOREGROUND_INTENSITY|BACKGROUND_INTENSITY));
+}
+
+
+/*
+ * Turn off standout mode
+ */
+ static void
+standend(void)
+{
+ if (g_attrPreStandout)
+ textattr(g_attrPreStandout);
+
+ g_attrPreStandout = 0;
+}
+
+
+/*
+ * Set normal fg/bg color, based on T_ME. Called when t_me has been set.
+ */
+ void
+mch_set_normal_colors(void)
+{
+ char_u *p;
+ int n;
+
+ cterm_normal_fg_color = (g_attrDefault & 0xf) + 1;
+ cterm_normal_bg_color = ((g_attrDefault >> 4) & 0xf) + 1;
+ if (
+# ifdef FEAT_TERMGUICOLORS
+ !p_tgc &&
+# endif
+ T_ME[0] == ESC && T_ME[1] == '|')
+ {
+ p = T_ME + 2;
+ n = getdigits(&p);
+ if (*p == 'm' && n > 0)
+ {
+ cterm_normal_fg_color = (n & 0xf) + 1;
+ cterm_normal_bg_color = ((n >> 4) & 0xf) + 1;
+ }
+ }
+# ifdef FEAT_TERMGUICOLORS
+ cterm_normal_fg_gui_color = INVALCOLOR;
+ cterm_normal_bg_gui_color = INVALCOLOR;
+# endif
+}
+
+
+/*
+ * visual bell: flash the screen
+ */
+ static void
+visual_bell(void)
+{
+ COORD coordOrigin = {0, 0};
+ WORD attrFlash = ~g_attrCurrent & 0xff;
+
+ DWORD dwDummy;
+ LPWORD oldattrs = NULL;
+
+# ifdef FEAT_TERMGUICOLORS
+ if (!(p_tgc || t_colors >= 256))
+# endif
+ {
+ oldattrs = ALLOC_MULT(WORD, Rows * Columns);
+ if (oldattrs == NULL)
+ return;
+ ReadConsoleOutputAttribute(g_hConOut, oldattrs, Rows * Columns,
+ coordOrigin, &dwDummy);
+ }
+
+ FillConsoleOutputAttribute(g_hConOut, attrFlash, Rows * Columns,
+ coordOrigin, &dwDummy);
+
+ Sleep(15); // wait for 15 msec
+
+ if (oldattrs != NULL)
+ {
+ WriteConsoleOutputAttribute(g_hConOut, oldattrs, Rows * Columns,
+ coordOrigin, &dwDummy);
+ vim_free(oldattrs);
+ }
+}
+
+
+/*
+ * Make the cursor visible or invisible
+ */
+ static void
+cursor_visible(BOOL fVisible)
+{
+ s_cursor_visible = fVisible;
+
+ if (vtp_working)
+ vtp_printf("\033[?25%c", fVisible ? 'h' : 'l');
+
+# ifdef MCH_CURSOR_SHAPE
+ mch_update_cursor();
+# endif
+}
+
+
+/*
+ * Write "cbToWrite" bytes in `pchBuf' to the screen.
+ * Returns the number of bytes actually written (at least one).
+ */
+ static DWORD
+write_chars(
+ char_u *pchBuf,
+ DWORD cbToWrite)
+{
+ COORD coord = g_coord;
+ DWORD written;
+ DWORD n, cchwritten;
+ static DWORD cells;
+ static WCHAR *unicodebuf = NULL;
+ static int unibuflen = 0;
+ static int length;
+ int cp = enc_utf8 ? CP_UTF8 : enc_codepage;
+ static WCHAR *utf8spbuf = NULL;
+ static int utf8splength;
+ static DWORD utf8spcells;
+ static WCHAR **utf8usingbuf = &unicodebuf;
+
+ if (cbToWrite != 1 || *pchBuf != ' ' || !enc_utf8)
+ {
+ utf8usingbuf = &unicodebuf;
+ do
+ {
+ length = MultiByteToWideChar(cp, 0, (LPCSTR)pchBuf, cbToWrite,
+ unicodebuf, unibuflen);
+ if (length && length <= unibuflen)
+ break;
+ vim_free(unicodebuf);
+ unicodebuf = length ? LALLOC_MULT(WCHAR, length) : NULL;
+ unibuflen = unibuflen ? 0 : length;
+ } while(1);
+ cells = mb_string2cells(pchBuf, cbToWrite);
+ }
+ else // cbToWrite == 1 && *pchBuf == ' ' && enc_utf8
+ {
+ if (utf8usingbuf != &utf8spbuf)
+ {
+ if (utf8spbuf == NULL)
+ {
+ cells = mb_string2cells((char_u *)" ", 1);
+ length = MultiByteToWideChar(CP_UTF8, 0, " ", 1, NULL, 0);
+ utf8spbuf = LALLOC_MULT(WCHAR, length);
+ if (utf8spbuf != NULL)
+ {
+ MultiByteToWideChar(CP_UTF8, 0, " ", 1, utf8spbuf, length);
+ utf8usingbuf = &utf8spbuf;
+ utf8splength = length;
+ utf8spcells = cells;
+ }
+ }
+ else
+ {
+ utf8usingbuf = &utf8spbuf;
+ length = utf8splength;
+ cells = utf8spcells;
+ }
+ }
+ }
+
+ if (!USE_VTP)
+ {
+ FillConsoleOutputAttribute(g_hConOut, g_attrCurrent, cells,
+ coord, &written);
+ // When writing fails or didn't write a single character, pretend one
+ // character was written, otherwise we get stuck.
+ if (WriteConsoleOutputCharacterW(g_hConOut, *utf8usingbuf, length,
+ coord, &cchwritten) == 0
+ || cchwritten == 0 || cchwritten == (DWORD)-1)
+ cchwritten = 1;
+ }
+ else
+ {
+ if (WriteConsoleW(g_hConOut, *utf8usingbuf, length, &cchwritten,
+ NULL) == 0 || cchwritten == 0)
+ cchwritten = 1;
+ }
+
+ if (cchwritten == (DWORD)length)
+ {
+ written = cbToWrite;
+ g_coord.X += (SHORT)cells;
+ }
+ else
+ {
+ char_u *p = pchBuf;
+ for (n = 0; n < cchwritten; n++)
+ MB_CPTR_ADV(p);
+ written = p - pchBuf;
+ g_coord.X += (SHORT)mb_string2cells(pchBuf, written);
+ }
+
+ while (g_coord.X > g_srScrollRegion.Right)
+ {
+ g_coord.X -= (SHORT) Columns;
+ if (g_coord.Y < g_srScrollRegion.Bottom)
+ g_coord.Y++;
+ }
+
+ // Cursor under VTP is always in the correct position, no need to reset.
+ if (!USE_VTP)
+ gotoxy(g_coord.X + 1, g_coord.Y + 1);
+
+ return written;
+}
+
+ static char_u *
+get_seq(
+ int *args,
+ int *count,
+ char_u *head)
+{
+ int argc;
+ char_u *p;
+
+ if (head == NULL || *head != '\033')
+ return NULL;
+
+ argc = 0;
+ p = head;
+ ++p;
+ do
+ {
+ ++p;
+ args[argc] = getdigits(&p);
+ argc += (argc < 15) ? 1 : 0;
+ } while (*p == ';');
+ *count = argc;
+
+ return p;
+}
+
+ static char_u *
+get_sgr(
+ int *args,
+ int *count,
+ char_u *head)
+{
+ char_u *p = get_seq(args, count, head);
+
+ return (p && *p == 'm') ? ++p : NULL;
+}
+
+/*
+ * Pointer to next if SGR (^[[n;2;*;*;*m), NULL otherwise.
+ */
+ static char_u *
+sgrn2(
+ char_u *head,
+ int n)
+{
+ int argc;
+ int args[16];
+ char_u *p = get_sgr(args, &argc, head);
+
+ return p && argc == 5 && args[0] == n && args[1] == 2 ? p : NULL;
+}
+
+/*
+ * Pointer to next if SGR(^[[nm)<space>ESC, NULL otherwise.
+ */
+ static char_u *
+sgrnc(
+ char_u *head,
+ int n)
+{
+ int argc;
+ int args[16];
+ char_u *p = get_sgr(args, &argc, head);
+
+ return p && argc == 1 && args[0] == n && (p = skipwhite(p)) && *p == '\033'
+ ? p : NULL;
+}
+
+ static char_u *
+skipblank(char_u *q)
+{
+ char_u *p = q;
+
+ while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')
+ ++p;
+ return p;
+}
+
+/*
+ * Pointer to the next if any whitespace that may follow SGR is ESC, otherwise
+ * NULL.
+ */
+ static char_u *
+sgrn2c(
+ char_u *head,
+ int n)
+{
+ char_u *p = sgrn2(head, n);
+
+ return p && *p != NUL && (p = skipblank(p)) && *p == '\033' ? p : NULL;
+}
+
+/*
+ * If there is only a newline between the sequence immediately following it,
+ * a pointer to the character following the newline is returned.
+ * Otherwise NULL.
+ */
+ static char_u *
+sgrn2cn(
+ char_u *head,
+ int n)
+{
+ char_u *p = sgrn2(head, n);
+
+ return p && p[0] == 0x0a && p[1] == '\033' ? ++p : NULL;
+}
+
+/*
+ * mch_write(): write the output buffer to the screen, translating ESC
+ * sequences into calls to console output routines.
+ */
+ void
+mch_write(
+ char_u *s,
+ int len)
+{
+ char_u *end = s + len;
+
+# ifdef VIMDLL
+ if (gui.in_use)
+ return;
+# endif
+
+ if (!term_console)
+ {
+ write(1, s, (unsigned)len);
+ return;
+ }
+
+ // translate ESC | sequences into faked bios calls
+ while (len--)
+ {
+ int prefix = -1;
+ char_u ch;
+
+ // While processing a sequence, on rare occasions it seems that another
+ // sequence may be inserted asynchronously.
+ if (len < 0)
+ {
+ redraw_all_later(UPD_CLEAR);
+ return;
+ }
+
+ while (s + ++prefix < end)
+ {
+ ch = s[prefix];
+ if (ch <= 0x1e && !(ch != '\n' && ch != '\r' && ch != '\b'
+ && ch != '\a' && ch != '\033'))
+ break;
+ }
+
+ if (p_wd)
+ {
+ WaitForChar(p_wd, FALSE);
+ if (prefix != 0)
+ prefix = 1;
+ }
+
+ if (prefix != 0)
+ {
+ DWORD nWritten;
+
+ nWritten = write_chars(s, prefix);
+# ifdef MCH_WRITE_DUMP
+ if (fdDump)
+ {
+ fputc('>', fdDump);
+ fwrite(s, sizeof(char_u), nWritten, fdDump);
+ fputs("<\n", fdDump);
+ }
+# endif
+ len -= (nWritten - 1);
+ s += nWritten;
+ }
+ else if (s[0] == '\n')
+ {
+ // \n, newline: go to the beginning of the next line or scroll
+ if (g_coord.Y == g_srScrollRegion.Bottom)
+ {
+ scroll(1);
+ gotoxy(g_srScrollRegion.Left + 1, g_srScrollRegion.Bottom + 1);
+ }
+ else
+ {
+ gotoxy(g_srScrollRegion.Left + 1, g_coord.Y + 2);
+ }
+# ifdef MCH_WRITE_DUMP
+ if (fdDump)
+ fputs("\\n\n", fdDump);
+# endif
+ s++;
+ }
+ else if (s[0] == '\r')
+ {
+ // \r, carriage return: go to beginning of line
+ gotoxy(g_srScrollRegion.Left+1, g_coord.Y + 1);
+# ifdef MCH_WRITE_DUMP
+ if (fdDump)
+ fputs("\\r\n", fdDump);
+# endif
+ s++;
+ }
+ else if (s[0] == '\b')
+ {
+ // \b, backspace: move cursor one position left
+ if (g_coord.X > g_srScrollRegion.Left)
+ g_coord.X--;
+ else if (g_coord.Y > g_srScrollRegion.Top)
+ {
+ g_coord.X = g_srScrollRegion.Right;
+ g_coord.Y--;
+ }
+ gotoxy(g_coord.X + 1, g_coord.Y + 1);
+# ifdef MCH_WRITE_DUMP
+ if (fdDump)
+ fputs("\\b\n", fdDump);
+# endif
+ s++;
+ }
+ else if (s[0] == '\a')
+ {
+ // \a, bell
+ MessageBeep(0xFFFFFFFF);
+# ifdef MCH_WRITE_DUMP
+ if (fdDump)
+ fputs("\\a\n", fdDump);
+# endif
+ s++;
+ }
+ else if (s[0] == ESC && len >= 3-1 && s[1] == '|')
+ {
+# ifdef MCH_WRITE_DUMP
+ char_u *old_s = s;
+# endif
+ char_u *p;
+ int arg1 = 0, arg2 = 0, argc = 0, args[16];
+ char_u *sp;
+
+ switch (s[2])
+ {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ if (*(p = get_seq(args, &argc, s)) != 'm')
+ goto notsgr;
+
+ p = s;
+
+ // Handling frequent optional sequences. Output to the screen
+ // takes too long, so do not output as much as possible.
+
+ // If resetFG,FG,BG,<cr>,BG,FG are connected, the preceding
+ // resetFG,FG,BG are omitted.
+ if (sgrn2(sgrn2(sgrn2cn(sgrn2(sgrnc(p, 39), 38), 48), 48), 38))
+ {
+ p = sgrn2(sgrn2(sgrnc(p, 39), 38), 48);
+ len = len + 1 - (int)(p - s);
+ s = p;
+ break;
+ }
+
+ // If FG,BG,BG,FG of SGR are connected, the first FG can be
+ // omitted.
+ if (sgrn2(sgrn2(sgrn2c((sp = sgrn2(p, 38)), 48), 48), 38))
+ p = sp;
+
+ // If FG,BG,FG,BG of SGR are connected, the first FG can be
+ // omitted.
+ if (sgrn2(sgrn2(sgrn2c((sp = sgrn2(p, 38)), 48), 38), 48))
+ p = sp;
+
+ // If BG,BG of SGR are connected, the first BG can be omitted.
+ if (sgrn2((sp = sgrn2(p, 48)), 48))
+ p = sp;
+
+ // If restoreFG and FG are connected, the restoreFG can be
+ // omitted.
+ if (sgrn2((sp = sgrnc(p, 39)), 38))
+ p = sp;
+
+ p = get_seq(args, &argc, p);
+
+notsgr:
+ arg1 = args[0];
+ arg2 = args[1];
+ if (*p == 'm')
+ {
+ if (argc == 1 && args[0] == 0)
+ normvideo();
+ else if (argc == 1)
+ {
+ if (USE_VTP)
+ textcolor((WORD)arg1);
+ else
+ textattr((WORD)arg1);
+ }
+ else if (vtp_working)
+ vtp_sgr_bulks(argc, args);
+ }
+ else if (argc == 2 && *p == 'H')
+ {
+ gotoxy(arg2, arg1);
+ }
+ else if (argc == 2 && *p == 'r')
+ {
+ set_scroll_region(0, arg1 - 1, Columns - 1, arg2 - 1);
+ }
+ else if (argc == 2 && *p == 'R')
+ {
+ set_scroll_region_tb(arg1, arg2);
+ }
+ else if (argc == 2 && *p == 'V')
+ {
+ set_scroll_region_lr(arg1, arg2);
+ }
+ else if (argc == 1 && *p == 'A')
+ {
+ gotoxy(g_coord.X + 1,
+ max(g_srScrollRegion.Top, g_coord.Y - arg1) + 1);
+ }
+ else if (argc == 1 && *p == 'b')
+ {
+ textbackground((WORD) arg1);
+ }
+ else if (argc == 1 && *p == 'C')
+ {
+ gotoxy(min(g_srScrollRegion.Right, g_coord.X + arg1) + 1,
+ g_coord.Y + 1);
+ }
+ else if (argc == 1 && *p == 'f')
+ {
+ textcolor((WORD) arg1);
+ }
+ else if (argc == 1 && *p == 'H')
+ {
+ gotoxy(1, arg1);
+ }
+ else if (argc == 1 && *p == 'L')
+ {
+ insert_lines(arg1);
+ }
+ else if (argc == 1 && *p == 'M')
+ {
+ delete_lines(arg1);
+ }
+
+ len -= (int)(p - s);
+ s = p + 1;
+ break;
+
+ case 'A':
+ gotoxy(g_coord.X + 1,
+ max(g_srScrollRegion.Top, g_coord.Y - 1) + 1);
+ goto got3;
+
+ case 'B':
+ visual_bell();
+ goto got3;
+
+ case 'C':
+ gotoxy(min(g_srScrollRegion.Right, g_coord.X + 1) + 1,
+ g_coord.Y + 1);
+ goto got3;
+
+ case 'E':
+ termcap_mode_end();
+ goto got3;
+
+ case 'F':
+ standout();
+ goto got3;
+
+ case 'f':
+ standend();
+ goto got3;
+
+ case 'H':
+ gotoxy(1, 1);
+ goto got3;
+
+ case 'j':
+ clear_to_end_of_display();
+ goto got3;
+
+ case 'J':
+ clear_screen();
+ goto got3;
+
+ case 'K':
+ clear_to_end_of_line();
+ goto got3;
+
+ case 'L':
+ insert_lines(1);
+ goto got3;
+
+ case 'M':
+ delete_lines(1);
+ goto got3;
+
+ case 'S':
+ termcap_mode_start();
+ goto got3;
+
+ case 'V':
+ cursor_visible(TRUE);
+ goto got3;
+
+ case 'v':
+ cursor_visible(FALSE);
+ goto got3;
+
+ got3:
+ s += 3;
+ len -= 2;
+ }
+
+# ifdef MCH_WRITE_DUMP
+ if (fdDump)
+ {
+ fputs("ESC | ", fdDump);
+ fwrite(old_s + 2, sizeof(char_u), s - old_s - 2, fdDump);
+ fputc('\n', fdDump);
+ }
+# endif
+ }
+ else if (s[0] == ESC && len >= 3-1 && s[1] == '[')
+ {
+ int l = 2;
+
+ if (isdigit(s[l]))
+ l++;
+ if (s[l] == ' ' && s[l + 1] == 'q')
+ {
+ // DECSCUSR (cursor style) sequences
+ if (vtp_working)
+ vtp_printf("%.*s", l + 2, s); // Pass through
+ s += l + 2;
+ len -= l + 1;
+ }
+ }
+ else
+ {
+ // Write a single character
+ DWORD nWritten;
+
+ nWritten = write_chars(s, 1);
+# ifdef MCH_WRITE_DUMP
+ if (fdDump)
+ {
+ fputc('>', fdDump);
+ fwrite(s, sizeof(char_u), nWritten, fdDump);
+ fputs("<\n", fdDump);
+ }
+# endif
+
+ len -= (nWritten - 1);
+ s += nWritten;
+ }
+ }
+
+# ifdef MCH_WRITE_DUMP
+ if (fdDump)
+ fflush(fdDump);
+# endif
+}
+
+#endif // FEAT_GUI_MSWIN
+
+
+/*
+ * Delay for "msec" milliseconds.
+ */
+ void
+mch_delay(
+ long msec,
+ int flags UNUSED)
+{
+#if defined(FEAT_GUI_MSWIN) && !defined(VIMDLL)
+ Sleep((int)msec); // never wait for input
+#else // Console
+# ifdef VIMDLL
+ if (gui.in_use)
+ {
+ Sleep((int)msec); // never wait for input
+ return;
+ }
+# endif
+ if (flags & MCH_DELAY_IGNOREINPUT)
+# ifdef FEAT_MZSCHEME
+ if (mzthreads_allowed() && p_mzq > 0 && msec > p_mzq)
+ {
+ int towait = p_mzq;
+
+ // if msec is large enough, wait by portions in p_mzq
+ while (msec > 0)
+ {
+ mzvim_check_threads();
+ if (msec < towait)
+ towait = msec;
+ Sleep(towait);
+ msec -= towait;
+ }
+ }
+ else
+# endif
+ Sleep((int)msec);
+ else
+ WaitForChar(msec, FALSE);
+#endif
+}
+
+
+/*
+ * This version of remove is not scared by a readonly (backup) file.
+ * This can also remove a symbolic link like Unix.
+ * Return 0 for success, -1 for failure.
+ */
+ int
+mch_remove(char_u *name)
+{
+ WCHAR *wn;
+ int n;
+
+ /*
+ * On Windows, deleting a directory's symbolic link is done by
+ * RemoveDirectory(): mch_rmdir. It seems unnatural, but it is fact.
+ */
+ if (mch_isdir(name) && mch_is_symbolic_link(name))
+ return mch_rmdir(name);
+
+ win32_setattrs(name, FILE_ATTRIBUTE_NORMAL);
+
+ wn = enc_to_utf16(name, NULL);
+ if (wn == NULL)
+ return -1;
+
+ n = DeleteFileW(wn) ? 0 : -1;
+ vim_free(wn);
+ return n;
+}
+
+
+/*
+ * Check for an "interrupt signal": CTRL-break or CTRL-C.
+ */
+ void
+mch_breakcheck(int force UNUSED)
+{
+#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL)
+# ifdef VIMDLL
+ if (!gui.in_use)
+# endif
+ if (g_fCtrlCPressed || g_fCBrkPressed)
+ {
+ ctrl_break_was_pressed = g_fCBrkPressed;
+ g_fCtrlCPressed = g_fCBrkPressed = FALSE;
+ got_int = TRUE;
+ }
+#endif
+}
+
+// physical RAM to leave for the OS
+#define WINNT_RESERVE_BYTES (256*1024*1024)
+
+/*
+ * How much main memory in KiB that can be used by VIM.
+ */
+ long_u
+mch_total_mem(int special UNUSED)
+{
+ MEMORYSTATUSEX ms;
+
+ // Need to use GlobalMemoryStatusEx() when there is more memory than
+ // what fits in 32 bits.
+ ms.dwLength = sizeof(MEMORYSTATUSEX);
+ GlobalMemoryStatusEx(&ms);
+ if (ms.ullAvailVirtual < ms.ullTotalPhys)
+ {
+ // Process address space fits in physical RAM, use all of it.
+ return (long_u)(ms.ullAvailVirtual / 1024);
+ }
+ if (ms.ullTotalPhys <= WINNT_RESERVE_BYTES)
+ {
+ // Catch old NT box or perverse hardware setup.
+ return (long_u)((ms.ullTotalPhys / 2) / 1024);
+ }
+ // Use physical RAM less reserve for OS + data.
+ return (long_u)((ms.ullTotalPhys - WINNT_RESERVE_BYTES) / 1024);
+}
+
+/*
+ * mch_wrename() works around a bug in rename (aka MoveFile) in
+ * Windows 95: rename("foo.bar", "foo.bar~") will generate a
+ * file whose short file name is "FOO.BAR" (its long file name will
+ * be correct: "foo.bar~"). Because a file can be accessed by
+ * either its SFN or its LFN, "foo.bar" has effectively been
+ * renamed to "foo.bar", which is not at all what was wanted. This
+ * seems to happen only when renaming files with three-character
+ * extensions by appending a suffix that does not include ".".
+ * Windows NT gets it right, however, with an SFN of "FOO~1.BAR".
+ *
+ * There is another problem, which isn't really a bug but isn't right either:
+ * When renaming "abcdef~1.txt" to "abcdef~1.txt~", the short name can be
+ * "abcdef~1.txt" again. This has been reported on Windows NT 4.0 with
+ * service pack 6. Doesn't seem to happen on Windows 98.
+ *
+ * Like rename(), returns 0 upon success, non-zero upon failure.
+ * Should probably set errno appropriately when errors occur.
+ */
+ int
+mch_wrename(WCHAR *wold, WCHAR *wnew)
+{
+ WCHAR *p;
+ int i;
+ WCHAR szTempFile[_MAX_PATH + 1];
+ WCHAR szNewPath[_MAX_PATH + 1];
+ HANDLE hf;
+
+ // No need to play tricks unless the file name contains a "~" as the
+ // seventh character.
+ p = wold;
+ for (i = 0; wold[i] != NUL; ++i)
+ if ((wold[i] == '/' || wold[i] == '\\' || wold[i] == ':')
+ && wold[i + 1] != 0)
+ p = wold + i + 1;
+ if ((int)(wold + i - p) < 8 || p[6] != '~')
+ return (MoveFileW(wold, wnew) == 0);
+
+ // Get base path of new file name. Undocumented feature: If pszNewFile is
+ // a directory, no error is returned and pszFilePart will be NULL.
+ if (GetFullPathNameW(wnew, _MAX_PATH, szNewPath, &p) == 0 || p == NULL)
+ return -1;
+ *p = NUL;
+
+ // Get (and create) a unique temporary file name in directory of new file
+ if (GetTempFileNameW(szNewPath, L"VIM", 0, szTempFile) == 0)
+ return -2;
+
+ // blow the temp file away
+ if (!DeleteFileW(szTempFile))
+ return -3;
+
+ // rename old file to the temp file
+ if (!MoveFileW(wold, szTempFile))
+ return -4;
+
+ // now create an empty file called pszOldFile; this prevents the operating
+ // system using pszOldFile as an alias (SFN) if we're renaming within the
+ // same directory. For example, we're editing a file called
+ // filename.asc.txt by its SFN, filena~1.txt. If we rename filena~1.txt
+ // to filena~1.txt~ (i.e., we're making a backup while writing it), the
+ // SFN for filena~1.txt~ will be filena~1.txt, by default, which will
+ // cause all sorts of problems later in buf_write(). So, we create an
+ // empty file called filena~1.txt and the system will have to find some
+ // other SFN for filena~1.txt~, such as filena~2.txt
+ if ((hf = CreateFileW(wold, GENERIC_WRITE, 0, NULL, CREATE_NEW,
+ FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE)
+ return -5;
+ if (!CloseHandle(hf))
+ return -6;
+
+ // rename the temp file to the new file
+ if (!MoveFileW(szTempFile, wnew))
+ {
+ // Renaming failed. Rename the file back to its old name, so that it
+ // looks like nothing happened.
+ (void)MoveFileW(szTempFile, wold);
+ return -7;
+ }
+
+ // Seems to be left around on Novell filesystems
+ DeleteFileW(szTempFile);
+
+ // finally, remove the empty old file
+ if (!DeleteFileW(wold))
+ return -8;
+
+ return 0;
+}
+
+
+/*
+ * Converts the filenames to UTF-16, then call mch_wrename().
+ * Like rename(), returns 0 upon success, non-zero upon failure.
+ */
+ int
+mch_rename(
+ const char *pszOldFile,
+ const char *pszNewFile)
+{
+ WCHAR *wold = NULL;
+ WCHAR *wnew = NULL;
+ int retval = -1;
+
+ wold = enc_to_utf16((char_u *)pszOldFile, NULL);
+ wnew = enc_to_utf16((char_u *)pszNewFile, NULL);
+ if (wold != NULL && wnew != NULL)
+ retval = mch_wrename(wold, wnew);
+ vim_free(wold);
+ vim_free(wnew);
+ return retval;
+}
+
+/*
+ * Get the default shell for the current hardware platform
+ */
+ char *
+default_shell(void)
+{
+ return "cmd.exe";
+}
+
+/*
+ * mch_access() extends access() to do more detailed check on network drives.
+ * Returns 0 if file "n" has access rights according to "p", -1 otherwise.
+ */
+ int
+mch_access(char *n, int p)
+{
+ HANDLE hFile;
+ int retval = -1; // default: fail
+ WCHAR *wn;
+
+ wn = enc_to_utf16((char_u *)n, NULL);
+ if (wn == NULL)
+ return -1;
+
+ if (mch_isdir((char_u *)n))
+ {
+ WCHAR TempNameW[_MAX_PATH + 16] = L"";
+
+ if (p & R_OK)
+ {
+ // Read check is performed by seeing if we can do a find file on
+ // the directory for any file.
+ int i;
+ WIN32_FIND_DATAW d;
+
+ for (i = 0; i < _MAX_PATH && wn[i] != 0; ++i)
+ TempNameW[i] = wn[i];
+ if (TempNameW[i - 1] != '\\' && TempNameW[i - 1] != '/')
+ TempNameW[i++] = '\\';
+ TempNameW[i++] = '*';
+ TempNameW[i++] = 0;
+
+ hFile = FindFirstFileW(TempNameW, &d);
+ if (hFile == INVALID_HANDLE_VALUE)
+ goto getout;
+ else
+ (void)FindClose(hFile);
+ }
+
+ if (p & W_OK)
+ {
+ // Trying to create a temporary file in the directory should catch
+ // directories on read-only network shares. However, in
+ // directories whose ACL allows writes but denies deletes will end
+ // up keeping the temporary file :-(.
+ if (!GetTempFileNameW(wn, L"VIM", 0, TempNameW))
+ goto getout;
+ else
+ DeleteFileW(TempNameW);
+ }
+ }
+ else
+ {
+ // Don't consider a file read-only if another process has opened it.
+ DWORD share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE;
+
+ // Trying to open the file for the required access does ACL, read-only
+ // network share, and file attribute checks.
+ DWORD access_mode = ((p & W_OK) ? GENERIC_WRITE : 0)
+ | ((p & R_OK) ? GENERIC_READ : 0);
+
+ hFile = CreateFileW(wn, access_mode, share_mode,
+ NULL, OPEN_EXISTING, 0, NULL);
+ if (hFile == INVALID_HANDLE_VALUE)
+ goto getout;
+ CloseHandle(hFile);
+ }
+
+ retval = 0; // success
+getout:
+ vim_free(wn);
+ return retval;
+}
+
+/*
+ * Version of open() that may use UTF-16 file name.
+ */
+ int
+mch_open(const char *name, int flags, int mode)
+{
+ WCHAR *wn;
+ int f;
+
+ wn = enc_to_utf16((char_u *)name, NULL);
+ if (wn == NULL)
+ return -1;
+
+ f = _wopen(wn, flags, mode);
+ vim_free(wn);
+ return f;
+}
+
+/*
+ * Version of fopen() that uses UTF-16 file name.
+ */
+ FILE *
+mch_fopen(const char *name, const char *mode)
+{
+ WCHAR *wn, *wm;
+ FILE *f = NULL;
+
+#if defined(DEBUG) && _MSC_VER >= 1400
+ // Work around an annoying assertion in the Microsoft debug CRT
+ // when mode's text/binary setting doesn't match _get_fmode().
+ char newMode = mode[strlen(mode) - 1];
+ int oldMode = 0;
+
+ _get_fmode(&oldMode);
+ if (newMode == 't')
+ _set_fmode(_O_TEXT);
+ else if (newMode == 'b')
+ _set_fmode(_O_BINARY);
+#endif
+ wn = enc_to_utf16((char_u *)name, NULL);
+ wm = enc_to_utf16((char_u *)mode, NULL);
+ if (wn != NULL && wm != NULL)
+ f = _wfopen(wn, wm);
+ vim_free(wn);
+ vim_free(wm);
+
+#if defined(DEBUG) && _MSC_VER >= 1400
+ _set_fmode(oldMode);
+#endif
+ return f;
+}
+
+/*
+ * SUB STREAM (aka info stream) handling:
+ *
+ * NTFS can have sub streams for each file. The normal contents of a file is
+ * stored in the main stream, and extra contents (author information, title and
+ * so on) can be stored in a sub stream. After Windows 2000, the user can
+ * access and store this information in sub streams via an explorer's property
+ * menu item in the right click menu. This information in sub streams was lost
+ * when copying only the main stream. Therefore we have to copy sub streams.
+ *
+ * Incomplete explanation:
+ * http://msdn.microsoft.com/library/en-us/dnw2k/html/ntfs5.asp
+ * More useful info and an example:
+ * http://www.sysinternals.com/ntw2k/source/misc.shtml#streams
+ */
+
+/*
+ * Copy info stream data "substream". Read from the file with BackupRead(sh)
+ * and write to stream "substream" of file "to".
+ * Errors are ignored.
+ */
+ static void
+copy_substream(HANDLE sh, void *context, WCHAR *to, WCHAR *substream, long len)
+{
+ HANDLE hTo;
+ WCHAR *to_name;
+
+ to_name = malloc((wcslen(to) + wcslen(substream) + 1) * sizeof(WCHAR));
+ wcscpy(to_name, to);
+ wcscat(to_name, substream);
+
+ hTo = CreateFileW(to_name, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+ if (hTo != INVALID_HANDLE_VALUE)
+ {
+ long done;
+ DWORD todo;
+ DWORD readcnt, written;
+ char buf[4096];
+
+ // Copy block of bytes at a time. Abort when something goes wrong.
+ for (done = 0; done < len; done += written)
+ {
+ // (size_t) cast for Borland C 5.5
+ todo = (DWORD)((size_t)(len - done) > sizeof(buf) ? sizeof(buf)
+ : (size_t)(len - done));
+ if (!BackupRead(sh, (LPBYTE)buf, todo, &readcnt,
+ FALSE, FALSE, context)
+ || readcnt != todo
+ || !WriteFile(hTo, buf, todo, &written, NULL)
+ || written != todo)
+ break;
+ }
+ CloseHandle(hTo);
+ }
+
+ free(to_name);
+}
+
+/*
+ * Copy info streams from file "from" to file "to".
+ */
+ static void
+copy_infostreams(char_u *from, char_u *to)
+{
+ WCHAR *fromw;
+ WCHAR *tow;
+ HANDLE sh;
+ WIN32_STREAM_ID sid;
+ int headersize;
+ WCHAR streamname[_MAX_PATH];
+ DWORD readcount;
+ void *context = NULL;
+ DWORD lo, hi;
+ int len;
+
+ // Convert the file names to wide characters.
+ fromw = enc_to_utf16(from, NULL);
+ tow = enc_to_utf16(to, NULL);
+ if (fromw != NULL && tow != NULL)
+ {
+ // Open the file for reading.
+ sh = CreateFileW(fromw, GENERIC_READ, FILE_SHARE_READ, NULL,
+ OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+ if (sh != INVALID_HANDLE_VALUE)
+ {
+ // Use BackupRead() to find the info streams. Repeat until we
+ // have done them all.
+ for (;;)
+ {
+ // Get the header to find the length of the stream name. If
+ // the "readcount" is zero we have done all info streams.
+ ZeroMemory(&sid, sizeof(WIN32_STREAM_ID));
+ headersize = (int)((char *)&sid.cStreamName - (char *)&sid.dwStreamId);
+ if (!BackupRead(sh, (LPBYTE)&sid, headersize,
+ &readcount, FALSE, FALSE, &context)
+ || readcount == 0)
+ break;
+
+ // We only deal with streams that have a name. The normal
+ // file data appears to be without a name, even though docs
+ // suggest it is called "::$DATA".
+ if (sid.dwStreamNameSize > 0)
+ {
+ // Read the stream name.
+ if (!BackupRead(sh, (LPBYTE)streamname,
+ sid.dwStreamNameSize,
+ &readcount, FALSE, FALSE, &context))
+ break;
+
+ // Copy an info stream with a name ":anything:$DATA".
+ // Skip "::$DATA", it has no stream name (examples suggest
+ // it might be used for the normal file contents).
+ // Note that BackupRead() counts bytes, but the name is in
+ // wide characters.
+ len = readcount / sizeof(WCHAR);
+ streamname[len] = 0;
+ if (len > 7 && wcsicmp(streamname + len - 6,
+ L":$DATA") == 0)
+ {
+ streamname[len - 6] = 0;
+ copy_substream(sh, &context, tow, streamname,
+ (long)sid.Size.u.LowPart);
+ }
+ }
+
+ // Advance to the next stream. We might try seeking too far,
+ // but BackupSeek() doesn't skip over stream borders, thus
+ // that's OK.
+ (void)BackupSeek(sh, sid.Size.u.LowPart, sid.Size.u.HighPart,
+ &lo, &hi, &context);
+ }
+
+ // Clear the context.
+ (void)BackupRead(sh, NULL, 0, &readcount, TRUE, FALSE, &context);
+
+ CloseHandle(sh);
+ }
+ }
+ vim_free(fromw);
+ vim_free(tow);
+}
+
+/*
+ * ntdll.dll definitions
+ */
+#define FileEaInformation 7
+#ifndef STATUS_SUCCESS
+# define STATUS_SUCCESS ((NTSTATUS) 0x00000000L)
+#endif
+
+typedef struct _FILE_FULL_EA_INFORMATION_ {
+ ULONG NextEntryOffset;
+ UCHAR Flags;
+ UCHAR EaNameLength;
+ USHORT EaValueLength;
+ CHAR EaName[1];
+} FILE_FULL_EA_INFORMATION_, *PFILE_FULL_EA_INFORMATION_;
+
+typedef struct _FILE_EA_INFORMATION_ {
+ ULONG EaSize;
+} FILE_EA_INFORMATION_, *PFILE_EA_INFORMATION_;
+
+#ifndef PROTO
+typedef NTSTATUS (NTAPI *PfnNtOpenFile)(
+ PHANDLE FileHandle,
+ ACCESS_MASK DesiredAccess,
+ POBJECT_ATTRIBUTES ObjectAttributes,
+ PIO_STATUS_BLOCK IoStatusBlock,
+ ULONG ShareAccess,
+ ULONG OpenOptions);
+typedef NTSTATUS (NTAPI *PfnNtClose)(
+ HANDLE Handle);
+typedef NTSTATUS (NTAPI *PfnNtSetEaFile)(
+ HANDLE FileHandle,
+ PIO_STATUS_BLOCK IoStatusBlock,
+ PVOID Buffer,
+ ULONG Length);
+typedef NTSTATUS (NTAPI *PfnNtQueryEaFile)(
+ HANDLE FileHandle,
+ PIO_STATUS_BLOCK IoStatusBlock,
+ PVOID Buffer,
+ ULONG Length,
+ BOOLEAN ReturnSingleEntry,
+ PVOID EaList,
+ ULONG EaListLength,
+ PULONG EaIndex,
+ BOOLEAN RestartScan);
+typedef NTSTATUS (NTAPI *PfnNtQueryInformationFile)(
+ HANDLE FileHandle,
+ PIO_STATUS_BLOCK IoStatusBlock,
+ PVOID FileInformation,
+ ULONG Length,
+ FILE_INFORMATION_CLASS FileInformationClass);
+typedef VOID (NTAPI *PfnRtlInitUnicodeString)(
+ PUNICODE_STRING DestinationString,
+ PCWSTR SourceString);
+
+PfnNtOpenFile pNtOpenFile = NULL;
+PfnNtClose pNtClose = NULL;
+PfnNtSetEaFile pNtSetEaFile = NULL;
+PfnNtQueryEaFile pNtQueryEaFile = NULL;
+PfnNtQueryInformationFile pNtQueryInformationFile = NULL;
+PfnRtlInitUnicodeString pRtlInitUnicodeString = NULL;
+#endif
+
+/*
+ * Load ntdll.dll functions.
+ */
+ static BOOL
+load_ntdll(void)
+{
+ static int loaded = -1;
+
+ if (loaded != -1)
+ return (BOOL) loaded;
+
+ HMODULE hNtdll = GetModuleHandle("ntdll.dll");
+ if (hNtdll != NULL)
+ {
+ pNtOpenFile = (PfnNtOpenFile) GetProcAddress(hNtdll, "NtOpenFile");
+ pNtClose = (PfnNtClose) GetProcAddress(hNtdll, "NtClose");
+ pNtSetEaFile = (PfnNtSetEaFile)
+ GetProcAddress(hNtdll, "NtSetEaFile");
+ pNtQueryEaFile = (PfnNtQueryEaFile)
+ GetProcAddress(hNtdll, "NtQueryEaFile");
+ pNtQueryInformationFile = (PfnNtQueryInformationFile)
+ GetProcAddress(hNtdll, "NtQueryInformationFile");
+ pRtlInitUnicodeString = (PfnRtlInitUnicodeString)
+ GetProcAddress(hNtdll, "RtlInitUnicodeString");
+ }
+ if (pNtOpenFile == NULL
+ || pNtClose == NULL
+ || pNtSetEaFile == NULL
+ || pNtQueryEaFile == NULL
+ || pNtQueryInformationFile == NULL
+ || pRtlInitUnicodeString == NULL)
+ loaded = FALSE;
+ else
+ loaded = TRUE;
+ return (BOOL) loaded;
+}
+
+/*
+ * Copy extended attributes (EA) from file "from" to file "to".
+ */
+ static void
+copy_extattr(char_u *from, char_u *to)
+{
+ char_u *fromf = NULL;
+ char_u *tof = NULL;
+ WCHAR *fromw = NULL;
+ WCHAR *tow = NULL;
+ UNICODE_STRING u;
+ HANDLE h;
+ OBJECT_ATTRIBUTES oa;
+ IO_STATUS_BLOCK iosb;
+ FILE_EA_INFORMATION_ eainfo = {0};
+ void *ea = NULL;
+
+ if (!load_ntdll())
+ return;
+
+ // Convert the file names to the fully qualified object names.
+ fromf = alloc(STRLEN(from) + 5);
+ tof = alloc(STRLEN(to) + 5);
+ if (fromf == NULL || tof == NULL)
+ goto theend;
+ STRCPY(fromf, "\\??\\");
+ STRCAT(fromf, from);
+ STRCPY(tof, "\\??\\");
+ STRCAT(tof, to);
+
+ // Convert the names to wide characters.
+ fromw = enc_to_utf16(fromf, NULL);
+ tow = enc_to_utf16(tof, NULL);
+ if (fromw == NULL || tow == NULL)
+ goto theend;
+
+ // Get the EA.
+ pRtlInitUnicodeString(&u, fromw);
+ InitializeObjectAttributes(&oa, &u, 0, NULL, NULL);
+ if (pNtOpenFile(&h, FILE_READ_EA, &oa, &iosb, 0,
+ FILE_NON_DIRECTORY_FILE) != STATUS_SUCCESS)
+ goto theend;
+ pNtQueryInformationFile(h, &iosb, &eainfo, sizeof(eainfo),
+ FileEaInformation);
+ if (eainfo.EaSize != 0)
+ {
+ ea = alloc(eainfo.EaSize);
+ if (ea != NULL)
+ {
+ if (pNtQueryEaFile(h, &iosb, ea, eainfo.EaSize, FALSE,
+ NULL, 0, NULL, TRUE) != STATUS_SUCCESS)
+ {
+ vim_free(ea);
+ ea = NULL;
+ }
+ }
+ }
+ pNtClose(h);
+
+ // Set the EA.
+ if (ea != NULL)
+ {
+ pRtlInitUnicodeString(&u, tow);
+ InitializeObjectAttributes(&oa, &u, 0, NULL, NULL);
+ if (pNtOpenFile(&h, FILE_WRITE_EA, &oa, &iosb, 0,
+ FILE_NON_DIRECTORY_FILE) != STATUS_SUCCESS)
+ goto theend;
+
+ pNtSetEaFile(h, &iosb, ea, eainfo.EaSize);
+ pNtClose(h);
+ }
+
+theend:
+ vim_free(fromf);
+ vim_free(tof);
+ vim_free(fromw);
+ vim_free(tow);
+ vim_free(ea);
+}
+
+/*
+ * Copy file attributes from file "from" to file "to".
+ * For Windows NT and later we copy info streams.
+ * Always returns zero, errors are ignored.
+ */
+ int
+mch_copy_file_attribute(char_u *from, char_u *to)
+{
+ // File streams only work on Windows NT and later.
+ copy_infostreams(from, to);
+ copy_extattr(from, to);
+ return 0;
+}
+
+
+/*
+ * The command line arguments in UTF-16
+ */
+static int nArgsW = 0;
+static LPWSTR *ArglistW = NULL;
+static int global_argc = 0;
+static char **global_argv;
+
+static int used_file_argc = 0; // last argument in global_argv[] used
+ // for the argument list.
+static int *used_file_indexes = NULL; // indexes in global_argv[] for
+ // command line arguments added to
+ // the argument list
+static int used_file_count = 0; // nr of entries in used_file_indexes
+static int used_file_literal = FALSE; // take file names literally
+static int used_file_full_path = FALSE; // file name was full path
+static int used_file_diff_mode = FALSE; // file name was with diff mode
+static int used_alist_count = 0;
+
+
+/*
+ * Get the command line arguments. Unicode version.
+ * Returns argc. Zero when something fails.
+ */
+ int
+get_cmd_argsW(char ***argvp)
+{
+ char **argv = NULL;
+ int argc = 0;
+ int i;
+
+ free_cmd_argsW();
+ ArglistW = CommandLineToArgvW(GetCommandLineW(), &nArgsW);
+ if (ArglistW != NULL)
+ {
+ argv = malloc((nArgsW + 1) * sizeof(char *));
+ if (argv != NULL)
+ {
+ argc = nArgsW;
+ argv[argc] = NULL;
+ for (i = 0; i < argc; ++i)
+ {
+ int len;
+
+ // Convert each Unicode argument to UTF-8.
+ WideCharToMultiByte_alloc(CP_UTF8, 0,
+ ArglistW[i], (int)wcslen(ArglistW[i]) + 1,
+ (LPSTR *)&argv[i], &len, 0, 0);
+ if (argv[i] == NULL)
+ {
+ // Out of memory, clear everything.
+ while (i > 0)
+ free(argv[--i]);
+ free(argv);
+ argv = NULL;
+ argc = 0;
+ }
+ }
+ }
+ }
+
+ global_argc = argc;
+ global_argv = argv;
+ if (argc > 0)
+ {
+ if (used_file_indexes != NULL)
+ free(used_file_indexes);
+ used_file_indexes = malloc(argc * sizeof(int));
+ }
+
+ if (argvp != NULL)
+ *argvp = argv;
+ return argc;
+}
+
+ void
+free_cmd_argsW(void)
+{
+ if (ArglistW == NULL)
+ return;
+
+ GlobalFree(ArglistW);
+ ArglistW = NULL;
+}
+
+/*
+ * Remember "name" is an argument that was added to the argument list.
+ * This avoids that we have to re-parse the argument list when fix_arg_enc()
+ * is called.
+ */
+ void
+used_file_arg(char *name, int literal, int full_path, int diff_mode)
+{
+ int i;
+
+ if (used_file_indexes == NULL)
+ return;
+ for (i = used_file_argc + 1; i < global_argc; ++i)
+ if (STRCMP(global_argv[i], name) == 0)
+ {
+ used_file_argc = i;
+ used_file_indexes[used_file_count++] = i;
+ break;
+ }
+ used_file_literal = literal;
+ used_file_full_path = full_path;
+ used_file_diff_mode = diff_mode;
+}
+
+/*
+ * Remember the length of the argument list as it was. If it changes then we
+ * leave it alone when 'encoding' is set.
+ */
+ void
+set_alist_count(void)
+{
+ used_alist_count = GARGCOUNT;
+}
+
+/*
+ * Fix the encoding of the command line arguments. Invoked when 'encoding'
+ * has been changed while starting up. Use the UTF-16 command line arguments
+ * and convert them to 'encoding'.
+ */
+ void
+fix_arg_enc(void)
+{
+ int i;
+ int idx;
+ char_u *str;
+ int *fnum_list;
+
+ // Safety checks:
+ // - if argument count differs between the wide and non-wide argument
+ // list, something must be wrong.
+ // - the file name arguments must have been located.
+ // - the length of the argument list wasn't changed by the user.
+ if (global_argc != nArgsW
+ || ArglistW == NULL
+ || used_file_indexes == NULL
+ || used_file_count == 0
+ || used_alist_count != GARGCOUNT)
+ return;
+
+ // Remember the buffer numbers for the arguments.
+ fnum_list = ALLOC_MULT(int, GARGCOUNT);
+ if (fnum_list == NULL)
+ return; // out of memory
+ for (i = 0; i < GARGCOUNT; ++i)
+ fnum_list[i] = GARGLIST[i].ae_fnum;
+
+ // Clear the argument list. Make room for the new arguments.
+ alist_clear(&global_alist);
+ if (ga_grow(&global_alist.al_ga, used_file_count) == FAIL)
+ return; // out of memory
+
+ for (i = 0; i < used_file_count; ++i)
+ {
+ idx = used_file_indexes[i];
+ str = utf16_to_enc(ArglistW[idx], NULL);
+ if (str != NULL)
+ {
+ int literal = used_file_literal;
+
+#ifdef FEAT_DIFF
+ // When using diff mode may need to concatenate file name to
+ // directory name. Just like it's done in main().
+ if (used_file_diff_mode && mch_isdir(str) && GARGCOUNT > 0
+ && !mch_isdir(alist_name(&GARGLIST[0])))
+ {
+ char_u *r;
+
+ r = concat_fnames(str, gettail(alist_name(&GARGLIST[0])), TRUE);
+ if (r != NULL)
+ {
+ vim_free(str);
+ str = r;
+ }
+ }
+#endif
+ // Re-use the old buffer by renaming it. When not using literal
+ // names it's done by alist_expand() below.
+ if (used_file_literal)
+ buf_set_name(fnum_list[i], str);
+
+ // Check backtick literal. backtick literal is already expanded in
+ // main.c, so this part add str as literal.
+ if (literal == FALSE)
+ {
+ size_t len = STRLEN(str);
+
+ if (len > 2 && *str == '`' && *(str + len - 1) == '`')
+ literal = TRUE;
+ }
+ alist_add(&global_alist, str, literal ? 2 : 0);
+ }
+ }
+
+ if (!used_file_literal)
+ {
+ // Now expand wildcards in the arguments.
+ // Temporarily add '(' and ')' to 'isfname'. These are valid
+ // filename characters but are excluded from 'isfname' to make
+ // "gf" work on a file name in parentheses (e.g.: see vim.h).
+ // Also, unset wildignore to not be influenced by this option.
+ // The arguments specified in command-line should be kept even if
+ // encoding options were changed.
+ // Use :legacy so that it also works when in Vim9 script.
+ do_cmdline_cmd((char_u *)":legacy let g:SaVe_ISF = &isf|set isf+=(,)");
+ do_cmdline_cmd((char_u *)":legacy let g:SaVe_WIG = &wig|set wig=");
+ alist_expand(fnum_list, used_alist_count);
+ do_cmdline_cmd(
+ (char_u *)":legacy let &isf = g:SaVe_ISF|unlet g:SaVe_ISF");
+ do_cmdline_cmd(
+ (char_u *)":legacy let &wig = g:SaVe_WIG|unlet g:SaVe_WIG");
+ }
+
+ // If wildcard expansion failed, we are editing the first file of the
+ // arglist and there is no file name: Edit the first argument now.
+ if (curwin->w_arg_idx == 0 && curbuf->b_fname == NULL)
+ {
+ do_cmdline_cmd((char_u *)":rewind");
+ if (GARGCOUNT == 1 && used_file_full_path
+ && vim_chdirfile(alist_name(&GARGLIST[0]), "drop") == OK)
+ last_chdir_reason = "drop";
+ }
+
+ set_alist_count();
+}
+
+ int
+mch_setenv(char *var, char *value, int x UNUSED)
+{
+ char_u *envbuf;
+ WCHAR *p;
+
+ envbuf = alloc(STRLEN(var) + STRLEN(value) + 2);
+ if (envbuf == NULL)
+ return -1;
+
+ sprintf((char *)envbuf, "%s=%s", var, value);
+
+ p = enc_to_utf16(envbuf, NULL);
+
+ vim_free(envbuf);
+ if (p == NULL)
+ return -1;
+ _wputenv(p);
+#ifdef libintl_wputenv
+ libintl_wputenv(p);
+#endif
+ // Unlike Un*x systems, we can free the string for _wputenv().
+ vim_free(p);
+
+ return 0;
+}
+
+/*
+ * Support for 256 colors and 24-bit colors was added in Windows 10
+ * version 1703 (Creators update).
+ */
+#define VTP_FIRST_SUPPORT_BUILD MAKE_VER(10, 0, 15063)
+
+/*
+ * Support for pseudo-console (ConPTY) was added in windows 10
+ * version 1809 (October 2018 update).
+ */
+#define CONPTY_FIRST_SUPPORT_BUILD MAKE_VER(10, 0, 17763)
+
+/*
+ * ConPTY differences between versions, need different logic.
+ * version 1903 (May 2019 update).
+ */
+#define CONPTY_1903_BUILD MAKE_VER(10, 0, 18362)
+
+/*
+ * version 1909 (November 2019 update).
+ */
+#define CONPTY_1909_BUILD MAKE_VER(10, 0, 18363)
+
+/*
+ * Stay ahead of the next update, and when it's done, fix this.
+ * version ? (2020 update, temporarily use the build number of insider preview)
+ */
+#define CONPTY_NEXT_UPDATE_BUILD MAKE_VER(10, 0, 19587)
+
+/*
+ * Confirm until this version. Also the logic changes.
+ * insider preview.
+ */
+#define CONPTY_INSIDER_BUILD MAKE_VER(10, 0, 18995)
+
+/*
+ * Not stable now.
+ */
+#define CONPTY_STABLE_BUILD MAKE_VER(10, 0, 32767) // T.B.D.
+// Notes:
+// Win 10 22H2 Final is build 19045, it's conpty is widely used.
+// Strangely, 19045 is newer but is a lower build number than the 2020 insider
+// preview which had a build 19587. And, not sure how stable that was?
+// Win Server 2022 (May 10, 2022) is build 20348, its conpty is widely used.
+// Win 11 starts from build 22000, even though the major version says 10!
+
+ static void
+vtp_flag_init(void)
+{
+ DWORD ver = get_build_number();
+#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL)
+ DWORD mode;
+ HANDLE out;
+
+# ifdef VIMDLL
+ if (!gui.in_use)
+# endif
+ {
+ out = GetStdHandle(STD_OUTPUT_HANDLE);
+
+ vtp_working = (ver >= VTP_FIRST_SUPPORT_BUILD) ? 1 : 0;
+ GetConsoleMode(out, &mode);
+ mode |= (ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
+ if (SetConsoleMode(out, mode) == 0)
+ vtp_working = 0;
+
+ // VTP uses alternate screen buffer.
+ // But, not if running in a nested terminal
+ use_alternate_screen_buffer = win10_22H2_or_later && p_rs && vtp_working
+ && !mch_getenv("VIM_TERMINAL");
+ }
+#endif
+
+ if (ver >= CONPTY_FIRST_SUPPORT_BUILD)
+ conpty_working = 1;
+ if (ver >= CONPTY_STABLE_BUILD)
+ conpty_stable = 1;
+
+ if (ver <= CONPTY_INSIDER_BUILD)
+ conpty_type = 3;
+ if (ver <= CONPTY_1909_BUILD)
+ conpty_type = 2;
+ if (ver <= CONPTY_1903_BUILD)
+ conpty_type = 2;
+ if (ver < CONPTY_FIRST_SUPPORT_BUILD)
+ conpty_type = 1;
+
+ if (ver >= CONPTY_NEXT_UPDATE_BUILD)
+ conpty_fix_type = 1;
+}
+
+#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL) || defined(PROTO)
+
+ static void
+vtp_init(void)
+{
+# ifdef FEAT_TERMGUICOLORS
+ CONSOLE_SCREEN_BUFFER_INFOEX csbi;
+ csbi.cbSize = sizeof(csbi);
+ GetConsoleScreenBufferInfoEx(g_hConOut, &csbi);
+ save_console_bg_rgb = (guicolor_T)csbi.ColorTable[g_color_index_bg];
+ save_console_fg_rgb = (guicolor_T)csbi.ColorTable[g_color_index_fg];
+ store_console_bg_rgb = save_console_bg_rgb;
+ store_console_fg_rgb = save_console_fg_rgb;
+
+ COLORREF bg;
+ bg = (COLORREF)csbi.ColorTable[g_color_index_bg];
+ bg = (GetRValue(bg) << 16) | (GetGValue(bg) << 8) | GetBValue(bg);
+ default_console_color_bg = bg;
+
+ COLORREF fg;
+ fg = (COLORREF)csbi.ColorTable[g_color_index_fg];
+ fg = (GetRValue(fg) << 16) | (GetGValue(fg) << 8) | GetBValue(fg);
+ default_console_color_fg = fg;
+# endif
+ set_console_color_rgb();
+}
+
+ static void
+vtp_exit(void)
+{
+ restore_console_color_rgb();
+}
+
+ int
+vtp_printf(
+ char *format,
+ ...)
+{
+ char_u buf[100];
+ va_list list;
+ DWORD result;
+ int len;
+
+ va_start(list, format);
+ len = vim_vsnprintf((char *)buf, 100, (char *)format, list);
+ va_end(list);
+ WriteConsoleA(g_hConOut, buf, (DWORD)len, &result, NULL);
+ return (int)result;
+}
+
+ static void
+vtp_sgr_bulk(
+ int arg)
+{
+ int args[1];
+
+ args[0] = arg;
+ vtp_sgr_bulks(1, args);
+}
+
+# define FAST256(x) \
+ if ((*p-- = "0123456789"[(n = x % 10)]) \
+ && x >= 10 && (*p-- = "0123456789"[((m = x % 100) - n) / 10]) \
+ && x >= 100 && (*p-- = "012"[((x & 0xff) - m) / 100]));
+
+# define FAST256CASE(x) \
+ case x: \
+ FAST256(newargs[x - 1]);
+
+ static void
+vtp_sgr_bulks(
+ int argc,
+ int *args)
+{
+# define MAXSGR 16
+# define SGRBUFSIZE 2 + 4 * MAXSGR + 1 // '\033[' + SGR + 'm'
+ char_u buf[SGRBUFSIZE];
+ char_u *p;
+ int in, out;
+ int newargs[16];
+ static int sgrfgr = -1, sgrfgg, sgrfgb;
+ static int sgrbgr = -1, sgrbgg, sgrbgb;
+
+ if (argc == 0)
+ {
+ sgrfgr = sgrbgr = -1;
+ vtp_printf("\033[m");
+ return;
+ }
+
+ in = out = 0;
+ while (in < argc)
+ {
+ int s = args[in];
+ int copylen = 1;
+
+ if (s == 38)
+ {
+ if (argc - in >= 5 && args[in + 1] == 2)
+ {
+ if (sgrfgr == args[in + 2] && sgrfgg == args[in + 3]
+ && sgrfgb == args[in + 4])
+ {
+ in += 5;
+ copylen = 0;
+ }
+ else
+ {
+ sgrfgr = args[in + 2];
+ sgrfgg = args[in + 3];
+ sgrfgb = args[in + 4];
+ copylen = 5;
+ }
+ }
+ else if (argc - in >= 3 && args[in + 1] == 5)
+ {
+ sgrfgr = -1;
+ copylen = 3;
+ }
+ }
+ else if (s == 48)
+ {
+ if (argc - in >= 5 && args[in + 1] == 2)
+ {
+ if (sgrbgr == args[in + 2] && sgrbgg == args[in + 3]
+ && sgrbgb == args[in + 4])
+ {
+ in += 5;
+ copylen = 0;
+ }
+ else
+ {
+ sgrbgr = args[in + 2];
+ sgrbgg = args[in + 3];
+ sgrbgb = args[in + 4];
+ copylen = 5;
+ }
+ }
+ else if (argc - in >= 3 && args[in + 1] == 5)
+ {
+ sgrbgr = -1;
+ copylen = 3;
+ }
+ }
+ else if (30 <= s && s <= 39)
+ sgrfgr = -1;
+ else if (90 <= s && s <= 97)
+ sgrfgr = -1;
+ else if (40 <= s && s <= 49)
+ sgrbgr = -1;
+ else if (100 <= s && s <= 107)
+ sgrbgr = -1;
+ else if (s == 0)
+ sgrfgr = sgrbgr = -1;
+
+ while (copylen--)
+ newargs[out++] = args[in++];
+ }
+
+ p = &buf[sizeof(buf) - 1];
+ *p-- = 'm';
+
+ switch (out)
+ {
+ int n, m;
+ DWORD r;
+
+ FAST256CASE(16);
+ *p-- = ';';
+ FAST256CASE(15);
+ *p-- = ';';
+ FAST256CASE(14);
+ *p-- = ';';
+ FAST256CASE(13);
+ *p-- = ';';
+ FAST256CASE(12);
+ *p-- = ';';
+ FAST256CASE(11);
+ *p-- = ';';
+ FAST256CASE(10);
+ *p-- = ';';
+ FAST256CASE(9);
+ *p-- = ';';
+ FAST256CASE(8);
+ *p-- = ';';
+ FAST256CASE(7);
+ *p-- = ';';
+ FAST256CASE(6);
+ *p-- = ';';
+ FAST256CASE(5);
+ *p-- = ';';
+ FAST256CASE(4);
+ *p-- = ';';
+ FAST256CASE(3);
+ *p-- = ';';
+ FAST256CASE(2);
+ *p-- = ';';
+ FAST256CASE(1);
+ *p-- = '[';
+ *p = '\033';
+ WriteConsoleA(g_hConOut, p, (DWORD)(&buf[SGRBUFSIZE] - p), &r, NULL);
+ default:
+ break;
+ }
+}
+
+ static void
+wt_init(void)
+{
+ wt_working = mch_getenv("WT_SESSION") != NULL;
+}
+
+# ifdef FEAT_TERMGUICOLORS
+ static int
+ctermtoxterm(
+ int cterm)
+{
+ char_u r, g, b, idx;
+
+ cterm_color2rgb(cterm, &r, &g, &b, &idx);
+ return (((int)r << 16) | ((int)g << 8) | (int)b);
+}
+# endif
+
+ static void
+set_console_color_rgb(void)
+{
+# ifdef FEAT_TERMGUICOLORS
+ CONSOLE_SCREEN_BUFFER_INFOEX csbi;
+ guicolor_T fg, bg;
+ int ctermfg, ctermbg;
+
+ if (!vtp_working)
+ return;
+
+ get_default_console_color(&ctermfg, &ctermbg, &fg, &bg);
+
+ if (p_tgc || t_colors >= 256)
+ {
+ term_fg_rgb_color(fg);
+ term_bg_rgb_color(bg);
+ return;
+ }
+
+ if (use_alternate_screen_buffer)
+ return;
+
+ fg = (GetRValue(fg) << 16) | (GetGValue(fg) << 8) | GetBValue(fg);
+ bg = (GetRValue(bg) << 16) | (GetGValue(bg) << 8) | GetBValue(bg);
+
+ csbi.cbSize = sizeof(csbi);
+ GetConsoleScreenBufferInfoEx(g_hConOut, &csbi);
+
+ csbi.cbSize = sizeof(csbi);
+ csbi.srWindow.Right += 1;
+ csbi.srWindow.Bottom += 1;
+ store_console_bg_rgb = csbi.ColorTable[g_color_index_bg];
+ store_console_fg_rgb = csbi.ColorTable[g_color_index_fg];
+ csbi.ColorTable[g_color_index_bg] = (COLORREF)bg;
+ csbi.ColorTable[g_color_index_fg] = (COLORREF)fg;
+ SetConsoleScreenBufferInfoEx(g_hConOut, &csbi);
+# endif
+}
+
+# if defined(FEAT_TERMGUICOLORS) || defined(PROTO)
+ void
+get_default_console_color(
+ int *cterm_fg,
+ int *cterm_bg,
+ guicolor_T *gui_fg,
+ guicolor_T *gui_bg)
+{
+ int id;
+ guicolor_T guifg = INVALCOLOR;
+ guicolor_T guibg = INVALCOLOR;
+ int ctermfg = 0;
+ int ctermbg = 0;
+ int dummynull = 0;
+
+ id = syn_name2id((char_u *)"Normal");
+ if (id > 0 && p_tgc)
+ syn_id2colors(id, &guifg, &guibg);
+ if (guifg == INVALCOLOR)
+ {
+ ctermfg = -1;
+ if (id > 0)
+ syn_id2cterm_bg(id, &ctermfg, &dummynull);
+ if (ctermfg != -1)
+ guifg = ctermtoxterm(ctermfg);
+ else
+ guifg = USE_WT ? INVALCOLOR : default_console_color_fg;
+ cterm_normal_fg_gui_color = guifg;
+ ctermfg = ctermfg < 0 ? 0 : ctermfg;
+ }
+ if (guibg == INVALCOLOR)
+ {
+ ctermbg = -1;
+ if (id > 0)
+ syn_id2cterm_bg(id, &dummynull, &ctermbg);
+ if (ctermbg != -1)
+ guibg = ctermtoxterm(ctermbg);
+ else
+ guibg = USE_WT ? INVALCOLOR : default_console_color_bg;
+ cterm_normal_bg_gui_color = guibg;
+ ctermbg = ctermbg < 0 ? 0 : ctermbg;
+ }
+
+ *cterm_fg = ctermfg;
+ *cterm_bg = ctermbg;
+ *gui_fg = guifg;
+ *gui_bg = guibg;
+}
+# endif
+
+/*
+ * Set the console colors to the original colors or the last set colors.
+ */
+ static void
+reset_console_color_rgb(void)
+{
+# ifdef FEAT_TERMGUICOLORS
+ if (use_alternate_screen_buffer)
+ return;
+
+ CONSOLE_SCREEN_BUFFER_INFOEX csbi;
+
+ csbi.cbSize = sizeof(csbi);
+ GetConsoleScreenBufferInfoEx(g_hConOut, &csbi);
+
+ csbi.cbSize = sizeof(csbi);
+ csbi.srWindow.Right += 1;
+ csbi.srWindow.Bottom += 1;
+ csbi.ColorTable[g_color_index_bg] = (COLORREF)store_console_bg_rgb;
+ csbi.ColorTable[g_color_index_fg] = (COLORREF)store_console_fg_rgb;
+ SetConsoleScreenBufferInfoEx(g_hConOut, &csbi);
+# endif
+}
+
+/*
+ * Set the console colors to the original colors.
+ */
+ static void
+restore_console_color_rgb(void)
+{
+# ifdef FEAT_TERMGUICOLORS
+ if (use_alternate_screen_buffer)
+ return;
+
+ CONSOLE_SCREEN_BUFFER_INFOEX csbi;
+
+ csbi.cbSize = sizeof(csbi);
+ GetConsoleScreenBufferInfoEx(g_hConOut, &csbi);
+
+ csbi.cbSize = sizeof(csbi);
+ csbi.srWindow.Right += 1;
+ csbi.srWindow.Bottom += 1;
+ csbi.ColorTable[g_color_index_bg] = (COLORREF)save_console_bg_rgb;
+ csbi.ColorTable[g_color_index_fg] = (COLORREF)save_console_fg_rgb;
+ SetConsoleScreenBufferInfoEx(g_hConOut, &csbi);
+# endif
+}
+
+ void
+control_console_color_rgb(void)
+{
+ if (vtp_working)
+ set_console_color_rgb();
+ else
+ reset_console_color_rgb();
+}
+
+ int
+use_vtp(void)
+{
+ return USE_VTP;
+}
+
+ int
+is_term_win32(void)
+{
+ return T_NAME != NULL && STRCMP(T_NAME, "win32") == 0;
+}
+
+ int
+has_vtp_working(void)
+{
+ return vtp_working;
+}
+
+#endif
+
+ int
+has_conpty_working(void)
+{
+ return conpty_working;
+}
+
+ int
+get_conpty_type(void)
+{
+ return conpty_type;
+}
+
+ int
+is_conpty_stable(void)
+{
+ return conpty_stable;
+}
+
+ int
+get_conpty_fix_type(void)
+{
+ return conpty_fix_type;
+}
+
+#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL) || defined(PROTO)
+ void
+resize_console_buf(void)
+{
+ if (use_alternate_screen_buffer)
+ return;
+
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+ COORD coord;
+ SMALL_RECT newsize;
+
+ if (!GetConsoleScreenBufferInfo(g_hConOut, &csbi))
+ return;
+
+ coord.X = SRWIDTH(csbi.srWindow);
+ coord.Y = SRHEIGHT(csbi.srWindow);
+ SetConsoleScreenBufferSize(g_hConOut, coord);
+
+ newsize.Left = 0;
+ newsize.Top = 0;
+ newsize.Right = coord.X - 1;
+ newsize.Bottom = coord.Y - 1;
+ SetConsoleWindowInfo(g_hConOut, TRUE, &newsize);
+
+ SetConsoleScreenBufferSize(g_hConOut, coord);
+}
+#endif
+
+ char *
+GetWin32Error(void)
+{
+ static char *oldmsg = NULL;
+ char *msg = NULL;
+
+ FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
+ NULL, GetLastError(), 0, (LPSTR)&msg, 0, NULL);
+ if (oldmsg != NULL)
+ LocalFree(oldmsg);
+ if (msg == NULL)
+ return NULL;
+
+ // remove trailing \r\n
+ char *pcrlf = strstr(msg, "\r\n");
+ if (pcrlf != NULL)
+ *pcrlf = '\0';
+ oldmsg = msg;
+ return msg;
+}
+
+#if defined(FEAT_RELTIME) || defined(PROTO)
+static HANDLE timer_handle;
+static int timer_active = FALSE;
+
+/*
+ * Calls to start_timeout alternate the return value pointer between the two
+ * entries in timeout_flags. If the previously active timeout is very close to
+ * expiring when start_timeout() is called then a race condition means that the
+ * set_flag() function may still be invoked after the previous timer is
+ * deleted. Ping-ponging between the two flags prevents this causing 'fake'
+ * timeouts.
+ */
+static sig_atomic_t timeout_flags[2];
+static int timeout_flag_idx = 0;
+static sig_atomic_t *timeout_flag = &timeout_flags[0];
+
+
+ static void CALLBACK
+set_flag(void *param, BOOLEAN unused2 UNUSED)
+{
+ int *timeout_flag = (int *)param;
+
+ *timeout_flag = TRUE;
+}
+
+/*
+ * Stop any active timeout.
+ */
+ void
+stop_timeout(void)
+{
+ if (timer_active)
+ {
+ BOOL ret = DeleteTimerQueueTimer(NULL, timer_handle, NULL);
+ timer_active = FALSE;
+ if (!ret && GetLastError() != ERROR_IO_PENDING)
+ {
+ semsg(_(e_could_not_clear_timeout_str), GetWin32Error());
+ }
+ }
+ *timeout_flag = FALSE;
+}
+
+/*
+ * Start the timeout timer.
+ *
+ * The period is defined in milliseconds.
+ *
+ * The return value is a pointer to a flag that is initialised to 0. If the
+ * timeout expires, the flag is set to 1. This will only return pointers to
+ * static memory; i.e. any pointer returned by this function may always be
+ * safely dereferenced.
+ *
+ * This function is not expected to fail, but if it does it still returns a
+ * valid flag pointer; the flag will remain stuck at zero.
+ */
+ volatile sig_atomic_t *
+start_timeout(long msec)
+{
+ BOOL ret;
+
+ timeout_flag = &timeout_flags[timeout_flag_idx];
+
+ stop_timeout();
+ ret = CreateTimerQueueTimer(
+ &timer_handle, NULL, set_flag, timeout_flag,
+ (DWORD)msec, 0, WT_EXECUTEDEFAULT);
+ if (!ret)
+ {
+ semsg(_(e_could_not_set_timeout_str), GetWin32Error());
+ }
+ else
+ {
+ timeout_flag_idx = (timeout_flag_idx + 1) % 2;
+ timer_active = TRUE;
+ *timeout_flag = FALSE;
+ }
+ return timeout_flag;
+}
+#endif