Initial commit
This commit is contained in:
commit
20b4b2c18f
9 changed files with 958 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
478
Cargo.lock
generated
Normal file
478
Cargo.lock
generated
Normal file
|
@ -0,0 +1,478 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.70"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
|
||||
|
||||
[[package]]
|
||||
name = "argh"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab257697eb9496bf75526f0217b5ed64636a9cfafa78b8365c71bd283fcef93e"
|
||||
dependencies = [
|
||||
"argh_derive",
|
||||
"argh_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "argh_derive"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b382dbd3288e053331f03399e1db106c9fb0d8562ad62cb04859ae926f324fa6"
|
||||
dependencies = [
|
||||
"argh_shared",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "argh_shared"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64cb94155d965e3d37ffbbe7cc5b82c3dd79dd33bd48e536f73d2cfb8d85506f"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
|
||||
dependencies = [
|
||||
"funty",
|
||||
"radium",
|
||||
"tap",
|
||||
"wyz",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea"
|
||||
dependencies = [
|
||||
"bytemuck_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck_derive"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdde5c9cd29ebd706ce1b35600920a33550e402fc998a2e53ad3b42c3c47a192"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0"
|
||||
dependencies = [
|
||||
"humantime",
|
||||
"is-terminal",
|
||||
"log",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
|
||||
dependencies = [
|
||||
"errno-dragonfly",
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno-dragonfly"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "evdev"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bed59fcc8cfd6b190814a509018388462d3b203cf6dd10db5c00087e72a83f3"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"nix 0.23.2",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"io-lifetimes",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keylightd"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argh",
|
||||
"bytemuck",
|
||||
"env_logger",
|
||||
"evdev",
|
||||
"log",
|
||||
"nix 0.26.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.141"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b085a4f2cde5781fc4b1717f2e86c62f5cda49de7ba99a7c2eae02b61c9064c"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.23.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"memoffset 0.6.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"memoffset 0.7.1",
|
||||
"pin-utils",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.37.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "722529a737f5a942fdbac3a46cee213053196737c5eaa3386d52e85b786f2659"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"io-lifetimes",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
|
||||
dependencies = [
|
||||
"tap",
|
||||
]
|
20
Cargo.toml
Normal file
20
Cargo.toml
Normal file
|
@ -0,0 +1,20 @@
|
|||
[package]
|
||||
name = "keylightd"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
license = "0BSD"
|
||||
description = "Keyboard backlight daemon for Framework laptops"
|
||||
repository = "https://github.com/jonas-schievink/keylightd"
|
||||
categories = ["hardware-support", "command-line-utilities"]
|
||||
|
||||
[dependencies]
|
||||
evdev = "0.12.1"
|
||||
nix = { version = "0.26.2", features = ["user"] }
|
||||
anyhow = "1.0.70"
|
||||
bytemuck = { version = "1.13.1", features = ["derive"] }
|
||||
log = "0.4.17"
|
||||
env_logger = { version = "0.10.0", default-features = false, features = [
|
||||
"auto-color",
|
||||
"humantime",
|
||||
] }
|
||||
argh = "0.1.10"
|
10
LICENSE
Normal file
10
LICENSE
Normal file
|
@ -0,0 +1,10 @@
|
|||
Permission to use, copy, modify, and/or distribute this software for
|
||||
any purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
56
README.md
Normal file
56
README.md
Normal file
|
@ -0,0 +1,56 @@
|
|||
<div align="center">
|
||||
|
||||
# `keylightd`
|
||||
|
||||
### Keyboard backlight daemon for Framework laptops
|
||||
|
||||
</div>
|
||||
|
||||
`keylightd` is a small system daemon for [Framework] laptops that listens to keyboard and touchpad input, and turns on the keyboard backlight while either is being used.
|
||||
|
||||
[Framework]: https://frame.work/
|
||||
|
||||
## Installation
|
||||
|
||||
To install from source, clone the repository and run:
|
||||
|
||||
```shell
|
||||
$ cargo build --release
|
||||
$ sudo cp target/release/keylightd /usr/local/bin
|
||||
```
|
||||
|
||||
`keylightd` has no native dependencies you have to install first (apart from a recent Rust toolchain for building it, of course).
|
||||
It implements communication with the Embedded Controller itself, and talks to the input devices using `evdev` ioctls directly.
|
||||
It also does not have any hard dependencies on a desktop environment or display server.
|
||||
|
||||
If you want to configure `keylightd` as a systemd service that starts on boot, you can use the provided service file:
|
||||
|
||||
```shell
|
||||
$ sudo cp etc/keylightd.service /etc/systemd/system
|
||||
$ sudo systemctl enable --now keylightd
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
Note that `keylightd` needs to be run as root, since it accesses the Embedded Controller to control the keyboard backlight.
|
||||
|
||||
`keylightd` takes the following command-line arguments:
|
||||
|
||||
```
|
||||
Usage: keylightd [--brightness <brightness>] [--timeout <timeout>]
|
||||
|
||||
keylightd - automatic keyboard backlight daemon for Framework laptops
|
||||
|
||||
Options:
|
||||
--brightness brightness level when active (0-100) [default=30]
|
||||
--timeout activity timeout in seconds [default=10]
|
||||
--help display usage information
|
||||
```
|
||||
|
||||
If you're using the provided `keylightd.service` file, you can adjust the command line parameters there.
|
||||
|
||||
## Contributing
|
||||
|
||||
This project does not generally accept contributions. It is finished and does what I want of it.
|
||||
|
||||
Minor fixes and readme additions *may* be accepted.
|
9
etc/keylightd.service
Normal file
9
etc/keylightd.service
Normal file
|
@ -0,0 +1,9 @@
|
|||
[Unit]
|
||||
Description=Keyboard backlight daemon
|
||||
|
||||
[Service]
|
||||
Type=exec
|
||||
ExecStart=/usr/local/bin/keylightd
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
108
src/command.rs
Normal file
108
src/command.rs
Normal file
|
@ -0,0 +1,108 @@
|
|||
use bytemuck::{NoUninit, Pod, Zeroable};
|
||||
|
||||
/// Trait implemented by Embedded Controller commands.
|
||||
pub trait Command: NoUninit {
|
||||
/// The command ID.
|
||||
const CMD: Cmd;
|
||||
|
||||
/// Command version.
|
||||
///
|
||||
/// Some commands come in multiple versions (although none of the ones supported here).
|
||||
const VERSION: u32 = 0;
|
||||
|
||||
/// The associated response type.
|
||||
type Response: Pod;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Cmd {
|
||||
#[allow(unused)] // no longer used by cros-ec
|
||||
ProtoVersion = 0x0000,
|
||||
Hello = 0x0001,
|
||||
GetVersion = 0x0002,
|
||||
// ...
|
||||
GetKeyboardBacklight = 0x0022,
|
||||
SetKeyboardBacklight = 0x0023,
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// Hello
|
||||
//////////////////////////////////
|
||||
|
||||
#[derive(Clone, Copy, NoUninit)]
|
||||
#[repr(C)]
|
||||
pub struct Hello {
|
||||
pub in_data: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct HelloResponse {
|
||||
pub out_data: u32,
|
||||
}
|
||||
|
||||
impl Command for Hello {
|
||||
const CMD: Cmd = Cmd::Hello;
|
||||
type Response = HelloResponse;
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// GetVersion
|
||||
//////////////////////////////////
|
||||
|
||||
#[derive(Clone, Copy, NoUninit)]
|
||||
#[repr(C)]
|
||||
pub struct GetVersion;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct GetVersionResponse {
|
||||
version_string_ro: [u8; 32],
|
||||
version_string_rw: [u8; 32],
|
||||
reserved: [u8; 32],
|
||||
current_image: u32,
|
||||
}
|
||||
|
||||
impl Command for GetVersion {
|
||||
const CMD: Cmd = Cmd::GetVersion;
|
||||
type Response = GetVersionResponse;
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// GetKeyboardBacklight
|
||||
//////////////////////////////////
|
||||
|
||||
#[derive(Clone, Copy, NoUninit)]
|
||||
#[repr(C)]
|
||||
pub struct GetKeyboardBacklight;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct GetKeyboardBacklightResponse {
|
||||
pub percent: u8,
|
||||
pub enabled: u8,
|
||||
}
|
||||
|
||||
impl Command for GetKeyboardBacklight {
|
||||
const CMD: Cmd = Cmd::GetKeyboardBacklight;
|
||||
type Response = GetKeyboardBacklightResponse;
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// SetKeyboardBacklight
|
||||
//////////////////////////////////
|
||||
|
||||
#[derive(Clone, Copy, NoUninit)]
|
||||
#[repr(C)]
|
||||
pub struct SetKeyboardBacklight {
|
||||
pub percent: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct SetKeyboardBacklightResponse;
|
||||
|
||||
impl Command for SetKeyboardBacklight {
|
||||
const CMD: Cmd = Cmd::SetKeyboardBacklight;
|
||||
type Response = SetKeyboardBacklightResponse;
|
||||
}
|
152
src/ec.rs
Normal file
152
src/ec.rs
Normal file
|
@ -0,0 +1,152 @@
|
|||
use std::{
|
||||
fs::File,
|
||||
io,
|
||||
mem::{size_of, size_of_val, MaybeUninit},
|
||||
os::fd::AsRawFd,
|
||||
};
|
||||
|
||||
use nix::{errno::Errno, libc::ioctl, request_code_readwrite};
|
||||
|
||||
use crate::command::{self, Hello};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum IoctlVersion {
|
||||
V1,
|
||||
V2,
|
||||
}
|
||||
|
||||
/// A handle to the system's ChromiumOS Embedded Controller.
|
||||
///
|
||||
/// This uses the ioctl interface of `/dev/cros_ec` to issue commands.
|
||||
pub struct EmbeddedController {
|
||||
fd: File,
|
||||
version: IoctlVersion,
|
||||
}
|
||||
|
||||
impl EmbeddedController {
|
||||
pub fn open() -> io::Result<Self> {
|
||||
let mut this = Self {
|
||||
fd: File::options()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open("/dev/cros_ec")?,
|
||||
version: IoctlVersion::V1,
|
||||
};
|
||||
|
||||
// The framework EC uses ioctl interface version 2, but this mirrors the logic in ectool
|
||||
// just to make sure it doesn't do something nonsensical on non-Framework machines.
|
||||
this.version = match this.cmd_v1(Hello {
|
||||
in_data: 0xa0b0c0d0,
|
||||
}) {
|
||||
Err(Errno::ENOTTY) => IoctlVersion::V2,
|
||||
_ => IoctlVersion::V1,
|
||||
};
|
||||
|
||||
log::debug!("ioctl version {:?}", this.version);
|
||||
|
||||
// Test communication by issuing a `Hello` command and reading back the result.
|
||||
let magic = 0xaa55dead;
|
||||
let resp = this.command(Hello { in_data: magic })?;
|
||||
let expected = magic + 0x01020304;
|
||||
if resp.out_data != expected {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!(
|
||||
"failed to connect to EC: invalid response to hello command (received {:010x}, expected {:010x})",
|
||||
resp.out_data, expected,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
log::info!("connected to embedded controller");
|
||||
|
||||
Ok(this)
|
||||
}
|
||||
|
||||
pub fn command<C: command::Command>(&self, cmd: C) -> io::Result<C::Response> {
|
||||
match self.version {
|
||||
IoctlVersion::V1 => self.cmd_v1(cmd),
|
||||
IoctlVersion::V2 => self.cmd_v2(cmd),
|
||||
}
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn cmd_v1<C: command::Command>(&self, cmd: C) -> nix::Result<C::Response> {
|
||||
let mut resp = MaybeUninit::<C::Response>::uninit();
|
||||
let mut cmd = CommandV1 {
|
||||
version: C::VERSION,
|
||||
command: C::CMD as u32,
|
||||
outdata: bytemuck::bytes_of(&cmd).as_ptr() as *mut _,
|
||||
outsize: size_of_val(&cmd).try_into().unwrap(),
|
||||
indata: resp.as_mut_ptr().cast(),
|
||||
insize: size_of_val(&resp).try_into().unwrap(),
|
||||
result: 0xff,
|
||||
};
|
||||
unsafe {
|
||||
let ret = ioctl(
|
||||
self.fd.as_raw_fd(),
|
||||
request_code_readwrite!(':', 0, size_of::<CommandV1>()),
|
||||
&mut cmd,
|
||||
);
|
||||
Errno::result(ret)?;
|
||||
Ok(resp.assume_init())
|
||||
}
|
||||
}
|
||||
|
||||
fn cmd_v2<C: command::Command>(&self, cmd: C) -> nix::Result<C::Response> {
|
||||
let mut cmd = CommandV2 {
|
||||
header: CommandV2Header {
|
||||
version: C::VERSION,
|
||||
command: C::CMD as u32,
|
||||
outsize: size_of::<C>().try_into().unwrap(),
|
||||
insize: size_of::<C::Response>().try_into().unwrap(),
|
||||
result: 0xff,
|
||||
},
|
||||
data: CommandV2Union { req: cmd },
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let ret = ioctl(
|
||||
self.fd.as_raw_fd(),
|
||||
request_code_readwrite!(0xEC, 0, size_of::<CommandV2Header>()),
|
||||
&mut cmd,
|
||||
);
|
||||
Errno::result(ret)?;
|
||||
Ok(cmd.data.resp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct CommandV1 {
|
||||
version: u32,
|
||||
command: u32,
|
||||
outdata: *mut u8,
|
||||
outsize: u32,
|
||||
indata: *mut u8,
|
||||
insize: u32,
|
||||
result: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct CommandV2<C: command::Command> {
|
||||
header: CommandV2Header,
|
||||
// Request and response are stored in a `union` rather than using an empty trailing array like
|
||||
// the C code does. I believe this is ABI-equivalent, so it shouldn't cause problems.
|
||||
data: CommandV2Union<C>,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct CommandV2Header {
|
||||
version: u32,
|
||||
command: u32,
|
||||
outsize: u32,
|
||||
insize: u32,
|
||||
result: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
union CommandV2Union<C: command::Command> {
|
||||
req: C,
|
||||
resp: C::Response,
|
||||
}
|
124
src/main.rs
Normal file
124
src/main.rs
Normal file
|
@ -0,0 +1,124 @@
|
|||
use std::{
|
||||
io,
|
||||
sync::{Arc, Condvar, Mutex},
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use argh::FromArgs;
|
||||
use command::{GetKeyboardBacklight, SetKeyboardBacklight};
|
||||
use ec::EmbeddedController;
|
||||
|
||||
mod command;
|
||||
mod ec;
|
||||
|
||||
/// keylightd - automatic keyboard backlight daemon for Framework laptops
|
||||
#[derive(FromArgs)]
|
||||
struct Args {
|
||||
/// brightness level when active (0-100) [default=30]
|
||||
#[argh(option, default = "30", from_str_fn(parse_brightness))]
|
||||
brightness: u8,
|
||||
|
||||
/// activity timeout in seconds [default=10]
|
||||
#[argh(option, default = "10")]
|
||||
timeout: u32,
|
||||
}
|
||||
|
||||
fn parse_brightness(s: &str) -> Result<u8, String> {
|
||||
let brightness = s.parse::<u8>().map_err(|e| e.to_string())?;
|
||||
if brightness > 100 {
|
||||
return Err("invalid brightness value {brightness} (valid range: 0-100)".into());
|
||||
}
|
||||
Ok(brightness)
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
env_logger::builder()
|
||||
.filter_module(env!("CARGO_PKG_NAME"), log::LevelFilter::Info)
|
||||
.init();
|
||||
|
||||
let args: Args = argh::from_env();
|
||||
|
||||
let ec = EmbeddedController::open()?;
|
||||
let fade_to = |target: u8| -> io::Result<()> {
|
||||
let resp = ec.command(GetKeyboardBacklight)?;
|
||||
let mut cur = if resp.enabled != 0 { resp.percent } else { 0 };
|
||||
while cur != target {
|
||||
if cur > target {
|
||||
cur -= 1;
|
||||
} else {
|
||||
cur += 1;
|
||||
}
|
||||
|
||||
ec.command(SetKeyboardBacklight { percent: cur })?;
|
||||
thread::sleep(Duration::from_millis(3));
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let act = Arc::new(ActivityState {
|
||||
last_activity: Mutex::new(Instant::now()),
|
||||
condvar: Condvar::new(),
|
||||
});
|
||||
|
||||
for (path, mut device) in evdev::enumerate() {
|
||||
// Filter devices so that only the Framework's builtin touchpad and keyboard are listened
|
||||
// to. Since we don't support hotplug, listening on USB devices wouldn't work reliably.
|
||||
match device.name() {
|
||||
Some("PIXA3854:00 093A:0274 Touchpad" | "AT Translated Set 2 keyboard") => {
|
||||
let act = act.clone();
|
||||
thread::spawn(move || -> io::Result<()> {
|
||||
let name = device.name();
|
||||
let name = name.as_deref().unwrap_or("<unknown>").to_string();
|
||||
log::info!("starting listener on {}: {name}", path.display());
|
||||
loop {
|
||||
if let Err(e) = device.fetch_events() {
|
||||
log::warn!(
|
||||
"error while fetching events for device '{name}': {e}; closing"
|
||||
);
|
||||
return Err(e);
|
||||
}
|
||||
*act.last_activity.lock().unwrap() = Instant::now();
|
||||
act.condvar.notify_one();
|
||||
|
||||
// Delay a bit, to avoid busy looping.
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
}
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("idle timeout: {} seconds", args.timeout);
|
||||
log::info!("brightness level: {}%", args.brightness);
|
||||
|
||||
let mut state = None;
|
||||
loop {
|
||||
let guard = act.last_activity.lock().unwrap();
|
||||
let last = *guard;
|
||||
let (_, result) = act
|
||||
.condvar
|
||||
.wait_timeout_while(guard, Duration::from_secs(args.timeout.into()), |instant| {
|
||||
*instant == last
|
||||
})
|
||||
.unwrap();
|
||||
let new_state = !result.timed_out();
|
||||
if state != Some(new_state) {
|
||||
log::info!("activity state changed: {state:?} -> {new_state}");
|
||||
if new_state {
|
||||
// Fade in
|
||||
fade_to(args.brightness)?;
|
||||
} else {
|
||||
// Fade out
|
||||
fade_to(0)?;
|
||||
}
|
||||
state = Some(new_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ActivityState {
|
||||
last_activity: Mutex<Instant>,
|
||||
condvar: Condvar,
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue