| author | |
| committer | |
| log | c77e7146f5fa8e83c06cd6612b7298df06912974 |
| tree | c49c30a3bdeddddede96e5cb7c0d813342a34bda |
| parent | f1507599531e00f40fd31bbe39f2526e70241e10 |
9 files changed, 477 insertions(+), 322 deletions(-)
lib/std/Io.zig+3-5| ... | ... | @@ -335,11 +335,9 @@ pub const Operation = union(enum) { |
| 335 | 335 | .wasi => noreturn, |
| 336 | 336 | .windows => struct { |
| 337 | 337 | file: File, |
| 338 | IoControlCode: std.os.windows.CTL_CODE, | |
| 339 | InputBuffer: ?*const anyopaque, | |
| 340 | InputBufferLength: u32, | |
| 341 | OutputBuffer: ?*anyopaque, | |
| 342 | OutputBufferLength: u32, | |
| 338 | code: std.os.windows.CTL_CODE, | |
| 339 | in: []const u8 = &.{}, | |
| 340 | out: []u8 = &.{}, | |
| 343 | 341 | |
| 344 | 342 | pub const Result = std.os.windows.IO_STATUS_BLOCK; |
| 345 | 343 | }, |
lib/std/Io/Terminal.zig+16-10| ... | ... | @@ -40,7 +40,8 @@ pub const Mode = union(enum) { |
| 40 | 40 | windows_api: WindowsApi, |
| 41 | 41 | |
| 42 | 42 | pub const WindowsApi = if (!is_windows) noreturn else struct { |
| 43 | handle: File.Handle, | |
| 43 | io: Io, | |
| 44 | file: File, | |
| 44 | 45 | reset_attributes: u16, |
| 45 | 46 | }; |
| 46 | 47 | |
| ... | ... | @@ -65,20 +66,21 @@ pub const Mode = union(enum) { |
| 65 | 66 | } |
| 66 | 67 | |
| 67 | 68 | if (is_windows and try file.isTty(io)) { |
| 68 | const windows = std.os.windows; | |
| 69 | var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; | |
| 70 | if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != 0) { | |
| 71 | return .{ .windows_api = .{ | |
| 72 | .handle = file.handle, | |
| 73 | .reset_attributes = info.wAttributes, | |
| 74 | } }; | |
| 69 | var get_console_info = std.os.windows.CONSOLE.USER_IO.GET_SCREEN_BUFFER_INFO; | |
| 70 | switch (try get_console_info.operate(io, file)) { | |
| 71 | .SUCCESS => return .{ .windows_api = .{ | |
| 72 | .io = io, | |
| 73 | .file = file, | |
| 74 | .reset_attributes = get_console_info.Data.wAttributes, | |
| 75 | } }, | |
| 76 | else => {}, | |
| 75 | 77 | } |
| 76 | 78 | } |
| 77 | 79 | return if (force_color == true) .escape_codes else .no_color; |
| 78 | 80 | } |
| 79 | 81 | }; |
| 80 | 82 | |
| 81 | pub const SetColorError = std.os.windows.SetConsoleTextAttributeError || Io.Writer.Error; | |
| 83 | pub const SetColorError = Io.Cancelable || Io.UnexpectedError || Io.Writer.Error; | |
| 82 | 84 | |
| 83 | 85 | pub fn setColor(t: Terminal, color: Color) SetColorError!void { |
| 84 | 86 | switch (t.mode) { |
| ... | ... | @@ -132,7 +134,11 @@ pub fn setColor(t: Terminal, color: Color) SetColorError!void { |
| 132 | 134 | .reset => wa.reset_attributes, |
| 133 | 135 | }; |
| 134 | 136 | try t.writer.flush(); |
| 135 | try windows.SetConsoleTextAttribute(wa.handle, attributes); | |
| 137 | var set_text_attribute = windows.CONSOLE.USER_IO.SET_TEXT_ATTRIBUTE(attributes); | |
| 138 | switch (try set_text_attribute.operate(wa.io, wa.file)) { | |
| 139 | .SUCCESS => {}, | |
| 140 | else => |status| return windows.unexpectedStatus(status), | |
| 141 | } | |
| 136 | 142 | }, |
| 137 | 143 | } |
| 138 | 144 | } |
lib/std/Io/Threaded.zig+93-115| ... | ... | @@ -3083,19 +3083,23 @@ fn batchAwaitWindows(b: *Io.Batch, concurrency: bool) error{ Canceled, Concurren |
| 3083 | 3083 | } |
| 3084 | 3084 | }, |
| 3085 | 3085 | .device_io_control => |o| { |
| 3086 | const NtControlFile = switch (o.code.DeviceType) { | |
| 3087 | .FILE_SYSTEM, .NAMED_PIPE => &windows.ntdll.NtFsControlFile, | |
| 3088 | else => &windows.ntdll.NtDeviceIoControlFile, | |
| 3089 | }; | |
| 3086 | 3090 | if (o.file.flags.nonblocking) { |
| 3087 | 3091 | context.file = o.file.handle; |
| 3088 | switch (windows.ntdll.NtDeviceIoControlFile( | |
| 3092 | switch (NtControlFile( | |
| 3089 | 3093 | o.file.handle, |
| 3090 | 3094 | null, // event |
| 3091 | 3095 | &batchApc, |
| 3092 | 3096 | b, |
| 3093 | 3097 | &context.iosb, |
| 3094 | o.IoControlCode, | |
| 3095 | o.InputBuffer, | |
| 3096 | o.InputBufferLength, | |
| 3097 | o.OutputBuffer, | |
| 3098 | o.OutputBufferLength, | |
| 3098 | o.code, | |
| 3099 | if (o.in.len > 0) o.in.ptr else null, | |
| 3100 | @intCast(o.in.len), | |
| 3101 | if (o.out.len > 0) o.out.ptr else null, | |
| 3102 | @intCast(o.out.len), | |
| 3099 | 3103 | )) { |
| 3100 | 3104 | .PENDING, .SUCCESS => {}, |
| 3101 | 3105 | .CANCELLED => unreachable, |
| ... | ... | @@ -3108,17 +3112,17 @@ fn batchAwaitWindows(b: *Io.Batch, concurrency: bool) error{ Canceled, Concurren |
| 3108 | 3112 | if (concurrency) return error.ConcurrencyUnavailable; |
| 3109 | 3113 | |
| 3110 | 3114 | const syscall: Syscall = try .start(); |
| 3111 | while (true) switch (windows.ntdll.NtDeviceIoControlFile( | |
| 3115 | while (true) switch (NtControlFile( | |
| 3112 | 3116 | o.file.handle, |
| 3113 | 3117 | null, // event |
| 3114 | 3118 | null, // APC routine |
| 3115 | 3119 | null, // APC context |
| 3116 | 3120 | &context.iosb, |
| 3117 | o.IoControlCode, | |
| 3118 | o.InputBuffer, | |
| 3119 | o.InputBufferLength, | |
| 3120 | o.OutputBuffer, | |
| 3121 | o.OutputBufferLength, | |
| 3121 | o.code, | |
| 3122 | if (o.in.len > 0) o.in.ptr else null, | |
| 3123 | @intCast(o.in.len), | |
| 3124 | if (o.out.len > 0) o.out.ptr else null, | |
| 3125 | @intCast(o.out.len), | |
| 3122 | 3126 | )) { |
| 3123 | 3127 | .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag |
| 3124 | 3128 | .CANCELLED => { |
| ... | ... | @@ -8547,29 +8551,24 @@ fn fileSyncWasi(userdata: ?*anyopaque, file: File) File.SyncError!void { |
| 8547 | 8551 | |
| 8548 | 8552 | fn fileIsTty(userdata: ?*anyopaque, file: File) Io.Cancelable!bool { |
| 8549 | 8553 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 8550 | _ = t; | |
| 8551 | return isTty(file); | |
| 8554 | return t.isTty(file); | |
| 8552 | 8555 | } |
| 8553 | 8556 | |
| 8554 | fn isTty(file: File) Io.Cancelable!bool { | |
| 8557 | fn isTty(t: *Threaded, file: File) Io.Cancelable!bool { | |
| 8555 | 8558 | if (is_windows) { |
| 8556 | if (try isCygwinPty(file)) return true; | |
| 8557 | var out: windows.DWORD = undefined; | |
| 8558 | const syscall: Syscall = try .start(); | |
| 8559 | while (windows.kernel32.GetConsoleMode(file.handle, &out) == 0) { | |
| 8560 | switch (windows.GetLastError()) { | |
| 8561 | .OPERATION_ABORTED => { | |
| 8562 | try syscall.checkCancel(); | |
| 8563 | continue; | |
| 8564 | }, | |
| 8565 | else => { | |
| 8566 | syscall.finish(); | |
| 8567 | return false; | |
| 8568 | }, | |
| 8569 | } | |
| 8559 | var get_console_mode = windows.CONSOLE.USER_IO.GET_MODE; | |
| 8560 | switch ((try t.deviceIoControl(&.{ | |
| 8561 | .file = .{ | |
| 8562 | .handle = windows.peb().ProcessParameters.ConsoleHandle, | |
| 8563 | .flags = .{ .nonblocking = false }, | |
| 8564 | }, | |
| 8565 | .code = windows.IOCTL.CONDRV.ISSUE_USER_IO, | |
| 8566 | .in = @ptrCast(&get_console_mode.request(file, 0, .{}, 0, .{})), | |
| 8567 | })).u.Status) { | |
| 8568 | .SUCCESS => return true, | |
| 8569 | .INVALID_HANDLE => return isCygwinPty(file), | |
| 8570 | else => return false, | |
| 8570 | 8571 | } |
| 8571 | syscall.finish(); | |
| 8572 | return true; | |
| 8573 | 8572 | } |
| 8574 | 8573 | |
| 8575 | 8574 | if (builtin.link_libc) { |
| ... | ... | @@ -8637,35 +8636,26 @@ fn isTty(file: File) Io.Cancelable!bool { |
| 8637 | 8636 | |
| 8638 | 8637 | fn fileEnableAnsiEscapeCodes(userdata: ?*anyopaque, file: File) File.EnableAnsiEscapeCodesError!void { |
| 8639 | 8638 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 8640 | _ = t; | |
| 8641 | 8639 | |
| 8642 | if (!is_windows) { | |
| 8643 | if (try supportsAnsiEscapeCodes(file)) return; | |
| 8644 | return error.NotTerminalDevice; | |
| 8645 | } | |
| 8640 | if (!is_windows) return if (!try t.supportsAnsiEscapeCodes(file)) error.NotTerminalDevice; | |
| 8646 | 8641 | |
| 8647 | 8642 | // For Windows Terminal, VT Sequences processing is enabled by default. |
| 8648 | var original_console_mode: windows.DWORD = 0; | |
| 8649 | ||
| 8650 | { | |
| 8651 | const syscall: Syscall = try .start(); | |
| 8652 | while (windows.kernel32.GetConsoleMode(file.handle, &original_console_mode) == 0) { | |
| 8653 | switch (windows.GetLastError()) { | |
| 8654 | .OPERATION_ABORTED => { | |
| 8655 | try syscall.checkCancel(); | |
| 8656 | continue; | |
| 8657 | }, | |
| 8658 | else => { | |
| 8659 | syscall.finish(); | |
| 8660 | if (try isCygwinPty(file)) return; | |
| 8661 | return error.NotTerminalDevice; | |
| 8662 | }, | |
| 8663 | } | |
| 8664 | } | |
| 8665 | syscall.finish(); | |
| 8643 | const console: File = .{ | |
| 8644 | .handle = windows.peb().ProcessParameters.ConsoleHandle, | |
| 8645 | .flags = .{ .nonblocking = false }, | |
| 8646 | }; | |
| 8647 | var get_console_mode = windows.CONSOLE.USER_IO.GET_MODE; | |
| 8648 | switch ((try t.deviceIoControl(&.{ | |
| 8649 | .file = console, | |
| 8650 | .code = windows.IOCTL.CONDRV.ISSUE_USER_IO, | |
| 8651 | .in = @ptrCast(&get_console_mode.request(file, 0, .{}, 0, .{})), | |
| 8652 | })).u.Status) { | |
| 8653 | .SUCCESS => {}, | |
| 8654 | .INVALID_HANDLE => return if (!try isCygwinPty(file)) error.NotTerminalDevice, | |
| 8655 | else => return error.NotTerminalDevice, | |
| 8666 | 8656 | } |
| 8667 | 8657 | |
| 8668 | if (original_console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return; | |
| 8658 | if (get_console_mode.Data & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return; | |
| 8669 | 8659 | |
| 8670 | 8660 | // For Windows Console, VT Sequences processing support was added in Windows 10 build 14361, but disabled by default. |
| 8671 | 8661 | // https://devblogs.microsoft.com/commandline/tmux-support-arrives-for-bash-on-ubuntu-on-windows/ |
| ... | ... | @@ -8678,58 +8668,40 @@ fn fileEnableAnsiEscapeCodes(userdata: ?*anyopaque, file: File) File.EnableAnsiE |
| 8678 | 8668 | // Additionally, the default console mode in Windows Terminal does not have |
| 8679 | 8669 | // `DISABLE_NEWLINE_AUTO_RETURN` set, so by only enabling `ENABLE_VIRTUAL_TERMINAL_PROCESSING` |
| 8680 | 8670 | // we end up matching the mode of Windows Terminal. |
| 8681 | const requested_console_modes = windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING; | |
| 8682 | const console_mode = original_console_mode | requested_console_modes; | |
| 8683 | ||
| 8684 | { | |
| 8685 | const syscall: Syscall = try .start(); | |
| 8686 | while (windows.kernel32.SetConsoleMode(file.handle, console_mode) == 0) { | |
| 8687 | switch (windows.GetLastError()) { | |
| 8688 | .OPERATION_ABORTED => { | |
| 8689 | try syscall.checkCancel(); | |
| 8690 | continue; | |
| 8691 | }, | |
| 8692 | else => { | |
| 8693 | syscall.finish(); | |
| 8694 | if (try isCygwinPty(file)) return; | |
| 8695 | return error.NotTerminalDevice; | |
| 8696 | }, | |
| 8697 | } | |
| 8698 | } | |
| 8699 | syscall.finish(); | |
| 8671 | var set_console_mode = windows.CONSOLE.USER_IO.SET_MODE( | |
| 8672 | get_console_mode.Data | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING, | |
| 8673 | ); | |
| 8674 | switch ((try t.deviceIoControl(&.{ | |
| 8675 | .file = console, | |
| 8676 | .code = windows.IOCTL.CONDRV.ISSUE_USER_IO, | |
| 8677 | .in = @ptrCast(&set_console_mode.request(file, 0, .{}, 0, .{})), | |
| 8678 | })).u.Status) { | |
| 8679 | .SUCCESS => {}, | |
| 8680 | else => |status| return windows.unexpectedStatus(status), | |
| 8700 | 8681 | } |
| 8701 | 8682 | } |
| 8702 | 8683 | |
| 8703 | 8684 | fn fileSupportsAnsiEscapeCodes(userdata: ?*anyopaque, file: File) Io.Cancelable!bool { |
| 8704 | 8685 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 8705 | _ = t; | |
| 8706 | return supportsAnsiEscapeCodes(file); | |
| 8686 | return t.supportsAnsiEscapeCodes(file); | |
| 8707 | 8687 | } |
| 8708 | 8688 | |
| 8709 | fn supportsAnsiEscapeCodes(file: File) Io.Cancelable!bool { | |
| 8689 | fn supportsAnsiEscapeCodes(t: *Threaded, file: File) Io.Cancelable!bool { | |
| 8710 | 8690 | if (is_windows) { |
| 8711 | var console_mode: windows.DWORD = 0; | |
| 8712 | ||
| 8713 | const syscall: Syscall = try .start(); | |
| 8714 | while (windows.kernel32.GetConsoleMode(file.handle, &console_mode) == 0) { | |
| 8715 | switch (windows.GetLastError()) { | |
| 8716 | .OPERATION_ABORTED => { | |
| 8717 | try syscall.checkCancel(); | |
| 8718 | continue; | |
| 8719 | }, | |
| 8720 | else => { | |
| 8721 | syscall.finish(); | |
| 8722 | break; | |
| 8723 | }, | |
| 8724 | } | |
| 8725 | } else { | |
| 8726 | syscall.finish(); | |
| 8727 | if (console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) { | |
| 8728 | return true; | |
| 8729 | } | |
| 8691 | var get_console_mode = windows.CONSOLE.USER_IO.GET_MODE; | |
| 8692 | switch ((try t.deviceIoControl(&.{ | |
| 8693 | .file = .{ | |
| 8694 | .handle = windows.peb().ProcessParameters.ConsoleHandle, | |
| 8695 | .flags = .{ .nonblocking = false }, | |
| 8696 | }, | |
| 8697 | .code = windows.IOCTL.CONDRV.ISSUE_USER_IO, | |
| 8698 | .in = @ptrCast(&get_console_mode.request(file, 0, .{}, 0, .{})), | |
| 8699 | })).u.Status) { | |
| 8700 | .SUCCESS => if (get_console_mode.Data & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) | |
| 8701 | return true, | |
| 8702 | .INVALID_HANDLE => return isCygwinPty(file), | |
| 8703 | else => return false, | |
| 8730 | 8704 | } |
| 8731 | ||
| 8732 | return isCygwinPty(file); | |
| 8733 | 8705 | } |
| 8734 | 8706 | |
| 8735 | 8707 | if (native_os == .wasi) { |
| ... | ... | @@ -8739,7 +8711,7 @@ fn supportsAnsiEscapeCodes(file: File) Io.Cancelable!bool { |
| 8739 | 8711 | return false; |
| 8740 | 8712 | } |
| 8741 | 8713 | |
| 8742 | if (try isTty(file)) return true; | |
| 8714 | if (try t.isTty(file)) return true; | |
| 8743 | 8715 | |
| 8744 | 8716 | return false; |
| 8745 | 8717 | } |
| ... | ... | @@ -14111,12 +14083,14 @@ fn initLockedStderr(t: *Threaded, terminal_mode: ?Io.Terminal.Mode) Io.Cancelabl |
| 14111 | 14083 | |
| 14112 | 14084 | fn unlockStderr(userdata: ?*anyopaque) void { |
| 14113 | 14085 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 14114 | t.stderr_writer.interface.flush() catch |err| switch (err) { | |
| 14115 | error.WriteFailed => switch (t.stderr_writer.err.?) { | |
| 14086 | if (t.stderr_writer.err == null) t.stderr_writer.interface.flush() catch {}; | |
| 14087 | if (t.stderr_writer.err) |err| { | |
| 14088 | switch (err) { | |
| 14116 | 14089 | error.Canceled => recancelInner(), |
| 14117 | 14090 | else => {}, |
| 14118 | }, | |
| 14119 | }; | |
| 14091 | } | |
| 14092 | t.stderr_writer.err = null; | |
| 14093 | } | |
| 14120 | 14094 | t.stderr_writer.interface.end = 0; |
| 14121 | 14095 | t.stderr_writer.interface.buffer = &.{}; |
| 14122 | 14096 | |
| ... | ... | @@ -18848,20 +18822,24 @@ fn mmSyncWrite(file: File, memory: []u8, offset: u64) File.WritePositionalError! |
| 18848 | 18822 | fn deviceIoControl(t: *Threaded, o: *const Io.Operation.DeviceIoControl) Io.Cancelable!Io.Operation.DeviceIoControl.Result { |
| 18849 | 18823 | _ = t; |
| 18850 | 18824 | if (is_windows) { |
| 18825 | const NtControlFile = switch (o.code.DeviceType) { | |
| 18826 | .FILE_SYSTEM, .NAMED_PIPE => &windows.ntdll.NtFsControlFile, | |
| 18827 | else => &windows.ntdll.NtDeviceIoControlFile, | |
| 18828 | }; | |
| 18851 | 18829 | var iosb: windows.IO_STATUS_BLOCK = undefined; |
| 18852 | 18830 | if (o.file.flags.nonblocking) { |
| 18853 | 18831 | var done: bool = false; |
| 18854 | switch (windows.ntdll.NtDeviceIoControlFile( | |
| 18832 | switch (NtControlFile( | |
| 18855 | 18833 | o.file.handle, |
| 18856 | 18834 | null, // event |
| 18857 | 18835 | flagApc, |
| 18858 | 18836 | &done, // APC context |
| 18859 | 18837 | &iosb, |
| 18860 | o.IoControlCode, | |
| 18861 | o.InputBuffer, | |
| 18862 | o.InputBufferLength, | |
| 18863 | o.OutputBuffer, | |
| 18864 | o.OutputBufferLength, | |
| 18838 | o.code, | |
| 18839 | if (o.in.len > 0) o.in.ptr else null, | |
| 18840 | @intCast(o.in.len), | |
| 18841 | if (o.out.len > 0) o.out.ptr else null, | |
| 18842 | @intCast(o.out.len), | |
| 18865 | 18843 | )) { |
| 18866 | 18844 | // We must wait for the APC routine. |
| 18867 | 18845 | .PENDING, .SUCCESS => while (!done) { |
| ... | ... | @@ -18882,17 +18860,17 @@ fn deviceIoControl(t: *Threaded, o: *const Io.Operation.DeviceIoControl) Io.Canc |
| 18882 | 18860 | } |
| 18883 | 18861 | } else { |
| 18884 | 18862 | const syscall: Syscall = try .start(); |
| 18885 | while (true) switch (windows.ntdll.NtDeviceIoControlFile( | |
| 18863 | while (true) switch (NtControlFile( | |
| 18886 | 18864 | o.file.handle, |
| 18887 | 18865 | null, // event |
| 18888 | 18866 | null, // APC routine |
| 18889 | 18867 | null, // APC context |
| 18890 | 18868 | &iosb, |
| 18891 | o.IoControlCode, | |
| 18892 | o.InputBuffer, | |
| 18893 | o.InputBufferLength, | |
| 18894 | o.OutputBuffer, | |
| 18895 | o.OutputBufferLength, | |
| 18869 | o.code, | |
| 18870 | if (o.in.len > 0) o.in.ptr else null, | |
| 18871 | @intCast(o.in.len), | |
| 18872 | if (o.out.len > 0) o.out.ptr else null, | |
| 18873 | @intCast(o.out.len), | |
| 18896 | 18874 | )) { |
| 18897 | 18875 | .PENDING => unreachable, // unrecoverable: wrong asynchronous flag |
| 18898 | 18876 | .CANCELLED => { |
lib/std/Progress.zig+102-69| ... | ... | @@ -157,7 +157,7 @@ pub const TerminalMode = union(enum) { |
| 157 | 157 | ansi_escape_codes, |
| 158 | 158 | /// This is not the same as being run on windows because other terminals |
| 159 | 159 | /// exist like MSYS/git-bash. |
| 160 | windows_api: if (is_windows) WindowsApi else void, | |
| 160 | windows_api: if (is_windows) WindowsApi else noreturn, | |
| 161 | 161 | |
| 162 | 162 | pub const WindowsApi = struct { |
| 163 | 163 | /// The output code page of the console. |
| ... | ... | @@ -614,33 +614,39 @@ pub fn start(io: Io, options: Options) Node { |
| 614 | 614 | if (stderr.enableAnsiEscapeCodes(io)) |_| { |
| 615 | 615 | global_progress.terminal_mode = .ansi_escape_codes; |
| 616 | 616 | } else |_| if (is_windows) { |
| 617 | if (stderr.isTty(io)) |is_tty| { | |
| 618 | if (is_tty) global_progress.terminal_mode = TerminalMode{ .windows_api = .{ | |
| 619 | .code_page = windows.kernel32.GetConsoleOutputCP(), | |
| 620 | } }; | |
| 621 | } else |err| switch (err) { | |
| 617 | var get_console_cp = windows.CONSOLE.USER_IO.GET_CP(.Output); | |
| 618 | // Normally, we would pass `null` to `operate` here as the kernel32 | |
| 619 | // function does not accept a handle, however, if we pass one anyway, | |
| 620 | // then we will get an error if the handle is not associated with | |
| 621 | // this process's console, effectively combining an `isTty` check | |
| 622 | // into the same syscall. | |
| 623 | switch (get_console_cp.operate(io, stderr) catch |err| switch (err) { | |
| 622 | 624 | error.Canceled => { |
| 623 | 625 | io.recancel(); |
| 624 | 626 | return .none; |
| 625 | 627 | }, |
| 628 | }) { | |
| 629 | .SUCCESS => global_progress.terminal_mode = .{ .windows_api = .{ | |
| 630 | .code_page = get_console_cp.Data.CodePage, | |
| 631 | } }, | |
| 632 | .INVALID_HANDLE => {}, | |
| 633 | else => {}, | |
| 626 | 634 | } |
| 627 | 635 | } |
| 628 | ||
| 629 | if (global_progress.terminal_mode == .off) return .none; | |
| 630 | ||
| 631 | if (have_sigwinch) { | |
| 632 | const act: posix.Sigaction = .{ | |
| 633 | .handler = .{ .sigaction = handleSigWinch }, | |
| 634 | .mask = posix.sigemptyset(), | |
| 635 | .flags = (posix.SA.SIGINFO | posix.SA.RESTART), | |
| 636 | }; | |
| 637 | posix.sigaction(.WINCH, &act, null); | |
| 638 | } | |
| 639 | ||
| 640 | if (switch (global_progress.terminal_mode) { | |
| 641 | .off => unreachable, // handled a few lines above | |
| 642 | .ansi_escape_codes => io.concurrent(updateTask, .{io}), | |
| 643 | .windows_api => if (is_windows) io.concurrent(windowsApiUpdateTask, .{io}) else unreachable, | |
| 636 | if (future: switch (global_progress.terminal_mode) { | |
| 637 | .off => return .none, | |
| 638 | .ansi_escape_codes => { | |
| 639 | if (have_sigwinch) { | |
| 640 | const act: posix.Sigaction = .{ | |
| 641 | .handler = .{ .sigaction = handleSigWinch }, | |
| 642 | .mask = posix.sigemptyset(), | |
| 643 | .flags = (posix.SA.SIGINFO | posix.SA.RESTART), | |
| 644 | }; | |
| 645 | posix.sigaction(.WINCH, &act, null); | |
| 646 | } | |
| 647 | break :future io.concurrent(updateTask, .{io}); | |
| 648 | }, | |
| 649 | .windows_api => io.concurrent(windowsApiUpdateTask, .{io}), | |
| 644 | 650 | }) |future| { |
| 645 | 651 | global_progress.update_worker = future; |
| 646 | 652 | } else |err| { |
| ... | ... | @@ -715,12 +721,24 @@ fn updateTask(io: Io) WorkerError!void { |
| 715 | 721 | } |
| 716 | 722 | } |
| 717 | 723 | |
| 718 | fn windowsApiWriteMarker() void { | |
| 724 | const WindowsApiError = Io.Cancelable || Io.UnexpectedError; | |
| 725 | ||
| 726 | fn windowsApiWriteMarker(io: Io) WindowsApiError!void { | |
| 719 | 727 | // Write the marker that we will use to find the beginning of the progress when clearing. |
| 720 | 728 | // Note: This doesn't have to use WriteConsoleW, but doing so avoids dealing with the code page. |
| 721 | var num_chars_written: windows.DWORD = undefined; | |
| 722 | const handle = global_progress.terminal.handle; | |
| 723 | _ = windows.kernel32.WriteConsoleW(handle, &[_]u16{windows_api_start_marker}, 1, &num_chars_written, null); | |
| 729 | const terminal = global_progress.terminal; | |
| 730 | var write_console = windows.CONSOLE.USER_IO.WRITE(.WideCharacter); | |
| 731 | const buffer = [1]windows.WCHAR{windows_api_start_marker}; | |
| 732 | switch ((try io.operate(.{ .device_io_control = .{ | |
| 733 | .file = terminal, | |
| 734 | .code = windows.IOCTL.CONDRV.ISSUE_USER_IO, | |
| 735 | .in = @ptrCast(&write_console.request(null, 1, .{ | |
| 736 | .{ .Size = @sizeOf(@TypeOf(buffer)), .Pointer = &buffer }, | |
| 737 | }, 0, .{})), | |
| 738 | } })).device_io_control.u.Status) { | |
| 739 | .SUCCESS => {}, | |
| 740 | else => |status| return windows.unexpectedStatus(status), | |
| 741 | } | |
| 724 | 742 | } |
| 725 | 743 | |
| 726 | 744 | fn windowsApiUpdateTask(io: Io) WorkerError!void { |
| ... | ... | @@ -743,19 +761,19 @@ fn windowsApiUpdateTask(io: Io) WorkerError!void { |
| 743 | 761 | error.Canceled => unreachable, // blocked |
| 744 | 762 | }; |
| 745 | 763 | defer io.unlockStderr(); |
| 746 | clearWrittenWindowsApi() catch {}; | |
| 764 | clearWrittenWindowsApi(io) catch {}; | |
| 747 | 765 | } |
| 748 | 766 | while (true) { |
| 749 | 767 | const buffer, const nl_n = try computeRedraw(io, &serialized_buffer); |
| 750 | 768 | if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| { |
| 751 | 769 | defer io.unlockStderr(); |
| 752 | try clearWrittenWindowsApi(); | |
| 753 | windowsApiWriteMarker(); | |
| 770 | try clearWrittenWindowsApi(io); | |
| 771 | try windowsApiWriteMarker(io); | |
| 754 | 772 | global_progress.need_clear = true; |
| 755 | 773 | locked_stderr.file_writer.interface.writeAll(buffer) catch |err| switch (err) { |
| 756 | 774 | error.WriteFailed => return locked_stderr.file_writer.err.?, |
| 757 | 775 | }; |
| 758 | windowsApiMoveToMarker(nl_n) catch return; | |
| 776 | windowsApiMoveToMarker(io, nl_n) catch return; | |
| 759 | 777 | } |
| 760 | 778 | |
| 761 | 779 | try maybeUpdateSize(io, try wait(io, global_progress.refresh_rate_ns)); |
| ... | ... | @@ -859,7 +877,7 @@ fn appendTreeSymbol(symbol: TreeSymbol, buf: []u8, start_i: usize) usize { |
| 859 | 877 | return start_i + bytes.len; |
| 860 | 878 | }, |
| 861 | 879 | .windows_api => |windows_api| { |
| 862 | const bytes = if (!is_windows) unreachable else switch (windows_api.code_page) { | |
| 880 | const bytes = switch (windows_api.code_page) { | |
| 863 | 881 | // Code page 437 is the default code page and contains the box drawing symbols |
| 864 | 882 | 437 => symbol.bytes(.code_page_437), |
| 865 | 883 | // UTF-8 |
| ... | ... | @@ -882,7 +900,7 @@ pub fn clearWrittenWithEscapeCodes(file_writer: *Io.File.Writer) Io.Writer.Error |
| 882 | 900 | /// U+25BA or ► |
| 883 | 901 | const windows_api_start_marker = 0x25BA; |
| 884 | 902 | |
| 885 | fn clearWrittenWindowsApi() error{Unexpected}!void { | |
| 903 | fn clearWrittenWindowsApi(io: Io) WindowsApiError!void { | |
| 886 | 904 | // This uses a 'marker' strategy. The idea is: |
| 887 | 905 | // - Always write a marker (in this case U+25BA or ►) at the beginning of the progress |
| 888 | 906 | // - Get the current cursor position (at the end of the progress) |
| ... | ... | @@ -903,43 +921,60 @@ fn clearWrittenWindowsApi() error{Unexpected}!void { |
| 903 | 921 | // character in order to be readable via ReadConsoleOutputAttribute. It doesn't seem |
| 904 | 922 | // like any of the available attributes are invisible/benign. |
| 905 | 923 | if (!global_progress.need_clear) return; |
| 906 | const handle = global_progress.terminal.handle; | |
| 924 | const terminal = global_progress.terminal; | |
| 907 | 925 | const screen_area = @as(windows.DWORD, global_progress.cols) * global_progress.rows; |
| 908 | 926 | |
| 909 | var console_info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; | |
| 910 | if (windows.kernel32.GetConsoleScreenBufferInfo(handle, &console_info) == 0) { | |
| 911 | return error.Unexpected; | |
| 927 | var get_console_info = windows.CONSOLE.USER_IO.GET_SCREEN_BUFFER_INFO; | |
| 928 | switch (try get_console_info.operate(io, terminal)) { | |
| 929 | .SUCCESS => {}, | |
| 930 | else => |status| return windows.unexpectedStatus(status), | |
| 912 | 931 | } |
| 913 | var num_chars_written: windows.DWORD = undefined; | |
| 914 | if (windows.kernel32.FillConsoleOutputCharacterW(handle, ' ', screen_area, console_info.dwCursorPosition, &num_chars_written) == 0) { | |
| 915 | return error.Unexpected; | |
| 932 | var fill_spaces = windows.CONSOLE.USER_IO.FILL( | |
| 933 | .{ .WideCharacter = ' ' }, | |
| 934 | screen_area, | |
| 935 | get_console_info.Data.dwCursorPosition, | |
| 936 | ); | |
| 937 | switch (try fill_spaces.operate(io, terminal)) { | |
| 938 | .SUCCESS => {}, | |
| 939 | else => |status| return windows.unexpectedStatus(status), | |
| 916 | 940 | } |
| 917 | 941 | } |
| 918 | 942 | |
| 919 | fn windowsApiMoveToMarker(nl_n: usize) error{Unexpected}!void { | |
| 920 | const handle = global_progress.terminal.handle; | |
| 921 | var console_info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; | |
| 922 | if (windows.kernel32.GetConsoleScreenBufferInfo(handle, &console_info) == 0) { | |
| 923 | return error.Unexpected; | |
| 943 | fn windowsApiMoveToMarker(io: Io, nl_n: usize) WindowsApiError!void { | |
| 944 | const terminal = global_progress.terminal; | |
| 945 | var get_console_info = windows.CONSOLE.USER_IO.GET_SCREEN_BUFFER_INFO; | |
| 946 | switch (try get_console_info.operate(io, terminal)) { | |
| 947 | .SUCCESS => {}, | |
| 948 | else => |status| return windows.unexpectedStatus(status), | |
| 924 | 949 | } |
| 925 | const cursor_pos = console_info.dwCursorPosition; | |
| 950 | const cursor_pos = get_console_info.Data.dwCursorPosition; | |
| 926 | 951 | const expected_y = cursor_pos.Y - @as(i16, @intCast(nl_n)); |
| 927 | 952 | var start_pos: windows.COORD = .{ .X = 0, .Y = expected_y }; |
| 928 | while (start_pos.Y >= 0) { | |
| 929 | var wchar: [1]u16 = undefined; | |
| 930 | var num_console_chars_read: windows.DWORD = undefined; | |
| 931 | if (windows.kernel32.ReadConsoleOutputCharacterW(handle, &wchar, wchar.len, start_pos, &num_console_chars_read) == 0) { | |
| 932 | return error.Unexpected; | |
| 953 | while (start_pos.Y >= 0) : (start_pos.Y -= 1) { | |
| 954 | var read_output_char = windows.CONSOLE.USER_IO.READ_OUTPUT_CHARACTER(start_pos, .WideCharacter); | |
| 955 | var buffer: [1]windows.WCHAR = undefined; | |
| 956 | switch ((try io.operate(.{ .device_io_control = .{ | |
| 957 | .file = .{ | |
| 958 | .handle = windows.peb().ProcessParameters.ConsoleHandle, | |
| 959 | .flags = .{ .nonblocking = false }, | |
| 960 | }, | |
| 961 | .code = windows.IOCTL.CONDRV.ISSUE_USER_IO, | |
| 962 | .in = @ptrCast(&read_output_char.request(terminal, 0, .{}, 1, .{ | |
| 963 | .{ .Size = @sizeOf(@TypeOf(buffer)), .Pointer = &buffer }, | |
| 964 | })), | |
| 965 | } })).device_io_control.u.Status) { | |
| 966 | .SUCCESS => {}, | |
| 967 | else => |status| return windows.unexpectedStatus(status), | |
| 933 | 968 | } |
| 934 | ||
| 935 | if (wchar[0] == windows_api_start_marker) break; | |
| 936 | start_pos.Y -= 1; | |
| 969 | if (read_output_char.Data.nLength >= 1 and buffer[0] == windows_api_start_marker) break; | |
| 937 | 970 | } else { |
| 938 | 971 | // If we couldn't find the marker, then just assume that no lines wrapped |
| 939 | 972 | start_pos = .{ .X = 0, .Y = expected_y }; |
| 940 | 973 | } |
| 941 | if (windows.kernel32.SetConsoleCursorPosition(handle, start_pos) == 0) { | |
| 942 | return error.Unexpected; | |
| 974 | var set_cursor_position = windows.CONSOLE.USER_IO.SET_CURSOR_POSITION(start_pos); | |
| 975 | switch (try set_cursor_position.operate(io, terminal)) { | |
| 976 | .SUCCESS => {}, | |
| 977 | else => |status| return windows.unexpectedStatus(status), | |
| 943 | 978 | } |
| 944 | 979 | } |
| 945 | 980 | |
| ... | ... | @@ -1279,7 +1314,7 @@ fn computeRedraw(io: Io, serialized_buffer: *Serialized.Buffer) !struct { []u8, |
| 1279 | 1314 | buf[i..][0..clear.len].* = clear.*; |
| 1280 | 1315 | i += clear.len; |
| 1281 | 1316 | }, |
| 1282 | .windows_api => if (!is_windows) unreachable, | |
| 1317 | .windows_api => {}, | |
| 1283 | 1318 | } |
| 1284 | 1319 | |
| 1285 | 1320 | const root_node_index: Node.Index = @enumFromInt(0); |
| ... | ... | @@ -1491,19 +1526,17 @@ fn maybeUpdateSize(io: Io, resize_flag: bool) !void { |
| 1491 | 1526 | const file = global_progress.terminal; |
| 1492 | 1527 | |
| 1493 | 1528 | if (is_windows) { |
| 1494 | var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; | |
| 1495 | ||
| 1496 | if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.FALSE) { | |
| 1497 | // In the old Windows console, dwSize.Y is the line count of the | |
| 1498 | // entire scrollback buffer, so we use this instead so that we | |
| 1499 | // always get the size of the screen. | |
| 1500 | const screen_height = info.srWindow.Bottom - info.srWindow.Top; | |
| 1501 | global_progress.rows = @intCast(screen_height); | |
| 1502 | global_progress.cols = @intCast(info.dwSize.X); | |
| 1503 | } else { | |
| 1504 | std.log.debug("failed to determine terminal size; using conservative guess 80x25", .{}); | |
| 1505 | global_progress.rows = 25; | |
| 1506 | global_progress.cols = 80; | |
| 1529 | var get_console_info = windows.CONSOLE.USER_IO.GET_SCREEN_BUFFER_INFO; | |
| 1530 | switch (try get_console_info.operate(io, file)) { | |
| 1531 | .SUCCESS => { | |
| 1532 | global_progress.rows = @intCast(get_console_info.Data.dwWindowSize.Y); | |
| 1533 | global_progress.cols = @intCast(get_console_info.Data.dwWindowSize.X); | |
| 1534 | }, | |
| 1535 | else => { | |
| 1536 | std.log.debug("failed to determine terminal size; using conservative guess 80x25", .{}); | |
| 1537 | global_progress.rows = 25; | |
| 1538 | global_progress.cols = 80; | |
| 1539 | }, | |
| 1507 | 1540 | } |
| 1508 | 1541 | } else { |
| 1509 | 1542 | var winsize: posix.winsize = .{ |
lib/std/log.zig+4-1| ... | ... | @@ -80,7 +80,7 @@ pub fn logEnabled(comptime level: Level, comptime scope: @EnumLiteral()) bool { |
| 80 | 80 | return @intFromEnum(level) <= @intFromEnum(std.options.log_level); |
| 81 | 81 | } |
| 82 | 82 | |
| 83 | pub const terminalMode = std.options.logTerminalMode; | |
| 83 | pub const terminalMode = std.Options.logTerminalMode; | |
| 84 | 84 | |
| 85 | 85 | pub fn defaultTerminalMode() std.Io.Terminal.Mode { |
| 86 | 86 | const stderr = std.debug.lockStderr(&.{}).terminal(); |
| ... | ... | @@ -99,6 +99,9 @@ pub fn defaultLog( |
| 99 | 99 | comptime format: []const u8, |
| 100 | 100 | args: anytype, |
| 101 | 101 | ) void { |
| 102 | const io = std.Options.debug_io; | |
| 103 | const prev = io.swapCancelProtection(.blocked); | |
| 104 | defer _ = io.swapCancelProtection(prev); | |
| 102 | 105 | var buffer: [64]u8 = undefined; |
| 103 | 106 | const stderr = std.debug.lockStderr(&buffer).terminal(); |
| 104 | 107 | defer std.debug.unlockStderr(); |
lib/std/os/windows.zig+254-35| ... | ... | @@ -649,6 +649,235 @@ pub const FILE = struct { |
| 649 | 649 | }; |
| 650 | 650 | }; |
| 651 | 651 | |
| 652 | pub const CONSOLE = struct { | |
| 653 | pub const USER_IO = struct { | |
| 654 | pub const INFO = struct { | |
| 655 | pub const CP = extern struct { | |
| 656 | /// GetCP: output | |
| 657 | /// SetCP: input | |
| 658 | CodePage: UINT, | |
| 659 | /// input | |
| 660 | Mode: MODE, | |
| 661 | ||
| 662 | pub const MODE = enum(BOOLEAN) { | |
| 663 | Input = FALSE, | |
| 664 | Output = TRUE, | |
| 665 | }; | |
| 666 | }; | |
| 667 | ||
| 668 | pub const WRITE = extern struct { | |
| 669 | /// output, in bytes | |
| 670 | Size: DWORD, | |
| 671 | /// input | |
| 672 | Mode: MODE, | |
| 673 | ||
| 674 | pub const MODE = enum(BOOLEAN) { | |
| 675 | Character = FALSE, | |
| 676 | WideCharacter = TRUE, | |
| 677 | }; | |
| 678 | }; | |
| 679 | ||
| 680 | pub const FILL = extern struct { | |
| 681 | /// input | |
| 682 | dwWriteCoord: COORD, | |
| 683 | /// input | |
| 684 | Tag: WITH.Tag, | |
| 685 | /// input | |
| 686 | With: WITH.Payload, | |
| 687 | /// input/output, in characters | |
| 688 | nLength: DWORD, | |
| 689 | ||
| 690 | pub const WITH = union(enum(DWORD)) { | |
| 691 | Character: CHAR = 1, | |
| 692 | WideCharacter: WCHAR = 2, | |
| 693 | Attribute: WORD = 3, | |
| 694 | ||
| 695 | pub const Tag = @typeInfo(WITH).@"union".tag_type.?; | |
| 696 | pub const Payload = PAYLOAD: { | |
| 697 | const with_fields = @typeInfo(WITH).@"union".fields; | |
| 698 | var field_names: [with_fields.len][]const u8 = undefined; | |
| 699 | var field_types: [with_fields.len]type = undefined; | |
| 700 | for (with_fields, &field_names, &field_types) |field, *field_name, *field_type| { | |
| 701 | field_name.* = field.name; | |
| 702 | field_type.* = field.type; | |
| 703 | } | |
| 704 | break :PAYLOAD @Union(.@"extern", null, &field_names, &field_types, &@splat(.{})); | |
| 705 | }; | |
| 706 | }; | |
| 707 | }; | |
| 708 | ||
| 709 | /// all output | |
| 710 | pub const SCREEN_BUFFER = extern struct { | |
| 711 | dwSize: COORD, | |
| 712 | dwCursorPosition: COORD, | |
| 713 | dwWindowPosition: COORD, | |
| 714 | wAttributes: WORD, | |
| 715 | dwWindowSize: COORD, | |
| 716 | dwMaximumWindowSize: COORD, | |
| 717 | wPopupAttributes: WORD, | |
| 718 | bFullscreenSupported: BOOL, | |
| 719 | ColorTable: [16]COLORREF, | |
| 720 | }; | |
| 721 | ||
| 722 | pub const READ_OUTPUT_CHARACTER = extern struct { | |
| 723 | /// input | |
| 724 | dwReadCoord: COORD, | |
| 725 | Mode: MODE, | |
| 726 | /// output, in characters | |
| 727 | nLength: DWORD, | |
| 728 | ||
| 729 | pub const MODE = enum(DWORD) { | |
| 730 | Character = 1, | |
| 731 | WideCharacter = 2, | |
| 732 | }; | |
| 733 | }; | |
| 734 | }; | |
| 735 | ||
| 736 | pub fn GET_CP(mode: INFO.CP.MODE) Header.With(INFO.CP) { | |
| 737 | return .init(.GetCP, .{ .CodePage = undefined, .Mode = mode }); | |
| 738 | } | |
| 739 | pub const GET_MODE: Header.With(DWORD) = .init(.GetMode, undefined); | |
| 740 | pub fn SET_MODE(mode: DWORD) Header.With(DWORD) { | |
| 741 | return .init(.SetMode, mode); | |
| 742 | } | |
| 743 | pub fn WRITE(mode: INFO.WRITE.MODE) Header.With(INFO.WRITE) { | |
| 744 | return .init(.Write, .{ .Size = undefined, .Mode = mode }); | |
| 745 | } | |
| 746 | pub fn FILL(with: INFO.FILL.WITH, len: DWORD, coord: COORD) Header.With(INFO.FILL) { | |
| 747 | return .init(.Fill, .{ | |
| 748 | .dwWriteCoord = coord, | |
| 749 | .Tag = with, | |
| 750 | .With = switch (with) { | |
| 751 | inline else => |payload, tag| @unionInit( | |
| 752 | INFO.FILL.WITH.Payload, | |
| 753 | @tagName(tag), | |
| 754 | payload, | |
| 755 | ), | |
| 756 | }, | |
| 757 | .nLength = len, | |
| 758 | }); | |
| 759 | } | |
| 760 | pub fn SET_CP(mode: INFO.CP.MODE, cp: UINT) Header.With(INFO.CP) { | |
| 761 | return .init(.SetCP, .{ .CodePage = cp, .Mode = mode }); | |
| 762 | } | |
| 763 | pub const GET_SCREEN_BUFFER_INFO: Header.With(INFO.SCREEN_BUFFER) = | |
| 764 | .init(.GetScreenBufferInfo, undefined); | |
| 765 | pub fn SET_CURSOR_POSITION(coord: COORD) Header.With(COORD) { | |
| 766 | return .init(.SetCursorPosition, coord); | |
| 767 | } | |
| 768 | pub fn SET_TEXT_ATTRIBUTE(attribute: WORD) Header.With(WORD) { | |
| 769 | return .init(.SetTextAttribute, attribute); | |
| 770 | } | |
| 771 | pub fn READ_OUTPUT_CHARACTER( | |
| 772 | coord: COORD, | |
| 773 | mode: INFO.READ_OUTPUT_CHARACTER.MODE, | |
| 774 | ) Header.With(INFO.READ_OUTPUT_CHARACTER) { | |
| 775 | return .init(.ReadOutputCharacter, .{ | |
| 776 | .dwReadCoord = coord, | |
| 777 | .Mode = mode, | |
| 778 | .nLength = undefined, | |
| 779 | }); | |
| 780 | } | |
| 781 | ||
| 782 | pub const InputBuffer = extern struct { | |
| 783 | Size: u32, | |
| 784 | Pointer: *const anyopaque, | |
| 785 | }; | |
| 786 | ||
| 787 | pub const OutputBuffer = extern struct { | |
| 788 | Size: u32, | |
| 789 | Pointer: *anyopaque, | |
| 790 | }; | |
| 791 | ||
| 792 | pub fn Request(comptime in_len: u32, comptime out_len: u32) type { | |
| 793 | return extern struct { | |
| 794 | Handle: ?HANDLE, | |
| 795 | InputBuffersLength: u32, | |
| 796 | OutputBuffersLength: u32, | |
| 797 | InputBuffers: [in_len]InputBuffer, | |
| 798 | OutputBuffers: [out_len]OutputBuffer, | |
| 799 | ||
| 800 | pub fn init( | |
| 801 | handle: ?HANDLE, | |
| 802 | in: [in_len]InputBuffer, | |
| 803 | out: [out_len]OutputBuffer, | |
| 804 | ) @This() { | |
| 805 | return .{ | |
| 806 | .Handle = handle, | |
| 807 | .InputBuffersLength = in_len, | |
| 808 | .OutputBuffersLength = out_len, | |
| 809 | .InputBuffers = in, | |
| 810 | .OutputBuffers = out, | |
| 811 | }; | |
| 812 | } | |
| 813 | }; | |
| 814 | } | |
| 815 | ||
| 816 | pub const Header = extern struct { | |
| 817 | Operation: Operation, | |
| 818 | Size: u32, | |
| 819 | ||
| 820 | pub fn With(comptime Data: type) type { | |
| 821 | return extern struct { | |
| 822 | Header: Header, | |
| 823 | Data: Data, | |
| 824 | ||
| 825 | pub fn init(operation: Operation, data: Data) @This() { | |
| 826 | return .{ | |
| 827 | .Header = .{ .Operation = operation, .Size = @sizeOf(Data) }, | |
| 828 | .Data = data, | |
| 829 | }; | |
| 830 | } | |
| 831 | ||
| 832 | pub fn request( | |
| 833 | with: *@This(), | |
| 834 | file: ?Io.File, | |
| 835 | comptime in_len: u32, | |
| 836 | in: [in_len]InputBuffer, | |
| 837 | comptime out_len: u32, | |
| 838 | out: [out_len]OutputBuffer, | |
| 839 | ) Request(1 + in_len, 1 + out_len) { | |
| 840 | return .init( | |
| 841 | if (file) |f| f.handle else null, | |
| 842 | [1]InputBuffer{.{ | |
| 843 | .Size = @offsetOf(@This(), "Data") + @sizeOf(Data), | |
| 844 | .Pointer = with, | |
| 845 | }} ++ in, | |
| 846 | [1]OutputBuffer{.{ .Size = @sizeOf(Data), .Pointer = &with.Data }} ++ out, | |
| 847 | ); | |
| 848 | } | |
| 849 | ||
| 850 | pub fn operate(with: *@This(), io: Io, file: ?Io.File) Io.Cancelable!NTSTATUS { | |
| 851 | return (try io.operate(.{ .device_io_control = .{ | |
| 852 | .file = .{ | |
| 853 | .handle = peb().ProcessParameters.ConsoleHandle, | |
| 854 | .flags = .{ .nonblocking = false }, | |
| 855 | }, | |
| 856 | .code = IOCTL.CONDRV.ISSUE_USER_IO, | |
| 857 | .in = @ptrCast(&with.request(file, 0, .{}, 0, .{})), | |
| 858 | } })).device_io_control.u.Status; | |
| 859 | } | |
| 860 | }; | |
| 861 | } | |
| 862 | }; | |
| 863 | ||
| 864 | pub const Operation = enum(u32) { | |
| 865 | GetCP = 0x1000000, | |
| 866 | GetMode = 0x1000001, | |
| 867 | SetMode = 0x1000002, | |
| 868 | Read = 0x1000005, | |
| 869 | Write = 0x1000006, | |
| 870 | Fill = 0x2000000, | |
| 871 | SetCP = 0x2000004, | |
| 872 | GetScreenBufferInfo = 0x2000007, | |
| 873 | SetCursorPosition = 0x200000a, | |
| 874 | SetTextAttribute = 0x200000d, | |
| 875 | ReadOutputCharacter = 0x200000f, | |
| 876 | _, | |
| 877 | }; | |
| 878 | }; | |
| 879 | }; | |
| 880 | ||
| 652 | 881 | // ref: km/ntddk.h |
| 653 | 882 | |
| 654 | 883 | pub const PROCESSINFOCLASS = enum(c_int) { |
| ... | ... | @@ -1160,6 +1389,22 @@ pub const CTL_CODE = packed struct(ULONG) { |
| 1160 | 1389 | }; |
| 1161 | 1390 | |
| 1162 | 1391 | pub const IOCTL = struct { |
| 1392 | pub const CONDRV = struct { | |
| 1393 | pub const READ_IO: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 1, .Method = .OUT_DIRECT, .Access = .ANY }; | |
| 1394 | pub const COMPLETE_IO: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 2, .Method = .NEITHER, .Access = .ANY }; | |
| 1395 | pub const READ_INPUT: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 3, .Method = .NEITHER, .Access = .ANY }; | |
| 1396 | pub const WRITE_OUTPUT: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 4, .Method = .NEITHER, .Access = .ANY }; | |
| 1397 | pub const ISSUE_USER_IO: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 5, .Method = .OUT_DIRECT, .Access = .ANY }; | |
| 1398 | pub const DISCONNECT_PIPE: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 6, .Method = .NEITHER, .Access = .ANY }; | |
| 1399 | pub const SET_SERVER_INFORMATION: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 7, .Method = .NEITHER, .Access = .ANY }; | |
| 1400 | pub const GET_SERVER_PID: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 8, .Method = .NEITHER, .Access = .ANY }; | |
| 1401 | pub const GET_DISPLAY_SIZE: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 9, .Method = .NEITHER, .Access = .ANY }; | |
| 1402 | pub const UPDATE_DISPLAY: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 10, .Method = .NEITHER, .Access = .ANY }; | |
| 1403 | pub const SET_CURSOR: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 11, .Method = .NEITHER, .Access = .ANY }; | |
| 1404 | pub const ALLOW_VIA_UIACCESS: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 12, .Method = .NEITHER, .Access = .ANY }; | |
| 1405 | pub const LAUNCH_SERVER: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 13, .Method = .NEITHER, .Access = .ANY }; | |
| 1406 | pub const GET_FONT_SIZE: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 14, .Method = .NEITHER, .Access = .ANY }; | |
| 1407 | }; | |
| 1163 | 1408 | pub const KSEC = struct { |
| 1164 | 1409 | pub const GEN_RANDOM: CTL_CODE = .{ .DeviceType = .KSEC, .Function = 2, .Method = .BUFFERED, .Access = .ANY }; |
| 1165 | 1410 | }; |
| ... | ... | @@ -2663,29 +2908,6 @@ pub fn NtFreeVirtualMemory(hProcess: HANDLE, addr: ?*PVOID, size: *SIZE_T, free_ |
| 2663 | 2908 | }; |
| 2664 | 2909 | } |
| 2665 | 2910 | |
| 2666 | pub const SetConsoleTextAttributeError = error{Unexpected}; | |
| 2667 | ||
| 2668 | pub fn SetConsoleTextAttribute(hConsoleOutput: HANDLE, wAttributes: WORD) SetConsoleTextAttributeError!void { | |
| 2669 | if (kernel32.SetConsoleTextAttribute(hConsoleOutput, wAttributes) == 0) { | |
| 2670 | switch (GetLastError()) { | |
| 2671 | else => |err| return unexpectedError(err), | |
| 2672 | } | |
| 2673 | } | |
| 2674 | } | |
| 2675 | ||
| 2676 | pub fn SetConsoleCtrlHandler(handler_routine: ?HANDLER_ROUTINE, add: bool) !void { | |
| 2677 | const success = kernel32.SetConsoleCtrlHandler( | |
| 2678 | handler_routine, | |
| 2679 | if (add) TRUE else FALSE, | |
| 2680 | ); | |
| 2681 | ||
| 2682 | if (success == FALSE) { | |
| 2683 | return switch (GetLastError()) { | |
| 2684 | else => |err| unexpectedError(err), | |
| 2685 | }; | |
| 2686 | } | |
| 2687 | } | |
| 2688 | ||
| 2689 | 2911 | pub fn SetFileCompletionNotificationModes(handle: HANDLE, flags: UCHAR) !void { |
| 2690 | 2912 | const success = kernel32.SetFileCompletionNotificationModes(handle, flags); |
| 2691 | 2913 | if (success == FALSE) { |
| ... | ... | @@ -3244,6 +3466,7 @@ pub const ULONGLONG = u64; |
| 3244 | 3466 | pub const LONGLONG = i64; |
| 3245 | 3467 | pub const HLOCAL = HANDLE; |
| 3246 | 3468 | pub const LANGID = c_ushort; |
| 3469 | pub const COLORREF = DWORD; | |
| 3247 | 3470 | |
| 3248 | 3471 | pub const WPARAM = usize; |
| 3249 | 3472 | pub const LPARAM = LONG_PTR; |
| ... | ... | @@ -3784,21 +4007,17 @@ pub const FileNotifyChangeFilter = packed struct(DWORD) { |
| 3784 | 4007 | _pad: u20 = 0, |
| 3785 | 4008 | }; |
| 3786 | 4009 | |
| 3787 | pub const CONSOLE_SCREEN_BUFFER_INFO = extern struct { | |
| 3788 | dwSize: COORD, | |
| 3789 | dwCursorPosition: COORD, | |
| 3790 | wAttributes: WORD, | |
| 3791 | srWindow: SMALL_RECT, | |
| 3792 | dwMaximumWindowSize: COORD, | |
| 3793 | }; | |
| 3794 | ||
| 3795 | 4010 | pub const ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4; |
| 3796 | 4011 | pub const DISABLE_NEWLINE_AUTO_RETURN = 0x8; |
| 3797 | 4012 | |
| 3798 | pub const FOREGROUND_BLUE = 1; | |
| 3799 | pub const FOREGROUND_GREEN = 2; | |
| 3800 | pub const FOREGROUND_RED = 4; | |
| 3801 | pub const FOREGROUND_INTENSITY = 8; | |
| 4013 | pub const FOREGROUND_BLUE = 0x0001; | |
| 4014 | pub const FOREGROUND_GREEN = 0x0002; | |
| 4015 | pub const FOREGROUND_RED = 0x0004; | |
| 4016 | pub const FOREGROUND_INTENSITY = 0x0008; | |
| 4017 | pub const BACKGROUND_BLUE = 0x0010; | |
| 4018 | pub const BACKGROUND_GREEN = 0x0020; | |
| 4019 | pub const BACKGROUND_RED = 0x0040; | |
| 4020 | pub const BACKGROUND_INTENSITY = 0x0080; | |
| 3802 | 4021 | |
| 3803 | 4022 | pub const LIST_ENTRY = extern struct { |
| 3804 | 4023 | Flink: *LIST_ENTRY, |
lib/std/os/windows/kernel32.zig-84| ... | ... | @@ -4,7 +4,6 @@ const windows = std.os.windows; |
| 4 | 4 | const ACCESS_MASK = windows.ACCESS_MASK; |
| 5 | 5 | const BOOL = windows.BOOL; |
| 6 | 6 | const CONDITION_VARIABLE = windows.CONDITION_VARIABLE; |
| 7 | const CONSOLE_SCREEN_BUFFER_INFO = windows.CONSOLE_SCREEN_BUFFER_INFO; | |
| 8 | 7 | const COORD = windows.COORD; |
| 9 | 8 | const DWORD = windows.DWORD; |
| 10 | 9 | const FARPROC = windows.FARPROC; |
| ... | ... | @@ -191,89 +190,6 @@ pub extern "kernel32" fn CreateThread( |
| 191 | 190 | lpThreadId: ?*DWORD, |
| 192 | 191 | ) callconv(.winapi) ?HANDLE; |
| 193 | 192 | |
| 194 | // Locks, critical sections, initializers | |
| 195 | ||
| 196 | // TODO: | |
| 197 | // - dwMilliseconds -> LARGE_INTEGER. | |
| 198 | // - RtlSleepConditionVariableSRW | |
| 199 | // - return rc != .TIMEOUT | |
| 200 | pub extern "kernel32" fn SleepConditionVariableSRW( | |
| 201 | ConditionVariable: *CONDITION_VARIABLE, | |
| 202 | SRWLock: *SRWLOCK, | |
| 203 | dwMilliseconds: DWORD, | |
| 204 | Flags: ULONG, | |
| 205 | ) callconv(.winapi) BOOL; | |
| 206 | ||
| 207 | // Console management | |
| 208 | ||
| 209 | pub extern "kernel32" fn GetConsoleMode( | |
| 210 | hConsoleHandle: HANDLE, | |
| 211 | lpMode: *DWORD, | |
| 212 | ) callconv(.winapi) BOOL; | |
| 213 | ||
| 214 | pub extern "kernel32" fn SetConsoleMode( | |
| 215 | hConsoleHandle: HANDLE, | |
| 216 | dwMode: DWORD, | |
| 217 | ) callconv(.winapi) BOOL; | |
| 218 | ||
| 219 | pub extern "kernel32" fn GetConsoleScreenBufferInfo( | |
| 220 | hConsoleOutput: HANDLE, | |
| 221 | lpConsoleScreenBufferInfo: *CONSOLE_SCREEN_BUFFER_INFO, | |
| 222 | ) callconv(.winapi) BOOL; | |
| 223 | ||
| 224 | pub extern "kernel32" fn SetConsoleTextAttribute( | |
| 225 | hConsoleOutput: HANDLE, | |
| 226 | wAttributes: WORD, | |
| 227 | ) callconv(.winapi) BOOL; | |
| 228 | ||
| 229 | pub extern "kernel32" fn SetConsoleCtrlHandler( | |
| 230 | HandlerRoutine: ?HANDLER_ROUTINE, | |
| 231 | Add: BOOL, | |
| 232 | ) callconv(.winapi) BOOL; | |
| 233 | ||
| 234 | pub extern "kernel32" fn SetConsoleOutputCP( | |
| 235 | wCodePageID: UINT, | |
| 236 | ) callconv(.winapi) BOOL; | |
| 237 | ||
| 238 | pub extern "kernel32" fn GetConsoleOutputCP() callconv(.winapi) UINT; | |
| 239 | ||
| 240 | pub extern "kernel32" fn FillConsoleOutputAttribute( | |
| 241 | hConsoleOutput: HANDLE, | |
| 242 | wAttribute: WORD, | |
| 243 | nLength: DWORD, | |
| 244 | dwWriteCoord: COORD, | |
| 245 | lpNumberOfAttrsWritten: *DWORD, | |
| 246 | ) callconv(.winapi) BOOL; | |
| 247 | ||
| 248 | pub extern "kernel32" fn FillConsoleOutputCharacterW( | |
| 249 | hConsoleOutput: HANDLE, | |
| 250 | cCharacter: WCHAR, | |
| 251 | nLength: DWORD, | |
| 252 | dwWriteCoord: COORD, | |
| 253 | lpNumberOfCharsWritten: *DWORD, | |
| 254 | ) callconv(.winapi) BOOL; | |
| 255 | ||
| 256 | pub extern "kernel32" fn SetConsoleCursorPosition( | |
| 257 | hConsoleOutput: HANDLE, | |
| 258 | dwCursorPosition: COORD, | |
| 259 | ) callconv(.winapi) BOOL; | |
| 260 | ||
| 261 | pub extern "kernel32" fn WriteConsoleW( | |
| 262 | hConsoleOutput: HANDLE, | |
| 263 | lpBuffer: [*]const u16, | |
| 264 | nNumberOfCharsToWrite: DWORD, | |
| 265 | lpNumberOfCharsWritten: ?*DWORD, | |
| 266 | lpReserved: ?LPVOID, | |
| 267 | ) callconv(.winapi) BOOL; | |
| 268 | ||
| 269 | pub extern "kernel32" fn ReadConsoleOutputCharacterW( | |
| 270 | hConsoleOutput: HANDLE, | |
| 271 | lpCharacter: [*]u16, | |
| 272 | nLength: DWORD, | |
| 273 | dwReadCoord: COORD, | |
| 274 | lpNumberOfCharsRead: *DWORD, | |
| 275 | ) callconv(.winapi) BOOL; | |
| 276 | ||
| 277 | 193 | // Code Libraries/Modules |
| 278 | 194 | |
| 279 | 195 | // TODO: Wrapper around LdrGetDllFullName. |
lib/std/std.zig+4-2| ... | ... | @@ -135,8 +135,6 @@ pub const Options = struct { |
| 135 | 135 | args: anytype, |
| 136 | 136 | ) void = log.defaultLog, |
| 137 | 137 | |
| 138 | logTerminalMode: fn () Io.Terminal.Mode = log.defaultTerminalMode, | |
| 139 | ||
| 140 | 138 | /// Overrides `std.heap.page_size_min`. |
| 141 | 139 | page_size_min: ?usize = null, |
| 142 | 140 | /// Overrides `std.heap.page_size_max`. |
| ... | ... | @@ -176,6 +174,10 @@ pub const Options = struct { |
| 176 | 174 | /// stack traces will just print an error to the relevant `Io.Writer` and return. |
| 177 | 175 | allow_stack_tracing: bool = !@import("builtin").strip_debug_info, |
| 178 | 176 | |
| 177 | /// TODO This is a separate decl instead of a field as a workaround around | |
| 178 | /// compilation errors due to zig not being lazy enough. | |
| 179 | pub const logTerminalMode: fn () Io.Terminal.Mode = log.defaultTerminalMode; | |
| 180 | ||
| 179 | 181 | /// TODO This is a separate decl instead of a field as a workaround around |
| 180 | 182 | /// compilation errors due to zig not being lazy enough. |
| 181 | 183 | pub const elf_debug_info_search_paths: ?fn (exe_path: []const u8) switch (@import("builtin").object_format) { |
src/libs/mingw.zig+1-1| ... | ... | @@ -347,7 +347,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { |
| 347 | 347 | if (msg.kind == .@"fatal error" or msg.kind == .@"error") { |
| 348 | 348 | msg.write(stderr.terminal(), true) catch |err| switch (err) { |
| 349 | 349 | error.WriteFailed => return stderr.file_writer.err.?, |
| 350 | error.Unexpected => |e| return e, | |
| 350 | error.Canceled, error.Unexpected => |e| return e, | |
| 351 | 351 | }; |
| 352 | 352 | return error.AroPreprocessorFailed; |
| 353 | 353 | } |