jay_config/keyboard/mod.rs
1//! Tools affecting the keyboard behavior.
2
3use {
4 crate::{
5 _private::{KeymapBuildParamsV1, KeymapBuildParamsV1Kind},
6 keyboard::{mods::Modifiers, syms::KeySym},
7 },
8 serde::{Deserialize, Serialize},
9 std::ops::{BitOr, BitOrAssign},
10};
11
12pub mod mods;
13pub mod syms;
14
15/// A keysym with zero or more modifiers
16#[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Hash, Debug)]
17pub struct ModifiedKeySym {
18 pub mods: Modifiers,
19 pub sym: KeySym,
20}
21
22impl From<KeySym> for ModifiedKeySym {
23 fn from(sym: KeySym) -> Self {
24 Self {
25 mods: Modifiers(0),
26 sym,
27 }
28 }
29}
30
31impl BitOr<Modifiers> for ModifiedKeySym {
32 type Output = ModifiedKeySym;
33
34 fn bitor(self, rhs: Modifiers) -> Self::Output {
35 ModifiedKeySym {
36 mods: self.mods | rhs,
37 sym: self.sym,
38 }
39 }
40}
41
42impl BitOrAssign<Modifiers> for ModifiedKeySym {
43 fn bitor_assign(&mut self, rhs: Modifiers) {
44 self.mods |= rhs;
45 }
46}
47
48/// A keymap.
49#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)]
50pub struct Keymap(pub u64);
51
52impl Keymap {
53 /// The invalid keymap.
54 pub const INVALID: Self = Self(0);
55
56 /// Returns whether this keymap is valid.
57 pub fn is_valid(self) -> bool {
58 self != Self::INVALID
59 }
60
61 /// Returns whether this keymap is invalid.
62 pub fn is_invalid(self) -> bool {
63 self == Self::INVALID
64 }
65
66 /// Destroys this reference to the keymap.
67 ///
68 /// Seats that are currently using this keymap are unaffected.
69 pub fn destroy(self) {
70 if self.is_valid() {
71 get!().destroy_keymap(self);
72 }
73 }
74
75 /// Creates a keymap builder.
76 pub fn builder<'a>() -> KeymapBuilder<'a> {
77 KeymapBuilder {
78 v1: KeymapBuildParamsV1 {
79 kind: None,
80 shortcuts_group: None,
81 },
82 }
83 }
84}
85
86/// An RMLVO group consisting of a layout and a variant.
87#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)]
88pub struct Group<'a> {
89 /// The layout of the group.
90 pub layout: &'a str,
91 /// The variant of the group. Can be an empty string.
92 pub variant: &'a str,
93}
94
95/// Parses a keymap.
96///
97/// The returned keymap can later be used to set the keymap of a seat. If the keymap cannot
98/// be parsed, returns an invalid keymap. Trying to set a seat's keymap to an invalid keymap
99/// has no effect.
100///
101/// A simple keymap looks as follows:
102///
103/// ```text
104/// xkb_keymap {
105/// xkb_keycodes { include "evdev+aliases(qwerty)" };
106/// xkb_types { include "complete" };
107/// xkb_compat { include "complete" };
108/// xkb_symbols { include "pc+inet(evdev)+us(basic)" };
109/// };
110/// ```
111///
112/// To use a programmer Dvorak, replace the corresponding line by
113///
114/// ```text
115/// xkb_symbols { include "pc+inet(evdev)+us(dvp)" };
116/// ```
117///
118/// To use a German keymap, replace the corresponding line by
119///
120/// ```text
121/// xkb_symbols { include "pc+inet(evdev)+de(basic)" };
122/// ```
123///
124/// You can also use a completely custom keymap that doesn't use any includes. See the
125/// [default][default] Jay keymap for an example.
126///
127/// General information about the keymap format can be found in the [arch wiki][wiki].
128///
129/// See also [`Keymap::builder`] for a more general interface.
130///
131/// [default]: https://github.com/mahkoh/jay/tree/master/src/keymap.xkb
132/// [wiki]: https://wiki.archlinux.org/title/X_keyboard_extension
133pub fn parse_keymap(keymap: &str) -> Keymap {
134 get!(Keymap::INVALID).parse_keymap(keymap)
135}
136
137/// Creates a keymap from RMLVO names.
138///
139/// If a parameter is not given, a value from the environment or a default is used:
140///
141/// | name | default |
142/// | ---------------------- | ---------------------- |
143/// | `XKB_DEFAULT_RULES` | `evdev` |
144/// | `XKB_DEFAULT_MODEL` | `pc105` |
145/// | `XKB_DEFAULT_LAYOUT` | `us` |
146/// | `XKB_DEFAULT_VARIANTS` | |
147/// | `XKB_DEFAULT_OPTIONS` | |
148///
149/// `XKB_DEFAULT_LAYOUT` and `XKB_DEFAULT_VARIANTS` are parsed into the `groups` parameter like this example:
150/// ```
151/// XKB_DEFAULT_LAYOUT = "us,il,ru,de,jp"
152/// XKB_DEFAULT_VARIANTS = ",,phonetic,neo"
153/// ```
154/// produces:
155/// ```
156/// [
157/// Group { layout: "us", variant: "" },
158/// Group { layout: "il", variant: "" },
159/// Group { layout: "ru", variant: "phonetic" },
160/// Group { layout: "de", variant: "neo" },
161/// Group { layout: "jp", variant: "" },
162/// ]
163/// ```
164///
165/// See also [`Keymap::builder`] for a more general interface.
166pub fn keymap_from_names(
167 rules: Option<&str>,
168 model: Option<&str>,
169 groups: Option<&[Group<'_>]>,
170 options: Option<&[&str]>,
171) -> Keymap {
172 get!(Keymap::INVALID).keymap_from_names(rules, model, groups, options)
173}
174
175/// A keymap builder.
176pub struct KeymapBuilder<'a> {
177 pub(crate) v1: KeymapBuildParamsV1<'a>,
178}
179
180impl<'a> KeymapBuilder<'a> {
181 /// Builds the keymap.
182 pub fn build(self) -> Keymap {
183 get!(Keymap::INVALID).parse_keymap_2(self)
184 }
185
186 /// Sets the XKB map to parse.
187 ///
188 /// See [`parse_keymap`] for details about the format.
189 pub fn map(mut self, map: &'a str) -> Self {
190 self.v1.kind = Some(KeymapBuildParamsV1Kind::Map(map));
191 self
192 }
193
194 /// Sets the RMLVO names to parse.
195 ///
196 /// See [`keymap_from_names`] for details about the format.
197 pub fn names(
198 mut self,
199 rules: Option<&'a str>,
200 model: Option<&'a str>,
201 groups: Option<&'a [Group<'a>]>,
202 options: Option<&'a [&'a str]>,
203 ) -> Self {
204 self.v1.kind = Some(KeymapBuildParamsV1Kind::Names {
205 rules,
206 model,
207 groups: groups.map(|v| v.to_vec()),
208 options: options.map(|v| v.to_vec()),
209 });
210 self
211 }
212
213 /// Sets the 0-based group index to use for shortcuts.
214 ///
215 /// By default, the group that is active at the time of the key press is used. For
216 /// example, if the keymap has two groups, US and RU, and the active group is 1,
217 /// shortcuts are looked up against the Russian group.
218 ///
219 /// By calling `shortcuts_group(0)`, shortcuts are instead always looked up against
220 /// the English group.
221 pub fn shortcuts_group(mut self, group: u32) -> Self {
222 self.v1.shortcuts_group = Some(group);
223 self
224 }
225}