200 lines
8.5 KiB
Rust
200 lines
8.5 KiB
Rust
//! 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 <Windows.Devices.Midi2.winmd>
|
|
//! cargo run --manifest-path scripts/midi2-bindings/Cargo.toml -- appsdk <Microsoft.Windows.Devices.Midi2.winmd>
|
|
//! ```
|
|
//!
|
|
//! 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<String> = 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 <inbox|appsdk> <path to the .winmd>");
|
|
std::process::exit(2);
|
|
}
|
|
};
|
|
let Some(metadata) = arguments.get(1).cloned() else {
|
|
eprintln!("usage: midi2-bindings <inbox|appsdk> <path to the .winmd>");
|
|
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
|
|
}
|