diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 0eb1df2d1a..47ba7c2072 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -335,11 +335,9 @@ pub const Operation = union(enum) { .wasi => noreturn, .windows => struct { file: File, - IoControlCode: std.os.windows.CTL_CODE, - InputBuffer: ?*const anyopaque, - InputBufferLength: u32, - OutputBuffer: ?*anyopaque, - OutputBufferLength: u32, + code: std.os.windows.CTL_CODE, + in: []const u8 = &.{}, + out: []u8 = &.{}, pub const Result = std.os.windows.IO_STATUS_BLOCK; }, diff --git a/lib/std/Io/Terminal.zig b/lib/std/Io/Terminal.zig index beacc4d301..27805f5e4d 100644 --- a/lib/std/Io/Terminal.zig +++ b/lib/std/Io/Terminal.zig @@ -40,7 +40,8 @@ pub const Mode = union(enum) { windows_api: WindowsApi, pub const WindowsApi = if (!is_windows) noreturn else struct { - handle: File.Handle, + io: Io, + file: File, reset_attributes: u16, }; @@ -65,20 +66,21 @@ pub const Mode = union(enum) { } if (is_windows and try file.isTty(io)) { - const windows = std.os.windows; - var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; - if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != 0) { - return .{ .windows_api = .{ - .handle = file.handle, - .reset_attributes = info.wAttributes, - } }; + var get_console_info = std.os.windows.CONSOLE.USER_IO.GET_SCREEN_BUFFER_INFO; + switch (try get_console_info.operate(io, file)) { + .SUCCESS => return .{ .windows_api = .{ + .io = io, + .file = file, + .reset_attributes = get_console_info.Data.wAttributes, + } }, + else => {}, } } return if (force_color == true) .escape_codes else .no_color; } }; -pub const SetColorError = std.os.windows.SetConsoleTextAttributeError || Io.Writer.Error; +pub const SetColorError = Io.Cancelable || Io.UnexpectedError || Io.Writer.Error; pub fn setColor(t: Terminal, color: Color) SetColorError!void { switch (t.mode) { @@ -132,7 +134,11 @@ pub fn setColor(t: Terminal, color: Color) SetColorError!void { .reset => wa.reset_attributes, }; try t.writer.flush(); - try windows.SetConsoleTextAttribute(wa.handle, attributes); + var set_text_attribute = windows.CONSOLE.USER_IO.SET_TEXT_ATTRIBUTE(attributes); + switch (try set_text_attribute.operate(wa.io, wa.file)) { + .SUCCESS => {}, + else => |status| return windows.unexpectedStatus(status), + } }, } } diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index c467615f9c..ab2e9af757 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -3083,19 +3083,23 @@ fn batchAwaitWindows(b: *Io.Batch, concurrency: bool) error{ Canceled, Concurren } }, .device_io_control => |o| { + const NtControlFile = switch (o.code.DeviceType) { + .FILE_SYSTEM, .NAMED_PIPE => &windows.ntdll.NtFsControlFile, + else => &windows.ntdll.NtDeviceIoControlFile, + }; if (o.file.flags.nonblocking) { context.file = o.file.handle; - switch (windows.ntdll.NtDeviceIoControlFile( + switch (NtControlFile( o.file.handle, null, // event &batchApc, b, &context.iosb, - o.IoControlCode, - o.InputBuffer, - o.InputBufferLength, - o.OutputBuffer, - o.OutputBufferLength, + o.code, + if (o.in.len > 0) o.in.ptr else null, + @intCast(o.in.len), + if (o.out.len > 0) o.out.ptr else null, + @intCast(o.out.len), )) { .PENDING, .SUCCESS => {}, .CANCELLED => unreachable, @@ -3108,17 +3112,17 @@ fn batchAwaitWindows(b: *Io.Batch, concurrency: bool) error{ Canceled, Concurren if (concurrency) return error.ConcurrencyUnavailable; const syscall: Syscall = try .start(); - while (true) switch (windows.ntdll.NtDeviceIoControlFile( + while (true) switch (NtControlFile( o.file.handle, null, // event null, // APC routine null, // APC context &context.iosb, - o.IoControlCode, - o.InputBuffer, - o.InputBufferLength, - o.OutputBuffer, - o.OutputBufferLength, + o.code, + if (o.in.len > 0) o.in.ptr else null, + @intCast(o.in.len), + if (o.out.len > 0) o.out.ptr else null, + @intCast(o.out.len), )) { .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag .CANCELLED => { @@ -8547,29 +8551,24 @@ fn fileSyncWasi(userdata: ?*anyopaque, file: File) File.SyncError!void { fn fileIsTty(userdata: ?*anyopaque, file: File) Io.Cancelable!bool { const t: *Threaded = @ptrCast(@alignCast(userdata)); - _ = t; - return isTty(file); + return t.isTty(file); } -fn isTty(file: File) Io.Cancelable!bool { +fn isTty(t: *Threaded, file: File) Io.Cancelable!bool { if (is_windows) { - if (try isCygwinPty(file)) return true; - var out: windows.DWORD = undefined; - const syscall: Syscall = try .start(); - while (windows.kernel32.GetConsoleMode(file.handle, &out) == 0) { - switch (windows.GetLastError()) { - .OPERATION_ABORTED => { - try syscall.checkCancel(); - continue; - }, - else => { - syscall.finish(); - return false; - }, - } + var get_console_mode = windows.CONSOLE.USER_IO.GET_MODE; + switch ((try t.deviceIoControl(&.{ + .file = .{ + .handle = windows.peb().ProcessParameters.ConsoleHandle, + .flags = .{ .nonblocking = false }, + }, + .code = windows.IOCTL.CONDRV.ISSUE_USER_IO, + .in = @ptrCast(&get_console_mode.request(file, 0, .{}, 0, .{})), + })).u.Status) { + .SUCCESS => return true, + .INVALID_HANDLE => return isCygwinPty(file), + else => return false, } - syscall.finish(); - return true; } if (builtin.link_libc) { @@ -8637,35 +8636,26 @@ fn isTty(file: File) Io.Cancelable!bool { fn fileEnableAnsiEscapeCodes(userdata: ?*anyopaque, file: File) File.EnableAnsiEscapeCodesError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); - _ = t; - if (!is_windows) { - if (try supportsAnsiEscapeCodes(file)) return; - return error.NotTerminalDevice; - } + if (!is_windows) return if (!try t.supportsAnsiEscapeCodes(file)) error.NotTerminalDevice; // For Windows Terminal, VT Sequences processing is enabled by default. - var original_console_mode: windows.DWORD = 0; - - { - const syscall: Syscall = try .start(); - while (windows.kernel32.GetConsoleMode(file.handle, &original_console_mode) == 0) { - switch (windows.GetLastError()) { - .OPERATION_ABORTED => { - try syscall.checkCancel(); - continue; - }, - else => { - syscall.finish(); - if (try isCygwinPty(file)) return; - return error.NotTerminalDevice; - }, - } - } - syscall.finish(); + const console: File = .{ + .handle = windows.peb().ProcessParameters.ConsoleHandle, + .flags = .{ .nonblocking = false }, + }; + var get_console_mode = windows.CONSOLE.USER_IO.GET_MODE; + switch ((try t.deviceIoControl(&.{ + .file = console, + .code = windows.IOCTL.CONDRV.ISSUE_USER_IO, + .in = @ptrCast(&get_console_mode.request(file, 0, .{}, 0, .{})), + })).u.Status) { + .SUCCESS => {}, + .INVALID_HANDLE => return if (!try isCygwinPty(file)) error.NotTerminalDevice, + else => return error.NotTerminalDevice, } - if (original_console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return; + if (get_console_mode.Data & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return; // For Windows Console, VT Sequences processing support was added in Windows 10 build 14361, but disabled by default. // 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 // Additionally, the default console mode in Windows Terminal does not have // `DISABLE_NEWLINE_AUTO_RETURN` set, so by only enabling `ENABLE_VIRTUAL_TERMINAL_PROCESSING` // we end up matching the mode of Windows Terminal. - const requested_console_modes = windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING; - const console_mode = original_console_mode | requested_console_modes; - - { - const syscall: Syscall = try .start(); - while (windows.kernel32.SetConsoleMode(file.handle, console_mode) == 0) { - switch (windows.GetLastError()) { - .OPERATION_ABORTED => { - try syscall.checkCancel(); - continue; - }, - else => { - syscall.finish(); - if (try isCygwinPty(file)) return; - return error.NotTerminalDevice; - }, - } - } - syscall.finish(); + var set_console_mode = windows.CONSOLE.USER_IO.SET_MODE( + get_console_mode.Data | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING, + ); + switch ((try t.deviceIoControl(&.{ + .file = console, + .code = windows.IOCTL.CONDRV.ISSUE_USER_IO, + .in = @ptrCast(&set_console_mode.request(file, 0, .{}, 0, .{})), + })).u.Status) { + .SUCCESS => {}, + else => |status| return windows.unexpectedStatus(status), } } fn fileSupportsAnsiEscapeCodes(userdata: ?*anyopaque, file: File) Io.Cancelable!bool { const t: *Threaded = @ptrCast(@alignCast(userdata)); - _ = t; - return supportsAnsiEscapeCodes(file); + return t.supportsAnsiEscapeCodes(file); } -fn supportsAnsiEscapeCodes(file: File) Io.Cancelable!bool { +fn supportsAnsiEscapeCodes(t: *Threaded, file: File) Io.Cancelable!bool { if (is_windows) { - var console_mode: windows.DWORD = 0; - - const syscall: Syscall = try .start(); - while (windows.kernel32.GetConsoleMode(file.handle, &console_mode) == 0) { - switch (windows.GetLastError()) { - .OPERATION_ABORTED => { - try syscall.checkCancel(); - continue; - }, - else => { - syscall.finish(); - break; - }, - } - } else { - syscall.finish(); - if (console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) { - return true; - } + var get_console_mode = windows.CONSOLE.USER_IO.GET_MODE; + switch ((try t.deviceIoControl(&.{ + .file = .{ + .handle = windows.peb().ProcessParameters.ConsoleHandle, + .flags = .{ .nonblocking = false }, + }, + .code = windows.IOCTL.CONDRV.ISSUE_USER_IO, + .in = @ptrCast(&get_console_mode.request(file, 0, .{}, 0, .{})), + })).u.Status) { + .SUCCESS => if (get_console_mode.Data & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) + return true, + .INVALID_HANDLE => return isCygwinPty(file), + else => return false, } - - return isCygwinPty(file); } if (native_os == .wasi) { @@ -8739,7 +8711,7 @@ fn supportsAnsiEscapeCodes(file: File) Io.Cancelable!bool { return false; } - if (try isTty(file)) return true; + if (try t.isTty(file)) return true; return false; } @@ -14111,12 +14083,14 @@ fn initLockedStderr(t: *Threaded, terminal_mode: ?Io.Terminal.Mode) Io.Cancelabl fn unlockStderr(userdata: ?*anyopaque) void { const t: *Threaded = @ptrCast(@alignCast(userdata)); - t.stderr_writer.interface.flush() catch |err| switch (err) { - error.WriteFailed => switch (t.stderr_writer.err.?) { + if (t.stderr_writer.err == null) t.stderr_writer.interface.flush() catch {}; + if (t.stderr_writer.err) |err| { + switch (err) { error.Canceled => recancelInner(), else => {}, - }, - }; + } + t.stderr_writer.err = null; + } t.stderr_writer.interface.end = 0; t.stderr_writer.interface.buffer = &.{}; @@ -18848,20 +18822,24 @@ fn mmSyncWrite(file: File, memory: []u8, offset: u64) File.WritePositionalError! fn deviceIoControl(t: *Threaded, o: *const Io.Operation.DeviceIoControl) Io.Cancelable!Io.Operation.DeviceIoControl.Result { _ = t; if (is_windows) { + const NtControlFile = switch (o.code.DeviceType) { + .FILE_SYSTEM, .NAMED_PIPE => &windows.ntdll.NtFsControlFile, + else => &windows.ntdll.NtDeviceIoControlFile, + }; var iosb: windows.IO_STATUS_BLOCK = undefined; if (o.file.flags.nonblocking) { var done: bool = false; - switch (windows.ntdll.NtDeviceIoControlFile( + switch (NtControlFile( o.file.handle, null, // event flagApc, &done, // APC context &iosb, - o.IoControlCode, - o.InputBuffer, - o.InputBufferLength, - o.OutputBuffer, - o.OutputBufferLength, + o.code, + if (o.in.len > 0) o.in.ptr else null, + @intCast(o.in.len), + if (o.out.len > 0) o.out.ptr else null, + @intCast(o.out.len), )) { // We must wait for the APC routine. .PENDING, .SUCCESS => while (!done) { @@ -18882,17 +18860,17 @@ fn deviceIoControl(t: *Threaded, o: *const Io.Operation.DeviceIoControl) Io.Canc } } else { const syscall: Syscall = try .start(); - while (true) switch (windows.ntdll.NtDeviceIoControlFile( + while (true) switch (NtControlFile( o.file.handle, null, // event null, // APC routine null, // APC context &iosb, - o.IoControlCode, - o.InputBuffer, - o.InputBufferLength, - o.OutputBuffer, - o.OutputBufferLength, + o.code, + if (o.in.len > 0) o.in.ptr else null, + @intCast(o.in.len), + if (o.out.len > 0) o.out.ptr else null, + @intCast(o.out.len), )) { .PENDING => unreachable, // unrecoverable: wrong asynchronous flag .CANCELLED => { diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index f17fed0a5b..2240f95fdd 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -157,7 +157,7 @@ pub const TerminalMode = union(enum) { ansi_escape_codes, /// This is not the same as being run on windows because other terminals /// exist like MSYS/git-bash. - windows_api: if (is_windows) WindowsApi else void, + windows_api: if (is_windows) WindowsApi else noreturn, pub const WindowsApi = struct { /// The output code page of the console. @@ -614,33 +614,39 @@ pub fn start(io: Io, options: Options) Node { if (stderr.enableAnsiEscapeCodes(io)) |_| { global_progress.terminal_mode = .ansi_escape_codes; } else |_| if (is_windows) { - if (stderr.isTty(io)) |is_tty| { - if (is_tty) global_progress.terminal_mode = TerminalMode{ .windows_api = .{ - .code_page = windows.kernel32.GetConsoleOutputCP(), - } }; - } else |err| switch (err) { + var get_console_cp = windows.CONSOLE.USER_IO.GET_CP(.Output); + // Normally, we would pass `null` to `operate` here as the kernel32 + // function does not accept a handle, however, if we pass one anyway, + // then we will get an error if the handle is not associated with + // this process's console, effectively combining an `isTty` check + // into the same syscall. + switch (get_console_cp.operate(io, stderr) catch |err| switch (err) { error.Canceled => { io.recancel(); return .none; }, + }) { + .SUCCESS => global_progress.terminal_mode = .{ .windows_api = .{ + .code_page = get_console_cp.Data.CodePage, + } }, + .INVALID_HANDLE => {}, + else => {}, } } - - if (global_progress.terminal_mode == .off) return .none; - - if (have_sigwinch) { - const act: posix.Sigaction = .{ - .handler = .{ .sigaction = handleSigWinch }, - .mask = posix.sigemptyset(), - .flags = (posix.SA.SIGINFO | posix.SA.RESTART), - }; - posix.sigaction(.WINCH, &act, null); - } - - if (switch (global_progress.terminal_mode) { - .off => unreachable, // handled a few lines above - .ansi_escape_codes => io.concurrent(updateTask, .{io}), - .windows_api => if (is_windows) io.concurrent(windowsApiUpdateTask, .{io}) else unreachable, + if (future: switch (global_progress.terminal_mode) { + .off => return .none, + .ansi_escape_codes => { + if (have_sigwinch) { + const act: posix.Sigaction = .{ + .handler = .{ .sigaction = handleSigWinch }, + .mask = posix.sigemptyset(), + .flags = (posix.SA.SIGINFO | posix.SA.RESTART), + }; + posix.sigaction(.WINCH, &act, null); + } + break :future io.concurrent(updateTask, .{io}); + }, + .windows_api => io.concurrent(windowsApiUpdateTask, .{io}), }) |future| { global_progress.update_worker = future; } else |err| { @@ -715,12 +721,24 @@ fn updateTask(io: Io) WorkerError!void { } } -fn windowsApiWriteMarker() void { +const WindowsApiError = Io.Cancelable || Io.UnexpectedError; + +fn windowsApiWriteMarker(io: Io) WindowsApiError!void { // Write the marker that we will use to find the beginning of the progress when clearing. // Note: This doesn't have to use WriteConsoleW, but doing so avoids dealing with the code page. - var num_chars_written: windows.DWORD = undefined; - const handle = global_progress.terminal.handle; - _ = windows.kernel32.WriteConsoleW(handle, &[_]u16{windows_api_start_marker}, 1, &num_chars_written, null); + const terminal = global_progress.terminal; + var write_console = windows.CONSOLE.USER_IO.WRITE(.WideCharacter); + const buffer = [1]windows.WCHAR{windows_api_start_marker}; + switch ((try io.operate(.{ .device_io_control = .{ + .file = terminal, + .code = windows.IOCTL.CONDRV.ISSUE_USER_IO, + .in = @ptrCast(&write_console.request(null, 1, .{ + .{ .Size = @sizeOf(@TypeOf(buffer)), .Pointer = &buffer }, + }, 0, .{})), + } })).device_io_control.u.Status) { + .SUCCESS => {}, + else => |status| return windows.unexpectedStatus(status), + } } fn windowsApiUpdateTask(io: Io) WorkerError!void { @@ -743,19 +761,19 @@ fn windowsApiUpdateTask(io: Io) WorkerError!void { error.Canceled => unreachable, // blocked }; defer io.unlockStderr(); - clearWrittenWindowsApi() catch {}; + clearWrittenWindowsApi(io) catch {}; } while (true) { const buffer, const nl_n = try computeRedraw(io, &serialized_buffer); if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| { defer io.unlockStderr(); - try clearWrittenWindowsApi(); - windowsApiWriteMarker(); + try clearWrittenWindowsApi(io); + try windowsApiWriteMarker(io); global_progress.need_clear = true; locked_stderr.file_writer.interface.writeAll(buffer) catch |err| switch (err) { error.WriteFailed => return locked_stderr.file_writer.err.?, }; - windowsApiMoveToMarker(nl_n) catch return; + windowsApiMoveToMarker(io, nl_n) catch return; } 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 { return start_i + bytes.len; }, .windows_api => |windows_api| { - const bytes = if (!is_windows) unreachable else switch (windows_api.code_page) { + const bytes = switch (windows_api.code_page) { // Code page 437 is the default code page and contains the box drawing symbols 437 => symbol.bytes(.code_page_437), // UTF-8 @@ -882,7 +900,7 @@ pub fn clearWrittenWithEscapeCodes(file_writer: *Io.File.Writer) Io.Writer.Error /// U+25BA or ► const windows_api_start_marker = 0x25BA; -fn clearWrittenWindowsApi() error{Unexpected}!void { +fn clearWrittenWindowsApi(io: Io) WindowsApiError!void { // This uses a 'marker' strategy. The idea is: // - Always write a marker (in this case U+25BA or ►) at the beginning of the progress // - Get the current cursor position (at the end of the progress) @@ -903,43 +921,60 @@ fn clearWrittenWindowsApi() error{Unexpected}!void { // character in order to be readable via ReadConsoleOutputAttribute. It doesn't seem // like any of the available attributes are invisible/benign. if (!global_progress.need_clear) return; - const handle = global_progress.terminal.handle; + const terminal = global_progress.terminal; const screen_area = @as(windows.DWORD, global_progress.cols) * global_progress.rows; - var console_info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; - if (windows.kernel32.GetConsoleScreenBufferInfo(handle, &console_info) == 0) { - return error.Unexpected; + var get_console_info = windows.CONSOLE.USER_IO.GET_SCREEN_BUFFER_INFO; + switch (try get_console_info.operate(io, terminal)) { + .SUCCESS => {}, + else => |status| return windows.unexpectedStatus(status), } - var num_chars_written: windows.DWORD = undefined; - if (windows.kernel32.FillConsoleOutputCharacterW(handle, ' ', screen_area, console_info.dwCursorPosition, &num_chars_written) == 0) { - return error.Unexpected; + var fill_spaces = windows.CONSOLE.USER_IO.FILL( + .{ .WideCharacter = ' ' }, + screen_area, + get_console_info.Data.dwCursorPosition, + ); + switch (try fill_spaces.operate(io, terminal)) { + .SUCCESS => {}, + else => |status| return windows.unexpectedStatus(status), } } -fn windowsApiMoveToMarker(nl_n: usize) error{Unexpected}!void { - const handle = global_progress.terminal.handle; - var console_info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; - if (windows.kernel32.GetConsoleScreenBufferInfo(handle, &console_info) == 0) { - return error.Unexpected; +fn windowsApiMoveToMarker(io: Io, nl_n: usize) WindowsApiError!void { + const terminal = global_progress.terminal; + var get_console_info = windows.CONSOLE.USER_IO.GET_SCREEN_BUFFER_INFO; + switch (try get_console_info.operate(io, terminal)) { + .SUCCESS => {}, + else => |status| return windows.unexpectedStatus(status), } - const cursor_pos = console_info.dwCursorPosition; + const cursor_pos = get_console_info.Data.dwCursorPosition; const expected_y = cursor_pos.Y - @as(i16, @intCast(nl_n)); var start_pos: windows.COORD = .{ .X = 0, .Y = expected_y }; - while (start_pos.Y >= 0) { - var wchar: [1]u16 = undefined; - var num_console_chars_read: windows.DWORD = undefined; - if (windows.kernel32.ReadConsoleOutputCharacterW(handle, &wchar, wchar.len, start_pos, &num_console_chars_read) == 0) { - return error.Unexpected; + while (start_pos.Y >= 0) : (start_pos.Y -= 1) { + var read_output_char = windows.CONSOLE.USER_IO.READ_OUTPUT_CHARACTER(start_pos, .WideCharacter); + var buffer: [1]windows.WCHAR = undefined; + switch ((try io.operate(.{ .device_io_control = .{ + .file = .{ + .handle = windows.peb().ProcessParameters.ConsoleHandle, + .flags = .{ .nonblocking = false }, + }, + .code = windows.IOCTL.CONDRV.ISSUE_USER_IO, + .in = @ptrCast(&read_output_char.request(terminal, 0, .{}, 1, .{ + .{ .Size = @sizeOf(@TypeOf(buffer)), .Pointer = &buffer }, + })), + } })).device_io_control.u.Status) { + .SUCCESS => {}, + else => |status| return windows.unexpectedStatus(status), } - - if (wchar[0] == windows_api_start_marker) break; - start_pos.Y -= 1; + if (read_output_char.Data.nLength >= 1 and buffer[0] == windows_api_start_marker) break; } else { // If we couldn't find the marker, then just assume that no lines wrapped start_pos = .{ .X = 0, .Y = expected_y }; } - if (windows.kernel32.SetConsoleCursorPosition(handle, start_pos) == 0) { - return error.Unexpected; + var set_cursor_position = windows.CONSOLE.USER_IO.SET_CURSOR_POSITION(start_pos); + switch (try set_cursor_position.operate(io, terminal)) { + .SUCCESS => {}, + else => |status| return windows.unexpectedStatus(status), } } @@ -1279,7 +1314,7 @@ fn computeRedraw(io: Io, serialized_buffer: *Serialized.Buffer) !struct { []u8, buf[i..][0..clear.len].* = clear.*; i += clear.len; }, - .windows_api => if (!is_windows) unreachable, + .windows_api => {}, } const root_node_index: Node.Index = @enumFromInt(0); @@ -1491,19 +1526,17 @@ fn maybeUpdateSize(io: Io, resize_flag: bool) !void { const file = global_progress.terminal; if (is_windows) { - var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; - - if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.FALSE) { - // In the old Windows console, dwSize.Y is the line count of the - // entire scrollback buffer, so we use this instead so that we - // always get the size of the screen. - const screen_height = info.srWindow.Bottom - info.srWindow.Top; - global_progress.rows = @intCast(screen_height); - global_progress.cols = @intCast(info.dwSize.X); - } else { - std.log.debug("failed to determine terminal size; using conservative guess 80x25", .{}); - global_progress.rows = 25; - global_progress.cols = 80; + var get_console_info = windows.CONSOLE.USER_IO.GET_SCREEN_BUFFER_INFO; + switch (try get_console_info.operate(io, file)) { + .SUCCESS => { + global_progress.rows = @intCast(get_console_info.Data.dwWindowSize.Y); + global_progress.cols = @intCast(get_console_info.Data.dwWindowSize.X); + }, + else => { + std.log.debug("failed to determine terminal size; using conservative guess 80x25", .{}); + global_progress.rows = 25; + global_progress.cols = 80; + }, } } else { var winsize: posix.winsize = .{ diff --git a/lib/std/log.zig b/lib/std/log.zig index df11fe205b..f66cb4e04d 100644 --- a/lib/std/log.zig +++ b/lib/std/log.zig @@ -80,7 +80,7 @@ pub fn logEnabled(comptime level: Level, comptime scope: @EnumLiteral()) bool { return @intFromEnum(level) <= @intFromEnum(std.options.log_level); } -pub const terminalMode = std.options.logTerminalMode; +pub const terminalMode = std.Options.logTerminalMode; pub fn defaultTerminalMode() std.Io.Terminal.Mode { const stderr = std.debug.lockStderr(&.{}).terminal(); @@ -99,6 +99,9 @@ pub fn defaultLog( comptime format: []const u8, args: anytype, ) void { + const io = std.Options.debug_io; + const prev = io.swapCancelProtection(.blocked); + defer _ = io.swapCancelProtection(prev); var buffer: [64]u8 = undefined; const stderr = std.debug.lockStderr(&buffer).terminal(); defer std.debug.unlockStderr(); diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 0180138a0f..86d4ce0efd 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -649,6 +649,235 @@ pub const FILE = struct { }; }; +pub const CONSOLE = struct { + pub const USER_IO = struct { + pub const INFO = struct { + pub const CP = extern struct { + /// GetCP: output + /// SetCP: input + CodePage: UINT, + /// input + Mode: MODE, + + pub const MODE = enum(BOOLEAN) { + Input = FALSE, + Output = TRUE, + }; + }; + + pub const WRITE = extern struct { + /// output, in bytes + Size: DWORD, + /// input + Mode: MODE, + + pub const MODE = enum(BOOLEAN) { + Character = FALSE, + WideCharacter = TRUE, + }; + }; + + pub const FILL = extern struct { + /// input + dwWriteCoord: COORD, + /// input + Tag: WITH.Tag, + /// input + With: WITH.Payload, + /// input/output, in characters + nLength: DWORD, + + pub const WITH = union(enum(DWORD)) { + Character: CHAR = 1, + WideCharacter: WCHAR = 2, + Attribute: WORD = 3, + + pub const Tag = @typeInfo(WITH).@"union".tag_type.?; + pub const Payload = PAYLOAD: { + const with_fields = @typeInfo(WITH).@"union".fields; + var field_names: [with_fields.len][]const u8 = undefined; + var field_types: [with_fields.len]type = undefined; + for (with_fields, &field_names, &field_types) |field, *field_name, *field_type| { + field_name.* = field.name; + field_type.* = field.type; + } + break :PAYLOAD @Union(.@"extern", null, &field_names, &field_types, &@splat(.{})); + }; + }; + }; + + /// all output + pub const SCREEN_BUFFER = extern struct { + dwSize: COORD, + dwCursorPosition: COORD, + dwWindowPosition: COORD, + wAttributes: WORD, + dwWindowSize: COORD, + dwMaximumWindowSize: COORD, + wPopupAttributes: WORD, + bFullscreenSupported: BOOL, + ColorTable: [16]COLORREF, + }; + + pub const READ_OUTPUT_CHARACTER = extern struct { + /// input + dwReadCoord: COORD, + Mode: MODE, + /// output, in characters + nLength: DWORD, + + pub const MODE = enum(DWORD) { + Character = 1, + WideCharacter = 2, + }; + }; + }; + + pub fn GET_CP(mode: INFO.CP.MODE) Header.With(INFO.CP) { + return .init(.GetCP, .{ .CodePage = undefined, .Mode = mode }); + } + pub const GET_MODE: Header.With(DWORD) = .init(.GetMode, undefined); + pub fn SET_MODE(mode: DWORD) Header.With(DWORD) { + return .init(.SetMode, mode); + } + pub fn WRITE(mode: INFO.WRITE.MODE) Header.With(INFO.WRITE) { + return .init(.Write, .{ .Size = undefined, .Mode = mode }); + } + pub fn FILL(with: INFO.FILL.WITH, len: DWORD, coord: COORD) Header.With(INFO.FILL) { + return .init(.Fill, .{ + .dwWriteCoord = coord, + .Tag = with, + .With = switch (with) { + inline else => |payload, tag| @unionInit( + INFO.FILL.WITH.Payload, + @tagName(tag), + payload, + ), + }, + .nLength = len, + }); + } + pub fn SET_CP(mode: INFO.CP.MODE, cp: UINT) Header.With(INFO.CP) { + return .init(.SetCP, .{ .CodePage = cp, .Mode = mode }); + } + pub const GET_SCREEN_BUFFER_INFO: Header.With(INFO.SCREEN_BUFFER) = + .init(.GetScreenBufferInfo, undefined); + pub fn SET_CURSOR_POSITION(coord: COORD) Header.With(COORD) { + return .init(.SetCursorPosition, coord); + } + pub fn SET_TEXT_ATTRIBUTE(attribute: WORD) Header.With(WORD) { + return .init(.SetTextAttribute, attribute); + } + pub fn READ_OUTPUT_CHARACTER( + coord: COORD, + mode: INFO.READ_OUTPUT_CHARACTER.MODE, + ) Header.With(INFO.READ_OUTPUT_CHARACTER) { + return .init(.ReadOutputCharacter, .{ + .dwReadCoord = coord, + .Mode = mode, + .nLength = undefined, + }); + } + + pub const InputBuffer = extern struct { + Size: u32, + Pointer: *const anyopaque, + }; + + pub const OutputBuffer = extern struct { + Size: u32, + Pointer: *anyopaque, + }; + + pub fn Request(comptime in_len: u32, comptime out_len: u32) type { + return extern struct { + Handle: ?HANDLE, + InputBuffersLength: u32, + OutputBuffersLength: u32, + InputBuffers: [in_len]InputBuffer, + OutputBuffers: [out_len]OutputBuffer, + + pub fn init( + handle: ?HANDLE, + in: [in_len]InputBuffer, + out: [out_len]OutputBuffer, + ) @This() { + return .{ + .Handle = handle, + .InputBuffersLength = in_len, + .OutputBuffersLength = out_len, + .InputBuffers = in, + .OutputBuffers = out, + }; + } + }; + } + + pub const Header = extern struct { + Operation: Operation, + Size: u32, + + pub fn With(comptime Data: type) type { + return extern struct { + Header: Header, + Data: Data, + + pub fn init(operation: Operation, data: Data) @This() { + return .{ + .Header = .{ .Operation = operation, .Size = @sizeOf(Data) }, + .Data = data, + }; + } + + pub fn request( + with: *@This(), + file: ?Io.File, + comptime in_len: u32, + in: [in_len]InputBuffer, + comptime out_len: u32, + out: [out_len]OutputBuffer, + ) Request(1 + in_len, 1 + out_len) { + return .init( + if (file) |f| f.handle else null, + [1]InputBuffer{.{ + .Size = @offsetOf(@This(), "Data") + @sizeOf(Data), + .Pointer = with, + }} ++ in, + [1]OutputBuffer{.{ .Size = @sizeOf(Data), .Pointer = &with.Data }} ++ out, + ); + } + + pub fn operate(with: *@This(), io: Io, file: ?Io.File) Io.Cancelable!NTSTATUS { + return (try io.operate(.{ .device_io_control = .{ + .file = .{ + .handle = peb().ProcessParameters.ConsoleHandle, + .flags = .{ .nonblocking = false }, + }, + .code = IOCTL.CONDRV.ISSUE_USER_IO, + .in = @ptrCast(&with.request(file, 0, .{}, 0, .{})), + } })).device_io_control.u.Status; + } + }; + } + }; + + pub const Operation = enum(u32) { + GetCP = 0x1000000, + GetMode = 0x1000001, + SetMode = 0x1000002, + Read = 0x1000005, + Write = 0x1000006, + Fill = 0x2000000, + SetCP = 0x2000004, + GetScreenBufferInfo = 0x2000007, + SetCursorPosition = 0x200000a, + SetTextAttribute = 0x200000d, + ReadOutputCharacter = 0x200000f, + _, + }; + }; +}; + // ref: km/ntddk.h pub const PROCESSINFOCLASS = enum(c_int) { @@ -1160,6 +1389,22 @@ pub const CTL_CODE = packed struct(ULONG) { }; pub const IOCTL = struct { + pub const CONDRV = struct { + pub const READ_IO: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 1, .Method = .OUT_DIRECT, .Access = .ANY }; + pub const COMPLETE_IO: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 2, .Method = .NEITHER, .Access = .ANY }; + pub const READ_INPUT: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 3, .Method = .NEITHER, .Access = .ANY }; + pub const WRITE_OUTPUT: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 4, .Method = .NEITHER, .Access = .ANY }; + pub const ISSUE_USER_IO: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 5, .Method = .OUT_DIRECT, .Access = .ANY }; + pub const DISCONNECT_PIPE: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 6, .Method = .NEITHER, .Access = .ANY }; + pub const SET_SERVER_INFORMATION: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 7, .Method = .NEITHER, .Access = .ANY }; + pub const GET_SERVER_PID: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 8, .Method = .NEITHER, .Access = .ANY }; + pub const GET_DISPLAY_SIZE: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 9, .Method = .NEITHER, .Access = .ANY }; + pub const UPDATE_DISPLAY: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 10, .Method = .NEITHER, .Access = .ANY }; + pub const SET_CURSOR: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 11, .Method = .NEITHER, .Access = .ANY }; + pub const ALLOW_VIA_UIACCESS: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 12, .Method = .NEITHER, .Access = .ANY }; + pub const LAUNCH_SERVER: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 13, .Method = .NEITHER, .Access = .ANY }; + pub const GET_FONT_SIZE: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 14, .Method = .NEITHER, .Access = .ANY }; + }; pub const KSEC = struct { pub const GEN_RANDOM: CTL_CODE = .{ .DeviceType = .KSEC, .Function = 2, .Method = .BUFFERED, .Access = .ANY }; }; @@ -2663,29 +2908,6 @@ pub fn NtFreeVirtualMemory(hProcess: HANDLE, addr: ?*PVOID, size: *SIZE_T, free_ }; } -pub const SetConsoleTextAttributeError = error{Unexpected}; - -pub fn SetConsoleTextAttribute(hConsoleOutput: HANDLE, wAttributes: WORD) SetConsoleTextAttributeError!void { - if (kernel32.SetConsoleTextAttribute(hConsoleOutput, wAttributes) == 0) { - switch (GetLastError()) { - else => |err| return unexpectedError(err), - } - } -} - -pub fn SetConsoleCtrlHandler(handler_routine: ?HANDLER_ROUTINE, add: bool) !void { - const success = kernel32.SetConsoleCtrlHandler( - handler_routine, - if (add) TRUE else FALSE, - ); - - if (success == FALSE) { - return switch (GetLastError()) { - else => |err| unexpectedError(err), - }; - } -} - pub fn SetFileCompletionNotificationModes(handle: HANDLE, flags: UCHAR) !void { const success = kernel32.SetFileCompletionNotificationModes(handle, flags); if (success == FALSE) { @@ -3244,6 +3466,7 @@ pub const ULONGLONG = u64; pub const LONGLONG = i64; pub const HLOCAL = HANDLE; pub const LANGID = c_ushort; +pub const COLORREF = DWORD; pub const WPARAM = usize; pub const LPARAM = LONG_PTR; @@ -3784,21 +4007,17 @@ pub const FileNotifyChangeFilter = packed struct(DWORD) { _pad: u20 = 0, }; -pub const CONSOLE_SCREEN_BUFFER_INFO = extern struct { - dwSize: COORD, - dwCursorPosition: COORD, - wAttributes: WORD, - srWindow: SMALL_RECT, - dwMaximumWindowSize: COORD, -}; - pub const ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4; pub const DISABLE_NEWLINE_AUTO_RETURN = 0x8; -pub const FOREGROUND_BLUE = 1; -pub const FOREGROUND_GREEN = 2; -pub const FOREGROUND_RED = 4; -pub const FOREGROUND_INTENSITY = 8; +pub const FOREGROUND_BLUE = 0x0001; +pub const FOREGROUND_GREEN = 0x0002; +pub const FOREGROUND_RED = 0x0004; +pub const FOREGROUND_INTENSITY = 0x0008; +pub const BACKGROUND_BLUE = 0x0010; +pub const BACKGROUND_GREEN = 0x0020; +pub const BACKGROUND_RED = 0x0040; +pub const BACKGROUND_INTENSITY = 0x0080; pub const LIST_ENTRY = extern struct { Flink: *LIST_ENTRY, diff --git a/lib/std/os/windows/kernel32.zig b/lib/std/os/windows/kernel32.zig index d6af93cfc3..ed9af392a3 100644 --- a/lib/std/os/windows/kernel32.zig +++ b/lib/std/os/windows/kernel32.zig @@ -4,7 +4,6 @@ const windows = std.os.windows; const ACCESS_MASK = windows.ACCESS_MASK; const BOOL = windows.BOOL; const CONDITION_VARIABLE = windows.CONDITION_VARIABLE; -const CONSOLE_SCREEN_BUFFER_INFO = windows.CONSOLE_SCREEN_BUFFER_INFO; const COORD = windows.COORD; const DWORD = windows.DWORD; const FARPROC = windows.FARPROC; @@ -191,89 +190,6 @@ pub extern "kernel32" fn CreateThread( lpThreadId: ?*DWORD, ) callconv(.winapi) ?HANDLE; -// Locks, critical sections, initializers - -// TODO: -// - dwMilliseconds -> LARGE_INTEGER. -// - RtlSleepConditionVariableSRW -// - return rc != .TIMEOUT -pub extern "kernel32" fn SleepConditionVariableSRW( - ConditionVariable: *CONDITION_VARIABLE, - SRWLock: *SRWLOCK, - dwMilliseconds: DWORD, - Flags: ULONG, -) callconv(.winapi) BOOL; - -// Console management - -pub extern "kernel32" fn GetConsoleMode( - hConsoleHandle: HANDLE, - lpMode: *DWORD, -) callconv(.winapi) BOOL; - -pub extern "kernel32" fn SetConsoleMode( - hConsoleHandle: HANDLE, - dwMode: DWORD, -) callconv(.winapi) BOOL; - -pub extern "kernel32" fn GetConsoleScreenBufferInfo( - hConsoleOutput: HANDLE, - lpConsoleScreenBufferInfo: *CONSOLE_SCREEN_BUFFER_INFO, -) callconv(.winapi) BOOL; - -pub extern "kernel32" fn SetConsoleTextAttribute( - hConsoleOutput: HANDLE, - wAttributes: WORD, -) callconv(.winapi) BOOL; - -pub extern "kernel32" fn SetConsoleCtrlHandler( - HandlerRoutine: ?HANDLER_ROUTINE, - Add: BOOL, -) callconv(.winapi) BOOL; - -pub extern "kernel32" fn SetConsoleOutputCP( - wCodePageID: UINT, -) callconv(.winapi) BOOL; - -pub extern "kernel32" fn GetConsoleOutputCP() callconv(.winapi) UINT; - -pub extern "kernel32" fn FillConsoleOutputAttribute( - hConsoleOutput: HANDLE, - wAttribute: WORD, - nLength: DWORD, - dwWriteCoord: COORD, - lpNumberOfAttrsWritten: *DWORD, -) callconv(.winapi) BOOL; - -pub extern "kernel32" fn FillConsoleOutputCharacterW( - hConsoleOutput: HANDLE, - cCharacter: WCHAR, - nLength: DWORD, - dwWriteCoord: COORD, - lpNumberOfCharsWritten: *DWORD, -) callconv(.winapi) BOOL; - -pub extern "kernel32" fn SetConsoleCursorPosition( - hConsoleOutput: HANDLE, - dwCursorPosition: COORD, -) callconv(.winapi) BOOL; - -pub extern "kernel32" fn WriteConsoleW( - hConsoleOutput: HANDLE, - lpBuffer: [*]const u16, - nNumberOfCharsToWrite: DWORD, - lpNumberOfCharsWritten: ?*DWORD, - lpReserved: ?LPVOID, -) callconv(.winapi) BOOL; - -pub extern "kernel32" fn ReadConsoleOutputCharacterW( - hConsoleOutput: HANDLE, - lpCharacter: [*]u16, - nLength: DWORD, - dwReadCoord: COORD, - lpNumberOfCharsRead: *DWORD, -) callconv(.winapi) BOOL; - // Code Libraries/Modules // TODO: Wrapper around LdrGetDllFullName. diff --git a/lib/std/std.zig b/lib/std/std.zig index 3998b3247a..d563cdfae7 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -135,8 +135,6 @@ pub const Options = struct { args: anytype, ) void = log.defaultLog, - logTerminalMode: fn () Io.Terminal.Mode = log.defaultTerminalMode, - /// Overrides `std.heap.page_size_min`. page_size_min: ?usize = null, /// Overrides `std.heap.page_size_max`. @@ -176,6 +174,10 @@ pub const Options = struct { /// stack traces will just print an error to the relevant `Io.Writer` and return. allow_stack_tracing: bool = !@import("builtin").strip_debug_info, + /// TODO This is a separate decl instead of a field as a workaround around + /// compilation errors due to zig not being lazy enough. + pub const logTerminalMode: fn () Io.Terminal.Mode = log.defaultTerminalMode; + /// TODO This is a separate decl instead of a field as a workaround around /// compilation errors due to zig not being lazy enough. pub const elf_debug_info_search_paths: ?fn (exe_path: []const u8) switch (@import("builtin").object_format) { diff --git a/src/libs/mingw.zig b/src/libs/mingw.zig index 568dd94551..a863f56913 100644 --- a/src/libs/mingw.zig +++ b/src/libs/mingw.zig @@ -347,7 +347,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { if (msg.kind == .@"fatal error" or msg.kind == .@"error") { msg.write(stderr.terminal(), true) catch |err| switch (err) { error.WriteFailed => return stderr.file_writer.err.?, - error.Unexpected => |e| return e, + error.Canceled, error.Unexpected => |e| return e, }; return error.AroPreprocessorFailed; }