1use 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#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
34#[repr(u32)]
35pub enum MouseButton {
36 Left = 0x110,
38 Right = 0x111,
40 Middle = 0x112,
42 Side = 0x113,
44 Extra = 0x114,
46 Forward = 0x115,
48 Back = 0x116,
50 #[num_enum(catch_all)]
52 Other(u32),
53}
54
55bitflags::bitflags! {
56 #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default)]
69 pub struct Mod: u16 {
70 const SHIFT = 1;
72 const CTRL = 1 << 1;
74 const ALT = 1 << 2;
76 const SUPER = 1 << 3;
78 const ISO_LEVEL3_SHIFT = 1 << 4;
80 const ISO_LEVEL5_SHIFT = 1 << 5;
82
83 const IGNORE_SHIFT = 1 << 6;
85 const IGNORE_CTRL = 1 << 7;
87 const IGNORE_ALT = 1 << 8;
89 const IGNORE_SUPER = 1 << 9;
91 const IGNORE_ISO_LEVEL3_SHIFT = 1 << 10;
93 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#[derive(Debug, Clone, Hash, PartialEq, Eq)]
151pub struct BindLayer {
152 name: Option<String>,
153}
154
155impl BindLayer {
156 pub const DEFAULT: Self = Self { name: None };
160
161 pub fn get(name: impl ToString) -> Self {
163 Self {
164 name: Some(name.to_string()),
165 }
166 }
167
168 pub fn keybind(&self, mods: Mod, key: impl ToKeysym) -> Keybind {
170 new_keybind(mods, key, self).block_on_tokio()
171 }
172
173 pub fn mousebind(&self, mods: Mod, button: MouseButton) -> Mousebind {
175 new_mousebind(mods, button, self).block_on_tokio()
176 }
177
178 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 pub fn name(&self) -> Option<String> {
190 self.name.clone()
191 }
192}
193
194pub trait Bind {
196 fn group(&mut self, group: impl ToString) -> &mut Self;
198 fn description(&mut self, desc: impl ToString) -> &mut Self;
200 fn set_as_quit(&mut self) -> &mut Self;
202 fn set_as_reload_config(&mut self) -> &mut Self;
204 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
291pub struct Keybind {
293 bind_id: u32,
294 callback_sender: Option<UnboundedSender<KeybindCallback>>,
295}
296
297bind_impl!(Keybind);
298
299pub fn keybind(mods: Mod, key: impl ToKeysym) -> Keybind {
301 BindLayer::DEFAULT.keybind(mods, key)
302}
303
304impl Keybind {
305 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 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
407type MousebindCallback = (Box<dyn FnMut() + Send + 'static>, Edge);
410
411pub struct Mousebind {
413 bind_id: u32,
414 callback_sender: Option<UnboundedSender<MousebindCallback>>,
415}
416
417bind_impl!(Mousebind);
418
419pub fn mousebind(mods: Mod, button: MouseButton) -> Mousebind {
421 BindLayer::DEFAULT.mousebind(mods, button)
422}
423
424impl Mousebind {
425 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 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#[derive(Clone, Debug, Hash, PartialEq, Eq, Default)]
530pub struct XkbConfig {
531 pub rules: Option<String>,
533 pub model: Option<String>,
535 pub layout: Option<String>,
537 pub variant: Option<String>,
539 pub options: Option<String>,
541}
542
543impl XkbConfig {
544 pub fn new() -> Self {
546 Default::default()
547 }
548
549 pub fn with_rules(mut self, rules: impl ToString) -> Self {
551 self.rules = Some(rules.to_string());
552 self
553 }
554
555 pub fn with_model(mut self, model: impl ToString) -> Self {
557 self.model = Some(model.to_string());
558 self
559 }
560
561 pub fn with_layout(mut self, layout: impl ToString) -> Self {
563 self.layout = Some(layout.to_string());
564 self
565 }
566
567 pub fn with_variant(mut self, variant: impl ToString) -> Self {
569 self.variant = Some(variant.to_string());
570 self
571 }
572
573 pub fn with_options(mut self, options: impl ToString) -> Self {
575 self.options = Some(options.to_string());
576 self
577 }
578}
579
580pub 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
608pub 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
631pub 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
641pub 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
651pub 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#[derive(Debug, Clone, PartialEq, Eq, Hash)]
667pub struct BindInfo {
668 pub group: String,
670 pub description: String,
672 pub mods: Mod,
674 pub layer: BindLayer,
676 pub quit: bool,
678 pub reload_config: bool,
680 pub allow_when_locked: bool,
682 pub kind: BindInfoKind,
684}
685
686#[derive(Debug, Clone, PartialEq, Eq, Hash)]
688pub enum BindInfoKind {
689 Key {
691 key_code: u32,
693 xkb_name: String,
695 },
696 Mouse {
698 button: MouseButton,
700 },
701}
702
703pub 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
728pub 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
749pub 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
770pub trait ToKeysym {
772 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
806pub 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
891pub 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}