| /* 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> |
| # include <direct.h> |
| |
| # if !defined(FEAT_GUI_MSWIN) |
| # include <shellapi.h> |
| # endif |
| |
| # ifdef FEAT_JOB_CHANNEL |
| # include <tlhelp32.h> |
| # 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 |
| } |
| |
| /* |
| * 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); |
| mch_signal(SIGBREAK, SIG_IGN); |
| 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); |
| mch_signal(SIGBREAK, SIG_DFL); |
| 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 |