From c94422414b4e04d3edd7522ff33cb58c360dd67c Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Sat, 22 Jun 2024 02:52:20 -0700 Subject: [PATCH 1/4] Add GitHub check and test workflows --- .github/workflows/check.yml | 88 +++++++++++++++++++++++++++++++++++ .github/workflows/test.yml | 93 +++++++++++++++++++++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 .github/workflows/check.yml create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 0000000..cd79978 --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,88 @@ +name: Check + +on: + push: + branches: + - master + pull_request: + +env: + CARGO_TERM_COLOR: always + MSRV: "1.56.0" + +# ensure that the workflow is only triggered once per PR, subsequent pushes to the PR will cancel +# and restart the workflow. See https://docs.github.com/en/actions/using-jobs/using-concurrency +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + fmt: + name: fmt (stable) + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + targets: x86_64-pc-windows-msvc,x86_64-pc-windows-gnu + - name: Run cargo fmt + run: cargo fmt -- --check + - name: Cache Cargo dependencies + uses: Swatinem/rust-cache@v2 + clippy: + name: clippy (${{ matrix.toolchain }}) + runs-on: windows-latest + permissions: + checks: write + strategy: + fail-fast: false + matrix: + # Get early warnings about new lints introduced in the beta channel + toolchain: [stable, beta] + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Rust stable + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.toolchain }} + components: clippy + targets: x86_64-pc-windows-msvc,x86_64-pc-windows-gnu + - name: Run clippy action + uses: clechasseur/rs-clippy-check@v3 + - name: Cache Cargo dependencies + uses: Swatinem/rust-cache@v2 + doc: + # run docs generation on nightly rather than stable. This enables features like + # https://doc.rust-lang.org/beta/unstable-book/language-features/doc-cfg.html which allows an + # API be documented as only available in some specific platforms. + name: doc (nightly) + runs-on: windows-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Rust nightly + uses: dtolnay/rust-toolchain@nightly + with: + targets: x86_64-pc-windows-msvc,x86_64-pc-windows-gnu + - name: Run cargo doc + run: cargo doc --no-deps --all-features + env: + RUSTDOCFLAGS: --cfg docsrs + msrv: + # check that we can build using the minimal rust version that is specified by this crate + name: check (1.56.0) + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Rust ${{ env.MSRV }} + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.MSRV }} + targets: x86_64-pc-windows-msvc,x86_64-pc-windows-gnu + - name: cargo +${{ env.MSRV }} check + run: cargo check diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..f8b9a7f --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,93 @@ +name: Test +# This is the main CI workflow that runs the test suite on all pushes to main and all pull requests. +# It runs the following jobs: +# - test: runs the test suite on ubuntu with stable and beta rust toolchains +# - minimal: runs the test suite with the minimal versions of the dependencies that satisfy the +# requirements of this crate, and its dependencies +# See check.yml for information about how the concurrency cancellation and workflow triggering works +# and for the fmt, clippy, doc, and msrv jobs. +on: + push: + branches: + - master + pull_request: + +# ensure that the workflow is only triggered once per PR, subsequent pushes to the PR will cancel +# and restart the workflow. See https://docs.github.com/en/actions/using-jobs/using-concurrency +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + test: + runs-on: windows-latest + name: Test (${{ matrix.toolchain }}) + strategy: + matrix: + # run on stable and beta to ensure that tests won't break on the next version of the rust + # toolchain + toolchain: [stable, beta, 1.56.0] + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Rust ${{ matrix.toolchain }} + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.toolchain }} + targets: x86_64-pc-windows-msvc,x86_64-pc-windows-gnu + # Generate a lockfile to use if one is not checked in. This makes the next step able to + # run regardless of whether a lockfile is checked in or not. + - name: cargo generate-lockfile + if: hashFiles('Cargo.lock') == '' + run: cargo generate-lockfile + - name: cargo test --locked + run: cargo test --locked --all-features --all-targets + - name: cargo test --doc + run: cargo test --locked --all-features --doc + - name: Cache Cargo dependencies + uses: Swatinem/rust-cache@v2 + + # This action chooses the oldest version of the dependencies permitted by Cargo.toml to ensure + # that this crate is compatible with the minimal version that this crate and its dependencies + # require. This will pickup issues where this create relies on functionality that was introduced + # later than the actual version specified (e.g., when we choose just a major version, but a + # method was added after this version). + # + # This particular check can be difficult to get to succeed as often transitive dependencies may + # be incorrectly specified (e.g., a dependency specifies 1.0 but really requires 1.1.5). There + # is an alternative flag available -Zdirect-minimal-versions that uses the minimal versions for + # direct dependencies of this crate, while selecting the maximal versions for the transitive + # dependencies. Alternatively, you can add a line in your Cargo.toml to artificially increase + # the minimal dependency, which you do with e.g.: + # ```toml + # # for minimal-versions + # [target.'cfg(any())'.dependencies] + # openssl = { version = "0.10.55", optional = true } # needed to allow foo to build with -Zminimal-versions + # ``` + # The optional = true is necessary in case that dependency isn't otherwise transitively required + # by your library, and the target bit is so that this dependency edge never actually affects + # Cargo build order. See also + # https://github.com/jonhoo/fantoccini/blob/fde336472b712bc7ebf5b4e772023a7ba71b2262/Cargo.toml#L47-L49. + # This action is run on ubuntu with the stable toolchain, as it is not expected to fail + minimal-versions: + runs-on: windows-latest + name: Check minimal-versions + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + with: + targets: x86_64-pc-windows-msvc,x86_64-pc-windows-gnu + - name: Install Rust nightly (for -Zdirect-minimal-versions) + uses: dtolnay/rust-toolchain@nightly + with: + targets: x86_64-pc-windows-msvc,x86_64-pc-windows-gnu + - name: rustup default stable + run: rustup default stable + - name: cargo update -Zdirect-minimal-versions + run: cargo +nightly update -Zdirect-minimal-versions + - name: cargo test + run: cargo test --locked --all-features --all-targets + - name: Cache Cargo dependencies + uses: Swatinem/rust-cache@v2 From d45a3bc0a151ed6444ee29627f99dc17fbae7858 Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Sat, 22 Jun 2024 06:10:52 -0700 Subject: [PATCH 2/4] bump msrv to 1.62.0 package `windows-result v0.1.2` cannot be built because it requires rustc 1.60 or newer --- .github/workflows/check.yml | 4 ++-- .github/workflows/test.yml | 6 +++--- Cargo.toml | 12 ++++++++++-- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index cd79978..57fdfcc 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -8,7 +8,7 @@ on: env: CARGO_TERM_COLOR: always - MSRV: "1.56.0" + MSRV: "1.62.0" # ensure that the workflow is only triggered once per PR, subsequent pushes to the PR will cancel # and restart the workflow. See https://docs.github.com/en/actions/using-jobs/using-concurrency @@ -74,7 +74,7 @@ jobs: RUSTDOCFLAGS: --cfg docsrs msrv: # check that we can build using the minimal rust version that is specified by this crate - name: check (1.56.0) + name: check msrv runs-on: ubuntu-latest steps: - name: Checkout diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f8b9a7f..9b190e2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,9 +24,9 @@ jobs: name: Test (${{ matrix.toolchain }}) strategy: matrix: - # run on stable and beta to ensure that tests won't break on the next version of the rust - # toolchain - toolchain: [stable, beta, 1.56.0] + # run on beta to ensure that tests won't break on the next version of the rust toolchain + # run on msrv to ensure that tests won't break on the minimum supported version of the rust + toolchain: [1.62.0, stable, beta] steps: - name: Checkout uses: actions/checkout@v4 diff --git a/Cargo.toml b/Cargo.toml index 775f85b..f8cf941 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,10 +9,18 @@ license = "MIT" keywords = ["winapi", "abstractions", "crossterm", "windows", "screen_buffer"] exclude = ["target", "Cargo.lock"] readme = "README.md" -edition = "2018" +edition = "2021" +rust-version = "1.62.0" [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3.8", features = ["winbase", "consoleapi", "processenv", "handleapi", "synchapi", "impl-default"] } +winapi = { version = "0.3.8", features = [ + "winbase", + "consoleapi", + "processenv", + "handleapi", + "synchapi", + "impl-default", +] } [package.metadata.docs.rs] default-target = "x86_64-pc-windows-msvc" From f0200fc3bba6dabeb6cec294341c9cf45cc01874 Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Sat, 22 Jun 2024 06:10:52 -0700 Subject: [PATCH 3/4] Use windows crate instead of winapi This replaces https://github.com/crossterm-rs/crossterm-winapi/pull/26 Using the windows crate instead of the windows-sys crate simplifies the code and avoids having to maintain code that handles errors, and various other simplifications provided by the higher level crate. From the windows crate documentation: > The windows-sys crate is a zero-overhead fallback for the most > demanding situations and primarily where the absolute best compile > time is essential. It only includes function declarations (externs), > structs, and constants. No convenience helpers, traits, or wrappers > are provided. --- .github/workflows/test.yml | 4 +- Cargo.toml | 16 ++++---- examples/coloring_example.rs | 4 +- src/cfi.rs | 13 +++++-- src/console.rs | 74 +++++++++++------------------------- src/console_mode.rs | 13 ++++--- src/csbi.rs | 15 +++++--- src/handle.rs | 63 ++++++++++++------------------ src/lib.rs | 35 +---------------- src/screen_buffer.rs | 66 ++++++++++++++++---------------- src/semaphore.rs | 22 +++++++---- src/structs/coord.rs | 8 ++-- src/structs/input.rs | 40 ++++++++----------- src/structs/size.rs | 8 ++-- src/structs/window_coords.rs | 2 +- 15 files changed, 159 insertions(+), 224 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9b190e2..0998f0b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,8 +24,8 @@ jobs: name: Test (${{ matrix.toolchain }}) strategy: matrix: - # run on beta to ensure that tests won't break on the next version of the rust toolchain - # run on msrv to ensure that tests won't break on the minimum supported version of the rust + # run on stable and beta to ensure that tests won't break on the next version of the rust + # toolchain toolchain: [1.62.0, stable, beta] steps: - name: Checkout diff --git a/Cargo.toml b/Cargo.toml index f8cf941..45587e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,14 +13,14 @@ edition = "2021" rust-version = "1.62.0" [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3.8", features = [ - "winbase", - "consoleapi", - "processenv", - "handleapi", - "synchapi", - "impl-default", +# Note that 0.56.0 is the last version that supports Rust 1.62.0, the next version requires Rust +# 1.70.0. This would prevent crossterm from being able to compile this on the stable version of +# Debian (which ships with Rust 1.63.0). +windows = { version = "0.56.0", features = [ + "Win32_Security", + "Win32_Storage_FileSystem", + "Win32_System_Console", + "Win32_System_Threading", ] } - [package.metadata.docs.rs] default-target = "x86_64-pc-windows-msvc" diff --git a/examples/coloring_example.rs b/examples/coloring_example.rs index 87f5dd8..3e2e910 100644 --- a/examples/coloring_example.rs +++ b/examples/coloring_example.rs @@ -42,8 +42,8 @@ fn set_foreground_color() -> Result<()> { // background intensity is a separate value in attrs, // wee need to check if this was applied to the current bg color. - if (attrs & 0x0080 as u16) != 0 { - color = color | 0x0080 as u16; + if (attrs & 0x0080_u16) != 0 { + color |= 0x0080_u16; } // set the console text attribute to the new color value. diff --git a/src/cfi.rs b/src/cfi.rs index da84a40..fd12b04 100644 --- a/src/cfi.rs +++ b/src/cfi.rs @@ -1,7 +1,6 @@ use std::fmt; -use std::mem::zeroed; -use winapi::um::wincontypes::CONSOLE_FONT_INFO; +use windows::Win32::System::Console::CONSOLE_FONT_INFO; use crate::Size; @@ -23,9 +22,9 @@ impl fmt::Debug for FontInfo { } impl FontInfo { - /// Create a new font info without all zeroed properties. + /// Create a new font info with default (zeroed) properties. pub fn new() -> FontInfo { - FontInfo(unsafe { zeroed() }) + FontInfo(CONSOLE_FONT_INFO::default()) } /// Get the size of the font. @@ -40,3 +39,9 @@ impl FontInfo { self.0.nFont } } + +impl Default for FontInfo { + fn default() -> Self { + Self::new() + } +} diff --git a/src/console.rs b/src/console.rs index 41499d8..3cddddc 100644 --- a/src/console.rs +++ b/src/console.rs @@ -1,18 +1,16 @@ -use std::io::{self, Result}; -use std::iter; -use std::slice; -use std::str; - -use winapi::ctypes::c_void; -use winapi::shared::minwindef::DWORD; -use winapi::shared::ntdef::NULL; -use winapi::um::consoleapi::{GetNumberOfConsoleInputEvents, ReadConsoleInputW, WriteConsoleW}; -use winapi::um::wincon::{ +use std::{ + io::{self, Result}, + iter, slice, str, +}; + +use windows::Win32::System::Console::{ FillConsoleOutputAttribute, FillConsoleOutputCharacterA, GetLargestConsoleWindowSize, - SetConsoleTextAttribute, SetConsoleWindowInfo, COORD, INPUT_RECORD, SMALL_RECT, + GetNumberOfConsoleInputEvents, ReadConsoleInputW, SetConsoleTextAttribute, + SetConsoleWindowInfo, WriteConsoleW, CONSOLE_CHARACTER_ATTRIBUTES, COORD, INPUT_RECORD, + SMALL_RECT, }; -use super::{result, Coord, Handle, HandleType, InputRecord, WindowPositions}; +use super::{Coord, Handle, HandleType, InputRecord, WindowPositions}; /// A wrapper around a screen buffer. #[derive(Debug, Clone)] @@ -39,7 +37,7 @@ impl Console { /// This wraps /// [`SetConsoleTextAttribute`](https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute). pub fn set_text_attribute(&self, value: u16) -> Result<()> { - result(unsafe { SetConsoleTextAttribute(*self.handle, value) })?; + unsafe { SetConsoleTextAttribute(*self.handle, CONSOLE_CHARACTER_ATTRIBUTES(value)) }?; Ok(()) } @@ -48,14 +46,8 @@ impl Console { /// This wraps /// [`SetConsoleWindowInfo`](https://docs.microsoft.com/en-us/windows/console/setconsolewindowinfo). pub fn set_console_info(&self, absolute: bool, rect: WindowPositions) -> Result<()> { - let absolute = match absolute { - true => 1, - false => 0, - }; let a = SMALL_RECT::from(rect); - - result(unsafe { SetConsoleWindowInfo(*self.handle, absolute, &a) })?; - + unsafe { SetConsoleWindowInfo(*self.handle, absolute, &a) }?; Ok(()) } @@ -64,14 +56,14 @@ impl Console { /// /// This wraps /// [`FillConsoleOutputCharacterA`](https://docs.microsoft.com/en-us/windows/console/fillconsoleoutputcharacter). - pub fn fill_whit_character( + pub fn fill_with_character( &self, start_location: Coord, cells_to_write: u32, filling_char: char, ) -> Result { let mut chars_written = 0; - result(unsafe { + unsafe { // fill the cells in console with blanks FillConsoleOutputCharacterA( *self.handle, @@ -80,7 +72,7 @@ impl Console { COORD::from(start_location), &mut chars_written, ) - })?; + }?; Ok(chars_written) } @@ -98,7 +90,7 @@ impl Console { ) -> Result { let mut cells_written = 0; // Get the position of the current console window - result(unsafe { + unsafe { FillConsoleOutputAttribute( *self.handle, dw_attribute, @@ -106,7 +98,7 @@ impl Console { COORD::from(start_location), &mut cells_written, ) - })?; + }?; Ok(cells_written) } @@ -134,20 +126,9 @@ impl Console { } }; - let utf16: Vec = utf8.encode_utf16().collect(); - let utf16_ptr: *const c_void = utf16.as_ptr() as *const _ as *const c_void; + let cells_written: Option<*mut u32> = None; - let mut cells_written: u32 = 0; - - result(unsafe { - WriteConsoleW( - *self.handle, - utf16_ptr, - utf16.len() as u32, - &mut cells_written, - NULL, - ) - })?; + unsafe { WriteConsoleW(*self.handle, buf, cells_written, None) }?; Ok(utf8.as_bytes().len()) } @@ -202,8 +183,8 @@ impl Console { /// This wraps /// [`GetNumberOfConsoleInputEvents`](https://docs.microsoft.com/en-us/windows/console/getnumberofconsoleinputevents). pub fn number_of_console_input_events(&self) -> Result { - let mut buf_len: DWORD = 0; - result(unsafe { GetNumberOfConsoleInputEvents(*self.handle, &mut buf_len) })?; + let mut buf_len = 0; + unsafe { GetNumberOfConsoleInputEvents(*self.handle, &mut buf_len) }?; Ok(buf_len) } @@ -213,17 +194,8 @@ impl Console { /// a u32. fn read_input(&self, buf: &mut [INPUT_RECORD]) -> Result { let mut num_records = 0; - debug_assert!(buf.len() < std::u32::MAX as usize); - - result(unsafe { - ReadConsoleInputW( - *self.handle, - buf.as_mut_ptr(), - buf.len() as u32, - &mut num_records, - ) - })?; - + debug_assert!(buf.len() < u32::MAX as usize); + unsafe { ReadConsoleInputW(*self.handle, buf, &mut num_records) }?; Ok(num_records as usize) } } diff --git a/src/console_mode.rs b/src/console_mode.rs index 1bef06b..ee71606 100644 --- a/src/console_mode.rs +++ b/src/console_mode.rs @@ -1,8 +1,8 @@ use std::io::Result; -use winapi::um::consoleapi::{GetConsoleMode, SetConsoleMode}; +use windows::Win32::System::Console::{GetConsoleMode, SetConsoleMode, CONSOLE_MODE}; -use super::{result, Handle, HandleType}; +use super::{Handle, HandleType}; /// A wrapper around a screen buffer, focusing on calls to get and set the console mode. /// @@ -32,7 +32,8 @@ impl ConsoleMode { /// This wraps /// [`SetConsoleMode`](https://docs.microsoft.com/en-us/windows/console/setconsolemode). pub fn set_mode(&self, console_mode: u32) -> Result<()> { - result(unsafe { SetConsoleMode(*self.handle, console_mode) }) + unsafe { SetConsoleMode(*self.handle, CONSOLE_MODE(console_mode)) }?; + Ok(()) } /// Get the console mode. @@ -42,9 +43,9 @@ impl ConsoleMode { /// This wraps /// [`GetConsoleMode`](https://docs.microsoft.com/en-us/windows/console/getconsolemode). pub fn mode(&self) -> Result { - let mut console_mode = 0; - result(unsafe { GetConsoleMode(*self.handle, &mut console_mode) })?; - Ok(console_mode) + let mut console_mode = CONSOLE_MODE(0); + unsafe { GetConsoleMode(*self.handle, &mut console_mode) }?; + Ok(console_mode.0) } } diff --git a/src/csbi.rs b/src/csbi.rs index ead6c1f..9b1bd23 100644 --- a/src/csbi.rs +++ b/src/csbi.rs @@ -1,7 +1,6 @@ use std::fmt; -use std::mem::zeroed; -use winapi::um::wincon::CONSOLE_SCREEN_BUFFER_INFO; +use windows::Win32::System::Console::CONSOLE_SCREEN_BUFFER_INFO; use super::{Coord, Size, WindowPositions}; @@ -31,9 +30,9 @@ impl fmt::Debug for ScreenBufferInfo { } impl ScreenBufferInfo { - /// Create a new console screen buffer without all zeroed properties. + /// Create a new console screen buffer with default (zeroed) properties. pub fn new() -> ScreenBufferInfo { - ScreenBufferInfo(unsafe { zeroed() }) + ScreenBufferInfo(CONSOLE_SCREEN_BUFFER_INFO::default()) } /// Get the size of the screen buffer. @@ -64,7 +63,7 @@ impl ScreenBufferInfo { /// /// Will take `wAttributes` from the current screen buffer. pub fn attributes(&self) -> u16 { - self.0.wAttributes + self.0.wAttributes.0 } /// Get the current column and row of the terminal cursor in the screen buffer. @@ -75,6 +74,12 @@ impl ScreenBufferInfo { } } +impl Default for ScreenBufferInfo { + fn default() -> Self { + Self::new() + } +} + impl From for ScreenBufferInfo { fn from(csbi: CONSOLE_SCREEN_BUFFER_INFO) -> Self { ScreenBufferInfo(csbi) diff --git a/src/handle.rs b/src/handle.rs index 504f1bd..dcd77e0 100644 --- a/src/handle.rs +++ b/src/handle.rs @@ -1,21 +1,15 @@ //! This module contains some logic for working with the console handle. -use std::io::Result; -use std::ops::Deref; -use std::ptr::null_mut; -use std::sync::Arc; - -use winapi::shared::minwindef::DWORD; -use winapi::um::{ - fileapi::{CreateFileW, OPEN_EXISTING}, - handleapi::{CloseHandle, INVALID_HANDLE_VALUE}, - processenv::GetStdHandle, - winbase::{STD_INPUT_HANDLE, STD_OUTPUT_HANDLE}, - winnt::{FILE_SHARE_READ, FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE, HANDLE}, +use std::{io::Result, ops::Deref, sync::Arc}; + +use windows::Win32::{ + Foundation::{CloseHandle, GENERIC_READ, GENERIC_WRITE, HANDLE, INVALID_HANDLE_VALUE}, + Storage::FileSystem::{ + CreateFileW, FILE_FLAGS_AND_ATTRIBUTES, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING, + }, + System::Console::{GetStdHandle, STD_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE}, }; -use super::handle_result; - /// The standard handles of a process. /// /// See [the Windows documentation on console @@ -64,7 +58,7 @@ impl Drop for Inner { fn drop(&mut self) { if self.is_exclusive { assert!( - unsafe { CloseHandle(self.handle) != 0 }, + unsafe { CloseHandle(self.handle).is_ok() }, "failed to close handle" ) } @@ -114,20 +108,17 @@ impl Handle { /// This wraps /// [`CreateFileW`](https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew). pub fn current_out_handle() -> Result { - let utf16: Vec = "CONOUT$\0".encode_utf16().collect(); - let utf16_ptr: *const u16 = utf16.as_ptr(); - - let handle = handle_result(unsafe { + let handle = unsafe { CreateFileW( - utf16_ptr, - GENERIC_READ | GENERIC_WRITE, + ::windows::core::w!("CONOUT$"), + (GENERIC_READ | GENERIC_WRITE).0, FILE_SHARE_READ | FILE_SHARE_WRITE, - null_mut(), + None, // no security attributes OPEN_EXISTING, - 0, - null_mut(), + FILE_FLAGS_AND_ATTRIBUTES::default(), + None, // no template file ) - })?; + }?; Ok(Handle { handle: Arc::new(Inner::new_exclusive(handle)), @@ -141,20 +132,17 @@ impl Handle { /// This wraps /// [`CreateFileW`](https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew). pub fn current_in_handle() -> Result { - let utf16: Vec = "CONIN$\0".encode_utf16().collect(); - let utf16_ptr: *const u16 = utf16.as_ptr(); - - let handle = handle_result(unsafe { + let handle = unsafe { CreateFileW( - utf16_ptr, - GENERIC_READ | GENERIC_WRITE, + ::windows::core::w!("CONIN$"), + (GENERIC_READ | GENERIC_WRITE).0, FILE_SHARE_READ | FILE_SHARE_WRITE, - null_mut(), + None, // no security attributes OPEN_EXISTING, - 0, - null_mut(), + FILE_FLAGS_AND_ATTRIBUTES::default(), + None, // no template file ) - })?; + }?; Ok(Handle { handle: Arc::new(Inner::new_exclusive(handle)), @@ -181,9 +169,8 @@ impl Handle { Self::std_handle(STD_INPUT_HANDLE) } - fn std_handle(which_std: DWORD) -> Result { - let handle = handle_result(unsafe { GetStdHandle(which_std) })?; - + fn std_handle(which_std: STD_HANDLE) -> Result { + let handle = unsafe { GetStdHandle(which_std) }?; Ok(Handle { handle: Arc::new(Inner::new_shared(handle)), }) diff --git a/src/lib.rs b/src/lib.rs index 0ccfd62..f55de5b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,10 +3,7 @@ use std::io; -use winapi::shared::minwindef::BOOL; -use winapi::um::handleapi::INVALID_HANDLE_VALUE; -use winapi::um::wincontypes::COORD; -use winapi::um::winnt::HANDLE; +use windows::Win32::System::Console::COORD; pub use self::{ cfi::FontInfo, @@ -31,16 +28,6 @@ mod screen_buffer; mod semaphore; mod structs; -/// Get the result of a call to WinAPI as an [`io::Result`]. -#[inline] -pub fn result(return_value: BOOL) -> io::Result<()> { - if return_value != 0 { - Ok(()) - } else { - Err(io::Error::last_os_error()) - } -} - /// Get the result of a call to WinAPI that returns a /// [`COORD`](https://docs.microsoft.com/en-us/windows/console/coord-str) as an [`io::Result`]. #[inline] @@ -51,23 +38,3 @@ pub fn coord_result(return_value: COORD) -> io::Result { Err(io::Error::last_os_error()) } } - -/// Get the result of a call to WinAPI that returns a handle or `INVALID_HANDLE_VALUE`. -#[inline] -pub fn handle_result(return_value: HANDLE) -> io::Result { - if return_value != INVALID_HANDLE_VALUE { - Ok(return_value) - } else { - Err(io::Error::last_os_error()) - } -} - -/// Get the result of a call to WinAPI that returns a handle or `NULL`. -#[inline] -pub fn nonnull_handle_result(return_value: HANDLE) -> io::Result { - if return_value.is_null() { - Err(io::Error::last_os_error()) - } else { - Ok(return_value) - } -} diff --git a/src/screen_buffer.rs b/src/screen_buffer.rs index e113ebf..28bdcc9 100644 --- a/src/screen_buffer.rs +++ b/src/screen_buffer.rs @@ -1,23 +1,18 @@ //! This contains the logic for working with the console buffer. -use std::io::Result; -use std::mem::size_of; - -use winapi::{ - shared::minwindef::TRUE, - shared::ntdef::NULL, - um::{ - minwinbase::SECURITY_ATTRIBUTES, - wincon::{ - CreateConsoleScreenBuffer, GetConsoleScreenBufferInfo, GetCurrentConsoleFont, - SetConsoleActiveScreenBuffer, SetConsoleScreenBufferSize, CONSOLE_TEXTMODE_BUFFER, - COORD, - }, - winnt::{FILE_SHARE_READ, FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE}, +use std::{io::Result, mem::size_of}; + +use windows::Win32::{ + Foundation::{GENERIC_READ, GENERIC_WRITE}, + Security::SECURITY_ATTRIBUTES, + Storage::FileSystem::{FILE_SHARE_READ, FILE_SHARE_WRITE}, + System::Console::{ + CreateConsoleScreenBuffer, GetConsoleScreenBufferInfo, GetCurrentConsoleFont, + SetConsoleActiveScreenBuffer, SetConsoleScreenBufferSize, CONSOLE_TEXTMODE_BUFFER, COORD, }, }; -use super::{handle_result, result, FontInfo, Handle, HandleType, ScreenBufferInfo}; +use super::{FontInfo, Handle, HandleType, ScreenBufferInfo}; /// A wrapper around a screen buffer. #[derive(Clone, Debug)] @@ -45,20 +40,19 @@ impl ScreenBuffer { pub fn create() -> Result { let security_attr: SECURITY_ATTRIBUTES = SECURITY_ATTRIBUTES { nLength: size_of::() as u32, - lpSecurityDescriptor: NULL, - bInheritHandle: TRUE, + lpSecurityDescriptor: ::std::ptr::null_mut(), + bInheritHandle: true.into(), }; - let new_screen_buffer = handle_result(unsafe { + let new_screen_buffer = unsafe { CreateConsoleScreenBuffer( - GENERIC_READ | // read/write access - GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE, // shared - &security_attr, // default security attributes - CONSOLE_TEXTMODE_BUFFER, // must be TEXTMODE - NULL, + (GENERIC_READ | GENERIC_WRITE).0, // read/write access + (FILE_SHARE_READ | FILE_SHARE_WRITE).0, // shared + Some(&security_attr), // security attributes + CONSOLE_TEXTMODE_BUFFER, // must be TEXTMODE + None, // no existing screen buffer to copy ) - })?; + }?; Ok(ScreenBuffer { handle: unsafe { Handle::from_raw(new_screen_buffer) }, }) @@ -69,7 +63,8 @@ impl ScreenBuffer { /// This wraps /// [`SetConsoleActiveScreenBuffer`](https://docs.microsoft.com/en-us/windows/console/setconsoleactivescreenbuffer). pub fn show(&self) -> Result<()> { - result(unsafe { SetConsoleActiveScreenBuffer(*self.handle) }) + unsafe { SetConsoleActiveScreenBuffer(*self.handle) }?; + Ok(()) } /// Get the screen buffer information like terminal size, cursor position, buffer size. @@ -78,18 +73,24 @@ impl ScreenBuffer { /// [`GetConsoleScreenBufferInfo`](https://docs.microsoft.com/en-us/windows/console/getconsolescreenbufferinfo). pub fn info(&self) -> Result { let mut csbi = ScreenBufferInfo::new(); - result(unsafe { GetConsoleScreenBufferInfo(*self.handle, &mut csbi.0) })?; + unsafe { GetConsoleScreenBufferInfo(*self.handle, &mut csbi.0) }?; Ok(csbi) } /// Get the current font information like size and font index. /// /// This wraps - /// [`GetConsoleFontSize`](https://learn.microsoft.com/en-us/windows/console/getconsolefontsize). + /// [`GetCurrentConsoleFont`](https://learn.microsoft.com/en-us/windows/console/getcurrentconsolefont). pub fn font_info(&self) -> Result { - let mut fi = FontInfo::new(); - result(unsafe { GetCurrentConsoleFont(*self.handle, 0, &mut fi.0) })?; - Ok(fi) + let mut font_info = FontInfo::new(); + unsafe { + GetCurrentConsoleFont( + *self.handle, + false, // get info for current window size not the maximum window size + &mut font_info.0, + ) + }?; + Ok(font_info) } /// Set the console screen buffer size to the given size. @@ -97,7 +98,8 @@ impl ScreenBuffer { /// This wraps /// [`SetConsoleScreenBufferSize`](https://docs.microsoft.com/en-us/windows/console/setconsolescreenbuffersize). pub fn set_size(&self, x: i16, y: i16) -> Result<()> { - result(unsafe { SetConsoleScreenBufferSize(*self.handle, COORD { X: x, Y: y }) }) + unsafe { SetConsoleScreenBufferSize(*self.handle, COORD { X: x, Y: y }) }?; + Ok(()) } /// Get the underlying raw `HANDLE` used by this type to execute with. diff --git a/src/semaphore.rs b/src/semaphore.rs index 494a166..ba36997 100644 --- a/src/semaphore.rs +++ b/src/semaphore.rs @@ -1,8 +1,8 @@ -use std::{io, ptr}; +use std::io; -use winapi::um::synchapi::{CreateSemaphoreW, ReleaseSemaphore}; +use windows::Win32::System::Threading::{CreateSemaphoreW, ReleaseSemaphore}; -use crate::{nonnull_handle_result, result, Handle}; +use crate::Handle; /// A [Windows semaphore](https://docs.microsoft.com/en-us/windows/win32/sync/semaphore-objects). #[derive(Clone, Debug)] @@ -14,10 +14,14 @@ impl Semaphore { /// This wraps /// [`CreateSemaphoreW`](https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createsemaphorew). pub fn new() -> io::Result { - let handle = nonnull_handle_result(unsafe { - CreateSemaphoreW(ptr::null_mut(), 0, 1, ptr::null_mut()) - })?; - + let handle = unsafe { + CreateSemaphoreW( + None, // no security attributes + 0, // initial count + 1, // maximum count + windows::core::w!(""), // unnamed semaphore + ) + }?; let handle = unsafe { Handle::from_raw(handle) }; Ok(Self(handle)) } @@ -27,7 +31,9 @@ impl Semaphore { /// This wraps /// [`ReleaseSemaphore`](https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-releasesemaphore). pub fn release(&self) -> io::Result<()> { - result(unsafe { ReleaseSemaphore(*self.0, 1, ptr::null_mut()) }) + let previous_count = None; + unsafe { ReleaseSemaphore(*self.0, 1, previous_count) }?; + Ok(()) } /// Access the underlying handle to the semaphore. diff --git a/src/structs/coord.rs b/src/structs/coord.rs index a96dbe9..6d77ff5 100644 --- a/src/structs/coord.rs +++ b/src/structs/coord.rs @@ -2,7 +2,7 @@ //! For example, in WinAPI we have `COORD` which looks and feels inconvenient. //! This module provides also some trait implementations who will make parsing and working with `COORD` easier. -use winapi::um::wincon::COORD; +use windows::Win32::System::Console::COORD; /// This is type represents the position of something on a certain 'x' and 'y'. #[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd)] @@ -35,8 +35,8 @@ impl From for COORD { } } -impl Into<(u16, u16)> for Coord { - fn into(self) -> (u16, u16) { - (self.x as u16, self.y as u16) +impl From for (u16, u16) { + fn from(val: Coord) -> Self { + (val.x as u16, val.y as u16) } } diff --git a/src/structs/input.rs b/src/structs/input.rs index 9b64627..5ca0b56 100644 --- a/src/structs/input.rs +++ b/src/structs/input.rs @@ -9,8 +9,7 @@ //! - `InputEventType` //! - `INPUT_RECORD` -use winapi::shared::minwindef::DWORD; -use winapi::um::wincon::{ +use windows::Win32::System::Console::{ FOCUS_EVENT, FOCUS_EVENT_RECORD, FROM_LEFT_1ST_BUTTON_PRESSED, FROM_LEFT_2ND_BUTTON_PRESSED, FROM_LEFT_3RD_BUTTON_PRESSED, FROM_LEFT_4TH_BUTTON_PRESSED, INPUT_RECORD, KEY_EVENT, KEY_EVENT_RECORD, MENU_EVENT, MENU_EVENT_RECORD, MOUSE_EVENT, MOUSE_EVENT_RECORD, @@ -18,6 +17,7 @@ use winapi::um::wincon::{ }; use super::Coord; + use crate::ScreenBuffer; /// A [keyboard input event](https://docs.microsoft.com/en-us/windows/console/key-event-record-str). @@ -51,11 +51,11 @@ impl KeyEventRecord { #[inline] fn from_winapi(record: &KEY_EVENT_RECORD) -> Self { KeyEventRecord { - key_down: record.bKeyDown != 0, + key_down: record.bKeyDown.as_bool(), repeat_count: record.wRepeatCount, virtual_key_code: record.wVirtualKeyCode, virtual_scan_code: record.wVirtualScanCode, - u_char: unsafe { *record.uChar.UnicodeChar() }, + u_char: unsafe { record.uChar.UnicodeChar }, control_key_state: ControlKeyState(record.dwControlKeyState), } } @@ -121,9 +121,9 @@ pub struct ButtonState { state: i32, } -impl From for ButtonState { +impl From for ButtonState { #[inline] - fn from(event: DWORD) -> Self { + fn from(event: u32) -> Self { let state = event as i32; ButtonState { state } } @@ -164,16 +164,6 @@ impl ButtonState { self.state > 0 } - /// Returns whether there is a horizontal scroll to the right. - pub fn scroll_right(&self) -> bool { - self.state > 0 - } - - /// Returns whether there is a horizontal scroll to the left. - pub fn scroll_left(&self) -> bool { - self.state < 0 - } - /// Returns the raw state. pub fn state(&self) -> i32 { self.state @@ -228,8 +218,8 @@ pub enum EventFlags { } // TODO: Replace with TryFrom. -impl From for EventFlags { - fn from(event: DWORD) -> Self { +impl From for EventFlags { + fn from(event: u32) -> Self { match event { 0x0000 => EventFlags::PressOrRelease, 0x0002 => EventFlags::DoubleClick, @@ -269,7 +259,7 @@ impl From for FocusEventRecord { #[inline] fn from(record: FOCUS_EVENT_RECORD) -> Self { FocusEventRecord { - set_focus: record.bSetFocus != 0, + set_focus: record.bSetFocus.as_bool(), } } } @@ -313,14 +303,14 @@ pub enum InputRecord { impl From for InputRecord { #[inline] fn from(record: INPUT_RECORD) -> Self { - match record.EventType { + match record.EventType as u32 { KEY_EVENT => InputRecord::KeyEvent(KeyEventRecord::from_winapi(unsafe { - record.Event.KeyEvent() + &record.Event.KeyEvent })), - MOUSE_EVENT => InputRecord::MouseEvent(unsafe { *record.Event.MouseEvent() }.into()), + MOUSE_EVENT => InputRecord::MouseEvent(unsafe { record.Event.MouseEvent }.into()), WINDOW_BUFFER_SIZE_EVENT => InputRecord::WindowBufferSizeEvent({ let mut buffer = - unsafe { WindowBufferSizeRecord::from(*record.Event.WindowBufferSizeEvent()) }; + unsafe { WindowBufferSizeRecord::from(record.Event.WindowBufferSizeEvent) }; let window = ScreenBuffer::current().unwrap().info().unwrap(); let screen_size = window.terminal_size(); @@ -329,8 +319,8 @@ impl From for InputRecord { buffer }), - FOCUS_EVENT => InputRecord::FocusEvent(unsafe { *record.Event.FocusEvent() }.into()), - MENU_EVENT => InputRecord::MenuEvent(unsafe { *record.Event.MenuEvent() }.into()), + FOCUS_EVENT => InputRecord::FocusEvent(unsafe { record.Event.FocusEvent }.into()), + MENU_EVENT => InputRecord::MenuEvent(unsafe { record.Event.MenuEvent }.into()), code => panic!("Unexpected INPUT_RECORD EventType: {}", code), } } diff --git a/src/structs/size.rs b/src/structs/size.rs index 4cba7a6..56ae98a 100644 --- a/src/structs/size.rs +++ b/src/structs/size.rs @@ -2,7 +2,7 @@ //! For example, in WinAPI we have `COORD` to represent screen/buffer size but this is a little inconvenient. //! This module provides some trait implementations who will make parsing and working with `COORD` easier. -use winapi::um::wincon::COORD; +use windows::Win32::System::Console::COORD; /// This is type represents the size of something in width and height. #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] @@ -24,8 +24,8 @@ impl From for Size { } } -impl Into<(u16, u16)> for Size { - fn into(self) -> (u16, u16) { - (self.width as u16, self.height as u16) +impl From for (u16, u16) { + fn from(val: Size) -> Self { + (val.width as u16, val.height as u16) } } diff --git a/src/structs/window_coords.rs b/src/structs/window_coords.rs index ef2017a..9ec1c10 100644 --- a/src/structs/window_coords.rs +++ b/src/structs/window_coords.rs @@ -2,7 +2,7 @@ //! For example, in WinAPI we have `SMALL_RECT` to represent a window size but this is a little inconvenient. //! This module provides some trait implementations who will make parsing and working with `SMALL_RECT` easier. -use winapi::um::wincon::{CONSOLE_SCREEN_BUFFER_INFO, SMALL_RECT}; +use windows::Win32::System::Console::{CONSOLE_SCREEN_BUFFER_INFO, SMALL_RECT}; /// This is a wrapper for the locations of a rectangle. #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] From 674e6dd20db0ccba940e37d2132cf18f123d3c6d Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Sat, 22 Jun 2024 07:26:04 -0700 Subject: [PATCH 4/4] add back in the scroll_right and left functions --- src/structs/input.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/structs/input.rs b/src/structs/input.rs index 5ca0b56..feeaa1f 100644 --- a/src/structs/input.rs +++ b/src/structs/input.rs @@ -164,6 +164,16 @@ impl ButtonState { self.state > 0 } + /// Returns whether there is a horizontal scroll to the right. + pub fn scroll_right(&self) -> bool { + self.state > 0 + } + + /// Returns whether there is a horizontal scroll to the left. + pub fn scroll_left(&self) -> bool { + self.state < 0 + } + /// Returns the raw state. pub fn state(&self) -> i32 { self.state