Mercurial > vim
view src/os_win32.c @ 34854:97b5457962ed v9.1.0296
patch 9.1.0296: regexp: engines do not handle case-folding well
Commit: https://github.com/vim/vim/commit/7a27c108e0509f3255ebdcb6558e896c223e4d23
Author: Christian Brabandt <cb@256bit.org>
Date: Tue Apr 9 22:53:19 2024 +0200
patch 9.1.0296: regexp: engines do not handle case-folding well
Problem: Regex engines do not handle case-folding well
Solution: Correctly calculate byte length of characters to skip
When the regexp engine compares two utf-8 codepoints case insensitively
it may match an adjacent character, because it assumes it can step over
as many bytes as the pattern contains.
This however is not necessarily true because of case-folding, a
multi-byte UTF-8 character can be considered equal to some single-byte
value.
Let's consider the pattern '?' and the string 's'. When comparing and
ignoring case, the single character 's' matches, and since it matches
Vim will try to step over the match (by the amount of bytes of the
pattern), assuming that since it matches, the length of both strings is
the same.
However in that case, it should only step over the single byte
value 's' so by 1 byte and try to start matching after it again. So for the
backtracking engine we need to ensure:
- we try to match the correct length for the pattern and the text
- in case of a match, we step over it correctly
The same thing can happen for the NFA engine, when skipping to the next
character to test for a match. We are skipping over the regstart
pointer, however we do not consider the case that because of
case-folding we may need to adjust the number of bytes to skip over. So
this needs to be adjusted in find_match_text() as well.
A related issue turned out, when prog->match_text is actually empty. In
that case we should try to find the next match and skip this condition.
fixes: #14294
closes: #14433
Signed-off-by: Christian Brabandt <cb@256bit.org>
author | Christian Brabandt <cb@256bit.org> |
---|---|
date | Tue, 09 Apr 2024 23:00:08 +0200 |
parents | a400c8f9506f |
children | 9982c22412af |
line wrap: on
line source
/* 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; // This is explicitly initialised to work around a LTCG issue on Windows ARM64 // (at least of 19.39.33321). This pushes this into the `.data` rather than // `.bss` which corrects code generation in `write_chars` (#13453). static COORD g_coord = {0, 0}; // 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. Add 1 for a NUL byte and 5 for // "PATH=". #define MAX_ENV_PATH_LEN (8191 + 1 + 5) WCHAR temp[MAX_ENV_PATH_LEN]; 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(); vim_free(exe_name); exe_name = utf16_to_enc(buf, NULL); enc_prev = enc_codepage; WCHAR *wp = wcsrchr(buf, '\\'); if (wp != NULL) *wp = NUL; vim_free(exe_pathw); exe_pathw = _wcsdup(buf); updated = TRUE; } } if (exe_pathw == NULL || !updated) 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. WCHAR *p = _wgetenv(L"PATH"); if (p == NULL || wcslen(p) + wcslen(exe_pathw) + 2 + 5 < MAX_ENV_PATH_LEN) { wcscpy(temp, L"PATH="); if (p == NULL || *p == NUL) wcscat(temp, exe_pathw); else { wcscat(temp, p); // Check if exe_path is already included in $PATH. if (wcsstr(temp, exe_pathw) == NULL) { // Append ';' if $PATH doesn't end with it. size_t len = wcslen(temp); if (temp[len - 1] != L';') wcscat(temp, L";"); wcscat(temp, exe_pathw); } } _wputenv(temp); #ifdef libintl_wputenv libintl_wputenv(temp); #endif } } /* * 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; DWORD ImpVA; 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; ImpVA = pPE->OptionalHeader .DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; if (ImpVA == 0) return NULL; // No Import Table pImpDesc = (PIMAGE_IMPORT_DESCRIPTOR)(pImage + ImpVA); 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(FEAT_PYTHON3) || defined(PROTO) /* * Check if the specified DLL is a function forwarder. * If yes, return the instance of the forwarded DLL. * If no, return the specified DLL. * If error, return NULL. * This assumes that the DLL forwards all the function to a single DLL. */ HINSTANCE get_forwarded_dll(HINSTANCE hInst) { PBYTE pImage = (PBYTE)hInst; PIMAGE_DOS_HEADER pDOS = (PIMAGE_DOS_HEADER)hInst; PIMAGE_NT_HEADERS pPE; PIMAGE_EXPORT_DIRECTORY pExpDir; DWORD ExpVA; DWORD ExpSize; LPDWORD pFunctionTable; 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; ExpVA = pPE->OptionalHeader .DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; ExpSize = pPE->OptionalHeader .DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size; if (ExpVA == 0) return hInst; // No Export Directory pExpDir = (PIMAGE_EXPORT_DIRECTORY)(pImage + ExpVA); pFunctionTable = (LPDWORD)(pImage + pExpDir->AddressOfFunctions); if (pExpDir->NumberOfNames == 0) return hInst; // No export names. // Check only the first entry. if ((pFunctionTable[0] < ExpVA) || (pFunctionTable[0] >= ExpVA + ExpSize)) // The first entry is not a function forwarder. return hInst; // The first entry is a function forwarder. // The name is represented as "DllName.FunctionName". const char *name = (const char *)(pImage + pFunctionTable[0]); const char *p = strchr(name, '.'); if (p == NULL) return hInst; // Extract DllName. char buf[MAX_PATH]; if (p - name + 1 > sizeof(buf)) return NULL; strncpy(buf, name, p - name); buf[p - name] = '\0'; return GetModuleHandleA(buf); } #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 modifier 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 doesn't 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) res = resolve_reparse_point((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_CLEAR(p); } } 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_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. */ 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); } } /* * Check for CTRL-D: EOF, close pipe to child. * Ctrl_D may be decorated by _OnChar() */ if ((len == 1 || len == 4 ) && cmd != NULL) { if (ta_buf[0] == Ctrl_D || (ta_buf[0] == CSI && ta_buf[1] == KS_MODIFIER && ta_buf[3] == Ctrl_D)) { CloseHandle(g_hChildStd_IN_Wr); g_hChildStd_IN_Wr = NULL; len = 0; } } 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. */ mch_signal(SIGINT, SIG_IGN); #if defined(__GNUC__) && !defined(__MINGW32__) mch_signal(SIGKILL, SIG_IGN); #else mch_signal(SIGBREAK, SIG_IGN); #endif mch_signal(SIGILL, SIG_IGN); mch_signal(SIGFPE, SIG_IGN); mch_signal(SIGSEGV, SIG_IGN); mch_signal(SIGTERM, SIG_IGN); mch_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(); mch_signal(SIGINT, SIG_DFL); #if defined(__GNUC__) && !defined(__MINGW32__) mch_signal(SIGKILL, SIG_DFL); #else mch_signal(SIGBREAK, SIG_DFL); #endif mch_signal(SIGILL, SIG_DFL); mch_signal(SIGFPE, SIG_DFL); mch_signal(SIGSEGV, SIG_DFL); mch_signal(SIGTERM, SIG_DFL); mch_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_ALL_HASHTAB_ITEMS(&env->dv_hashtab, hi, todo) { 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++; } FreeEnvironmentStringsW(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 (TRUE); 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 (SAFE_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_CLEAR(ea); } } } 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; if (silent_mode) return 0; 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