@@ -445,6 +445,9 @@ pub fn GetQueuedCompletionStatusEx(
445445
446446pub fn CloseHandle (hObject : HANDLE ) void {
447447 assert (ntdll .NtClose (hObject ) == .SUCCESS );
448+ if (IsConsoleHandle (hObject )) {
449+ _ = RemoveConsoleHandleData (hObject ) catch {};
450+ }
448451}
449452
450453pub fn FindClose (hFindFile : HANDLE ) void {
@@ -547,6 +550,9 @@ pub const WriteFileError = error{
547550 /// a portion of the file.
548551 LockViolation ,
549552 Unexpected ,
553+ /// This error occurs when trying to write UTF-8 text to a Windows console,
554+ /// and the UTF-8 to UTF-16 conversion fails.
555+ InvalidUtf8 ,
550556};
551557
552558pub fn WriteFile (
@@ -617,21 +623,109 @@ pub fn WriteFile(
617623 break :blk & overlapped_data ;
618624 } else null ;
619625 const adjusted_len = math .cast (u32 , bytes .len ) orelse maxInt (u32 );
620- if (kernel32 .WriteFile (handle , bytes .ptr , adjusted_len , & bytes_written , overlapped ) == 0 ) {
626+ if (IsConsoleHandle (handle )) {
627+ assert (offset == null );
628+ bytes_written = WriteConsoleWithUtf8ToUtf16Conversion (handle , bytes ) catch | err | return err ;
629+ } else {
630+ if (kernel32 .WriteFile (handle , bytes .ptr , adjusted_len , & bytes_written , overlapped ) == 0 ) {
631+ switch (kernel32 .GetLastError ()) {
632+ .INVALID_USER_BUFFER = > return error .SystemResources ,
633+ .NOT_ENOUGH_MEMORY = > return error .SystemResources ,
634+ .OPERATION_ABORTED = > return error .OperationAborted ,
635+ .NOT_ENOUGH_QUOTA = > return error .SystemResources ,
636+ .IO_PENDING = > unreachable ,
637+ .BROKEN_PIPE = > return error .BrokenPipe ,
638+ .INVALID_HANDLE = > return error .NotOpenForWriting ,
639+ .LOCK_VIOLATION = > return error .LockViolation ,
640+ else = > | err | return unexpectedError (err ),
641+ }
642+ }
643+ }
644+ return bytes_written ;
645+ }
646+ }
647+
648+ fn WriteConsoleWithUtf8ToUtf16Conversion (handle : HANDLE , bytes : []const u8 ) WriteFileError ! DWORD {
649+ const handle_data : * ConsoleHandleData = GetConsoleHandleData (handle ) catch | err | switch (err ) {
650+ error .ConsoleHandleLimitReached = > @panic ("Reached maximum number of 64 console handles." ),
651+ else = > return error .Unexpected ,
652+ };
653+ var bytes_written : DWORD = 0 ;
654+ var byte_index : DWORD = 0 ;
655+ while (byte_index < bytes .len ) {
656+ var utf16_buffer : [2 ]u16 = undefined ;
657+ var utf16_code_units : usize = undefined ;
658+ if (handle_data .utf8_buffer .bytes_used == 0 ) {
659+ const utf8_byte_sequence_length : u3 = std .unicode .utf8ByteSequenceLength (bytes [byte_index ]) catch return error .InvalidUtf8 ;
660+ const bytes_available : usize = bytes .len - byte_index ;
661+ if (bytes_available < utf8_byte_sequence_length ) {
662+ var index : usize = 0 ;
663+ while (index < bytes_available ) : (index += 1 ) {
664+ handle_data .utf8_buffer .data [index ] = bytes [index ];
665+ }
666+ bytes_written += @truncate (bytes_available );
667+ return bytes_written ;
668+ } else {
669+ utf16_code_units = std .unicode .utf8ToUtf16Le (& utf16_buffer , bytes [byte_index .. byte_index + utf8_byte_sequence_length ]) catch return error .InvalidUtf8 ;
670+ byte_index += utf8_byte_sequence_length ;
671+ }
672+ } else {
673+ const utf8_byte_sequence_length : u3 = std .unicode .utf8ByteSequenceLength (handle_data .utf8_buffer .data [0 ]) catch return error .InvalidUtf8 ;
674+ assert (utf8_byte_sequence_length > 1 and utf8_byte_sequence_length > handle_data .utf8_buffer .bytes_used );
675+ const bytes_available : usize = bytes .len - byte_index ;
676+ const bytes_needed : u3 = utf8_byte_sequence_length - handle_data .utf8_buffer .bytes_used ;
677+ if (bytes_available < bytes_needed ) {
678+ assert (handle_data .utf8_buffer .bytes_used + bytes_available < utf8_byte_sequence_length );
679+ for (0.. bytes_available ) | index | {
680+ handle_data .utf8_buffer .data [handle_data .utf8_buffer .bytes_used + index ] = bytes [index ];
681+ }
682+ bytes_written += @truncate (bytes_available );
683+ return bytes_written ;
684+ } else {
685+ for (0.. bytes_needed ) | index | {
686+ handle_data .utf8_buffer .data [handle_data .utf8_buffer .bytes_used + index ] = bytes [index ];
687+ }
688+ utf16_code_units = std .unicode .utf8ToUtf16Le (& utf16_buffer , handle_data .utf8_buffer .data [0.. utf8_byte_sequence_length ]) catch return error .InvalidUtf8 ;
689+ byte_index += bytes_needed ;
690+ }
691+ }
692+ switch (utf16_buffer [0 ]) {
693+ 0x000D = > {
694+ handle_data .last_character_written_is_CR = true ;
695+ },
696+ 0x000A = > {
697+ if (handle_data .last_character_written_is_CR ) {
698+ handle_data .last_character_written_is_CR = false ;
699+ } else {
700+ utf16_buffer = .{ 0x000D , 0x000A };
701+ utf16_code_units = 2 ;
702+ }
703+ },
704+ else = > {
705+ handle_data .last_character_written_is_CR = false ;
706+ },
707+ }
708+ var utf16_code_units_written : DWORD = undefined ;
709+ if (kernel32 .WriteConsoleW (handle , & utf16_buffer , @truncate (utf16_code_units ), & utf16_code_units_written , null ) == FALSE ) {
621710 switch (kernel32 .GetLastError ()) {
622711 .INVALID_USER_BUFFER = > return error .SystemResources ,
623712 .NOT_ENOUGH_MEMORY = > return error .SystemResources ,
624713 .OPERATION_ABORTED = > return error .OperationAborted ,
625714 .NOT_ENOUGH_QUOTA = > return error .SystemResources ,
626715 .IO_PENDING = > unreachable ,
627- .BROKEN_PIPE = > return error . BrokenPipe ,
716+ .BROKEN_PIPE = > unreachable ,
628717 .INVALID_HANDLE = > return error .NotOpenForWriting ,
629718 .LOCK_VIOLATION = > return error .LockViolation ,
630719 else = > | err | return unexpectedError (err ),
631720 }
632721 }
633- return bytes_written ;
722+ if (utf16_code_units_written < utf16_code_units ) {
723+ return bytes_written ;
724+ } else {
725+ bytes_written = byte_index ;
726+ }
634727 }
728+ return bytes_written ;
635729}
636730
637731pub const SetCurrentDirectoryError = error {
@@ -5073,3 +5167,85 @@ pub fn ProcessBaseAddress(handle: HANDLE) ProcessBaseAddressError!HMODULE {
50735167 const ppeb : * const PEB = @ptrCast (@alignCast (peb_out .ptr ));
50745168 return ppeb .ImageBaseAddress ;
50755169}
5170+
5171+ pub fn IsConsoleHandle (handle : HANDLE ) bool {
5172+ var out : DWORD = undefined ;
5173+ return kernel32 .GetConsoleMode (handle , & out ) != FALSE ;
5174+ }
5175+
5176+ const ConsoleHandleData = struct {
5177+ is_assigned : bool = false ,
5178+
5179+ handle : ? HANDLE = null ,
5180+
5181+ /// On Windows NT, UTF-8 encoded strings should be converted to UTF-16 before writing to the
5182+ /// native console. Since write() might be called with a string fragment or even a single byte,
5183+ /// we have to store residual UTF-8 byte(s) without returning error. UTF-16 code unit(s) will be
5184+ /// generated when we have enough bytes to complete a code point.
5185+ utf8_buffer : Utf8Buffer = .{},
5186+
5187+ last_character_written_is_CR : bool = false ,
5188+
5189+ pub const Utf8Buffer = struct {
5190+ data : [4 ]u8 = .{ 0x00 , 0x00 , 0x00 , 0x00 },
5191+ bytes_used : u3 = 0 ,
5192+ };
5193+ };
5194+
5195+ const console_handle_data_limit = 64 ;
5196+
5197+ var console_handle_data_array : switch (builtin .os .tag ) {
5198+ .windows = > [console_handle_data_limit ]ConsoleHandleData ,
5199+ else = > void ,
5200+ } = switch (builtin .os .tag ) {
5201+ .windows = > [_ ]ConsoleHandleData {.{}} ** console_handle_data_limit ,
5202+ else = > void {},
5203+ };
5204+
5205+ const ConsoleHandleDataError = error {
5206+ OsUnsupported ,
5207+ DataNotFound ,
5208+ ConsoleHandleLimitReached ,
5209+ };
5210+
5211+ fn GetConsoleHandleData (handle : HANDLE ) ConsoleHandleDataError ! * ConsoleHandleData {
5212+ if (builtin .os .tag == .windows ) {
5213+ var found_unassigned : bool = false ;
5214+ var first_unassigned_index : usize = undefined ;
5215+ for (0.. console_handle_data_limit ) | index | {
5216+ if (console_handle_data_array [index ].is_assigned ) {
5217+ if (console_handle_data_array [index ].handle == handle ) {
5218+ return & console_handle_data_array [index ];
5219+ }
5220+ } else if (! found_unassigned ) {
5221+ found_unassigned = true ;
5222+ first_unassigned_index = index ;
5223+ }
5224+ }
5225+ if (found_unassigned ) {
5226+ console_handle_data_array [first_unassigned_index ].is_assigned = true ;
5227+ console_handle_data_array [first_unassigned_index ].handle = handle ;
5228+ console_handle_data_array [first_unassigned_index ].utf8_buffer .bytes_used = 0 ;
5229+ console_handle_data_array [first_unassigned_index ].last_character_written_is_CR = false ;
5230+ return & console_handle_data_array [first_unassigned_index ];
5231+ } else {
5232+ return error .ConsoleHandleLimitReached ;
5233+ }
5234+ } else {
5235+ return error .OsUnsupported ;
5236+ }
5237+ }
5238+
5239+ fn RemoveConsoleHandleData (handle : HANDLE ) ConsoleHandleDataError ! usize {
5240+ if (builtin .os .tag == .windows ) {
5241+ for (0.. console_handle_data_limit ) | index | {
5242+ if (console_handle_data_array [index ].is_assigned and console_handle_data_array [index ].handle == handle ) {
5243+ console_handle_data_array [index ].is_assigned = false ;
5244+ return index ;
5245+ }
5246+ }
5247+ return error .DataNotFound ;
5248+ } else {
5249+ return error .OsUnsupported ;
5250+ }
5251+ }
0 commit comments