Compare commits
No commits in common. "master" and "dev" have entirely different histories.
46
.ci/Jenkinsfile
vendored
46
.ci/Jenkinsfile
vendored
@ -1,46 +0,0 @@
|
||||
properties(
|
||||
[
|
||||
disableConcurrentBuilds()
|
||||
]
|
||||
)
|
||||
|
||||
node('linux && docker') {
|
||||
try {
|
||||
stage('Checkout') {
|
||||
//branch name from Jenkins environment variables
|
||||
echo "My branch is: ${env.BRANCH_NAME}"
|
||||
|
||||
// this doesn't grab tags pointing to this branch
|
||||
//checkout scm
|
||||
// this hack does... https://issues.jenkins.io/browse/JENKINS-45164
|
||||
checkout([
|
||||
$class: 'GitSCM',
|
||||
branches: [[name: 'refs/heads/'+env.BRANCH_NAME]],
|
||||
extensions: [[$class: 'CloneOption', noTags: false, shallow: false, depth: 0, reference: '']],
|
||||
userRemoteConfigs: scm.userRemoteConfigs,
|
||||
])
|
||||
sh '''
|
||||
set -euxo pipefail
|
||||
git checkout "$BRANCH_NAME" --
|
||||
git reset --hard "origin/$BRANCH_NAME"
|
||||
'''
|
||||
}
|
||||
|
||||
stage('Build + Deploy') {
|
||||
sh '''
|
||||
mkdir -p release
|
||||
cp keymap.toml release
|
||||
curl --compressed -sL https://code.moparisthebest.com/moparisthebest/self-ci/raw/branch/master/build-ci.sh | bash
|
||||
'''
|
||||
}
|
||||
|
||||
currentBuild.result = 'SUCCESS'
|
||||
} catch (Exception err) {
|
||||
currentBuild.result = 'FAILURE'
|
||||
} finally {
|
||||
stage('Email') {
|
||||
step([$class: 'Mailer', notifyEveryUnstableBuild: true, recipients: 'admin.jenkins@moparisthebest.com', sendToIndividuals: true])
|
||||
}
|
||||
deleteDir()
|
||||
}
|
||||
}
|
45
.ci/build.sh
45
.ci/build.sh
@ -1,45 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -exo pipefail
|
||||
|
||||
echo "starting build for TARGET $TARGET"
|
||||
|
||||
export CRATE_NAME=rusty-keys
|
||||
|
||||
DISABLE_TESTS=${DISABLE_TESTS:-0}
|
||||
|
||||
SUFFIX=""
|
||||
|
||||
echo "$TARGET" | grep -E '^x86_64-pc-windows-gnu$' >/dev/null && SUFFIX=".exe"
|
||||
|
||||
[ "$TARGET" == 'riscv64gc-unknown-linux-gnu' ] && echo 'riscv64gc-unknown-linux-gnu is not yet supported by inotify, skipping build...' && exit 0
|
||||
|
||||
# no main impl for these platforms
|
||||
echo "$TARGET" | grep -E '(android|solaris$)' >/dev/null && DISABLE_TESTS=1
|
||||
|
||||
cross build --target $TARGET --release
|
||||
|
||||
# to check how they are built
|
||||
file "target/$TARGET/release/rusty-keys$SUFFIX"
|
||||
|
||||
if [ $DISABLE_TESTS -ne 1 ]
|
||||
then
|
||||
# only going to run --help I guess
|
||||
cross run --target $TARGET --release --bin rusty-keys -- -h
|
||||
fi
|
||||
|
||||
# if this commit has a tag, upload artifact to release
|
||||
strip "target/$TARGET/release/rusty-keys$SUFFIX" || true # if strip fails, it's fine
|
||||
mkdir -p release
|
||||
mv "target/$TARGET/release/rusty-keys$SUFFIX" "release/rusty-keys-$TARGET$SUFFIX"
|
||||
|
||||
if [ "$TARGET" == 'x86_64-unknown-linux-musl' ]
|
||||
then
|
||||
# for this arch only, we are going to build with each feature combo to test that the build succeeds, but not archive them
|
||||
# the default for now is all features, so that's already tested above
|
||||
cross build --target $TARGET --release --no-default-features
|
||||
cross build --target $TARGET --release --no-default-features --features epoll_inotify
|
||||
cross build --target $TARGET --release --no-default-features --features toml_serde
|
||||
fi
|
||||
|
||||
echo 'build success!'
|
||||
exit 0
|
97
.travis.yml
Normal file
97
.travis.yml
Normal file
@ -0,0 +1,97 @@
|
||||
# Based on the "trust" template v0.1.2
|
||||
# https://github.com/japaric/trust/tree/v0.1.2
|
||||
|
||||
dist: trusty
|
||||
language: rust
|
||||
services: docker
|
||||
sudo: required
|
||||
|
||||
# TODO Rust builds on stable by default, this can be
|
||||
# overridden on a case by case basis down below.
|
||||
|
||||
env:
|
||||
global:
|
||||
# TODO Update this to match the name of your project.
|
||||
- CRATE_NAME=rusty-keys
|
||||
|
||||
matrix:
|
||||
# TODO These are all the build jobs. Adjust as necessary. Comment out what you
|
||||
# don't need
|
||||
include:
|
||||
# Android
|
||||
- env: TARGET=aarch64-linux-android DISABLE_TESTS=1
|
||||
- env: TARGET=arm-linux-androideabi DISABLE_TESTS=1
|
||||
- env: TARGET=armv7-linux-androideabi DISABLE_TESTS=1
|
||||
- env: TARGET=i686-linux-android DISABLE_TESTS=1
|
||||
- env: TARGET=x86_64-linux-android DISABLE_TESTS=1
|
||||
|
||||
# Linux
|
||||
- env: TARGET=aarch64-unknown-linux-gnu
|
||||
- env: TARGET=arm-unknown-linux-gnueabi
|
||||
- env: TARGET=armv7-unknown-linux-gnueabihf
|
||||
- env: TARGET=i686-unknown-linux-gnu
|
||||
- env: TARGET=i686-unknown-linux-musl
|
||||
- env: TARGET=mips-unknown-linux-gnu
|
||||
- env: TARGET=mips64-unknown-linux-gnuabi64
|
||||
- env: TARGET=mips64el-unknown-linux-gnuabi64
|
||||
- env: TARGET=mipsel-unknown-linux-gnu
|
||||
- env: TARGET=powerpc-unknown-linux-gnu
|
||||
- env: TARGET=powerpc64le-unknown-linux-gnu
|
||||
- env: TARGET=s390x-unknown-linux-gnu
|
||||
- env: TARGET=x86_64-unknown-linux-gnu
|
||||
- env: TARGET=x86_64-unknown-linux-musl
|
||||
|
||||
# Windows
|
||||
- env: TARGET=x86_64-pc-windows-gnu
|
||||
|
||||
# Testing other channels
|
||||
- env: TARGET=x86_64-unknown-linux-gnu
|
||||
rust: nightly
|
||||
|
||||
before_install:
|
||||
- set -e
|
||||
- rustup self update
|
||||
|
||||
install:
|
||||
- sh ci/install.sh
|
||||
- source ~/.cargo/env || true
|
||||
|
||||
script:
|
||||
- bash ci/script.sh
|
||||
|
||||
after_script: set +e
|
||||
|
||||
before_deploy:
|
||||
- sh ci/before_deploy.sh
|
||||
|
||||
deploy:
|
||||
# TODO update `api_key.secure`
|
||||
# - Create a `public_repo` GitHub token. Go to: https://github.com/settings/tokens/new
|
||||
# - Encrypt it: `travis encrypt 0123456789012345678901234567890123456789
|
||||
# - Paste the output down here
|
||||
api_key:
|
||||
secure: $GITHUB_OAUTH
|
||||
file_glob: true
|
||||
file: $CRATE_NAME-$TRAVIS_TAG-$TARGET*
|
||||
on:
|
||||
# TODO Here you can pick which targets will generate binary releases
|
||||
# In this example, there are some targets that are tested using the stable
|
||||
# and nightly channels. This condition makes sure there is only one release
|
||||
# for such targets and that's generated using the stable channel
|
||||
condition: $TRAVIS_RUST_VERSION = stable
|
||||
tags: true
|
||||
provider: releases
|
||||
skip_cleanup: true
|
||||
|
||||
cache: cargo
|
||||
before_cache:
|
||||
# Travis can't cache files that are not readable by "others"
|
||||
- chmod -R a+r $HOME/.cargo
|
||||
|
||||
branches:
|
||||
only:
|
||||
# release tags
|
||||
- /^v\d+\.\d+\.\d+.*$/
|
||||
- master
|
||||
- dev
|
||||
|
150
Cargo.lock
generated
150
Cargo.lock
generated
@ -1,13 +1,5 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
@ -16,81 +8,15 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.71"
|
||||
version = "1.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd"
|
||||
checksum = "f9a06fb2e53271d7c279ec1efea6ab691c35a2ae67ec0d91d7acec0caf13b518"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
|
||||
|
||||
[[package]]
|
||||
name = "core-graphics"
|
||||
version = "0.22.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "269f35f69b542b80e736a20a89a05215c0ce80c2c03c514abb2e318b78379d86"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"core-foundation",
|
||||
"core-graphics-types",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-graphics-types"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"core-foundation",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "epoll"
|
||||
version = "4.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20df693c700404f7e19d4d6fae6b15215d2913c27955d2b9d6f2c0f537511cd0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||
dependencies = [
|
||||
"foreign-types-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
|
||||
[[package]]
|
||||
name = "getopts"
|
||||
@ -103,9 +29,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.9.5"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e5fc8f41dbaa9c8492a96c8afffda4f76896ee041d6a57606e70581b80c901f"
|
||||
checksum = "46dd0a94b393c730779ccfd2a872b67b1eb67be3fc33082e733bdb38b5fde4d4"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"inotify-sys",
|
||||
@ -114,9 +40,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "inotify-sys"
|
||||
version = "0.1.5"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
|
||||
checksum = "e74a1aa87c59aeff6ef2cc2fa62d41bc43f54952f55652656b18a02fd5e356c0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@ -129,46 +55,37 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.103"
|
||||
version = "0.2.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
checksum = "a9f8082297d534141b30c8d39e9b1773713ab50fdbe4ff30f750d063b3bfd701"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.22.2"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3bb9a13fa32bc5aeb64150cd3f32d6cf4c748f8f8a417cce5d2eb976a8370ba"
|
||||
checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"memoffset",
|
||||
"void",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.29"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d"
|
||||
checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.10"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
|
||||
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@ -177,9 +94,6 @@ dependencies = [
|
||||
name = "rusty-keys"
|
||||
version = "0.0.3"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"core-graphics",
|
||||
"epoll",
|
||||
"getopts",
|
||||
"inotify",
|
||||
"lazy_static",
|
||||
@ -192,18 +106,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.130"
|
||||
version = "1.0.114"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
|
||||
checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.130"
|
||||
version = "1.0.114"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
|
||||
checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -212,9 +126,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.80"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194"
|
||||
checksum = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -223,24 +137,30 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.8"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
|
||||
checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.9"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
|
||||
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||
|
||||
[[package]]
|
||||
name = "void"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
|
22
Cargo.toml
22
Cargo.toml
@ -22,26 +22,14 @@ include = [
|
||||
|
||||
[dependencies]
|
||||
getopts = "0.2.21"
|
||||
toml = { version = "0.5.8", optional = true }
|
||||
serde = { version = "1.0.130", features = ["derive"], optional = true }
|
||||
|
||||
[target.'cfg(target_os="macos")'.dependencies]
|
||||
core-graphics = "0.22"
|
||||
core-foundation-sys = "0.8"
|
||||
#rustkit = "0.0.1"
|
||||
libc = "0.2"
|
||||
toml = "0.5.6"
|
||||
serde = { version = "1.0.114", features = ["derive"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = { version = "0.3.9", features = ["winuser", "wincon"] }
|
||||
lazy_static = "1.4.0"
|
||||
|
||||
[target.'cfg(target_os="linux")'.dependencies]
|
||||
libc = "0.2.102"
|
||||
nix = "0.22.1"
|
||||
epoll = { version = "4.3.1", optional = true }
|
||||
inotify = { version = "0.9.3", default-features = false, features = [], optional = true }
|
||||
|
||||
[features]
|
||||
default = ["epoll_inotify", "toml_serde"]
|
||||
toml_serde = ["toml", "serde"]
|
||||
epoll_inotify = ["epoll", "inotify"]
|
||||
libc = "0.2.72"
|
||||
nix = "0.17.0"
|
||||
inotify = { version = "0.8.3", default-features = false, features = [] }
|
||||
|
12
README.md
12
README.md
@ -1,11 +1,8 @@
|
||||
rusty-keys
|
||||
==========
|
||||
======
|
||||
uinput level keyboard mapper for linux, with advanced caps lock and shift swapping behavior
|
||||
|
||||
[![Build Status](https://ci.moparisthe.best/job/moparisthebest/job/rusty-keys/job/master/badge/icon%3Fstyle=plastic)](https://ci.moparisthe.best/job/moparisthebest/job/rusty-keys/job/master/)
|
||||
|
||||
low level keyboard mapper for linux and windows, with advanced caps lock and shift swapping behavior
|
||||
|
||||
This is the only keymapper I am aware of capable of implementing this layout, which I call Unix Programmer's Dvorak, which has been my daily driver since 2014:
|
||||
This is the only keymapper I am aware of capable of implementing this layout:
|
||||
![Unix Programmer's Dvorak](https://www.moparisthebest.com/kbs/programmer-dvorak-NoSecondary-NumpadStandard-NoSwap-StandardNums-SwapAt-SwapPipe.svg)
|
||||
|
||||
The Problem
|
||||
@ -20,7 +17,7 @@ The Solution
|
||||
2. Create a new keyboard input device with uinput, this looks identical to any other keyboard device to anything running on the box.
|
||||
3. Read input_events from the real device, map them, send them to our created device.
|
||||
|
||||
This solution is what rusty-keys implements, it works in ttys, in X, in Wayland, in virtualbox even running windows or whatever,
|
||||
This solution is what rusty-keys implements, it works in ttys, in X, in virtualbox even running windows or whatever,
|
||||
on SDL games, it will work literally everywhere, because rusty-keys just creates a regular keyboard.
|
||||
|
||||
How to run
|
||||
@ -52,7 +49,6 @@ How to install
|
||||
--------------
|
||||
* `cargo install rusty-keys`
|
||||
* Arch Linux [rusty-keys](https://aur.archlinux.org/packages/rusty-keys/) [rusty-keys-git](https://aur.archlinux.org/packages/rusty-keys-git/)
|
||||
* Download a static binary for your system from the [releases](https://code.moparisthebest.com/moparisthebest/rusty-keys/releases) section. [github mirror](https://github.com/moparisthebest/rusty-keys/releases)
|
||||
|
||||
License
|
||||
-------
|
||||
|
21
ci/before_deploy.ps1
Normal file
21
ci/before_deploy.ps1
Normal file
@ -0,0 +1,21 @@
|
||||
# This script takes care of packaging the build artifacts that will go in the
|
||||
# release zipfile
|
||||
|
||||
$SRC_DIR = $PWD.Path
|
||||
$STAGE = [System.Guid]::NewGuid().ToString()
|
||||
|
||||
Set-Location $ENV:Temp
|
||||
New-Item -Type Directory -Name $STAGE
|
||||
Set-Location $STAGE
|
||||
|
||||
$ZIP = "$SRC_DIR\$($Env:CRATE_NAME)-$($Env:APPVEYOR_REPO_TAG_NAME)-$($Env:TARGET).exe"
|
||||
|
||||
# TODO Update this to package the right artifacts
|
||||
Copy-Item "$SRC_DIR\target\$($Env:TARGET)\release\rusty-keys.exe" "$ZIP"
|
||||
|
||||
Push-AppveyorArtifact "$ZIP"
|
||||
|
||||
Remove-Item *.* -Force
|
||||
Set-Location ..
|
||||
Remove-Item $STAGE
|
||||
Set-Location $SRC_DIR
|
26
ci/before_deploy.sh
Normal file
26
ci/before_deploy.sh
Normal file
@ -0,0 +1,26 @@
|
||||
# This script takes care of building your crate and packaging it for release
|
||||
|
||||
set -ex
|
||||
|
||||
main() {
|
||||
local src=$(pwd)
|
||||
|
||||
test -f Cargo.lock || cargo generate-lockfile
|
||||
|
||||
# TODO Update this to build the artifacts that matter to you
|
||||
cross rustc --bin rusty-keys --target $TARGET --release -- -C lto
|
||||
|
||||
# TODO Update this to package the right artifacts, this needs to handle .exe too...
|
||||
case $TARGET in
|
||||
x86_64-pc-windows-gnu)
|
||||
strip target/$TARGET/release/rusty-keys.exe || echo 'strip failed, ignoring...'
|
||||
cp target/$TARGET/release/rusty-keys.exe $src/$CRATE_NAME-$TRAVIS_TAG-$TARGET.exe
|
||||
;;
|
||||
*)
|
||||
strip target/$TARGET/release/rusty-keys || echo 'strip failed, ignoring...'
|
||||
cp target/$TARGET/release/rusty-keys $src/$CRATE_NAME-$TRAVIS_TAG-$TARGET
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main
|
28
ci/cert.key
Normal file
28
ci/cert.key
Normal file
@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCrvZ+p0/2i/p1s
|
||||
xzg3ydOS4z5S3QD0WAD1gdBTUBPjqmeGNbtXl5XLGPTYJeBqNNGRv6louB+jL8U+
|
||||
TSBsyyARVZfJ9EDU3iG1fOQzz6sK8yhVL15bL4wJHobDGkL1zc0//ozDbr9iH5Xi
|
||||
5Xh1q9lEyLMviASNjZbWcdGWxxKQRceluzcTyowesBr7K9nQaQF7cSmetCYaA1L9
|
||||
JXokHY3P8pPEOhfo8SI7Lkt7XzKrnI0RBEIQBDF5F/XsKHO2Iso8bVq1huNr37MH
|
||||
QDEDo+D8803oFS+89j9SFDv2QgITZgl0gtY5w911qbNtyz0hWYloXzmlPH2QF/+Z
|
||||
XThI2Kd/AgMBAAECggEARmvN4Xxsv346HRWfhri6ibumnaHDt22yjvj47ICkdzEz
|
||||
nAPCWwtsP8hu9Yaqe8JGwMXfeHIvfuGitoY3qoSsFI+NWyFNyDuBhQK+LESWNTo5
|
||||
qpxuy2M2v7KFvdCx7krCQ+Bj5esujNS4yD4h49Zgk+TcHLxgaY7KcAphz7q3cPKP
|
||||
hJPkwSvFmLMdqomyLJfBTWPx6Ue3ioAfKxM62hbaYlBth1ch5YqOhL84YCMnPmbq
|
||||
hL/iTTlPcXeZoCodEHoOac/t8Nvv4fRetrBqk9uMZXZ1Bm9VfKbVDysfiKp9W+np
|
||||
uvtYUht/TdlrzjE01h3QHNnkYgJA+yuK/qjL0nvoYQKBgQDjN4HdZqA7ZBPvQUtq
|
||||
LVfpm2jy/8Sf3ewUUx7Pwselft7FOpKzkouVhdyWY8BweN97zwaYPJo7OtfMzT3Y
|
||||
NPO5Vz7nxMcvwZXir0VV514lLIYjqkZkDY94thGayFaF8DmyqfIfTuyVPrigRfOR
|
||||
8+dbYJrSVn9hnP4i9cxoIu39TwKBgQDBfxAFp2gJ1G3U2YAjNt4qrbsdPZvsX6CI
|
||||
A8T5EuiPanAqaVWbQnkPFQq077qBSlCI/zUunw01I0pyuR74paY7PaiKT3hLkovh
|
||||
v2VlOFEMaA7K/TTjv6tZS5P8DXuM/r34h5XRVsaKXKPNBGoAcbvanJ/96N42j+E1
|
||||
G4+R8FMG0QKBgFMmKfkKqFJzojPpEh8N7uEHRVW/sYXLYaxiaqEfJ45xqjZE5BCg
|
||||
7UHPldTXNkIyiZ42ObSWYN6R/wzsgthPMG2/9r48LaRVVHN7LoVsQPCbpY8BrfbJ
|
||||
W5qSDkk1TSyAp6yxMnCwojVPmaLVVngv6Jdw99dHXiAronjKuH3XYn5TAoGAUUpd
|
||||
Y9Kx3bdWMR7zO1gYvBtiyeURNZvzKFFVFkMAWwgfeWHpaiHiFBkF93/jfd/Ht9Zn
|
||||
9F8zwEhERbBKN7H4BVlhDkJWyoEVrVCoe37OZgTtehAogSoMBaa/1Buh9VksXFYx
|
||||
9dGb9ZL36fDZy7f8cNpuSNDlUkzeE16x0WECsJECgYBdekYJFJWppHjQ0ID5Jt1T
|
||||
GHQIovXFsHfACIKC1lyytqgtEdSaWQD0SfFsg/s6BYcW7bfs/tejTnbJySvcp3rC
|
||||
+FCznrEfk6wVFuj/nrgB/MpmxbsG2N5EmchRX5YJHVDRtWpQxPkNgRvdNBPkPY54
|
||||
5zSUEJh+lLSg+ZjuM52eOg==
|
||||
-----END PRIVATE KEY-----
|
21
ci/cert.pem
Normal file
21
ci/cert.pem
Normal file
@ -0,0 +1,21 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDazCCAlOgAwIBAgIUfeb7Ocg4fLv5BEiXLhLS5/fQGm0wDQYJKoZIhvcNAQEL
|
||||
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
||||
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0xOTEyMTcwMjE2MDhaFw0yOTEy
|
||||
MTQwMjE2MDhaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
|
||||
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
|
||||
AQUAA4IBDwAwggEKAoIBAQCrvZ+p0/2i/p1sxzg3ydOS4z5S3QD0WAD1gdBTUBPj
|
||||
qmeGNbtXl5XLGPTYJeBqNNGRv6louB+jL8U+TSBsyyARVZfJ9EDU3iG1fOQzz6sK
|
||||
8yhVL15bL4wJHobDGkL1zc0//ozDbr9iH5Xi5Xh1q9lEyLMviASNjZbWcdGWxxKQ
|
||||
RceluzcTyowesBr7K9nQaQF7cSmetCYaA1L9JXokHY3P8pPEOhfo8SI7Lkt7XzKr
|
||||
nI0RBEIQBDF5F/XsKHO2Iso8bVq1huNr37MHQDEDo+D8803oFS+89j9SFDv2QgIT
|
||||
Zgl0gtY5w911qbNtyz0hWYloXzmlPH2QF/+ZXThI2Kd/AgMBAAGjUzBRMB0GA1Ud
|
||||
DgQWBBSxoWXwmMEmKrsRsii2l1/IBhlvMzAfBgNVHSMEGDAWgBSxoWXwmMEmKrsR
|
||||
sii2l1/IBhlvMzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQB3
|
||||
NIlwmAL3FBrqHmg5M+zh+xKkNl/O8SK4bJwhPoTYR+DHsDlEQWvwSVaGV5HGyqy2
|
||||
cv39kHJ6OisSuOitESV4gdOUZvm/WCSV5xHpokJpGlztRSKi4iwFNONn0LUi4lnF
|
||||
gkYgjS4OfOCjVJ0YgAkYaBYALM3PTY3VpG32vaz62A7mIzO5Jn/kMtEIgFT+32Be
|
||||
BE/8E+pcOhgkvoE1xwv0STbrnM8dGN8/zyXvb1wt4b2ijkBlT5Wsqs0yvPa31SD0
|
||||
FDqc+4/H3bJXjwfBGDbf18sTY1UQEPyQdNC7vhiy/w2AgjVNjVpNBI9nvj+9rkZ5
|
||||
8m8sP3ldEkdIqSRCl95o
|
||||
-----END CERTIFICATE-----
|
47
ci/install.sh
Normal file
47
ci/install.sh
Normal file
@ -0,0 +1,47 @@
|
||||
set -ex
|
||||
|
||||
main() {
|
||||
local target=
|
||||
if [ $TRAVIS_OS_NAME = linux ]; then
|
||||
target=x86_64-unknown-linux-musl
|
||||
sort=sort
|
||||
else
|
||||
target=x86_64-apple-darwin
|
||||
sort=gsort # for `sort --sort-version`, from brew's coreutils.
|
||||
fi
|
||||
|
||||
# Builds for iOS are done on OSX, but require the specific target to be
|
||||
# installed.
|
||||
case $TARGET in
|
||||
aarch64-apple-ios)
|
||||
rustup target install aarch64-apple-ios
|
||||
;;
|
||||
armv7-apple-ios)
|
||||
rustup target install armv7-apple-ios
|
||||
;;
|
||||
armv7s-apple-ios)
|
||||
rustup target install armv7s-apple-ios
|
||||
;;
|
||||
i386-apple-ios)
|
||||
rustup target install i386-apple-ios
|
||||
;;
|
||||
x86_64-apple-ios)
|
||||
rustup target install x86_64-apple-ios
|
||||
;;
|
||||
esac
|
||||
|
||||
# This fetches latest stable release
|
||||
local tag=$(git ls-remote --tags --refs --exit-code https://github.com/japaric/cross \
|
||||
| cut -d/ -f3 \
|
||||
| grep -E '^v[0.1.0-9.]+$' \
|
||||
| $sort --version-sort \
|
||||
| tail -n1)
|
||||
curl -LSfs https://japaric.github.io/trust/install.sh | \
|
||||
sh -s -- \
|
||||
--force \
|
||||
--git japaric/cross \
|
||||
--tag $tag \
|
||||
--target $target
|
||||
}
|
||||
|
||||
main
|
20
ci/script.sh
Normal file
20
ci/script.sh
Normal file
@ -0,0 +1,20 @@
|
||||
# This script takes care of testing your crate
|
||||
|
||||
set -ex
|
||||
|
||||
# TODO This is the "test phase", tweak it as you see fit
|
||||
main() {
|
||||
cross build --target $TARGET --release
|
||||
|
||||
if [ ! -z $DISABLE_TESTS ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
# only going to run --help I guess
|
||||
cross run --target $TARGET --release --bin rusty-keys -- -h
|
||||
}
|
||||
|
||||
# we don't run the "test phase" when doing deploys
|
||||
if [ -z $TRAVIS_TAG ]; then
|
||||
main
|
||||
fi
|
76
keymap.orig.toml
Normal file
76
keymap.orig.toml
Normal file
@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env python
|
||||
# pressing all of these keys along with a number key representing the index of keymaps changes the layout
|
||||
# ie, in this case pressing both and 0 would go QWERTY, while both and 1 would go dvorak
|
||||
switch_layout_keys = ['LEFTSHIFT','RIGHTSHIFT']
|
||||
|
||||
# pressing QWERTY reverts to the index specified in revert_keymap_index for only the duration of the pressing
|
||||
# used so QWERTY shortcuts like Ctrl+C still work
|
||||
revert_default_key = 'LEFTCTRL'
|
||||
revert_keymap_index = 0
|
||||
|
||||
# this is the default index to use when the program first starts
|
||||
# in this case, 2 means modified Progammer Dvorak
|
||||
default_keymap_index = 2
|
||||
|
||||
# these are keys that caps_lock doesn't modify by default, but that you would like it to, affects all keymaps
|
||||
caps_lock_modify = """
|
||||
GRV, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, MINS,EQL, BSPC, PSLS,PAST,PMNS,
|
||||
LBRC,RBRC,BSLS, P7, P8, P9,
|
||||
SCLN,QUOT, P4, P5, P6, PPLS,
|
||||
COMM,DOT, SLSH, P1, P2, P3,
|
||||
P0, PDOT
|
||||
"""
|
||||
|
||||
# these are the keymaps available, you can add as many as you want or re-order them, just be aware the mapping is
|
||||
# always done from the first one to all subsequent ones, so you probably want to leave QWERTY or similar up top
|
||||
keymaps = [
|
||||
# default key layout, QWERTY in this case
|
||||
"""
|
||||
ESC, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, PSCR,SLCK,BRK,
|
||||
GRV, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, MINS,EQL, BSPC, INS, HOME,PGUP, NLCK,PSLS,PAST,PMNS,
|
||||
TAB, Q, W, E, R, T, Y, U, I, O, P, LBRC,RBRC,BSLS, DEL, END, PGDN, P7, P8, P9,
|
||||
CAPS,A, S, D, F, G, H, J, K, L, SCLN,QUOT, ENT, P4, P5, P6, PPLS,
|
||||
LSFT,Z, X, C, V, B, N, M, COMM,DOT, SLSH, RSFT, UP, P1, P2, P3,
|
||||
LCTL,LGUI,LALT, SPC, RALT,RGUI,APP, RCTL, LEFT,DOWN,RGHT, P0, PDOT,PENT
|
||||
""",
|
||||
# Dvorak http://en.wikipedia.org/wiki/Dvorak_Simplified_Keyboard
|
||||
# https://www.moparisthebest.com/kbs/standard-dvorak-QwertySecondary.svg
|
||||
"""
|
||||
ESC, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, PSCR,SLCK,BRK,
|
||||
GRV, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, LBRC,RBRC,BSPC, INS, HOME,PGUP, NLCK,PSLS,PAST,PMNS,
|
||||
TAB, QUOT,COMM,DOT, P, Y, F, G, C, R, L, SLSH,EQL, BSLS, DEL, END, PGDN, P7, P8, P9,
|
||||
CAPS,A, O, E, U, I, D, H, T, N, S, MINS, ENT, P4, P5, P6, PPLS,
|
||||
LSFT,SCLN,Q, J, K, X, B, M, W, V, Z, RSFT, UP, P1, P2, P3,
|
||||
LCTL,LGUI,LALT, SPC, RALT,RGUI,APP, RCTL, LEFT,DOWN,RGHT, P0, PDOT,PENT
|
||||
""",
|
||||
# Unix Dvorak Programmer Dvorak - for unix developers who are switching from dvorak
|
||||
# https://www.moparisthebest.com/kbs/programmer-dvorak-NoSecondary-NumpadStandard-NoSwap-StandardNums-SwapAt-SwapPipe.svg
|
||||
"""
|
||||
ESC, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, PSCR,SLCK,BRK,
|
||||
^4:^GRV, ^7:1, LBRC:2, ^LBRC:3, ^RBRC:4, ^9:5, ^2:6, ^8:7, ^0:8, ^EQL:9, RBRC:0, ^1:^5, ^3:GRV, BSPC, INS, HOME,PGUP, NLCK, PSLS:^9, PAST:^0, PMNS:^4,
|
||||
TAB, QUOT, COMM, DOT, P, Y, F, G, C, R, L, SLSH, EQL:^6, ^BSLS:BSLS, DEL, END, PGDN, P7:^A, P8:^B, P9:^C,
|
||||
CAPS, A, O, E, U, I, D, H, T, N, S, MINS, ENT, P4:^D, P5:^E, P6:^F, PPLS:COMM,
|
||||
LSFT, SCLN, Q, J, K, X, B, M, W, V, Z, RSFT, UP, P1:EQL, P2:X, P3:^SCLN,
|
||||
LCTL, LGUI, LALT, SPC, RALT, RGUI, APP, RCTL, LEFT,DOWN,RGHT, P0:BSLS, PDOT:SCLN, PENT
|
||||
""",
|
||||
# Unix Dvorak Programmer Dvorak - for unix developers who are switching from dvorak - phone numpad
|
||||
# https://www.moparisthebest.com/kbs/programmer-dvorak-QwertySecondary-NumpadPhone-NoSwap-StandardNums-SwapAt-SwapPipe.svg
|
||||
"""
|
||||
ESC, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, PSCR,SLCK,BRK,
|
||||
^4:^GRV, ^7:1, LBRC:2, ^LBRC:3, ^RBRC:4, ^9:5, ^2:6, ^8:7, ^0:8, ^EQL:9, RBRC:0, ^1:^5, ^3:GRV, BSPC, INS, HOME,PGUP, NLCK, PSLS:^9, PAST:^0, PMNS:^4,
|
||||
TAB, QUOT, COMM, DOT, P, Y, F, G, C, R, L, SLSH, EQL:^6, ^BSLS:BSLS, DEL, END, PGDN, P1:^A, P2:^B, P3:^C,
|
||||
CAPS, A, O, E, U, I, D, H, T, N, S, MINS, ENT, P4:^D, P5:^E, P6:^F, PPLS:COMM,
|
||||
LSFT, SCLN, Q, J, K, X, B, M, W, V, Z, RSFT, UP, P7:EQL, P8:X, P9:^SCLN,
|
||||
LCTL, LGUI, LALT, SPC, RALT, RGUI, APP, RCTL, LEFT,DOWN,RGHT, P0:BSLS, PDOT:SCLN, PENT
|
||||
""",
|
||||
# Programmer Dvorak http://www.kaufmann.no/roland/dvorak/
|
||||
# https://www.moparisthebest.com/kbs/programmer-dvorak-QwertySecondary-NumpadPhone-StrictSwap-StrictNums-StrictAt-StrictPipe.svg
|
||||
"""
|
||||
ESC, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, PSCR,SLCK,BRK,
|
||||
^4:^GRV, ^7:^5, LBRC:7, ^LBRC:5, ^RBRC:3, ^9:1, EQL:9, ^8:0, ^0:2, ^EQL:4, RBRC:6, ^1:8, ^3:GRV, BSPC, INS, HOME,PGUP, NLCK, PSLS:^9, PAST:^0, PMNS:^4,
|
||||
TAB, SCLN, COMM, DOT, P, Y, F, G, C, R, L, SLSH, ^2:^6, BSLS, DEL, END, PGDN, P1:^A, P2:^B, P3:^C,
|
||||
CAPS, A, O, E, U, I, D, H, T, N, S, MINS, ENT, P4:^D, P5:^E, P6:^F, PPLS:COMM,
|
||||
LSFT, QUOT, Q, J, K, X, B, M, W, V, Z, RSFT, UP, P7:EQL, P8:X, P9:^SCLN,
|
||||
LCTL, LGUI, LALT, SPC, RALT, RGUI, APP, RCTL, LEFT,DOWN,RGHT, P0:BSLS, PDOT:SCLN, PENT
|
||||
""",
|
||||
]
|
@ -3,9 +3,9 @@
|
||||
# ie, in this case pressing both and 0 would go QWERTY, while both and 1 would go dvorak
|
||||
switch_layout_keys = ['LEFTSHIFT','RIGHTSHIFT']
|
||||
|
||||
# pressing any of these keys reverts to the index specified in revert_keymap_index for only the duration of the pressing
|
||||
# pressing QWERTY reverts to the index specified in revert_keymap_index for only the duration of the pressing
|
||||
# used so QWERTY shortcuts like Ctrl+C still work
|
||||
revert_default_keys = ['LCTL','LGUI','LALT']
|
||||
revert_default_key = 'LEFTCTRL'
|
||||
revert_keymap_index = 0
|
||||
|
||||
# this is the default index to use when the program first starts
|
||||
|
37
src/error.rs
37
src/error.rs
@ -3,9 +3,15 @@ use std::error;
|
||||
use std::ffi;
|
||||
use std::io;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::sync::mpsc;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use nix;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use libc;
|
||||
|
||||
/// UInput error.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
@ -17,17 +23,15 @@ pub enum Error {
|
||||
Nul(ffi::NulError),
|
||||
|
||||
Io(io::Error),
|
||||
|
||||
#[cfg(feature = "toml_serde")]
|
||||
Toml(toml::de::Error),
|
||||
|
||||
NotAKeyboard,
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
Send(mpsc::SendError<libc::input_event>),
|
||||
|
||||
/// The uinput file could not be found.
|
||||
NotFound,
|
||||
|
||||
/// error reading input_event
|
||||
ShortRead,
|
||||
|
||||
/// epoll already added
|
||||
EpollAlreadyAdded,
|
||||
}
|
||||
|
||||
impl From<ffi::NulError> for Error {
|
||||
@ -49,6 +53,13 @@ impl From<io::Error> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
impl From<mpsc::SendError<libc::input_event>> for Error {
|
||||
fn from(value: mpsc::SendError<libc::input_event>) -> Self {
|
||||
Error::Send(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
match self {
|
||||
@ -59,14 +70,12 @@ impl fmt::Display for Error {
|
||||
|
||||
&Error::Io(ref err) => err.fmt(f),
|
||||
|
||||
#[cfg(feature = "toml_serde")]
|
||||
&Error::Toml(ref err) => err.fmt(f),
|
||||
|
||||
&Error::NotAKeyboard => f.write_str("This device file is not a keyboard"),
|
||||
#[cfg(target_os = "linux")]
|
||||
&Error::Send(ref err) => err.fmt(f),
|
||||
|
||||
&Error::NotFound => f.write_str("Device not found."),
|
||||
|
||||
&Error::ShortRead => f.write_str("Error while reading from device file."),
|
||||
|
||||
&Error::EpollAlreadyAdded => f.write_str("epoll already added, delete first"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
300
src/keymapper.rs
300
src/keymapper.rs
@ -1,12 +1,11 @@
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::collections::HashMap;
|
||||
use std::hash::Hash;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
#[cfg(feature = "toml_serde")]
|
||||
use std::path::Path;
|
||||
|
||||
use crate::Result;
|
||||
use crate::{Error, Result};
|
||||
|
||||
const INVERT_KEY_FLAG: char = '^';
|
||||
const CAPS_MODIFY_KEY_FLAG: char = '*';
|
||||
@ -15,7 +14,7 @@ const HALF_KEY_SEPARATOR: char = ':';
|
||||
// nightly only...
|
||||
//pub trait KeyCode = Into<usize> + TryFrom<usize> + Copy + Clone + Eq + Hash + Default + 'static;
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
#[derive(PartialEq)]
|
||||
pub enum KeyState {
|
||||
DOWN,
|
||||
UP,
|
||||
@ -32,7 +31,7 @@ pub trait KeyEvent<T>
|
||||
|
||||
pub trait Keyboard<T, E, R = ()>
|
||||
where
|
||||
T: Into<usize> + Copy,
|
||||
T: Into<usize>,
|
||||
E: KeyEvent<T>,
|
||||
{
|
||||
fn send(&self, event: &mut E) -> Result<R>;
|
||||
@ -43,52 +42,11 @@ pub trait Keyboard<T, E, R = ()>
|
||||
fn right_shift_code(&self) -> T;
|
||||
fn caps_lock_code(&self) -> T;
|
||||
fn block_key(&self) -> Result<R>;
|
||||
|
||||
fn send_half_inverted_key(&self, half_inverted_key: &HalfInvertedKey<T>, event: &mut E, left_shift: bool, right_shift: bool, caps_lock: bool) -> Result<R> {
|
||||
let value = event.value();
|
||||
let mut invert_shift = half_inverted_key.invert_shift;
|
||||
if value == KeyState::DOWN {
|
||||
if caps_lock && half_inverted_key.capslock_nomodify {
|
||||
invert_shift = !invert_shift;
|
||||
}
|
||||
if invert_shift {
|
||||
let (shift_code, up_not_down) = if left_shift {
|
||||
(self.left_shift_code(), true)
|
||||
} else if right_shift {
|
||||
(self.right_shift_code(), true)
|
||||
} else {
|
||||
(self.left_shift_code(), false)
|
||||
};
|
||||
self.send_mod_code_value(shift_code, up_not_down, event)?;
|
||||
// SYN_REPORT after, then key, then key's SYN_REPORT
|
||||
self.synchronize()?;
|
||||
}
|
||||
}
|
||||
let ret = self.send_mod_code(half_inverted_key.code, event)?;
|
||||
if value == KeyState::UP {
|
||||
if caps_lock && half_inverted_key.capslock_nomodify {
|
||||
invert_shift = !invert_shift;
|
||||
}
|
||||
if invert_shift {
|
||||
let (shift_code, up_not_down) = if left_shift {
|
||||
(self.left_shift_code(), false)
|
||||
} else if right_shift {
|
||||
(self.right_shift_code(), false)
|
||||
} else {
|
||||
(self.left_shift_code(), true)
|
||||
};
|
||||
// SYN_REPORT first after key, then shift, then key's SYN_REPORT which will be used for shift's
|
||||
self.synchronize()?;
|
||||
self.send_mod_code_value(shift_code, up_not_down, event)?;
|
||||
}
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait KeyMapper<K, T, E, R>
|
||||
where
|
||||
T: Into<usize> + Copy,
|
||||
T: Into<usize>,
|
||||
E: KeyEvent<T>,
|
||||
K: Keyboard<T, E, R>,
|
||||
{
|
||||
@ -105,7 +63,7 @@ pub struct KeyMaps<K, T, E, R = ()>
|
||||
keymap_index_keys: HashMap<T, usize>,
|
||||
switch_layout_keys: Vec<usize>,
|
||||
key_state: [bool; KEY_MAX],
|
||||
revert_default_keys: Vec<T>,
|
||||
revert_default_key: T,
|
||||
revert_keymap_index: usize,
|
||||
// above do not change, below does
|
||||
chosen_keymap_index: usize,
|
||||
@ -162,7 +120,6 @@ impl<K, T, E, R> KeyMaps<K, T, E, R>
|
||||
E: KeyEvent<T>,
|
||||
K: Keyboard<T, E, R>,
|
||||
{
|
||||
#[cfg(feature = "toml_serde")]
|
||||
pub fn from_cfg<P: AsRef<Path>>(key_map: &HashMap<&'static str, T>, path: P) -> KeyMaps<K, T, E, R> {
|
||||
let key_map_config = parse_cfg(path).expect("provided config cannot be found/parsed");
|
||||
KeyMaps::new(key_map, key_map_config)
|
||||
@ -190,14 +147,8 @@ impl<K, T, E, R> KeyMaps<K, T, E, R>
|
||||
let mut keymap = KeyMap::new();
|
||||
let mut i: usize = 0;
|
||||
for key_code in v {
|
||||
// if it's a direct key and it's the same, don't do any mapping
|
||||
if let Key::Direct(key) = key_code {
|
||||
if base_keymap[i] != key {
|
||||
keymap.map(base_keymap[i], key_code);
|
||||
}
|
||||
} else {
|
||||
keymap.map(base_keymap[i], key_code);
|
||||
}
|
||||
// todo: if these are the same, do Noop instead
|
||||
keymap.map(base_keymap[i], key_code);
|
||||
i = i + 1;
|
||||
if i > base_keymap.len() {
|
||||
panic!("all keymaps must be the same length, keymap index 0 length: {}, index {} length: {},", base_keymap.len(), x, i);
|
||||
@ -210,9 +161,7 @@ impl<K, T, E, R> KeyMaps<K, T, E, R>
|
||||
let mut keymap = CodeKeyMap::new();
|
||||
let mut i: usize = 0;
|
||||
for key_code in v {
|
||||
if base_keymap[i] != key_code {
|
||||
keymap.map(base_keymap[i], key_code);
|
||||
}
|
||||
keymap.map(base_keymap[i], key_code);
|
||||
i = i + 1;
|
||||
if i > base_keymap.len() {
|
||||
panic!("all keymaps must be the same length, keymap index 0 length: {}, index {} length: {},", base_keymap.len(), x, i);
|
||||
@ -224,27 +173,13 @@ impl<K, T, E, R> KeyMaps<K, T, E, R>
|
||||
//println!("keymaps: {:?}", keymaps);
|
||||
//println!("keymap_index_keys: {:?}", keymap_index_keys);
|
||||
|
||||
let mut revert_default_keys = Vec::new();
|
||||
if config.revert_default_key.is_some() {
|
||||
revert_default_keys.push(parse_key(key_map, &config.revert_default_key.unwrap()));
|
||||
}
|
||||
if config.revert_default_keys.is_some() {
|
||||
for revert_default_key in config.revert_default_keys.unwrap() {
|
||||
let revert_default_key = parse_key(key_map, &revert_default_key);
|
||||
if !revert_default_keys.contains(&revert_default_key) {
|
||||
revert_default_keys.push(revert_default_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
// revert_default_keys may be empty, but that's ok
|
||||
|
||||
KeyMaps {
|
||||
keymaps: keymaps,
|
||||
keymap_index_keys: keymap_index_keys,
|
||||
switch_layout_keys: config.switch_layout_keys.iter().map(|k| parse_key(key_map, k).into()).collect(),
|
||||
key_state: [false; KEY_MAX],
|
||||
// todo: detect key state? at least CAPSLOCK...
|
||||
revert_default_keys,
|
||||
revert_default_key: parse_key(key_map, &config.revert_default_key),
|
||||
revert_keymap_index: config.revert_keymap_index,
|
||||
chosen_keymap_index: config.default_keymap_index,
|
||||
current_keymap_index: config.default_keymap_index,
|
||||
@ -255,6 +190,7 @@ impl<K, T, E, R> KeyMaps<K, T, E, R>
|
||||
//impl KeyMapper for KeyMaps {
|
||||
//impl KeyMaps {
|
||||
pub fn send_event(&mut self, mut event: &mut E, device: &K) -> Result<R> {
|
||||
//println!("type: {} code: {} value: {}", event.type_, event.code, event.value);
|
||||
let value = event.value();
|
||||
if value != KeyState::OTHER {
|
||||
// todo: index check here...
|
||||
@ -263,12 +199,7 @@ pub fn send_event(&mut self, mut event: &mut E, device: &K) -> Result<R> {
|
||||
self.key_state[device.caps_lock_code().into()] = !self.key_state[device.caps_lock_code().into()];
|
||||
}
|
||||
} else {
|
||||
let idx = event.code().into();
|
||||
if idx >= KEY_MAX {
|
||||
// oh well, send it directly then
|
||||
return device.send(event);
|
||||
}
|
||||
self.key_state[idx] = value == KeyState::DOWN;
|
||||
self.key_state[event.code().into()] = value == KeyState::DOWN;
|
||||
}
|
||||
let mut switch_layout_keys_pressed = true;
|
||||
for layout_switch_key in self.switch_layout_keys.iter_mut() {
|
||||
@ -286,27 +217,11 @@ pub fn send_event(&mut self, mut event: &mut E, device: &K) -> Result<R> {
|
||||
return device.block_key(); // we don't want to also send this keypress, so bail
|
||||
}
|
||||
}
|
||||
if self.revert_default_keys.contains(&event.code()) {
|
||||
if event.code() == self.revert_default_key {
|
||||
match value {
|
||||
KeyState::DOWN => {
|
||||
// todo: should we release currently held keys and then press them back down here, kinda the opposite of below? not for now...
|
||||
self.current_keymap_index = self.revert_keymap_index
|
||||
},
|
||||
KeyState::UP => {
|
||||
self.current_keymap_index = self.chosen_keymap_index;
|
||||
#[cfg(not(target_os = "macos"))] {
|
||||
// need to release all currently held down keys, except this one, otherwise ctrl+c will get c stuck because code c value 1 will be sent, but then we'll let go of ctrl, and code j value 0 is sent, so c is never released
|
||||
let orig_code = event.code();
|
||||
for (idx, key_down) in self.key_state.iter_mut().enumerate() {
|
||||
if *key_down {
|
||||
device.send_mod_code_value(T::try_from(idx).unwrap_or_else(|_| panic!("cannot convert from usize to T ????")), true, event)?;
|
||||
*key_down = false;
|
||||
}
|
||||
}
|
||||
// todo: seems like we should not send this here, and instead just set the original code back, and pass it through the keymaps?
|
||||
return device.send_mod_code_value(orig_code, true, event)
|
||||
}
|
||||
},
|
||||
// todo: ctrl+c will get c stuck because code c value 1 will be sent, but then we'll let go of ctrl, and code j value 0 is sent, so c is never released... fix that...
|
||||
KeyState::DOWN => self.current_keymap_index = self.revert_keymap_index,
|
||||
KeyState::UP => self.current_keymap_index = self.chosen_keymap_index,
|
||||
_ => () // do nothing for 2
|
||||
}
|
||||
}
|
||||
@ -319,16 +234,42 @@ pub fn send_event(&mut self, mut event: &mut E, device: &K) -> Result<R> {
|
||||
const KEY_MAX: usize = 249;
|
||||
|
||||
struct KeyMap<T: Into<usize> + Copy> {
|
||||
//keymap: Vec<Key>,
|
||||
keymap: [Key<T>; KEY_MAX],
|
||||
}
|
||||
|
||||
impl<T: Into<usize> + Copy> KeyMap<T> {
|
||||
pub fn new() -> Self {
|
||||
//let mut keymap = [0u16; KEY_MAX];
|
||||
//let mut keymap : [Box<KeyMapper>; KEY_MAX] = [Box::new(NOOP); KEY_MAX];
|
||||
//let mut keymap : [Box<KeyMapper>; KEY_MAX] = [Box::new(0u16); KEY_MAX];
|
||||
let keymap: [Key<T>; KEY_MAX] = [Key::Noop; KEY_MAX];
|
||||
/*
|
||||
let mut keymap: Vec<Key> = Vec::with_capacity(KEY_MAX);
|
||||
#[allow(unused_variables)]
|
||||
for x in 0..KEY_MAX {
|
||||
keymap.push(Key::Noop);
|
||||
}
|
||||
*/
|
||||
// which is rustier
|
||||
/*
|
||||
for x in 0..KEY_MAX {
|
||||
keymap[x as usize] = x as u16;
|
||||
}
|
||||
for (x, v) in keymap.iter_mut().enumerate() {
|
||||
*v = x as u16;
|
||||
}
|
||||
*/
|
||||
//println!("keymap: {:?}", &keymap[..]);
|
||||
KeyMap {
|
||||
keymap: [Key::Noop; KEY_MAX]
|
||||
keymap: keymap
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
pub fn map(&mut self, from : u16, to: u16) {
|
||||
self.keymap[from as usize] = to;
|
||||
}
|
||||
*/
|
||||
pub fn map(&mut self, from: T, to: Key<T>) {
|
||||
self.keymap[from.into()] = to;
|
||||
}
|
||||
@ -346,18 +287,25 @@ impl<K, T, E, R> KeyMapper<K, T, E, R> for KeyMap<T>
|
||||
}
|
||||
|
||||
struct CodeKeyMap<T: Into<usize> + TryFrom<usize> + Copy + Default> {
|
||||
//keymap: Vec<Key>,
|
||||
keymap: [T; KEY_MAX],
|
||||
}
|
||||
|
||||
impl<T: Into<usize> + TryFrom<usize> + Copy + Default> CodeKeyMap<T> {
|
||||
pub fn new() -> Self {
|
||||
let mut keymap = [T::default(); KEY_MAX];
|
||||
// which is rustier
|
||||
/*
|
||||
for x in 0..KEY_MAX {
|
||||
keymap[x as usize] = x as u16;
|
||||
}
|
||||
*/
|
||||
for (x, v) in keymap.iter_mut().enumerate() {
|
||||
*v = T::try_from(x).unwrap_or_else(|_| panic!("cannot convert from usize to T ????"));
|
||||
}
|
||||
//println!("keymap: {:?}", &keymap[..]);
|
||||
CodeKeyMap {
|
||||
keymap
|
||||
keymap: keymap
|
||||
}
|
||||
}
|
||||
|
||||
@ -380,15 +328,61 @@ impl<K, T, E, R> KeyMapper<K, T, E, R> for CodeKeyMap<T>
|
||||
|
||||
// todo:capslock_nomodify is like a whole-key thing, not a half-key thing, split code/invert_shift to own struct, send into send_key from *InvertedKey, maybe anyway, consider it, maybe 1 char for whole key and another for half?
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct HalfInvertedKey<T: Clone + Copy> {
|
||||
pub code: T,
|
||||
struct HalfInvertedKey<T: Clone + Copy> {
|
||||
code: T,
|
||||
// code this is describing
|
||||
pub invert_shift: bool,
|
||||
invert_shift: bool,
|
||||
// true to invert shift for this code
|
||||
pub capslock_nomodify: bool,
|
||||
capslock_nomodify: bool,
|
||||
// true means capslock does not normally modify this, but you would like it to
|
||||
}
|
||||
|
||||
fn send_half_inverted_key<K, T, E, R>(half_inverted_key: &HalfInvertedKey<T>, event: &mut E, device: &K, left_shift: bool, right_shift: bool, caps_lock: bool) -> Result<R>
|
||||
where
|
||||
T: Into<usize> + Clone + Copy,
|
||||
E: KeyEvent<T>,
|
||||
K: Keyboard<T, E, R>,
|
||||
{
|
||||
let value = event.value();
|
||||
let mut invert_shift = half_inverted_key.invert_shift;
|
||||
if value == KeyState::DOWN {
|
||||
if caps_lock && half_inverted_key.capslock_nomodify {
|
||||
invert_shift = !invert_shift;
|
||||
}
|
||||
if invert_shift {
|
||||
let (shift_code, up_not_down) = if left_shift {
|
||||
(device.left_shift_code(), true)
|
||||
} else if right_shift {
|
||||
(device.right_shift_code(), true)
|
||||
} else {
|
||||
(device.left_shift_code(), false)
|
||||
};
|
||||
device.send_mod_code_value(shift_code, up_not_down, event)?;
|
||||
// SYN_REPORT after, then key, then key's SYN_REPORT
|
||||
device.synchronize()?;
|
||||
}
|
||||
}
|
||||
let ret = device.send_mod_code(half_inverted_key.code, event)?;
|
||||
if value == KeyState::UP {
|
||||
if caps_lock && half_inverted_key.capslock_nomodify {
|
||||
invert_shift = !invert_shift;
|
||||
}
|
||||
if invert_shift {
|
||||
let (shift_code, up_not_down) = if left_shift {
|
||||
(device.left_shift_code(), false)
|
||||
} else if right_shift {
|
||||
(device.right_shift_code(), false)
|
||||
} else {
|
||||
(device.left_shift_code(), true)
|
||||
};
|
||||
// SYN_REPORT first after key, then shift, then key's SYN_REPORT which will be used for shift's
|
||||
device.synchronize()?;
|
||||
device.send_mod_code_value(shift_code, up_not_down, event)?;
|
||||
}
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
impl<K, T, E, R> KeyMapper<K, T, E, R> for HalfInvertedKey<T>
|
||||
where
|
||||
T: Into<usize> + Clone + Copy,
|
||||
@ -399,7 +393,7 @@ impl<K, T, E, R> KeyMapper<K, T, E, R> for HalfInvertedKey<T>
|
||||
let left_shift = key_state[device.left_shift_code().into()];
|
||||
let right_shift = key_state[device.right_shift_code().into()];
|
||||
let caps_lock = key_state[device.caps_lock_code().into()];
|
||||
device.send_half_inverted_key(self, event, left_shift, right_shift, caps_lock)
|
||||
send_half_inverted_key(self, event, device, left_shift, right_shift, caps_lock)
|
||||
}
|
||||
}
|
||||
|
||||
@ -436,96 +430,34 @@ impl<K, T, E, R> KeyMapper<K, T, E, R> for Key<T>
|
||||
let right_shift = key_state[device.right_shift_code().into()];
|
||||
let caps_lock = key_state[device.caps_lock_code().into()];
|
||||
if caps_lock != (left_shift || right_shift) {
|
||||
device.send_half_inverted_key(shift_half, event, left_shift, right_shift, caps_lock)
|
||||
send_half_inverted_key(shift_half, event, device, left_shift, right_shift, caps_lock)
|
||||
} else {
|
||||
device.send_half_inverted_key(noshift_half, event, left_shift, right_shift, caps_lock)
|
||||
send_half_inverted_key(noshift_half, event, device, left_shift, right_shift, caps_lock)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "toml_serde")]
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
use std::path::Path;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct KeymapConfig {
|
||||
switch_layout_keys: Vec<String>,
|
||||
revert_default_key: Option<String>,
|
||||
revert_default_keys: Option<Vec<String>>,
|
||||
revert_default_key: String,
|
||||
revert_keymap_index: usize,
|
||||
default_keymap_index: usize,
|
||||
keymaps: Vec<String>
|
||||
}
|
||||
|
||||
#[cfg(feature = "toml_serde")]
|
||||
fn parse_cfg<P: AsRef<Path>>(path: P) -> Result<KeymapConfig> {
|
||||
use std::io::Read;
|
||||
let mut f = std::fs::File::open(path)?;
|
||||
let mut f = File::open(path)?;
|
||||
let mut input = String::new();
|
||||
f.read_to_string(&mut input)?;
|
||||
toml::from_str(&input).map_err(|e| crate::Error::Toml(e))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "toml_serde"))]
|
||||
#[derive(Debug)]
|
||||
pub struct KeymapConfig {
|
||||
switch_layout_keys: Vec<&'static str>,
|
||||
revert_default_key: Option<&'static str>,
|
||||
revert_default_keys: Option<Vec<&'static str>>,
|
||||
revert_keymap_index: usize,
|
||||
default_keymap_index: usize,
|
||||
keymaps: Vec<&'static str>
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "toml_serde"))]
|
||||
impl Default for KeymapConfig {
|
||||
fn default() -> Self {
|
||||
KeymapConfig {
|
||||
switch_layout_keys: vec!["LEFTSHIFT", "RIGHTSHIFT"],
|
||||
|
||||
// pressing any of these keys reverts to the index specified in revert_keymap_index for only the duration of the pressing
|
||||
// used so QWERTY shortcuts like Ctrl+C still work
|
||||
revert_default_keys: Some(vec!["LCTL", "LGUI", "LALT"]),
|
||||
revert_keymap_index: 0,
|
||||
|
||||
// this is the default index to use when the program first starts
|
||||
// in this case, 2 means Unix Programmer Dvorak
|
||||
default_keymap_index: 2,
|
||||
|
||||
// these are the keymaps available, you can add as many as you want or re-order them, just be aware the mapping is
|
||||
// always done from the first one to all subsequent ones, so you probably want to leave QWERTY or similar up top
|
||||
keymaps: vec![
|
||||
// default key layout, QWERTY in this case
|
||||
r###"
|
||||
ESC, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, PSCR,SLCK,BRK,
|
||||
GRV, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, MINS,EQL, BSPC, INS, HOME,PGUP, NLCK,PSLS,PAST,PMNS,
|
||||
TAB, Q, W, E, R, T, Y, U, I, O, P, LBRC,RBRC,BSLS, DEL, END, PGDN, P7, P8, P9,
|
||||
CAPS,A, S, D, F, G, H, J, K, L, SCLN,QUOT, ENT, P4, P5, P6, PPLS,
|
||||
LSFT,Z, X, C, V, B, N, M, COMM,DOT, SLSH, RSFT, UP, P1, P2, P3,
|
||||
LCTL,LGUI,LALT, SPC, RALT,RGUI,APP, RCTL, LEFT,DOWN,RGHT, P0, PDOT,PENT
|
||||
"###,
|
||||
// Dvorak http://en.wikipedia.org/wiki/Dvorak_Simplified_Keyboard
|
||||
// https://www.moparisthebest.com/kbs/standard-dvorak-QwertySecondary.svg
|
||||
r###"
|
||||
ESC, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, PSCR,SLCK,BRK,
|
||||
GRV, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, LBRC,RBRC,BSPC, INS, HOME,PGUP, NLCK,PSLS,PAST,PMNS,
|
||||
TAB, QUOT,COMM,DOT, P, Y, F, G, C, R, L, SLSH,EQL, BSLS, DEL, END, PGDN, P7, P8, P9,
|
||||
CAPS,A, O, E, U, I, D, H, T, N, S, MINS, ENT, P4, P5, P6, PPLS,
|
||||
LSFT,SCLN,Q, J, K, X, B, M, W, V, Z, RSFT, UP, P1, P2, P3,
|
||||
LCTL,LGUI,LALT, SPC, RALT,RGUI,APP, RCTL, LEFT,DOWN,RGHT, P0, PDOT,PENT
|
||||
"###,
|
||||
// Unix Programmer Dvorak - for unix developers who are switching from dvorak
|
||||
// https://www.moparisthebest.com/kbs/programmer-dvorak-NoSecondary-NumpadStandard-NoSwap-StandardNums-SwapAt-SwapPipe.svg
|
||||
r###"
|
||||
ESC, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, PSCR,SLCK,BRK,
|
||||
*^4:*^GRV, *^7:*1, *LBRC:*2, *^LBRC:*3, *^RBRC:*4, *^9:*5, *^2:*6, *^8:*7, *^0:*8, *^EQL:*9, *RBRC:*0, *^1:*^5, *^3:*GRV, BSPC, INS, HOME,PGUP, NLCK, *PSLS:*^9, *PAST:*^0, *PMNS:*^4,
|
||||
TAB, *QUOT, *COMM, *DOT, P, Y, F, G, C, R, L, *SLSH, *EQL:*^6, *^BSLS, DEL, END, PGDN, *P7:^A, *P8:^B, *P9:^C,
|
||||
CAPS, A, O, E, U, I, D, H, T, N, S, *MINS, ENT, *P4:^D, *P5:^E, *P6:^F, *PPLS:*COMM,
|
||||
LSFT, *SCLN, Q, J, K, X, B, M, W, V, Z, RSFT, UP, *P1:*EQL, *P2:X, *P3:*^SCLN,
|
||||
LCTL, LGUI, LALT, SPC, RALT, RGUI, APP, RCTL, LEFT,DOWN,RGHT, *P0:*BSLS, *PDOT:*SCLN, PENT
|
||||
"###,
|
||||
],
|
||||
revert_default_key: None, // use revert_default_keys instead
|
||||
}
|
||||
//toml::from_str(&input)?
|
||||
match toml::from_str(&input) {
|
||||
Ok(toml) => Ok(toml),
|
||||
Err(_) => Err(Error::NotFound) // todo: something better
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
#![recursion_limit = "1000"]
|
||||
|
||||
pub const NAME: &'static str = env!("CARGO_PKG_NAME");
|
||||
pub const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
pub mod error;
|
||||
@ -20,8 +19,3 @@ pub use windows::*;
|
||||
mod linux;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use linux::*;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use macos::*;
|
||||
|
@ -2,14 +2,24 @@ use std::path::Path;
|
||||
use std::{mem, slice};
|
||||
use std::ffi::CString;
|
||||
use libc::c_int;
|
||||
use nix::{self, fcntl, unistd, ioctl_write_ptr, ioctl_none};
|
||||
use nix::{self, fcntl, unistd, errno::Errno, ioctl_write_ptr, ioctl_none};
|
||||
use nix::sys::stat;
|
||||
use crate::{Result, Device};
|
||||
//use uinput_sys::*;
|
||||
use crate::{Result as Res, Device};
|
||||
use std::collections::hash_map::Values;
|
||||
use std::os::raw::c_char;
|
||||
|
||||
use crate::linux::device::codes::*;
|
||||
|
||||
/*
|
||||
uin!(write ui_set_evbit with b'U', 100; c_int);
|
||||
uin!(write ui_set_keybit with b'U', 101; c_int);
|
||||
|
||||
ioctl!(none ui_dev_create with b'U', 1);
|
||||
|
||||
ioctl!(none ui_dev_destroy with b'U', 2);
|
||||
*/
|
||||
|
||||
ioctl_write_ptr!(ui_set_evbit, b'U', 100, c_int);
|
||||
ioctl_write_ptr!(ui_set_keybit, b'U', 101, c_int);
|
||||
ioctl_none!(ui_dev_create, b'U', 1);
|
||||
@ -48,7 +58,7 @@ pub struct Builder {
|
||||
|
||||
impl Builder {
|
||||
/// Create a builder from the specified path.
|
||||
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
|
||||
pub fn open<P: AsRef<Path>>(path: P) -> Res<Self> {
|
||||
Ok(Builder {
|
||||
fd: fcntl::open(path.as_ref(), fcntl::OFlag::O_WRONLY | fcntl::OFlag::O_NONBLOCK, stat::Mode::empty())?,
|
||||
def: unsafe { mem::zeroed() },
|
||||
@ -57,17 +67,17 @@ impl Builder {
|
||||
}
|
||||
|
||||
/// Create a builder from `/dev/uinput`.
|
||||
pub fn default() -> Result<Self> {
|
||||
pub fn default() -> Res<Self> {
|
||||
Builder::open("/dev/uinput")
|
||||
}
|
||||
|
||||
/// Set the name.
|
||||
pub fn name<T: AsRef<str>>(mut self, value: T) -> Result<Self> {
|
||||
pub fn name<T: AsRef<str>>(mut self, value: T) -> Res<Self> {
|
||||
let string = CString::new(value.as_ref())?;
|
||||
let bytes = string.as_bytes_with_nul();
|
||||
|
||||
if bytes.len() > UINPUT_MAX_NAME_SIZE as usize {
|
||||
Err(nix::Error::EINVAL)?;
|
||||
Err(nix::Error::from_errno(Errno::EINVAL))?;
|
||||
}
|
||||
|
||||
(&mut self.def.name)[..bytes.len()]
|
||||
@ -100,18 +110,168 @@ impl Builder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn event(mut self, key_codes: Values<&str, u16>) -> Result<Self> {
|
||||
pub fn event(mut self, key_codes: Values<&str, u16>) -> Res<Self> {
|
||||
self.abs = None;
|
||||
//let test_ev_key : c_int = EV_KEY as c_int;
|
||||
unsafe {
|
||||
//try!(Errno::result(ui_set_evbit(self.fd, EV_KEY)));
|
||||
//try!(Errno::result(ui_set_keybit(self.fd, KEY_H)));
|
||||
|
||||
//Errno::result(ui_set_evbit(self.fd, EV_KEY as *const c_int))?;
|
||||
|
||||
ui_set_evbit(self.fd, EV_KEY as *const c_int)?;
|
||||
|
||||
//ui_set_keybit(self.fd, KEY_H as *const c_int)?;
|
||||
for key_code in key_codes {
|
||||
ui_set_keybit(self.fd, *key_code as *const c_int)?;
|
||||
}
|
||||
//try!(ui_set_keybit(self.fd, &KEY_H));
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
/*
|
||||
/// Enable the given event.
|
||||
pub fn event<T: Into<Event>>(mut self, value: T) -> Res<Self> {
|
||||
self.abs = None;
|
||||
|
||||
match value.into() {
|
||||
Event::All => {
|
||||
try!(self.event(Event::Keyboard(event::Keyboard::All)))
|
||||
.event(Event::Controller(event::Controller::All))
|
||||
}
|
||||
|
||||
Event::Keyboard(value) => {
|
||||
match value {
|
||||
event::Keyboard::All => {
|
||||
let mut builder = self;
|
||||
|
||||
for item in event::keyboard::Key::iter_variants() {
|
||||
builder = try!(builder.event(item));
|
||||
}
|
||||
|
||||
for item in event::keyboard::KeyPad::iter_variants() {
|
||||
builder = try!(builder.event(item));
|
||||
}
|
||||
|
||||
for item in event::keyboard::Misc::iter_variants() {
|
||||
builder = try!(builder.event(item));
|
||||
}
|
||||
|
||||
for item in event::keyboard::InputAssist::iter_variants() {
|
||||
builder = try!(builder.event(item));
|
||||
}
|
||||
|
||||
for item in event::keyboard::Function::iter_variants() {
|
||||
builder = try!(builder.event(item));
|
||||
}
|
||||
|
||||
for item in event::keyboard::Braille::iter_variants() {
|
||||
builder = try!(builder.event(item));
|
||||
}
|
||||
|
||||
for item in event::keyboard::Numeric::iter_variants() {
|
||||
builder = try!(builder.event(item));
|
||||
}
|
||||
|
||||
for item in event::keyboard::TouchPad::iter_variants() {
|
||||
builder = try!(builder.event(item));
|
||||
}
|
||||
|
||||
for item in event::keyboard::Camera::iter_variants() {
|
||||
builder = try!(builder.event(item));
|
||||
}
|
||||
|
||||
for item in event::keyboard::Attendant::iter_variants() {
|
||||
builder = try!(builder.event(item));
|
||||
}
|
||||
|
||||
Ok(builder)
|
||||
}
|
||||
|
||||
value => {
|
||||
unsafe {
|
||||
try!(Errno::result(ui_set_evbit(self.fd, value.kind())));
|
||||
try!(Errno::result(ui_set_keybit(self.fd, value.code())));
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Event::Controller(value) => {
|
||||
match value {
|
||||
event::Controller::All => {
|
||||
let mut builder = self;
|
||||
|
||||
for item in event::controller::Misc::iter_variants() {
|
||||
builder = try!(builder.event(item));
|
||||
}
|
||||
|
||||
for item in event::controller::Mouse::iter_variants() {
|
||||
builder = try!(builder.event(item));
|
||||
}
|
||||
|
||||
for item in event::controller::JoyStick::iter_variants() {
|
||||
builder = try!(builder.event(item));
|
||||
}
|
||||
|
||||
for item in event::controller::GamePad::iter_variants() {
|
||||
builder = try!(builder.event(item));
|
||||
}
|
||||
|
||||
for item in event::controller::Digi::iter_variants() {
|
||||
builder = try!(builder.event(item));
|
||||
}
|
||||
|
||||
for item in event::controller::Wheel::iter_variants() {
|
||||
builder = try!(builder.event(item));
|
||||
}
|
||||
|
||||
for item in event::controller::DPad::iter_variants() {
|
||||
builder = try!(builder.event(item));
|
||||
}
|
||||
|
||||
for item in event::controller::TriggerHappy::iter_variants() {
|
||||
builder = try!(builder.event(item));
|
||||
}
|
||||
|
||||
Ok(builder)
|
||||
}
|
||||
|
||||
value => {
|
||||
unsafe {
|
||||
try!(Errno::result(ui_set_evbit(self.fd, value.kind())));
|
||||
try!(Errno::result(ui_set_keybit(self.fd, value.code())));
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Event::Relative(value) => {
|
||||
unsafe {
|
||||
try!(Errno::result(ui_set_evbit(self.fd, value.kind())));
|
||||
try!(Errno::result(ui_set_relbit(self.fd, value.code())));
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
Event::Absolute(value) => {
|
||||
unsafe {
|
||||
try!(Errno::result(ui_set_evbit(self.fd, value.kind())));
|
||||
try!(Errno::result(ui_set_absbit(self.fd, value.code())));
|
||||
}
|
||||
|
||||
self.abs = Some(value.code());
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
/// Set the maximum value for the previously enabled absolute event.
|
||||
pub fn max(mut self, value: i32) -> Self {
|
||||
self.def.absmax[self.abs.unwrap() as usize] = value;
|
||||
@ -137,12 +297,14 @@ impl Builder {
|
||||
}
|
||||
|
||||
/// Create the defined device.
|
||||
pub fn create(self) -> Result<Device> {
|
||||
pub fn create(self) -> Res<Device> {
|
||||
unsafe {
|
||||
let ptr = &self.def as *const _ as *const u8;
|
||||
let size = mem::size_of_val(&self.def);
|
||||
|
||||
unistd::write(self.fd, slice::from_raw_parts(ptr, size))?;
|
||||
//todo: try!(Errno::result(ui_dev_create(self.fd)));
|
||||
// try1: Errno::result(ui_dev_create(self.fd)).unwrap();
|
||||
ui_dev_create(self.fd)?;
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::{mem, ptr, slice};
|
||||
use libc::{timeval, gettimeofday, input_event, c_int};
|
||||
use nix::{unistd, ioctl_none};
|
||||
use crate::Result;
|
||||
use crate::{Result as Res};
|
||||
|
||||
use crate::linux::device::codes::*;
|
||||
|
||||
@ -21,7 +21,7 @@ impl Device {
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn write(&self, kind: c_int, code: c_int, value: c_int) -> Result<()> {
|
||||
pub fn write(&self, kind: c_int, code: c_int, value: c_int) -> Res<()> {
|
||||
let mut event = input_event {
|
||||
time: timeval { tv_sec: 0, tv_usec: 0 },
|
||||
type_: kind as u16,
|
||||
@ -33,7 +33,7 @@ impl Device {
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn write_event(&self, event: &mut input_event) -> Result<()> {
|
||||
pub fn write_event(&self, event: &mut input_event) -> Res<()> {
|
||||
unsafe {
|
||||
gettimeofday(&mut event.time, ptr::null_mut());
|
||||
|
||||
@ -47,33 +47,35 @@ impl Device {
|
||||
}
|
||||
|
||||
/// Synchronize the device.
|
||||
pub fn synchronize(&self) -> Result<()> {
|
||||
pub fn synchronize(&self) -> Res<()> {
|
||||
self.write(EV_SYN, SYN_REPORT, 0)
|
||||
}
|
||||
|
||||
/// Send an event.
|
||||
pub fn send(&self, kind: c_int, code: c_int, value: i32) -> Result<()> {
|
||||
pub fn send(&self, kind: c_int, code: c_int, value: i32) -> Res<()> {
|
||||
self.write(kind, code, value)
|
||||
}
|
||||
|
||||
/// Send a press event.
|
||||
pub fn press(&self, kind: c_int, code: c_int) -> Result<()> {
|
||||
pub fn press(&self, kind: c_int, code: c_int) -> Res<()> {
|
||||
self.write(kind, code, 1)
|
||||
}
|
||||
|
||||
/// Send a release event.
|
||||
pub fn release(&self, kind: c_int, code: c_int) -> Result<()> {
|
||||
pub fn release(&self, kind: c_int, code: c_int) -> Res<()> {
|
||||
self.write(kind, code, 0)
|
||||
}
|
||||
|
||||
/// Send a press and release event.
|
||||
pub fn click(&self, kind: c_int, code: c_int) -> Result<()> {
|
||||
pub fn click(&self, kind: c_int, code: c_int) -> Res<()> {
|
||||
self.press(kind, code)?;
|
||||
self.release(kind, code)
|
||||
self.release(kind, code)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a relative or absolute positioning event.
|
||||
pub fn position(&self, kind: c_int, code: c_int, value: i32) -> Result<()> {
|
||||
pub fn position(&self, kind: c_int, code: c_int, value: i32) -> Res<()> {
|
||||
self.write(kind, code, value)
|
||||
}
|
||||
}
|
||||
@ -81,8 +83,7 @@ impl Device {
|
||||
impl Drop for Device {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
// ignore error here so as to not panic in a drop
|
||||
ui_dev_destroy(self.fd).ok();
|
||||
ui_dev_destroy(self.fd).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,148 +3,53 @@ use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use libc::{input_event, c_int};
|
||||
use nix::{ioctl_write_ptr, ioctl_read_buf};
|
||||
|
||||
#[cfg(feature = "epoll_inotify")]
|
||||
use std::os::unix::prelude::RawFd;
|
||||
|
||||
use nix::ioctl_write_ptr;
|
||||
use crate::{Error,Result};
|
||||
use crate::linux::{EV_KEY, KEY_MAX, NAME, KEY_W, KEY_A, KEY_S, KEY_D, BTN_LEFT};
|
||||
|
||||
ioctl_write_ptr!(eviocgrab, b'E', 0x90, c_int);
|
||||
ioctl_read_buf!(eviocgname, b'E', 0x06, u8);
|
||||
ioctl_read_buf!(eviocgbit, b'E', 0x20, u8);
|
||||
ioctl_read_buf!(eviocgbit_ev_key, b'E', 0x20 + EV_KEY, u8);
|
||||
|
||||
const SIZE_OF_INPUT_EVENT: usize = mem::size_of::<input_event>();
|
||||
|
||||
pub struct InputDevice {
|
||||
device_file: File,
|
||||
grabbed: bool,
|
||||
#[cfg(feature = "epoll_inotify")]
|
||||
epoll_fd: Option<RawFd>,
|
||||
buf: [u8; SIZE_OF_INPUT_EVENT],
|
||||
}
|
||||
|
||||
impl InputDevice {
|
||||
pub fn open<P: AsRef<std::path::Path>>(path: P) -> Result<Self> {
|
||||
pub fn open(device_file: &str) -> Result<Self> {
|
||||
let device_file = File::open(device_file)?;
|
||||
Ok(InputDevice {
|
||||
device_file: File::open(path)?,
|
||||
grabbed: false,
|
||||
#[cfg(feature = "epoll_inotify")]
|
||||
epoll_fd: None,
|
||||
device_file: device_file,
|
||||
buf: [0u8; SIZE_OF_INPUT_EVENT],
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_input_event_buf() -> [u8; SIZE_OF_INPUT_EVENT] {
|
||||
[0u8; SIZE_OF_INPUT_EVENT]
|
||||
}
|
||||
|
||||
pub fn read_event(&mut self, buf: &mut [u8; SIZE_OF_INPUT_EVENT]) -> Result<input_event> {
|
||||
let num_bytes = self.device_file.read(buf)?;
|
||||
pub fn read_event(&mut self) -> Result<input_event> {
|
||||
let num_bytes = self.device_file.read(&mut self.buf)?;
|
||||
if num_bytes != SIZE_OF_INPUT_EVENT {
|
||||
return Err(Error::ShortRead);
|
||||
}
|
||||
let event: input_event = unsafe { mem::transmute(*buf) };
|
||||
let event: input_event = unsafe { mem::transmute(self.buf) };
|
||||
Ok(event)
|
||||
}
|
||||
|
||||
pub fn valid_keyboard_device(self) -> Result<Self> {
|
||||
use std::os::unix::fs::FileTypeExt;
|
||||
|
||||
// must be a character device
|
||||
if !self.device_file.metadata()?.file_type().is_char_device() {
|
||||
return Err(Error::NotAKeyboard);
|
||||
}
|
||||
|
||||
let raw_fd = self.device_file.as_raw_fd();
|
||||
|
||||
// does it support EV_KEY
|
||||
let mut evbit = [0u8; 8];
|
||||
unsafe {
|
||||
eviocgbit(raw_fd, &mut evbit)?;
|
||||
};
|
||||
let evbit = u64::from_ne_bytes(evbit);
|
||||
if (evbit & (1 << EV_KEY)) == 0 {
|
||||
return Err(Error::NotAKeyboard);
|
||||
}
|
||||
|
||||
// does it support all keys WASD and *not* LEFT mouse button ? (yes this is fairly random but probably good enough, could make configuration probably)
|
||||
let mut key_bits = [0u8; (KEY_MAX as usize / 8) + 1];
|
||||
unsafe {
|
||||
eviocgbit_ev_key(raw_fd, &mut key_bits)?;
|
||||
};
|
||||
let key_unsupported = |key : c_int| (key_bits[key as usize / 8] & (1 << (key % 8))) == 0;
|
||||
if key_unsupported(KEY_W) || key_unsupported(KEY_A) || key_unsupported(KEY_S) || key_unsupported(KEY_D) || !key_unsupported(BTN_LEFT) {
|
||||
return Err(Error::NotAKeyboard);
|
||||
}
|
||||
|
||||
// is it another running copy of rusty-keys ?
|
||||
let mut name = [0u8; NAME.len()];
|
||||
unsafe {
|
||||
eviocgname(raw_fd, &mut name)?
|
||||
};
|
||||
// exclude anything starting with "Yubico" also
|
||||
if NAME.as_bytes() == &name || "Yubico".as_bytes() == &name[0..6] {
|
||||
return Err(Error::NotAKeyboard);
|
||||
}
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
pub fn grab(mut self) -> Result<Self> {
|
||||
pub fn grab(&mut self) -> Result<()> {
|
||||
unsafe {
|
||||
eviocgrab(self.device_file.as_raw_fd(), 1 as *const c_int)?;
|
||||
}
|
||||
self.grabbed = true;
|
||||
Ok(self)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn release(&mut self) -> Result<()> {
|
||||
if self.grabbed {
|
||||
unsafe {
|
||||
eviocgrab(self.device_file.as_raw_fd(), 0 as *const c_int)?;
|
||||
}
|
||||
self.grabbed = false;
|
||||
unsafe {
|
||||
eviocgrab(self.device_file.as_raw_fd(), 0 as *const c_int)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "epoll_inotify")]
|
||||
pub fn epoll_add(mut self, epoll_fd: RawFd, data: u64) -> Result<Self> {
|
||||
use nix::fcntl::{OFlag, fcntl, FcntlArg};
|
||||
|
||||
if None != self.epoll_fd {
|
||||
return Err(Error::EpollAlreadyAdded);
|
||||
}
|
||||
let raw_fd = self.device_file.as_raw_fd();
|
||||
let flags = unsafe {
|
||||
// https://github.com/nix-rust/nix/issues/1102
|
||||
OFlag::from_bits_unchecked(fcntl(raw_fd, FcntlArg::F_GETFL)?)
|
||||
};
|
||||
fcntl(raw_fd, FcntlArg::F_SETFL(flags | OFlag::O_NONBLOCK))?;
|
||||
|
||||
let epoll_event = epoll::Event::new(epoll::Events::EPOLLIN | epoll::Events::EPOLLET, data);
|
||||
epoll::ctl(epoll_fd, epoll::ControlOptions::EPOLL_CTL_ADD, raw_fd, epoll_event)?;
|
||||
self.epoll_fd = Some(epoll_fd);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
#[cfg(feature = "epoll_inotify")]
|
||||
pub fn epoll_del(&mut self) -> Result<&mut Self> {
|
||||
if let Some(epoll_fd) = self.epoll_fd {
|
||||
// set this to None first, if we end up returning an Err early, we can't do anything else anyway...
|
||||
self.epoll_fd = None;
|
||||
let empty_event = epoll::Event::new(epoll::Events::empty(), 0);
|
||||
epoll::ctl(epoll_fd, epoll::ControlOptions::EPOLL_CTL_DEL, self.device_file.as_raw_fd(), empty_event)?;
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for InputDevice {
|
||||
fn drop(&mut self) {
|
||||
// ignore any errors here, what could we do anyhow?
|
||||
self.release().ok();
|
||||
#[cfg(feature = "epoll_inotify")]
|
||||
self.epoll_del().ok();
|
||||
self.release().ok(); // ignore any errors here, what could we do anyhow?
|
||||
}
|
||||
}
|
||||
|
245
src/linux/mod.rs
245
src/linux/mod.rs
@ -2,17 +2,26 @@
|
||||
use crate::*;
|
||||
|
||||
use crate::linux::device::codes::*;
|
||||
use std::path::Path;
|
||||
|
||||
pub mod device;
|
||||
pub use device::{Device,InputDevice, Builder};
|
||||
pub use device::{Device,InputDevice};
|
||||
|
||||
/// Open the default uinput device.
|
||||
pub fn default() -> Result<device::Builder> {
|
||||
device::Builder::default()
|
||||
}
|
||||
|
||||
/// Open the specified uinput device.
|
||||
pub fn open<P: AsRef<Path>>(path: P) -> Result<device::Builder> {
|
||||
device::Builder::open(path)
|
||||
}
|
||||
|
||||
use libc::input_event;
|
||||
use std::process::exit;
|
||||
use std::env;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[cfg(feature = "epoll_inotify")]
|
||||
const INPUT_FOLDER: &str = "/dev/input/";
|
||||
use std::{env, thread};
|
||||
use std::sync::mpsc;
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
// 1 is down, 0 is up
|
||||
const DOWN: i32 = 1;
|
||||
@ -20,6 +29,13 @@ const UP: i32 = 0;
|
||||
|
||||
use getopts::Options;
|
||||
|
||||
use inotify::{
|
||||
EventMask,
|
||||
Inotify,
|
||||
WatchMask,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
const EV_KEY_U16: u16 = EV_KEY as u16;
|
||||
|
||||
type LinuxKeyMaps = KeyMaps<Device, u16, input_event>;
|
||||
@ -83,6 +99,8 @@ impl Keyboard<u16, input_event> for Device {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Config {
|
||||
device_files: Vec<String>,
|
||||
@ -102,133 +120,88 @@ pub fn main_res() -> Result<()> {
|
||||
let key_map = key_map();
|
||||
//println!("key_map: {:?}", key_map);
|
||||
|
||||
let device = Builder::open("/dev/uinput")
|
||||
.or_else(|_| Builder::open("/dev/input/uinput"))
|
||||
.or_else(|_| Builder::default())?
|
||||
.name(NAME)?
|
||||
let device = open("/dev/uinput")
|
||||
.or_else(|_| open("/dev/input/uinput"))
|
||||
.or_else(|_| default())?
|
||||
.name("rusty-keys")?
|
||||
.event(key_map.values())?
|
||||
.create()?;
|
||||
|
||||
#[cfg(not(feature = "toml_serde"))]
|
||||
let mut key_map = LinuxKeyMaps::new(&key_map, KeymapConfig::default());
|
||||
#[cfg(feature = "toml_serde")]
|
||||
let mut key_map = LinuxKeyMaps::from_cfg(&key_map, &config.config_file);
|
||||
//println!("keymaps: {:?}", keymaps);
|
||||
|
||||
let mut input_event_buf = InputDevice::new_input_event_buf();
|
||||
|
||||
if config.device_files.len() == 1 {
|
||||
// shortcut, don't bother with epoll
|
||||
let mut input_device = InputDevice::open(&config.device_files[0])?.grab()?;
|
||||
// shortcut, don't bother with threads
|
||||
let mut input_device = InputDevice::open(&config.device_files[0])?;
|
||||
input_device.grab()?;
|
||||
|
||||
loop {
|
||||
let event = input_device.read_event(&mut input_event_buf)?;
|
||||
let event = input_device.read_event()?;
|
||||
send_event(&mut key_map, event, &device)?
|
||||
}
|
||||
} else {
|
||||
#[cfg(not(feature = "epoll_inotify"))]
|
||||
panic!("without epoll_inotify feature, only exactly 1 device is supported");
|
||||
// start up some intra thread communication
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
#[cfg(feature = "epoll_inotify")]
|
||||
{
|
||||
use inotify::{Inotify, WatchMask};
|
||||
if config.device_files.len() > 0 {
|
||||
// we only want to operate on device files sent in then quit
|
||||
for device_file in config.device_files.iter() {
|
||||
let device_file = device_file.clone();
|
||||
let tx = tx.clone();
|
||||
thread::spawn(move || {
|
||||
let ret = spawn_map_thread(tx, &device_file);
|
||||
if let Err(e) = ret {
|
||||
println!("mapping for {} ended due to error: {}", device_file, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
let tx = tx.clone();
|
||||
thread::spawn(move || {
|
||||
// we want to wait forever starting new threads for any new keyboard devices
|
||||
let mut inotify = Inotify::init().expect("Failed to initialize inotify");
|
||||
|
||||
let epoll_fd = epoll::create(true)?;
|
||||
const INOTIFY_DATA: u64 = u64::max_value();
|
||||
inotify.add_watch("/dev/input/", WatchMask::CREATE).expect("Failed to add inotify watch");
|
||||
|
||||
let (device_files, mut inotify) = if config.device_files.len() > 0 {
|
||||
// we operate on exactly the devices sent in and never watch for new devices
|
||||
(config.device_files.iter().map(|device_file| InputDevice::open(&device_file).expect("device_file does not exist!")).collect(), None)
|
||||
} else {
|
||||
use std::os::unix::io::AsRawFd;
|
||||
// we want to wait forever starting new threads for any new keyboard devices
|
||||
// there is a slight race condition here, if a keyboard is plugged in between the time we
|
||||
// enumerate the devices and set up the inotify watch, we'll miss it, doing it the other way
|
||||
// can bring duplicates though, todo: think about this...
|
||||
let device_files = get_keyboard_devices();
|
||||
let mut inotify = Inotify::init()?;
|
||||
inotify.add_watch(INPUT_FOLDER, WatchMask::CREATE)?;
|
||||
let epoll_event = epoll::Event::new(epoll::Events::EPOLLIN | epoll::Events::EPOLLET, INOTIFY_DATA);
|
||||
epoll::ctl(epoll_fd, epoll::ControlOptions::EPOLL_CTL_ADD, inotify.as_raw_fd(), epoll_event)?;
|
||||
(device_files, Some(inotify))
|
||||
};
|
||||
let mut input_devices = Vec::with_capacity(device_files.len());
|
||||
for (idx, device_file) in device_files.into_iter().enumerate() {
|
||||
input_devices.push(Some(device_file.grab()?.epoll_add(epoll_fd, idx as u64)?));
|
||||
let device_files = get_keyboard_device_filenames();
|
||||
println!("Detected devices: {:?}", device_files);
|
||||
for device_file in device_files.iter() {
|
||||
inotify_spawn_thread(&tx, device_file);
|
||||
}
|
||||
|
||||
let mut epoll_buf = [epoll::Event::new(epoll::Events::empty(), 0); 4];
|
||||
let mut inotify_buf = [0u8; 256];
|
||||
|
||||
let mut buffer = [0u8; 4096];
|
||||
loop {
|
||||
let num_events = epoll::wait(epoll_fd, -1, &mut epoll_buf)?;
|
||||
for event in &epoll_buf[0..num_events] {
|
||||
let idx = event.data as usize;
|
||||
if let Some(Some(input_device)) = &mut input_devices.get_mut(idx) {
|
||||
loop {
|
||||
match input_device.read_event(&mut input_event_buf) {
|
||||
Ok(event) => {
|
||||
//println!("input event: {:?}", event);
|
||||
send_event(&mut key_map, event, &device)?
|
||||
}
|
||||
Err(err) => {
|
||||
if let Error::Io(ref err) = err {
|
||||
if err.kind() == std::io::ErrorKind::WouldBlock {
|
||||
// go back to epoll event loop
|
||||
break;
|
||||
}
|
||||
}
|
||||
// otherwise it's some other error, don't read anything from this again
|
||||
println!("input err: {:?}", err);
|
||||
// remove it from input_devices and drop it
|
||||
let _ = std::mem::replace(&mut input_devices[idx], None);
|
||||
if inotify.is_none() {
|
||||
// if we aren't watching with inotify, and the last device is removed (Vec only has None's in it), exit the program
|
||||
if input_devices.iter().all(|id| id.is_none()) {
|
||||
println!("last device went away, exiting...");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if event.data == INOTIFY_DATA {
|
||||
if let Some(inotify) = &mut inotify {
|
||||
for event in inotify.read_events(&mut inotify_buf)? {
|
||||
if let Some(device_file) = event.name.and_then(|name| name.to_str()) {
|
||||
// check if this is an eligible keyboard device
|
||||
let mut path = std::path::PathBuf::new();
|
||||
path.push(INPUT_FOLDER);
|
||||
path.push(device_file);
|
||||
|
||||
if let Ok(input_device) = InputDevice::open(path).and_then(|id| id.valid_keyboard_device()) {
|
||||
println!("starting mapping for new keyboard: {}", device_file);
|
||||
|
||||
let idx = input_devices.iter().position(|id| id.is_none()).unwrap_or(input_devices.len());
|
||||
|
||||
let input_device = input_device.grab()?.epoll_add(epoll_fd, idx as u64)?;
|
||||
|
||||
if idx == input_devices.len() {
|
||||
input_devices.push(Some(input_device));
|
||||
} else {
|
||||
// simply replacing None here
|
||||
let _ = std::mem::replace(&mut input_devices[idx], Some(input_device));
|
||||
}
|
||||
}
|
||||
let events = inotify.read_events_blocking(&mut buffer);
|
||||
|
||||
if let Ok(events) = events {
|
||||
for event in events {
|
||||
if !event.mask.contains(EventMask::ISDIR) {
|
||||
if let Some(device_file) = event.name.and_then(|name|name.to_str()) {
|
||||
// check if this is an eligible keyboard device
|
||||
let device_files = get_keyboard_device_filenames();
|
||||
if !device_files.contains(&device_file.to_string()) {
|
||||
continue;
|
||||
}
|
||||
println!("starting mapping thread for: {}", device_file);
|
||||
inotify_spawn_thread(&tx, device_file.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
drop(tx); // drop our last one, so when the threads finish, everything stops
|
||||
// process all events
|
||||
for event in rx {
|
||||
send_event(&mut key_map, event, &device)?
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_event(key_map: &mut LinuxKeyMaps, mut event: input_event, device: &Device) -> Result<()> {
|
||||
if event.type_ == EV_KEY_U16 {
|
||||
// println!("type: {} code: {:?} value: {:?}", event.type_, event.code(), event.value());
|
||||
key_map.send_event(&mut event, &device)?
|
||||
} else {
|
||||
device.write_event(&mut event)?
|
||||
@ -236,6 +209,28 @@ fn send_event(key_map: &mut LinuxKeyMaps, mut event: input_event, device: &Devic
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn inotify_spawn_thread(tx: &Sender<input_event>, device_file: &str) {
|
||||
let mut filename = "/dev/input/".to_string();
|
||||
filename.push_str(&device_file);
|
||||
let tx = tx.clone();
|
||||
thread::spawn(move || {
|
||||
let ret = spawn_map_thread(tx, &filename);
|
||||
if let Err(e) = ret {
|
||||
println!("mapping for {} ended due to error: {}", filename, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_map_thread(tx: Sender<input_event>, device_file: &str) -> Result<()> {
|
||||
let mut input_device = InputDevice::open(device_file)?;
|
||||
input_device.grab()?;
|
||||
|
||||
loop {
|
||||
let event = input_device.read_event()?;
|
||||
tx.send(event)?
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_args() -> Config {
|
||||
fn print_usage(program: &str, opts: Options) {
|
||||
let brief = format!("Usage: {} [options] [device_files...]", program);
|
||||
@ -261,7 +256,7 @@ fn parse_args() -> Config {
|
||||
}
|
||||
|
||||
if matches.opt_present("v") {
|
||||
println!("{} {}", NAME, VERSION);
|
||||
println!("rusty-keys {}", VERSION);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
@ -270,19 +265,37 @@ fn parse_args() -> Config {
|
||||
Config::new(matches.free, config_file)
|
||||
}
|
||||
|
||||
#[cfg(feature = "epoll_inotify")]
|
||||
fn get_keyboard_devices() -> Vec<InputDevice> {
|
||||
let mut res = Vec::new();
|
||||
if let Ok(entries) = std::fs::read_dir(INPUT_FOLDER) {
|
||||
for entry in entries {
|
||||
if let Ok(entry) = entry {
|
||||
if let Ok(input_device) = InputDevice::open(entry.path()).and_then(|id|id.valid_keyboard_device()) {
|
||||
res.push(input_device);
|
||||
// Detects and returns the name of the keyboard device file. This function uses
|
||||
// the fact that all device information is shown in /proc/bus/input/devices and
|
||||
// the keyboard device file should always have an EV of 120013
|
||||
// grep -E 'Handlers|EV' /proc/bus/input/devices | grep -B1 120013 | grep -Eo event[0-9]+
|
||||
fn get_keyboard_device_filenames() -> Vec<String> {
|
||||
use std::io::BufReader;
|
||||
use std::io::prelude::*;
|
||||
use std::fs::File;
|
||||
|
||||
let f = File::open("/proc/bus/input/devices");
|
||||
if f.is_err() {
|
||||
return Vec::new();
|
||||
}
|
||||
let f = BufReader::new(f.unwrap());
|
||||
let mut filename = None;
|
||||
let mut filenames = Vec::new();
|
||||
for line in f.lines() {
|
||||
if let Ok(line) = line {
|
||||
if line.starts_with("H: Handlers=") {
|
||||
if let Some(event_index) = line.find("event") {
|
||||
let last_index = line[event_index..line.len()-1].find(" ").and_then(|i| Some(i + event_index)).unwrap_or(line.len() - 1);
|
||||
filename = Some(line[event_index..last_index].to_owned());
|
||||
}
|
||||
} else if line.starts_with("B: EV=") && line.contains("120013") {
|
||||
if let Some(ref filename) = filename {
|
||||
filenames.push(filename.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
res
|
||||
filenames
|
||||
}
|
||||
|
||||
pub fn key_map() -> HashMap<&'static str, u16> {
|
||||
@ -593,7 +606,5 @@ pub fn key_map() -> HashMap<&'static str, u16> {
|
||||
("P0", KEY_KP0),
|
||||
("PDOT", KEY_KPDOT),
|
||||
("PENT", KEY_KPENTER),
|
||||
("TOUCHPAD_TOGGLE", KEY_TOUCHPAD_TOGGLE),
|
||||
].iter().cloned().map(|(m, v)| (m, v as u16)).collect()
|
||||
}
|
||||
|
||||
}
|
@ -1,568 +0,0 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
use core_graphics::event::CGKeyCode;
|
||||
|
||||
pub const KEY_RESERVED: CGKeyCode = 0x31;
|
||||
pub const KEY_ESC: CGKeyCode = 0x00;
|
||||
pub const KEY_1: CGKeyCode = 0x0012;
|
||||
pub const KEY_2: CGKeyCode = 0x0013;
|
||||
pub const KEY_3: CGKeyCode = 0x0014;
|
||||
pub const KEY_4: CGKeyCode = 0x0015;
|
||||
pub const KEY_5: CGKeyCode = 0x0017;
|
||||
pub const KEY_6: CGKeyCode = 0x0016;
|
||||
pub const KEY_7: CGKeyCode = 0x001A;
|
||||
pub const KEY_8: CGKeyCode = 0x001C;
|
||||
pub const KEY_9: CGKeyCode = 0x0019;
|
||||
pub const KEY_10: CGKeyCode = 0x001D;
|
||||
pub const KEY_MINUS: CGKeyCode = 0x001B;
|
||||
pub const KEY_EQUAL: CGKeyCode = 0x0018;
|
||||
pub const KEY_BACKSPACE: CGKeyCode = 0x0033;
|
||||
pub const KEY_TAB: CGKeyCode = 0x0030;
|
||||
pub const KEY_Q: CGKeyCode = 0x000C;
|
||||
pub const KEY_W: CGKeyCode = 0x000D;
|
||||
pub const KEY_E: CGKeyCode = 0x000E;
|
||||
pub const KEY_R: CGKeyCode = 0x000F;
|
||||
pub const KEY_T: CGKeyCode = 0x0011;
|
||||
pub const KEY_Y: CGKeyCode = 0x0010;
|
||||
pub const KEY_U: CGKeyCode = 0x0020;
|
||||
pub const KEY_I: CGKeyCode = 0x0022;
|
||||
pub const KEY_O: CGKeyCode = 0x001F;
|
||||
pub const KEY_P: CGKeyCode = 0x0023;
|
||||
pub const KEY_LEFTBRACE: CGKeyCode = 0x0021;
|
||||
pub const KEY_RIGHTBRACE: CGKeyCode = 0x001E;
|
||||
pub const KEY_ENTER: CGKeyCode = 0x0024;
|
||||
pub const KEY_LEFTCTRL: CGKeyCode = 0x003B;
|
||||
pub const KEY_A: CGKeyCode = 0x0000;
|
||||
pub const KEY_S: CGKeyCode = 0x0001;
|
||||
pub const KEY_D: CGKeyCode = 0x0002;
|
||||
pub const KEY_F: CGKeyCode = 0x0003;
|
||||
pub const KEY_G: CGKeyCode = 0x0005;
|
||||
pub const KEY_H: CGKeyCode = 0x0004;
|
||||
pub const KEY_J: CGKeyCode = 0x0026;
|
||||
pub const KEY_K: CGKeyCode = 0x0028;
|
||||
pub const KEY_L: CGKeyCode = 0x0025;
|
||||
pub const KEY_SEMICOLON: CGKeyCode = 0x0029;
|
||||
pub const KEY_APOSTROPHE: CGKeyCode = 0x0027;
|
||||
pub const KEY_GRAVE: CGKeyCode = 0x0032;
|
||||
pub const KEY_LEFTSHIFT: CGKeyCode = 0x0038;
|
||||
pub const KEY_BACKSLASH: CGKeyCode = 0x002A;
|
||||
pub const KEY_Z: CGKeyCode = 0x0006;
|
||||
pub const KEY_X: CGKeyCode = 0x0007;
|
||||
pub const KEY_C: CGKeyCode = 0x0008;
|
||||
pub const KEY_V: CGKeyCode = 0x0009;
|
||||
pub const KEY_B: CGKeyCode = 0x000B;
|
||||
pub const KEY_N: CGKeyCode = 0x002D;
|
||||
pub const KEY_M: CGKeyCode = 0x002E;
|
||||
pub const KEY_COMMA: CGKeyCode = 0x002B;
|
||||
pub const KEY_DOT: CGKeyCode = 0x002F;
|
||||
pub const KEY_SLASH: CGKeyCode = 0x002C;
|
||||
pub const KEY_RIGHTSHIFT: CGKeyCode = 0x003C;
|
||||
pub const KEY_KPASTERISK: CGKeyCode = 0x0043;
|
||||
pub const KEY_LEFTALT: CGKeyCode = 0x003A;
|
||||
pub const KEY_SPACE: CGKeyCode = 0x0031;
|
||||
pub const KEY_CAPSLOCK: CGKeyCode = 0x0039;
|
||||
pub const KEY_F1: CGKeyCode = 0x007A;
|
||||
pub const KEY_F2: CGKeyCode = 0x0078;
|
||||
pub const KEY_F3: CGKeyCode = 0x0063;
|
||||
pub const KEY_F4: CGKeyCode = 0x0076;
|
||||
pub const KEY_F5: CGKeyCode = 0x0060;
|
||||
pub const KEY_F6: CGKeyCode = 0x0061;
|
||||
pub const KEY_F7: CGKeyCode = 0x0062;
|
||||
pub const KEY_F8: CGKeyCode = 0x0064;
|
||||
pub const KEY_F9: CGKeyCode = 0x0065;
|
||||
pub const KEY_F10: CGKeyCode = 0x006D;
|
||||
pub const KEY_NUMLOCK: CGKeyCode = 0x0047;
|
||||
pub const KEY_SCROLLLOCK: CGKeyCode = 0x006B;
|
||||
pub const KEY_KP7: CGKeyCode = 0x0059;
|
||||
pub const KEY_KP8: CGKeyCode = 0x005B;
|
||||
pub const KEY_KP9: CGKeyCode = 0x005C;
|
||||
pub const KEY_KPMINUS: CGKeyCode = 0x004E;
|
||||
pub const KEY_KP4: CGKeyCode = 0x0056;
|
||||
pub const KEY_KP5: CGKeyCode = 0x0057;
|
||||
pub const KEY_KP6: CGKeyCode = 0x0058;
|
||||
pub const KEY_KPPLUS: CGKeyCode = 0x0045;
|
||||
pub const KEY_KP1: CGKeyCode = 0x0053;
|
||||
pub const KEY_KP2: CGKeyCode = 0x0054;
|
||||
pub const KEY_KP3: CGKeyCode = 0x0055;
|
||||
pub const KEY_KP0: CGKeyCode = 0x0052; // https://code-with-me.jetbrains.com/UCsz0dzSd1QAbmOi8g0V3w#p=IC&fp=6298EDAF97FA62E9897E2556D1A6631FB66974568C7252E696472EE85078E8A0
|
||||
pub const KEY_KPDOT: CGKeyCode = 0x0041;
|
||||
// pub const KEY_ZENKAKUHANKAKU: CGKeyCode = NOT_FOUND;
|
||||
pub const KEY_102ND: CGKeyCode = 0x000A;
|
||||
pub const KEY_F11: CGKeyCode = 0x0067;
|
||||
pub const KEY_F12: CGKeyCode = 0x006F;
|
||||
// pub const KEY_RO: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_KATAKANA: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_HIRAGANA: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_HENKAN: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_KATAKANAHIRAGANA: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_MUHENKAN: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_KPJPCOMMA: CGKeyCode = NOT_FOUND; // https://code-with-me.jetbrains.com/Mf59EFUeJZQ2mpGCCCjNWw#p=IC&fp=6298EDAF97FA62E9897E2556D1A6631FB66974568C7252E696472EE85078E8A0
|
||||
pub const KEY_KPENTER: CGKeyCode = 0x004C;
|
||||
pub const KEY_RIGHTCTRL: CGKeyCode = 0x003E;
|
||||
pub const KEY_KPSLASH: CGKeyCode = 0x004B;
|
||||
pub const KEY_SYSRQ: CGKeyCode = 0x0069;
|
||||
pub const KEY_RIGHTALT: CGKeyCode = 0x003D;
|
||||
pub const KEY_LINEFEED: CGKeyCode = 0x0071;
|
||||
pub const KEY_HOME: CGKeyCode = 0x0073;
|
||||
pub const KEY_UP: CGKeyCode = 0x007E;
|
||||
pub const KEY_PAGEUP: CGKeyCode = 0x0074;
|
||||
pub const KEY_LEFT: CGKeyCode = 0x007B;
|
||||
pub const KEY_RIGHT: CGKeyCode = 0x007C;
|
||||
pub const KEY_END: CGKeyCode = 0x0077;
|
||||
pub const KEY_DOWN: CGKeyCode = 0x007D;
|
||||
pub const KEY_PAGEDOWN: CGKeyCode = 0x0079;
|
||||
pub const KEY_INSERT: CGKeyCode = 0x0072;
|
||||
pub const KEY_DELETE: CGKeyCode = 0x0075;
|
||||
// pub const KEY_MACRO: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_MUTE: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_VOLUMEDOWN: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_VOLUMEUP: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_POWER: CGKeyCode = NOT_FOUND;
|
||||
pub const KEY_KPEQUAL: CGKeyCode = 0x0069;
|
||||
// pub const KEY_KPPLUSMINUS: CGKeyCode = NOT_FOUND;
|
||||
pub const KEY_PAUSE: CGKeyCode = KEY_LINEFEED;
|
||||
pub const KEY_SCALE: CGKeyCode = 0x0047;
|
||||
pub const KEY_KPCOMMA: CGKeyCode = 0x0036;
|
||||
// pub const KEY_HANGEUL: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_HANGUEL: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_HANGEUL: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_HANJA: CGKeyCode = NOT_FOUND;
|
||||
//pub const KEY_YEN: CGKeyCode = NOT_FOUND;
|
||||
pub const KEY_LEFTMETA: CGKeyCode = 0x0037;
|
||||
pub const KEY_RIGHTMETA: CGKeyCode = 0x0036;
|
||||
pub const KEY_COMPOSE: CGKeyCode = 0x006E;
|
||||
// pub const KEY_STOP: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_AGAIN: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_PROPS: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_UNDO: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_FRONT: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_COPY: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_OPEN: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_PASTE: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_FIND: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_CUT: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_HELP: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_MENU: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_CALC: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_SETUP: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_SLEEP: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_WAKEUP: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_FILE: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_SENDFILE: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_DELETEFILE: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_XFER: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_PROG1: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_PROG2: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_WWW: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_MSDOS: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_COFFEE: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_SCREENLOCK: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_COFFEE: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_ROTATE_DISPLAY: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_DIRECTION: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_ROTATE_DISPLAY: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_CYCLEWINDOWS: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_MAIL: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_BOOKMARKS: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_COMPUTER: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_BACK: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_FORWARD: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_CLOSECD: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_EJECTCD: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_EJECTCLOSECD: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_NEXTSONG: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_PLAYPAUSE: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_PREVIOUSSONG: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_STOPCD: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_RECORD: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_REWIND: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_PHONE: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_ISO: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_CONFIG: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_HOMEPAGE: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_REFRESH: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_EXIT: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_MOVE: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_EDIT: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_SCROLLUP: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_SCROLLDOWN: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_KPLEFTPAREN: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_KPRIGHTPAREN: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_NEW: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_REDO: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_F13: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_F14: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_F15: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_F16: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_F17: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_F18: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_F19: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_F20: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_F21: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_F22: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_F23: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_F24: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_PLAYCD: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_PAUSECD: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_PROG3: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_PROG4: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_DASHBOARD: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_SUSPEND: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_CLOSE: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_PLAY: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_FASTFORWARD: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_BASSBOOST: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_PRINT: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_HP: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_CAMERA: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_SOUND: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_QUESTION: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_EMAIL: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_CHAT: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_SEARCH: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_CONNECT: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_FINANCE: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_SPORT: CGKeyCode = NOT_FOUND;
|
||||
//pub const KEY_SHOP: CGKeyCode = NOT_FOUND;
|
||||
pub const KEY_ALTERASE: CGKeyCode = 0x0047;
|
||||
// pub const KEY_CANCEL: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_BRIGHTNESSDOWN: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_BRIGHTNESSUP: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_MEDIA: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_SWITCHVIDEOMODE: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_KBDILLUMTOGGLE: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_KBDILLUMDOWN: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_KBDILLUMUP: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_SEND: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_REPLY: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_FORWARDMAIL: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_SAVE: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_DOCUMENTS: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_BATTERY: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_BLUETOOTH: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_UWB: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_UNKNOWN: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_VIDEO_NEXT: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_VIDEO_PREV: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_BRIGHTNESS_CYCLE: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_BRIGHTNESS_AUTO: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_BRIGHTNESS_ZERO: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_BRIGHTNESS_AUTO: CGKeyCode = NOT_FOUND;
|
||||
// pub const KEY_DISPLAY_OFF: CGKeyCode = NOT_FOUND;
|
||||
|
||||
pub fn key_map() -> HashMap<&'static str, CGKeyCode> {
|
||||
[
|
||||
// grep 'Key => 0x' ../rusty-keys-win/src/windows/inputs.rs | tr '[a-z]' '[A-Z]' | sed -r -e 's/KEY => 0X/: CGKeyCode = 0x/' -e 's/^[ ]+/pub const KEY_/' | tr ',' ';'
|
||||
("ESC", KEY_ESC),
|
||||
("1", KEY_1),
|
||||
("2", KEY_2),
|
||||
("3", KEY_3),
|
||||
("4", KEY_4),
|
||||
("5", KEY_5),
|
||||
("6", KEY_6),
|
||||
("7", KEY_7),
|
||||
("8", KEY_8),
|
||||
("9", KEY_9),
|
||||
("10", KEY_10),
|
||||
("MINUS", KEY_MINUS),
|
||||
("EQUAL", KEY_EQUAL),
|
||||
("BACKSPACE", KEY_BACKSPACE),
|
||||
("TAB", KEY_TAB),
|
||||
("Q", KEY_Q),
|
||||
("W", KEY_W),
|
||||
("E", KEY_E),
|
||||
("R", KEY_R),
|
||||
("T", KEY_T),
|
||||
("Y", KEY_Y),
|
||||
("U", KEY_U),
|
||||
("I", KEY_I),
|
||||
("O", KEY_O),
|
||||
("P", KEY_P),
|
||||
("LEFTBRACE", KEY_LEFTBRACE),
|
||||
("RIGHTBRACE", KEY_RIGHTBRACE),
|
||||
("ENTER", KEY_ENTER),
|
||||
("LEFTCTRL", KEY_LEFTCTRL),
|
||||
("A", KEY_A),
|
||||
("S", KEY_S),
|
||||
("D", KEY_D),
|
||||
("F", KEY_F),
|
||||
("G", KEY_G),
|
||||
("H", KEY_H),
|
||||
("J", KEY_J),
|
||||
("K", KEY_K),
|
||||
("L", KEY_L),
|
||||
("SEMICOLON", KEY_SEMICOLON),
|
||||
("APOSTROPHE", KEY_APOSTROPHE),
|
||||
("GRAVE", KEY_GRAVE),
|
||||
("LEFTSHIFT", KEY_LEFTSHIFT),
|
||||
("BACKSLASH", KEY_BACKSLASH),
|
||||
("Z", KEY_Z),
|
||||
("X", KEY_X),
|
||||
("C", KEY_C),
|
||||
("V", KEY_V),
|
||||
("B", KEY_B),
|
||||
("N", KEY_N),
|
||||
("M", KEY_M),
|
||||
("COMMA", KEY_COMMA),
|
||||
("DOT", KEY_DOT),
|
||||
("SLASH", KEY_SLASH),
|
||||
("RIGHTSHIFT", KEY_RIGHTSHIFT),
|
||||
("KPASTERISK", KEY_KPASTERISK),
|
||||
("LEFTALT", KEY_LEFTALT),
|
||||
("SPACE", KEY_SPACE),
|
||||
("CAPSLOCK", KEY_CAPSLOCK),
|
||||
("F1", KEY_F1),
|
||||
("F2", KEY_F2),
|
||||
("F3", KEY_F3),
|
||||
("F4", KEY_F4),
|
||||
("F5", KEY_F5),
|
||||
("F6", KEY_F6),
|
||||
("F7", KEY_F7),
|
||||
("F8", KEY_F8),
|
||||
("F9", KEY_F9),
|
||||
("F10", KEY_F10),
|
||||
("NUMLOCK", KEY_NUMLOCK),
|
||||
("SCROLLLOCK", KEY_SCROLLLOCK),
|
||||
("KP7", KEY_KP7),
|
||||
("KP8", KEY_KP8),
|
||||
("KP9", KEY_KP9),
|
||||
("KPMINUS", KEY_KPMINUS),
|
||||
("KP4", KEY_KP4),
|
||||
("KP5", KEY_KP5),
|
||||
("KP6", KEY_KP6),
|
||||
("KPPLUS", KEY_KPPLUS),
|
||||
("KP1", KEY_KP1),
|
||||
("KP2", KEY_KP2),
|
||||
("KP3", KEY_KP3),
|
||||
("KP0", KEY_KP0),
|
||||
("KPDOT", KEY_KPDOT),
|
||||
/*
|
||||
("ZENKAKUHANKAKU", KEY_ZENKAKUHANKAKU),
|
||||
("102ND", KEY_102ND),
|
||||
*/
|
||||
("F11", KEY_F11),
|
||||
("F12", KEY_F12),
|
||||
/*
|
||||
("RO", KEY_RO),
|
||||
("KATAKANA", KEY_KATAKANA),
|
||||
("HIRAGANA", KEY_HIRAGANA),
|
||||
("HENKAN", KEY_HENKAN),
|
||||
("KATAKANAHIRAGANA", KEY_KATAKANAHIRAGANA),
|
||||
("MUHENKAN", KEY_MUHENKAN),
|
||||
("KPJPCOMMA", KEY_KPJPCOMMA),
|
||||
*/
|
||||
("KPENTER", KEY_KPENTER),
|
||||
("RIGHTCTRL", KEY_RIGHTCTRL),
|
||||
("KPSLASH", KEY_KPSLASH),
|
||||
("SYSRQ", KEY_SYSRQ),
|
||||
("RIGHTALT", KEY_RIGHTALT),
|
||||
/*
|
||||
("LINEFEED", KEY_LINEFEED),
|
||||
*/
|
||||
("HOME", KEY_HOME),
|
||||
("UP", KEY_UP),
|
||||
("PAGEUP", KEY_PAGEUP),
|
||||
("LEFT", KEY_LEFT),
|
||||
("RIGHT", KEY_RIGHT),
|
||||
("END", KEY_END),
|
||||
("DOWN", KEY_DOWN),
|
||||
("PAGEDOWN", KEY_PAGEDOWN),
|
||||
("INSERT", KEY_INSERT),
|
||||
("DELETE", KEY_DELETE),
|
||||
/*
|
||||
("MACRO", KEY_MACRO),
|
||||
("MUTE", KEY_MUTE),
|
||||
("VOLUMEDOWN", KEY_VOLUMEDOWN),
|
||||
("VOLUMEUP", KEY_VOLUMEUP),
|
||||
("POWER", KEY_POWER),
|
||||
("KPEQUAL", KEY_KPEQUAL),
|
||||
("KPPLUSMINUS", KEY_KPPLUSMINUS),
|
||||
("PAUSE", KEY_PAUSE),
|
||||
("SCALE", KEY_SCALE),
|
||||
("KPCOMMA", KEY_KPCOMMA),
|
||||
("HANGEUL", KEY_HANGEUL),
|
||||
("HANGUEL", KEY_HANGUEL),
|
||||
("HANGEUL", KEY_HANGEUL),
|
||||
("HANJA", KEY_HANJA),
|
||||
("YEN", KEY_YEN),
|
||||
("LEFTMETA", KEY_LEFTMETA),
|
||||
("RIGHTMETA", KEY_RIGHTMETA),
|
||||
("COMPOSE", KEY_COMPOSE),
|
||||
("STOP", KEY_STOP),
|
||||
("AGAIN", KEY_AGAIN),
|
||||
("PROPS", KEY_PROPS),
|
||||
("UNDO", KEY_UNDO),
|
||||
("FRONT", KEY_FRONT),
|
||||
("COPY", KEY_COPY),
|
||||
("OPEN", KEY_OPEN),
|
||||
("PASTE", KEY_PASTE),
|
||||
("FIND", KEY_FIND),
|
||||
("CUT", KEY_CUT),
|
||||
("HELP", KEY_HELP),
|
||||
("MENU", KEY_MENU),
|
||||
("CALC", KEY_CALC),
|
||||
("SETUP", KEY_SETUP),
|
||||
("SLEEP", KEY_SLEEP),
|
||||
("WAKEUP", KEY_WAKEUP),
|
||||
("FILE", KEY_FILE),
|
||||
("SENDFILE", KEY_SENDFILE),
|
||||
("DELETEFILE", KEY_DELETEFILE),
|
||||
("XFER", KEY_XFER),
|
||||
("PROG1", KEY_PROG1),
|
||||
("PROG2", KEY_PROG2),
|
||||
("WWW", KEY_WWW),
|
||||
("MSDOS", KEY_MSDOS),
|
||||
("COFFEE", KEY_COFFEE),
|
||||
("SCREENLOCK", KEY_SCREENLOCK),
|
||||
("COFFEE", KEY_COFFEE),
|
||||
("ROTATE_DISPLAY", KEY_ROTATE_DISPLAY),
|
||||
("DIRECTION", KEY_DIRECTION),
|
||||
("ROTATE_DISPLAY", KEY_ROTATE_DISPLAY),
|
||||
("CYCLEWINDOWS", KEY_CYCLEWINDOWS),
|
||||
("MAIL", KEY_MAIL),
|
||||
("BOOKMARKS", KEY_BOOKMARKS),
|
||||
("COMPUTER", KEY_COMPUTER),
|
||||
("BACK", KEY_BACK),
|
||||
("FORWARD", KEY_FORWARD),
|
||||
("CLOSECD", KEY_CLOSECD),
|
||||
("EJECTCD", KEY_EJECTCD),
|
||||
("EJECTCLOSECD", KEY_EJECTCLOSECD),
|
||||
("NEXTSONG", KEY_NEXTSONG),
|
||||
("PLAYPAUSE", KEY_PLAYPAUSE),
|
||||
("PREVIOUSSONG", KEY_PREVIOUSSONG),
|
||||
("STOPCD", KEY_STOPCD),
|
||||
("RECORD", KEY_RECORD),
|
||||
("REWIND", KEY_REWIND),
|
||||
("PHONE", KEY_PHONE),
|
||||
("ISO", KEY_ISO),
|
||||
("CONFIG", KEY_CONFIG),
|
||||
("HOMEPAGE", KEY_HOMEPAGE),
|
||||
("REFRESH", KEY_REFRESH),
|
||||
("EXIT", KEY_EXIT),
|
||||
("MOVE", KEY_MOVE),
|
||||
("EDIT", KEY_EDIT),
|
||||
("SCROLLUP", KEY_SCROLLUP),
|
||||
("SCROLLDOWN", KEY_SCROLLDOWN),
|
||||
("KPLEFTPAREN", KEY_KPLEFTPAREN),
|
||||
("KPRIGHTPAREN", KEY_KPRIGHTPAREN),
|
||||
("NEW", KEY_NEW),
|
||||
("REDO", KEY_REDO),
|
||||
("F13", KEY_F13),
|
||||
("F14", KEY_F14),
|
||||
("F15", KEY_F15),
|
||||
("F16", KEY_F16),
|
||||
("F17", KEY_F17),
|
||||
("F18", KEY_F18),
|
||||
("F19", KEY_F19),
|
||||
("F20", KEY_F20),
|
||||
("F21", KEY_F21),
|
||||
("F22", KEY_F22),
|
||||
("F23", KEY_F23),
|
||||
("F24", KEY_F24),
|
||||
("PLAYCD", KEY_PLAYCD),
|
||||
("PAUSECD", KEY_PAUSECD),
|
||||
("PROG3", KEY_PROG3),
|
||||
("PROG4", KEY_PROG4),
|
||||
("DASHBOARD", KEY_DASHBOARD),
|
||||
("SUSPEND", KEY_SUSPEND),
|
||||
("CLOSE", KEY_CLOSE),
|
||||
("PLAY", KEY_PLAY),
|
||||
("FASTFORWARD", KEY_FASTFORWARD),
|
||||
("BASSBOOST", KEY_BASSBOOST),
|
||||
("PRINT", KEY_PRINT),
|
||||
("HP", KEY_HP),
|
||||
("CAMERA", KEY_CAMERA),
|
||||
("SOUND", KEY_SOUND),
|
||||
("QUESTION", KEY_QUESTION),
|
||||
("EMAIL", KEY_EMAIL),
|
||||
("CHAT", KEY_CHAT),
|
||||
("SEARCH", KEY_SEARCH),
|
||||
("CONNECT", KEY_CONNECT),
|
||||
("FINANCE", KEY_FINANCE),
|
||||
("SPORT", KEY_SPORT),
|
||||
("SHOP", KEY_SHOP),
|
||||
("ALTERASE", KEY_ALTERASE),
|
||||
("CANCEL", KEY_CANCEL),
|
||||
("BRIGHTNESSDOWN", KEY_BRIGHTNESSDOWN),
|
||||
("BRIGHTNESSUP", KEY_BRIGHTNESSUP),
|
||||
("MEDIA", KEY_MEDIA),
|
||||
("SWITCHVIDEOMODE", KEY_SWITCHVIDEOMODE),
|
||||
("KBDILLUMTOGGLE", KEY_KBDILLUMTOGGLE),
|
||||
("KBDILLUMDOWN", KEY_KBDILLUMDOWN),
|
||||
("KBDILLUMUP", KEY_KBDILLUMUP),
|
||||
("SEND", KEY_SEND),
|
||||
("REPLY", KEY_REPLY),
|
||||
("FORWARDMAIL", KEY_FORWARDMAIL),
|
||||
("SAVE", KEY_SAVE),
|
||||
("DOCUMENTS", KEY_DOCUMENTS),
|
||||
("BATTERY", KEY_BATTERY),
|
||||
("BLUETOOTH", KEY_BLUETOOTH),
|
||||
("WLAN", KEY_WLAN),
|
||||
("UWB", KEY_UWB),
|
||||
("UNKNOWN", KEY_UNKNOWN),
|
||||
("VIDEO_NEXT", KEY_VIDEO_NEXT),
|
||||
("VIDEO_PREV", KEY_VIDEO_PREV),
|
||||
("BRIGHTNESS_CYCLE", KEY_BRIGHTNESS_CYCLE),
|
||||
("BRIGHTNESS_AUTO", KEY_BRIGHTNESS_AUTO),
|
||||
("BRIGHTNESS_ZERO", KEY_BRIGHTNESS_ZERO),
|
||||
("BRIGHTNESS_AUTO", KEY_BRIGHTNESS_AUTO),
|
||||
("DISPLAY_OFF", KEY_DISPLAY_OFF),
|
||||
("WWAN", KEY_WWAN),
|
||||
("WIMAX", KEY_WIMAX),
|
||||
("WWAN", KEY_WWAN),
|
||||
("RFKILL", KEY_RFKILL),
|
||||
("MICMUTE", KEY_MICMUTE),
|
||||
*/
|
||||
// below manual shortcuts
|
||||
("PSCR", KEY_SYSRQ),
|
||||
("SLCK", KEY_SCROLLLOCK),
|
||||
("BRK", KEY_PAUSE),
|
||||
("GRV", KEY_GRAVE),
|
||||
("0", KEY_10), // dumb or named wrong?
|
||||
("MINS", KEY_MINUS),
|
||||
("EQL", KEY_EQUAL),
|
||||
("BSPC", KEY_BACKSPACE),
|
||||
("LBRC", KEY_LEFTBRACE),
|
||||
("RBRC", KEY_RIGHTBRACE),
|
||||
("BSLS", KEY_BACKSLASH),
|
||||
("SCLN", KEY_SEMICOLON),
|
||||
("QUOT", KEY_APOSTROPHE),
|
||||
("ENT", KEY_ENTER),
|
||||
("COMM", KEY_COMMA),
|
||||
("DOT", KEY_DOT),
|
||||
("SLSH", KEY_SLASH),
|
||||
("CAPS", KEY_CAPSLOCK),
|
||||
("LSFT", KEY_LEFTSHIFT),
|
||||
("RSFT", KEY_RIGHTSHIFT),
|
||||
("SPC", KEY_SPACE),
|
||||
("APP", KEY_COMPOSE),
|
||||
("LCTL", KEY_LEFTCTRL),
|
||||
("RCTL", KEY_RIGHTCTRL),
|
||||
("LALT", KEY_LEFTALT),
|
||||
("RALT", KEY_RIGHTALT),
|
||||
("LGUI", KEY_LEFTMETA),
|
||||
("RGUI", KEY_RIGHTMETA),
|
||||
("INS", KEY_INSERT),
|
||||
("PGUP", KEY_PAGEUP),
|
||||
("PGDN", KEY_PAGEDOWN),
|
||||
("DEL", KEY_DELETE),
|
||||
("RGHT", KEY_RIGHT),
|
||||
("NLCK", KEY_NUMLOCK),
|
||||
("PSLS", KEY_KPSLASH),
|
||||
("PAST", KEY_KPASTERISK),
|
||||
("PMNS", KEY_KPMINUS),
|
||||
("P7", KEY_KP7),
|
||||
("P8", KEY_KP8),
|
||||
("P9", KEY_KP9),
|
||||
("P4", KEY_KP4),
|
||||
("P5", KEY_KP5),
|
||||
("P6", KEY_KP6),
|
||||
("PPLS", KEY_KPPLUS),
|
||||
("P1", KEY_KP1),
|
||||
("P2", KEY_KP2),
|
||||
("P3", KEY_KP3),
|
||||
("P0", KEY_KP0),
|
||||
("PDOT", KEY_KPDOT),
|
||||
("PENT", KEY_KPENTER),
|
||||
].iter().cloned().map(|(m, v)| (m, v)).collect()
|
||||
}
|
414
src/macos/mod.rs
414
src/macos/mod.rs
@ -1,414 +0,0 @@
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(improper_ctypes)]
|
||||
|
||||
use crate::*;
|
||||
use std::env;
|
||||
use std::process::exit;
|
||||
|
||||
use getopts::Options;
|
||||
use std::fs::File;
|
||||
|
||||
pub mod codes;
|
||||
use codes::*;
|
||||
|
||||
use core_graphics::event::CGKeyCode;
|
||||
use core_graphics::event::*;
|
||||
use core_graphics::event_source::*;
|
||||
|
||||
use core_graphics::event::{CGEventTapLocation, CGEventType};
|
||||
|
||||
type MacOSKeyMaps = KeyMaps<CGEventSource, CGKeyCode, CGEvent, Option<CGEvent>>;
|
||||
type CallbackPointer = (MacOSKeyMaps, CGEventSource);
|
||||
|
||||
/*
|
||||
// possible types for event_source
|
||||
Private = -1,
|
||||
CombinedSessionState = 0,
|
||||
HIDSystemState = 1,
|
||||
|
||||
// possible types for tapLocation
|
||||
HID,
|
||||
Session,
|
||||
AnnotatedSession,
|
||||
*/
|
||||
|
||||
// macOS seems to require this, or it ignores shift, WHY?
|
||||
const delay: std::time::Duration = std::time::Duration::from_millis(20);
|
||||
const tapLocation: CGEventTapLocation = CGEventTapLocation::Session;
|
||||
// this is only used if tapLocation is HID, to prevent us from mapping our own key inputs
|
||||
const uniqueHIDUserData: i64 = 45;
|
||||
|
||||
impl KeyEvent<CGKeyCode> for CGEvent {
|
||||
fn code(&self) -> CGKeyCode {
|
||||
self.get_integer_value_field(EventField::KEYBOARD_EVENT_KEYCODE) as CGKeyCode
|
||||
}
|
||||
|
||||
fn value(&self) -> KeyState {
|
||||
let event_type = self.get_type();
|
||||
match event_type {
|
||||
CGEventType::FlagsChanged => {
|
||||
let flags = self.get_flags().bits(); // todo: fix cast?
|
||||
let mask = match self.code() {
|
||||
KEY_LEFTCTRL => NX_DEVICELCTLKEYMASK,
|
||||
KEY_RIGHTCTRL => NX_DEVICERCTLKEYMASK,
|
||||
KEY_LEFTSHIFT => NX_DEVICELSHIFTKEYMASK,
|
||||
KEY_RIGHTSHIFT => NX_DEVICERSHIFTKEYMASK,
|
||||
KEY_LEFTMETA => NX_DEVICELCMDKEYMASK,
|
||||
KEY_RIGHTMETA => NX_DEVICERCMDKEYMASK,
|
||||
KEY_LEFTALT => NX_DEVICELALTKEYMASK,
|
||||
KEY_RIGHTALT => NX_DEVICERALTKEYMASK,
|
||||
KEY_CAPSLOCK => NX_DEVICECAPSLOCKMASK,
|
||||
_ => panic!("unhandled key: {}", self.code()),
|
||||
};
|
||||
if (flags & mask) != 0 { KeyState::DOWN } else { KeyState::UP }
|
||||
},
|
||||
CGEventType::KeyDown => KeyState::DOWN,
|
||||
CGEventType::KeyUp => KeyState::UP,
|
||||
CGEventType::TapDisabledByTimeout => {
|
||||
println!("Quartz event tap disabled because of timeout; attempting to reregister.");
|
||||
todo!("implement register listener");
|
||||
//register_listener(channel);
|
||||
//KeyState::OTHER
|
||||
},
|
||||
_ => {
|
||||
println!("Received unknown EventType: {:?}", event_type);
|
||||
KeyState::OTHER
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Keyboard<CGKeyCode, CGEvent, Option<CGEvent>> for CGEventSource {
|
||||
fn send(&self, event: &mut CGEvent) -> Result<Option<CGEvent>> {
|
||||
//println!("send orig: {}", event.code());
|
||||
|
||||
//Ok(Some(event.event))
|
||||
|
||||
self.send_mod_code_value(event.code(), event.value() == KeyState::UP, event)
|
||||
}
|
||||
|
||||
fn send_mod_code(&self, code: CGKeyCode, event: &mut CGEvent) -> Result<Option<CGEvent>> {
|
||||
// event.value should only ever be UP/DOWN when this method is called
|
||||
//println!("send_mod_code orig: {} code: {}", event.code(), code);
|
||||
|
||||
//unsafe { CGEventSetIntegerValueField(event.event, kCGKeyboardEventKeycode, code as i64) };
|
||||
//Ok(Some(event.event))
|
||||
|
||||
self.send_mod_code_value(code, event.value() == KeyState::UP, event)
|
||||
}
|
||||
|
||||
fn send_mod_code_value(&self, code: CGKeyCode, up_not_down: bool, _event: &mut CGEvent) -> Result<Option<CGEvent>> {
|
||||
//println!("send_mod_code_value orig: {} code: {}, up_not_down: {}", event.code(), code, up_not_down);
|
||||
//return Ok(None);
|
||||
|
||||
let event =
|
||||
CGEvent::new_keyboard_event(self.clone(), code, !up_not_down)
|
||||
.expect("Failed creating event");
|
||||
match tapLocation {
|
||||
CGEventTapLocation::HID => event.set_integer_value_field(EventField::EVENT_SOURCE_USER_DATA, uniqueHIDUserData),
|
||||
_ => {}
|
||||
};
|
||||
event.post(tapLocation);
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn synchronize(&self) -> Result<Option<CGEvent>> {
|
||||
std::thread::sleep(delay);
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn left_shift_code(&self) -> CGKeyCode {
|
||||
KEY_LEFTSHIFT
|
||||
}
|
||||
|
||||
fn right_shift_code(&self) -> CGKeyCode {
|
||||
KEY_RIGHTSHIFT
|
||||
}
|
||||
|
||||
fn caps_lock_code(&self) -> CGKeyCode {
|
||||
KEY_CAPSLOCK
|
||||
}
|
||||
|
||||
fn block_key(&self) -> Result<Option<CGEvent>> {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main_res() -> Result<()> {
|
||||
let config = parse_args();
|
||||
//println!("Config: {:?}", config);
|
||||
|
||||
let key_map = key_map();
|
||||
//println!("key_map: {:?}", key_map);
|
||||
|
||||
let key_maps = MacOSKeyMaps::from_cfg(&key_map, &config.config_file);
|
||||
//println!("key_maps: {}", key_maps);
|
||||
let callback_pointer: CallbackPointer = (key_maps, CGEventSource::new(CGEventSourceStateID::Private).expect("Failed creating event source"));
|
||||
|
||||
let mask = CGEventMaskBit(CGEventType::KeyDown)
|
||||
| CGEventMaskBit(CGEventType::KeyUp)
|
||||
| CGEventMaskBit(CGEventType::FlagsChanged)
|
||||
;
|
||||
|
||||
unsafe {
|
||||
let options = 0;
|
||||
|
||||
// Create the event tap
|
||||
let event_tap = CGEventTapCreate(
|
||||
kCGSessionEventTap,
|
||||
kCGHeadInsertEventTap,
|
||||
options,
|
||||
mask,
|
||||
callback,
|
||||
&callback_pointer,
|
||||
);
|
||||
if event_tap.is_null() {
|
||||
panic!("Unable to create event tap. Please make sure you have the correct permissions");
|
||||
}
|
||||
println!("Created event tap...");
|
||||
|
||||
let allocator = kCFAllocatorDefault;
|
||||
let current_event_loop = CFRunLoopGetCurrent();
|
||||
let mode = kCFRunLoopCommonModes;
|
||||
|
||||
// Create Run Loop Source
|
||||
let run_loop_source = CFMachPortCreateRunLoopSource(allocator, event_tap, 0);
|
||||
|
||||
// Add Run Loop Source to the current event loop
|
||||
CFRunLoopAddSource(current_event_loop, run_loop_source, mode);
|
||||
|
||||
// Enable the tap
|
||||
CGEventTapEnable(event_tap, true);
|
||||
|
||||
CFRunLoopRun();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Config {
|
||||
config_file: String
|
||||
}
|
||||
|
||||
impl Config {
|
||||
fn new(config_file: String) -> Self {
|
||||
Config { config_file: config_file }
|
||||
}
|
||||
}
|
||||
|
||||
fn get_env_push(key: &str, to_push: &str, vec: &mut Vec<String>) {
|
||||
if let Some(var) = env::var_os(key) {
|
||||
if let Ok(str) = var.into_string() {
|
||||
let mut str = str.to_owned();
|
||||
str.push_str(to_push);
|
||||
vec.push(str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_args() -> Config {
|
||||
fn print_usage(program: &str, opts: Options) {
|
||||
let brief = format!("Usage: {} [options] [keymap.toml]", program);
|
||||
println!("{}", opts.usage(&brief));
|
||||
}
|
||||
|
||||
let args: Vec<_> = env::args().collect();
|
||||
|
||||
let mut default_configs = Vec::new();
|
||||
get_env_push("USERPROFILE", "\\keymap.toml", &mut default_configs);
|
||||
get_env_push("APPDATA", "\\keymap.toml", &mut default_configs);
|
||||
|
||||
default_configs.push("keymap.toml".to_string());
|
||||
|
||||
let c_msg = format!("specify the keymap config file to use (default in order: {:?})", default_configs);
|
||||
|
||||
let mut opts = Options::new();
|
||||
opts.optflag("h", "help", "prints this help message");
|
||||
opts.optflag("v", "version", "prints the version");
|
||||
opts.optopt("c", "config", &c_msg, "FILE");
|
||||
|
||||
let matches = opts.parse(&args[1..]);
|
||||
if matches.is_err() {
|
||||
print_usage(&args[0], opts);
|
||||
exit(1);
|
||||
}
|
||||
let matches = matches.unwrap();
|
||||
if matches.opt_present("h") {
|
||||
print_usage(&args[0], opts);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if matches.opt_present("v") {
|
||||
println!("rusty-keys {}", VERSION);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
let config_file = matches.opt_str("c").unwrap_or_else(|| {
|
||||
let remaining_args = matches.free;
|
||||
if remaining_args.len() > 0 {
|
||||
remaining_args[0].clone()
|
||||
} else {
|
||||
for keymap in default_configs.drain(..) {
|
||||
if File::open(&keymap).is_ok() {
|
||||
return keymap;
|
||||
}
|
||||
}
|
||||
println!("Error: no keymap.toml found...");
|
||||
print_usage(&args[0], opts);
|
||||
exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
Config::new(config_file)
|
||||
}
|
||||
|
||||
use libc;
|
||||
|
||||
// Opaque Pointer Types
|
||||
type Pointer = *mut libc::c_void;
|
||||
type CFMachPortRef = Pointer;
|
||||
|
||||
// Integer Types
|
||||
type CGEventMask = u64;
|
||||
type CGEventTapOptions = u32;
|
||||
type CGEventTapPlacement = u32;
|
||||
|
||||
// Callback Type
|
||||
type CGEventTapCallBack = extern "C" fn(Pointer, CGEventType, CGEvent, &mut CallbackPointer) -> CGEvent;
|
||||
|
||||
// Constants
|
||||
const kCGSessionEventTap: CGEventTapLocation = CGEventTapLocation::HID;
|
||||
const kCGHeadInsertEventTap: CGEventTapPlacement = 0;
|
||||
|
||||
// Link to ApplicationServices/ApplicationServices.h and Carbon/Carbon.h
|
||||
#[link(name = "ApplicationServices", kind = "framework")]
|
||||
#[link(name = "Carbon", kind = "framework")]
|
||||
extern {
|
||||
|
||||
/// Pass through to the default loop modes
|
||||
pub static kCFRunLoopCommonModes: Pointer;
|
||||
|
||||
/// Pass through to the default allocator
|
||||
pub static kCFAllocatorDefault: Pointer;
|
||||
|
||||
/// Run the current threads loop in default mode
|
||||
pub fn CFRunLoopRun();
|
||||
|
||||
/// Obtain the current threads loop
|
||||
pub fn CFRunLoopGetCurrent() -> Pointer;
|
||||
|
||||
/// Create an event tap
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `place` - The location of the new event tap. Pass one of
|
||||
/// the constants listed in Event Tap Locations. Only
|
||||
/// processes running as the root user may locate an
|
||||
/// event tap at the point where HID events enter the
|
||||
/// window server; for other users, this function
|
||||
/// returns NULL.
|
||||
///
|
||||
/// * `options` - The placement of the new event tap in the
|
||||
/// list of active event taps. Pass one of the
|
||||
/// constants listed in Event Tap Placement.
|
||||
///
|
||||
/// * `eventsOfInterest` - A constant that specifies whether
|
||||
/// the new event tap is a passive listener or an
|
||||
/// active filter.
|
||||
///
|
||||
/// * `callback` - A bit mask that specifies the set of events
|
||||
/// to be observed. For a list of possible events,
|
||||
/// see Event Types. For information on how to
|
||||
/// specify the mask, see CGEventMask. If the event
|
||||
/// tap is not permitted to monitor one or more of
|
||||
/// the events specified in the eventsOfInterest
|
||||
/// parameter, then the appropriate bits in the mask
|
||||
/// are cleared. If that action results in an empty
|
||||
/// mask, this function returns NULL. callback
|
||||
///
|
||||
/// * `refcon` - An event tap callback function that you
|
||||
/// provide. Your callback function is invoked from
|
||||
/// the run loop to which the event tap is added as a
|
||||
/// source. The thread safety of the callback is
|
||||
/// defined by the run loop’s environment. To learn
|
||||
/// more about event tap callbacks, see
|
||||
/// CGEventTapCallBack. refcon
|
||||
///
|
||||
/// * `channel` - A pointer to user-defined data. This pointer
|
||||
/// is passed into the callback function specified in
|
||||
/// the callback parameter. Here we use it as a mpsc
|
||||
/// channel.
|
||||
pub fn CGEventTapCreate(
|
||||
tap: CGEventTapLocation,
|
||||
place: CGEventTapPlacement,
|
||||
options: CGEventTapOptions,
|
||||
eventsOfInterest: CGEventMask,
|
||||
callback: CGEventTapCallBack,
|
||||
channel: &CallbackPointer,
|
||||
) -> CFMachPortRef;
|
||||
|
||||
/// Creates a CFRunLoopSource object for a CFMachPort
|
||||
/// object.
|
||||
///
|
||||
/// The run loop source is not automatically added to
|
||||
/// a run loop. To add the source to a run loop, use
|
||||
/// CFRunLoopAddSource
|
||||
pub fn CFMachPortCreateRunLoopSource(
|
||||
allocator: Pointer,
|
||||
port: CFMachPortRef,
|
||||
order: libc::c_int,
|
||||
) -> Pointer;
|
||||
|
||||
/// Adds a CFRunLoopSource object to a run loop mode.
|
||||
pub fn CFRunLoopAddSource(
|
||||
run_loop: Pointer,
|
||||
run_loop_source: Pointer,
|
||||
mode: Pointer,
|
||||
);
|
||||
|
||||
pub fn CGEventTapEnable(port: CFMachPortRef, enable: bool);
|
||||
}
|
||||
|
||||
const NX_DEVICELCTLKEYMASK: u64 = 0x00000001;
|
||||
const NX_DEVICERCTLKEYMASK: u64 = 0x00002000;
|
||||
const NX_DEVICELSHIFTKEYMASK: u64 = 0x00000002;
|
||||
const NX_DEVICERSHIFTKEYMASK: u64 = 0x00000004;
|
||||
const NX_DEVICELCMDKEYMASK: u64 = 0x00000008;
|
||||
const NX_DEVICERCMDKEYMASK: u64 = 0x00000010;
|
||||
const NX_DEVICELALTKEYMASK: u64 = 0x00000020;
|
||||
const NX_DEVICERALTKEYMASK: u64 = 0x00000040;
|
||||
|
||||
const NX_DEVICECAPSLOCKMASK: u64 = 1 << 16;
|
||||
|
||||
/// This callback will be registered to be invoked from the run loop
|
||||
/// to which the event tap is added as a source.
|
||||
#[no_mangle]
|
||||
#[allow(unused_variables, improper_ctypes_definitions)]
|
||||
pub extern fn callback(proxy: Pointer, event_type: CGEventType, mut event: CGEvent, callback_pointer: &mut CallbackPointer) -> CGEvent {
|
||||
let (key_maps, event_source) = callback_pointer;
|
||||
match tapLocation {
|
||||
CGEventTapLocation::HID => {
|
||||
let user_data = event.get_integer_value_field(EventField::EVENT_SOURCE_USER_DATA);
|
||||
if user_data == uniqueHIDUserData {
|
||||
return event;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
key_maps.send_event(&mut event, &event_source).expect("macos shouldn't error...")
|
||||
.unwrap_or_else(|| {
|
||||
event.set_type(CGEventType::Null);
|
||||
event
|
||||
}) // None means return NULL
|
||||
}
|
||||
|
||||
/// Redefine macro for bitshifting from header as function here
|
||||
pub fn CGEventMaskBit(eventType: CGEventType) -> CGEventMask {
|
||||
1 << (eventType as u32)
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
fn main() {
|
||||
let ret = rusty_keys::main_res();
|
||||
if let Err(e) = ret {
|
||||
@ -7,7 +7,7 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
|
||||
#[cfg(not(any(target_os = "windows", target_os = "linux")))]
|
||||
fn main() {
|
||||
panic!("sorry no main impl for this platform");
|
||||
}
|
||||
|
@ -4,8 +4,6 @@ After=systemd-udevd.service
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/rusty-keys
|
||||
Restart=always
|
||||
RestartSec=1s
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
|
Loading…
Reference in New Issue
Block a user