nexus\api/
keybind.rs

1//! Addon keybinds.
2
3use crate::{revertible::Revertible, util::str_to_c, AddonApi, InputBindsApi};
4use std::ffi::c_char;
5
6/// A keybind.
7#[derive(Debug, Clone)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9#[repr(C)]
10pub struct Keybind {
11    /// The key.
12    pub key: u16,
13
14    /// Alt modifier.
15    pub alt: bool,
16
17    /// Control modifier.
18    pub ctrl: bool,
19
20    /// Shift modifier.
21    pub shift: bool,
22}
23
24impl Keybind {
25    /// Creates a new keybind without modifiers.
26    #[inline]
27    pub fn without_modifiers(key: u16) -> Self {
28        Self {
29            key,
30            alt: false,
31            ctrl: false,
32            shift: false,
33        }
34    }
35
36    /// Checks whether the keybind has modifiers.
37    #[inline]
38    pub fn has_modifiers(&self) -> bool {
39        !self.alt && !self.ctrl && !self.shift
40    }
41}
42
43pub type RawKeybindHandler = extern "C-unwind" fn(identifier: *const c_char, is_release: bool);
44
45pub type RawKeybindInvoke =
46    unsafe extern "C-unwind" fn(identifier: *const c_char, is_release: bool);
47
48pub type RawKeybindRegisterWithString = unsafe extern "C-unwind" fn(
49    identifier: *const c_char,
50    keybind_handler: RawKeybindHandler,
51    keybind: *const c_char,
52);
53
54pub type RawKeybindRegisterWithStruct = unsafe extern "C-unwind" fn(
55    identifier: *const c_char,
56    keybind_handler: RawKeybindHandler,
57    keybind: Keybind,
58);
59
60pub type RawKeybindDeregister = unsafe extern "C-unwind" fn(identifier: *const c_char);
61
62pub type RawKeybindHandlerOld = extern "C-unwind" fn(identifier: *const c_char);
63
64pub type RawKeybindRegisterWithStringOld = unsafe extern "C-unwind" fn(
65    identifier: *const c_char,
66    keybind_handler: RawKeybindHandlerOld,
67    keybind: *const c_char,
68);
69
70pub type RawKeybindRegisterWithStructOld = unsafe extern "C-unwind" fn(
71    identifier: *const c_char,
72    keybind_handler: RawKeybindHandlerOld,
73    keybind: Keybind,
74);
75
76/// Triggers a previously registered keybind programmatically.
77pub fn invoke_keybind(identifier: impl AsRef<str>, is_release: bool) {
78    let InputBindsApi { invoke, .. } = AddonApi::get().input_binds;
79    let identifier = str_to_c(identifier, "failed to convert keybind identifier");
80    unsafe { invoke(identifier.as_ptr(), is_release) }
81}
82
83/// Registers a new keybind using a keybind string like `"ALT+SHIFT+T"`.
84///
85/// Returns a [`Revertible`] to revert the register.
86///
87/// # Usage
88/// ```no_run
89/// use nexus::keybind::{register_keybind_with_string, keybind_handler};
90/// let keybind_handler = keybind_handler!(|id, is_release| {
91///     use nexus::log::{log, LogLevel};
92///     log(LogLevel::Info, "My Addon", format!(
93///         "keybind {id} {}",
94///         if is_release { "released" } else { "pressed "},
95///     ));
96/// });
97/// register_keybind_with_string("MY_KEYBIND", keybind_handler, "ALT+SHIFT+X")
98///     .revert_on_unload();
99/// ```
100pub fn register_keybind_with_string(
101    identifier: impl AsRef<str>,
102    handler: RawKeybindHandler,
103    keybind: impl AsRef<str>,
104) -> Revertible<impl Fn() + Send + Sync + Clone + 'static> {
105    let InputBindsApi {
106        register_with_string,
107        deregister,
108        ..
109    } = AddonApi::get().input_binds;
110    let identifier = str_to_c(identifier, "failed to convert keybind identifier");
111    let keybind = str_to_c(keybind, "failed to convert keybind string");
112    unsafe { register_with_string(identifier.as_ptr(), handler, keybind.as_ptr()) };
113    let revert = move || unsafe { deregister(identifier.as_ptr()) };
114    revert.into()
115}
116
117/// Registers a new keybind using a [`Keybind`] struct.
118///
119/// Returns a [`Revertible`] to revert the register.
120///
121/// # Usage
122/// ```no_run
123/// use nexus::keybind::{register_keybind_with_struct, Keybind, keybind_handler};
124/// let keybind = Keybind {
125///     key: 123,
126///     alt: true,
127///     ctrl: false,
128///     shift: true,
129/// };
130/// let keybind_handler = keybind_handler!(|id, is_release| {
131///     use nexus::log::{log, LogLevel};
132///     log(LogLevel::Info, "My Addon", format!(
133///         "keybind {id} {}",
134///         if is_release { "released" } else { "pressed "},
135///     ));
136/// });
137/// register_keybind_with_struct("MY_KEYBIND", keybind_handler, keybind)
138///     .revert_on_unload();
139/// ```
140pub fn register_keybind_with_struct(
141    identifier: impl AsRef<str>,
142    handler: RawKeybindHandler,
143    keybind: Keybind,
144) -> Revertible<impl Fn() + Send + Sync + Clone + 'static> {
145    let InputBindsApi {
146        register_with_struct,
147        deregister,
148        ..
149    } = AddonApi::get().input_binds;
150    let identifier = str_to_c(identifier, "failed to convert keybind identifier");
151    unsafe { register_with_struct(identifier.as_ptr(), handler, keybind) };
152    let revert = move || unsafe { deregister(identifier.as_ptr()) };
153    revert.into()
154}
155
156/// Unregisters a previously registered keybind.
157pub fn unregister_keybind(identifier: impl AsRef<str>) {
158    let InputBindsApi { deregister, .. } = AddonApi::get().input_binds;
159    let identifier = str_to_c(identifier, "failed to convert keybind identifier");
160    unsafe { deregister(identifier.as_ptr()) }
161}
162
163/// Macro to wrap a keybind handler callback.
164///
165/// Generates a [`RawKeybindHandler`] wrapper around the passed callback.
166///
167/// # Usage
168/// ```no_run
169/// # use nexus::keybind::*;
170/// let keybind_handler: RawKeybindHandler = keybind_handler!(|id, is_release| {
171///     use nexus::log::{log, LogLevel};
172///     log(LogLevel::Info, "My Addon", format!(
173///         "keybind {id} {}",
174///         if is_release { "released" } else { "pressed "},
175///     ));
176/// });
177/// ```
178#[macro_export]
179macro_rules! keybind_handler {
180    ( $callback:expr $(,)? ) => {{
181        const __CALLBACK: fn(&::std::primitive::str, ::std::primitive::bool) = $callback;
182
183        extern "C-unwind" fn __keybind_callback_wrapper(
184            identifier: *const ::std::ffi::c_char,
185            is_release: ::std::primitive::bool,
186        ) {
187            let identifier = unsafe { $crate::__macro::str_from_c(identifier) }
188                .expect("invalid identifier in keybind callback");
189            __CALLBACK(identifier, is_release)
190        }
191
192        __keybind_callback_wrapper
193    }};
194}
195
196pub use keybind_handler;