Skip to content

Commit 3b84291

Browse files
committed
feat: Extras, tap-hold, and modifier keys
Initial implementation of #146. Backend representation for mod-tap, layer-tap, and modifiers+basic keycode, with conversion to and from text format and numeric keycode. Add shift-select for modifiers with a non-modifier key, extras tab, and tap-hold section. Sensitivity needs to be set based on whether shift is held and what is already selected. Getting this to work properly is awkward with how GTK modifiers are handled. More things need to be added to extras, the layout for basic keys needs to be updated for the latest design, and various style details need to be addressed. And it needs to not show these things for internal keycodes.
1 parent 8527120 commit 3b84291

20 files changed

+1170
-200
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ members = [ "tools", "ffi", "backend", "widgets" ]
1111
[dependencies]
1212
cascade = "1"
1313
futures = "0.3.13"
14-
gtk = { version = "0.15.0", features = ["v3_22"] }
14+
# 3.24 is packaged by focal
15+
gtk = { version = "0.15.0", features = ["v3_24"] }
1516
libc = "0.2"
1617
once_cell = "1.4"
1718
pangocairo = "0.15.0"

backend/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ license = "GPL-3.0-or-later"
66
edition = "2018"
77

88
[dependencies]
9+
bitflags = "1.3.2"
910
cascade = "1"
1011
futures = "0.3.13"
1112
futures-timer = "3.0.2"

backend/src/board.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,12 @@ impl Board {
259259
let mut key_leds = HashMap::new();
260260
for key in self.keys().iter() {
261261
let scancodes = (0..self.layout().meta.num_layers as usize)
262-
.map(|layer| key.get_scancode(layer).unwrap().1)
262+
.map(|layer| {
263+
key.get_scancode(layer)
264+
.unwrap()
265+
.1
266+
.map_or_else(String::new, |x| x.to_string())
267+
})
263268
.collect();
264269
map.insert(key.logical_name.clone(), scancodes);
265270
if !key.leds.is_empty() {

backend/src/key.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use glib::prelude::*;
22
use std::cell::Cell;
33

4-
use crate::{Board, Daemon, Hs, PhysicalLayoutKey, Rect, Rgb};
4+
use crate::{Board, Daemon, Hs, Keycode, PhysicalLayoutKey, Rect, Rgb};
55

66
#[derive(Debug)]
77
pub struct Key {
@@ -143,17 +143,16 @@ impl Key {
143143
Ok(())
144144
}
145145

146-
pub fn get_scancode(&self, layer: usize) -> Option<(u16, String)> {
146+
// TODO: operate on keycode enum
147+
pub fn get_scancode(&self, layer: usize) -> Option<(u16, Option<Keycode>)> {
147148
let board = self.board();
148149
let scancode = self.scancodes.get(layer)?.get();
149-
let scancode_name = match board.layout().scancode_to_name(scancode) {
150-
Some(some) => some,
151-
None => String::new(),
152-
};
150+
let scancode_name = board.layout().scancode_to_name(scancode);
153151
Some((scancode, scancode_name))
154152
}
155153

156-
pub async fn set_scancode(&self, layer: usize, scancode_name: &str) -> Result<(), String> {
154+
// TODO: operate on keycode enum
155+
pub async fn set_scancode(&self, layer: usize, scancode_name: &Keycode) -> Result<(), String> {
157156
let board = self.board();
158157
let scancode = board
159158
.layout()

backend/src/keycode.rs

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
// has_keycode logic; convert to/from int
2+
// serialize (Display?)/format
3+
// serde: serialize/deserialize as string
4+
5+
use bitflags::bitflags;
6+
7+
bitflags! {
8+
pub struct Mods: u16 {
9+
const CTRL = 0x1;
10+
const SHIFT = 0x2;
11+
const ALT = 0x4;
12+
const SUPER = 0x8;
13+
14+
const RIGHT = 0x10;
15+
16+
const RIGHT_CTRL = Self::RIGHT.bits | Self::CTRL.bits;
17+
const RIGHT_SHIFT = Self::RIGHT.bits | Self::SHIFT.bits;
18+
const RIGHT_ALT = Self::RIGHT.bits | Self::ALT.bits;
19+
const RIGHT_SUPER = Self::RIGHT.bits | Self::SUPER.bits;
20+
}
21+
}
22+
23+
impl Default for Mods {
24+
fn default() -> Self {
25+
Self::empty()
26+
}
27+
}
28+
29+
impl Mods {
30+
// Convert single modifier from name
31+
pub fn from_mod_str(s: &str) -> Option<Self> {
32+
match s {
33+
"LEFT_CTRL" => Some(Self::CTRL),
34+
"LEFT_SHIFT" => Some(Self::SHIFT),
35+
"LEFT_ALT" => Some(Self::ALT),
36+
"LEFT_SUPER" => Some(Self::SUPER),
37+
"RIGHT_CTRL" => Some(Self::RIGHT_CTRL),
38+
"RIGHT_SHIFT" => Some(Self::RIGHT_SHIFT),
39+
"RIGHT_ALT" => Some(Self::RIGHT_ALT),
40+
"RIGHT_SUPER" => Some(Self::RIGHT_SUPER),
41+
_ => None,
42+
}
43+
}
44+
45+
// Convert to single modifier
46+
pub(crate) fn as_mod_str(self) -> Option<&'static str> {
47+
match self {
48+
Self::CTRL => Some("LEFT_CTRL"),
49+
Self::SHIFT => Some("LEFT_SHIFT"),
50+
Self::ALT => Some("LEFT_ALT"),
51+
Self::SUPER => Some("LEFT_SUPER"),
52+
Self::RIGHT_CTRL => Some("RIGHT_CTRL"),
53+
Self::RIGHT_SHIFT => Some("RIGHT_SHIFT"),
54+
Self::RIGHT_ALT => Some("RIGHT_ALT"),
55+
Self::RIGHT_SUPER => Some("RIGHT_SUPER"),
56+
_ => None,
57+
}
58+
}
59+
60+
pub fn mod_names(self) -> impl Iterator<Item = &'static str> {
61+
[Self::CTRL, Self::SHIFT, Self::ALT, Self::SUPER]
62+
.iter()
63+
.filter_map(move |i| (self & (*i | Self::RIGHT)).as_mod_str())
64+
}
65+
66+
pub fn toggle_mod(self, other: Self) -> Self {
67+
let other_key = other & !Self::RIGHT;
68+
if !self.contains(other_key) {
69+
self | other
70+
} else {
71+
let key = self & !other_key;
72+
if key == Self::RIGHT {
73+
Self::empty()
74+
} else {
75+
key
76+
}
77+
}
78+
}
79+
}
80+
81+
#[derive(Debug, Hash, PartialEq, Eq, Clone, glib::Boxed)]
82+
#[boxed_type(name = "S76Keycode")]
83+
pub enum Keycode {
84+
Basic(Mods, String),
85+
MT(Mods, String),
86+
LT(u8, String),
87+
}
88+
89+
impl Keycode {
90+
pub fn parse(s: &str) -> Option<Self> {
91+
let mut tokens = tokenize(s);
92+
match tokens.next()? {
93+
"MT" => parse_mt(tokens),
94+
"LT" => parse_lt(tokens),
95+
keycode => parse_basic(tokenize(s)),
96+
}
97+
}
98+
99+
pub fn none() -> Self {
100+
Self::Basic(Mods::empty(), "NONE".to_string())
101+
}
102+
103+
pub fn is_none(&self) -> bool {
104+
if let Keycode::Basic(mode, keycode) = self {
105+
mode.is_empty() && keycode.as_str() == "NONE"
106+
} else {
107+
false
108+
}
109+
}
110+
111+
pub fn is_roll_over(&self) -> bool {
112+
if let Keycode::Basic(mode, keycode) = self {
113+
mode.is_empty() && keycode.as_str() == "ROLL_OVER"
114+
} else {
115+
false
116+
}
117+
}
118+
}
119+
120+
impl std::fmt::Display for Keycode {
121+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122+
match self {
123+
Self::Basic(mods, scancode_name) => {
124+
let mut has_mod = false;
125+
for mod_name in mods.mod_names() {
126+
if has_mod {
127+
write!(f, " | ")?;
128+
}
129+
write!(f, "{}", mod_name)?;
130+
}
131+
if !(scancode_name == "NONE" && has_mod) {
132+
write!(f, "{}", scancode_name)?;
133+
}
134+
}
135+
Self::MT(mods, scancode_name) => {
136+
write!(f, "MT(")?;
137+
let mut has_mod = false;
138+
for mod_name in mods.mod_names() {
139+
if has_mod {
140+
write!(f, " | ")?;
141+
}
142+
write!(f, "{}", mod_name)?;
143+
}
144+
write!(f, ", {})", scancode_name)?;
145+
}
146+
Self::LT(layer, scancode_name) => {
147+
write!(f, "LT({}, {})", layer, scancode_name)?;
148+
}
149+
}
150+
Ok(())
151+
}
152+
}
153+
154+
const SEPARATORS: &[char] = &[',', '|', '(', ')'];
155+
156+
// Tokenize into iterator of &str, splitting on whitespace and putting
157+
// separators in their own tokens.
158+
fn tokenize(mut s: &str) -> impl Iterator<Item = &str> {
159+
std::iter::from_fn(move || {
160+
s = s.trim_start_matches(' ');
161+
let idx = if SEPARATORS.contains(&s.chars().next()?) {
162+
1
163+
} else {
164+
s.find(|c| c == ' ' || SEPARATORS.contains(&c))
165+
.unwrap_or(s.len())
166+
};
167+
let tok = &s[..idx];
168+
s = &s[idx..];
169+
Some(tok)
170+
})
171+
}
172+
173+
fn parse_mt<'a>(mut tokens: impl Iterator<Item = &'a str>) -> Option<Keycode> {
174+
if tokens.next() != Some("(") {
175+
return None;
176+
}
177+
178+
let mut mods = Mods::empty();
179+
loop {
180+
mods |= Mods::from_mod_str(tokens.next()?)?;
181+
match tokens.next()? {
182+
"|" => {}
183+
"," => {
184+
break;
185+
}
186+
_ => {
187+
return None;
188+
}
189+
}
190+
}
191+
192+
let keycode = tokens.next()?.to_string();
193+
194+
if (tokens.next(), tokens.next()) != (Some(")"), None) {
195+
return None;
196+
}
197+
198+
Some(Keycode::MT(mods, keycode))
199+
}
200+
201+
fn parse_lt<'a>(mut tokens: impl Iterator<Item = &'a str>) -> Option<Keycode> {
202+
if tokens.next() != Some("(") {
203+
return None;
204+
}
205+
206+
let layer = tokens.next()?.parse().ok()?;
207+
208+
if tokens.next() != Some(",") {
209+
return None;
210+
}
211+
212+
let keycode = tokens.next()?.to_string();
213+
214+
if (tokens.next(), tokens.next()) != (Some(")"), None) {
215+
return None;
216+
}
217+
218+
Some(Keycode::LT(layer, keycode))
219+
}
220+
221+
// XXX limit to basic if there are mods?
222+
fn parse_basic<'a>(mut tokens: impl Iterator<Item = &'a str>) -> Option<Keycode> {
223+
let mut mods = Mods::empty();
224+
let mut keycode = None;
225+
226+
loop {
227+
let token = tokens.next()?;
228+
if let Some(mod_) = Mods::from_mod_str(token) {
229+
mods |= mod_;
230+
} else if keycode.is_none() && token.chars().next()?.is_alphanumeric() {
231+
keycode = Some(token.to_string());
232+
} else {
233+
return None;
234+
}
235+
match tokens.next() {
236+
Some("|") => {}
237+
Some(_) => {
238+
return None;
239+
}
240+
None => {
241+
break;
242+
}
243+
}
244+
}
245+
246+
Some(Keycode::Basic(
247+
mods,
248+
keycode.unwrap_or_else(|| "NONE".to_string()),
249+
))
250+
}

0 commit comments

Comments
 (0)