Skip to main content

zmk_studio_api/
client.rs

1use std::collections::{HashMap, VecDeque};
2use std::io::{Read, Write};
3
4use crate::binding::{Behavior, BehaviorRole, role_from_display_name};
5use crate::framing::FrameDecoder;
6use crate::hid_usage::HidUsage;
7use crate::proto::zmk;
8use crate::proto::zmk::studio;
9use crate::protocol::{ProtocolError, decode_responses, encode_request};
10#[cfg(feature = "serial")]
11use crate::transport::serial::{SerialTransport, SerialTransportError};
12#[cfg(feature = "ble")]
13use crate::transport::{
14    BleDeviceInfo, BleDiscoveryMode, PlatformBleError, PlatformBleTransport,
15    discover_platform_ble_devices,
16};
17
18/// High-level error type returned by [`StudioClient`] operations.
19#[derive(Debug)]
20pub enum ClientError {
21    Io(std::io::Error),
22    Protocol(ProtocolError),
23    Meta(zmk::meta::ErrorConditions),
24    NoResponse,
25    MissingResponseType,
26    MissingSubsystem,
27    UnexpectedSubsystem(&'static str),
28    UnexpectedRequestId { expected: u32, actual: u32 },
29    UnknownEnumValue { field: &'static str, value: i32 },
30    SetLayerBindingFailed(zmk::keymap::SetLayerBindingResponse),
31    SaveChangesFailed(zmk::keymap::SaveChangesErrorCode),
32    SetActivePhysicalLayoutFailed(zmk::keymap::SetActivePhysicalLayoutErrorCode),
33    MoveLayerFailed(zmk::keymap::MoveLayerErrorCode),
34    AddLayerFailed(zmk::keymap::AddLayerErrorCode),
35    RemoveLayerFailed(zmk::keymap::RemoveLayerErrorCode),
36    RestoreLayerFailed(zmk::keymap::RestoreLayerErrorCode),
37    SetLayerPropsFailed(zmk::keymap::SetLayerPropsResponse),
38    InvalidLayerOrPosition { layer_id: u32, key_position: i32 },
39    MissingBehaviorRole(&'static str),
40    BehaviorIdOutOfRange { behavior_id: u32 },
41}
42
43impl std::fmt::Display for ClientError {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        match self {
46            Self::Io(err) => write!(f, "I/O error: {err}"),
47            Self::Protocol(err) => write!(f, "Protocol error: {err}"),
48            Self::Meta(cond) => write!(f, "Device returned meta error: {}", cond.as_str_name()),
49            Self::NoResponse => write!(f, "Device returned no response"),
50            Self::MissingResponseType => write!(f, "Response was missing type"),
51            Self::MissingSubsystem => write!(f, "Request response was missing subsystem"),
52            Self::UnexpectedSubsystem(expected) => {
53                write!(f, "Unexpected subsystem in response; expected {expected}")
54            }
55            Self::UnexpectedRequestId { expected, actual } => {
56                write!(
57                    f,
58                    "Unexpected request ID in response: expected {expected}, got {actual}"
59                )
60            }
61            Self::UnknownEnumValue { field, value } => {
62                write!(f, "Unknown enum value for {field}: {value}")
63            }
64            Self::SetLayerBindingFailed(code) => {
65                write!(f, "Set layer binding failed: {}", code.as_str_name())
66            }
67            Self::SaveChangesFailed(code) => {
68                write!(f, "Save changes failed: {}", code.as_str_name())
69            }
70            Self::SetActivePhysicalLayoutFailed(code) => {
71                write!(
72                    f,
73                    "Set active physical layout failed: {}",
74                    code.as_str_name()
75                )
76            }
77            Self::MoveLayerFailed(code) => write!(f, "Move layer failed: {}", code.as_str_name()),
78            Self::AddLayerFailed(code) => write!(f, "Add layer failed: {}", code.as_str_name()),
79            Self::RemoveLayerFailed(code) => {
80                write!(f, "Remove layer failed: {}", code.as_str_name())
81            }
82            Self::RestoreLayerFailed(code) => {
83                write!(f, "Restore layer failed: {}", code.as_str_name())
84            }
85            Self::SetLayerPropsFailed(code) => {
86                write!(f, "Set layer properties failed: {}", code.as_str_name())
87            }
88            Self::InvalidLayerOrPosition {
89                layer_id,
90                key_position,
91            } => write!(
92                f,
93                "Invalid layer/position: layer_id={layer_id}, key_position={key_position}"
94            ),
95            Self::MissingBehaviorRole(role) => {
96                write!(f, "Missing required behavior role in firmware: {role}")
97            }
98            Self::BehaviorIdOutOfRange { behavior_id } => {
99                write!(f, "Behavior ID is out of i32 range: {behavior_id}")
100            }
101        }
102    }
103}
104
105impl std::error::Error for ClientError {
106    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
107        match self {
108            Self::Io(err) => Some(err),
109            Self::Protocol(err) => Some(err),
110            _ => None,
111        }
112    }
113}
114
115impl From<std::io::Error> for ClientError {
116    fn from(value: std::io::Error) -> Self {
117        Self::Io(value)
118    }
119}
120
121impl From<ProtocolError> for ClientError {
122    fn from(value: ProtocolError) -> Self {
123        Self::Protocol(value)
124    }
125}
126
127/// High-level synchronous ZMK Studio RPC client.
128///
129/// The generic parameter `T` is any transport implementing [`Read`] + [`Write`]
130/// (for example [`crate::transport::serial::SerialTransport`]).
131pub struct StudioClient<T> {
132    io: T,
133    next_request_id: u32,
134    decoder: FrameDecoder,
135    read_buffer: Vec<u8>,
136    responses: VecDeque<studio::Response>,
137    notifications: VecDeque<studio::Notification>,
138    behavior_role_by_id: HashMap<u32, BehaviorRole>,
139    behavior_id_by_role: HashMap<BehaviorRole, u32>,
140}
141
142impl<T: Read + Write> StudioClient<T> {
143    pub fn new(io: T) -> Self {
144        Self::with_read_buffer(io, 256)
145    }
146
147    fn with_read_buffer(io: T, read_buffer_size: usize) -> Self {
148        Self {
149            io,
150            next_request_id: 0,
151            decoder: FrameDecoder::new(),
152            read_buffer: vec![0; read_buffer_size.max(1)],
153            responses: VecDeque::new(),
154            notifications: VecDeque::new(),
155            behavior_role_by_id: HashMap::new(),
156            behavior_id_by_role: HashMap::new(),
157        }
158    }
159
160    /// Returns the next queued notification, if any.
161    pub fn next_notification(&mut self) -> Option<studio::Notification> {
162        self.notifications.pop_front()
163    }
164
165    /// Blocks until a notification arrives and returns it.
166    pub fn read_notification_blocking(&mut self) -> Result<studio::Notification, ClientError> {
167        loop {
168            if let Some(notification) = self.next_notification() {
169                return Ok(notification);
170            }
171
172            let _ = self.read_next_response()?;
173        }
174    }
175
176    /// Returns static device information.
177    pub fn get_device_info(&mut self) -> Result<zmk::core::GetDeviceInfoResponse, ClientError> {
178        let response = self.call_core(zmk::core::request::RequestType::GetDeviceInfo(true))?;
179        match response.response_type {
180            Some(zmk::core::response::ResponseType::GetDeviceInfo(info)) => Ok(info),
181            _ => Err(ClientError::MissingResponseType),
182        }
183    }
184
185    /// Returns the current Studio lock state.
186    pub fn get_lock_state(&mut self) -> Result<zmk::core::LockState, ClientError> {
187        let response = self.call_core(zmk::core::request::RequestType::GetLockState(true))?;
188        match response.response_type {
189            Some(zmk::core::response::ResponseType::GetLockState(state)) => {
190                zmk::core::LockState::try_from(state).map_err(|_| ClientError::UnknownEnumValue {
191                    field: "core.get_lock_state",
192                    value: state,
193                })
194            }
195            _ => Err(ClientError::MissingResponseType),
196        }
197    }
198
199    /// Resets settings on the device.
200    ///
201    /// Returns the firmware-provided success boolean.
202    pub fn reset_settings(&mut self) -> Result<bool, ClientError> {
203        let response = self.call_core(zmk::core::request::RequestType::ResetSettings(true))?;
204        match response.response_type {
205            Some(zmk::core::response::ResponseType::ResetSettings(ok)) => Ok(ok),
206            _ => Err(ClientError::MissingResponseType),
207        }
208    }
209
210    /// Lists behavior IDs available on the connected device.
211    pub fn list_all_behaviors(&mut self) -> Result<Vec<u32>, ClientError> {
212        let response =
213            self.call_behaviors(zmk::behaviors::request::RequestType::ListAllBehaviors(true))?;
214        match response.response_type {
215            Some(zmk::behaviors::response::ResponseType::ListAllBehaviors(items)) => {
216                Ok(items.behaviors)
217            }
218            _ => Err(ClientError::MissingResponseType),
219        }
220    }
221
222    /// Returns details for a behavior ID (name and parameter metadata).
223    pub fn get_behavior_details(
224        &mut self,
225        behavior_id: u32,
226    ) -> Result<zmk::behaviors::GetBehaviorDetailsResponse, ClientError> {
227        let request = zmk::behaviors::GetBehaviorDetailsRequest { behavior_id };
228        let response = self.call_behaviors(
229            zmk::behaviors::request::RequestType::GetBehaviorDetails(request),
230        )?;
231        match response.response_type {
232            Some(zmk::behaviors::response::ResponseType::GetBehaviorDetails(details)) => {
233                Ok(details)
234            }
235            _ => Err(ClientError::MissingResponseType),
236        }
237    }
238
239    /// Returns the current keymap state from the device.
240    pub fn get_keymap(&mut self) -> Result<zmk::keymap::Keymap, ClientError> {
241        let response = self.call_keymap(zmk::keymap::request::RequestType::GetKeymap(true))?;
242        match response.response_type {
243            Some(zmk::keymap::response::ResponseType::GetKeymap(keymap)) => Ok(keymap),
244            _ => Err(ClientError::MissingResponseType),
245        }
246    }
247
248    /// Returns available physical layouts and the active layout index.
249    pub fn get_physical_layouts(&mut self) -> Result<zmk::keymap::PhysicalLayouts, ClientError> {
250        let response =
251            self.call_keymap(zmk::keymap::request::RequestType::GetPhysicalLayouts(true))?;
252        match response.response_type {
253            Some(zmk::keymap::response::ResponseType::GetPhysicalLayouts(layouts)) => Ok(layouts),
254            _ => Err(ClientError::MissingResponseType),
255        }
256    }
257
258    /// Sets a raw behavior binding for a specific layer position.
259    pub fn set_layer_binding(
260        &mut self,
261        layer_id: u32,
262        key_position: i32,
263        binding: zmk::keymap::BehaviorBinding,
264    ) -> Result<(), ClientError> {
265        let request = zmk::keymap::SetLayerBindingRequest {
266            layer_id,
267            key_position,
268            binding: Some(binding),
269        };
270
271        let response =
272            self.call_keymap(zmk::keymap::request::RequestType::SetLayerBinding(request))?;
273
274        match response.response_type {
275            Some(zmk::keymap::response::ResponseType::SetLayerBinding(raw)) => {
276                let code = zmk::keymap::SetLayerBindingResponse::try_from(raw).map_err(|_| {
277                    ClientError::UnknownEnumValue {
278                        field: "keymap.set_layer_binding",
279                        value: raw,
280                    }
281                })?;
282
283                if code == zmk::keymap::SetLayerBindingResponse::SetLayerBindingRespOk {
284                    Ok(())
285                } else {
286                    Err(ClientError::SetLayerBindingFailed(code))
287                }
288            }
289            _ => Err(ClientError::MissingResponseType),
290        }
291    }
292
293    /// Reads a behavior from a specific layer/key position.
294    pub fn get_key_at(
295        &mut self,
296        layer_id: u32,
297        key_position: i32,
298    ) -> Result<Behavior, ClientError> {
299        self.ensure_behavior_catalog()?;
300
301        let keymap = self.get_keymap()?;
302        let binding = binding_at(&keymap, layer_id, key_position).ok_or(
303            ClientError::InvalidLayerOrPosition {
304                layer_id,
305                key_position,
306            },
307        )?;
308
309        Ok(self.resolve_binding(&binding))
310    }
311
312    /// Fetches the keymap and resolves every binding into a typed [`Behavior`].
313    ///
314    /// Returns a `Vec` of layers, each layer being a `Vec<Behavior>` matching
315    /// the order of bindings in the keymap. It fetches the keymap once and converts all
316    /// bindings in a single pass.
317    pub fn resolve_keymap(&mut self) -> Result<Vec<Vec<Behavior>>, ClientError> {
318        self.ensure_behavior_catalog()?;
319        let keymap = self.get_keymap()?;
320
321        let layers = keymap
322            .layers
323            .iter()
324            .map(|layer| {
325                layer
326                    .bindings
327                    .iter()
328                    .map(|binding| self.resolve_binding(binding))
329                    .collect()
330            })
331            .collect();
332
333        Ok(layers)
334    }
335
336    fn resolve_binding(&self, binding: &zmk::keymap::BehaviorBinding) -> Behavior {
337        let Ok(binding_behavior_id) = u32::try_from(binding.behavior_id) else {
338            return Behavior::Unknown {
339                behavior_id: binding.behavior_id,
340                param1: binding.param1,
341                param2: binding.param2,
342            };
343        };
344        let Some(role) = self.behavior_role_by_id.get(&binding_behavior_id).copied() else {
345            return Behavior::Unknown {
346                behavior_id: binding.behavior_id,
347                param1: binding.param1,
348                param2: binding.param2,
349            };
350        };
351
352        match role {
353            BehaviorRole::KeyPress => Behavior::KeyPress(HidUsage::from_encoded(binding.param1)),
354            BehaviorRole::KeyToggle => Behavior::KeyToggle(HidUsage::from_encoded(binding.param1)),
355            BehaviorRole::LayerTap => Behavior::LayerTap {
356                layer_id: binding.param1,
357                tap: HidUsage::from_encoded(binding.param2),
358            },
359            BehaviorRole::ModTap => Behavior::ModTap {
360                hold: HidUsage::from_encoded(binding.param1),
361                tap: HidUsage::from_encoded(binding.param2),
362            },
363            BehaviorRole::StickyKey => Behavior::StickyKey(HidUsage::from_encoded(binding.param1)),
364            BehaviorRole::StickyLayer => Behavior::StickyLayer {
365                layer_id: binding.param1,
366            },
367            BehaviorRole::MomentaryLayer => Behavior::MomentaryLayer {
368                layer_id: binding.param1,
369            },
370            BehaviorRole::ToggleLayer => Behavior::ToggleLayer {
371                layer_id: binding.param1,
372            },
373            BehaviorRole::ToLayer => Behavior::ToLayer {
374                layer_id: binding.param1,
375            },
376            BehaviorRole::Bluetooth => Behavior::Bluetooth {
377                command: binding.param1,
378                value: binding.param2,
379            },
380            BehaviorRole::ExternalPower => Behavior::ExternalPower {
381                value: binding.param1,
382            },
383            BehaviorRole::OutputSelection => Behavior::OutputSelection {
384                value: binding.param1,
385            },
386            BehaviorRole::Backlight => Behavior::Backlight {
387                command: binding.param1,
388                value: binding.param2,
389            },
390            BehaviorRole::Underglow => Behavior::Underglow {
391                command: binding.param1,
392                value: binding.param2,
393            },
394            BehaviorRole::MouseKeyPress => Behavior::MouseKeyPress {
395                value: binding.param1,
396            },
397            BehaviorRole::MouseMove => Behavior::MouseMove {
398                value: binding.param1,
399            },
400            BehaviorRole::MouseScroll => Behavior::MouseScroll {
401                value: binding.param1,
402            },
403            BehaviorRole::CapsWord => Behavior::CapsWord,
404            BehaviorRole::KeyRepeat => Behavior::KeyRepeat,
405            BehaviorRole::Reset => Behavior::Reset,
406            BehaviorRole::Bootloader => Behavior::Bootloader,
407            BehaviorRole::SoftOff => Behavior::SoftOff,
408            BehaviorRole::StudioUnlock => Behavior::StudioUnlock,
409            BehaviorRole::GraveEscape => Behavior::GraveEscape,
410            BehaviorRole::Transparent => Behavior::Transparent,
411            BehaviorRole::None => Behavior::None,
412        }
413    }
414
415    /// Set a behavior at a specific layer/key position.
416    ///
417    /// Persist with [`StudioClient::save_changes`] or revert with [`StudioClient::discard_changes`].
418    pub fn set_key_at(
419        &mut self,
420        layer_id: u32,
421        key_position: i32,
422        behavior: Behavior,
423    ) -> Result<(), ClientError> {
424        self.ensure_behavior_catalog()?;
425        let binding = match behavior {
426            Behavior::KeyPress(key) => zmk::keymap::BehaviorBinding {
427                behavior_id: self.behavior_id_for(BehaviorRole::KeyPress, "Key Press")?,
428                param1: key.to_hid_usage(),
429                param2: 0,
430            },
431            Behavior::KeyToggle(key) => zmk::keymap::BehaviorBinding {
432                behavior_id: self.behavior_id_for(BehaviorRole::KeyToggle, "Key Toggle")?,
433                param1: key.to_hid_usage(),
434                param2: 0,
435            },
436            Behavior::LayerTap {
437                layer_id: hold_layer_id,
438                tap,
439            } => zmk::keymap::BehaviorBinding {
440                behavior_id: self.behavior_id_for(BehaviorRole::LayerTap, "Layer-Tap")?,
441                param1: hold_layer_id,
442                param2: tap.to_hid_usage(),
443            },
444            Behavior::ModTap { hold, tap } => zmk::keymap::BehaviorBinding {
445                behavior_id: self.behavior_id_for(BehaviorRole::ModTap, "Mod-Tap")?,
446                param1: hold.to_hid_usage(),
447                param2: tap.to_hid_usage(),
448            },
449            Behavior::StickyKey(key) => zmk::keymap::BehaviorBinding {
450                behavior_id: self.behavior_id_for(BehaviorRole::StickyKey, "Sticky Key")?,
451                param1: key.to_hid_usage(),
452                param2: 0,
453            },
454            Behavior::StickyLayer {
455                layer_id: target_layer_id,
456            } => zmk::keymap::BehaviorBinding {
457                behavior_id: self.behavior_id_for(BehaviorRole::StickyLayer, "Sticky Layer")?,
458                param1: target_layer_id,
459                param2: 0,
460            },
461            Behavior::MomentaryLayer {
462                layer_id: hold_layer_id,
463            } => zmk::keymap::BehaviorBinding {
464                behavior_id: self
465                    .behavior_id_for(BehaviorRole::MomentaryLayer, "Momentary Layer")?,
466                param1: hold_layer_id,
467                param2: 0,
468            },
469            Behavior::ToggleLayer {
470                layer_id: target_layer_id,
471            } => zmk::keymap::BehaviorBinding {
472                behavior_id: self.behavior_id_for(BehaviorRole::ToggleLayer, "Toggle Layer")?,
473                param1: target_layer_id,
474                param2: 0,
475            },
476            Behavior::ToLayer {
477                layer_id: target_layer_id,
478            } => zmk::keymap::BehaviorBinding {
479                behavior_id: self.behavior_id_for(BehaviorRole::ToLayer, "To Layer")?,
480                param1: target_layer_id,
481                param2: 0,
482            },
483            Behavior::Bluetooth { command, value } => zmk::keymap::BehaviorBinding {
484                behavior_id: self.behavior_id_for(BehaviorRole::Bluetooth, "Bluetooth")?,
485                param1: command,
486                param2: value,
487            },
488            Behavior::ExternalPower { value } => zmk::keymap::BehaviorBinding {
489                behavior_id: self.behavior_id_for(BehaviorRole::ExternalPower, "External Power")?,
490                param1: value,
491                param2: 0,
492            },
493            Behavior::OutputSelection { value } => zmk::keymap::BehaviorBinding {
494                behavior_id: self
495                    .behavior_id_for(BehaviorRole::OutputSelection, "Output Selection")?,
496                param1: value,
497                param2: 0,
498            },
499            Behavior::Backlight { command, value } => zmk::keymap::BehaviorBinding {
500                behavior_id: self.behavior_id_for(BehaviorRole::Backlight, "Backlight")?,
501                param1: command,
502                param2: value,
503            },
504            Behavior::Underglow { command, value } => zmk::keymap::BehaviorBinding {
505                behavior_id: self.behavior_id_for(BehaviorRole::Underglow, "Underglow")?,
506                param1: command,
507                param2: value,
508            },
509            Behavior::MouseKeyPress { value } => zmk::keymap::BehaviorBinding {
510                behavior_id: self
511                    .behavior_id_for(BehaviorRole::MouseKeyPress, "Mouse Key Press")?,
512                param1: value,
513                param2: 0,
514            },
515            Behavior::MouseMove { value } => zmk::keymap::BehaviorBinding {
516                behavior_id: self.behavior_id_for(BehaviorRole::MouseMove, "Mouse Move")?,
517                param1: value,
518                param2: 0,
519            },
520            Behavior::MouseScroll { value } => zmk::keymap::BehaviorBinding {
521                behavior_id: self.behavior_id_for(BehaviorRole::MouseScroll, "Mouse Scroll")?,
522                param1: value,
523                param2: 0,
524            },
525            Behavior::CapsWord => zmk::keymap::BehaviorBinding {
526                behavior_id: self.behavior_id_for(BehaviorRole::CapsWord, "Caps Word")?,
527                param1: 0,
528                param2: 0,
529            },
530            Behavior::KeyRepeat => zmk::keymap::BehaviorBinding {
531                behavior_id: self.behavior_id_for(BehaviorRole::KeyRepeat, "Key Repeat")?,
532                param1: 0,
533                param2: 0,
534            },
535            Behavior::Reset => zmk::keymap::BehaviorBinding {
536                behavior_id: self.behavior_id_for(BehaviorRole::Reset, "Reset")?,
537                param1: 0,
538                param2: 0,
539            },
540            Behavior::Bootloader => zmk::keymap::BehaviorBinding {
541                behavior_id: self.behavior_id_for(BehaviorRole::Bootloader, "Bootloader")?,
542                param1: 0,
543                param2: 0,
544            },
545            Behavior::SoftOff => zmk::keymap::BehaviorBinding {
546                behavior_id: self.behavior_id_for(BehaviorRole::SoftOff, "Soft Off")?,
547                param1: 0,
548                param2: 0,
549            },
550            Behavior::StudioUnlock => zmk::keymap::BehaviorBinding {
551                behavior_id: self.behavior_id_for(BehaviorRole::StudioUnlock, "Studio Unlock")?,
552                param1: 0,
553                param2: 0,
554            },
555            Behavior::GraveEscape => zmk::keymap::BehaviorBinding {
556                behavior_id: self.behavior_id_for(BehaviorRole::GraveEscape, "Grave/Escape")?,
557                param1: 0,
558                param2: 0,
559            },
560            Behavior::Transparent => zmk::keymap::BehaviorBinding {
561                behavior_id: self.behavior_id_for(BehaviorRole::Transparent, "Transparent")?,
562                param1: 0,
563                param2: 0,
564            },
565            Behavior::None => zmk::keymap::BehaviorBinding {
566                behavior_id: self.behavior_id_for(BehaviorRole::None, "None")?,
567                param1: 0,
568                param2: 0,
569            },
570            Behavior::Unknown {
571                behavior_id,
572                param1,
573                param2,
574            } => zmk::keymap::BehaviorBinding {
575                behavior_id,
576                param1,
577                param2,
578            },
579        };
580
581        self.set_layer_binding(layer_id, key_position, binding)
582    }
583
584    /// Returns whether there are pending unsaved keymap/layout changes.
585    pub fn check_unsaved_changes(&mut self) -> Result<bool, ClientError> {
586        let response =
587            self.call_keymap(zmk::keymap::request::RequestType::CheckUnsavedChanges(true))?;
588        match response.response_type {
589            Some(zmk::keymap::response::ResponseType::CheckUnsavedChanges(has_changes)) => {
590                Ok(has_changes)
591            }
592            _ => Err(ClientError::MissingResponseType),
593        }
594    }
595
596    /// Saves pending keymap/layout mutations made by methods like [`StudioClient::set_key_at`].
597    ///
598    /// After this succeeds, changes are persisted on the device.
599    pub fn save_changes(&mut self) -> Result<(), ClientError> {
600        let response = self.call_keymap(zmk::keymap::request::RequestType::SaveChanges(true))?;
601        match response.response_type {
602            Some(zmk::keymap::response::ResponseType::SaveChanges(save)) => match save.result {
603                Some(zmk::keymap::save_changes_response::Result::Ok(_)) => Ok(()),
604                Some(zmk::keymap::save_changes_response::Result::Err(raw)) => {
605                    let err = zmk::keymap::SaveChangesErrorCode::try_from(raw).map_err(|_| {
606                        ClientError::UnknownEnumValue {
607                            field: "keymap.save_changes",
608                            value: raw,
609                        }
610                    })?;
611                    Err(ClientError::SaveChangesFailed(err))
612                }
613                None => Err(ClientError::MissingResponseType),
614            },
615            _ => Err(ClientError::MissingResponseType),
616        }
617    }
618
619    /// Discards pending keymap/layout mutations made since the last save.
620    ///
621    /// Returns `true` if there were pending changes and they were discarded.
622    pub fn discard_changes(&mut self) -> Result<bool, ClientError> {
623        let response = self.call_keymap(zmk::keymap::request::RequestType::DiscardChanges(true))?;
624        match response.response_type {
625            Some(zmk::keymap::response::ResponseType::DiscardChanges(discarded)) => Ok(discarded),
626            _ => Err(ClientError::MissingResponseType),
627        }
628    }
629
630    /// Sets the active physical layout by index and returns the resulting keymap.
631    pub fn set_active_physical_layout(
632        &mut self,
633        index: u32,
634    ) -> Result<zmk::keymap::Keymap, ClientError> {
635        let response = self.call_keymap(
636            zmk::keymap::request::RequestType::SetActivePhysicalLayout(index),
637        )?;
638        match response.response_type {
639            Some(zmk::keymap::response::ResponseType::SetActivePhysicalLayout(resp)) => {
640                match resp.result {
641                    Some(zmk::keymap::set_active_physical_layout_response::Result::Ok(keymap)) => {
642                        Ok(keymap)
643                    }
644                    Some(zmk::keymap::set_active_physical_layout_response::Result::Err(raw)) => {
645                        let err = zmk::keymap::SetActivePhysicalLayoutErrorCode::try_from(raw)
646                            .map_err(|_| ClientError::UnknownEnumValue {
647                                field: "keymap.set_active_physical_layout",
648                                value: raw,
649                            })?;
650                        Err(ClientError::SetActivePhysicalLayoutFailed(err))
651                    }
652                    None => Err(ClientError::MissingResponseType),
653                }
654            }
655            _ => Err(ClientError::MissingResponseType),
656        }
657    }
658
659    /// Moves a layer from `start_index` to `dest_index` and returns the updated keymap.
660    pub fn move_layer(
661        &mut self,
662        start_index: u32,
663        dest_index: u32,
664    ) -> Result<zmk::keymap::Keymap, ClientError> {
665        let request = zmk::keymap::MoveLayerRequest {
666            start_index,
667            dest_index,
668        };
669        let response = self.call_keymap(zmk::keymap::request::RequestType::MoveLayer(request))?;
670        match response.response_type {
671            Some(zmk::keymap::response::ResponseType::MoveLayer(resp)) => match resp.result {
672                Some(zmk::keymap::move_layer_response::Result::Ok(keymap)) => Ok(keymap),
673                Some(zmk::keymap::move_layer_response::Result::Err(raw)) => {
674                    let err = zmk::keymap::MoveLayerErrorCode::try_from(raw).map_err(|_| {
675                        ClientError::UnknownEnumValue {
676                            field: "keymap.move_layer",
677                            value: raw,
678                        }
679                    })?;
680                    Err(ClientError::MoveLayerFailed(err))
681                }
682                None => Err(ClientError::MissingResponseType),
683            },
684            _ => Err(ClientError::MissingResponseType),
685        }
686    }
687
688    /// Adds a layer and returns firmware-provided details about the created layer.
689    pub fn add_layer(&mut self) -> Result<zmk::keymap::AddLayerResponseDetails, ClientError> {
690        let response = self.call_keymap(zmk::keymap::request::RequestType::AddLayer(
691            zmk::keymap::AddLayerRequest {},
692        ))?;
693        match response.response_type {
694            Some(zmk::keymap::response::ResponseType::AddLayer(resp)) => match resp.result {
695                Some(zmk::keymap::add_layer_response::Result::Ok(details)) => Ok(details),
696                Some(zmk::keymap::add_layer_response::Result::Err(raw)) => {
697                    let err = zmk::keymap::AddLayerErrorCode::try_from(raw).map_err(|_| {
698                        ClientError::UnknownEnumValue {
699                            field: "keymap.add_layer",
700                            value: raw,
701                        }
702                    })?;
703                    Err(ClientError::AddLayerFailed(err))
704                }
705                None => Err(ClientError::MissingResponseType),
706            },
707            _ => Err(ClientError::MissingResponseType),
708        }
709    }
710
711    /// Removes a layer by index.
712    pub fn remove_layer(&mut self, layer_index: u32) -> Result<(), ClientError> {
713        let request = zmk::keymap::RemoveLayerRequest { layer_index };
714        let response = self.call_keymap(zmk::keymap::request::RequestType::RemoveLayer(request))?;
715        match response.response_type {
716            Some(zmk::keymap::response::ResponseType::RemoveLayer(resp)) => match resp.result {
717                Some(zmk::keymap::remove_layer_response::Result::Ok(_)) => Ok(()),
718                Some(zmk::keymap::remove_layer_response::Result::Err(raw)) => {
719                    let err = zmk::keymap::RemoveLayerErrorCode::try_from(raw).map_err(|_| {
720                        ClientError::UnknownEnumValue {
721                            field: "keymap.remove_layer",
722                            value: raw,
723                        }
724                    })?;
725                    Err(ClientError::RemoveLayerFailed(err))
726                }
727                None => Err(ClientError::MissingResponseType),
728            },
729            _ => Err(ClientError::MissingResponseType),
730        }
731    }
732
733    /// Restores a previously removed layer at a specific index.
734    pub fn restore_layer(
735        &mut self,
736        layer_id: u32,
737        at_index: u32,
738    ) -> Result<zmk::keymap::Layer, ClientError> {
739        let request = zmk::keymap::RestoreLayerRequest { layer_id, at_index };
740        let response =
741            self.call_keymap(zmk::keymap::request::RequestType::RestoreLayer(request))?;
742        match response.response_type {
743            Some(zmk::keymap::response::ResponseType::RestoreLayer(resp)) => match resp.result {
744                Some(zmk::keymap::restore_layer_response::Result::Ok(layer)) => Ok(layer),
745                Some(zmk::keymap::restore_layer_response::Result::Err(raw)) => {
746                    let err = zmk::keymap::RestoreLayerErrorCode::try_from(raw).map_err(|_| {
747                        ClientError::UnknownEnumValue {
748                            field: "keymap.restore_layer",
749                            value: raw,
750                        }
751                    })?;
752                    Err(ClientError::RestoreLayerFailed(err))
753                }
754                None => Err(ClientError::MissingResponseType),
755            },
756            _ => Err(ClientError::MissingResponseType),
757        }
758    }
759
760    /// Sets user-facing properties for a layer (currently just `name`).
761    pub fn set_layer_props(
762        &mut self,
763        layer_id: u32,
764        name: impl Into<String>,
765    ) -> Result<(), ClientError> {
766        let request = zmk::keymap::SetLayerPropsRequest {
767            layer_id,
768            name: name.into(),
769        };
770        let response =
771            self.call_keymap(zmk::keymap::request::RequestType::SetLayerProps(request))?;
772        match response.response_type {
773            Some(zmk::keymap::response::ResponseType::SetLayerProps(raw)) => {
774                let code = zmk::keymap::SetLayerPropsResponse::try_from(raw).map_err(|_| {
775                    ClientError::UnknownEnumValue {
776                        field: "keymap.set_layer_props",
777                        value: raw,
778                    }
779                })?;
780
781                if code == zmk::keymap::SetLayerPropsResponse::SetLayerPropsRespOk {
782                    Ok(())
783                } else {
784                    Err(ClientError::SetLayerPropsFailed(code))
785                }
786            }
787            _ => Err(ClientError::MissingResponseType),
788        }
789    }
790
791    fn behavior_id_for(
792        &self,
793        role: BehaviorRole,
794        display_name: &'static str,
795    ) -> Result<i32, ClientError> {
796        let behavior_id = self
797            .behavior_id_by_role
798            .get(&role)
799            .copied()
800            .ok_or(ClientError::MissingBehaviorRole(display_name))?;
801        i32::try_from(behavior_id).map_err(|_| ClientError::BehaviorIdOutOfRange { behavior_id })
802    }
803
804    fn ensure_behavior_catalog(&mut self) -> Result<(), ClientError> {
805        if !self.behavior_role_by_id.is_empty() {
806            return Ok(());
807        }
808
809        let ids = self.list_all_behaviors()?;
810        for id in ids {
811            let details = self.get_behavior_details(id)?;
812            let role = role_from_display_name(&details.display_name);
813            if let Some(role) = role {
814                self.behavior_role_by_id.insert(id, role);
815                self.behavior_id_by_role.entry(role).or_insert(id);
816            }
817        }
818
819        Ok(())
820    }
821
822    fn call_core(
823        &mut self,
824        request_type: zmk::core::request::RequestType,
825    ) -> Result<zmk::core::Response, ClientError> {
826        let request = zmk::core::Request {
827            request_type: Some(request_type),
828        };
829        let rr = self.call(studio::request::Subsystem::Core(request))?;
830
831        match rr.subsystem {
832            Some(studio::request_response::Subsystem::Core(resp)) => Ok(resp),
833            Some(_) => Err(ClientError::UnexpectedSubsystem("core")),
834            None => Err(ClientError::MissingSubsystem),
835        }
836    }
837
838    fn call_behaviors(
839        &mut self,
840        request_type: zmk::behaviors::request::RequestType,
841    ) -> Result<zmk::behaviors::Response, ClientError> {
842        let request = zmk::behaviors::Request {
843            request_type: Some(request_type),
844        };
845        let rr = self.call(studio::request::Subsystem::Behaviors(request))?;
846
847        match rr.subsystem {
848            Some(studio::request_response::Subsystem::Behaviors(resp)) => Ok(resp),
849            Some(_) => Err(ClientError::UnexpectedSubsystem("behaviors")),
850            None => Err(ClientError::MissingSubsystem),
851        }
852    }
853
854    fn call_keymap(
855        &mut self,
856        request_type: zmk::keymap::request::RequestType,
857    ) -> Result<zmk::keymap::Response, ClientError> {
858        let request = zmk::keymap::Request {
859            request_type: Some(request_type),
860        };
861        let rr = self.call(studio::request::Subsystem::Keymap(request))?;
862
863        match rr.subsystem {
864            Some(studio::request_response::Subsystem::Keymap(resp)) => Ok(resp),
865            Some(_) => Err(ClientError::UnexpectedSubsystem("keymap")),
866            None => Err(ClientError::MissingSubsystem),
867        }
868    }
869
870    fn call(
871        &mut self,
872        subsystem: studio::request::Subsystem,
873    ) -> Result<studio::RequestResponse, ClientError> {
874        let request_id = self.next_request_id;
875        self.next_request_id = self.next_request_id.wrapping_add(1);
876
877        let request = studio::Request {
878            request_id,
879            subsystem: Some(subsystem),
880        };
881        let bytes = encode_request(&request);
882        self.io.write_all(&bytes)?;
883
884        loop {
885            let response = self.read_next_response()?;
886            match response.r#type {
887                Some(studio::response::Type::Notification(notification)) => {
888                    self.notifications.push_back(notification);
889                }
890                Some(studio::response::Type::RequestResponse(rr)) => {
891                    if rr.request_id != request_id {
892                        return Err(ClientError::UnexpectedRequestId {
893                            expected: request_id,
894                            actual: rr.request_id,
895                        });
896                    }
897
898                    if let Some(studio::request_response::Subsystem::Meta(meta)) = &rr.subsystem {
899                        match meta.response_type {
900                            Some(zmk::meta::response::ResponseType::NoResponse(true)) => {
901                                return Err(ClientError::NoResponse);
902                            }
903                            Some(zmk::meta::response::ResponseType::SimpleError(raw)) => {
904                                let cond =
905                                    zmk::meta::ErrorConditions::try_from(raw).map_err(|_| {
906                                        ClientError::UnknownEnumValue {
907                                            field: "meta.simple_error",
908                                            value: raw,
909                                        }
910                                    })?;
911                                return Err(ClientError::Meta(cond));
912                            }
913                            _ => return Err(ClientError::MissingResponseType),
914                        }
915                    }
916
917                    return Ok(rr);
918                }
919                None => return Err(ClientError::MissingResponseType),
920            }
921        }
922    }
923
924    fn read_next_response(&mut self) -> Result<studio::Response, ClientError> {
925        if let Some(response) = self.responses.pop_front() {
926            return Ok(response);
927        }
928
929        loop {
930            let read = self.io.read(&mut self.read_buffer)?;
931            if read == 0 {
932                return Err(ClientError::Io(std::io::Error::new(
933                    std::io::ErrorKind::UnexpectedEof,
934                    "Transport reached EOF",
935                )));
936            }
937
938            let decoded = decode_responses(&mut self.decoder, &self.read_buffer[..read])?;
939            self.responses.extend(decoded);
940
941            if let Some(response) = self.responses.pop_front() {
942                return Ok(response);
943            }
944        }
945    }
946}
947
948fn binding_at(
949    keymap: &zmk::keymap::Keymap,
950    layer_id: u32,
951    key_position: i32,
952) -> Option<zmk::keymap::BehaviorBinding> {
953    let pos = usize::try_from(key_position).ok()?;
954    let layer = keymap.layers.iter().find(|l| l.id == layer_id)?;
955    layer.bindings.get(pos).copied()
956}
957
958#[cfg(feature = "serial")]
959impl StudioClient<SerialTransport> {
960    /// Convenience constructor for opening a serial transport and wrapping it in a client.
961    pub fn open_serial(path: &str) -> Result<Self, SerialTransportError> {
962        Ok(Self::new(SerialTransport::open(path)?))
963    }
964}
965
966#[cfg(feature = "ble")]
967impl StudioClient<PlatformBleTransport> {
968    /// Lists BLE devices using the backend strategy chosen for the current OS.
969    pub fn list_ble_devices() -> Result<Vec<BleDeviceInfo>, PlatformBleError> {
970        Self::list_ble_devices_with_mode(BleDiscoveryMode::Any)
971    }
972
973    /// Lists BLE devices using an explicit discovery mode.
974    pub fn list_ble_devices_with_mode(
975        mode: BleDiscoveryMode,
976    ) -> Result<Vec<BleDeviceInfo>, PlatformBleError> {
977        discover_platform_ble_devices(mode)
978    }
979
980    /// Open a BLE connection to a device previously returned by discovery.
981    pub fn open_ble(device_id: &str) -> Result<Self, PlatformBleError> {
982        Ok(Self::new(PlatformBleTransport::connect_device(device_id)?))
983    }
984}