1use num_enum::{FromPrimitive, IntoPrimitive};
10use pinnacle_api_defs::pinnacle::input::{
11 self,
12 v1::{
13 BindProperties, BindRequest, EnterBindLayerRequest, GetBindInfosRequest,
14 KeybindOnPressRequest, KeybindStreamRequest, MousebindOnPressRequest,
15 MousebindStreamRequest, SetBindPropertiesRequest, SetRepeatRateRequest, SetXcursorRequest,
16 SetXkbConfigRequest, SetXkbKeymapRequest, SwitchXkbLayoutRequest,
17 switch_xkb_layout_request,
18 },
19};
20use tokio::sync::mpsc::{UnboundedSender, unbounded_channel};
21use tokio_stream::StreamExt;
22
23use crate::{
24 BlockOnTokio,
25 client::Client,
26 signal::{InputSignal, SignalHandle},
27};
28
29pub mod libinput;
30
31pub use xkbcommon::xkb::Keysym;
32
33#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
35#[repr(u32)]
36pub enum MouseButton {
37 Left = 0x110,
39 Right = 0x111,
41 Middle = 0x112,
43 Side = 0x113,
45 Extra = 0x114,
47 Forward = 0x115,
49 Back = 0x116,
51 #[num_enum(catch_all)]
53 Other(u32),
54}
55
56bitflags::bitflags! {
57 #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default)]
70 pub struct Mod: u16 {
71 const SHIFT = 1;
73 const CTRL = 1 << 1;
75 const ALT = 1 << 2;
77 const SUPER = 1 << 3;
79 const ISO_LEVEL3_SHIFT = 1 << 4;
81 const ISO_LEVEL5_SHIFT = 1 << 5;
83
84 const IGNORE_SHIFT = 1 << 6;
86 const IGNORE_CTRL = 1 << 7;
88 const IGNORE_ALT = 1 << 8;
90 const IGNORE_SUPER = 1 << 9;
92 const IGNORE_ISO_LEVEL3_SHIFT = 1 << 10;
94 const IGNORE_ISO_LEVEL5_SHIFT = 1 << 11;
96 }
97}
98
99impl Mod {
100 fn api_mods(&self) -> Vec<input::v1::Modifier> {
101 let mut mods = Vec::new();
102 if self.contains(Mod::SHIFT) {
103 mods.push(input::v1::Modifier::Shift);
104 }
105 if self.contains(Mod::CTRL) {
106 mods.push(input::v1::Modifier::Ctrl);
107 }
108 if self.contains(Mod::ALT) {
109 mods.push(input::v1::Modifier::Alt);
110 }
111 if self.contains(Mod::SUPER) {
112 mods.push(input::v1::Modifier::Super);
113 }
114 if self.contains(Mod::ISO_LEVEL3_SHIFT) {
115 mods.push(input::v1::Modifier::IsoLevel3Shift);
116 }
117 if self.contains(Mod::ISO_LEVEL5_SHIFT) {
118 mods.push(input::v1::Modifier::IsoLevel5Shift);
119 }
120 mods
121 }
122
123 fn api_ignore_mods(&self) -> Vec<input::v1::Modifier> {
124 let mut mods = Vec::new();
125 if self.contains(Mod::IGNORE_SHIFT) {
126 mods.push(input::v1::Modifier::Shift);
127 }
128 if self.contains(Mod::IGNORE_CTRL) {
129 mods.push(input::v1::Modifier::Ctrl);
130 }
131 if self.contains(Mod::IGNORE_ALT) {
132 mods.push(input::v1::Modifier::Alt);
133 }
134 if self.contains(Mod::IGNORE_SUPER) {
135 mods.push(input::v1::Modifier::Super);
136 }
137 if self.contains(Mod::IGNORE_ISO_LEVEL3_SHIFT) {
138 mods.push(input::v1::Modifier::IsoLevel3Shift);
139 }
140 if self.contains(Mod::IGNORE_ISO_LEVEL5_SHIFT) {
141 mods.push(input::v1::Modifier::IsoLevel5Shift);
142 }
143 mods
144 }
145}
146
147#[derive(Debug, Clone, Hash, PartialEq, Eq)]
152pub struct BindLayer {
153 name: Option<String>,
154}
155
156impl BindLayer {
157 pub const DEFAULT: Self = Self { name: None };
161
162 pub fn get(name: impl ToString) -> Self {
164 Self {
165 name: Some(name.to_string()),
166 }
167 }
168
169 pub fn keybind(&self, mods: Mod, key: impl ToKeysym) -> Keybind {
171 new_keybind(mods, key, self).block_on_tokio()
172 }
173
174 pub fn mousebind(&self, mods: Mod, button: MouseButton) -> Mousebind {
176 new_mousebind(mods, button, self).block_on_tokio()
177 }
178
179 pub fn enter(&self) {
181 Client::input()
182 .enter_bind_layer(EnterBindLayerRequest {
183 layer_name: self.name.clone(),
184 })
185 .block_on_tokio()
186 .unwrap();
187 }
188
189 pub fn name(&self) -> Option<String> {
191 self.name.clone()
192 }
193}
194
195pub trait Bind {
197 fn group(&mut self, group: impl ToString) -> &mut Self;
199 fn description(&mut self, desc: impl ToString) -> &mut Self;
201 fn set_as_quit(&mut self) -> &mut Self;
203 fn set_as_reload_config(&mut self) -> &mut Self;
205 fn allow_when_locked(&mut self) -> &mut Self;
207}
208
209macro_rules! bind_impl {
210 ($ty:ty) => {
211 impl Bind for $ty {
212 fn group(&mut self, group: impl ToString) -> &mut Self {
213 Client::input()
214 .set_bind_properties(SetBindPropertiesRequest {
215 bind_id: self.bind_id,
216 properties: Some(BindProperties {
217 group: Some(group.to_string()),
218 ..Default::default()
219 }),
220 })
221 .block_on_tokio()
222 .unwrap();
223 self
224 }
225
226 fn description(&mut self, desc: impl ToString) -> &mut Self {
227 Client::input()
228 .set_bind_properties(SetBindPropertiesRequest {
229 bind_id: self.bind_id,
230 properties: Some(BindProperties {
231 description: Some(desc.to_string()),
232 ..Default::default()
233 }),
234 })
235 .block_on_tokio()
236 .unwrap();
237 self
238 }
239
240 fn set_as_quit(&mut self) -> &mut Self {
241 Client::input()
242 .set_bind_properties(SetBindPropertiesRequest {
243 bind_id: self.bind_id,
244 properties: Some(BindProperties {
245 quit: Some(true),
246 ..Default::default()
247 }),
248 })
249 .block_on_tokio()
250 .unwrap();
251 self
252 }
253
254 fn set_as_reload_config(&mut self) -> &mut Self {
255 Client::input()
256 .set_bind_properties(SetBindPropertiesRequest {
257 bind_id: self.bind_id,
258 properties: Some(BindProperties {
259 reload_config: Some(true),
260 ..Default::default()
261 }),
262 })
263 .block_on_tokio()
264 .unwrap();
265 self
266 }
267
268 fn allow_when_locked(&mut self) -> &mut Self {
269 Client::input()
270 .set_bind_properties(SetBindPropertiesRequest {
271 bind_id: self.bind_id,
272 properties: Some(BindProperties {
273 allow_when_locked: Some(true),
274 ..Default::default()
275 }),
276 })
277 .block_on_tokio()
278 .unwrap();
279 self
280 }
281 }
282 };
283}
284
285enum Edge {
286 Press,
287 Release,
288}
289
290type KeybindCallback = (Box<dyn FnMut() + Send + 'static>, Edge);
291
292pub struct Keybind {
294 bind_id: u32,
295 callback_sender: Option<UnboundedSender<KeybindCallback>>,
296}
297
298bind_impl!(Keybind);
299
300pub fn keybind(mods: Mod, key: impl ToKeysym) -> Keybind {
302 BindLayer::DEFAULT.keybind(mods, key)
303}
304
305impl Keybind {
306 pub fn on_press<F: FnMut() + Send + 'static>(&mut self, on_press: F) -> &mut Self {
308 let sender = self
309 .callback_sender
310 .get_or_insert_with(|| new_keybind_stream(self.bind_id).block_on_tokio());
311 let _ = sender.send((Box::new(on_press), Edge::Press));
312
313 Client::input()
314 .keybind_on_press(KeybindOnPressRequest {
315 bind_id: self.bind_id,
316 })
317 .block_on_tokio()
318 .unwrap();
319
320 self
321 }
322
323 pub fn on_release<F: FnMut() + Send + 'static>(&mut self, on_release: F) -> &mut Self {
325 let sender = self
326 .callback_sender
327 .get_or_insert_with(|| new_keybind_stream(self.bind_id).block_on_tokio());
328 let _ = sender.send((Box::new(on_release), Edge::Release));
329
330 self
331 }
332}
333
334async fn new_keybind(mods: Mod, key: impl ToKeysym, layer: &BindLayer) -> Keybind {
335 let ignore_mods = mods.api_ignore_mods();
336 let mods = mods.api_mods();
337
338 let bind_id = Client::input()
339 .bind(BindRequest {
340 bind: Some(input::v1::Bind {
341 mods: mods.into_iter().map(|m| m.into()).collect(),
342 ignore_mods: ignore_mods.into_iter().map(|m| m.into()).collect(),
343 layer_name: layer.name.clone(),
344 properties: Some(BindProperties::default()),
345 bind: Some(input::v1::bind::Bind::Key(input::v1::Keybind {
346 key_code: Some(key.to_keysym().raw()),
347 xkb_name: None,
348 })),
349 }),
350 })
351 .await
352 .unwrap()
353 .into_inner()
354 .bind_id;
355
356 Keybind {
357 bind_id,
358 callback_sender: None,
359 }
360}
361
362async fn new_keybind_stream(
363 bind_id: u32,
364) -> UnboundedSender<(Box<dyn FnMut() + Send + 'static>, Edge)> {
365 let mut from_server = Client::input()
366 .keybind_stream(KeybindStreamRequest { bind_id })
367 .await
368 .unwrap()
369 .into_inner();
370
371 let (send, mut recv) = unbounded_channel();
372
373 tokio::spawn(async move {
374 let mut on_presses = Vec::<Box<dyn FnMut() + Send + 'static>>::new();
375 let mut on_releases = Vec::<Box<dyn FnMut() + Send + 'static>>::new();
376
377 loop {
378 tokio::select! {
379 Some(Ok(response)) = from_server.next() => {
380 match response.edge() {
381 input::v1::Edge::Unspecified => (),
382 input::v1::Edge::Press => {
383 for on_press in on_presses.iter_mut() {
384 on_press();
385 }
386 }
387 input::v1::Edge::Release => {
388 for on_release in on_releases.iter_mut() {
389 on_release();
390 }
391 }
392 }
393 }
394 Some((cb, edge)) = recv.recv() => {
395 match edge {
396 Edge::Press => on_presses.push(cb),
397 Edge::Release => on_releases.push(cb),
398 }
399 }
400 else => break,
401 }
402 }
403 });
404
405 send
406}
407
408type MousebindCallback = (Box<dyn FnMut() + Send + 'static>, Edge);
411
412pub struct Mousebind {
414 bind_id: u32,
415 callback_sender: Option<UnboundedSender<MousebindCallback>>,
416}
417
418bind_impl!(Mousebind);
419
420pub fn mousebind(mods: Mod, button: MouseButton) -> Mousebind {
422 BindLayer::DEFAULT.mousebind(mods, button)
423}
424
425impl Mousebind {
426 pub fn on_press<F: FnMut() + Send + 'static>(&mut self, on_press: F) -> &mut Self {
428 let sender = self
429 .callback_sender
430 .get_or_insert_with(|| new_mousebind_stream(self.bind_id).block_on_tokio());
431 let _ = sender.send((Box::new(on_press), Edge::Press));
432
433 Client::input()
434 .mousebind_on_press(MousebindOnPressRequest {
435 bind_id: self.bind_id,
436 })
437 .block_on_tokio()
438 .unwrap();
439
440 self
441 }
442
443 pub fn on_release<F: FnMut() + Send + 'static>(&mut self, on_release: F) -> &mut Self {
445 let sender = self
446 .callback_sender
447 .get_or_insert_with(|| new_mousebind_stream(self.bind_id).block_on_tokio());
448 let _ = sender.send((Box::new(on_release), Edge::Release));
449
450 self
451 }
452}
453
454async fn new_mousebind(mods: Mod, button: MouseButton, layer: &BindLayer) -> Mousebind {
455 let ignore_mods = mods.api_ignore_mods();
456 let mods = mods.api_mods();
457
458 let bind_id = Client::input()
459 .bind(BindRequest {
460 bind: Some(input::v1::Bind {
461 mods: mods.into_iter().map(|m| m.into()).collect(),
462 ignore_mods: ignore_mods.into_iter().map(|m| m.into()).collect(),
463 layer_name: layer.name.clone(),
464 properties: Some(BindProperties::default()),
465 bind: Some(input::v1::bind::Bind::Mouse(input::v1::Mousebind {
466 button: button.into(),
467 })),
468 }),
469 })
470 .await
471 .unwrap()
472 .into_inner()
473 .bind_id;
474
475 Mousebind {
476 bind_id,
477 callback_sender: None,
478 }
479}
480
481async fn new_mousebind_stream(
482 bind_id: u32,
483) -> UnboundedSender<(Box<dyn FnMut() + Send + 'static>, Edge)> {
484 let mut from_server = Client::input()
485 .mousebind_stream(MousebindStreamRequest { bind_id })
486 .await
487 .unwrap()
488 .into_inner();
489
490 let (send, mut recv) = unbounded_channel();
491
492 tokio::spawn(async move {
493 let mut on_presses = Vec::<Box<dyn FnMut() + Send + 'static>>::new();
494 let mut on_releases = Vec::<Box<dyn FnMut() + Send + 'static>>::new();
495
496 loop {
497 tokio::select! {
498 Some(Ok(response)) = from_server.next() => {
499 match response.edge() {
500 input::v1::Edge::Unspecified => (),
501 input::v1::Edge::Press => {
502 for on_press in on_presses.iter_mut() {
503 on_press();
504 }
505 }
506 input::v1::Edge::Release => {
507 for on_release in on_releases.iter_mut() {
508 on_release();
509 }
510 }
511 }
512 }
513 Some((cb, edge)) = recv.recv() => {
514 match edge {
515 Edge::Press => on_presses.push(cb),
516 Edge::Release => on_releases.push(cb),
517 }
518 }
519 else => break,
520 }
521 }
522 });
523
524 send
525}
526
527#[derive(Clone, Debug, Hash, PartialEq, Eq, Default)]
531pub struct XkbConfig {
532 pub rules: Option<String>,
534 pub model: Option<String>,
536 pub layout: Option<String>,
538 pub variant: Option<String>,
540 pub options: Option<String>,
542}
543
544impl XkbConfig {
545 pub fn new() -> Self {
547 Default::default()
548 }
549
550 pub fn with_rules(mut self, rules: impl ToString) -> Self {
552 self.rules = Some(rules.to_string());
553 self
554 }
555
556 pub fn with_model(mut self, model: impl ToString) -> Self {
558 self.model = Some(model.to_string());
559 self
560 }
561
562 pub fn with_layout(mut self, layout: impl ToString) -> Self {
564 self.layout = Some(layout.to_string());
565 self
566 }
567
568 pub fn with_variant(mut self, variant: impl ToString) -> Self {
570 self.variant = Some(variant.to_string());
571 self
572 }
573
574 pub fn with_options(mut self, options: impl ToString) -> Self {
576 self.options = Some(options.to_string());
577 self
578 }
579}
580
581pub fn set_xkb_config(xkb_config: XkbConfig) {
597 Client::input()
598 .set_xkb_config(SetXkbConfigRequest {
599 rules: xkb_config.rules,
600 variant: xkb_config.variant,
601 layout: xkb_config.layout,
602 model: xkb_config.model,
603 options: xkb_config.options,
604 })
605 .block_on_tokio()
606 .unwrap();
607}
608
609pub fn set_xkb_keymap(keymap: impl ToString) {
624 Client::input()
625 .set_xkb_keymap(SetXkbKeymapRequest {
626 keymap: keymap.to_string(),
627 })
628 .block_on_tokio()
629 .unwrap();
630}
631
632pub fn cycle_xkb_layout_forward() {
634 Client::input()
635 .switch_xkb_layout(SwitchXkbLayoutRequest {
636 action: Some(switch_xkb_layout_request::Action::Next(())),
637 })
638 .block_on_tokio()
639 .unwrap();
640}
641
642pub fn cycle_xkb_layout_backward() {
644 Client::input()
645 .switch_xkb_layout(SwitchXkbLayoutRequest {
646 action: Some(switch_xkb_layout_request::Action::Prev(())),
647 })
648 .block_on_tokio()
649 .unwrap();
650}
651
652pub fn switch_xkb_layout(index: u32) {
656 Client::input()
657 .switch_xkb_layout(SwitchXkbLayoutRequest {
658 action: Some(switch_xkb_layout_request::Action::Index(index)),
659 })
660 .block_on_tokio()
661 .unwrap();
662}
663
664#[derive(Debug, Clone, PartialEq, Eq, Hash)]
668pub struct BindInfo {
669 pub group: String,
671 pub description: String,
673 pub mods: Mod,
675 pub layer: BindLayer,
677 pub quit: bool,
679 pub reload_config: bool,
681 pub allow_when_locked: bool,
683 pub kind: BindInfoKind,
685}
686
687#[derive(Debug, Clone, PartialEq, Eq, Hash)]
689pub enum BindInfoKind {
690 Key {
692 key_code: u32,
694 xkb_name: String,
696 },
697 Mouse {
699 button: MouseButton,
701 },
702}
703
704pub fn set_repeat_rate(rate: i32, delay: i32) {
720 Client::input()
721 .set_repeat_rate(SetRepeatRateRequest {
722 rate: Some(rate),
723 delay: Some(delay),
724 })
725 .block_on_tokio()
726 .unwrap();
727}
728
729pub fn set_xcursor_theme(theme: impl ToString) {
741 Client::input()
742 .set_xcursor(SetXcursorRequest {
743 theme: Some(theme.to_string()),
744 size: None,
745 })
746 .block_on_tokio()
747 .unwrap();
748}
749
750pub fn set_xcursor_size(size: u32) {
762 Client::input()
763 .set_xcursor(SetXcursorRequest {
764 theme: None,
765 size: Some(size),
766 })
767 .block_on_tokio()
768 .unwrap();
769}
770
771pub trait ToKeysym {
773 fn to_keysym(&self) -> Keysym;
775}
776
777impl ToKeysym for Keysym {
778 fn to_keysym(&self) -> Keysym {
779 *self
780 }
781}
782
783impl ToKeysym for char {
784 fn to_keysym(&self) -> Keysym {
785 Keysym::from_char(*self)
786 }
787}
788
789impl ToKeysym for &str {
790 fn to_keysym(&self) -> Keysym {
791 xkbcommon::xkb::keysym_from_name(self, xkbcommon::xkb::KEYSYM_NO_FLAGS)
792 }
793}
794
795impl ToKeysym for String {
796 fn to_keysym(&self) -> Keysym {
797 xkbcommon::xkb::keysym_from_name(self, xkbcommon::xkb::KEYSYM_NO_FLAGS)
798 }
799}
800
801impl ToKeysym for u32 {
802 fn to_keysym(&self) -> Keysym {
803 Keysym::from(*self)
804 }
805}
806
807pub fn bind_infos() -> impl Iterator<Item = BindInfo> {
809 let infos = Client::input()
810 .get_bind_infos(GetBindInfosRequest {})
811 .block_on_tokio()
812 .unwrap()
813 .into_inner()
814 .bind_infos;
815
816 infos.into_iter().filter_map(|info| {
817 let info = info.bind?;
818 let mut mods = info.mods().fold(Mod::empty(), |acc, m| match m {
819 input::v1::Modifier::Unspecified => acc,
820 input::v1::Modifier::Shift => acc | Mod::SHIFT,
821 input::v1::Modifier::Ctrl => acc | Mod::CTRL,
822 input::v1::Modifier::Alt => acc | Mod::ALT,
823 input::v1::Modifier::Super => acc | Mod::SUPER,
824 input::v1::Modifier::IsoLevel3Shift => acc | Mod::ISO_LEVEL3_SHIFT,
825 input::v1::Modifier::IsoLevel5Shift => acc | Mod::ISO_LEVEL5_SHIFT,
826 });
827
828 for ignore_mod in info.ignore_mods() {
829 match ignore_mod {
830 input::v1::Modifier::Unspecified => (),
831 input::v1::Modifier::Shift => mods |= Mod::IGNORE_SHIFT,
832 input::v1::Modifier::Ctrl => mods |= Mod::IGNORE_CTRL,
833 input::v1::Modifier::Alt => mods |= Mod::IGNORE_ALT,
834 input::v1::Modifier::Super => mods |= Mod::IGNORE_SUPER,
835 input::v1::Modifier::IsoLevel3Shift => mods |= Mod::ISO_LEVEL3_SHIFT,
836 input::v1::Modifier::IsoLevel5Shift => mods |= Mod::ISO_LEVEL5_SHIFT,
837 }
838 }
839
840 let bind_kind = match info.bind? {
841 input::v1::bind::Bind::Key(keybind) => BindInfoKind::Key {
842 key_code: keybind.key_code(),
843 xkb_name: keybind.xkb_name().to_string(),
844 },
845 input::v1::bind::Bind::Mouse(mousebind) => BindInfoKind::Mouse {
846 button: MouseButton::from(mousebind.button),
847 },
848 };
849
850 let layer = BindLayer {
851 name: info.layer_name,
852 };
853 let group = info
854 .properties
855 .as_ref()
856 .and_then(|props| props.group.clone())
857 .unwrap_or_default();
858 let description = info
859 .properties
860 .as_ref()
861 .and_then(|props| props.description.clone())
862 .unwrap_or_default();
863 let quit = info
864 .properties
865 .as_ref()
866 .and_then(|props| props.quit)
867 .unwrap_or_default();
868 let reload_config = info
869 .properties
870 .as_ref()
871 .and_then(|props| props.reload_config)
872 .unwrap_or_default();
873 let allow_when_locked = info
874 .properties
875 .as_ref()
876 .and_then(|props| props.allow_when_locked)
877 .unwrap_or_default();
878
879 Some(BindInfo {
880 group,
881 description,
882 mods,
883 layer,
884 quit,
885 reload_config,
886 allow_when_locked,
887 kind: bind_kind,
888 })
889 })
890}
891
892pub fn connect_signal(signal: InputSignal) -> SignalHandle {
904 let mut signal_state = Client::signal_state();
905
906 match signal {
907 InputSignal::DeviceAdded(f) => signal_state.input_device_added.add_callback(f),
908 }
909}