1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211 | diff --git a/Cargo.lock b/Cargo.lock
index 01bed55..8eb3e07 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -68,7 +68,7 @@ dependencies = [
[[package]]
name = "pind"
-version = "0.1.5"
+version = "0.2.0"
dependencies = [
"evdev",
]
diff --git a/Cargo.toml b/Cargo.toml
index 7d50d96..bace109 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,7 +4,7 @@ version = "0.2.0"
edition = "2024"
[dependencies]
-evdev = {path = "libs/evdev" }
+evdev = {path = "libs/evdev", features = ["uinput"] }
[[bin]]
name = "pindd"
diff --git a/libs/evdev/Cargo.toml b/libs/evdev/Cargo.toml
index fc0f185..1d31b83 100644
--- a/libs/evdev/Cargo.toml
+++ b/libs/evdev/Cargo.toml
@@ -13,6 +13,7 @@ rust-version = "1.64"
serde = ["dep:serde"]
tokio = ["dep:tokio"]
stream-trait = ["tokio", "futures-core"]
+uinput = [] # Add this line
device-test = []
[dependencies]
diff --git a/src/main.rs b/src/main.rs
index 58beb9e..511d3f2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -23,16 +23,15 @@
// - evdev crate for reading keyboard input
// - std for filesystem, environment, threading, and process handling
-
-
-use evdev::{AttributeSet, Device, KeyCode, enumerate};
+use evdev::{AttributeSet, Device, KeyCode, enumerate, EventType, uinput::VirtualDevice};
use std::{env::{var,args},fs::{read_to_string,canonicalize}, path::PathBuf, process::{exit,Command, Stdio}, sync::{Arc, OnceLock}, thread};
const DELAY :u64 = 25; // 25ms
-const CONFIG :&str = "/etc/pind/pindrc"; // You can use ~/ for user path
+const CONFIG :&str = "~/etc/pind/pindrc"; // You can use ~/ for user path
// like ~/.config/pind/pindrc
static BINDINGS: OnceLock<Arc<Vec<(AttributeSet<KeyCode>, String)>>> = OnceLock::new();
+
fn get_bindings() -> Arc<Vec<(AttributeSet<KeyCode>, String)>>
{
BINDINGS.get_or_init(|| Arc::new(load_config(CONFIG))).clone()
@@ -153,31 +152,135 @@ fn load_config(config: &str) -> Vec<(AttributeSet<KeyCode>, String)>
.collect()
}
+// Modify the read_keys function to properly block shortcut keys
fn read_keys(kc: &[(AttributeSet<KeyCode>, String)], kbs: PathBuf, delay: u64, user: String)
{
let mut state: Vec<(u64, u64)> = vec![(0, 0); kc.len()];
let mut tick = 1;
let init = (320 + delay - 1) / delay;
+
+ let mut device = match Device::open(&kbs) {
+ Ok(dev) => dev,
+ Err(_) => return, // Device no longer exists, exit thread
+ };
+
+ // Create a virtual device to forward non-shortcut keys
+ // First, get all supported keys from the physical device to set up the virtual device properly
+ let supported_keys = match device.supported_keys() {
+ Some(keys) => keys,
+ None => {
+ eprintln!("Failed to get supported keys from device");
+ return;
+ }
+ };
+
+ let mut virtual_device = match VirtualDevice::builder() {
+ Ok(builder) => builder,
+ Err(e) => {
+ eprintln!("Failed to create virtual device builder: {}", e);
+ return;
+ }
+ };
+
+ virtual_device = match virtual_device
+ .name("pind-virtual-keyboard")
+ .with_keys(&supported_keys)
+ {
+ Ok(builder) => builder,
+ Err(e) => {
+ eprintln!("Failed to set up virtual device with keys: {}", e);
+ return;
+ }
+ };
+
+ let mut virtual_device = match virtual_device.build() {
+ Ok(dev) => dev,
+ Err(e) => {
+ eprintln!("Failed to build virtual device: {}", e);
+ return;
+ }
+ };
+
+ // Grab the physical device to capture all events
+ if let Err(e) = device.grab() {
+ eprintln!("Failed to grab device: {}", e);
+ return;
+ }
loop {
- if let Ok(keys) = Device::open(&kbs).and_then(|d| d.get_key_state()) {
- for (i, (combo, cmd)) in kc.iter().enumerate() {
- let pressed = combo.iter().all(|k| keys.contains(k));
- let (since, last) = &mut state[i];
- if pressed {
- if *since == 0 {
- run(cmd, &user);
- *since = tick;
- *last = tick;
- } else if tick - *since >= init && tick > *last {
- run(cmd, &user);
- *last = tick;
+ // Collect events
+ let events: Vec<_> = match device.fetch_events() {
+ Ok(events_iter) => events_iter.collect(),
+ Err(_) => break, // Device no longer exists
+ };
+
+ let mut events_to_forward = Vec::new();
+ let current_state = match device.get_key_state() {
+ Ok(state) => state,
+ Err(_) => break, // Device no longer exists
+ };
+
+ // Check if any shortcut is currently active
+ let mut active_shortcut = false;
+ for (combo, _) in kc {
+ // Check if all keys in combo are pressed
+ if combo.iter().all(|k| current_state.contains(k)) {
+ active_shortcut = true;
+ break;
+ }
+ }
+
+ for event in events {
+ if event.event_type() == EventType::KEY {
+ let key_code = KeyCode::new(event.code());
+
+ // Check if this key is part of any shortcut
+ let mut is_shortcut_key = false;
+ for (combo, _) in kc {
+ if combo.contains(key_code) {
+ is_shortcut_key = true;
+ break;
+ }
+ }
+
+ if is_shortcut_key && active_shortcut {
+ // This is a shortcut key and we're in an active shortcut combo
+ // Process the shortcut but DON'T forward the key event
+ for (i, (combo, cmd)) in kc.iter().enumerate() {
+ // Check if all keys in combo are pressed
+ let pressed = combo.iter().all(|k| current_state.contains(k));
+
+ let (since, last) = &mut state[i];
+ if pressed {
+ if *since == 0 {
+ run(cmd, &user);
+ *since = tick;
+ *last = tick;
+ } else if tick - *since >= init && tick > *last {
+ run(cmd, &user);
+ *last = tick;
+ }
+ } else {
+ *since = 0;
+ }
}
} else {
- *since = 0;
+ // Forward non-shortcut keys or shortcut keys when not in active combo
+ events_to_forward.push(event);
}
+ } else {
+ // Forward non-key events (like SYN_REPORT, etc.)
+ events_to_forward.push(event);
+ }
+ }
+
+ // Forward non-shortcut events through virtual device
+ if !events_to_forward.is_empty() {
+ if let Err(e) = virtual_device.emit(&events_to_forward) {
+ eprintln!("Failed to emit events: {}", e);
}
}
+
thread::sleep(std::time::Duration::from_millis(delay));
tick = tick.wrapping_add(1);
}
|