tiny-dfr-0.3.6/.cargo_vcs_info.json0000644000000001360000000000100125760ustar { "git": { "sha1": "76ea1b42e5466e8c7f72f98dee62660fcf19a270" }, "path_in_vcs": "" }tiny-dfr-0.3.6/.gitignore000064400000000000000000000000261046102023000133540ustar 00000000000000/target /result .idea/tiny-dfr-0.3.6/Cargo.lock0000644000001031740000000000100105570ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "anyhow" version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "bumpalo" version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "bytemuck" version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "cairo-rs" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58e62a27cd02fb3f63f82bb31fdda7e6c43141497cbe97e8816d7c914043f55" dependencies = [ "bitflags 2.9.0", "cairo-sys-rs", "freetype-rs", "glib", "libc", ] [[package]] name = "cairo-sys-rs" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "059cc746549898cbfd9a47754288e5a958756650ef4652bbb6c5f71a6bda4f8b" dependencies = [ "glib-sys", "libc", "system-deps", ] [[package]] name = "cc" version = "1.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1" dependencies = [ "shlex", ] [[package]] name = "cfg-expr" version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d4ba6e40bd1184518716a6e1a781bf9160e286d219ccdb8ab2612e74cfe4789" dependencies = [ "smallvec", "target-lexicon", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cfg_aliases" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" [[package]] name = "cfg_aliases" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "pure-rust-locales", "wasm-bindgen", "windows-link", ] [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "dirs" version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" dependencies = [ "libc", "option-ext", "redox_users", "windows-sys 0.48.0", ] [[package]] name = "drm" version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80bc8c5c6c2941f70a55c15f8d9f00f9710ebda3ffda98075f996a0e6c92756f" dependencies = [ "bitflags 2.9.0", "bytemuck", "drm-ffi", "drm-fourcc", "libc", "rustix", ] [[package]] name = "drm-ffi" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8e41459d99a9b529845f6d2c909eb9adf3b6d2f82635ae40be8de0601726e8b" dependencies = [ "drm-sys", "rustix", ] [[package]] name = "drm-fourcc" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" [[package]] name = "drm-sys" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bafb66c8dbc944d69e15cfcc661df7e703beffbaec8bd63151368b06c5f9858c" dependencies = [ "libc", "linux-raw-sys 0.6.5", ] [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", "windows-sys 0.59.0", ] [[package]] name = "freedesktop-icons" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95f87364ea709292a3b3f74014ce3ee78412c89807eea75a358c8e029b000994" dependencies = [ "dirs", "ini_core", "once_cell", "thiserror", "tracing", "xdg", ] [[package]] name = "freetype-rs" version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1d1f81b925f09d7040682dbc91eb1b6ad43232f4bc6ee080f518001c05b5415" dependencies = [ "bitflags 2.9.0", "freetype-sys", "libc", ] [[package]] name = "freetype-sys" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddb84abd9992afaa8eb9b8bef907a94553504d0a890924e1bf1f1ab1249455af" dependencies = [ "cc", "libc", "pkg-config", ] [[package]] name = "futures-channel" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", ] [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "futures-task" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-macro", "futures-task", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "gdk-pixbuf-sys" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b34f3b580c988bd217e9543a2de59823fafae369d1a055555e5f95a8b130b96" dependencies = [ "gio-sys", "glib-sys", "gobject-sys", "libc", "system-deps", ] [[package]] name = "getrandom" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "gio" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab2a654c887546d14fdb214cc04641cd30450c9b4fa4525fd989d25fd5a5561e" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-util", "gio-sys", "glib", "libc", "pin-project-lite", "smallvec", ] [[package]] name = "gio-sys" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521e93a7e56fc89e84aea9a52cfc9436816a4b363b030260b699950ff1336c83" dependencies = [ "glib-sys", "gobject-sys", "libc", "system-deps", "windows-sys 0.59.0", ] [[package]] name = "glib" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c501c495842c2b23cdacead803a5a343ca2a5d7a7ddaff14cc5f6cf22cfb92c2" dependencies = [ "bitflags 2.9.0", "futures-channel", "futures-core", "futures-executor", "futures-task", "futures-util", "gio-sys", "glib-macros", "glib-sys", "gobject-sys", "libc", "memchr", "smallvec", ] [[package]] name = "glib-macros" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebe6dc9ce29887c4b3b74d78d5ba473db160a258ae7ed883d23632ac7fed7bc9" dependencies = [ "heck", "proc-macro-crate", "proc-macro2", "quote", "syn", ] [[package]] name = "glib-sys" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ab79e1ed126803a8fb827e3de0e2ff95191912b8db65cee467edb56fc4cc215" dependencies = [ "libc", "system-deps", ] [[package]] name = "gobject-sys" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec9aca94bb73989e3cfdbf8f2e0f1f6da04db4d291c431f444838925c4c63eda" dependencies = [ "glib-sys", "libc", "system-deps", ] [[package]] name = "hashbrown" version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "iana-time-zone" version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "log", "wasm-bindgen", "windows-core", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", ] [[package]] name = "indexmap" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "ini_core" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a467a31a9f439b5262fa99c17084537bff57f24703d5a09a2b5c9657ec73a61" dependencies = [ "cfg-if", ] [[package]] name = "input" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e74cd82cedcd66db78742a8337bdc48f188c4d2c12742cbc5cd85113f0b059" dependencies = [ "bitflags 1.3.2", "input-sys", "io-lifetimes", "libc", "log", "udev 0.7.0", ] [[package]] name = "input-linux" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e8c4821c88b95582ca69234a1d233f87e44182c42e121f740efb0bec1142e0" dependencies = [ "input-linux-sys", "nix 0.29.0", "serde", ] [[package]] name = "input-linux-sys" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b91b2248b0eaf0a576ef5e60b7f2107a749e705a876bc0b9fe952ac8d83a724" dependencies = [ "libc", "nix 0.29.0", ] [[package]] name = "input-sys" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd4f5b4d1c00331c5245163aacfe5f20be75b564c7112d45893d4ae038119eb0" [[package]] name = "io-lifetimes" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi", "libc", "windows-sys 0.48.0", ] [[package]] name = "js-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "libc" version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libredox" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.9.0", "libc", ] [[package]] name = "librsvg-rebind" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15630f09af77f5e84889ce244a8402a1f9ac0b5546171ad77a02fe514a830d06" dependencies = [ "bitflags 2.9.0", "cairo-rs", "gio", "glib", "libc", "librsvg-rebind-sys", ] [[package]] name = "librsvg-rebind-sys" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "635a4abf7b375659c9bab1839779487eb2a964934ad4f67ceb4e0364b75c37e2" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", "gio-sys", "glib-sys", "gobject-sys", "libc", "system-deps", ] [[package]] name = "libudev-sys" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" dependencies = [ "libc", "pkg-config", ] [[package]] name = "linux-raw-sys" version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a385b1be4e5c3e362ad2ffa73c392e53f031eaa5b7d648e64cd87f27f6063d7" [[package]] name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "nix" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ "bitflags 2.9.0", "cfg-if", "cfg_aliases 0.1.1", "libc", ] [[package]] name = "nix" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ "bitflags 2.9.0", "cfg-if", "cfg_aliases 0.2.1", "libc", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "option-ext" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "ppv-lite86" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "privdrop" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879d008129b086c1c067a3b7ce406bb9766c29f20387e7883724d8dddbce7064" dependencies = [ "libc", "nix 0.28.0", ] [[package]] name = "proc-macro-crate" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "pure-rust-locales" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1190fd18ae6ce9e137184f207593877e70f39b015040156b1e05081cdfe3733a" [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "redox_users" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox", "thiserror", ] [[package]] name = "rustix" version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags 2.9.0", "errno", "libc", "linux-raw-sys 0.4.15", "windows-sys 0.59.0", ] [[package]] name = "rustversion" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "serde" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_spanned" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "syn" version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "system-deps" version = "7.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66d23aaf9f331227789a99e8de4c91bf46703add012bdfd45fdecdfb2975a005" dependencies = [ "cfg-expr", "heck", "pkg-config", "toml", "version-compare", ] [[package]] name = "target-lexicon" version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "thiserror" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tiny-dfr" version = "0.3.6" dependencies = [ "anyhow", "cairo-rs", "chrono", "drm", "freedesktop-icons", "freetype-rs", "input", "input-linux", "input-linux-sys", "libc", "librsvg-rebind", "nix 0.29.0", "pkg-config", "privdrop", "rand", "serde", "toml", "udev 0.9.3", ] [[package]] name = "toml" version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" dependencies = [ "serde", "serde_spanned", "toml_datetime", "toml_edit", ] [[package]] name = "toml_datetime" version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" dependencies = [ "serde", ] [[package]] name = "toml_edit" version = "0.22.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", "toml_write", "winnow", ] [[package]] name = "toml_write" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" [[package]] name = "tracing" version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-attributes" version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tracing-core" version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", ] [[package]] name = "udev" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebdbbd670373442a12fe9ef7aeb53aec4147a5a27a00bbc3ab639f08f48191a" dependencies = [ "libc", "libudev-sys", "pkg-config", ] [[package]] name = "udev" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af4e37e9ea4401fc841ff54b9ddfc9be1079b1e89434c1a6a865dd68980f7e9f" dependencies = [ "io-lifetimes", "libc", "libudev-sys", "pkg-config", ] [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "version-compare" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ "unicode-ident", ] [[package]] name = "windows-core" version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" dependencies = [ "windows-implement", "windows-interface", "windows-link", "windows-result", "windows-strings", ] [[package]] name = "windows-implement" version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "windows-interface" version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "windows-link" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" [[package]] name = "windows-result" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" dependencies = [ "windows-link", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.5", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" dependencies = [ "memchr", ] [[package]] name = "xdg" version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" [[package]] name = "zerocopy" version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", "syn", ] tiny-dfr-0.3.6/Cargo.toml0000644000000035040000000000100105760ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "tiny-dfr" version = "0.3.6" build = "build.rs" autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "The most basic dynamic function row daemon possible" homepage = "https://github.com/AsahiLinux/tiny-dfr" readme = "README.md" license = "MIT AND Apache-2.0" repository = "https://github.com/AsahiLinux/tiny-dfr.git" [[bin]] name = "tiny-dfr" path = "src/main.rs" [dependencies.anyhow] version = "1" [dependencies.cairo-rs] version = "0.20" features = [ "freetype", "png", ] default-features = false [dependencies.chrono] version = "0.4" features = ["unstable-locales"] [dependencies.drm] version = "0.14" [dependencies.freedesktop-icons] version = "0.4.0" [dependencies.freetype-rs] version = "0.37" [dependencies.input] version = "0.8" [dependencies.input-linux] version = "0.7" features = ["serde"] [dependencies.input-linux-sys] version = "0.9" [dependencies.libc] version = "0.2" [dependencies.librsvg-rebind] version = "0.1" [dependencies.nix] version = "0.29" features = [ "event", "signal", "inotify", ] [dependencies.privdrop] version = "0.5.3" [dependencies.rand] version = "0.8" [dependencies.serde] version = "1" features = ["derive"] [dependencies.toml] version = "0.8" [dependencies.udev] version = "0.9" [build-dependencies.pkg-config] version = "0.3" tiny-dfr-0.3.6/Cargo.toml.orig000064400000000000000000000017011046102023000142540ustar 00000000000000[package] name = "tiny-dfr" version = "0.3.6" edition = "2021" license = "MIT AND Apache-2.0" description = "The most basic dynamic function row daemon possible" homepage = "https://github.com/AsahiLinux/tiny-dfr" repository = "https://github.com/AsahiLinux/tiny-dfr.git" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] cairo-rs = { version = "0.20", default-features = false, features = ["freetype", "png"] } librsvg-rebind = "0.1" drm = "0.14" anyhow = "1" input = "0.8" libc = "0.2" input-linux = { version = "0.7", features = ["serde"] } input-linux-sys = "0.9" nix = { version = "0.29", features = ["event", "signal", "inotify"] } privdrop = "0.5.3" serde = { version = "1", features = ["derive"] } toml = "0.8" rand = "0.8" freetype-rs = "0.37" freedesktop-icons = "0.4.0" chrono = { version = "0.4", features = ["unstable-locales"] } udev = "0.9" [build-dependencies] pkg-config = "0.3" tiny-dfr-0.3.6/LICENSE000064400000000000000000000020711046102023000123730ustar 00000000000000MIT License Copyright (c) 2023 WhatAmISupposedToPutHere Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. tiny-dfr-0.3.6/LICENSE.material000064400000000000000000000261351046102023000141770ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.tiny-dfr-0.3.6/README.md000064400000000000000000000011521046102023000126440ustar 00000000000000# tiny-dfr The most basic dynamic function row daemon possible ## Dependencies cairo, libinput, freetype, fontconfig, librsvg 2.59 or later, uinput enabled in kernel config ## License tiny-dfr is licensed under the MIT license, as included in the [LICENSE](LICENSE) file. * Copyright The Asahi Linux Contributors Please see the Git history for authorship information. tiny-dfr embeds Google's [material-design-icons](https://github.com/google/material-design-icons) which are licensed under [Apache License Version 2.0](LICENSE.material) Some icons are derivatives of material-icons, with edits made by kekrby. tiny-dfr-0.3.6/build.rs000064400000000000000000000001041046102023000130260ustar 00000000000000fn main() { pkg_config::probe_library("fontconfig").unwrap(); } tiny-dfr-0.3.6/etc/systemd/system/systemd-backlight@backlight:228200000.display-pipe.0.service000064400000000000000000000000001046102023000276410ustar 00000000000000tiny-dfr-0.3.6/etc/systemd/system/systemd-backlight@backlight:appletb_backlight.service000064400000000000000000000000001046102023000275660ustar 00000000000000tiny-dfr-0.3.6/etc/systemd/system/tiny-dfr.service000064400000000000000000000013021046102023000202670ustar 00000000000000[Unit] Description=Tiny Apple T2 and Silicon Macs Touch Bar daemon After=systemd-user-sessions.service getty@tty1.service plymouth-quit.service systemd-logind.service dev-tiny_dfr_display.device dev-tiny_dfr_backlight.device dev-tiny_dfr_display_backlight.device BindsTo=dev-tiny_dfr_display.device dev-tiny_dfr_backlight.device dev-tiny_dfr_display_backlight.device [Service] ExecStart=/usr/bin/tiny-dfr Restart=always NoNewPrivileges=true ProtectSystem=strict ProtectHome=true PrivateTmp=true PrivateIPC=true ProtectKernelTunables=true ProtectKernelModules=true ProtectKernelLogs=true ProtectControlGroups=strict RestrictAddressFamilies=AF_UNIX AF_NETLINK RestrictNamespaces=true RestrictSUIDSGID=true tiny-dfr-0.3.6/etc/udev/rules.d/99-touchbar-seat.rules000064400000000000000000000006041046102023000205340ustar 00000000000000SUBSYSTEM=="drm", KERNEL=="card*", DRIVERS=="adp|appletbdrm", TAG-="master-of-seat", ENV{ID_SEAT}="seat-touchbar" SUBSYSTEM=="input", ATTR{name}=="Apple Inc. Touch Bar Display Touchpad", ENV{ID_SEAT}="seat-touchbar" SUBSYSTEM=="input", ATTR{name}=="MacBookPro17,1 Touch Bar", ENV{ID_SEAT}="seat-touchbar" SUBSYSTEM=="input", ATTR{name}=="Mac14,7 Touch Bar", ENV{ID_SEAT}="seat-touchbar" tiny-dfr-0.3.6/etc/udev/rules.d/99-touchbar-tiny-dfr.rules000064400000000000000000000030201046102023000213270ustar 00000000000000ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="05ac", ATTR{idProduct}=="8302", ATTR{bConfigurationValue}=="1", ATTR{bConfigurationValue}="0", ATTR{bConfigurationValue}="2" SUBSYSTEM=="input", ATTR{name}=="Apple Inc. Touch Bar Display Touchpad", TAG+="systemd", ENV{SYSTEMD_WANTS}="tiny-dfr.service" SUBSYSTEM=="input", ATTR{name}=="MacBookPro17,1 Touch Bar", TAG+="systemd", ENV{SYSTEMD_WANTS}="tiny-dfr.service" SUBSYSTEM=="input", ATTR{name}=="Mac14,7 Touch Bar", TAG+="systemd", ENV{SYSTEMD_WANTS}="tiny-dfr.service" SUBSYSTEM=="drm", KERNEL=="card[0-9]*", DRIVERS=="adp|appletbdrm", TAG+="systemd", ENV{SYSTEMD_ALIAS}="/dev/tiny_dfr_display" SUBSYSTEM=="backlight", KERNEL=="appletb_backlight", DRIVERS=="hid-appletb-bl", TAG+="systemd", ENV{SYSTEMD_ALIAS}="/dev/tiny_dfr_backlight" SUBSYSTEM=="backlight", KERNEL=="228200000.display-pipe.0", DRIVERS=="panel-summit", TAG+="systemd", ENV{SYSTEMD_ALIAS}="/dev/tiny_dfr_backlight" SUBSYSTEM=="backlight", KERNELS=="228600000.dsi.0", DRIVERS=="panel-summit", TAG+="systemd", ENV{SYSTEMD_ALIAS}="/dev/tiny_dfr_backlight" SUBSYSTEM=="backlight", KERNEL=="apple-panel-bl", TAG+="systemd", ENV{SYSTEMD_ALIAS}="/dev/tiny_dfr_display_backlight" SUBSYSTEM=="backlight", KERNEL=="gmux_backlight", TAG+="systemd", ENV{SYSTEMD_ALIAS}="/dev/tiny_dfr_display_backlight" SUBSYSTEM=="backlight", KERNEL=="intel_backlight", TAG+="systemd", ENV{SYSTEMD_ALIAS}="/dev/tiny_dfr_display_backlight" SUBSYSTEM=="backlight", KERNEL=="acpi_video0", TAG+="systemd", ENV{SYSTEMD_ALIAS}="/dev/tiny_dfr_display_backlight" tiny-dfr-0.3.6/flake.lock000064400000000000000000000010641046102023000133230ustar 00000000000000{ "nodes": { "nixpkgs": { "locked": { "lastModified": 1746957726, "narHash": "sha256-k9ut1LSfHCr0AW82ttEQzXVCqmyWVA5+SHJkS5ID/Jo=", "owner": "nixos", "repo": "nixpkgs", "rev": "a39ed32a651fdee6842ec930761e31d1f242cb94", "type": "github" }, "original": { "owner": "nixos", "ref": "nixos-24.11", "repo": "nixpkgs", "type": "github" } }, "root": { "inputs": { "nixpkgs": "nixpkgs" } } }, "root": "root", "version": 7 } tiny-dfr-0.3.6/flake.nix000064400000000000000000000033161046102023000131730ustar 00000000000000{ description = "The most basic dynamic function row daemon possible"; inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11"; }; outputs = { self, nixpkgs }: let supportedSystems = [ "x86_64-linux" "aarch64-linux" ]; forAllSystems = nixpkgs.lib.genAttrs supportedSystems; pkgsFor = forAllSystems (system: import nixpkgs { inherit system; }); in rec { packages = forAllSystems (system: let pkgs = pkgsFor.${system}; in { default = pkgs.rustPlatform.buildRustPackage { pname = "tiny-dfr"; version = "0.3.5"; src = ./.; cargoLock = { lockFile = ./Cargo.lock; }; nativeBuildInputs = [ pkgs.pkg-config ]; buildInputs = [ pkgs.cairo pkgs.libinput pkgs.freetype pkgs.fontconfig pkgs.glib pkgs.pango pkgs.gdk-pixbuf pkgs.libxml2 pkgs.librsvg ]; postConfigure = '' substituteInPlace etc/systemd/system/tiny-dfr.service \ --replace-fail /usr/bin $out/bin substituteInPlace src/*.rs --replace-quiet /usr/share $out/share ''; postInstall = '' cp -R etc $out/lib cp -R share $out ''; }; }); devShells = forAllSystems (system: let pkgs = pkgsFor.${system}; in { default = pkgs.mkShell { inputsFrom = [ packages.${system}.default ]; packages = [ pkgs.rustfmt pkgs.rust-analyzer ]; RUST_SRC_PATH = "${pkgs.rustPlatform.rustLibSrc}"; }; }); }; } tiny-dfr-0.3.6/share/tiny-dfr/backlight_high.svg000064400000000000000000000017761046102023000177070ustar 00000000000000tiny-dfr-0.3.6/share/tiny-dfr/backlight_low.svg000064400000000000000000000017551046102023000175660ustar 00000000000000tiny-dfr-0.3.6/share/tiny-dfr/battery_0_bar.svg000064400000000000000000000025041046102023000174630ustar 00000000000000 tiny-dfr-0.3.6/share/tiny-dfr/battery_1_bar.svg000064400000000000000000000025051046102023000174650ustar 00000000000000 tiny-dfr-0.3.6/share/tiny-dfr/battery_2_bar.svg000064400000000000000000000025051046102023000174660ustar 00000000000000 tiny-dfr-0.3.6/share/tiny-dfr/battery_3_bar.svg000064400000000000000000000025051046102023000174670ustar 00000000000000 tiny-dfr-0.3.6/share/tiny-dfr/battery_4_bar.svg000064400000000000000000000025051046102023000174700ustar 00000000000000 tiny-dfr-0.3.6/share/tiny-dfr/battery_5_bar.svg000064400000000000000000000025051046102023000174710ustar 00000000000000 tiny-dfr-0.3.6/share/tiny-dfr/battery_6_bar.svg000064400000000000000000000025111046102023000174670ustar 00000000000000 tiny-dfr-0.3.6/share/tiny-dfr/battery_charging_20.svg000064400000000000000000000025511046102023000205650ustar 00000000000000 tiny-dfr-0.3.6/share/tiny-dfr/battery_charging_30.svg000064400000000000000000000026031046102023000205640ustar 00000000000000 tiny-dfr-0.3.6/share/tiny-dfr/battery_charging_50.svg000064400000000000000000000026071046102023000205720ustar 00000000000000 tiny-dfr-0.3.6/share/tiny-dfr/battery_charging_60.svg000064400000000000000000000026021046102023000205660ustar 00000000000000 tiny-dfr-0.3.6/share/tiny-dfr/battery_charging_80.svg000064400000000000000000000026021046102023000205700ustar 00000000000000 tiny-dfr-0.3.6/share/tiny-dfr/battery_charging_90.svg000064400000000000000000000026021046102023000205710ustar 00000000000000 tiny-dfr-0.3.6/share/tiny-dfr/battery_charging_full.svg000064400000000000000000000025411046102023000213050ustar 00000000000000 tiny-dfr-0.3.6/share/tiny-dfr/battery_full.svg000064400000000000000000000024501046102023000174420ustar 00000000000000 tiny-dfr-0.3.6/share/tiny-dfr/bolt.svg000064400000000000000000000002621046102023000157050ustar 00000000000000tiny-dfr-0.3.6/share/tiny-dfr/brightness_high.svg000064400000000000000000000026741046102023000201250ustar 00000000000000tiny-dfr-0.3.6/share/tiny-dfr/brightness_low.svg000064400000000000000000000035671046102023000200110ustar 00000000000000tiny-dfr-0.3.6/share/tiny-dfr/config.toml000064400000000000000000000172211046102023000163710ustar 00000000000000# tiny-dfr config template. Do not edit this file directly, instead # copy it to /etc/tiny-dfr/config.toml and edit that copy. # The daemon will merge those two files, giving preference to the one in /etc # F{number} keys are shown when Fn is not pressed by default. # Set this to true if you want the media keys to be shown without Fn pressed MediaLayerDefault = false # Set this to false if you want to hide the button outline, # leaving only the text/logo ShowButtonOutlines = true # Set this to true to slowly shift the entire screen contents. # In theory this helps with screen longevity, but macos does not bother doing it # Disabling ShowButtonOutlines will make this effect less noticeable to the eye EnablePixelShift = false # Set this to the fontconfig pattern to be used to pick a font for text labels # Some examples are: # "" - default regular sans-serif font # ":bold" - default bold sans-serif font # For full reference on accepted values see the fontconfig user guide, # section "Font Names" # https://www.freedesktop.org/software/fontconfig/fontconfig-user.html FontTemplate = ":bold" # Set this to false if you want the brightness of the touchbar # to be set to a static value instead of following the primary # screen's brightness AdaptiveBrightness = true # With adaptive brightness disabled this is used as the brightness # in the active state # With it enabled, this is the maximum point on the brightness curve # Accepted values are 0-255 ActiveBrightness = 128 # This key defines the contents of the primary layer # (the one with F{number} keys) # You can change the individual buttons, add, or remove them # Any number of keys that is greater than 0 is allowed # however rendering will start to break around 24 keys # Buttons can be made larger by setting the optional Stretch field # to a number greater than 1 (which means the button will take up # that many button spaces). PrimaryLayerKeys = [ # Action defines the key code to send when the button is pressed # Text defines the button label # Icon specifies the icon to be used for the button. # Theme specifies the XDG icons theme. # Stretch specifies how many button spaces the button should take up # and defaults to 1 # Icons can either be svgs or pngs, with svgs being preferred # For best results with pngs, they should be 48x48 # Do not include the extension in the file name. # If a Theme is set, icons are looked up in XDG_DATA_DIRS. # Otherwise, they are first looked up in /etc/tiny-dfr, and then in /usr/share/tiny-dfr. # Time can be either 24hr, or 12hr. Locale is optional and will default to POSIX. # Only one of Text, Icon or Time is allowed, # if both are present, the behavior is undefined. # For the list of supported key codes see # https://docs.rs/input-linux/latest/input_linux/enum.Key.html # Note that the escape key is not specified here, as it is added # automatically on Macs without a physical one { Text = "F1", Action = "F1" }, { Text = "F2", Action = "F2" }, { Text = "F3", Action = "F3" }, { Text = "F4", Action = "F4" }, { Text = "F5", Action = "F5" }, { Text = "F6", Action = "F6" }, { Text = "F7", Action = "F7" }, { Text = "F8", Action = "F8" }, { Text = "F9", Action = "F9" }, { Text = "F10", Action = "F10" }, { Text = "F11", Action = "F11" }, { Text = "F12", Action = "F12" } # Example with Stretch: # # because most buttons have stretch 2, they behave as if they all had 1: # { Text = "F1", Action = "F1", Stretch = 2 }, # { Text = "F2", Action = "F2", Stretch = 2 }, # # these two buttons are half the size of the other buttons: # { Text = "F3", Action = "F3", Stretch = 1 }, # { Text = "F4", Action = "F4", Stretch = 1 }, # { Text = "F5", Action = "F5", Stretch = 2 }, # { Text = "F6", Action = "F6", Stretch = 2 }, # { Text = "F7", Action = "F7", Stretch = 2 }, # # these two buttons are one and a half the size of the other buttons: # { Text = "F8", Action = "F8", Stretch = 3 }, # { Text = "F9", Action = "F9", Stretch = 3 }, # { Text = "F10", Action = "F10", Stretch = 2 }, # { Text = "F11", Action = "F11", Stretch = 2 }, # { Text = "F12", Action = "F12", Stretch = 2 } # Example of Time: # { Time = "12hr", Action = "Time"}, # Example of Time with locale: # { Time = "12hr", Locale = "en_IN", Action = "Time"}, # Example of Time with stretch: # # the time key by default will be too small to accomodate date and time. # # So it is recommended to have a stretch of atleast 3 times the rest keys. # { Time = "12hr", Action = "Time", Stretch = 3}, # You can also put in your format string directly. # The available variables can be found here: https://docs.rs/chrono/latest/chrono/format/strftime/index.html # If your time block says "Time format error" you are using some invalid parameter. # { Time = "%H:%M %-e.%m.%Y", Action = "Time", Stretch = 2} # Example of Battery: # { Battery = "percentage", Action = "Battery" } # This will display a button showing the current battery percentage. # { Battery = "icon", Action = "Battery" } # This will display a button showing a battery icon instead of percentage. # { Battery = "both", Action = "Battery" } # This will display a button showing a battery icon as well as percentage. # The logic used to calcuate the percentage will be: # 1. On T2 Macs: # (Amount of energy currently available / Amount of energy in the battery when it’s considered full) * 100 # 2. On Apple Silicon Macs: # Get the value reported by the SMC in /sys/class/power_supply. # Button will turn green if battery is charging, and red if charge is <10% and is not charging. ] # This key defines the contents of the media key layer MediaLayerKeys = [ { Icon = "brightness_low", Action = "BrightnessDown" }, { Icon = "brightness_high", Action = "BrightnessUp" }, { Icon = "mic_off", Action = "MicMute" }, { Icon = "search", Action = "Search" }, { Icon = "backlight_low", Action = "IllumDown" }, { Icon = "backlight_high", Action = "IllumUp" }, { Icon = "fast_rewind", Action = "PreviousSong" }, { Icon = "play_pause", Action = "PlayPause" }, { Icon = "fast_forward", Action = "NextSong" }, { Icon = "volume_off", Action = "Mute" }, { Icon = "volume_down", Action = "VolumeDown" }, { Icon = "volume_up", Action = "VolumeUp" } # Example with XDG icons (requires `breeze-dark` theme installed): # { Icon = "brightness-low", Theme = "breeze-dark", Action = "BrightnessDown" }, # { Icon = "brightness-high", Theme = "breeze-dark", Action = "BrightnessUp" }, # { Icon = "microphone", Theme = "breeze-dark", Action = "MicMute" }, # { Icon = "search", Theme = "breeze-dark", Action = "Search" }, # { Icon = "redshift-status-day", Theme = "breeze-dark", Action = "IllumDown" }, # { Icon = "redshift-status-on", Theme = "breeze-dark", Action = "IllumUp" }, # { Icon = "media-skip-backward", Theme = "breeze-dark", Action = "PreviousSong" }, # { Icon = "media-playback-start", Theme = "breeze-dark", Action = "PlayPause" }, # { Icon = "media-skip-forward", Theme = "breeze-dark", Action = "NextSong" }, # { Icon = "audio-volume-muted", Theme = "breeze-dark", Action = "Mute" }, # { Icon = "audio-volume-low", Theme = "breeze-dark", Action = "VolumeDown" }, # { Icon = "audio-volume-high", Theme = "breeze-dark", Action = "VolumeUp" } ] tiny-dfr-0.3.6/share/tiny-dfr/fast_forward.svg000064400000000000000000000004511046102023000174260ustar 00000000000000tiny-dfr-0.3.6/share/tiny-dfr/fast_rewind.svg000064400000000000000000000004501046102023000172510ustar 00000000000000tiny-dfr-0.3.6/share/tiny-dfr/mic_off.svg000064400000000000000000000011251046102023000163460ustar 00000000000000tiny-dfr-0.3.6/share/tiny-dfr/play_pause.svg000064400000000000000000000015051046102023000171100ustar 00000000000000tiny-dfr-0.3.6/share/tiny-dfr/search.svg000064400000000000000000000007351046102023000162170ustar 00000000000000tiny-dfr-0.3.6/share/tiny-dfr/volume_down.svg000064400000000000000000000005241046102023000173040ustar 00000000000000tiny-dfr-0.3.6/share/tiny-dfr/volume_off.svg000064400000000000000000000012651046102023000171120ustar 00000000000000tiny-dfr-0.3.6/share/tiny-dfr/volume_up.svg000064400000000000000000000010101046102023000167500ustar 00000000000000tiny-dfr-0.3.6/src/backlight.rs000064400000000000000000000113711046102023000144560ustar 00000000000000use crate::config::Config; use crate::TIMEOUT_MS; use anyhow::{anyhow, Result}; use input::event::{ switch::{Switch, SwitchEvent, SwitchState}, Event, }; use std::{ cmp::min, fs::{self, File, OpenOptions}, io::Write, path::{Path, PathBuf}, time::Instant, }; const MAX_DISPLAY_BRIGHTNESS: u32 = 509; const MAX_TOUCH_BAR_BRIGHTNESS: u32 = 255; const BRIGHTNESS_DIM_TIMEOUT: i32 = TIMEOUT_MS * 3; // should be a multiple of TIMEOUT_MS const BRIGHTNESS_OFF_TIMEOUT: i32 = TIMEOUT_MS * 6; // should be a multiple of TIMEOUT_MS const DIMMED_BRIGHTNESS: u32 = 1; fn read_attr(path: &Path, attr: &str) -> u32 { fs::read_to_string(path.join(attr)) .unwrap_or_else(|_| panic!("Failed to read {attr}")) .trim() .parse::() .unwrap_or_else(|_| panic!("Failed to parse {attr}")) } fn find_backlight() -> Result { for entry in fs::read_dir("/sys/class/backlight/")? { let entry = entry?; let file_name = entry.file_name(); let name = file_name.to_string_lossy(); if ["display-pipe", "228600000.dsi.0", "appletb_backlight"] .iter() .any(|s| name.contains(s)) { return Ok(entry.path()); } } Err(anyhow!("No Touch Bar backlight device found")) } fn find_display_backlight() -> Result { for entry in fs::read_dir("/sys/class/backlight/")? { let entry = entry?; if [ "apple-panel-bl", "gmux_backlight", "intel_backlight", "acpi_video0", ] .iter() .any(|s| entry.file_name().to_string_lossy().contains(s)) { return Ok(entry.path()); } } Err(anyhow!("No Built-in Retina Display backlight device found")) } fn set_backlight(mut file: &File, value: u32) { file.write_all(format!("{}\n", value).as_bytes()).unwrap(); } pub struct BacklightManager { last_active: Instant, max_bl: u32, current_bl: u32, lid_state: SwitchState, bl_file: File, display_bl_path: PathBuf, } impl BacklightManager { pub fn new() -> BacklightManager { let bl_path = find_backlight().unwrap(); let display_bl_path = find_display_backlight().unwrap(); let bl_file = OpenOptions::new() .write(true) .open(bl_path.join("brightness")) .unwrap(); BacklightManager { bl_file, lid_state: SwitchState::Off, max_bl: read_attr(&bl_path, "max_brightness"), current_bl: read_attr(&bl_path, "brightness"), last_active: Instant::now(), display_bl_path, } } fn display_to_touchbar(display: u32, active_brightness: u32) -> u32 { let normalized = display as f64 / MAX_DISPLAY_BRIGHTNESS as f64; // Add one so that the touch bar does not turn off let adjusted = (normalized.powf(0.5) * active_brightness as f64) as u32 + 1; adjusted.min(MAX_TOUCH_BAR_BRIGHTNESS) // Clamp the value to the maximum allowed brightness } pub fn process_event(&mut self, event: &Event) { match event { Event::Keyboard(_) | Event::Pointer(_) | Event::Gesture(_) | Event::Touch(_) => { self.last_active = Instant::now(); } Event::Switch(SwitchEvent::Toggle(toggle)) => { if let Some(Switch::Lid) = toggle.switch() { self.lid_state = toggle.switch_state(); println!("Lid Switch event: {:?}", self.lid_state); if toggle.switch_state() == SwitchState::Off { self.last_active = Instant::now(); } } } _ => {} } } pub fn update_backlight(&mut self, cfg: &Config) { let since_last_active = (Instant::now() - self.last_active).as_millis() as u64; let new_bl = min( self.max_bl, if self.lid_state == SwitchState::On { 0 } else if since_last_active < BRIGHTNESS_DIM_TIMEOUT as u64 { if cfg.adaptive_brightness { BacklightManager::display_to_touchbar( read_attr(&self.display_bl_path, "brightness"), cfg.active_brightness, ) } else { cfg.active_brightness } } else if since_last_active < BRIGHTNESS_OFF_TIMEOUT as u64 { DIMMED_BRIGHTNESS } else { 0 }, ); if self.current_bl != new_bl { self.current_bl = new_bl; set_backlight(&self.bl_file, self.current_bl); } } pub fn current_bl(&self) -> u32 { self.current_bl } } tiny-dfr-0.3.6/src/config.rs000064400000000000000000000136441046102023000140000ustar 00000000000000use crate::fonts::{FontConfig, Pattern}; use crate::FunctionLayer; use anyhow::Error; use cairo::FontFace; use freetype::Library as FtLibrary; use input_linux::Key; use nix::{ errno::Errno, sys::inotify::{AddWatchFlags, InitFlags, Inotify, WatchDescriptor}, }; use serde::Deserialize; use std::{fs::read_to_string, os::fd::AsFd}; const USER_CFG_PATH: &str = "/etc/tiny-dfr/config.toml"; pub struct Config { pub show_button_outlines: bool, pub enable_pixel_shift: bool, pub font_face: FontFace, pub adaptive_brightness: bool, pub active_brightness: u32, } #[derive(Deserialize)] #[serde(rename_all = "PascalCase")] struct ConfigProxy { media_layer_default: Option, show_button_outlines: Option, enable_pixel_shift: Option, font_template: Option, adaptive_brightness: Option, active_brightness: Option, primary_layer_keys: Option>, media_layer_keys: Option>, } #[derive(Deserialize)] #[serde(rename_all = "PascalCase")] pub struct ButtonConfig { #[serde(alias = "Svg")] pub icon: Option, pub text: Option, pub theme: Option, pub time: Option, pub battery: Option, pub locale: Option, pub action: Key, pub stretch: Option, } fn load_font(name: &str) -> FontFace { let fontconfig = FontConfig::new(); let mut pattern = Pattern::new(name); fontconfig.perform_substitutions(&mut pattern); let pat_match = match fontconfig.match_pattern(&pattern) { Ok(pat) => pat, Err(_) => panic!("Unable to find specified font. If you are using the default config, make sure you have at least one font installed") }; let file_name = pat_match.get_file_name(); let file_idx = pat_match.get_font_index(); let ft_library = FtLibrary::init().unwrap(); let face = ft_library.new_face(file_name, file_idx).unwrap(); FontFace::create_from_ft(&face).unwrap() } fn load_config(width: u16) -> (Config, [FunctionLayer; 2]) { let mut base = toml::from_str::(&read_to_string("/usr/share/tiny-dfr/config.toml").unwrap()) .unwrap(); let user = read_to_string(USER_CFG_PATH) .map_err::(|e| e.into()) .and_then(|r| Ok(toml::from_str::(&r)?)); if let Ok(user) = user { base.media_layer_default = user.media_layer_default.or(base.media_layer_default); base.show_button_outlines = user.show_button_outlines.or(base.show_button_outlines); base.enable_pixel_shift = user.enable_pixel_shift.or(base.enable_pixel_shift); base.font_template = user.font_template.or(base.font_template); base.adaptive_brightness = user.adaptive_brightness.or(base.adaptive_brightness); base.media_layer_keys = user.media_layer_keys.or(base.media_layer_keys); base.primary_layer_keys = user.primary_layer_keys.or(base.primary_layer_keys); base.active_brightness = user.active_brightness.or(base.active_brightness); }; let mut media_layer_keys = base.media_layer_keys.unwrap(); let mut primary_layer_keys = base.primary_layer_keys.unwrap(); if width >= 2170 { for layer in [&mut media_layer_keys, &mut primary_layer_keys] { layer.insert( 0, ButtonConfig { icon: None, text: Some("esc".into()), theme: None, action: Key::Esc, stretch: None, time: None, locale: None, battery: None, }, ); } } let media_layer = FunctionLayer::with_config(media_layer_keys); let fkey_layer = FunctionLayer::with_config(primary_layer_keys); let layers = if base.media_layer_default.unwrap() { [media_layer, fkey_layer] } else { [fkey_layer, media_layer] }; let cfg = Config { show_button_outlines: base.show_button_outlines.unwrap(), enable_pixel_shift: base.enable_pixel_shift.unwrap(), adaptive_brightness: base.adaptive_brightness.unwrap(), font_face: load_font(&base.font_template.unwrap()), active_brightness: base.active_brightness.unwrap(), }; (cfg, layers) } pub struct ConfigManager { inotify_fd: Inotify, watch_desc: Option, } fn arm_inotify(inotify_fd: &Inotify) -> Option { let flags = AddWatchFlags::IN_MOVED_TO | AddWatchFlags::IN_CLOSE | AddWatchFlags::IN_ONESHOT; match inotify_fd.add_watch(USER_CFG_PATH, flags) { Ok(wd) => Some(wd), Err(Errno::ENOENT) => None, e => Some(e.unwrap()), } } impl ConfigManager { pub fn new() -> ConfigManager { let inotify_fd = Inotify::init(InitFlags::IN_NONBLOCK).unwrap(); let watch_desc = arm_inotify(&inotify_fd); ConfigManager { inotify_fd, watch_desc, } } pub fn load_config(&self, width: u16) -> (Config, [FunctionLayer; 2]) { load_config(width) } pub fn update_config( &mut self, cfg: &mut Config, layers: &mut [FunctionLayer; 2], width: u16, ) -> bool { if self.watch_desc.is_none() { self.watch_desc = arm_inotify(&self.inotify_fd); return false; } let evts = match self.inotify_fd.read_events() { Ok(e) => e, Err(Errno::EAGAIN) => Vec::new(), r => r.unwrap(), }; let mut ret = false; for evt in evts { if evt.wd != self.watch_desc.unwrap() { continue; } let parts = load_config(width); *cfg = parts.0; *layers = parts.1; ret = true; self.watch_desc = arm_inotify(&self.inotify_fd); } ret } pub fn fd(&self) -> &impl AsFd { &self.inotify_fd } } tiny-dfr-0.3.6/src/crash_bitmap.raw000064400000000000000000000373001046102023000153270ustar 00000000000000?????????????????00 ?0?????????????????????????????????????93333?3333;???????;;;;;??????????????????????????????????????????????????933333???????????????????;;;;;???????????????????????????0 !????????????????????3333;???;;;;;????????0?8!???????????????;;;;;?????????????????????????????????????????????;;;;;?????????????????????????tiny-dfr-0.3.6/src/display.rs000064400000000000000000000137331046102023000141770ustar 00000000000000use anyhow::{anyhow, Result}; use drm::{ buffer::DrmFourcc, control::{ atomic, connector, dumbbuffer::{DumbBuffer, DumbMapping}, framebuffer, property, AtomicCommitFlags, ClipRect, Device as ControlDevice, Mode, ResourceHandle, }, ClientCapability, Device as DrmDevice, }; use std::{ fs::{self, File, OpenOptions}, os::unix::io::{AsFd, BorrowedFd}, path::Path, }; struct Card(File); impl AsFd for Card { fn as_fd(&self) -> BorrowedFd<'_> { self.0.as_fd() } } impl ControlDevice for Card {} impl DrmDevice for Card {} impl Card { fn open(path: &Path) -> Result { let mut options = OpenOptions::new(); options.read(true); options.write(true); Ok(Card(options.open(path)?)) } } pub struct DrmBackend { card: Card, mode: Mode, db: DumbBuffer, fb: framebuffer::Handle, } impl Drop for DrmBackend { fn drop(&mut self) { self.card.destroy_framebuffer(self.fb).unwrap(); self.card.destroy_dumb_buffer(self.db).unwrap(); } } fn find_prop_id( card: &Card, handle: T, name: &'static str, ) -> Result { let props = card.get_properties(handle)?; for id in props.as_props_and_values().0 { let info = card.get_property(*id)?; if info.name().to_str()? == name { return Ok(*id); } } Err(anyhow!("Property not found")) } fn try_open_card(path: &Path) -> Result { let card = Card::open(path)?; card.set_client_capability(ClientCapability::UniversalPlanes, true)?; card.set_client_capability(ClientCapability::Atomic, true)?; card.acquire_master_lock()?; let res = card.resource_handles()?; let coninfo = res .connectors() .iter() .flat_map(|con| card.get_connector(*con, true)) .collect::>(); let crtcinfo = res .crtcs() .iter() .flat_map(|crtc| card.get_crtc(*crtc)) .collect::>(); let con = coninfo .iter() .find(|&i| i.state() == connector::State::Connected) .ok_or(anyhow!("No connected connectors found"))?; let &mode = con.modes().first().ok_or(anyhow!("No modes found"))?; let (disp_width, disp_height) = mode.size(); if disp_height / disp_width < 30 { return Err(anyhow!("This does not look like a touchbar")); } let crtc = crtcinfo.first().ok_or(anyhow!("No crtcs found"))?; let fmt = DrmFourcc::Xrgb8888; let db = card.create_dumb_buffer((64, disp_height.into()), fmt, 32)?; let fb = card.add_framebuffer(&db, 24, 32)?; let plane = *card .plane_handles()? .first() .ok_or(anyhow!("No planes found"))?; let mut atomic_req = atomic::AtomicModeReq::new(); atomic_req.add_property( con.handle(), find_prop_id(&card, con.handle(), "CRTC_ID")?, property::Value::CRTC(Some(crtc.handle())), ); let blob = card.create_property_blob(&mode)?; atomic_req.add_property( crtc.handle(), find_prop_id(&card, crtc.handle(), "MODE_ID")?, blob, ); atomic_req.add_property( crtc.handle(), find_prop_id(&card, crtc.handle(), "ACTIVE")?, property::Value::Boolean(true), ); atomic_req.add_property( plane, find_prop_id(&card, plane, "FB_ID")?, property::Value::Framebuffer(Some(fb)), ); atomic_req.add_property( plane, find_prop_id(&card, plane, "CRTC_ID")?, property::Value::CRTC(Some(crtc.handle())), ); atomic_req.add_property( plane, find_prop_id(&card, plane, "SRC_X")?, property::Value::UnsignedRange(0), ); atomic_req.add_property( plane, find_prop_id(&card, plane, "SRC_Y")?, property::Value::UnsignedRange(0), ); atomic_req.add_property( plane, find_prop_id(&card, plane, "SRC_W")?, property::Value::UnsignedRange((mode.size().0 as u64) << 16), ); atomic_req.add_property( plane, find_prop_id(&card, plane, "SRC_H")?, property::Value::UnsignedRange((mode.size().1 as u64) << 16), ); atomic_req.add_property( plane, find_prop_id(&card, plane, "CRTC_X")?, property::Value::SignedRange(0), ); atomic_req.add_property( plane, find_prop_id(&card, plane, "CRTC_Y")?, property::Value::SignedRange(0), ); atomic_req.add_property( plane, find_prop_id(&card, plane, "CRTC_W")?, property::Value::UnsignedRange(mode.size().0 as u64), ); atomic_req.add_property( plane, find_prop_id(&card, plane, "CRTC_H")?, property::Value::UnsignedRange(mode.size().1 as u64), ); card.atomic_commit(AtomicCommitFlags::ALLOW_MODESET, atomic_req)?; Ok(DrmBackend { card, mode, db, fb }) } impl DrmBackend { pub fn open_card() -> Result { let mut errors = Vec::new(); for entry in fs::read_dir("/dev/dri/")? { let entry = entry?; if !entry.file_name().to_string_lossy().starts_with("card") { continue; } match try_open_card(&entry.path()) { Ok(card) => return Ok(card), Err(err) => errors.push(format!( "{}: {}", entry.path().as_os_str().to_string_lossy(), err )), } } Err(anyhow!( "No touchbar device found, attempted: [\n {}\n]", errors.join(",\n ") )) } pub fn mode(&self) -> Mode { self.mode } pub fn fb_info(&self) -> Result { Ok(self.card.get_framebuffer(self.fb)?) } pub fn dirty(&self, clips: &[ClipRect]) -> Result<()> { Ok(self.card.dirty_framebuffer(self.fb, clips)?) } pub fn map(&mut self) -> Result { Ok(self.card.map_dumb_buffer(&mut self.db)?) } } tiny-dfr-0.3.6/src/fonts.rs000064400000000000000000000073661046102023000136700ustar 00000000000000#![allow(non_upper_case_globals)] use std::ffi::{c_char, c_int, CStr, CString}; use std::ptr; #[repr(C)] struct FcPattern { _data: [u8; 0], } #[repr(C)] struct FcConfig { _data: [u8; 0], } type FcResult = c_int; const FcResultMatch: FcResult = 0; const FcResultNoMatch: FcResult = 1; const FcResultTypeMismatch: FcResult = 2; const FcResultNoId: FcResult = 3; const FcResultOutOfMemory: FcResult = 4; type FcMatchKind = c_int; const FcMatchPattern: FcMatchKind = 0; pub enum FontConfigError { FontNotFound, } pub struct FontConfig { config: *const FcConfig, } impl FontConfig { pub fn new() -> FontConfig { let config = unsafe { FcInitLoadConfigAndFonts() }; FontConfig { config } } pub fn match_pattern(&self, pattern: &Pattern) -> Result { let mut result: FcResult = 0; let match_ = unsafe { FcFontMatch(self.config, pattern.pattern, &mut result) }; if match_.is_null() { return Err(FontConfigError::FontNotFound); } Ok(Pattern { pattern: match_ }) } pub fn perform_substitutions(&self, pattern: &mut Pattern) { unsafe { if (FcConfigSubstitute(self.config, pattern.pattern, FcMatchPattern)) == 0 { panic!("Allocation error while loading fontconfig data"); } FcDefaultSubstitute(pattern.pattern); } } } impl Drop for FontConfig { fn drop(&mut self) { unsafe { FcConfigDestroy(self.config) } } } fn throw_on_fcpattern_result(res: FcResult) { match res { FcResultMatch => {} FcResultNoMatch => { panic!("NULL pattern"); } FcResultTypeMismatch => { panic!("Wrong type for pattern element"); } FcResultNoId => { panic!("Unknown pattern element"); } FcResultOutOfMemory => { panic!("Out of memory"); } r => { panic!("Unknown fontconfig return value {:?}", r) } } } pub struct Pattern { pattern: *const FcPattern, } impl Pattern { pub fn new(st: &str) -> Pattern { let cstr = CString::new(st).unwrap(); let pattern = unsafe { FcNameParse(cstr.as_ptr()) }; Pattern { pattern } } pub fn get_file_name(&self) -> &str { let name = CString::new("file").unwrap(); unsafe { let mut file_name = ptr::null(); let res = FcPatternGetString(self.pattern, name.as_ptr(), 0, &mut file_name); throw_on_fcpattern_result(res); CStr::from_ptr(file_name).to_str().unwrap() } } pub fn get_font_index(&self) -> isize { let name = CString::new("index").unwrap(); unsafe { let mut index = 0; let res = FcPatternGetInteger(self.pattern, name.as_ptr(), 0, &mut index); throw_on_fcpattern_result(res); index as isize } } } impl Drop for Pattern { fn drop(&mut self) { unsafe { FcPatternDestroy(self.pattern) } } } extern "C" { fn FcInitLoadConfigAndFonts() -> *const FcConfig; fn FcConfigDestroy(_: *const FcConfig); fn FcNameParse(_: *const c_char) -> *const FcPattern; fn FcPatternDestroy(_: *const FcPattern); fn FcFontMatch(_: *const FcConfig, _: *const FcPattern, _: *mut FcResult) -> *mut FcPattern; fn FcPatternGetString( _: *const FcPattern, _: *const c_char, _: c_int, _: *mut *const c_char, ) -> FcResult; fn FcPatternGetInteger( _: *const FcPattern, _: *const c_char, _: c_int, _: *mut c_int, ) -> FcResult; fn FcConfigSubstitute(_: *const FcConfig, _: *const FcPattern, _: FcMatchKind) -> c_int; fn FcDefaultSubstitute(_: *const FcPattern); } tiny-dfr-0.3.6/src/main.rs000064400000000000000000001005051046102023000134500ustar 00000000000000use anyhow::{anyhow, Result}; use cairo::{Antialias, Context, Format, ImageSurface, Surface}; use chrono::{Local, Locale, Timelike, format::{StrftimeItems, Item as ChronoItem}}; use drm::control::ClipRect; use freedesktop_icons::lookup; use input::{ event::{ device::DeviceEvent, keyboard::{KeyState, KeyboardEvent, KeyboardEventTrait}, touch::{TouchEvent, TouchEventPosition, TouchEventSlot}, Event, EventTrait, }, Device as InputDevice, Libinput, LibinputInterface, }; use input_linux::{uinput::UInputHandle, EventKind, Key, SynchronizeKind}; use input_linux_sys::{input_event, input_id, timeval, uinput_setup}; use libc::{c_char, O_ACCMODE, O_RDONLY, O_RDWR, O_WRONLY}; use librsvg_rebind::{prelude::HandleExt, Handle, Rectangle}; use nix::{ errno::Errno, sys::{ epoll::{Epoll, EpollCreateFlags, EpollEvent, EpollFlags}, signal::{SigSet, Signal}, }, }; use privdrop::PrivDrop; use std::{ cmp::min, collections::HashMap, fs::{self, File, OpenOptions}, os::{ fd::{AsFd, AsRawFd}, unix::{fs::OpenOptionsExt, io::OwnedFd}, }, panic::{self, AssertUnwindSafe}, path::{Path, PathBuf}, }; use udev::MonitorBuilder; mod backlight; mod config; mod display; mod fonts; mod pixel_shift; use crate::config::ConfigManager; use backlight::BacklightManager; use config::{ButtonConfig, Config}; use display::DrmBackend; use pixel_shift::{PixelShiftManager, PIXEL_SHIFT_WIDTH_PX}; const BUTTON_SPACING_PX: i32 = 16; const BUTTON_COLOR_INACTIVE: f64 = 0.200; const BUTTON_COLOR_ACTIVE: f64 = 0.400; const ICON_SIZE: i32 = 48; const TIMEOUT_MS: i32 = 10 * 1000; #[derive(Clone, Copy, PartialEq, Eq)] enum BatteryState { NotCharging, Charging, Low, } struct BatteryImages { plain: Vec, charging: Vec, bolt: Handle, } #[derive(Eq, PartialEq, Copy, Clone)] enum BatteryIconMode { Percentage, Icon, Both } impl BatteryIconMode { fn should_draw_icon(self) -> bool { self != BatteryIconMode::Percentage } fn should_draw_text(self) -> bool { self != BatteryIconMode::Icon } } enum ButtonImage { Text(String), Svg(Handle), Bitmap(ImageSurface), Time(Vec>, Locale), Battery(String, BatteryIconMode, BatteryImages), } struct Button { image: ButtonImage, changed: bool, active: bool, action: Key, } fn try_load_svg(path: &str) -> Result { Ok(ButtonImage::Svg( Handle::from_file(path)?.ok_or(anyhow!("failed to load image"))?, )) } fn try_load_png(path: impl AsRef) -> Result { let mut file = File::open(path)?; let surf = ImageSurface::create_from_png(&mut file)?; if surf.height() == ICON_SIZE && surf.width() == ICON_SIZE { return Ok(ButtonImage::Bitmap(surf)); } let resized = ImageSurface::create(Format::ARgb32, ICON_SIZE, ICON_SIZE).unwrap(); let c = Context::new(&resized).unwrap(); c.scale( ICON_SIZE as f64 / surf.width() as f64, ICON_SIZE as f64 / surf.height() as f64, ); c.set_source_surface(surf, 0.0, 0.0).unwrap(); c.set_antialias(Antialias::Best); c.paint().unwrap(); Ok(ButtonImage::Bitmap(resized)) } fn try_load_image(name: impl AsRef, theme: Option>) -> Result { let name = name.as_ref(); let locations; // Load list of candidate locations if let Some(theme) = theme { // Freedesktop icons let theme = theme.as_ref(); let candidates = vec![ lookup(name) .with_cache() .with_theme(theme) .with_size(ICON_SIZE as u16) .force_svg() .find(), lookup(name) .with_cache() .with_theme(theme) .force_svg() .find(), ]; // .flatten() removes `None` and unwraps `Some` values locations = candidates.into_iter().flatten().collect(); } else { // Standard file icons locations = vec![ PathBuf::from(format!("/etc/tiny-dfr/{name}.svg")), PathBuf::from(format!("/etc/tiny-dfr/{name}.png")), PathBuf::from(format!("/usr/share/tiny-dfr/{name}.svg")), PathBuf::from(format!("/usr/share/tiny-dfr/{name}.png")), ]; }; // Try to load each candidate let mut last_err = anyhow!("no suitable icon path was found"); // in case locations is empty for location in locations { let result = match location.extension().and_then(|s| s.to_str()) { Some("png") => try_load_png(&location), Some("svg") => try_load_svg( location .to_str() .ok_or(anyhow!("image path is not unicode"))?, ), _ => Err(anyhow!("invalid file extension")), }; match result { Ok(image) => return Ok(image), Err(err) => { last_err = err.context(format!("while loading path {}", location.display())); } }; } // if function hasn't returned by now, all sources have been exhausted Err(last_err.context(format!("failed loading all possible paths for icon {name}"))) } fn find_battery_device() -> Option { let power_supply_path = "/sys/class/power_supply"; if let Ok(entries) = fs::read_dir(power_supply_path) { for entry in entries.flatten() { let dev_path = entry.path(); let type_path = dev_path.join("type"); if let Ok(typ) = fs::read_to_string(&type_path) { if typ.trim() == "Battery" { if let Some(name) = dev_path.file_name().and_then(|n| n.to_str()) { return Some(name.to_string()); } } } } } None } fn get_battery_state(battery: &str) -> (u32, BatteryState) { let status_path = format!("/sys/class/power_supply/{}/status", battery); let status = fs::read_to_string(&status_path) .unwrap_or_else(|_| "Unknown".to_string()); #[cfg(target_arch = "x86_64")] let capacity = { let charge_now_path = format!("/sys/class/power_supply/{}/charge_now", battery); let charge_full_path = format!("/sys/class/power_supply/{}/charge_full", battery); let charge_now = fs::read_to_string(&charge_now_path) .ok() .and_then(|s| s.trim().parse::().ok()); let charge_full = fs::read_to_string(&charge_full_path) .ok() .and_then(|s| s.trim().parse::().ok()); match (charge_now, charge_full) { (Some(now), Some(full)) if full > 0.0 => ((now / full) * 100.0).round() as u32, _ => 100, } }; #[cfg(target_arch = "aarch64")] let capacity = { let capacity_path = format!("/sys/class/power_supply/{}/capacity", battery); fs::read_to_string(&capacity_path) .ok() .and_then(|s| s.trim().parse::().ok()) .unwrap_or(100) }; let status = match status.trim() { "Charging" | "Full" => BatteryState::Charging, "Discharging" if capacity < 10 => BatteryState::Low, _ => BatteryState::NotCharging, }; (capacity, status) } impl Button { fn with_config(cfg: ButtonConfig) -> Button { if let Some(text) = cfg.text { Button::new_text(text, cfg.action) } else if let Some(icon) = cfg.icon { Button::new_icon(&icon, cfg.theme, cfg.action) } else if let Some(time) = cfg.time { Button::new_time(cfg.action, &time, cfg.locale.as_deref()) } else if let Some(battery_mode) = cfg.battery { if let Some(battery) = find_battery_device() { Button::new_battery(cfg.action, battery, battery_mode, cfg.theme) } else { Button::new_text("Battery N/A".to_string(), cfg.action) } } else { panic!("Invalid config, a button must have either Text, Icon or Time") } } fn new_text(text: String, action: Key) -> Button { Button { action, active: false, changed: false, image: ButtonImage::Text(text), } } fn new_icon(path: impl AsRef, theme: Option>, action: Key) -> Button { let image = try_load_image(path, theme).expect("failed to load icon"); Button { action, image, active: false, changed: false, } } fn load_battery_image(icon: &str, theme: Option>) -> Handle { if let ButtonImage::Svg(svg) = try_load_image(icon, theme).unwrap() { return svg; } panic!("failed to load icon"); } fn new_battery(action: Key, battery: String, battery_mode: String, theme: Option>) -> Button { let bolt = Self::load_battery_image("bolt", theme.as_ref()); let mut plain = Vec::new(); let mut charging = Vec::new(); for icon in [ "battery_0_bar", "battery_1_bar", "battery_2_bar", "battery_3_bar", "battery_4_bar", "battery_5_bar", "battery_6_bar", "battery_full", ] { plain.push(Self::load_battery_image(icon, theme.as_ref())); } for icon in [ "battery_charging_20", "battery_charging_30", "battery_charging_50", "battery_charging_60", "battery_charging_80", "battery_charging_90", "battery_charging_full", ] { charging.push(Self::load_battery_image(icon, theme.as_ref())); } let battery_mode = match battery_mode.as_str() { "icon" => BatteryIconMode::Icon, "percentage" => BatteryIconMode::Percentage, "both" => BatteryIconMode::Both, _ => panic!("invalid battery mode, accepted modes: icon, percentage, both"), }; Button { action, active: false, changed: false, image: ButtonImage::Battery(battery, battery_mode, BatteryImages { plain, bolt, charging }), } } fn new_time(action: Key, format: &str, locale_str: Option<&str>) -> Button { let format_str = if format == "24hr" { "%H:%M %a %-e %b" } else if format == "12hr" { "%-l:%M %p %a %-e %b" } else { format }; let format_items = match StrftimeItems::new(format_str).parse_to_owned() { Ok(s) => s, Err(e) => panic!("Invalid time format, consult the configuration file for examples of correct ones: {e:?}"), }; let locale = locale_str.and_then(|l| Locale::try_from(l).ok()).unwrap_or(Locale::POSIX); Button { action, active: false, changed: false, image: ButtonImage::Time(format_items, locale), } } fn render( &self, c: &Context, height: i32, button_left_edge: f64, button_width: u64, y_shift: f64, ) { match &self.image { ButtonImage::Text(text) => { let extents = c.text_extents(text).unwrap(); c.move_to( button_left_edge + (button_width as f64 / 2.0 - extents.width() / 2.0).round(), y_shift + (height as f64 / 2.0 + extents.height() / 2.0).round(), ); c.show_text(text).unwrap(); } ButtonImage::Svg(svg) => { let x = button_left_edge + (button_width as f64 / 2.0 - (ICON_SIZE / 2) as f64).round(); let y = y_shift + ((height as f64 - ICON_SIZE as f64) / 2.0).round(); svg.render_document(c, &Rectangle::new(x, y, ICON_SIZE as f64, ICON_SIZE as f64)) .unwrap(); } ButtonImage::Bitmap(surf) => { let x = button_left_edge + (button_width as f64 / 2.0 - (ICON_SIZE / 2) as f64).round(); let y = y_shift + ((height as f64 - ICON_SIZE as f64) / 2.0).round(); c.set_source_surface(surf, x, y).unwrap(); c.rectangle(x, y, ICON_SIZE as f64, ICON_SIZE as f64); c.fill().unwrap(); } ButtonImage::Time(format, locale) => { let current_time = Local::now(); let formatted_time = current_time.format_localized_with_items(format.iter(), *locale).to_string(); let time_extents = c.text_extents(&formatted_time).unwrap(); c.move_to( button_left_edge + (button_width as f64 / 2.0 - time_extents.width() / 2.0).round(), y_shift + (height as f64 / 2.0 + time_extents.height() / 2.0).round() ); c.show_text(&formatted_time).unwrap(); } ButtonImage::Battery(battery, battery_mode, icons) => { let (capacity, state) = get_battery_state(battery); let icon = if battery_mode.should_draw_icon() { Some(match state { BatteryState::Charging => match capacity { 0..=20 => &icons.charging[0], 21..=30 => &icons.charging[1], 31..=50 => &icons.charging[2], 51..=60 => &icons.charging[3], 61..=80 => &icons.charging[4], 81..=99 => &icons.charging[5], _ => &icons.charging[6], }, _ => match capacity { 0 => &icons.plain[0], 1..=20 => &icons.plain[1], 21..=30 => &icons.plain[2], 31..=50 => &icons.plain[3], 51..=60 => &icons.plain[4], 61..=80 => &icons.plain[5], 81..=99 => &icons.plain[6], _ => &icons.plain[7], }, }) } else if state == BatteryState::Charging { Some(&icons.bolt) } else { None }; let percent_str = format!("{:.0}%", capacity); let extents = c.text_extents(&percent_str).unwrap(); let mut width = extents.width(); let mut text_offset = 0; if let Some(svg) = icon { if !battery_mode.should_draw_text() { width = ICON_SIZE as f64; } else { width += ICON_SIZE as f64; } text_offset = ICON_SIZE; let x = button_left_edge + (button_width as f64 / 2.0 - width / 2.0).round(); let y = y_shift + ((height as f64 - ICON_SIZE as f64) / 2.0).round(); svg.render_document(c, &Rectangle::new(x, y, ICON_SIZE as f64, ICON_SIZE as f64)) .unwrap(); } if battery_mode.should_draw_text() { c.move_to( button_left_edge + (button_width as f64 / 2.0 - width / 2.0 + text_offset as f64).round(), y_shift + (height as f64 / 2.0 + extents.height() / 2.0).round(), ); c.show_text(&percent_str).unwrap(); } } } } fn set_active(&mut self, uinput: &mut UInputHandle, active: bool) where F: AsRawFd, { if self.active != active { self.active = active; self.changed = true; toggle_key(uinput, self.action, active as i32); } } fn set_backround_color(&self, c: &Context, color: f64) { if let ButtonImage::Battery(battery, _, _) = &self.image { let (_, state) = get_battery_state(battery); match state { BatteryState::NotCharging => c.set_source_rgb(color, color, color), BatteryState::Charging => c.set_source_rgb(0.0, color, 0.0), BatteryState::Low => c.set_source_rgb(color, 0.0, 0.0), } } else { c.set_source_rgb(color, color, color); } } } #[derive(Default)] pub struct FunctionLayer { buttons: Vec<(usize, Button)>, virtual_button_count: usize, } impl FunctionLayer { fn with_config(cfg: Vec) -> FunctionLayer { if cfg.is_empty() { panic!("Invalid configuration, layer has 0 buttons"); } let mut virtual_button_count = 0; FunctionLayer { buttons: cfg .into_iter() .scan(&mut virtual_button_count, |state, cfg| { let i = **state; let mut stretch = cfg.stretch.unwrap_or(1); if stretch < 1 { println!("Stretch value must be at least 1, setting to 1."); stretch = 1; } **state += stretch; Some((i, Button::with_config(cfg))) }) .collect(), virtual_button_count, } } fn draw( &mut self, config: &Config, width: i32, height: i32, surface: &Surface, pixel_shift: (f64, f64), complete_redraw: bool, ) -> Vec { let c = Context::new(surface).unwrap(); let mut modified_regions = if complete_redraw { vec![ClipRect::new(0, 0, height as u16, width as u16)] } else { Vec::new() }; c.translate(height as f64, 0.0); c.rotate((90.0f64).to_radians()); let pixel_shift_width = if config.enable_pixel_shift { PIXEL_SHIFT_WIDTH_PX } else { 0 }; let virtual_button_width = ((width - pixel_shift_width as i32) - (BUTTON_SPACING_PX * (self.virtual_button_count - 1) as i32)) as f64 / self.virtual_button_count as f64; let radius = 8.0f64; let bot = (height as f64) * 0.15; let top = (height as f64) * 0.85; let (pixel_shift_x, pixel_shift_y) = pixel_shift; if complete_redraw { c.set_source_rgb(0.0, 0.0, 0.0); c.paint().unwrap(); } c.set_font_face(&config.font_face); c.set_font_size(32.0); for i in 0..self.buttons.len() { let end = if i + 1 < self.buttons.len() { self.buttons[i + 1].0 } else { self.virtual_button_count }; let (start, button) = &mut self.buttons[i]; let start = *start; if !button.changed && !complete_redraw { continue; }; let left_edge = (start as f64 * (virtual_button_width + BUTTON_SPACING_PX as f64)) .floor() + pixel_shift_x + (pixel_shift_width / 2) as f64; let button_width = virtual_button_width + ((end - start - 1) as f64 * (virtual_button_width + BUTTON_SPACING_PX as f64)) .floor(); let color = if button.active { BUTTON_COLOR_ACTIVE } else if config.show_button_outlines { BUTTON_COLOR_INACTIVE } else { 0.0 }; if !complete_redraw { c.set_source_rgb(0.0, 0.0, 0.0); c.rectangle( left_edge, bot - radius, button_width, top - bot + radius * 2.0, ); c.fill().unwrap(); } button.set_backround_color(&c, color); // draw box with rounded corners c.new_sub_path(); let left = left_edge + radius; let right = (left_edge + button_width.ceil()) - radius; c.arc( right, bot, radius, (-90.0f64).to_radians(), (0.0f64).to_radians(), ); c.arc( right, top, radius, (0.0f64).to_radians(), (90.0f64).to_radians(), ); c.arc( left, top, radius, (90.0f64).to_radians(), (180.0f64).to_radians(), ); c.arc( left, bot, radius, (180.0f64).to_radians(), (270.0f64).to_radians(), ); c.close_path(); c.fill().unwrap(); c.set_source_rgb(1.0, 1.0, 1.0); button.render( &c, height, left_edge, button_width.ceil() as u64, pixel_shift_y, ); button.changed = false; if !complete_redraw { modified_regions.push(ClipRect::new( height as u16 - top as u16 - radius as u16, left_edge as u16, height as u16 - bot as u16 + radius as u16, left_edge as u16 + button_width as u16, )); } } modified_regions } fn hit(&self, width: u16, height: u16, x: f64, y: f64, i: Option) -> Option { let virtual_button_width = (width as i32 - (BUTTON_SPACING_PX * (self.virtual_button_count - 1) as i32)) as f64 / self.virtual_button_count as f64; let i = i.unwrap_or_else(|| { let virtual_i = (x / (width as f64 / self.virtual_button_count as f64)) as usize; self.buttons .iter() .position(|(start, _)| *start > virtual_i) .unwrap_or(self.buttons.len()) - 1 }); if i >= self.buttons.len() { return None; } let start = self.buttons[i].0; let end = if i + 1 < self.buttons.len() { self.buttons[i + 1].0 } else { self.virtual_button_count }; let left_edge = (start as f64 * (virtual_button_width + BUTTON_SPACING_PX as f64)).floor(); let button_width = virtual_button_width + ((end - start - 1) as f64 * (virtual_button_width + BUTTON_SPACING_PX as f64)) .floor(); if x < left_edge || x > (left_edge + button_width) || y < 0.1 * height as f64 || y > 0.9 * height as f64 { return None; } Some(i) } } struct Interface; impl LibinputInterface for Interface { fn open_restricted(&mut self, path: &Path, flags: i32) -> Result { let mode = flags & O_ACCMODE; OpenOptions::new() .custom_flags(flags) .read(mode == O_RDONLY || mode == O_RDWR) .write(mode == O_WRONLY || mode == O_RDWR) .open(path) .map(|file| file.into()) .map_err(|err| err.raw_os_error().unwrap()) } fn close_restricted(&mut self, fd: OwnedFd) { _ = File::from(fd); } } fn emit(uinput: &mut UInputHandle, ty: EventKind, code: u16, value: i32) where F: AsRawFd, { uinput .write(&[input_event { value, type_: ty as u16, code, time: timeval { tv_sec: 0, tv_usec: 0, }, }]) .unwrap(); } fn toggle_key(uinput: &mut UInputHandle, code: Key, value: i32) where F: AsRawFd, { emit(uinput, EventKind::Key, code as u16, value); emit( uinput, EventKind::Synchronize, SynchronizeKind::Report as u16, 0, ); } fn main() { let mut drm = DrmBackend::open_card().unwrap(); let (height, width) = drm.mode().size(); let _ = panic::catch_unwind(AssertUnwindSafe(|| real_main(&mut drm))); let crash_bitmap = include_bytes!("crash_bitmap.raw"); let mut map = drm.map().unwrap(); let data = map.as_mut(); let mut wptr = 0; for byte in crash_bitmap { for i in 0..8 { let bit = ((byte >> i) & 0x1) == 0; let color = if bit { 0xFF } else { 0x0 }; data[wptr] = color; data[wptr + 1] = color; data[wptr + 2] = color; data[wptr + 3] = color; wptr += 4; } } drop(map); drm.dirty(&[ClipRect::new(0, 0, height, width)]).unwrap(); let mut sigset = SigSet::empty(); sigset.add(Signal::SIGTERM); sigset.wait().unwrap(); } fn real_main(drm: &mut DrmBackend) { let (height, width) = drm.mode().size(); let (db_width, db_height) = drm.fb_info().unwrap().size(); let mut uinput = UInputHandle::new(OpenOptions::new().write(true).open("/dev/uinput").unwrap()); let mut backlight = BacklightManager::new(); let mut last_redraw_minute = Local::now().minute(); let mut cfg_mgr = ConfigManager::new(); let (mut cfg, mut layers) = cfg_mgr.load_config(width); let mut pixel_shift = PixelShiftManager::new(); // drop privileges to input and video group let groups = ["input", "video"]; PrivDrop::default() .user("nobody") .group_list(&groups) .apply() .unwrap_or_else(|e| panic!("Failed to drop privileges: {}", e)); let mut surface = ImageSurface::create(Format::ARgb32, db_width as i32, db_height as i32).unwrap(); let mut active_layer = 0; let mut needs_complete_redraw = true; let mut input_tb = Libinput::new_with_udev(Interface); let mut input_main = Libinput::new_with_udev(Interface); input_tb.udev_assign_seat("seat-touchbar").unwrap(); input_main.udev_assign_seat("seat0").unwrap(); let udev_monitor = MonitorBuilder::new() .unwrap() .match_subsystem("power_supply") .unwrap() .listen() .unwrap(); let epoll = Epoll::new(EpollCreateFlags::empty()).unwrap(); epoll .add(input_main.as_fd(), EpollEvent::new(EpollFlags::EPOLLIN, 0)) .unwrap(); epoll .add(input_tb.as_fd(), EpollEvent::new(EpollFlags::EPOLLIN, 1)) .unwrap(); epoll .add(cfg_mgr.fd(), EpollEvent::new(EpollFlags::EPOLLIN, 2)) .unwrap(); epoll .add(&udev_monitor, EpollEvent::new(EpollFlags::EPOLLIN, 3)) .unwrap(); uinput.set_evbit(EventKind::Key).unwrap(); for layer in &layers { for button in &layer.buttons { uinput.set_keybit(button.1.action).unwrap(); } } let mut dev_name_c = [0 as c_char; 80]; let dev_name = "Dynamic Function Row Virtual Input Device".as_bytes(); for i in 0..dev_name.len() { dev_name_c[i] = dev_name[i] as c_char; } uinput .dev_setup(&uinput_setup { id: input_id { bustype: 0x19, vendor: 0x1209, product: 0x316E, version: 1, }, ff_effects_max: 0, name: dev_name_c, }) .unwrap(); uinput.dev_create().unwrap(); let mut digitizer: Option = None; let mut touches = HashMap::new(); loop { if cfg_mgr.update_config(&mut cfg, &mut layers, width) { active_layer = 0; needs_complete_redraw = true; } let now = Local::now(); let ms_left = ((60 - now.second()) * 1000) as i32; let mut next_timeout_ms = min(ms_left, TIMEOUT_MS); if cfg.enable_pixel_shift { let (pixel_shift_needs_redraw, pixel_shift_next_timeout_ms) = pixel_shift.update(); if pixel_shift_needs_redraw { needs_complete_redraw = true; } next_timeout_ms = min(next_timeout_ms, pixel_shift_next_timeout_ms); } let current_minute = now.minute(); for button in &mut layers[active_layer].buttons { if matches!(button.1.image, ButtonImage::Time(_, _)) && (current_minute != last_redraw_minute) { needs_complete_redraw = true; last_redraw_minute = current_minute; } if let ButtonImage::Battery(_, _, _) = button.1.image { button.1.changed = true; } } if needs_complete_redraw || layers[active_layer].buttons.iter().any(|b| b.1.changed) { let shift = if cfg.enable_pixel_shift { pixel_shift.get() } else { (0.0, 0.0) }; let clips = layers[active_layer].draw( &cfg, width as i32, height as i32, &surface, shift, needs_complete_redraw, ); let data = surface.data().unwrap(); drm.map().unwrap().as_mut()[..data.len()].copy_from_slice(&data); drm.dirty(&clips).unwrap(); needs_complete_redraw = false; } match epoll.wait( &mut [EpollEvent::new(EpollFlags::EPOLLIN, 0)], next_timeout_ms as u16, ) { Err(Errno::EINTR) | Ok(_) => 0, e => e.unwrap(), }; _ = udev_monitor.iter().last(); input_tb.dispatch().unwrap(); input_main.dispatch().unwrap(); for event in &mut input_tb.clone().chain(input_main.clone()) { backlight.process_event(&event); match event { Event::Device(DeviceEvent::Added(evt)) => { let dev = evt.device(); if dev.name().contains(" Touch Bar") { digitizer = Some(dev); } } Event::Keyboard(KeyboardEvent::Key(key)) => { if key.key() == Key::Fn as u32 { let new_layer = match key.key_state() { KeyState::Pressed => 1, KeyState::Released => 0, }; if active_layer != new_layer { active_layer = new_layer; needs_complete_redraw = true; } } } Event::Touch(te) => { if Some(te.device()) != digitizer || backlight.current_bl() == 0 { continue; } match te { TouchEvent::Down(dn) => { let x = dn.x_transformed(width as u32); let y = dn.y_transformed(height as u32); if let Some(btn) = layers[active_layer].hit(width, height, x, y, None) { touches.insert(dn.seat_slot(), (active_layer, btn)); layers[active_layer].buttons[btn] .1 .set_active(&mut uinput, true); } } TouchEvent::Motion(mtn) => { if !touches.contains_key(&mtn.seat_slot()) { continue; } let x = mtn.x_transformed(width as u32); let y = mtn.y_transformed(height as u32); let (layer, btn) = *touches.get(&mtn.seat_slot()).unwrap(); let hit = layers[active_layer] .hit(width, height, x, y, Some(btn)) .is_some(); layers[layer].buttons[btn].1.set_active(&mut uinput, hit); } TouchEvent::Up(up) => { if !touches.contains_key(&up.seat_slot()) { continue; } let (layer, btn) = *touches.get(&up.seat_slot()).unwrap(); layers[layer].buttons[btn].1.set_active(&mut uinput, false); } _ => {} } } _ => {} } } backlight.update_backlight(&cfg); } } tiny-dfr-0.3.6/src/pixel_shift.rs000064400000000000000000000103301046102023000150360ustar 00000000000000use crate::TIMEOUT_MS; use rand::Rng; use std::time::Instant; const INTERVAL_MS: i32 = TIMEOUT_MS; // should be a multiple of TIMEOUT_MS const PROLONGED_INTERVAL_MS: i32 = TIMEOUT_MS * 5; // should be a multiple of TIMEOUT_MS and more than INTERVAL_MS const ANIMATION_INTERVAL_MS: i32 = 200; // should be less than TIMEOUT_MS const ANIMATION_DURATION_MS: i32 = 4000; // should be a multiple of ANIMATION_INTERVAL_MS // This is the total range on the x-axis that pixels will shift by over time, ie. they will shift by // PIXEL_SHIFT_WIDTH_PX / 2 to the right and to the left. // To make sure that no pixel ends up being always on, the minimum value to be safe here is the // size of the largest continuous colored line in the x-direction. The higher this value, the less // strain is put on the panel. pub const PIXEL_SHIFT_WIDTH_PX: u64 = 22; // should be divisible by 2 // in y direction we can't really shift by a lot since icons still need to appear centered, // 2 pixels in each direction seems to be the maximum before it gets really visible. const PIXEL_SHIFT_HEIGHT_PX: u64 = 4; // should be divisible by 2 #[derive(Clone, Copy)] enum ShiftState { WaitingAtEnd, ShiftingSubpixel, Normal, } pub struct PixelShiftManager { last_active: Instant, y_constant: f64, pixel_progress: u64, subpixel_progress: f64, direction: i64, state: ShiftState, } fn wait_for_state(state: ShiftState) -> i32 { match state { ShiftState::ShiftingSubpixel => ANIMATION_INTERVAL_MS, ShiftState::Normal => INTERVAL_MS, ShiftState::WaitingAtEnd => PROLONGED_INTERVAL_MS, } } impl PixelShiftManager { pub fn new() -> PixelShiftManager { let pixel_progress = rand::thread_rng().gen_range(0..PIXEL_SHIFT_WIDTH_PX); // add some randomness to the relationship between shifting on the x and y axis // so that pixel shifting doesn't follow the same 2d pattern every time let y_constant: f64 = rand::thread_rng().gen_range(0..PIXEL_SHIFT_HEIGHT_PX * 2) as f64; PixelShiftManager { last_active: Instant::now(), y_constant, state: ShiftState::Normal, pixel_progress, subpixel_progress: 0.0, direction: 1, } } pub fn update(&mut self) -> (bool, i32) { let time_now = Instant::now(); let since_last_pixel_shift = (time_now - self.last_active).as_millis() as i32; if since_last_pixel_shift < wait_for_state(self.state) { return (false, i32::MAX); } self.last_active = time_now; match self.state { ShiftState::Normal => { self.state = ShiftState::ShiftingSubpixel; } ShiftState::ShiftingSubpixel => { let shift_by = ANIMATION_INTERVAL_MS as f64 / ANIMATION_DURATION_MS as f64; self.subpixel_progress += shift_by * self.direction as f64; if self.subpixel_progress <= -0.99 || self.subpixel_progress >= 0.99 { self.pixel_progress = (self.direction + self.pixel_progress as i64) as u64; self.state = ShiftState::Normal; self.subpixel_progress = 0.0; if self.pixel_progress == 0 || self.pixel_progress >= PIXEL_SHIFT_WIDTH_PX { self.state = ShiftState::WaitingAtEnd; self.direction = -self.direction; } } } ShiftState::WaitingAtEnd => { self.state = ShiftState::Normal; self.subpixel_progress = 0.0; } } (true, wait_for_state(self.state)) } pub fn get(&self) -> (f64, f64) { let x_progress = self.pixel_progress as f64 + self.subpixel_progress; let mut y_progress = (x_progress + self.y_constant) % (PIXEL_SHIFT_HEIGHT_PX * 2) as f64; if y_progress > PIXEL_SHIFT_HEIGHT_PX as f64 { y_progress = (PIXEL_SHIFT_HEIGHT_PX * 2) as f64 - y_progress; } ( x_progress - (PIXEL_SHIFT_WIDTH_PX / 2) as f64, y_progress - (PIXEL_SHIFT_HEIGHT_PX / 2) as f64, ) } }