From 779c2f1889286ec49c193f845db9ed5d3d724fe0 Mon Sep 17 00:00:00 2001 From: jthomas39 Date: Sat, 28 Feb 2026 21:35:36 -0600 Subject: [PATCH 1/4] Added more logging to the relay --- relay/src/state.rs | 137 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 124 insertions(+), 13 deletions(-) diff --git a/relay/src/state.rs b/relay/src/state.rs index 05a0e5f..e64a84d 100644 --- a/relay/src/state.rs +++ b/relay/src/state.rs @@ -1,6 +1,6 @@ use anyhow::{anyhow, bail, Result}; use iced::time::Duration; -use interprocess::local_socket::{traits::Listener, GenericNamespaced, ListenerOptions, ToNsName}; +use interprocess::local_socket::{traits::{Listener, Stream}, GenericNamespaced, GenericFilePath, ListenerOptions, NameType, ToFsName, ToNsName}; use std::io::{BufRead, BufReader, Write}; use std::net::TcpStream; use std::thread::{spawn, JoinHandle}; @@ -157,11 +157,33 @@ impl State { let handle = spawn(move || { let printname = "baton.sock"; - let name = printname.to_ns_name::().map_err(|e| { - relay_log(&format!("Failed to convert name to NsName: {}", e)); - anyhow!("Name conversion failed") - })?; - let opts = ListenerOptions::new().name(name); + + // Pre-build an owned filesystem path string so any FsName conversion borrows an owned value + // that lives for the duration of this closure (avoids temporary-borrow lifetime issues). + let mut tmp_path_buf = std::env::temp_dir(); + tmp_path_buf.push(printname); + let temp_path_string = tmp_path_buf.to_string_lossy().into_owned(); + + // Prefer namespaced sockets when supported; otherwise fall back to filesystem-backed socket + // in the system temp dir. Log which path form is selected and any conversion errors. + let opts = if GenericNamespaced::is_supported() { + // namespaced socket + relay_log(&format!("IPC server using namespaced socket: {}", printname)); + let ns_name = printname.to_ns_name::().map_err(|e| { + relay_log(&format!("Failed to convert name to NsName: {}", e)); + anyhow!("Name conversion failed") + })?; + ListenerOptions::new().name(ns_name) + } else { + // filesystem-backed socket in temp dir + relay_log(&format!("IPC server using filesystem socket path: {}", temp_path_string)); + let fs_name = temp_path_string.as_str().to_fs_name::().map_err(|e| { + relay_log(&format!("Failed to convert name to FsName: {}", e)); + anyhow!("Name conversion failed") + })?; + ListenerOptions::new().name(fs_name) + }; + let listener = opts.create_sync().map_err(|e| { relay_log(&format!("Failed to create IPC listener: {}", e)); anyhow!("Listener create failed") @@ -177,10 +199,71 @@ impl State { Ok(conn) => { relay_log("IPC incoming connection accepted"); let mut conn = BufReader::new(conn); - let _ = child_bichannel.set_is_conn_to_endpoint(true); + + // Perform a blocking handshake with the client (baton). + // The baton writes a Hello line immediately after connecting and + // expects a reply before switching to non-blocking mode. Read + // that initial line, reply, then switch the underlying stream + // to non-blocking for normal operation. + match conn.read_line(&mut buffer) { + Ok(0) => { + relay_log("Handshake read returned 0 bytes; closing connection"); + buffer.clear(); + if let Err(e) = child_bichannel.send_to_parent(FromIpcThreadMessage::BatonShutdown) { + relay_log(&format!("Failed to send BatonShutdown during handshake: {}", e)); + } + continue; + } + Ok(_) => { + let h = buffer.trim_end().to_string(); + relay_log(&format!("IPC handshake received: {}", h)); + buffer.clear(); + } + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { + // Unlikely for blocking socket, but be tolerant: treat as no handshake + relay_log("Handshake read would block; treating as failed handshake"); + if let Err(e) = child_bichannel.send_to_parent(FromIpcThreadMessage::BatonShutdown) { + relay_log(&format!("Failed to send BatonShutdown after WouldBlock handshake: {}", e)); + } + continue; + } + Err(e) => { + relay_log(&format!("Handshake read error: {}", e)); + if let Err(e2) = child_bichannel.send_to_parent(FromIpcThreadMessage::BatonShutdown) { + relay_log(&format!("Failed to send BatonShutdown after handshake error: {}", e2)); + } + continue; + } + } + + // Send handshake reply + if let Err(e) = conn.get_mut().write_all(b"Hello, from the relay prototype (Rust)!\n") { + relay_log(&format!("Handshake reply failed: {}", e)); + if let Err(e2) = child_bichannel.send_to_parent(FromIpcThreadMessage::BatonShutdown) { + relay_log(&format!("Failed to send BatonShutdown after reply fail: {}", e2)); + } + continue; + } else { + relay_log("Handshake reply sent"); + } + + // Switch connection to non-blocking mode for the main receive loop + if let Err(e) = conn.get_mut().set_nonblocking(true) { + relay_log(&format!("Failed to set connection non-blocking: {}", e)); + // continue anyway; subsequent reads will attempt and handle WouldBlock + } else { + relay_log("Set accepted connection to non-blocking"); + } + + // mark connected and log result + match child_bichannel.set_is_conn_to_endpoint(true) { + Ok(_) => relay_log("Marked child_bichannel connected -> true"), + Err(e) => relay_log(&format!("Failed to mark child_bichannel connected: {}", e)), + } loop { if child_bichannel.is_killswitch_engaged() { + relay_log("Child killswitch engaged; breaking connection loop"); break; } @@ -191,28 +274,55 @@ impl State { match conn.read_line(&mut buffer) { Ok(0) => { + // Connection closed by client — notify parent that baton disconnected + relay_log("IPC read returned Ok(0) — connection closed by peer (EOF)"); buffer.clear(); + if let Err(e) = child_bichannel.send_to_parent(FromIpcThreadMessage::BatonShutdown) { + relay_log(&format!("Failed to send BatonShutdown on EOF: {}", e)); + } break; } Ok(_) => { let _ = buffer.pop(); // remove trailing newline if present if buffer.starts_with("SHUTDOWN") { - let _ = child_bichannel.send_to_parent(FromIpcThreadMessage::BatonShutdown); + relay_log("IPC received SHUTDOWN message from client"); + if let Err(e) = child_bichannel.send_to_parent(FromIpcThreadMessage::BatonShutdown) { + relay_log(&format!("Failed to send BatonShutdown on SHUTDOWN: {}", e)); + } break; } else { - let _ = child_bichannel.send_to_parent(FromIpcThreadMessage::BatonData(buffer.clone())); + // forward to parent and log on error + if let Err(e) = child_bichannel.send_to_parent(FromIpcThreadMessage::BatonData(buffer.clone())) { + relay_log(&format!("Failed to forward BatonData to parent: {}", e)); + } else { + // update last seen timestamp for diagnostics + // (we cannot access State here, but logging helps) + relay_log(&format!("Forwarded BatonData ({} bytes)", buffer.len())); + } } buffer.clear(); } - Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => continue, + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { + // normal when no data available + std::thread::sleep(StdDuration::from_millis(1)); + continue; + } Err(e) => { relay_log(&format!("IPC connection read error: {}", e)); + // Treat read error as a disconnect and notify parent + if let Err(e2) = child_bichannel.send_to_parent(FromIpcThreadMessage::BatonShutdown) { + relay_log(&format!("Failed to send BatonShutdown after read error: {}", e2)); + } break; } } } - let _ = child_bichannel.set_is_conn_to_endpoint(false); + // mark disconnected and log + match child_bichannel.set_is_conn_to_endpoint(false) { + Ok(_) => relay_log("Marked child_bichannel connected -> false"), + Err(e) => relay_log(&format!("Failed to mark child_bichannel disconnected: {}", e)), + }; } Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { // no incoming connection right now @@ -255,6 +365,7 @@ impl State { bail!("TCP thread already exists.") } let (tcp_bichannel, mut child_bichannel) = bichannel::create_bichannels(); + // store parent side so UI/State can query/send to the TCP thread self.tcp_bichannel = Some(tcp_bichannel); let handle = spawn(move || { @@ -366,12 +477,12 @@ impl State { if self.tcp_thread_handle.is_none() { bail!("TCP thread does not exist.") } - let Some((bichannel, handle)) = + let Some((biconductor, handle)) = self.tcp_bichannel.take().zip(self.tcp_thread_handle.take()) else { bail!("TCP thread does not exist.") }; - bichannel.killswitch_engage()?; + biconductor.killswitch_engage()?; let res = handle .join() .map_err(|e| anyhow!("Join handle err: {e:?}"))?; From 9e26a819f3bef7521d83bff7f4a2d1ac7722705b Mon Sep 17 00:00:00 2001 From: jthomas39 Date: Sat, 28 Feb 2026 22:14:25 -0600 Subject: [PATCH 2/4] more changes for error messages --- relay/src/state.rs | 137 +++++---------------------------------------- 1 file changed, 13 insertions(+), 124 deletions(-) diff --git a/relay/src/state.rs b/relay/src/state.rs index e64a84d..05a0e5f 100644 --- a/relay/src/state.rs +++ b/relay/src/state.rs @@ -1,6 +1,6 @@ use anyhow::{anyhow, bail, Result}; use iced::time::Duration; -use interprocess::local_socket::{traits::{Listener, Stream}, GenericNamespaced, GenericFilePath, ListenerOptions, NameType, ToFsName, ToNsName}; +use interprocess::local_socket::{traits::Listener, GenericNamespaced, ListenerOptions, ToNsName}; use std::io::{BufRead, BufReader, Write}; use std::net::TcpStream; use std::thread::{spawn, JoinHandle}; @@ -157,33 +157,11 @@ impl State { let handle = spawn(move || { let printname = "baton.sock"; - - // Pre-build an owned filesystem path string so any FsName conversion borrows an owned value - // that lives for the duration of this closure (avoids temporary-borrow lifetime issues). - let mut tmp_path_buf = std::env::temp_dir(); - tmp_path_buf.push(printname); - let temp_path_string = tmp_path_buf.to_string_lossy().into_owned(); - - // Prefer namespaced sockets when supported; otherwise fall back to filesystem-backed socket - // in the system temp dir. Log which path form is selected and any conversion errors. - let opts = if GenericNamespaced::is_supported() { - // namespaced socket - relay_log(&format!("IPC server using namespaced socket: {}", printname)); - let ns_name = printname.to_ns_name::().map_err(|e| { - relay_log(&format!("Failed to convert name to NsName: {}", e)); - anyhow!("Name conversion failed") - })?; - ListenerOptions::new().name(ns_name) - } else { - // filesystem-backed socket in temp dir - relay_log(&format!("IPC server using filesystem socket path: {}", temp_path_string)); - let fs_name = temp_path_string.as_str().to_fs_name::().map_err(|e| { - relay_log(&format!("Failed to convert name to FsName: {}", e)); - anyhow!("Name conversion failed") - })?; - ListenerOptions::new().name(fs_name) - }; - + let name = printname.to_ns_name::().map_err(|e| { + relay_log(&format!("Failed to convert name to NsName: {}", e)); + anyhow!("Name conversion failed") + })?; + let opts = ListenerOptions::new().name(name); let listener = opts.create_sync().map_err(|e| { relay_log(&format!("Failed to create IPC listener: {}", e)); anyhow!("Listener create failed") @@ -199,71 +177,10 @@ impl State { Ok(conn) => { relay_log("IPC incoming connection accepted"); let mut conn = BufReader::new(conn); - - // Perform a blocking handshake with the client (baton). - // The baton writes a Hello line immediately after connecting and - // expects a reply before switching to non-blocking mode. Read - // that initial line, reply, then switch the underlying stream - // to non-blocking for normal operation. - match conn.read_line(&mut buffer) { - Ok(0) => { - relay_log("Handshake read returned 0 bytes; closing connection"); - buffer.clear(); - if let Err(e) = child_bichannel.send_to_parent(FromIpcThreadMessage::BatonShutdown) { - relay_log(&format!("Failed to send BatonShutdown during handshake: {}", e)); - } - continue; - } - Ok(_) => { - let h = buffer.trim_end().to_string(); - relay_log(&format!("IPC handshake received: {}", h)); - buffer.clear(); - } - Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { - // Unlikely for blocking socket, but be tolerant: treat as no handshake - relay_log("Handshake read would block; treating as failed handshake"); - if let Err(e) = child_bichannel.send_to_parent(FromIpcThreadMessage::BatonShutdown) { - relay_log(&format!("Failed to send BatonShutdown after WouldBlock handshake: {}", e)); - } - continue; - } - Err(e) => { - relay_log(&format!("Handshake read error: {}", e)); - if let Err(e2) = child_bichannel.send_to_parent(FromIpcThreadMessage::BatonShutdown) { - relay_log(&format!("Failed to send BatonShutdown after handshake error: {}", e2)); - } - continue; - } - } - - // Send handshake reply - if let Err(e) = conn.get_mut().write_all(b"Hello, from the relay prototype (Rust)!\n") { - relay_log(&format!("Handshake reply failed: {}", e)); - if let Err(e2) = child_bichannel.send_to_parent(FromIpcThreadMessage::BatonShutdown) { - relay_log(&format!("Failed to send BatonShutdown after reply fail: {}", e2)); - } - continue; - } else { - relay_log("Handshake reply sent"); - } - - // Switch connection to non-blocking mode for the main receive loop - if let Err(e) = conn.get_mut().set_nonblocking(true) { - relay_log(&format!("Failed to set connection non-blocking: {}", e)); - // continue anyway; subsequent reads will attempt and handle WouldBlock - } else { - relay_log("Set accepted connection to non-blocking"); - } - - // mark connected and log result - match child_bichannel.set_is_conn_to_endpoint(true) { - Ok(_) => relay_log("Marked child_bichannel connected -> true"), - Err(e) => relay_log(&format!("Failed to mark child_bichannel connected: {}", e)), - } + let _ = child_bichannel.set_is_conn_to_endpoint(true); loop { if child_bichannel.is_killswitch_engaged() { - relay_log("Child killswitch engaged; breaking connection loop"); break; } @@ -274,55 +191,28 @@ impl State { match conn.read_line(&mut buffer) { Ok(0) => { - // Connection closed by client — notify parent that baton disconnected - relay_log("IPC read returned Ok(0) — connection closed by peer (EOF)"); buffer.clear(); - if let Err(e) = child_bichannel.send_to_parent(FromIpcThreadMessage::BatonShutdown) { - relay_log(&format!("Failed to send BatonShutdown on EOF: {}", e)); - } break; } Ok(_) => { let _ = buffer.pop(); // remove trailing newline if present if buffer.starts_with("SHUTDOWN") { - relay_log("IPC received SHUTDOWN message from client"); - if let Err(e) = child_bichannel.send_to_parent(FromIpcThreadMessage::BatonShutdown) { - relay_log(&format!("Failed to send BatonShutdown on SHUTDOWN: {}", e)); - } + let _ = child_bichannel.send_to_parent(FromIpcThreadMessage::BatonShutdown); break; } else { - // forward to parent and log on error - if let Err(e) = child_bichannel.send_to_parent(FromIpcThreadMessage::BatonData(buffer.clone())) { - relay_log(&format!("Failed to forward BatonData to parent: {}", e)); - } else { - // update last seen timestamp for diagnostics - // (we cannot access State here, but logging helps) - relay_log(&format!("Forwarded BatonData ({} bytes)", buffer.len())); - } + let _ = child_bichannel.send_to_parent(FromIpcThreadMessage::BatonData(buffer.clone())); } buffer.clear(); } - Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { - // normal when no data available - std::thread::sleep(StdDuration::from_millis(1)); - continue; - } + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => continue, Err(e) => { relay_log(&format!("IPC connection read error: {}", e)); - // Treat read error as a disconnect and notify parent - if let Err(e2) = child_bichannel.send_to_parent(FromIpcThreadMessage::BatonShutdown) { - relay_log(&format!("Failed to send BatonShutdown after read error: {}", e2)); - } break; } } } - // mark disconnected and log - match child_bichannel.set_is_conn_to_endpoint(false) { - Ok(_) => relay_log("Marked child_bichannel connected -> false"), - Err(e) => relay_log(&format!("Failed to mark child_bichannel disconnected: {}", e)), - }; + let _ = child_bichannel.set_is_conn_to_endpoint(false); } Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { // no incoming connection right now @@ -365,7 +255,6 @@ impl State { bail!("TCP thread already exists.") } let (tcp_bichannel, mut child_bichannel) = bichannel::create_bichannels(); - // store parent side so UI/State can query/send to the TCP thread self.tcp_bichannel = Some(tcp_bichannel); let handle = spawn(move || { @@ -477,12 +366,12 @@ impl State { if self.tcp_thread_handle.is_none() { bail!("TCP thread does not exist.") } - let Some((biconductor, handle)) = + let Some((bichannel, handle)) = self.tcp_bichannel.take().zip(self.tcp_thread_handle.take()) else { bail!("TCP thread does not exist.") }; - biconductor.killswitch_engage()?; + bichannel.killswitch_engage()?; let res = handle .join() .map_err(|e| anyhow!("Join handle err: {e:?}"))?; From c1d0944eb12cf4aa808288ba51d45c8483506d5d Mon Sep 17 00:00:00 2001 From: jthomas39 Date: Sat, 28 Feb 2026 23:09:08 -0600 Subject: [PATCH 3/4] bug fix: baton connected now appears in the top left as it was suppsoed to --- relay/src/state.rs | 517 ++++++++++++++++++++++++++++++--------------- 1 file changed, 351 insertions(+), 166 deletions(-) diff --git a/relay/src/state.rs b/relay/src/state.rs index 05a0e5f..e3765bb 100644 --- a/relay/src/state.rs +++ b/relay/src/state.rs @@ -1,20 +1,22 @@ -use anyhow::{anyhow, bail, Result}; -use iced::time::Duration; -use interprocess::local_socket::{traits::Listener, GenericNamespaced, ListenerOptions, ToNsName}; -use std::io::{BufRead, BufReader, Write}; +use anyhow::Result; +use anyhow::{anyhow, bail}; +use std::io::BufRead; +use std::io::BufReader; +use std::io::Write; use std::net::TcpStream; -use std::thread::{spawn, JoinHandle}; -use std::time::{Duration as StdDuration, Instant}; - +use std::thread::{JoinHandle, spawn}; +use iced::time::Duration; use crate::bichannel; use crate::bichannel::ParentBiChannel; -use crate::message::{FromIpcThreadMessage, FromTcpThreadMessage, ToIpcThreadMessage, ToTcpThreadMessage}; - -/// Simple, consistent log helper used inside this module and spawned threads. -fn relay_log(msg: &str) { - eprintln!("[relay] {}", msg); -} +use crate::message::FromIpcThreadMessage; +use crate::message::FromTcpThreadMessage; +use crate::message::ToIpcThreadMessage; +use crate::message::ToTcpThreadMessage; +use interprocess::local_socket::{traits::Listener, GenericNamespaced, ListenerOptions, ToNsName}; +use std::time::{Instant, SystemTime, UNIX_EPOCH, Duration as StdDuration}; +use std::sync::{OnceLock, Mutex}; +// --- State definition and Default impl (replace existing block) --- #[allow(unused)] pub(crate) struct State { pub elapsed_time: Duration, @@ -25,17 +27,17 @@ pub(crate) struct State { pub tcp_addr_field: String, pub latest_baton_send: Option, pub active_baton_connection: bool, - /// timestamp of last received baton packet (used by update logic) + // timestamp of last received baton packet (used by update logic) pub last_baton_instant: Option, - /// simple metrics/UI helpers + // simple metrics/UI helpers pub show_metrics: bool, pub packets_last_60s: usize, pub bps: f64, - /// Optional GUI error message + // Optional GUI error message pub error_message: Option, - /// Is GUI pop-up card open + // Is GUI pop-up card open pub card_open: bool, - /// GUI Toggle state elements + // GUI Toggle state elements pub altitude_toggle: bool, pub airspeed_toggle: bool, pub vertical_airspeed_toggle: bool, @@ -44,7 +46,6 @@ pub(crate) struct State { pub tcp_bichannel: Option>, pub last_send_timestamp: Option, } - impl Default for State { fn default() -> State { State { @@ -72,32 +73,35 @@ impl Default for State { } } } - -// --- helpers ---------------------------------------------------------------- - +// --- helper functions ------------------------------------------------------- fn sanitize_field(s: &str) -> String { + // remove CR/LF and replace any internal semicolons with commas, + // trim whitespace s.replace('\r', "") .replace('\n', "") .replace(';', ",") .trim() .to_string() } - fn normalize_baton_payload(raw: &str) -> Vec { + // trim whitespace, remove surrounding CR/LF let mut s = raw.trim().replace('\r', "").replace('\n', ""); + // remove leading semicolons that create empty first fields while s.starts_with(';') { s.remove(0); } + // also remove trailing semicolons (avoid empty trailing field) while s.ends_with(';') { s.pop(); } + // split on semicolon and sanitize each field s.split(';') .map(|f| sanitize_field(f)) .filter(|f| !f.is_empty()) .collect() } - fn build_imotions_packet(event_name: &str, fields: &[String]) -> String { + // Header used in previous code: "E;1;PilotDataSync;;;;;{Event};{fields...}\r\n" let mut packet = String::from("E;1;PilotDataSync;;;;;"); packet.push_str(event_name); if !fields.is_empty() { @@ -108,132 +112,232 @@ fn build_imotions_packet(event_name: &str, fields: &[String]) -> String { packet } -fn send_packet(stream: &mut TcpStream, packet: &str) -> Result<()> { +/// Produce a compact, human-friendly timestamp (seconds since epoch + millis). +fn now_epoch_millis() -> String { + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_else(|_| std::time::Duration::from_secs(0)); + format!("{}.{:03}", now.as_secs(), now.subsec_millis()) +} + +// -- Buffered human logger -------------------------------------------------- +// +// Behaviour: +// - Messages are buffered instead of printed immediately. +// - If a new message differs from the last logged message it is flushed +// immediately (change detected). +// - If messages are identical, they will be flushed at most once per +// `LOG_FLUSH_INTERVAL_MS` milliseconds (periodic heartbeat). +// - This reduces continuous identical output in PowerShell while keeping +// readable, timestamped logs on change or periodically. +// +const LOG_FLUSH_INTERVAL_MS: u64 = 2000; + +struct LoggerState { + buffer: Vec, + last_flush: Instant, + last_msg: Option, +} + +static LOGGER: OnceLock> = OnceLock::new(); + +fn init_logger() -> &'static Mutex { + LOGGER.get_or_init(|| { + Mutex::new(LoggerState { + buffer: Vec::new(), + last_flush: Instant::now(), + last_msg: None, + }) + }) +} + +/// Flush buffered log lines to stderr (PowerShell) and update last_flush. +fn flush_logger() { + let mutex = init_logger(); + let mut st = mutex.lock().expect("logger mutex poisoned"); + if st.buffer.is_empty() { + return; + } + for line in st.buffer.drain(..) { + eprintln!("{}", line); + } + st.last_flush = Instant::now(); +} + +/// Human-facing logger that buffers and flushes on change or periodically. +/// The formatted entry excludes the timestamp part used for deduplication, +/// so only the message content + level is compared. +fn human_log(level: &str, msg: &str) { + let entry_body = format!("{} - {}", level, msg); + let full_entry = format!("[{}] {}", now_epoch_millis(), entry_body); + + let mutex = init_logger(); + let now = Instant::now(); + + { + let mut st = mutex.lock().expect("logger mutex poisoned"); + + // If content changed, push and mark for immediate flush. + if st.last_msg.as_deref() != Some(&entry_body) { + st.buffer.push(full_entry); + st.last_msg = Some(entry_body); + // drop lock before flushing to avoid double-lock + drop(st); + flush_logger(); + return; + } + + // If identical and enough time passed since last flush, push & flush. + if now.duration_since(st.last_flush) >= StdDuration::from_millis(LOG_FLUSH_INTERVAL_MS) { + st.buffer.push(full_entry); + // drop lock before flushing + drop(st); + flush_logger(); + return; + } + + // Identical message and too soon to flush: skip pushing to avoid spam. + // (We intentionally don't update last_flush or last_msg here.) + } +} + +/// Log and store event in in-memory event log (with timestamp). +fn push_event_log(state: &mut State, event: &str) { + let entry = format!("[{}] {}", now_epoch_millis(), event); + state.event_log.push(entry); +} +// Add this helper near your other helpers +fn send_packet_and_debug(stream: &mut std::net::TcpStream, packet: &str) -> Result<()> { + // Print readable and hex views for debugging in a human-friendly format + human_log("TX", &format!("packet len={} text={:?}", packet.len(), packet)); + let hex: String = packet + .as_bytes() + .iter() + .map(|b| format!("{:02X}", b)) + .collect::>() + .join(" "); + human_log("TX", &format!("hex: {}", hex)); + // Write then flush -- report any error stream .write_all(packet.as_bytes()) - .map_err(|e| anyhow!("write_all failed: {}", e))?; + .map_err(|e| anyhow::anyhow!("write_all failed: {}", e))?; stream .flush() - .map_err(|e| anyhow!("flush failed: {}", e))?; + .map_err(|e| anyhow::anyhow!("flush failed: {}", e))?; Ok(()) } - // --- State impl ------------------------------------------------------------- - impl State { + // Simple metric helpers used by update/view code that expect them. pub fn refresh_metrics_now(&mut self) { + // placeholder: in future compute accurate rates from history + // Here we keep current values; could implement sliding window later. if self.packets_last_60s > 0 { + // naive decay to avoid stale large counts (noop for now) self.packets_last_60s = self.packets_last_60s.saturating_sub(0); } } - pub fn on_tcp_packet_sent(&mut self, bytes: usize) { + // Update simple counters and log self.packets_last_60s = self.packets_last_60s.saturating_add(1); self.bps = bytes as f64; self.log_event(format!("Sent packet ({} bytes)", bytes)); } - - pub fn is_ipc_connected(&self) -> bool { - self.ipc_bichannel - .as_ref() - .and_then(|b| b.is_conn_to_endpoint().ok()) - .unwrap_or(false) - } - - pub fn is_tcp_connected(&self) -> bool { - self.tcp_bichannel - .as_ref() - .and_then(|b| b.is_conn_to_endpoint().ok()) - .unwrap_or(false) - } - pub fn ipc_connect(&mut self) -> Result<()> { if self.ipc_thread_handle.is_some() { - bail!("IPC thread already exists."); + bail!("IPC thread already exists.") } - let (ipc_bichannel, mut child_bichannel) = bichannel::create_bichannels::(); - - let handle = spawn(move || { + let ipc_thread_handle = spawn(move || { let printname = "baton.sock"; - let name = printname.to_ns_name::().map_err(|e| { - relay_log(&format!("Failed to convert name to NsName: {}", e)); - anyhow!("Name conversion failed") - })?; + let name = printname.to_ns_name::().unwrap(); let opts = ListenerOptions::new().name(name); - let listener = opts.create_sync().map_err(|e| { - relay_log(&format!("Failed to create IPC listener: {}", e)); - anyhow!("Listener create failed") - })?; + let listener = match opts.create_sync() { + Err(e) if e.kind() == std::io::ErrorKind::AddrInUse => { + human_log("IPC", &format!( + "Could not start server because the socket file is occupied. Check if {} is in use.", + printname + )); + return Ok(()); + } + x => x.unwrap(), + }; listener .set_nonblocking(interprocess::local_socket::ListenerNonblockingMode::Both) - .map_err(|e| anyhow!("set_nonblocking failed: {}", e))?; - relay_log("IPC server running"); - + .expect("Error setting non-blocking mode on listener"); + human_log("IPC", &format!("Server running at {}", printname)); let mut buffer = String::with_capacity(128); while !child_bichannel.is_killswitch_engaged() { - match listener.accept() { - Ok(conn) => { - relay_log("IPC incoming connection accepted"); - let mut conn = BufReader::new(conn); - let _ = child_bichannel.set_is_conn_to_endpoint(true); - - loop { - if child_bichannel.is_killswitch_engaged() { - break; - } - - // Check parent messages (none expected for now) - for _msg in child_bichannel.received_messages() { - // intentionally no-op; reserved for future commands - } - - match conn.read_line(&mut buffer) { - Ok(0) => { - buffer.clear(); - break; - } - Ok(_) => { - let _ = buffer.pop(); // remove trailing newline if present - if buffer.starts_with("SHUTDOWN") { - let _ = child_bichannel.send_to_parent(FromIpcThreadMessage::BatonShutdown); - break; - } else { - let _ = child_bichannel.send_to_parent(FromIpcThreadMessage::BatonData(buffer.clone())); - } - buffer.clear(); - } - Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => continue, - Err(e) => { - relay_log(&format!("IPC connection read error: {}", e)); - break; - } - } - } - - let _ = child_bichannel.set_is_conn_to_endpoint(false); + let conn = listener.accept(); + let conn = match (child_bichannel.is_killswitch_engaged(), conn) { + (true, _) => return Ok(()), + (_, Ok(c)) => { + human_log("IPC", "Accepted incoming connection"); + c } - Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { - // no incoming connection right now - std::thread::sleep(StdDuration::from_millis(5)); + (_, Err(e)) if e.kind() == std::io::ErrorKind::WouldBlock => { continue; } - Err(e) => { - relay_log(&format!("IPC accept failed: {}", e)); - std::thread::sleep(StdDuration::from_millis(50)); + (_, Err(e)) => { + human_log("IPC", &format!("Incoming connection failed: {}", e)); continue; } + }; + let mut conn = BufReader::new(conn); + child_bichannel.set_is_conn_to_endpoint(true)?; + match conn.read_line(&mut buffer) { + Ok(_) => (), + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => continue, + _ => panic!(), + } + let write_res = conn + .get_mut() + .write_all(b"Hello, from the relay prototype (Rust)!\n"); + match write_res { + Ok(_) => (), + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => continue, + _ => panic!(), + } + human_log("IPC", &format!("Client answered: {}", buffer.trim_end())); + buffer.clear(); + // Continuously receive data from plugin + while !child_bichannel.is_killswitch_engaged() { + // check for any new messages from parent and act accordingly + for message in child_bichannel.received_messages() { + match message {} + } + // read from connection input + match conn.read_line(&mut buffer) { + Ok(s) if s == 0 || buffer.len() == 0 => { + buffer.clear(); + continue; + } + Ok(_s) => { + let _ = buffer.pop(); // remove trailing newline + human_log("IPC", &format!("Got: {} ({} bytes read)", buffer, _s)); + if buffer.starts_with("SHUTDOWN") { + let _ = child_bichannel + .send_to_parent(FromIpcThreadMessage::BatonShutdown); + return Ok(()); + } else { + let _ = child_bichannel.send_to_parent( + FromIpcThreadMessage::BatonData(buffer.clone()), + ); + } + buffer.clear(); + } + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => continue, + Err(e) => panic!("Got err {e}"), + } } } Ok(()) }); - self.ipc_bichannel = Some(ipc_bichannel); - self.ipc_thread_handle = Some(handle); + self.ipc_thread_handle = Some(ipc_thread_handle); Ok(()) } - pub fn ipc_disconnect(&mut self) -> Result<()> { if self.ipc_thread_handle.is_none() { bail!("IPC thread does not exist.") @@ -249,119 +353,188 @@ impl State { .map_err(|e| anyhow!("Join handle err: {e:?}"))?; Ok(res?) } - + pub fn is_ipc_connected(&self) -> bool { + if let Some(status) = self + .ipc_bichannel + .as_ref() + .and_then(|bichannel| bichannel.is_conn_to_endpoint().ok()) + { + status + } else { + false + } + } pub fn tcp_connect(&mut self, address: String) -> Result<()> { if self.tcp_thread_handle.is_some() { bail!("TCP thread already exists.") } let (tcp_bichannel, mut child_bichannel) = bichannel::create_bichannels(); self.tcp_bichannel = Some(tcp_bichannel); - - let handle = spawn(move || { - let mut stream = TcpStream::connect(address).map_err(|e| { - relay_log(&format!("TCP connect failed: {}", e)); - anyhow!("Failed to connect to TCP") - })?; - relay_log("TCP connected"); - let _ = child_bichannel.set_is_conn_to_endpoint(true); - + let tcp_thread_handle = spawn(move || { + let mut stream = match TcpStream::connect(address) { + Ok(stream) => { + human_log("TCP", "Successfully connected to iMotions server."); + let _ = child_bichannel.set_is_conn_to_endpoint(true); + stream + } + Err(e) => { + human_log("TCP", &format!("Connection failed: {}", e)); + bail!("Failed to connect to TCP"); + } + }; while !child_bichannel.is_killswitch_engaged() { for message in child_bichannel.received_messages() { match message { ToTcpThreadMessage::Send(data) => { + // Normalize baton payload let fields = normalize_baton_payload(&data); - + // --- Flexible mapping for iMOTIONS events --- + // The relay accepts two common payload shapes: + // 1) Paired fields for each sample (FM,Pilot) in sequence: + // [Alt_FM, Alt_Pilot, Air_FM, Air_Pilot, Vert_FM, Vert_Pilot, Head_FM, Head_Pilot] + // 2) Single pilot-only values in order: + // [Altitude, Airspeed, Heading, VerticalVelocity] + // + // Emit whatever events we can from the incoming payload. if fields.len() < 2 { - relay_log(&format!("Dropping packet: not enough fields (need >=2) but baton sent {}: {:?}", fields.len(), fields)); + human_log("TCP", &format!( + "Dropping packet: not enough fields (need >=2) but baton sent {}: {:?}", + fields.len(), fields + )); continue; } - - // Pilot-only 4-field payload handling + // If payload is exactly 4 fields, assume pilot-only order if fields.len() == 4 { - let alt = fields[0].clone(); - let air = fields[1].clone(); - let head = fields[2].clone(); - let vv = fields[3].clone(); - - let packets = [ - build_imotions_packet("AltitudeSync", &[alt.clone(), alt.clone()]), - build_imotions_packet("AirspeedSync", &[air.clone(), air.clone()]), - build_imotions_packet("VerticalVelocitySync", &[vv.clone(), vv.clone()]), - build_imotions_packet("HeadingSync", &[head.clone(), head.clone()]), - ]; - - for pkt in &packets { - if let Err(e) = send_packet(&mut stream, pkt) { - relay_log(&format!("TCP send failed: {}", e)); - let _ = child_bichannel.set_is_conn_to_endpoint(false); - return Err(e); - } else { - let _ = child_bichannel.set_is_conn_to_endpoint(true); - } + // plugin order: Altitude, Airspeed, Heading, VerticalVelocity + // For iMotions we need (FlightModel, Pilot) pairs. Use the pilot value for both slots. + let alt = fields.get(0).unwrap().clone(); + let air = fields.get(1).unwrap().clone(); + let head = fields.get(2).unwrap().clone(); + let vv = fields.get(3).unwrap().clone(); + // Altitude + let altitude_packet = build_imotions_packet("AltitudeSync", &[alt.clone(), alt.clone()]); + human_log("TCP", &format!("Sending AltitudeSync: {:?}", altitude_packet)); + if let Err(e) = send_packet_and_debug(&mut stream, &altitude_packet) { + human_log("TCP", &format!("Failed to send Altitude packet: {}", e)); + let _ = child_bichannel.set_is_conn_to_endpoint(false); + return Err(e); + } else { + let _ = child_bichannel.set_is_conn_to_endpoint(true); } + // Airspeed + let airspeed_packet = build_imotions_packet("AirspeedSync", &[air.clone(), air.clone()]); + human_log("TCP", &format!("Sending AirspeedSync: {:?}", airspeed_packet)); + if let Err(e) = send_packet_and_debug(&mut stream, &airspeed_packet) { + human_log("TCP", &format!("Failed to send Airspeed packet: {}", e)); + let _ = child_bichannel.set_is_conn_to_endpoint(false); + return Err(e); + } else { + let _ = child_bichannel.set_is_conn_to_endpoint(true); + } + // Vertical velocity + let vv_packet = build_imotions_packet("VerticalVelocitySync", &[vv.clone(), vv.clone()]); + human_log("TCP", &format!("Sending VerticalVelocitySync: {:?}", vv_packet)); + if let Err(e) = send_packet_and_debug(&mut stream, &vv_packet) { + human_log("TCP", &format!("Failed to send Vertical Velocity packet: {}", e)); + let _ = child_bichannel.set_is_conn_to_endpoint(false); + return Err(e); + } else { + let _ = child_bichannel.set_is_conn_to_endpoint(true); + } + // Heading + let heading_packet = build_imotions_packet("HeadingSync", &[head.clone(), head.clone()]); + human_log("TCP", &format!("Sending HeadingSync: {:?}", heading_packet)); + if let Err(e) = send_packet_and_debug(&mut stream, &heading_packet) { + human_log("TCP", &format!("Failed to send Heading packet: {}", e)); + let _ = child_bichannel.set_is_conn_to_endpoint(false); + return Err(e); + } else { + let _ = child_bichannel.set_is_conn_to_endpoint(true); + } + // done with this message continue; } - - // Paired-fields mapping (legacy behavior) + // Otherwise attempt the paired-fields mapping (previous behavior) + // Send AltitudeSync if we have at least 2 fields if fields.len() >= 2 { - let altitude_payload = vec![fields[0].clone(), fields[1].clone()]; + let altitude_payload = vec![ + fields.get(0).unwrap().clone(), + fields.get(1).unwrap().clone(), + ]; let altitude_packet = build_imotions_packet("AltitudeSync", &altitude_payload); - if let Err(e) = send_packet(&mut stream, &altitude_packet) { - relay_log(&format!("Failed to send Altitude packet: {}", e)); + human_log("TCP", &format!("Sending AltitudeSync: {:?}", altitude_packet)); + if let Err(e) = send_packet_and_debug(&mut stream, &altitude_packet) { + human_log("TCP", &format!("Failed to send Altitude packet: {}", e)); let _ = child_bichannel.set_is_conn_to_endpoint(false); return Err(e); } else { let _ = child_bichannel.set_is_conn_to_endpoint(true); } } - + // Send AirspeedSync if we have at least 4 fields if fields.len() >= 4 { - let airspeed_payload = vec![fields[2].clone(), fields[3].clone()]; + let airspeed_payload = vec![ + fields.get(2).unwrap().clone(), + fields.get(3).unwrap().clone(), + ]; let airspeed_packet = build_imotions_packet("AirspeedSync", &airspeed_payload); - if let Err(e) = send_packet(&mut stream, &airspeed_packet) { - relay_log(&format!("Failed to send Airspeed packet: {}", e)); + human_log("TCP", &format!("Sending AirspeedSync: {:?}", airspeed_packet)); + if let Err(e) = send_packet_and_debug(&mut stream, &airspeed_packet) { + human_log("TCP", &format!("Failed to send Airspeed packet: {}", e)); let _ = child_bichannel.set_is_conn_to_endpoint(false); return Err(e); } else { let _ = child_bichannel.set_is_conn_to_endpoint(true); } + } else { + human_log("TCP", &format!("Airspeed packet skipped: need >=4 fields, have {}", fields.len())); } - + // Send VerticalVelocitySync if we have at least 6 fields if fields.len() >= 6 { - let vv_payload = vec![fields[4].clone(), fields[5].clone()]; + let vv_payload = vec![ + fields.get(4).unwrap().clone(), + fields.get(5).unwrap().clone(), + ]; let vv_packet = build_imotions_packet("VerticalVelocitySync", &vv_payload); - if let Err(e) = send_packet(&mut stream, &vv_packet) { - relay_log(&format!("Failed to send Vertical Velocity packet: {}", e)); + human_log("TCP", &format!("Sending VerticalVelocitySync: {:?}", vv_packet)); + if let Err(e) = send_packet_and_debug(&mut stream, &vv_packet) { + human_log("TCP", &format!("Failed to send Vertical Velocity packet: {}", e)); let _ = child_bichannel.set_is_conn_to_endpoint(false); return Err(e); } else { let _ = child_bichannel.set_is_conn_to_endpoint(true); } + } else { + human_log("TCP", &format!("VerticalVelocity packet skipped: need >=6 fields, have {}", fields.len())); } - + // Send HeadingSync if we have at least 8 fields if fields.len() >= 8 { - let heading_payload = vec![fields[6].clone(), fields[7].clone()]; + let heading_payload = vec![ + fields.get(6).unwrap().clone(), + fields.get(7).unwrap().clone(), + ]; let heading_packet = build_imotions_packet("HeadingSync", &heading_payload); - if let Err(e) = send_packet(&mut stream, &heading_packet) { - relay_log(&format!("Failed to send Heading packet: {}", e)); + human_log("TCP", &format!("Sending HeadingSync: {:?}", heading_packet)); + if let Err(e) = send_packet_and_debug(&mut stream, &heading_packet) { + human_log("TCP", &format!("Failed to send Heading packet: {}", e)); let _ = child_bichannel.set_is_conn_to_endpoint(false); return Err(e); } else { let _ = child_bichannel.set_is_conn_to_endpoint(true); } + } else { + human_log("TCP", &format!("Heading packet skipped: need >=8 fields, have {}", fields.len())); } } } } - std::thread::sleep(StdDuration::from_millis(1)); + std::thread::sleep(std::time::Duration::from_millis(1)); } Ok(()) }); - - self.tcp_thread_handle = Some(handle); + self.tcp_thread_handle = Some(tcp_thread_handle); Ok(()) } - pub fn tcp_disconnect(&mut self) -> Result<()> { if self.tcp_thread_handle.is_none() { bail!("TCP thread does not exist.") @@ -377,8 +550,20 @@ impl State { .map_err(|e| anyhow!("Join handle err: {e:?}"))?; Ok(res?) } - + pub fn is_tcp_connected(&self) -> bool { + if let Some(status) = self + .tcp_bichannel + .as_ref() + .and_then(|bichannel| bichannel.is_conn_to_endpoint().ok()) + { + status + } else { + false + } + } pub fn log_event(&mut self, event: String) { - self.event_log.push(event); + // store a timestamped copy for the UI/event history + let entry = format!("[{}] {}", now_epoch_millis(), event); + self.event_log.push(entry); } } \ No newline at end of file From 217dfd3d2d889ecf3518eee6f192fbcffb71dc85 Mon Sep 17 00:00:00 2001 From: Nyla Date: Mon, 2 Mar 2026 10:22:31 -0600 Subject: [PATCH 4/4] Code clean review and Mac changes --- xplane_plugin/mac-x86_64.ini | 17 + xplane_plugin/meson.build | 113 +-- .../src/pilotdatasync-xp11.macos.cpp | 653 ++++++++---------- xplane_plugin/subprojects/baton/build.rs | 16 +- xplane_plugin/subprojects/baton/meson.build | 15 +- xplane_plugin/subprojects/baton/script.py | 31 +- 6 files changed, 379 insertions(+), 466 deletions(-) create mode 100644 xplane_plugin/mac-x86_64.ini diff --git a/xplane_plugin/mac-x86_64.ini b/xplane_plugin/mac-x86_64.ini new file mode 100644 index 0000000..c080b93 --- /dev/null +++ b/xplane_plugin/mac-x86_64.ini @@ -0,0 +1,17 @@ +[binaries] +c = 'clang' +cpp = 'clang++' +ar = 'ar' +strip = 'strip' + +[host_machine] +system = 'darwin' +cpu_family = 'x86_64' +cpu = 'x86_64' +endian = 'little' + +[built-in options] +c_args = ['-arch', 'x86_64'] +cpp_args = ['-arch', 'x86_64'] +c_link_args = ['-arch', 'x86_64'] +cpp_link_args = ['-arch', 'x86_64'] diff --git a/xplane_plugin/meson.build b/xplane_plugin/meson.build index c7cabf6..68fff0f 100644 --- a/xplane_plugin/meson.build +++ b/xplane_plugin/meson.build @@ -1,10 +1,14 @@ -# init -project('PilotDataSyncPlugin', 'cpp', default_options: ['cpp_std=c++20'], meson_version: '>=1.7.0') +project( + 'PilotDataSyncPlugin', + 'cpp', + default_options: ['cpp_std=c++20'], + meson_version: '>=1.7.0', +) -# imports -fs = import('fs') +inc_dir = include_directories('include') +compiler = meson.get_compiler('cpp') +host_system = host_machine.system() -# these defines are necessary as required by XPlane compiler_args = [ '-std=c++20', '-DXPLM200', @@ -12,101 +16,53 @@ compiler_args = [ '-DXPLM300', '-DXPLM301', '-DXPLM303', - '-DIBM=1', ] -# TODO review if this is still necessary -add_project_arguments('-Llib/SDK/Libraries/Win', language: 'cpp') - - -# includes -inc_dir = include_directories('include') - -compiler = meson.get_compiler('cpp') -# dependencies -winsock = compiler.find_library( - 'ws2_32', - has_headers: ['winsock2.h', 'ws2tcpip.h'], -) - -# x-plane sdk handled by wrapDB instead of installing it manually -xplane_sdk_proj = subproject('x-plane-sdk') -xplm = xplane_sdk_proj.get_variable('xplm_dep') -xpwidgets = xplane_sdk_proj.get_variable('xpwidgets_dep') -xpcpp = xplane_sdk_proj.get_variable('xpcpp_dep') - -# rust library from subproject -baton_subp = subproject('baton') -baton = baton_subp.get_variable('baton_dep') - -# Add this line to define opengl_dep -opengl_dep = compiler.find_library('opengl32', required: host_machine.system() == 'windows') - -# dependencies -deps = [winsock, xplm, xpwidgets, xpcpp, baton] - -if host_machine.system() == 'windows' +plugin_src = 'src/pilotdatasync-xp11.cpp' +deps = [] +link_args = [] + +if host_system == 'windows' + winsock = compiler.find_library( + 'ws2_32', + has_headers: ['winsock2.h', 'ws2tcpip.h'], + ) + opengl_dep = compiler.find_library('opengl32') + deps += [winsock, opengl_dep] + link_args += ['-lntdll', '-static-libstdc++', '-static-libgcc', '-static'] +elif host_system == 'darwin' + plugin_src = 'src/pilotdatasync-xp11.macos.cpp' + opengl_dep = dependency('appleframeworks', modules: ['OpenGL']) deps += [opengl_dep] endif - - - - -# x-plane sdk handled by wrapDB instead of installing it manually xplane_sdk_proj = subproject('x-plane-sdk') xplm = xplane_sdk_proj.get_variable('xplm_dep') xpwidgets = xplane_sdk_proj.get_variable('xpwidgets_dep') xpcpp = xplane_sdk_proj.get_variable('xpcpp_dep') -# rust library from subproject baton_subp = subproject('baton') baton = baton_subp.get_variable('baton_dep') -# dependencies -winsock = compiler.find_library( - 'ws2_32', - has_headers: ['winsock2.h', 'ws2tcpip.h'], -) - -deps = [winsock, xplm, xpwidgets, xpcpp, baton] +deps += [xplm, xpwidgets, xpcpp, baton] -if host_machine.system() == 'windows' - deps += [opengl_dep] -endif - -lib = shared_library( +shared_library( 'DataSync', - 'src/pilotdatasync-xp11.cpp', + plugin_src, dependencies: deps, include_directories: [inc_dir], cpp_args: compiler_args, - link_args: ['-lntdll', '-static-libstdc++', '-static-libgcc', '-static'], - + link_args: link_args, name_prefix: 'Pilot', - name_suffix: 'xpl' - + name_suffix: 'xpl', ) - -if host_machine.system() == 'windows' - test_args = ['-DGTEST_ENABLE_CATCH_EXCEPTIONS_=0', '-std=c++20'] +if host_system == 'windows' + test_args = ['-DGTEST_ENABLE_CATCH_EXCEPTIONS_=0', '-std=c++20'] else - test_args = [] + test_args = [] endif -# gtest -# gtest_proj = subproject('gtest') -# gtest_dep = gtest_proj.get_variable('gtest_dep') -# test_exec = executable('testsuite', -# 'tests/test.cc', -# dependencies: [gtest_dep], -# cpp_args: test_args, -# native: true -# ) -# test('gtest test', test_exec) - -#gtest gtest_proj = subproject('gtest') gtest_dep = gtest_proj.get_variable('gtest_dep') gtest_main_dep = gtest_proj.get_variable('gtest_main_dep') @@ -116,6 +72,7 @@ test_exe = executable( 'test_threading_tools', 'tests/test_threading_tools.cpp', include_directories: inc, - dependencies: [gtest_dep, gtest_main_dep] + dependencies: [gtest_dep, gtest_main_dep], + cpp_args: test_args, ) -test('test_threading_tools', test_exe) \ No newline at end of file +test('test_threading_tools', test_exe) diff --git a/xplane_plugin/src/pilotdatasync-xp11.macos.cpp b/xplane_plugin/src/pilotdatasync-xp11.macos.cpp index fdaf1b7..d48d2ce 100644 --- a/xplane_plugin/src/pilotdatasync-xp11.macos.cpp +++ b/xplane_plugin/src/pilotdatasync-xp11.macos.cpp @@ -1,371 +1,282 @@ -// This is the mac version of the same .cpp file for xplane 11 - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using std::string; -using std::vector; - -#ifdef __cplusplus -extern "C" { -#endif - -#include "XPLMDataAccess.h" -#include "XPLMDisplay.h" -#include "XPLMGraphics.h" -#include "XPLMPlugin.h" -#include "XPLMProcessing.h" -#include "XPLMUtilities.h" - -#ifdef APL -#include -#else -#include -#endif - -#ifdef __cplusplus -} -#endif - -#include -#include -#include -#include -#include - -#ifndef XPLM300 -#error This is made to be compiled against the XPLM300 SDK -#endif - -static std::string g_udp_ip = "127.0.0.1"; -static int g_udp_port = 49005; - -static XPLMWindowID g_window; - -static XPLMDataRef elevationFlightmodelRef; -static XPLMDataRef elevationPilotRef; -static XPLMDataRef airspeedFlightmodelRef; -static XPLMDataRef airspeedPilotRef; -static XPLMDataRef verticalVelocityFlightmodelRef; -static XPLMDataRef verticalVelocityPilotRef; -static XPLMDataRef headingFlightmodelRef; -static XPLMDataRef headingPilotRef; - -static void load_udp_config() { - char name[256] = {0}, sig[256] = {0}, desc[256] = {0}, xpl_path[1024] = {0}; - XPLMGetPluginInfo(XPLMGetMyID(), name, sig, desc, xpl_path); - - std::filesystem::path cfg = - std::filesystem::path(xpl_path).parent_path() / "config.txt"; - - std::ifstream file(cfg.string()); - if (!file.is_open()) { - XPLMDebugString("Could not load port and ip info in config.txt\n"); - return; - } - - std::string ip; - int port; - std::getline(file, ip); - file >> port; - file.close(); - - if (!ip.empty()) - g_udp_ip = ip; - if (port > 0) - g_udp_port = port; -} - -void draw_pilotdatasync_plugin(XPLMWindowID in_window_id, void *in_refcon); - -int dummy_mouse_handler(XPLMWindowID in_window_id, int x, int y, int is_down, - void *in_refcon) { - return 0; -} - -XPLMCursorStatus dummy_cursor_status_handler(XPLMWindowID in_window_id, int x, - int y, void *in_refcon) { - return xplm_CursorDefault; -} - -int dummy_wheel_handler(XPLMWindowID in_window_id, int x, int y, int wheel, - int clicks, void *in_refcon) { - return 0; -} - -void dummy_key_handler(XPLMWindowID in_window_id, char key, XPLMKeyFlags flags, - char virtual_key, void *in_refcon, int losing_focus) {} - -volatile bool stop_exec = false; - -static int button_left = 0, button_top = 0, button_right = 0, button_bottom = 0; -static std::string last_send_timestamp = ""; - -static std::chrono::steady_clock::time_point g_last_udp_sent = - std::chrono::steady_clock::now(); - -static int g_udp_socket = -1; -static struct sockaddr_in g_udp_addr; - -std::string get_current_timestamp() { - std::time_t now = std::time(nullptr); - char buf[32]; - std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", std::localtime(&now)); - return buf; -} - -static void udp_init(const char *ip, int port) { - g_udp_socket = socket(AF_INET, SOCK_DGRAM, 0); - if (g_udp_socket < 0) { - XPLMDebugString("[PilotDataSync] UDP socket create FAILED\n"); - return; - } - std::memset(&g_udp_addr, 0, sizeof(g_udp_addr)); - g_udp_addr.sin_family = AF_INET; - g_udp_addr.sin_port = htons(port); - inet_pton(AF_INET, ip, &g_udp_addr.sin_addr); -} - -static void udp_send(const std::string &payload) { - if (g_udp_socket < 0) - return; - sendto(g_udp_socket, payload.c_str(), (int)payload.size(), 0, - (struct sockaddr *)&g_udp_addr, sizeof(g_udp_addr)); -} - -// This is the format that I motions wants the data to be sent in. -static std::string make_imotions_packet(float alt_ft, float kts, float vs_fpm, - float hdg_deg) { - char pkt[256]; - std::snprintf(pkt, sizeof(pkt), - "E;1;PilotDataSync;1;;;;FlightData;%.5f;%.5f;%.5f;%.5f\r\n", - alt_ft, kts, vs_fpm, hdg_deg); - return std::string(pkt); -} - -int mouse_handler(XPLMWindowID in_window_id, int x, int y, int is_down, - void *in_refcon) { - if (is_down) { - if (x >= button_left && x <= button_right && y >= button_bottom && - y <= button_top) { - - float currentPilotElevation = XPLMGetDataf(elevationPilotRef); - float currentPilotAirspeed = XPLMGetDataf(airspeedPilotRef); - float currentPilotHeading = XPLMGetDataf(headingPilotRef); - float currentPilotVerticalVelocity = - XPLMGetDataf(verticalVelocityPilotRef); - - std::vector send_to_baton = { - currentPilotElevation, - currentPilotAirspeed, - currentPilotVerticalVelocity, - }; - - last_send_timestamp = get_current_timestamp(); - - char clickPkt[256]; - std::snprintf( - clickPkt, sizeof(clickPkt), - "Packet button clicked Altitude: %.5f ft | Airspeed: %.5f knots | " - "Vertical Speed: %.5f ft/min | Heading: %.5f deg M | \n", - currentPilotElevation, currentPilotAirspeed, - currentPilotVerticalVelocity, currentPilotHeading); - udp_send(std::string(clickPkt)); - XPLMDebugString( - (std::string("[PilotDataSync] ") + clickPkt + "\n").c_str()); - } - } - return 0; -} - -PLUGIN_API int XPluginStart(char *outName, char *outSig, char *outDesc) { - strcpy(outName, "PilotDataSyncPlugin"); - strcpy(outSig, "oss.pilotdatasyncplugin"); - strcpy(outDesc, "A plug-in that collects and transmits X-Plane 11 data to " - "the iMotions platform for data collection and research"); - - XPLMCreateWindow_t params; - params.structSize = sizeof(params); - params.visible = 1; - params.drawWindowFunc = draw_pilotdatasync_plugin; - params.handleMouseClickFunc = mouse_handler; - params.handleRightClickFunc = dummy_mouse_handler; - params.handleMouseWheelFunc = dummy_wheel_handler; - params.handleKeyFunc = dummy_key_handler; - params.handleCursorFunc = dummy_cursor_status_handler; - params.refcon = NULL; - params.layer = xplm_WindowLayerFloatingWindows; - - int left, bottom, right, top; - XPLMGetScreenBoundsGlobal(&left, &top, &right, &bottom); - params.left = left + 50; - params.bottom = bottom + 150; - params.right = params.left + 350; - params.top = params.bottom + 200; - - elevationFlightmodelRef = - XPLMFindDataRef("sim/flightmodel/position/elevation"); - elevationPilotRef = - XPLMFindDataRef("sim/cockpit2/gauges/indicators/altitude_ft_pilot"); - airspeedFlightmodelRef = - XPLMFindDataRef("sim/flightmodel/position/true_airspeed"); - airspeedPilotRef = - XPLMFindDataRef("sim/cockpit2/gauges/indicators/airspeed_kts_pilot"); - verticalVelocityFlightmodelRef = - XPLMFindDataRef("sim/flightmodel/position/vh_ind_fpm"); - verticalVelocityPilotRef = - XPLMFindDataRef("sim/cockpit2/gauges/indicators/vvi_fpm_pilot"); - headingFlightmodelRef = XPLMFindDataRef("sim/flightmodel/position/mag_psi"); - headingPilotRef = XPLMFindDataRef( - "sim/cockpit2/gauges/indicators/heading_AHARS_deg_mag_pilot"); - - g_window = XPLMCreateWindowEx(¶ms); - XPLMSetWindowPositioningMode(g_window, xplm_WindowPositionFree, -1); - XPLMSetWindowTitle(g_window, "Positional Flight Data"); - - load_udp_config(); - udp_init(g_udp_ip.c_str(), g_udp_port); - - return g_window != NULL; -} - -PLUGIN_API void XPluginStop() { - if (g_udp_socket >= 0) { - close(g_udp_socket); - g_udp_socket = -1; - } - - XPLMDestroyWindow(g_window); - g_window = NULL; -} - -PLUGIN_API int XPluginEnable(void) { return 1; } - -PLUGIN_API void XPluginReceiveMessage(XPLMPluginID inFrom, int inMsg, - void *inParam) {} - -void draw_pilotdatasync_plugin(XPLMWindowID in_window_id, void *in_refcon) { - XPLMSetGraphicsState(0, 0, 0, 0, 1, 1, 0); - - int l, t, r, b; - XPLMGetWindowGeometry(in_window_id, &l, &t, &r, &b); - float col_white[] = {1.0, 1.0, 1.0}; - - float msToFeetRate = 3.28084; - float msToKnotsRate = 1.94384; - - auto build_str = [](string label, string unit, float value) { - string suffix = !std::isnan(value) ? std::to_string(value) + " " + unit - : "(Error Reading Data)"; - return label + ": " + suffix; - }; - - float currentFlightmodelElevation = - XPLMGetDataf(elevationFlightmodelRef) * msToFeetRate; - string elevationFlightmodelStr = build_str("Elevation, Flightmodel (MSL)", - "ft", currentFlightmodelElevation); - - float currentPilotElevation = XPLMGetDataf(elevationPilotRef); - string elevationPilotStr = - build_str("Elevation, Pilot (MSL)", "ft", currentPilotElevation); - - float currentFlightmodelAirspeed = - XPLMGetDataf(airspeedFlightmodelRef) * msToKnotsRate; - string airspeedFlightmodelStr = - build_str("Airspeed, Flightmodel", "knots", currentFlightmodelAirspeed); - - float currentPilotAirspeed = XPLMGetDataf(airspeedPilotRef); - string airspeedPilotStr = - build_str("Airspeed, Pilot", "knots", currentPilotAirspeed); - - float currentFlightmodelVerticalVelocity = - XPLMGetDataf(verticalVelocityFlightmodelRef); - string verticalVelocityFlightmodelStr = - build_str("Vertical Velocity, Flightmodel", "ft/min", - currentFlightmodelVerticalVelocity); - - float currentPilotVerticalVelocity = XPLMGetDataf(verticalVelocityPilotRef); - string verticalVelocityPilotStr = build_str( - "Vertical Velocity, Flightmodel", "ft/min", currentPilotVerticalVelocity); - - float currentFlightmodelHeading = XPLMGetDataf(headingFlightmodelRef); - string headingFlightmodelStr = - build_str("Heading, Flightmodel", "°M", currentFlightmodelHeading); - - float currentPilotHeading = XPLMGetDataf(headingPilotRef); - string headingPilotStr = - build_str("Heading, Pilot", "°M", currentPilotHeading); - - auto now_tp = std::chrono::steady_clock::now(); - if (now_tp - g_last_udp_sent >= std::chrono::milliseconds(1)) { - auto payload = - make_imotions_packet(currentPilotElevation, currentPilotAirspeed, - currentPilotVerticalVelocity, currentPilotHeading); - udp_send(payload); - g_last_udp_sent = now_tp; - last_send_timestamp = get_current_timestamp(); - } - - int last_offset = 10; - auto get_next_y_offset = [&last_offset, t]() { - last_offset = last_offset + 10; - return t - last_offset; - }; - - vector draw_order = { - elevationFlightmodelStr, elevationPilotStr, - airspeedFlightmodelStr, airspeedPilotStr, - verticalVelocityFlightmodelStr, verticalVelocityPilotStr, - headingFlightmodelStr, headingPilotStr, - }; - - for (const string &line : draw_order) { - XPLMDrawString(col_white, l + 10, get_next_y_offset(), (char *)line.c_str(), - NULL, xplmFont_Proportional); - } - - int button_width = 120; - int button_height = 30; - int button_x = l + 10; - int button_y = b + 40; - - button_left = button_x; - button_right = button_x + button_width; - button_bottom = button_y; - button_top = button_y + button_height; - - float col_button[] = {0.2f, 0.5f, 0.8f, 1.0f}; - glColor4fv(col_button); - glBegin(GL_QUADS); - glVertex2i(button_left, button_bottom); - glVertex2i(button_right, button_bottom); - glVertex2i(button_right, button_top); - glVertex2i(button_left, button_top); - glEnd(); - - float col_text[] = {1.0f, 1.0f, 1.0f}; - std::string button_label = "Send Packet"; - XPLMDrawString(col_text, button_left + 10, button_bottom + 8, - (char *)button_label.c_str(), NULL, xplmFont_Proportional); - - std::string ts_label = - "Last sent: " + - (last_send_timestamp.empty() ? "Never" : last_send_timestamp); - XPLMDrawString(col_text, button_right + 10, button_bottom + 8, - (char *)ts_label.c_str(), NULL, xplmFont_Proportional); - - vector send_to_baton = { - currentPilotElevation, - currentPilotAirspeed, - currentPilotHeading, - currentPilotVerticalVelocity, - }; -} \ No newline at end of file +#include +#include +#include +#include +#include +#include +#include + +#include "subprojects/baton/lib.rs.h" + +using std::string; +using std::vector; + +#ifdef __cplusplus +extern "C" { +#endif + +#include "XPLMDataAccess.h" +#include "XPLMDisplay.h" +#include "XPLMGraphics.h" +#include "XPLMPlugin.h" +#include "XPLMProcessing.h" +#include "XPLMUtilities.h" + +#ifdef APL +#include +#else +#include +#endif + +#ifdef __cplusplus +} +#endif + +#ifndef XPLM300 +#error This is made to be compiled against the XPLM300 SDK +#endif + +static XPLMWindowID g_window; +static XPLMDataRef elevationFlightmodelRef; +static XPLMDataRef elevationPilotRef; +static XPLMDataRef airspeedFlightmodelRef; +static XPLMDataRef airspeedPilotRef; +static XPLMDataRef verticalVelocityFlightmodelRef; +static XPLMDataRef verticalVelocityPilotRef; +static XPLMDataRef headingFlightmodelRef; +static XPLMDataRef headingPilotRef; + +rust::cxxbridge1::Box baton = new_baton_handle(); + +void draw_pilotdatasync_plugin(XPLMWindowID in_window_id, void *in_refcon); + +int dummy_mouse_handler(XPLMWindowID in_window_id, int x, int y, int is_down, + void *in_refcon) { + return 0; +} + +XPLMCursorStatus dummy_cursor_status_handler(XPLMWindowID in_window_id, int x, + int y, void *in_refcon) { + return xplm_CursorDefault; +} + +int dummy_wheel_handler(XPLMWindowID in_window_id, int x, int y, int wheel, + int clicks, void *in_refcon) { + return 0; +} + +void dummy_key_handler(XPLMWindowID in_window_id, char key, XPLMKeyFlags flags, + char virtual_key, void *in_refcon, int losing_focus) {} + +volatile bool stop_exec = false; + +static int button_left = 0, button_top = 0, button_right = 0, button_bottom = 0; +static std::string last_send_timestamp = ""; + +std::string get_current_timestamp() { + std::time_t now = std::time(nullptr); + char buf[32]; + std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", std::localtime(&now)); + return buf; +} + +int mouse_handler(XPLMWindowID in_window_id, int x, int y, int is_down, + void *in_refcon) { + if (is_down) { + if (x >= button_left && x <= button_right && y >= button_bottom && + y <= button_top) { + float msToFeetRate = 3.28084; + float msToKnotsRate = 1.94384; + float currentPilotElevation = + XPLMGetDataf(elevationPilotRef) * msToFeetRate; + float currentPilotAirspeed = + XPLMGetDataf(airspeedPilotRef) * msToKnotsRate; + float currentPilotHeading = XPLMGetDataf(headingPilotRef); + float currentPilotVerticalVelocity = + XPLMGetDataf(verticalVelocityPilotRef); + + std::vector send_to_baton = { + currentPilotElevation, + currentPilotAirspeed, + currentPilotHeading, + currentPilotVerticalVelocity, + }; + baton->send(send_to_baton); + last_send_timestamp = get_current_timestamp(); + } + } + return 0; +} + +PLUGIN_API int XPluginStart(char *outName, char *outSig, char *outDesc) { + strcpy(outName, "PilotDataSyncPlugin"); + strcpy(outSig, "oss.pilotdatasyncplugin"); + strcpy(outDesc, "A plug-in that collects and transmits X-Plane 11 data to " + "the iMotions platform for data collection and research"); + + XPLMCreateWindow_t params; + params.structSize = sizeof(params); + params.visible = 1; + params.drawWindowFunc = draw_pilotdatasync_plugin; + params.handleMouseClickFunc = mouse_handler; + params.handleRightClickFunc = dummy_mouse_handler; + params.handleMouseWheelFunc = dummy_wheel_handler; + params.handleKeyFunc = dummy_key_handler; + params.handleCursorFunc = dummy_cursor_status_handler; + params.refcon = NULL; + params.layer = xplm_WindowLayerFloatingWindows; + params.decorateAsFloatingWindow = xplm_WindowDecorationRoundRectangle; + + int left, bottom, right, top; + XPLMGetScreenBoundsGlobal(&left, &top, &right, &bottom); + params.left = left + 50; + params.bottom = bottom + 150; + params.right = params.left + 350; + params.top = params.bottom + 200; + + elevationFlightmodelRef = + XPLMFindDataRef("sim/flightmodel/position/elevation"); + elevationPilotRef = + XPLMFindDataRef("sim/cockpit2/gauges/indicators/altitude_ft_pilot"); + airspeedFlightmodelRef = + XPLMFindDataRef("sim/flightmodel/position/true_airspeed"); + airspeedPilotRef = + XPLMFindDataRef("sim/cockpit2/gauges/indicators/airspeed_kts_pilot"); + verticalVelocityFlightmodelRef = + XPLMFindDataRef("sim/flightmodel/position/vh_ind_fpm"); + verticalVelocityPilotRef = + XPLMFindDataRef("sim/cockpit2/gauges/indicators/vvi_fpm_pilot"); + headingFlightmodelRef = XPLMFindDataRef("sim/flightmodel/position/mag_psi"); + headingPilotRef = XPLMFindDataRef( + "sim/cockpit2/gauges/indicators/heading_AHARS_deg_mag_pilot"); + + g_window = XPLMCreateWindowEx(¶ms); + XPLMSetWindowPositioningMode(g_window, xplm_WindowPositionFree, -1); + XPLMSetWindowTitle(g_window, "Positional Flight Data"); + + return g_window != NULL; +} + +PLUGIN_API void XPluginStop() { + XPLMDestroyWindow(g_window); + g_window = NULL; +} + +PLUGIN_API void XPluginDisable(void) { baton->stop(); } + +PLUGIN_API int XPluginEnable(void) { + baton->start(); + return 1; +} + +PLUGIN_API void XPluginReceiveMessage(XPLMPluginID inFrom, int inMsg, + void *inParam) {} + +void draw_pilotdatasync_plugin(XPLMWindowID in_window_id, void *in_refcon) { + XPLMSetGraphicsState(0, 0, 0, 0, 1, 1, 0); + + int l, t, r, b; + XPLMGetWindowGeometry(in_window_id, &l, &t, &r, &b); + + float col_white[] = {1.0, 1.0, 1.0}; + float msToFeetRate = 3.28084; + float msToKnotsRate = 1.94384; + + auto build_str = [](string label, string unit, float value) { + string suffix = !std::isnan(value) ? std::to_string(value) + " " + unit + : "(Error Reading Data)"; + return label + ": " + suffix; + }; + + float currentFlightmodelElevation = + XPLMGetDataf(elevationFlightmodelRef) * msToFeetRate; + string elevationFlightmodelStr = build_str("Elevation, Flightmodel (MSL)", + "ft", currentFlightmodelElevation); + + float currentPilotElevation = XPLMGetDataf(elevationPilotRef) * msToFeetRate; + string elevationPilotStr = + build_str("Elevation, Pilot (MSL)", "ft", currentPilotElevation); + + float currentFlightmodelAirspeed = + XPLMGetDataf(airspeedFlightmodelRef) * msToKnotsRate; + string airspeedFlightmodelStr = + build_str("Airspeed, Flightmodel", "knots", currentFlightmodelAirspeed); + + float currentPilotAirspeed = XPLMGetDataf(airspeedPilotRef) * msToKnotsRate; + string airspeedPilotStr = + build_str("Airspeed, Pilot", "knots", currentPilotAirspeed); + + float currentFlightmodelVerticalVelocity = + XPLMGetDataf(verticalVelocityFlightmodelRef); + string verticalVelocityFlightmodelStr = + build_str("Vertical Velocity, Flightmodel", "ft/min", + currentFlightmodelVerticalVelocity); + + float currentPilotVerticalVelocity = XPLMGetDataf(verticalVelocityPilotRef); + string verticalVelocityPilotStr = build_str( + "Vertical Velocity, Flightmodel", "ft/min", currentPilotVerticalVelocity); + + float currentFlightmodelHeading = XPLMGetDataf(headingFlightmodelRef); + string headingFlightmodelStr = + build_str("Heading, Flightmodel", "°M", currentFlightmodelHeading); + + float currentPilotHeading = XPLMGetDataf(headingPilotRef); + string headingPilotStr = + build_str("Heading, Pilot", "°M", currentPilotHeading); + + int last_offset = 10; + auto get_next_y_offset = [&last_offset, t]() { + last_offset = last_offset + 10; + return t - last_offset; + }; + + vector draw_order = { + elevationFlightmodelStr, elevationPilotStr, + airspeedFlightmodelStr, verticalVelocityFlightmodelStr, + verticalVelocityPilotStr, headingFlightmodelStr, + headingPilotStr, + }; + + for (string line : draw_order) { + XPLMDrawString(col_white, l + 10, get_next_y_offset(), (char *)line.c_str(), + NULL, xplmFont_Proportional); + } + + int button_width = 120; + int button_height = 30; + int button_x = l + 10; + int button_y = b + 40; + + button_left = button_x; + button_right = button_x + button_width; + button_bottom = button_y; + button_top = button_y + button_height; + + float col_button[] = {0.2f, 0.5f, 0.8f, 1.0f}; + glColor4fv(col_button); + glBegin(GL_QUADS); + glVertex2i(button_left, button_bottom); + glVertex2i(button_right, button_bottom); + glVertex2i(button_right, button_top); + glVertex2i(button_left, button_top); + glEnd(); + + float col_text[] = {1.0f, 1.0f, 1.0f}; + std::string button_label = "Send Packet"; + XPLMDrawString(col_text, button_left + 10, button_bottom + 8, + (char *)button_label.c_str(), NULL, xplmFont_Proportional); + + std::string ts_label = + "Last sent: " + + (last_send_timestamp.empty() ? "Never" : last_send_timestamp); + XPLMDrawString(col_text, button_right + 10, button_bottom + 8, + (char *)ts_label.c_str(), NULL, xplmFont_Proportional); + + vector send_to_baton = { + currentPilotElevation, + currentPilotAirspeed, + currentPilotHeading, + currentPilotVerticalVelocity, + }; + baton->send(send_to_baton); +} diff --git a/xplane_plugin/subprojects/baton/build.rs b/xplane_plugin/subprojects/baton/build.rs index 5d67d94..f2d9515 100644 --- a/xplane_plugin/subprojects/baton/build.rs +++ b/xplane_plugin/subprojects/baton/build.rs @@ -1,9 +1,15 @@ fn main() { - cxx_build::bridge("src/lib.rs") - .std("c++20") - .compiler("x86_64-w64-mingw32-g++") - .target("x86_64-pc-windows-gnu") - .compile("baton"); + let mut bridge = cxx_build::bridge("src/lib.rs"); + bridge.std("c++20"); + + if let Ok(target) = std::env::var("BATON_TARGET") { + if !target.is_empty() { + bridge.target(&target); + } + } + + bridge.compile("baton"); println!("cargo:rerun-if-changed=src/lib.rs"); + println!("cargo:rerun-if-env-changed=BATON_TARGET"); } diff --git a/xplane_plugin/subprojects/baton/meson.build b/xplane_plugin/subprojects/baton/meson.build index bcc192d..f0653dc 100644 --- a/xplane_plugin/subprojects/baton/meson.build +++ b/xplane_plugin/subprojects/baton/meson.build @@ -4,13 +4,26 @@ project( meson_version: '>=1.7.0', ) +rust_target = '' + +if host_machine.system() == 'windows' and host_machine.cpu_family() == 'x86_64' + rust_target = 'x86_64-pc-windows-gnu' +elif host_machine.system() == 'darwin' and host_machine.cpu_family() == 'aarch64' + rust_target = 'aarch64-apple-darwin' +elif host_machine.system() == 'darwin' and host_machine.cpu_family() == 'x86_64' + rust_target = 'x86_64-apple-darwin' +elif host_machine.system() == 'linux' and host_machine.cpu_family() == 'x86_64' + rust_target = 'x86_64-unknown-linux-gnu' +endif + baton_tgt = custom_target( 'baton', command: [ 'python3', '@CURRENT_SOURCE_DIR@/script.py', '@OUTDIR@', - '@CURRENT_SOURCE_DIR@' + '@CURRENT_SOURCE_DIR@', + rust_target, ], output: ['libbaton.a', 'lib.rs.cc', 'lib.rs.h'], build_always_stale: true, diff --git a/xplane_plugin/subprojects/baton/script.py b/xplane_plugin/subprojects/baton/script.py index e5e05fe..00918a7 100644 --- a/xplane_plugin/subprojects/baton/script.py +++ b/xplane_plugin/subprojects/baton/script.py @@ -1,35 +1,44 @@ +import os import subprocess import sys import shutil outdir = sys.argv[1] curr_src_dir = sys.argv[2] +target = sys.argv[3] if len(sys.argv) > 3 else '' -print(f"outdir: {outdir}, curr_src_dir: {curr_src_dir}") - -subprocess.run([ - 'cargo', +command = [ + 'cargo', 'build', - '--target', - 'x86_64-pc-windows-gnu', '--release', '--target-dir', outdir, '--manifest-path', - f'{curr_src_dir}/Cargo.toml' -]) + f'{curr_src_dir}/Cargo.toml' +] + +if target: + command.extend(['--target', target]) + +env = dict(os.environ) +if target: + env['BATON_TARGET'] = target + +subprocess.run(command, check=True, env=env) + +artifact_root = f'{outdir}/{target}' if target else outdir shutil.copyfile( - f'{outdir}/x86_64-pc-windows-gnu/release/libbaton.a', + f'{artifact_root}/release/libbaton.a', f'{outdir}/libbaton.a' ) shutil.copyfile( - f'{outdir}/x86_64-pc-windows-gnu/cxxbridge/baton/src/lib.rs.cc', + f'{artifact_root}/cxxbridge/baton/src/lib.rs.cc', f'{outdir}/lib.rs.cc' ) shutil.copyfile( - f'{outdir}/x86_64-pc-windows-gnu/cxxbridge/baton/src/lib.rs.h', + f'{artifact_root}/cxxbridge/baton/src/lib.rs.h', f'{outdir}/lib.rs.h' )