rs-uinput-kb 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,533 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "anyhow"
7
+ version = "1.0.102"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
10
+
11
+ [[package]]
12
+ name = "bitflags"
13
+ version = "2.11.1"
14
+ source = "registry+https://github.com/rust-lang/crates.io-index"
15
+ checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
16
+
17
+ [[package]]
18
+ name = "cfg-if"
19
+ version = "1.0.4"
20
+ source = "registry+https://github.com/rust-lang/crates.io-index"
21
+ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
22
+
23
+ [[package]]
24
+ name = "cfg_aliases"
25
+ version = "0.2.1"
26
+ source = "registry+https://github.com/rust-lang/crates.io-index"
27
+ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
28
+
29
+ [[package]]
30
+ name = "chacha20"
31
+ version = "0.10.0"
32
+ source = "registry+https://github.com/rust-lang/crates.io-index"
33
+ checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
34
+ dependencies = [
35
+ "cfg-if",
36
+ "cpufeatures",
37
+ "rand_core",
38
+ ]
39
+
40
+ [[package]]
41
+ name = "cpufeatures"
42
+ version = "0.3.0"
43
+ source = "registry+https://github.com/rust-lang/crates.io-index"
44
+ checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
45
+ dependencies = [
46
+ "libc",
47
+ ]
48
+
49
+ [[package]]
50
+ name = "equivalent"
51
+ version = "1.0.2"
52
+ source = "registry+https://github.com/rust-lang/crates.io-index"
53
+ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
54
+
55
+ [[package]]
56
+ name = "foldhash"
57
+ version = "0.1.5"
58
+ source = "registry+https://github.com/rust-lang/crates.io-index"
59
+ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
60
+
61
+ [[package]]
62
+ name = "getrandom"
63
+ version = "0.4.2"
64
+ source = "registry+https://github.com/rust-lang/crates.io-index"
65
+ checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
66
+ dependencies = [
67
+ "cfg-if",
68
+ "libc",
69
+ "r-efi",
70
+ "rand_core",
71
+ "wasip2",
72
+ "wasip3",
73
+ ]
74
+
75
+ [[package]]
76
+ name = "hashbrown"
77
+ version = "0.15.5"
78
+ source = "registry+https://github.com/rust-lang/crates.io-index"
79
+ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
80
+ dependencies = [
81
+ "foldhash",
82
+ ]
83
+
84
+ [[package]]
85
+ name = "hashbrown"
86
+ version = "0.17.1"
87
+ source = "registry+https://github.com/rust-lang/crates.io-index"
88
+ checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
89
+
90
+ [[package]]
91
+ name = "heck"
92
+ version = "0.5.0"
93
+ source = "registry+https://github.com/rust-lang/crates.io-index"
94
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
95
+
96
+ [[package]]
97
+ name = "id-arena"
98
+ version = "2.3.0"
99
+ source = "registry+https://github.com/rust-lang/crates.io-index"
100
+ checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
101
+
102
+ [[package]]
103
+ name = "indexmap"
104
+ version = "2.14.0"
105
+ source = "registry+https://github.com/rust-lang/crates.io-index"
106
+ checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
107
+ dependencies = [
108
+ "equivalent",
109
+ "hashbrown 0.17.1",
110
+ "serde",
111
+ "serde_core",
112
+ ]
113
+
114
+ [[package]]
115
+ name = "input-linux"
116
+ version = "0.7.1"
117
+ source = "registry+https://github.com/rust-lang/crates.io-index"
118
+ checksum = "b7e8c4821c88b95582ca69234a1d233f87e44182c42e121f740efb0bec1142e0"
119
+ dependencies = [
120
+ "input-linux-sys",
121
+ "nix",
122
+ ]
123
+
124
+ [[package]]
125
+ name = "input-linux-sys"
126
+ version = "0.9.0"
127
+ source = "registry+https://github.com/rust-lang/crates.io-index"
128
+ checksum = "7b91b2248b0eaf0a576ef5e60b7f2107a749e705a876bc0b9fe952ac8d83a724"
129
+ dependencies = [
130
+ "libc",
131
+ "nix",
132
+ ]
133
+
134
+ [[package]]
135
+ name = "itoa"
136
+ version = "1.0.18"
137
+ source = "registry+https://github.com/rust-lang/crates.io-index"
138
+ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
139
+
140
+ [[package]]
141
+ name = "leb128fmt"
142
+ version = "0.1.0"
143
+ source = "registry+https://github.com/rust-lang/crates.io-index"
144
+ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
145
+
146
+ [[package]]
147
+ name = "libc"
148
+ version = "0.2.186"
149
+ source = "registry+https://github.com/rust-lang/crates.io-index"
150
+ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
151
+
152
+ [[package]]
153
+ name = "log"
154
+ version = "0.4.30"
155
+ source = "registry+https://github.com/rust-lang/crates.io-index"
156
+ checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5"
157
+
158
+ [[package]]
159
+ name = "memchr"
160
+ version = "2.8.1"
161
+ source = "registry+https://github.com/rust-lang/crates.io-index"
162
+ checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
163
+
164
+ [[package]]
165
+ name = "nix"
166
+ version = "0.29.0"
167
+ source = "registry+https://github.com/rust-lang/crates.io-index"
168
+ checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
169
+ dependencies = [
170
+ "bitflags",
171
+ "cfg-if",
172
+ "cfg_aliases",
173
+ "libc",
174
+ ]
175
+
176
+ [[package]]
177
+ name = "once_cell"
178
+ version = "1.21.4"
179
+ source = "registry+https://github.com/rust-lang/crates.io-index"
180
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
181
+
182
+ [[package]]
183
+ name = "portable-atomic"
184
+ version = "1.13.1"
185
+ source = "registry+https://github.com/rust-lang/crates.io-index"
186
+ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
187
+
188
+ [[package]]
189
+ name = "prettyplease"
190
+ version = "0.2.37"
191
+ source = "registry+https://github.com/rust-lang/crates.io-index"
192
+ checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
193
+ dependencies = [
194
+ "proc-macro2",
195
+ "syn",
196
+ ]
197
+
198
+ [[package]]
199
+ name = "proc-macro2"
200
+ version = "1.0.106"
201
+ source = "registry+https://github.com/rust-lang/crates.io-index"
202
+ checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
203
+ dependencies = [
204
+ "unicode-ident",
205
+ ]
206
+
207
+ [[package]]
208
+ name = "pyo3"
209
+ version = "0.28.3"
210
+ source = "registry+https://github.com/rust-lang/crates.io-index"
211
+ checksum = "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12"
212
+ dependencies = [
213
+ "libc",
214
+ "once_cell",
215
+ "portable-atomic",
216
+ "pyo3-build-config",
217
+ "pyo3-ffi",
218
+ "pyo3-macros",
219
+ ]
220
+
221
+ [[package]]
222
+ name = "pyo3-build-config"
223
+ version = "0.28.3"
224
+ source = "registry+https://github.com/rust-lang/crates.io-index"
225
+ checksum = "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e"
226
+ dependencies = [
227
+ "target-lexicon",
228
+ ]
229
+
230
+ [[package]]
231
+ name = "pyo3-ffi"
232
+ version = "0.28.3"
233
+ source = "registry+https://github.com/rust-lang/crates.io-index"
234
+ checksum = "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e"
235
+ dependencies = [
236
+ "libc",
237
+ "pyo3-build-config",
238
+ ]
239
+
240
+ [[package]]
241
+ name = "pyo3-macros"
242
+ version = "0.28.3"
243
+ source = "registry+https://github.com/rust-lang/crates.io-index"
244
+ checksum = "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813"
245
+ dependencies = [
246
+ "proc-macro2",
247
+ "pyo3-macros-backend",
248
+ "quote",
249
+ "syn",
250
+ ]
251
+
252
+ [[package]]
253
+ name = "pyo3-macros-backend"
254
+ version = "0.28.3"
255
+ source = "registry+https://github.com/rust-lang/crates.io-index"
256
+ checksum = "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb"
257
+ dependencies = [
258
+ "heck",
259
+ "proc-macro2",
260
+ "pyo3-build-config",
261
+ "quote",
262
+ "syn",
263
+ ]
264
+
265
+ [[package]]
266
+ name = "quote"
267
+ version = "1.0.45"
268
+ source = "registry+https://github.com/rust-lang/crates.io-index"
269
+ checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
270
+ dependencies = [
271
+ "proc-macro2",
272
+ ]
273
+
274
+ [[package]]
275
+ name = "r-efi"
276
+ version = "6.0.0"
277
+ source = "registry+https://github.com/rust-lang/crates.io-index"
278
+ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
279
+
280
+ [[package]]
281
+ name = "rand"
282
+ version = "0.10.1"
283
+ source = "registry+https://github.com/rust-lang/crates.io-index"
284
+ checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
285
+ dependencies = [
286
+ "chacha20",
287
+ "getrandom",
288
+ "rand_core",
289
+ ]
290
+
291
+ [[package]]
292
+ name = "rand_core"
293
+ version = "0.10.1"
294
+ source = "registry+https://github.com/rust-lang/crates.io-index"
295
+ checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
296
+
297
+ [[package]]
298
+ name = "rs_uinput_kb"
299
+ version = "0.1.0"
300
+ dependencies = [
301
+ "input-linux",
302
+ "pyo3",
303
+ "rand",
304
+ ]
305
+
306
+ [[package]]
307
+ name = "semver"
308
+ version = "1.0.28"
309
+ source = "registry+https://github.com/rust-lang/crates.io-index"
310
+ checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
311
+
312
+ [[package]]
313
+ name = "serde"
314
+ version = "1.0.228"
315
+ source = "registry+https://github.com/rust-lang/crates.io-index"
316
+ checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
317
+ dependencies = [
318
+ "serde_core",
319
+ ]
320
+
321
+ [[package]]
322
+ name = "serde_core"
323
+ version = "1.0.228"
324
+ source = "registry+https://github.com/rust-lang/crates.io-index"
325
+ checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
326
+ dependencies = [
327
+ "serde_derive",
328
+ ]
329
+
330
+ [[package]]
331
+ name = "serde_derive"
332
+ version = "1.0.228"
333
+ source = "registry+https://github.com/rust-lang/crates.io-index"
334
+ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
335
+ dependencies = [
336
+ "proc-macro2",
337
+ "quote",
338
+ "syn",
339
+ ]
340
+
341
+ [[package]]
342
+ name = "serde_json"
343
+ version = "1.0.150"
344
+ source = "registry+https://github.com/rust-lang/crates.io-index"
345
+ checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
346
+ dependencies = [
347
+ "itoa",
348
+ "memchr",
349
+ "serde",
350
+ "serde_core",
351
+ "zmij",
352
+ ]
353
+
354
+ [[package]]
355
+ name = "syn"
356
+ version = "2.0.117"
357
+ source = "registry+https://github.com/rust-lang/crates.io-index"
358
+ checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
359
+ dependencies = [
360
+ "proc-macro2",
361
+ "quote",
362
+ "unicode-ident",
363
+ ]
364
+
365
+ [[package]]
366
+ name = "target-lexicon"
367
+ version = "0.13.5"
368
+ source = "registry+https://github.com/rust-lang/crates.io-index"
369
+ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
370
+
371
+ [[package]]
372
+ name = "unicode-ident"
373
+ version = "1.0.24"
374
+ source = "registry+https://github.com/rust-lang/crates.io-index"
375
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
376
+
377
+ [[package]]
378
+ name = "unicode-xid"
379
+ version = "0.2.6"
380
+ source = "registry+https://github.com/rust-lang/crates.io-index"
381
+ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
382
+
383
+ [[package]]
384
+ name = "wasip2"
385
+ version = "1.0.3+wasi-0.2.9"
386
+ source = "registry+https://github.com/rust-lang/crates.io-index"
387
+ checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
388
+ dependencies = [
389
+ "wit-bindgen 0.57.1",
390
+ ]
391
+
392
+ [[package]]
393
+ name = "wasip3"
394
+ version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
395
+ source = "registry+https://github.com/rust-lang/crates.io-index"
396
+ checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
397
+ dependencies = [
398
+ "wit-bindgen 0.51.0",
399
+ ]
400
+
401
+ [[package]]
402
+ name = "wasm-encoder"
403
+ version = "0.244.0"
404
+ source = "registry+https://github.com/rust-lang/crates.io-index"
405
+ checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
406
+ dependencies = [
407
+ "leb128fmt",
408
+ "wasmparser",
409
+ ]
410
+
411
+ [[package]]
412
+ name = "wasm-metadata"
413
+ version = "0.244.0"
414
+ source = "registry+https://github.com/rust-lang/crates.io-index"
415
+ checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
416
+ dependencies = [
417
+ "anyhow",
418
+ "indexmap",
419
+ "wasm-encoder",
420
+ "wasmparser",
421
+ ]
422
+
423
+ [[package]]
424
+ name = "wasmparser"
425
+ version = "0.244.0"
426
+ source = "registry+https://github.com/rust-lang/crates.io-index"
427
+ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
428
+ dependencies = [
429
+ "bitflags",
430
+ "hashbrown 0.15.5",
431
+ "indexmap",
432
+ "semver",
433
+ ]
434
+
435
+ [[package]]
436
+ name = "wit-bindgen"
437
+ version = "0.51.0"
438
+ source = "registry+https://github.com/rust-lang/crates.io-index"
439
+ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
440
+ dependencies = [
441
+ "wit-bindgen-rust-macro",
442
+ ]
443
+
444
+ [[package]]
445
+ name = "wit-bindgen"
446
+ version = "0.57.1"
447
+ source = "registry+https://github.com/rust-lang/crates.io-index"
448
+ checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
449
+
450
+ [[package]]
451
+ name = "wit-bindgen-core"
452
+ version = "0.51.0"
453
+ source = "registry+https://github.com/rust-lang/crates.io-index"
454
+ checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
455
+ dependencies = [
456
+ "anyhow",
457
+ "heck",
458
+ "wit-parser",
459
+ ]
460
+
461
+ [[package]]
462
+ name = "wit-bindgen-rust"
463
+ version = "0.51.0"
464
+ source = "registry+https://github.com/rust-lang/crates.io-index"
465
+ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
466
+ dependencies = [
467
+ "anyhow",
468
+ "heck",
469
+ "indexmap",
470
+ "prettyplease",
471
+ "syn",
472
+ "wasm-metadata",
473
+ "wit-bindgen-core",
474
+ "wit-component",
475
+ ]
476
+
477
+ [[package]]
478
+ name = "wit-bindgen-rust-macro"
479
+ version = "0.51.0"
480
+ source = "registry+https://github.com/rust-lang/crates.io-index"
481
+ checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
482
+ dependencies = [
483
+ "anyhow",
484
+ "prettyplease",
485
+ "proc-macro2",
486
+ "quote",
487
+ "syn",
488
+ "wit-bindgen-core",
489
+ "wit-bindgen-rust",
490
+ ]
491
+
492
+ [[package]]
493
+ name = "wit-component"
494
+ version = "0.244.0"
495
+ source = "registry+https://github.com/rust-lang/crates.io-index"
496
+ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
497
+ dependencies = [
498
+ "anyhow",
499
+ "bitflags",
500
+ "indexmap",
501
+ "log",
502
+ "serde",
503
+ "serde_derive",
504
+ "serde_json",
505
+ "wasm-encoder",
506
+ "wasm-metadata",
507
+ "wasmparser",
508
+ "wit-parser",
509
+ ]
510
+
511
+ [[package]]
512
+ name = "wit-parser"
513
+ version = "0.244.0"
514
+ source = "registry+https://github.com/rust-lang/crates.io-index"
515
+ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
516
+ dependencies = [
517
+ "anyhow",
518
+ "id-arena",
519
+ "indexmap",
520
+ "log",
521
+ "semver",
522
+ "serde",
523
+ "serde_derive",
524
+ "serde_json",
525
+ "unicode-xid",
526
+ "wasmparser",
527
+ ]
528
+
529
+ [[package]]
530
+ name = "zmij"
531
+ version = "1.0.21"
532
+ source = "registry+https://github.com/rust-lang/crates.io-index"
533
+ checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
@@ -0,0 +1,17 @@
1
+ [package]
2
+ name = "rs_uinput_kb"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ description = "A Python wrapper for Linux /dev/uinput keystroke simulation"
6
+ license = "MIT"
7
+ readme = "README.md"
8
+ repository = "https://github.com/Dwarakesh-V/rs_uinput_kb"
9
+
10
+ [lib]
11
+ name = "rs_uinput_kb"
12
+ crate-type = ["cdylib"]
13
+
14
+ [dependencies]
15
+ pyo3 = { version = "0.28.3", features = ["extension-module"] }
16
+ rand = "0.10.1"
17
+ input-linux = "0.7.1"
@@ -0,0 +1,91 @@
1
+ Metadata-Version: 2.4
2
+ Name: rs_uinput_kb
3
+ Version: 0.1.0
4
+ Classifier: Programming Language :: Rust
5
+ Classifier: Programming Language :: Python :: Implementation :: CPython
6
+ Classifier: Operating System :: POSIX :: Linux
7
+ Summary: A Python wrapper for Linux /dev/uinput keystroke simulation
8
+ Requires-Python: >=3.8
9
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
10
+
11
+ # rs_uinput
12
+
13
+ `rs_uinput` is a Python library backed by Rust that types text through Linux's `uinput` subsystem.
14
+
15
+ Instead of injecting keystrokes into a specific application, it creates a virtual keyboard device and sends real keyboard events through the Linux input stack.
16
+
17
+ ## Example
18
+
19
+ ```python
20
+ import rs_uinput
21
+
22
+ rs_uinput.type_text(
23
+ text="Hello, world!",
24
+ min_delay=0.05,
25
+ max_delay=0.15
26
+ )
27
+ ```
28
+
29
+ ## How It Works
30
+
31
+ 1. Python calls the Rust extension through PyO3.
32
+ 2. Rust creates a virtual keyboard using `/dev/uinput`.
33
+ 3. Each character is converted into the corresponding Linux key code.
34
+ 4. Key press and key release events are sent through the kernel.
35
+ 5. A random delay is applied between characters.
36
+
37
+ ## How It Differs From pynput
38
+
39
+ `pynput` typically relies on higher-level desktop APIs (X11, Wayland compatibility layers, platform-specific event injection APIs, etc.).
40
+
41
+ `rs_uinput` operates at the Linux input-device layer by creating a virtual keyboard device.
42
+
43
+ ```text
44
+ pynput
45
+
46
+ Desktop API
47
+
48
+ Application
49
+
50
+ rs_uinput
51
+
52
+ Virtual Keyboard Device
53
+
54
+ Linux Input Subsystem
55
+
56
+ Application
57
+ ```
58
+
59
+ Because it acts as an input device, applications receive the events through the normal Linux input pipeline rather than through a specific GUI automation API.
60
+
61
+ ## How It Differs From Selenium
62
+
63
+ Selenium does not generate system-wide keyboard input.
64
+
65
+ Instead, Selenium controls a web browser through the browser's automation interface:
66
+
67
+ ```text
68
+ Selenium
69
+
70
+ Browser Driver
71
+
72
+ Browser
73
+ ```
74
+
75
+ This means Selenium can only interact with browser content that it controls.
76
+
77
+ `rs_uinput` generates keyboard events at the operating-system level, allowing text to be entered into any focused application that accepts keyboard input.
78
+
79
+ ## Use Cases
80
+
81
+ - Desktop automation
82
+ - Automated testing
83
+ - Kiosk systems
84
+ - Accessibility tools
85
+ - Input simulation on Linux
86
+
87
+ ## Requirements
88
+
89
+ - Linux
90
+ - Kernel `uinput` support
91
+ - Access to `/dev/uinput`
@@ -0,0 +1,81 @@
1
+ # rs_uinput
2
+
3
+ `rs_uinput` is a Python library backed by Rust that types text through Linux's `uinput` subsystem.
4
+
5
+ Instead of injecting keystrokes into a specific application, it creates a virtual keyboard device and sends real keyboard events through the Linux input stack.
6
+
7
+ ## Example
8
+
9
+ ```python
10
+ import rs_uinput
11
+
12
+ rs_uinput.type_text(
13
+ text="Hello, world!",
14
+ min_delay=0.05,
15
+ max_delay=0.15
16
+ )
17
+ ```
18
+
19
+ ## How It Works
20
+
21
+ 1. Python calls the Rust extension through PyO3.
22
+ 2. Rust creates a virtual keyboard using `/dev/uinput`.
23
+ 3. Each character is converted into the corresponding Linux key code.
24
+ 4. Key press and key release events are sent through the kernel.
25
+ 5. A random delay is applied between characters.
26
+
27
+ ## How It Differs From pynput
28
+
29
+ `pynput` typically relies on higher-level desktop APIs (X11, Wayland compatibility layers, platform-specific event injection APIs, etc.).
30
+
31
+ `rs_uinput` operates at the Linux input-device layer by creating a virtual keyboard device.
32
+
33
+ ```text
34
+ pynput
35
+
36
+ Desktop API
37
+
38
+ Application
39
+
40
+ rs_uinput
41
+
42
+ Virtual Keyboard Device
43
+
44
+ Linux Input Subsystem
45
+
46
+ Application
47
+ ```
48
+
49
+ Because it acts as an input device, applications receive the events through the normal Linux input pipeline rather than through a specific GUI automation API.
50
+
51
+ ## How It Differs From Selenium
52
+
53
+ Selenium does not generate system-wide keyboard input.
54
+
55
+ Instead, Selenium controls a web browser through the browser's automation interface:
56
+
57
+ ```text
58
+ Selenium
59
+
60
+ Browser Driver
61
+
62
+ Browser
63
+ ```
64
+
65
+ This means Selenium can only interact with browser content that it controls.
66
+
67
+ `rs_uinput` generates keyboard events at the operating-system level, allowing text to be entered into any focused application that accepts keyboard input.
68
+
69
+ ## Use Cases
70
+
71
+ - Desktop automation
72
+ - Automated testing
73
+ - Kiosk systems
74
+ - Accessibility tools
75
+ - Input simulation on Linux
76
+
77
+ ## Requirements
78
+
79
+ - Linux
80
+ - Kernel `uinput` support
81
+ - Access to `/dev/uinput`
@@ -0,0 +1,18 @@
1
+ [build-system]
2
+ requires = ["maturin>=1.0,<2.0"]
3
+ build-backend = "maturin"
4
+
5
+ [project]
6
+ name = "rs_uinput_kb"
7
+ description = "A Python wrapper for Linux /dev/uinput keystroke simulation"
8
+ readme = "README.md"
9
+ requires-python = ">=3.8"
10
+ classifiers = [
11
+ "Programming Language :: Rust",
12
+ "Programming Language :: Python :: Implementation :: CPython",
13
+ "Operating System :: POSIX :: Linux",
14
+ ]
15
+
16
+ [tool.maturin]
17
+ features = ["pyo3/extension-module"]
18
+ module-name = "rs_uinput_kb.rs_uinput_kb"
@@ -0,0 +1 @@
1
+ from .rs_uinput_kb import *
@@ -0,0 +1,25 @@
1
+ def type_text(
2
+ text: str,
3
+ min_char_delay: float = 0.05,
4
+ max_char_delay: float = 0.1,
5
+ min_dwell_time: int = 20,
6
+ max_dwell_time: int = 60,
7
+ min_shift_delay: int = 10,
8
+ max_shift_delay: int = 30
9
+ ) -> None:
10
+ """
11
+ Simulates hardware keystrokes using Linux's /dev/uinput.
12
+
13
+ By default, this uses jittered delays to mimic natural human typing dynamics.
14
+ For instantaneous machine-speed typing, set all delays to 0.
15
+
16
+ Args:
17
+ text (str): The string of characters to type.
18
+ min_char_delay (float): Minimum delay between keystrokes in seconds. Default 0.05.
19
+ max_char_delay (float): Maximum delay between keystrokes in seconds. Default 0.1.
20
+ min_dwell_time (int): Minimum time a key is physically held down in milliseconds. Default 20.
21
+ max_dwell_time (int): Maximum time a key is physically held down in milliseconds. Default 60.
22
+ min_shift_delay (int): Minimum hesitation before/after pressing shift in milliseconds. Default 10.
23
+ max_shift_delay (int): Maximum hesitation before/after pressing shift in milliseconds. Default 30.
24
+ """
25
+ ...
@@ -0,0 +1,328 @@
1
+ use input_linux::{EventKind, Key, UInputHandle};
2
+ use pyo3::prelude::*;
3
+ use rand::RngExt;
4
+ use std::thread::sleep;
5
+ use std::time::Duration;
6
+
7
+ struct TypingDelays {
8
+ min_dwell: u64,
9
+ max_dwell: u64,
10
+ min_shift: u64,
11
+ max_shift: u64,
12
+ }
13
+
14
+ // Safely generate an integer delay (prevents panic if min == max or if min > max)
15
+ fn get_delay(min: u64, max: u64) -> u64 {
16
+ let low = min.min(max);
17
+ let high = min.max(max);
18
+ if high == 0 {
19
+ 0
20
+ } else if low == high {
21
+ low
22
+ } else {
23
+ rand::rng().random_range(low..=high)
24
+ }
25
+ }
26
+
27
+ // Safely generate a float delay
28
+ fn get_char_delay(min: f64, max: f64) -> f64 {
29
+ let low = min.min(max);
30
+ let high = min.max(max);
31
+ if high <= 0.0 {
32
+ 0.0
33
+ } else if low == high {
34
+ low
35
+ } else {
36
+ rand::rng().random_range(low..=high)
37
+ }
38
+ }
39
+
40
+ fn write_event(
41
+ ui: &UInputHandle<std::fs::File>,
42
+ type_: u16,
43
+ code: u16,
44
+ value: i32,
45
+ ) -> Result<(), Box<dyn std::error::Error>> {
46
+ let raw = input_linux::sys::input_event {
47
+ time: input_linux::sys::timeval {
48
+ tv_sec: 0,
49
+ tv_usec: 0,
50
+ },
51
+ type_,
52
+ code,
53
+ value,
54
+ };
55
+ ui.write(&[raw])?;
56
+ Ok(())
57
+ }
58
+
59
+ fn write_sync_event(ui: &UInputHandle<std::fs::File>) -> Result<(), Box<dyn std::error::Error>> {
60
+ write_event(
61
+ ui,
62
+ input_linux::sys::EV_SYN as u16,
63
+ input_linux::sys::SYN_REPORT as u16,
64
+ 0,
65
+ )
66
+ }
67
+
68
+ fn send_key(
69
+ ui: &UInputHandle<std::fs::File>,
70
+ key: Key,
71
+ delays: &TypingDelays,
72
+ ) -> Result<(), Box<dyn std::error::Error>> {
73
+ write_event(ui, input_linux::sys::EV_KEY as u16, key.code(), 1)?;
74
+ write_sync_event(ui)?;
75
+
76
+ let dwell_time = get_delay(delays.min_dwell, delays.max_dwell);
77
+ if dwell_time > 0 {
78
+ sleep(Duration::from_millis(dwell_time));
79
+ }
80
+
81
+ write_event(ui, input_linux::sys::EV_KEY as u16, key.code(), 0)?;
82
+ write_sync_event(ui)?;
83
+
84
+ Ok(())
85
+ }
86
+
87
+ fn send_shifted_key(
88
+ ui: &UInputHandle<std::fs::File>,
89
+ key: Key,
90
+ delays: &TypingDelays,
91
+ ) -> Result<(), Box<dyn std::error::Error>> {
92
+ write_event(ui, input_linux::sys::EV_KEY as u16, Key::LeftShift.code(), 1)?;
93
+ write_sync_event(ui)?;
94
+
95
+ let shift_delay = get_delay(delays.min_shift, delays.max_shift);
96
+ if shift_delay > 0 {
97
+ sleep(Duration::from_millis(shift_delay));
98
+ }
99
+
100
+ write_event(ui, input_linux::sys::EV_KEY as u16, key.code(), 1)?;
101
+ write_sync_event(ui)?;
102
+
103
+ let dwell_time = get_delay(delays.min_dwell, delays.max_dwell);
104
+ if dwell_time > 0 {
105
+ sleep(Duration::from_millis(dwell_time));
106
+ }
107
+
108
+ write_event(ui, input_linux::sys::EV_KEY as u16, key.code(), 0)?;
109
+ write_sync_event(ui)?;
110
+
111
+ let unshift_delay = get_delay(delays.min_shift, delays.max_shift);
112
+ if unshift_delay > 0 {
113
+ sleep(Duration::from_millis(unshift_delay));
114
+ }
115
+
116
+ write_event(ui, input_linux::sys::EV_KEY as u16, Key::LeftShift.code(), 0)?;
117
+ write_sync_event(ui)?;
118
+
119
+ Ok(())
120
+ }
121
+
122
+ fn char_to_key(c: char) -> Option<(Key, bool)> {
123
+ use Key::*;
124
+ Some(match c {
125
+ 'a' => (A, false),
126
+ 'b' => (B, false),
127
+ 'c' => (C, false),
128
+ 'd' => (D, false),
129
+ 'e' => (E, false),
130
+ 'f' => (F, false),
131
+ 'g' => (G, false),
132
+ 'h' => (H, false),
133
+ 'i' => (I, false),
134
+ 'j' => (J, false),
135
+ 'k' => (K, false),
136
+ 'l' => (L, false),
137
+ 'm' => (M, false),
138
+ 'n' => (N, false),
139
+ 'o' => (O, false),
140
+ 'p' => (P, false),
141
+ 'q' => (Q, false),
142
+ 'r' => (R, false),
143
+ 's' => (S, false),
144
+ 't' => (T, false),
145
+ 'u' => (U, false),
146
+ 'v' => (V, false),
147
+ 'w' => (W, false),
148
+ 'x' => (X, false),
149
+ 'y' => (Y, false),
150
+ 'z' => (Z, false),
151
+
152
+ 'A' => (A, true),
153
+ 'B' => (B, true),
154
+ 'C' => (C, true),
155
+ 'D' => (D, true),
156
+ 'E' => (E, true),
157
+ 'F' => (F, true),
158
+ 'G' => (G, true),
159
+ 'H' => (H, true),
160
+ 'I' => (I, true),
161
+ 'J' => (J, true),
162
+ 'K' => (K, true),
163
+ 'L' => (L, true),
164
+ 'M' => (M, true),
165
+ 'N' => (N, true),
166
+ 'O' => (O, true),
167
+ 'P' => (P, true),
168
+ 'Q' => (Q, true),
169
+ 'R' => (R, true),
170
+ 'S' => (S, true),
171
+ 'T' => (T, true),
172
+ 'U' => (U, true),
173
+ 'V' => (V, true),
174
+ 'W' => (W, true),
175
+ 'X' => (X, true),
176
+ 'Y' => (Y, true),
177
+ 'Z' => (Z, true),
178
+
179
+ '0' => (Num0, false),
180
+ '1' => (Num1, false),
181
+ '2' => (Num2, false),
182
+ '3' => (Num3, false),
183
+ '4' => (Num4, false),
184
+ '5' => (Num5, false),
185
+ '6' => (Num6, false),
186
+ '7' => (Num7, false),
187
+ '8' => (Num8, false),
188
+ '9' => (Num9, false),
189
+
190
+ '!' => (Num1, true),
191
+ '@' => (Num2, true),
192
+ '#' => (Num3, true),
193
+ '$' => (Num4, true),
194
+ '%' => (Num5, true),
195
+ '^' => (Num6, true),
196
+ '&' => (Num7, true),
197
+ '*' => (Num8, true),
198
+ '(' => (Num9, true),
199
+ ')' => (Num0, true),
200
+
201
+ ' ' => (Space, false),
202
+ '-' => (Minus, false),
203
+ '_' => (Minus, true),
204
+ '=' => (Equal, false),
205
+ '+' => (Equal, true),
206
+
207
+ '[' => (LeftBrace, false),
208
+ '{' => (LeftBrace, true),
209
+ ']' => (RightBrace, false),
210
+ '}' => (RightBrace, true),
211
+
212
+ ';' => (Semicolon, false),
213
+ ':' => (Semicolon, true),
214
+ '\'' => (Apostrophe, false),
215
+ '"' => (Apostrophe, true),
216
+
217
+ ',' => (Comma, false),
218
+ '<' => (Comma, true),
219
+ '.' => (Dot, false),
220
+ '>' => (Dot, true),
221
+ '/' => (Slash, false),
222
+ '?' => (Slash, true),
223
+
224
+ '\\' => (Backslash, false),
225
+ '|' => (Backslash, true),
226
+ '`' => (Grave, false),
227
+ '~' => (Grave, true),
228
+
229
+ '\n' => (Enter, false),
230
+
231
+ _ => return None,
232
+ })
233
+ }
234
+
235
+ /// Simulates hardware keystrokes using Linux's /dev/uinput.
236
+ ///
237
+ /// By default, this uses jittered delays to mimic natural human typing dynamics.
238
+ /// For instantaneous machine-speed typing, set all delays to 0.
239
+ ///
240
+ /// Args:
241
+ /// text (str): The string of characters to type.
242
+ /// min_char_delay (float): Minimum delay between keystrokes in seconds. Default 0.05.
243
+ /// max_char_delay (float): Maximum delay between keystrokes in seconds. Default 0.1.
244
+ /// min_dwell_time (int): Minimum time a key is physically held down in milliseconds. Default 20.
245
+ /// max_dwell_time (int): Maximum time a key is physically held down in milliseconds. Default 60.
246
+ /// min_shift_delay (int): Minimum hesitation before/after pressing shift in milliseconds. Default 10.
247
+ /// max_shift_delay (int): Maximum hesitation before/after pressing shift in milliseconds. Default 30.
248
+ #[pyfunction]
249
+ #[pyo3(signature = (
250
+ text,
251
+ min_char_delay=0.05,
252
+ max_char_delay=0.1,
253
+ min_dwell_time=20,
254
+ max_dwell_time=60,
255
+ min_shift_delay=10,
256
+ max_shift_delay=30
257
+ ))]
258
+ fn type_text(
259
+ text: String,
260
+ min_char_delay: f64,
261
+ max_char_delay: f64,
262
+ min_dwell_time: u64,
263
+ max_dwell_time: u64,
264
+ min_shift_delay: u64,
265
+ max_shift_delay: u64,
266
+ ) -> PyResult<()> {
267
+ let file = std::fs::OpenOptions::new()
268
+ .read(true)
269
+ .write(true)
270
+ .open("/dev/uinput")
271
+ .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
272
+
273
+ let ui = UInputHandle::new(file);
274
+
275
+ ui.set_evbit(EventKind::Key)
276
+ .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
277
+
278
+ for key in Key::iter() {
279
+ let _ = ui.set_keybit(key);
280
+ }
281
+
282
+ let input_id_raw = input_linux::sys::input_id {
283
+ bustype: input_linux::sys::BUS_USB as u16,
284
+ vendor: 0x1234,
285
+ product: 0x5678,
286
+ version: 0,
287
+ };
288
+
289
+ let input_id: input_linux::InputId = unsafe { std::mem::transmute(input_id_raw) };
290
+
291
+ ui.create(&input_id, b"rs_uinput_kb\0", 0, &[])
292
+ .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
293
+
294
+ // The OS needs a fraction of a second to register the virtual device
295
+ sleep(Duration::from_millis(500));
296
+
297
+ let delays = TypingDelays {
298
+ min_dwell: min_dwell_time,
299
+ max_dwell: max_dwell_time,
300
+ min_shift: min_shift_delay,
301
+ max_shift: max_shift_delay,
302
+ };
303
+
304
+ for ch in text.chars() {
305
+ if let Some((key, shifted)) = char_to_key(ch) {
306
+ if shifted {
307
+ send_shifted_key(&ui, key, &delays)
308
+ .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
309
+ } else {
310
+ send_key(&ui, key, &delays)
311
+ .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
312
+ }
313
+
314
+ let char_delay = get_char_delay(min_char_delay, max_char_delay);
315
+ if char_delay > 0.0 {
316
+ sleep(Duration::from_secs_f64(char_delay));
317
+ }
318
+ }
319
+ }
320
+
321
+ Ok(())
322
+ }
323
+
324
+ #[pymodule]
325
+ fn rs_uinput_kb(m: &Bound<'_, PyModule>) -> PyResult<()> {
326
+ m.add_function(wrap_pyfunction!(type_text, m)?)?;
327
+ Ok(())
328
+ }