static int stdin_is_interactive = 1;
/*
-** If build is for Windows, without 3rd-party line editing, Console
-** input and output may be done in a UTF-8 compatible way. This is
-** determined by invocation option and OS installed capability.
+** If build is for non-RT Windows, without 3rd-party line editing,
+** console input and output may be done in a UTF-8 compatible way,
+** if the OS is capable of it and the --no-utf8 option is not seen.
*/
#if (defined(_WIN32) || defined(WIN32)) && SHELL_USE_LOCAL_GETLINE \
- && !defined(SHELL_OMIT_WIN_UTF8)
+ && !defined(SHELL_OMIT_WIN_UTF8) && !SQLITE_OS_WINRT
# define SHELL_WIN_UTF8_OPT 1
+/* Record whether to do UTF-8 console I/O translation per stream. */
static int console_utf8_in = 0;
static int console_utf8_out = 0;
- static int mbcs_opted = 0;
+/* Record whether can do UTF-8 or --no-utf8 seen in invocation. */
+ static int mbcs_opted = 1; /* Assume cannot do until shown otherwise. */
#else
# define console_utf8_in 0
# define console_utf8_out 0
#endif /* !defined(SQLITE_OMIT_DYNAPROMPT) */
#if SHELL_WIN_UTF8_OPT
-/* Following struct is used for UTF-8 operation. */
+/* Following struct is used for UTF-8 console I/O. */
static struct ConsoleState {
int stdinEof; /* EOF has been seen on console input */
int infsMode; /* Input file stream mode upon shell start */
# define _O_U16TEXT 0x20000
#endif
-
-#if !SQLITE_OS_WINRT
/*
-** Check Windows major version against given value, returning
-** 1 if the OS major version is no less than the argument.
-** This check uses very late binding to the registry access
-** API so that it can operate gracefully on OS versions that
-** do not have that API. The Windows NT registry, for versions
-** through Windows 11 (at least, as of October 2023), keeps
-** the actual major version number at registry key/value
-** HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\CurrentMajorVersionNumber
-** where it can be read more reliably than allowed by various
-** version info APIs which "process" the result in a manner
-** incompatible with the purpose of the CLI's version check.
-**
-** If the registry API is unavailable, or the location of
-** the above registry value changes, or the OS major version
-** is less than the argument, this function returns 0.
-*/
-static int CheckAtLeastWinX(DWORD major_version){
- typedef LONG (WINAPI *REG_OPEN)(HKEY,LPCSTR,DWORD,REGSAM,PHKEY);
- typedef LSTATUS (WINAPI *REG_READ)(HKEY,LPCSTR,LPCSTR,DWORD,
- LPDWORD,PVOID,LPDWORD);
- typedef LSTATUS (WINAPI *REG_CLOSE)(HKEY);
- int rv = 0;
- HINSTANCE hLib = LoadLibrary(TEXT("Advapi32.dll"));
- if( NULL != hLib ){
- REG_OPEN rkOpen = (REG_OPEN)GetProcAddress(hLib, "RegOpenKeyExA");
- REG_READ rkRead = (REG_READ)GetProcAddress(hLib, "RegGetValueA");
- REG_CLOSE rkFree = (REG_CLOSE)GetProcAddress(hLib, "RegCloseKey");
- if( rkOpen != NULL && rkRead != NULL && rkFree != NULL ){
- HKEY hk;
- const char *zsk = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion";
- if( ERROR_SUCCESS == rkOpen(HKEY_LOCAL_MACHINE, zsk, 0, KEY_READ, &hk) ){
- DWORD kv = 0, kvsize = sizeof(kv);
- if( ERROR_SUCCESS == rkRead(hk, 0, "CurrentMajorVersionNumber",
- RRF_RT_REG_DWORD, 0, &kv, &kvsize) ){
- rv = (kv >= major_version);
- }
- rkFree(hk);
- }
- }
- FreeLibrary(hLib);
+** If given stream number is a console, return 1 and get some attributes,
+** else return 0 and set the output attributes to invalid values.
+*/
+static short console_attrs(unsigned stnum, HANDLE *pH, DWORD *pConsMode){
+ static int stid[3] = { STD_INPUT_HANDLE,STD_OUTPUT_HANDLE,STD_ERROR_HANDLE };
+ HANDLE h;
+ *pH = INVALID_HANDLE_VALUE;
+ *pConsMode = 0;
+ if( stnum > 2 ) return 0;
+ h = GetStdHandle(stid[stnum]);
+ if( h!=*pH && GetFileType(h)==FILE_TYPE_CHAR && GetConsoleMode(h,pConsMode) ){
+ *pH = h;
+ return 1;
}
- return rv;
+ return 0;
}
-# define IS_WIN10_OR_LATER() CheckAtLeastWinX(10)
-#else /* defined(SQLITE_OS_WINRT) */
-# define IS_WIN10_OR_LATER() 0
-#endif
/*
-** Prepare console, (if known to be a WIN32 console), for UTF-8 input
-** (from either typing or suitable paste operations) and/or for UTF-8
-** output rendering. This may "fail" with a message to stderr, where
-** the preparation is not done and common "code page" issues occur.
+** Perform a runtime test of Windows console to determine if it can
+** do char-stream I/O correctly when the code page is set to CP_UTF8.
+** Returns are: 1 => yes it can, 0 => no it cannot
+**
+** The console's output code page is momentarily set, then restored.
+** So this should only be run when the process is given use of the
+** console for either input or output.
+*/
+static short ConsoleDoesUTF8(void){
+ UINT ocp = GetConsoleOutputCP();
+ CONSOLE_SCREEN_BUFFER_INFO csbInfo = {0};
+ /* Create an inactive screen buffer with which to do the experiment. */
+ HANDLE hCSB = CreateConsoleScreenBuffer(GENERIC_READ|GENERIC_WRITE, 0, 0,
+ CONSOLE_TEXTMODE_BUFFER, NULL);
+ if( hCSB!=INVALID_HANDLE_VALUE ){
+ const char TrialUtf8[] = { '\xC8', '\xAB' }; /* "ȫ" or 2 MBCS characters */
+ COORD cpos = {0,0};
+ SetConsoleCursorPosition(hCSB, cpos);
+ SetConsoleOutputCP(CP_UTF8);
+ /* Write 2 chars which are a single character in UTF-8 but more in MBCS. */
+ WriteConsoleA(hCSB, TrialUtf8, sizeof(TrialUtf8), NULL, NULL);
+ GetConsoleScreenBufferInfo(hCSB, &csbInfo);
+ SetConsoleOutputCP(ocp);
+ CloseHandle(hCSB);
+ }
+ /* Return 1 if cursor advanced by 1 position, else 0. */
+ return (short)(csbInfo.dwCursorPosition.X == 1);
+}
+
+static short in_console = 0;
+static short out_console = 0;
+
+/*
+** Determine whether either normal I/O stream is the console,
+** and whether it can do UTF-8 translation, setting globals
+** in_console, out_console and mbcs_opted accordingly.
+*/
+static void probe_console(void){
+ HANDLE h;
+ DWORD cMode;
+ in_console = console_attrs(0, &h, &cMode);
+ out_console = console_attrs(1, &h, &cMode);
+ if( in_console || out_console ) mbcs_opted = !ConsoleDoesUTF8();
+}
+
+/*
+** If console is used for normal I/O, absent a --no-utf8 option,
+** prepare console for UTF-8 input (from either typing or suitable
+** paste operations) and/or for UTF-8 output rendering.
**
** The console state upon entry is preserved, in conState, so that
** console_restore() can later restore the same console state.
**
** The globals console_utf8_in and console_utf8_out are set, for
** later use in selecting UTF-8 or MBCS console I/O translations.
+** This routine depends upon globals set by probe_console().
*/
static void console_prepare_utf8(void){
- HANDLE hCI = GetStdHandle(STD_INPUT_HANDLE);
- HANDLE hCO = GetStdHandle(STD_OUTPUT_HANDLE);
- HANDLE hCC = INVALID_HANDLE_VALUE;
- DWORD consoleMode = 0;
- u8 conI = 0, conO = 0;
struct ConsoleState csWork = { 0, 0, 0, 0, INVALID_HANDLE_VALUE, 0 };
console_utf8_in = console_utf8_out = 0;
- if( isatty(0) && GetFileType(hCI)==FILE_TYPE_CHAR ) conI = 1;
- if( isatty(1) && GetFileType(hCO)==FILE_TYPE_CHAR ) conO = 1;
- if( (!conI && !conO) || mbcs_opted ) return;
- if( conI ) hCC = hCI;
- else hCC = hCO;
- if( !IsValidCodePage(CP_UTF8) || !GetConsoleMode( hCC, &consoleMode) ){
- bail:
- fprintf(stderr, "Cannot use UTF-8 code page.\n");
- return;
- }
- csWork.hConsole = hCC;
- csWork.consoleMode = consoleMode;
- csWork.inCodePage = GetConsoleCP();
- csWork.outCodePage = GetConsoleOutputCP();
- if( conI ){
- if( !SetConsoleCP(CP_UTF8) ) goto bail;
- consoleMode |= ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT;
- SetConsoleMode(conState.hConsole, consoleMode);
- csWork.infsMode = _setmode(_fileno(stdin), _O_U16TEXT);
+ if( (!in_console && !out_console) || mbcs_opted ) return;
+ console_attrs((in_console)? 0 : 1, &conState.hConsole, &conState.consoleMode);
+ conState.inCodePage = GetConsoleCP();
+ conState.outCodePage = GetConsoleOutputCP();
+ if( in_console ){
+ SetConsoleCP(CP_UTF8);
+ DWORD newConsoleMode = conState.consoleMode
+ | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT;
+ SetConsoleMode(conState.hConsole, newConsoleMode);
+ conState.infsMode = _setmode(_fileno(stdin), _O_U16TEXT);
+ console_utf8_in = 1;
}
- if( conO ){
- /* Here, it is assumed that if conI is true, this call will also
- ** succeed, so there is no need to undo above setup upon failure. */
- if( !SetConsoleOutputCP(CP_UTF8) ) goto bail;
+ if( out_console ){
+ SetConsoleOutputCP(CP_UTF8);
+ console_utf8_out = 1;
}
- console_utf8_in = conI;
- console_utf8_out = conO;
- conState = csWork;
}
/*
stdout_is_console = isatty(1);
#endif
#if SHELL_WIN_UTF8_OPT
- atexit(console_restore); /* Needs revision for CLI as library call */
+ probe_console(); /* Check for console I/O and UTF-8 capability. */
+ if( !mbcs_opted ) atexit(console_restore);
#endif
atexit(sayAbnormalExit);
#ifdef SQLITE_DEBUG
}
#endif
-#if SHELL_WIN_UTF8_OPT
- /* If Windows build and not RT, set default MBCS/UTF-8 translation for
- ** console according to detected Windows version. This default may be
- ** overridden by a -no-utf8 or (undocumented) -utf8 invocation option.
- ** If a runtime check for UTF-8 console I/O capability is devised,
- ** that should be preferred over this version check.
- */
- mbcs_opted = (IS_WIN10_OR_LATER())? 0 : 1;
-#endif
-
/* Do an initial pass through the command-line argument to locate
** the name of the database file, the name of the initialization file,
** the size of the alternative malloc heap, options affecting commands
stdin_is_interactive = 0;
}else if( cli_strcmp(z,"-utf8")==0 ){
#if SHELL_WIN_UTF8_OPT
- /* Option accepted, but just specifies default UTF-8 console I/O. */
- mbcs_opted = 0;
+ /* Option accepted, but is ignored except for this diagnostic. */
+ if( mbcs_opted ) fprintf(stderr, "Cannot do UTF-8 at this console.\n");
#endif /* SHELL_WIN_UTF8_OPT */
}else if( cli_strcmp(z,"-no-utf8")==0 ){
#if SHELL_WIN_UTF8_OPT
}
#if SHELL_WIN_UTF8_OPT
/* Get indicated Windows console setup done before running invocation commands. */
- if( stdin_is_interactive || stdout_is_console ){
+ if( in_console || out_console ){
console_prepare_utf8();
}
- if( !stdin_is_interactive ){
+ if( !in_console ){
setBinaryMode(stdin, 0);
}
#endif