From 83547a8940dfda601ec0afb0d0860fa963f8e202 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Fri, 21 Mar 2025 13:04:48 +0100 Subject: [PATCH 1/4] Allow compiling on tvOS, watchOS and visionOS too Assuming that the `mach2` crate is updated to support these platforms, or we get rid of that dependency. --- Cargo.toml | 2 +- README.md | 2 +- doc/platforms.md | 4 ++-- src/posix/enumerate.rs | 24 +++++++++-------------- src/posix/ioctl.rs | 16 +++++++-------- src/posix/termios.rs | 9 ++++----- src/posix/tty.rs | 44 +++++++++++++++++++++--------------------- tests/test_tty.rs | 13 ++++--------- 8 files changed, 50 insertions(+), 64 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2790b55a..eeb6a727 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ nix = { version = "0.26", default-features = false, features = ["fs", "ioctl", " libudev = { version = "0.3.0", optional = true } unescaper = "0.1.3" -[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies] +[target.'cfg(target_vendor = "apple")'.dependencies] # TODO: Remove pinning this dependency when we are bumping our MSRV. core-foundation = "=0.10.0" core-foundation-sys = "0.8.4" diff --git a/README.md b/README.md index 6a782dcf..55ed69a2 100644 --- a/README.md +++ b/README.md @@ -152,7 +152,7 @@ demand. - `i686-unknown-linux-musl` - `x86_64-unknown-linux-gnu` - `x86_64-unknown-linux-musl` -- macOS/iOS +- macOS/iOS/tvOS/watchOS/visionOS - `aarch64-apple-darwin` - `aarch64-apple-ios` - `x86_64-apple-darwin` diff --git a/doc/platforms.md b/doc/platforms.md index 9c0fd329..247be35a 100644 --- a/doc/platforms.md +++ b/doc/platforms.md @@ -33,8 +33,8 @@ The BSDs basically **only** have the Termios2 API, but they call it Termios. It * https://man.openbsd.org/tty.4 -## macOS and iOS +## Darwin -While macOS and iOS have the heritage of a BSD, their support is slightly different. In theory, they support arbitrary baud rates in their Termios API much like the BSDs, but in practice this doesn't work with many hardware devices, as it's dependent on driver support. Instead, Apple added the `IOSSIOSPEED` ioctl in Mac OS X 10.4, which can set the baud rate to an arbitrary value. As the oldest macOS version supported by Rust is 10.7, it's available on all Mac platforms. +While macOS, iOS, and similar Apple platforms have the heritage of a BSD, their support is slightly different. In theory, they support arbitrary baud rates in their Termios API much like the BSDs, but in practice this doesn't work with many hardware devices, as it's dependent on driver support. Instead, Apple added the `IOSSIOSPEED` ioctl in Mac OS X 10.4, which can set the baud rate to an arbitrary value. As the oldest macOS version supported by Rust is 10.7, it's available on all Mac platforms. This API requires the port to be set into raw mode with `cfmakeraw`, and must be done after every call to `tcsetattr`, as that will reset the baud rate. Additionally, there is no way to retrieve the actual baud rate from the OS. This is therefore the clunkiest API of any platform. diff --git a/src/posix/enumerate.rs b/src/posix/enumerate.rs index 360c0cfc..7cbeb644 100644 --- a/src/posix/enumerate.rs +++ b/src/posix/enumerate.rs @@ -7,7 +7,7 @@ cfg_if! { } cfg_if! { - if #[cfg(any(target_os = "ios", target_os = "macos"))] { + if #[cfg(target_vendor = "apple")] { use core_foundation::base::CFType; use core_foundation::base::TCFType; use core_foundation::dictionary::CFDictionary; @@ -26,22 +26,16 @@ cfg_if! { } } -#[cfg(any( - target_os = "freebsd", - target_os = "ios", - target_os = "linux", - target_os = "macos" -))] +#[cfg(any(target_os = "freebsd", target_os = "linux", target_vendor = "apple"))] use crate::SerialPortType; -#[cfg(any(target_os = "ios", target_os = "linux", target_os = "macos"))] +#[cfg(any(target_os = "linux", target_vendor = "apple"))] use crate::UsbPortInfo; #[cfg(any( target_os = "android", - target_os = "ios", all(target_os = "linux", not(target_env = "musl"), feature = "libudev"), - target_os = "macos", target_os = "netbsd", target_os = "openbsd", + target_vendor = "apple", ))] use crate::{Error, ErrorKind}; use crate::{Result, SerialPortInfo}; @@ -265,7 +259,7 @@ fn parse_modalias(moda: &str) -> Option { }) } -#[cfg(any(target_os = "ios", target_os = "macos"))] +#[cfg(target_vendor = "apple")] fn get_parent_device_by_type( device: io_object_t, parent_type: *const c_char, @@ -292,7 +286,7 @@ fn get_parent_device_by_type( } } -#[cfg(any(target_os = "ios", target_os = "macos"))] +#[cfg(target_vendor = "apple")] #[allow(non_upper_case_globals)] /// Returns a specific property of the given device as an integer. fn get_int_property(device_type: io_registry_entry_t, property: &str) -> Result { @@ -321,7 +315,7 @@ fn get_int_property(device_type: io_registry_entry_t, property: &str) -> Result< )) } -#[cfg(any(target_os = "ios", target_os = "macos"))] +#[cfg(target_vendor = "apple")] /// Returns a specific property of the given device as a string. fn get_string_property(device_type: io_registry_entry_t, property: &str) -> Result { let cf_property = CFString::new(property); @@ -345,7 +339,7 @@ fn get_string_property(device_type: io_registry_entry_t, property: &str) -> Resu .ok_or(Error::new(ErrorKind::Unknown, "Failed to get string value")) } -#[cfg(any(target_os = "ios", target_os = "macos"))] +#[cfg(target_vendor = "apple")] /// Determine the serial port type based on the service object (like that returned by /// `IOIteratorNext`). Specific properties are extracted for USB devices. fn port_type(service: io_object_t) -> SerialPortType { @@ -381,7 +375,7 @@ fn port_type(service: io_object_t) -> SerialPortType { } cfg_if! { - if #[cfg(any(target_os = "ios", target_os = "macos"))] { + if #[cfg(target_vendor = "apple")] { /// Scans the system for serial ports and returns a list of them. /// The `SerialPortInfo` struct contains the name of the port which can be used for opening it. pub fn available_ports() -> Result> { diff --git a/src/posix/ioctl.rs b/src/posix/ioctl.rs index ced1c091..ffefd409 100644 --- a/src/posix/ioctl.rs +++ b/src/posix/ioctl.rs @@ -23,10 +23,9 @@ mod raw { #[cfg(any( target_os = "dragonfly", target_os = "freebsd", - target_os = "ios", - target_os = "macos", target_os = "netbsd", - target_os = "openbsd" + target_os = "openbsd", + target_vendor = "apple", ))] ioctl_read!(fionread, b'f', 127, libc::c_int); @@ -37,10 +36,9 @@ mod raw { #[cfg(any( target_os = "dragonfly", target_os = "freebsd", - target_os = "ios", - target_os = "macos", target_os = "netbsd", - target_os = "openbsd" + target_os = "openbsd", + target_vendor = "apple" ))] ioctl_read!(tiocoutq, b't', 115, libc::c_int); @@ -80,10 +78,10 @@ mod raw { 0x2B, libc::termios2 ); - #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg(target_vendor = "apple")] const IOSSIOSPEED: libc::c_ulong = 0x80045402; ioctl_write_ptr_bad!( - #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg(target_vendor = "apple")] iossiospeed, IOSSIOSPEED, libc::speed_t @@ -199,7 +197,7 @@ pub fn tcsets2(fd: RawFd, options: &libc::termios2) -> Result<()> { .map_err(|e| e.into()) } -#[cfg(any(target_os = "ios", target_os = "macos"))] +#[cfg(target_vendor = "apple")] pub fn iossiospeed(fd: RawFd, baud_rate: &libc::speed_t) -> Result<()> { unsafe { raw::iossiospeed(fd, baud_rate) } .map(|_| ()) diff --git a/src/posix/termios.rs b/src/posix/termios.rs index 23988218..42594b03 100644 --- a/src/posix/termios.rs +++ b/src/posix/termios.rs @@ -10,8 +10,6 @@ cfg_if! { if #[cfg(any( target_os = "dragonfly", target_os = "freebsd", - target_os = "ios", - target_os = "macos", target_os = "netbsd", target_os = "openbsd", all( @@ -21,7 +19,8 @@ cfg_if! { target_arch = "powerpc", target_arch = "powerpc64" ) - ) + ), + target_vendor = "apple", ))] { pub(crate) type Termios = libc::termios; } else if #[cfg(any( @@ -45,7 +44,7 @@ cfg_if! { // calls in this lib to the IOSSIOSPEED ioctl. So whenever we get this struct, make sure to // reset the input & output baud rates to a safe default. This is accounted for by the // corresponding set_termios that is mac-specific and always calls IOSSIOSPEED. -#[cfg(any(target_os = "ios", target_os = "macos",))] +#[cfg(target_vendor = "apple")] pub(crate) fn get_termios(fd: RawFd) -> Result { use std::mem::MaybeUninit; @@ -96,7 +95,7 @@ pub(crate) fn get_termios(fd: RawFd) -> Result { crate::posix::ioctl::tcgets2(fd) } -#[cfg(any(target_os = "ios", target_os = "macos",))] +#[cfg(target_vendor = "apple")] pub(crate) fn set_termios(fd: RawFd, termios: &libc::termios, baud_rate: u32) -> Result<()> { let res = unsafe { libc::tcsetattr(fd, libc::TCSANOW, termios) }; nix::errno::Errno::result(res)?; diff --git a/src/posix/tty.rs b/src/posix/tty.rs index 678c7c94..66ffb33e 100644 --- a/src/posix/tty.rs +++ b/src/posix/tty.rs @@ -68,7 +68,7 @@ pub struct TTYPort { timeout: Duration, exclusive: bool, port_name: Option, - #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg(target_vendor = "apple")] baud_rate: u32, } @@ -169,7 +169,7 @@ impl TTYPort { )); }; - #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg(target_vendor = "apple")] if builder.baud_rate > 0 { unsafe { libc::tcflush(fd.0, libc::TCIOFLUSH) }; } @@ -183,11 +183,11 @@ impl TTYPort { termios::set_flow_control(&mut termios, builder.flow_control); termios::set_data_bits(&mut termios, builder.data_bits); termios::set_stop_bits(&mut termios, builder.stop_bits); - #[cfg(not(any(target_os = "ios", target_os = "macos")))] + #[cfg(not(target_vendor = "apple"))] termios::set_baud_rate(&mut termios, builder.baud_rate)?; - #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg(target_vendor = "apple")] termios::set_termios(fd.0, &termios, builder.baud_rate)?; - #[cfg(not(any(target_os = "ios", target_os = "macos")))] + #[cfg(not(target_vendor = "apple"))] termios::set_termios(fd.0, &termios)?; // Return the final port object @@ -196,7 +196,7 @@ impl TTYPort { timeout: builder.timeout, exclusive: true, port_name: Some(builder.path.clone()), - #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg(target_vendor = "apple")] baud_rate: builder.baud_rate, }; @@ -318,7 +318,7 @@ impl TTYPort { let ptty_name = nix::pty::ptsname_r(&next_pty_fd)?; // Open the slave port - #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg(target_vendor = "apple")] let baud_rate = 9600; let fd = nix::fcntl::open( Path::new(&ptty_name), @@ -347,7 +347,7 @@ impl TTYPort { timeout: Duration::from_millis(100), exclusive: true, port_name: Some(ptty_name), - #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg(target_vendor = "apple")] baud_rate, }; @@ -359,7 +359,7 @@ impl TTYPort { timeout: Duration::from_millis(100), exclusive: true, port_name: None, - #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg(target_vendor = "apple")] baud_rate, }; @@ -396,7 +396,7 @@ impl TTYPort { exclusive: self.exclusive, port_name: self.port_name.clone(), timeout: self.timeout, - #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg(target_vendor = "apple")] baud_rate: self.baud_rate, }) } @@ -426,7 +426,7 @@ impl IntoRawFd for TTYPort { } /// Get the baud speed for a port from its file descriptor -#[cfg(any(target_os = "ios", target_os = "macos"))] +#[cfg(target_vendor = "apple")] fn get_termios_speed(fd: RawFd) -> u32 { let mut termios = MaybeUninit::uninit(); let res = unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) }; @@ -458,7 +458,7 @@ impl FromRawFd for TTYPort { // It's not guaranteed that the baud rate in the `termios` struct is correct, as // setting an arbitrary baud rate via the `iossiospeed` ioctl overrides that value, // but extract that value anyways as a best-guess of the actual baud rate. - #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg(target_vendor = "apple")] baud_rate: get_termios_speed(fd), } } @@ -559,7 +559,7 @@ impl SerialPort for TTYPort { /// /// On some platforms this will be the actual device baud rate, which may differ from the /// desired baud rate. - #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg(target_vendor = "apple")] fn baud_rate(&self) -> Result { Ok(self.baud_rate) } @@ -695,7 +695,7 @@ impl SerialPort for TTYPort { } // Mac OS needs special logic for setting arbitrary baud rates. - #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg(target_vendor = "apple")] fn set_baud_rate(&mut self, baud_rate: u32) -> Result<()> { ioctl::iossiospeed(self.fd, &(baud_rate as libc::speed_t))?; self.baud_rate = baud_rate; @@ -705,36 +705,36 @@ impl SerialPort for TTYPort { fn set_flow_control(&mut self, flow_control: FlowControl) -> Result<()> { let mut termios = termios::get_termios(self.fd)?; termios::set_flow_control(&mut termios, flow_control); - #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg(target_vendor = "apple")] return termios::set_termios(self.fd, &termios, self.baud_rate); - #[cfg(not(any(target_os = "ios", target_os = "macos")))] + #[cfg(not(target_vendor = "apple"))] return termios::set_termios(self.fd, &termios); } fn set_parity(&mut self, parity: Parity) -> Result<()> { let mut termios = termios::get_termios(self.fd)?; termios::set_parity(&mut termios, parity); - #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg(target_vendor = "apple")] return termios::set_termios(self.fd, &termios, self.baud_rate); - #[cfg(not(any(target_os = "ios", target_os = "macos")))] + #[cfg(not(target_vendor = "apple"))] return termios::set_termios(self.fd, &termios); } fn set_data_bits(&mut self, data_bits: DataBits) -> Result<()> { let mut termios = termios::get_termios(self.fd)?; termios::set_data_bits(&mut termios, data_bits); - #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg(target_vendor = "apple")] return termios::set_termios(self.fd, &termios, self.baud_rate); - #[cfg(not(any(target_os = "ios", target_os = "macos")))] + #[cfg(not(target_vendor = "apple"))] return termios::set_termios(self.fd, &termios); } fn set_stop_bits(&mut self, stop_bits: StopBits) -> Result<()> { let mut termios = termios::get_termios(self.fd)?; termios::set_stop_bits(&mut termios, stop_bits); - #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg(target_vendor = "apple")] return termios::set_termios(self.fd, &termios, self.baud_rate); - #[cfg(not(any(target_os = "ios", target_os = "macos")))] + #[cfg(not(target_vendor = "apple"))] return termios::set_termios(self.fd, &termios); } diff --git a/tests/test_tty.rs b/tests/test_tty.rs index 46f4ae72..0b242553 100644 --- a/tests/test_tty.rs +++ b/tests/test_tty.rs @@ -91,7 +91,7 @@ fn test_ttyport_timeout() { } #[test] -#[cfg(any(target_os = "ios", target_os = "macos"))] +#[cfg(target_vendor = "apple")] fn test_osx_pty_pair() { #![allow(unused_variables)] let (mut master, slave) = TTYPort::pair().expect("Unable to create ptty pair"); @@ -116,7 +116,7 @@ fn test_osx_pty_pair() { // On Mac this should work (in fact used to in b77768a) but now fails. It's not functionality that // should be required, and the ptys work otherwise. So going to just disable this test instead. #[test] -#[cfg_attr(any(target_os = "ios", target_os = "macos"), ignore)] +#[cfg_attr(target_vendor = "apple", ignore)] fn test_ttyport_set_standard_baud() { // `master` must be used here as Dropping it causes slave to be deleted by the OS. // TODO: Convert this to a statement-level attribute once @@ -133,15 +133,10 @@ fn test_ttyport_set_standard_baud() { assert_eq!(slave.baud_rate().unwrap(), 115_200); } -// On mac this fails because you can't set nonstandard baud rates for these virtual ports #[test] #[cfg_attr( - any( - target_os = "ios", - all(target_os = "linux", target_env = "musl"), - target_os = "macos" - ), - ignore + any(all(target_os = "linux", target_env = "musl"), target_vendor = "apple"), + ignore = "fails on Mac because you can't set nonstandard baud rates for these virtual ports" )] fn test_ttyport_set_nonstandard_baud() { // `master` must be used here as Dropping it causes slave to be deleted by the OS. From 0d9bbcbc0f8d2aee3f539f27621b60b8ca73b346 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sun, 20 Apr 2025 02:34:56 +0200 Subject: [PATCH 2/4] Avoid mach2 direct dependency --- CHANGELOG.md | 1 + Cargo.toml | 1 - src/posix/enumerate.rs | 21 ++++++++++++++------- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3028fc6d..b6fa2449 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ project adheres to [Semantic Versioning](https://semver.org/). * Output USB VID and PID in hexadecimal digits in `UsbPortInfo`'s `Debug` representation. [#290](https://github.com/serialport/serialport-rs/pull/290) +* Migrated away from unmaintainted dependency `mach2`. ### Fixed ### Removed diff --git a/Cargo.toml b/Cargo.toml index eeb6a727..23bfa477 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,6 @@ unescaper = "0.1.3" core-foundation = "=0.10.0" core-foundation-sys = "0.8.4" io-kit-sys = "0.4.0" -mach2 = "0.4.1" [target."cfg(windows)".dependencies.windows-sys] version = "0.52.0" diff --git a/src/posix/enumerate.rs b/src/posix/enumerate.rs index 7cbeb644..7df15661 100644 --- a/src/posix/enumerate.rs +++ b/src/posix/enumerate.rs @@ -20,9 +20,20 @@ cfg_if! { use io_kit_sys::serial::keys::*; use io_kit_sys::types::*; use io_kit_sys::usb::lib::*; - use nix::libc::{c_char, c_void}; - use std::ffi::CStr; - use std::mem::MaybeUninit; + use core::ffi::{c_char, c_int, c_uint, c_void, CStr}; + use core::mem::MaybeUninit; + + // NOTE: Do not use `mach` nor `mach2` crates, they're unmaintained, + // and don't work on tvOS/watchOS/visionOS. + // + // Instead, define the types ourselves, we use sufficiently little + // that this is not a very hard task. + #[allow(non_camel_case_types)] + type kern_return_t = c_int; + #[allow(non_camel_case_types)] + type mach_port_t = c_uint; + const KERN_SUCCESS: kern_return_t = 0; + const MACH_PORT_NULL: mach_port_t = 0; } } @@ -265,7 +276,6 @@ fn get_parent_device_by_type( parent_type: *const c_char, ) -> Option { let parent_type = unsafe { CStr::from_ptr(parent_type) }; - use mach2::kern_return::KERN_SUCCESS; let mut device = device; loop { let mut class_name = MaybeUninit::<[c_char; 128]>::uninit(); @@ -379,9 +389,6 @@ cfg_if! { /// Scans the system for serial ports and returns a list of them. /// The `SerialPortInfo` struct contains the name of the port which can be used for opening it. pub fn available_ports() -> Result> { - use mach2::kern_return::KERN_SUCCESS; - use mach2::port::{mach_port_t, MACH_PORT_NULL}; - let mut vec = Vec::new(); unsafe { // Create a dictionary for specifying the search terms against the IOService From bc212fb06b43e45a084e1edb08f6931739278a65 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sun, 20 Apr 2025 02:25:03 +0200 Subject: [PATCH 3/4] Bump MSRV to 1.71 Required for `extern "C-unwind"`. --- .github/workflows/ci.yaml | 14 +++++++------- Cargo.toml | 2 +- README.md | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1c0d5ce8..87597f9b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -67,7 +67,7 @@ jobs: disable_extra_builds: true disable_tests: true target: aarch64-apple-darwin - toolchain: "1.59.0" + toolchain: "1.71.0" msrv-arm-linux-androideabi: uses: ./.github/workflows/build.yaml @@ -75,7 +75,7 @@ jobs: disable_extra_builds: true disable_tests: true target: arm-linux-androideabi - toolchain: "1.59.0" + toolchain: "1.71.0" msrv-x86_64-unknown-freebsd: uses: ./.github/workflows/build.yaml @@ -83,7 +83,7 @@ jobs: disable_extra_builds: true disable_tests: true target: x86_64-unknown-freebsd - toolchain: "1.59.0" + toolchain: "1.71.0" msrv-x86_64-unknown-linux-gnu: uses: ./.github/workflows/build.yaml @@ -92,7 +92,7 @@ jobs: disable_tests: true extra_packages: libudev-dev target: x86_64-unknown-linux-gnu - toolchain: "1.59.0" + toolchain: "1.71.0" msrv-x86_64-unknown-linux-musl: uses: ./.github/workflows/build.yaml @@ -101,7 +101,7 @@ jobs: disable_tests: true extra_packages: gcc-aarch64-linux-gnu target: aarch64-unknown-linux-musl - toolchain: "1.59.0" + toolchain: "1.71.0" msrv-x86_64-pc-windows-msvc: uses: ./.github/workflows/build.yaml @@ -110,7 +110,7 @@ jobs: disable_tests: true runs_on: windows-2025 target: x86_64-pc-windows-msvc - toolchain: "1.59.0" + toolchain: "1.71.0" msrv-x86_64-unknown-netbsd: uses: ./.github/workflows/build.yaml @@ -118,7 +118,7 @@ jobs: disable_extra_builds: true disable_tests: true target: x86_64-unknown-netbsd - toolchain: "1.59.0" + toolchain: "1.71.0" # -------------------------------------------------------------------------- # Semantic Versioning diff --git a/Cargo.toml b/Cargo.toml index 23bfa477..6daaefb2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ authors = [ "Jesse Braham ", ] edition = "2021" -rust-version = "1.59.0" +rust-version = "1.71.0" description = "A cross-platform low-level serial port library." documentation = "https://docs.rs/serialport" repository = "https://github.com/serialport/serialport-rs" diff --git a/README.md b/README.md index 55ed69a2..fb523f69 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![crates.io version badge](https://img.shields.io/crates/v/serialport.svg)](https://crates.io/crates/serialport) [![Documentation](https://docs.rs/serialport/badge.svg)](https://docs.rs/serialport) [![GitHub workflow status](https://img.shields.io/github/actions/workflow/status/serialport/serialport-rs/ci.yaml?branch=main&logo=github)](https://github.com/serialport/serialport-rs/actions) -[![Minimum Stable Rust Version](https://img.shields.io/badge/Rust-1.59.0-blue?logo=rust)](https://blog.rust-lang.org/2022/02/24/Rust-1.59.0.html) +[![Minimum Stable Rust Version](https://img.shields.io/badge/Rust-1.71.0-blue?logo=rust)](https://blog.rust-lang.org/2023/07/13/Rust-1.71.0/) # Introduction @@ -116,7 +116,7 @@ can help debug software or hardware errors. # Dependencies -Rust versions 1.59.0 and higher are supported by the library itself. There are +Rust versions 1.71.0 and higher are supported by the library itself. There are examples requiring newer versions of Rust. For GNU/Linux `pkg-config` headers are required: From 85c1e68f67b09f2378a04e499317086f885e978f Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sun, 20 Apr 2025 02:25:08 +0200 Subject: [PATCH 4/4] Use objc2-core-foundation and objc2-io-kit crates --- CHANGELOG.md | 2 + Cargo.toml | 19 +++++-- src/posix/enumerate.rs | 125 +++++++++++++++++------------------------ 3 files changed, 69 insertions(+), 77 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6fa2449..b0a3d0b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ project adheres to [Semantic Versioning](https://semver.org/). representation. [#290](https://github.com/serialport/serialport-rs/pull/290) * Migrated away from unmaintainted dependency `mach2`. +* Migrated from `io-kit-sys` to `objc2-io-kit`. +* Migrated from `core-foundation` to `objc2-core-foundation`. ### Fixed ### Removed diff --git a/Cargo.toml b/Cargo.toml index 6daaefb2..383cc476 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,10 +23,21 @@ libudev = { version = "0.3.0", optional = true } unescaper = "0.1.3" [target.'cfg(target_vendor = "apple")'.dependencies] -# TODO: Remove pinning this dependency when we are bumping our MSRV. -core-foundation = "=0.10.0" -core-foundation-sys = "0.8.4" -io-kit-sys = "0.4.0" +objc2-io-kit = { version = "0.3.2", default-features = false, features = [ + "std", + "libc", + "usb", + "serial", + "IOUSBLib", + "IOUSBHostFamilyDefinitions", +] } +objc2-core-foundation = { version = "0.3.2", default-features = false, features = [ + "std", + "CFBase", + "CFDictionary", + "CFNumber", + "CFString", +] } [target."cfg(windows)".dependencies.windows-sys] version = "0.52.0" diff --git a/src/posix/enumerate.rs b/src/posix/enumerate.rs index 7df15661..e2ade32c 100644 --- a/src/posix/enumerate.rs +++ b/src/posix/enumerate.rs @@ -8,21 +8,22 @@ cfg_if! { cfg_if! { if #[cfg(target_vendor = "apple")] { - use core_foundation::base::CFType; - use core_foundation::base::TCFType; - use core_foundation::dictionary::CFDictionary; - use core_foundation::dictionary::CFMutableDictionary; - use core_foundation::number::CFNumber; - use core_foundation::string::CFString; - use core_foundation_sys::base::{kCFAllocatorDefault, CFRetain}; - use io_kit_sys::*; - use io_kit_sys::keys::*; - use io_kit_sys::serial::keys::*; - use io_kit_sys::types::*; - use io_kit_sys::usb::lib::*; - use core::ffi::{c_char, c_int, c_uint, c_void, CStr}; + use core::ffi::{c_char, c_int, c_uint, CStr}; use core::mem::MaybeUninit; + use objc2_core_foundation::{ + kCFAllocatorDefault, CFMutableDictionary, CFNumber, CFRetained, CFString, CFType, + }; + #[allow(deprecated)] + use objc2_io_kit::{kIOMasterPortDefault, IOMasterPort}; + use objc2_io_kit::{ + io_object_t, io_registry_entry_t, kIOServiceClass, kIOUSBDeviceClassName, + kIOUSBHostInterfaceClassName, IOIteratorNext, IOObjectGetClass, IOObjectRelease, + IORegistryEntryCreateCFProperties, IORegistryEntryCreateCFProperty, + IORegistryEntryGetParentEntry, IOServiceGetMatchingServices, IOServiceMatching, + kIOSerialBSDServiceValue, kIOSerialBSDTypeKey, kIOSerialBSDAllTypes, + }; + // NOTE: Do not use `mach` nor `mach2` crates, they're unmaintained, // and don't work on tvOS/watchOS/visionOS. // @@ -273,13 +274,12 @@ fn parse_modalias(moda: &str) -> Option { #[cfg(target_vendor = "apple")] fn get_parent_device_by_type( device: io_object_t, - parent_type: *const c_char, + parent_type: &CStr, ) -> Option { - let parent_type = unsafe { CStr::from_ptr(parent_type) }; let mut device = device; loop { let mut class_name = MaybeUninit::<[c_char; 128]>::uninit(); - unsafe { IOObjectGetClass(device, class_name.as_mut_ptr() as *mut c_char) }; + unsafe { IOObjectGetClass(device, class_name.as_mut_ptr()) }; let class_name = unsafe { class_name.assume_init() }; let name = unsafe { CStr::from_ptr(&class_name[0]) }; if name == parent_type { @@ -287,8 +287,11 @@ fn get_parent_device_by_type( } let mut parent = MaybeUninit::uninit(); if unsafe { - IORegistryEntryGetParentEntry(device, kIOServiceClass, parent.as_mut_ptr()) - != KERN_SUCCESS + IORegistryEntryGetParentEntry( + device, + kIOServiceClass.as_ptr() as _, + parent.as_mut_ptr(), + ) != KERN_SUCCESS } { return None; } @@ -300,24 +303,17 @@ fn get_parent_device_by_type( #[allow(non_upper_case_globals)] /// Returns a specific property of the given device as an integer. fn get_int_property(device_type: io_registry_entry_t, property: &str) -> Result { - let cf_property = CFString::new(property); - - let cf_type_ref = unsafe { - IORegistryEntryCreateCFProperty( - device_type, - cf_property.as_concrete_TypeRef(), - kCFAllocatorDefault, - 0, - ) - }; - if cf_type_ref.is_null() { - return Err(Error::new(ErrorKind::Unknown, "Failed to get property")); + let cf_property = CFString::from_str(property); + + let cf_type = unsafe { + IORegistryEntryCreateCFProperty(device_type, Some(&cf_property), kCFAllocatorDefault, 0) } + .ok_or_else(|| Error::new(ErrorKind::Unknown, "Failed to get property"))?; - let cf_type = unsafe { CFType::wrap_under_create_rule(cf_type_ref) }; cf_type .downcast::() - .and_then(|n| n.to_i64()) + .ok() + .and_then(|n| n.as_i64()) .map(|n| n as u32) .ok_or(Error::new( ErrorKind::Unknown, @@ -328,23 +324,16 @@ fn get_int_property(device_type: io_registry_entry_t, property: &str) -> Result< #[cfg(target_vendor = "apple")] /// Returns a specific property of the given device as a string. fn get_string_property(device_type: io_registry_entry_t, property: &str) -> Result { - let cf_property = CFString::new(property); - - let cf_type_ref = unsafe { - IORegistryEntryCreateCFProperty( - device_type, - cf_property.as_concrete_TypeRef(), - kCFAllocatorDefault, - 0, - ) - }; - if cf_type_ref.is_null() { - return Err(Error::new(ErrorKind::Unknown, "Failed to get property")); + let cf_property = CFString::from_str(property); + + let cf_type = unsafe { + IORegistryEntryCreateCFProperty(device_type, Some(&cf_property), kCFAllocatorDefault, 0) } + .ok_or_else(|| Error::new(ErrorKind::Unknown, "Failed to get property"))?; - let cf_type = unsafe { CFType::wrap_under_create_rule(cf_type_ref) }; cf_type .downcast::() + .ok() .map(|s| s.to_string()) .ok_or(Error::new(ErrorKind::Unknown, "Failed to get string value")) } @@ -353,8 +342,9 @@ fn get_string_property(device_type: io_registry_entry_t, property: &str) -> Resu /// Determine the serial port type based on the service object (like that returned by /// `IOIteratorNext`). Specific properties are extracted for USB devices. fn port_type(service: io_object_t) -> SerialPortType { - let bluetooth_device_class_name = b"IOBluetoothSerialClient\0".as_ptr() as *const c_char; - let usb_device_class_name = b"IOUSBHostInterface\0".as_ptr() as *const c_char; + let bluetooth_device_class_name = + CStr::from_bytes_with_nul(b"IOBluetoothSerialClient\0").unwrap(); + let usb_device_class_name = kIOUSBHostInterfaceClassName; let legacy_usb_device_class_name = kIOUSBDeviceClassName; let maybe_usb_device = get_parent_device_by_type(service, usb_device_class_name) @@ -388,29 +378,24 @@ cfg_if! { if #[cfg(target_vendor = "apple")] { /// Scans the system for serial ports and returns a list of them. /// The `SerialPortInfo` struct contains the name of the port which can be used for opening it. + #[allow(deprecated)] pub fn available_ports() -> Result> { let mut vec = Vec::new(); unsafe { // Create a dictionary for specifying the search terms against the IOService - let classes_to_match = IOServiceMatching(kIOSerialBSDServiceValue); - if classes_to_match.is_null() { - return Err(Error::new( - ErrorKind::Unknown, - "IOServiceMatching returned a NULL dictionary.", - )); - } - let mut classes_to_match = CFMutableDictionary::wrap_under_create_rule(classes_to_match); + let classes_to_match = IOServiceMatching(kIOSerialBSDServiceValue.as_ptr()) + .ok_or_else(|| Error::new(ErrorKind::Unknown, "IOServiceMatching returned a NULL dictionary."))?; + let classes_to_match = classes_to_match.cast_unchecked::(); // Populate the search dictionary with a single key/value pair indicating that we're // searching for serial devices matching the RS232 device type. - let search_key = CStr::from_ptr(kIOSerialBSDTypeKey); - let search_key = CFString::from_static_string(search_key.to_str().map_err(|_| Error::new(ErrorKind::Unknown, "Failed to convert search key string"))?); - let search_value = CStr::from_ptr(kIOSerialBSDAllTypes); - let search_value = CFString::from_static_string(search_value.to_str().map_err(|_| Error::new(ErrorKind::Unknown, "Failed to convert search key string"))?); - classes_to_match.set(search_key, search_value); + let search_key = CFString::from_static_str(kIOSerialBSDTypeKey.to_str().map_err(|_| Error::new(ErrorKind::Unknown, "Failed to convert search key string"))?); + let search_value = CFString::from_static_str(kIOSerialBSDAllTypes.to_str().map_err(|_| Error::new(ErrorKind::Unknown, "Failed to convert search key string"))?); + classes_to_match.set(&search_key, &search_value); // Get an interface to IOKit let mut master_port: mach_port_t = MACH_PORT_NULL; + #[allow(deprecated)] let mut kern_result = IOMasterPort(MACH_PORT_NULL, &mut master_port); if kern_result != KERN_SUCCESS { return Err(Error::new( @@ -419,17 +404,11 @@ cfg_if! { )); } - // Run the search. IOServiceGetMatchingServices consumes one reference count of - // classes_to_match, so explicitly retain. - // - // TODO: We could also just mem::forget classes_to_match like in - // TCFType::into_CFType. Is there a special reason that there is no - // TCFType::into_concrete_TypeRef()? - CFRetain(classes_to_match.as_CFTypeRef()); + // Run the search. let mut matching_services = MaybeUninit::uninit(); kern_result = IOServiceGetMatchingServices( kIOMasterPortDefault, - classes_to_match.as_concrete_TypeRef(), + Some(classes_to_match.as_opaque().into()), matching_services.as_mut_ptr(), ); if kern_result != KERN_SUCCESS { @@ -467,15 +446,15 @@ cfg_if! { // properties dict has been allocated and we as the caller are in charge of // releasing it. let props = props.assume_init(); - let props: CFDictionary = CFDictionary::wrap_under_create_rule(props); + let props: CFRetained = CFRetained::from_raw(core::ptr::NonNull::new(props).unwrap()); + let props = props.cast_unchecked::(); for key in ["IOCalloutDevice", "IODialinDevice"].iter() { - let cf_key = CFString::new(key); + let cf_key = CFString::from_str(key); - if let Some(cf_ref) = props.find(cf_key) { - let cf_type = CFType::wrap_under_get_rule(*cf_ref); + if let Some(cf_type) = props.get(&cf_key) { match cf_type - .downcast::() + .downcast_ref::() .map(|s| s.to_string()) { Some(path) => {