diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 18c75d7..5cff34c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,12 +38,12 @@ jobs: platform: Linux ARM64 lib_name: libgdserial.so use_cross: true - - os: macos-13 + - os: macos-15-intel target: x86_64-apple-darwin platform: macOS Intel lib_name: libgdserial.dylib use_cross: false - - os: macos-15 + - os: macos-latest target: aarch64-apple-darwin platform: macOS Apple Silicon lib_name: libgdserial.dylib diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8eec953..02c4e3d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,11 +35,11 @@ jobs: target: aarch64-unknown-linux-gnu lib_name: libgdserial.so use_cross: true - - os: macos-13 + - os: macos-15-intel target: x86_64-apple-darwin lib_name: libgdserial.dylib use_cross: false - - os: macos-15 + - os: macos-latest target: aarch64-apple-darwin lib_name: libgdserial.dylib use_cross: false diff --git a/Cargo.lock b/Cargo.lock index 9e6c471..d57fd48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,8 +47,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "gdextension-api" -version = "0.2.2" -source = "git+https://github.com/godot-rust/godot4-prebuilt?branch=releases#09432b4c2c16f044f0296f96512661ea106003ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e25d88dabe9fdb2e064cb545312178ec4025eefb62d9a3bbce1769088e2c381d" [[package]] name = "gdserial" @@ -66,8 +67,9 @@ checksum = "50a99dbe56b72736564cfa4b85bf9a33079f16ae8b74983ab06af3b1a3696b11" [[package]] name = "godot" -version = "0.3.3" -source = "git+https://github.com/godot-rust/gdext?branch=master#c6be825f14e443b20644b3b3e7048f2844784d61" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f6915102c188fd5548ceb08a87e292529498952b5c4700287db2bf28e48e70" dependencies = [ "godot-core", "godot-macros", @@ -75,21 +77,24 @@ dependencies = [ [[package]] name = "godot-bindings" -version = "0.3.3" -source = "git+https://github.com/godot-rust/gdext?branch=master#c6be825f14e443b20644b3b3e7048f2844784d61" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273f5cefa14e32fa67ba9af88fd6cdd10ddf7e6fc7f75c7c641c704bc096a9c" dependencies = [ "gdextension-api", ] [[package]] name = "godot-cell" -version = "0.3.3" -source = "git+https://github.com/godot-rust/gdext?branch=master#c6be825f14e443b20644b3b3e7048f2844784d61" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e6d8f2ec5d2d9da9d7e1b4ad96c7a3cf4daa7c9c8add3bbd2ee3b2e19e28c5" [[package]] name = "godot-codegen" -version = "0.3.3" -source = "git+https://github.com/godot-rust/gdext?branch=master#c6be825f14e443b20644b3b3e7048f2844784d61" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "195e88c3e3325374c58814aeefa3090376bd1f7f0af4cc4bbd7babafa5469c9b" dependencies = [ "godot-bindings", "heck", @@ -101,8 +106,9 @@ dependencies = [ [[package]] name = "godot-core" -version = "0.3.3" -source = "git+https://github.com/godot-rust/gdext?branch=master#c6be825f14e443b20644b3b3e7048f2844784d61" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44b717a7a6f9fa40678ceda6ea0356e2beb059c276b93f56881ebe01e0d023f" dependencies = [ "glam", "godot-bindings", @@ -113,8 +119,9 @@ dependencies = [ [[package]] name = "godot-ffi" -version = "0.3.3" -source = "git+https://github.com/godot-rust/gdext?branch=master#c6be825f14e443b20644b3b3e7048f2844784d61" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b741a136345a057869703bb474da3657a71cc87e92d7a1e4e21544eaf5a317fc" dependencies = [ "godot-bindings", "godot-codegen", @@ -124,8 +131,9 @@ dependencies = [ [[package]] name = "godot-macros" -version = "0.3.3" -source = "git+https://github.com/godot-rust/gdext?branch=master#c6be825f14e443b20644b3b3e7048f2844784d61" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6fc4dde5748e86417ee875e8ef4396a103b29b5189b54a10863afb9604c646" dependencies = [ "godot-bindings", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 94dfd93..0d7d180 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ authors = ["Sujith Christopher"] crate-type = ["cdylib"] [dependencies] -godot = { git = "https://github.com/godot-rust/gdext", branch = "master" } +godot = "0.4.4" serialport = "4.7.2" [profile.release] diff --git a/addons/gdserial/bin/windows-x86_64/gdserial.dll b/addons/gdserial/bin/windows-x86_64/gdserial.dll index 765f97b..65c2013 100644 Binary files a/addons/gdserial/bin/windows-x86_64/gdserial.dll and b/addons/gdserial/bin/windows-x86_64/gdserial.dll differ diff --git a/src/lib.rs b/src/lib.rs index 000a21a..6885f46 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,31 +1,36 @@ use godot::prelude::*; -use serialport::{SerialPort, SerialPortType, DataBits, Parity, StopBits, FlowControl, ErrorKind}; -use std::time::Duration; +use serialport::{DataBits, ErrorKind, FlowControl, Parity, SerialPort, SerialPortType, StopBits}; use std::io::{self, Read}; +use std::time::Duration; -fn get_usb_device_name(vid: u16, pid: u16, manufacturer: &Option, product: &Option) -> String { +fn get_usb_device_name( + vid: u16, + pid: u16, + manufacturer: &Option, + product: &Option, +) -> String { // Build device name from available USB descriptor information let mut parts = Vec::new(); - + // Add manufacturer if available if let Some(mfg) = manufacturer { if !mfg.trim().is_empty() { parts.push(mfg.trim().to_string()); } } - + // Add product if available if let Some(prod) = product { if !prod.trim().is_empty() { parts.push(prod.trim().to_string()); } } - + // If we have any descriptor strings, use them if !parts.is_empty() { return parts.join(" "); } - + // Otherwise, show VID/PID for identification format!("USB Serial (VID: 0x{:04X}, PID: 0x{:04X})", vid, pid) } @@ -47,7 +52,7 @@ pub struct GdSerial { parity: Parity, flow_control: FlowControl, timeout: Duration, - is_connected: bool, // Track connection state + is_connected: bool, // Track connection state } #[godot_api] @@ -76,29 +81,31 @@ impl GdSerial { ErrorKind::NoDevice => true, ErrorKind::Io(io_error) => { // Check for common disconnection errors - matches!(io_error, - io::ErrorKind::BrokenPipe | - io::ErrorKind::ConnectionAborted | - io::ErrorKind::NotConnected | - io::ErrorKind::UnexpectedEof | - io::ErrorKind::PermissionDenied // Can occur on disconnect + matches!( + io_error, + io::ErrorKind::BrokenPipe + | io::ErrorKind::ConnectionAborted + | io::ErrorKind::NotConnected + | io::ErrorKind::UnexpectedEof + | io::ErrorKind::PermissionDenied // Can occur on disconnect ) } - _ => false + _ => false, } } - + /// Check if IO error indicates disconnection fn is_io_disconnection_error(error: &io::Error) -> bool { - matches!(error.kind(), - io::ErrorKind::BrokenPipe | - io::ErrorKind::ConnectionAborted | - io::ErrorKind::NotConnected | - io::ErrorKind::UnexpectedEof | - io::ErrorKind::PermissionDenied + matches!( + error.kind(), + io::ErrorKind::BrokenPipe + | io::ErrorKind::ConnectionAborted + | io::ErrorKind::NotConnected + | io::ErrorKind::UnexpectedEof + | io::ErrorKind::PermissionDenied ) } - + /// Handle potential disconnection by closing the port if device is no longer available fn handle_potential_disconnection(&mut self, error: &serialport::Error) { if Self::is_disconnection_error(error) { @@ -107,7 +114,7 @@ impl GdSerial { self.is_connected = false; } } - + /// Handle potential disconnection for IO errors fn handle_potential_io_disconnection(&mut self, error: &io::Error) { if Self::is_io_disconnection_error(error) { @@ -116,7 +123,7 @@ impl GdSerial { self.is_connected = false; } } - + /// Actively test if the port is still connected by attempting a non-destructive operation fn test_connection(&mut self) -> bool { if let Some(ref mut port) = self.port { @@ -135,36 +142,44 @@ impl GdSerial { false } } - + #[func] - pub fn list_ports(&self) -> Dictionary { - let mut ports_dict = Dictionary::new(); - + pub fn list_ports(&self) -> VarDictionary { + let mut ports_dict = VarDictionary::new(); + match serialport::available_ports() { Ok(ports) => { for (i, port) in ports.iter().enumerate() { - let mut port_info = Dictionary::new(); + let mut port_info = VarDictionary::new(); port_info.set(GString::from("port_name"), GString::from(&port.port_name)); - + let (port_type, device_name) = match &port.port_type { SerialPortType::UsbPort(usb_info) => { - let port_type = format!("USB - VID: {:04X}, PID: {:04X}", - usb_info.vid, usb_info.pid); + let port_type = format!( + "USB - VID: {:04X}, PID: {:04X}", + usb_info.vid, usb_info.pid + ); let device_name = get_usb_device_name( - usb_info.vid, - usb_info.pid, - &usb_info.manufacturer, - &usb_info.product + usb_info.vid, + usb_info.pid, + &usb_info.manufacturer, + &usb_info.product, ); (port_type, device_name) } - SerialPortType::PciPort => ("PCI".to_string(), "PCI Serial Port".to_string()), - SerialPortType::BluetoothPort => ("Bluetooth".to_string(), "Bluetooth Serial Port".to_string()), - SerialPortType::Unknown => ("Unknown".to_string(), "Unknown Serial Device".to_string()), + SerialPortType::PciPort => { + ("PCI".to_string(), "PCI Serial Port".to_string()) + } + SerialPortType::BluetoothPort => { + ("Bluetooth".to_string(), "Bluetooth Serial Port".to_string()) + } + SerialPortType::Unknown => { + ("Unknown".to_string(), "Unknown Serial Device".to_string()) + } }; - - port_info.set(GString::from("port_type"), GString::from(port_type)); - port_info.set(GString::from("device_name"), GString::from(device_name)); + + port_info.set(GString::from("port_type"), GString::from(&port_type)); + port_info.set(GString::from("device_name"), GString::from(&device_name)); ports_dict.set(i as i32, port_info); } } @@ -172,15 +187,15 @@ impl GdSerial { godot_error!("Failed to list ports: {}", e); } } - + ports_dict } - + #[func] pub fn set_port(&mut self, port_name: GString) { self.port_name = port_name.to_string(); } - + #[func] pub fn set_baud_rate(&mut self, baud_rate: u32) { self.baud_rate = baud_rate; @@ -191,13 +206,13 @@ impl GdSerial { match data_bits { 6 => { self.data_bits = DataBits::Six; - }, + } 7 => { self.data_bits = DataBits::Seven; - }, + } 8 => { self.data_bits = DataBits::Eight; - }, + } _ => { godot_error!("Data bits must be between 6 and 8") } @@ -209,7 +224,7 @@ impl GdSerial { match parity { false => { self.parity = Parity::None; - }, + } true => { self.parity = Parity::Odd; } @@ -221,10 +236,10 @@ impl GdSerial { match stop_bits { 1 => { self.stop_bits = StopBits::One; - }, + } 2 => { self.stop_bits = StopBits::Two; - }, + } _ => { godot_error!("Stop bits must be between 1 and 2") } @@ -236,31 +251,31 @@ impl GdSerial { match flow_control { 0 => { self.flow_control = FlowControl::None; - }, + } 1 => { self.flow_control = FlowControl::Software; - }, + } 2 => { self.flow_control = FlowControl::Hardware; - }, + } _ => { godot_error!("Data bits must be between 0 and 2") } } } - + #[func] pub fn set_timeout(&mut self, timeout_ms: u32) { self.timeout = Duration::from_millis(timeout_ms as u64); } - + #[func] pub fn open(&mut self) -> bool { if self.port_name.is_empty() { godot_error!("Port name not set"); return false; } - + match serialport::new(&self.port_name, self.baud_rate) .timeout(self.timeout) .data_bits(self.data_bits) @@ -281,7 +296,7 @@ impl GdSerial { } } } - + #[func] pub fn close(&mut self) { if self.port.is_some() { @@ -290,7 +305,7 @@ impl GdSerial { // Port closed - removed print output per issue #1 } } - + #[func] pub fn is_open(&mut self) -> bool { // Always test the actual connection state @@ -300,7 +315,7 @@ impl GdSerial { false } } - + #[func] pub fn write(&mut self, data: PackedByteArray) -> bool { // First check if connected @@ -308,21 +323,19 @@ impl GdSerial { godot_error!("Port not connected"); return false; } - + match &mut self.port { Some(port) => { let bytes = data.to_vec(); match port.write_all(&bytes) { - Ok(_) => { - match port.flush() { - Ok(_) => true, - Err(e) => { - self.handle_potential_io_disconnection(&e); - godot_error!("Failed to flush port: {}", e); - false - } + Ok(_) => match port.flush() { + Ok(_) => true, + Err(e) => { + self.handle_potential_io_disconnection(&e); + godot_error!("Failed to flush port: {}", e); + false } - } + }, Err(e) => { self.handle_potential_io_disconnection(&e); godot_error!("Failed to write to port: {}", e); @@ -336,14 +349,14 @@ impl GdSerial { } } } - + #[func] pub fn write_string(&mut self, data: GString) -> bool { let bytes = data.to_string().into_bytes(); let packed_bytes = PackedByteArray::from(&bytes[..]); self.write(packed_bytes) } - + #[func] pub fn writeline(&mut self, data: GString) -> bool { let data_with_newline = format!("{}\n", data.to_string()); @@ -351,14 +364,14 @@ impl GdSerial { let packed_bytes = PackedByteArray::from(&bytes[..]); self.write(packed_bytes) } - + #[func] pub fn read(&mut self, size: u32) -> PackedByteArray { // First check if connected if !self.test_connection() { return PackedByteArray::new(); } - + match &mut self.port { Some(port) => { let mut buffer = vec![0; size as usize]; @@ -369,7 +382,9 @@ impl GdSerial { } Err(e) => { // Don't treat timeout as disconnection - if e.kind() != io::ErrorKind::TimedOut && e.kind() != io::ErrorKind::WouldBlock { + if e.kind() != io::ErrorKind::TimedOut + && e.kind() != io::ErrorKind::WouldBlock + { self.handle_potential_io_disconnection(&e); godot_error!("Failed to read from port: {}", e); } @@ -383,31 +398,31 @@ impl GdSerial { } } } - + #[func] pub fn read_string(&mut self, size: u32) -> GString { let bytes = self.read(size); match String::from_utf8(bytes.to_vec()) { - Ok(string) => GString::from(string), + Ok(string) => GString::from(&string), Err(e) => { godot_error!("Failed to convert bytes to string: {}", e); GString::new() } } } - + #[func] pub fn readline(&mut self) -> GString { // First check if connected if !self.test_connection() { return GString::new(); } - + match &mut self.port { Some(port) => { let mut line = String::new(); let mut byte = [0u8; 1]; - + loop { match port.read(&mut byte) { Ok(0) => { @@ -430,7 +445,7 @@ impl GdSerial { if Self::is_io_disconnection_error(&e) { self.handle_potential_io_disconnection(&e); } - + if line.is_empty() && e.kind() != io::ErrorKind::WouldBlock { godot_error!("Failed to read line: {}", e); return GString::new(); @@ -440,8 +455,8 @@ impl GdSerial { } } } - - GString::from(line) + + GString::from(&line) } None => { godot_error!("Port not open"); @@ -449,14 +464,14 @@ impl GdSerial { } } } - + #[func] pub fn bytes_available(&mut self) -> u32 { // First check if connected if !self.test_connection() { return 0; } - + match &mut self.port { Some(port) => { match port.bytes_to_read() { @@ -464,35 +479,36 @@ impl GdSerial { Err(e) => { // Any error in bytes_to_read likely means the port is in a bad state // Mark as disconnected regardless of error type - godot_error!("Failed to get available bytes: {} - marking port as disconnected", e); + godot_error!( + "Failed to get available bytes: {} - marking port as disconnected", + e + ); self.port = None; self.is_connected = false; 0 } } } - None => 0 + None => 0, } } - + #[func] pub fn clear_buffer(&mut self) -> bool { // First check if connected if !self.test_connection() { return false; } - + match &mut self.port { - Some(port) => { - match port.clear(serialport::ClearBuffer::All) { - Ok(_) => true, - Err(e) => { - self.handle_potential_disconnection(&e); - godot_error!("Failed to clear buffer: {}", e); - false - } + Some(port) => match port.clear(serialport::ClearBuffer::All) { + Ok(_) => true, + Err(e) => { + self.handle_potential_disconnection(&e); + godot_error!("Failed to clear buffer: {}", e); + false } - } + }, None => { godot_error!("Port not open"); false @@ -500,4 +516,3 @@ impl GdSerial { } } } -