/* * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 * Jonathan Schleifer <js@heap.zone> * * All rights reserved. * * This file is part of ObjFW. It may be distributed under the terms of the * Q Public License 1.0, which can be found in the file LICENSE.QPL included in * the packaging of this file. * * Alternatively, it may be distributed under the terms of the GNU General * Public License, either version 2 or 3, which can be found in the file * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ /* * This file tries to make writing UTF-8 strings to the console "just work" on * Windows. * * Windows does provide a way to change the codepage of the console to UTF-8, * but unfortunately, different Windows versions handle that differently. For * example on Windows XP when using Windows XP's console, changing the codepage * to UTF-8 mostly breaks write() and completely breaks read(): write() * suddenly returns the number of characters - instead of bytes - written and * read() just returns 0 as soon as a Unicode character is being read. * * So instead of just using the UTF-8 codepage, this captures all reads and * writes to of_std{in,err,out} on the lowlevel, interprets the buffer as UTF-8 * and converts to / from UTF-16 to use ReadConsoleW() and WriteConsoleW(), as * reading or writing binary from / to the console would not make any sense * anyway and thus it's safe to assume it's text. * * In order to not do this when redirecting input / output to a file, it checks * that the handle is indeed a console. * * TODO: Properly handle surrogates being cut in the middle */ #define OF_STDIO_STREAM_WIN32_CONSOLE_M #include "config.h" #import "OFStdIOStream_Win32Console.h" #import "OFStdIOStream+Private.h" #import "OFDataArray.h" #import "OFInvalidArgumentException.h" #import "OFInvalidEncodingException.h" #import "OFOutOfRangeException.h" #import "OFReadFailedException.h" #import "OFWriteFailedException.h" #include <windows.h> @implementation OFStdIOStream_Win32Console + (void)load { of_stdin = [[OFStdIOStream_Win32Console alloc] OF_initWithFileDescriptor: 0]; of_stdout = [[OFStdIOStream_Win32Console alloc] OF_initWithFileDescriptor: 1]; of_stderr = [[OFStdIOStream_Win32Console alloc] OF_initWithFileDescriptor: 2]; } - (instancetype)OF_initWithFileDescriptor: (int)fd { self = [super OF_initWithFileDescriptor: fd]; @try { DWORD mode; switch (fd) { case 0: _handle = GetStdHandle(STD_INPUT_HANDLE); break; case 1: _handle = GetStdHandle(STD_OUTPUT_HANDLE); break; case 2: _handle = GetStdHandle(STD_ERROR_HANDLE); break; default: @throw [OFInvalidArgumentException exception]; } /* Not a console: Treat it as a regular OFStdIOStream */ if (!GetConsoleMode(_handle, &mode)) object_setClass(self, [OFStdIOStream class]); } @catch (id e) { [self release]; @throw e; } return self; } - (size_t)lowlevelReadIntoBuffer: (void*)buffer_ length: (size_t)length { void *pool = objc_autoreleasePoolPush(); char *buffer = buffer_; of_char16_t *UTF16; size_t j = 0; OFDataArray *rest = nil; UTF16 = [self allocMemoryWithSize: sizeof(of_char16_t) count: length]; @try { DWORD UTF16Len; if (!ReadConsoleW(_handle, UTF16, length, &UTF16Len, NULL)) @throw [OFReadFailedException exceptionWithObject: self requestedLength: length]; for (size_t i = 0; i < UTF16Len; i++) { of_unichar_t c = UTF16[i]; char UTF8[4]; size_t UTF8Len; /* Missing high surrogate */ if ((c & 0xFC00) == 0xDC00) @throw [OFInvalidEncodingException exception]; if ((c & 0xFC00) == 0xD800) { of_char16_t next; if (UTF16Len <= i + 1) @throw [OFInvalidEncodingException exception]; next = UTF16[i + 1]; if ((next & 0xFC00) != 0xDC00) @throw [OFInvalidEncodingException exception]; c = (((c & 0x3FF) << 10) | (next & 0x3FF)) + 0x10000; i++; } if ((UTF8Len = of_string_utf8_encode(c, UTF8)) == 0) @throw [OFInvalidEncodingException exception]; if (j + UTF8Len <= length) { memcpy(buffer + j, UTF8, UTF8Len); j += UTF8Len; } else { if (rest == nil) rest = [OFDataArray dataArray]; [rest addItems: UTF8 count: UTF8Len]; } } if (rest != nil) [self unreadFromBuffer: [rest items] length: [rest count]]; } @finally { [self freeMemory: UTF16]; } objc_autoreleasePoolPop(pool); return j; } - (void)lowlevelWriteBuffer: (const void*)buffer_ length: (size_t)length { const char *buffer = buffer_; of_char16_t *tmp; if (length > SIZE_MAX / 2) @throw [OFOutOfRangeException exception]; tmp = [self allocMemoryWithSize: sizeof(of_char16_t) count: length * 2]; @try { size_t i = 0, j = 0; DWORD written; while (i < length) { of_unichar_t c; size_t cLen; cLen = of_string_utf8_decode(buffer + i, length - i, &c); if (cLen == 0 || c > 0x10FFFF) @throw [OFInvalidEncodingException exception]; if (c > 0xFFFF) { c -= 0x10000; tmp[j++] = 0xD800 | (c >> 10); tmp[j++] = 0xDC00 | (c & 0x3FF); } else tmp[j++] = c; i += cLen; } if (!WriteConsoleW(_handle, tmp, j, &written, NULL) || written != j) @throw [OFWriteFailedException exceptionWithObject: self requestedLength: j]; } @finally { [self freeMemory: tmp]; } } @end