pinnacle_api/
input.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Input management.
6//!
7//! This module provides ways to manage bindings, input devices, and other input settings.
8
9use num_enum::{FromPrimitive, IntoPrimitive};
10use pinnacle_api_defs::pinnacle::input::{
11    self,
12    v1::{
13        switch_xkb_layout_request, BindProperties, BindRequest, EnterBindLayerRequest,
14        GetBindInfosRequest, KeybindOnPressRequest, KeybindStreamRequest, MousebindOnPressRequest,
15        MousebindStreamRequest, SetBindPropertiesRequest, SetRepeatRateRequest, SetXcursorRequest,
16        SetXkbConfigRequest, SetXkbKeymapRequest, SwitchXkbLayoutRequest,
17    },
18};
19use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
20use tokio_stream::StreamExt;
21
22use crate::{
23    client::Client,
24    signal::{InputSignal, SignalHandle},
25    BlockOnTokio,
26};
27
28pub mod libinput;
29
30pub use xkbcommon::xkb::Keysym;
31
32/// A mouse button.
33#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
34#[repr(u32)]
35pub enum MouseButton {
36    /// The left mouse button
37    Left = 0x110,
38    /// The right mouse button
39    Right = 0x111,
40    /// The middle mouse button
41    Middle = 0x112,
42    /// The side mouse button
43    Side = 0x113,
44    /// The extra mouse button
45    Extra = 0x114,
46    /// The forward mouse button
47    Forward = 0x115,
48    /// The backward mouse button
49    Back = 0x116,
50    /// Some other mouse button
51    #[num_enum(catch_all)]
52    Other(u32),
53}
54
55bitflags::bitflags! {
56    /// A keyboard modifier for use in binds.
57    ///
58    /// Binds can be configured to require certain keyboard modifiers to be held down to trigger.
59    /// For example, a bind with `Mod::SUPER | Mod::CTRL` requires both the super and control keys
60    /// to be held down.
61    ///
62    /// Normally, modifiers must be in the exact same state as passed in to trigger a bind.
63    /// This means if you use `Mod::SUPER` in a bind, *only* super must be held down; holding
64    /// down any other modifier will invalidate the bind.
65    ///
66    /// To circumvent this, you can ignore certain modifiers by OR-ing with the respective
67    /// `Mod::IGNORE_*`.
68    #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default)]
69    pub struct Mod: u16 {
70        /// The shift key
71        const SHIFT = 1;
72        /// The ctrl key
73        const CTRL = 1 << 1;
74        /// The alt key
75        const ALT = 1 << 2;
76        /// The super key, aka meta, win, mod4
77        const SUPER = 1 << 3;
78        /// The IsoLevel3Shift modifier
79        const ISO_LEVEL3_SHIFT = 1 << 4;
80        /// The IsoLevel5Shift modifer
81        const ISO_LEVEL5_SHIFT = 1 << 5;
82
83        /// Ignore the shift key
84        const IGNORE_SHIFT = 1 << 6;
85        /// Ignore the ctrl key
86        const IGNORE_CTRL = 1 << 7;
87        /// Ignore the alt key
88        const IGNORE_ALT = 1 << 8;
89        /// Ignore the super key
90        const IGNORE_SUPER = 1 << 9;
91        /// Ignore the IsoLevel3Shift modifier
92        const IGNORE_ISO_LEVEL3_SHIFT = 1 << 10;
93        /// Ignore the IsoLevel5Shift modifier
94        const IGNORE_ISO_LEVEL5_SHIFT = 1 << 11;
95    }
96}
97
98impl Mod {
99    fn api_mods(&self) -> Vec<input::v1::Modifier> {
100        let mut mods = Vec::new();
101        if self.contains(Mod::SHIFT) {
102            mods.push(input::v1::Modifier::Shift);
103        }
104        if self.contains(Mod::CTRL) {
105            mods.push(input::v1::Modifier::Ctrl);
106        }
107        if self.contains(Mod::ALT) {
108            mods.push(input::v1::Modifier::Alt);
109        }
110        if self.contains(Mod::SUPER) {
111            mods.push(input::v1::Modifier::Super);
112        }
113        if self.contains(Mod::ISO_LEVEL3_SHIFT) {
114            mods.push(input::v1::Modifier::IsoLevel3Shift);
115        }
116        if self.contains(Mod::ISO_LEVEL5_SHIFT) {
117            mods.push(input::v1::Modifier::IsoLevel5Shift);
118        }
119        mods
120    }
121
122    fn api_ignore_mods(&self) -> Vec<input::v1::Modifier> {
123        let mut mods = Vec::new();
124        if self.contains(Mod::IGNORE_SHIFT) {
125            mods.push(input::v1::Modifier::Shift);
126        }
127        if self.contains(Mod::IGNORE_CTRL) {
128            mods.push(input::v1::Modifier::Ctrl);
129        }
130        if self.contains(Mod::IGNORE_ALT) {
131            mods.push(input::v1::Modifier::Alt);
132        }
133        if self.contains(Mod::IGNORE_SUPER) {
134            mods.push(input::v1::Modifier::Super);
135        }
136        if self.contains(Mod::IGNORE_ISO_LEVEL3_SHIFT) {
137            mods.push(input::v1::Modifier::IsoLevel3Shift);
138        }
139        if self.contains(Mod::IGNORE_ISO_LEVEL5_SHIFT) {
140            mods.push(input::v1::Modifier::IsoLevel5Shift);
141        }
142        mods
143    }
144}
145
146/// A bind layer, also known as a bind mode.
147///
148/// Normally all binds belong to the [`DEFAULT`][Self::DEFAULT] mode.
149/// You can bind binding to different layers and switch between them to enable modal binds.
150#[derive(Debug, Clone, Hash, PartialEq, Eq)]
151pub struct BindLayer {
152    name: Option<String>,
153}
154
155impl BindLayer {
156    /// The default bind layer.
157    ///
158    /// This is the layer [`input::keybind`][self::keybind] uses.
159    pub const DEFAULT: Self = Self { name: None };
160
161    /// Gets the bind layer with the given `name`.
162    pub fn get(name: impl ToString) -> Self {
163        Self {
164            name: Some(name.to_string()),
165        }
166    }
167
168    /// Creates a keybind on this layer.
169    pub fn keybind(&self, mods: Mod, key: impl ToKeysym) -> Keybind {
170        new_keybind(mods, key, self).block_on_tokio()
171    }
172
173    /// Creates a mousebind on this layer.
174    pub fn mousebind(&self, mods: Mod, button: MouseButton) -> Mousebind {
175        new_mousebind(mods, button, self).block_on_tokio()
176    }
177
178    /// Enters this layer, causing only its binds to be in effect.
179    pub fn enter(&self) {
180        Client::input()
181            .enter_bind_layer(EnterBindLayerRequest {
182                layer_name: self.name.clone(),
183            })
184            .block_on_tokio()
185            .unwrap();
186    }
187
188    /// Returns this bind layer's name, or `None` if this is the default bind layer.
189    pub fn name(&self) -> Option<String> {
190        self.name.clone()
191    }
192}
193
194/// Functionality common to all bind types.
195pub trait Bind {
196    /// Sets this bind's group.
197    fn group(&mut self, group: impl ToString) -> &mut Self;
198    /// Sets this bind's description.
199    fn description(&mut self, desc: impl ToString) -> &mut Self;
200    /// Sets this bind as a quit bind.
201    fn set_as_quit(&mut self) -> &mut Self;
202    /// Sets this bind as a reload config bind.
203    fn set_as_reload_config(&mut self) -> &mut Self;
204    /// Allows this bind to trigger when the session is locked.
205    fn allow_when_locked(&mut self) -> &mut Self;
206}
207
208macro_rules! bind_impl {
209    ($ty:ty) => {
210        impl Bind for $ty {
211            fn group(&mut self, group: impl ToString) -> &mut Self {
212                Client::input()
213                    .set_bind_properties(SetBindPropertiesRequest {
214                        bind_id: self.bind_id,
215                        properties: Some(BindProperties {
216                            group: Some(group.to_string()),
217                            ..Default::default()
218                        }),
219                    })
220                    .block_on_tokio()
221                    .unwrap();
222                self
223            }
224
225            fn description(&mut self, desc: impl ToString) -> &mut Self {
226                Client::input()
227                    .set_bind_properties(SetBindPropertiesRequest {
228                        bind_id: self.bind_id,
229                        properties: Some(BindProperties {
230                            description: Some(desc.to_string()),
231                            ..Default::default()
232                        }),
233                    })
234                    .block_on_tokio()
235                    .unwrap();
236                self
237            }
238
239            fn set_as_quit(&mut self) -> &mut Self {
240                Client::input()
241                    .set_bind_properties(SetBindPropertiesRequest {
242                        bind_id: self.bind_id,
243                        properties: Some(BindProperties {
244                            quit: Some(true),
245                            ..Default::default()
246                        }),
247                    })
248                    .block_on_tokio()
249                    .unwrap();
250                self
251            }
252
253            fn set_as_reload_config(&mut self) -> &mut Self {
254                Client::input()
255                    .set_bind_properties(SetBindPropertiesRequest {
256                        bind_id: self.bind_id,
257                        properties: Some(BindProperties {
258                            reload_config: Some(true),
259                            ..Default::default()
260                        }),
261                    })
262                    .block_on_tokio()
263                    .unwrap();
264                self
265            }
266
267            fn allow_when_locked(&mut self) -> &mut Self {
268                Client::input()
269                    .set_bind_properties(SetBindPropertiesRequest {
270                        bind_id: self.bind_id,
271                        properties: Some(BindProperties {
272                            allow_when_locked: Some(true),
273                            ..Default::default()
274                        }),
275                    })
276                    .block_on_tokio()
277                    .unwrap();
278                self
279            }
280        }
281    };
282}
283
284enum Edge {
285    Press,
286    Release,
287}
288
289type KeybindCallback = (Box<dyn FnMut() + Send + 'static>, Edge);
290
291/// A keybind.
292pub struct Keybind {
293    bind_id: u32,
294    callback_sender: Option<UnboundedSender<KeybindCallback>>,
295}
296
297bind_impl!(Keybind);
298
299/// Creates a keybind on the [`DEFAULT`][BindLayer::DEFAULT] bind layer.
300pub fn keybind(mods: Mod, key: impl ToKeysym) -> Keybind {
301    BindLayer::DEFAULT.keybind(mods, key)
302}
303
304impl Keybind {
305    /// Runs a closure whenever this keybind is pressed.
306    pub fn on_press<F: FnMut() + Send + 'static>(&mut self, on_press: F) -> &mut Self {
307        let sender = self
308            .callback_sender
309            .get_or_insert_with(|| new_keybind_stream(self.bind_id).block_on_tokio());
310        let _ = sender.send((Box::new(on_press), Edge::Press));
311
312        Client::input()
313            .keybind_on_press(KeybindOnPressRequest {
314                bind_id: self.bind_id,
315            })
316            .block_on_tokio()
317            .unwrap();
318
319        self
320    }
321
322    /// Runs a closure whenever this keybind is released.
323    pub fn on_release<F: FnMut() + Send + 'static>(&mut self, on_release: F) -> &mut Self {
324        let sender = self
325            .callback_sender
326            .get_or_insert_with(|| new_keybind_stream(self.bind_id).block_on_tokio());
327        let _ = sender.send((Box::new(on_release), Edge::Release));
328
329        self
330    }
331}
332
333async fn new_keybind(mods: Mod, key: impl ToKeysym, layer: &BindLayer) -> Keybind {
334    let ignore_mods = mods.api_ignore_mods();
335    let mods = mods.api_mods();
336
337    let bind_id = Client::input()
338        .bind(BindRequest {
339            bind: Some(input::v1::Bind {
340                mods: mods.into_iter().map(|m| m.into()).collect(),
341                ignore_mods: ignore_mods.into_iter().map(|m| m.into()).collect(),
342                layer_name: layer.name.clone(),
343                properties: Some(BindProperties::default()),
344                bind: Some(input::v1::bind::Bind::Key(input::v1::Keybind {
345                    key_code: Some(key.to_keysym().raw()),
346                    xkb_name: None,
347                })),
348            }),
349        })
350        .await
351        .unwrap()
352        .into_inner()
353        .bind_id;
354
355    Keybind {
356        bind_id,
357        callback_sender: None,
358    }
359}
360
361async fn new_keybind_stream(
362    bind_id: u32,
363) -> UnboundedSender<(Box<dyn FnMut() + Send + 'static>, Edge)> {
364    let mut from_server = Client::input()
365        .keybind_stream(KeybindStreamRequest { bind_id })
366        .await
367        .unwrap()
368        .into_inner();
369
370    let (send, mut recv) = unbounded_channel();
371
372    tokio::spawn(async move {
373        let mut on_presses = Vec::<Box<dyn FnMut() + Send + 'static>>::new();
374        let mut on_releases = Vec::<Box<dyn FnMut() + Send + 'static>>::new();
375
376        loop {
377            tokio::select! {
378                Some(Ok(response)) = from_server.next() => {
379                    match response.edge() {
380                        input::v1::Edge::Unspecified => (),
381                        input::v1::Edge::Press => {
382                            for on_press in on_presses.iter_mut() {
383                                on_press();
384                            }
385                        }
386                        input::v1::Edge::Release => {
387                            for on_release in on_releases.iter_mut() {
388                                on_release();
389                            }
390                        }
391                    }
392                }
393                Some((cb, edge)) = recv.recv() => {
394                    match edge {
395                        Edge::Press => on_presses.push(cb),
396                        Edge::Release => on_releases.push(cb),
397                    }
398                }
399                else => break,
400            }
401        }
402    });
403
404    send
405}
406
407// Mousebinds
408
409type MousebindCallback = (Box<dyn FnMut() + Send + 'static>, Edge);
410
411/// A mousebind.
412pub struct Mousebind {
413    bind_id: u32,
414    callback_sender: Option<UnboundedSender<MousebindCallback>>,
415}
416
417bind_impl!(Mousebind);
418
419/// Creates a mousebind on the [`DEFAULT`][BindLayer::DEFAULT] bind layer.
420pub fn mousebind(mods: Mod, button: MouseButton) -> Mousebind {
421    BindLayer::DEFAULT.mousebind(mods, button)
422}
423
424impl Mousebind {
425    /// Runs a closure whenever this mousebind is pressed.
426    pub fn on_press<F: FnMut() + Send + 'static>(&mut self, on_press: F) -> &mut Self {
427        let sender = self
428            .callback_sender
429            .get_or_insert_with(|| new_mousebind_stream(self.bind_id).block_on_tokio());
430        let _ = sender.send((Box::new(on_press), Edge::Press));
431
432        Client::input()
433            .mousebind_on_press(MousebindOnPressRequest {
434                bind_id: self.bind_id,
435            })
436            .block_on_tokio()
437            .unwrap();
438
439        self
440    }
441
442    /// Runs a closure whenever this mousebind is released.
443    pub fn on_release<F: FnMut() + Send + 'static>(&mut self, on_release: F) -> &mut Self {
444        let sender = self
445            .callback_sender
446            .get_or_insert_with(|| new_mousebind_stream(self.bind_id).block_on_tokio());
447        let _ = sender.send((Box::new(on_release), Edge::Release));
448
449        self
450    }
451}
452
453async fn new_mousebind(mods: Mod, button: MouseButton, layer: &BindLayer) -> Mousebind {
454    let ignore_mods = mods.api_ignore_mods();
455    let mods = mods.api_mods();
456
457    let bind_id = Client::input()
458        .bind(BindRequest {
459            bind: Some(input::v1::Bind {
460                mods: mods.into_iter().map(|m| m.into()).collect(),
461                ignore_mods: ignore_mods.into_iter().map(|m| m.into()).collect(),
462                layer_name: layer.name.clone(),
463                properties: Some(BindProperties::default()),
464                bind: Some(input::v1::bind::Bind::Mouse(input::v1::Mousebind {
465                    button: button.into(),
466                })),
467            }),
468        })
469        .await
470        .unwrap()
471        .into_inner()
472        .bind_id;
473
474    Mousebind {
475        bind_id,
476        callback_sender: None,
477    }
478}
479
480async fn new_mousebind_stream(
481    bind_id: u32,
482) -> UnboundedSender<(Box<dyn FnMut() + Send + 'static>, Edge)> {
483    let mut from_server = Client::input()
484        .mousebind_stream(MousebindStreamRequest { bind_id })
485        .await
486        .unwrap()
487        .into_inner();
488
489    let (send, mut recv) = unbounded_channel();
490
491    tokio::spawn(async move {
492        let mut on_presses = Vec::<Box<dyn FnMut() + Send + 'static>>::new();
493        let mut on_releases = Vec::<Box<dyn FnMut() + Send + 'static>>::new();
494
495        loop {
496            tokio::select! {
497                Some(Ok(response)) = from_server.next() => {
498                    match response.edge() {
499                        input::v1::Edge::Unspecified => (),
500                        input::v1::Edge::Press => {
501                            for on_press in on_presses.iter_mut() {
502                                on_press();
503                            }
504                        }
505                        input::v1::Edge::Release => {
506                            for on_release in on_releases.iter_mut() {
507                                on_release();
508                            }
509                        }
510                    }
511                }
512                Some((cb, edge)) = recv.recv() => {
513                    match edge {
514                        Edge::Press => on_presses.push(cb),
515                        Edge::Release => on_releases.push(cb),
516                    }
517                }
518                else => break,
519            }
520        }
521    });
522
523    send
524}
525
526/// A struct that lets you define xkeyboard config options.
527///
528/// See `xkeyboard-config(7)` for more information.
529#[derive(Clone, Debug, Hash, PartialEq, Eq, Default)]
530pub struct XkbConfig {
531    /// Files of rules to be used for keyboard mapping composition
532    pub rules: Option<String>,
533    /// Name of the model of your keyboard type
534    pub model: Option<String>,
535    /// Layout(s) you intend to use
536    pub layout: Option<String>,
537    /// Variant(s) of the layout you intend to use
538    pub variant: Option<String>,
539    /// Extra xkb configuration options
540    pub options: Option<String>,
541}
542
543impl XkbConfig {
544    /// Creates a new, empty [`XkbConfig`].
545    pub fn new() -> Self {
546        Default::default()
547    }
548
549    /// Sets this config's `rules`.
550    pub fn with_rules(mut self, rules: impl ToString) -> Self {
551        self.rules = Some(rules.to_string());
552        self
553    }
554
555    /// Sets this config's `model`.
556    pub fn with_model(mut self, model: impl ToString) -> Self {
557        self.model = Some(model.to_string());
558        self
559    }
560
561    /// Sets this config's `layout`.
562    pub fn with_layout(mut self, layout: impl ToString) -> Self {
563        self.layout = Some(layout.to_string());
564        self
565    }
566
567    /// Sets this config's `variant`.
568    pub fn with_variant(mut self, variant: impl ToString) -> Self {
569        self.variant = Some(variant.to_string());
570        self
571    }
572
573    /// Sets this config's `options`.
574    pub fn with_options(mut self, options: impl ToString) -> Self {
575        self.options = Some(options.to_string());
576        self
577    }
578}
579
580/// Sets the xkeyboard config.
581///
582/// This allows you to set several xkeyboard options like `layout` and `rules`.
583///
584/// See `xkeyboard-config(7)` for more information.
585///
586/// # Examples
587///
588/// ```no_run
589/// # use pinnacle_api::input;
590/// # use pinnacle_api::input::XkbConfig;
591/// input::set_xkb_config(XkbConfig::new()
592///     .with_layout("us,fr,ge")
593///     .with_options("ctrl:swapcaps,caps:shift"));
594/// ```
595pub fn set_xkb_config(xkb_config: XkbConfig) {
596    Client::input()
597        .set_xkb_config(SetXkbConfigRequest {
598            rules: xkb_config.rules,
599            variant: xkb_config.variant,
600            layout: xkb_config.layout,
601            model: xkb_config.model,
602            options: xkb_config.options,
603        })
604        .block_on_tokio()
605        .unwrap();
606}
607
608/// Sets the XKB keymap.
609///
610/// # Examples
611///
612/// ```no_run
613/// # use pinnacle_api::input;
614/// input::set_xkb_keymap("keymap here...");
615///
616/// // From a file
617/// # || {
618/// input::set_xkb_keymap(std::fs::read_to_string("/path/to/keymap.xkb")?);
619/// # Ok::<_, std::io::Error>(())
620/// # };
621/// ```
622pub fn set_xkb_keymap(keymap: impl ToString) {
623    Client::input()
624        .set_xkb_keymap(SetXkbKeymapRequest {
625            keymap: keymap.to_string(),
626        })
627        .block_on_tokio()
628        .unwrap();
629}
630
631/// Cycles the current XKB layout forward.
632pub fn cycle_xkb_layout_forward() {
633    Client::input()
634        .switch_xkb_layout(SwitchXkbLayoutRequest {
635            action: Some(switch_xkb_layout_request::Action::Next(())),
636        })
637        .block_on_tokio()
638        .unwrap();
639}
640
641/// Cycles the current XKB layout backward.
642pub fn cycle_xkb_layout_backward() {
643    Client::input()
644        .switch_xkb_layout(SwitchXkbLayoutRequest {
645            action: Some(switch_xkb_layout_request::Action::Prev(())),
646        })
647        .block_on_tokio()
648        .unwrap();
649}
650
651/// Switches the current XKB layout to the one at the provided `index`.
652///
653/// Fails if the index is out of bounds.
654pub fn switch_xkb_layout(index: u32) {
655    Client::input()
656        .switch_xkb_layout(SwitchXkbLayoutRequest {
657            action: Some(switch_xkb_layout_request::Action::Index(index)),
658        })
659        .block_on_tokio()
660        .unwrap();
661}
662
663/// Bind information.
664///
665/// Mainly used for the bind overlay.
666#[derive(Debug, Clone, PartialEq, Eq, Hash)]
667pub struct BindInfo {
668    /// The group to place this bind in. Empty if it is not in one.
669    pub group: String,
670    /// The description of this bind. Empty if it does not have one.
671    pub description: String,
672    /// The bind's modifiers.
673    pub mods: Mod,
674    /// The bind's layer.
675    pub layer: BindLayer,
676    /// Whether this bind is a quit bind.
677    pub quit: bool,
678    /// Whether this bind is a reload config bind.
679    pub reload_config: bool,
680    /// Whether this bind is allowed when the session is locked.
681    pub allow_when_locked: bool,
682    /// What kind of bind this is.
683    pub kind: BindInfoKind,
684}
685
686/// The kind of a bind (hey that rhymes).
687#[derive(Debug, Clone, PartialEq, Eq, Hash)]
688pub enum BindInfoKind {
689    /// This is a keybind.
690    Key {
691        /// The numeric key code.
692        key_code: u32,
693        /// The xkeyboard name of this key.
694        xkb_name: String,
695    },
696    /// This is a mousebind.
697    Mouse {
698        /// Which mouse button this bind uses.
699        button: MouseButton,
700    },
701}
702
703/// Sets the keyboard's repeat rate.
704///
705/// This allows you to set the time between holding down a key and it repeating
706/// as well as the time between each repeat.
707///
708/// Units are in milliseconds.
709///
710/// # Examples
711///
712/// ```no_run
713/// # use pinnacle_api::input;
714/// // Set keyboard to repeat after holding down for half a second,
715/// // and repeat once every 25ms (40 times a second)
716/// input::set_repeat_rate(25, 500);
717/// ```
718pub fn set_repeat_rate(rate: i32, delay: i32) {
719    Client::input()
720        .set_repeat_rate(SetRepeatRateRequest {
721            rate: Some(rate),
722            delay: Some(delay),
723        })
724        .block_on_tokio()
725        .unwrap();
726}
727
728/// Sets the xcursor theme.
729///
730/// Pinnacle reads `$XCURSOR_THEME` on startup to determine the theme.
731/// This allows you to set it at runtime.
732///
733/// # Examples
734///
735/// ```no_run
736/// # use pinnacle_api::input;
737/// input::set_xcursor_theme("Adwaita");
738/// ```
739pub fn set_xcursor_theme(theme: impl ToString) {
740    Client::input()
741        .set_xcursor(SetXcursorRequest {
742            theme: Some(theme.to_string()),
743            size: None,
744        })
745        .block_on_tokio()
746        .unwrap();
747}
748
749/// Sets the xcursor size.
750///
751/// Pinnacle reads `$XCURSOR_SIZE` on startup to determine the cursor size.
752/// This allows you to set it at runtime.
753///
754/// # Examples
755///
756/// ```no_run
757/// # use pinnacle_api::input;
758/// input::set_xcursor_size(64);
759/// ```
760pub fn set_xcursor_size(size: u32) {
761    Client::input()
762        .set_xcursor(SetXcursorRequest {
763            theme: None,
764            size: Some(size),
765        })
766        .block_on_tokio()
767        .unwrap();
768}
769
770/// A trait that designates anything that can be converted into a [`Keysym`].
771pub trait ToKeysym {
772    /// Converts this into a [`Keysym`].
773    fn to_keysym(&self) -> Keysym;
774}
775
776impl ToKeysym for Keysym {
777    fn to_keysym(&self) -> Keysym {
778        *self
779    }
780}
781
782impl ToKeysym for char {
783    fn to_keysym(&self) -> Keysym {
784        Keysym::from_char(*self)
785    }
786}
787
788impl ToKeysym for &str {
789    fn to_keysym(&self) -> Keysym {
790        xkbcommon::xkb::keysym_from_name(self, xkbcommon::xkb::KEYSYM_NO_FLAGS)
791    }
792}
793
794impl ToKeysym for String {
795    fn to_keysym(&self) -> Keysym {
796        xkbcommon::xkb::keysym_from_name(self, xkbcommon::xkb::KEYSYM_NO_FLAGS)
797    }
798}
799
800impl ToKeysym for u32 {
801    fn to_keysym(&self) -> Keysym {
802        Keysym::from(*self)
803    }
804}
805
806/// Gets all bind information.
807pub fn bind_infos() -> impl Iterator<Item = BindInfo> {
808    let infos = Client::input()
809        .get_bind_infos(GetBindInfosRequest {})
810        .block_on_tokio()
811        .unwrap()
812        .into_inner()
813        .bind_infos;
814
815    infos.into_iter().filter_map(|info| {
816        let info = info.bind?;
817        let mut mods = info.mods().fold(Mod::empty(), |acc, m| match m {
818            input::v1::Modifier::Unspecified => acc,
819            input::v1::Modifier::Shift => acc | Mod::SHIFT,
820            input::v1::Modifier::Ctrl => acc | Mod::CTRL,
821            input::v1::Modifier::Alt => acc | Mod::ALT,
822            input::v1::Modifier::Super => acc | Mod::SUPER,
823            input::v1::Modifier::IsoLevel3Shift => acc | Mod::ISO_LEVEL3_SHIFT,
824            input::v1::Modifier::IsoLevel5Shift => acc | Mod::ISO_LEVEL5_SHIFT,
825        });
826
827        for ignore_mod in info.ignore_mods() {
828            match ignore_mod {
829                input::v1::Modifier::Unspecified => (),
830                input::v1::Modifier::Shift => mods |= Mod::IGNORE_SHIFT,
831                input::v1::Modifier::Ctrl => mods |= Mod::IGNORE_CTRL,
832                input::v1::Modifier::Alt => mods |= Mod::IGNORE_ALT,
833                input::v1::Modifier::Super => mods |= Mod::IGNORE_SUPER,
834                input::v1::Modifier::IsoLevel3Shift => mods |= Mod::ISO_LEVEL3_SHIFT,
835                input::v1::Modifier::IsoLevel5Shift => mods |= Mod::ISO_LEVEL5_SHIFT,
836            }
837        }
838
839        let bind_kind = match info.bind? {
840            input::v1::bind::Bind::Key(keybind) => BindInfoKind::Key {
841                key_code: keybind.key_code(),
842                xkb_name: keybind.xkb_name().to_string(),
843            },
844            input::v1::bind::Bind::Mouse(mousebind) => BindInfoKind::Mouse {
845                button: MouseButton::from(mousebind.button),
846            },
847        };
848
849        let layer = BindLayer {
850            name: info.layer_name,
851        };
852        let group = info
853            .properties
854            .as_ref()
855            .and_then(|props| props.group.clone())
856            .unwrap_or_default();
857        let description = info
858            .properties
859            .as_ref()
860            .and_then(|props| props.description.clone())
861            .unwrap_or_default();
862        let quit = info
863            .properties
864            .as_ref()
865            .and_then(|props| props.quit)
866            .unwrap_or_default();
867        let reload_config = info
868            .properties
869            .as_ref()
870            .and_then(|props| props.reload_config)
871            .unwrap_or_default();
872        let allow_when_locked = info
873            .properties
874            .as_ref()
875            .and_then(|props| props.allow_when_locked)
876            .unwrap_or_default();
877
878        Some(BindInfo {
879            group,
880            description,
881            mods,
882            layer,
883            quit,
884            reload_config,
885            allow_when_locked,
886            kind: bind_kind,
887        })
888    })
889}
890
891/// Connects to an [`InputSignal`].
892///
893/// # Examples
894///
895/// ```no_run
896/// # use pinnacle_api::input;
897/// # use pinnacle_api::signal::InputSignal;
898/// input::connect_signal(InputSignal::DeviceAdded(Box::new(|device| {
899///     println!("New device: {}", device.name());
900/// })));
901/// ```
902pub fn connect_signal(signal: InputSignal) -> SignalHandle {
903    let mut signal_state = Client::signal_state();
904
905    match signal {
906        InputSignal::DeviceAdded(f) => signal_state.input_device_added.add_callback(f),
907    }
908}