From 7f2dac5a615c09f9fe3596555900e70d50cc6068 Mon Sep 17 00:00:00 2001 From: Niverton Date: Sun, 18 Aug 2024 17:43:59 +0200 Subject: [PATCH] feat(teipe): initial commit --- .gitignore | 1 + .vscode/launch.json | 24 + Cargo.lock | 1682 +++++++++++++++++++++++++++++++++ Cargo.toml | 36 + libeirs/Cargo.toml | 16 + libeirs/build.rs | 25 + libeirs/src/device.rs | 129 +++ libeirs/src/eirc.rs | 107 +++ libeirs/src/events.rs | 47 + libeirs/src/ffi.rs | 49 + libeirs/src/keymap.rs | 49 + libeirs/src/lib.rs | 13 + libeirs/src/seat.rs | 41 + libeirs/src/sender.rs | 23 + libxkbcommonrs/.clangd | 3 + libxkbcommonrs/Cargo.toml | 15 + libxkbcommonrs/build.rs | 209 ++++ libxkbcommonrs/ffi/logger.c | 17 + libxkbcommonrs/ffi/logger.h | 11 + libxkbcommonrs/src/context.rs | 65 ++ libxkbcommonrs/src/error.rs | 15 + libxkbcommonrs/src/ffi.rs | 61 ++ libxkbcommonrs/src/keymap.rs | 133 +++ libxkbcommonrs/src/layout.rs | 33 + libxkbcommonrs/src/lib.rs | 11 + src/app.rs | 73 ++ src/ei.rs | 309 ++++++ src/keyboard.rs | 142 +++ src/main.rs | 11 + src/portal.rs | 40 + 30 files changed, 3390 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 libeirs/Cargo.toml create mode 100644 libeirs/build.rs create mode 100644 libeirs/src/device.rs create mode 100644 libeirs/src/eirc.rs create mode 100644 libeirs/src/events.rs create mode 100644 libeirs/src/ffi.rs create mode 100644 libeirs/src/keymap.rs create mode 100644 libeirs/src/lib.rs create mode 100644 libeirs/src/seat.rs create mode 100644 libeirs/src/sender.rs create mode 100644 libxkbcommonrs/.clangd create mode 100644 libxkbcommonrs/Cargo.toml create mode 100644 libxkbcommonrs/build.rs create mode 100644 libxkbcommonrs/ffi/logger.c create mode 100644 libxkbcommonrs/ffi/logger.h create mode 100644 libxkbcommonrs/src/context.rs create mode 100644 libxkbcommonrs/src/error.rs create mode 100644 libxkbcommonrs/src/ffi.rs create mode 100644 libxkbcommonrs/src/keymap.rs create mode 100644 libxkbcommonrs/src/layout.rs create mode 100644 libxkbcommonrs/src/lib.rs create mode 100644 src/app.rs create mode 100644 src/ei.rs create mode 100644 src/keyboard.rs create mode 100644 src/main.rs create mode 100644 src/portal.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..fb57346 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,24 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "codelldb", + "request": "launch", + "name": "Debug teipe", + "program": "target/debug/teipe", + "cargo": { + "args": [ + "build", + "--bin=teipe", + "--package=teipe" + ], + "filter": { + "name": "teipe", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..6ee3bc2 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1682 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "annotate-snippets" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccaf7e9dfbb6ab22c82e473cd1a8a7bd313c19a5b7e40970f3d89ef5a5c9e81e" +dependencies = [ + "unicode-width", + "yansi-term", +] + +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "ashpd" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe7e0dd0ac5a401dc116ed9f9119cf9decc625600474cb41f0fc0a0050abc9a" +dependencies = [ + "async-fs", + "async-net", + "enumflags2", + "futures-channel", + "futures-util", + "rand", + "serde", + "serde_repr", + "url", + "zbus", +] + +[[package]] +name = "async-broadcast" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "tracing", + "windows-sys", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-process" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7eda79bbd84e29c2b308d1dc099d7de8dcc7035e48f4bf5dc4a531a44ff5e2a" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", + "tracing", + "windows-sys", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-signal" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfb3634b73397aa844481f814fad23bbf07fdb0eabec10f2eb95e58944b1ec32" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "backtrace" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bindgen" +version = "0.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0127a1da21afb5adaae26910922c3f7afd3d329ba1a1b98a0884cab4907a251" +dependencies = [ + "annotate-snippets", + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" + +[[package]] +name = "cc" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + +[[package]] +name = "enumflags2" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "env_filter" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-lite" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "libeirs" +version = "0.1.0" +dependencies = [ + "anyhow", + "bindgen", + "log", + "memmap2", + "nix", + "tokio", + "tokio-stream", +] + +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets", +] + +[[package]] +name = "libxkbcommonrs" +version = "0.1.0" +dependencies = [ + "anyhow", + "bindgen", + "cc", + "convert_case", + "log", + "tokio", + "tokio-stream", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memmap2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "object" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1d5c74c9876f070d3e8fd503d748c7d974c3e48da8f41350fa5222ef9b4391" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "polling" +version = "3.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.4.0", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "serde" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "teipe" +version = "0.1.0" +dependencies = [ + "anyhow", + "ashpd", + "clap", + "env_logger", + "libeirs", + "libxkbcommonrs", + "log", + "polling", + "tokio", + "tokio-stream", + "tokio-util", + "widestring", +] + +[[package]] +name = "tempfile" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fcd239983515c23a32fb82099f97d0b11b8c72f654ed659363a95c3dad7a53" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.39.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" +dependencies = [ + "backtrace", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-stream" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "widestring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "xdg-home" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca91dcf8f93db085f3a0a29358cd0b9d670915468f4290e8b85d118a34211ab8" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "yansi-term" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5c30ade05e61656247b2e334a031dfd0cc466fadef865bdcdea8d537951bf1" +dependencies = [ + "winapi", +] + +[[package]] +name = "zbus" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix", + "ordered-stream", + "rand", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "windows-sys", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zvariant" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" +dependencies = [ + "endi", + "enumflags2", + "serde", + "static_assertions", + "url", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..dac87f9 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,36 @@ +[workspace] +members = ["libeirs", "libxkbcommonrs"] + +[workspace.dependencies] +tokio = { version = "1.39", features = [ + "rt", + "macros", + "rt-multi-thread", + "net", +] } +tokio-stream = "0.1" +log = { version = "0.4", features = ["kv"] } +anyhow = "1.0" +bindgen = "0.70" + + +[package] +name = "teipe" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +libeirs = { path = "libeirs" } +libxkbcommonrs = { path = "libxkbcommonrs" } +ashpd = "0.9" +env_logger = { version = "0.11", features = ["unstable-kv"] } +tokio = { workspace = true, features = ["rt", "net"] } +tokio-stream = { workspace = true } +tokio-util = "0.7" +log = { workspace = true, features = ["kv"] } +anyhow = { workspace = true } +widestring = "1.1.0" +polling = "3.7" +clap = { version = "4.5.16", features = ["derive"] } diff --git a/libeirs/Cargo.toml b/libeirs/Cargo.toml new file mode 100644 index 0000000..52e0c26 --- /dev/null +++ b/libeirs/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "libeirs" +version = "0.1.0" +edition = "2021" + +[build-dependencies] +bindgen.workspace = true +anyhow.workspace = true + +[dependencies] +tokio = { workspace = true, features = ["rt", "net"] } +tokio-stream = { workspace = true } +log = { workspace = true, features = ["kv"] } +memmap2 = "0.9.4" +nix = { version = "0.29", features = ["poll"] } +anyhow.workspace = true diff --git a/libeirs/build.rs b/libeirs/build.rs new file mode 100644 index 0000000..147f919 --- /dev/null +++ b/libeirs/build.rs @@ -0,0 +1,25 @@ +use std::{env, path::PathBuf}; + +fn main() -> anyhow::Result<()> { + let bindings = bindgen::builder() + .header("/usr/include/libei-1.0/libei.h") + .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) + .allowlist_item("ei_.*") + .default_enum_style(bindgen::EnumVariation::NewType { + is_bitfield: false, + is_global: false, + }) + .no_debug("ei_event_type") // Implemented manually to use Display + .bitfield_enum("ei_device_capability") + .generate()?; + + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + let out_file = out_path.join("libei.rs"); + bindings + .write_to_file(out_file) + .expect("Couldn't write bindings!"); + + println!("cargo:rustc-link-lib=ei"); + + Ok(()) +} diff --git a/libeirs/src/device.rs b/libeirs/src/device.rs new file mode 100644 index 0000000..de030d2 --- /dev/null +++ b/libeirs/src/device.rs @@ -0,0 +1,129 @@ +use std::{borrow::Borrow, ffi::CStr, fmt::Display, ops::Deref, ptr::NonNull}; + +use crate::{eirc::Ei, ffi::ei_device}; + +use super::{ffi, keymap::Keymap}; + +#[derive(PartialEq, Eq)] +pub struct Device { + device: DeviceRef, +} + +impl Drop for Device { + fn drop(&mut self) { + log::trace!("Device dropped"); + let device = self.as_raw(); + unsafe { + ffi::ei_device_close(device); + ffi::ei_device_unref(device) + }; + } +} + +impl Deref for Device { + type Target = DeviceRef; + + fn deref(&self) -> &Self::Target { + &self.device + } +} + +impl Clone for Device { + fn clone(&self) -> Self { + self.device.to_owned() + } +} + +impl Borrow for Device { + fn borrow(&self) -> &DeviceRef { + &self.device + } +} + +impl AsRef for Device { + fn as_ref(&self) -> &DeviceRef { + &self.device + } +} + +impl Display for Device { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.device) + } +} + +impl PartialEq for Device { + fn eq(&self, other: &DeviceRef) -> bool { + self.device == *other + } +} + +#[derive(PartialEq, Eq)] +pub struct DeviceRef { + device: NonNull, +} + +impl DeviceRef { + pub(crate) fn new(device: *mut ei_device) -> Option { + NonNull::new(device).map(|device| Self { device }) + } + pub fn start_emulating(&self, sequence: u32) { + unsafe { ffi::ei_device_start_emulating(self.as_raw(), sequence) } + } + + pub fn keyboard_key(&self, key: u32, pressed: bool) { + unsafe { + ffi::ei_device_keyboard_key(self.as_raw(), key, pressed); + } + } + + pub fn frame(&self, time: Option) { + let time = time.unwrap_or_else(|| unsafe { + let ei = ffi::ei_device_get_context(self.as_raw()); + ffi::ei_now(ei) + }); + unsafe { + ffi::ei_device_frame(self.as_raw(), time); + } + } + + pub(crate) fn as_raw(&self) -> *mut ffi::ei_device { + self.device.as_ptr() + } + + pub fn get_keymap(&self) -> Option { + Keymap::from_device(self) + } + + pub fn get_context(&self) -> Ei { + unsafe { Ei::from_raw(ffi::ei_device_get_context(self.as_raw())) } + } +} + +impl Display for DeviceRef { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let str = unsafe { CStr::from_ptr(ffi::ei_device_get_name(self.as_raw())) } + .to_str() + .unwrap(); + write!(f, "{str}",) + } +} + +impl ToOwned for DeviceRef { + type Owned = Device; + + fn to_owned(&self) -> Self::Owned { + let ptr = unsafe { ffi::ei_device_ref(self.as_raw()) }; + Device { + device: DeviceRef { + device: NonNull::new(ptr).unwrap(), + }, + } + } +} + +impl PartialEq for DeviceRef { + fn eq(&self, other: &Device) -> bool { + *other == *self + } +} diff --git a/libeirs/src/eirc.rs b/libeirs/src/eirc.rs new file mode 100644 index 0000000..267559d --- /dev/null +++ b/libeirs/src/eirc.rs @@ -0,0 +1,107 @@ +use std::{ + os::fd::{BorrowedFd, IntoRawFd, OwnedFd}, + ptr::NonNull, +}; + +use super::{events::Event, ffi}; + +const CLIENT_NAME: &std::ffi::CStr = c"teipe"; + +pub struct Ei { + ei: *mut ffi::ei, +} + +unsafe extern "C" fn null_handler( + _ei: *mut ffi::ei, + _priority: ffi::ei_log_priority, + _message: *const ::std::os::raw::c_char, + _context: *mut ffi::ei_log_context, +) { + // no op +} + +unsafe extern "C" fn log_handler( + _ei: *mut ffi::ei, + priority: ffi::ei_log_priority, + message: *const ::std::os::raw::c_char, + ctx: *mut ffi::ei_log_context, +) { + let (file, func, line, msg) = unsafe { + ( + std::ffi::CStr::from_ptr(ffi::ei_log_context_get_file(ctx)), + std::ffi::CStr::from_ptr(ffi::ei_log_context_get_func(ctx)), + ffi::ei_log_context_get_line(ctx), + std::ffi::CStr::from_ptr(message), + ) + }; + log::log!( + target:"libei", + priority.into(), + file:?, func:?, line:?; + "{msg:?}" + ); +} + +impl Ei { + pub(crate) fn new_sender(fd: OwnedFd) -> Self { + // FIX: handle errors + log::info!("Creating ei"); + let ei = unsafe { ffi::ei_new_sender(std::ptr::null_mut()) }; + + unsafe { + if let Some(level) = log::max_level().to_level() { + ffi::ei_log_set_handler(ei, Some(log_handler)); + ffi::ei_log_set_priority(ei, level.into()); + } else { + ffi::ei_log_set_handler(ei, Some(null_handler)); + } + ffi::ei_configure_name(ei, CLIENT_NAME.as_ptr()); + ffi::ei_setup_backend_fd(ei, fd.into_raw_fd()); + } + Self { ei } + } + + pub(crate) unsafe fn from_raw(ei: *mut ffi::ei) -> Self { + unsafe { + ffi::ei_ref(ei); + } + Self { ei } + } + + pub fn get_fd(&self) -> BorrowedFd { + unsafe { + let fd = ffi::ei_get_fd(self.ei); + BorrowedFd::borrow_raw(fd) + } + } + + pub fn dispatch(&self) { + log::trace!("Dispatch"); + unsafe { + ffi::ei_dispatch(self.ei); + } + } + + pub fn get_event(&self) -> Option { + let e = unsafe { ffi::ei_get_event(self.ei) }; + NonNull::new(e).map(Event::new) + } + + pub fn now(&self) -> u64 { + unsafe { ffi::ei_now(self.ei) } + } +} + +impl Clone for Ei { + fn clone(&self) -> Self { + Self { + ei: unsafe { ffi::ei_ref(self.ei) }, + } + } +} + +impl Drop for Ei { + fn drop(&mut self) { + unsafe { ffi::ei_unref(self.ei) }; + } +} diff --git a/libeirs/src/events.rs b/libeirs/src/events.rs new file mode 100644 index 0000000..fe7e99b --- /dev/null +++ b/libeirs/src/events.rs @@ -0,0 +1,47 @@ +use std::{fmt::Display, ptr::NonNull}; + +use crate::device::DeviceRef; + +use super::{ + ffi::{self, ei_event}, + seat::Seat, +}; + +pub use ffi::ei_event_type as EventType; +pub struct Event(NonNull); + +impl Drop for Event { + fn drop(&mut self) { + unsafe { ffi::ei_event_unref(self.as_raw()) }; + } +} + +impl Event { + pub fn new(event: std::ptr::NonNull) -> Self { + Self(event) + } + + pub fn as_raw(&self) -> *mut ei_event { + self.0.as_ptr() + } + + pub fn get_type(&self) -> EventType { + unsafe { ffi::ei_event_get_type(self.as_raw()) } + } + + pub fn get_seat(&self) -> Option { + Seat::from_event(self) + } + + pub fn get_device(&self) -> Option { + DeviceRef::new(unsafe { ffi::ei_event_get_device(self.as_raw()) }) + } +} + +impl Display for Event { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Event") + .field("Type", &self.get_type()) + .finish() + } +} diff --git a/libeirs/src/ffi.rs b/libeirs/src/ffi.rs new file mode 100644 index 0000000..f5b0df5 --- /dev/null +++ b/libeirs/src/ffi.rs @@ -0,0 +1,49 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(unused)] + +use std::{ + ffi::CStr, + fmt::{Debug, Display}, +}; + +include!(concat!(env!("OUT_DIR"), "/libei.rs")); + +impl From for log::Level { + fn from(value: ei_log_priority) -> Self { + match value { + ei_log_priority::EI_LOG_PRIORITY_DEBUG => log::Level::Debug, + ei_log_priority::EI_LOG_PRIORITY_INFO => log::Level::Info, + ei_log_priority::EI_LOG_PRIORITY_WARNING => log::Level::Warn, + ei_log_priority::EI_LOG_PRIORITY_ERROR => log::Level::Error, + _ => log::Level::Debug, + } + } +} + +impl From for ei_log_priority { + fn from(value: log::Level) -> Self { + match value { + log::Level::Trace | log::Level::Debug => ei_log_priority::EI_LOG_PRIORITY_DEBUG, + log::Level::Info => ei_log_priority::EI_LOG_PRIORITY_INFO, + log::Level::Warn => ei_log_priority::EI_LOG_PRIORITY_WARNING, + log::Level::Error => ei_log_priority::EI_LOG_PRIORITY_ERROR, + } + } +} + +impl Debug for ei_event_type { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Display::fmt(self, f) + } +} + +impl Display for ei_event_type { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let str = unsafe { CStr::from_ptr(ei_event_type_to_string(*self)) } + .to_str() + .unwrap(); + write!(f, "{}", str) + } +} diff --git a/libeirs/src/keymap.rs b/libeirs/src/keymap.rs new file mode 100644 index 0000000..1909717 --- /dev/null +++ b/libeirs/src/keymap.rs @@ -0,0 +1,49 @@ +use std::{error::Error, fmt::Debug, ptr::NonNull}; + +use super::{device::DeviceRef, ffi}; +use ffi::ei_keymap_type as KeymapType; + +pub struct Keymap(NonNull); + +impl Keymap { + pub(crate) fn from_device(device: &DeviceRef) -> Option { + let keymap = unsafe { + let keymap = ffi::ei_device_keyboard_get_keymap(device.as_raw()); + ffi::ei_keymap_ref(keymap) + }; + NonNull::new(keymap).map(Self) + } + + pub fn as_raw(&self) -> *mut ffi::ei_keymap { + self.0.as_ptr() + } + + pub fn get_type(&self) -> KeymapType { + unsafe { ffi::ei_keymap_get_type(self.as_raw()) } + } + + pub fn mmap_keymap(&self) -> Result, impl Error> { + unsafe { + let fd = ffi::ei_keymap_get_fd(self.as_raw()); + memmap2::Mmap::map(fd) + } + } +} + +impl Debug for Keymap { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("Keymap").field(&self.0).finish() + } +} + +impl Drop for Keymap { + fn drop(&mut self) { + unsafe { ffi::ei_keymap_unref(self.as_raw()) }; + } +} + +impl Clone for Keymap { + fn clone(&self) -> Self { + Self(unsafe { NonNull::new_unchecked(ffi::ei_keymap_ref(self.as_raw())) }) + } +} diff --git a/libeirs/src/lib.rs b/libeirs/src/lib.rs new file mode 100644 index 0000000..a7a356e --- /dev/null +++ b/libeirs/src/lib.rs @@ -0,0 +1,13 @@ +mod device; +mod eirc; +pub mod events; +mod ffi; +mod keymap; +pub mod seat; +mod sender; + +pub use device::*; +pub use events::*; +pub use keymap::Keymap; +pub use seat::Seat; +pub use sender::Sender; diff --git a/libeirs/src/seat.rs b/libeirs/src/seat.rs new file mode 100644 index 0000000..865d190 --- /dev/null +++ b/libeirs/src/seat.rs @@ -0,0 +1,41 @@ +use std::{ffi::CStr, fmt::Display, ptr::NonNull}; + +use super::{events::Event, ffi}; + +pub use ffi::ei_device_capability as DeviceCapability; + +pub struct Seat { + seat: NonNull, +} + +impl Seat { + pub fn from_event(event: &Event) -> Option { + let seat = unsafe { + let seat = ffi::ei_event_get_seat(event.as_raw()); + ffi::ei_seat_ref(seat) + }; + NonNull::new(seat).map(|seat| Self { seat }) + } + + pub fn bind_capabilities(&self, capabilities: DeviceCapability) { + unsafe { + ffi::ei_seat_bind_capabilities(self.seat.as_ptr(), capabilities, 0); + } + } +} + +impl Display for Seat { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let str = unsafe { CStr::from_ptr(ffi::ei_seat_get_name(self.seat.as_ptr())) } + .to_str() + .unwrap(); + write!(f, "{str}") + } +} + +impl Drop for Seat { + fn drop(&mut self) { + log::trace!("Dropping seat {self}"); + unsafe { ffi::ei_seat_unref(self.seat.as_ptr()) }; + } +} diff --git a/libeirs/src/sender.rs b/libeirs/src/sender.rs new file mode 100644 index 0000000..8b28122 --- /dev/null +++ b/libeirs/src/sender.rs @@ -0,0 +1,23 @@ +use std::{ops::Deref, os::fd::OwnedFd}; + +use super::eirc::Ei; + +pub struct Sender { + ei: Ei, +} + +impl Sender { + pub fn new(fd: OwnedFd) -> Self { + Self { + ei: Ei::new_sender(fd), + } + } +} + +impl Deref for Sender { + type Target = Ei; + + fn deref(&self) -> &Self::Target { + &self.ei + } +} diff --git a/libxkbcommonrs/.clangd b/libxkbcommonrs/.clangd new file mode 100644 index 0000000..24a3cde --- /dev/null +++ b/libxkbcommonrs/.clangd @@ -0,0 +1,3 @@ +CompileFlags: + Add: [-xc, -Wall, -Werror, -Wpedantic, -std=c17] + Compiler: clang diff --git a/libxkbcommonrs/Cargo.toml b/libxkbcommonrs/Cargo.toml new file mode 100644 index 0000000..f905bb1 --- /dev/null +++ b/libxkbcommonrs/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "libxkbcommonrs" +version = "0.1.0" +edition = "2021" + +[build-dependencies] +bindgen = { workspace = true, features = ["experimental"] } +anyhow.workspace = true +cc = "1.1" +convert_case = "0.6" + +[dependencies] +tokio = { workspace = true, features = ["rt", "net"] } +tokio-stream = { workspace = true } +log = { workspace = true, features = ["kv"] } diff --git a/libxkbcommonrs/build.rs b/libxkbcommonrs/build.rs new file mode 100644 index 0000000..d4cf4b0 --- /dev/null +++ b/libxkbcommonrs/build.rs @@ -0,0 +1,209 @@ +use std::{env, path::PathBuf}; + +use bindgen::callbacks::{EnumVariantValue, ParseCallbacks}; +use convert_case::{Case, Casing}; + +#[derive(Debug)] +struct RemovePrefixesCallback {} + +impl RemovePrefixesCallback { + fn new() -> Self { + Self {} + } + + fn boxed() -> Box { + Box::new(Self::new()) + } +} + +impl ParseCallbacks for RemovePrefixesCallback { + fn item_name(&self, original_item_name: &str) -> Option { + const PREFIXES: &[&str] = &["XKB_", "xkb_"]; + let mut ret = None; + for prefix in PREFIXES { + if let Some(name) = original_item_name.strip_prefix(prefix) { + ret = Some(name.to_owned()); + break; + } + } + ret + } + + fn enum_variant_name( + &self, + enum_name: Option<&str>, + original_variant_name: &str, + _variant_value: EnumVariantValue, + ) -> Option { + if enum_name.is_none() { + return self.item_name(original_variant_name); + } + // let original_variant_name = self + // .item_name(original_variant_name) + // .unwrap_or(original_variant_name.to_owned()); + let enum_name = enum_name.unwrap(); + let enum_name = enum_name.strip_prefix("enum ").unwrap_or(enum_name); + let enum_lower = original_variant_name.to_lowercase(); + let diff_index = enum_lower + .chars() + .zip(enum_name.chars()) + .enumerate() + .find(|(_i, (v, e))| v != e) + .map(|it| it.0) + .unwrap_or(enum_name.len()); + let name = original_variant_name[diff_index..] + .trim_start_matches('_') + .to_case(Case::Pascal); + + Some(name) + } +} + +#[derive(Debug)] +struct TypedefCallback {} + +impl TypedefCallback { + fn new() -> Self { + Self {} + } + + fn boxed() -> Box { + Box::new(Self::new()) + } +} + +impl ParseCallbacks for TypedefCallback { + fn item_name(&self, original_item_name: &str) -> Option { + if original_item_name == "xkb_keysym_t" { + Some(String::from("Keysym")) + } else { + None + } + } + + // fn int_macro(&self, name: &str, _value: i64) -> Option { + // if name.starts_with("XKB_KEY_") { + // Some(IntKind::Custom { + // name: "Keysym", + // is_signed: false, + // }) + // } else { + // None + // } + // } + + // fn add_derives(&self, info: &DeriveInfo<'_>) -> Vec { + // if (info.name.contains("keysym") && !info.name.contains("flags")) + // || info.name == "Keysym" + // || info.name == "xkb_keysym_t" + // || info.name == "keysym_t" + // { + // panic!("{info:?}"); + // vec![ + // "PartialEq".to_string(), + // "PartialOrd".to_string(), + // "Ord".to_string(), + // "Eq".to_string(), + // ] + // } else { + // vec![] + // } + // } +} + +// #[derive(Debug)] +// struct VariadicsCallback {} +// +// impl VariadicsCallback { +// fn new() -> Self { +// Self {} +// } +// fn boxed() -> Box { +// Box::new(Self::new()) +// } +// } +// +// impl ParseCallbacks for VariadicsCallback { +// fn wrap_as_variadic_fn(&self, name: &str) -> Option { +// panic!("mdr {name}"); +// Some(format!("{name}_var")) +// } +// } + +fn build_xkb_keysyms() -> anyhow::Result<()> { + let common_bindings = bindgen::builder() + .header("/usr/include/xkbcommon/xkbcommon-keysyms.h") + .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) + .parse_callbacks(RemovePrefixesCallback::boxed()) + .parse_callbacks(TypedefCallback::boxed()) + .allowlist_var("XKB_.*") + .generate()?; + + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + let out_file = out_path.join("libxkbcommon-keysyms.rs"); + common_bindings + .write_to_file(out_file) + .expect("Couldn't write bindings!"); + + Ok(()) +} + +fn build_xkbcommon() -> anyhow::Result<()> { + let common_bindings = bindgen::builder() + .header("ffi/logger.h") + .header("/usr/include/xkbcommon/xkbcommon.h") + .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) + .parse_callbacks(RemovePrefixesCallback::boxed()) + .parse_callbacks(TypedefCallback::boxed()) + // .parse_callbacks(VariadicsCallback::boxed()) + .allowlist_item("xkb_.*") + .allowlist_item("log_.*") // Log wrapper + // .allowlist_item("XKB_.*") + .new_type_alias_deref("Keysym") + .no_debug("xkb_keysym_t") + .allowlist_var("XKB_KEYCODE_INVALID") + .allowlist_var("XKB_LAYOUT_INVALID") + .allowlist_var("XKB_LEVEL_INVALID") + .allowlist_var("XKB_MOD_INVALID") + .allowlist_var("XKB_LED_INVALID") + .allowlist_var("XKB_KEYCODE_MAX") + .allowlist_var("XKB_KEYSYM_MAX") + .default_enum_style(bindgen::EnumVariation::NewType { + is_bitfield: false, + is_global: false, + }) + .derive_eq(true) + .derive_partialeq(true) + .derive_ord(true) + .derive_partialord(true) + .derive_hash(true) + .generate()?; + + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + let out_file = out_path.join("libxkbcommon.rs"); + common_bindings + .write_to_file(out_file) + .expect("Couldn't write bindings!"); + + println!("cargo:rustc-link-lib=xkbcommon"); + + Ok(()) +} + +fn build_logger() -> anyhow::Result<()> { + cc::Build::new() + .file("ffi/logger.c") + .std("c17") + .warnings(true) + .warnings_into_errors(true) + .extra_warnings(true) + .compile("logger"); + Ok(()) +} + +fn main() -> anyhow::Result<()> { + build_xkbcommon()?; + build_xkb_keysyms()?; + build_logger()?; + Ok(()) +} diff --git a/libxkbcommonrs/ffi/logger.c b/libxkbcommonrs/ffi/logger.c new file mode 100644 index 0000000..cf0c2b7 --- /dev/null +++ b/libxkbcommonrs/ffi/logger.c @@ -0,0 +1,17 @@ +#include "logger.h" + +#include + +#define buf_size 256 +static callback_type global_callback = NULL; + +void log_set_callback(callback_type callback) { global_callback = callback; } + +void log_logger(struct xkb_context *context, enum xkb_log_level level, + const char *format, va_list args) { + if (global_callback) { + char buf[buf_size] = {0}; + vsnprintf(buf, buf_size, format, args); + global_callback(context, level, buf); + } +} diff --git a/libxkbcommonrs/ffi/logger.h b/libxkbcommonrs/ffi/logger.h new file mode 100644 index 0000000..eec78c3 --- /dev/null +++ b/libxkbcommonrs/ffi/logger.h @@ -0,0 +1,11 @@ +#include +#include + +typedef void (*callback_type)(struct xkb_context *context, + enum xkb_log_level level, const char *message); + +void log_set_callback(callback_type callback); + +/// xkb log callback wrapper for printf formatting +void log_logger(struct xkb_context *context, enum xkb_log_level level, + const char *format, va_list args); diff --git a/libxkbcommonrs/src/context.rs b/libxkbcommonrs/src/context.rs new file mode 100644 index 0000000..740fff2 --- /dev/null +++ b/libxkbcommonrs/src/context.rs @@ -0,0 +1,65 @@ +use super::{ + error, + ffi::{self, context}, +}; + +use std::{ffi::CStr, ptr::NonNull}; + +pub struct Context { + ctx: NonNull, +} + +impl Context { + pub fn new() -> Result { + let ctx = NonNull::new(unsafe { ffi::context_new(ffi::context_flags::NoFlags) }) + .ok_or(error::Error::ContextNewFailed)?; + unsafe { + let ctx = ctx.as_ptr(); + if let Some(level) = log::max_level().to_level() { + ffi::context_set_log_level(ctx, level.into()); + ffi::log_set_callback(Some(log_callback)); + ffi::context_set_log_fn(ctx, Some(ffi::log_logger)); + } else { + ffi::context_set_log_fn(ctx, Some(null_handler)) + } + } + + Ok(Self { ctx }) + } + + pub(crate) fn as_raw(&self) -> *mut context { + self.ctx.as_ptr() + } +} + +impl Drop for Context { + fn drop(&mut self) { + unsafe { ffi::context_unref(self.as_raw()) } + } +} + +unsafe extern "C" fn null_handler( + _ctx: *mut context, + _priority: ffi::log_level, + _format: *const ::std::os::raw::c_char, + _args: *mut ffi::__va_list_tag, +) { + // no op +} + +unsafe extern "C" fn log_callback( + _ctx: *mut context, + priority: ffi::log_level, + str: *const std::os::raw::c_char, +) { + match unsafe { CStr::from_ptr(str).to_str() } { + Ok(str) => { + log::log!( + target: "libxkbcommon", + priority.into(), + "{str}" + ); + } + Err(e) => log::error!("Failed to log message with severity {priority:?}.\n\t{e}"), + } +} diff --git a/libxkbcommonrs/src/error.rs b/libxkbcommonrs/src/error.rs new file mode 100644 index 0000000..167c20e --- /dev/null +++ b/libxkbcommonrs/src/error.rs @@ -0,0 +1,15 @@ +use std::fmt::{Debug, Display}; + +#[derive(Debug)] +pub enum Error { + ContextNewFailed, + InvalidKeymap, +} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Debug::fmt(&self, f) + } +} + +impl std::error::Error for Error {} diff --git a/libxkbcommonrs/src/ffi.rs b/libxkbcommonrs/src/ffi.rs new file mode 100644 index 0000000..fed7277 --- /dev/null +++ b/libxkbcommonrs/src/ffi.rs @@ -0,0 +1,61 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(unused)] + +use std::fmt::{Debug, Display}; + +pub mod syms { + include!(concat!(env!("OUT_DIR"), "/libxkbcommon-keysyms.rs")); +} + +include!(concat!(env!("OUT_DIR"), "/libxkbcommon.rs")); + +impl From for log::Level { + fn from(value: log_level) -> Self { + match value { + log_level::Debug => log::Level::Debug, + log_level::Info => log::Level::Info, + log_level::Warning => log::Level::Warn, + log_level::Error => log::Level::Error, + _ => log::Level::Debug, + } + } +} + +impl From for log_level { + fn from(value: log::Level) -> Self { + match value { + log::Level::Trace | log::Level::Debug => log_level::Debug, + log::Level::Info => log_level::Info, + log::Level::Warn => log_level::Warning, + log::Level::Error => log_level::Error, + } + } +} + +impl Display for Keysym { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut buffer = [0_u8; 32]; + let written = + unsafe { keysym_to_utf8(*self, buffer.as_mut_ptr() as *mut _, buffer.len()) } as usize; + let str = std::str::from_utf8(&buffer[0..written]).unwrap_or(""); + write!(f, "{str}") + } +} + +impl std::fmt::Debug for Keysym { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut buffer = [0_u8; 32]; + let written = + unsafe { keysym_get_name(*self, buffer.as_mut_ptr() as *mut _, buffer.len()) } as usize; + let str = std::str::from_utf8(&buffer[0..written]).unwrap_or(""); + write!(f, "{str}") + } +} + +impl From for Keysym { + fn from(value: u32) -> Self { + Self(value) + } +} diff --git a/libxkbcommonrs/src/keymap.rs b/libxkbcommonrs/src/keymap.rs new file mode 100644 index 0000000..67ac514 --- /dev/null +++ b/libxkbcommonrs/src/keymap.rs @@ -0,0 +1,133 @@ +use std::ffi::c_void; +use std::{ffi::CStr, ptr::NonNull}; + +use crate::{error, ffi, LayoutID, LayoutRef}; + +use super::context::Context; + +pub use ffi::keycode_t as Key; +pub use ffi::layout_index_t as Level; +pub use ffi::Keysym; + +pub type KeyIter<'a> = Box; + +pub struct Keymap { + keymap: NonNull, + format: ffi::keymap_format, +} + +impl Keymap { + pub fn new(ctx: &Context, buffer: &[u8]) -> Result { + let format = ffi::keymap_format::TextV1; + let keymap = unsafe { + ffi::keymap_new_from_buffer( + ctx.as_raw(), + buffer.as_ptr() as *const i8, + buffer.len(), + format, + ffi::keymap_compile_flags::NoFlags, + ) + }; + Ok(Self { + keymap: NonNull::new(keymap).ok_or(error::Error::InvalidKeymap)?, + format, + }) + } + + pub(crate) fn as_raw(&self) -> *mut ffi::keymap { + self.keymap.as_ptr() + } +} + +impl<'a> Keymap { + pub fn get_layouts(&'a self) -> Vec> { + let num_layouts = unsafe { ffi::keymap_num_layouts(self.as_raw()) }; + let mut v = Vec::with_capacity(num_layouts as usize); + + for i in 0..num_layouts { + v.push(LayoutRef::new(self, i)); + } + v + } + + pub fn get_sims_by_level( + &'a self, + key: Key, + layout: Option, + level: Level, + ) -> &'a [Keysym] { + let mut syms_out: *const Keysym = std::ptr::null_mut(); + unsafe { + let ptr = &mut syms_out as *mut _; + let len = ffi::keymap_key_get_syms_by_level( + self.as_raw(), + key, + layout.unwrap_or_default(), + level, + ptr, + ); + if syms_out.is_null() || !syms_out.is_aligned() { + &[] + } else { + std::slice::from_raw_parts(syms_out, len as usize) + } + } + } + + pub fn num_levels_for_key(&self, key: Key, layout: Option) -> u32 { + unsafe { ffi::keymap_num_levels_for_key(self.as_raw(), key, layout.unwrap_or_default()) } + } + + pub fn key_for_each(&'a self, visitor: KeyIter<'a>) { + let ptr = (&visitor) as *const _; + unsafe { + ffi::keymap_key_for_each(self.as_raw(), Some(key_iter), ptr as *mut c_void); + } + } + + pub fn key_get_name(&self, key: Key) -> Option<&str> { + let ptr = unsafe { ffi::keymap_key_get_name(self.as_raw(), key) }; + if ptr.is_null() { + return None; + } + unsafe { CStr::from_ptr(ptr) } + .to_str() + .inspect_err(|e| log::warn!("Failed to get name for key {key}: {e}")) + .ok() + } +} + +impl std::fmt::Debug for Keymap { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let str = unsafe { CStr::from_ptr(ffi::keymap_get_as_string(self.as_raw(), self.format)) } + .to_str() + .unwrap(); + write!(f, "{str}") + } +} + +pub fn utf32_to_keysim(utf32: &[u32]) -> Vec { + let mut ret = Vec::with_capacity(utf32.len()); + for c in utf32 { + let ks = unsafe { ffi::utf32_to_keysym(*c) }; + ret.push(ks); + } + ret +} + +pub fn keycode_is_legal_ext(key: Key) -> bool { + key <= ffi::KEYCODE_MAX +} + +pub fn keycode_is_legal_x11(key: Key) -> bool { + (8..=255).contains(&key) +} + +unsafe extern "C" fn key_iter( + _keymap: *mut ffi::keymap, + key: ffi::keycode_t, + data: *mut ::std::os::raw::c_void, +) { + let visitor = unsafe { &mut *(data as *mut KeyIter<'_>) }; + visitor(key); +} diff --git a/libxkbcommonrs/src/layout.rs b/libxkbcommonrs/src/layout.rs new file mode 100644 index 0000000..8c88833 --- /dev/null +++ b/libxkbcommonrs/src/layout.rs @@ -0,0 +1,33 @@ +use std::{ffi::CStr, fmt::Display}; + +use crate::{ffi, Keymap}; + +pub type LayoutID = ffi::layout_index_t; + +#[derive(Copy, Clone)] +pub struct LayoutRef<'a> { + keymap: &'a Keymap, + id: ffi::layout_index_t, +} + +impl<'a> LayoutRef<'a> { + pub fn new(keymap: &'a Keymap, id: ffi::layout_index_t) -> Self { + Self { keymap, id } + } + + pub fn get_name(&'a self) -> &'a str { + unsafe { CStr::from_ptr(ffi::keymap_layout_get_name(self.keymap.as_raw(), self.id)) } + .to_str() + .unwrap_or("") + } + + pub fn get_id(&self) -> LayoutID { + self.id + } +} + +impl<'a> Display for LayoutRef<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.get_name()) + } +} diff --git a/libxkbcommonrs/src/lib.rs b/libxkbcommonrs/src/lib.rs new file mode 100644 index 0000000..a3b7a8a --- /dev/null +++ b/libxkbcommonrs/src/lib.rs @@ -0,0 +1,11 @@ +mod context; +mod error; +mod ffi; +mod keymap; +mod layout; + +pub use context::Context; +pub use keymap::*; +pub use layout::*; + +pub use ffi::syms; diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..a24835b --- /dev/null +++ b/src/app.rs @@ -0,0 +1,73 @@ +use clap::Parser; +use log::LevelFilter; +use tokio::sync::mpsc; + +use crate::{ei, keyboard::Keyboard, portal::Portal}; + +pub enum Event { + KeyboardAdded(Keyboard), + KeyboardRemoved, +} +pub type EventSender = mpsc::Sender; + +#[derive(clap::Parser, Debug)] +struct Config { + #[arg(short, long, action = clap::ArgAction::Count)] + verbose: u8, + + text: String, +} + +pub async fn run() -> anyhow::Result<()> { + let config = Config::parse(); + + setup_logger(config.verbose); + + let portal = Portal::new_session().await?; + let eis_fd = portal.connect_to_eis().await?; + let (sndr, mut recv) = mpsc::channel(10); + let (ei_hdl, ei_sender) = ei::start_thread(eis_fd, sndr)?; + + while let Some(event) = recv.recv().await { + match event { + Event::KeyboardAdded(k) => { + log::info!("Got keyboard"); + let seq = k.string_to_sequence(&config.text); + ei_sender.send(seq).await.unwrap_or_default(); + // Done + ei_sender.close().await; + } + Event::KeyboardRemoved => { + log::info!("Keyboard removed"); + ei_sender.cancel().await; + } + } + } + + match ei_hdl.await { + Err(error) => { + if let Ok(panic) = error.try_into_panic() { + std::panic::resume_unwind(panic) + } + } + Ok(res) => res?, + } + Ok(()) +} + +fn setup_logger(verbose: u8) { + let (base, external) = match verbose { + 0 => (LevelFilter::Warn, LevelFilter::Warn), + 1 => (LevelFilter::Info, LevelFilter::Warn), + 2 => (LevelFilter::Debug, LevelFilter::Info), + 3 => (LevelFilter::Debug, LevelFilter::Debug), + 4.. => (LevelFilter::Trace, LevelFilter::Trace), + }; + + env_logger::Builder::new() + .parse_env("RUST_LOG") + .filter_level(base) + .filter(Some("libei"), external) + .init(); + log::info!("Hello, world!"); +} diff --git a/src/ei.rs b/src/ei.rs new file mode 100644 index 0000000..30c2fd8 --- /dev/null +++ b/src/ei.rs @@ -0,0 +1,309 @@ +use std::{ + collections::VecDeque, + os::fd::{AsRawFd, OwnedFd}, + sync::{Arc, Weak}, + time::Duration, +}; + +use libeirs as ei; +use libxkbcommonrs as xkb; +use tokio::{ + sync::mpsc::{self, error::TryRecvError}, + task::JoinHandle, +}; + +use crate::{ + app::{self, EventSender}, + keyboard::Keyboard, +}; + +#[derive(Debug, Clone)] +pub struct KeyEvent(xkb::Key, bool); +pub type KeySequence = Vec; + +impl KeyEvent { + pub fn new(k: xkb::Key, down: bool) -> Self { + Self(k, down) + } + + pub fn is_pressed(&self) -> bool { + self.1 + } + + pub fn key(&self) -> xkb::Key { + self.0 + } +} + +pub enum Message { + Keys(KeySequence), + Cancel, + Close, +} + +/// Wrapper around a sender to wake the EI thread +pub struct EIThreadSender { + send: mpsc::Sender, + poll: Weak, +} + +impl EIThreadSender { + pub async fn send(&self, keys: KeySequence) -> anyhow::Result<()> { + self.notify()?; + self.send.send(Message::Keys(keys)).await?; + Ok(()) + } + + pub async fn cancel(&self) { + if self.notify().is_ok() { + self.send.send(Message::Cancel).await.unwrap_or_default(); + } + } + + pub async fn close(&self) { + if self.notify().is_ok() { + self.send.send(Message::Close).await.unwrap_or_default(); + } + } + + fn notify(&self) -> anyhow::Result<()> { + Ok(self + .poll + .upgrade() + .ok_or(anyhow::anyhow!("Poller is dead?"))? + .notify()?) + } +} + +pub fn start_thread( + fd: OwnedFd, + event_sender: EventSender, +) -> anyhow::Result<(JoinHandle>, EIThreadSender)> { + let (send, rcv) = mpsc::channel(10); + let poll = Arc::new(polling::Poller::new()?); + let weak = Arc::downgrade(&poll); + Ok(( + tokio::task::spawn_blocking(move || run(fd, poll, rcv, event_sender)), + EIThreadSender { send, poll: weak }, + )) +} + +fn run( + fd: OwnedFd, + poll: Arc, + mut rcv: mpsc::Receiver, + event_sender: mpsc::Sender, +) -> anyhow::Result<(), anyhow::Error> { + const EI_FD_KEY: usize = 1; + let raw_fd = fd.as_raw_fd(); + let ei = ei::Sender::new(fd); + let xkb = xkb::Context::new()?; + let mut state = State::new(xkb); + + let mut events = polling::Events::new(); + let fd_event = polling::Event::readable(EI_FD_KEY).with_interrupt(); + unsafe { + // This here is why we're not running in a normal tokio task. Tokio (Mio) only allow us to use the EdgeTrigger + // mode of epoll, so we need to reset the readiness of the file descriptor ourselves. But since we're not the + // ones reading from it, we'd have to reset the ready flag when we're sure libei got everything. Unfortunately, + // libei doesn't document how the buffer is read, and the user is expected to use the LevelTrigger mode of epoll + // to loop until libei is done reading. + poll.add_with_mode(raw_fd, fd_event, polling::PollMode::Level)?; + } + + let mut done = false; + let mut timeout = None; + while poll.wait(&mut events, timeout).map(|_| true)? { + if done && state.to_send.is_empty() { + log::trace!("Done sending events"); + break; + } + + match rcv.try_recv() { + Ok(msg) => match msg { + Message::Keys(keys) => { + state.to_send.extend(keys.into_iter()); + // libei asks us to not send event frames with timestamps into the future, so set up a timer using + // poll to time slice our events + timeout = Some(Duration::from_micros(10)); + } + Message::Cancel => { + log::trace!("Thread cancelled"); + break; + } + Message::Close => { + log::trace!("Closing message channel, finishing up..."); + done = true; + poll.notify().unwrap_or_default(); + } + }, + Err(e) => { + if e == TryRecvError::Disconnected { + log::trace!("Lost message channel, finishing up..."); + done = true; + poll.notify().unwrap_or_default(); + } + } + } + + ei.dispatch(); + while let Some(event) = ei.get_event() { + if !handle_event(&mut state, event, &event_sender)? { + break; + } + } + + if !state.to_send.is_empty() && state.ready { + send_keys( + &mut state, + timeout.expect("timeout is none but we have keys to send?"), + )?; + if state.to_send.is_empty() { + timeout = None; + } + } + } + + log::trace!("EI Thread over"); + Ok(()) +} + +struct State { + xkb: xkb::Context, + keymap: Option, + seat: Option, + device: Option, + to_send: VecDeque, + ready: bool, + sequence: u32, + elapsed: Duration, +} + +impl State { + fn new(xkb: xkb::Context) -> Self { + Self { + xkb, + keymap: None, + seat: None, + device: None, + to_send: Default::default(), + ready: false, + sequence: 0, + elapsed: Default::default(), + } + } +} + +fn handle_event(state: &mut State, event: ei::Event, send: &EventSender) -> anyhow::Result { + log::trace!("Got EI event {event}"); + match event.get_type() { + ei::EventType::EI_EVENT_CONNECT => log::info!("Client connected"), + ei::EventType::EI_EVENT_DISCONNECT => { + log::info!("Client disconnected"); + return Ok(true); + } + ei::EventType::EI_EVENT_SEAT_ADDED => { + if state.seat.is_none() { + state.seat = event.get_seat(); + if let Some(seat) = state.seat.as_ref() { + seat.bind_capabilities(ei::seat::DeviceCapability::EI_DEVICE_CAP_KEYBOARD); + log::info!("Found seat \"{seat}\""); + } + } + } + ei::EventType::EI_EVENT_SEAT_REMOVED => { + if let Some(seat) = state.seat.take() { + log::info!("state.seat \"{seat}\" removed"); + } + } + ei::EventType::EI_EVENT_DEVICE_ADDED => { + if let Some(d) = event.get_device() { + log::trace!("Got device {d}"); + if state.device.is_none() { + state.device = Some(d.to_owned()); + let device = state.device.as_ref().unwrap(); + let keymap = device + .get_keymap() + .ok_or(anyhow::anyhow!("Device {device} has no keymap"))?; + let keymap_buffer = keymap.mmap_keymap()?; + let xkb = libxkbcommonrs::Keymap::new(&state.xkb, keymap_buffer.as_ref())?; + let keyboard = Keyboard::new(&xkb)?; + state.keymap = Some(xkb); + send.blocking_send(app::Event::KeyboardAdded(keyboard))?; + } + } + } + ei::EventType::EI_EVENT_DEVICE_REMOVED => { + if let Some(d) = event.get_device() { + log::trace!("Removed device {d}"); + if state.device.as_ref().is_some_and(|device| *device == d) { + state.device.take(); + state.keymap.take(); + state.ready = false; + send.blocking_send(app::Event::KeyboardRemoved).unwrap(); + } + } + } + + ei::EventType::EI_EVENT_DEVICE_RESUMED => { + if event.get_device().as_ref() == state.device.as_ref().map(ei::Device::as_ref) { + log::debug!("device resumed"); + state.sequence += 1; + state + .device + .as_ref() + .unwrap() + .start_emulating(state.sequence); + state.ready = true; + } + } + ei::EventType::EI_EVENT_DEVICE_PAUSED => { + if event.get_device().as_ref() == state.device.as_ref().map(ei::Device::as_ref) { + log::debug!("device paused"); + state.ready = false; + } + } + + e => log::debug!("Unhandled event {e}"), + } + Ok(false) +} + +fn send_keys(state: &mut State, dt: Duration) -> anyhow::Result<()> { + log::trace!("Sending keys"); + + let first_pressed = if let Some(front) = state.to_send.front() { + front.is_pressed() + } else { + return Ok(()); + }; + + let d = state.device.as_mut().ok_or(anyhow::anyhow!("No device"))?; + let mut count = 0; + let now = (state.elapsed + dt).as_micros() as u64; + for key in state + .to_send + .iter() + .take_while(|e| e.is_pressed() == first_pressed) + { + log::trace!( + "Sending event {}({}): {}", + state + .keymap + .as_ref() + .unwrap() + .key_get_name(key.key()) + .unwrap_or(""), + key.key(), + key.is_pressed() + ); + count += 1; + // XKB keys are offset by 8 + let ei_key = key.key() - 8; + d.keyboard_key(ei_key, key.is_pressed()); + } + d.frame(Some(now)); + state.to_send.drain(0..count); + + Ok(()) +} diff --git a/src/keyboard.rs b/src/keyboard.rs new file mode 100644 index 0000000..d3c4945 --- /dev/null +++ b/src/keyboard.rs @@ -0,0 +1,142 @@ +use std::{collections::BTreeMap, fmt::Display}; + +use anyhow::anyhow; +use libxkbcommonrs::{self as xkb, Key, Keymap, Keysym, LayoutID, Level}; +use widestring::Utf32String; +use xkb::syms; + +use crate::ei::{KeyEvent, KeySequence}; + +#[derive(Default)] +struct Layout { + keysym_to_key_level: BTreeMap, + shift_mod: Key, + graph_mod: Key, + name: String, +} + +impl Layout { + fn from_xkb(keymap: &Keymap, l: &xkb::LayoutRef<'_>) -> anyhow::Result { + let id = l.get_id(); + let keysym_to_key_level = build_keysym_key_map(keymap, Some(id)); + let (shift_mod, shift_level) = keysym_to_key_level + .get(&syms::KEY_Shift_L.into()) + .or_else(|| keysym_to_key_level.get(&syms::KEY_Shift_R.into())) + .copied() + .ok_or(anyhow::anyhow!("No shift key?"))?; + if shift_level > 0 { + Err(anyhow::anyhow!("Shift level need for shift key?"))?; + } + let (graph_mod, graph_level) = keysym_to_key_level + .get(&syms::KEY_ISO_Level3_Shift.into()) + .copied() + .ok_or(anyhow!("No AltGr key?"))?; + if graph_level > 0 { + Err(anyhow::anyhow!("Graph level need for graph key?"))?; + } + + Ok(Layout { + keysym_to_key_level, + shift_mod, + graph_mod, + name: l.get_name().to_owned(), + }) + } + + fn get_key_level_from_sym(&self, sym: Keysym) -> Option<(Key, Level)> { + self.keysym_to_key_level.get(&sym).cloned() + } +} + +impl Display for Layout { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name) + } +} + +#[derive(Default)] +pub struct Keyboard { + layouts: Vec, +} + +impl Keyboard { + pub fn new(xkb: &libxkbcommonrs::Keymap) -> anyhow::Result { + let layouts = xkb.get_layouts(); + log::debug!( + "Keymap has layouts {:?}", + layouts + .iter() + .map(|l| format!("{l}: {}", l.get_name())) + .collect::>() + ); + + let layouts: Vec = layouts + .into_iter() + .filter_map(|l| match Layout::from_xkb(xkb, &l) { + Ok(layout) => Some(layout), + Err(e) => { + log::warn!("Discarding invalid layout {l}: {e}"); + None + } + }) + .collect(); + + if layouts.is_empty() { + return Err(anyhow!("Failed to get any working layouts")); + } + + Ok(Self { layouts }) + } + + pub fn string_to_sequence(&self, string: &str) -> KeySequence { + let utf32 = Utf32String::from(string); + let keysyms = xkb::utf32_to_keysim(utf32.as_slice()); + log::trace!("Syms: {string} => {keysyms:?}"); + keysyms + .into_iter() + .flat_map(|sym| self.get_key_sequence(None, sym)) + .collect() + } + + pub fn get_key_sequence(&self, l: Option, sym: Keysym) -> KeySequence { + let layout = self.layouts.get(l.unwrap_or_default() as usize).unwrap(); + if let Some((key, level)) = layout.get_key_level_from_sym(sym) { + if level == 0 { + return vec![KeyEvent::new(key, true), KeyEvent::new(key, false)]; + } + let shift_key = match level { + 1 => layout.shift_mod, + 2 => layout.graph_mod, + o => panic!("What: {o}"), + }; + vec![ + KeyEvent::new(shift_key, true), + KeyEvent::new(key, true), + KeyEvent::new(key, false), + KeyEvent::new(shift_key, false), + ] + } else { + log::warn!("No keys for keysym {sym} in layout {layout}"); + KeySequence::default() + } + } +} + +fn build_keysym_key_map(keymap: &Keymap, l: Option) -> BTreeMap { + let mut ret = BTreeMap::default(); + keymap.key_for_each(Box::new(|k| { + for level in 0..keymap.num_levels_for_key(k, l) { + for sym in keymap.get_sims_by_level(k, l, level) { + ret.entry(*sym) + .and_modify(|(old_key, old_level)| { + if *old_level > level { + *old_key = k; + *old_level = level; + } + }) + .or_insert((k, level)); + } + } + })); + ret +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..02a173a --- /dev/null +++ b/src/main.rs @@ -0,0 +1,11 @@ +mod app; +mod ei; +mod keyboard; +mod portal; + +#[tokio::main(flavor = "current_thread")] +async fn main() -> anyhow::Result<()> { + app::run().await?; + + Ok(()) +} diff --git a/src/portal.rs b/src/portal.rs new file mode 100644 index 0000000..413bc5b --- /dev/null +++ b/src/portal.rs @@ -0,0 +1,40 @@ +use std::os::fd::OwnedFd; + +use ashpd::{ + desktop::{ + remote_desktop::{DeviceType, RemoteDesktop}, + PersistMode, Session, + }, + WindowIdentifier, +}; + +pub struct Portal<'a> { + remote: RemoteDesktop<'a>, + session: Session<'a, RemoteDesktop<'a>>, +} + +impl<'a> Portal<'a> { + pub async fn new_session() -> anyhow::Result { + log::info!("Connect to portal"); + let remote = RemoteDesktop::new().await?; + let session = remote.create_session().await?; + remote + .select_devices( + &session, + DeviceType::Keyboard.into(), + None, + PersistMode::ExplicitlyRevoked, + ) + .await?; + + Ok(Self { remote, session }) + } + + pub async fn connect_to_eis(&self) -> anyhow::Result { + self.remote + .start(&self.session, &WindowIdentifier::None) + .await?; + let fd = self.remote.connect_to_eis(&self.session).await?; + Ok(fd) + } +}