//! Generates the Windows MIDI Services bindings in `crates/platform/src/midi/wms/`. //! //! Windows MIDI Services has two forms of its API: the one Windows itself carries from late 2026, //! `Windows.Devices.Midi2`, and the App SDK, `Microsoft.Windows.Devices.Midi2`, which works with //! the service Windows ships before then once the user installs its runtime. Midi Harbor binds //! both. From the repository root: //! //! ```text //! cargo run --manifest-path scripts/midi2-bindings/Cargo.toml -- inbox //! cargo run --manifest-path scripts/midi2-bindings/Cargo.toml -- appsdk //! ``` //! //! The metadata is in the `Windows.Devices.Midi2` and `Microsoft.Windows.Devices.Midi2` NuGet //! packages, under `ref/native`. use std::path::Path; /// The in-box API's types the backend uses. Their dependencies are generated with them. const INBOX: &[&str] = &[ "Windows.Devices.Midi2.MidiApi", "Windows.Devices.Midi2.MidiSession", "Windows.Devices.Midi2.MidiEndpointConnection", "Windows.Devices.Midi2.MidiEndpointConnectionSettings", "Windows.Devices.Midi2.MidiMessageReceivedEventArgs", "Windows.Devices.Midi2.IMidiMessageReceivedEventSource", "Windows.Devices.Midi2.MidiGroup", "Windows.Devices.Midi2.MidiSendMessageResults", "Windows.Devices.Midi2.MidiMessageProcessingPluginAddResult", "Windows.Devices.Midi2.Enumeration.MidiFunctionBlock", "Windows.Devices.Midi2.Enumeration.MidiFunctionBlockDirection", "Windows.Devices.Midi2.Enumeration.MidiFunctionBlockRepresentsMidi10Connection", "Windows.Devices.Midi2.Enumeration.MidiDeclaredEndpointInfo", "Windows.Devices.Midi2.ServiceConfig.MidiServiceTransportPluginConfigManager", "Windows.Devices.Midi2.ServiceConfig.MidiServiceConfigResponse", "Windows.Devices.Midi2.ServiceConfig.MidiServiceConfigResponseStatus", "Windows.Devices.Midi2.Transports.Virtual.MidiVirtualDeviceManager", "Windows.Devices.Midi2.Transports.Virtual.MidiVirtualDevice", "Windows.Devices.Midi2.Transports.Virtual.MidiVirtualDeviceCreationConfig", ]; /// The App SDK's types the backend uses, which sit in slightly different places. const APPSDK: &[&str] = &[ "Microsoft.Windows.Devices.Midi2.MidiSession", "Microsoft.Windows.Devices.Midi2.MidiEndpointConnection", "Microsoft.Windows.Devices.Midi2.IMidiEndpointConnectionSettings", "Microsoft.Windows.Devices.Midi2.MidiMessageReceivedEventArgs", "Microsoft.Windows.Devices.Midi2.IMidiMessageReceivedEventSource", "Microsoft.Windows.Devices.Midi2.MidiGroup", "Microsoft.Windows.Devices.Midi2.MidiSendMessageResults", "Microsoft.Windows.Devices.Midi2.MidiFunctionBlock", "Microsoft.Windows.Devices.Midi2.MidiFunctionBlockDirection", "Microsoft.Windows.Devices.Midi2.MidiFunctionBlockRepresentsMidi10Connection", "Microsoft.Windows.Devices.Midi2.MidiDeclaredEndpointInfo", "Microsoft.Windows.Devices.Midi2.Endpoints.Virtual.MidiVirtualDeviceManager", "Microsoft.Windows.Devices.Midi2.Endpoints.Virtual.MidiVirtualDevice", "Microsoft.Windows.Devices.Midi2.Endpoints.Virtual.MidiVirtualDeviceCreationConfig", ]; /// What goes above the generated code. const HEADER: &str = "// Generated by scripts/midi2-bindings with `windows-bindgen` 0.64.0. Not edited by hand: // run that tool to regenerate. #![allow( non_snake_case, non_upper_case_globals, non_camel_case_types, dead_code, unsafe_op_in_unsafe_fn, clippy::all, clippy::pedantic, clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::indexing_slicing, clippy::integer_division )] "; fn main() { let arguments: Vec = std::env::args().skip(1).collect(); let (types, output) = match arguments.first().map(String::as_str) { Some("inbox") => (INBOX, "crates/platform/src/midi/wms/inbox_bindings.rs"), Some("appsdk") => (APPSDK, "crates/platform/src/midi/wms/appsdk_bindings.rs"), _ => { eprintln!("usage: midi2-bindings "); std::process::exit(2); } }; let Some(metadata) = arguments.get(1).cloned() else { eprintln!("usage: midi2-bindings "); std::process::exit(2); }; // Generate. let mut bindgen = vec![ "--in".to_owned(), metadata, "default".to_owned(), "--out".to_owned(), output.to_owned(), "--reference".to_owned(), "windows,skip-root,Windows.Foundation".to_owned(), "--reference".to_owned(), "windows,skip-root,Windows.Data".to_owned(), "--filter".to_owned(), ]; bindgen.extend(types.iter().map(|name| (*name).to_owned())); // Methods whose types are not asked for are skipped, which is expected, so the warnings are // shown and not treated as failure. let warnings = windows_bindgen::bindgen(bindgen); eprintln!("{warnings}"); // Correct what the generator gets wrong, and add the header. let generated = std::fs::read_to_string(Path::new(output)).unwrap_or_default(); let body = generated .find("pub mod ") .and_then(|start| generated.get(start..)) .unwrap_or_default(); let fixed = fallible_statics(body); if let Err(error) = std::fs::write(output, format!("{HEADER}{fixed}")) { eprintln!("could not write {output}: {error}"); std::process::exit(1); } } /// Makes static methods marked as never failing return a `Result` after all. /// /// The metadata marks some static methods `[noexcept]`, and bindgen 0.64 then returns the bare /// value from inside the closure that fetches the class's factory, which must return a `Result`: /// fetching the factory can fail, since the class may not be installed. Such a method returns a /// `windows_core::Result` of its value here instead. fn fallible_statics(source: &str) -> String { const OPENING: &str = "(|this| unsafe {"; const NOEXCEPT: &str = "debug_assert!(hresult__.0 == 0);"; let mut edits: Vec<(usize, usize, String)> = Vec::new(); let mut from = 0; while let Some(found) = source.get(from..).and_then(|rest| rest.find(OPENING)) { let opening = from + found; from = opening + OPENING.len(); // Only a static helper call, `Self::I...(`, whose closure is noexcept. let before = source.get(..opening).unwrap_or_default(); let is_static = before .rsplit_once("Self::I") .is_some_and(|(_, name)| name.chars().all(|c| c.is_alphanumeric() || c == '_')); if !is_static { continue; } let Some(close) = source.get(from..).and_then(|rest| rest.find("})")) else { continue; }; let closure = source.get(from..from + close).unwrap_or_default(); let Some(check) = closure.find(NOEXCEPT) else { continue; }; // Wrap the value the closure ends with. let tail_start = from + check + NOEXCEPT.len(); let tail = source.get(tail_start..from + close).unwrap_or_default(); let expression = tail.trim(); let offset = tail_start + tail.find(expression).unwrap_or(0); edits.push((offset, offset + expression.len(), format!("Ok({expression})"))); // Make the function return a Result. let Some(signature) = before.rfind("pub fn ") else { continue; }; let Some(body) = before.get(signature..).and_then(|rest| rest.find(" {\n")) else { continue; }; let header = before.get(signature..signature + body).unwrap_or_default(); if let Some(arrow) = header.rfind(") -> ") { let returned_start = signature + arrow + ") -> ".len(); let mut returned_end = signature + body; // A generic method's `where` clause follows its return type. if let Some(clause) = source .get(returned_start..returned_end) .and_then(|returned| returned.find("\n")) { returned_end = returned_start + clause; } let returned = source.get(returned_start..returned_end).unwrap_or_default(); edits.push(( returned_start, returned_end, format!("windows_core::Result<{returned}>"), )); } } // Applied from the end, so earlier offsets stay valid. edits.sort_by_key(|edit| std::cmp::Reverse(edit.0)); let mut fixed = source.to_owned(); for (start, end, replacement) in edits { fixed.replace_range(start..end, &replacement); } fixed }