diff src/os_win32.c @ 13314:65c3e8259124 v8.0.1531

patch 8.0.1531: cannot use 24 bit colors in MS-Windows console commit https://github.com/vim/vim/commit/cafafb381a04e33f3ce9cd15dd9f94b73226831f Author: Bram Moolenaar <Bram@vim.org> Date: Thu Feb 22 21:07:09 2018 +0100 patch 8.0.1531: cannot use 24 bit colors in MS-Windows console Problem: Cannot use 24 bit colors in MS-Windows console. Solution: Add support for vcon. (Nobuhiro Takasaki, Ken Takasaki, fixes #1270, fixes #2060)
author Christian Brabandt <cb@256bit.org>
date Thu, 22 Feb 2018 21:15:05 +0100
parents ee1a1276a759
children 69517d67421f
line wrap: on
line diff
--- a/src/os_win32.c
+++ b/src/os_win32.c
@@ -203,6 +203,32 @@ static int win32_setattrs(char_u *name, 
 static int win32_set_archive(char_u *name);
 
 #ifndef FEAT_GUI_W32
+static int vtp_working = 0;
+static void vtp_init();
+static void vtp_exit();
+static int vtp_printf(char *format, ...);
+static void vtp_sgr_bulk(int arg);
+static void vtp_sgr_bulks(int argc, int *argv);
+
+static guicolor_T save_console_bg_rgb;
+static guicolor_T save_console_fg_rgb;
+
+# ifdef FEAT_TERMGUICOLORS
+#  define USE_VTP		(vtp_working && p_tgc)
+# else
+#  define USE_VTP		0
+# endif
+
+static void set_console_color_rgb(void);
+static void reset_console_color_rgb(void);
+#endif
+
+/* This flag is newly created from Windows 10 */
+#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
+# define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
+#endif
+
+#ifndef FEAT_GUI_W32
 static int suppress_winsize = 1;	/* don't fiddle with console */
 #endif
 
@@ -211,6 +237,54 @@ static char_u *exe_path = NULL;
 static BOOL win8_or_later = FALSE;
 
 #ifndef FEAT_GUI_W32
+/* Dynamic loading for portability */
+typedef struct _DYN_CONSOLE_SCREEN_BUFFER_INFOEX
+{
+    ULONG cbSize;
+    COORD dwSize;
+    COORD dwCursorPosition;
+    WORD wAttributes;
+    SMALL_RECT srWindow;
+    COORD dwMaximumWindowSize;
+    WORD wPopupAttributes;
+    BOOL bFullscreenSupported;
+    COLORREF ColorTable[16];
+} DYN_CONSOLE_SCREEN_BUFFER_INFOEX, *PDYN_CONSOLE_SCREEN_BUFFER_INFOEX;
+typedef BOOL (WINAPI *PfnGetConsoleScreenBufferInfoEx)(HANDLE, PDYN_CONSOLE_SCREEN_BUFFER_INFOEX);
+static PfnGetConsoleScreenBufferInfoEx pGetConsoleScreenBufferInfoEx;
+typedef BOOL (WINAPI *PfnSetConsoleScreenBufferInfoEx)(HANDLE, PDYN_CONSOLE_SCREEN_BUFFER_INFOEX);
+static PfnSetConsoleScreenBufferInfoEx pSetConsoleScreenBufferInfoEx;
+static BOOL has_csbiex = FALSE;
+
+/*
+ * 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 = {sizeof(OSVERSIONINFOW)};
+    HMODULE		hNtdll;
+    PfnRtlGetVersion	pRtlGetVersion;
+    DWORD		ver = MAKE_VER(0, 0, 0);
+
+    hNtdll = GetModuleHandle("ntdll.dll");
+    if (hNtdll != NULL)
+    {
+	pRtlGetVersion =
+	    (PfnRtlGetVersion)GetProcAddress(hNtdll, "RtlGetVersion");
+	pRtlGetVersion(&osver);
+	ver = MAKE_VER(min(osver.dwMajorVersion, 255),
+		       min(osver.dwMinorVersion, 255),
+		       min(osver.dwBuildNumber, 32767));
+    }
+    return ver;
+}
+
+
 /*
  * Version of ReadConsoleInput() that works with IME.
  * Works around problems on Windows 8.
@@ -2537,6 +2611,7 @@ mch_init(void)
 
     /* set termcap codes to current text attributes */
     update_tcap(g_attrCurrent);
+    swap_tcap();
 
     GetConsoleCursorInfo(g_hConOut, &g_cci);
     GetConsoleMode(g_hConIn,  &g_cmodein);
@@ -2577,6 +2652,8 @@ mch_init(void)
 #ifdef FEAT_CLIPBOARD
     win_clip_init();
 #endif
+
+    vtp_init();
 }
 
 /*
@@ -2589,6 +2666,8 @@ mch_exit(int r)
 {
     exiting = TRUE;
 
+    vtp_exit();
+
     stoptermcap();
     if (g_fWindInitCalled)
 	settmode(TMODE_COOK);
@@ -3801,7 +3880,15 @@ mch_settmode(int tmode)
 	if (g_fMouseActive)
 	    cmodein |= ENABLE_MOUSE_INPUT;
 #endif
-	cmodeout &= ~(ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT);
+	cmodeout &= ~(
+#ifdef FEAT_TERMGUICOLORS
+	    /* Do not turn off the ENABLE_PROCESSRD_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 */
@@ -5448,6 +5535,7 @@ termcap_mode_start(void)
 	 * 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;
@@ -5460,6 +5548,7 @@ termcap_mode_start(void)
 	 * size.  We will use this as the size of our editing environment.
 	 */
 	ClearConsoleBuffer(g_attrCurrent);
+	set_console_color_rgb();
 	ResizeConBufAndWindow(g_hConOut, Columns, Rows);
     }
 
@@ -5508,6 +5597,7 @@ termcap_mode_end(void)
     cb = &g_cbNonTermcap;
 #endif
     RestoreConsoleBuffer(cb, p_rs);
+    reset_console_color_rgb();
     SetConsoleCursorInfo(g_hConOut, &g_cci);
 
     if (p_rs || exiting)
@@ -5562,7 +5652,11 @@ clear_chars(
     DWORD dwDummy;
 
     FillConsoleOutputCharacter(g_hConOut, ' ', n, coord, &dwDummy);
-    FillConsoleOutputAttribute(g_hConOut, g_attrCurrent, n, coord, &dwDummy);
+
+    if (!USE_VTP)
+	FillConsoleOutputAttribute(g_hConOut, g_attrCurrent, n, coord, &dwDummy);
+    else
+	FillConsoleOutputAttribute(g_hConOut, 0, n, coord, &dwDummy);
 }
 
 
@@ -5573,7 +5667,15 @@ clear_chars(
 clear_screen(void)
 {
     g_coord.X = g_coord.Y = 0;
-    clear_chars(g_coord, Rows * Columns);
+
+    if (!USE_VTP)
+	clear_chars(g_coord, Rows * Columns);
+    else
+    {
+	set_console_color_rgb();
+	gotoxy(1, 1);
+	vtp_printf("\033[2J");
+    }
 }
 
 
@@ -5583,8 +5685,20 @@ clear_screen(void)
     static void
 clear_to_end_of_display(void)
 {
-    clear_chars(g_coord, (Rows - g_coord.Y - 1)
+    COORD save = g_coord;
+
+    if (!USE_VTP)
+	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;
+    }
 }
 
 
@@ -5594,7 +5708,19 @@ clear_to_end_of_display(void)
     static void
 clear_to_end_of_line(void)
 {
-    clear_chars(g_coord, Columns - g_coord.X);
+    COORD save = g_coord;
+
+    if (!USE_VTP)
+	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;
+    }
 }
 
 
@@ -5633,6 +5759,9 @@ set_scroll_region(
     g_srScrollRegion.Top =    top;
     g_srScrollRegion.Right =  right;
     g_srScrollRegion.Bottom = bottom;
+
+    if (USE_VTP)
+	vtp_printf("\033[%d;%dr", top + 1, bottom + 1);
 }
 
 
@@ -5654,10 +5783,20 @@ insert_lines(unsigned cLines)
     source.Right  = g_srScrollRegion.Right;
     source.Bottom = g_srScrollRegion.Bottom - cLines;
 
-    fill.Char.AsciiChar = ' ';
-    fill.Attributes = g_attrCurrent;
-
-    ScrollConsoleScreenBuffer(g_hConOut, &source, NULL, dest, &fill);
+    if (!USE_VTP)
+    {
+	fill.Char.AsciiChar = ' ';
+	fill.Attributes = g_attrCurrent;
+
+	ScrollConsoleScreenBuffer(g_hConOut, &source, NULL, dest, &fill);
+    }
+    else
+    {
+	set_console_color_rgb();
+
+	gotoxy(1, source.Top + 1);
+	vtp_printf("\033[%dT", cLines);
+    }
 
     /* 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
@@ -5696,10 +5835,20 @@ delete_lines(unsigned cLines)
     source.Right  = g_srScrollRegion.Right;
     source.Bottom = g_srScrollRegion.Bottom;
 
-    fill.Char.AsciiChar = ' ';
-    fill.Attributes = g_attrCurrent;
-
-    ScrollConsoleScreenBuffer(g_hConOut, &source, NULL, dest, &fill);
+    if (!USE_VTP)
+    {
+	fill.Char.AsciiChar = ' ';
+	fill.Attributes = g_attrCurrent;
+
+	ScrollConsoleScreenBuffer(g_hConOut, &source, NULL, dest, &fill);
+    }
+    else
+    {
+	set_console_color_rgb();
+
+	gotoxy(1, source.Top + 1);
+	vtp_printf("\033[%dS", cLines);
+    }
 
     /* 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
@@ -5735,7 +5884,11 @@ gotoxy(
     /* 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);
+
+    if (!USE_VTP)
+	SetConsoleCursorPosition(g_hConOut, g_coord);
+    else
+	vtp_printf("\033[%d;%dH", y, x);
 }
 
 
@@ -5757,7 +5910,10 @@ textcolor(WORD wAttr)
 {
     g_attrCurrent = (g_attrCurrent & 0xf0) + (wAttr & 0x0f);
 
-    SetConsoleTextAttribute(g_hConOut, g_attrCurrent);
+    if (!USE_VTP)
+	SetConsoleTextAttribute(g_hConOut, g_attrCurrent);
+    else
+	vtp_sgr_bulk(wAttr);
 }
 
 
@@ -5766,7 +5922,10 @@ textbackground(WORD wAttr)
 {
     g_attrCurrent = (g_attrCurrent & 0x0f) + ((wAttr & 0x0f) << 4);
 
-    SetConsoleTextAttribute(g_hConOut, g_attrCurrent);
+    if (!USE_VTP)
+	SetConsoleTextAttribute(g_hConOut, g_attrCurrent);
+    else
+	vtp_sgr_bulk(wAttr);
 }
 
 
@@ -5776,7 +5935,10 @@ textbackground(WORD wAttr)
     static void
 normvideo(void)
 {
-    textattr(g_attrDefault);
+    if (!USE_VTP)
+	textattr(g_attrDefault);
+    else
+	vtp_sgr_bulk(0);
 }
 
 
@@ -5789,6 +5951,7 @@ static WORD g_attrPreStandout = 0;
 standout(void)
 {
     g_attrPreStandout = g_attrCurrent;
+
     textattr((WORD) (g_attrCurrent|FOREGROUND_INTENSITY|BACKGROUND_INTENSITY));
 }
 
@@ -5800,10 +5963,9 @@ standout(void)
 standend(void)
 {
     if (g_attrPreStandout)
-    {
 	textattr(g_attrPreStandout);
-	g_attrPreStandout = 0;
-    }
+
+    g_attrPreStandout = 0;
 }
 
 
@@ -5818,7 +5980,11 @@ mch_set_normal_colors(void)
 
     cterm_normal_fg_color = (g_attrDefault & 0xf) + 1;
     cterm_normal_bg_color = ((g_attrDefault >> 4) & 0xf) + 1;
-    if (T_ME[0] == ESC && T_ME[1] == '|')
+    if (
+#ifdef FEAT_TERMGUICOLORS
+	!p_tgc &&
+#endif
+	T_ME[0] == ESC && T_ME[1] == '|')
     {
 	p = T_ME + 2;
 	n = getdigits(&p);
@@ -5828,6 +5994,10 @@ mch_set_normal_colors(void)
 	    cterm_normal_bg_color = ((n >> 4) & 0xf) + 1;
 	}
     }
+#ifdef FEAT_TERMGUICOLORS
+    cterm_normal_fg_gui_color = INVALCOLOR;
+    cterm_normal_bg_gui_color = INVALCOLOR;
+#endif
 }
 
 
@@ -5851,7 +6021,8 @@ visual_bell(void)
 			       coordOrigin, &dwDummy);
 
     Sleep(15);	    /* wait for 15 msec */
-    WriteConsoleOutputAttribute(g_hConOut, oldattrs, Rows * Columns,
+    if (!USE_VTP)
+	WriteConsoleOutputAttribute(g_hConOut, oldattrs, Rows * Columns,
 				coordOrigin, &dwDummy);
     vim_free(oldattrs);
 }
@@ -5901,14 +6072,24 @@ write_chars(
 			    unicodebuf, unibuflen);
 
 	cells = mb_string2cells(pchBuf, cbToWrite);
-	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, unicodebuf, length,
-		    coord, &cchwritten) == 0
-		|| cchwritten == 0)
-	    cchwritten = 1;
+
+	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, unicodebuf, length,
+			coord, &cchwritten) == 0
+		    || cchwritten == 0)
+		cchwritten = 1;
+	}
+	else
+	{
+	    if (WriteConsoleW(g_hConOut, unicodebuf, length, &cchwritten,
+		    NULL) == 0 || cchwritten == 0)
+		cchwritten = 1;
+	}
 
 	if (cchwritten == length)
 	{
@@ -5927,14 +6108,23 @@ write_chars(
     else
 #endif
     {
-	FillConsoleOutputAttribute(g_hConOut, g_attrCurrent, cbToWrite,
-				    coord, &written);
-	/* When writing fails or didn't write a single character, pretend one
-	 * character was written, otherwise we get stuck. */
-	if (WriteConsoleOutputCharacter(g_hConOut, (LPCSTR)pchBuf, cbToWrite,
-		    coord, &written) == 0
-		|| written == 0)
-	    written = 1;
+	if (!USE_VTP)
+	{
+	    FillConsoleOutputAttribute(g_hConOut, g_attrCurrent, cbToWrite,
+					coord, &written);
+	    /* When writing fails or didn't write a single character, pretend one
+	     * character was written, otherwise we get stuck. */
+	    if (WriteConsoleOutputCharacter(g_hConOut, (LPCSTR)pchBuf, cbToWrite,
+			coord, &written) == 0
+		    || written == 0)
+		written = 1;
+	}
+	else
+	{
+	    if (WriteConsole(g_hConOut, (LPCSTR)pchBuf, cbToWrite, &written,
+		    NULL) == 0 || written == 0)
+		written = 1;
+	}
 
 	g_coord.X += (SHORT) written;
     }
@@ -6060,67 +6250,76 @@ mch_write(
 	    char_u  *old_s = s;
 #endif
 	    char_u  *p;
-	    int	    arg1 = 0, arg2 = 0;
+	    int	    arg1 = 0, arg2 = 0, argc = 0, args[16];
 
 	    switch (s[2])
 	    {
-	    /* one or two numeric arguments, separated by ';' */
-
 	    case '0': case '1': case '2': case '3': case '4':
 	    case '5': case '6': case '7': case '8': case '9':
-		p = s + 2;
-		arg1 = getdigits(&p);	    /* no check for length! */
-		if (p > s + len)
-		    break;
-
-		if (*p == ';')
+		p = s + 1;
+		do
 		{
 		    ++p;
-		    arg2 = getdigits(&p);   /* no check for length! */
+		    args[argc] = getdigits(&p);
+		    argc += (argc < 15) ? 1 : 0;
 		    if (p > s + len)
 			break;
-
-		    if (*p == 'H')
-			gotoxy(arg2, arg1);
-		    else if (*p == 'r')
-			set_scroll_region(0, arg1 - 1, Columns - 1, arg2 - 1);
+		} while (*p == ';');
+
+		if (p > s + len)
+		    break;
+
+		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 (USE_VTP)
+			vtp_sgr_bulks(argc, args);
 		}
-		else if (*p == 'A')
+		else if (argc == 2 && *p == 'H')
 		{
-		    /* move cursor up arg1 lines in same column */
+		    gotoxy(arg2, arg1);
+		}
+		else if (argc == 2 && *p == 'r')
+		{
+		    set_scroll_region(0, arg1 - 1, Columns - 1, arg2 - 1);
+		}
+		else if (argc == 1 && *p == 'A')
+		{
 		    gotoxy(g_coord.X + 1,
 			   max(g_srScrollRegion.Top, g_coord.Y - arg1) + 1);
 		}
-		else if (*p == 'C')
+		else if (argc == 1 && *p == 'b')
 		{
-		    /* move cursor right arg1 columns in same line */
+		    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 (*p == 'H')
+		else if (argc == 1 && *p == 'f')
+		{
+		    textcolor((WORD) arg1);
+		}
+		else if (argc == 1 && *p == 'H')
 		{
 		    gotoxy(1, arg1);
 		}
-		else if (*p == 'L')
+		else if (argc == 1 && *p == 'L')
 		{
 		    insert_lines(arg1);
 		}
-		else if (*p == 'm')
-		{
-		    if (arg1 == 0)
-			normvideo();
-		    else
-			textattr((WORD) arg1);
-		}
-		else if (*p == 'f')
-		{
-		    textcolor((WORD) arg1);
-		}
-		else if (*p == 'b')
-		{
-		    textbackground((WORD) arg1);
-		}
-		else if (*p == 'M')
+		else if (argc == 1 && *p == 'M')
 		{
 		    delete_lines(arg1);
 		}
@@ -6129,11 +6328,7 @@ mch_write(
 		s = p + 1;
 		break;
 
-
-	    /* Three-character escape sequences */
-
 	    case 'A':
-		/* move cursor up one line in same column */
 		gotoxy(g_coord.X + 1,
 		       max(g_srScrollRegion.Top, g_coord.Y - 1) + 1);
 		goto got3;
@@ -6143,7 +6338,6 @@ mch_write(
 		goto got3;
 
 	    case 'C':
-		/* move cursor right one column in same line */
 		gotoxy(min(g_srScrollRegion.Right, g_coord.X + 1) + 1,
 		       g_coord.Y + 1);
 		goto got3;
@@ -7242,3 +7436,186 @@ mch_setenv(char *var, char *value, int x
 
     return 0;
 }
+
+#ifndef FEAT_GUI_W32
+
+/*
+ * 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)
+
+    static void
+vtp_init(void)
+{
+    DWORD   ver, mode;
+    HMODULE hKerneldll;
+    DYN_CONSOLE_SCREEN_BUFFER_INFOEX csbi;
+
+    ver = get_build_number();
+    vtp_working = (ver >= VTP_FIRST_SUPPORT_BUILD) ? 1 : 0;
+    GetConsoleMode(g_hConOut, &mode);
+    mode |= (ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
+    if (SetConsoleMode(g_hConOut, mode) == 0)
+	vtp_working = 0;
+
+    /* Use functions supported from Vista */
+    hKerneldll = GetModuleHandle("kernel32.dll");
+    if (hKerneldll != NULL)
+    {
+	pGetConsoleScreenBufferInfoEx =
+		(PfnGetConsoleScreenBufferInfoEx)GetProcAddress(
+		hKerneldll, "GetConsoleScreenBufferInfoEx");
+	pSetConsoleScreenBufferInfoEx =
+		(PfnSetConsoleScreenBufferInfoEx)GetProcAddress(
+		hKerneldll, "SetConsoleScreenBufferInfoEx");
+	if (pGetConsoleScreenBufferInfoEx != NULL
+		&& pSetConsoleScreenBufferInfoEx != NULL)
+	    has_csbiex = TRUE;
+    }
+
+    csbi.cbSize = sizeof(csbi);
+    if (has_csbiex)
+	pGetConsoleScreenBufferInfoEx(g_hConOut, &csbi);
+    save_console_bg_rgb = (guicolor_T)csbi.ColorTable[0];
+    save_console_fg_rgb = (guicolor_T)csbi.ColorTable[7];
+
+    set_console_color_rgb();
+}
+
+    static void
+vtp_exit(void)
+{
+    reset_console_color_rgb();
+}
+
+    static int
+vtp_printf(
+    char *format,
+    ...)
+{
+    char_u  buf[100];
+    va_list list;
+    DWORD   result;
+
+    va_start(list, format);
+    vim_vsnprintf((char *)buf, 100, (char *)format, list);
+    va_end(list);
+    WriteConsoleA(g_hConOut, buf, (DWORD)STRLEN(buf), &result, NULL);
+    return (int)result;
+}
+
+    static void
+vtp_sgr_bulk(
+    int arg)
+{
+    int args[1];
+
+    args[0] = arg;
+    vtp_sgr_bulks(1, args);
+}
+
+    static void
+vtp_sgr_bulks(
+    int argc,
+    int *args
+)
+{
+    /* 2('\033[') + 4('255.') * 16 + NUL */
+    char_u buf[2 + (4 * 16) + 1];
+    char_u *p;
+    int    i;
+
+    p = buf;
+    *p++ = '\033';
+    *p++ = '[';
+
+    for (i = 0; i < argc; ++i)
+    {
+	p += vim_snprintf((char *)p, 4, "%d", args[i] & 0xff);
+	*p++ = ';';
+    }
+    p--;
+    *p++ = 'm';
+    *p = NUL;
+    vtp_printf((char *)buf);
+}
+
+    static void
+set_console_color_rgb(void)
+{
+# ifdef FEAT_TERMGUICOLORS
+    DYN_CONSOLE_SCREEN_BUFFER_INFOEX csbi;
+    int id;
+    guicolor_T fg = INVALCOLOR;
+    guicolor_T bg = INVALCOLOR;
+
+    if (!USE_VTP)
+	return;
+
+    id = syn_name2id((char_u *)"Normal");
+    if (id > 0)
+	syn_id2colors(id, &fg, &bg);
+    if (fg == INVALCOLOR)
+	fg = 0xc0c0c0;	    /* white text */
+    if (bg == INVALCOLOR)
+	bg = 0x000000;	    /* black background */
+    fg = (GetRValue(fg) << 16) | (GetGValue(fg) << 8) | GetBValue(fg);
+    bg = (GetRValue(bg) << 16) | (GetGValue(bg) << 8) | GetBValue(bg);
+
+    csbi.cbSize = sizeof(csbi);
+    if (has_csbiex)
+	pGetConsoleScreenBufferInfoEx(g_hConOut, &csbi);
+
+    csbi.cbSize = sizeof(csbi);
+    csbi.srWindow.Right += 1;
+    csbi.srWindow.Bottom += 1;
+    csbi.ColorTable[0] = (COLORREF)bg;
+    csbi.ColorTable[7] = (COLORREF)fg;
+    if (has_csbiex)
+	pSetConsoleScreenBufferInfoEx(g_hConOut, &csbi);
+# endif
+}
+
+    static void
+reset_console_color_rgb(void)
+{
+# ifdef FEAT_TERMGUICOLORS
+    DYN_CONSOLE_SCREEN_BUFFER_INFOEX csbi;
+
+    csbi.cbSize = sizeof(csbi);
+    if (has_csbiex)
+	pGetConsoleScreenBufferInfoEx(g_hConOut, &csbi);
+
+    csbi.cbSize = sizeof(csbi);
+    csbi.srWindow.Right += 1;
+    csbi.srWindow.Bottom += 1;
+    csbi.ColorTable[0] = (COLORREF)save_console_bg_rgb;
+    csbi.ColorTable[7] = (COLORREF)save_console_fg_rgb;
+    if (has_csbiex)
+	pSetConsoleScreenBufferInfoEx(g_hConOut, &csbi);
+# endif
+}
+
+    void
+control_console_color_rgb(void)
+{
+    if (USE_VTP)
+	set_console_color_rgb();
+    else
+	reset_console_color_rgb();
+}
+
+    int
+has_vtp_working(void)
+{
+    return vtp_working;
+}
+
+    int
+use_vtp(void)
+{
+    return USE_VTP;
+}
+
+#endif