Skip to main content

basic_example/
basic_example.rs

1use std::error::Error;
2use std::io::{Read, Write};
3use std::process::ExitCode;
4
5#[cfg(feature = "ble")]
6use zmk_studio_api::transport::ble::BluestTransport;
7#[cfg(feature = "serial")]
8use zmk_studio_api::transport::serial::SerialTransport;
9use zmk_studio_api::{Behavior, ClientError, HidUsage, Keycode, StudioClient};
10
11fn main() -> ExitCode {
12    match run() {
13        Ok(()) => ExitCode::SUCCESS,
14        Err(err) => {
15            eprintln!("error: {err}");
16            ExitCode::from(1)
17        }
18    }
19}
20
21fn run() -> Result<(), Box<dyn Error>> {
22    let mut args = std::env::args().skip(1);
23    let Some(mode) = args.next() else {
24        print_usage();
25        return Ok(());
26    };
27
28    match mode.as_str() {
29        "serial" => {
30            #[cfg(feature = "serial")]
31            {
32                let Some(port) = args.next() else {
33                    print_usage();
34                    return Ok(());
35                };
36                let client = StudioClient::new(SerialTransport::open(&port)?);
37                run_example(client)
38            }
39            #[cfg(not(feature = "serial"))]
40            {
41                Err("built without `serial` feature".into())
42            }
43        }
44        "ble" => {
45            #[cfg(feature = "ble")]
46            {
47                let devices = StudioClient::<BluestTransport>::list_ble_devices()?;
48                if devices.is_empty() {
49                    return Err(
50                        "No Bluetooth keyboards found. Pair/connect your keyboard first.".into(),
51                    );
52                }
53                let device = &devices[0];
54                println!("Using BLE device: {}", device.display_name());
55
56                let client = StudioClient::new(BluestTransport::connect_device(&device.device_id)?);
57                run_example(client)
58            }
59            #[cfg(not(feature = "ble"))]
60            {
61                Err("built without `ble` feature".into())
62            }
63        }
64        _ => {
65            print_usage();
66            Ok(())
67        }
68    }
69}
70
71fn run_example<T: Read + Write>(mut client: StudioClient<T>) -> Result<(), Box<dyn Error>> {
72    let info = client.get_device_info()?;
73    println!("Device: {}", info.name);
74    println!("Lock: {:?}", client.get_lock_state()?);
75
76    let behavior_ids = client.list_all_behaviors()?;
77    println!("Behavior count: {}", behavior_ids.len());
78    if let Some(first_behavior_id) = behavior_ids.first().copied() {
79        let details = client.get_behavior_details(first_behavior_id)?;
80        println!("First behavior: {} ({})", details.id, details.display_name);
81    }
82
83    let keymap = match client.get_keymap() {
84        Ok(keymap) => keymap,
85        Err(ClientError::Meta(_)) => {
86            println!("Keymap request denied (likely locked); press `&studio_unlock` then rerun.");
87            return Ok(());
88        }
89        Err(err) => return Err(Box::new(err)),
90    };
91    println!("Layers: {}", keymap.layers.len());
92
93    let layouts = client.get_physical_layouts()?;
94    println!(
95        "Physical layouts: {} (active index: {})",
96        layouts.layouts.len(),
97        layouts.active_layout_index
98    );
99
100    let Some(first_layer) = keymap.layers.first() else {
101        return Ok(());
102    };
103    if first_layer.bindings.is_empty() {
104        return Ok(());
105    }
106
107    let layer_id = first_layer.id;
108    let key_position = 0;
109
110    let before = client.get_key_at(layer_id, key_position)?;
111    println!("Before: {before:?}");
112
113    client.set_key_at(
114        layer_id,
115        key_position,
116        Behavior::KeyPress(HidUsage::from_encoded(Keycode::A.to_hid_usage())),
117    )?;
118    let after = client.get_key_at(layer_id, key_position)?;
119    println!("After:  {after:?}");
120
121    // Change management APIs.
122    let has_changes = client.check_unsaved_changes()?;
123    println!("Unsaved changes: {has_changes}");
124    if has_changes {
125        client.discard_changes()?;
126    }
127
128    Ok(())
129}
130
131fn print_usage() {
132    println!("Usage:");
133    println!("  cargo run --example basic_example -- serial <PORT>");
134    println!("  cargo run --example basic_example --features ble -- ble");
135}