feat: Dynamically parse and apply GTK UI hotkeys from config json

This commit is contained in:
katR 2026-09-02 16:41:34 +05:30
parent 587b164457
commit 32ada5214a
6 changed files with 757 additions and 17 deletions

562
Cargo.lock generated
View file

@ -11,6 +11,28 @@ dependencies = [
"memchr",
]
[[package]]
name = "alsa"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812947049edcd670a82cd5c73c3661d2e58468577ba8489de58e1a73c04cbd5d"
dependencies = [
"alsa-sys",
"bitflags",
"cfg-if",
"libc",
]
[[package]]
name = "alsa-sys"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad7569085a265dd3f607ebecce7458eaab2132a84393534c95b18dcbc3f31e04"
dependencies = [
"libc",
"pkg-config",
]
[[package]]
name = "annotate-snippets"
version = "0.11.5"
@ -99,6 +121,39 @@ dependencies = [
"system-deps 6.2.2",
]
[[package]]
name = "audio-codec-algorithms"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1254ebf6529f3763c491acfb5ab6e960809e3c75a38584cc664f8c5667fb7107"
[[package]]
name = "audioadapter"
version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1292ef9edf681b7426ed089004b021a42896492f58bd0c909ad44e5faec9ac4"
[[package]]
name = "audioadapter-buffers"
version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46289a81a3bfa26d0f8b2415f8ca9decde9a66308a150568af5c102381b3c8ce"
dependencies = [
"audioadapter",
"audioadapter-sample",
"num-traits",
]
[[package]]
name = "audioadapter-sample"
version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5258faecf4edbe35ec483bda9b1f7438e6b391891819269c6b5461aeaa0e8d7d"
dependencies = [
"audio-codec-algorithms",
"num-traits",
]
[[package]]
name = "autocfg"
version = "1.5.1"
@ -133,6 +188,15 @@ dependencies = [
"serde_core",
]
[[package]]
name = "block2"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
dependencies = [
"objc2",
]
[[package]]
name = "bumpalo"
version = "3.20.3"
@ -232,12 +296,66 @@ dependencies = [
"libloading",
]
[[package]]
name = "combine"
version = "4.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e"
dependencies = [
"bytes",
"memchr",
]
[[package]]
name = "cookie-factory"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2"
[[package]]
name = "coreaudio-rs"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d5d7dca3ebcf65a035582c9ad4385371a9d9ee6537474d2a278f4e1e475bb58"
dependencies = [
"bitflags",
"libc",
"objc2-audio-toolbox",
"objc2-core-audio",
"objc2-core-audio-types",
"objc2-core-foundation",
]
[[package]]
name = "cpal"
version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f02e8d0327b42d3e2e4ab2119af397344eb9fc54a34bf0ddeaa1277af8681f1"
dependencies = [
"alsa",
"block2",
"coreaudio-rs",
"dasp_sample",
"jni",
"js-sys",
"libc",
"mach2",
"ndk",
"ndk-context",
"num-derive",
"num-traits",
"objc2",
"objc2-audio-toolbox",
"objc2-avf-audio",
"objc2-core-audio",
"objc2-core-audio-types",
"objc2-core-foundation",
"objc2-foundation",
"web-sys",
"windows",
"windows-core",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.16"
@ -253,6 +371,12 @@ version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
[[package]]
name = "dasp_sample"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
[[package]]
name = "dirs"
version = "6.0.0"
@ -880,6 +1004,64 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "jni"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
dependencies = [
"cfg-if",
"combine",
"jni-macros",
"jni-sys 0.4.1",
"log",
"simd_cesu8",
"thiserror 2.0.20",
"walkdir",
"windows-link",
]
[[package]]
name = "jni-macros"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
dependencies = [
"proc-macro2",
"quote",
"rustc_version",
"simd_cesu8",
"syn 2.0.119",
]
[[package]]
name = "jni-sys"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258"
dependencies = [
"jni-sys 0.4.1",
]
[[package]]
name = "jni-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
dependencies = [
"jni-sys-macros",
]
[[package]]
name = "jni-sys-macros"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
dependencies = [
"quote",
"syn 2.0.119",
]
[[package]]
name = "js-sys"
version = "0.3.104"
@ -960,6 +1142,18 @@ version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]]
name = "log"
version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
[[package]]
name = "mach2"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b"
[[package]]
name = "memchr"
version = "2.8.3"
@ -1015,6 +1209,35 @@ dependencies = [
"thiserror 1.0.69",
]
[[package]]
name = "ndk"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4"
dependencies = [
"bitflags",
"jni-sys 0.3.1",
"log",
"ndk-sys",
"num_enum",
"thiserror 1.0.69",
]
[[package]]
name = "ndk-context"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
[[package]]
name = "ndk-sys"
version = "0.6.0+11769913"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873"
dependencies = [
"jni-sys 0.3.1",
]
[[package]]
name = "nom"
version = "7.1.3"
@ -1044,6 +1267,26 @@ dependencies = [
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
dependencies = [
"num-traits",
]
[[package]]
name = "num-derive"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "num-integer"
version = "0.1.47"
@ -1084,6 +1327,28 @@ dependencies = [
"libc",
]
[[package]]
name = "num_enum"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26"
dependencies = [
"num_enum_derive",
"rustversion",
]
[[package]]
name = "num_enum_derive"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8"
dependencies = [
"proc-macro-crate 3.5.0",
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "objc2"
version = "0.6.4"
@ -1104,6 +1369,55 @@ dependencies = [
"objc2-foundation",
]
[[package]]
name = "objc2-audio-toolbox"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6948501a91121d6399b79abaa33a8aa4ea7857fe019f341b8c23ad6e81b79b08"
dependencies = [
"bitflags",
"libc",
"objc2",
"objc2-core-audio",
"objc2-core-audio-types",
"objc2-core-foundation",
"objc2-foundation",
]
[[package]]
name = "objc2-avf-audio"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13a380031deed8e99db00065c45937da434ca987c034e13b87e4441f9e4090be"
dependencies = [
"bitflags",
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-audio"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1eebcea8b0dbff5f7c8504f3107c68fc061a3eb44932051c8cf8a68d969c3b2"
dependencies = [
"dispatch2",
"objc2",
"objc2-core-audio-types",
"objc2-core-foundation",
"objc2-foundation",
]
[[package]]
name = "objc2-core-audio-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c"
dependencies = [
"bitflags",
"objc2",
]
[[package]]
name = "objc2-core-foundation"
version = "0.3.2"
@ -1111,7 +1425,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [
"bitflags",
"block2",
"dispatch2",
"libc",
"objc2",
]
@ -1128,6 +1444,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [
"bitflags",
"block2",
"libc",
"objc2",
"objc2-core-foundation",
]
@ -1237,6 +1555,15 @@ dependencies = [
"portable-atomic",
]
[[package]]
name = "primal-check"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc0d895b311e3af9902528fbb8f928688abbd95872819320517cc24ca6b2bd08"
dependencies = [
"num-integer",
]
[[package]]
name = "proc-macro-crate"
version = "1.3.1"
@ -1323,6 +1650,15 @@ version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "realfft"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f821338fddb99d089116342c46e9f1fbf3828dba077674613e734e01d6ea8677"
dependencies = [
"rustfft",
]
[[package]]
name = "redox_users"
version = "0.5.2"
@ -1374,6 +1710,22 @@ dependencies = [
"portable-atomic-util",
]
[[package]]
name = "rubato"
version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7cb1ffaf8738df50aab642a7f6465df81c6ba9e2818268053487165298114be"
dependencies = [
"audioadapter",
"audioadapter-buffers",
"num-complex",
"num-integer",
"num-traits",
"realfft",
"visibility",
"windowfunctions",
]
[[package]]
name = "rustc-hash"
version = "2.1.3"
@ -1389,6 +1741,20 @@ dependencies = [
"semver",
]
[[package]]
name = "rustfft"
version = "6.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21db5f9893e91f41798c88680037dba611ca6674703c1a18601b01a72c8adb89"
dependencies = [
"num-complex",
"num-integer",
"num-traits",
"primal-check",
"strength_reduce",
"transpose",
]
[[package]]
name = "rustix"
version = "1.1.4"
@ -1408,6 +1774,15 @@ version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "semver"
version = "1.0.28"
@ -1508,6 +1883,22 @@ dependencies = [
"libc",
]
[[package]]
name = "simd_cesu8"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520"
dependencies = [
"rustc_version",
"simdutf8",
]
[[package]]
name = "simdutf8"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]]
name = "slab"
version = "0.4.12"
@ -1530,6 +1921,12 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "strength_reduce"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82"
[[package]]
name = "syn"
version = "1.0.109"
@ -1821,6 +2218,16 @@ dependencies = [
"once_cell",
]
[[package]]
name = "transpose"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e"
dependencies = [
"num-integer",
"strength_reduce",
]
[[package]]
name = "uds_windows"
version = "1.2.1"
@ -1879,11 +2286,23 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "visibility"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "vrec"
version = "0.1.0"
dependencies = [
"ashpd",
"cpal",
"crossbeam-channel",
"dirs",
"ffmpeg-next",
@ -1898,12 +2317,23 @@ dependencies = [
"mp4",
"pipewire",
"ringbuf",
"rubato",
"serde",
"serde_json",
"tokio",
"xcb",
]
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
@ -1955,6 +2385,16 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "web-sys"
version = "0.3.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi"
version = "0.3.9"
@ -1971,18 +2411,131 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windowfunctions"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90628d739333b7c5d2ee0b70210b97b8cddc38440c682c96fd9e2c24c2db5f3a"
dependencies = [
"num-traits",
]
[[package]]
name = "windows"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
dependencies = [
"windows-collections",
"windows-core",
"windows-future",
"windows-numerics",
]
[[package]]
name = "windows-collections"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
dependencies = [
"windows-core",
]
[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-future"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
dependencies = [
"windows-core",
"windows-link",
"windows-threading",
]
[[package]]
name = "windows-implement"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "windows-interface"
version = "0.59.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-numerics"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
dependencies = [
"windows-core",
"windows-link",
]
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
@ -2017,6 +2570,15 @@ dependencies = [
"windows_x86_64_msvc",
]
[[package]]
name = "windows-threading"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
dependencies = [
"windows-link",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"

View file

@ -23,3 +23,5 @@ serde_json = "1.0.151"
tokio = { version = "1.53.1", features = ["rt", "macros", "rt-multi-thread"] }
xcb = { version = "1.7.1", features = ["shm", "xfixes"] }
dirs = "6.0.0"
cpal = "0.18.2"
rubato = "5.0.0"

View file

@ -38,8 +38,11 @@ fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
}
let manager = GlobalHotKeyManager::new().unwrap();
let save_hotkey = HotKey::new(Some(Modifiers::CONTROL | Modifiers::SHIFT), Code::KeyR);
let menu_hotkey = HotKey::new(Some(Modifiers::ALT), Code::KeyZ);
let config = vrec::config::VrecConfig::load();
let save_hotkey = vrec::hotkey::parse_hotkey(&config.save_hotkey)
.unwrap_or_else(|| HotKey::new(Some(Modifiers::CONTROL | Modifiers::SHIFT), Code::KeyR));
let menu_hotkey = vrec::hotkey::parse_hotkey(&config.menu_hotkey)
.unwrap_or_else(|| HotKey::new(Some(Modifiers::ALT), Code::KeyZ));
if let Err(e) = manager.register(save_hotkey) {
eprintln!("Failed to register global hotkey! Wayland compositor might be blocking it: {}", e);

View file

@ -35,3 +35,4 @@ pub trait FrameSource: Send {
fn next_frame(&mut self) -> Result<Frame, Box<dyn std::error::Error + Send + Sync>>;
}
pub mod mock;
pub mod audio;

View file

@ -220,3 +220,141 @@ impl Drop for VaapiEncoder {
}
}
}
pub struct AudioEncoder {
codec_ctx: *mut AVCodecContext,
swr_ctx: *mut SwrContext,
next_pts: i64,
frame_size: i32,
channels: i32,
fifo: *mut AVAudioFifo,
}
impl AudioEncoder {
pub fn new(sample_rate: i32, channels: i32) -> Result<Self, String> {
unsafe {
let codec = avcodec_find_encoder(AVCodecID::AV_CODEC_ID_AAC);
if codec.is_null() {
return Err("AAC encoder not found".into());
}
let codec_ctx = avcodec_alloc_context3(codec);
(*codec_ctx).sample_rate = sample_rate;
(*codec_ctx).ch_layout.nb_channels = channels;
// AAC requires FLTP (planar float)
(*codec_ctx).sample_fmt = AVSampleFormat::AV_SAMPLE_FMT_FLTP;
(*codec_ctx).bit_rate = 192_000;
// For older ffmpeg compatibility:
(*codec_ctx).channel_layout = if channels == 2 { AV_CH_LAYOUT_STEREO } else { AV_CH_LAYOUT_MONO } as u64;
let ret = avcodec_open2(codec_ctx, codec, std::ptr::null_mut());
if ret < 0 {
return Err("Failed to open AAC encoder".into());
}
// Create SwrContext to convert from interleaved F32 (AV_SAMPLE_FMT_FLT) to planar F32 (AV_SAMPLE_FMT_FLTP)
let mut swr_ctx = swr_alloc();
av_opt_set_int(swr_ctx as _, c"in_channel_layout".as_ptr(), (*codec_ctx).channel_layout as i64, 0);
av_opt_set_int(swr_ctx as _, c"in_sample_rate".as_ptr(), sample_rate as i64, 0);
av_opt_set_sample_fmt(swr_ctx as _, c"in_sample_fmt".as_ptr(), AVSampleFormat::AV_SAMPLE_FMT_FLT, 0);
av_opt_set_int(swr_ctx as _, c"out_channel_layout".as_ptr(), (*codec_ctx).channel_layout as i64, 0);
av_opt_set_int(swr_ctx as _, c"out_sample_rate".as_ptr(), sample_rate as i64, 0);
av_opt_set_sample_fmt(swr_ctx as _, c"out_sample_fmt".as_ptr(), AVSampleFormat::AV_SAMPLE_FMT_FLTP, 0);
swr_init(swr_ctx);
let frame_size = (*codec_ctx).frame_size;
let fifo = av_audio_fifo_alloc(AVSampleFormat::AV_SAMPLE_FMT_FLTP, channels, 1024 * 10);
Ok(Self {
codec_ctx,
swr_ctx,
next_pts: 0,
frame_size,
channels,
fifo,
})
}
}
pub fn codec_ctx(&self) -> *mut AVCodecContext {
self.codec_ctx
}
pub fn encode_pcm(&mut self, data: &[f32]) -> Result<Vec<crate::ring::Packet>, String> {
let mut packets = Vec::new();
unsafe {
let nb_samples = data.len() as i32 / self.channels;
// Allocate input frame (interleaved FLT)
let mut in_frame = av_frame_alloc();
(*in_frame).nb_samples = nb_samples;
(*in_frame).format = AVSampleFormat::AV_SAMPLE_FMT_FLT as i32;
(*in_frame).channel_layout = (*self.codec_ctx).channel_layout;
(*in_frame).sample_rate = (*self.codec_ctx).sample_rate;
av_frame_get_buffer(in_frame, 0);
std::slice::from_raw_parts_mut((*in_frame).data[0], data.len() * 4)
.copy_from_slice(std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 4));
// Allocate output frame (planar FLTP)
let mut out_frame = av_frame_alloc();
(*out_frame).nb_samples = nb_samples;
(*out_frame).format = AVSampleFormat::AV_SAMPLE_FMT_FLTP as i32;
(*out_frame).channel_layout = (*self.codec_ctx).channel_layout;
(*out_frame).sample_rate = (*self.codec_ctx).sample_rate;
av_frame_get_buffer(out_frame, 0);
swr_convert(
self.swr_ctx,
(*out_frame).data.as_mut_ptr(),
nb_samples,
(*in_frame).data.as_ptr() as *const *const u8,
nb_samples
);
av_audio_fifo_write(self.fifo, (*out_frame).data.as_mut_ptr() as *mut *mut std::ffi::c_void, nb_samples);
av_frame_free(&mut in_frame);
av_frame_free(&mut out_frame);
// Read exactly frame_size chunks from FIFO and encode
while av_audio_fifo_size(self.fifo) >= self.frame_size {
let mut enc_frame = av_frame_alloc();
(*enc_frame).nb_samples = self.frame_size;
(*enc_frame).format = AVSampleFormat::AV_SAMPLE_FMT_FLTP as i32;
(*enc_frame).channel_layout = (*self.codec_ctx).channel_layout;
(*enc_frame).sample_rate = (*self.codec_ctx).sample_rate;
av_frame_get_buffer(enc_frame, 0);
av_audio_fifo_read(self.fifo, (*enc_frame).data.as_mut_ptr() as *mut *mut std::ffi::c_void, self.frame_size);
(*enc_frame).pts = self.next_pts;
self.next_pts += self.frame_size as i64;
if avcodec_send_frame(self.codec_ctx, enc_frame) >= 0 {
let mut pkt = av_packet_alloc();
while avcodec_receive_packet(self.codec_ctx, pkt) >= 0 {
let new_pkt = av_packet_alloc();
av_packet_move_ref(new_pkt, pkt);
packets.push(crate::ring::Packet::new(new_pkt));
}
av_packet_free(&mut pkt);
}
av_frame_free(&mut enc_frame);
}
}
Ok(packets)
}
}
impl Drop for AudioEncoder {
fn drop(&mut self) {
unsafe {
av_audio_fifo_free(self.fifo);
swr_free(&mut self.swr_ctx);
avcodec_free_context(&mut self.codec_ctx);
}
}
}

View file

@ -1,19 +1,53 @@
use global_hotkey::{GlobalHotKeyManager, hotkey::{HotKey, Modifiers, Code}, GlobalHotKeyEvent};
use crossbeam_channel::Sender;
use global_hotkey::hotkey::{HotKey, Modifiers, Code};
pub fn run_hotkey_listener(trigger_tx: Sender<()>) -> Result<(), Box<dyn std::error::Error>> {
let manager = GlobalHotKeyManager::new()?;
let hotkey = HotKey::new(Some(Modifiers::ALT), Code::KeyZ);
manager.register(hotkey)?;
println!("Registered global hotkey Ctrl+Shift+R to trigger replay save.");
let receiver = GlobalHotKeyEvent::receiver();
loop {
if let Ok(event) = receiver.recv()
&& event.id == hotkey.id() && event.state == global_hotkey::HotKeyState::Pressed {
println!("Hotkey triggered! Saving replay...");
let _ = trigger_tx.send(());
pub fn parse_hotkey(s: &str) -> Option<HotKey> {
let mut mods = Modifiers::empty();
let mut code = None;
for part in s.split('+') {
let p = part.trim().to_lowercase();
match p.as_str() {
"ctrl" | "control" => mods |= Modifiers::CONTROL,
"shift" => mods |= Modifiers::SHIFT,
"alt" => mods |= Modifiers::ALT,
"meta" | "super" | "windows" | "cmd" => mods |= Modifiers::META,
_ => {
// simple mapping
code = match p.as_str() {
"a" => Some(Code::KeyA),
"b" => Some(Code::KeyB),
"c" => Some(Code::KeyC),
"d" => Some(Code::KeyD),
"e" => Some(Code::KeyE),
"f" => Some(Code::KeyF),
"g" => Some(Code::KeyG),
"h" => Some(Code::KeyH),
"i" => Some(Code::KeyI),
"j" => Some(Code::KeyJ),
"k" => Some(Code::KeyK),
"l" => Some(Code::KeyL),
"m" => Some(Code::KeyM),
"n" => Some(Code::KeyN),
"o" => Some(Code::KeyO),
"p" => Some(Code::KeyP),
"q" => Some(Code::KeyQ),
"r" => Some(Code::KeyR),
"s" => Some(Code::KeyS),
"t" => Some(Code::KeyT),
"u" => Some(Code::KeyU),
"v" => Some(Code::KeyV),
"w" => Some(Code::KeyW),
"x" => Some(Code::KeyX),
"y" => Some(Code::KeyY),
"z" => Some(Code::KeyZ),
_ => None,
};
}
}
}
if let Some(c) = code {
Some(HotKey::new(Some(mods), c))
} else {
None
}
}